@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.
- package/README.md +28 -28
- package/dist/agent-ir.d.ts +103 -0
- package/dist/agent-ir.js +134 -0
- package/dist/agent.d.ts +67 -0
- package/dist/agent.js +36 -0
- package/dist/collaboration/harness.js +1 -1
- package/dist/collaboration/modes/creator-verifier.d.ts +2 -7
- package/dist/collaboration/modes/creator-verifier.js +4 -6
- package/dist/collaboration/pool.d.ts +8 -20
- package/dist/collaboration/pool.js +27 -97
- package/dist/compat/anthropic/mcp.d.ts +15 -0
- package/dist/compat/anthropic/mcp.js +10 -0
- package/dist/compat/openai/agent.d.ts +34 -0
- package/dist/compat/openai/agent.js +24 -0
- package/dist/governance.d.ts +1 -17
- package/dist/governance.js +1 -34
- package/dist/guardrail.d.ts +6 -0
- package/dist/guardrail.js +1 -0
- package/dist/handoff-target.d.ts +12 -0
- package/dist/handoff-target.js +1 -0
- package/dist/harness/manifest.js +0 -4
- package/dist/index.d.ts +19 -7
- package/dist/index.js +8 -5
- package/dist/kernel.d.ts +2 -20
- package/dist/knowledge/public.d.ts +29 -0
- package/dist/knowledge/public.js +1 -0
- package/dist/mcp-server.d.ts +28 -0
- package/dist/mcp-server.js +1 -0
- package/dist/memory/agent.d.ts +2 -2
- package/dist/memory/agent.js +2 -2
- package/dist/memory/durable.d.ts +16 -0
- package/dist/memory/durable.js +46 -0
- package/dist/memory/in-memory-store.d.ts +9 -7
- package/dist/memory/in-memory-store.js +8 -2
- package/dist/memory/protocols.d.ts +22 -4
- package/dist/memory/public.d.ts +4 -3
- package/dist/memory/public.js +3 -2
- package/dist/providers/anthropic-adapter.d.ts +47 -0
- package/dist/providers/anthropic-adapter.js +444 -0
- package/dist/providers/anthropic-compatible.d.ts +2 -3
- package/dist/providers/anthropic-compatible.js +8 -5
- package/dist/providers/anthropic.d.ts +19 -23
- package/dist/providers/anthropic.js +169 -395
- package/dist/providers/base.js +43 -3
- package/dist/providers/capability-router.d.ts +29 -0
- package/dist/providers/capability-router.js +43 -0
- package/dist/providers/catalog.d.ts +16 -4
- package/dist/providers/catalog.js +112 -21
- package/dist/providers/content-normalization.d.ts +57 -0
- package/dist/providers/content-normalization.js +238 -0
- package/dist/providers/content-policy.d.ts +16 -0
- package/dist/providers/content-policy.js +39 -0
- package/dist/providers/credentials.d.ts +83 -0
- package/dist/providers/credentials.js +190 -0
- package/dist/providers/endpoints.d.ts +137 -0
- package/dist/providers/endpoints.js +128 -0
- package/dist/providers/factories.js +9 -3
- package/dist/providers/gemini-adapter.d.ts +33 -0
- package/dist/providers/gemini-adapter.js +272 -0
- package/dist/providers/gemini.d.ts +16 -3
- package/dist/providers/gemini.js +97 -195
- package/dist/providers/model-catalog.d.ts +37 -0
- package/dist/providers/model-catalog.js +62 -0
- package/dist/providers/model-registry.d.ts +98 -0
- package/dist/providers/model-registry.js +315 -0
- package/dist/providers/ollama-adapter.d.ts +65 -0
- package/dist/providers/ollama-adapter.js +188 -0
- package/dist/providers/ollama.d.ts +9 -4
- package/dist/providers/ollama.js +96 -109
- package/dist/providers/openai-chat-dialects.d.ts +154 -0
- package/dist/providers/openai-chat-dialects.js +179 -0
- package/dist/providers/openai-chat.d.ts +46 -18
- package/dist/providers/openai-chat.js +418 -51
- package/dist/providers/openai-responses-adapter.d.ts +42 -0
- package/dist/providers/openai-responses-adapter.js +341 -0
- package/dist/providers/openai-responses.d.ts +14 -33
- package/dist/providers/openai-responses.js +131 -264
- package/dist/providers/openai.d.ts +29 -76
- package/dist/providers/openai.js +195 -292
- package/dist/providers/protocol-adapter.d.ts +34 -0
- package/dist/providers/protocol-adapter.js +9 -0
- package/dist/providers/protocol-capabilities.d.ts +34 -0
- package/dist/providers/protocol-capabilities.js +44 -0
- package/dist/providers/provider-error.d.ts +31 -0
- package/dist/providers/provider-error.js +149 -0
- package/dist/providers/public.d.ts +26 -3
- package/dist/providers/public.js +13 -1
- package/dist/providers/registry.d.ts +7 -6
- package/dist/providers/registry.js +47 -20
- package/dist/providers/request-plan.d.ts +86 -0
- package/dist/providers/request-plan.js +175 -0
- package/dist/providers/usage-normalizer.d.ts +48 -0
- package/dist/providers/usage-normalizer.js +96 -0
- package/dist/providers/vendor-profiles.d.ts +2 -15
- package/dist/providers/vendor-profiles.js +14 -60
- package/dist/runtime/canonical-kernel-step.d.ts +1 -2
- package/dist/runtime/canonical-kernel-step.js +47 -12
- package/dist/runtime/context-policy.d.ts +10 -12
- package/dist/runtime/context-policy.js +6 -8
- package/dist/runtime/durable-content.d.ts +50 -0
- package/dist/runtime/durable-content.js +159 -0
- package/dist/runtime/execution-plane.d.ts +2 -2
- package/dist/runtime/execution-plane.js +2 -2
- package/dist/runtime/kernel-event-log.js +0 -1
- package/dist/runtime/kernel-step.d.ts +0 -1
- package/dist/runtime/kernel-step.js +4 -2
- package/dist/runtime/mcp-proxy-plane.d.ts +25 -1
- package/dist/runtime/mcp-proxy-plane.js +44 -6
- package/dist/runtime/output-schema.d.ts +1 -2
- package/dist/runtime/provider-replay.d.ts +1 -1
- package/dist/runtime/provider-replay.js +12 -27
- package/dist/runtime/reactive-session.d.ts +1 -1
- package/dist/runtime/reactive-session.js +2 -3
- package/dist/runtime/run-group.d.ts +1 -1
- package/dist/runtime/runner.d.ts +26 -40
- package/dist/runtime/runner.js +145 -63
- package/dist/runtime/session-log.d.ts +8 -1
- package/dist/runtime/session-log.js +42 -2
- package/dist/runtime/session-repair.d.ts +1 -1
- package/dist/runtime/session-repair.js +1 -1
- package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
- package/dist/runtime/sub-agent-orchestrator.js +8 -11
- package/dist/runtime/workflow-control-flow.d.ts +0 -4
- package/dist/runtime/workflow-control-flow.js +0 -16
- package/dist/session.d.ts +11 -0
- package/dist/session.js +1 -0
- package/dist/skill.d.ts +17 -0
- package/dist/skill.js +16 -0
- package/dist/skills/loader.d.ts +3 -0
- package/dist/tools/errors.d.ts +1 -3
- package/dist/tools/errors.js +1 -3
- package/dist/tools/index.d.ts +3 -0
- package/dist/types/agent.d.ts +21 -9
- package/dist/types/agent.js +30 -4
- package/dist/types.d.ts +128 -13
- package/package.json +3 -3
- package/dist/providers/deepseek.d.ts +0 -46
- package/dist/providers/deepseek.js +0 -97
- package/dist/providers/glm.d.ts +0 -25
- package/dist/providers/glm.js +0 -48
- package/dist/providers/kimi.d.ts +0 -23
- package/dist/providers/kimi.js +0 -30
- package/dist/providers/minimax.d.ts +0 -49
- package/dist/providers/minimax.js +0 -98
- package/dist/providers/profiles.d.ts +0 -1992
- package/dist/providers/profiles.js +0 -796
- package/dist/providers/qwen.d.ts +0 -38
- 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
|
-
|
|
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 {};
|
package/dist/providers/gemini.js
CHANGED
|
@@ -1,163 +1,98 @@
|
|
|
1
1
|
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
2
|
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
3
|
-
import { CircuitBreaker
|
|
4
|
-
import { endpointProfiles } from "./
|
|
5
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
132
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
return
|
|
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
|
-
|
|
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
|
}
|