@deepstrike/sdk 0.2.52 → 0.2.61

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 (151) hide show
  1. package/README.md +28 -28
  2. package/dist/agent-ir.d.ts +103 -0
  3. package/dist/agent-ir.js +134 -0
  4. package/dist/agent.d.ts +67 -0
  5. package/dist/agent.js +36 -0
  6. package/dist/collaboration/harness.js +1 -1
  7. package/dist/collaboration/modes/creator-verifier.d.ts +2 -7
  8. package/dist/collaboration/modes/creator-verifier.js +4 -6
  9. package/dist/collaboration/pool.d.ts +8 -20
  10. package/dist/collaboration/pool.js +27 -97
  11. package/dist/compat/anthropic/mcp.d.ts +15 -0
  12. package/dist/compat/anthropic/mcp.js +10 -0
  13. package/dist/compat/openai/agent.d.ts +34 -0
  14. package/dist/compat/openai/agent.js +24 -0
  15. package/dist/governance.d.ts +1 -17
  16. package/dist/governance.js +1 -34
  17. package/dist/guardrail.d.ts +6 -0
  18. package/dist/guardrail.js +1 -0
  19. package/dist/handoff-target.d.ts +12 -0
  20. package/dist/handoff-target.js +1 -0
  21. package/dist/harness/manifest.js +0 -4
  22. package/dist/index.d.ts +19 -7
  23. package/dist/index.js +8 -5
  24. package/dist/kernel.d.ts +2 -20
  25. package/dist/knowledge/public.d.ts +29 -0
  26. package/dist/knowledge/public.js +1 -0
  27. package/dist/mcp-server.d.ts +28 -0
  28. package/dist/mcp-server.js +1 -0
  29. package/dist/memory/agent.d.ts +2 -2
  30. package/dist/memory/agent.js +2 -2
  31. package/dist/memory/durable.d.ts +16 -0
  32. package/dist/memory/durable.js +46 -0
  33. package/dist/memory/in-memory-store.d.ts +9 -7
  34. package/dist/memory/in-memory-store.js +8 -2
  35. package/dist/memory/protocols.d.ts +22 -4
  36. package/dist/memory/public.d.ts +4 -3
  37. package/dist/memory/public.js +3 -2
  38. package/dist/os/public.d.ts +1 -1
  39. package/dist/os/public.js +1 -1
  40. package/dist/providers/anthropic-adapter.d.ts +59 -0
  41. package/dist/providers/anthropic-adapter.js +530 -0
  42. package/dist/providers/anthropic-compatible.d.ts +2 -3
  43. package/dist/providers/anthropic-compatible.js +8 -5
  44. package/dist/providers/anthropic.d.ts +20 -23
  45. package/dist/providers/anthropic.js +176 -395
  46. package/dist/providers/base.d.ts +2 -2
  47. package/dist/providers/base.js +50 -8
  48. package/dist/providers/capability-router.d.ts +29 -0
  49. package/dist/providers/capability-router.js +43 -0
  50. package/dist/providers/catalog.d.ts +16 -4
  51. package/dist/providers/catalog.js +112 -36
  52. package/dist/providers/content-normalization.d.ts +57 -0
  53. package/dist/providers/content-normalization.js +238 -0
  54. package/dist/providers/content-policy.d.ts +16 -0
  55. package/dist/providers/content-policy.js +39 -0
  56. package/dist/providers/credentials.d.ts +83 -0
  57. package/dist/providers/credentials.js +190 -0
  58. package/dist/providers/endpoints.d.ts +137 -0
  59. package/dist/providers/endpoints.js +128 -0
  60. package/dist/providers/factories.js +25 -9
  61. package/dist/providers/gemini-adapter.d.ts +33 -0
  62. package/dist/providers/gemini-adapter.js +264 -0
  63. package/dist/providers/gemini.d.ts +16 -3
  64. package/dist/providers/gemini.js +97 -195
  65. package/dist/providers/model-catalog.d.ts +37 -0
  66. package/dist/providers/model-catalog.js +62 -0
  67. package/dist/providers/model-registry.d.ts +119 -0
  68. package/dist/providers/model-registry.js +379 -0
  69. package/dist/providers/ollama-adapter.d.ts +65 -0
  70. package/dist/providers/ollama-adapter.js +188 -0
  71. package/dist/providers/ollama.d.ts +9 -4
  72. package/dist/providers/ollama.js +96 -109
  73. package/dist/providers/openai-chat-dialects.d.ts +154 -0
  74. package/dist/providers/openai-chat-dialects.js +179 -0
  75. package/dist/providers/openai-chat.d.ts +46 -18
  76. package/dist/providers/openai-chat.js +416 -51
  77. package/dist/providers/openai-responses-adapter.d.ts +42 -0
  78. package/dist/providers/openai-responses-adapter.js +343 -0
  79. package/dist/providers/openai-responses.d.ts +19 -33
  80. package/dist/providers/openai-responses.js +164 -264
  81. package/dist/providers/openai.d.ts +29 -76
  82. package/dist/providers/openai.js +195 -292
  83. package/dist/providers/protocol-adapter.d.ts +39 -0
  84. package/dist/providers/protocol-adapter.js +13 -0
  85. package/dist/providers/protocol-capabilities.d.ts +34 -0
  86. package/dist/providers/protocol-capabilities.js +44 -0
  87. package/dist/providers/provider-error.d.ts +31 -0
  88. package/dist/providers/provider-error.js +153 -0
  89. package/dist/providers/public.d.ts +26 -3
  90. package/dist/providers/public.js +13 -1
  91. package/dist/providers/registry.d.ts +7 -6
  92. package/dist/providers/registry.js +47 -20
  93. package/dist/providers/request-plan.d.ts +89 -0
  94. package/dist/providers/request-plan.js +199 -0
  95. package/dist/providers/usage-normalizer.d.ts +48 -0
  96. package/dist/providers/usage-normalizer.js +139 -0
  97. package/dist/providers/vendor-profiles.d.ts +2 -15
  98. package/dist/providers/vendor-profiles.js +14 -60
  99. package/dist/runtime/canonical-kernel-step.d.ts +1 -2
  100. package/dist/runtime/canonical-kernel-step.js +47 -12
  101. package/dist/runtime/context-policy.d.ts +10 -12
  102. package/dist/runtime/context-policy.js +6 -8
  103. package/dist/runtime/durable-content.d.ts +50 -0
  104. package/dist/runtime/durable-content.js +159 -0
  105. package/dist/runtime/execution-plane.d.ts +2 -2
  106. package/dist/runtime/execution-plane.js +2 -2
  107. package/dist/runtime/kernel-event-log.js +0 -1
  108. package/dist/runtime/kernel-step.d.ts +0 -1
  109. package/dist/runtime/kernel-step.js +4 -2
  110. package/dist/runtime/mcp-proxy-plane.d.ts +25 -1
  111. package/dist/runtime/mcp-proxy-plane.js +44 -6
  112. package/dist/runtime/output-schema.d.ts +1 -2
  113. package/dist/runtime/provider-replay.d.ts +5 -1
  114. package/dist/runtime/provider-replay.js +26 -27
  115. package/dist/runtime/reactive-session.d.ts +1 -1
  116. package/dist/runtime/reactive-session.js +2 -3
  117. package/dist/runtime/run-group.d.ts +1 -1
  118. package/dist/runtime/runner.d.ts +31 -45
  119. package/dist/runtime/runner.js +178 -63
  120. package/dist/runtime/session-log.d.ts +8 -1
  121. package/dist/runtime/session-log.js +42 -2
  122. package/dist/runtime/session-repair.d.ts +1 -1
  123. package/dist/runtime/session-repair.js +1 -1
  124. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  125. package/dist/runtime/sub-agent-orchestrator.js +8 -11
  126. package/dist/runtime/workflow-control-flow.d.ts +0 -4
  127. package/dist/runtime/workflow-control-flow.js +0 -16
  128. package/dist/session.d.ts +11 -0
  129. package/dist/session.js +1 -0
  130. package/dist/skill.d.ts +17 -0
  131. package/dist/skill.js +16 -0
  132. package/dist/skills/loader.d.ts +3 -0
  133. package/dist/tools/errors.d.ts +1 -3
  134. package/dist/tools/errors.js +1 -3
  135. package/dist/tools/index.d.ts +3 -0
  136. package/dist/types/agent.d.ts +21 -9
  137. package/dist/types/agent.js +30 -4
  138. package/dist/types.d.ts +135 -17
  139. package/package.json +4 -4
  140. package/dist/providers/deepseek.d.ts +0 -46
  141. package/dist/providers/deepseek.js +0 -97
  142. package/dist/providers/glm.d.ts +0 -25
  143. package/dist/providers/glm.js +0 -48
  144. package/dist/providers/kimi.d.ts +0 -23
  145. package/dist/providers/kimi.js +0 -30
  146. package/dist/providers/minimax.d.ts +0 -49
  147. package/dist/providers/minimax.js +0 -98
  148. package/dist/providers/profiles.d.ts +0 -1992
  149. package/dist/providers/profiles.js +0 -796
  150. package/dist/providers/qwen.d.ts +0 -38
  151. package/dist/providers/qwen.js +0 -97
@@ -1,30 +1,35 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import { assistantReplayKey } from "../runtime/provider-replay.js";
3
3
  import { withServerRuntimeGuard } from "../runtime/server.js";
4
- import { CircuitBreaker, normalizeToolCall, omitExtensionKeys, toAnthropicMessages } from "./base.js";
5
- const CLAUDE_POLICIES = {
6
- "claude-opus-4-1": { maxTurns: 50 },
7
- "claude-opus-4-7": { maxTurns: 50 },
8
- "claude-opus-4-6": { maxTurns: 50 },
9
- "claude-opus-4-0": { maxTurns: 50 },
10
- "claude-sonnet-4-6": { maxTurns: 25 },
11
- "claude-sonnet-4-0": { maxTurns: 25 },
12
- "claude-haiku-4-5": { maxTurns: 15 },
13
- "claude-3-5-haiku-latest": { maxTurns: 15 },
14
- };
4
+ import { CircuitBreaker } from "./base.js";
5
+ import { AnthropicMessagesAdapter, } from "./anthropic-adapter.js";
6
+ import { normalizeCanonicalAdapterInput, } from "./content-normalization.js";
7
+ import { endpointProfiles } from "./endpoints.js";
8
+ import { circuitOpenError, classifyProviderError } from "./provider-error.js";
15
9
  export class AnthropicProvider {
16
10
  client;
17
11
  circuit;
18
12
  maxRetries;
19
13
  baseDelay;
20
14
  model;
15
+ adapter = new AnthropicMessagesAdapter();
21
16
  nativeAssistantBlocks = new Map();
22
- // Accepts the options object (`new AnthropicProvider({ apiKey, model, baseURL })`) or the legacy
23
- // positional form (still used by the Anthropic-compatible backend subclasses' `super(...)` calls).
24
- constructor(apiKeyOrConfig, model = "claude-sonnet-4-6", retry = { maxRetries: 3, baseDelay: 1000 }, options = {}) {
25
- const c = typeof apiKeyOrConfig === "string"
26
- ? { apiKey: apiKeyOrConfig, model, retry, ...options }
27
- : { model: "claude-sonnet-4-6", retry: { maxRetries: 3, baseDelay: 1000 }, ...apiKeyOrConfig };
17
+ resolvedRuntimePolicy;
18
+ directNativeTokenCounting;
19
+ defaultTextualToolCallPolicy;
20
+ resolvedRuntime;
21
+ constructor(config) {
22
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
23
+ throw new TypeError("AnthropicProvider requires a configuration object");
24
+ }
25
+ if (typeof config.apiKey !== "string" || config.apiKey.length === 0) {
26
+ throw new TypeError("AnthropicProvider requires a non-empty apiKey");
27
+ }
28
+ const c = {
29
+ model: "claude-sonnet-4-6",
30
+ retry: { maxRetries: 3, baseDelay: 1000 },
31
+ ...config,
32
+ };
28
33
  this.model = c.model ?? "claude-sonnet-4-6";
29
34
  this.client = withServerRuntimeGuard(() => new Anthropic({
30
35
  ...(c.authMode === "bearer"
@@ -35,11 +40,17 @@ export class AnthropicProvider {
35
40
  this.circuit = new CircuitBreaker();
36
41
  this.maxRetries = c.retry?.maxRetries ?? 3;
37
42
  this.baseDelay = c.retry?.baseDelay ?? 1000;
43
+ this.resolvedRuntimePolicy = c.runtimePolicy ?? {};
44
+ const configuredBaseURL = c.baseURL?.replace(/\/+$/, "");
45
+ const officialBaseURL = endpointProfiles["anthropic.messages"].baseURL.replace(/\/+$/, "");
46
+ this.directNativeTokenCounting = configuredBaseURL === undefined
47
+ || configuredBaseURL === officialBaseURL;
48
+ this.defaultTextualToolCallPolicy = this.directNativeTokenCounting ? "off" : "reject";
38
49
  }
39
50
  runtimePolicy() {
40
- return CLAUDE_POLICIES[this.model] ?? {};
51
+ return this.resolvedRuntimePolicy;
41
52
  }
42
- /** Identity advertised in the descriptor; overridden by Anthropic-compatible vendors (e.g. MiniMax). */
53
+ /** Identity advertised in the descriptor; overridden by Anthropic-compatible vendors. */
43
54
  providerName() {
44
55
  return "anthropic";
45
56
  }
@@ -59,419 +70,189 @@ export class AnthropicProvider {
59
70
  },
60
71
  };
61
72
  }
73
+ bindResolvedRuntime(resolved) {
74
+ if (resolved.identity.protocol !== "anthropic-messages"
75
+ || resolved.identity.providerId !== this.providerName()
76
+ || resolved.identity.modelId !== this.model) {
77
+ throw new Error("AnthropicProvider received a mismatched resolved runtime");
78
+ }
79
+ this.resolvedRuntime = resolved;
80
+ }
62
81
  peekProviderReplay(message) {
63
82
  const blocks = this.nativeAssistantBlocks.get(assistantReplayKey(message));
64
- return blocks?.length ? { native_blocks: blocks } : undefined;
83
+ return blocks?.length ? { protocol: "anthropic-messages", native_blocks: blocks } : undefined;
65
84
  }
66
85
  seedProviderReplay(message, replay) {
67
- if (replay.native_blocks?.length) {
86
+ if (replay.protocol === "anthropic-messages" && replay.native_blocks?.length) {
68
87
  this.nativeAssistantBlocks.set(assistantReplayKey(message), replay.native_blocks);
69
- return;
70
88
  }
71
- // Legacy log without persisted native blocks: reconstruct neutral
72
- // text + tool_use blocks from the transcript so a tool-use turn can be
73
- // replayed. Thinking blocks were never persisted, so they are not recovered.
74
- const blocks = reconstructAnthropicBlocks(message);
75
- if (blocks.length)
76
- this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
77
89
  }
78
- /**
79
- * Build tool definitions. A cache breakpoint is anchored on the final tool
80
- * only when the system blocks won't carry one (`anchorCache`). When structured
81
- * system blocks are present, their breakpoints already cache the tools prefix
82
- * (tools render before system), so a redundant tool breakpoint would only burn
83
- * one of Anthropic's 4 cache_control slots — slots the message history needs.
84
- */
85
- buildTools(tools, anchorCache, strategy) {
86
- // Tool cache_control is emitted under "default" and "tools-only". "system-only",
87
- // "frozen-prefix", and "none" all skip it.
88
- const emitOnLastTool = anchorCache &&
89
- (strategy === "default" || strategy === "tools-only");
90
- return tools.map((t, i) => ({
91
- name: t.name,
92
- description: t.description,
93
- input_schema: JSON.parse(t.parameters),
94
- ...(emitOnLastTool && i === tools.length - 1 ? { cache_control: { type: "ephemeral" } } : {}),
95
- }));
90
+ adapterInput(context, tools, extensions) {
91
+ const providerId = this.providerName();
92
+ const endpoint = Object.values(endpointProfiles).find(profile => profile.providerId === providerId && profile.protocol === "anthropic-messages") ?? endpointProfiles["anthropic.messages"];
93
+ const resolved = this.resolvedRuntime ?? {
94
+ identity: {
95
+ providerId,
96
+ modelId: this.model,
97
+ endpointId: endpoint.id,
98
+ protocol: "anthropic-messages",
99
+ },
100
+ model: {
101
+ id: `${providerId}/${this.model}`,
102
+ providerId,
103
+ kind: "generation",
104
+ intrinsic: {},
105
+ },
106
+ endpoint,
107
+ adapter: this,
108
+ effectiveCapabilities: compatibilityCapabilities(),
109
+ };
110
+ return normalizeCanonicalAdapterInput({
111
+ context,
112
+ tools,
113
+ resolved,
114
+ extensions: {
115
+ textualToolCallPolicy: this.defaultTextualToolCallPolicy,
116
+ ...extensions,
117
+ },
118
+ replayForMessage: message => this.peekProviderReplay(message),
119
+ });
120
+ }
121
+ buildPlan(context, tools, extensions) {
122
+ const input = this.adapterInput(context, tools, extensions);
123
+ return { input, plan: this.adapter.buildRequest(input) };
96
124
  }
97
125
  async complete(context, tools, extensions) {
126
+ const provider = this.providerName();
98
127
  if (this.circuit.isOpen())
99
- throw new Error("Circuit breaker open");
100
- const strategy = resolveCacheBreakpointStrategy(extensions);
101
- const system = this.buildSystem(context, strategy);
102
- const msgs = this.buildMessages(context, strategy);
103
- assertCacheBudget(system, tools.length);
104
- const requestExtensions = this.requestExtensions(extensions);
128
+ throw circuitOpenError(provider);
129
+ let input;
130
+ let plan;
131
+ try {
132
+ ;
133
+ ({ input, plan } = this.buildPlan(context, tools, extensions));
134
+ }
135
+ catch (error) {
136
+ throw classifyProviderError(provider, error);
137
+ }
105
138
  let lastErr;
106
- for (let i = 0; i < this.maxRetries; i++) {
139
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
107
140
  try {
108
- const resp = await this.createMessage({
109
- ...requestExtensions,
110
- model: this.model,
111
- max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
112
- ...(system ? { system } : {}),
113
- messages: msgs,
114
- ...(tools.length ? { tools: this.buildTools(tools, !Array.isArray(system), strategy) } : {}),
115
- }, extensions);
141
+ const raw = await this.createMessage(plan.params, plan.transport);
116
142
  this.circuit.recordSuccess();
117
- let content = "";
118
- const toolCalls = [];
119
- for (const block of resp.content) {
120
- if (block.type === "text")
121
- content += block.text;
122
- else if (block.type === "tool_use") {
123
- const tc = normalizeToolCall(block.id, block.name, block.input);
124
- if (tc)
125
- toolCalls.push(tc);
126
- }
143
+ const decoded = this.adapter.decodeComplete(raw, { input });
144
+ if (decoded.replay?.native_blocks) {
145
+ this.rememberNativeBlocks(decoded.message, decoded.replay.native_blocks);
127
146
  }
128
- const message = { role: "assistant", content, tokenCount: resp.usage.output_tokens, toolCalls };
129
- this.rememberNativeBlocks(message, resp.content);
130
- return message;
147
+ return decoded.message;
131
148
  }
132
- catch (err) {
133
- lastErr = err;
149
+ catch (error) {
150
+ lastErr = error;
134
151
  this.circuit.recordFailure();
135
- if (i < this.maxRetries - 1)
136
- await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
152
+ if (attempt < this.maxRetries - 1) {
153
+ await new Promise(resolve => setTimeout(resolve, this.baseDelay * 2 ** attempt));
154
+ }
137
155
  }
138
156
  }
139
- throw lastErr;
157
+ throw classifyProviderError(provider, lastErr);
140
158
  }
141
- async *stream(context, tools, extensions, _state, signal) {
142
- const strategy = resolveCacheBreakpointStrategy(extensions);
143
- const system = this.buildSystem(context, strategy);
144
- const msgs = this.buildMessages(context, strategy);
145
- assertCacheBudget(system, tools.length);
146
- const requestExtensions = this.requestExtensions(extensions);
147
- const builtTools = tools.length ? this.buildTools(tools, !Array.isArray(system), strategy) : undefined;
148
- // I1: capture which slots will carry cache_control so cache_read_input_tokens can be
149
- // attributed pro-rata when the response arrives. Honest annotation: Anthropic returns one
150
- // scalar (no per-slot breakdown), so this is an estimate, not authoritative.
151
- const slotBp = countCacheControlSlots(system, builtTools, msgs);
152
- const toolBlocks = {};
153
- const nativeBlocks = {};
154
- let finalText = "";
155
- const finalToolCalls = [];
156
- const stream = this.streamMessage({
157
- ...requestExtensions,
158
- model: this.model,
159
- max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
159
+ /** Native measurement belongs to the verified official endpoint, not the wire protocol. */
160
+ async countTokens(context, tools, extensions) {
161
+ const enabled = this.resolvedRuntime
162
+ ? this.resolvedRuntime.effectiveCapabilities.nativeTokenCounting.state === "supported"
163
+ : this.providerName() === "anthropic" && this.directNativeTokenCounting;
164
+ if (!enabled) {
165
+ throw new Error(`Native token counting is unavailable on ${this.providerName()} Anthropic-compatible endpoint`);
166
+ }
167
+ const { plan } = this.buildPlan(context, tools, extensions);
168
+ const { model, system, messages, tools: requestTools } = plan.params;
169
+ const response = await this.client.messages.countTokens({
170
+ model,
160
171
  ...(system ? { system } : {}),
161
- messages: msgs,
162
- ...(builtTools ? { tools: builtTools } : {}),
163
- }, extensions, signal);
164
- let uncachedInput = 0;
165
- let cacheReadTokens = 0;
166
- let cacheCreationTokens = 0;
167
- let outputTokens = 0;
168
- for await (const evt of stream) {
169
- if (evt.type === "message_start" || evt.type === "message_delta") {
170
- const usage = evt.usage ?? evt.message?.usage;
171
- if (usage) {
172
- // input + cache counts are cumulative and pinned at message_start; a
173
- // later message_delta may omit them (null), so Math.max keeps the
174
- // running totals from being clobbered back to zero.
175
- uncachedInput = Math.max(uncachedInput, usage.input_tokens ?? 0);
176
- cacheReadTokens = Math.max(cacheReadTokens, usage.cache_read_input_tokens ?? 0);
177
- cacheCreationTokens = Math.max(cacheCreationTokens, usage.cache_creation_input_tokens ?? 0);
178
- outputTokens = Math.max(outputTokens, usage.output_tokens ?? 0);
179
- // inputTokens is the FULL prompt size (uncached + cache read + cache
180
- // write). The kernel reads it as the authoritative prompt size for
181
- // context-pressure/compaction — excluding cached tokens would make a
182
- // cache-heavy turn look tiny and suppress compaction until a 413.
183
- const inputTokens = uncachedInput + cacheReadTokens + cacheCreationTokens;
184
- const bySlot = estimateCacheReadBySlot(cacheReadTokens, slotBp);
185
- // stop_reason is only present on message_delta (the closing frame). `max_tokens` drives
186
- // the kernel's output-cap recovery; other reasons (end_turn/tool_use) are informational.
187
- const stopReason = evt.delta?.stop_reason;
188
- yield {
189
- type: "usage",
190
- totalTokens: inputTokens + outputTokens,
191
- inputTokens,
192
- outputTokens,
193
- cacheReadInputTokens: cacheReadTokens,
194
- cacheCreationInputTokens: cacheCreationTokens,
195
- ...(bySlot ? { cacheReadInputTokensBySlot: bySlot } : {}),
196
- ...(stopReason ? { stopReason } : {}),
197
- };
198
- }
199
- }
200
- else if (evt.type === "content_block_start") {
201
- nativeBlocks[evt.index] = { ...evt.content_block };
202
- if (evt.content_block.type === "tool_use") {
203
- toolBlocks[evt.index] = { id: evt.content_block.id, name: evt.content_block.name, argsBuf: "" };
204
- }
172
+ messages,
173
+ ...(requestTools ? { tools: requestTools } : {}),
174
+ });
175
+ return {
176
+ inputTokens: response.input_tokens,
177
+ source: { kind: "native", provider: "anthropic" },
178
+ confidence: "exact",
179
+ };
180
+ }
181
+ async *stream(context, tools, extensions, _state, signal) {
182
+ const provider = this.providerName();
183
+ try {
184
+ const { input, plan } = this.buildPlan(context, tools, extensions);
185
+ const state = this.adapter.createStreamState({ input });
186
+ for await (const chunk of this.streamMessage(plan.params, plan.transport, signal)) {
187
+ for (const event of this.adapter.pushStreamChunk(chunk, state).events)
188
+ yield event;
205
189
  }
206
- else if (evt.type === "content_block_delta") {
207
- const d = evt.delta;
208
- if (d.type === "text_delta") {
209
- finalText += d.text;
210
- nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], text: String(nativeBlocks[evt.index]?.text ?? "") + d.text };
211
- yield { type: "text_delta", delta: d.text };
212
- }
213
- else if (d.type === "thinking_delta") {
214
- nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], thinking: String(nativeBlocks[evt.index]?.thinking ?? "") + d.thinking };
215
- yield { type: "thinking_delta", delta: d.thinking };
216
- }
217
- else if (d.type === "signature_delta") {
218
- nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], signature: String(nativeBlocks[evt.index]?.signature ?? "") + d.signature };
219
- }
220
- else if (d.type === "input_json_delta" && toolBlocks[evt.index]) {
221
- toolBlocks[evt.index].argsBuf += d.partial_json;
222
- }
223
- }
224
- else if (evt.type === "content_block_stop" && toolBlocks[evt.index] !== undefined) {
225
- const tb = toolBlocks[evt.index];
226
- delete toolBlocks[evt.index];
227
- let args = {};
228
- try {
229
- args = JSON.parse(tb.argsBuf || "{}");
230
- }
231
- catch {
232
- args = {};
233
- }
234
- nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], input: args };
235
- finalToolCalls.push({ id: tb.id, name: tb.name, arguments: JSON.stringify(args) });
236
- yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
190
+ const final = this.adapter.finishStream(state);
191
+ for (const event of final.events)
192
+ yield event;
193
+ if (final.replay?.native_blocks) {
194
+ this.rememberNativeBlocks({ content: state.finalText, toolCalls: state.finalToolCalls }, final.replay.native_blocks);
237
195
  }
238
196
  }
239
- this.rememberNativeBlocks({ content: finalText, toolCalls: finalToolCalls }, Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]));
240
- }
241
- requestExtensions(extensions) {
242
- return omitExtensionKeys(extensions, ["model", "messages", "system", "tools", "max_tokens", "stream"]);
243
- }
244
- hasBetas(extensions) {
245
- const betas = extensions?.betas;
246
- return Array.isArray(betas) && betas.length > 0;
197
+ catch (error) {
198
+ throw classifyProviderError(provider, error);
199
+ }
247
200
  }
248
- createMessage(params, extensions) {
249
- return this.hasBetas(extensions)
201
+ createMessage(params, transport) {
202
+ return (transport === "beta"
250
203
  ? this.client.beta.messages.create(params)
251
- : this.client.messages.create(params);
204
+ : this.client.messages.create(params));
252
205
  }
253
- streamMessage(params, extensions, signal) {
254
- // #2-B-ii: forward the abort signal as a request option so a preempt cancels the HTTP request.
255
- const opts = signal ? { signal } : undefined;
256
- return (this.hasBetas(extensions)
257
- ? this.client.beta.messages.stream(params, opts)
258
- : this.client.messages.stream(params, opts));
206
+ streamMessage(params, transport, signal) {
207
+ const options = signal ? { signal } : undefined;
208
+ return (transport === "beta"
209
+ ? this.client.beta.messages.stream(params, options)
210
+ : this.client.messages.stream(params, options));
259
211
  }
212
+ // White-box test seams. Request construction itself belongs to the adapter.
260
213
  buildSystem(context, strategy) {
261
- // B3 note: the system shape is content-driven — 0 blocks (string), 1 block
262
- // (stable only), or 2 blocks (stable + knowledge). The first turn `systemKnowledge`
263
- // appears, the block count rises 1→2, which is a one-time prompt-cache invalidation
264
- // (the knowledge prefix didn't exist to cache before). It is byte-stable thereafter;
265
- // dynamic per-turn knowledge belongs in the uncached tail, not this block. An empty
266
- // knowledge string is intentionally never emitted (the API rejects empty text blocks).
267
- if (!context.systemStable && !context.systemKnowledge) {
268
- return context.systemText || undefined;
269
- }
270
- // System cache_control is emitted under "default" and "system-only". Other strategies
271
- // keep the text-block structure for protocol parity but omit cache_control.
272
- const emitOnSystemBlocks = strategy === "default" || strategy === "system-only";
273
- const cc = { type: "ephemeral" };
274
- const blocks = [];
275
- if (context.systemStable) {
276
- blocks.push({ type: "text", text: context.systemStable, ...(emitOnSystemBlocks ? { cache_control: cc } : {}) });
277
- }
278
- if (context.systemKnowledge) {
279
- blocks.push({ type: "text", text: context.systemKnowledge, ...(emitOnSystemBlocks ? { cache_control: cc } : {}) });
280
- }
281
- return blocks.length ? blocks : undefined;
214
+ return this.buildPlan(context, [], { cacheBreakpointStrategy: strategy }).plan.params.system;
282
215
  }
283
216
  buildMessages(context, strategy) {
284
- const msgs = toAnthropicMessages(context.turns, message => this.nativeAssistantBlocks.get(assistantReplayKey(message)));
285
- // Cache breakpoints anchor on the stable history; the volatile State turn is
286
- // appended AFTER them as the uncached tail (so the history prefix re-reads
287
- // across turns). On un-rebuilt bindings stateTurn is absent and the state is
288
- // already inside `turns` — rendered as-is above. `frozenPrefixLen` (P1-E) pins
289
- // the deep breakpoint at the compaction boundary; absent ⇒ rolling-pair fallback.
290
- applyMessageCacheControl(msgs, context.frozenPrefixLen, strategy);
291
- if (context.stateTurn) {
292
- // Render through toAnthropicMessages so assistant tool_use blocks and
293
- // tool-role tool_result parts are serialized correctly — toAnthropicContent
294
- // only handles contentParts/content and would silently drop toolCalls.
295
- const stateMsgs = toAnthropicMessages([context.stateTurn], message => this.nativeAssistantBlocks.get(assistantReplayKey(message)));
296
- msgs.push(...stateMsgs);
297
- }
298
- if (msgs.length === 0) {
299
- msgs.push({ role: "user", content: "Proceed." });
300
- }
301
- return msgs;
217
+ return this.buildPlan(context, [], { cacheBreakpointStrategy: strategy }).plan.params.messages;
302
218
  }
303
219
  rememberNativeBlocks(message, blocks) {
304
220
  if (!blocks.length)
305
221
  return;
306
- if (!message.toolCalls?.length && !blocks.some(b => b.type === "thinking"))
222
+ if (!message.toolCalls?.length && !blocks.some(block => block.type === "thinking"))
307
223
  return;
308
224
  this.nativeAssistantBlocks.set(assistantReplayKey(message), blocks);
309
225
  }
310
226
  }
311
- /** Recognised cache-breakpoint strategy values; any other input (incl. undefined) falls to `"default"`. */
312
- const CACHE_BREAKPOINT_STRATEGIES = new Set([
313
- "default", "tools-only", "system-only", "frozen-prefix", "none",
314
- ]);
315
- /** Pull `cacheBreakpointStrategy` from per-call extensions; unrecognised values → `"default"`. */
316
- function resolveCacheBreakpointStrategy(extensions) {
317
- const raw = extensions?.cacheBreakpointStrategy;
318
- if (typeof raw === "string" && CACHE_BREAKPOINT_STRATEGIES.has(raw)) {
319
- return raw;
320
- }
321
- return "default";
322
- }
323
- /**
324
- * I1: count which slots of the outgoing request carry a `cache_control` breakpoint. Used to
325
- * pro-rata-attribute the response's `cache_read_input_tokens` (a single scalar with no per-slot
326
- * breakdown) across the slots that contributed to the cache hit. Returns whether each slot has
327
- * any breakpoint — not the actual count, since pro-rata only needs the contributing slot set.
328
- */
329
- function countCacheControlSlots(system, builtTools, msgs) {
330
- const sysBp = Array.isArray(system) && system.some(b => b?.cache_control != null);
331
- const toolBp = !!builtTools && builtTools.some(t => t?.cache_control != null);
332
- let msgBp = false;
333
- for (const m of msgs) {
334
- if (Array.isArray(m.content)) {
335
- if (m.content.some(b => b?.cache_control != null)) {
336
- msgBp = true;
337
- break;
338
- }
339
- }
340
- }
341
- return { system: sysBp, tools: toolBp, messages: msgBp };
342
- }
343
- /**
344
- * I1: split the response's `cache_read_input_tokens` evenly across the slots that carried a
345
- * cache_control breakpoint on the request. Returns undefined when there's no cache read or no
346
- * contributing slot — in those cases the consumer is better off seeing the field absent than
347
- * seeing all zeros. The remainder (if the total doesn't divide evenly) lands on the first
348
- * contributing slot to keep the sum exact.
349
- */
350
- function estimateCacheReadBySlot(cacheRead, slotBp) {
351
- if (cacheRead <= 0)
352
- return undefined;
353
- const count = (slotBp.system ? 1 : 0) + (slotBp.tools ? 1 : 0) + (slotBp.messages ? 1 : 0);
354
- if (count === 0)
355
- return undefined;
356
- const share = Math.floor(cacheRead / count);
357
- const remainder = cacheRead - share * count;
358
- const out = {};
359
- let firstDone = false;
360
- const give = () => {
361
- if (!firstDone) {
362
- firstDone = true;
363
- return share + remainder;
364
- }
365
- return share;
227
+ function compatibilityCapabilities() {
228
+ const unknown = { state: "unknown", evidence: [] };
229
+ const unsupported = { state: "unsupported", evidence: ["protocol"] };
230
+ return {
231
+ inputModalities: {
232
+ text: unknown,
233
+ image: unknown,
234
+ audio: unsupported,
235
+ video: unsupported,
236
+ file: unsupported,
237
+ },
238
+ outputModalities: {
239
+ text: unknown,
240
+ image: unsupported,
241
+ audio: unsupported,
242
+ embedding: unsupported,
243
+ },
244
+ tools: unknown,
245
+ reasoning: unknown,
246
+ parallelToolCalls: unknown,
247
+ structuredOutput: unsupported,
248
+ promptCaching: unknown,
249
+ nativeTokenCounting: unknown,
250
+ mediaForms: {
251
+ imageUrl: unknown,
252
+ imageBase64: unknown,
253
+ fileId: unsupported,
254
+ audioUrl: unsupported,
255
+ audioBase64: unsupported,
256
+ },
366
257
  };
367
- if (slotBp.system)
368
- out.system = give();
369
- if (slotBp.tools)
370
- out.tools = give();
371
- if (slotBp.messages)
372
- out.messages = give();
373
- return out;
374
- }
375
- /** Anthropic accepts at most this many cache_control breakpoints per request. */
376
- const MAX_CACHE_BREAKPOINTS = 4;
377
- /**
378
- * Number of rolling cache breakpoints to spend on the message history. Anthropic
379
- * allows 4 cache_control breakpoints total; the static system/tools prefix
380
- * consumes up to 2 (systemStable + systemKnowledge), leaving 2 for the history.
381
- */
382
- const MESSAGE_CACHE_BREAKPOINTS = 2;
383
- /**
384
- * Regression guard: fail loudly if the static (system + tools) breakpoints plus
385
- * the rolling message budget could exceed Anthropic's hard limit, instead of
386
- * letting the API reject the request with an opaque 400. Uses the worst-case
387
- * message count (`MESSAGE_CACHE_BREAKPOINTS`), so it can only fire if a future
388
- * change adds a system partition or raises the message budget.
389
- */
390
- function assertCacheBudget(system, toolCount) {
391
- const systemBreakpoints = Array.isArray(system) ? system.length : 0;
392
- const toolBreakpoints = toolCount > 0 && !Array.isArray(system) ? 1 : 0;
393
- const worstCase = systemBreakpoints + toolBreakpoints + MESSAGE_CACHE_BREAKPOINTS;
394
- if (worstCase > MAX_CACHE_BREAKPOINTS) {
395
- throw new Error(`Anthropic cache_control budget exceeded: ${systemBreakpoints} system + ${toolBreakpoints} tool + ${MESSAGE_CACHE_BREAKPOINTS} message > ${MAX_CACHE_BREAKPOINTS}`);
396
- }
397
- }
398
- /**
399
- * Place the (≤2) message-history cache breakpoints. The final message always gets
400
- * one — it writes the current full prefix for the next turn to read. The second is
401
- * placed by one of two strategies:
402
- *
403
- * • **Deep anchor (P1-E)** — when `frozenPrefixLen` marks a distinct frozen prefix
404
- * (the compaction boundary), pin the second breakpoint there. It is byte-stable
405
- * across turns, so `[0..frozen]` is re-read cheaply every turn and is immune to
406
- * the 20-block lookback miss that strikes heavy tool turns (>20 blocks/turn); the
407
- * tail breakpoint then writes only the incremental `[frozen..tail]`.
408
- * • **Rolling fallback** — otherwise (older binding / no compaction yet / whole
409
- * render hot), roll the second breakpoint to the nearest preceding user turn, the
410
- * previous turn's read anchor (Anthropic's 20-block lookback bridges light turns).
411
- *
412
- * Without any of this the cached prefix stops at the end of `system` and every turn
413
- * re-bills the entire tool-result history at full price (~quadratic cumulative cost).
414
- * cache_control attaches to the last content block of each target, promoting a bare
415
- * string body to a text block.
416
- */
417
- function applyMessageCacheControl(msgs, frozenPrefixLen, strategy) {
418
- if (!msgs.length)
419
- return;
420
- // Message-level cache_control is emitted under "default" and "frozen-prefix" only.
421
- // "tools-only", "system-only", and "none" skip the history entirely.
422
- if (strategy === "tools-only" || strategy === "system-only" || strategy === "none")
423
- return;
424
- const targets = new Set([msgs.length - 1]);
425
- if (typeof frozenPrefixLen === "number" && frozenPrefixLen >= 1 && frozenPrefixLen < msgs.length) {
426
- // Deep anchor at the frozen-prefix boundary (last frozen turn). Fixed between compactions.
427
- targets.add(frozenPrefixLen - 1);
428
- }
429
- else if (strategy === "default") {
430
- // Rolling fallback is part of the default strategy only — `"frozen-prefix"` deliberately
431
- // skips it so a verify can isolate the deep-anchor contribution from the rolling pair.
432
- for (let i = msgs.length - 2; i >= 0 && targets.size < MESSAGE_CACHE_BREAKPOINTS; i--) {
433
- if (msgs[i].role === "user")
434
- targets.add(i);
435
- }
436
- }
437
- for (const idx of targets)
438
- markLastBlockCacheable(msgs[idx]);
439
- }
440
- /** Attach an ephemeral cache breakpoint to a message's final content block. */
441
- function markLastBlockCacheable(msg) {
442
- const cache_control = { type: "ephemeral" };
443
- if (typeof msg.content === "string") {
444
- if (!msg.content)
445
- return; // don't synthesize an empty (API-rejected) text block
446
- msg.content = [{ type: "text", text: msg.content, cache_control }];
447
- return;
448
- }
449
- if (Array.isArray(msg.content) && msg.content.length) {
450
- const last = msg.content[msg.content.length - 1];
451
- last.cache_control = cache_control;
452
- }
453
- }
454
- /**
455
- * Reconstruct Anthropic assistant content blocks from a neutral transcript when
456
- * no provider replay was persisted. Only meaningful for tool-use turns: a plain
457
- * text turn needs no native blocks to replay.
458
- */
459
- function reconstructAnthropicBlocks(message) {
460
- const toolCalls = message.toolCalls ?? [];
461
- if (!toolCalls.length)
462
- return [];
463
- const blocks = [];
464
- if (message.content)
465
- blocks.push({ type: "text", text: message.content });
466
- for (const tc of toolCalls) {
467
- let input = {};
468
- try {
469
- input = JSON.parse(tc.arguments || "{}");
470
- }
471
- catch {
472
- input = {};
473
- }
474
- blocks.push({ type: "tool_use", id: tc.id, name: tc.name, input });
475
- }
476
- return blocks;
477
258
  }