@broberg/ai-sdk 0.55.3 → 0.56.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ILZ33HZO.js → chunk-BOZYLPIM.js} +5 -213
- package/dist/chunk-BOZYLPIM.js.map +1 -0
- package/dist/chunk-QVMGFHCF.js +219 -0
- package/dist/chunk-QVMGFHCF.js.map +1 -0
- package/dist/{chunk-PDGFAD66.js → chunk-VMQSIO3J.js} +37 -8
- package/dist/chunk-VMQSIO3J.js.map +1 -0
- package/dist/index.d.ts +51 -769
- package/dist/index.js +53 -10
- package/dist/index.js.map +1 -1
- package/dist/pricing.js +2 -1
- package/dist/registry-U66vzTm0.d.ts +874 -0
- package/dist/registry.d.ts +1 -91
- package/dist/registry.js +4 -1
- package/package.json +1 -1
- package/dist/chunk-ILZ33HZO.js.map +0 -1
- package/dist/chunk-PDGFAD66.js.map +0 -1
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
type FailureKind = "auth" | "not_found" | "rate_limit" | "server" | "timeout" | "network" | "other";
|
|
2
|
+
interface CallFailure {
|
|
3
|
+
provider: string;
|
|
4
|
+
/** The model we ASKED for (the route that failed). */
|
|
5
|
+
model: string;
|
|
6
|
+
transport: string;
|
|
7
|
+
capability: Capability;
|
|
8
|
+
tier?: Tier;
|
|
9
|
+
purpose?: string;
|
|
10
|
+
labels?: Record<string, string>;
|
|
11
|
+
latencyMs: number;
|
|
12
|
+
ts: string;
|
|
13
|
+
/** HTTP status as a string ("401", "503"), or "timeout" | "network" | "unknown". */
|
|
14
|
+
errorCode: string;
|
|
15
|
+
errorKind: FailureKind;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Coarse data-residency of the endpoint a call went to.
|
|
19
|
+
*
|
|
20
|
+
* - `"eu"` — EU/EEA. **The only positive residency claim.** Nothing else is folded
|
|
21
|
+
* in: an adequacy decision is not EU residency, so the UK, Switzerland
|
|
22
|
+
* and Canada are deliberately NOT `"eu"` here.
|
|
23
|
+
* - `"us"` — United States.
|
|
24
|
+
* - `"cn"` — China.
|
|
25
|
+
* - `"unknown"` — we genuinely cannot say: an aggregator that picks its own upstream,
|
|
26
|
+
* or a region string we do not recognise.
|
|
27
|
+
*
|
|
28
|
+
* **`"unknown"` is NOT a synonym for safe.** `region !== "us"` is not an EU check —
|
|
29
|
+
* it passes every OpenRouter call. Only `region === "eu"` may be treated as EU-resident.
|
|
30
|
+
*/
|
|
31
|
+
type Region = "eu" | "us" | "cn" | "unknown";
|
|
32
|
+
/** Region of the endpoint a call will actually hit. Never throws — a residency
|
|
33
|
+
* reading must not be able to break a call that already succeeded. */
|
|
34
|
+
declare function regionOfHost(hostOrUrl: string | undefined): Region;
|
|
35
|
+
/** Classify a provider REGION STRING — a Vertex/GCP location like `europe-west1`, or
|
|
36
|
+
* an Azure region like `westeurope`. Anything unrecognised is `"unknown"`: a region
|
|
37
|
+
* name we have never seen is exactly the case where guessing is most tempting and
|
|
38
|
+
* least defensible. Never throws — a residency reading must not be able to break a
|
|
39
|
+
* call that already succeeded. */
|
|
40
|
+
declare function classifyRegionName(name: string | undefined): Region;
|
|
41
|
+
/** Region for an adapter whose endpoint is fixed. Unknown provider → `"unknown"`.
|
|
42
|
+
*
|
|
43
|
+
* **DO NOT BUILD A RESIDENCY GUARD ON THIS.** Use {@link regionOfHost} on the base URL
|
|
44
|
+
* the call will actually use. Found by a consumer building exactly that guard (super,
|
|
45
|
+
* 2026-08-30): `regionOfProvider("mistral")` is `"unknown"` — correct, because Mistral
|
|
46
|
+
* takes a `baseUrl` and a name cannot answer for residency — but a guard written as
|
|
47
|
+
* `regionOfProvider(p) === "eu"` therefore REJECTS Mistral, which is the only EU route
|
|
48
|
+
* we have. It fails closed and is useless: it filters out the thing it exists to allow.
|
|
49
|
+
*
|
|
50
|
+
* The trap is invisible from the signature, which is why it is written here rather than
|
|
51
|
+
* only in the docs. This function answers "is this provider's endpoint fixed, and where
|
|
52
|
+
* is it?" — a narrower question than "where will my call go?". */
|
|
53
|
+
declare function regionOfProvider(provider: string): Region;
|
|
54
|
+
|
|
55
|
+
/** One dictionary entry. `alias` says it differently; `ipa` says it in phonemes.
|
|
56
|
+
*
|
|
57
|
+
* **`ipa` is NOT the "more precise" option for a FOREIGN word — measured 2026-09-04.**
|
|
58
|
+
* Christian listened to `workflow` as `ˈwɜːkfloʊ` through the da-DK voices jeppe and
|
|
59
|
+
* christel and reported: *"ikke korrekt engelsk med et sjovt mix"*. A Danish neural
|
|
60
|
+
* voice renders English phonemes through Danish phonology, so foreign IPA comes out a
|
|
61
|
+
* hybrid rather than English. The symbols are honoured; the accent is not.
|
|
62
|
+
*
|
|
63
|
+
* So pick by what the word IS, not by which field sounds more exact:
|
|
64
|
+
* - a DANISH word the voice mispronounces → `ipa` does what you want.
|
|
65
|
+
* - an ENGLISH word inside Danish text → neither field is right yet. `alias` (spell it
|
|
66
|
+
* the way a Dane would say it) is the honest workaround today; a real engine switch
|
|
67
|
+
* is F051.2, and it is blocked on a listening test, not on code.
|
|
68
|
+
*
|
|
69
|
+
* Dated on purpose. This is a measurement of two specific voices on one date, not a
|
|
70
|
+
* permanent property of IPA — Azure ships new voices, and the next reader deserves to
|
|
71
|
+
* see that it may have moved. */
|
|
72
|
+
interface Pronunciation {
|
|
73
|
+
/** The word as it appears in the text. Matched whole-word, case-insensitively. */
|
|
74
|
+
word: string;
|
|
75
|
+
/** Say this instead. Azure `<sub alias>`; ElevenLabs plain substitution. */
|
|
76
|
+
alias?: string;
|
|
77
|
+
/** Also match INSIDE a hyphenated compound — `AI` in `AI-agenter` (F051.3).
|
|
78
|
+
*
|
|
79
|
+
* Danish puts abbreviations there constantly. cms measured their own articles:
|
|
80
|
+
* "AI-agenter" 13 times, "AI-native" 12, 60+ occurrences in all, every one read aloud
|
|
81
|
+
* as one mangled word. Christian heard it as "HTLM" and asked why AI was not spelled
|
|
82
|
+
* out too — it WAS, just never in a compound.
|
|
83
|
+
*
|
|
84
|
+
* **Per entry, and defaulting to `false`, because the right answer depends on the
|
|
85
|
+
* word.** `AI` yes; `mail` no — a rule for `mail` must not fire inside `e-mail`.
|
|
86
|
+
* Measured rather than reasoned: `@broberg/speech-dictionary` ships the general
|
|
87
|
+
* loosening and pays exactly that price today. A single global answer is wrong for
|
|
88
|
+
* one of the two classes whichever way it is set, so the person who wrote the entry
|
|
89
|
+
* decides.
|
|
90
|
+
*
|
|
91
|
+
* Default `false` is also the safe direction to be wrong in: flipping it would change
|
|
92
|
+
* the audio for every existing consumer IN SOUND, where there is no compile error, no
|
|
93
|
+
* red test and no log line to notice it by. */
|
|
94
|
+
matchInCompounds?: boolean;
|
|
95
|
+
/** IPA phonemes. Azure `<phoneme alphabet="ipa" ph>`. ElevenLabs cannot do this
|
|
96
|
+
* (it THROWS rather than silently skipping). See the type doc above before reaching
|
|
97
|
+
* for this on a foreign word. */
|
|
98
|
+
ipa?: string;
|
|
99
|
+
/** Reserved for a future per-entry language switch; carried but not yet emitted. */
|
|
100
|
+
lang?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** One entry of Azure's `[nnnn].word.json`, verbatim field names. */
|
|
104
|
+
interface AzureWordBoundary {
|
|
105
|
+
Text: string;
|
|
106
|
+
/** Milliseconds into the audio. */
|
|
107
|
+
AudioOffset: number;
|
|
108
|
+
/** Milliseconds. */
|
|
109
|
+
Duration: number;
|
|
110
|
+
}
|
|
111
|
+
interface WordTiming {
|
|
112
|
+
/** The spoken word, as Azure reported it. */
|
|
113
|
+
text: string;
|
|
114
|
+
startMs: number;
|
|
115
|
+
endMs: number;
|
|
116
|
+
/** Character span in the SOURCE text. Several spoken words share one span when they
|
|
117
|
+
* came from one substituted manuscript word. */
|
|
118
|
+
sourceStart: number;
|
|
119
|
+
sourceEnd: number;
|
|
120
|
+
}
|
|
121
|
+
interface AlignedWordTimings {
|
|
122
|
+
/** Every word we could place in the source, in audio order. */
|
|
123
|
+
words: WordTiming[];
|
|
124
|
+
/** Spoken words we could NOT place, in order, verbatim.
|
|
125
|
+
*
|
|
126
|
+
* Named rather than dropped, and never given a guessed offset. "I could not place
|
|
127
|
+
* this" and "it was here" must not be the same answer — the same discipline as
|
|
128
|
+
* `rerank.unscored` (F052). A highlighter that treats `words` as complete must look
|
|
129
|
+
* here. */
|
|
130
|
+
unaligned: string[];
|
|
131
|
+
}
|
|
132
|
+
/** A pronunciation entry, narrowed to what alignment needs. */
|
|
133
|
+
interface AliasEntry {
|
|
134
|
+
word: string;
|
|
135
|
+
alias?: string;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Map Azure's spoken word list onto positions in the SOURCE text.
|
|
139
|
+
*
|
|
140
|
+
* Two rules carry the whole function:
|
|
141
|
+
*
|
|
142
|
+
* **The walk only moves FORWARD.** That is what puts a repeated word on its own
|
|
143
|
+
* occurrence — "AI er AI" must not place the second on the first. Searching from 0 each
|
|
144
|
+
* time passes every test with distinct words and fails silently on real prose.
|
|
145
|
+
*
|
|
146
|
+
* **A substitution is matched as a RUN, not word by word.** Azure speaks SSML, so
|
|
147
|
+
* `<sub alias='broberg punktum a i'>broberg.ai</sub>` puts four words in the list where
|
|
148
|
+
* the manuscript has one. Matching them individually is not merely imprecise, it is
|
|
149
|
+
* wrong in a way that bites every Danish text: MEASURED on cms's own dictionary entry,
|
|
150
|
+
* a per-word map made the alias's "i" swallow the next real Danish word "i" in
|
|
151
|
+
* "broberg.ai i dag". An alias's parts are often ordinary words ("a", "i", "punktum"),
|
|
152
|
+
* so a lookup table cannot be the mechanism — the run has to be consumed positionally.
|
|
153
|
+
*/
|
|
154
|
+
declare function alignWordTimings(text: string, words: AzureWordBoundary[], opts?: {
|
|
155
|
+
pronunciations?: AliasEntry[];
|
|
156
|
+
}): AlignedWordTimings;
|
|
157
|
+
|
|
158
|
+
/** How a call reaches the model. `http` = provider REST API; `subprocess` = local
|
|
159
|
+
* `claude -p` CLI (Max plan, costUsd 0). */
|
|
160
|
+
type Transport = "http" | "subprocess";
|
|
161
|
+
/** Named capability tier. Resolves to a (provider, model, transport) triple via
|
|
162
|
+
* the tier map, overridable per call. */
|
|
163
|
+
type Tier = "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding";
|
|
164
|
+
/** The concrete routing target a Tier (or a per-call override) resolves to. */
|
|
165
|
+
interface TierSpec {
|
|
166
|
+
provider: string;
|
|
167
|
+
model: string;
|
|
168
|
+
transport: Transport;
|
|
169
|
+
}
|
|
170
|
+
/** High-level capability a call exercises. Mirrors the capability layer (F5). */
|
|
171
|
+
type Capability = "chat" | "vision" | "video" | "translate" | "image" | "animate" | "embedding" | "transcribe" | "ocr" | "moderation" | "podcast" | "tts" | "trainStyle" | "mockup" | "design" | "extract" | "classify" | "rerank" | "judge";
|
|
172
|
+
type Role = "system" | "user" | "assistant" | "tool";
|
|
173
|
+
/** A piece of message content. Text everywhere; image parts feed vision. */
|
|
174
|
+
type ContentPart = {
|
|
175
|
+
type: "text";
|
|
176
|
+
text: string;
|
|
177
|
+
} | {
|
|
178
|
+
type: "image";
|
|
179
|
+
image: string | Uint8Array;
|
|
180
|
+
mimeType?: string;
|
|
181
|
+
} | {
|
|
182
|
+
type: "video";
|
|
183
|
+
video: string | Uint8Array;
|
|
184
|
+
mimeType?: string;
|
|
185
|
+
};
|
|
186
|
+
interface Message {
|
|
187
|
+
role: Role;
|
|
188
|
+
content: string | ContentPart[];
|
|
189
|
+
/** Set on assistant messages that called tools. Accepts either spelling of the
|
|
190
|
+
* arguments field — see ToolCallLike. */
|
|
191
|
+
toolCalls?: ToolCallLike[];
|
|
192
|
+
/** Set on `tool` role messages — which call this result answers. */
|
|
193
|
+
toolCallId?: string;
|
|
194
|
+
/** F049 — Mistral's `prefix`: start the model's reply with this text instead of
|
|
195
|
+
* asking it to. Only valid on the LAST message, and only when that message is an
|
|
196
|
+
* `assistant` one; anything else throws rather than being sent, because Mistral
|
|
197
|
+
* documents no behaviour for it elsewhere.
|
|
198
|
+
*
|
|
199
|
+
* Mistral's own first-listed use case is Language Adherence, and it pairs with a
|
|
200
|
+
* system instruction rather than replacing one — they warn that a prefix alone
|
|
201
|
+
* gives "noisy and unpredictable answers". Measured on this repo's own data: an
|
|
202
|
+
* explicit language rule in the system prompt already took 5/15 wrong answers to
|
|
203
|
+
* 0/15, and every leak began in the FIRST words ("Tak for din henvendelse"), which
|
|
204
|
+
* is exactly what a prefix pins.
|
|
205
|
+
*
|
|
206
|
+
* **The SDK strips the prefix back off the response**, in `chat` and `chatStream`
|
|
207
|
+
* alike, so a caller never has to. Mistral's own example does
|
|
208
|
+
* `content[len(prefix):]` by hand; forgetting it puts "Here is the answer in
|
|
209
|
+
* Norwegian:" at the top of a real customer's email — a defect that reads as
|
|
210
|
+
* formatting rather than as a bug. */
|
|
211
|
+
prefix?: boolean;
|
|
212
|
+
}
|
|
213
|
+
/** SDK-level tool definition. Adapters convert this to each provider's format
|
|
214
|
+
* (F4.5). `parameters` is a JSON Schema object. */
|
|
215
|
+
interface Tool {
|
|
216
|
+
name: string;
|
|
217
|
+
description: string;
|
|
218
|
+
parameters: Record<string, unknown>;
|
|
219
|
+
}
|
|
220
|
+
/** A model's request to invoke a tool, normalized across providers (F4.5).
|
|
221
|
+
* This is what we EMIT — `arguments` is always present on a ToolCall we return. */
|
|
222
|
+
interface ToolCall {
|
|
223
|
+
id: string;
|
|
224
|
+
name: string;
|
|
225
|
+
arguments: Record<string, unknown>;
|
|
226
|
+
}
|
|
227
|
+
/** What we ACCEPT when a tool call is handed BACK to us in a message history.
|
|
228
|
+
*
|
|
229
|
+
* F043. `@broberg/chat` spells the same field `args`, consistently across its
|
|
230
|
+
* ModelEvent / ChatFrame / ChatTool.run. Two packages describing the same thing
|
|
231
|
+
* with two names cost cms a production outage: a rename living in a workaround was
|
|
232
|
+
* deleted along with the workaround, and the next call failed with
|
|
233
|
+
* `messages.1.toolCalls.0.arguments — Required`.
|
|
234
|
+
*
|
|
235
|
+
* So we read both and keep emitting `arguments`. Accepting is cheap and cannot
|
|
236
|
+
* break anyone; renaming would have broken a consumer who adopted the same day, and
|
|
237
|
+
* asking every consumer that bridges the two packages to remember a translation is
|
|
238
|
+
* how this happens again. Precedence is `arguments` when both are set. */
|
|
239
|
+
type ToolCallLike = {
|
|
240
|
+
id: string;
|
|
241
|
+
name: string;
|
|
242
|
+
arguments?: Record<string, unknown>;
|
|
243
|
+
args?: Record<string, unknown>;
|
|
244
|
+
};
|
|
245
|
+
/** Per-call usage. Fields mirror the upmetrics `agent_runs` schema 1:1 so the
|
|
246
|
+
* upmetricsSink (F3.7) forwards without re-mapping. `costUsd` is 0 for
|
|
247
|
+
* subprocess (Max plan); `subprocess:true` lets dashboards split free vs paid. */
|
|
248
|
+
interface Usage {
|
|
249
|
+
provider: string;
|
|
250
|
+
/** The model we ASKED for — the pricing key and the stable grouping. */
|
|
251
|
+
model: string;
|
|
252
|
+
/** F077.3 — true when `model` was a floating "-latest" alias the caller chose
|
|
253
|
+
* without allowFloating: the provider can move it to another model and price. */
|
|
254
|
+
floating?: boolean;
|
|
255
|
+
/** F071.2 — the model that ANSWERED, as the provider reported it. Differs from `model`
|
|
256
|
+
* when an alias like "mistral-large-latest" resolves to a dated model; that is how a
|
|
257
|
+
* silent overnight move becomes visible. Unset when the provider does not say. */
|
|
258
|
+
servedModel?: string;
|
|
259
|
+
tier?: Tier;
|
|
260
|
+
/** Data-residency of the endpoint that ACTUALLY answered (F042). Derived from the
|
|
261
|
+
* host/region the request used, not from the provider's name — vertex, azure and
|
|
262
|
+
* bfl are EU by default but a consumer can point them elsewhere.
|
|
263
|
+
*
|
|
264
|
+
* **Only `"eu"` is a positive residency claim.** `"unknown"` means we cannot say
|
|
265
|
+
* (an aggregator picked its own upstream, or the region string is one we do not
|
|
266
|
+
* recognise) — it is NOT a synonym for safe, so `region !== "us"` is not an EU check. */
|
|
267
|
+
region: Region;
|
|
268
|
+
transport: Transport;
|
|
269
|
+
inputTokens: number;
|
|
270
|
+
outputTokens: number;
|
|
271
|
+
cacheReadTokens: number;
|
|
272
|
+
cacheCreationTokens: number;
|
|
273
|
+
costUsd: number;
|
|
274
|
+
/** How `costUsd` was arrived at (F050). Absent means the same as `"computed"`;
|
|
275
|
+
* it is optional only so that adding it could not break a consumer building a
|
|
276
|
+
* Usage by hand.
|
|
277
|
+
*
|
|
278
|
+
* - `"reported"` — the PROVIDER told us what the call cost (BFL returns credits).
|
|
279
|
+
* - `"computed"` — a price table rate × a quantity we actually measured (tokens).
|
|
280
|
+
* - `"estimated"` — a quantity or a rate was ASSUMED. A video call with no
|
|
281
|
+
* `durationSec` is billed at {@link DEFAULT_CLIP_SEC} seconds regardless of the
|
|
282
|
+
* clip that came back, and fal's per-image rates are fal's own estimates.
|
|
283
|
+
* - `"unpriced"` — we have NO price for this model, so `costUsd` is 0 because we
|
|
284
|
+
* could not answer, not because the call was free. Those two were the same
|
|
285
|
+
* number, and the free one is much rarer than the unknown one.
|
|
286
|
+
*
|
|
287
|
+
* This is a DATA field on purpose. upmetrics distinguishes reported / computed /
|
|
288
|
+
* unpriced / untokened, and it reads fields — a JSDoc note saying "this one is a
|
|
289
|
+
* guess" never reaches it, so an assumed number arrived there indistinguishable
|
|
290
|
+
* from a measured one. super did exactly that and passed it on as measured. */
|
|
291
|
+
costBasis?: "reported" | "computed" | "estimated" | "unpriced";
|
|
292
|
+
toolCalls?: {
|
|
293
|
+
name: string;
|
|
294
|
+
count: number;
|
|
295
|
+
errorCount?: number;
|
|
296
|
+
}[];
|
|
297
|
+
latencyMs: number;
|
|
298
|
+
capability: Capability;
|
|
299
|
+
purpose?: string;
|
|
300
|
+
/** Consumer-defined attribution dimensions (e.g. {tenantId}) for per-tenant
|
|
301
|
+
* cost breakdown — the sink forwards these (F011). */
|
|
302
|
+
labels?: Record<string, string>;
|
|
303
|
+
ts: string;
|
|
304
|
+
subprocess?: true;
|
|
305
|
+
}
|
|
306
|
+
/** A cost reporter. Fans `Usage` out to Upmetrics / Discord / sqlite / etc.
|
|
307
|
+
* Implementations must never throw into the caller (F3.3). */
|
|
308
|
+
interface CostSink {
|
|
309
|
+
record(usage: Usage): void | Promise<void>;
|
|
310
|
+
/** F071.1 — optional: a FAILED call attempt (no cost). Sinks that sum spend (sqlite,
|
|
311
|
+
* budget, your own) may ignore it; leaving it out means they never see failures. */
|
|
312
|
+
recordFailure?(failure: CallFailure): void | Promise<void>;
|
|
313
|
+
}
|
|
314
|
+
/** Backing store for a BudgetGuard's rolling total. Default is in-memory
|
|
315
|
+
* (per createAI instance). A persistent store (e.g. sqliteBudgetStore) makes
|
|
316
|
+
* the rolling budget survive restarts + shared across processes (F7.1). */
|
|
317
|
+
interface BudgetStore {
|
|
318
|
+
getSpent(): number | Promise<number>;
|
|
319
|
+
addSpent(usd: number): void | Promise<void>;
|
|
320
|
+
}
|
|
321
|
+
/** Pre-flight budget ceilings, enforced before a call fires (F3.2). */
|
|
322
|
+
interface BudgetConfig {
|
|
323
|
+
/** Reject a single call whose estimated cost exceeds this many USD. */
|
|
324
|
+
perCallUsd?: number;
|
|
325
|
+
/** Reject once cumulative spend exceeds this many USD. */
|
|
326
|
+
rollingUsd?: number;
|
|
327
|
+
/** Where the rolling total lives. Omit for in-memory (per instance). */
|
|
328
|
+
store?: BudgetStore;
|
|
329
|
+
}
|
|
330
|
+
/** Per-call options shared by every capability: which tier, optional routing
|
|
331
|
+
* override, optional fallback chain, and a free-text purpose for cost reports. */
|
|
332
|
+
interface CallOptions {
|
|
333
|
+
tier?: Tier;
|
|
334
|
+
/** Override any part of the resolved TierSpec for this call only. */
|
|
335
|
+
override?: Partial<TierSpec>;
|
|
336
|
+
/** Ordered fallbacks tried if the primary route errors (inventory finding:
|
|
337
|
+
* cms/trail/sanne/xrt81 all hand-roll this — make it first-class). */
|
|
338
|
+
fallback?: (Tier | TierSpec)[];
|
|
339
|
+
purpose?: string;
|
|
340
|
+
/** Consumer-defined attribution dimensions (e.g. {tenantId}) carried onto
|
|
341
|
+
* Usage and forwarded by the cost sink for per-tenant cost breakdown (F011). */
|
|
342
|
+
labels?: Record<string, string>;
|
|
343
|
+
}
|
|
344
|
+
interface ChatRequest {
|
|
345
|
+
messages: Message[];
|
|
346
|
+
spec: TierSpec;
|
|
347
|
+
tools?: Tool[];
|
|
348
|
+
maxTokens?: number;
|
|
349
|
+
/** F039 — Mistral prompt caching. A stable application-level id for "the same
|
|
350
|
+
* conversation" (a conversation id, a session id). Requests sharing a prefix AND
|
|
351
|
+
* this key reuse the cached prefix at 10% of the input rate. Not auto-generated:
|
|
352
|
+
* only the caller knows what "the same conversation" means, and an SDK-invented
|
|
353
|
+
* key would either collide across tenants or never hit.
|
|
354
|
+
*
|
|
355
|
+
* BIND IT TO THE TENANT, not only to the conversation. This key IS a shared-prefix
|
|
356
|
+
* identity: two callers passing the same key can be served each other's cached
|
|
357
|
+
* prefix. So two tenants with identical transcripts must still get DIFFERENT keys
|
|
358
|
+
* — derive it from (tenant, conversation), never from the conversation alone.
|
|
359
|
+
* (components' requirement while building @broberg/chat, 2026-08-27, where two
|
|
360
|
+
* knowledge bases are written for readers with different permissions.) */
|
|
361
|
+
promptCacheKey?: string;
|
|
362
|
+
/** F039.2 — prompt caching is ON by default where the provider supports it.
|
|
363
|
+
* Set false to opt out (e.g. a one-shot call whose prefix will never repeat).
|
|
364
|
+
* Off has no cost benefit and no penalty; a cache MISS was measured to cost
|
|
365
|
+
* exactly the same as a call with no key at all. */
|
|
366
|
+
promptCache?: boolean;
|
|
367
|
+
temperature?: number;
|
|
368
|
+
/** F074.3 — Mistral's `reasoning_effort`. Undefined = the SDK's default: "none" for
|
|
369
|
+
* models that think by default (Large 4), nothing for the rest. */
|
|
370
|
+
reasoningEffort?: "none" | "high";
|
|
371
|
+
/** "json" → request JSON-object output where the provider supports it (F009). */
|
|
372
|
+
responseFormat?: "json" | "text";
|
|
373
|
+
}
|
|
374
|
+
interface ChatResult {
|
|
375
|
+
text: string;
|
|
376
|
+
toolCalls?: ToolCall[];
|
|
377
|
+
usage: Usage;
|
|
378
|
+
}
|
|
379
|
+
/** A single event from `ai.chatStream` / `adapter.chatStream` (F8). A streamed
|
|
380
|
+
* turn yields `text`/`tool_call` events as they arrive, one `usage` event when
|
|
381
|
+
* the provider reports totals, and a terminal `finish` (or `error`). Tool calls
|
|
382
|
+
* are emitted COMPLETE — accumulated across wire fragments, never partial. */
|
|
383
|
+
type ChatStreamEvent = {
|
|
384
|
+
type: "text";
|
|
385
|
+
delta: string;
|
|
386
|
+
} | {
|
|
387
|
+
type: "tool_call";
|
|
388
|
+
id: string;
|
|
389
|
+
name: string;
|
|
390
|
+
args: Record<string, unknown>;
|
|
391
|
+
} | {
|
|
392
|
+
type: "usage";
|
|
393
|
+
costUsd: number;
|
|
394
|
+
model: string;
|
|
395
|
+
usage: Usage;
|
|
396
|
+
} | {
|
|
397
|
+
type: "finish";
|
|
398
|
+
reason: "end_turn" | "tool_calls" | "length" | "stop";
|
|
399
|
+
} | {
|
|
400
|
+
type: "error";
|
|
401
|
+
message: string;
|
|
402
|
+
status?: number;
|
|
403
|
+
};
|
|
404
|
+
/** A trained LoRA to merge at inference time (F021). */
|
|
405
|
+
interface LoraWeight {
|
|
406
|
+
/** URL (or fal path) to the LoRA weights. */
|
|
407
|
+
path: string;
|
|
408
|
+
/** Scales the LoRA before merging (default 1). */
|
|
409
|
+
scale?: number;
|
|
410
|
+
}
|
|
411
|
+
interface ImageRequest {
|
|
412
|
+
prompt: string;
|
|
413
|
+
spec: TierSpec;
|
|
414
|
+
width?: number;
|
|
415
|
+
height?: number;
|
|
416
|
+
/** LoRAs to merge at inference (F021) — e.g. a trained brand/style LoRA. */
|
|
417
|
+
loras?: LoraWeight[];
|
|
418
|
+
/** F023 — a BFL finetune id (a subject trained in the BFL dashboard). Routes to the
|
|
419
|
+
* EU-resident BFL finetuned-portrait endpoint. */
|
|
420
|
+
finetune?: string;
|
|
421
|
+
/** F023 — how strongly the finetune is applied (BFL finetune_strength, ~0–2). */
|
|
422
|
+
finetuneStrength?: number;
|
|
423
|
+
/** F023.5 — 1–8 reference photos of a subject (URL or raw bytes). Routes to the
|
|
424
|
+
* EU-resident BFL FLUX 2 multi-reference endpoint — generate a likeness with NO
|
|
425
|
+
* training step. Bytes are base64-inlined into the EU call (no cross-region fetch). */
|
|
426
|
+
referenceImages?: (string | Uint8Array)[];
|
|
427
|
+
/** F023.5 — fixed seed for reproducible output (BFL). */
|
|
428
|
+
seed?: number;
|
|
429
|
+
/** F023.5 — output container (BFL FLUX 2): "jpeg" | "png" | "webp". Default jpeg. */
|
|
430
|
+
outputFormat?: "jpeg" | "png" | "webp";
|
|
431
|
+
/** F023.5 — BFL content-moderation strictness, 0 (strict) … 6 (lax). Default 2. */
|
|
432
|
+
safetyTolerance?: number;
|
|
433
|
+
/** F021.4 — re-roll once with a fresh seed if fal's safety-checker false-positives
|
|
434
|
+
* and returns a black image (has_nsfw_concepts). fal only. */
|
|
435
|
+
retryOnBlack?: boolean;
|
|
436
|
+
}
|
|
437
|
+
interface ImageResult {
|
|
438
|
+
url: string;
|
|
439
|
+
usage: Usage;
|
|
440
|
+
}
|
|
441
|
+
/** Image-to-video generation (F024) — animate a still into a short clip. */
|
|
442
|
+
interface AnimateRequest {
|
|
443
|
+
/** Input image: a URL (passed through) or raw bytes. Where bytes are uploaded is
|
|
444
|
+
* the ADAPTER's business — fal uses fal storage, the Gemini/Veo default inlines
|
|
445
|
+
* them — so do not read a storage provider out of this field. */
|
|
446
|
+
image: string | Uint8Array;
|
|
447
|
+
/** Motion/scene prompt, e.g. "the subject turns and smiles". */
|
|
448
|
+
prompt?: string;
|
|
449
|
+
/** Clip length in seconds (provider-dependent; Veo ≤ 8s). */
|
|
450
|
+
durationSec?: number;
|
|
451
|
+
/** e.g. "720p" / "1080p" (provider-dependent). */
|
|
452
|
+
resolution?: string;
|
|
453
|
+
spec: TierSpec;
|
|
454
|
+
}
|
|
455
|
+
interface AnimateResult {
|
|
456
|
+
/** URL to the generated video. For fal this is a public hosted URL; for the
|
|
457
|
+
* Gemini/Veo route it is Google's file URI (needs the API key to fetch — the
|
|
458
|
+
* bytes are also returned below, already downloaded). */
|
|
459
|
+
url: string;
|
|
460
|
+
/** The downloaded video bytes — set by providers whose result URL is auth-gated
|
|
461
|
+
* or short-lived (Gemini/Veo). Absent for providers that return a public URL (fal). */
|
|
462
|
+
bytes?: Uint8Array;
|
|
463
|
+
/** MIME type of `bytes` (e.g. "video/mp4"). */
|
|
464
|
+
mimeType?: string;
|
|
465
|
+
usage: Usage;
|
|
466
|
+
}
|
|
467
|
+
/** Style/brand LoRA training (F021) — fal fal-ai/flux-lora-fast-training. */
|
|
468
|
+
interface TrainStyleRequest {
|
|
469
|
+
/** A hosted archive URL, or an array of image URLs the SDK zips in-memory. */
|
|
470
|
+
images: string | string[];
|
|
471
|
+
spec: TierSpec;
|
|
472
|
+
/** Style LoRA (disables captioning/masks). Default true. */
|
|
473
|
+
isStyle?: boolean;
|
|
474
|
+
triggerWord?: string;
|
|
475
|
+
/** Training steps (~1000 typical). */
|
|
476
|
+
steps?: number;
|
|
477
|
+
createMasks?: boolean;
|
|
478
|
+
}
|
|
479
|
+
interface TrainStyleResult {
|
|
480
|
+
/** URL to the trained LoRA weights — pass to ai.image({ lora }). */
|
|
481
|
+
loraUrl: string;
|
|
482
|
+
/** URL to the training config file. */
|
|
483
|
+
configUrl: string;
|
|
484
|
+
usage: Usage;
|
|
485
|
+
}
|
|
486
|
+
interface EmbeddingRequest {
|
|
487
|
+
input: string[];
|
|
488
|
+
spec: TierSpec;
|
|
489
|
+
}
|
|
490
|
+
interface EmbeddingResult {
|
|
491
|
+
vectors: number[][];
|
|
492
|
+
usage: Usage;
|
|
493
|
+
}
|
|
494
|
+
/** Timestamp granularity a caller can request from `ai.transcribe` (F036). */
|
|
495
|
+
type TimestampGranularity = "word" | "segment";
|
|
496
|
+
/** A single word with its start/end offset in seconds. */
|
|
497
|
+
interface WordTimestamp {
|
|
498
|
+
word: string;
|
|
499
|
+
start: number;
|
|
500
|
+
end: number;
|
|
501
|
+
}
|
|
502
|
+
/** A phrase/sentence segment with its start/end offset in seconds. */
|
|
503
|
+
interface SegmentTimestamp {
|
|
504
|
+
text: string;
|
|
505
|
+
start: number;
|
|
506
|
+
end: number;
|
|
507
|
+
}
|
|
508
|
+
interface TranscribeRequest {
|
|
509
|
+
/** Raw audio bytes (the client resolves a URL to bytes before calling). */
|
|
510
|
+
audio: Uint8Array;
|
|
511
|
+
language?: string;
|
|
512
|
+
/** Audio length in seconds — enables per-minute cost (Whisper). Omit → cost 0. */
|
|
513
|
+
durationSec?: number;
|
|
514
|
+
/** Bias recognition toward these brand/jargon terms (Azure phraseList, F029.3).
|
|
515
|
+
* Providers without biasing support (Voxtral/Whisper) ignore it. */
|
|
516
|
+
phrases?: string[];
|
|
517
|
+
/** Request timestamps (F036). The client normalizes the input to this array.
|
|
518
|
+
* Adapters without timestamp support ignore it (result omits words/segments). */
|
|
519
|
+
timestamps?: TimestampGranularity[];
|
|
520
|
+
spec: TierSpec;
|
|
521
|
+
}
|
|
522
|
+
interface TranscribeResult {
|
|
523
|
+
text: string;
|
|
524
|
+
/** Word-level timing — present only when "word" timestamps were requested. */
|
|
525
|
+
words?: WordTimestamp[];
|
|
526
|
+
/** Segment (phrase/sentence) timing — present when any timestamps were requested. */
|
|
527
|
+
segments?: SegmentTimestamp[];
|
|
528
|
+
usage: Usage;
|
|
529
|
+
}
|
|
530
|
+
interface OcrRequest {
|
|
531
|
+
/** A URL, data-URL, or raw bytes of the document/image. */
|
|
532
|
+
document: string | Uint8Array;
|
|
533
|
+
mimeType?: string;
|
|
534
|
+
spec: TierSpec;
|
|
535
|
+
}
|
|
536
|
+
interface OcrPage {
|
|
537
|
+
index: number;
|
|
538
|
+
markdown: string;
|
|
539
|
+
}
|
|
540
|
+
interface OcrResult {
|
|
541
|
+
pages: OcrPage[];
|
|
542
|
+
usage: Usage;
|
|
543
|
+
}
|
|
544
|
+
interface ModerationRequest {
|
|
545
|
+
input: string[];
|
|
546
|
+
spec: TierSpec;
|
|
547
|
+
}
|
|
548
|
+
interface ModerationItem {
|
|
549
|
+
/** True if any category tripped. */
|
|
550
|
+
flagged: boolean;
|
|
551
|
+
categories: Record<string, boolean>;
|
|
552
|
+
categoryScores: Record<string, number>;
|
|
553
|
+
}
|
|
554
|
+
interface ModerationResult {
|
|
555
|
+
results: ModerationItem[];
|
|
556
|
+
usage: Usage;
|
|
557
|
+
}
|
|
558
|
+
/** TypeSafe's EntryType: plain text OR JSON structure. Instructions, choice option
|
|
559
|
+
* descriptions, score levels and noul criteria all accept it — and TypeSafe's own
|
|
560
|
+
* headline patterns (walking a taxonomy, a JSON rubric that sharpens the boundary
|
|
561
|
+
* between options) depend on it. Measured live 2026-10-01: structured instructions and
|
|
562
|
+
* object-valued choice options → 200. */
|
|
563
|
+
type JudgeEntry = string | null | unknown[] | {
|
|
564
|
+
[key: string]: unknown;
|
|
565
|
+
};
|
|
566
|
+
type JudgeQuestion =
|
|
567
|
+
/** `criteria` is an OBJECT with `true`/`false`, not a string — measured live: a string
|
|
568
|
+
* is a 422 from TypeSafe. The first version of this type had it as a string, which
|
|
569
|
+
* let through exactly the shape the server refuses. */
|
|
570
|
+
{
|
|
571
|
+
type: "noul";
|
|
572
|
+
instructions: JudgeEntry;
|
|
573
|
+
criteria?: {
|
|
574
|
+
true?: JudgeEntry;
|
|
575
|
+
false?: JudgeEntry;
|
|
576
|
+
};
|
|
577
|
+
} | {
|
|
578
|
+
type: "choice";
|
|
579
|
+
instructions: JudgeEntry;
|
|
580
|
+
criteria: Record<string, JudgeEntry>;
|
|
581
|
+
} | {
|
|
582
|
+
type: "score";
|
|
583
|
+
instructions: JudgeEntry;
|
|
584
|
+
criteria: JudgeEntry[];
|
|
585
|
+
};
|
|
586
|
+
interface JudgeNoulAnswer {
|
|
587
|
+
type: "noul";
|
|
588
|
+
/** Probability 0–1 that the answer is yes. */
|
|
589
|
+
noul: number;
|
|
590
|
+
}
|
|
591
|
+
interface JudgeChoiceAnswer {
|
|
592
|
+
type: "choice";
|
|
593
|
+
/** The key of the most probable option. ALWAYS one of yours — Jev cannot answer
|
|
594
|
+
* "none of these". Read `confidence` to decide whether to act on it. */
|
|
595
|
+
choice: string;
|
|
596
|
+
probabilities: Record<string, number>;
|
|
597
|
+
/** How peaked the distribution is, 0–1. NOT the same as the winning probability. */
|
|
598
|
+
confidence: number;
|
|
599
|
+
}
|
|
600
|
+
interface JudgeScoreAnswer {
|
|
601
|
+
type: "score";
|
|
602
|
+
/** Probability-weighted level; can fall BETWEEN levels (1.5). */
|
|
603
|
+
score: number;
|
|
604
|
+
probabilities: Record<string, number>;
|
|
605
|
+
/** Level number → the level as you DESCRIBED it. If you passed structured levels,
|
|
606
|
+
* the legend echoes structure, not text — measured live: {"0":{"level":"minor"}}. */
|
|
607
|
+
legend: Record<string, JudgeEntry>;
|
|
608
|
+
confidence: number;
|
|
609
|
+
}
|
|
610
|
+
type JudgeAnswer = JudgeNoulAnswer | JudgeChoiceAnswer | JudgeScoreAnswer;
|
|
611
|
+
interface JudgeRequest {
|
|
612
|
+
state: string | string[] | Record<string, unknown>;
|
|
613
|
+
questions: Record<string, JudgeQuestion>;
|
|
614
|
+
spec: TierSpec;
|
|
615
|
+
}
|
|
616
|
+
interface JudgeResult {
|
|
617
|
+
answers: Record<string, JudgeAnswer>;
|
|
618
|
+
usage: Usage;
|
|
619
|
+
}
|
|
620
|
+
interface DialogueTurn {
|
|
621
|
+
text: string;
|
|
622
|
+
voiceId: string;
|
|
623
|
+
}
|
|
624
|
+
interface DialogueRequest {
|
|
625
|
+
inputs: DialogueTurn[];
|
|
626
|
+
/** F051.4 — the pronunciation dictionary, applied to EACH line. cms measured the
|
|
627
|
+
* gap: a two-host podcast could not get a dictionary at all, so "broberg.ai" was
|
|
628
|
+
* said wrong in every episode — they could fix the sponsor read and the host's
|
|
629
|
+
* hand-off (both `ai.tts`) and not the conversation, which is 95% of the audio. */
|
|
630
|
+
pronunciations?: Pronunciation[];
|
|
631
|
+
/** Output container, e.g. "mp3" (default). */
|
|
632
|
+
format?: string;
|
|
633
|
+
spec: TierSpec;
|
|
634
|
+
}
|
|
635
|
+
interface PodcastResult {
|
|
636
|
+
/** Episode audio bytes. */
|
|
637
|
+
audio: Uint8Array;
|
|
638
|
+
mimeType: string;
|
|
639
|
+
/** F055 — per-word timings, present only when `wordTimings` was asked for AND the
|
|
640
|
+
* route produces them. Its ABSENCE says "not available here"; an empty `words` array
|
|
641
|
+
* would say "the text had no words", which is a different claim.
|
|
642
|
+
*
|
|
643
|
+
* `sourceStart`/`sourceEnd` index the ORIGINAL text you passed, not the SSML we sent
|
|
644
|
+
* — Azure reports only audio time, so the link back to the manuscript is derived
|
|
645
|
+
* here. `unaligned` names any spoken word that could not be placed. */
|
|
646
|
+
wordTimings?: AlignedWordTimings;
|
|
647
|
+
/** F055.4 — the EXACT markup we sent the provider, verbatim, not rebuilt.
|
|
648
|
+
*
|
|
649
|
+
* Why it exists: the text you passed is not what was spoken. A pronunciation
|
|
650
|
+
* dictionary rewrites it, so `text` is a RETELLING of the audio — and without this
|
|
651
|
+
* field a consumer cannot check our retelling any more than we could check theirs.
|
|
652
|
+
* cms filed exactly that (11 September 2026) while we were asking them for markup
|
|
653
|
+
* only we could produce.
|
|
654
|
+
*
|
|
655
|
+
* UNDEFINED when the route builds no markup (ElevenLabs routes by voice, not SSML).
|
|
656
|
+
* An empty string would say "we sent empty markup", which is a different claim. */
|
|
657
|
+
ssml?: string;
|
|
658
|
+
usage: Usage;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
interface TtsRequest {
|
|
662
|
+
text: string;
|
|
663
|
+
voiceId: string;
|
|
664
|
+
/** BCP-47 locale (e.g. "da-DK"). Azure uses it for SSML xml:lang; ElevenLabs ignores it. */
|
|
665
|
+
lang?: string;
|
|
666
|
+
/** Provider output-format hint (e.g. an Azure X-Microsoft-OutputFormat); ElevenLabs ignores it. */
|
|
667
|
+
format?: string;
|
|
668
|
+
/** Speaking-rate multiplier (Azure): 1 = normal, 0.9 = 10% slower, 1.1 = faster. ElevenLabs ignores it. */
|
|
669
|
+
rate?: number;
|
|
670
|
+
/** F051 — pronunciation dictionary. A Danish voice says English jargon wrongly:
|
|
671
|
+
* measured on da-DK, "AI" comes out as the word "aj", "native" as "nativ".
|
|
672
|
+
*
|
|
673
|
+
* Azure renders these as SSML (`<sub alias>` / `<phoneme alphabet="ipa" ph>`);
|
|
674
|
+
* ElevenLabs has no SSML and can only apply `alias`. **This is the CONTROLLED DOOR
|
|
675
|
+
* into SSML** — the substitution happens adapter-side AFTER the text is escaped, so
|
|
676
|
+
* `text` can never inject markup, and `alias`/`ipa` are escaped too. */
|
|
677
|
+
pronunciations?: Pronunciation[];
|
|
678
|
+
/** F055 — ask for per-word timings so a reader can highlight each word as it is
|
|
679
|
+
* spoken. Azure only. Absent from the result means "this route does not produce
|
|
680
|
+
* them", which is a different answer from an empty list.
|
|
681
|
+
*
|
|
682
|
+
* **This changes the ROUTE, not just the payload.** The real-time endpoint returns
|
|
683
|
+
* audio only; word boundaries exist solely on Azure's asynchronous batch synthesis
|
|
684
|
+
* API, so the call becomes submit → poll → fetch. Microsoft quotes 10–20s for half
|
|
685
|
+
* of all jobs and up to 120s for 95%. Worth it for a pre-generated, cached reading;
|
|
686
|
+
* wrong for anything a user is waiting on. */
|
|
687
|
+
wordTimings?: boolean;
|
|
688
|
+
spec: TierSpec;
|
|
689
|
+
}
|
|
690
|
+
interface BatchRequestItem {
|
|
691
|
+
customId: string;
|
|
692
|
+
prompt: string;
|
|
693
|
+
}
|
|
694
|
+
interface BatchJob {
|
|
695
|
+
jobId: string;
|
|
696
|
+
status: string;
|
|
697
|
+
total?: number;
|
|
698
|
+
completed?: number;
|
|
699
|
+
}
|
|
700
|
+
interface BatchResultItem {
|
|
701
|
+
customId: string;
|
|
702
|
+
text: string;
|
|
703
|
+
}
|
|
704
|
+
/** The thin contract every provider implements (F4). A provider need only
|
|
705
|
+
* support the capabilities it offers — `chat` is the baseline; vision/image/
|
|
706
|
+
* embedding are optional and absence is a typed capability gap. */
|
|
707
|
+
interface ProviderAdapter {
|
|
708
|
+
readonly name: string;
|
|
709
|
+
/** Every capability is optional — an adapter implements only what it supports
|
|
710
|
+
* (e.g. fal does image only). The client guards each call and throws a clear
|
|
711
|
+
* "provider X does not support Y" when a capability is absent. */
|
|
712
|
+
chat?(req: ChatRequest): Promise<ChatResult>;
|
|
713
|
+
/** Streaming chat (F8). Optional — absence is a typed "no streaming support".
|
|
714
|
+
* Same request shape as chat; yields ChatStreamEvents as the turn unfolds. */
|
|
715
|
+
chatStream?(req: ChatRequest): AsyncIterable<ChatStreamEvent>;
|
|
716
|
+
/** Dedicated translation engine (F032) — e.g. DeepL. When absent, `ai.translate`
|
|
717
|
+
* falls back to a chat prompt-contract (the historical default for every
|
|
718
|
+
* other provider). `to`/`from` are provider-specific: a chat-routed call
|
|
719
|
+
* accepts free-form names ("Danish"); DeepL requires real codes ("DA"). */
|
|
720
|
+
translate?(req: TranslateRequest): Promise<TranslateResult>;
|
|
721
|
+
vision?(req: ChatRequest): Promise<ChatResult>;
|
|
722
|
+
image?(req: ImageRequest): Promise<ImageResult>;
|
|
723
|
+
/** Image-to-video generation (F024) — animate a still into a short clip. gemini
|
|
724
|
+
* (Veo 3.1) by default; override to fal for Kling/Seedance. Name only the DEFAULT
|
|
725
|
+
* route here — provider-doc-drift.test.ts fails if this and DEFAULT_ANIMATE_SPEC
|
|
726
|
+
* disagree, because this sentence is the editor tooltip a consumer reads when
|
|
727
|
+
* deciding which API key to buy. */
|
|
728
|
+
animate?(req: AnimateRequest): Promise<AnimateResult>;
|
|
729
|
+
/** Train a style/brand LoRA from images (F021). fal. */
|
|
730
|
+
trainStyle?(req: TrainStyleRequest): Promise<TrainStyleResult>;
|
|
731
|
+
embedding?(req: EmbeddingRequest): Promise<EmbeddingResult>;
|
|
732
|
+
transcribe?(req: TranscribeRequest): Promise<TranscribeResult>;
|
|
733
|
+
ocr?(req: OcrRequest): Promise<OcrResult>;
|
|
734
|
+
moderate?(req: ModerationRequest): Promise<ModerationResult>;
|
|
735
|
+
/** F066 — typed decisions with calibrated probabilities (TypeSafe Jev). */
|
|
736
|
+
judge?(req: JudgeRequest): Promise<JudgeResult>;
|
|
737
|
+
/** Multi-voice dialogue → one audio episode (F020). ElevenLabs. */
|
|
738
|
+
dialogue?(req: DialogueRequest): Promise<PodcastResult>;
|
|
739
|
+
/** Single-voice TTS (F020.4) → audio. ElevenLabs. */
|
|
740
|
+
tts?(req: TtsRequest): Promise<PodcastResult>;
|
|
741
|
+
/** Batch (F016.1) — async chat-request processing at 50% cost. Mistral. */
|
|
742
|
+
batchSubmit?(req: {
|
|
743
|
+
items: BatchRequestItem[];
|
|
744
|
+
spec: TierSpec;
|
|
745
|
+
}): Promise<BatchJob>;
|
|
746
|
+
batchStatus?(req: {
|
|
747
|
+
jobId: string;
|
|
748
|
+
spec: TierSpec;
|
|
749
|
+
}): Promise<BatchJob>;
|
|
750
|
+
batchResults?(req: {
|
|
751
|
+
jobId: string;
|
|
752
|
+
spec: TierSpec;
|
|
753
|
+
}): Promise<BatchResultItem[]>;
|
|
754
|
+
}
|
|
755
|
+
interface TranslateResult {
|
|
756
|
+
text: string;
|
|
757
|
+
usage: Usage;
|
|
758
|
+
}
|
|
759
|
+
/** Dedicated-engine translation request (F032) — used only by adapters that
|
|
760
|
+
* implement `ProviderAdapter.translate` directly (e.g. DeepL); chat-routed
|
|
761
|
+
* providers never see this shape, they get a built prompt instead. */
|
|
762
|
+
interface TranslateRequest {
|
|
763
|
+
text: string;
|
|
764
|
+
to: string;
|
|
765
|
+
from?: string;
|
|
766
|
+
spec: TierSpec;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
type AvailabilityStatus = "available" | "suspended" | "unknown";
|
|
770
|
+
/** Where a model's current availability came from: the curated default seed,
|
|
771
|
+
* or a live provider refresh (Anthropic GET /v1/models). */
|
|
772
|
+
type AvailabilitySource = "default" | "refresh";
|
|
773
|
+
/** One row of the shared status read — what a UI model-picker renders. */
|
|
774
|
+
interface ModelStatus {
|
|
775
|
+
/** Canonical provider model id, e.g. "claude-fable-5". */
|
|
776
|
+
id: string;
|
|
777
|
+
/** Short/tier alias, e.g. "fable" (the first registered alias). */
|
|
778
|
+
alias?: string;
|
|
779
|
+
/** "anthropic" | "openai" | "gemini" | "mistral" | … */
|
|
780
|
+
provider: string;
|
|
781
|
+
available: boolean;
|
|
782
|
+
status: AvailabilityStatus;
|
|
783
|
+
/** Friendly reason, e.g. "suspended — US export-control directive (2026-06-12)". */
|
|
784
|
+
note?: string;
|
|
785
|
+
source: AvailabilitySource;
|
|
786
|
+
}
|
|
787
|
+
/** Result of resolveModel — the spawn / call path consumes this synchronously. */
|
|
788
|
+
interface ResolveResult {
|
|
789
|
+
/** True when the requested model itself is available. */
|
|
790
|
+
ok: boolean;
|
|
791
|
+
/** The id to actually use: `requested` when ok, else the first available fallback. */
|
|
792
|
+
model: string;
|
|
793
|
+
/** What the caller asked for (id or alias, normalized to the canonical id). */
|
|
794
|
+
requested: string;
|
|
795
|
+
provider?: string;
|
|
796
|
+
/** True when `model` differs from `requested` because we fell back. */
|
|
797
|
+
fellBack: boolean;
|
|
798
|
+
status: AvailabilityStatus;
|
|
799
|
+
/** Why it degraded / why it is unavailable. */
|
|
800
|
+
reason?: string;
|
|
801
|
+
/** The registry row's caveat, surfaced on the SUCCESS path too (F057.2).
|
|
802
|
+
*
|
|
803
|
+
* `reason` only ever appears when a model is unavailable, so a caveat written
|
|
804
|
+
* on an AVAILABLE row — "documented to deprecate 2026-07-24", "preview", a
|
|
805
|
+
* known rate limit — was reachable through listModels() and invisible to the
|
|
806
|
+
* one caller who most needs it: the gate. A consumer passing requireKnown got
|
|
807
|
+
* a clean green with the warning sitting one call away, unread. Carrying it
|
|
808
|
+
* here costs an optional field and makes `ok:true` able to say "yes, but". */
|
|
809
|
+
note?: string;
|
|
810
|
+
}
|
|
811
|
+
/** Thrown by resolveModel when the requested model is unavailable, no usable
|
|
812
|
+
* fallback exists, and the caller passed `throwIfUnavailable`. Callers flag on
|
|
813
|
+
* `.code === "model_unavailable"`. */
|
|
814
|
+
declare class ModelUnavailableError extends Error {
|
|
815
|
+
readonly code = "model_unavailable";
|
|
816
|
+
readonly requested: string;
|
|
817
|
+
readonly provider?: string;
|
|
818
|
+
readonly note?: string;
|
|
819
|
+
constructor(requested: string, note?: string, provider?: string);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
interface ResolveOptions {
|
|
823
|
+
/** One id/alias or an ordered chain to try when `requested` is unavailable. */
|
|
824
|
+
fallback?: string | string[];
|
|
825
|
+
/** Scope hint (passed through to the result); does not gate lookup. */
|
|
826
|
+
provider?: string;
|
|
827
|
+
/** Throw ModelUnavailableError instead of returning ok:false when there is no
|
|
828
|
+
* usable fallback. For callers that want to flag rather than degrade. */
|
|
829
|
+
throwIfUnavailable?: boolean;
|
|
830
|
+
/** Treat an id we do not track as UNUSABLE instead of fail-open.
|
|
831
|
+
*
|
|
832
|
+
* Default is fail-open: we never block a model we simply do not track. That is
|
|
833
|
+
* right for liveness, and wrong for a caller who is GATING — cms measured the
|
|
834
|
+
* consequence: resolveModel("cheap") answered {ok:true, model:"cheap"}, so a
|
|
835
|
+
* consumer following our own instruction to gate on `ok` passed the gate and
|
|
836
|
+
* then sent the literal string "cheap" to a provider as a model id. A
|
|
837
|
+
* success-shaped non-answer is worse than an error, because an error gets
|
|
838
|
+
* handled and a shape does not.
|
|
839
|
+
*
|
|
840
|
+
* Set this when you need "a model you actually know about". Off by default so
|
|
841
|
+
* no existing caller changes behaviour. */
|
|
842
|
+
requireKnown?: boolean;
|
|
843
|
+
}
|
|
844
|
+
/** The shared status read — UI pickers grey out `available:false` rows. */
|
|
845
|
+
declare function listModels(opts?: {
|
|
846
|
+
provider?: string;
|
|
847
|
+
}): ModelStatus[];
|
|
848
|
+
/**
|
|
849
|
+
* Resolve a requested model (id or alias) to one that is actually usable.
|
|
850
|
+
* Synchronous + offline by contract (cardmem #4842) — reads the registry only.
|
|
851
|
+
*
|
|
852
|
+
* - Available → pass through ({ ok:true, fellBack:false }).
|
|
853
|
+
* - Unavailable + a fallback that IS available → swap ({ ok:false, fellBack:true }).
|
|
854
|
+
* - Unavailable + no usable fallback → throw (throwIfUnavailable) or return ok:false.
|
|
855
|
+
* - Unknown id → treated available (never block a model we do not track).
|
|
856
|
+
*/
|
|
857
|
+
declare function resolveModel(requested: string, opts?: ResolveOptions): ResolveResult;
|
|
858
|
+
|
|
859
|
+
interface TierDescription {
|
|
860
|
+
tier: Tier;
|
|
861
|
+
provider: string;
|
|
862
|
+
model: string;
|
|
863
|
+
/** false when the model is a "-latest" alias the provider can move under us. */
|
|
864
|
+
pinned: boolean;
|
|
865
|
+
/** USD per 1M tokens from the pricing table; undefined when the model is unpriced. */
|
|
866
|
+
inputPer1M?: number;
|
|
867
|
+
outputPer1M?: number;
|
|
868
|
+
cacheReadPer1M?: number;
|
|
869
|
+
}
|
|
870
|
+
/** The built-in default for a tier. A client's own `defaults` or a per-call
|
|
871
|
+
* `override` can route elsewhere — read usage.model off the response for that. */
|
|
872
|
+
declare function describeTier(tier: Tier): TierDescription;
|
|
873
|
+
|
|
874
|
+
export { type Role as $, type AnimateResult as A, type BatchRequestItem as B, type ChatResult as C, type DialogueRequest as D, type EmbeddingResult as E, type JudgeChoiceAnswer as F, type JudgeNoulAnswer as G, type JudgeQuestion as H, type ImageResult as I, type JudgeResult as J, type JudgeScoreAnswer as K, type LoraWeight as L, type ModerationResult as M, type Message as N, type OcrResult as O, type PodcastResult as P, type ModelStatus as Q, type Region as R, ModelUnavailableError as S, type Tier as T, type Usage as U, type ModerationItem as V, type ModerationRequest as W, type OcrPage as X, type OcrRequest as Y, type ResolveOptions as Z, type ResolveResult as _, type ChatStreamEvent as a, type TierDescription as a0, type TrainStyleRequest as a1, type TranscribeRequest as a2, type TtsRequest as a3, type WordTiming as a4, alignWordTimings as a5, classifyRegionName as a6, describeTier as a7, listModels as a8, regionOfHost as a9, regionOfProvider as aa, resolveModel as ab, type TranslateResult as b, type TrainStyleResult as c, type TranscribeResult as d, type TierSpec as e, type BatchJob as f, type BatchResultItem as g, type ProviderAdapter as h, type CostSink as i, type ToolCall as j, type Tool as k, type Transport as l, type Capability as m, type BudgetConfig as n, type BudgetStore as o, type AlignedWordTimings as p, type AvailabilitySource as q, type AvailabilityStatus as r, type AzureWordBoundary as s, type CallOptions as t, type ChatRequest as u, type ContentPart as v, type DialogueTurn as w, type EmbeddingRequest as x, type ImageRequest as y, type JudgeAnswer as z };
|