melchizedek-agents 0.9.0
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/AGENT_SETUP.md +182 -0
- package/DOCUMENTATION.md +614 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +164 -0
- package/README.md +151 -0
- package/config/agents/README.md +27 -0
- package/config/agents/examples/ares.yaml +74 -0
- package/config/agents/examples/cartographers.yaml +115 -0
- package/config/agents/examples/claude.yaml +17 -0
- package/config/agents/examples/council.yaml +65 -0
- package/config/agents/examples/critic.yaml +74 -0
- package/config/agents/examples/delegation.yaml +29 -0
- package/config/agents/examples/hierarchical.yaml +26 -0
- package/config/agents/examples/image_production.yaml +112 -0
- package/config/agents/examples/librarian.yaml +75 -0
- package/config/agents/examples/model_zoo.yaml +81 -0
- package/config/agents/examples/patient_advocate.yaml +107 -0
- package/config/agents/examples/scriptorium.yaml +96 -0
- package/config/agents/examples/style_council.yaml +83 -0
- package/config/agents/examples/syndicate.yaml +34 -0
- package/config/agents/examples/tutor.yaml +66 -0
- package/config/agents/syndicateSchema.yaml +291 -0
- package/dist/lib/config.d.ts +108 -0
- package/dist/lib/config.js +110 -0
- package/dist/lib/dispatch.d.ts +133 -0
- package/dist/lib/dispatch.js +182 -0
- package/dist/lib/grounding.d.ts +28 -0
- package/dist/lib/grounding.js +61 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/loadEnv.d.ts +21 -0
- package/dist/lib/loadEnv.js +58 -0
- package/dist/lib/loadSyndicate.d.ts +177 -0
- package/dist/lib/loadSyndicate.js +264 -0
- package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
- package/dist/lib/memory/supabaseMemoryService.js +499 -0
- package/dist/lib/models/claudeLlm.d.ts +93 -0
- package/dist/lib/models/claudeLlm.js +332 -0
- package/dist/lib/models/gptLlm.d.ts +110 -0
- package/dist/lib/models/gptLlm.js +497 -0
- package/dist/lib/models/grokLlm.d.ts +64 -0
- package/dist/lib/models/grokLlm.js +86 -0
- package/dist/lib/models/ollamaLlm.d.ts +105 -0
- package/dist/lib/models/ollamaLlm.js +134 -0
- package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
- package/dist/lib/models/openAiCompatibleLlm.js +331 -0
- package/dist/lib/models/providerMap.d.ts +28 -0
- package/dist/lib/models/providerMap.js +45 -0
- package/dist/lib/models/registry.d.ts +71 -0
- package/dist/lib/models/registry.js +159 -0
- package/dist/lib/models/schemaNormalize.d.ts +22 -0
- package/dist/lib/models/schemaNormalize.js +58 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
- package/dist/lib/observability/supabaseSpanExporter.js +115 -0
- package/dist/lib/observability/tracer.d.ts +44 -0
- package/dist/lib/observability/tracer.js +242 -0
- package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
- package/dist/lib/persistence/supabaseProvider.js +88 -0
- package/dist/lib/session/supabaseSessionService.d.ts +26 -0
- package/dist/lib/session/supabaseSessionService.js +193 -0
- package/dist/lib/session/transcript.d.ts +193 -0
- package/dist/lib/session/transcript.js +309 -0
- package/dist/lib/toolRegistry.d.ts +15 -0
- package/dist/lib/toolRegistry.js +58 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
- package/dist/lib/tools/collectionsSearchTool.js +86 -0
- package/dist/lib/tools/generateImageTool.d.ts +16 -0
- package/dist/lib/tools/generateImageTool.js +119 -0
- package/dist/lib/tools/inspectImageTool.d.ts +21 -0
- package/dist/lib/tools/inspectImageTool.js +97 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
- package/dist/lib/tools/mcpToolFactory.js +116 -0
- package/dist/lib/tools/toolContract.d.ts +78 -0
- package/dist/lib/tools/toolContract.js +129 -0
- package/dist/lib/tools/webExtractTool.d.ts +70 -0
- package/dist/lib/tools/webExtractTool.js +461 -0
- package/dist/lib/tools/webSearchTool.d.ts +53 -0
- package/dist/lib/tools/webSearchTool.js +126 -0
- package/dist/lib/tools/wikiTools.d.ts +112 -0
- package/dist/lib/tools/wikiTools.js +697 -0
- package/dist/lib/tools/xSearchTool.d.ts +61 -0
- package/dist/lib/tools/xSearchTool.js +125 -0
- package/dist/lib/wiki/agentRun.d.ts +39 -0
- package/dist/lib/wiki/agentRun.js +79 -0
- package/dist/lib/wiki/builder.d.ts +71 -0
- package/dist/lib/wiki/builder.js +165 -0
- package/dist/lib/wiki/dive.d.ts +56 -0
- package/dist/lib/wiki/dive.js +208 -0
- package/dist/lib/wiki/entities.d.ts +220 -0
- package/dist/lib/wiki/entities.js +661 -0
- package/dist/lib/wiki/extract.d.ts +97 -0
- package/dist/lib/wiki/extract.js +305 -0
- package/dist/lib/wiki/fill.d.ts +37 -0
- package/dist/lib/wiki/fill.js +175 -0
- package/dist/lib/wiki/format.d.ts +132 -0
- package/dist/lib/wiki/format.js +177 -0
- package/dist/lib/wiki/graph.d.ts +80 -0
- package/dist/lib/wiki/graph.js +147 -0
- package/dist/lib/wiki/lint.d.ts +58 -0
- package/dist/lib/wiki/lint.js +267 -0
- package/dist/lib/wiki/markdown.d.ts +127 -0
- package/dist/lib/wiki/markdown.js +336 -0
- package/dist/lib/wiki/pipeline.d.ts +69 -0
- package/dist/lib/wiki/pipeline.js +119 -0
- package/dist/lib/wiki/vault.d.ts +84 -0
- package/dist/lib/wiki/vault.js +194 -0
- package/dist/scripts/a2a_server.d.ts +2 -0
- package/dist/scripts/a2a_server.js +997 -0
- package/dist/scripts/syndicate_chat.d.ts +2 -0
- package/dist/scripts/syndicate_chat.js +485 -0
- package/package.json +96 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/claudeLlm.ts — Anthropic Claude provider for the ADK LLMRegistry.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* The ADK JS package (@google/adk v1.x) is Gemini-native. It does not ship a
|
|
6
|
+
* Claude adapter the way the ADK Java SDK does. However, the ADK exposes a
|
|
7
|
+
* clean extension point: subclass BaseLlm, implement generateContentAsync(),
|
|
8
|
+
* and register the class via LLMRegistry.register(). The registry maps model
|
|
9
|
+
* name patterns to provider classes, so once registered, any LlmAgent with
|
|
10
|
+
* model: "claude-*" will automatically route through this class.
|
|
11
|
+
*
|
|
12
|
+
* HOW TO ENABLE:
|
|
13
|
+
* 1. Install the Anthropic SDK:
|
|
14
|
+
* npm install @anthropic-ai/sdk
|
|
15
|
+
* 2. Add your API key to .env:
|
|
16
|
+
* ANTHROPIC_API_KEY=sk-ant-...
|
|
17
|
+
* 3. registerAvailableProviders() (lib/models/registry.ts) registers this
|
|
18
|
+
* adapter automatically when the key is set.
|
|
19
|
+
* 4. Set model: "claude-sonnet-4-6" (or any claude-* id) in your YAML.
|
|
20
|
+
*
|
|
21
|
+
* CAPABILITIES:
|
|
22
|
+
* - Tools: FunctionTools AND MCP-discovered tools work. Tool schemas are
|
|
23
|
+
* normalized from the ADK/Gemini UPPERCASE dialect to the lowercase
|
|
24
|
+
* JSON-Schema types Anthropic requires (lib/models/schemaNormalize.ts).
|
|
25
|
+
* - web_search: declaring the `web_search` tool in YAML enables Anthropic's
|
|
26
|
+
* native web_search server tool — searches run on Anthropic's side.
|
|
27
|
+
* - Extended thinking: generateContentConfig.thinkingConfig.thinkingBudget
|
|
28
|
+
* maps to Anthropic's thinking parameter. Thinking blocks are surfaced
|
|
29
|
+
* as { text, thought: true } parts (display-only; kept out of session
|
|
30
|
+
* history). NOTE: thinking + tool use in the same agent is NOT supported
|
|
31
|
+
* this pass — Anthropic requires signed thinking blocks to be replayed
|
|
32
|
+
* on tool loops; the raw blocks are stashed in customMetadata
|
|
33
|
+
* ['anthropic.thinking'] for a future upgrade.
|
|
34
|
+
* - Token usage: response.usage is mapped to LlmResponse.usageMetadata, so
|
|
35
|
+
* traceAgentRun / llm.request spans count Claude tokens like Gemini's.
|
|
36
|
+
*
|
|
37
|
+
* LIMITATIONS vs Gemini:
|
|
38
|
+
* - generate_image tool uses the Gemini image model directly and is not
|
|
39
|
+
* affected by the inference model choice.
|
|
40
|
+
* - Live/bidirectional streaming (connect) is not supported.
|
|
41
|
+
*
|
|
42
|
+
* CONTENT FORMAT TRANSLATION:
|
|
43
|
+
* The ADK uses Google GenAI Content/Part objects internally. This adapter
|
|
44
|
+
* translates them to Anthropic MessageParam format and maps the response back
|
|
45
|
+
* to LlmResponse. Tool calls (function_call / function_response parts) are
|
|
46
|
+
* translated to Anthropic's tool_use / tool_result blocks.
|
|
47
|
+
*/
|
|
48
|
+
import { BaseLlm, LLMRegistry } from '@google/adk';
|
|
49
|
+
import { traceLlmGeneration, setLlmSpanAttribute, } from "../observability/tracer.js";
|
|
50
|
+
import { wantsWebSearch, isWebSearchSentinel, } from "../tools/webSearchTool.js";
|
|
51
|
+
import { toLowercaseJsonSchema } from "./schemaNormalize.js";
|
|
52
|
+
/** Anthropic's minimum extended-thinking budget. */
|
|
53
|
+
const MIN_THINKING_BUDGET = 1024;
|
|
54
|
+
// ── Request building (exported for offline tests) ────────────────────────────
|
|
55
|
+
/** ADK toolsDict → Anthropic tool definitions (lowercase schemas; the
|
|
56
|
+
* web_search sentinel becomes Anthropic's native server tool). */
|
|
57
|
+
export function buildAnthropicTools(llmRequest) {
|
|
58
|
+
const anthropicTools = [];
|
|
59
|
+
for (const [, tool] of Object.entries(llmRequest.toolsDict ?? {})) {
|
|
60
|
+
if (isWebSearchSentinel(tool))
|
|
61
|
+
continue; // added as a server tool below
|
|
62
|
+
const t = tool;
|
|
63
|
+
if (t.name && t.description) {
|
|
64
|
+
anthropicTools.push({
|
|
65
|
+
name: t.name,
|
|
66
|
+
description: t.description,
|
|
67
|
+
input_schema: toLowercaseJsonSchema(t.parameters ?? { type: 'object', properties: {} }),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (wantsWebSearch(llmRequest)) {
|
|
72
|
+
// Anthropic-native server tool: search runs on Anthropic's side, results
|
|
73
|
+
// are grounded into the reply — no client-side execution.
|
|
74
|
+
anthropicTools.push({
|
|
75
|
+
type: 'web_search_20250305',
|
|
76
|
+
name: 'web_search',
|
|
77
|
+
max_uses: 5,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return anthropicTools;
|
|
81
|
+
}
|
|
82
|
+
// ── ClaudeLlm ─────────────────────────────────────────────────────────────────
|
|
83
|
+
export class ClaudeLlm extends BaseLlm {
|
|
84
|
+
/**
|
|
85
|
+
* Regex patterns that map model name strings to this provider.
|
|
86
|
+
* Any model: "claude-*" in a YAML config will route here after registration.
|
|
87
|
+
*/
|
|
88
|
+
static supportedModels = [/^claude-.+/];
|
|
89
|
+
apiKey;
|
|
90
|
+
constructor({ model, apiKey }) {
|
|
91
|
+
super({ model });
|
|
92
|
+
this.apiKey = apiKey;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Main generation method called by the ADK Runner for every turn.
|
|
96
|
+
*
|
|
97
|
+
* We translate ADK's LlmRequest (Google Content format) → Anthropic
|
|
98
|
+
* MessageParam format, call the Anthropic Messages API, then translate
|
|
99
|
+
* the response back to LlmResponse. The whole call is wrapped in an
|
|
100
|
+
* llm.request OpenTelemetry span (provider/model/tokens/latency).
|
|
101
|
+
*/
|
|
102
|
+
async *generateContentAsync(llmRequest, stream = false) {
|
|
103
|
+
yield* traceLlmGeneration({ provider: 'anthropic', model: this.model }, this.generateInner(llmRequest, stream));
|
|
104
|
+
}
|
|
105
|
+
async *generateInner(llmRequest, stream) {
|
|
106
|
+
const apiKey = this.apiKey || process.env.ANTHROPIC_API_KEY;
|
|
107
|
+
if (!apiKey) {
|
|
108
|
+
yield {
|
|
109
|
+
errorCode: 'MISSING_API_KEY',
|
|
110
|
+
errorMessage: 'ANTHROPIC_API_KEY is not set in environment.',
|
|
111
|
+
};
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Dynamic import — only loads @anthropic-ai/sdk when actually called,
|
|
115
|
+
// so the framework boots without the SDK installed for Gemini-only users.
|
|
116
|
+
let Anthropic;
|
|
117
|
+
try {
|
|
118
|
+
const mod = await import('@anthropic-ai/sdk');
|
|
119
|
+
Anthropic = mod.default ?? mod.Anthropic;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
yield {
|
|
123
|
+
errorCode: 'SDK_NOT_INSTALLED',
|
|
124
|
+
errorMessage: 'The @anthropic-ai/sdk package is not installed. Run: npm install @anthropic-ai/sdk',
|
|
125
|
+
};
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const client = new Anthropic({ apiKey });
|
|
129
|
+
// ── Translate ADK Contents → Anthropic messages ──────────────────────────
|
|
130
|
+
const systemParts = [];
|
|
131
|
+
const messages = [];
|
|
132
|
+
for (const content of llmRequest.contents) {
|
|
133
|
+
// System instruction lives in config.systemInstruction, not contents.
|
|
134
|
+
// But ADK also injects it as a 'system' role content — extract it.
|
|
135
|
+
if (content.role === 'system') {
|
|
136
|
+
const text = content.parts
|
|
137
|
+
?.filter((p) => p.text)
|
|
138
|
+
.map((p) => p.text)
|
|
139
|
+
.join('\n');
|
|
140
|
+
if (text)
|
|
141
|
+
systemParts.push(text);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const role = content.role === 'model' ? 'assistant' : 'user';
|
|
145
|
+
const blocks = [];
|
|
146
|
+
for (const part of content.parts ?? []) {
|
|
147
|
+
const p = part;
|
|
148
|
+
if (p.thought) {
|
|
149
|
+
// Prior-turn scratchpad is display-only; never replay it as text.
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (p.text) {
|
|
153
|
+
blocks.push({ type: 'text', text: p.text });
|
|
154
|
+
}
|
|
155
|
+
else if (p.functionCall) {
|
|
156
|
+
// ADK function_call → Anthropic tool_use
|
|
157
|
+
blocks.push({
|
|
158
|
+
type: 'tool_use',
|
|
159
|
+
id: p.functionCall.id ?? `tool_${Date.now()}`,
|
|
160
|
+
name: p.functionCall.name,
|
|
161
|
+
input: p.functionCall.args ?? {},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else if (p.functionResponse) {
|
|
165
|
+
// ADK function_response → Anthropic tool_result
|
|
166
|
+
blocks.push({
|
|
167
|
+
type: 'tool_result',
|
|
168
|
+
tool_use_id: p.functionResponse.id ?? '',
|
|
169
|
+
content: JSON.stringify(p.functionResponse.response ?? {}),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (blocks.length > 0) {
|
|
174
|
+
messages.push({ role, content: blocks });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Also pull system instruction from generateContentConfig if present
|
|
178
|
+
const configSystem = llmRequest.config?.systemInstruction;
|
|
179
|
+
if (configSystem) {
|
|
180
|
+
const text = typeof configSystem === 'string'
|
|
181
|
+
? configSystem
|
|
182
|
+
: configSystem.parts?.map((p) => p.text).join('\n') ?? '';
|
|
183
|
+
if (text)
|
|
184
|
+
systemParts.unshift(text);
|
|
185
|
+
}
|
|
186
|
+
// ── Build Anthropic tool definitions from ADK toolsDict ──────────────────
|
|
187
|
+
const anthropicTools = buildAnthropicTools(llmRequest);
|
|
188
|
+
if (wantsWebSearch(llmRequest)) {
|
|
189
|
+
setLlmSpanAttribute('llm.web_search.native', true);
|
|
190
|
+
}
|
|
191
|
+
// ── Extended thinking (Gemini thinkingConfig → Anthropic thinking) ───────
|
|
192
|
+
const cfg = llmRequest.config ?? {};
|
|
193
|
+
let maxTokens = cfg.maxOutputTokens ?? 4096;
|
|
194
|
+
let thinking;
|
|
195
|
+
const requestedBudget = cfg.thinkingConfig?.thinkingBudget;
|
|
196
|
+
if (typeof requestedBudget === 'number' && requestedBudget > 0) {
|
|
197
|
+
const budget = Math.max(requestedBudget, MIN_THINKING_BUDGET);
|
|
198
|
+
thinking = { type: 'enabled', budget_tokens: budget };
|
|
199
|
+
// Anthropic requires max_tokens to exceed the thinking budget.
|
|
200
|
+
maxTokens = Math.max(maxTokens, budget + 2048);
|
|
201
|
+
}
|
|
202
|
+
const requestBase = {
|
|
203
|
+
model: this.model,
|
|
204
|
+
max_tokens: maxTokens,
|
|
205
|
+
system: systemParts.join('\n\n') || undefined,
|
|
206
|
+
messages,
|
|
207
|
+
tools: anthropicTools.length > 0 ? anthropicTools : undefined,
|
|
208
|
+
...(thinking ? { thinking } : {}),
|
|
209
|
+
};
|
|
210
|
+
// ── Call Anthropic API ────────────────────────────────────────────────────
|
|
211
|
+
try {
|
|
212
|
+
if (stream) {
|
|
213
|
+
// Streaming path
|
|
214
|
+
const streamResponse = await client.messages.stream(requestBase);
|
|
215
|
+
for await (const chunk of streamResponse) {
|
|
216
|
+
if (chunk.type === 'content_block_delta') {
|
|
217
|
+
if (chunk.delta.type === 'text_delta') {
|
|
218
|
+
yield {
|
|
219
|
+
content: {
|
|
220
|
+
role: 'model',
|
|
221
|
+
parts: [{ text: chunk.delta.text }],
|
|
222
|
+
},
|
|
223
|
+
partial: true,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
else if (chunk.delta.type === 'thinking_delta') {
|
|
227
|
+
yield {
|
|
228
|
+
content: {
|
|
229
|
+
role: 'model',
|
|
230
|
+
parts: [{ text: chunk.delta.thinking, thought: true }],
|
|
231
|
+
},
|
|
232
|
+
partial: true,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Final message: tool_use blocks, thinking stash, and token usage.
|
|
238
|
+
const final = await streamResponse.finalMessage();
|
|
239
|
+
yield this.finalResponse(final, /*includeText=*/ false);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// Non-streaming path
|
|
243
|
+
const response = await client.messages.create(requestBase);
|
|
244
|
+
// Thinking first, as a display-only partial.
|
|
245
|
+
const thinkingText = (response.content ?? [])
|
|
246
|
+
.filter((b) => b.type === 'thinking')
|
|
247
|
+
.map((b) => b.thinking)
|
|
248
|
+
.join('\n\n');
|
|
249
|
+
if (thinkingText) {
|
|
250
|
+
yield {
|
|
251
|
+
content: {
|
|
252
|
+
role: 'model',
|
|
253
|
+
parts: [{ text: thinkingText, thought: true }],
|
|
254
|
+
},
|
|
255
|
+
partial: true,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
yield this.finalResponse(response, /*includeText=*/ true);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
263
|
+
yield { errorCode: 'ANTHROPIC_ERROR', errorMessage: msg };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** Maps a complete Anthropic message → the final LlmResponse. */
|
|
267
|
+
finalResponse(response, includeText) {
|
|
268
|
+
const parts = [];
|
|
269
|
+
const thinkingBlocks = [];
|
|
270
|
+
for (const block of response.content ?? []) {
|
|
271
|
+
if (block.type === 'text' && includeText) {
|
|
272
|
+
parts.push({ text: block.text });
|
|
273
|
+
}
|
|
274
|
+
else if (block.type === 'tool_use') {
|
|
275
|
+
parts.push({
|
|
276
|
+
functionCall: { name: block.name, args: block.input, id: block.id },
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
else if (block.type === 'thinking') {
|
|
280
|
+
// Raw signed blocks, preserved for a future thinking+tools upgrade
|
|
281
|
+
// (Anthropic requires them replayed verbatim on tool loops).
|
|
282
|
+
thinkingBlocks.push(block);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const usage = response.usage;
|
|
286
|
+
return {
|
|
287
|
+
...(parts.length > 0 ? { content: { role: 'model', parts } } : {}),
|
|
288
|
+
turnComplete: true,
|
|
289
|
+
...(usage
|
|
290
|
+
? {
|
|
291
|
+
usageMetadata: {
|
|
292
|
+
...(usage.input_tokens !== undefined
|
|
293
|
+
? { promptTokenCount: usage.input_tokens }
|
|
294
|
+
: {}),
|
|
295
|
+
...(usage.output_tokens !== undefined
|
|
296
|
+
? { candidatesTokenCount: usage.output_tokens }
|
|
297
|
+
: {}),
|
|
298
|
+
...(usage.input_tokens !== undefined &&
|
|
299
|
+
usage.output_tokens !== undefined
|
|
300
|
+
? { totalTokenCount: usage.input_tokens + usage.output_tokens }
|
|
301
|
+
: {}),
|
|
302
|
+
},
|
|
303
|
+
}
|
|
304
|
+
: {}),
|
|
305
|
+
...(thinkingBlocks.length > 0
|
|
306
|
+
? { customMetadata: { 'anthropic.thinking': thinkingBlocks } }
|
|
307
|
+
: {}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Live/bidirectional streaming connection — not supported by the Anthropic
|
|
312
|
+
* Messages API. Throws to surface this clearly rather than silently failing.
|
|
313
|
+
*/
|
|
314
|
+
async connect(_llmRequest) {
|
|
315
|
+
throw new Error('ClaudeLlm does not support live bidirectional connections. ' +
|
|
316
|
+
'Use a Gemini model for live/streaming sessions.');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
// ── Registration helper ───────────────────────────────────────────────────────
|
|
320
|
+
/**
|
|
321
|
+
* Registers ClaudeLlm with the ADK LLMRegistry.
|
|
322
|
+
*
|
|
323
|
+
* WHY a separate function instead of auto-registering at import time:
|
|
324
|
+
* Auto-registration at module load would cause every consumer of this file
|
|
325
|
+
* to unconditionally register Claude, even if ANTHROPIC_API_KEY is absent.
|
|
326
|
+
* By making registration explicit, registerAvailableProviders() can
|
|
327
|
+
* conditionally call this only when the key is present, and log a clear
|
|
328
|
+
* message about which provider is active.
|
|
329
|
+
*/
|
|
330
|
+
export function registerClaudeLlm() {
|
|
331
|
+
LLMRegistry.register(ClaudeLlm);
|
|
332
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/gptLlm.ts — OpenAI GPT provider for the ADK LLMRegistry.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* Model optionality: any agent YAML with model: "gpt-*" (or an o-series
|
|
6
|
+
* id like "o4-mini") routes here after registration. Unlike Grok/Ollama
|
|
7
|
+
* this adapter does NOT reuse the chat-completions base class — it speaks
|
|
8
|
+
* OpenAI's Responses API through the official `openai` SDK, because the
|
|
9
|
+
* Responses API is where OpenAI exposes reasoning summaries (the model's
|
|
10
|
+
* thinking, surfaced as { thought: true } parts here) and the first-class
|
|
11
|
+
* `web_search` tool this framework's web_search abstraction maps to.
|
|
12
|
+
*
|
|
13
|
+
* HOW TO ENABLE:
|
|
14
|
+
* 1. Install the SDK (already in package.json): npm install
|
|
15
|
+
* 2. Add your API key to .env: OPENAI_API_KEY=sk-...
|
|
16
|
+
* 3. Set model: "gpt-5-mini" (or any gpt-* / o-series id) in your YAML.
|
|
17
|
+
* registerAvailableProviders() registers this adapter when the key is set.
|
|
18
|
+
*
|
|
19
|
+
* CONTENT FORMAT TRANSLATION:
|
|
20
|
+
* ADK Content/Part objects → Responses API `input` items:
|
|
21
|
+
* user/model text → { role, content } message items
|
|
22
|
+
* functionCall part → { type:'function_call', call_id, name, arguments }
|
|
23
|
+
* functionResponse part → { type:'function_call_output', call_id, output }
|
|
24
|
+
* The call_id round-trip matters: this adapter emits functionCall.id so
|
|
25
|
+
* ADK echoes it back on the tool response, and the Responses API requires
|
|
26
|
+
* function_call_output.call_id to match the originating call.
|
|
27
|
+
*
|
|
28
|
+
* Response `output` items map back:
|
|
29
|
+
* 'reasoning' summary → { text, thought: true } partial (display-only)
|
|
30
|
+
* 'message' output_text → text part
|
|
31
|
+
* 'function_call' → functionCall part
|
|
32
|
+
* usage.input_tokens / output_tokens / output_tokens_details.reasoning_tokens
|
|
33
|
+
* → LlmResponse.usageMetadata, so token telemetry works like every provider.
|
|
34
|
+
*
|
|
35
|
+
* STREAMING (ADK stream=true, i.e. RunConfig streamingMode: SSE):
|
|
36
|
+
* The request is sent with { stream: true }; SSE deltas
|
|
37
|
+
* (response.output_text.delta / response.reasoning_summary_text.delta)
|
|
38
|
+
* are yielded as { partial: true } responses — the ADK Runner displays
|
|
39
|
+
* but does NOT persist partial events — and the terminal
|
|
40
|
+
* response.completed payload is mapped through the same final-response
|
|
41
|
+
* translator as the non-streaming path, carrying full content + usage
|
|
42
|
+
* as the ONE event that lands in the session. Function calls are never
|
|
43
|
+
* delta-streamed (both OpenAI and xAI deliver them whole).
|
|
44
|
+
*/
|
|
45
|
+
import { BaseLlm } from '@google/adk';
|
|
46
|
+
import type { LlmRequest, LlmResponse } from '@google/adk';
|
|
47
|
+
import type { BaseLlmConnection } from '@google/adk';
|
|
48
|
+
/** ADK Contents → Responses API `input` items + `instructions` string. */
|
|
49
|
+
export declare function buildResponsesInput(llmRequest: LlmRequest): {
|
|
50
|
+
instructions: string | undefined;
|
|
51
|
+
input: any[];
|
|
52
|
+
};
|
|
53
|
+
/** ADK toolsDict (+ web_search sentinel) → Responses API tool definitions. */
|
|
54
|
+
export declare function buildResponsesTools(llmRequest: LlmRequest): any[];
|
|
55
|
+
/** Maps one Responses-API SSE event to a displayable delta, or null for
|
|
56
|
+
* event types that carry none. Function calls are NOT delta-streamed —
|
|
57
|
+
* both OpenAI and xAI deliver them whole in the final response. */
|
|
58
|
+
export declare function streamEventDelta(ev: any): {
|
|
59
|
+
thought: boolean;
|
|
60
|
+
text: string;
|
|
61
|
+
} | null;
|
|
62
|
+
export declare class GptLlm extends BaseLlm {
|
|
63
|
+
/** gpt-* and o-series ids route here after registration. */
|
|
64
|
+
static readonly supportedModels: Array<string | RegExp>;
|
|
65
|
+
protected apiKey?: string;
|
|
66
|
+
constructor({ model, apiKey }: {
|
|
67
|
+
model: string;
|
|
68
|
+
apiKey?: string;
|
|
69
|
+
});
|
|
70
|
+
/** Provider id for telemetry and error codes. */
|
|
71
|
+
protected providerId(): string;
|
|
72
|
+
/** SDK baseURL override; undefined = api.openai.com. */
|
|
73
|
+
protected baseURL(): string | undefined;
|
|
74
|
+
protected apiKeyFromEnv(): string | undefined;
|
|
75
|
+
protected missingKeyMessage(): string;
|
|
76
|
+
/** Responses API `reasoning` request param, or undefined to omit it.
|
|
77
|
+
* Base: reasoning summaries for OpenAI's reasoning-capable ids.
|
|
78
|
+
* Subclasses override per vendor (GrokLlm pins grok-4.5 to a reasoning
|
|
79
|
+
* effort). A 400 from a model that rejects the param is retried once
|
|
80
|
+
* without it — see createWithRetry. */
|
|
81
|
+
protected reasoningParam(): Record<string, unknown> | undefined;
|
|
82
|
+
/** Extra options for the OpenAI SDK client constructor. Subclasses
|
|
83
|
+
* override per vendor (GrokLlm sets a long request timeout, per xAI's
|
|
84
|
+
* streaming guidance for reasoning models). */
|
|
85
|
+
protected clientOptions(): Record<string, unknown>;
|
|
86
|
+
generateContentAsync(llmRequest: LlmRequest, stream?: boolean): AsyncGenerator<LlmResponse, void>;
|
|
87
|
+
private generateInner;
|
|
88
|
+
/** responses.create with the guarded reasoning retry: if the model
|
|
89
|
+
* rejects the reasoning param with a 400 (a non-reasoning model matched
|
|
90
|
+
* the pattern), drop it and try once more. */
|
|
91
|
+
private createWithRetry;
|
|
92
|
+
/** Final (non-delta) Response → LlmResponse yields: reasoning summaries
|
|
93
|
+
* as a display-only thought part, then text/functionCall parts with
|
|
94
|
+
* usage. Shared by the non-streaming path and the stream finalizer
|
|
95
|
+
* (which sets skipThoughts — its summaries already streamed as deltas). */
|
|
96
|
+
private mapFinalResponse;
|
|
97
|
+
/** SSE streaming: yield displayable DELTAS as { partial: true } responses
|
|
98
|
+
* (the ADK Runner shows but never persists partials), then map the
|
|
99
|
+
* terminal response.completed payload through mapFinalResponse — full
|
|
100
|
+
* content + usage, the ONE event that lands in the session. Usage rides
|
|
101
|
+
* only the final response so the tracer never double-counts tokens. */
|
|
102
|
+
private streamResponses;
|
|
103
|
+
/** Live/bidirectional streaming is not wired for this adapter. */
|
|
104
|
+
connect(_llmRequest: LlmRequest): Promise<BaseLlmConnection>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Registers GptLlm with the ADK LLMRegistry. Called by
|
|
108
|
+
* registerAvailableProviders() when OPENAI_API_KEY is present.
|
|
109
|
+
*/
|
|
110
|
+
export declare function registerGptLlm(): void;
|