@gajae-code/ai 0.18.12 → 0.19.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.
@@ -87,12 +87,23 @@ export declare function mapEffortToAnthropicAdaptiveEffort<TApi extends Api>(mod
87
87
  * - Thinking content is omitted by default (needs display: "summarized")
88
88
  */
89
89
  export declare function hasOpus47ApiRestrictions(modelId: string): boolean;
90
+ /**
91
+ * Haiku 5+ enables adaptive thinking when the `thinking` field is omitted.
92
+ * An explicit user-facing Off choice must therefore send `type: "disabled"`.
93
+ */
94
+ export declare function requiresAnthropicThinkingDisableWhenOff(modelId: string): boolean;
95
+ /**
96
+ * Haiku 5+ and Opus 4.7+ reject sampling parameters on the Anthropic Messages API.
97
+ * https://platform.claude.com/docs/en/models/haiku-5-5/migration-guide#remove-sampling-parameters
98
+ */
99
+ export declare function hasAnthropicModelSamplingRestrictions(modelId: string): boolean;
90
100
  /**
91
101
  * Adaptive thinking `display` is supported starting with Anthropic Opus 4.7.
92
102
  * Older adaptive-thinking models (Opus 4.6, Sonnet 4.6+) reject the field.
93
103
  * Fable (5+) postdates Opus 4.7, accepts `display`, and defaults it to
94
104
  * "omitted" — thinking tokens are billed but no content streams back — so it
95
105
  * must opt in like Opus 4.7+ (issue #2791).
106
+ * Haiku 5+ also accepts `display` and defaults it to "omitted".
96
107
  *
97
108
  * Shares `hasOpus47ApiRestrictions` version parsing on purpose: the two
98
109
  * predicates describe the same API generation, and a private `claude-opus-(\d+)-(\d+)`
@@ -5,7 +5,7 @@ import {
5
5
  isCodexGpt56Tier,
6
6
  isCodexProductTransport,
7
7
  } from "./context-cap-policy";
8
- import { applyOpenAIModelPricing } from "./model-pricing";
8
+ import { applyAnthropicModelPricing, applyOpenAIModelPricing } from "./model-pricing";
9
9
  import {
10
10
  isAuditedOpenAIReasoningTransport,
11
11
  parseDirectXaiReasoningEffortGeneration,
@@ -56,6 +56,17 @@ const DEFAULT_REASONING_EFFORTS_WITH_XHIGH_AND_MAX: readonly Effort[] = [
56
56
  Effort.XHigh,
57
57
  Effort.Max,
58
58
  ];
59
+ // Haiku 5.5 supports low, medium, high, xhigh, and max on both Anthropic
60
+ // Messages and Bedrock Converse; neither contract lists minimal.
61
+ // https://platform.claude.com/docs/en/build-with-claude/effort#effort-levels
62
+ // https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-haiku-5-5.html
63
+ const ANTHROPIC_HAIKU_5_5_EFFORTS: readonly Effort[] = [
64
+ Effort.Low,
65
+ Effort.Medium,
66
+ Effort.High,
67
+ Effort.XHigh,
68
+ Effort.Max,
69
+ ];
59
70
  const GEMINI_3_PRO_EFFORTS: readonly Effort[] = [Effort.Low, Effort.High];
60
71
  const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High];
61
72
  // Gemini 3.7 Flash dropped `minimal`; the official API returns an error for it.
@@ -96,7 +107,7 @@ type SemVer = {
96
107
  };
97
108
 
98
109
  type GeminiKind = "pro" | "flash" | "argon";
99
- type AnthropicKind = "opus" | "sonnet" | "fable";
110
+ type AnthropicKind = "opus" | "sonnet" | "fable" | "haiku";
100
111
  type OpenAIVariant =
101
112
  | "astra"
102
113
  | "base"
@@ -538,12 +549,34 @@ export function hasOpus47ApiRestrictions(modelId: string): boolean {
538
549
  return semverGte(parsed.version, "4.7") && parsed.kind === "opus";
539
550
  }
540
551
 
552
+ function isAnthropicHaikuAdaptiveModel(modelId: string): boolean {
553
+ const parsed = parseAnthropicModel(getCanonicalModelId(modelId));
554
+ return parsed?.kind === "haiku" && semverGte(parsed.version, "5.0");
555
+ }
556
+
557
+ /**
558
+ * Haiku 5+ enables adaptive thinking when the `thinking` field is omitted.
559
+ * An explicit user-facing Off choice must therefore send `type: "disabled"`.
560
+ */
561
+ export function requiresAnthropicThinkingDisableWhenOff(modelId: string): boolean {
562
+ return isAnthropicHaikuAdaptiveModel(modelId);
563
+ }
564
+
565
+ /**
566
+ * Haiku 5+ and Opus 4.7+ reject sampling parameters on the Anthropic Messages API.
567
+ * https://platform.claude.com/docs/en/models/haiku-5-5/migration-guide#remove-sampling-parameters
568
+ */
569
+ export function hasAnthropicModelSamplingRestrictions(modelId: string): boolean {
570
+ return hasOpus47ApiRestrictions(modelId) || isAnthropicHaikuAdaptiveModel(modelId);
571
+ }
572
+
541
573
  /**
542
574
  * Adaptive thinking `display` is supported starting with Anthropic Opus 4.7.
543
575
  * Older adaptive-thinking models (Opus 4.6, Sonnet 4.6+) reject the field.
544
576
  * Fable (5+) postdates Opus 4.7, accepts `display`, and defaults it to
545
577
  * "omitted" — thinking tokens are billed but no content streams back — so it
546
578
  * must opt in like Opus 4.7+ (issue #2791).
579
+ * Haiku 5+ also accepts `display` and defaults it to "omitted".
547
580
  *
548
581
  * Shares `hasOpus47ApiRestrictions` version parsing on purpose: the two
549
582
  * predicates describe the same API generation, and a private `claude-opus-(\d+)-(\d+)`
@@ -554,7 +587,7 @@ export function hasOpus47ApiRestrictions(modelId: string): boolean {
554
587
  */
555
588
  export function supportsAnthropicAdaptiveThinkingDisplay(modelId: string): boolean {
556
589
  if (/claude-fable-\d/.test(modelId)) return true;
557
- return hasOpus47ApiRestrictions(modelId);
590
+ return hasOpus47ApiRestrictions(modelId) || isAnthropicHaikuAdaptiveModel(modelId);
558
591
  }
559
592
 
560
593
  function anthropicModelHasRealXHighEffort<TApi extends Api>(model: ApiModel<TApi>): boolean {
@@ -576,6 +609,7 @@ function anthropicModelHasRealXHighEffort<TApi extends Api>(model: ApiModel<TApi
576
609
 
577
610
  function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
578
611
  applyOpenAIModelPricing(model);
612
+ applyAnthropicModelPricing(model);
579
613
  const copilotLimits = model.provider === "github-copilot" ? COPILOT_GENERATED_LIMITS[model.id] : undefined;
580
614
  if (copilotLimits) {
581
615
  model.contextWindow = copilotLimits.contextWindow;
@@ -879,6 +913,19 @@ function inferDefaultEffort<TApi extends Api>(model: ApiModel<TApi>, parsedModel
879
913
  if (model.provider === "omlx") {
880
914
  return Effort.Medium;
881
915
  }
916
+ if (
917
+ parsedModel.family === "anthropic" &&
918
+ parsedModel.kind === "haiku" &&
919
+ ((model.provider === "anthropic" &&
920
+ model.api === "anthropic-messages" &&
921
+ semverGte(parsedModel.version, "5.0")) ||
922
+ (model.provider === "amazon-bedrock" &&
923
+ model.api === "bedrock-converse-stream" &&
924
+ semverGte(parsedModel.version, "5.5")))
925
+ ) {
926
+ // Haiku 5.5 defaults to medium adaptive effort instead of the global high.
927
+ return Effort.Medium;
928
+ }
882
929
  return undefined;
883
930
  }
884
931
 
@@ -1027,6 +1074,9 @@ function inferAnthropicSupportedEfforts<TApi extends Api>(
1027
1074
  (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") &&
1028
1075
  semverGte(parsedModel.version, "4.6")
1029
1076
  ) {
1077
+ if (parsedModel.kind === "haiku" && semverGte(parsedModel.version, "5.5")) {
1078
+ return ANTHROPIC_HAIKU_5_5_EFFORTS;
1079
+ }
1030
1080
  if (parsedModel.kind === "fable") {
1031
1081
  // Fable exposes Anthropic's Messages-only xhigh preset; Bedrock
1032
1082
  // Converse lacks it (same split as Opus 4.7+ below).
@@ -1105,7 +1155,7 @@ function inferThinkingControlMode<TApi extends Api>(
1105
1155
  if (parsedModel.family === "anthropic") {
1106
1156
  if (
1107
1157
  semverGte(parsedModel.version, "4.6") &&
1108
- (parsedModel.kind === "opus" || parsedModel.kind === "fable")
1158
+ (parsedModel.kind === "opus" || parsedModel.kind === "fable" || parsedModel.kind === "haiku")
1109
1159
  ) {
1110
1160
  return "anthropic-adaptive";
1111
1161
  }
@@ -1146,7 +1196,7 @@ function parseGeminiModel(modelId: string): GeminiModel | null {
1146
1196
  }
1147
1197
 
1148
1198
  function parseAnthropicModel(modelId: string): AnthropicModel | null {
1149
- const match = /claude-(opus|sonnet|fable)-(\d{1,2}(?:[.-]\d{1,2}){0,2})\b/.exec(modelId);
1199
+ const match = /claude-(opus|sonnet|fable|haiku)-(\d{1,2}(?:[.-]\d{1,2}){0,2})\b/.exec(modelId);
1150
1200
  if (!match) {
1151
1201
  return null;
1152
1202
  }
@@ -1154,6 +1204,11 @@ function parseAnthropicModel(modelId: string): AnthropicModel | null {
1154
1204
  if (!version) {
1155
1205
  return null;
1156
1206
  }
1207
+ // Haiku is recognized from generation 5 only. Haiku 4.5 predates the
1208
+ // adaptive-thinking policy and keeps the generic (unparsed) budget ladder.
1209
+ if (match[1] === "haiku" && !semverGte(version, "5.0")) {
1210
+ return null;
1211
+ }
1157
1212
  return { family: "anthropic", kind: match[1] as AnthropicKind, version };
1158
1213
  }
1159
1214
 
package/src/models.json CHANGED
@@ -779,6 +779,41 @@
779
779
  "mode": "anthropic-adaptive"
780
780
  }
781
781
  },
782
+ "anthropic.claude-haiku-5-5": {
783
+ "api": "bedrock-converse-stream",
784
+ "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
785
+ "contextWindow": 1000000,
786
+ "cost": {
787
+ "cacheRead": 0.01,
788
+ "cacheWrite": 0.125,
789
+ "input": 0.1,
790
+ "output": 0.5
791
+ },
792
+ "id": "anthropic.claude-haiku-5-5",
793
+ "input": [
794
+ "text",
795
+ "image"
796
+ ],
797
+ "longContextPricing": {
798
+ "cost": {
799
+ "cacheRead": 0.05,
800
+ "cacheWrite": 0.625,
801
+ "input": 0.5,
802
+ "output": 2.5
803
+ },
804
+ "threshold": 100000
805
+ },
806
+ "maxTokens": 128000,
807
+ "name": "Anthropic Haiku 5.5",
808
+ "provider": "amazon-bedrock",
809
+ "reasoning": true,
810
+ "thinking": {
811
+ "defaultLevel": "medium",
812
+ "maxLevel": "max",
813
+ "minLevel": "low",
814
+ "mode": "anthropic-adaptive"
815
+ }
816
+ },
782
817
  "anthropic.claude-opus-4-6-v1": {
783
818
  "api": "bedrock-converse-stream",
784
819
  "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
@@ -1098,6 +1133,41 @@
1098
1133
  "mode": "budget"
1099
1134
  }
1100
1135
  },
1136
+ "au.anthropic.claude-haiku-5-5": {
1137
+ "api": "bedrock-converse-stream",
1138
+ "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
1139
+ "contextWindow": 1000000,
1140
+ "cost": {
1141
+ "cacheRead": 0.011,
1142
+ "cacheWrite": 0.1375,
1143
+ "input": 0.11,
1144
+ "output": 0.55
1145
+ },
1146
+ "id": "au.anthropic.claude-haiku-5-5",
1147
+ "input": [
1148
+ "text",
1149
+ "image"
1150
+ ],
1151
+ "longContextPricing": {
1152
+ "cost": {
1153
+ "cacheRead": 0.055,
1154
+ "cacheWrite": 0.6875,
1155
+ "input": 0.55,
1156
+ "output": 2.75
1157
+ },
1158
+ "threshold": 100000
1159
+ },
1160
+ "maxTokens": 128000,
1161
+ "name": "Anthropic Haiku 5.5 (AU)",
1162
+ "provider": "amazon-bedrock",
1163
+ "reasoning": true,
1164
+ "thinking": {
1165
+ "defaultLevel": "medium",
1166
+ "maxLevel": "max",
1167
+ "minLevel": "low",
1168
+ "mode": "anthropic-adaptive"
1169
+ }
1170
+ },
1101
1171
  "au.anthropic.claude-opus-4-6-v1": {
1102
1172
  "api": "bedrock-converse-stream",
1103
1173
  "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
@@ -1787,6 +1857,41 @@
1787
1857
  "mode": "budget"
1788
1858
  }
1789
1859
  },
1860
+ "eu.anthropic.claude-haiku-5-5": {
1861
+ "api": "bedrock-converse-stream",
1862
+ "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
1863
+ "contextWindow": 1000000,
1864
+ "cost": {
1865
+ "cacheRead": 0.011,
1866
+ "cacheWrite": 0.1375,
1867
+ "input": 0.11,
1868
+ "output": 0.55
1869
+ },
1870
+ "id": "eu.anthropic.claude-haiku-5-5",
1871
+ "input": [
1872
+ "text",
1873
+ "image"
1874
+ ],
1875
+ "longContextPricing": {
1876
+ "cost": {
1877
+ "cacheRead": 0.055,
1878
+ "cacheWrite": 0.6875,
1879
+ "input": 0.55,
1880
+ "output": 2.75
1881
+ },
1882
+ "threshold": 100000
1883
+ },
1884
+ "maxTokens": 128000,
1885
+ "name": "Anthropic Haiku 5.5 (EU)",
1886
+ "provider": "amazon-bedrock",
1887
+ "reasoning": true,
1888
+ "thinking": {
1889
+ "defaultLevel": "medium",
1890
+ "maxLevel": "max",
1891
+ "minLevel": "low",
1892
+ "mode": "anthropic-adaptive"
1893
+ }
1894
+ },
1790
1895
  "eu.anthropic.claude-opus-4-1-20250805-v1:0": {
1791
1896
  "api": "bedrock-converse-stream",
1792
1897
  "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
@@ -2267,6 +2372,41 @@
2267
2372
  "mode": "budget"
2268
2373
  }
2269
2374
  },
2375
+ "global.anthropic.claude-haiku-5-5": {
2376
+ "api": "bedrock-converse-stream",
2377
+ "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
2378
+ "contextWindow": 1000000,
2379
+ "cost": {
2380
+ "cacheRead": 0.01,
2381
+ "cacheWrite": 0.125,
2382
+ "input": 0.1,
2383
+ "output": 0.5
2384
+ },
2385
+ "id": "global.anthropic.claude-haiku-5-5",
2386
+ "input": [
2387
+ "text",
2388
+ "image"
2389
+ ],
2390
+ "longContextPricing": {
2391
+ "cost": {
2392
+ "cacheRead": 0.05,
2393
+ "cacheWrite": 0.625,
2394
+ "input": 0.5,
2395
+ "output": 2.5
2396
+ },
2397
+ "threshold": 100000
2398
+ },
2399
+ "maxTokens": 128000,
2400
+ "name": "Anthropic Haiku 5.5 (Global)",
2401
+ "provider": "amazon-bedrock",
2402
+ "reasoning": true,
2403
+ "thinking": {
2404
+ "defaultLevel": "medium",
2405
+ "maxLevel": "max",
2406
+ "minLevel": "low",
2407
+ "mode": "anthropic-adaptive"
2408
+ }
2409
+ },
2270
2410
  "global.anthropic.claude-opus-4-5-20251101-v1:0": {
2271
2411
  "api": "bedrock-converse-stream",
2272
2412
  "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
@@ -3124,6 +3264,41 @@
3124
3264
  "mode": "budget"
3125
3265
  }
3126
3266
  },
3267
+ "jp.anthropic.claude-haiku-5-5": {
3268
+ "api": "bedrock-converse-stream",
3269
+ "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
3270
+ "contextWindow": 1000000,
3271
+ "cost": {
3272
+ "cacheRead": 0.011,
3273
+ "cacheWrite": 0.1375,
3274
+ "input": 0.11,
3275
+ "output": 0.55
3276
+ },
3277
+ "id": "jp.anthropic.claude-haiku-5-5",
3278
+ "input": [
3279
+ "text",
3280
+ "image"
3281
+ ],
3282
+ "longContextPricing": {
3283
+ "cost": {
3284
+ "cacheRead": 0.055,
3285
+ "cacheWrite": 0.6875,
3286
+ "input": 0.55,
3287
+ "output": 2.75
3288
+ },
3289
+ "threshold": 100000
3290
+ },
3291
+ "maxTokens": 128000,
3292
+ "name": "Anthropic Haiku 5.5 (JP)",
3293
+ "provider": "amazon-bedrock",
3294
+ "reasoning": true,
3295
+ "thinking": {
3296
+ "defaultLevel": "medium",
3297
+ "maxLevel": "max",
3298
+ "minLevel": "low",
3299
+ "mode": "anthropic-adaptive"
3300
+ }
3301
+ },
3127
3302
  "jp.anthropic.claude-opus-4-7": {
3128
3303
  "api": "bedrock-converse-stream",
3129
3304
  "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
@@ -4573,6 +4748,41 @@
4573
4748
  "mode": "budget"
4574
4749
  }
4575
4750
  },
4751
+ "us.anthropic.claude-haiku-5-5": {
4752
+ "api": "bedrock-converse-stream",
4753
+ "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
4754
+ "contextWindow": 1000000,
4755
+ "cost": {
4756
+ "cacheRead": 0.011,
4757
+ "cacheWrite": 0.1375,
4758
+ "input": 0.11,
4759
+ "output": 0.55
4760
+ },
4761
+ "id": "us.anthropic.claude-haiku-5-5",
4762
+ "input": [
4763
+ "text",
4764
+ "image"
4765
+ ],
4766
+ "longContextPricing": {
4767
+ "cost": {
4768
+ "cacheRead": 0.055,
4769
+ "cacheWrite": 0.6875,
4770
+ "input": 0.55,
4771
+ "output": 2.75
4772
+ },
4773
+ "threshold": 100000
4774
+ },
4775
+ "maxTokens": 128000,
4776
+ "name": "Anthropic Haiku 5.5 (US)",
4777
+ "provider": "amazon-bedrock",
4778
+ "reasoning": true,
4779
+ "thinking": {
4780
+ "defaultLevel": "medium",
4781
+ "maxLevel": "max",
4782
+ "minLevel": "low",
4783
+ "mode": "anthropic-adaptive"
4784
+ }
4785
+ },
4576
4786
  "us.anthropic.claude-opus-4-1-20250805-v1:0": {
4577
4787
  "api": "bedrock-converse-stream",
4578
4788
  "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
@@ -5782,6 +5992,41 @@
5782
5992
  "mode": "budget"
5783
5993
  }
5784
5994
  },
5995
+ "claude-haiku-5-5": {
5996
+ "api": "anthropic-messages",
5997
+ "baseUrl": "https://api.anthropic.com",
5998
+ "contextWindow": 1000000,
5999
+ "cost": {
6000
+ "cacheRead": 0.01,
6001
+ "cacheWrite": 0.125,
6002
+ "input": 0.1,
6003
+ "output": 0.5
6004
+ },
6005
+ "id": "claude-haiku-5-5",
6006
+ "input": [
6007
+ "text",
6008
+ "image"
6009
+ ],
6010
+ "longContextPricing": {
6011
+ "cost": {
6012
+ "cacheRead": 0.05,
6013
+ "cacheWrite": 0.625,
6014
+ "input": 0.5,
6015
+ "output": 2.5
6016
+ },
6017
+ "threshold": 100000
6018
+ },
6019
+ "maxTokens": 128000,
6020
+ "name": "Anthropic Haiku 5.5",
6021
+ "provider": "anthropic",
6022
+ "reasoning": true,
6023
+ "thinking": {
6024
+ "defaultLevel": "medium",
6025
+ "maxLevel": "max",
6026
+ "minLevel": "low",
6027
+ "mode": "anthropic-adaptive"
6028
+ }
6029
+ },
5785
6030
  "claude-opus-4-0": {
5786
6031
  "api": "anthropic-messages",
5787
6032
  "baseUrl": "https://api.anthropic.com",
package/src/models.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { registerProviderSafetyStopModel } from "./adapter-internals/provider-safety-stop";
3
- import { getOpenAIModelCost } from "./model-pricing";
3
+ import { getAnthropicCacheWriteOneHourRate, getAnthropicModelCost, getOpenAIModelCost } from "./model-pricing";
4
4
  import { isRetiredModelKey } from "./model-retirements";
5
5
  import { applyGeneratedModelPolicies, enrichModelThinking } from "./model-thinking";
6
6
  // `with { type: "file" }` is embedded by `bun build --compile` and resolves to
@@ -105,11 +105,18 @@ export function getBundledModels(provider: GeneratedProvider): Model<Api>[] {
105
105
 
106
106
  export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
107
107
  const inputTokens = usage.input + usage.cacheRead + usage.cacheWrite;
108
- const pricing = getOpenAIModelCost(model, inputTokens) ?? model.cost;
108
+ const pricing = getAnthropicModelCost(model, inputTokens) ?? getOpenAIModelCost(model, inputTokens) ?? model.cost;
109
109
  usage.cost.input = (pricing.input / 1000000) * usage.input;
110
110
  usage.cost.output = (pricing.output / 1000000) * usage.output;
111
111
  usage.cost.cacheRead = (pricing.cacheRead / 1000000) * usage.cacheRead;
112
- usage.cost.cacheWrite = (pricing.cacheWrite / 1000000) * usage.cacheWrite;
112
+ const oneHourRate = getAnthropicCacheWriteOneHourRate(model, inputTokens);
113
+ // Bill the response's 1-hour cache-write tokens at the 1-hour rate; everything else
114
+ // (5-minute writes and any unattributed remainder) uses the model's cacheWrite rate.
115
+ const oneHourTokens =
116
+ oneHourRate === undefined ? 0 : Math.min(Math.max(usage.cttl?.ephemeral1h ?? 0, 0), usage.cacheWrite);
117
+ usage.cost.cacheWrite =
118
+ (pricing.cacheWrite / 1000000) * (usage.cacheWrite - oneHourTokens) +
119
+ ((oneHourRate ?? 0) / 1000000) * oneHourTokens;
113
120
  usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
114
121
  return usage.cost;
115
122
  }
@@ -14,11 +14,13 @@ export interface GoogleVertexModelManagerConfig {
14
14
  export interface GoogleAntigravityModelManagerConfig {
15
15
  oauthToken?: string;
16
16
  endpoint?: string;
17
+ project?: string;
17
18
  }
18
19
 
19
20
  export interface GoogleGeminiCliModelManagerConfig {
20
21
  oauthToken?: string;
21
22
  endpoint?: string;
23
+ project?: string;
22
24
  }
23
25
 
24
26
  const CLOUD_CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
@@ -58,6 +60,7 @@ export function googleAntigravityModelManagerOptions(
58
60
  fetchAntigravityDiscoveryModels({
59
61
  token,
60
62
  endpoint: config?.endpoint,
63
+ project: config?.project,
61
64
  userAgent: ua,
62
65
  }),
63
66
  }
@@ -80,6 +83,7 @@ export function googleGeminiCliModelManagerOptions(
80
83
  const models = await fetchAntigravityDiscoveryModels({
81
84
  token,
82
85
  endpoint,
86
+ project: config?.project,
83
87
  targetProvider: "google-gemini-cli",
84
88
  userAgent: ua,
85
89
  });
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { parseBedrockClaudeGeneration } from "../bedrock-claude-cache-policy";
10
10
  import type { Effort } from "../model-thinking";
11
- import type { Model, StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
11
+ import type { AssistantMessage, CacheRetention, Model, StreamFunction, StreamOptions, ThinkingBudgets, Tool, ToolChoice } from "../types";
12
12
  export type BedrockThinkingDisplay = "summarized" | "omitted";
13
13
  export interface BedrockOptions extends StreamOptions {
14
14
  region?: string;
@@ -51,7 +51,18 @@ interface WireToolConfig {
51
51
  tools: WireToolSpec[];
52
52
  toolChoice?: WireToolChoice;
53
53
  }
54
+ interface MetadataEvent {
55
+ usage?: {
56
+ inputTokens?: number;
57
+ outputTokens?: number;
58
+ cacheReadInputTokens?: number;
59
+ cacheWriteInputTokens?: number;
60
+ totalTokens?: number;
61
+ };
62
+ }
54
63
  export declare const streamBedrock: StreamFunction<"bedrock-converse-stream">;
64
+ /** Exported for unit tests of the usage-to-cost path. */
65
+ export declare function handleMetadata(event: MetadataEvent, model: Model<"bedrock-converse-stream">, output: AssistantMessage, cacheRetention: CacheRetention): void;
55
66
  /**
56
67
  * Check if the model supports prompt caching.
57
68
  * Supported: Claude 3.5 Haiku, Claude 3.7 Sonnet, and every later Claude
@@ -71,5 +82,9 @@ export declare function stripBedrockForcedToolChoiceForRetry<T extends {
71
82
  toolConfig?: {
72
83
  toolChoice?: unknown;
73
84
  };
74
- }>(body: T): T;
85
+ additionalModelRequestFields?: Record<string, unknown>;
86
+ }>(body: T, thinking?: {
87
+ forced: Record<string, unknown> | undefined;
88
+ restored: Record<string, unknown>;
89
+ }): T;
75
90
  export declare function convertToolConfig(tools: Tool[] | undefined, toolChoice: BedrockOptions["toolChoice"]): WireToolConfig | undefined;
@@ -10,10 +10,12 @@
10
10
  import { $credentialEnv, $env, $flag, extractHttpStatusFromError, fetchWithRetry } from "@gajae-code/utils";
11
11
  import { assertAwsRegionLabel } from "../adapter-internals/aws-region";
12
12
  import { parseBedrockClaudeGeneration, supportsBedrockClaudePromptCaching } from "../bedrock-claude-cache-policy";
13
+ import { getAnthropicCacheWriteOneHourRate } from "../model-pricing";
13
14
  import type { Effort } from "../model-thinking";
14
15
  import {
15
16
  mapEffortToAnthropicAdaptiveEffort,
16
17
  requireSupportedEffort,
18
+ requiresAnthropicThinkingDisableWhenOff,
17
19
  supportsAnthropicAdaptiveThinkingDisplay as supportsAdaptiveThinkingDisplay,
18
20
  } from "../model-thinking";
19
21
  import { calculateCost } from "../models";
@@ -213,12 +215,30 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
213
215
  const resolvedToolChoice = resolveToolChoice(model, options.toolChoice);
214
216
  const toolConfig = convertToolConfig(context.tools, resolvedToolChoice.resolvedChoice);
215
217
  let additionalModelRequestFields = buildAdditionalModelRequestFields(model, options);
218
+ // Selected reasoning fields before forced-tool normalization, restored if the forced
219
+ // tool choice is later dropped on retry (Haiku 5+ only; see below).
220
+ let unforcedModelRequestFields: Record<string, unknown> | undefined;
216
221
 
217
222
  // Bedrock rejects thinking + forced tool_choice ("any" or specific tool).
218
223
  // When the resolved tool_choice forces tool use, disable thinking to avoid API errors.
219
224
  if (toolConfig?.toolChoice && additionalModelRequestFields) {
220
225
  const tc = toolConfig.toolChoice;
221
- if (tc.any || tc.tool) additionalModelRequestFields = undefined;
226
+ if ((tc.any || tc.tool) && requiresAnthropicThinkingDisableWhenOff(model.id)) {
227
+ // Haiku 5+ enables adaptive thinking when the field is omitted, which a forced
228
+ // tool choice rejects, and applies the effort to tool-call arguments too: send an
229
+ // explicit `disabled` and keep the selected effort (capped at `high` while
230
+ // thinking is disabled).
231
+ const effort = (additionalModelRequestFields.output_config as { effort?: string } | undefined)?.effort;
232
+ unforcedModelRequestFields = additionalModelRequestFields;
233
+ additionalModelRequestFields = {
234
+ thinking: { type: "disabled" },
235
+ ...(effort
236
+ ? { output_config: { effort: effort === "xhigh" || effort === "max" ? "high" : effort } }
237
+ : {}),
238
+ };
239
+ } else if (tc.any || tc.tool) {
240
+ additionalModelRequestFields = undefined;
241
+ }
222
242
  }
223
243
 
224
244
  let commandInput: ConverseStreamRequest = {
@@ -307,7 +327,12 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
307
327
  reason,
308
328
  registryKey: resolvedToolChoice.registryKey,
309
329
  });
310
- stripBedrockForcedToolChoiceForRetry(commandInput);
330
+ stripBedrockForcedToolChoiceForRetry(
331
+ commandInput,
332
+ unforcedModelRequestFields
333
+ ? { forced: additionalModelRequestFields, restored: unforcedModelRequestFields }
334
+ : undefined,
335
+ );
311
336
  const retryBodyText = JSON.stringify(commandInput);
312
337
  const retryBody = new TextEncoder().encode(retryBodyText);
313
338
  if (rawRequestDump) rawRequestDump.body = commandInput;
@@ -429,7 +454,7 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
429
454
  break;
430
455
  }
431
456
  case "metadata": {
432
- handleMetadata(payload as MetadataEvent, model, output);
457
+ handleMetadata(payload as MetadataEvent, model, output, cacheRetention);
433
458
  break;
434
459
  }
435
460
  default:
@@ -580,13 +605,28 @@ function handleContentBlockDelta(
580
605
  }
581
606
  }
582
607
 
583
- function handleMetadata(event: MetadataEvent, model: Model<"bedrock-converse-stream">, output: AssistantMessage): void {
608
+ /** Exported for unit tests of the usage-to-cost path. */
609
+ export function handleMetadata(
610
+ event: MetadataEvent,
611
+ model: Model<"bedrock-converse-stream">,
612
+ output: AssistantMessage,
613
+ cacheRetention: CacheRetention,
614
+ ): void {
584
615
  if (event.usage) {
585
616
  output.usage.input = event.usage.inputTokens || 0;
586
617
  output.usage.output = event.usage.outputTokens || 0;
587
618
  output.usage.cacheRead = event.usage.cacheReadInputTokens || 0;
588
619
  output.usage.cacheWrite = event.usage.cacheWriteInputTokens || 0;
589
620
  output.usage.totalTokens = event.usage.totalTokens || output.usage.input + output.usage.output;
621
+ // Converse reports aggregate cache writes only. Where 1-hour writes have their own rate
622
+ // (Haiku 5.5), the request's `ttl: "1h"` cache points mean every write was 1-hour.
623
+ if (
624
+ cacheRetention === "long" &&
625
+ output.usage.cacheWrite > 0 &&
626
+ getAnthropicCacheWriteOneHourRate(model, 0) !== undefined
627
+ ) {
628
+ output.usage.cttl = { ephemeral1h: output.usage.cacheWrite };
629
+ }
590
630
  calculateCost(model, output.usage);
591
631
  }
592
632
  }
@@ -829,10 +869,18 @@ function convertMessages(
829
869
  return result;
830
870
  }
831
871
 
832
- export function stripBedrockForcedToolChoiceForRetry<T extends { toolConfig?: { toolChoice?: unknown } }>(body: T): T {
872
+ export function stripBedrockForcedToolChoiceForRetry<
873
+ T extends { toolConfig?: { toolChoice?: unknown }; additionalModelRequestFields?: Record<string, unknown> },
874
+ >(body: T, thinking?: { forced: Record<string, unknown> | undefined; restored: Record<string, unknown> }): T {
833
875
  if (body.toolConfig) {
834
876
  body.toolConfig = { ...body.toolConfig, toolChoice: undefined };
835
877
  }
878
+ // The forced request replaced the selected thinking fields with a disabled block; with the
879
+ // forced constraint gone, put the selected reasoning back. A payload the caller replaced via
880
+ // onPayload (not the object this provider sent) is left untouched.
881
+ if (thinking && body.additionalModelRequestFields === thinking.forced) {
882
+ body.additionalModelRequestFields = thinking.restored;
883
+ }
836
884
  return body;
837
885
  }
838
886
 
@@ -888,6 +936,9 @@ function buildAdditionalModelRequestFields(
888
936
  options: BedrockOptions,
889
937
  ): Record<string, unknown> | undefined {
890
938
  const reasoning = options.reasoning;
939
+ if (requiresAnthropicThinkingDisableWhenOff(model.id) && (!reasoning || !model.reasoning)) {
940
+ return { thinking: { type: "disabled" } };
941
+ }
891
942
  if (!reasoning || !model.reasoning) return undefined;
892
943
 
893
944
  const mode = model.thinking?.mode;