@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,39 +1,12 @@
1
1
  import OpenAI from "openai";
2
+ import { assistantReplayKey } from "../runtime/provider-replay.js";
2
3
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker, omitExtensionKeys, openAICachedPromptTokens, stablePromptCacheKey, ThinkingTagStreamExtractor } from "./base.js";
4
- import { OpenAIChatAdapter } from "./openai-chat.js";
5
- const OPENAI_POLICIES = {
6
- "gpt-5.5": { maxTurns: 60 },
7
- "gpt-5.4": { maxTurns: 50 },
8
- "gpt-5.4-mini": { maxTurns: 25 },
9
- "gpt-5.4-nano": { maxTurns: 15 },
10
- "gpt-5.2": { maxTurns: 50 },
11
- "gpt-5.2-pro": { maxTurns: 60 },
12
- "gpt-5.1": { maxTurns: 50 },
13
- "gpt-4o": { maxTurns: 25 },
14
- "gpt-4o-mini": { maxTurns: 15 },
15
- "gpt-4.1": { maxTurns: 35 },
16
- "gpt-4.1-mini": { maxTurns: 20 },
17
- "gpt-4.1-nano": { maxTurns: 15 },
18
- "gpt-5": { maxTurns: 50 },
19
- "gpt-5-pro": { maxTurns: 60 },
20
- "gpt-5-mini": { maxTurns: 25 },
21
- "gpt-5-nano": { maxTurns: 15 },
22
- "o1": { maxTurns: 50 },
23
- "o1-mini": { maxTurns: 25 },
24
- "o3": { maxTurns: 50 },
25
- "o3-mini": { maxTurns: 25 },
26
- "o4-mini": { maxTurns: 25 },
27
- };
28
- /** Rebuild OpenAI-native `tool_calls` blocks from the streamed buffers — needed by reasoning
29
- * vendors (DeepSeek/MiniMax) that persist the native blocks in their replay envelope. */
30
- export function nativeToolCallsFromBuffers(toolCallBufs) {
31
- return Object.values(toolCallBufs).map(tb => ({
32
- id: tb.id,
33
- type: "function",
34
- function: { name: tb.name, arguments: tb.argsBuf || "{}" },
35
- }));
36
- }
4
+ import { CircuitBreaker, omitExtensionKeys, stablePromptCacheKey } from "./base.js";
5
+ import { normalizeCanonicalAdapterInput, } from "./content-normalization.js";
6
+ import { endpointProfiles } from "./endpoints.js";
7
+ import { OpenAIChatAdapter, } from "./openai-chat.js";
8
+ import { openAIChatDialects, } from "./openai-chat-dialects.js";
9
+ import { circuitOpenError, classifyProviderError } from "./provider-error.js";
37
10
  export class OpenAIChatProvider {
38
11
  client;
39
12
  circuit;
@@ -41,302 +14,232 @@ export class OpenAIChatProvider {
41
14
  baseDelay;
42
15
  model;
43
16
  chat = new OpenAIChatAdapter();
44
- // Accepts either the options object (`new OpenAIProvider({ apiKey, model, baseURL })`) or the legacy
45
- // positional form (still used by the backend subclasses' `super(...)` calls).
46
- constructor(apiKeyOrOptions, model = "gpt-4o", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = "https://api.openai.com/v1") {
47
- const o = typeof apiKeyOrOptions === "string"
48
- ? { apiKey: apiKeyOrOptions, model, retry, baseURL }
49
- : { model: "gpt-4o", retry: { maxRetries: 3, baseDelay: 1000 }, baseURL: "https://api.openai.com/v1", ...apiKeyOrOptions };
50
- this.model = o.model;
51
- this.client = withServerRuntimeGuard(() => new OpenAI({ apiKey: o.apiKey, baseURL: o.baseURL }));
17
+ dialect;
18
+ replayStore = new Map();
19
+ resolvedRuntimePolicy;
20
+ resolvedRuntime;
21
+ constructor(options) {
22
+ const resolved = {
23
+ apiKey: options.apiKey,
24
+ model: options.model ?? "gpt-4o",
25
+ retry: options.retry ?? { maxRetries: 3, baseDelay: 1000 },
26
+ baseURL: options.baseURL ?? endpointProfiles["openai.chat"].baseURL,
27
+ runtimePolicy: options.runtimePolicy ?? {},
28
+ dialect: options.dialect ?? openAIChatDialects.openai,
29
+ authMode: options.authMode ?? "api_key",
30
+ };
31
+ this.model = resolved.model;
32
+ this.client = withServerRuntimeGuard(() => new OpenAI({
33
+ apiKey: resolved.apiKey,
34
+ baseURL: resolved.baseURL,
35
+ ...(resolved.authMode === "bearer" ? { defaultHeaders: { Authorization: `Bearer ${resolved.apiKey}` } } : {}),
36
+ }));
52
37
  this.circuit = new CircuitBreaker();
53
- this.maxRetries = o.retry.maxRetries;
54
- this.baseDelay = o.retry.baseDelay;
38
+ this.maxRetries = resolved.retry.maxRetries;
39
+ this.baseDelay = resolved.retry.baseDelay;
40
+ this.resolvedRuntimePolicy = resolved.runtimePolicy;
41
+ this.dialect = resolved.dialect;
55
42
  }
56
43
  runtimePolicy() {
57
- return OPENAI_POLICIES[this.model] ?? {};
44
+ return this.resolvedRuntimePolicy;
58
45
  }
59
46
  descriptor() {
60
47
  return {
61
- provider: "openai",
48
+ provider: this.dialect.providerId,
62
49
  protocol: "openai-chat",
63
50
  model: this.model,
64
- reasoning: {
65
- supported: true,
66
- preserveAcrossToolTurns: false,
67
- },
68
- toolCalls: {
69
- supported: true,
70
- requiresStrictPairing: true,
71
- },
51
+ reasoning: this.dialect.descriptor.reasoning,
52
+ toolCalls: { supported: true, requiresStrictPairing: true },
72
53
  };
73
54
  }
74
- requireNonEmptyReasoningReplayForToolTurns(_extensions) {
75
- return false;
76
- }
77
- degradeMissingReasoningReplay(extensions) {
78
- return extensions?.degradeMissingReasoningReplay === true;
79
- }
80
- buildChatMessages(context, extensions) {
81
- return this.chat.buildMessages(context, {
82
- descriptor: this.descriptor(),
83
- requireNonEmptyReasoningForToolCalls: this.requireNonEmptyReasoningReplayForToolTurns(extensions),
84
- degradeMissingReasoning: this.degradeMissingReasoningReplay(extensions),
85
- });
86
- }
87
- // ── Template-Method hooks ───────────────────────────────────────────────────
88
- // Defaults reproduce the plain OpenAI-chat behavior; reasoning vendors
89
- // (DeepSeek/MiniMax) override these instead of duplicating complete()/stream().
90
- /** Pre-process caller extensions before they reach buildChatMessages + the wire request
91
- * (e.g. set `__deepstrikeThinkingEnabled`). Default: pass through unchanged. */
92
- prepareExtensions(extensions) {
93
- return extensions;
94
- }
95
- /** Extra top-level request-body fields merged into the chat.completions call (vendor thinking
96
- * knobs like `reasoning_effort`, `extra_body`, `reasoning_split`). Default: none. */
97
- requestBodyExtras(_extensions) {
98
- return {};
99
- }
100
- /** Vendor server tools (e.g. web search) injected into the `tools[]` array alongside the function
101
- * tools, driven by caller `extensions`. These run server-side — the model invokes them and the
102
- * results come back inline, with no client tool-loop round-trip. Default: none. Vendors that ship
103
- * built-in tools (GLM web_search, …) override this and strip the consumed key in `prepareExtensions`
104
- * so it does not also leak into the request body. */
105
- serverTools(_extensions) {
106
- return [];
107
- }
108
- /** Merge function tools + vendor server tools into the wire `tools[]` (undefined when empty). Server
109
- * tools (e.g. web_search) are non-standard wire entries, so the array is cast to the SDK tool type. */
110
- assembleTools(tools, extensions) {
111
- const fnTools = tools.length ? this.chat.buildTools(tools) : [];
112
- const all = [...fnTools, ...this.serverTools(extensions)];
113
- return all.length ? all : undefined;
114
- }
115
- /** Request-body params controlling prompt caching. Default sends OpenAI's `prompt_cache_key`;
116
- * vendors whose endpoints reject unknown params (e.g. DeepSeek 400s) override to `{}`. */
117
- cacheKeyParams(context, tools) {
118
- return { prompt_cache_key: this.promptCacheKey(context, tools) };
119
- }
120
- /** Whether streamed `content` may carry inline `<thinking>…</thinking>` tags to split out.
121
- * Default true (OpenAI). Reasoning vendors emit reasoning out-of-band, so they return false. */
122
- usesInlineThinkingTags() {
123
- return true;
124
- }
125
- /** Whether to surface streamed `reasoning_content` as thinking_delta events. Default true;
126
- * vendors gate this behind an `exposeReasoning` extension. */
127
- exposeReasoningDelta(_extensions) {
128
- return true;
129
- }
130
- /** Persist replay after a non-streaming turn. Default: nothing (plain OpenAI has no reasoning
131
- * to replay). Reasoning vendors override to store their envelope. */
132
- rememberCompleteReplay(_content, _toolCalls, _reasoning) {
133
- /* no-op */
134
- }
135
- /** Persist replay after a streamed turn. Default: store `{ reasoning_content }` when there is a
136
- * tool-call turn or captured reasoning (the prior base behavior). Vendors override. */
137
- rememberStreamReplay(content, toolCalls, reasoning) {
138
- if (toolCalls.length || reasoning.reasoningContent) {
139
- this.chat.rememberReplayFields({ content, toolCalls }, { reasoning_content: reasoning.reasoningContent });
55
+ bindResolvedRuntime(resolved) {
56
+ if (resolved.identity.protocol !== "openai-chat"
57
+ || resolved.identity.providerId !== this.dialect.providerId
58
+ || resolved.identity.modelId !== this.model) {
59
+ throw new Error("OpenAIChatProvider received a mismatched resolved runtime");
140
60
  }
61
+ this.resolvedRuntime = resolved;
141
62
  }
142
- /**
143
- * Pre-flight query: would this history validate against this provider with the
144
- * given extensions, without sending the request? Lets an embedder route around
145
- * a reasoning-replay failure (keep thinking on, disable it, or skip this
146
- * candidate) before issuing the request. `ok: true` when this provider does
147
- * not require reasoning replay for the current extensions.
148
- */
149
63
  assessReplayability(context, extensions) {
150
- if (!this.requireNonEmptyReasoningReplayForToolTurns(extensions)) {
64
+ const prepared = this.dialect.prepareExtensions(extensions ?? {});
65
+ if (!this.dialect.requireReasoningReplay(prepared)) {
151
66
  return { ok: true, offendingCallIds: [] };
152
67
  }
153
- return this.chat.assessReasoning(context);
68
+ const offendingCallIds = context.turns.flatMap(message => {
69
+ if (message.role !== "assistant" || !message.toolCalls?.length)
70
+ return [];
71
+ const replay = this.peekProviderReplay(message);
72
+ return typeof replay?.reasoning_content === "string" && replay.reasoning_content.trim()
73
+ ? []
74
+ : message.toolCalls.map(call => call.id);
75
+ });
76
+ return { ok: offendingCallIds.length === 0, offendingCallIds };
154
77
  }
155
78
  peekProviderReplay(message) {
156
- const fields = this.chat.peekReplayFields(message);
157
- if (!fields || !("reasoning_content" in fields || "reasoning_details" in fields))
79
+ const replay = this.replayStore.get(assistantReplayKey(message));
80
+ if (!replay || !("reasoning_content" in replay || "reasoning_details" in replay))
158
81
  return undefined;
159
- return fields;
82
+ if (this.dialect.id === "qwen" && replay.reasoning_content !== undefined) {
83
+ return { protocol: "openai-chat", reasoning_content: String(replay.reasoning_content ?? "") };
84
+ }
85
+ return replay;
160
86
  }
161
87
  seedProviderReplay(message, replay) {
162
- if (replay.reasoning_content !== undefined || replay.reasoning_details !== undefined) {
163
- this.chat.rememberReplayFields(message, replay);
164
- }
88
+ if (replay.protocol !== "openai-chat"
89
+ || (replay.reasoning_content === undefined && replay.reasoning_details === undefined))
90
+ return;
91
+ this.replayStore.set(assistantReplayKey(message), this.dialect.id === "qwen"
92
+ ? { protocol: "openai-chat", reasoning_content: replay.reasoning_content }
93
+ : replay);
94
+ }
95
+ rememberReplay(message, replay) {
96
+ if (replay)
97
+ this.replayStore.set(assistantReplayKey(message), replay);
98
+ }
99
+ adapterInput(context, tools, extensions) {
100
+ const endpoint = endpointProfiles[this.dialect.endpointId];
101
+ const resolved = this.resolvedRuntime ?? {
102
+ identity: {
103
+ providerId: this.dialect.providerId,
104
+ modelId: this.model,
105
+ endpointId: this.dialect.endpointId,
106
+ protocol: "openai-chat",
107
+ },
108
+ model: {
109
+ id: `${this.dialect.providerId}/${this.model}`,
110
+ providerId: this.dialect.providerId,
111
+ kind: "generation",
112
+ intrinsic: {},
113
+ },
114
+ endpoint,
115
+ adapter: this,
116
+ effectiveCapabilities: compatibilityCapabilities(),
117
+ };
118
+ return normalizeCanonicalAdapterInput({
119
+ context,
120
+ tools,
121
+ resolved,
122
+ extensions,
123
+ replayForMessage: message => this.peekProviderReplay(message),
124
+ });
165
125
  }
166
126
  async complete(context, tools, extensions) {
167
- const prepared = this.prepareExtensions(extensions);
127
+ const provider = this.dialect.providerId;
168
128
  if (this.circuit.isOpen())
169
- throw new Error("Circuit breaker open");
170
- const msgs = this.buildChatMessages(context, prepared);
171
- let lastErr;
172
- for (let i = 0; i < this.maxRetries; i++) {
129
+ throw circuitOpenError(provider);
130
+ let input;
131
+ let plan;
132
+ try {
133
+ input = this.adapterInput(context, tools, extensions);
134
+ plan = this.chat.buildRequest(input, this.dialect);
135
+ }
136
+ catch (error) {
137
+ throw classifyProviderError(provider, error);
138
+ }
139
+ let lastError;
140
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
173
141
  try {
174
- const resp = await this.client.chat.completions.create({
175
- ...this.cacheKeyParams(context, tools),
176
- ...this.requestExtensions(prepared),
177
- ...this.requestBodyExtras(extensions),
178
- model: this.model,
179
- messages: msgs,
180
- ...((t => t ? { tools: t } : {})(this.assembleTools(tools, extensions))),
181
- });
142
+ const response = await this.client.chat.completions.create(plan.params);
182
143
  this.circuit.recordSuccess();
183
- const choice = resp.choices[0].message;
184
- const nativeToolCalls = choice.tool_calls ?? [];
185
- const toolCalls = this.chat.normalizeToolCalls(nativeToolCalls);
186
- const content = choice.content ?? "";
187
- this.rememberCompleteReplay(content, toolCalls, {
188
- reasoningContent: typeof choice.reasoning_content === "string" ? choice.reasoning_content : "",
189
- reasoningDetails: choice.reasoning_details,
190
- nativeToolCalls: nativeToolCalls,
191
- });
192
- return { role: "assistant", content, tokenCount: resp.usage?.completion_tokens ?? resp.usage?.total_tokens, toolCalls };
144
+ const decoded = this.chat.decodeComplete(response, { input }, this.dialect);
145
+ this.rememberReplay(decoded.message, decoded.replay);
146
+ return decoded.message;
193
147
  }
194
- catch (err) {
195
- lastErr = err;
148
+ catch (error) {
149
+ lastError = error;
196
150
  this.circuit.recordFailure();
197
- if (i < this.maxRetries - 1)
198
- await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
151
+ if (attempt < this.maxRetries - 1) {
152
+ await new Promise(resolve => setTimeout(resolve, this.baseDelay * 2 ** attempt));
153
+ }
199
154
  }
200
155
  }
201
- throw lastErr;
156
+ throw classifyProviderError(provider, lastError);
202
157
  }
203
158
  async *stream(context, tools, extensions, _state, signal) {
204
- const prepared = this.prepareExtensions(extensions);
205
- const msgs = this.buildChatMessages(context, prepared);
206
- const toolCallBufs = {};
207
- const emittedToolCallIndexes = new Set();
208
- const useTags = this.usesInlineThinkingTags();
209
- const exposeReasoning = this.exposeReasoningDelta(extensions);
210
- const extractor = new ThinkingTagStreamExtractor();
211
- let accumulatedReasoning = "";
212
- let accumulatedReasoningDetails;
213
- let accumulatedContent = "";
214
- const stream = await this.client.chat.completions.create({
215
- ...this.cacheKeyParams(context, tools),
216
- ...this.requestExtensions(prepared),
217
- ...this.requestBodyExtras(extensions),
218
- model: this.model,
219
- messages: msgs,
220
- ...((t => t ? { tools: t } : {})(this.assembleTools(tools, extensions))),
221
- stream: true,
222
- stream_options: { include_usage: true },
223
- // #2-B-ii: forward the abort signal so a preempt cancels the in-flight HTTP request.
224
- }, signal ? { signal } : undefined);
225
- const rememberStream = () => {
226
- const toolCalls = Object.values(toolCallBufs).map(tb => ({ id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}" }));
227
- this.rememberStreamReplay(accumulatedContent, toolCalls, {
228
- reasoningContent: accumulatedReasoning,
229
- reasoningDetails: accumulatedReasoningDetails,
230
- nativeToolCalls: nativeToolCallsFromBuffers(toolCallBufs),
231
- });
232
- };
233
- const emitPendingToolCalls = function* () {
234
- for (const [index, tb] of Object.entries(toolCallBufs)) {
235
- const idx = Number(index);
236
- if (emittedToolCallIndexes.has(idx))
237
- continue;
238
- let args = {};
239
- try {
240
- args = JSON.parse(tb.argsBuf || "{}");
241
- }
242
- catch {
243
- args = {};
244
- }
245
- emittedToolCallIndexes.add(idx);
246
- yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
247
- }
248
- };
249
- let totalTokens = 0;
250
- let inputTokens = 0;
251
- let outputTokens = 0;
252
- let cacheReadTokens = 0;
253
- // Phase 4: OpenAI signals an output-cap truncation via finish_reason="length", which arrives on
254
- // a `choices` frame separate from the trailing `usage` frame — so capture it and attach it to the
255
- // usage event the runner reads. The kernel treats "length" as a truncation (== Anthropic
256
- // "max_tokens"); other reasons ("stop"/"tool_calls") pass through harmlessly.
257
- let finishReason;
258
- for await (const chunk of stream) {
259
- if (chunk.usage) {
260
- totalTokens = chunk.usage.total_tokens;
261
- inputTokens = chunk.usage.prompt_tokens ?? 0;
262
- outputTokens = chunk.usage.completion_tokens ?? 0;
263
- cacheReadTokens = openAICachedPromptTokens(chunk.usage);
264
- continue;
265
- }
266
- const choice = chunk.choices[0];
267
- if (!choice)
268
- continue;
269
- if (choice.finish_reason)
270
- finishReason = choice.finish_reason;
271
- const delta = choice.delta;
272
- if (!delta)
273
- continue;
274
- if (delta.reasoning_content) {
275
- accumulatedReasoning += String(delta.reasoning_content);
276
- if (exposeReasoning)
277
- yield { type: "thinking_delta", delta: String(delta.reasoning_content) };
278
- }
279
- if (delta.reasoning_details !== undefined && delta.reasoning_details !== null)
280
- accumulatedReasoningDetails = delta.reasoning_details;
281
- if (delta.content) {
282
- if (useTags) {
283
- for (const part of extractor.feed(String(delta.content))) {
284
- if (part.type === "thinking") {
285
- accumulatedReasoning += part.content;
286
- yield { type: "thinking_delta", delta: part.content };
287
- }
288
- else {
289
- accumulatedContent += part.content;
290
- yield { type: "text_delta", delta: part.content };
291
- }
292
- }
293
- }
294
- else {
295
- accumulatedContent += String(delta.content);
296
- yield { type: "text_delta", delta: delta.content };
159
+ const provider = this.dialect.providerId;
160
+ try {
161
+ const input = this.adapterInput(context, tools, extensions);
162
+ const plan = this.chat.buildRequest(input, this.dialect);
163
+ const state = this.chat.createStreamState({ input }, this.dialect);
164
+ const stream = await this.client.chat.completions.create({
165
+ ...plan.params,
166
+ stream: true,
167
+ stream_options: { include_usage: true },
168
+ }, signal ? { signal } : undefined);
169
+ for await (const chunk of stream) {
170
+ const output = this.chat.pushStreamChunk(chunk, state);
171
+ if (output.replay) {
172
+ this.rememberReplay({
173
+ content: state.accumulatedContent,
174
+ toolCalls: Object.values(state.toolCallBuffers).map(call => ({
175
+ id: call.id,
176
+ name: call.name,
177
+ arguments: call.argsBuffer || "{}",
178
+ })),
179
+ }, output.replay);
297
180
  }
181
+ for (const event of output.events)
182
+ yield event;
298
183
  }
299
- for (const tc of delta.tool_calls ?? []) {
300
- const idx = tc.index;
301
- if (!toolCallBufs[idx])
302
- toolCallBufs[idx] = { id: tc.id ?? "", name: "", argsBuf: "" };
303
- if (tc.function?.name)
304
- toolCallBufs[idx].name += tc.function.name;
305
- toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
306
- }
307
- if (choice.finish_reason === "tool_calls") {
308
- rememberStream();
309
- yield* emitPendingToolCalls();
310
- }
184
+ const final = this.chat.finishStream(state);
185
+ for (const event of final.events)
186
+ yield event;
187
+ this.rememberReplay({
188
+ content: state.accumulatedContent,
189
+ toolCalls: Object.values(state.toolCallBuffers).map(call => ({
190
+ id: call.id,
191
+ name: call.name,
192
+ arguments: call.argsBuffer || "{}",
193
+ })),
194
+ }, final.replay);
311
195
  }
312
- if (useTags) {
313
- for (const part of extractor.flush()) {
314
- if (part.type === "thinking") {
315
- accumulatedReasoning += part.content;
316
- yield { type: "thinking_delta", delta: part.content };
317
- }
318
- else {
319
- accumulatedContent += part.content;
320
- yield { type: "text_delta", delta: part.content };
321
- }
322
- }
196
+ catch (error) {
197
+ throw classifyProviderError(provider, error);
323
198
  }
324
- rememberStream();
325
- yield* emitPendingToolCalls();
326
- if (totalTokens > 0)
327
- yield { type: "usage", totalTokens, inputTokens, outputTokens, ...(cacheReadTokens > 0 ? { cacheReadInputTokens: cacheReadTokens } : {}), ...(finishReason ? { stopReason: finishReason } : {}) };
328
199
  }
329
- /**
330
- * Default `prompt_cache_key` derived from the cacheable prefix (system prompt +
331
- * tool names) so requests for the same agent config route to the same cache.
332
- * A caller-supplied `prompt_cache_key` in extensions overrides it (it is spread
333
- * after this default). Unknown to non-OpenAI compatible endpoints, which ignore it.
334
- */
335
- promptCacheKey(context, tools) {
336
- return stablePromptCacheKey([context.systemText, tools.map(t => t.name).join(",")]);
200
+ // White-box test seams. Runtime request shaping uses the dialect through the adapter.
201
+ prepareExtensions(extensions) {
202
+ return this.dialect.prepareExtensions(extensions ?? {});
203
+ }
204
+ requestBodyExtras(extensions) {
205
+ const prepared = this.dialect.prepareExtensions(extensions ?? {});
206
+ return Object.fromEntries(Object.entries(prepared).filter(([key]) => key === "extra_body" || key === "reasoning_effort" || key === "reasoning_split"));
207
+ }
208
+ serverTools(extensions) {
209
+ return this.dialect.serverTools?.(extensions ?? {}) ?? [];
337
210
  }
338
211
  requestExtensions(extensions) {
339
- return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream", "stream_options", "__deepstrikeThinkingEnabled"]);
212
+ const prepared = this.dialect.prepareExtensions(extensions ?? {});
213
+ return omitExtensionKeys(prepared, [
214
+ "model", "messages", "tools", "stream", "stream_options", "extra_body",
215
+ "reasoning_effort", "reasoning_split", "__deepstrikeThinkingEnabled",
216
+ ]);
340
217
  }
218
+ promptCacheKey(context, tools) {
219
+ return stablePromptCacheKey([context.systemText, tools.map(tool => tool.name).join(",")]);
220
+ }
221
+ rememberCompleteReplay(_content, _toolCalls, _reasoning) { }
222
+ rememberStreamReplay(_content, _toolCalls, _reasoning) { }
223
+ }
224
+ function compatibilityCapabilities() {
225
+ const unknown = { state: "unknown", evidence: [] };
226
+ const unsupported = { state: "unsupported", evidence: ["protocol"] };
227
+ return {
228
+ inputModalities: { text: unknown, image: unknown, audio: unknown, video: unsupported, file: unsupported },
229
+ outputModalities: { text: unknown, image: unsupported, audio: unsupported, embedding: unsupported },
230
+ tools: unknown,
231
+ reasoning: unknown,
232
+ parallelToolCalls: unknown,
233
+ structuredOutput: unknown,
234
+ promptCaching: unknown,
235
+ nativeTokenCounting: unknown,
236
+ mediaForms: {
237
+ imageUrl: unknown,
238
+ imageBase64: unknown,
239
+ fileId: unsupported,
240
+ audioUrl: unsupported,
241
+ audioBase64: unknown,
242
+ },
243
+ };
341
244
  }
342
245
  export { OpenAIChatProvider as OpenAIProvider };
@@ -0,0 +1,39 @@
1
+ import type { Message, ProviderReplay, ProviderRunState, ProviderUsage, StreamEvent } from "../types.js";
2
+ import type { CanonicalAdapterInput } from "./content-normalization.js";
3
+ import type { GenerationProtocol, ProtocolRuntimeCapabilities } from "./protocol-capabilities.js";
4
+ export { GEMINI_PROTOCOL_CAPABILITIES, OLLAMA_PROTOCOL_CAPABILITIES, } from "./protocol-capabilities.js";
5
+ export type CanonicalStopReason = "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | "content_filter" | "other";
6
+ export interface AdapterOutput {
7
+ events: StreamEvent[];
8
+ replay?: ProviderReplay;
9
+ runStatePatch?: Partial<ProviderRunState>;
10
+ }
11
+ export interface AdapterDecodeInput {
12
+ input: CanonicalAdapterInput;
13
+ }
14
+ export interface AdapterStreamInput {
15
+ input: CanonicalAdapterInput;
16
+ }
17
+ export interface ProtocolAdapter<TRequest, TCompleteResponse, TStreamChunk, TStreamState, TStreamFinal = undefined> {
18
+ readonly protocol: GenerationProtocol;
19
+ readonly protocolCapabilities: ProtocolRuntimeCapabilities;
20
+ buildRequest(input: CanonicalAdapterInput): TRequest;
21
+ decodeComplete(raw: TCompleteResponse, input: AdapterDecodeInput): {
22
+ message: Message;
23
+ replay?: ProviderReplay;
24
+ };
25
+ createStreamState(input: AdapterStreamInput): TStreamState;
26
+ pushStreamChunk(chunk: TStreamChunk, state: TStreamState): AdapterOutput;
27
+ finishStream(state: TStreamState, final: TStreamFinal): AdapterOutput | Promise<AdapterOutput>;
28
+ normalizeUsage(raw: unknown): ProviderUsage | undefined;
29
+ normalizeStopReason(raw: string | undefined): CanonicalStopReason | undefined;
30
+ }
31
+ export declare class ProtocolResponseError extends Error {
32
+ readonly protocol: GenerationProtocol;
33
+ readonly providerCode?: string;
34
+ readonly retryable?: boolean;
35
+ constructor(protocol: GenerationProtocol, message: string, options?: {
36
+ providerCode?: string;
37
+ retryable?: boolean;
38
+ });
39
+ }
@@ -0,0 +1,13 @@
1
+ export { GEMINI_PROTOCOL_CAPABILITIES, OLLAMA_PROTOCOL_CAPABILITIES, } from "./protocol-capabilities.js";
2
+ export class ProtocolResponseError extends Error {
3
+ protocol;
4
+ providerCode;
5
+ retryable;
6
+ constructor(protocol, message, options = {}) {
7
+ super(options.providerCode ? message : `${protocol} protocol response error: ${message}`);
8
+ this.name = "ProtocolResponseError";
9
+ this.protocol = protocol;
10
+ this.providerCode = options.providerCode;
11
+ this.retryable = options.retryable;
12
+ }
13
+ }
@@ -0,0 +1,34 @@
1
+ export type InputModality = "text" | "image" | "audio" | "video" | "file";
2
+ export type OutputModality = "text" | "image" | "audio" | "embedding";
3
+ export type GenerationProtocol = "anthropic-messages" | "openai-chat" | "openai-responses" | "gemini" | "ollama-chat";
4
+ export interface ProtocolRuntimeCapabilities {
5
+ acceptedInputModalities: readonly InputModality[];
6
+ emittedOutputModalities: readonly OutputModality[];
7
+ tools: boolean;
8
+ parallelToolCalls?: boolean;
9
+ structuredOutput?: boolean;
10
+ reasoningReplay: "none" | "optional" | "required";
11
+ promptCaching?: boolean;
12
+ mediaForms: {
13
+ imageUrl?: boolean;
14
+ imageBase64?: boolean;
15
+ fileId?: boolean;
16
+ audioUrl?: boolean;
17
+ audioBase64?: boolean;
18
+ };
19
+ }
20
+ export interface ProtocolRuntimeCapabilityOverrides {
21
+ acceptedInputModalities?: readonly InputModality[];
22
+ emittedOutputModalities?: readonly OutputModality[];
23
+ tools?: boolean;
24
+ parallelToolCalls?: boolean;
25
+ structuredOutput?: boolean;
26
+ reasoningReplay?: ProtocolRuntimeCapabilities["reasoningReplay"];
27
+ promptCaching?: boolean;
28
+ mediaForms?: Partial<ProtocolRuntimeCapabilities["mediaForms"]>;
29
+ }
30
+ export declare const ANTHROPIC_PROTOCOL_CAPABILITIES: ProtocolRuntimeCapabilities;
31
+ export declare const GEMINI_PROTOCOL_CAPABILITIES: ProtocolRuntimeCapabilities;
32
+ export declare const OLLAMA_PROTOCOL_CAPABILITIES: ProtocolRuntimeCapabilities;
33
+ export declare const OPENAI_RESPONSES_PROTOCOL_CAPABILITIES: ProtocolRuntimeCapabilities;
34
+ export declare const OPENAI_CHAT_PROTOCOL_CAPABILITIES: ProtocolRuntimeCapabilities;