1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# ---------- Decision request contracts ---------------------------------------
#
# One owner for "how is the `decision` operation dialled on this route". The
# catalog `operations` set on the model row stays the admission owner: it says
# WHETHER a route answers decision requests. This file says HOW, because the
# three decision endpoints are not interchangeable chat endpoints and the
# protocol picks the URL and body shape before transport.
#
# Every row here is `extends = true`, and the file lives in its own
# `10-decisions/` directory so it loads before `20-providers/`. That placement
# is load-bearing, not tidiness: capability resolution is first-match-wins, and
# several provider files carry a broad terminating row (`claude-haiku-*` in
# `20-providers/00-anthropic.toml`, for one). Contributed from a later file,
# the decision fields were never reached for those routes and
# `decision_contract_for_route` returned `None` for a row that plainly declared
# the operation. Nothing here changes a chat capability; a row contributes only
# the decision fields it sets.
#
# `decision_protocol` is a closed enum. An unknown value fails the capability
# load rather than falling back to the structured-output chat backend, because
# falling back would send a decision request to a model that cannot answer one.
#
# `decision_limits` is declared only for the native decision APIs, which
# publish request ceilings. A `structured_llm` route has no published decision
# ceilings — its bound is the ordinary context window already on its catalog
# row — so inventing numbers there would read as researched.
# ---------- Native decision APIs ---------------------------------------------
# TypeSafe System One, POST /v1/systemone. Documented, not reached: direct
# access was waitlisted on 2026-09-20 and Harn holds no key.
# https://docs.typesafe.ai/api
#
# Limits provenance:
# max_choice_options 255 https://docs.typesafe.ai/primitives/choice
# score_levels 2..10 https://docs.typesafe.ai/primitives/score
# state_window_tokens 32000 https://docs.typesafe.ai/models ("32k tokens for
# `state` plus the longest question"; the overall
# request ceiling is 64k)
# `max_questions` is deliberately absent: TypeSafe publishes no question-count
# ceiling and says adding questions barely moves response time.
# https://docs.typesafe.ai/patterns
[[]]
= ["jev-*", "typesafe/jev-*"]
= true
= "typesafe_system_one"
= ["boolean", "choice", "score"]
= { = 255, = 2, = 10, = 32000, = 64000 }
# Vercel AI Gateway evaluation modality. The gateway serves Jev on two paths:
# `POST /v1/evaluate` (Vercel-native, documented under the evaluation modality)
# and `POST /typesafe/v1/systemone` (a TypeSafe-compatible passthrough). Harn
# names the Vercel-native path, because it is the one Vercel documents for this
# modality and the one whose response carries gateway cost metadata. The
# compatibility path is recorded here so a future reader does not rediscover it.
# https://vercel.com/docs/ai-gateway/modalities/evaluation
#
# Observed live on 2026-09-20: the gateway model list carries `typesafe-ai/jev`
# with `"type": "evaluation"`, and `/v1/chat/completions` refuses it with
# `ModelTypeMismatchError`.
[[]]
= "typesafe-ai/jev*"
= true
= "vercel_evaluate"
= ["boolean", "choice", "score"]
= { = 255, = 2, = 10, = 32000, = 64000 }
# OpenRouter alpha decisions, POST /api/alpha/decisions. All three question
# kinds answered through the canonical native evaluator on 2026-09-22.
# https://openrouter.ai/typesafe
# The match pattern carries the `openrouter/` prefix because OpenRouter does
# not declare `wire_model_capabilities`: its rules are matched against the
# CATALOG id, which is `openrouter/typesafe/jev-1.13`, not against the wire
# model `typesafe/jev-1.13`.
[[]]
= "openrouter/typesafe/jev*"
= true
= "openrouter_decisions"
= ["boolean", "choice", "score"]
= { = 255, = 2, = 10, = 32000, = 64000 }
# ---------- Chat routes serving decisions through structured output ----------
#
# A curated, small set: the cheap auxiliary-classification routes, each of
# which already declares native structured output with a strict JSON schema in
# its own capability row. A repository test resolves every route declaring
# `structured_llm` and fails if its structured-output capability is not native,
# so this list cannot drift into a route that cannot honour a schema.
#
# These rows also need `operations = ["text_generation", "decision"]` on their
# catalog model row. `structured_llm` is the one protocol that dials the
# ordinary chat endpoint, so it is the one that needs text generation too.
[[]]
= ["gpt-5.4-nano", "gpt-5.4-mini"]
= true
= "structured_llm"
= ["boolean", "choice", "score"]
[[]]
= "claude-haiku-4-5*"
= true
= "structured_llm"
= ["boolean", "choice", "score"]
[[]]
= "gemini-3.1-flash-lite"
= true
= "structured_llm"
= ["boolean", "choice", "score"]
# Vercel resolves capabilities by wire model and inherits OpenRouter's rules
# (`provider_family`: vercel_ai_gateway -> openrouter -> openai). Its curated
# GPT-5.4 Nano row therefore inherits this contract deliberately; both
# gateway catalogs declare the decision operation alongside text generation.
[[]]
= ["openai/gpt-5.4-nano", "openai/gpt-5.4-mini"]
= true
= "structured_llm"
= ["boolean", "choice", "score"]