@broberg/ai-sdk 0.63.1 → 0.64.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FPW2WGJS.js → chunk-JMRWFOAC.js} +10 -1
- package/dist/chunk-JMRWFOAC.js.map +1 -0
- package/dist/{chunk-2BNVXTGO.js → chunk-Q2PPQ5WT.js} +4 -1
- package/dist/chunk-Q2PPQ5WT.js.map +1 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.js +119 -16
- package/dist/index.js.map +1 -1
- package/dist/pricing.js +1 -1
- package/dist/{registry-BEyz-O5V.d.ts → registry-NdNUaIv4.d.ts} +6 -2
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-2BNVXTGO.js.map +0 -1
- package/dist/chunk-FPW2WGJS.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/routing/tier-map.ts","../src/availability/registry.ts","../src/availability/types.ts","../src/availability/resolve.ts","../src/routing/describe-tier.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). */\n//\n// F077 — PINNED, never \"-latest\" (Christian 2026-10-08: «vi skal ingen steder i flåden\n// tilbyde -latest som default, for så ender vi med at bruge v4 om nogle dage, og\n// regningen følger pludselig efter med en stor overraskelse»). Mistral moves its\n// -latest names on its own schedule (three moved on 2026-10-08 alone), and echoes the\n// alias back so the move is invisible in usage.model. A dated id changes only in a\n// release of ours, with a changelog line and the price difference. The ids below are\n// exactly what each -latest meant on 2026-10-08 — same model, same price.\nexport const DEFAULT_TIER_MAP: Record<Tier, TierSpec> = {\n fast: { provider: \"mistral\", model: \"mistral-small-2603\", transport: \"http\" }, // Small 4\n smart: { provider: \"mistral\", model: \"mistral-large-2512\", transport: \"http\" }, // Large 3\n powerful: { provider: \"mistral\", model: \"mistral-large-2512\", transport: \"http\" }, // Large 3\n cheap: { provider: \"mistral\", model: \"mistral-small-2603\", transport: \"http\" }, // Small 4\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-2604\", transport: \"http\" }, // Medium 3.5 (was medium-latest, F077)\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, ModelGrade, ModelKind, 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 /** F022.6. Omitted = \"chat\" (most rows); set explicitly for anything else. */\n kind?: ModelKind;\n default?: true;\n grade?: ModelGrade;\n}\n\n/** A `-latest` id is a moving alias — the provider can swap the model behind it.\n * Derived, not hand-written, so a new -latest row cannot forget the flag. */\nexport const isFloatingId = (id: string): boolean => /-latest$/.test(id);\n\n/** F079 — routes that float without saying so in the id: the provider serves only a\n * moving `latest` and has no dated version to pin. Measured 20/10 2026 on BFL's\n * openapi.json: FLUX 3 Video's `version` is the const \"latest\" (\"dated pinnable\n * release tags are added here as they are published\"). Remove a row when it can pin. */\nexport const LATEST_ONLY_ROUTES: ReadonlySet<string> = new Set([\"bfl:flux-3-video\"]);\n\n/** Does this route float — a `-latest` id, or a provider that only serves latest? */\nexport const isFloatingRoute = (provider: string, model: string): boolean =>\n isFloatingId(model) || LATEST_ONLY_ROUTES.has(`${provider}:${model}`);\n\nconst NO_OPENAI_SNAPSHOT = \"no dated snapshot published (measured 2026-10-11) — the id may start pointing at a newer model\";\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 // F022.6 — the live Claude 5 family, measured with GET /v1/models on 2026-10-11.\n // These ids have no dated snapshot: the id IS the pin. The older rows above stay,\n // because removing one would turn a passing requireKnown gate into a refusal.\n { id: \"claude-haiku-5-5\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", source: \"default\", grade: \"cheap\" },\n { id: \"claude-sonnet-5-5\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", source: \"default\", grade: \"standard\", default: true },\n { id: \"claude-opus-5-5\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", source: \"default\", grade: \"strong\" },\n { id: \"claude-fable-5-1\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-opus-5\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-sonnet-5\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-opus-4-7\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", source: \"default\" },\n { id: \"claude-opus-4-6\", aliases: [], provider: \"anthropic\", available: true, status: \"available\", 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\", kind: \"embedding\" },\n // F022.6 — chat models, measured with GET /v1/models on 2026-10-11 and priced from\n // developers.openai.com/api/docs/pricing the same day. The cheap/standard/strong\n // picks are DATED snapshots, so a picker's default cannot move under anyone\n // (D-385d97).\n { id: \"gpt-5.4-nano-2026-03-17\", aliases: [], provider: \"openai\", available: true, status: \"available\", source: \"default\", grade: \"cheap\" },\n { id: \"gpt-5.4-mini-2026-03-17\", aliases: [], provider: \"openai\", available: true, status: \"available\", source: \"default\", grade: \"standard\", default: true },\n { id: \"gpt-5.5-2026-04-23\", aliases: [], provider: \"openai\", available: true, status: \"available\", source: \"default\", grade: \"strong\" },\n // OpenAI's newest families had NO dated snapshot in /v1/models on 2026-10-11. They\n // are offered, but not as a default: we cannot show that the id will keep meaning\n // the same model.\n ...[\"gpt-6-luna\", \"gpt-6-sol\", \"gpt-6.1-sol\", \"gpt-6-astra\", \"gpt-5.6-luna\", \"gpt-5.6-terra\", \"gpt-5.6-sol\"].map(\n (id): RegistryEntry => ({ id, aliases: [], provider: \"openai\", available: true, status: \"available\", source: \"default\", note: NO_OPENAI_SNAPSHOT }),\n ),\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 // ── TypeSafe (US-hosted — NOT for personal data) ─────────────────────────\n //\n // F066. Two rows, not an alias. TypeSafe's \"jev-latest\" points at jev-1.13.0 TODAY and\n // moves on their next release; writing it here as an alias of 1.13.0 would make this\n // registry assert a mapping we would have to remember to undo. As separate rows, both\n // pass a requireKnown gate and neither claims to be the other.\n { id: \"jev-1.13.0\", aliases: [\"jev\"], provider: \"typesafe\", available: true, status: \"available\", note: \"TypeSafe Jev, pinned version. US-hosted — not for personal data. Decision model: use ai.judge, not ai.chat\", source: \"default\", kind: \"judge\" },\n { id: \"jev-latest\", aliases: [], provider: \"typesafe\", available: true, status: \"available\", note: \"MOVING alias (jev-1.13.0 as of 2026-10-01). Pin jev-1.13.0 if you tuned thresholds on confidence. US-hosted — not for personal data\", source: \"default\", kind: \"judge\" },\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 // F077 — the dated ids the tiers are pinned to. The -latest rows above stay: callers\n // still pass them, and an unknown id would fail a requireKnown gate.\n // F022.6 — default = what the `smart` tier calls (a test holds them together).\n { id: \"mistral-large-2512\", aliases: [], provider: \"mistral\", available: true, status: \"available\", source: \"default\", grade: \"standard\", default: true },\n { id: \"mistral-medium-2604\", aliases: [], provider: \"mistral\", available: true, status: \"available\", source: \"default\" },\n { id: \"mistral-small-2603\", aliases: [], provider: \"mistral\", available: true, status: \"available\", source: \"default\", grade: \"cheap\" },\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 kind: e.kind ?? \"chat\",\n ...(e.default ? { default: true as const } : {}),\n ...(e.grade ? { grade: e.grade } : {}),\n ...(isFloatingId(e.id) ? { floating: true as const } : {}),\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/** What a model IS, so a picker can offer only chat models in a chat dropdown (F022.6). */\nexport type ModelKind = \"chat\" | \"embedding\" | \"judge\";\n\n/** The provider's price/quality step, as a picker labels it (F022.6). Not a tier:\n * tiers route calls, grades only describe a row. */\nexport type ModelGrade = \"cheap\" | \"standard\" | \"strong\";\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 /** F022.6 — chat / embedding / judge. */\n kind: ModelKind;\n /** F022.6 — exactly one row per provider carries this: the model a picker\n * pre-selects. Always a pinned (non-floating), priced id. */\n default?: true;\n /** F022.6 — present on the cheap / standard / strong pick for a provider. */\n grade?: ModelGrade;\n /** F022.6 — the id is a moving alias (`-latest`): the provider can swap the\n * model behind it without a deploy. Never the default (D-385d97). */\n floating?: true;\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","// F077.2 — \"which concrete version is this tier right now, and what does it cost?\"\n// Asked by trail for its settings page and by the owner for the fleet: a tier name\n// says nothing about the bill, and resolveModel answers the model but not the price.\nimport type { Tier } from \"../types.js\";\nimport { DEFAULT_TIER_MAP } from \"./tier-map.js\";\nimport { getPrice } from \"../cost/pricing.js\";\n\nexport interface TierDescription {\n tier: Tier;\n provider: string;\n model: string;\n /** false when the model is a \"-latest\" alias the provider can move under us. */\n pinned: boolean;\n /** USD per 1M tokens from the pricing table; undefined when the model is unpriced. */\n inputPer1M?: number;\n outputPer1M?: number;\n cacheReadPer1M?: number;\n}\n\n/** The built-in default for a tier. A client's own `defaults` or a per-call\n * `override` can route elsewhere — read usage.model off the response for that. */\nexport function describeTier(tier: Tier): TierDescription {\n const spec = DEFAULT_TIER_MAP[tier];\n if (!spec) throw new Error(`describeTier: unknown tier \"${tier}\" — valid: ${Object.keys(DEFAULT_TIER_MAP).join(\", \")}`);\n const price = getPrice(spec.provider, spec.model);\n return {\n tier,\n provider: spec.provider,\n model: spec.model,\n pinned: !spec.model.endsWith(\"-latest\"),\n ...(price ? { inputPer1M: price.inputPer1M, outputPer1M: price.outputPer1M, ...(price.cacheReadPer1M !== undefined ? { cacheReadPer1M: price.cacheReadPer1M } : {}) } : {}),\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as CostSink, T as Tier, U as Usage, a as ChatResult, b as ChatStreamEvent, c as TranslateResult, I as ImageResult, A as AnimateResult, d as TrainStyleResult, E as EmbeddingResult, e as TranscribeResult, O as OcrResult, M as ModerationResult, J as JudgeResult, P as PodcastResult, B as BatchRequestItem, f as TierSpec, g as BatchJob, h as BatchResultItem, i as ProviderAdapter, j as ToolCall, k as Tool, l as Transport, m as Capability, R as Region, n as BudgetConfig, o as BudgetStore } from './registry-
|
|
2
|
-
export { p as AlignedWordTimings, q as AvailabilitySource, r as AvailabilityStatus, s as AzureWordBoundary, t as CallOptions, u as ChatRequest, v as ContentPart, D as DialogueRequest, w as DialogueTurn, x as EmbeddingRequest, y as ImageRequest, z as JudgeAnswer, F as JudgeChoiceAnswer, G as JudgeNoulAnswer, H as JudgeQuestion, K as JudgeScoreAnswer, L as LoraWeight, N as Message, Q as ModelStatus, S as ModelUnavailableError, V as ModerationItem, W as ModerationRequest, X as OcrPage, Y as OcrRequest, Z as ResolveOptions, _ as ResolveResult, $ as Role, a0 as TierDescription, a1 as TrainStyleRequest, a2 as TranscribeRequest, a3 as TtsRequest, a4 as WordTiming, a5 as alignWordTimings, a6 as classifyRegionName, a7 as describeTier, a8 as listModels, a9 as regionOfHost, aa as regionOfProvider, ab as resolveModel } from './registry-
|
|
1
|
+
import { C as CostSink, T as Tier, U as Usage, a as ChatResult, b as ChatStreamEvent, c as TranslateResult, I as ImageResult, A as AnimateResult, d as TrainStyleResult, E as EmbeddingResult, e as TranscribeResult, O as OcrResult, M as ModerationResult, J as JudgeResult, P as PodcastResult, B as BatchRequestItem, f as TierSpec, g as BatchJob, h as BatchResultItem, i as ProviderAdapter, j as ToolCall, k as Tool, l as Transport, m as Capability, R as Region, n as BudgetConfig, o as BudgetStore } from './registry-NdNUaIv4.js';
|
|
2
|
+
export { p as AlignedWordTimings, q as AvailabilitySource, r as AvailabilityStatus, s as AzureWordBoundary, t as CallOptions, u as ChatRequest, v as ContentPart, D as DialogueRequest, w as DialogueTurn, x as EmbeddingRequest, y as ImageRequest, z as JudgeAnswer, F as JudgeChoiceAnswer, G as JudgeNoulAnswer, H as JudgeQuestion, K as JudgeScoreAnswer, L as LoraWeight, N as Message, Q as ModelStatus, S as ModelUnavailableError, V as ModerationItem, W as ModerationRequest, X as OcrPage, Y as OcrRequest, Z as ResolveOptions, _ as ResolveResult, $ as Role, a0 as TierDescription, a1 as TrainStyleRequest, a2 as TranscribeRequest, a3 as TtsRequest, a4 as WordTiming, a5 as alignWordTimings, a6 as classifyRegionName, a7 as describeTier, a8 as listModels, a9 as regionOfHost, aa as regionOfProvider, ab as resolveModel } from './registry-NdNUaIv4.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export { DEFAULT_CLIP_SEC, MEDIA_PRICING_CHECKED_AT, MediaPrice, MediaUnit, PRICING_STALE_AFTER_DAYS, PricingFreshness, UnitFreshness, getMediaPrice, listMediaPrices, pricingFreshness, pricingGeneratedAt } from './pricing.js';
|
|
5
5
|
|
|
@@ -992,20 +992,24 @@ declare const animateInputSchema: z.ZodObject<{
|
|
|
992
992
|
allowFloating: z.ZodOptional<z.ZodBoolean>;
|
|
993
993
|
labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
994
994
|
/** Input image: a URL or raw bytes. Where bytes are uploaded is the ADAPTER's
|
|
995
|
-
* business — fal uses fal storage, the Gemini/Veo default inlines them.
|
|
996
|
-
|
|
995
|
+
* business — fal uses fal storage, the Gemini/Veo default inlines them.
|
|
996
|
+
* Omit for text→video (F079.2): only bfl/flux-3-video supports it. */
|
|
997
|
+
image: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodType<Uint8Array<ArrayBuffer>, z.ZodTypeDef, Uint8Array<ArrayBuffer>>]>>;
|
|
997
998
|
/** Motion/scene prompt. */
|
|
998
999
|
prompt: z.ZodOptional<z.ZodString>;
|
|
999
1000
|
/** Clip length in seconds (provider-dependent; Veo ≤ 8s). */
|
|
1000
1001
|
durationSec: z.ZodOptional<z.ZodNumber>;
|
|
1001
1002
|
/** e.g. "720p" / "1080p" (provider-dependent). */
|
|
1002
1003
|
resolution: z.ZodOptional<z.ZodString>;
|
|
1004
|
+
/** F079.6 — false = no audio track at all (bfl/flux-3-video only; other routes refuse). */
|
|
1005
|
+
audio: z.ZodOptional<z.ZodBoolean>;
|
|
1003
1006
|
}, "strip", z.ZodTypeAny, {
|
|
1004
|
-
image
|
|
1007
|
+
image?: string | Uint8Array<ArrayBuffer> | undefined;
|
|
1005
1008
|
purpose?: string | undefined;
|
|
1006
1009
|
labels?: Record<string, string> | undefined;
|
|
1007
1010
|
tier?: "vision" | "video" | "embedding" | "fast" | "smart" | "powerful" | "cheap" | undefined;
|
|
1008
1011
|
prompt?: string | undefined;
|
|
1012
|
+
audio?: boolean | undefined;
|
|
1009
1013
|
resolution?: string | undefined;
|
|
1010
1014
|
override?: {
|
|
1011
1015
|
provider?: string | undefined;
|
|
@@ -1020,11 +1024,12 @@ declare const animateInputSchema: z.ZodObject<{
|
|
|
1020
1024
|
allowFloating?: boolean | undefined;
|
|
1021
1025
|
durationSec?: number | undefined;
|
|
1022
1026
|
}, {
|
|
1023
|
-
image
|
|
1027
|
+
image?: string | Uint8Array<ArrayBuffer> | undefined;
|
|
1024
1028
|
purpose?: string | undefined;
|
|
1025
1029
|
labels?: Record<string, string> | undefined;
|
|
1026
1030
|
tier?: "vision" | "video" | "embedding" | "fast" | "smart" | "powerful" | "cheap" | undefined;
|
|
1027
1031
|
prompt?: string | undefined;
|
|
1032
|
+
audio?: boolean | undefined;
|
|
1028
1033
|
resolution?: string | undefined;
|
|
1029
1034
|
override?: {
|
|
1030
1035
|
provider?: string | undefined;
|
|
@@ -2222,6 +2227,8 @@ interface BflAdapterConfig {
|
|
|
2222
2227
|
fetch?: typeof fetch;
|
|
2223
2228
|
/** Override the per-image USD price (else the built-in finetuned-inference estimate). */
|
|
2224
2229
|
pricePerImage?: number;
|
|
2230
|
+
/** F079 — video generation is slower; measured 20/10 2026: a 5 s hd clip took ~50 s. */
|
|
2231
|
+
videoTimeoutMs?: number;
|
|
2225
2232
|
}
|
|
2226
2233
|
/** Remaining BFL account credits + their USD value (1 credit = $0.01). */
|
|
2227
2234
|
interface BflCredits {
|
|
@@ -2339,8 +2346,8 @@ declare const falStubAdapter: ProviderAdapter;
|
|
|
2339
2346
|
* wires the live adapters. */
|
|
2340
2347
|
declare const stubProviders: Record<string, ProviderAdapter>;
|
|
2341
2348
|
|
|
2342
|
-
declare const VERSION: "0.
|
|
2343
|
-
declare const SDK_TAG: "@broberg/ai-sdk@0.
|
|
2349
|
+
declare const VERSION: "0.64.1";
|
|
2350
|
+
declare const SDK_TAG: "@broberg/ai-sdk@0.64.1";
|
|
2344
2351
|
|
|
2345
2352
|
/** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
|
|
2346
2353
|
* per-call override.
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
allEntries,
|
|
5
5
|
assertOverrideProvider,
|
|
6
6
|
describeTier,
|
|
7
|
+
isFloatingRoute,
|
|
7
8
|
listModels,
|
|
8
9
|
providerIds,
|
|
9
10
|
resetRegistry,
|
|
10
11
|
resolveModel,
|
|
11
12
|
resolveTier,
|
|
12
13
|
setAvailability
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-Q2PPQ5WT.js";
|
|
14
15
|
import {
|
|
15
16
|
DEFAULT_CLIP_SEC,
|
|
16
17
|
MEDIA_PRICING,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
listMediaPrices,
|
|
21
22
|
pricingFreshness,
|
|
22
23
|
pricingGeneratedAt
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-JMRWFOAC.js";
|
|
24
25
|
import {
|
|
25
26
|
PRICING,
|
|
26
27
|
getPrice
|
|
@@ -1411,6 +1412,14 @@ function sniffMime(b) {
|
|
|
1411
1412
|
if (b[0] === 82 && b[1] === 73 && b[8] === 87) return "image/webp";
|
|
1412
1413
|
return "image/jpeg";
|
|
1413
1414
|
}
|
|
1415
|
+
function requireAnimateImage(provider, image) {
|
|
1416
|
+
if (image === void 0 || typeof image === "string" && image.trim() === "" || typeof image !== "string" && image.length === 0) {
|
|
1417
|
+
throw new Error(
|
|
1418
|
+
`${provider} animate: needs an image (image\u2192video). For text\u2192video use override:{ provider:"bfl", model:"flux-3-video" }.`
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
return image;
|
|
1422
|
+
}
|
|
1414
1423
|
|
|
1415
1424
|
// src/providers/gemini.ts
|
|
1416
1425
|
function splitCached(meta) {
|
|
@@ -1596,7 +1605,7 @@ function geminiAdapter(config = {}) {
|
|
|
1596
1605
|
const fetchImpl = config.fetch ?? fetch;
|
|
1597
1606
|
const pollIntervalMs = config.pollIntervalMs ?? 5e3;
|
|
1598
1607
|
const deadline = Date.now() + (config.videoTimeoutMs ?? 3e5);
|
|
1599
|
-
const { data, mimeType } = await toInlineImage(req.image, fetchImpl);
|
|
1608
|
+
const { data, mimeType } = await toInlineImage(requireAnimateImage("gemini", req.image), fetchImpl);
|
|
1600
1609
|
const parameters = {};
|
|
1601
1610
|
if (req.durationSec !== void 0) parameters.durationSeconds = req.durationSec;
|
|
1602
1611
|
if (req.resolution !== void 0) parameters.resolution = req.resolution;
|
|
@@ -2539,13 +2548,14 @@ function vertexAdapter(config = {}) {
|
|
|
2539
2548
|
return cached.token;
|
|
2540
2549
|
}
|
|
2541
2550
|
async function animate(req) {
|
|
2551
|
+
const image = requireAnimateImage("vertex", req.image);
|
|
2542
2552
|
const token = await accessToken();
|
|
2543
2553
|
const proj = project();
|
|
2544
2554
|
const reg = region();
|
|
2545
2555
|
const pollIntervalMs = config.pollIntervalMs ?? 5e3;
|
|
2546
2556
|
const deadline = Date.now() + (config.videoTimeoutMs ?? 3e5);
|
|
2547
2557
|
const baseUrl = `https://${reg}-aiplatform.googleapis.com/v1`;
|
|
2548
|
-
const { data, mimeType } = await toInlineImage(
|
|
2558
|
+
const { data, mimeType } = await toInlineImage(image, fetchImpl);
|
|
2549
2559
|
const parameters = {};
|
|
2550
2560
|
if (req.durationSec !== void 0) parameters.durationSeconds = req.durationSec;
|
|
2551
2561
|
if (req.resolution !== void 0) parameters.resolution = req.resolution;
|
|
@@ -2814,9 +2824,10 @@ function falAdapter(config = {}) {
|
|
|
2814
2824
|
}
|
|
2815
2825
|
async function animate(req) {
|
|
2816
2826
|
const apiKey = resolveKey();
|
|
2827
|
+
const image2 = requireAnimateImage("fal", req.image);
|
|
2817
2828
|
if (!apiKey) throw new Error("fal adapter: API key not set (env FAL_KEY or FAL_API_KEY)");
|
|
2818
2829
|
const headers = authHeaders(apiKey);
|
|
2819
|
-
const imageUrl = typeof
|
|
2830
|
+
const imageUrl = typeof image2 === "string" && /^https?:\/\//i.test(image2) ? image2 : await uploadToFalStorage(toBytes(image2), sniffImageType(image2), "input", apiKey);
|
|
2820
2831
|
const body = { image_url: imageUrl };
|
|
2821
2832
|
if (req.prompt !== void 0) body.prompt = req.prompt;
|
|
2822
2833
|
if (req.durationSec !== void 0) body.duration = req.durationSec;
|
|
@@ -3075,6 +3086,24 @@ function buildZip(files) {
|
|
|
3075
3086
|
// src/providers/bfl.ts
|
|
3076
3087
|
var EU_BASE2 = "https://api.eu.bfl.ai";
|
|
3077
3088
|
var BFL_CREDIT_USD = 0.01;
|
|
3089
|
+
var FLUX3_RESOLUTION = {
|
|
3090
|
+
hd: "hd",
|
|
3091
|
+
"720p": "hd",
|
|
3092
|
+
fhd: "fhd",
|
|
3093
|
+
"1080p": "fhd",
|
|
3094
|
+
qhd: "qhd",
|
|
3095
|
+
"1440p": "qhd",
|
|
3096
|
+
uhd: "uhd",
|
|
3097
|
+
"2160p": "uhd",
|
|
3098
|
+
"4k": "uhd"
|
|
3099
|
+
};
|
|
3100
|
+
function isBflEuHost(url) {
|
|
3101
|
+
try {
|
|
3102
|
+
return /(^|\.)eu\d*\.bfl\.ai$/.test(new URL(url).host.toLowerCase());
|
|
3103
|
+
} catch {
|
|
3104
|
+
return false;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3078
3107
|
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
3079
3108
|
function gcd(a, b) {
|
|
3080
3109
|
return b === 0 ? a : gcd(b, a % b);
|
|
@@ -3167,8 +3196,11 @@ function bflAdapter(config = {}) {
|
|
|
3167
3196
|
return comma >= 0 ? img.slice(comma + 1) : img;
|
|
3168
3197
|
}
|
|
3169
3198
|
async function poll(id, apiKey) {
|
|
3199
|
+
return (await pollResult(id, apiKey, timeoutMs)).sample;
|
|
3200
|
+
}
|
|
3201
|
+
async function pollResult(id, apiKey, limitMs) {
|
|
3170
3202
|
const headers = { "x-key": apiKey };
|
|
3171
|
-
const deadline = Date.now() +
|
|
3203
|
+
const deadline = Date.now() + limitMs;
|
|
3172
3204
|
for (; ; ) {
|
|
3173
3205
|
const res = await doFetch(`${base}/v1/get_result?id=${encodeURIComponent(id)}`, { headers });
|
|
3174
3206
|
if (!res.ok) throw new Error(`bfl get_result ${res.status}`);
|
|
@@ -3177,7 +3209,7 @@ function bflAdapter(config = {}) {
|
|
|
3177
3209
|
if (status === "Ready") {
|
|
3178
3210
|
const sample = data.result?.sample;
|
|
3179
3211
|
if (!sample) throw new Error("bfl: status Ready but no result.sample url");
|
|
3180
|
-
return sample;
|
|
3212
|
+
return { sample, ...typeof data.cost === "number" ? { cost: data.cost } : {} };
|
|
3181
3213
|
}
|
|
3182
3214
|
if (status === "Error" || status === "Failed" || status === "Task not found") {
|
|
3183
3215
|
throw new Error(`bfl: generation ${status}`);
|
|
@@ -3185,11 +3217,69 @@ function bflAdapter(config = {}) {
|
|
|
3185
3217
|
if (status.includes("Moderated")) {
|
|
3186
3218
|
throw new Error(`bfl: ${status} \u2014 prompt or output flagged by BFL safety`);
|
|
3187
3219
|
}
|
|
3188
|
-
if (Date.now() >= deadline) throw new Error(`bfl: timed out after ${
|
|
3220
|
+
if (Date.now() >= deadline) throw new Error(`bfl: timed out after ${limitMs}ms`);
|
|
3189
3221
|
await sleep2(pollIntervalMs);
|
|
3190
3222
|
}
|
|
3191
3223
|
}
|
|
3192
|
-
|
|
3224
|
+
async function animate(req) {
|
|
3225
|
+
const apiKey = resolveKey();
|
|
3226
|
+
if (!apiKey) throw new Error("bfl adapter: BFL_API_KEY not set");
|
|
3227
|
+
const prompt = req.prompt?.trim();
|
|
3228
|
+
if (!prompt) throw new Error("bfl animate: FLUX 3 Video requires a prompt (BFL rejects a video request without one)");
|
|
3229
|
+
const body = { prompt, mode: req.image ? "i2v" : "t2v" };
|
|
3230
|
+
if (req.image) body.keyframes = [toBflImage(req.image)];
|
|
3231
|
+
if (req.durationSec !== void 0) {
|
|
3232
|
+
const d = Math.round(req.durationSec);
|
|
3233
|
+
if (d < 5 || d > 20) throw new Error(`bfl animate: durationSec must be 5\u201320 for FLUX 3 Video (got ${req.durationSec})`);
|
|
3234
|
+
body.duration = d;
|
|
3235
|
+
}
|
|
3236
|
+
if (req.resolution !== void 0) {
|
|
3237
|
+
const r = FLUX3_RESOLUTION[req.resolution.toLowerCase()];
|
|
3238
|
+
if (!r) {
|
|
3239
|
+
throw new Error(
|
|
3240
|
+
`bfl animate: unknown resolution "${req.resolution}" \u2014 use one of ${Object.keys(FLUX3_RESOLUTION).join(", ")}`
|
|
3241
|
+
);
|
|
3242
|
+
}
|
|
3243
|
+
body.resolution = r;
|
|
3244
|
+
}
|
|
3245
|
+
if (req.audio === false) body.generate_audio = false;
|
|
3246
|
+
const submitRes = await doFetch(`${base}/v1/${req.spec.model}`, {
|
|
3247
|
+
method: "POST",
|
|
3248
|
+
headers: { "content-type": "application/json", "x-key": apiKey },
|
|
3249
|
+
body: JSON.stringify(body)
|
|
3250
|
+
});
|
|
3251
|
+
if (!submitRes.ok) {
|
|
3252
|
+
throw new Error(`bfl ${submitRes.status}: ${(await submitRes.text().catch(() => "")).slice(0, 200)}`);
|
|
3253
|
+
}
|
|
3254
|
+
const submit = await submitRes.json();
|
|
3255
|
+
if (!submit.id) throw new Error("bfl: no task id in submit response");
|
|
3256
|
+
const { sample, cost } = await pollResult(submit.id, apiKey, config.videoTimeoutMs ?? 6e5);
|
|
3257
|
+
if (!isBflEuHost(sample)) {
|
|
3258
|
+
throw new Error(`bfl animate: result URL is not on an EU host (${new URL(sample).host}) \u2014 refusing to fetch it`);
|
|
3259
|
+
}
|
|
3260
|
+
const videoRes = await doFetch(sample);
|
|
3261
|
+
if (!videoRes.ok) throw new Error(`bfl animate: download ${videoRes.status}`);
|
|
3262
|
+
const bytes = new Uint8Array(await videoRes.arrayBuffer());
|
|
3263
|
+
const usage = freshUsage({
|
|
3264
|
+
provider: "bfl",
|
|
3265
|
+
model: req.spec.model,
|
|
3266
|
+
region: bflRegion(base),
|
|
3267
|
+
transport: "http",
|
|
3268
|
+
capability: "animate",
|
|
3269
|
+
inputTokens: 0,
|
|
3270
|
+
outputTokens: 0
|
|
3271
|
+
});
|
|
3272
|
+
const credits = cost ?? (typeof submit.cost === "number" ? submit.cost : void 0);
|
|
3273
|
+
if (credits !== void 0) {
|
|
3274
|
+
usage.costUsd = credits * BFL_CREDIT_USD;
|
|
3275
|
+
usage.costBasis = "reported";
|
|
3276
|
+
} else {
|
|
3277
|
+
usage.costUsd = (getMediaPrice("bfl", req.spec.model)?.usd ?? 0) * (body.duration ?? 5);
|
|
3278
|
+
usage.costBasis = "estimated";
|
|
3279
|
+
}
|
|
3280
|
+
return { url: sample, bytes, mimeType: videoRes.headers.get("content-type") ?? "video/mp4", usage };
|
|
3281
|
+
}
|
|
3282
|
+
return { name: "bfl", image, animate };
|
|
3193
3283
|
}
|
|
3194
3284
|
|
|
3195
3285
|
// src/providers/registry.ts
|
|
@@ -3759,14 +3849,17 @@ var imageInputSchema = z.object({
|
|
|
3759
3849
|
});
|
|
3760
3850
|
var animateInputSchema = z.object({
|
|
3761
3851
|
/** Input image: a URL or raw bytes. Where bytes are uploaded is the ADAPTER's
|
|
3762
|
-
* business — fal uses fal storage, the Gemini/Veo default inlines them.
|
|
3763
|
-
|
|
3852
|
+
* business — fal uses fal storage, the Gemini/Veo default inlines them.
|
|
3853
|
+
* Omit for text→video (F079.2): only bfl/flux-3-video supports it. */
|
|
3854
|
+
image: z.union([z.string(), z.instanceof(Uint8Array)]).optional(),
|
|
3764
3855
|
/** Motion/scene prompt. */
|
|
3765
3856
|
prompt: z.string().optional(),
|
|
3766
3857
|
/** Clip length in seconds (provider-dependent; Veo ≤ 8s). */
|
|
3767
3858
|
durationSec: z.number().positive().optional(),
|
|
3768
3859
|
/** e.g. "720p" / "1080p" (provider-dependent). */
|
|
3769
3860
|
resolution: z.string().optional(),
|
|
3861
|
+
/** F079.6 — false = no audio track at all (bfl/flux-3-video only; other routes refuse). */
|
|
3862
|
+
audio: z.boolean().optional(),
|
|
3770
3863
|
...callOptions
|
|
3771
3864
|
});
|
|
3772
3865
|
var trainStyleInputSchema = z.object({
|
|
@@ -3921,8 +4014,8 @@ var aiConfigSchema = z.object({
|
|
|
3921
4014
|
import { randomUUID } from "crypto";
|
|
3922
4015
|
|
|
3923
4016
|
// src/version.ts
|
|
3924
|
-
var VERSION = "0.
|
|
3925
|
-
var SDK_TAG = "@broberg/ai-sdk@0.
|
|
4017
|
+
var VERSION = "0.64.1";
|
|
4018
|
+
var SDK_TAG = "@broberg/ai-sdk@0.64.1";
|
|
3926
4019
|
|
|
3927
4020
|
// src/cost/sinks/upmetrics.ts
|
|
3928
4021
|
function isRetryableStatus(status) {
|
|
@@ -4231,6 +4324,9 @@ function routeHasAudio(spec) {
|
|
|
4231
4324
|
if (m.includes("kling") || m.includes("seedance") || m.includes("wan-")) return false;
|
|
4232
4325
|
return true;
|
|
4233
4326
|
}
|
|
4327
|
+
function canSilenceAudio(spec) {
|
|
4328
|
+
return spec.provider === "bfl" && spec.model === "flux-3-video";
|
|
4329
|
+
}
|
|
4234
4330
|
var DEFAULT_BFL_FINETUNE_SPEC = {
|
|
4235
4331
|
provider: "bfl",
|
|
4236
4332
|
model: "flux-pro-1.1-ultra-finetuned",
|
|
@@ -4354,11 +4450,11 @@ function createAI(config = {}) {
|
|
|
4354
4450
|
}
|
|
4355
4451
|
const warnedFloating = /* @__PURE__ */ new Set();
|
|
4356
4452
|
function checkPinning(spec, allowFloating) {
|
|
4357
|
-
if (!spec.model
|
|
4453
|
+
if (!spec.model || !isFloatingRoute(spec.provider, spec.model) || allowFloating) return false;
|
|
4358
4454
|
const key = `${spec.provider}:${spec.model}`;
|
|
4359
4455
|
if (cfg.strictPinning) {
|
|
4360
4456
|
throw new Error(
|
|
4361
|
-
`createAI: "${key}"
|
|
4457
|
+
`createAI: "${key}" floats (a -latest alias, or a provider that only serves latest) and strictPinning is on. Use a dated id (describeTier() names the pinned ones) or pass allowFloating: true for this call.`
|
|
4362
4458
|
);
|
|
4363
4459
|
}
|
|
4364
4460
|
if (!warnedFloating.has(key)) {
|
|
@@ -4646,12 +4742,19 @@ function createAI(config = {}) {
|
|
|
4646
4742
|
invoke: async (spec) => {
|
|
4647
4743
|
const adapter = pickProvider(spec.provider);
|
|
4648
4744
|
if (!adapter.animate) throw new Error(`createAI: provider "${spec.provider}" does not support animate`);
|
|
4649
|
-
const
|
|
4745
|
+
const silent = input.audio === false;
|
|
4746
|
+
if (silent && routeHasAudio(spec) && !canSilenceAudio(spec)) {
|
|
4747
|
+
throw new Error(
|
|
4748
|
+
`createAI: animate route "${spec.provider}:${spec.model}" cannot switch audio off. For a silent clip use override:{ provider:"bfl", model:"flux-3-video" }.`
|
|
4749
|
+
);
|
|
4750
|
+
}
|
|
4751
|
+
const prompt = routeHasAudio(spec) && !silent ? `${basePrompt} ${ANIMATE_AUDIO_DIRECTIVE}`.trim() : basePrompt;
|
|
4650
4752
|
return adapter.animate({
|
|
4651
4753
|
image: input.image,
|
|
4652
4754
|
prompt: prompt || void 0,
|
|
4653
4755
|
durationSec: input.durationSec,
|
|
4654
4756
|
resolution: input.resolution,
|
|
4757
|
+
...silent ? { audio: false } : {},
|
|
4655
4758
|
spec
|
|
4656
4759
|
});
|
|
4657
4760
|
}
|