@broberg/ai-sdk 0.47.1 → 0.49.0
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/README.md +12 -2
- package/dist/{chunk-ZFWSLSE7.js → chunk-SWV5YIEE.js} +27 -2
- package/dist/chunk-SWV5YIEE.js.map +1 -0
- package/dist/index.d.ts +97 -9
- package/dist/index.js +221 -68
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +9 -0
- package/dist/registry.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-ZFWSLSE7.js.map +0 -1
package/README.md
CHANGED
|
@@ -189,13 +189,23 @@ const ai = createAI({
|
|
|
189
189
|
budget: { perCallUsd: 0.05, rollingUsd: 5 }, // pre-flight guard (throws BudgetExceededError)
|
|
190
190
|
costSink: multiSink([
|
|
191
191
|
upmetricsSink({ baseUrl: "https://upmetrics.org", apiKey: process.env.UPMETRICS_API_KEY!, agentName: "my-app" }),
|
|
192
|
-
sqliteSink({ dbPath: "./ai-cost.db" }),
|
|
192
|
+
sqliteSink({ dbPath: "./ai-cost.db" }), // Bun only — throws on Node, see below
|
|
193
193
|
]),
|
|
194
194
|
});
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
Sinks: `upmetricsSink` (canonical), `discordSink`, `sqliteSink`, `multiSink`,
|
|
198
|
-
`noopSink`. A
|
|
198
|
+
`noopSink`. A sink that fails *during* a call never crashes that call.
|
|
199
|
+
|
|
200
|
+
> **`sqliteSink` and `getCostSummary` are Bun-only** (changed in v0.48.0, released
|
|
201
|
+
> 22 September 2026). They are backed by
|
|
202
|
+
> `bun:sqlite`, which Node cannot import at all. On Node they now **throw where you
|
|
203
|
+
> construct them** — deliberately, and this is a behaviour change: up to v0.47 you
|
|
204
|
+
> got a sink object that threw on every `record()`, and the client swallows per-call
|
|
205
|
+
> sink errors by design, so cost tracking went silently dead. An empty cost dataset
|
|
206
|
+
> and a working one look identical in a report. **On Node use `upmetricsSink`.**
|
|
207
|
+
> `sqliteBudgetStore` is Bun-only for the same reason; it has always failed loudly
|
|
208
|
+
> there, because a budget error is not swallowed.
|
|
199
209
|
|
|
200
210
|
### Cost-tracking is on by default (v0.24+)
|
|
201
211
|
|
|
@@ -50,6 +50,27 @@ var DEFAULTS = [
|
|
|
50
50
|
{ id: "gemini-2.5-flash-lite", aliases: ["gemini-flash-lite"], provider: "gemini", available: true, status: "available", source: "default" },
|
|
51
51
|
// ── OpenAI ───────────────────────────────────────────────────────────────
|
|
52
52
|
{ id: "text-embedding-3-small", aliases: [], provider: "openai", available: true, status: "available", source: "default" },
|
|
53
|
+
// ── DeepSeek (CN-hosted — NOT GDPR-safe; non-PII workloads only) ─────────
|
|
54
|
+
//
|
|
55
|
+
// F057.2. These were missing while pricing.ts carried OFFICIAL rates for them
|
|
56
|
+
// and providers/deepseek.ts shipped an adapter (F030.2) — so our own CLAUDE.md
|
|
57
|
+
// instruction ("if you are GATING, pass requireKnown:true") refused a route we
|
|
58
|
+
// built, priced and wrote an F-number for. Two tables disagreed, and the gate
|
|
59
|
+
// is the one that decides. Reported by pitch (ref 28546) via components.
|
|
60
|
+
//
|
|
61
|
+
// The note carries the sunset because the registry is what a picker renders, and
|
|
62
|
+
// a row that says only "available" would hide it. Both ids were documented to
|
|
63
|
+
// deprecate 2026-07-24 in favour of `deepseek-v4-flash` — we have NOT verified
|
|
64
|
+
// against a live key (no DEEPSEEK_API_KEY here), so this is the curated seed's
|
|
65
|
+
// usual standing: a default, not a measurement. A live refresh may overrule it.
|
|
66
|
+
//
|
|
67
|
+
// `deepseek-v4-flash` on the DIRECT api is deliberately NOT here: it has no
|
|
68
|
+
// price in pricing.ts (measured — getPrice("deepseek","deepseek-v4-flash") is
|
|
69
|
+
// undefined), so a row would pass the gate for a route that silently bills
|
|
70
|
+
// nothing. That is the same green-direction failure this card exists to remove,
|
|
71
|
+
// and it needs a rate from a real source, not a guess. See the plan-doc.
|
|
72
|
+
{ id: "deepseek-chat", aliases: [], provider: "deepseek", available: true, status: "available", note: "direct api.deepseek.com; documented to deprecate 2026-07-24 in favour of deepseek-v4-flash \u2014 not live-verified", source: "default" },
|
|
73
|
+
{ id: "deepseek-reasoner", aliases: [], provider: "deepseek", available: true, status: "available", note: "direct api.deepseek.com (thinking); documented to deprecate 2026-07-24 in favour of deepseek-v4-flash \u2014 not live-verified", source: "default" },
|
|
53
74
|
// ── Mistral (EU / GDPR) ──────────────────────────────────────────────────
|
|
54
75
|
{ id: "mistral-large-latest", aliases: ["mistral-large"], provider: "mistral", available: true, status: "available", source: "default" },
|
|
55
76
|
{ id: "mistral-medium-latest", aliases: ["mistral-medium"], provider: "mistral", available: true, status: "available", source: "default" },
|
|
@@ -153,7 +174,11 @@ function resolveModel(requested, opts = {}) {
|
|
|
153
174
|
requested: id,
|
|
154
175
|
provider,
|
|
155
176
|
fellBack: false,
|
|
156
|
-
status: entry?.status ?? "unknown"
|
|
177
|
+
status: entry?.status ?? "unknown",
|
|
178
|
+
// A green gate that hides a known caveat is the failure this whole card is
|
|
179
|
+
// about, one level up: the answer is shaped like "fine" and the warning is
|
|
180
|
+
// somewhere else. If the row has a note, the caller gets it with the yes.
|
|
181
|
+
...entry?.note ? { note: entry.note } : {}
|
|
157
182
|
};
|
|
158
183
|
}
|
|
159
184
|
const chain = opts.fallback === void 0 ? [] : Array.isArray(opts.fallback) ? opts.fallback : [opts.fallback];
|
|
@@ -198,4 +223,4 @@ export {
|
|
|
198
223
|
listModels,
|
|
199
224
|
resolveModel
|
|
200
225
|
};
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
226
|
+
//# sourceMappingURL=chunk-SWV5YIEE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routing/tier-map.ts","../src/availability/registry.ts","../src/availability/types.ts","../src/availability/resolve.ts"],"sourcesContent":["// Tier routing: a named Tier resolves to a concrete (provider, model, transport).\n// Precedence is per-call override > client config map > built-in defaults.\nimport type { Tier, TierSpec } from \"../types.js\";\n\n/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a\n * per-call override.\n *\n * F030 — Anthropic API phase-out: `ANTHROPIC_API_KEY` was globally removed, so the\n * default cloud route may NOT hit Anthropic Console. `fast`/`smart`/`powerful`/\n * `vision` now default to **Mistral EU** (Paris-hosted, Schrems II-safe — so every\n * default text/vision call is GDPR-safe by default). Claude stays reachable as a\n * NON-default quality fallback for non-PII via `override:{provider:\"openrouter\",\n * model:\"anthropic/claude-…\"}`. DeepSeek (CN) is the opt-in non-PII secondary\n * (`provider:\"deepseek\"`), never a default. Magistral (reasoning) / mistral-large\n * for vision are per-call overrides, not defaults (don't pay the premium on all). */\nexport const DEFAULT_TIER_MAP: Record<Tier, TierSpec> = {\n fast: { provider: \"mistral\", model: \"mistral-small-latest\", transport: \"http\" },\n smart: { provider: \"mistral\", model: \"mistral-large-latest\", transport: \"http\" },\n powerful: { provider: \"mistral\", model: \"mistral-large-latest\", transport: \"http\" },\n cheap: { provider: \"mistral\", model: \"mistral-small-latest\", transport: \"http\" },\n // Vision: small-latest (vision-capable, cheap EU) is the default; override to\n // mistral-large-latest for demanding image/spatial/composition work.\n // F041 — bumped from mistral-small on Christian's ask, and the choice is MEASURED,\n // not assumed from price. On a fine-discrimination test (an 8x10 grid where one cell\n // differs only in its blue channel, 190->150) across 9 cases:\n // mistral-medium-latest 4/9 <- best\n // mistral-small-latest 1/6 + 0/3 = 1/9\n // mistral-large-latest 0/9 <- WORSE than small, despite costing 5x more\n // Large ties small on easy colour blocks (4/4 each) and collapses on subtle ones,\n // so \"bigger is better at vision\" does not hold in Mistral's lineup. Nobody is good\n // at this task; medium is simply the only one that sees anything.\n vision: { provider: \"mistral\", model: \"mistral-medium-latest\", transport: \"http\" },\n // Native video understanding — Gemini leads; flash-lite is the cheap default (F019).\n // NOT Anthropic → out of the F030 phase-out (its own EU epic if/when needed).\n video: { provider: \"gemini\", model: \"gemini-2.5-flash-lite\", transport: \"http\" },\n // NOT Anthropic → out of F030 (EU-embedding migration is its own future epic).\n embedding: { provider: \"openai\", model: \"text-embedding-3-small\", transport: \"http\" },\n};\n\n/**\n * Resolve a Tier to a concrete TierSpec.\n *\n * Merge order (later wins): DEFAULT_TIER_MAP < configMap < override.\n * - `configMap` is the client-level AiConfig.defaults (per-tier full specs).\n * - `override` is a per-call Partial<TierSpec> — only the fields it sets win.\n */\n/** Refuse an override that names a different provider without naming a model (F043.2).\n *\n * Exported and called at EVERY spec merge, not only inside resolveTier. The first\n * version guarded only the six capabilities that route via a tier; image, animate,\n * trainStyle, ocr, moderate, podcast, tts, transcribe and batch merge the override\n * themselves and skipped it entirely — so `ai.image({override:{provider:\"fal\"}})`\n * still posted BFL's flux-2-pro to fal, the exact misleading upstream error this was\n * written to kill. A guard at six of fifteen call sites is a guard you cannot rely on.\n *\n * We REFUSE rather than re-resolve: choosing a model for the new provider would be\n * the SDK making a price decision on the caller's behalf, visible only on the bill.\n *\n * `knownProviders` (when given) lets an UNREGISTERED provider through — that is a\n * typo, and \"no provider adapter registered\" is the useful thing to say about it. */\nexport function assertOverrideProvider(\n base: { provider: string; model: string },\n override: Partial<TierSpec> | undefined,\n label: string,\n knownProviders?: readonly string[],\n): void {\n if (!override?.provider || override.model !== undefined) return;\n if (override.provider === base.provider) return;\n if (knownProviders !== undefined && !knownProviders.includes(override.provider)) return;\n throw new Error(\n `createAI: override sets provider \"${override.provider}\", but \"${label}\" resolves to model ` +\n `\"${base.model}\", which belongs to \"${base.provider}\". Set a model too, e.g. ` +\n `override: { provider: \"${override.provider}\", model: \"<a ${override.provider} model>\" }.`,\n );\n}\n\nexport function resolveTier(\n tier: Tier,\n override?: Partial<TierSpec>,\n configMap?: Partial<Record<Tier, TierSpec>>,\n knownProviders?: readonly string[],\n): TierSpec {\n const base = configMap?.[tier] ?? DEFAULT_TIER_MAP[tier];\n assertOverrideProvider(base, override, tier, knownProviders);\n return { ...base, ...override };\n}\n","// F022 — the model-availability registry: the ONE source both resolveModel()\n// (spawn / call path) and listModels() (UI picker) read. A curated default seed\n// (works offline — the durable floor) plus a mutable overlay that\n// refreshAvailability() updates from the live provider list.\n//\n// Scope note: this is a LIVENESS view (is this id alive right now?), not the\n// rich capability/price inventory (that is F017 src/catalogue). We only track\n// ids we want to assert status on; anything not here is fail-open (treated\n// available) so we never block a model we simply do not track.\nimport { DEFAULT_TIER_MAP } from \"../routing/tier-map.js\";\nimport type { AvailabilityStatus, AvailabilitySource, ModelStatus } from \"./types.js\";\n\n/** Internal registry row. `aliases[0]` surfaces as ModelStatus.alias. */\nexport interface RegistryEntry {\n id: string;\n aliases: string[];\n provider: string;\n available: boolean;\n status: AvailabilityStatus;\n note?: string;\n source: AvailabilitySource;\n}\n\nconst SUSPENDED_FABLE_MYTHOS = \"suspended — US export-control directive (2026-06-12)\";\n\n/** Curated defaults. Aliases here are MODEL-IDENTITY names only (short names for\n * the model itself). TIER aliases — fast/smart/powerful/cheap/vision/video/\n * embedding — are NOT written here: they are derived from DEFAULT_TIER_MAP below,\n * because hand-maintaining them in two places is exactly how they drifted.\n *\n * They HAD drifted, and it crossed the EU border: this list said `smart` was\n * claude-sonnet-4-6 (Anthropic, US) for the ~3 months after F030 pointed the\n * default tiers at Mistral EU, so resolveModel('smart') named a US model while\n * ai.chat({tier:'smart'}) called an EU one. Anyone using resolveModel to SHOW or\n * DECIDE where data goes — the obvious use — got the wrong answer. Measured by\n * fd-sundhed in 0.21.1, still true in 0.28.0, fixed here by deleting the second\n * list rather than correcting it. */\nconst DEFAULTS: RegistryEntry[] = [\n // ── Anthropic ────────────────────────────────────────────────────────────\n { id: \"claude-haiku-4-5\", aliases: [\"haiku\"], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-sonnet-4-6\", aliases: [\"sonnet\"], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-opus-4-8\", aliases: [\"opus\"], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-fable-5\", aliases: [\"fable\"], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-mythos-5\", aliases: [\"mythos\"], provider: \"anthropic\", available: false, status: \"suspended\", note: SUSPENDED_FABLE_MYTHOS, source: \"default\" },\n // ── Gemini ───────────────────────────────────────────────────────────────\n { id: \"gemini-2.5-flash\", aliases: [\"gemini-flash\"], provider: \"gemini\", available: true, status: \"available\", source: \"default\" },\n { id: \"gemini-2.5-flash-lite\", aliases: [\"gemini-flash-lite\"], provider: \"gemini\", available: true, status: \"available\", source: \"default\" },\n // ── OpenAI ───────────────────────────────────────────────────────────────\n { id: \"text-embedding-3-small\", aliases: [], provider: \"openai\", available: true, status: \"available\", source: \"default\" },\n // ── DeepSeek (CN-hosted — NOT GDPR-safe; non-PII workloads only) ─────────\n //\n // F057.2. These were missing while pricing.ts carried OFFICIAL rates for them\n // and providers/deepseek.ts shipped an adapter (F030.2) — so our own CLAUDE.md\n // instruction (\"if you are GATING, pass requireKnown:true\") refused a route we\n // built, priced and wrote an F-number for. Two tables disagreed, and the gate\n // is the one that decides. Reported by pitch (ref 28546) via components.\n //\n // The note carries the sunset because the registry is what a picker renders, and\n // a row that says only \"available\" would hide it. Both ids were documented to\n // deprecate 2026-07-24 in favour of `deepseek-v4-flash` — we have NOT verified\n // against a live key (no DEEPSEEK_API_KEY here), so this is the curated seed's\n // usual standing: a default, not a measurement. A live refresh may overrule it.\n //\n // `deepseek-v4-flash` on the DIRECT api is deliberately NOT here: it has no\n // price in pricing.ts (measured — getPrice(\"deepseek\",\"deepseek-v4-flash\") is\n // undefined), so a row would pass the gate for a route that silently bills\n // nothing. That is the same green-direction failure this card exists to remove,\n // and it needs a rate from a real source, not a guess. See the plan-doc.\n { id: \"deepseek-chat\", aliases: [], provider: \"deepseek\", available: true, status: \"available\", note: \"direct api.deepseek.com; documented to deprecate 2026-07-24 in favour of deepseek-v4-flash — not live-verified\", source: \"default\" },\n { id: \"deepseek-reasoner\", aliases: [], provider: \"deepseek\", available: true, status: \"available\", note: \"direct api.deepseek.com (thinking); documented to deprecate 2026-07-24 in favour of deepseek-v4-flash — not live-verified\", source: \"default\" },\n // ── Mistral (EU / GDPR) ──────────────────────────────────────────────────\n { id: \"mistral-large-latest\", aliases: [\"mistral-large\"], provider: \"mistral\", available: true, status: \"available\", source: \"default\" },\n { id: \"mistral-medium-latest\", aliases: [\"mistral-medium\"], provider: \"mistral\", available: true, status: \"available\", source: \"default\" },\n { id: \"mistral-small-latest\", aliases: [\"mistral-small\"], provider: \"mistral\", available: true, status: \"available\", source: \"default\" },\n];\n\n/** Attach every tier name as an alias of the model that tier ACTUALLY calls.\n * One source: DEFAULT_TIER_MAP decides, the registry follows. A tier whose model\n * is not in the registry is left unaliased on purpose — resolveModel then reports\n * status \"unknown\" (fail-open) instead of inventing a row, and the drift test\n * catches it. */\nexport const TIER_ALIAS_CONFLICTS: string[] = [];\n\nfunction applyTierAliases(entries: RegistryEntry[]): RegistryEntry[] {\n for (const [tier, spec] of Object.entries(DEFAULT_TIER_MAP)) {\n const owner = entries.find((e) => e.id === spec.model && e.provider === spec.provider);\n // A tier name hand-written on any OTHER row is the drift bug returning. It\n // would resolve correctly TODAY only because seed() lets the last write win\n // and the rows happen to be ordered favourably — reorder the array and the\n // tier silently points at the wrong provider again. Record it so a test can\n // fail on it, then strip it so runtime is right regardless.\n for (const e of entries) {\n if (e !== owner && e.aliases.includes(tier)) {\n TIER_ALIAS_CONFLICTS.push(`${tier} is hand-declared on ${e.id} (${e.provider}) but tier ${tier} calls ${spec.model} (${spec.provider})`);\n e.aliases = e.aliases.filter((a) => a !== tier);\n }\n }\n if (owner && !owner.aliases.includes(tier)) owner.aliases.push(tier);\n }\n return entries;\n}\napplyTierAliases(DEFAULTS);\n\n/** The live overlay, keyed by canonical id. Seeded from DEFAULTS (deep-copied so\n * resetting is clean). refreshAvailability() mutates this; resolve/listModels\n * read it synchronously. */\nlet OVERLAY = new Map<string, RegistryEntry>();\n/** alias → canonical id, rebuilt whenever the overlay is seeded. */\nlet ALIAS_INDEX = new Map<string, string>();\n\nfunction seed(): void {\n OVERLAY = new Map(DEFAULTS.map((e) => [e.id, { ...e, aliases: [...e.aliases] }]));\n ALIAS_INDEX = new Map();\n for (const e of DEFAULTS) for (const a of e.aliases) ALIAS_INDEX.set(a, e.id);\n}\nseed();\n\n/** Reset the overlay back to the curated defaults. For tests. */\nexport function resetRegistry(): void {\n seed();\n}\n\n/** Canonical id for a model id OR alias; null when we track neither. */\nexport function canonicalId(requested: string): string | null {\n if (OVERLAY.has(requested)) return requested;\n return ALIAS_INDEX.get(requested) ?? null;\n}\n\n/** The current entry for an id/alias, or undefined when untracked (fail-open). */\nexport function getEntry(requested: string): RegistryEntry | undefined {\n const id = canonicalId(requested);\n return id ? OVERLAY.get(id) : undefined;\n}\n\n/** All tracked entries (optionally provider-scoped), as a public ModelStatus[]. */\nexport function allEntries(provider?: string): ModelStatus[] {\n const rows: ModelStatus[] = [];\n for (const e of OVERLAY.values()) {\n if (provider && e.provider !== provider) continue;\n rows.push({\n id: e.id,\n alias: e.aliases[0],\n provider: e.provider,\n available: e.available,\n status: e.status,\n note: e.note,\n source: e.source,\n });\n }\n return rows;\n}\n\n/** Provider-scoped canonical ids (for refresh reconciliation). */\nexport function providerIds(provider: string): string[] {\n return [...OVERLAY.values()].filter((e) => e.provider === provider).map((e) => e.id);\n}\n\n/** Mark a tracked id available/suspended from a live refresh. No-op if untracked. */\nexport function setAvailability(id: string, available: boolean, note?: string): void {\n const e = OVERLAY.get(id);\n if (!e) return;\n e.available = available;\n e.status = available ? \"available\" : \"suspended\";\n e.source = \"refresh\";\n if (note !== undefined) e.note = note;\n else if (available) e.note = undefined;\n}\n","// F022 — Model Availability Harness. Public types for the availability layer:\n// the shared status read (ModelStatus), the resolve result, and the structured\n// error a caller can flag on. The registry is the one source both the spawn /\n// call path (resolveModel) and UI pickers (listModels) read.\n\nexport type AvailabilityStatus = \"available\" | \"suspended\" | \"unknown\";\n\n/** Where a model's current availability came from: the curated default seed,\n * or a live provider refresh (Anthropic GET /v1/models). */\nexport type AvailabilitySource = \"default\" | \"refresh\";\n\n/** One row of the shared status read — what a UI model-picker renders. */\nexport interface ModelStatus {\n /** Canonical provider model id, e.g. \"claude-fable-5\". */\n id: string;\n /** Short/tier alias, e.g. \"fable\" (the first registered alias). */\n alias?: string;\n /** \"anthropic\" | \"openai\" | \"gemini\" | \"mistral\" | … */\n provider: string;\n available: boolean;\n status: AvailabilityStatus;\n /** Friendly reason, e.g. \"suspended — US export-control directive (2026-06-12)\". */\n note?: string;\n source: AvailabilitySource;\n}\n\n/** Result of resolveModel — the spawn / call path consumes this synchronously. */\nexport interface ResolveResult {\n /** True when the requested model itself is available. */\n ok: boolean;\n /** The id to actually use: `requested` when ok, else the first available fallback. */\n model: string;\n /** What the caller asked for (id or alias, normalized to the canonical id). */\n requested: string;\n provider?: string;\n /** True when `model` differs from `requested` because we fell back. */\n fellBack: boolean;\n status: AvailabilityStatus;\n /** Why it degraded / why it is unavailable. */\n reason?: string;\n /** The registry row's caveat, surfaced on the SUCCESS path too (F057.2).\n *\n * `reason` only ever appears when a model is unavailable, so a caveat written\n * on an AVAILABLE row — \"documented to deprecate 2026-07-24\", \"preview\", a\n * known rate limit — was reachable through listModels() and invisible to the\n * one caller who most needs it: the gate. A consumer passing requireKnown got\n * a clean green with the warning sitting one call away, unread. Carrying it\n * here costs an optional field and makes `ok:true` able to say \"yes, but\". */\n note?: string;\n}\n\n/** Thrown by resolveModel when the requested model is unavailable, no usable\n * fallback exists, and the caller passed `throwIfUnavailable`. Callers flag on\n * `.code === \"model_unavailable\"`. */\nexport class ModelUnavailableError extends Error {\n readonly code = \"model_unavailable\";\n readonly requested: string;\n readonly provider?: string;\n readonly note?: string;\n constructor(requested: string, note?: string, provider?: string) {\n super(`model \"${requested}\" is unavailable${note ? ` (${note})` : \"\"}`);\n this.name = \"ModelUnavailableError\";\n this.requested = requested;\n this.note = note;\n this.provider = provider;\n }\n}\n","// F022 — the synchronous, zero-I/O resolve + status read. This is the spawn /\n// call hot path (buddy's launcher calls resolveModel per spawn, cardmem #4842):\n// it MUST never await and never touch the network. Freshness comes only from a\n// prior async refreshAvailability(); resolve just reads the in-memory registry.\nimport { allEntries, canonicalId, getEntry } from \"./registry.js\";\nimport { ModelUnavailableError } from \"./types.js\";\nimport type { ModelStatus, ResolveResult } from \"./types.js\";\n\nexport interface ResolveOptions {\n /** One id/alias or an ordered chain to try when `requested` is unavailable. */\n fallback?: string | string[];\n /** Scope hint (passed through to the result); does not gate lookup. */\n provider?: string;\n /** Throw ModelUnavailableError instead of returning ok:false when there is no\n * usable fallback. For callers that want to flag rather than degrade. */\n throwIfUnavailable?: boolean;\n /** Treat an id we do not track as UNUSABLE instead of fail-open.\n *\n * Default is fail-open: we never block a model we simply do not track. That is\n * right for liveness, and wrong for a caller who is GATING — cms measured the\n * consequence: resolveModel(\"cheap\") answered {ok:true, model:\"cheap\"}, so a\n * consumer following our own instruction to gate on `ok` passed the gate and\n * then sent the literal string \"cheap\" to a provider as a model id. A\n * success-shaped non-answer is worse than an error, because an error gets\n * handled and a shape does not.\n *\n * Set this when you need \"a model you actually know about\". Off by default so\n * no existing caller changes behaviour. */\n requireKnown?: boolean;\n}\n\n/** The shared status read — UI pickers grey out `available:false` rows. */\nexport function listModels(opts: { provider?: string } = {}): ModelStatus[] {\n return allEntries(opts.provider);\n}\n\n/** Is this id/alias currently usable? Untracked ids are fail-open (true) unless\n * the caller asked for requireKnown. */\nfunction isAvailable(requested: string, requireKnown = false): boolean {\n const e = getEntry(requested);\n if (!e) return !requireKnown; // fail-open on unknown, unless gating\n return e.available;\n}\n\n/**\n * Resolve a requested model (id or alias) to one that is actually usable.\n * Synchronous + offline by contract (cardmem #4842) — reads the registry only.\n *\n * - Available → pass through ({ ok:true, fellBack:false }).\n * - Unavailable + a fallback that IS available → swap ({ ok:false, fellBack:true }).\n * - Unavailable + no usable fallback → throw (throwIfUnavailable) or return ok:false.\n * - Unknown id → treated available (never block a model we do not track).\n */\nexport function resolveModel(requested: string, opts: ResolveOptions = {}): ResolveResult {\n const id = canonicalId(requested) ?? requested;\n const entry = getEntry(requested);\n const provider = opts.provider ?? entry?.provider;\n\n if (isAvailable(requested, opts.requireKnown)) {\n return {\n ok: true,\n model: id,\n requested: id,\n provider,\n fellBack: false,\n status: entry?.status ?? \"unknown\",\n // A green gate that hides a known caveat is the failure this whole card is\n // about, one level up: the answer is shaped like \"fine\" and the warning is\n // somewhere else. If the row has a note, the caller gets it with the yes.\n ...(entry?.note ? { note: entry.note } : {}),\n };\n }\n\n // Requested is suspended — walk the fallback chain for the first available one.\n const chain = opts.fallback === undefined ? [] : Array.isArray(opts.fallback) ? opts.fallback : [opts.fallback];\n for (const fb of chain) {\n if (isAvailable(fb, opts.requireKnown)) {\n const fbId = canonicalId(fb) ?? fb;\n return {\n ok: false,\n model: fbId,\n requested: id,\n provider,\n fellBack: true,\n status: entry?.status ?? \"suspended\",\n reason: entry?.note ?? `${id} is unavailable`,\n };\n }\n }\n\n // No usable fallback.\n const unknownAndGating = !entry && opts.requireKnown;\n const reason = unknownAndGating\n ? `${id} is not a model this registry knows — requireKnown was set, so it is not assumed usable`\n : (entry?.note ?? `${id} is unavailable`);\n if (opts.throwIfUnavailable) {\n throw new ModelUnavailableError(id, reason, provider);\n }\n return {\n ok: false,\n model: id,\n requested: id,\n provider,\n fellBack: false,\n status: entry?.status ?? \"unknown\",\n reason,\n };\n}\n"],"mappings":";AAeO,IAAM,mBAA2C;AAAA,EACtD,MAAM,EAAE,UAAU,WAAW,OAAO,wBAAwB,WAAW,OAAO;AAAA,EAC9E,OAAO,EAAE,UAAU,WAAW,OAAO,wBAAwB,WAAW,OAAO;AAAA,EAC/E,UAAU,EAAE,UAAU,WAAW,OAAO,wBAAwB,WAAW,OAAO;AAAA,EAClF,OAAO,EAAE,UAAU,WAAW,OAAO,wBAAwB,WAAW,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY/E,QAAQ,EAAE,UAAU,WAAW,OAAO,yBAAyB,WAAW,OAAO;AAAA;AAAA;AAAA,EAGjF,OAAO,EAAE,UAAU,UAAU,OAAO,yBAAyB,WAAW,OAAO;AAAA;AAAA,EAE/E,WAAW,EAAE,UAAU,UAAU,OAAO,0BAA0B,WAAW,OAAO;AACtF;AAuBO,SAAS,uBACd,MACA,UACA,OACA,gBACM;AACN,MAAI,CAAC,UAAU,YAAY,SAAS,UAAU,OAAW;AACzD,MAAI,SAAS,aAAa,KAAK,SAAU;AACzC,MAAI,mBAAmB,UAAa,CAAC,eAAe,SAAS,SAAS,QAAQ,EAAG;AACjF,QAAM,IAAI;AAAA,IACR,qCAAqC,SAAS,QAAQ,WAAW,KAAK,wBAChE,KAAK,KAAK,wBAAwB,KAAK,QAAQ,mDACzB,SAAS,QAAQ,iBAAiB,SAAS,QAAQ;AAAA,EACjF;AACF;AAEO,SAAS,YACd,MACA,UACA,WACA,gBACU;AACV,QAAM,OAAO,YAAY,IAAI,KAAK,iBAAiB,IAAI;AACvD,yBAAuB,MAAM,UAAU,MAAM,cAAc;AAC3D,SAAO,EAAE,GAAG,MAAM,GAAG,SAAS;AAChC;;;AC9DA,IAAM,yBAAyB;AAc/B,IAAM,WAA4B;AAAA;AAAA,EAEhC,EAAE,IAAI,oBAAoB,SAAS,CAAC,OAAO,GAAG,UAAU,aAAa,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA,EAC7H,EAAE,IAAI,qBAAqB,SAAS,CAAC,QAAQ,GAAG,UAAU,aAAa,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA,EAC/H,EAAE,IAAI,mBAAmB,SAAS,CAAC,MAAM,GAAG,UAAU,aAAa,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA,EAC3H,EAAE,IAAI,kBAAkB,SAAS,CAAC,OAAO,GAAG,UAAU,aAAa,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA,EAC3H,EAAE,IAAI,mBAAmB,SAAS,CAAC,QAAQ,GAAG,UAAU,aAAa,WAAW,OAAO,QAAQ,aAAa,MAAM,wBAAwB,QAAQ,UAAU;AAAA;AAAA,EAE5J,EAAE,IAAI,oBAAoB,SAAS,CAAC,cAAc,GAAG,UAAU,UAAU,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA,EACjI,EAAE,IAAI,yBAAyB,SAAS,CAAC,mBAAmB,GAAG,UAAU,UAAU,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA;AAAA,EAE3I,EAAE,IAAI,0BAA0B,SAAS,CAAC,GAAG,UAAU,UAAU,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBzH,EAAE,IAAI,iBAAiB,SAAS,CAAC,GAAG,UAAU,YAAY,WAAW,MAAM,QAAQ,aAAa,MAAM,uHAAkH,QAAQ,UAAU;AAAA,EAC1O,EAAE,IAAI,qBAAqB,SAAS,CAAC,GAAG,UAAU,YAAY,WAAW,MAAM,QAAQ,aAAa,MAAM,kIAA6H,QAAQ,UAAU;AAAA;AAAA,EAEzP,EAAE,IAAI,wBAAwB,SAAS,CAAC,eAAe,GAAG,UAAU,WAAW,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA,EACvI,EAAE,IAAI,yBAAyB,SAAS,CAAC,gBAAgB,GAAG,UAAU,WAAW,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AAAA,EACzI,EAAE,IAAI,wBAAwB,SAAS,CAAC,eAAe,GAAG,UAAU,WAAW,WAAW,MAAM,QAAQ,aAAa,QAAQ,UAAU;AACzI;AAOO,IAAM,uBAAiC,CAAC;AAE/C,SAAS,iBAAiB,SAA2C;AACnE,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,gBAAgB,GAAG;AAC3D,UAAM,QAAQ,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,EAAE,aAAa,KAAK,QAAQ;AAMrF,eAAW,KAAK,SAAS;AACvB,UAAI,MAAM,SAAS,EAAE,QAAQ,SAAS,IAAI,GAAG;AAC3C,6BAAqB,KAAK,GAAG,IAAI,wBAAwB,EAAE,EAAE,KAAK,EAAE,QAAQ,cAAc,IAAI,UAAU,KAAK,KAAK,KAAK,KAAK,QAAQ,GAAG;AACvI,UAAE,UAAU,EAAE,QAAQ,OAAO,CAAC,MAAM,MAAM,IAAI;AAAA,MAChD;AAAA,IACF;AACA,QAAI,SAAS,CAAC,MAAM,QAAQ,SAAS,IAAI,EAAG,OAAM,QAAQ,KAAK,IAAI;AAAA,EACrE;AACA,SAAO;AACT;AACA,iBAAiB,QAAQ;AAKzB,IAAI,UAAU,oBAAI,IAA2B;AAE7C,IAAI,cAAc,oBAAI,IAAoB;AAE1C,SAAS,OAAa;AACpB,YAAU,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,GAAG,GAAG,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAChF,gBAAc,oBAAI,IAAI;AACtB,aAAW,KAAK,SAAU,YAAW,KAAK,EAAE,QAAS,aAAY,IAAI,GAAG,EAAE,EAAE;AAC9E;AACA,KAAK;AAGE,SAAS,gBAAsB;AACpC,OAAK;AACP;AAGO,SAAS,YAAY,WAAkC;AAC5D,MAAI,QAAQ,IAAI,SAAS,EAAG,QAAO;AACnC,SAAO,YAAY,IAAI,SAAS,KAAK;AACvC;AAGO,SAAS,SAAS,WAA8C;AACrE,QAAM,KAAK,YAAY,SAAS;AAChC,SAAO,KAAK,QAAQ,IAAI,EAAE,IAAI;AAChC;AAGO,SAAS,WAAW,UAAkC;AAC3D,QAAM,OAAsB,CAAC;AAC7B,aAAW,KAAK,QAAQ,OAAO,GAAG;AAChC,QAAI,YAAY,EAAE,aAAa,SAAU;AACzC,SAAK,KAAK;AAAA,MACR,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,QAAQ,CAAC;AAAA,MAClB,UAAU,EAAE;AAAA,MACZ,WAAW,EAAE;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,MAAM,EAAE;AAAA,MACR,QAAQ,EAAE;AAAA,IACZ,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGO,SAAS,YAAY,UAA4B;AACtD,SAAO,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;AACrF;AAGO,SAAS,gBAAgB,IAAY,WAAoB,MAAqB;AACnF,QAAM,IAAI,QAAQ,IAAI,EAAE;AACxB,MAAI,CAAC,EAAG;AACR,IAAE,YAAY;AACd,IAAE,SAAS,YAAY,cAAc;AACrC,IAAE,SAAS;AACX,MAAI,SAAS,OAAW,GAAE,OAAO;AAAA,WACxB,UAAW,GAAE,OAAO;AAC/B;;;AChHO,IAAM,wBAAN,cAAoC,MAAM;AAAA,EACtC,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY,WAAmB,MAAe,UAAmB;AAC/D,UAAM,UAAU,SAAS,mBAAmB,OAAO,KAAK,IAAI,MAAM,EAAE,EAAE;AACtE,SAAK,OAAO;AACZ,SAAK,YAAY;AACjB,SAAK,OAAO;AACZ,SAAK,WAAW;AAAA,EAClB;AACF;;;AClCO,SAAS,WAAW,OAA8B,CAAC,GAAkB;AAC1E,SAAO,WAAW,KAAK,QAAQ;AACjC;AAIA,SAAS,YAAY,WAAmB,eAAe,OAAgB;AACrE,QAAM,IAAI,SAAS,SAAS;AAC5B,MAAI,CAAC,EAAG,QAAO,CAAC;AAChB,SAAO,EAAE;AACX;AAWO,SAAS,aAAa,WAAmB,OAAuB,CAAC,GAAkB;AACxF,QAAM,KAAK,YAAY,SAAS,KAAK;AACrC,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,WAAW,KAAK,YAAY,OAAO;AAEzC,MAAI,YAAY,WAAW,KAAK,YAAY,GAAG;AAC7C,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,WAAW;AAAA,MACX;AAAA,MACA,UAAU;AAAA,MACV,QAAQ,OAAO,UAAU;AAAA;AAAA;AAAA;AAAA,MAIzB,GAAI,OAAO,OAAO,EAAE,MAAM,MAAM,KAAK,IAAI,CAAC;AAAA,IAC5C;AAAA,EACF;AAGA,QAAM,QAAQ,KAAK,aAAa,SAAY,CAAC,IAAI,MAAM,QAAQ,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAC,KAAK,QAAQ;AAC9G,aAAW,MAAM,OAAO;AACtB,QAAI,YAAY,IAAI,KAAK,YAAY,GAAG;AACtC,YAAM,OAAO,YAAY,EAAE,KAAK;AAChC,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,WAAW;AAAA,QACX;AAAA,QACA,UAAU;AAAA,QACV,QAAQ,OAAO,UAAU;AAAA,QACzB,QAAQ,OAAO,QAAQ,GAAG,EAAE;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAGA,QAAM,mBAAmB,CAAC,SAAS,KAAK;AACxC,QAAM,SAAS,mBACX,GAAG,EAAE,iGACJ,OAAO,QAAQ,GAAG,EAAE;AACzB,MAAI,KAAK,oBAAoB;AAC3B,UAAM,IAAI,sBAAsB,IAAI,QAAQ,QAAQ;AAAA,EACtD;AACA,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,WAAW;AAAA,IACX;AAAA,IACA,UAAU;AAAA,IACV,QAAQ,OAAO,UAAU;AAAA,IACzB;AAAA,EACF;AACF;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -713,6 +713,19 @@ interface ClassifyInput {
|
|
|
713
713
|
labels: string[];
|
|
714
714
|
tier?: Tier;
|
|
715
715
|
purpose?: string;
|
|
716
|
+
/** What to do when the model's reply contains no JSON at all (F059).
|
|
717
|
+
*
|
|
718
|
+
* `"throw"` (the DEFAULT, and unchanged) is right in product use: a refusal or an
|
|
719
|
+
* outage is not a classification, and a throw is the loudest honest answer.
|
|
720
|
+
*
|
|
721
|
+
* `"value"` is for MEASURING. Requested by trail with the measurement behind it:
|
|
722
|
+
* over 444 golden examples in one batch, a throw at example 212 is not informative,
|
|
723
|
+
* it is destructive — the 232 that were never measured afterwards look like they
|
|
724
|
+
* did not exist. Their alternative was to wrap every call in try/catch and count
|
|
725
|
+
* the throws, which is this field built by hand, worse.
|
|
726
|
+
*
|
|
727
|
+
* Opt-in on purpose: the caller who sets it is the caller who is reading for it. */
|
|
728
|
+
onUnparseable?: "throw" | "value";
|
|
716
729
|
}
|
|
717
730
|
interface ClassifyResult {
|
|
718
731
|
/** The chosen label, or `null` when the model named a label that is not in `labels`
|
|
@@ -728,15 +741,55 @@ interface ClassifyResult {
|
|
|
728
741
|
* an autonomy level off this field — an unclassifiable ticket therefore landed on the
|
|
729
742
|
* tenant's first intent, chosen by the order of a config array, with no error and no
|
|
730
743
|
* log trace. `null` is the whole fix: there is no field to hardcode, because the
|
|
731
|
-
* absence IS the signal.
|
|
744
|
+
* absence IS the signal.
|
|
745
|
+
*
|
|
746
|
+
* **Expect `null` routinely since F060 (23 September 2026)** — the prompt now
|
|
747
|
+
* explicitly lets the model say none of the labels fit. Before that it could not,
|
|
748
|
+
* and trail measured the cost: 34 of 38 honest refusals came back as confident
|
|
749
|
+
* WRONG labels. How OFTEN `null` now occurs under this exact prompt is not yet
|
|
750
|
+
* measured (F060.2) — expect it to be common, not rare. If you route anything
|
|
751
|
+
* automatic off this field, send `null` to a human. */
|
|
732
752
|
label: string | null;
|
|
733
753
|
/** The model's own answer when it did not match, so a caller can log or route what
|
|
734
|
-
* actually came back instead of only knowing that something did not.
|
|
754
|
+
* actually came back instead of only knowing that something did not.
|
|
755
|
+
*
|
|
756
|
+
* **It can contain RAW model output** — on the `unparseable` path it always does
|
|
757
|
+
* (the first 200 characters of the reply), and on `out-of-set` it does whenever the
|
|
758
|
+
* reply had no usable `label` string. A model that echoes part of your prompt can
|
|
759
|
+
* therefore put part of YOUR INPUT here. On a path carrying personal or health data,
|
|
760
|
+
* treat this field with the same care as the input before you log it. */
|
|
735
761
|
rawLabel?: string;
|
|
736
762
|
/** `null` when the model reported no confidence. `0` is a REAL confidence and stays
|
|
737
763
|
* `0` — the two used to be the same number, which made the field unusable as a
|
|
738
764
|
* signal even for a caller who wanted to check it. */
|
|
739
765
|
confidence: number | null;
|
|
766
|
+
/** WHICH of the three things happened, as a value you must read rather than a shape
|
|
767
|
+
* you might infer (F059).
|
|
768
|
+
*
|
|
769
|
+
* `"answered"` the model named a label from `labels`; `label` is it.
|
|
770
|
+
* `"out-of-set"` it named something else; `label` is null, `rawLabel` is its answer.
|
|
771
|
+
* `"unparseable"` we could not read the reply. Only reachable with
|
|
772
|
+
* `onUnparseable: "value"` — the default still throws. It covers
|
|
773
|
+
* BOTH ways parsing fails: no JSON in the reply at all, AND JSON
|
|
774
|
+
* that is present but malformed or truncated. Said explicitly
|
|
775
|
+
* because you are going to COUNT this, and a count that quietly
|
|
776
|
+
* includes a category the docs deny is a wrong number that reads
|
|
777
|
+
* as a right one.
|
|
778
|
+
*
|
|
779
|
+
* It is NOT decoration. Once the throw is optional, `out-of-set` and `unparseable`
|
|
780
|
+
* both yield `label: null` with `rawLabel` set, so without this field they cannot be
|
|
781
|
+
* told apart — and telling them apart ("got it wrong" vs "did not answer" vs "could
|
|
782
|
+
* not be read") is the whole reason the flag was asked for.
|
|
783
|
+
*
|
|
784
|
+
* The form is components' argument, not ours: a boolean like `fallbackUsed` can be
|
|
785
|
+
* destructured away as easily as a `confidence` field can be ignored, while a value
|
|
786
|
+
* you must read to proceed cannot. `answered` ⟺ `label !== null`.
|
|
787
|
+
*
|
|
788
|
+
* REQUIRED, not optional — additive for anyone READING a result, a compile fix for
|
|
789
|
+
* anyone CONSTRUCTING one (a test stub or mock of `classify`). Saying it rather than
|
|
790
|
+
* calling the change "purely additive": this package has shipped that exact
|
|
791
|
+
* over-claim before, about `toolCall.arguments`, and it was wrong then. */
|
|
792
|
+
outcome: "answered" | "out-of-set" | "unparseable";
|
|
740
793
|
usage: Usage;
|
|
741
794
|
}
|
|
742
795
|
interface RerankInput {
|
|
@@ -2268,7 +2321,6 @@ interface AiClient {
|
|
|
2268
2321
|
|
|
2269
2322
|
declare function createAI(config?: AiConfig): AiClient;
|
|
2270
2323
|
|
|
2271
|
-
/** Pull the first JSON value out of a model reply (tolerates ```json fences + prose). */
|
|
2272
2324
|
declare function parseJsonLoose(text: string): unknown;
|
|
2273
2325
|
type ChatVision = Pick<AiClient, "chat" | "vision">;
|
|
2274
2326
|
/** F052.2 — resolve the model's answer to one of the CALLER's labels, or to null.
|
|
@@ -2552,8 +2604,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
2552
2604
|
* wires the live adapters. */
|
|
2553
2605
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
2554
2606
|
|
|
2555
|
-
declare const VERSION: "0.
|
|
2556
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
2607
|
+
declare const VERSION: "0.49.0";
|
|
2608
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.49.0";
|
|
2557
2609
|
|
|
2558
2610
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
2559
2611
|
* per-call override.
|
|
@@ -2758,10 +2810,46 @@ interface UpmetricsSinkConfig {
|
|
|
2758
2810
|
complianceMode?: boolean;
|
|
2759
2811
|
/** Injectable fetch for testing; defaults to global fetch. */
|
|
2760
2812
|
fetch?: typeof fetch;
|
|
2761
|
-
/**
|
|
2813
|
+
/** Called when a record is actually LOST (dropped) or REFUSED (rejected) — not on a
|
|
2814
|
+
* transient failure that is still being retried. See F061 below. */
|
|
2762
2815
|
onError?: (err: unknown) => void;
|
|
2763
|
-
|
|
2764
|
-
|
|
2816
|
+
/** Retry transient failures in the background (F061). Default `true`.
|
|
2817
|
+
*
|
|
2818
|
+
* SAFE ONLY IF THIS SINK'S RECEIVER DEDUPLICATES on `tags.idempotencyKey`. That is
|
|
2819
|
+
* a property of your configuration, not of this package: upmetrics does (live since
|
|
2820
|
+
* 2026-09-08, measured by them — two deliveries of one payload → one row). If you
|
|
2821
|
+
* point `baseUrl` at something that does not, a retry trades a loss for a double
|
|
2822
|
+
* count there — set `retry: false`. We do not guess from the hostname: a sink that
|
|
2823
|
+
* takes a baseUrl cannot have its behaviour decided by a name. */
|
|
2824
|
+
retry?: boolean;
|
|
2825
|
+
/** Base backoff in ms (doubles per attempt, capped at 30 s). For tests. */
|
|
2826
|
+
retryBaseMs?: number;
|
|
2827
|
+
}
|
|
2828
|
+
/** What the sink has done with what it was given — "we lost nothing" and "we do not
|
|
2829
|
+
* know whether we lost anything" are different statements, and only a count can make
|
|
2830
|
+
* the first one. In-process: it resets on deploy, so a zero is a claim about uptime,
|
|
2831
|
+
* not about history (trail's caveat, and it is right). */
|
|
2832
|
+
interface UpmetricsSinkStats {
|
|
2833
|
+
/** Accepted by the receiver. */
|
|
2834
|
+
sent: number;
|
|
2835
|
+
/** Retry ATTEMPTS made (not records). */
|
|
2836
|
+
retried: number;
|
|
2837
|
+
/** Given up on: attempts exhausted, or pushed out of a full queue. LOST. */
|
|
2838
|
+
dropped: number;
|
|
2839
|
+
/** Permanently refused by the receiver (4xx other than 408/429). Not retried. */
|
|
2840
|
+
rejected: number;
|
|
2841
|
+
/** Waiting for a retry right now, including one in flight. */
|
|
2842
|
+
queued: number;
|
|
2843
|
+
}
|
|
2844
|
+
interface UpmetricsSink extends CostSink {
|
|
2845
|
+
/** Try everything waiting, once, now. Call before exit in a SHORT-LIVED process
|
|
2846
|
+
* (a script, a serverless function): the retry timer is unref'd so it never holds
|
|
2847
|
+
* a process open, which also means it will not finish on its own before exit.
|
|
2848
|
+
* Whatever still fails stays queued — check `stats().queued` afterwards. */
|
|
2849
|
+
flush(): Promise<void>;
|
|
2850
|
+
stats(): UpmetricsSinkStats;
|
|
2851
|
+
}
|
|
2852
|
+
declare function upmetricsSink(config: UpmetricsSinkConfig): UpmetricsSink;
|
|
2765
2853
|
|
|
2766
2854
|
interface DiscordSinkConfig {
|
|
2767
2855
|
webhookUrl: string;
|
|
@@ -3027,4 +3115,4 @@ interface StreamTransportRequest extends TransportRequest {
|
|
|
3027
3115
|
*/
|
|
3028
3116
|
declare function streamTransport(req: StreamTransportRequest): AsyncIterable<string>;
|
|
3029
3117
|
|
|
3030
|
-
export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AlignedWordTimings, type AzureVoiceInfo, type AzureWordBoundary, type BatchJob, type BatchRequestItem, type BatchResultItem, type BflAdapterConfig, type BflCredits, type BudgetConfig, BudgetExceededError, BudgetGuard, type BudgetStore, CONFIG_DERIVED_HOSTS, type CallOptions, type Capability, type ChatInput, type ChatRequest, type ChatResult, type ChatStreamEvent, type CheckVoiceOptions, type ClassifyInput, type ClassifyResult, type ContentPart, type Contracts, type CostQuery, type CostSink, type CostSummary, type CostSummaryQuery, type CostTimeseriesQuery, DEFAULT_BASE_URLS, DEFAULT_TIER_MAP, type DesignInput, type DesignResult, type DialogueRequest, type DialogueTurn, type DiscordSinkConfig, ELEVENLABS_DANISH_VOICES, type EmbeddingInput, type EmbeddingRequest, type EmbeddingResult, type ExtractInput, type ExtractResult, type FalAdapterConfig, type FixedHostProvider, type HttpResponse, type ImageInput, type ImageRequest, type ImageResult, LOCALLY_PINNED_HOSTS, type LoraWeight, type Message, type MockupInput, type MockupResult, type ModerationInput, type ModerationItem, type ModerationRequest, type ModerationResult, type OcrInput, type OcrPage, type OcrRequest, type OcrResult, type OpenAICompatibleConfig, type PodcastInput, type PodcastResult, type PricingEntry, type ProviderAdapter, type RefreshOptions, type RefreshResult, type Region, type RerankInput, type RerankResult, type Role, type RouteForecast, SDK_TAG, type SqliteBudgetStoreConfig, type SqliteSinkConfig, StreamHttpError, type SubprocessResponse, type Tier, type TierSpec, type Tool, type ToolCall, type TrainStyleInput, type TrainStyleRequest, type TrainStyleResult, type TranscribeInput, type TranscribeRequest, type TranscribeResult, type TranslateInput, type TranslateResult, type Transport, type TransportRequest, type TransportResponse, type TtsInput, type TtsRequest, type UpmetricsCostClientConfig, UpmetricsCostError, type UpmetricsCostRow, type UpmetricsCostSummary, type UpmetricsCostTimeseries, type UpmetricsSinkConfig, type Usage, VERSION, type VideoInput, type VisionInput, type VoiceInfo, type VoiceProvider, type VoiceResolveResult, type VoiceStatus, VoiceUnavailableError, type WordTiming, aiConfigSchema, alignWordTimings, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, chatInputSchema, checkVoice, classifyRegionName, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultBaseUrl, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, listAzureDanishVoices, listVoices, makeContracts, makeOpenAICompatibleAdapter, matchLabel, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, regionOfHost, regionOfProvider, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema, wouldProviderRouteTo, wouldRouteTo };
|
|
3118
|
+
export { AZURE_DANISH_VOICES, AZURE_DANISH_VOICE_LIST, type AiClient, type AiConfig, type AlignedWordTimings, type AzureVoiceInfo, type AzureWordBoundary, type BatchJob, type BatchRequestItem, type BatchResultItem, type BflAdapterConfig, type BflCredits, type BudgetConfig, BudgetExceededError, BudgetGuard, type BudgetStore, CONFIG_DERIVED_HOSTS, type CallOptions, type Capability, type ChatInput, type ChatRequest, type ChatResult, type ChatStreamEvent, type CheckVoiceOptions, type ClassifyInput, type ClassifyResult, type ContentPart, type Contracts, type CostQuery, type CostSink, type CostSummary, type CostSummaryQuery, type CostTimeseriesQuery, DEFAULT_BASE_URLS, DEFAULT_TIER_MAP, type DesignInput, type DesignResult, type DialogueRequest, type DialogueTurn, type DiscordSinkConfig, ELEVENLABS_DANISH_VOICES, type EmbeddingInput, type EmbeddingRequest, type EmbeddingResult, type ExtractInput, type ExtractResult, type FalAdapterConfig, type FixedHostProvider, type HttpResponse, type ImageInput, type ImageRequest, type ImageResult, LOCALLY_PINNED_HOSTS, type LoraWeight, type Message, type MockupInput, type MockupResult, type ModerationInput, type ModerationItem, type ModerationRequest, type ModerationResult, type OcrInput, type OcrPage, type OcrRequest, type OcrResult, type OpenAICompatibleConfig, type PodcastInput, type PodcastResult, type PricingEntry, type ProviderAdapter, type RefreshOptions, type RefreshResult, type Region, type RerankInput, type RerankResult, type Role, type RouteForecast, SDK_TAG, type SqliteBudgetStoreConfig, type SqliteSinkConfig, StreamHttpError, type SubprocessResponse, type Tier, type TierSpec, type Tool, type ToolCall, type TrainStyleInput, type TrainStyleRequest, type TrainStyleResult, type TranscribeInput, type TranscribeRequest, type TranscribeResult, type TranslateInput, type TranslateResult, type Transport, type TransportRequest, type TransportResponse, type TtsInput, type TtsRequest, type UpmetricsCostClientConfig, UpmetricsCostError, type UpmetricsCostRow, type UpmetricsCostSummary, type UpmetricsCostTimeseries, type UpmetricsSink, type UpmetricsSinkConfig, type UpmetricsSinkStats, type Usage, VERSION, type VideoInput, type VisionInput, type VoiceInfo, type VoiceProvider, type VoiceResolveResult, type VoiceStatus, VoiceUnavailableError, type WordTiming, aiConfigSchema, alignWordTimings, anthropicAdapter, anthropicApiAdapter, anthropicSubprocessAdapter, azureAdapter, bflAdapter, bflCredits, chatInputSchema, checkVoice, classifyRegionName, computeCost, createAI, deepinfraAdapter, deeplAdapter, deepseekAdapter, defaultBaseUrl, defaultProviders, discordSink, elevenlabsAdapter, embeddingInputSchema, falAdapter, falStubAdapter, freshUsage, fromProviderToolCall, geminiAdapter, getCostSummary, getPrice, httpTransport, imageInputSchema, listAzureDanishVoices, listVoices, makeContracts, makeOpenAICompatibleAdapter, matchLabel, messageSchema, mistralAdapter, mistralStubAdapter, multiSink, noopSink, openaiAdapter, openaiStubAdapter, openrouterAdapter, parseClaudeCliJson, parseJsonLoose, refreshAvailability, regionOfHost, regionOfProvider, requestyAdapter, resetRefreshState, resetRegistry, resolveAzureVoice, resolveTier, resolveVoice, sqliteBudgetStore, sqliteSink, streamTransport, stubProviders, subprocessTransport, tierSpecSchema, toProviderTools, toolSchema, translateInputSchema, upmetricsCostClient, upmetricsSink, usdFromMicro, vertexAdapter, visionInputSchema, wouldProviderRouteTo, wouldRouteTo };
|