@broberg/ai-sdk 0.30.1 → 0.32.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.
@@ -61,18 +61,24 @@ var PRICING = {
61
61
  // Verify against a real key when it lands.
62
62
  "deepseek:deepseek-chat": { inputPer1M: 0.14, outputPer1M: 0.28, version: "2026-06-30-deepseek-direct" },
63
63
  "deepseek:deepseek-reasoner": { inputPer1M: 0.14, outputPer1M: 0.28, version: "2026-06-30-deepseek-direct" },
64
+ // Cached input tokens cost 10% of the input rate — $0.03 vs $0.30 (2.5-flash) and
65
+ // $0.01 vs $0.10 (2.5-flash-lite), read from ai.google.dev/gemini-api/docs/pricing
66
+ // on 2026-08-27 rather than recalled. NB the storage fee on that page ($1/1M
67
+ // tokens/hour) applies to EXPLICIT context caching, where you create a CachedContent
68
+ // object with a TTL. We use IMPLICIT caching, which has no storage charge — so this
69
+ // table is not silently under-billing.
64
70
  // Google Gemini (direct). Provider key is "gemini" — matches the adapter's
65
71
  // usage.provider + the override.provider callers pass. (Image-gen models are
66
72
  // priced per-image in the adapter, not here.)
67
- "gemini:gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, version: V },
73
+ "gemini:gemini-2.5-flash": { inputPer1M: 0.3, cacheReadPer1M: 0.03, outputPer1M: 2.5, version: "2026-08-27-ai.google.dev" },
68
74
  // flash-lite is the default `video` tier (F019) — cheap native video understanding.
69
- "gemini:gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, version: "2026-06-04-or-xref" },
75
+ "gemini:gemini-2.5-flash-lite": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0.4, version: "2026-08-27-ai.google.dev" },
70
76
  // Vertex AI (F038) — the EU-resident route to the SAME Gemini models, so Google's
71
77
  // published Gemini token prices apply. Listed separately because cost lookups key on
72
78
  // `provider:model`: without these rows an EU vision/video call would silently log
73
79
  // $0, which is worse than no tracking (a confident wrong number).
74
- "vertex:gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, version: V },
75
- "vertex:gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, version: "2026-06-04-or-xref" },
80
+ "vertex:gemini-2.5-flash": { inputPer1M: 0.3, cacheReadPer1M: 0.03, outputPer1M: 2.5, version: "2026-08-27-ai.google.dev" },
81
+ "vertex:gemini-2.5-flash-lite": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0.4, version: "2026-08-27-ai.google.dev" },
76
82
  // Cached prompt tokens bill at 10% of the input rate (F039, measured 2026-08-27:
77
83
  // an 8,810-token prefix reported 8,784 cached on the second call WITH a
78
84
  // prompt_cache_key, and 0 without one at every size up to 57k).
@@ -114,4 +120,4 @@ export {
114
120
  PRICING,
115
121
  getPrice
116
122
  };
117
- //# sourceMappingURL=chunk-LKVCPMVI.js.map
123
+ //# sourceMappingURL=chunk-DEIY7O3T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cost/pricing.ts"],"sourcesContent":["// Versioned per-(provider, model) pricing. F3.6 populates the table + adds tests\n// + MiniMax coverage. F3.1 ships the type + lookup with an empty table, so\n// computeCost returns 0 for every model until F3.6 lands (calls still complete).\nexport interface PricingEntry {\n /** USD per 1M input tokens. */\n inputPer1M: number;\n /** USD per 1M output tokens. */\n outputPer1M: number;\n /** USD per 1M cache-read tokens (falls back to input rate if unset). */\n cacheReadPer1M?: number;\n /** USD per 1M cache-write/creation tokens (falls back to input rate if unset). */\n cacheWritePer1M?: number;\n /** Pricing snapshot version (date or tag) so stale entries are detectable. */\n version: string;\n}\n\n// USD per 1M tokens. Anthropic cache multipliers follow the standard model:\n// cache-read ≈ 0.1× input, cache-write ≈ 1.25× input. Verified against the\n// pricing tables in cms (packages/cms-ai/src/providers) + trail (model-lab).\n// MiniMax M2.7 is an estimate pending confirmation against OpenRouter's live\n// price page — flagged in its version string.\nconst V = \"2026-06-02\";\n// Mistral prices come straight from mistral.ai/pricing (per Christian's CD report).\nconst MS = \"2026-06-04-mistral.ai\";\n\n/** Keyed `${provider}:${model}`. Exported so the catalogue-research job (F014)\n * can enumerate every priced entry and diff it against the live provider lists. */\nexport const PRICING: Record<string, PricingEntry> = {\n // Anthropic (direct API). DEFAULT_TIER_MAP: fast/cheap=haiku, smart/vision=sonnet, powerful=opus.\n \"anthropic:claude-haiku-4-5\": {\n inputPer1M: 0.8,\n outputPer1M: 4.0,\n cacheReadPer1M: 0.08,\n cacheWritePer1M: 1.0,\n version: V,\n },\n \"anthropic:claude-sonnet-4-6\": {\n inputPer1M: 3.0,\n outputPer1M: 15.0,\n cacheReadPer1M: 0.3,\n cacheWritePer1M: 3.75,\n version: V,\n },\n \"anthropic:claude-opus-4-8\": {\n inputPer1M: 15.0,\n outputPer1M: 75.0,\n cacheReadPer1M: 1.5,\n cacheWritePer1M: 18.75,\n version: V,\n },\n\n // OpenAI. embedding default tier = text-embedding-3-small (no output tokens).\n \"openai:text-embedding-3-small\": { inputPer1M: 0.02, outputPer1M: 0, version: V },\n \"openai:text-embedding-3-large\": { inputPer1M: 0.13, outputPer1M: 0, version: V },\n \"openai:gpt-4o\": { inputPer1M: 2.5, outputPer1M: 10.0, version: V },\n \"openai:gpt-4o-mini\": { inputPer1M: 0.15, outputPer1M: 0.6, version: V },\n // Whisper is priced per minute, not per token — not representable here; transcribe\n // (F5.6) computes its own cost. Listed as 0 so token-based compute never charges it.\n \"openai:whisper-1\": { inputPer1M: 0, outputPer1M: 0, version: V },\n\n // OpenRouter (meta-router — model slugs include the upstream vendor). Slugs use\n // dots (claude-sonnet-4.6) to match OpenRouter's live ids; the dashed forms\n // never matched a real call. Caught by the F014 catalogue research.\n \"openrouter:anthropic/claude-sonnet-4.6\": { inputPer1M: 3.0, outputPer1M: 15.0, version: V },\n // OpenRouter ground-truth $1/$5 — a markup over Anthropic-direct's $0.8/$4\n // (the `anthropic:` entry above). Was masked while the slug used dashes.\n \"openrouter:anthropic/claude-haiku-4.5\": { inputPer1M: 1.0, outputPer1M: 5.0, version: \"2026-06-04\" },\n \"openrouter:google/gemini-2.5-flash\": { inputPer1M: 0.3, outputPer1M: 2.5, version: V },\n // Ground-truth from OpenRouter /api/v1/models (was a 0.3 estimate; now 0.279).\n \"openrouter:minimax/minimax-m2.7\": {\n inputPer1M: 0.279,\n outputPer1M: 1.2,\n version: \"2026-06-04\",\n },\n // DeepSeek V4 (CN-hosted — NOT GDPR-safe; non-personal-data workloads only).\n // On 2026-05-22 DeepSeek made the \"75% off\" promo the permanent official price.\n // V4-Pro $0.435/$0.87 is ~34x cheaper than GPT-5.5 on output; flash is cheaper\n // still. Numbers match OpenRouter /api/v1/models 1:1 (no router markup). A strong\n // cheap route for fleet background work once `claude -p` is API-billed (15 Jun).\n \"openrouter:deepseek/deepseek-v4-pro\": { inputPer1M: 0.435, outputPer1M: 0.87, version: \"2026-05-22-deepseek-official\" },\n \"openrouter:deepseek/deepseek-v4-flash\": { inputPer1M: 0.0983, outputPer1M: 0.1966, version: \"2026-05-22-deepseek-official\" },\n // DeepSeek DIRECT API (provider \"deepseek\", F030 non-PII secondary). Rates from\n // api-docs.deepseek.com 2026-06-30 ($0.14/$0.28 per 1M; both map to deepseek-v4-flash).\n // `deepseek-chat` (non-thinking) + `deepseek-reasoner` (thinking) DEPRECATE 2026-07-24.\n // (The bare `deepseek-v4-flash` basename is already priced via the openrouter entry\n // above — kept distinct here to avoid a basename collision in the F027 pricing-API.)\n // Verify against a real key when it lands.\n \"deepseek:deepseek-chat\": { inputPer1M: 0.14, outputPer1M: 0.28, version: \"2026-06-30-deepseek-direct\" },\n \"deepseek:deepseek-reasoner\": { inputPer1M: 0.14, outputPer1M: 0.28, version: \"2026-06-30-deepseek-direct\" },\n\n // Cached input tokens cost 10% of the input rate — $0.03 vs $0.30 (2.5-flash) and\n // $0.01 vs $0.10 (2.5-flash-lite), read from ai.google.dev/gemini-api/docs/pricing\n // on 2026-08-27 rather than recalled. NB the storage fee on that page ($1/1M\n // tokens/hour) applies to EXPLICIT context caching, where you create a CachedContent\n // object with a TTL. We use IMPLICIT caching, which has no storage charge — so this\n // table is not silently under-billing.\n // Google Gemini (direct). Provider key is \"gemini\" — matches the adapter's\n // usage.provider + the override.provider callers pass. (Image-gen models are\n // priced per-image in the adapter, not here.)\n \"gemini:gemini-2.5-flash\": { inputPer1M: 0.3, cacheReadPer1M: 0.03, outputPer1M: 2.5, version: \"2026-08-27-ai.google.dev\" },\n // flash-lite is the default `video` tier (F019) — cheap native video understanding.\n \"gemini:gemini-2.5-flash-lite\": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0.4, version: \"2026-08-27-ai.google.dev\" },\n\n // Vertex AI (F038) — the EU-resident route to the SAME Gemini models, so Google's\n // published Gemini token prices apply. Listed separately because cost lookups key on\n // `provider:model`: without these rows an EU vision/video call would silently log\n // $0, which is worse than no tracking (a confident wrong number).\n \"vertex:gemini-2.5-flash\": { inputPer1M: 0.3, cacheReadPer1M: 0.03, outputPer1M: 2.5, version: \"2026-08-27-ai.google.dev\" },\n \"vertex:gemini-2.5-flash-lite\": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0.4, version: \"2026-08-27-ai.google.dev\" },\n\n // Cached prompt tokens bill at 10% of the input rate (F039, measured 2026-08-27:\n // an 8,810-token prefix reported 8,784 cached on the second call WITH a\n // prompt_cache_key, and 0 without one at every size up to 57k).\n // Mistral (direct, La Plateforme). Official prices from mistral.ai/pricing\n // (2026-06-04, per Christian's CD report). EU/Paris-hosted — the designated\n // GDPR-safe provider for client/personal-data workloads (see F015). NB:\n // medium-3.5 is the premium \"Vibe\" coding tier ($1.5/$7.5); Large 3 ($0.5/$1.5)\n // is the cheaper frontier general-purpose model despite the higher number.\n \"mistral:mistral-large-latest\": { inputPer1M: 0.5, cacheReadPer1M: 0.05, outputPer1M: 1.5, version: MS },\n \"mistral:mistral-large-2512\": { inputPer1M: 0.5, cacheReadPer1M: 0.05, outputPer1M: 1.5, version: MS },\n \"mistral:mistral-medium-latest\": { inputPer1M: 1.5, cacheReadPer1M: 0.15, outputPer1M: 7.5, version: MS },\n \"mistral:mistral-medium-3.5\": { inputPer1M: 1.5, cacheReadPer1M: 0.15, outputPer1M: 7.5, version: MS },\n \"mistral:mistral-medium-3\": { inputPer1M: 0.4, cacheReadPer1M: 0.04, outputPer1M: 2.0, version: \"2026-06-04-or-xref\" },\n \"mistral:mistral-small-latest\": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0.3, version: MS },\n \"mistral:mistral-small-2603\": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0.3, version: MS },\n \"mistral:ministral-3b-latest\": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0.1, version: MS },\n \"mistral:ministral-8b-latest\": { inputPer1M: 0.15, cacheReadPer1M: 0.015, outputPer1M: 0.15, version: MS },\n \"mistral:ministral-14b-latest\": { inputPer1M: 0.2, cacheReadPer1M: 0.02, outputPer1M: 0.2, version: MS },\n \"mistral:magistral-medium-latest\": { inputPer1M: 2.0, cacheReadPer1M: 0.2, outputPer1M: 5.0, version: MS },\n \"mistral:magistral-small-latest\": { inputPer1M: 0.5, cacheReadPer1M: 0.05, outputPer1M: 1.5, version: MS },\n \"mistral:devstral-latest\": { inputPer1M: 0.4, cacheReadPer1M: 0.04, outputPer1M: 2.0, version: MS },\n \"mistral:codestral-latest\": { inputPer1M: 0.3, cacheReadPer1M: 0.03, outputPer1M: 0.9, version: MS },\n \"mistral:open-mistral-nemo\": { inputPer1M: 0.15, cacheReadPer1M: 0.015, outputPer1M: 0.15, version: MS },\n // Moderation (F016.4) — per input token; output 0. (OCR is per-page in the adapter.)\n \"mistral:mistral-moderation-latest\": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0, version: MS },\n // Embeddings (F016.5) — per input token.\n \"mistral:mistral-embed\": { inputPer1M: 0.1, cacheReadPer1M: 0.01, outputPer1M: 0, version: MS },\n \"mistral:codestral-embed\": { inputPer1M: 0.15, cacheReadPer1M: 0.015, outputPer1M: 0, version: MS },\n};\n\nexport function getPrice(provider: string, model: string): PricingEntry | undefined {\n const exact = PRICING[`${provider}:${model}`];\n if (exact) return exact;\n // Providers ship dated model snapshots, e.g. \"claude-haiku-4-5-20251001\".\n // Strip a trailing -YYYYMMDD and retry the base lookup so a dated variant\n // prices the same as its base model instead of falling through to 0 — a real\n // paid call must never be logged as $0 (F012). Covers openrouter slugs too.\n const base = model.replace(/-\\d{8}$/, \"\");\n if (base !== model) return PRICING[`${provider}:${base}`];\n return undefined;\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -130,6 +130,11 @@ interface ChatRequest {
130
130
  * (components' requirement while building @broberg/chat, 2026-08-27, where two
131
131
  * knowledge bases are written for readers with different permissions.) */
132
132
  promptCacheKey?: string;
133
+ /** F039.2 — prompt caching is ON by default where the provider supports it.
134
+ * Set false to opt out (e.g. a one-shot call whose prefix will never repeat).
135
+ * Off has no cost benefit and no penalty; a cache MISS was measured to cost
136
+ * exactly the same as a call with no key at all. */
137
+ promptCache?: boolean;
133
138
  temperature?: number;
134
139
  /** "json" → request JSON-object output where the provider supports it (F009). */
135
140
  responseFormat?: "json" | "text";
@@ -546,7 +551,6 @@ declare const messageSchema: z.ZodObject<{
546
551
  }>, "many">>;
547
552
  toolCallId: z.ZodOptional<z.ZodString>;
548
553
  }, "strip", z.ZodTypeAny, {
549
- role: "system" | "user" | "assistant" | "tool";
550
554
  content: string | ({
551
555
  text: string;
552
556
  type: "text";
@@ -555,6 +559,7 @@ declare const messageSchema: z.ZodObject<{
555
559
  type: "image";
556
560
  mimeType?: string | undefined;
557
561
  })[];
562
+ role: "system" | "user" | "assistant" | "tool";
558
563
  toolCalls?: {
559
564
  id: string;
560
565
  name: string;
@@ -562,7 +567,6 @@ declare const messageSchema: z.ZodObject<{
562
567
  }[] | undefined;
563
568
  toolCallId?: string | undefined;
564
569
  }, {
565
- role: "system" | "user" | "assistant" | "tool";
566
570
  content: string | ({
567
571
  text: string;
568
572
  type: "text";
@@ -571,6 +575,7 @@ declare const messageSchema: z.ZodObject<{
571
575
  type: "image";
572
576
  mimeType?: string | undefined;
573
577
  })[];
578
+ role: "system" | "user" | "assistant" | "tool";
574
579
  toolCalls?: {
575
580
  id: string;
576
581
  name: string;
@@ -613,6 +618,9 @@ declare const chatInputSchema: z.ZodObject<{
613
618
  * Derive it from (tenant, conversation): the key is a shared-prefix identity,
614
619
  * so two tenants with identical transcripts must get different keys. */
615
620
  promptCacheKey: z.ZodOptional<z.ZodString>;
621
+ /** F039.2 — prompt caching is ON by default on providers that support it.
622
+ * false opts out; an explicit promptCacheKey always wins. */
623
+ promptCache: z.ZodOptional<z.ZodBoolean>;
616
624
  prompt: z.ZodOptional<z.ZodString>;
617
625
  messages: z.ZodOptional<z.ZodArray<z.ZodObject<{
618
626
  role: z.ZodEnum<["system", "user", "assistant", "tool"]>;
@@ -653,7 +661,6 @@ declare const chatInputSchema: z.ZodObject<{
653
661
  }>, "many">>;
654
662
  toolCallId: z.ZodOptional<z.ZodString>;
655
663
  }, "strip", z.ZodTypeAny, {
656
- role: "system" | "user" | "assistant" | "tool";
657
664
  content: string | ({
658
665
  text: string;
659
666
  type: "text";
@@ -662,6 +669,7 @@ declare const chatInputSchema: z.ZodObject<{
662
669
  type: "image";
663
670
  mimeType?: string | undefined;
664
671
  })[];
672
+ role: "system" | "user" | "assistant" | "tool";
665
673
  toolCalls?: {
666
674
  id: string;
667
675
  name: string;
@@ -669,7 +677,6 @@ declare const chatInputSchema: z.ZodObject<{
669
677
  }[] | undefined;
670
678
  toolCallId?: string | undefined;
671
679
  }, {
672
- role: "system" | "user" | "assistant" | "tool";
673
680
  content: string | ({
674
681
  text: string;
675
682
  type: "text";
@@ -678,6 +685,7 @@ declare const chatInputSchema: z.ZodObject<{
678
685
  type: "image";
679
686
  mimeType?: string | undefined;
680
687
  })[];
688
+ role: "system" | "user" | "assistant" | "tool";
681
689
  toolCalls?: {
682
690
  id: string;
683
691
  name: string;
@@ -708,7 +716,6 @@ declare const chatInputSchema: z.ZodObject<{
708
716
  tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
709
717
  prompt?: string | undefined;
710
718
  messages?: {
711
- role: "system" | "user" | "assistant" | "tool";
712
719
  content: string | ({
713
720
  text: string;
714
721
  type: "text";
@@ -717,6 +724,7 @@ declare const chatInputSchema: z.ZodObject<{
717
724
  type: "image";
718
725
  mimeType?: string | undefined;
719
726
  })[];
727
+ role: "system" | "user" | "assistant" | "tool";
720
728
  toolCalls?: {
721
729
  id: string;
722
730
  name: string;
@@ -732,6 +740,7 @@ declare const chatInputSchema: z.ZodObject<{
732
740
  temperature?: number | undefined;
733
741
  purpose?: string | undefined;
734
742
  promptCacheKey?: string | undefined;
743
+ promptCache?: boolean | undefined;
735
744
  maxTokens?: number | undefined;
736
745
  responseFormat?: "text" | "json" | undefined;
737
746
  override?: {
@@ -750,7 +759,6 @@ declare const chatInputSchema: z.ZodObject<{
750
759
  tier?: "fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding" | undefined;
751
760
  prompt?: string | undefined;
752
761
  messages?: {
753
- role: "system" | "user" | "assistant" | "tool";
754
762
  content: string | ({
755
763
  text: string;
756
764
  type: "text";
@@ -759,6 +767,7 @@ declare const chatInputSchema: z.ZodObject<{
759
767
  type: "image";
760
768
  mimeType?: string | undefined;
761
769
  })[];
770
+ role: "system" | "user" | "assistant" | "tool";
762
771
  toolCalls?: {
763
772
  id: string;
764
773
  name: string;
@@ -774,6 +783,7 @@ declare const chatInputSchema: z.ZodObject<{
774
783
  temperature?: number | undefined;
775
784
  purpose?: string | undefined;
776
785
  promptCacheKey?: string | undefined;
786
+ promptCache?: boolean | undefined;
777
787
  maxTokens?: number | undefined;
778
788
  responseFormat?: "text" | "json" | undefined;
779
789
  override?: {
@@ -1716,6 +1726,9 @@ declare const ttsInputSchema: z.ZodObject<{
1716
1726
  rate?: number | undefined;
1717
1727
  }>;
1718
1728
  declare const aiConfigSchema: z.ZodObject<{
1729
+ /** F039.2 — client-wide opt-out from prompt caching (default: on where the
1730
+ * provider supports it). A per-call `promptCache` wins over this. */
1731
+ promptCache: z.ZodOptional<z.ZodBoolean>;
1719
1732
  defaults: z.ZodOptional<z.ZodRecord<z.ZodEnum<["fast", "smart", "powerful", "cheap", "vision", "video", "embedding"]>, z.ZodObject<{
1720
1733
  provider: z.ZodString;
1721
1734
  model: z.ZodString;
@@ -1755,6 +1768,7 @@ declare const aiConfigSchema: z.ZodObject<{
1755
1768
  autoResolve?: boolean | undefined;
1756
1769
  }>>;
1757
1770
  }, "strip", z.ZodTypeAny, {
1771
+ promptCache?: boolean | undefined;
1758
1772
  defaults?: Partial<Record<"fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding", {
1759
1773
  provider: string;
1760
1774
  model: string;
@@ -1771,6 +1785,7 @@ declare const aiConfigSchema: z.ZodObject<{
1771
1785
  autoResolve?: boolean | undefined;
1772
1786
  } | undefined;
1773
1787
  }, {
1788
+ promptCache?: boolean | undefined;
1774
1789
  defaults?: Partial<Record<"fast" | "smart" | "powerful" | "cheap" | "vision" | "video" | "embedding", {
1775
1790
  provider: string;
1776
1791
  model: string;
@@ -2060,6 +2075,13 @@ interface OpenAICompatibleConfig {
2060
2075
  * response's `usage.cost` (USD) as costUsd, falling back to the pricing table.
2061
2076
  * Only OpenRouter returns this field — openai/deepinfra leave it false. */
2062
2077
  costFromResponseField?: boolean;
2078
+ /** F039.2 — this provider accepts `prompt_cache_key` and caches a shared prefix
2079
+ * on it. Mistral only, deliberately: an unknown field is ignored by some
2080
+ * OpenAI-compatible servers and rejected with a 400 by others, so sending it
2081
+ * everywhere would trade a saving for an outage. Providers that cache
2082
+ * AUTOMATICALLY (openai, deepseek, gemini) need no key — they only need their
2083
+ * cached counts read back, which happens for every provider below. */
2084
+ supportsPromptCacheKey?: boolean;
2063
2085
  }
2064
2086
  declare function makeOpenAICompatibleAdapter(config: OpenAICompatibleConfig): ProviderAdapter;
2065
2087
 
@@ -2081,8 +2103,8 @@ declare const falStubAdapter: ProviderAdapter;
2081
2103
  * wires the live adapters. */
2082
2104
  declare const stubProviders: Record<string, ProviderAdapter>;
2083
2105
 
2084
- declare const VERSION: "0.30.1";
2085
- declare const SDK_TAG: "@broberg/ai-sdk@0.30.1";
2106
+ declare const VERSION: "0.32.0";
2107
+ declare const SDK_TAG: "@broberg/ai-sdk@0.32.0";
2086
2108
 
2087
2109
  /** Built-in defaults. Every entry is overridable via AiConfig.defaults or a
2088
2110
  * per-call override.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-V2PD522L.js";
11
11
  import {
12
12
  getPrice
13
- } from "./chunk-LKVCPMVI.js";
13
+ } from "./chunk-DEIY7O3T.js";
14
14
 
15
15
  // src/transport/http.ts
16
16
  async function httpTransport(req) {
@@ -485,6 +485,16 @@ function mapAnthropicStop(reason) {
485
485
  }
486
486
 
487
487
  // src/providers/openai-compatible.ts
488
+ function autoCacheKey(messages) {
489
+ const system = messages.find((m) => m.role === "system");
490
+ if (!system || typeof system.content !== "string" || system.content.length < 200) return void 0;
491
+ let h = 2166136261;
492
+ for (let i = 0; i < system.content.length; i++) {
493
+ h ^= system.content.charCodeAt(i);
494
+ h = Math.imul(h, 16777619) >>> 0;
495
+ }
496
+ return `ai-sdk-auto-${h.toString(16)}-${system.content.length}`;
497
+ }
488
498
  function toOpenAIMessage(m) {
489
499
  if (typeof m.content === "string") {
490
500
  const base = { role: m.role, content: m.content };
@@ -523,7 +533,10 @@ function makeOpenAICompatibleAdapter(config) {
523
533
  if (req.maxTokens !== void 0) body.max_tokens = req.maxTokens;
524
534
  if (req.temperature !== void 0) body.temperature = req.temperature;
525
535
  if (req.responseFormat === "json") body.response_format = { type: "json_object" };
526
- if (req.promptCacheKey !== void 0) body.prompt_cache_key = req.promptCacheKey;
536
+ if (config.supportsPromptCacheKey && req.promptCache !== false) {
537
+ const k = req.promptCacheKey ?? autoCacheKey(req.messages);
538
+ if (k !== void 0) body.prompt_cache_key = k;
539
+ }
527
540
  if (config.costFromResponseField) body.usage = { include: true };
528
541
  const res = await httpTransport({
529
542
  spec: req.spec,
@@ -794,6 +807,12 @@ var GEMINI_IMAGE_PRICE_PER_IMAGE = {
794
807
  "gemini-3-pro-image-preview": 0.134
795
808
  // was $0.039 — wrong (that's the flash price); pro is $0.134
796
809
  };
810
+ function splitCached(meta) {
811
+ const prompt = meta?.promptTokenCount ?? 0;
812
+ const cached = meta?.cachedContentTokenCount;
813
+ if (cached === void 0) return { inputTokens: prompt };
814
+ return { inputTokens: Math.max(0, prompt - cached), cacheReadTokens: cached };
815
+ }
797
816
  function partsFrom(content) {
798
817
  if (typeof content === "string") return [{ text: content }];
799
818
  return content.map((p) => {
@@ -868,7 +887,7 @@ function geminiAdapter(config = {}) {
868
887
  model: req.spec.model,
869
888
  transport: "http",
870
889
  capability: "chat",
871
- inputTokens: data.usageMetadata?.promptTokenCount ?? 0,
890
+ ...splitCached(data.usageMetadata),
872
891
  outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0
873
892
  });
874
893
  const result = { text, usage };
@@ -890,6 +909,7 @@ function geminiAdapter(config = {}) {
890
909
  const toolCalls = [];
891
910
  let inputTokens = 0;
892
911
  let outputTokens = 0;
912
+ let cacheReadTokens;
893
913
  let finishReason = null;
894
914
  for await (const data of stream) {
895
915
  let chunk;
@@ -908,7 +928,9 @@ function geminiAdapter(config = {}) {
908
928
  }
909
929
  if (candidate?.finishReason) finishReason = candidate.finishReason;
910
930
  if (chunk.usageMetadata) {
911
- inputTokens = chunk.usageMetadata.promptTokenCount ?? inputTokens;
931
+ const split = splitCached(chunk.usageMetadata);
932
+ inputTokens = chunk.usageMetadata.promptTokenCount === void 0 ? inputTokens : split.inputTokens;
933
+ if (split.cacheReadTokens !== void 0) cacheReadTokens = split.cacheReadTokens;
912
934
  outputTokens = chunk.usageMetadata.candidatesTokenCount ?? outputTokens;
913
935
  }
914
936
  }
@@ -921,7 +943,8 @@ function geminiAdapter(config = {}) {
921
943
  transport: "http",
922
944
  capability: "chat",
923
945
  inputTokens,
924
- outputTokens
946
+ outputTokens,
947
+ ...cacheReadTokens === void 0 ? {} : { cacheReadTokens }
925
948
  });
926
949
  yield { type: "usage", costUsd: usage.costUsd, model: usage.model, usage };
927
950
  yield {
@@ -965,7 +988,7 @@ function geminiAdapter(config = {}) {
965
988
  model: req.spec.model,
966
989
  transport: "http",
967
990
  capability: "image",
968
- inputTokens: data.usageMetadata?.promptTokenCount ?? 0,
991
+ ...splitCached(data.usageMetadata),
969
992
  outputTokens: data.usageMetadata?.candidatesTokenCount ?? 0
970
993
  });
971
994
  usage.costUsd = config.pricePerImage ?? GEMINI_IMAGE_PRICE_PER_IMAGE[req.spec.model] ?? 0;
@@ -1150,7 +1173,7 @@ var VOXTRAL_PRICE_PER_MIN = {
1150
1173
  };
1151
1174
  function mistralAdapter(config = {}) {
1152
1175
  const baseUrl = config.baseUrl ?? "https://api.mistral.ai/v1";
1153
- const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey });
1176
+ const base = makeOpenAICompatibleAdapter({ name: "mistral", baseUrl, apiKey: config.apiKey, supportsPromptCacheKey: true });
1154
1177
  function key() {
1155
1178
  const k = config.apiKey ?? process.env.MISTRAL_API_KEY;
1156
1179
  if (!k) throw new Error("mistral adapter: API key not set (env MISTRAL_API_KEY)");
@@ -1726,7 +1749,7 @@ function vertexAdapter(config = {}) {
1726
1749
  }
1727
1750
  const data = await res.json();
1728
1751
  const text = (data.candidates?.[0]?.content?.parts ?? []).map((p) => p.text ?? "").join("").trim();
1729
- const inputTokens = data.usageMetadata?.promptTokenCount ?? 0;
1752
+ const { inputTokens, cacheReadTokens } = splitCached(data.usageMetadata);
1730
1753
  const outputTokens = data.usageMetadata?.candidatesTokenCount ?? 0;
1731
1754
  const usage = freshUsage({
1732
1755
  provider: "vertex",
@@ -1734,9 +1757,10 @@ function vertexAdapter(config = {}) {
1734
1757
  transport: "http",
1735
1758
  capability: "vision",
1736
1759
  inputTokens,
1737
- outputTokens
1760
+ outputTokens,
1761
+ ...cacheReadTokens === void 0 ? {} : { cacheReadTokens }
1738
1762
  });
1739
- usage.costUsd = computeCost("vertex", req.spec.model, inputTokens, outputTokens);
1763
+ usage.costUsd = computeCost("vertex", req.spec.model, inputTokens, outputTokens, cacheReadTokens ?? 0);
1740
1764
  return { text, usage };
1741
1765
  }
1742
1766
  return { name: "vertex", animate, vision };
@@ -2631,6 +2655,9 @@ var chatInputSchema = z.object({
2631
2655
  * Derive it from (tenant, conversation): the key is a shared-prefix identity,
2632
2656
  * so two tenants with identical transcripts must get different keys. */
2633
2657
  promptCacheKey: z.string().optional(),
2658
+ /** F039.2 — prompt caching is ON by default on providers that support it.
2659
+ * false opts out; an explicit promptCacheKey always wins. */
2660
+ promptCache: z.boolean().optional(),
2634
2661
  prompt: z.string().optional(),
2635
2662
  messages: z.array(messageSchema).optional(),
2636
2663
  system: z.string().optional(),
@@ -2779,6 +2806,9 @@ var availabilitySchema = z.object({
2779
2806
  fallback: z.union([z.string(), z.array(z.string())]).optional()
2780
2807
  });
2781
2808
  var aiConfigSchema = z.object({
2809
+ /** F039.2 — client-wide opt-out from prompt caching (default: on where the
2810
+ * provider supports it). A per-call `promptCache` wins over this. */
2811
+ promptCache: z.boolean().optional(),
2782
2812
  defaults: z.record(tierSchema, tierSpecSchema).optional(),
2783
2813
  // Functions can't be deeply validated — z.custom asserts the TS type and
2784
2814
  // passes the value through untouched.
@@ -2792,8 +2822,8 @@ var aiConfigSchema = z.object({
2792
2822
  });
2793
2823
 
2794
2824
  // src/version.ts
2795
- var VERSION = "0.30.1";
2796
- var SDK_TAG = "@broberg/ai-sdk@0.30.1";
2825
+ var VERSION = "0.32.0";
2826
+ var SDK_TAG = "@broberg/ai-sdk@0.32.0";
2797
2827
 
2798
2828
  // src/cost/sinks/upmetrics.ts
2799
2829
  function upmetricsSink(config) {
@@ -3077,7 +3107,7 @@ function createAI(config = {}) {
3077
3107
  invoke: async (spec) => {
3078
3108
  const adapter = pickProvider(spec.provider);
3079
3109
  if (!adapter.chat) throw new Error(`createAI: provider "${spec.provider}" does not support chat`);
3080
- return adapter.chat({ messages, spec, tools: input.tools, maxTokens: input.maxTokens, temperature: input.temperature, responseFormat: input.responseFormat, promptCacheKey: input.promptCacheKey });
3110
+ return adapter.chat({ messages, spec, tools: input.tools, maxTokens: input.maxTokens, temperature: input.temperature, responseFormat: input.responseFormat, promptCacheKey: input.promptCacheKey, promptCache: input.promptCache ?? cfg.promptCache });
3081
3111
  }
3082
3112
  });
3083
3113
  },