@deepstrike/sdk 0.2.52 → 0.2.60

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 (148) 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/providers/anthropic-adapter.d.ts +47 -0
  39. package/dist/providers/anthropic-adapter.js +444 -0
  40. package/dist/providers/anthropic-compatible.d.ts +2 -3
  41. package/dist/providers/anthropic-compatible.js +8 -5
  42. package/dist/providers/anthropic.d.ts +19 -23
  43. package/dist/providers/anthropic.js +169 -395
  44. package/dist/providers/base.js +43 -3
  45. package/dist/providers/capability-router.d.ts +29 -0
  46. package/dist/providers/capability-router.js +43 -0
  47. package/dist/providers/catalog.d.ts +16 -4
  48. package/dist/providers/catalog.js +112 -21
  49. package/dist/providers/content-normalization.d.ts +57 -0
  50. package/dist/providers/content-normalization.js +238 -0
  51. package/dist/providers/content-policy.d.ts +16 -0
  52. package/dist/providers/content-policy.js +39 -0
  53. package/dist/providers/credentials.d.ts +83 -0
  54. package/dist/providers/credentials.js +190 -0
  55. package/dist/providers/endpoints.d.ts +137 -0
  56. package/dist/providers/endpoints.js +128 -0
  57. package/dist/providers/factories.js +9 -3
  58. package/dist/providers/gemini-adapter.d.ts +33 -0
  59. package/dist/providers/gemini-adapter.js +272 -0
  60. package/dist/providers/gemini.d.ts +16 -3
  61. package/dist/providers/gemini.js +97 -195
  62. package/dist/providers/model-catalog.d.ts +37 -0
  63. package/dist/providers/model-catalog.js +62 -0
  64. package/dist/providers/model-registry.d.ts +98 -0
  65. package/dist/providers/model-registry.js +315 -0
  66. package/dist/providers/ollama-adapter.d.ts +65 -0
  67. package/dist/providers/ollama-adapter.js +188 -0
  68. package/dist/providers/ollama.d.ts +9 -4
  69. package/dist/providers/ollama.js +96 -109
  70. package/dist/providers/openai-chat-dialects.d.ts +154 -0
  71. package/dist/providers/openai-chat-dialects.js +179 -0
  72. package/dist/providers/openai-chat.d.ts +46 -18
  73. package/dist/providers/openai-chat.js +418 -51
  74. package/dist/providers/openai-responses-adapter.d.ts +42 -0
  75. package/dist/providers/openai-responses-adapter.js +341 -0
  76. package/dist/providers/openai-responses.d.ts +14 -33
  77. package/dist/providers/openai-responses.js +131 -264
  78. package/dist/providers/openai.d.ts +29 -76
  79. package/dist/providers/openai.js +195 -292
  80. package/dist/providers/protocol-adapter.d.ts +34 -0
  81. package/dist/providers/protocol-adapter.js +9 -0
  82. package/dist/providers/protocol-capabilities.d.ts +34 -0
  83. package/dist/providers/protocol-capabilities.js +44 -0
  84. package/dist/providers/provider-error.d.ts +31 -0
  85. package/dist/providers/provider-error.js +149 -0
  86. package/dist/providers/public.d.ts +26 -3
  87. package/dist/providers/public.js +13 -1
  88. package/dist/providers/registry.d.ts +7 -6
  89. package/dist/providers/registry.js +47 -20
  90. package/dist/providers/request-plan.d.ts +86 -0
  91. package/dist/providers/request-plan.js +175 -0
  92. package/dist/providers/usage-normalizer.d.ts +48 -0
  93. package/dist/providers/usage-normalizer.js +96 -0
  94. package/dist/providers/vendor-profiles.d.ts +2 -15
  95. package/dist/providers/vendor-profiles.js +14 -60
  96. package/dist/runtime/canonical-kernel-step.d.ts +1 -2
  97. package/dist/runtime/canonical-kernel-step.js +47 -12
  98. package/dist/runtime/context-policy.d.ts +10 -12
  99. package/dist/runtime/context-policy.js +6 -8
  100. package/dist/runtime/durable-content.d.ts +50 -0
  101. package/dist/runtime/durable-content.js +159 -0
  102. package/dist/runtime/execution-plane.d.ts +2 -2
  103. package/dist/runtime/execution-plane.js +2 -2
  104. package/dist/runtime/kernel-event-log.js +0 -1
  105. package/dist/runtime/kernel-step.d.ts +0 -1
  106. package/dist/runtime/kernel-step.js +4 -2
  107. package/dist/runtime/mcp-proxy-plane.d.ts +25 -1
  108. package/dist/runtime/mcp-proxy-plane.js +44 -6
  109. package/dist/runtime/output-schema.d.ts +1 -2
  110. package/dist/runtime/provider-replay.d.ts +1 -1
  111. package/dist/runtime/provider-replay.js +12 -27
  112. package/dist/runtime/reactive-session.d.ts +1 -1
  113. package/dist/runtime/reactive-session.js +2 -3
  114. package/dist/runtime/run-group.d.ts +1 -1
  115. package/dist/runtime/runner.d.ts +26 -40
  116. package/dist/runtime/runner.js +145 -63
  117. package/dist/runtime/session-log.d.ts +8 -1
  118. package/dist/runtime/session-log.js +42 -2
  119. package/dist/runtime/session-repair.d.ts +1 -1
  120. package/dist/runtime/session-repair.js +1 -1
  121. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  122. package/dist/runtime/sub-agent-orchestrator.js +8 -11
  123. package/dist/runtime/workflow-control-flow.d.ts +0 -4
  124. package/dist/runtime/workflow-control-flow.js +0 -16
  125. package/dist/session.d.ts +11 -0
  126. package/dist/session.js +1 -0
  127. package/dist/skill.d.ts +17 -0
  128. package/dist/skill.js +16 -0
  129. package/dist/skills/loader.d.ts +3 -0
  130. package/dist/tools/errors.d.ts +1 -3
  131. package/dist/tools/errors.js +1 -3
  132. package/dist/tools/index.d.ts +3 -0
  133. package/dist/types/agent.d.ts +21 -9
  134. package/dist/types/agent.js +30 -4
  135. package/dist/types.d.ts +128 -13
  136. package/package.json +3 -3
  137. package/dist/providers/deepseek.d.ts +0 -46
  138. package/dist/providers/deepseek.js +0 -97
  139. package/dist/providers/glm.d.ts +0 -25
  140. package/dist/providers/glm.js +0 -48
  141. package/dist/providers/kimi.d.ts +0 -23
  142. package/dist/providers/kimi.js +0 -30
  143. package/dist/providers/minimax.d.ts +0 -49
  144. package/dist/providers/minimax.js +0 -98
  145. package/dist/providers/profiles.d.ts +0 -1992
  146. package/dist/providers/profiles.js +0 -796
  147. package/dist/providers/qwen.d.ts +0 -38
  148. package/dist/providers/qwen.js +0 -97
@@ -0,0 +1,272 @@
1
+ import { projectToolOutputToText } from "./content-normalization.js";
2
+ import { normalizeToolCall } from "./base.js";
3
+ import { ProtocolResponseError, GEMINI_PROTOCOL_CAPABILITIES, } from "./protocol-adapter.js";
4
+ // Google Generate Content streams response chunks, while @google/generative-ai 0.24.1 exposes
5
+ // a separate promise for the aggregated response. Candidate finishReason and aggregate usage are
6
+ // therefore decoded only by finishStream.
7
+ // Source: https://ai.google.dev/api/generate-content#method:-models.streamgeneratecontent
8
+ // Source: https://github.com/google-gemini/deprecated-generative-ai-js/blob/v0.24.1/types/responses.ts
9
+ function parseArguments(argumentsJson) {
10
+ try {
11
+ const value = JSON.parse(argumentsJson);
12
+ return value && typeof value === "object" && !Array.isArray(value)
13
+ ? value
14
+ : {};
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ }
20
+ function toolName(callId, messages) {
21
+ for (let index = messages.length - 1; index >= 0; index--) {
22
+ const match = messages[index].toolCalls?.find(call => call.id === callId);
23
+ if (match)
24
+ return match.name;
25
+ }
26
+ return callId;
27
+ }
28
+ function toolResultPart(result, messages) {
29
+ return {
30
+ functionResponse: {
31
+ name: toolName(result.callId, messages),
32
+ response: { output: projectToolOutputToText(result.blocks) },
33
+ },
34
+ };
35
+ }
36
+ function contentPart(block) {
37
+ if (block.type === "text")
38
+ return block.text ? { text: block.text } : undefined;
39
+ if (block.type === "image" || block.type === "audio") {
40
+ if (block.source.kind === "base64") {
41
+ return {
42
+ inlineData: {
43
+ mimeType: block.mediaType ?? (block.type === "image" ? "image/png" : "audio/wav"),
44
+ data: block.source.data,
45
+ },
46
+ };
47
+ }
48
+ if (block.source.kind === "url") {
49
+ return {
50
+ fileData: {
51
+ mimeType: block.mediaType ?? (block.type === "image" ? "image/png" : "audio/wav"),
52
+ fileUri: block.source.url,
53
+ },
54
+ };
55
+ }
56
+ }
57
+ if (block.type === "video" || block.type === "file") {
58
+ throw new ProtocolResponseError("gemini", `cannot serialize ${block.type}`);
59
+ }
60
+ throw new ProtocolResponseError("gemini", `cannot serialize ${block.type} source`);
61
+ }
62
+ export function canonicalGeminiContents(context) {
63
+ const messages = context.stateTurn ? [...context.turns, context.stateTurn] : context.turns;
64
+ const contents = [];
65
+ for (const message of messages) {
66
+ const parts = [];
67
+ for (const call of message.toolCalls ?? []) {
68
+ parts.push({
69
+ functionCall: { name: call.name, args: parseArguments(call.arguments) },
70
+ });
71
+ }
72
+ for (const block of message.blocks) {
73
+ if (block.type === "tool_result") {
74
+ parts.push(toolResultPart(block, messages));
75
+ }
76
+ else {
77
+ const part = contentPart(block);
78
+ if (part)
79
+ parts.push(part);
80
+ }
81
+ }
82
+ if (!parts.length)
83
+ continue;
84
+ contents.push({
85
+ role: message.role === "assistant" ? "model" : "user",
86
+ parts,
87
+ });
88
+ }
89
+ return contents;
90
+ }
91
+ function buildTools(tools) {
92
+ if (!tools.length)
93
+ return [];
94
+ return [{
95
+ functionDeclarations: tools.map(tool => ({
96
+ name: tool.name,
97
+ description: tool.description,
98
+ parameters: JSON.parse(tool.parameters),
99
+ })),
100
+ }];
101
+ }
102
+ function modelExtensions(extensions) {
103
+ const { model: _model, systemInstruction: _systemInstruction, tools: _tools, google_search: _googleSearch, response_mime_type: _responseMimeType, response_schema: _responseSchema, generationConfig: _generationConfig, ...rest } = extensions;
104
+ return rest;
105
+ }
106
+ export function geminiVendorConfig(extensions) {
107
+ const tools = [];
108
+ if (extensions.google_search) {
109
+ tools.push({
110
+ googleSearch: typeof extensions.google_search === "object"
111
+ ? extensions.google_search
112
+ : {},
113
+ });
114
+ }
115
+ const generationConfig = {
116
+ ...extensions.generationConfig,
117
+ };
118
+ if (extensions.response_mime_type != null) {
119
+ generationConfig.responseMimeType = extensions.response_mime_type;
120
+ }
121
+ if (extensions.response_schema != null) {
122
+ generationConfig.responseSchema = extensions.response_schema;
123
+ }
124
+ return {
125
+ ...(tools.length ? { tools } : {}),
126
+ ...(Object.keys(generationConfig).length ? { generationConfig } : {}),
127
+ };
128
+ }
129
+ function decodeParts(raw) {
130
+ const candidate = raw.candidates?.[0];
131
+ let content = "";
132
+ const toolCalls = [];
133
+ for (const part of candidate?.content.parts ?? []) {
134
+ if (part.text)
135
+ content += part.text;
136
+ else if (part.functionCall) {
137
+ const call = normalizeToolCall(part.functionCall.name, part.functionCall.name, part.functionCall.args);
138
+ if (call)
139
+ toolCalls.push(call);
140
+ }
141
+ }
142
+ return { content, toolCalls };
143
+ }
144
+ function numberField(raw, field) {
145
+ const value = raw[field];
146
+ if (value === undefined)
147
+ return undefined;
148
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
149
+ throw new ProtocolResponseError("gemini", `usage.${field} must be a non-negative finite number`);
150
+ }
151
+ return value;
152
+ }
153
+ export class GeminiAdapter {
154
+ protocol = "gemini";
155
+ protocolCapabilities = GEMINI_PROTOCOL_CAPABILITIES;
156
+ buildRequest(input) {
157
+ const extensions = input.extensions;
158
+ const vendor = geminiVendorConfig(extensions);
159
+ const tools = [...buildTools(input.tools), ...(vendor.tools ?? [])];
160
+ return {
161
+ modelParams: {
162
+ ...modelExtensions(extensions),
163
+ model: input.resolved.identity.modelId,
164
+ ...(input.context.systemText ? { systemInstruction: input.context.systemText } : {}),
165
+ ...(tools.length ? { tools } : {}),
166
+ ...(vendor.generationConfig ? { generationConfig: vendor.generationConfig } : {}),
167
+ },
168
+ request: { contents: canonicalGeminiContents(input.context) },
169
+ };
170
+ }
171
+ decodeComplete(raw, _input) {
172
+ const decoded = decodeParts(raw);
173
+ const usage = this.normalizeUsage(raw.usageMetadata);
174
+ const rawUsage = raw.usageMetadata;
175
+ const tokenCount = usage?.outputTokens
176
+ ?? (rawUsage ? numberField(rawUsage, "totalTokenCount") : undefined);
177
+ return {
178
+ message: {
179
+ role: "assistant",
180
+ content: decoded.content,
181
+ ...(tokenCount !== undefined ? { tokenCount } : {}),
182
+ toolCalls: decoded.toolCalls,
183
+ },
184
+ };
185
+ }
186
+ createStreamState(input) {
187
+ return { input: input.input, toolCalls: [] };
188
+ }
189
+ pushStreamChunk(chunk, state) {
190
+ const events = [];
191
+ for (const part of chunk.candidates?.[0]?.content.parts ?? []) {
192
+ if (part.text)
193
+ events.push({ type: "text_delta", delta: part.text });
194
+ else if (part.functionCall) {
195
+ state.toolCalls.push({
196
+ name: part.functionCall.name,
197
+ args: part.functionCall.args,
198
+ });
199
+ }
200
+ }
201
+ return { events };
202
+ }
203
+ finishStream(state, final) {
204
+ const events = state.toolCalls.map((call, index) => ({
205
+ type: "tool_call",
206
+ id: `call_${index + 1}`,
207
+ name: call.name,
208
+ arguments: call.args,
209
+ }));
210
+ const usage = this.normalizeUsage(final.usageMetadata);
211
+ const rawStopReason = final.candidates?.[0]?.finishReason;
212
+ const stopReason = this.normalizeStopReason(rawStopReason);
213
+ if (usage) {
214
+ const raw = final.usageMetadata;
215
+ const totalTokens = numberField(raw, "totalTokenCount")
216
+ ?? usage.inputTokens + usage.outputTokens;
217
+ events.push({
218
+ type: "usage",
219
+ totalTokens,
220
+ inputTokens: usage.inputTokens,
221
+ outputTokens: usage.outputTokens,
222
+ ...(usage.cacheReadInputTokens
223
+ ? { cacheReadInputTokens: usage.cacheReadInputTokens }
224
+ : {}),
225
+ providerUsage: usage,
226
+ ...(stopReason ? { stopReason } : {}),
227
+ ...(rawStopReason ? { rawStopReason } : {}),
228
+ });
229
+ }
230
+ return { events };
231
+ }
232
+ normalizeUsage(raw) {
233
+ if (raw === undefined || raw === null)
234
+ return undefined;
235
+ if (typeof raw !== "object" || Array.isArray(raw)) {
236
+ throw new ProtocolResponseError("gemini", "usage must be an object");
237
+ }
238
+ const usage = raw;
239
+ const inputTokens = numberField(usage, "promptTokenCount");
240
+ const outputTokens = numberField(usage, "candidatesTokenCount");
241
+ numberField(usage, "totalTokenCount");
242
+ const cacheReadInputTokens = numberField(usage, "cachedContentTokenCount");
243
+ if (inputTokens === undefined
244
+ && outputTokens === undefined
245
+ && cacheReadInputTokens === undefined)
246
+ return undefined;
247
+ return {
248
+ inputTokens: inputTokens ?? 0,
249
+ outputTokens: outputTokens ?? 0,
250
+ ...(cacheReadInputTokens ? { cacheReadInputTokens } : {}),
251
+ };
252
+ }
253
+ normalizeStopReason(raw) {
254
+ if (raw === undefined)
255
+ return undefined;
256
+ switch (raw) {
257
+ case "STOP":
258
+ case "FINISH_REASON_STOP":
259
+ return "end_turn";
260
+ case "MAX_TOKENS":
261
+ return "max_tokens";
262
+ case "SAFETY":
263
+ case "RECITATION":
264
+ case "BLOCKLIST":
265
+ case "PROHIBITED_CONTENT":
266
+ case "SPII":
267
+ return "content_filter";
268
+ default:
269
+ return "other";
270
+ }
271
+ }
272
+ }
@@ -1,21 +1,33 @@
1
1
  import { type Content } from "@google/generative-ai";
2
- import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider, RuntimePolicy } from "../types.js";
2
+ import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider, RuntimePolicy, PromptMeasurement } from "../types.js";
3
+ import { type CanonicalAdapterInput } from "./content-normalization.js";
4
+ type ResolvedGeminiRuntime = CanonicalAdapterInput["resolved"];
3
5
  export declare function buildContents(turns: Message[]): Content[];
4
6
  export declare class GeminiProvider implements LLMProvider {
5
7
  private readonly model;
8
+ private resolvedRuntime?;
6
9
  private genAI;
7
10
  private circuit;
8
11
  private maxRetries;
9
12
  private baseDelay;
10
13
  private requestOptions;
14
+ private readonly resolvedRuntimePolicy;
15
+ private readonly adapter;
11
16
  constructor(apiKey: string, model?: string, retry?: {
12
17
  maxRetries: number;
13
18
  baseDelay: number;
14
- }, baseURL?: string);
19
+ }, baseURL?: string, runtimePolicy?: RuntimePolicy, resolvedRuntime?: ResolvedGeminiRuntime | undefined);
15
20
  runtimePolicy(): RuntimePolicy;
21
+ bindResolvedRuntime(resolved: ResolvedGeminiRuntime): void;
22
+ private adapterInput;
16
23
  complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
17
24
  stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
18
- private modelExtensions;
25
+ /**
26
+ * spc_011-C-05: preflight native token count via `GenerativeModel.countTokens` — reuses the
27
+ * same contents/tools/vendorConfig construction `complete()` uses so the counted request and
28
+ * the sent request never diverge.
29
+ */
30
+ countTokens(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<PromptMeasurement>;
19
31
  /**
20
32
  * Gemini vendor features from extensions, mapped to the Node SDK shape (mirrors the Python provider's
21
33
  * extension keys for a consistent cross-SDK API):
@@ -29,3 +41,4 @@ export declare class GeminiProvider implements LLMProvider {
29
41
  generationConfig?: Record<string, unknown>;
30
42
  };
31
43
  }
44
+ export {};
@@ -1,163 +1,98 @@
1
1
  import { GoogleGenerativeAI } from "@google/generative-ai";
2
2
  import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { CircuitBreaker, normalizeToolCall, turnsWithStateAppended } from "./base.js";
4
- import { endpointProfiles } from "./profiles.js";
5
- import { UnsupportedModalityError } from "./base.js";
3
+ import { CircuitBreaker } from "./base.js";
4
+ import { endpointProfiles } from "./endpoints.js";
5
+ import { normalizeCanonicalAdapterInput, normalizeCanonicalContext, } from "./content-normalization.js";
6
+ import { GeminiAdapter, canonicalGeminiContents, geminiVendorConfig } from "./gemini-adapter.js";
7
+ import { circuitOpenError, classifyProviderError } from "./provider-error.js";
6
8
  const GEMINI_BASE = endpointProfiles["gemini.google"].baseURL;
7
- const GEMINI_POLICIES = {
8
- "gemini-3-pro-preview": { maxTurns: 50 },
9
- "gemini-3-flash-preview": { maxTurns: 25 },
10
- "gemini-3.5-flash": { maxTurns: 30 },
11
- "gemini-2.5-pro": { maxTurns: 35 },
12
- "gemini-2.5-flash": { maxTurns: 20 },
13
- "gemini-2.0-flash": { maxTurns: 15 },
14
- "gemini-2.0-flash-lite": { maxTurns: 10 },
15
- "gemini-1.5-pro": { maxTurns: 30 },
16
- "gemini-1.5-flash": { maxTurns: 15 },
17
- };
18
9
  export function buildContents(turns) {
19
- const contents = [];
20
- for (const msg of turns) {
21
- if (msg.role === "tool") {
22
- const parts = (msg.contentParts ?? [])
23
- .filter(p => p.type === "tool_result")
24
- .map(p => {
25
- if (p.type !== "tool_result")
26
- return { text: "" };
27
- let toolName = p.callId;
28
- for (let i = turns.length - 1; i >= 0; i--) {
29
- const turn = turns[i];
30
- if (turn.role === "assistant" && turn.toolCalls) {
31
- const matched = turn.toolCalls.find(tc => tc.id === p.callId);
32
- if (matched) {
33
- toolName = matched.name;
34
- break;
35
- }
36
- }
37
- }
38
- return {
39
- functionResponse: { name: toolName, response: { output: p.output } },
40
- };
41
- });
42
- if (parts.length)
43
- contents.push({ role: "user", parts });
44
- continue;
45
- }
46
- const role = msg.role === "assistant" ? "model" : "user";
47
- const parts = [];
48
- if (msg.toolCalls?.length) {
49
- for (const tc of msg.toolCalls) {
50
- let args = {};
51
- try {
52
- args = JSON.parse(tc.arguments);
53
- }
54
- catch {
55
- args = {};
56
- }
57
- parts.push({ functionCall: { name: tc.name, args } });
58
- }
59
- }
60
- // Multimodal: render contentParts (text + image) when present, else the plain
61
- // text body. Without this, image inputs to Gemini were silently dropped.
62
- if (msg.contentParts?.length) {
63
- for (const p of msg.contentParts) {
64
- if (p.type === "text")
65
- parts.push({ text: p.text });
66
- else if (p.type === "image") {
67
- if (p.data)
68
- parts.push({ inlineData: { mimeType: p.mediaType ?? "image/png", data: p.data } });
69
- else if (p.url)
70
- parts.push({ fileData: { mimeType: p.mediaType ?? "image/png", fileUri: p.url } });
71
- }
72
- else if (p.type === "audio") {
73
- if (!p.data)
74
- throw new UnsupportedModalityError("audio", "gemini");
75
- parts.push({ inlineData: { mimeType: p.mediaType ?? "audio/wav", data: p.data } });
76
- }
77
- else if (p.type === "tool_result") {
78
- // tool results are handled via functionResponse on tool role messages
79
- }
80
- else {
81
- throw new UnsupportedModalityError(String(p.type ?? "unknown"), "gemini");
82
- }
83
- }
84
- }
85
- else if (msg.content) {
86
- parts.push({ text: msg.content });
87
- }
88
- if (parts.length)
89
- contents.push({ role, parts });
90
- }
91
- return contents;
92
- }
93
- function buildTools(tools) {
94
- if (!tools.length)
95
- return [];
96
- return [{
97
- functionDeclarations: tools.map(t => ({
98
- name: t.name,
99
- description: t.description,
100
- parameters: JSON.parse(t.parameters),
101
- })),
102
- }];
10
+ return canonicalGeminiContents(normalizeCanonicalContext({ systemText: "", turns }));
103
11
  }
104
12
  export class GeminiProvider {
105
13
  model;
14
+ resolvedRuntime;
106
15
  genAI;
107
16
  circuit;
108
17
  maxRetries;
109
18
  baseDelay;
110
19
  requestOptions;
111
- constructor(apiKey, model = "gemini-2.0-flash", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = GEMINI_BASE) {
20
+ resolvedRuntimePolicy;
21
+ adapter = new GeminiAdapter();
22
+ constructor(apiKey, model = "gemini-2.0-flash", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = GEMINI_BASE, runtimePolicy = {}, resolvedRuntime) {
112
23
  this.model = model;
24
+ this.resolvedRuntime = resolvedRuntime;
113
25
  this.genAI = withServerRuntimeGuard(() => new GoogleGenerativeAI(apiKey));
114
26
  this.circuit = new CircuitBreaker();
115
27
  this.maxRetries = retry.maxRetries;
116
28
  this.baseDelay = retry.baseDelay;
117
29
  this.requestOptions = { baseUrl: baseURL };
30
+ this.resolvedRuntimePolicy = runtimePolicy;
118
31
  }
119
32
  runtimePolicy() {
120
- return GEMINI_POLICIES[this.model] ?? {};
33
+ return this.resolvedRuntimePolicy;
34
+ }
35
+ bindResolvedRuntime(resolved) {
36
+ if (resolved.identity.protocol !== "gemini"
37
+ || resolved.identity.modelId !== this.model) {
38
+ throw new Error("GeminiProvider received a mismatched resolved runtime");
39
+ }
40
+ this.resolvedRuntime = resolved;
41
+ }
42
+ adapterInput(context, tools, extensions) {
43
+ if (!this.resolvedRuntime) {
44
+ // Direct class construction is a published compatibility path. A-07 replaces it with
45
+ // injected runtime profiles; until then this local descriptor contains no Registry lookup.
46
+ const resolved = {
47
+ identity: {
48
+ providerId: "gemini",
49
+ modelId: this.model,
50
+ endpointId: "gemini.google",
51
+ protocol: "gemini",
52
+ },
53
+ model: { id: `gemini/${this.model}`, providerId: "gemini", kind: "generation", intrinsic: {} },
54
+ endpoint: endpointProfiles["gemini.google"],
55
+ adapter: this,
56
+ effectiveCapabilities: {
57
+ inputModalities: Object.fromEntries(["text", "image", "audio", "video", "file"].map(modality => [modality, { state: modality === "video" || modality === "file" ? "unsupported" : "unknown", evidence: [] }])),
58
+ outputModalities: Object.fromEntries(["text", "image", "audio", "embedding"].map(modality => [modality, { state: "unknown", evidence: [] }])),
59
+ tools: { state: "unknown", evidence: [] },
60
+ reasoning: { state: "unknown", evidence: [] },
61
+ parallelToolCalls: { state: "unknown", evidence: [] },
62
+ structuredOutput: { state: "unknown", evidence: [] },
63
+ promptCaching: { state: "unknown", evidence: [] },
64
+ nativeTokenCounting: { state: "unknown", evidence: [] },
65
+ mediaForms: Object.fromEntries(["imageUrl", "imageBase64", "fileId", "audioUrl", "audioBase64"].map(form => [form, { state: "unknown", evidence: [] }])),
66
+ },
67
+ };
68
+ return normalizeCanonicalAdapterInput({ context, tools, resolved, extensions });
69
+ }
70
+ return normalizeCanonicalAdapterInput({
71
+ context,
72
+ tools,
73
+ resolved: this.resolvedRuntime,
74
+ extensions,
75
+ });
121
76
  }
122
77
  async complete(context, tools, extensions) {
123
78
  if (this.circuit.isOpen())
124
- throw new Error("Circuit breaker open");
125
- const system = context.systemText || undefined;
126
- const contents = buildContents(turnsWithStateAppended(context));
127
- const geminiTools = buildTools(tools);
79
+ throw circuitOpenError("gemini");
80
+ let input;
81
+ let plan;
82
+ try {
83
+ input = this.adapterInput(context, tools, extensions);
84
+ plan = this.adapter.buildRequest(input);
85
+ }
86
+ catch (error) {
87
+ throw classifyProviderError("gemini", error);
88
+ }
128
89
  let lastErr;
129
90
  for (let i = 0; i < this.maxRetries; i++) {
130
91
  try {
131
- const vc = this.vendorConfig(extensions);
132
- const allTools = [...geminiTools, ...(vc.tools ?? [])];
133
- const m = this.genAI.getGenerativeModel({
134
- ...this.modelExtensions(extensions),
135
- model: this.model,
136
- ...(system ? { systemInstruction: system } : {}),
137
- ...(allTools.length ? { tools: allTools } : {}),
138
- ...(vc.generationConfig ? { generationConfig: vc.generationConfig } : {}),
139
- }, this.requestOptions);
140
- const resp = await m.generateContent({ contents });
92
+ const m = this.genAI.getGenerativeModel(plan.modelParams, this.requestOptions);
93
+ const resp = await m.generateContent(plan.request);
141
94
  this.circuit.recordSuccess();
142
- const candidate = resp.response.candidates?.[0];
143
- let content = "";
144
- const toolCalls = [];
145
- for (const part of candidate?.content.parts ?? []) {
146
- if (part.text)
147
- content += part.text;
148
- else if (part.functionCall) {
149
- const tc = normalizeToolCall(part.functionCall.name, part.functionCall.name, part.functionCall.args);
150
- if (tc)
151
- toolCalls.push(tc);
152
- }
153
- }
154
- const usage = resp.response.usageMetadata;
155
- return {
156
- role: "assistant",
157
- content,
158
- tokenCount: usage?.candidatesTokenCount ?? usage?.totalTokenCount,
159
- toolCalls,
160
- };
95
+ return this.adapter.decodeComplete(resp.response, { input }).message;
161
96
  }
162
97
  catch (err) {
163
98
  lastErr = err;
@@ -166,60 +101,40 @@ export class GeminiProvider {
166
101
  await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
167
102
  }
168
103
  }
169
- throw lastErr;
104
+ throw classifyProviderError("gemini", lastErr);
170
105
  }
171
106
  async *stream(context, tools, extensions) {
172
- const system = context.systemText || undefined;
173
- const contents = buildContents(turnsWithStateAppended(context));
174
- const geminiTools = buildTools(tools);
175
- const vc = this.vendorConfig(extensions);
176
- const allTools = [...geminiTools, ...(vc.tools ?? [])];
177
- const m = this.genAI.getGenerativeModel({
178
- ...this.modelExtensions(extensions),
179
- model: this.model,
180
- ...(system ? { systemInstruction: system } : {}),
181
- ...(allTools.length ? { tools: allTools } : {}),
182
- ...(vc.generationConfig ? { generationConfig: vc.generationConfig } : {}),
183
- }, this.requestOptions);
184
- const result = await m.generateContentStream({ contents });
185
- const toolCalls = [];
186
- for await (const chunk of result.stream) {
187
- for (const part of chunk.candidates?.[0]?.content.parts ?? []) {
188
- if (part.text)
189
- yield { type: "text_delta", delta: part.text };
190
- else if (part.functionCall) {
191
- const { name, args } = part.functionCall;
192
- toolCalls.push({ id: `call_${toolCalls.length + 1}`, name, args: args });
193
- }
107
+ try {
108
+ const input = this.adapterInput(context, tools, extensions);
109
+ const plan = this.adapter.buildRequest(input);
110
+ const m = this.genAI.getGenerativeModel(plan.modelParams, this.requestOptions);
111
+ const result = await m.generateContentStream(plan.request);
112
+ const state = this.adapter.createStreamState({ input });
113
+ for await (const chunk of result.stream) {
114
+ for (const event of this.adapter.pushStreamChunk(chunk, state).events)
115
+ yield event;
194
116
  }
117
+ for (const event of this.adapter.finishStream(state, await result.response).events)
118
+ yield event;
195
119
  }
196
- for (const tc of toolCalls) {
197
- yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.args };
198
- }
199
- const usage = (await result.response).usageMetadata;
200
- if (usage?.totalTokenCount) {
201
- // Gemini implicit/explicit cache hits are reported as cachedContentTokenCount,
202
- // a subset of promptTokenCount (which stays the full prompt for accounting).
203
- const cachedTokens = usage.cachedContentTokenCount ?? 0;
204
- yield {
205
- type: "usage",
206
- totalTokens: usage.totalTokenCount,
207
- inputTokens: usage.promptTokenCount ?? 0,
208
- outputTokens: usage.candidatesTokenCount ?? 0,
209
- ...(cachedTokens > 0 ? { cacheReadInputTokens: cachedTokens } : {}),
210
- };
120
+ catch (error) {
121
+ throw classifyProviderError("gemini", error);
211
122
  }
212
123
  }
213
- modelExtensions(extensions) {
214
- if (!extensions)
215
- return {};
216
- // Strip keys handled explicitly elsewhere (incl. the vendor server-tool / structured-output keys
217
- // consumed by `vendorConfig`) so they never leak raw into getGenerativeModel.
218
- // Strip keys handled explicitly: the SDK fields set below + the named vendor keys consumed by
219
- // `vendorConfig`. A caller-provided raw `generationConfig` still passes through (and is merged with
220
- // any structured-output config at the call site).
221
- const { model: _model, systemInstruction: _systemInstruction, tools: _tools, google_search: _gs, response_mime_type: _rmt, response_schema: _rs, ...rest } = extensions;
222
- return rest;
124
+ /**
125
+ * spc_011-C-05: preflight native token count via `GenerativeModel.countTokens` — reuses the
126
+ * same contents/tools/vendorConfig construction `complete()` uses so the counted request and
127
+ * the sent request never diverge.
128
+ */
129
+ async countTokens(context, tools, extensions) {
130
+ const plan = this.adapter.buildRequest(this.adapterInput(context, tools, extensions));
131
+ const m = this.genAI.getGenerativeModel(plan.modelParams, this.requestOptions);
132
+ const resp = await m.countTokens(plan.request);
133
+ return {
134
+ inputTokens: resp.totalTokens,
135
+ source: { kind: "native", provider: "gemini" },
136
+ confidence: "exact",
137
+ };
223
138
  }
224
139
  /**
225
140
  * Gemini vendor features from extensions, mapped to the Node SDK shape (mirrors the Python provider's
@@ -230,19 +145,6 @@ export class GeminiProvider {
230
145
  * pairing this with google_search).
231
146
  */
232
147
  vendorConfig(extensions) {
233
- const ext = extensions ?? {};
234
- const tools = [];
235
- if (ext.google_search)
236
- tools.push({ googleSearch: typeof ext.google_search === "object" ? ext.google_search : {} });
237
- // Seed from any caller-provided raw generationConfig, then layer the named structured-output keys.
238
- const gc = { ...ext.generationConfig };
239
- if (ext.response_mime_type != null)
240
- gc.responseMimeType = ext.response_mime_type;
241
- if (ext.response_schema != null)
242
- gc.responseSchema = ext.response_schema;
243
- return {
244
- ...(tools.length ? { tools } : {}),
245
- ...(Object.keys(gc).length ? { generationConfig: gc } : {}),
246
- };
148
+ return geminiVendorConfig(extensions ?? {});
247
149
  }
248
150
  }