@broberg/ai-sdk 0.35.0 → 0.36.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/dist/{chunk-Y3EMGZFH.js → chunk-ZFWSLSE7.js} +11 -7
- package/dist/chunk-ZFWSLSE7.js.map +1 -0
- package/dist/index.d.ts +52 -23
- package/dist/index.js +91 -29
- package/dist/index.js.map +1 -1
- package/dist/registry.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-Y3EMGZFH.js.map +0 -1
|
@@ -22,14 +22,17 @@ var DEFAULT_TIER_MAP = {
|
|
|
22
22
|
// NOT Anthropic → out of F030 (EU-embedding migration is its own future epic).
|
|
23
23
|
embedding: { provider: "openai", model: "text-embedding-3-small", transport: "http" }
|
|
24
24
|
};
|
|
25
|
+
function assertOverrideProvider(base, override, label, knownProviders) {
|
|
26
|
+
if (!override?.provider || override.model !== void 0) return;
|
|
27
|
+
if (override.provider === base.provider) return;
|
|
28
|
+
if (knownProviders !== void 0 && !knownProviders.includes(override.provider)) return;
|
|
29
|
+
throw new Error(
|
|
30
|
+
`createAI: override sets provider "${override.provider}", but "${label}" resolves to model "${base.model}", which belongs to "${base.provider}". Set a model too, e.g. override: { provider: "${override.provider}", model: "<a ${override.provider} model>" }.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
25
33
|
function resolveTier(tier, override, configMap, knownProviders) {
|
|
26
34
|
const base = configMap?.[tier] ?? DEFAULT_TIER_MAP[tier];
|
|
27
|
-
|
|
28
|
-
if (providerIsReal && override?.provider && override.model === void 0 && override.provider !== base.provider) {
|
|
29
|
-
throw new Error(
|
|
30
|
-
`createAI: override sets provider "${override.provider}", but tier "${tier}" resolves to model "${base.model}", which belongs to "${base.provider}". Set a model too, e.g. override: { provider: "${override.provider}", model: "<a ${override.provider} model>" }.`
|
|
31
|
-
);
|
|
32
|
-
}
|
|
35
|
+
assertOverrideProvider(base, override, tier, knownProviders);
|
|
33
36
|
return { ...base, ...override };
|
|
34
37
|
}
|
|
35
38
|
|
|
@@ -186,6 +189,7 @@ function resolveModel(requested, opts = {}) {
|
|
|
186
189
|
|
|
187
190
|
export {
|
|
188
191
|
DEFAULT_TIER_MAP,
|
|
192
|
+
assertOverrideProvider,
|
|
189
193
|
resolveTier,
|
|
190
194
|
resetRegistry,
|
|
191
195
|
providerIds,
|
|
@@ -194,4 +198,4 @@ export {
|
|
|
194
198
|
listModels,
|
|
195
199
|
resolveModel
|
|
196
200
|
};
|
|
197
|
-
//# sourceMappingURL=chunk-
|
|
201
|
+
//# sourceMappingURL=chunk-ZFWSLSE7.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 // ── 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}\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 };\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":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -579,18 +579,31 @@ declare const messageSchema: z.ZodObject<{
|
|
|
579
579
|
type: "image";
|
|
580
580
|
mimeType?: string | undefined;
|
|
581
581
|
}>]>, "many">]>;
|
|
582
|
-
toolCalls: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
582
|
+
toolCalls: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
|
|
583
583
|
id: z.ZodString;
|
|
584
584
|
name: z.ZodString;
|
|
585
|
-
arguments: z.ZodRecord<z.ZodString, z.ZodUnknown
|
|
585
|
+
arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
586
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
586
587
|
}, "strip", z.ZodTypeAny, {
|
|
587
588
|
id: string;
|
|
588
589
|
name: string;
|
|
589
|
-
|
|
590
|
+
args?: Record<string, unknown> | undefined;
|
|
591
|
+
arguments?: Record<string, unknown> | undefined;
|
|
590
592
|
}, {
|
|
591
593
|
id: string;
|
|
592
594
|
name: string;
|
|
593
|
-
|
|
595
|
+
args?: Record<string, unknown> | undefined;
|
|
596
|
+
arguments?: Record<string, unknown> | undefined;
|
|
597
|
+
}>, {
|
|
598
|
+
id: string;
|
|
599
|
+
name: string;
|
|
600
|
+
args?: Record<string, unknown> | undefined;
|
|
601
|
+
arguments?: Record<string, unknown> | undefined;
|
|
602
|
+
}, {
|
|
603
|
+
id: string;
|
|
604
|
+
name: string;
|
|
605
|
+
args?: Record<string, unknown> | undefined;
|
|
606
|
+
arguments?: Record<string, unknown> | undefined;
|
|
594
607
|
}>, "many">>;
|
|
595
608
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
596
609
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -606,7 +619,8 @@ declare const messageSchema: z.ZodObject<{
|
|
|
606
619
|
toolCalls?: {
|
|
607
620
|
id: string;
|
|
608
621
|
name: string;
|
|
609
|
-
|
|
622
|
+
args?: Record<string, unknown> | undefined;
|
|
623
|
+
arguments?: Record<string, unknown> | undefined;
|
|
610
624
|
}[] | undefined;
|
|
611
625
|
toolCallId?: string | undefined;
|
|
612
626
|
}, {
|
|
@@ -622,7 +636,8 @@ declare const messageSchema: z.ZodObject<{
|
|
|
622
636
|
toolCalls?: {
|
|
623
637
|
id: string;
|
|
624
638
|
name: string;
|
|
625
|
-
|
|
639
|
+
args?: Record<string, unknown> | undefined;
|
|
640
|
+
arguments?: Record<string, unknown> | undefined;
|
|
626
641
|
}[] | undefined;
|
|
627
642
|
toolCallId?: string | undefined;
|
|
628
643
|
}>;
|
|
@@ -689,18 +704,31 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
689
704
|
type: "image";
|
|
690
705
|
mimeType?: string | undefined;
|
|
691
706
|
}>]>, "many">]>;
|
|
692
|
-
toolCalls: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
707
|
+
toolCalls: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodObject<{
|
|
693
708
|
id: z.ZodString;
|
|
694
709
|
name: z.ZodString;
|
|
695
|
-
arguments: z.ZodRecord<z.ZodString, z.ZodUnknown
|
|
710
|
+
arguments: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
711
|
+
args: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
696
712
|
}, "strip", z.ZodTypeAny, {
|
|
697
713
|
id: string;
|
|
698
714
|
name: string;
|
|
699
|
-
|
|
715
|
+
args?: Record<string, unknown> | undefined;
|
|
716
|
+
arguments?: Record<string, unknown> | undefined;
|
|
717
|
+
}, {
|
|
718
|
+
id: string;
|
|
719
|
+
name: string;
|
|
720
|
+
args?: Record<string, unknown> | undefined;
|
|
721
|
+
arguments?: Record<string, unknown> | undefined;
|
|
722
|
+
}>, {
|
|
723
|
+
id: string;
|
|
724
|
+
name: string;
|
|
725
|
+
args?: Record<string, unknown> | undefined;
|
|
726
|
+
arguments?: Record<string, unknown> | undefined;
|
|
700
727
|
}, {
|
|
701
728
|
id: string;
|
|
702
729
|
name: string;
|
|
703
|
-
|
|
730
|
+
args?: Record<string, unknown> | undefined;
|
|
731
|
+
arguments?: Record<string, unknown> | undefined;
|
|
704
732
|
}>, "many">>;
|
|
705
733
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
706
734
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -716,7 +744,8 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
716
744
|
toolCalls?: {
|
|
717
745
|
id: string;
|
|
718
746
|
name: string;
|
|
719
|
-
|
|
747
|
+
args?: Record<string, unknown> | undefined;
|
|
748
|
+
arguments?: Record<string, unknown> | undefined;
|
|
720
749
|
}[] | undefined;
|
|
721
750
|
toolCallId?: string | undefined;
|
|
722
751
|
}, {
|
|
@@ -732,7 +761,8 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
732
761
|
toolCalls?: {
|
|
733
762
|
id: string;
|
|
734
763
|
name: string;
|
|
735
|
-
|
|
764
|
+
args?: Record<string, unknown> | undefined;
|
|
765
|
+
arguments?: Record<string, unknown> | undefined;
|
|
736
766
|
}[] | undefined;
|
|
737
767
|
toolCallId?: string | undefined;
|
|
738
768
|
}>, "many">>;
|
|
@@ -771,7 +801,8 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
771
801
|
toolCalls?: {
|
|
772
802
|
id: string;
|
|
773
803
|
name: string;
|
|
774
|
-
|
|
804
|
+
args?: Record<string, unknown> | undefined;
|
|
805
|
+
arguments?: Record<string, unknown> | undefined;
|
|
775
806
|
}[] | undefined;
|
|
776
807
|
toolCallId?: string | undefined;
|
|
777
808
|
}[] | undefined;
|
|
@@ -814,7 +845,8 @@ declare const chatInputSchema: z.ZodObject<{
|
|
|
814
845
|
toolCalls?: {
|
|
815
846
|
id: string;
|
|
816
847
|
name: string;
|
|
817
|
-
|
|
848
|
+
args?: Record<string, unknown> | undefined;
|
|
849
|
+
arguments?: Record<string, unknown> | undefined;
|
|
818
850
|
}[] | undefined;
|
|
819
851
|
toolCallId?: string | undefined;
|
|
820
852
|
}[] | undefined;
|
|
@@ -2146,8 +2178,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
2146
2178
|
* wires the live adapters. */
|
|
2147
2179
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
2148
2180
|
|
|
2149
|
-
declare const VERSION: "0.
|
|
2150
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
2181
|
+
declare const VERSION: "0.36.0";
|
|
2182
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.36.0";
|
|
2151
2183
|
|
|
2152
2184
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
2153
2185
|
* per-call override.
|
|
@@ -2161,13 +2193,6 @@ declare const SDK_TAG: "@broberg/ai-sdk@0.35.0";
|
|
|
2161
2193
|
* (`provider:"deepseek"`), never a default. Magistral (reasoning) / mistral-large
|
|
2162
2194
|
* for vision are per-call overrides, not defaults (don't pay the premium on all). */
|
|
2163
2195
|
declare const DEFAULT_TIER_MAP: Record<Tier, TierSpec>;
|
|
2164
|
-
/**
|
|
2165
|
-
* Resolve a Tier to a concrete TierSpec.
|
|
2166
|
-
*
|
|
2167
|
-
* Merge order (later wins): DEFAULT_TIER_MAP < configMap < override.
|
|
2168
|
-
* - `configMap` is the client-level AiConfig.defaults (per-tier full specs).
|
|
2169
|
-
* - `override` is a per-call Partial<TierSpec> — only the fields it sets win.
|
|
2170
|
-
*/
|
|
2171
2196
|
declare function resolveTier(tier: Tier, override?: Partial<TierSpec>, configMap?: Partial<Record<Tier, TierSpec>>, knownProviders?: readonly string[]): TierSpec;
|
|
2172
2197
|
|
|
2173
2198
|
interface RefreshOptions {
|
|
@@ -2523,6 +2548,10 @@ interface HttpResponse {
|
|
|
2523
2548
|
ok: boolean;
|
|
2524
2549
|
status: number;
|
|
2525
2550
|
json: unknown;
|
|
2551
|
+
/** The raw body as text. `json` is undefined when it would not parse; `text` still
|
|
2552
|
+
* holds what the server actually said, which is the only useful thing to show on a
|
|
2553
|
+
* gateway error. */
|
|
2554
|
+
text: string;
|
|
2526
2555
|
}
|
|
2527
2556
|
/** Subprocess transport result — already normalized from the `claude -p` JSON.
|
|
2528
2557
|
* costUsd is always 0 (Max plan is not a metered charge); subprocess flags it. */
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_TIER_MAP,
|
|
3
3
|
ModelUnavailableError,
|
|
4
|
+
assertOverrideProvider,
|
|
4
5
|
listModels,
|
|
5
6
|
providerIds,
|
|
6
7
|
resetRegistry,
|
|
7
8
|
resolveModel,
|
|
8
9
|
resolveTier,
|
|
9
10
|
setAvailability
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ZFWSLSE7.js";
|
|
11
12
|
import {
|
|
12
13
|
getPrice
|
|
13
14
|
} from "./chunk-TENEIW7I.js";
|
|
@@ -23,10 +24,17 @@ async function httpTransport(req) {
|
|
|
23
24
|
headers,
|
|
24
25
|
body: body === void 0 ? void 0 : typeof body === "string" ? body : JSON.stringify(body)
|
|
25
26
|
});
|
|
26
|
-
const
|
|
27
|
-
|
|
27
|
+
const text = await res.text().catch(() => "");
|
|
28
|
+
let json;
|
|
29
|
+
try {
|
|
30
|
+
json = text ? JSON.parse(text) : void 0;
|
|
31
|
+
} catch {
|
|
32
|
+
json = void 0;
|
|
33
|
+
}
|
|
34
|
+
return { ok: res.ok, status: res.status, json, text };
|
|
28
35
|
}
|
|
29
|
-
function errorBody(json,
|
|
36
|
+
function errorBody(json, rawText) {
|
|
37
|
+
const fallback = rawText && rawText.trim() ? rawText.slice(0, 300) : "(no body)";
|
|
30
38
|
if (json === void 0 || json === null) return fallback;
|
|
31
39
|
if (typeof json === "string") return json.slice(0, 300);
|
|
32
40
|
try {
|
|
@@ -216,11 +224,36 @@ function toolCallArgs(tc) {
|
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
// src/cost/region.ts
|
|
227
|
+
var HOST_REGION = {
|
|
228
|
+
"api.mistral.ai": "eu",
|
|
229
|
+
"api.deepl.com": "eu",
|
|
230
|
+
"api-free.deepl.com": "eu",
|
|
231
|
+
"api.eu.bfl.ai": "eu",
|
|
232
|
+
"api.openai.com": "us",
|
|
233
|
+
"api.anthropic.com": "us",
|
|
234
|
+
"api.deepinfra.com": "us",
|
|
235
|
+
"generativelanguage.googleapis.com": "us",
|
|
236
|
+
"api.elevenlabs.io": "us",
|
|
237
|
+
"fal.run": "us",
|
|
238
|
+
"queue.fal.run": "us",
|
|
239
|
+
"api.deepseek.com": "cn",
|
|
240
|
+
// Aggregators: the host is theirs, the upstream is not ours to know.
|
|
241
|
+
"openrouter.ai": "unknown",
|
|
242
|
+
"router.requesty.ai": "unknown"
|
|
243
|
+
};
|
|
244
|
+
function regionOfHost(url) {
|
|
245
|
+
if (!url) return "unknown";
|
|
246
|
+
try {
|
|
247
|
+
const host = new URL(url).host.toLowerCase();
|
|
248
|
+
return Object.hasOwn(HOST_REGION, host) ? HOST_REGION[host] : "unknown";
|
|
249
|
+
} catch {
|
|
250
|
+
return "unknown";
|
|
251
|
+
}
|
|
252
|
+
}
|
|
219
253
|
var FIXED_PROVIDER_REGION = {
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
deepl: "eu",
|
|
254
|
+
// NB: mistral and deepl are deliberately ABSENT — both take a config.baseUrl, so
|
|
255
|
+
// their region is a property of the URL, not of the name. They derive via
|
|
256
|
+
// regionOfHost. Anything left here genuinely cannot be moved by config.
|
|
224
257
|
openai: "us",
|
|
225
258
|
anthropic: "us",
|
|
226
259
|
// generativelanguage.googleapis.com is Google's GLOBAL endpoint, not a US-pinned one.
|
|
@@ -277,7 +310,7 @@ function classifyRegionName(name) {
|
|
|
277
310
|
return "unknown";
|
|
278
311
|
}
|
|
279
312
|
function regionOfProvider(provider) {
|
|
280
|
-
return FIXED_PROVIDER_REGION[provider]
|
|
313
|
+
return Object.hasOwn(FIXED_PROVIDER_REGION, provider) ? FIXED_PROVIDER_REGION[provider] : "unknown";
|
|
281
314
|
}
|
|
282
315
|
|
|
283
316
|
// src/cost/usage.ts
|
|
@@ -418,7 +451,7 @@ function anthropicAdapter(config = {}) {
|
|
|
418
451
|
body
|
|
419
452
|
}
|
|
420
453
|
});
|
|
421
|
-
if (!res.ok) throw new Error(`anthropic ${res.status}: ${errorBody(res.json)}`);
|
|
454
|
+
if (!res.ok) throw new Error(`anthropic ${res.status}: ${errorBody(res.json, res.text)}`);
|
|
422
455
|
const data = res.json;
|
|
423
456
|
const blocks = data.content ?? [];
|
|
424
457
|
const text = blocks.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("");
|
|
@@ -633,7 +666,7 @@ function makeOpenAICompatibleAdapter(config) {
|
|
|
633
666
|
}
|
|
634
667
|
});
|
|
635
668
|
if (!res.ok) {
|
|
636
|
-
throw new Error(`${config.name} ${res.status}: ${errorBody(res.json)}`);
|
|
669
|
+
throw new Error(`${config.name} ${res.status}: ${errorBody(res.json, res.text)}`);
|
|
637
670
|
}
|
|
638
671
|
const data = res.json;
|
|
639
672
|
const msg = data.choices?.[0]?.message;
|
|
@@ -645,6 +678,9 @@ function makeOpenAICompatibleAdapter(config) {
|
|
|
645
678
|
const usage = freshUsage({
|
|
646
679
|
provider: config.name,
|
|
647
680
|
model: req.spec.model,
|
|
681
|
+
// From the URL we actually called, not from config.name — mistral and deepseek
|
|
682
|
+
// both take a baseUrl, and a custom gateway must not inherit their EU/CN claim.
|
|
683
|
+
region: regionOfHost(config.baseUrl),
|
|
648
684
|
transport: "http",
|
|
649
685
|
capability: "chat",
|
|
650
686
|
// prompt_tokens INCLUDES the cached ones; computeCost adds cacheReadTokens on
|
|
@@ -714,6 +750,7 @@ function makeOpenAICompatibleAdapter(config) {
|
|
|
714
750
|
const usage = freshUsage({
|
|
715
751
|
provider: config.name,
|
|
716
752
|
model: req.spec.model,
|
|
753
|
+
region: regionOfHost(config.baseUrl),
|
|
717
754
|
transport: "http",
|
|
718
755
|
capability: "chat",
|
|
719
756
|
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
@@ -787,7 +824,7 @@ function openaiAdapter(config = {}) {
|
|
|
787
824
|
}
|
|
788
825
|
});
|
|
789
826
|
if (!res.ok) {
|
|
790
|
-
throw new Error(`openai ${res.status}: ${errorBody(res.json)}`);
|
|
827
|
+
throw new Error(`openai ${res.status}: ${errorBody(res.json, res.text)}`);
|
|
791
828
|
}
|
|
792
829
|
const data = res.json;
|
|
793
830
|
const vectors = (data.data ?? []).map((d) => d.embedding);
|
|
@@ -952,7 +989,7 @@ function geminiAdapter(config = {}) {
|
|
|
952
989
|
}
|
|
953
990
|
});
|
|
954
991
|
if (!res.ok) {
|
|
955
|
-
throw new Error(`gemini ${res.status}: ${errorBody(res.json)}`);
|
|
992
|
+
throw new Error(`gemini ${res.status}: ${errorBody(res.json, res.text)}`);
|
|
956
993
|
}
|
|
957
994
|
const data = res.json;
|
|
958
995
|
const parts = data.candidates?.[0]?.content?.parts ?? [];
|
|
@@ -1102,7 +1139,7 @@ function geminiAdapter(config = {}) {
|
|
|
1102
1139
|
if (opData.error) throw new Error(`gemini animate: ${opData.error.message ?? "operation error"}`);
|
|
1103
1140
|
if (opData.done) {
|
|
1104
1141
|
videoUri = opData.response?.generateVideoResponse?.generatedSamples?.[0]?.video?.uri;
|
|
1105
|
-
if (!videoUri) throw new Error(`gemini animate: done but no video uri: ${
|
|
1142
|
+
if (!videoUri) throw new Error(`gemini animate: done but no video uri: ${errorBody(opData.response)}`);
|
|
1106
1143
|
break;
|
|
1107
1144
|
}
|
|
1108
1145
|
if (Date.now() >= deadline) throw new Error("gemini animate: timed out");
|
|
@@ -1546,6 +1583,12 @@ function azureAdapter(config = {}) {
|
|
|
1546
1583
|
function region() {
|
|
1547
1584
|
return config.region ?? process.env.AZURE_SPEECH_REGION ?? DEFAULT_REGION;
|
|
1548
1585
|
}
|
|
1586
|
+
function sttRegion() {
|
|
1587
|
+
if (config.sttBaseUrl || config.resource || process.env.AZURE_SPEECH_RESOURCE) {
|
|
1588
|
+
return config.region ?? process.env.AZURE_SPEECH_REGION ? classifyRegionName(region()) : "unknown";
|
|
1589
|
+
}
|
|
1590
|
+
return classifyRegionName(region());
|
|
1591
|
+
}
|
|
1549
1592
|
function sttBaseUrl() {
|
|
1550
1593
|
if (config.sttBaseUrl) return config.sttBaseUrl.replace(/\/$/, "");
|
|
1551
1594
|
const resource = config.resource ?? process.env.AZURE_SPEECH_RESOURCE;
|
|
@@ -1619,7 +1662,7 @@ function azureAdapter(config = {}) {
|
|
|
1619
1662
|
const usage = freshUsage({
|
|
1620
1663
|
provider: "azure",
|
|
1621
1664
|
model: req.spec.model,
|
|
1622
|
-
region:
|
|
1665
|
+
region: sttRegion(),
|
|
1623
1666
|
transport: "http",
|
|
1624
1667
|
capability: "transcribe",
|
|
1625
1668
|
inputTokens: 0,
|
|
@@ -1773,7 +1816,7 @@ function vertexAdapter(config = {}) {
|
|
|
1773
1816
|
if (opData.done) {
|
|
1774
1817
|
const video = opData.response?.videos?.[0];
|
|
1775
1818
|
if (!video) {
|
|
1776
|
-
throw new Error(`vertex animate: done but no video in response: ${
|
|
1819
|
+
throw new Error(`vertex animate: done but no video in response: ${errorBody(opData.response)}`);
|
|
1777
1820
|
}
|
|
1778
1821
|
if (!video.bytesBase64Encoded) {
|
|
1779
1822
|
if (video.gcsUri) {
|
|
@@ -1781,7 +1824,7 @@ function vertexAdapter(config = {}) {
|
|
|
1781
1824
|
`vertex animate: response returned a gcsUri ("${video.gcsUri}") \u2014 GCS download not yet supported (F031.x); this build only handles inline bytes`
|
|
1782
1825
|
);
|
|
1783
1826
|
}
|
|
1784
|
-
throw new Error(`vertex animate: done but no bytesBase64Encoded in response: ${
|
|
1827
|
+
throw new Error(`vertex animate: done but no bytesBase64Encoded in response: ${errorBody(opData.response)}`);
|
|
1785
1828
|
}
|
|
1786
1829
|
videoB64 = video.bytesBase64Encoded;
|
|
1787
1830
|
videoMime = video.mimeType ?? "video/mp4";
|
|
@@ -1884,6 +1927,7 @@ function deeplAdapter(config = {}) {
|
|
|
1884
1927
|
if (text === void 0) throw new Error("deepl translate: response contained no translation");
|
|
1885
1928
|
const usage = freshUsage({
|
|
1886
1929
|
provider: "deepl",
|
|
1930
|
+
region: regionOfHost(baseUrl(apiKey)),
|
|
1887
1931
|
model: req.spec.model,
|
|
1888
1932
|
transport: "http",
|
|
1889
1933
|
capability: "translate",
|
|
@@ -2720,7 +2764,11 @@ var toolSchema = z.object({
|
|
|
2720
2764
|
var toolCallSchema = z.object({
|
|
2721
2765
|
id: z.string(),
|
|
2722
2766
|
name: z.string(),
|
|
2723
|
-
arguments: z.record(z.unknown())
|
|
2767
|
+
arguments: z.record(z.unknown()).optional(),
|
|
2768
|
+
args: z.record(z.unknown()).optional()
|
|
2769
|
+
}).refine((tc) => tc.arguments !== void 0 || tc.args !== void 0, {
|
|
2770
|
+
message: "tool call needs `arguments` (canonical) or `args` (@broberg/chat's spelling)",
|
|
2771
|
+
path: ["arguments"]
|
|
2724
2772
|
});
|
|
2725
2773
|
var contentPartSchema = z.union([
|
|
2726
2774
|
z.object({ type: z.literal("text"), text: z.string() }),
|
|
@@ -2918,8 +2966,8 @@ var aiConfigSchema = z.object({
|
|
|
2918
2966
|
});
|
|
2919
2967
|
|
|
2920
2968
|
// src/version.ts
|
|
2921
|
-
var VERSION = "0.
|
|
2922
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
2969
|
+
var VERSION = "0.36.0";
|
|
2970
|
+
var SDK_TAG = "@broberg/ai-sdk@0.36.0";
|
|
2923
2971
|
|
|
2924
2972
|
// src/cost/sinks/upmetrics.ts
|
|
2925
2973
|
function upmetricsSink(config) {
|
|
@@ -2955,6 +3003,10 @@ function upmetricsSink(config) {
|
|
|
2955
3003
|
...usage.labels,
|
|
2956
3004
|
capability: usage.capability,
|
|
2957
3005
|
transport: usage.transport,
|
|
3006
|
+
// F042: data residency of the route that answered. Rides in tags like
|
|
3007
|
+
// capability/transport — no ingest-schema change, and without it the one
|
|
3008
|
+
// field built for auditability existed only in memory.
|
|
3009
|
+
region: usage.region,
|
|
2958
3010
|
sdk: SDK_TAG
|
|
2959
3011
|
}
|
|
2960
3012
|
};
|
|
@@ -3050,8 +3102,12 @@ function createAI(config = {}) {
|
|
|
3050
3102
|
if (budget) await budget.record(usage.costUsd);
|
|
3051
3103
|
}
|
|
3052
3104
|
const providerNames = Object.keys(providers);
|
|
3105
|
+
function withOverride(base, override, label) {
|
|
3106
|
+
assertOverrideProvider(base, override, label, providerNames);
|
|
3107
|
+
return { ...base, ...override };
|
|
3108
|
+
}
|
|
3053
3109
|
function pickProvider(name) {
|
|
3054
|
-
const adapter = providers[name];
|
|
3110
|
+
const adapter = Object.hasOwn(providers, name) ? providers[name] : void 0;
|
|
3055
3111
|
if (!adapter) {
|
|
3056
3112
|
throw new Error(
|
|
3057
3113
|
`createAI: no provider adapter registered for "${name}". Registered: ${Object.keys(providers).join(", ") || "(none)"}`
|
|
@@ -3288,7 +3344,7 @@ function createAI(config = {}) {
|
|
|
3288
3344
|
];
|
|
3289
3345
|
const base = input.referenceImages?.length ? DEFAULT_BFL_REFERENCE_SPEC : input.finetune ? DEFAULT_BFL_FINETUNE_SPEC : loras.length > 0 ? DEFAULT_LORA_IMAGE_SPEC : DEFAULT_IMAGE_SPEC;
|
|
3290
3346
|
return runCapability({
|
|
3291
|
-
primary:
|
|
3347
|
+
primary: withOverride(base, input.override, "image"),
|
|
3292
3348
|
fallback: input.fallback,
|
|
3293
3349
|
capability: "image",
|
|
3294
3350
|
purpose: input.purpose,
|
|
@@ -3320,7 +3376,7 @@ function createAI(config = {}) {
|
|
|
3320
3376
|
input = animateInputSchema.parse(input);
|
|
3321
3377
|
const prompt = input.prompt?.trim() ? `${input.prompt.trim()} ${ANIMATE_AUDIO_DIRECTIVE}` : ANIMATE_AUDIO_DIRECTIVE;
|
|
3322
3378
|
return runCapability({
|
|
3323
|
-
primary:
|
|
3379
|
+
primary: withOverride(DEFAULT_ANIMATE_SPEC, input.override, "animate"),
|
|
3324
3380
|
fallback: input.fallback,
|
|
3325
3381
|
capability: "animate",
|
|
3326
3382
|
purpose: input.purpose,
|
|
@@ -3344,7 +3400,7 @@ function createAI(config = {}) {
|
|
|
3344
3400
|
async trainStyle(input) {
|
|
3345
3401
|
input = trainStyleInputSchema.parse(input);
|
|
3346
3402
|
return runCapability({
|
|
3347
|
-
primary:
|
|
3403
|
+
primary: withOverride(DEFAULT_TRAINSTYLE_SPEC, input.override, "trainStyle"),
|
|
3348
3404
|
fallback: input.fallback,
|
|
3349
3405
|
capability: "trainStyle",
|
|
3350
3406
|
purpose: input.purpose,
|
|
@@ -3370,7 +3426,7 @@ function createAI(config = {}) {
|
|
|
3370
3426
|
async ocr(input) {
|
|
3371
3427
|
input = ocrInputSchema.parse(input);
|
|
3372
3428
|
return runCapability({
|
|
3373
|
-
primary:
|
|
3429
|
+
primary: withOverride(DEFAULT_OCR_SPEC, input.override, "ocr"),
|
|
3374
3430
|
fallback: input.fallback,
|
|
3375
3431
|
capability: "ocr",
|
|
3376
3432
|
purpose: input.purpose,
|
|
@@ -3389,7 +3445,7 @@ function createAI(config = {}) {
|
|
|
3389
3445
|
input = moderationInputSchema.parse(input);
|
|
3390
3446
|
const items = Array.isArray(input.input) ? input.input : [input.input];
|
|
3391
3447
|
return runCapability({
|
|
3392
|
-
primary:
|
|
3448
|
+
primary: withOverride(DEFAULT_MODERATION_SPEC, input.override, "moderate"),
|
|
3393
3449
|
fallback: input.fallback,
|
|
3394
3450
|
capability: "moderation",
|
|
3395
3451
|
purpose: input.purpose,
|
|
@@ -3489,7 +3545,7 @@ function createAI(config = {}) {
|
|
|
3489
3545
|
},
|
|
3490
3546
|
batch: {
|
|
3491
3547
|
async submit(input) {
|
|
3492
|
-
const spec =
|
|
3548
|
+
const spec = withOverride(DEFAULT_BATCH_SPEC, input.override, "batch");
|
|
3493
3549
|
const adapter = pickProvider(spec.provider);
|
|
3494
3550
|
if (!adapter.batchSubmit) throw new Error(`createAI: provider "${spec.provider}" does not support batch`);
|
|
3495
3551
|
return adapter.batchSubmit({ items: input.requests, spec });
|
|
@@ -3752,6 +3808,7 @@ CREATE TABLE IF NOT EXISTS ai_usage (
|
|
|
3752
3808
|
tier TEXT,
|
|
3753
3809
|
transport TEXT NOT NULL,
|
|
3754
3810
|
capability TEXT NOT NULL,
|
|
3811
|
+
region TEXT NOT NULL DEFAULT 'unknown',
|
|
3755
3812
|
purpose TEXT,
|
|
3756
3813
|
input_tokens INTEGER NOT NULL,
|
|
3757
3814
|
output_tokens INTEGER NOT NULL,
|
|
@@ -3770,12 +3827,16 @@ function sqliteSink(config) {
|
|
|
3770
3827
|
const init = async () => {
|
|
3771
3828
|
const db = await openDb(config.dbPath);
|
|
3772
3829
|
db.run(CREATE_TABLE);
|
|
3830
|
+
const cols = db.query(`PRAGMA table_info(ai_usage)`).all();
|
|
3831
|
+
if (!cols.some((c) => c.name === "region")) {
|
|
3832
|
+
db.run(`ALTER TABLE ai_usage ADD COLUMN region TEXT NOT NULL DEFAULT 'unknown'`);
|
|
3833
|
+
}
|
|
3773
3834
|
const insert = db.prepare(
|
|
3774
3835
|
`INSERT INTO ai_usage
|
|
3775
|
-
(ts, provider, model, tier, transport, capability, purpose,
|
|
3836
|
+
(ts, provider, model, tier, transport, capability, region, purpose,
|
|
3776
3837
|
input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens,
|
|
3777
3838
|
cost_usd, latency_ms, subprocess)
|
|
3778
|
-
VALUES ($ts, $provider, $model, $tier, $transport, $capability, $purpose,
|
|
3839
|
+
VALUES ($ts, $provider, $model, $tier, $transport, $capability, $region, $purpose,
|
|
3779
3840
|
$input, $output, $cacheRead, $cacheCreation, $cost, $latency, $subprocess)`
|
|
3780
3841
|
);
|
|
3781
3842
|
return insert;
|
|
@@ -3790,6 +3851,7 @@ function sqliteSink(config) {
|
|
|
3790
3851
|
$tier: usage.tier ?? null,
|
|
3791
3852
|
$transport: usage.transport,
|
|
3792
3853
|
$capability: usage.capability,
|
|
3854
|
+
$region: usage.region,
|
|
3793
3855
|
$purpose: usage.purpose ?? null,
|
|
3794
3856
|
$input: usage.inputTokens,
|
|
3795
3857
|
$output: usage.outputTokens,
|