@framers/agentos 0.10.22 → 0.10.24

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.
Files changed (204) hide show
  1. package/README.md +1 -1
  2. package/dist/api/AgentOS.d.ts.map +1 -1
  3. package/dist/api/AgentOS.js +3 -2
  4. package/dist/api/AgentOS.js.map +1 -1
  5. package/dist/api/agent.d.ts.map +1 -1
  6. package/dist/api/agent.js +25 -10
  7. package/dist/api/agent.js.map +1 -1
  8. package/dist/api/embedText.d.ts.map +1 -1
  9. package/dist/api/embedText.js +53 -17
  10. package/dist/api/embedText.js.map +1 -1
  11. package/dist/api/generateImage.d.ts.map +1 -1
  12. package/dist/api/generateImage.js +51 -7
  13. package/dist/api/generateImage.js.map +1 -1
  14. package/dist/api/generateText.d.ts +50 -7
  15. package/dist/api/generateText.d.ts.map +1 -1
  16. package/dist/api/generateText.js +265 -35
  17. package/dist/api/generateText.js.map +1 -1
  18. package/dist/api/model.d.ts +25 -1
  19. package/dist/api/model.d.ts.map +1 -1
  20. package/dist/api/model.js +108 -3
  21. package/dist/api/model.js.map +1 -1
  22. package/dist/api/runtime/SelfImprovementSessionManager.d.ts +34 -2
  23. package/dist/api/runtime/SelfImprovementSessionManager.d.ts.map +1 -1
  24. package/dist/api/runtime/SelfImprovementSessionManager.js +119 -33
  25. package/dist/api/runtime/SelfImprovementSessionManager.js.map +1 -1
  26. package/dist/api/runtime/global-default.d.ts +9 -1
  27. package/dist/api/runtime/global-default.d.ts.map +1 -1
  28. package/dist/api/runtime/global-default.js.map +1 -1
  29. package/dist/api/runtime/provider-defaults.d.ts.map +1 -1
  30. package/dist/api/runtime/provider-defaults.js +10 -4
  31. package/dist/api/runtime/provider-defaults.js.map +1 -1
  32. package/dist/api/runtime/tool-emulation/history.d.ts +16 -0
  33. package/dist/api/runtime/tool-emulation/history.d.ts.map +1 -0
  34. package/dist/api/runtime/tool-emulation/history.js +89 -0
  35. package/dist/api/runtime/tool-emulation/history.js.map +1 -0
  36. package/dist/api/runtime/tool-emulation/index.d.ts +1 -0
  37. package/dist/api/runtime/tool-emulation/index.d.ts.map +1 -1
  38. package/dist/api/runtime/tool-emulation/index.js +1 -0
  39. package/dist/api/runtime/tool-emulation/index.js.map +1 -1
  40. package/dist/api/runtime/tool-emulation/loop.d.ts +6 -0
  41. package/dist/api/runtime/tool-emulation/loop.d.ts.map +1 -1
  42. package/dist/api/runtime/tool-emulation/loop.js +1 -0
  43. package/dist/api/runtime/tool-emulation/loop.js.map +1 -1
  44. package/dist/api/sessionTranscript.d.ts +34 -0
  45. package/dist/api/sessionTranscript.d.ts.map +1 -1
  46. package/dist/api/sessionTranscript.js +61 -1
  47. package/dist/api/sessionTranscript.js.map +1 -1
  48. package/dist/api/streamText.d.ts.map +1 -1
  49. package/dist/api/streamText.js +120 -24
  50. package/dist/api/streamText.js.map +1 -1
  51. package/dist/api/types/AgentOSInput.d.ts +11 -0
  52. package/dist/api/types/AgentOSInput.d.ts.map +1 -1
  53. package/dist/api/types.d.ts +14 -6
  54. package/dist/api/types.d.ts.map +1 -1
  55. package/dist/api/types.js.map +1 -1
  56. package/dist/cognition/emergent/AdaptPersonalityTool.d.ts +13 -4
  57. package/dist/cognition/emergent/AdaptPersonalityTool.d.ts.map +1 -1
  58. package/dist/cognition/emergent/AdaptPersonalityTool.js +32 -23
  59. package/dist/cognition/emergent/AdaptPersonalityTool.js.map +1 -1
  60. package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts +18 -6
  61. package/dist/cognition/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
  62. package/dist/cognition/emergent/EmergentCapabilityEngine.js.map +1 -1
  63. package/dist/cognition/emergent/SelfEvaluateTool.d.ts +10 -3
  64. package/dist/cognition/emergent/SelfEvaluateTool.d.ts.map +1 -1
  65. package/dist/cognition/emergent/SelfEvaluateTool.js +3 -2
  66. package/dist/cognition/emergent/SelfEvaluateTool.js.map +1 -1
  67. package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts +51 -2
  68. package/dist/cognition/substrate/CognitiveMemoryBridge.d.ts.map +1 -1
  69. package/dist/cognition/substrate/CognitiveMemoryBridge.js +84 -2
  70. package/dist/cognition/substrate/CognitiveMemoryBridge.js.map +1 -1
  71. package/dist/cognition/substrate/ConversationHistoryManager.d.ts +8 -4
  72. package/dist/cognition/substrate/ConversationHistoryManager.d.ts.map +1 -1
  73. package/dist/cognition/substrate/ConversationHistoryManager.js +17 -5
  74. package/dist/cognition/substrate/ConversationHistoryManager.js.map +1 -1
  75. package/dist/cognition/substrate/GMI.d.ts +92 -5
  76. package/dist/cognition/substrate/GMI.d.ts.map +1 -1
  77. package/dist/cognition/substrate/GMI.js +291 -65
  78. package/dist/cognition/substrate/GMI.js.map +1 -1
  79. package/dist/cognition/substrate/GMIManager.d.ts +12 -0
  80. package/dist/cognition/substrate/GMIManager.d.ts.map +1 -1
  81. package/dist/cognition/substrate/GMIManager.js +22 -16
  82. package/dist/cognition/substrate/GMIManager.js.map +1 -1
  83. package/dist/cognition/substrate/IGMI.d.ts +23 -0
  84. package/dist/cognition/substrate/IGMI.d.ts.map +1 -1
  85. package/dist/cognition/substrate/IGMI.js.map +1 -1
  86. package/dist/cognition/substrate/MetapromptExecutor.d.ts +183 -28
  87. package/dist/cognition/substrate/MetapromptExecutor.d.ts.map +1 -1
  88. package/dist/cognition/substrate/MetapromptExecutor.js +431 -108
  89. package/dist/cognition/substrate/MetapromptExecutor.js.map +1 -1
  90. package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts +5 -0
  91. package/dist/cognition/substrate/personas/IPersonaDefinition.d.ts.map +1 -1
  92. package/dist/cognition/substrate/personas/PersonaValidation.d.ts.map +1 -1
  93. package/dist/cognition/substrate/personas/PersonaValidation.js +30 -0
  94. package/dist/cognition/substrate/personas/PersonaValidation.js.map +1 -1
  95. package/dist/cognition/substrate/personas/SoulLoader.d.ts +14 -1
  96. package/dist/cognition/substrate/personas/SoulLoader.d.ts.map +1 -1
  97. package/dist/cognition/substrate/personas/SoulLoader.js +45 -12
  98. package/dist/cognition/substrate/personas/SoulLoader.js.map +1 -1
  99. package/dist/cognition/substrate/personas/definitions/default_assistant_persona.json +3 -35
  100. package/dist/cognition/substrate/personas/hexaco.d.ts +34 -0
  101. package/dist/cognition/substrate/personas/hexaco.d.ts.map +1 -0
  102. package/dist/cognition/substrate/personas/hexaco.js +66 -0
  103. package/dist/cognition/substrate/personas/hexaco.js.map +1 -0
  104. package/dist/cognition/substrate/userFeedback.d.ts +69 -0
  105. package/dist/cognition/substrate/userFeedback.d.ts.map +1 -0
  106. package/dist/cognition/substrate/userFeedback.js +107 -0
  107. package/dist/cognition/substrate/userFeedback.js.map +1 -0
  108. package/dist/core/conversation/ConversationMessage.d.ts +9 -0
  109. package/dist/core/conversation/ConversationMessage.d.ts.map +1 -1
  110. package/dist/core/conversation/ConversationMessage.js.map +1 -1
  111. package/dist/core/llm/IPromptEngine.d.ts +9 -0
  112. package/dist/core/llm/IPromptEngine.d.ts.map +1 -1
  113. package/dist/core/llm/IPromptEngine.js.map +1 -1
  114. package/dist/core/llm/PromptEngine.d.ts +30 -1
  115. package/dist/core/llm/PromptEngine.d.ts.map +1 -1
  116. package/dist/core/llm/PromptEngine.js +229 -92
  117. package/dist/core/llm/PromptEngine.js.map +1 -1
  118. package/dist/core/llm/providers/AIModelProviderManager.d.ts +20 -0
  119. package/dist/core/llm/providers/AIModelProviderManager.d.ts.map +1 -1
  120. package/dist/core/llm/providers/AIModelProviderManager.js +35 -5
  121. package/dist/core/llm/providers/AIModelProviderManager.js.map +1 -1
  122. package/dist/core/llm/providers/IProvider.d.ts +28 -8
  123. package/dist/core/llm/providers/IProvider.d.ts.map +1 -1
  124. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts +135 -49
  125. package/dist/core/llm/providers/implementations/AnthropicProvider.d.ts.map +1 -1
  126. package/dist/core/llm/providers/implementations/AnthropicProvider.js +323 -114
  127. package/dist/core/llm/providers/implementations/AnthropicProvider.js.map +1 -1
  128. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.d.ts.map +1 -1
  129. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js +15 -1
  130. package/dist/core/llm/providers/implementations/ClaudeCodeProvider.js.map +1 -1
  131. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts +1 -1
  132. package/dist/core/llm/providers/implementations/GeminiCLIProvider.d.ts.map +1 -1
  133. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js +51 -24
  134. package/dist/core/llm/providers/implementations/GeminiCLIProvider.js.map +1 -1
  135. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts +25 -6
  136. package/dist/core/llm/providers/implementations/GeminiProvider.d.ts.map +1 -1
  137. package/dist/core/llm/providers/implementations/GeminiProvider.js +108 -90
  138. package/dist/core/llm/providers/implementations/GeminiProvider.js.map +1 -1
  139. package/dist/core/llm/providers/implementations/MistralProvider.d.ts +11 -0
  140. package/dist/core/llm/providers/implementations/MistralProvider.d.ts.map +1 -1
  141. package/dist/core/llm/providers/implementations/MistralProvider.js +32 -2
  142. package/dist/core/llm/providers/implementations/MistralProvider.js.map +1 -1
  143. package/dist/core/llm/providers/implementations/OllamaProvider.js +7 -7
  144. package/dist/core/llm/providers/implementations/OllamaProvider.js.map +1 -1
  145. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts +53 -2
  146. package/dist/core/llm/providers/implementations/OpenAIProvider.d.ts.map +1 -1
  147. package/dist/core/llm/providers/implementations/OpenAIProvider.js +84 -17
  148. package/dist/core/llm/providers/implementations/OpenAIProvider.js.map +1 -1
  149. package/dist/core/llm/providers/implementations/OpenRouterProvider.d.ts.map +1 -1
  150. package/dist/core/llm/providers/implementations/OpenRouterProvider.js +8 -3
  151. package/dist/core/llm/providers/implementations/OpenRouterProvider.js.map +1 -1
  152. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts +3 -3
  153. package/dist/core/llm/providers/implementations/TogetherProvider.d.ts.map +1 -1
  154. package/dist/core/llm/providers/implementations/TogetherProvider.js +25 -13
  155. package/dist/core/llm/providers/implementations/TogetherProvider.js.map +1 -1
  156. package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts +15 -7
  157. package/dist/core/llm/providers/implementations/cacheLeakDetector.d.ts.map +1 -1
  158. package/dist/core/llm/providers/implementations/cacheLeakDetector.js +12 -7
  159. package/dist/core/llm/providers/implementations/cacheLeakDetector.js.map +1 -1
  160. package/dist/core/llm/providers/model-cache-capabilities.d.ts +24 -9
  161. package/dist/core/llm/providers/model-cache-capabilities.d.ts.map +1 -1
  162. package/dist/core/llm/providers/model-cache-capabilities.js +59 -44
  163. package/dist/core/llm/providers/model-cache-capabilities.js.map +1 -1
  164. package/dist/core/llm/providers/model-effort.d.ts +14 -0
  165. package/dist/core/llm/providers/model-effort.d.ts.map +1 -1
  166. package/dist/core/llm/providers/model-effort.js +22 -0
  167. package/dist/core/llm/providers/model-effort.js.map +1 -1
  168. package/dist/core/llm/providers/model-forced-tool-choice.d.ts +13 -6
  169. package/dist/core/llm/providers/model-forced-tool-choice.d.ts.map +1 -1
  170. package/dist/core/llm/providers/model-forced-tool-choice.js +14 -7
  171. package/dist/core/llm/providers/model-forced-tool-choice.js.map +1 -1
  172. package/dist/core/llm/providers/model-thinking.d.ts +64 -13
  173. package/dist/core/llm/providers/model-thinking.d.ts.map +1 -1
  174. package/dist/core/llm/providers/model-thinking.js +52 -12
  175. package/dist/core/llm/providers/model-thinking.js.map +1 -1
  176. package/dist/core/llm/providers/openrouter-only-params.d.ts +26 -7
  177. package/dist/core/llm/providers/openrouter-only-params.d.ts.map +1 -1
  178. package/dist/core/llm/providers/openrouter-only-params.js +58 -9
  179. package/dist/core/llm/providers/openrouter-only-params.js.map +1 -1
  180. package/dist/core/llm/providers/url-secrets.d.ts +42 -0
  181. package/dist/core/llm/providers/url-secrets.d.ts.map +1 -0
  182. package/dist/core/llm/providers/url-secrets.js +64 -0
  183. package/dist/core/llm/providers/url-secrets.js.map +1 -0
  184. package/dist/core/llm/streaming/StreamingBatcher.d.ts +4 -3
  185. package/dist/core/llm/streaming/StreamingBatcher.d.ts.map +1 -1
  186. package/dist/core/llm/streaming/StreamingBatcher.js +8 -3
  187. package/dist/core/llm/streaming/StreamingBatcher.js.map +1 -1
  188. package/dist/core/safety/LLMProviderHealthRegistry.d.ts +4 -0
  189. package/dist/core/safety/LLMProviderHealthRegistry.d.ts.map +1 -1
  190. package/dist/core/safety/LLMProviderHealthRegistry.js +26 -0
  191. package/dist/core/safety/LLMProviderHealthRegistry.js.map +1 -1
  192. package/dist/core/utils/sha256.d.ts +19 -0
  193. package/dist/core/utils/sha256.d.ts.map +1 -0
  194. package/dist/core/utils/sha256.js +101 -0
  195. package/dist/core/utils/sha256.js.map +1 -0
  196. package/dist/index.d.ts +1 -1
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +1 -1
  199. package/dist/index.js.map +1 -1
  200. package/dist/orchestration/pipeline/query/types.js +3 -3
  201. package/dist/orchestration/pipeline/query/types.js.map +1 -1
  202. package/dist/orchestration/planning/ProviderAssignmentEngine.js +2 -2
  203. package/dist/orchestration/planning/ProviderAssignmentEngine.js.map +1 -1
  204. package/package.json +1 -1
@@ -1,14 +1,15 @@
1
1
  // File: backend/agentos/core/llm/providers/implementations/AnthropicProvider.ts
2
- import { stripOpenRouterOnlyParams } from '../openrouter-only-params.js';
2
+ import { stripForeignVendorParams } from '../openrouter-only-params.js';
3
3
  import { AnthropicProviderError } from '../errors/AnthropicProviderError.js';
4
4
  import { ApiKeyPool } from '../../../providers/ApiKeyPool.js';
5
- import { resolveThinkingPayload } from '../model-thinking.js';
5
+ import { resolveThinkingOff, resolveThinkingPayload } from '../model-thinking.js';
6
6
  import { modelSupportsForcedToolChoice } from '../model-forced-tool-choice.js';
7
7
  import { modelSupportsStrictToolUse, toolInputSchemaSupportsStrict, toolInputSchemaWithExplicitNoExtraProps, } from '../model-strict-tool-use.js';
8
- import { modelSupportsEffort, isEffortLevel } from '../model-effort.js';
8
+ import { resolveAnthropicEffort } from '../model-effort.js';
9
9
  import { computeRetryBackoffMs } from './retry-backoff.js';
10
10
  import { recordCacheUsage } from './cacheLeakDetector.js';
11
11
  import { resolveCacheCapabilities } from '../model-cache-capabilities.js';
12
+ import { redactUrlSecrets } from '../url-secrets.js';
12
13
  // Re-export so callers that already reach for Anthropic model-capability
13
14
  // predicates (modelSupportsTemperature lives here too) find this one next to
14
15
  // it, even though its source of truth is the pure model-forced-tool-choice
@@ -21,11 +22,13 @@ export { modelSupportsForcedToolChoice };
21
22
  * Whether the given Claude model id accepts the `temperature` parameter.
22
23
  *
23
24
  * Anthropic deprecated `temperature` on reasoning-default models. Opus 4.7,
24
- * Opus 4.8, Opus 5, Opus 5.5, Sonnet 5, Fable 5 and Fable 5.1 reject requests
25
- * that include it with HTTP 400 "`temperature` is deprecated for this model."
26
- * Opus 5, Opus 5.5 and Fable 5.1 were live-probed on 2026-09-29. Opus 5.5 and
27
- * Fable 5.1 match the existing `opus-5` and `fable-5` alternatives because
28
- * `\b` matches at the hyphen before their trailing version digit.
25
+ * Opus 4.8, Opus 5, Opus 5.5, Sonnet 5, Sonnet 5.5, Fable 5 and Fable 5.1
26
+ * reject requests that include it with HTTP 400 "`temperature` is deprecated
27
+ * for this model." Opus 5, Opus 5.5 and Fable 5.1 were live-probed on
28
+ * 2026-09-29 and Sonnet 5.5 on 2026-09-30. Opus 5.5, Sonnet 5.5 and Fable 5.1
29
+ * match the existing `opus-5`, `sonnet-5` and `fable-5` alternatives on
30
+ * purpose, because `\b` matches at the hyphen before their trailing version
31
+ * digit.
29
32
  * Every earlier Claude model (Opus ≤ 4.6, Sonnet 4.6 and earlier, Haiku) still accepts it.
30
33
  * The same family also rejects `top_p` / `top_k`, so {@link buildRequestPayload}
31
34
  * gates `top_p` on this predicate too.
@@ -43,8 +46,8 @@ export { modelSupportsForcedToolChoice };
43
46
  */
44
47
  export function modelSupportsTemperature(modelId) {
45
48
  // Reasoning-default models that reject `temperature` (and `top_p` / `top_k`):
46
- // Claude Opus 4.7 / 4.8 / 5 / 5.5, Sonnet 5, Fable 5 / 5.1, and any dated
47
- // variant. Future reasoning-first siblings get added here as Anthropic
49
+ // Claude Opus 4.7 / 4.8 / 5 / 5.5, Sonnet 5 / 5.5, Fable 5 / 5.1, and any
50
+ // dated variant. Future reasoning-first siblings get added here as Anthropic
48
51
  // releases them, in lockstep with modelSupportsThinking.
49
52
  return !/^claude-(opus-4-(7|8)|opus-5|sonnet-5|fable-5)\b/i.test(modelId);
50
53
  }
@@ -77,17 +80,22 @@ export function normalizeCacheDiagnostics(diagnostics) {
77
80
  /**
78
81
  * Static catalog of well-known Anthropic models and their metadata.
79
82
  *
80
- * Pricing verified against anthropic.com/pricing on 2026-04-16 (USD per 1M tokens).
81
- * Update when Anthropic publishes new rate cards.
83
+ * Prices are USD per 1M tokens, verified against Anthropic's pricing page and
84
+ * model pages on 2026-09-30. A cache hit costs 0.1x the input price except
85
+ * where a row sets `pricePer1MTokensCacheRead`: Opus 5.5 (0.05x) and Fable 5.1
86
+ * (0.025x) differ, and Fable 5 states its standard 0.1x rate explicitly so
87
+ * the Fable 5.1 rate is not mistaken for it. Cache writes cost 1.25x the input
88
+ * price at the 5-minute TTL and 2x at the 1-hour TTL on every model; see
89
+ * {@link estimateAnthropicCostUSD}.
82
90
  *
83
91
  * `outputTokenLimit` is the model's real max output ceiling, surfaced via
84
- * getModelInfo for callers that want to size requests. It is informational —
85
- * NOT the per-request default: when a caller omits `maxTokens`, the request
86
- * falls back to `config.defaultMaxTokens`, not this value — but a per-call
87
- * `maxTokens` IS clamped to it via {@link clampAnthropicMaxTokens}. Anthropic
88
- * specs: Fable 5 = 128K output / 1M context, Opus 5 / 4.x = 128K output / 1M
89
- * context, Sonnet 5 / 4.6 = 128K output / 1M context, Sonnet 4.5 = 64K / 200K,
90
- * Haiku 4.5 = 64K output / 200K.
92
+ * getModelInfo for callers that want to size requests. It is informational,
93
+ * not the per-request default: when a caller omits `maxTokens`, the request
94
+ * falls back to `config.defaultMaxTokens`, but a per-call `maxTokens` is
95
+ * clamped to it via {@link clampAnthropicMaxTokens}. Anthropic's limits:
96
+ * Opus 5.5, Opus 5, Opus 4.6-4.8, Fable 5.1, Fable 5, Sonnet 5.5, Sonnet 5 and
97
+ * Sonnet 4.6 take 128K output with a 1M context; Opus 4.5, Sonnet 4.5 and
98
+ * Haiku 4.5 take 64K output with a 200K context.
91
99
  */
92
100
  const ANTHROPIC_MODELS = [
93
101
  {
@@ -100,6 +108,8 @@ const ANTHROPIC_MODELS = [
100
108
  outputTokenLimit: 128000,
101
109
  pricePer1MTokensInput: 4,
102
110
  pricePer1MTokensOutput: 20,
111
+ // Cache hits and refreshes bill at 0.05x the input price.
112
+ pricePer1MTokensCacheRead: 0.2,
103
113
  supportsStreaming: true,
104
114
  status: 'active',
105
115
  },
@@ -113,6 +123,8 @@ const ANTHROPIC_MODELS = [
113
123
  outputTokenLimit: 128000,
114
124
  pricePer1MTokensInput: 10,
115
125
  pricePer1MTokensOutput: 50,
126
+ // Cache hits and refreshes bill at 0.025x the input price.
127
+ pricePer1MTokensCacheRead: 0.25,
116
128
  supportsStreaming: true,
117
129
  status: 'active',
118
130
  },
@@ -126,6 +138,8 @@ const ANTHROPIC_MODELS = [
126
138
  outputTokenLimit: 128000,
127
139
  pricePer1MTokensInput: 10,
128
140
  pricePer1MTokensOutput: 50,
141
+ // The standard 0.1x rate. Fable 5.1's 0.025x does not apply to Fable 5.
142
+ pricePer1MTokensCacheRead: 1,
129
143
  supportsStreaming: true,
130
144
  status: 'active',
131
145
  },
@@ -181,6 +195,35 @@ const ANTHROPIC_MODELS = [
181
195
  supportsStreaming: true,
182
196
  status: 'active',
183
197
  },
198
+ {
199
+ // The dated id is the canonical row: the bare alias `claude-opus-4-5`
200
+ // resolves to it through the prefix fallback in resolveModelCatalogEntry,
201
+ // as `claude-haiku-4-5` resolves to the dated Haiku row.
202
+ modelId: 'claude-opus-4-5-20251101',
203
+ providerId: 'anthropic',
204
+ displayName: 'Claude Opus 4.5',
205
+ description: 'Legacy Opus, still served. Superseded by Claude Opus 5.5.',
206
+ capabilities: ['chat', 'tool_use', 'vision_input'],
207
+ contextWindowSize: 200000,
208
+ outputTokenLimit: 64000,
209
+ pricePer1MTokensInput: 5,
210
+ pricePer1MTokensOutput: 25,
211
+ supportsStreaming: true,
212
+ status: 'active',
213
+ },
214
+ {
215
+ modelId: 'claude-sonnet-5-5',
216
+ providerId: 'anthropic',
217
+ displayName: 'Claude Sonnet 5.5',
218
+ description: 'Current Sonnet for everyday coding, agent and enterprise work; adaptive thinking on by default.',
219
+ capabilities: ['chat', 'tool_use', 'vision_input'],
220
+ contextWindowSize: 1000000,
221
+ outputTokenLimit: 128000,
222
+ pricePer1MTokensInput: 2,
223
+ pricePer1MTokensOutput: 10,
224
+ supportsStreaming: true,
225
+ status: 'active',
226
+ },
184
227
  {
185
228
  modelId: 'claude-sonnet-5',
186
229
  providerId: 'anthropic',
@@ -189,13 +232,14 @@ const ANTHROPIC_MODELS = [
189
232
  capabilities: ['chat', 'tool_use', 'vision_input'],
190
233
  contextWindowSize: 1000000,
191
234
  outputTokenLimit: 128000,
192
- // Sticker rates ($2/$10 intro pricing runs through 2026-08-31 —
193
- // meter at sticker so cost rollups stay conservative). Absent from
194
- // this table the model priced as costUSD undefined, which turned
195
- // the 2026-07-20..26 quota-outage fallback traffic (thousands of
196
- // sonnet-5 leg calls/day) into unmetered spend.
197
- pricePer1MTokensInput: 3,
198
- pricePer1MTokensOutput: 15,
235
+ // $2/$10 is the standard price: Anthropic kept its launch pricing and
236
+ // cancelled the increase to $3/$15 scheduled for 2026-09-01 (pricing
237
+ // page, 2026-09-30). The row must stay in this table: without it the
238
+ // model prices as costUSD undefined, which turned the 2026-07-20..26
239
+ // quota-outage fallback traffic (thousands of sonnet-5 leg calls a day)
240
+ // into unmetered spend.
241
+ pricePer1MTokensInput: 2,
242
+ pricePer1MTokensOutput: 10,
199
243
  supportsStreaming: true,
200
244
  status: 'active',
201
245
  },
@@ -293,9 +337,10 @@ export function clampAnthropicMaxTokens(modelId, requested) {
293
337
  * Resolves a caller-supplied model id to its ANTHROPIC_MODELS catalog row.
294
338
  * Exact id match wins; otherwise a dated snapshot (`claude-sonnet-5-20260101`)
295
339
  * or bare alias is matched by prefix — the SAME resolution the max-tokens
296
- * clamp uses, shared so pricing and clamping can never disagree about which
297
- * row a model id means (estimateCost's exact-only match priced every dated
298
- * snapshot id as costUSD undefined: unmetered spend).
340
+ * clamp uses, shared so pricing ({@link estimateAnthropicCostUSD}) and
341
+ * clamping can never disagree about which row a model id means (an
342
+ * exact-only match prices every dated snapshot id as costUSD undefined:
343
+ * unmetered spend).
299
344
  *
300
345
  * The longest catalog id that prefixes `modelId` wins. Catalog ids nest:
301
346
  * `claude-opus-5` is a prefix of `claude-opus-5-5`. A first-match scan makes
@@ -315,9 +360,13 @@ export function resolveAnthropicModelEntry(modelId) {
315
360
  * @param catalog Model rows to search, in any order.
316
361
  * @param modelId Caller-supplied model id, bare or dated.
317
362
  * @returns The exact row, else the row with the longest id that prefixes
318
- * `modelId`, else the first row whose id starts with `modelId`.
363
+ * `modelId`, else the first row whose id starts with `modelId`. An empty id
364
+ * resolves to nothing: every catalog id starts with `''`, so the last rule
365
+ * would otherwise price a response with no model echo as the first row.
319
366
  */
320
367
  export function resolveModelCatalogEntry(catalog, modelId) {
368
+ if (!modelId)
369
+ return undefined;
321
370
  const exact = catalog.find((m) => m.modelId === modelId);
322
371
  if (exact)
323
372
  return exact;
@@ -329,9 +378,64 @@ export function resolveModelCatalogEntry(catalog, modelId) {
329
378
  }
330
379
  return longest ?? catalog.find((m) => m.modelId.startsWith(modelId));
331
380
  }
381
+ /**
382
+ * Estimates the USD cost of one Anthropic Messages API call from its usage.
383
+ *
384
+ * | Tokens | Price |
385
+ * | --- | --- |
386
+ * | `input_tokens` | the input price |
387
+ * | `cache_read_input_tokens` | the row's `pricePer1MTokensCacheRead`, else 0.1x the input price |
388
+ * | 5-minute cache writes | 1.25x the input price |
389
+ * | 1-hour cache writes | 2x the input price |
390
+ * | `output_tokens` | the output price |
391
+ *
392
+ * `input_tokens` excludes cached tokens, so the input components add up
393
+ * without double counting. The 1-hour share of the writes comes from
394
+ * `cache_creation.ephemeral_1h_input_tokens`: a response without that split
395
+ * is priced as 5-minute writes only, and a 1-hour count above the write total
396
+ * is capped at the total.
397
+ *
398
+ * @param modelId Model id, bare or dated, resolved with
399
+ * {@link resolveAnthropicModelEntry}.
400
+ * @param usage Token counts from the response.
401
+ * @returns The cost in USD, or `undefined` when the catalog has no price for
402
+ * the model (unknown, empty or unpriced id).
403
+ */
404
+ export function estimateAnthropicCostUSD(modelId, usage) {
405
+ const info = resolveAnthropicModelEntry(modelId);
406
+ const inputPrice = info?.pricePer1MTokensInput;
407
+ const outputPrice = info?.pricePer1MTokensOutput;
408
+ if (!inputPrice || !outputPrice)
409
+ return undefined;
410
+ const cacheReadPrice = info?.pricePer1MTokensCacheRead ?? inputPrice * 0.1;
411
+ const split = usage.cache_creation ?? undefined;
412
+ const written = usage.cache_creation_input_tokens
413
+ ?? ((split?.ephemeral_5m_input_tokens ?? 0) + (split?.ephemeral_1h_input_tokens ?? 0));
414
+ const writtenOneHour = Math.min(Math.max(split?.ephemeral_1h_input_tokens ?? 0, 0), written);
415
+ // Tokens times USD per 1M tokens: the sum is in millionths of a dollar.
416
+ const microDollars = usage.input_tokens * inputPrice
417
+ + (usage.cache_read_input_tokens ?? 0) * cacheReadPrice
418
+ + (written - writtenOneHour) * inputPrice * 1.25
419
+ + writtenOneHour * inputPrice * 2
420
+ + usage.output_tokens * outputPrice;
421
+ return microDollars / 1000000;
422
+ }
332
423
  // ---------------------------------------------------------------------------
333
424
  // Provider implementation
334
425
  // ---------------------------------------------------------------------------
426
+ /** Keys of warnings already printed by {@link warnOnce}. */
427
+ const warnedKeys = new Set();
428
+ /**
429
+ * Prints `message` once per process for `key`, so a setting applied to every
430
+ * request (thinking off on an always-on model, a capped effort) warns once
431
+ * rather than on every call.
432
+ */
433
+ function warnOnce(key, message) {
434
+ if (warnedKeys.has(key))
435
+ return;
436
+ warnedKeys.add(key);
437
+ console.warn(message);
438
+ }
335
439
  /**
336
440
  * @class AnthropicProvider
337
441
  * @implements {IProvider}
@@ -427,7 +531,10 @@ export class AnthropicProvider {
427
531
  * @param {ModelCompletionOptions} options - Completion options. `maxTokens` is strongly
428
532
  * recommended; defaults to {@link AnthropicProviderConfig.defaultMaxTokens} if omitted.
429
533
  * @returns {Promise<ModelCompletionResponse>} A normalized completion response.
430
- * @throws {AnthropicProviderError} On authentication, validation, or network errors.
534
+ * @throws {AnthropicProviderError} On authentication, validation, or network
535
+ * errors, and with code `content_filter` when the model refuses
536
+ * (`stop_reason: 'refusal'`); partial output from a refused turn is
537
+ * discarded.
431
538
  */
432
539
  async generateCompletion(modelId, messages, options) {
433
540
  this.ensureInitialized();
@@ -442,8 +549,8 @@ export class AnthropicProvider {
442
549
  if (this.config.streamCompletions === false) {
443
550
  const payload = this.buildRequestPayload(modelId, messages, options, false);
444
551
  const apiResponse = await this.makeApiRequest('/v1/messages', 'POST', payload, options.requestTimeout);
445
- this.recordCacheLeakSample(modelId, payload, apiResponse);
446
- return this.mapResponseToCompletion(apiResponse, structuredOutputName);
552
+ this.recordCacheLeakSample(modelId, payload, apiResponse, options.cache === false);
553
+ return this.mapResponseToCompletion(apiResponse, structuredOutputName, modelId);
447
554
  }
448
555
  // Default: ride the SSE path so parseSseStream's idle watchdog bounds
449
556
  // mid-body stalls. A non-streaming POST cannot tell a hung connection
@@ -454,8 +561,8 @@ export class AnthropicProvider {
454
561
  // let the caller (or the retry loop below) recover in seconds.
455
562
  const payload = this.buildRequestPayload(modelId, messages, options, true);
456
563
  const apiResponse = await this.streamMessagesToResponse(payload, options.requestTimeout);
457
- this.recordCacheLeakSample(modelId, payload, apiResponse);
458
- return this.mapResponseToCompletion(apiResponse, structuredOutputName);
564
+ this.recordCacheLeakSample(modelId, payload, apiResponse, options.cache === false);
565
+ return this.mapResponseToCompletion(apiResponse, structuredOutputName, modelId);
459
566
  }
460
567
  /**
461
568
  * Feed one request's cache usage to the leak detector (see
@@ -464,9 +571,12 @@ export class AnthropicProvider {
464
571
  * 256 chars of the system prompt, read from the just-built payload so it
465
572
  * reflects what actually went to the wire. Fail-open by construction.
466
573
  *
574
+ * @param cacheOptOut True when the caller turned caching off for this
575
+ * request (`options.cache === false`). Its uncached input is deliberate,
576
+ * so the detector does not count it toward the `unmarked` warning.
467
577
  * @private
468
578
  */
469
- recordCacheLeakSample(modelId, payload, apiResponse) {
579
+ recordCacheLeakSample(modelId, payload, apiResponse, cacheOptOut = false) {
470
580
  try {
471
581
  const system = payload.system;
472
582
  // Bound both branches to the 256-char callsite-identity window the
@@ -483,6 +593,7 @@ export class AnthropicProvider {
483
593
  uncachedInputTokens: apiResponse.usage?.input_tokens ?? 0,
484
594
  cacheReadTokens: apiResponse.usage?.cache_read_input_tokens ?? 0,
485
595
  cacheCreationTokens: apiResponse.usage?.cache_creation_input_tokens ?? 0,
596
+ ...(cacheOptOut ? { cacheOptOut: true } : {}),
486
597
  });
487
598
  }
488
599
  catch {
@@ -564,6 +675,11 @@ export class AnthropicProvider {
564
675
  * {@link mapResponseToCompletion} stays the single mapping path
565
676
  * (structured output, stop reasons, cost, thinking blocks).
566
677
  *
678
+ * A refusal returns normally with `stop_reason: 'refusal'` and its
679
+ * `stop_details`, even when it cut a tool_use input off mid-JSON:
680
+ * {@link mapResponseToCompletion} raises it as a content-policy error, and
681
+ * a transport error here would be retried instead.
682
+ *
567
683
  * @throws {AnthropicProviderError} `STREAM_ERROR_EVENT` on an SSE error
568
684
  * event, `STREAM_INCOMPLETE` when the stream ends before `message_delta`
569
685
  * or a tool_use input JSON is truncated, plus whatever
@@ -574,10 +690,12 @@ export class AnthropicProvider {
574
690
  let id = `anthropic-stream-${Date.now()}`;
575
691
  let model = '';
576
692
  let stopReason = null;
693
+ let stopDetails;
577
694
  let inputTokens = 0;
578
695
  let outputTokens = 0;
579
696
  let cacheCreationTokens;
580
697
  let cacheReadTokens;
698
+ let cacheCreationSplit;
581
699
  let diagnostics;
582
700
  let sawMessageDelta = false;
583
701
  const blocks = new Map();
@@ -597,6 +715,8 @@ export class AnthropicProvider {
597
715
  inputTokens = event.message.usage?.input_tokens ?? 0;
598
716
  cacheCreationTokens = event.message.usage?.cache_creation_input_tokens;
599
717
  cacheReadTokens = event.message.usage?.cache_read_input_tokens;
718
+ // The 5m/1h split of the cache writes prices the 1h share at 2x.
719
+ cacheCreationSplit = event.message.usage?.cache_creation;
600
720
  // Cache diagnostics ride message_start in streaming responses.
601
721
  diagnostics = event.message.diagnostics;
602
722
  break;
@@ -647,6 +767,7 @@ export class AnthropicProvider {
647
767
  case 'message_delta': {
648
768
  sawMessageDelta = true;
649
769
  stopReason = (event.delta.stop_reason ?? null);
770
+ stopDetails = event.delta.stop_details ?? stopDetails;
650
771
  // Anthropic reports the cumulative output total here — latest wins.
651
772
  outputTokens = event.usage?.output_tokens ?? outputTokens;
652
773
  break;
@@ -663,6 +784,9 @@ export class AnthropicProvider {
663
784
  throw new AnthropicProviderError('Stream ended before message_delta — response incomplete (connection dropped mid-message).', 'STREAM_INCOMPLETE');
664
785
  }
665
786
  const content = Array.from(blocks.entries())
787
+ // A refusal can cut a tool input off mid-JSON. The refused turn is
788
+ // discarded whole, so its tool calls are dropped rather than parsed.
789
+ .filter(([, block]) => stopReason !== 'refusal' || block.type !== 'tool_use')
666
790
  .sort((a, b) => a[0] - b[0])
667
791
  .map(([, block]) => {
668
792
  if (block.type === 'tool_use') {
@@ -691,11 +815,13 @@ export class AnthropicProvider {
691
815
  model,
692
816
  stop_reason: stopReason,
693
817
  stop_sequence: null,
818
+ ...(stopDetails !== undefined && { stop_details: stopDetails }),
694
819
  usage: {
695
820
  input_tokens: inputTokens,
696
821
  output_tokens: outputTokens,
697
822
  ...(cacheCreationTokens !== undefined && { cache_creation_input_tokens: cacheCreationTokens }),
698
823
  ...(cacheReadTokens !== undefined && { cache_read_input_tokens: cacheReadTokens }),
824
+ ...(cacheCreationSplit != null && { cache_creation: cacheCreationSplit }),
699
825
  },
700
826
  ...(diagnostics !== undefined && { diagnostics }),
701
827
  };
@@ -718,7 +844,10 @@ export class AnthropicProvider {
718
844
  * @param {ChatMessage[]} messages - Conversation messages.
719
845
  * @param {ModelCompletionOptions} options - Completion options.
720
846
  * @returns {AsyncGenerator<ModelCompletionResponse>} Incremental response chunks.
721
- * @throws {AnthropicProviderError} On connection or stream errors.
847
+ * @throws {AnthropicProviderError} On connection errors, and with code
848
+ * `content_filter` when the model refuses (`stop_reason: 'refusal'`),
849
+ * before or after text has streamed. Other stream errors end the stream
850
+ * with an error chunk.
722
851
  */
723
852
  async *generateCompletionStream(modelId, messages, options) {
724
853
  this.ensureInitialized();
@@ -737,6 +866,8 @@ export class AnthropicProvider {
737
866
  let outputTokens = 0;
738
867
  let cacheCreationTokens;
739
868
  let cacheReadTokens;
869
+ /** The 5m/1h split of the cache writes, reported on message_start. */
870
+ let cacheCreationSplit;
740
871
  /**
741
872
  * Cache-diagnostics verdict, delivered ONLY on message_start (no later
742
873
  * SSE event repeats it). `undefined` = the API sent none (not opted in);
@@ -779,6 +910,7 @@ export class AnthropicProvider {
779
910
  // accumulate path at streamToCompletion does the same).
780
911
  cacheCreationTokens = event.message.usage?.cache_creation_input_tokens;
781
912
  cacheReadTokens = event.message.usage?.cache_read_input_tokens;
913
+ cacheCreationSplit = event.message.usage?.cache_creation;
782
914
  // Cache diagnostics ride message_start in streaming responses
783
915
  // (mirrors the streamToCompletion aggregate path).
784
916
  streamDiagnostics = event.message.diagnostics;
@@ -894,8 +1026,14 @@ export class AnthropicProvider {
894
1026
  // Cache-aware like the non-streaming path: input_tokens
895
1027
  // excludes cached tokens, so dropping the cache counts here
896
1028
  // undercosted every streamed turn once automatic caching
897
- // landed (reads 0.10x + writes 1.25x are real spend).
898
- costUSD: this.estimateCost(inputTokens, outputTokens, modelId, cacheReadTokens, cacheCreationTokens),
1029
+ // landed (cache reads and writes are real spend).
1030
+ costUSD: estimateAnthropicCostUSD(modelId, {
1031
+ input_tokens: inputTokens,
1032
+ output_tokens: outputTokens,
1033
+ cache_read_input_tokens: cacheReadTokens,
1034
+ cache_creation_input_tokens: cacheCreationTokens,
1035
+ cache_creation: cacheCreationSplit,
1036
+ }),
899
1037
  ...(cacheCreationTokens !== undefined && {
900
1038
  cacheCreationInputTokens: cacheCreationTokens,
901
1039
  }),
@@ -925,7 +1063,14 @@ export class AnthropicProvider {
925
1063
  cache_read_input_tokens: cacheReadTokens,
926
1064
  }),
927
1065
  },
928
- });
1066
+ }, options.cache === false);
1067
+ // A refusal ends the turn unsuccessfully, whatever streamed before
1068
+ // it. Anthropic's contract is to discard the partial output; a
1069
+ // final chunk would let the caller keep it as the answer and run
1070
+ // tool calls parsed out of it.
1071
+ if (event.delta.stop_reason === 'refusal') {
1072
+ throw this.refusalError(modelId, event.delta.stop_details, accumulatedContent, usage);
1073
+ }
929
1074
  yield {
930
1075
  id: responseId,
931
1076
  object: 'chat.completion.chunk',
@@ -974,6 +1119,12 @@ export class AnthropicProvider {
974
1119
  }
975
1120
  }
976
1121
  catch (streamError) {
1122
+ // A refusal propagates as a thrown content-policy error, as
1123
+ // GeminiProvider's safety block does, so the fallback chain can act on
1124
+ // it instead of reading an error chunk.
1125
+ if (streamError instanceof AnthropicProviderError && streamError.code === 'content_filter') {
1126
+ throw streamError;
1127
+ }
977
1128
  const message = streamError instanceof Error ? streamError.message : 'Anthropic stream processing error';
978
1129
  console.error(`AnthropicProvider stream error for model ${modelId}:`, message);
979
1130
  yield {
@@ -1234,14 +1385,39 @@ export class AnthropicProvider {
1234
1385
  delete payload.temperature;
1235
1386
  delete payload.top_p;
1236
1387
  }
1388
+ // --- Thinking off (options.thinking === false) ---
1389
+ // Leaving the field out keeps the model's default, which is thinking ON
1390
+ // for Opus 5 and Sonnet 5+, so turning it off takes the model's own
1391
+ // shape. Some models accept that shape only at effort high or below; the
1392
+ // effort block below caps it there.
1393
+ let thinkingOffEffortCap;
1394
+ if (!thinkingResolved && options.thinking === false) {
1395
+ const off = resolveThinkingOff(modelId);
1396
+ if (off.kind === 'send') {
1397
+ payload.thinking = off.thinking;
1398
+ thinkingOffEffortCap = off.maxEffort;
1399
+ }
1400
+ else if (off.kind === 'always_on') {
1401
+ warnOnce(`thinking-always-on:${modelId}`, `[agentos] AnthropicProvider: thinking cannot be turned off on ${modelId}; the request runs ` +
1402
+ `with adaptive thinking. Lower options.effort to reduce it.`);
1403
+ }
1404
+ }
1237
1405
  // --- Effort (output_config.effort) ---
1238
1406
  // Reasoning depth + token-spend control on effort-capable Claude models
1239
1407
  // (Opus 4.5+/Sonnet 4.6/Fable/Mythos). Independent of thinking + tool_choice
1240
- // — it rides on output_config. Dropped on unsupported models OR invalid
1241
- // values so an out-of-range effort can never 400 the request.
1242
- if (options.effort && isEffortLevel(options.effort) && modelSupportsEffort(modelId)) {
1408
+ // — it rides on output_config. Dropped on unsupported models or invalid
1409
+ // values, and lowered to a level the model takes, so an out-of-range
1410
+ // effort never returns 400 (see resolveAnthropicEffort).
1411
+ let effort = resolveAnthropicEffort(modelId, options.effort);
1412
+ if (thinkingOffEffortCap && (effort === 'xhigh' || effort === 'max')) {
1413
+ warnOnce(`thinking-off-effort:${modelId}`, `[agentos] AnthropicProvider: ${modelId} turns thinking off only at effort ` +
1414
+ `${thinkingOffEffortCap} or below; sending effort '${thinkingOffEffortCap}' ` +
1415
+ `instead of '${effort}'.`);
1416
+ effort = thinkingOffEffortCap;
1417
+ }
1418
+ if (effort) {
1243
1419
  const oc = payload.output_config ?? {};
1244
- payload.output_config = { ...oc, effort: options.effort };
1420
+ payload.output_config = { ...oc, effort };
1245
1421
  }
1246
1422
  // --- Cache diagnostics (beta cache-diagnosis-2026-04-07) ---
1247
1423
  // Opt-in observability: the API compares this request against the one
@@ -1332,8 +1508,9 @@ export class AnthropicProvider {
1332
1508
  payload.tool_choice = { type: 'tool', name: sf.tool.name };
1333
1509
  }
1334
1510
  // Pass through any custom model params — minus OpenRouter's routing
1335
- // controls, which only OpenRouter's body accepts (a fallback leg reuses
1336
- // the same params object across hosts; see openrouter-only-params).
1511
+ // controls and Gemini's request fields, which only those vendors' bodies
1512
+ // accept (a fallback leg reuses the same params object across hosts; see
1513
+ // openrouter-only-params).
1337
1514
  //
1338
1515
  // This runs BEFORE the forced tool_choice reconciliation below so that a
1339
1516
  // `tool_choice` injected through customModelParams is reconciled too —
@@ -1348,7 +1525,7 @@ export class AnthropicProvider {
1348
1525
  let customSystemOverride = false;
1349
1526
  let customMessagesOverride = false;
1350
1527
  {
1351
- const passthrough = stripOpenRouterOnlyParams(options.customModelParams);
1528
+ const passthrough = stripForeignVendorParams(options.customModelParams);
1352
1529
  if (passthrough) {
1353
1530
  customSystemOverride = Object.prototype.hasOwnProperty.call(passthrough, 'system');
1354
1531
  customMessagesOverride = Object.prototype.hasOwnProperty.call(passthrough, 'messages');
@@ -1372,7 +1549,10 @@ export class AnthropicProvider {
1372
1549
  {
1373
1550
  const tc = payload.tool_choice;
1374
1551
  if (tc && (tc.type === 'any' || tc.type === 'tool')) {
1375
- const thinkingActive = Boolean(payload.thinking);
1552
+ // Only a thinking-on shape conflicts with a forced tool_choice; the
1553
+ // off shapes (disabled, between_tools) do not.
1554
+ const thinkingType = payload.thinking?.type;
1555
+ const thinkingActive = thinkingType === 'adaptive' || thinkingType === 'enabled';
1376
1556
  const modelRejectsForced = !modelSupportsForcedToolChoice(modelId);
1377
1557
  if (thinkingActive || modelRejectsForced) {
1378
1558
  const reason = thinkingActive
@@ -1756,6 +1936,30 @@ export class AnthropicProvider {
1756
1936
  }],
1757
1937
  };
1758
1938
  }
1939
+ // --- Assistant text turn that carries thinking ---
1940
+ // A session transcript records a final answer's signed thinking; it
1941
+ // replays verbatim ahead of the answer's content, like a tool turn's.
1942
+ // The content converts exactly as it would without thinking, so text
1943
+ // parts keep their cache_control breakpoints. For models that do not
1944
+ // retain prior thinking, the caller strips it from every assistant turn
1945
+ // but the last one before this runs.
1946
+ if (msg.role === 'assistant' && msg.thinkingBlocks?.length) {
1947
+ const rest = this.toAnthropicMessage({ ...msg, thinkingBlocks: undefined });
1948
+ const restContent = rest.content;
1949
+ const blocks = Array.isArray(restContent)
1950
+ ? restContent
1951
+ : typeof restContent === 'string' && restContent
1952
+ ? [{ type: 'text', text: restContent }]
1953
+ : [];
1954
+ // An answer with no content keeps the plain shape: Anthropic rejects an
1955
+ // assistant turn that holds only thinking.
1956
+ if (blocks.length === 0)
1957
+ return rest;
1958
+ const thinking = msg.thinkingBlocks.map((tb) => tb.type === 'thinking'
1959
+ ? { type: 'thinking', thinking: tb.thinking, signature: tb.signature }
1960
+ : { type: 'redacted_thinking', data: tb.data });
1961
+ return { role: 'assistant', content: [...thinking, ...blocks] };
1962
+ }
1759
1963
  // --- Multimodal content (vision) ---
1760
1964
  if (Array.isArray(msg.content)) {
1761
1965
  const anthropicContent = [];
@@ -1865,15 +2069,43 @@ export class AnthropicProvider {
1865
2069
  * the stop reason from Anthropic's vocabulary to IProvider conventions.
1866
2070
  *
1867
2071
  * @param {AnthropicMessagesResponse} apiResponse - Raw Anthropic response.
2072
+ * @param structuredOutputName - Name of the forced structured-output tool,
2073
+ * whose input becomes the message content.
2074
+ * @param requestedModelId - The model the request named. Prices the call
2075
+ * when the response carries no model echo.
1868
2076
  * @returns {ModelCompletionResponse} Normalized completion response.
2077
+ * @throws {AnthropicProviderError} With code `content_filter` when the model
2078
+ * refused (`stop_reason: 'refusal'`), whether or not it wrote any output
2079
+ * first.
1869
2080
  * @private
1870
2081
  */
1871
- mapResponseToCompletion(apiResponse, structuredOutputName) {
2082
+ mapResponseToCompletion(apiResponse, structuredOutputName, requestedModelId) {
2083
+ const pricedModelId = apiResponse.model || requestedModelId || '';
1872
2084
  // Collect text content
1873
2085
  const textParts = apiResponse.content
1874
2086
  .filter(block => block.type === 'text' && block.text)
1875
2087
  .map(block => block.text);
1876
2088
  let fullText = textParts.join('');
2089
+ const usage = {
2090
+ promptTokens: apiResponse.usage.input_tokens,
2091
+ completionTokens: apiResponse.usage.output_tokens,
2092
+ totalTokens: apiResponse.usage.input_tokens + apiResponse.usage.output_tokens,
2093
+ costUSD: estimateAnthropicCostUSD(pricedModelId, apiResponse.usage),
2094
+ cacheCreationInputTokens: apiResponse.usage.cache_creation_input_tokens,
2095
+ cacheReadInputTokens: apiResponse.usage.cache_read_input_tokens,
2096
+ // Anthropic's input_tokens EXCLUDES cache reads/writes; the
2097
+ // provider-independent inclusive input total adds them back
2098
+ // (OpenAI/OpenRouter report prompt_tokens already inclusive).
2099
+ inclusiveInputTokens: apiResponse.usage.input_tokens
2100
+ + (apiResponse.usage.cache_read_input_tokens ?? 0)
2101
+ + (apiResponse.usage.cache_creation_input_tokens ?? 0),
2102
+ };
2103
+ // A refused turn is discarded whole, even when the model wrote text or
2104
+ // tool_use blocks before refusing, so it never reaches tool-call mapping
2105
+ // and the caller can never run a tool from it.
2106
+ if (apiResponse.stop_reason === 'refusal') {
2107
+ throw this.refusalError(pricedModelId, apiResponse.stop_details, fullText, usage);
2108
+ }
1877
2109
  // Collect tool_use blocks and convert to OpenAI-style tool_calls
1878
2110
  const toolCalls = apiResponse.content
1879
2111
  .filter(block => block.type === 'tool_use')
@@ -1913,20 +2145,6 @@ export class AnthropicProvider {
1913
2145
  }
1914
2146
  const hasToolCalls = toolCalls.length > 0;
1915
2147
  const finishReason = this.mapStopReason(apiResponse.stop_reason);
1916
- const usage = {
1917
- promptTokens: apiResponse.usage.input_tokens,
1918
- completionTokens: apiResponse.usage.output_tokens,
1919
- totalTokens: apiResponse.usage.input_tokens + apiResponse.usage.output_tokens,
1920
- costUSD: this.estimateCost(apiResponse.usage.input_tokens, apiResponse.usage.output_tokens, apiResponse.model, apiResponse.usage.cache_read_input_tokens, apiResponse.usage.cache_creation_input_tokens),
1921
- cacheCreationInputTokens: apiResponse.usage.cache_creation_input_tokens,
1922
- cacheReadInputTokens: apiResponse.usage.cache_read_input_tokens,
1923
- // Anthropic's input_tokens EXCLUDES cache reads/writes; the
1924
- // provider-independent inclusive input total adds them back
1925
- // (OpenAI/OpenRouter report prompt_tokens already inclusive).
1926
- inclusiveInputTokens: apiResponse.usage.input_tokens
1927
- + (apiResponse.usage.cache_read_input_tokens ?? 0)
1928
- + (apiResponse.usage.cache_creation_input_tokens ?? 0),
1929
- };
1930
2148
  const choice = {
1931
2149
  index: 0,
1932
2150
  message: {
@@ -1955,7 +2173,14 @@ export class AnthropicProvider {
1955
2173
  * - `end_turn` → `"stop"` (natural completion)
1956
2174
  * - `tool_use` → `"tool_calls"` (model wants to invoke tools)
1957
2175
  * - `max_tokens` → `"length"` (hit token limit)
2176
+ * - `model_context_window_exceeded` → `"length"` (output cut off at the
2177
+ * context window)
1958
2178
  * - `stop_sequence` → `"stop"` (hit a caller-specified stop sequence)
2179
+ * - `refusal` → `"content_filter"`, the name GeminiProvider and
2180
+ * OpenAIProvider use. Both response paths raise a refusal as an error
2181
+ * before a finish reason is reported; the mapping keeps the vocabulary
2182
+ * complete.
2183
+ * - anything else (such as `pause_turn`) passes through unchanged.
1959
2184
  *
1960
2185
  * @param {string | null} stopReason - Anthropic's stop_reason value.
1961
2186
  * @returns {string} Normalized finish reason.
@@ -1966,10 +2191,32 @@ export class AnthropicProvider {
1966
2191
  case 'end_turn': return 'stop';
1967
2192
  case 'tool_use': return 'tool_calls';
1968
2193
  case 'max_tokens': return 'length';
2194
+ case 'model_context_window_exceeded': return 'length';
1969
2195
  case 'stop_sequence': return 'stop';
2196
+ case 'refusal': return 'content_filter';
1970
2197
  default: return stopReason ?? 'stop';
1971
2198
  }
1972
2199
  }
2200
+ /**
2201
+ * The error a refused turn raises. Code `content_filter` is what
2202
+ * `isContentPolicyRefusal` recognizes, so the generateText and streamText
2203
+ * fallback chains act on it, and the provider-health registry does not
2204
+ * count it as a provider failure. The message carries no HTTP status or
2205
+ * rate-limit wording, which retry classifiers grep for.
2206
+ *
2207
+ * @param modelId Model that refused.
2208
+ * @param stopDetails The response's `stop_details`, kept verbatim.
2209
+ * @param partialText Text the model wrote before refusing, discarded as an
2210
+ * answer but kept for diagnostics.
2211
+ * @param usage Tokens the refused call consumed.
2212
+ * @returns An error with code `content_filter` and Anthropic error type
2213
+ * `refusal`.
2214
+ * @private
2215
+ */
2216
+ refusalError(modelId, stopDetails, partialText, usage) {
2217
+ const category = stopDetails?.category;
2218
+ return new AnthropicProviderError(`Claude declined the request on ${modelId || 'the requested model'} (stop_reason refusal${category ? `, category ${category}` : ''}).`, 'content_filter', undefined, 'refusal', { stopReason: 'refusal', stopDetails: stopDetails ?? null, partialText, usage });
2219
+ }
1973
2220
  /**
1974
2221
  * Assembles completed tool calls from the streaming accumulator.
1975
2222
  *
@@ -1989,53 +2236,6 @@ export class AnthropicProvider {
1989
2236
  },
1990
2237
  }));
1991
2238
  }
1992
- /**
1993
- * Estimates USD cost for a given model and token counts.
1994
- *
1995
- * Looks up pricing from the static model catalog. Returns undefined
1996
- * if the model is not found in the catalog.
1997
- *
1998
- * @param {number} inputTokens - Number of input tokens.
1999
- * @param {number} outputTokens - Number of output tokens.
2000
- * @param {string} modelId - Model identifier for pricing lookup.
2001
- * @returns {number | undefined} Estimated cost in USD.
2002
- * @private
2003
- */
2004
- /**
2005
- * Estimate cost in USD for a completion, including Anthropic's prompt-
2006
- * caching tier pricing.
2007
- *
2008
- * Anthropic billing tiers (as of 2025):
2009
- * input_tokens × 1.00 × base input rate (non-cached input)
2010
- * cache_read_input_tokens × 0.10 × base input rate (cache hit)
2011
- * cache_creation_input_tokens × 1.25 × base input rate (5-min TTL write)
2012
- * output_tokens × 1.00 × base output rate
2013
- *
2014
- * The API's `input_tokens` field already EXCLUDES cached tokens, so we
2015
- * sum three separate components for total input cost. Previous
2016
- * implementation used only `input_tokens` × rate, which happened to
2017
- * be correct for the non-cached portion but hid cache creation cost
2018
- * and ignored cache read cost entirely — meaning reported costUSD
2019
- * was always BELOW true billed amount whenever caching was active.
2020
- *
2021
- * 1-hour TTL cache-creation rate is 2× the base input rate, not 1.25×.
2022
- * We can't tell which TTL was used from the response, so we assume
2023
- * the default 5-minute tier. For long-lived cached contexts the
2024
- * reported cost will under-estimate by the 0.75× difference on
2025
- * creation tokens (minor; mostly one-shot at run start).
2026
- */
2027
- estimateCost(inputTokens, outputTokens, modelId, cacheReadTokens, cacheCreationTokens) {
2028
- const info = resolveAnthropicModelEntry(modelId);
2029
- if (!info?.pricePer1MTokensInput || !info?.pricePer1MTokensOutput)
2030
- return undefined;
2031
- const inputPrice = info.pricePer1MTokensInput;
2032
- const outputPrice = info.pricePer1MTokensOutput;
2033
- const nonCachedInput = (inputTokens / 1000000) * inputPrice;
2034
- const cachedRead = ((cacheReadTokens ?? 0) / 1000000) * inputPrice * 0.10;
2035
- const cachedCreate = ((cacheCreationTokens ?? 0) / 1000000) * inputPrice * 1.25;
2036
- const output = (outputTokens / 1000000) * outputPrice;
2037
- return nonCachedInput + cachedRead + cachedCreate + output;
2038
- }
2039
2239
  /**
2040
2240
  * Builds an abort chunk for early stream termination.
2041
2241
  *
@@ -2175,7 +2375,9 @@ export class AnthropicProvider {
2175
2375
  lastError = new AnthropicProviderError(`Request timed out after ${effRequestTimeout}ms.`, 'REQUEST_TIMEOUT');
2176
2376
  }
2177
2377
  else {
2178
- lastError = new AnthropicProviderError(error instanceof Error ? error.message : 'Network or unknown error', 'NETWORK_ERROR');
2378
+ // fetch names the URL (credentials included) when it rejects one,
2379
+ // and quotes a header value it rejects, which carries the key.
2380
+ lastError = new AnthropicProviderError(redactUrlSecrets(error instanceof Error ? error.message : 'Network or unknown error', this.config.baseURL, [apiKey]), 'NETWORK_ERROR');
2179
2381
  }
2180
2382
  if (attempt === this.config.maxRetries - 1)
2181
2383
  break;
@@ -2264,7 +2466,9 @@ export class AnthropicProvider {
2264
2466
  clearTimeout(timeoutId);
2265
2467
  if (error instanceof AnthropicProviderError)
2266
2468
  throw error;
2267
- throw new AnthropicProviderError(error instanceof Error ? error.message : 'Failed to connect to Anthropic stream.', 'STREAM_CONNECTION_FAILED');
2469
+ // Masked like makeApiRequest's network failures: fetch's rejection
2470
+ // can name a credential-bearing base URL or quote the key header.
2471
+ throw new AnthropicProviderError(redactUrlSecrets(error instanceof Error ? error.message : 'Failed to connect to Anthropic stream.', this.config.baseURL, [apiKey]), 'STREAM_CONNECTION_FAILED');
2268
2472
  }
2269
2473
  }
2270
2474
  /**
@@ -2365,9 +2569,14 @@ export class AnthropicProvider {
2365
2569
  /* diagnostics must never break the request path */
2366
2570
  }
2367
2571
  }
2368
- /** True when the request enables extended thinking or replays thinking blocks. */
2572
+ /**
2573
+ * True when the request enables extended thinking or replays thinking
2574
+ * blocks. `{ type: 'disabled' }` turns thinking off; `between_tools` still
2575
+ * returns thinking blocks between tool calls, so it counts.
2576
+ */
2369
2577
  payloadUsesThinking(body) {
2370
- if (body.thinking != null)
2578
+ const thinkingType = body.thinking?.type;
2579
+ if (body.thinking != null && thinkingType !== 'disabled')
2371
2580
  return true;
2372
2581
  const messages = body.messages;
2373
2582
  if (!Array.isArray(messages))