melchizedek-agents 1.0.0 → 1.0.2
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/CHANGELOG.md +53 -0
- package/DOCUMENTATION.md +11 -9
- package/config/agents/examples/critic.yaml +12 -7
- package/config/agents/examples/image_production.yaml +2 -2
- package/config/agents/examples/librarian.yaml +1 -1
- package/config/agents/examples/pipeline.yaml +1 -1
- package/config/agents/examples/research.yaml +3 -2
- package/config/agents/examples/scribe.yaml +5 -4
- package/config/agents/examples/weather.yaml +2 -1
- package/config/agents/syndicateSchema.yaml +18 -15
- package/dist/lib/dispatch.d.ts +4 -4
- package/dist/lib/dispatch.js +4 -4
- package/dist/lib/syndicateSchema.js +6 -6
- package/package.json +2 -2
- package/skills/briefs/melchizedek-author.md +1 -1
- package/skills/melchizedek-author/SKILL.md +1 -1
- package/skills/melchizedek-author/assets/minimal.yaml +1 -1
- package/dist/lib/adkPeer.d.ts +0 -63
- package/dist/lib/adkPeer.js +0 -240
- package/dist/lib/compileAdk.d.ts +0 -32
- package/dist/lib/compileAdk.js +0 -142
- package/dist/lib/models/adkGeminiAdapter.d.ts +0 -90
- package/dist/lib/models/adkGeminiAdapter.js +0 -369
- package/dist/lib/models/adkShim.d.ts +0 -111
- package/dist/lib/models/adkShim.js +0 -136
- package/dist/lib/models/claudeLlm.d.ts +0 -79
- package/dist/lib/models/claudeLlm.js +0 -87
- package/dist/lib/models/fallback.d.ts +0 -48
- package/dist/lib/models/fallback.js +0 -116
- package/dist/lib/models/gatewayLlm.d.ts +0 -36
- package/dist/lib/models/gatewayLlm.js +0 -44
- package/dist/lib/models/gptLlm.d.ts +0 -99
- package/dist/lib/models/gptLlm.js +0 -129
- package/dist/lib/models/grokLlm.d.ts +0 -50
- package/dist/lib/models/grokLlm.js +0 -56
- package/dist/lib/models/kimiLlm.d.ts +0 -31
- package/dist/lib/models/kimiLlm.js +0 -35
- package/dist/lib/models/ollamaLlm.d.ts +0 -37
- package/dist/lib/models/ollamaLlm.js +0 -42
- package/dist/lib/models/openAiCompatibleLlm.d.ts +0 -96
- package/dist/lib/models/openAiCompatibleLlm.js +0 -156
- package/dist/lib/models/tracedGemini.d.ts +0 -41
- package/dist/lib/models/tracedGemini.js +0 -98
- package/dist/lib/runtime/adkMemoryBridge.d.ts +0 -46
- package/dist/lib/runtime/adkMemoryBridge.js +0 -68
- package/dist/lib/runtime/adkSessionBridge.d.ts +0 -78
- package/dist/lib/runtime/adkSessionBridge.js +0 -176
- package/dist/lib/tools/adkTool.d.ts +0 -81
- package/dist/lib/tools/adkTool.js +0 -268
- package/dist/lib/tools/collectionsSearchTool.d.ts +0 -56
- package/dist/lib/tools/collectionsSearchTool.js +0 -71
- package/dist/lib/tools/urlContextTool.d.ts +0 -32
- package/dist/lib/tools/urlContextTool.js +0 -43
- package/dist/lib/tools/webSearchTool.d.ts +0 -51
- package/dist/lib/tools/webSearchTool.js +0 -82
- package/dist/lib/tools/xSearchTool.d.ts +0 -59
- package/dist/lib/tools/xSearchTool.js +0 -74
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/models/adkGeminiAdapter.ts — TEMPORARY: Gemini as a contract
|
|
3
|
-
* ModelAdapter (ADR 0048) over ADK's own Gemini, the TracedGemini path of
|
|
4
|
-
* lib/models/tracedGemini.ts, through the genai mapping (lib/models/genaiMapping.ts).
|
|
5
|
-
*
|
|
6
|
-
* WHY this file exists:
|
|
7
|
-
* The native loop (ADR 0045) calls ModelAdapters only. The engine's own
|
|
8
|
-
* Gemini adapter (lib/models/geminiAdapter.ts) calls @google/genai with no
|
|
9
|
-
* ADK, and waits for its live parity run at gate G3. Until it passes,
|
|
10
|
-
* Gemini on the native runtime goes through ADK's proven Gemini, wrapped
|
|
11
|
-
* here, so every provider has a contract adapter before gate G1. Delete
|
|
12
|
-
* this file once G3 is signed and GeminiAdapter serves every Gemini id
|
|
13
|
-
* (the plan's G3 risk: "The WS1-8 wrapper over ADK's Gemini stays until
|
|
14
|
-
* the cells pass"). resolveAdapter (lib/models/registry.ts) returns it for Gemini ids.
|
|
15
|
-
*
|
|
16
|
-
* THE CALL:
|
|
17
|
-
* 1. The ModelRequest becomes an LlmRequest (modelRequestToLlmRequest),
|
|
18
|
-
* and then what ADK's path does to one before its Gemini sees it:
|
|
19
|
-
* - system messages joined onto the system prompt, in order, since
|
|
20
|
-
* Gemini takes no `system` content;
|
|
21
|
-
* - `adk-` call ids taken off calls and results, as ADK's flow does
|
|
22
|
-
* before every model call (ids the mapping minted are already off);
|
|
23
|
-
* - `includeServerSideToolInvocations`, which the compiler sends on
|
|
24
|
-
* every Gemini agent (lib/compile.ts);
|
|
25
|
-
* - `includeThoughts` under any reasoning but `none`, the contract's
|
|
26
|
-
* Gemini table, as GeminiAdapter sends it;
|
|
27
|
-
* - the platform's wire model (GEMINI_MODEL_MAP), as TracedGemini's own.
|
|
28
|
-
* The request is copied where ADK writes to it (a blob's display name),
|
|
29
|
-
* so the caller's history is never changed.
|
|
30
|
-
* 2. TracedGemini.generateWithRetries runs it: ADK's Gemini with the
|
|
31
|
-
* shared retries, tagging the span that is open around this adapter
|
|
32
|
-
* (llm.web_search.native, llm.retries, llm.http_status), as it tags its
|
|
33
|
-
* own. `llm.capability.dropped` marks native tools Gemini has none for.
|
|
34
|
-
* 3. Each LlmResponse comes back through llmResponseToModelResponse,
|
|
35
|
-
* folded into the contract's stream. A thought signature becomes
|
|
36
|
-
* providerState naming the request's model. A call Gemini returns
|
|
37
|
-
* without an id gets the mapping's `genai-noid-<position>-<part>`, or,
|
|
38
|
-
* streamed, ADK's own `adk-` id; neither goes back on the wire.
|
|
39
|
-
*
|
|
40
|
-
* REPLAY: every Gemini signature in the history goes back on its part, as
|
|
41
|
-
* ADK sends a stored session's, whichever turn or model wrote it.
|
|
42
|
-
* GeminiAdapter replays only the current turn's, for its own model.
|
|
43
|
-
*
|
|
44
|
-
* THE SPAN AND THE CHARGE are the caller's (ADR 0053): this adapter opens no
|
|
45
|
-
* llm.request span and never charges the turn. Behind the ADK shim
|
|
46
|
-
* (lib/models/adkShim.ts) it records the span TracedGemini records today for
|
|
47
|
-
* the same exchange, but for a failed call's code (tests/telemetryLedger.test.ts).
|
|
48
|
-
*
|
|
49
|
-
* THE ADAPTER RULES (contract.ts header) as this adapter keeps them:
|
|
50
|
-
* - Partials: a streamed call's text and thinking deltas, as ADK's stream
|
|
51
|
-
* aggregator yields them. A call that does not stream yields its
|
|
52
|
-
* thinking as one partial before the final.
|
|
53
|
-
* - One final: every non-partial response ADK yields for the call (its
|
|
54
|
-
* aggregator flushes the text before each tool call) folds into it, with
|
|
55
|
-
* the last usage, grounding and finish reason.
|
|
56
|
-
* - Errors are finals, never throws. ADK's Gemini throws on a failed call:
|
|
57
|
-
* that is GEMINI_ERROR, `retryable` from lib/models/retry.ts's
|
|
58
|
-
* classifyError, `status` when the error had one. An error code ADK
|
|
59
|
-
* yields (a finish or block reason) is the code, not retryable; ADK's
|
|
60
|
-
* `STOP` is no error. Setup failures are MISSING_API_KEY and
|
|
61
|
-
* ENDPOINT_MISCONFIGURED, as GeminiAdapter's.
|
|
62
|
-
* - The abort: `request.signal` (the shim aborts it when the turn stops)
|
|
63
|
-
* goes on the request config. An aborted call is GEMINI_ERROR, never
|
|
64
|
-
* retryable, and the iteration ends at once whatever the transport does;
|
|
65
|
-
* a signal aborted before the call sends nothing.
|
|
66
|
-
* - Retries: TracedGemini's, only before the first chunk.
|
|
67
|
-
* - Messages never carry the key in use or anything shaped like a key.
|
|
68
|
-
*/
|
|
69
|
-
import { endpointFromEnv, endpointProblems, platformModel } from './endpoints.js';
|
|
70
|
-
import { errorText } from './errorResponse.js';
|
|
71
|
-
import { GEMINI_PROVIDER } from './geminiState.js';
|
|
72
|
-
import { llmResponseToModelResponse, modelRequestToLlmRequest, nativeToolsWithoutGeminiTool } from './genaiMapping.js';
|
|
73
|
-
import { TracedGemini } from './tracedGemini.js';
|
|
74
|
-
import { requireAdk } from '../adkPeer.js';
|
|
75
|
-
import { classifyError, errorStatus } from './retry.js';
|
|
76
|
-
import { setLlmSpanAttribute } from '../observability/tracer.js';
|
|
77
|
-
/** The prefix of a call id ADK (or the engine after it) made: such an id never goes on the wire. */
|
|
78
|
-
const ADK_CALL_ID_PREFIX = 'adk-';
|
|
79
|
-
// ── The adapter ──────────────────────────────────────────────────────────────
|
|
80
|
-
/** Temporary: ADK's Gemini behind the model contract, until GeminiAdapter passes gate G3. */
|
|
81
|
-
export class AdkGeminiAdapter {
|
|
82
|
-
provider = GEMINI_PROVIDER;
|
|
83
|
-
model;
|
|
84
|
-
#apiKey;
|
|
85
|
-
#endpoint;
|
|
86
|
-
#connection;
|
|
87
|
-
#warned = new Set();
|
|
88
|
-
constructor(options) {
|
|
89
|
-
requireAdk('GEMINI_ADAPTER=adk (AdkGeminiAdapter, which runs ADK\'s Gemini)');
|
|
90
|
-
this.model = options.model;
|
|
91
|
-
this.#apiKey = nonEmpty(options.apiKey);
|
|
92
|
-
this.#endpoint = options.endpoint;
|
|
93
|
-
}
|
|
94
|
-
async *generate(request) {
|
|
95
|
-
const { signal } = request;
|
|
96
|
-
const model = request.model || this.model;
|
|
97
|
-
const stream = request.stream === true;
|
|
98
|
-
const named = request.nativeTools ?? [];
|
|
99
|
-
const searchTool = named.includes('web_search') || !named.includes('google_search') ? 'web_search' : 'google_search';
|
|
100
|
-
const reply = new Reply({ model, position: request.messages.length, searchTool, stream });
|
|
101
|
-
if (signal?.aborted) {
|
|
102
|
-
yield reply.aborted();
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const connection = this.#connect();
|
|
106
|
-
if ('code' in connection) {
|
|
107
|
-
yield { partial: false, parts: [], finishReason: 'error', error: connection };
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const { gemini, endpoint, secret } = connection;
|
|
111
|
-
try {
|
|
112
|
-
const llmRequest = adkRequest(request, model, platformModel(endpoint, model));
|
|
113
|
-
this.#report(nativeToolsWithoutGeminiTool(named));
|
|
114
|
-
for await (const response of untilAborted(gemini.generateWithRetries(llmRequest, stream, signal), signal)) {
|
|
115
|
-
const delta = reply.add(response);
|
|
116
|
-
if (delta)
|
|
117
|
-
yield delta;
|
|
118
|
-
}
|
|
119
|
-
const thinking = reply.thinking();
|
|
120
|
-
if (thinking)
|
|
121
|
-
yield thinking;
|
|
122
|
-
yield reply.final();
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
if (signal?.aborted) {
|
|
126
|
-
yield reply.aborted();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
yield reply.failed(err, secret);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/** Marks the open span with the native tools Gemini has none for, and warns once per tool. */
|
|
133
|
-
#report(dropped) {
|
|
134
|
-
if (dropped.length === 0)
|
|
135
|
-
return;
|
|
136
|
-
setLlmSpanAttribute('llm.capability.dropped', dropped.join(','));
|
|
137
|
-
for (const tool of dropped) {
|
|
138
|
-
if (this.#warned.has(tool))
|
|
139
|
-
continue;
|
|
140
|
-
this.#warned.add(tool);
|
|
141
|
-
console.warn(`⚠ ${tool} is not a Gemini tool; ${this.model} runs without it.`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
/** ADK's Gemini for this adapter's endpoint, built once; or why it cannot be built. */
|
|
145
|
-
#connect() {
|
|
146
|
-
if (this.#connection)
|
|
147
|
-
return this.#connection;
|
|
148
|
-
let endpoint;
|
|
149
|
-
try {
|
|
150
|
-
endpoint = this.#endpoint ?? endpointFromEnv('gemini');
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
return setupError('ENDPOINT_MISCONFIGURED', errorText(err));
|
|
154
|
-
}
|
|
155
|
-
let secret;
|
|
156
|
-
if (endpoint.platform === 'vertex') {
|
|
157
|
-
const problems = endpointProblems('gemini', endpoint);
|
|
158
|
-
if (problems.length > 0)
|
|
159
|
-
return setupError('ENDPOINT_MISCONFIGURED', `Gemini on Vertex AI: ${problems.join('; ')}.`);
|
|
160
|
-
}
|
|
161
|
-
else if (endpoint.platform === 'direct') {
|
|
162
|
-
secret = this.#apiKey ?? nonEmpty(endpoint.apiKey) ?? envApiKey();
|
|
163
|
-
if (!secret) {
|
|
164
|
-
return setupError('MISSING_API_KEY', 'No Gemini API key: set GOOGLE_GENAI_API_KEY (or GEMINI_API_KEY), or GEMINI_PLATFORM=vertex for Vertex AI.');
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
return setupError('ENDPOINT_MISCONFIGURED', `Gemini has no ${endpoint.platform} platform; use direct or vertex.`);
|
|
169
|
-
}
|
|
170
|
-
try {
|
|
171
|
-
// TracedGemini applies the platform (ADR 0023): on Vertex AI, Google
|
|
172
|
-
// Application Default Credentials and no AI Studio key.
|
|
173
|
-
const gemini = new TracedGemini({ model: this.model, ...(secret ? { apiKey: secret } : {}), endpoint });
|
|
174
|
-
this.#connection = { gemini, endpoint, secret };
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
return setupError('ENDPOINT_MISCONFIGURED', `ADK's Gemini failed to build: ${scrub(err, secret)}`);
|
|
178
|
-
}
|
|
179
|
-
return this.#connection;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// ── The request ──────────────────────────────────────────────────────────────
|
|
183
|
-
/** The LlmRequest ADK's Gemini is handed for this ModelRequest (see the header, THE CALL). */
|
|
184
|
-
function adkRequest(request, model, wireModel) {
|
|
185
|
-
const extra = [];
|
|
186
|
-
for (const message of request.messages) {
|
|
187
|
-
if (message.role === 'system')
|
|
188
|
-
for (const part of message.parts)
|
|
189
|
-
if (part.text)
|
|
190
|
-
extra.push(part.text);
|
|
191
|
-
}
|
|
192
|
-
const llmRequest = modelRequestToLlmRequest({ ...request, model, messages: request.messages.filter((m) => m.role !== 'system') });
|
|
193
|
-
const config = llmRequest.config ?? {};
|
|
194
|
-
if (extra.length > 0) {
|
|
195
|
-
config.systemInstruction = { parts: [...(request.system ? [request.system] : []), ...extra].map((text) => ({ text })) };
|
|
196
|
-
}
|
|
197
|
-
config.toolConfig = { ...config.toolConfig, includeServerSideToolInvocations: true };
|
|
198
|
-
const thinking = config.thinkingConfig;
|
|
199
|
-
const none = request.reasoning === 'none' || (typeof request.reasoning === 'object' && request.reasoning.budget_tokens <= 0);
|
|
200
|
-
if (thinking && !none)
|
|
201
|
-
config.thinkingConfig = { ...thinking, includeThoughts: true };
|
|
202
|
-
return { ...llmRequest, model: wireModel, contents: llmRequest.contents.map(forAdk), config };
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* A content as ADK's Gemini may take it: `adk-` ids off calls and results,
|
|
206
|
-
* and every part copied where ADK writes (it clears a blob's display name in
|
|
207
|
-
* place), so a part the history shares (a carried genai part) is never changed.
|
|
208
|
-
*/
|
|
209
|
-
function forAdk(content) {
|
|
210
|
-
return {
|
|
211
|
-
...content,
|
|
212
|
-
parts: (content.parts ?? []).map((part) => {
|
|
213
|
-
const out = { ...part };
|
|
214
|
-
if (part.inlineData)
|
|
215
|
-
out.inlineData = { ...part.inlineData };
|
|
216
|
-
if (part.fileData)
|
|
217
|
-
out.fileData = { ...part.fileData };
|
|
218
|
-
if (part.functionCall)
|
|
219
|
-
out.functionCall = withoutAdkId(part.functionCall);
|
|
220
|
-
if (part.functionResponse)
|
|
221
|
-
out.functionResponse = withoutAdkId(part.functionResponse);
|
|
222
|
-
return out;
|
|
223
|
-
}),
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
function withoutAdkId(value) {
|
|
227
|
-
if (typeof value.id !== 'string' || !value.id.startsWith(ADK_CALL_ID_PREFIX))
|
|
228
|
-
return { ...value };
|
|
229
|
-
const { id: _id, ...rest } = value;
|
|
230
|
-
return rest;
|
|
231
|
-
}
|
|
232
|
-
// ── The response ─────────────────────────────────────────────────────────────
|
|
233
|
-
/** Folds the LlmResponses ADK yields for one call into the contract's deltas and its one final. */
|
|
234
|
-
class Reply {
|
|
235
|
-
#model;
|
|
236
|
-
#position;
|
|
237
|
-
#searchTool;
|
|
238
|
-
#stream;
|
|
239
|
-
#parts = [];
|
|
240
|
-
#thinking = [];
|
|
241
|
-
#usage;
|
|
242
|
-
#grounding;
|
|
243
|
-
#finishReason;
|
|
244
|
-
#error;
|
|
245
|
-
constructor(options) {
|
|
246
|
-
this.#model = options.model;
|
|
247
|
-
this.#position = options.position;
|
|
248
|
-
this.#searchTool = options.searchTool;
|
|
249
|
-
this.#stream = options.stream;
|
|
250
|
-
}
|
|
251
|
-
/** Takes one LlmResponse. A streamed partial's deltas come back to be yielded; everything else folds into the final. */
|
|
252
|
-
add(response) {
|
|
253
|
-
if (response.usageMetadata)
|
|
254
|
-
this.#usage = response.usageMetadata;
|
|
255
|
-
if (response.groundingMetadata)
|
|
256
|
-
this.#grounding = response.groundingMetadata;
|
|
257
|
-
if (response.finishReason)
|
|
258
|
-
this.#finishReason = response.finishReason;
|
|
259
|
-
if (response.partial === true) {
|
|
260
|
-
if (!this.#stream)
|
|
261
|
-
return undefined;
|
|
262
|
-
const delta = llmResponseToModelResponse(response);
|
|
263
|
-
return delta.parts.length > 0 ? delta : undefined;
|
|
264
|
-
}
|
|
265
|
-
if (response.errorCode) {
|
|
266
|
-
this.#error = { errorCode: response.errorCode, errorMessage: response.errorMessage, customMetadata: response.customMetadata };
|
|
267
|
-
}
|
|
268
|
-
for (const part of response.content?.parts ?? []) {
|
|
269
|
-
this.#parts.push(part);
|
|
270
|
-
if (part.thought === true && part.text)
|
|
271
|
-
this.#thinking.push({ type: 'thinking', text: part.text });
|
|
272
|
-
}
|
|
273
|
-
return undefined;
|
|
274
|
-
}
|
|
275
|
-
/** The thinking of a call that did not stream, as one partial before its final. */
|
|
276
|
-
thinking() {
|
|
277
|
-
return !this.#stream && this.#thinking.length > 0 ? { partial: true, parts: this.#thinking } : undefined;
|
|
278
|
-
}
|
|
279
|
-
/** The one final for a call that ran to its end. */
|
|
280
|
-
final() {
|
|
281
|
-
return this.#fold(true);
|
|
282
|
-
}
|
|
283
|
-
/** The final for a call ADK's Gemini threw on: GEMINI_ERROR, with what arrived before it. */
|
|
284
|
-
failed(err, secret) {
|
|
285
|
-
const status = errorStatus(err);
|
|
286
|
-
return {
|
|
287
|
-
...this.#fold(false),
|
|
288
|
-
finishReason: 'error',
|
|
289
|
-
error: {
|
|
290
|
-
code: 'GEMINI_ERROR',
|
|
291
|
-
message: scrub(err, secret),
|
|
292
|
-
retryable: classifyError(err).retryable,
|
|
293
|
-
...(status !== undefined ? { status } : {}),
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
/** The final for a canceled call: never retryable, so no fallback answers it. */
|
|
298
|
-
aborted() {
|
|
299
|
-
return { ...this.#fold(false), finishReason: 'error', error: { code: 'GEMINI_ERROR', message: 'The Gemini request was aborted.', retryable: false } };
|
|
300
|
-
}
|
|
301
|
-
/** Everything ADK yielded as one LlmResponse, mapped; with the error it yielded, or (for a throw) without. */
|
|
302
|
-
#fold(withError) {
|
|
303
|
-
const folded = {
|
|
304
|
-
...(this.#parts.length > 0 ? { content: { role: 'model', parts: this.#parts } } : {}),
|
|
305
|
-
...(this.#usage ? { usageMetadata: this.#usage } : {}),
|
|
306
|
-
...(this.#grounding ? { groundingMetadata: this.#grounding } : {}),
|
|
307
|
-
...(this.#finishReason ? { finishReason: this.#finishReason } : {}),
|
|
308
|
-
...(withError ? this.#error : {}),
|
|
309
|
-
};
|
|
310
|
-
return llmResponseToModelResponse(folded, { model: this.#model, index: this.#position, searchTool: this.#searchTool });
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// ── Transport ────────────────────────────────────────────────────────────────
|
|
314
|
-
function abortError() {
|
|
315
|
-
return Object.assign(new Error('The Gemini request was aborted.'), { name: 'AbortError' });
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Iterates `source` until `signal` aborts, and then throws an AbortError at
|
|
319
|
-
* once: the SDK aborts its fetch on the same signal, but a stalled stream or
|
|
320
|
-
* a backoff sleep must not hold the caller. The source is closed in the
|
|
321
|
-
* background.
|
|
322
|
-
*/
|
|
323
|
-
async function* untilAborted(source, signal) {
|
|
324
|
-
if (!signal) {
|
|
325
|
-
yield* source;
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
let onAbort = () => { };
|
|
329
|
-
const aborted = new Promise((_, reject) => {
|
|
330
|
-
onAbort = () => reject(abortError());
|
|
331
|
-
});
|
|
332
|
-
aborted.catch(() => { });
|
|
333
|
-
if (signal.aborted)
|
|
334
|
-
onAbort();
|
|
335
|
-
else
|
|
336
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
337
|
-
let done = false;
|
|
338
|
-
try {
|
|
339
|
-
for (;;) {
|
|
340
|
-
const next = await Promise.race([source.next(), aborted]);
|
|
341
|
-
if (next.done) {
|
|
342
|
-
done = true;
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
yield next.value;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
finally {
|
|
349
|
-
signal.removeEventListener('abort', onAbort);
|
|
350
|
-
if (!done)
|
|
351
|
-
source.return(undefined).catch(() => { });
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
355
|
-
function setupError(code, message) {
|
|
356
|
-
return { code, message, retryable: false };
|
|
357
|
-
}
|
|
358
|
-
function nonEmpty(value) {
|
|
359
|
-
return value?.trim() || undefined;
|
|
360
|
-
}
|
|
361
|
-
/** The Gemini API key from the environment, in ADK's order. */
|
|
362
|
-
function envApiKey() {
|
|
363
|
-
return nonEmpty(process.env.GOOGLE_GENAI_API_KEY) ?? nonEmpty(process.env.GOOGLE_API_KEY) ?? nonEmpty(process.env.GEMINI_API_KEY);
|
|
364
|
-
}
|
|
365
|
-
/** An error's text without the key in use, or anything shaped like a key. */
|
|
366
|
-
function scrub(err, secret) {
|
|
367
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
368
|
-
return errorText(secret ? message.split(secret).join('[redacted]') : message);
|
|
369
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/models/adkShim.ts — any ModelAdapter on the engine's own contract
|
|
3
|
-
* (lib/models/contract.ts, ADR 0048) as an ADK BaseLlm, so an adapter can
|
|
4
|
-
* move onto the contract while ADK still runs every turn (ADR 0045).
|
|
5
|
-
*
|
|
6
|
-
* WHY this file exists:
|
|
7
|
-
* ADK calls `BaseLlm.generateContentAsync(llmRequest, stream, abortSignal)`
|
|
8
|
-
* and expects LlmResponses back. An adapter on the contract implements
|
|
9
|
-
* `generate(ModelRequest)` and yields ModelResponses. The shim is the one
|
|
10
|
-
* class between them: it maps the LlmRequest in and each ModelResponse out
|
|
11
|
-
* through lib/models/genaiMapping.ts, and does no translation of its own.
|
|
12
|
-
* The adapter tickets move one adapter at a time behind it, and ADK sees
|
|
13
|
-
* no change: ClaudeLlm (lib/models/claudeLlm.ts) is the shim around
|
|
14
|
-
* ClaudeAdapter. A provider's ADK class may extend `toModelRequest` with
|
|
15
|
-
* what its adapter reads on the ADK path only (ADR 0055).
|
|
16
|
-
*
|
|
17
|
-
* WHAT THE SHIM DOES ONCE, SO NO CONTRACT ADAPTER DOES IT (ADR 0053):
|
|
18
|
-
* - The turn's charge and the llm.request span. The call goes through
|
|
19
|
-
* `traceLlmGeneration` (lib/observability/tracer.ts) exactly as every
|
|
20
|
-
* ADK-path adapter's call does, with the adapter's `provider`, the
|
|
21
|
-
* shim's model id and the request, here the ModelRequest the shim hands
|
|
22
|
-
* the adapter (an ADK-path adapter maps its LlmRequest to one for a
|
|
23
|
-
* failed call's payload). So the step budget, a stopped
|
|
24
|
-
* turn's refusal (STEP_LIMIT, DEADLINE_EXCEEDED, CANCELED, as the same
|
|
25
|
-
* LlmResponse), the token charge and the span's attributes are what
|
|
26
|
-
* GptLlm and the chat-completions adapters produce. A refused
|
|
27
|
-
* call never reaches the adapter. The adapter decorates the open span
|
|
28
|
-
* with `setLlmSpanAttribute`, and never opens one of its own.
|
|
29
|
-
* - The abort signal. The request's `signal` aborts when the turn stops
|
|
30
|
-
* (`currentTurnSignal()`) or when the signal ADK passes aborts, whichever
|
|
31
|
-
* comes first; an adapter hands it to its provider call.
|
|
32
|
-
*
|
|
33
|
-
* TWO SEAMS FOR A SUBCLASS, the genai mapping by default:
|
|
34
|
-
* - `toLlmResponse` (ADR 0056) keeps what an ADK-path adapter wrote beyond
|
|
35
|
-
* the contract: GptLlm's usage and server-side tool record, and the
|
|
36
|
-
* chat-completions shims' usage and final shape. It runs inside the
|
|
37
|
-
* span, so the tracer reads what it returns.
|
|
38
|
-
* - `toModelRequest` (ADR 0057) carries what an adapter reads beside the
|
|
39
|
-
* contract: the chat-completions shims' older generateContentConfig
|
|
40
|
-
* spelling. The span records the request it returns.
|
|
41
|
-
*
|
|
42
|
-
* WHAT IT DOES NOT DO:
|
|
43
|
-
* - Repair an adapter that breaks the contract. A throw reaches ADK as a
|
|
44
|
-
* throw (as Gemini's does today), and every response is mapped as it
|
|
45
|
-
* comes, so a missing final or a response after it passes through.
|
|
46
|
-
* - Live connections. `connect()` is refused, as on every adapter but
|
|
47
|
-
* ADK's Gemini: live sessions are outside the contract.
|
|
48
|
-
*
|
|
49
|
-
* ONE SHIM PER LEAF ADAPTER. A fallback pair on the ADK path is
|
|
50
|
-
* `FallbackLlm(shim(primary), shim(fallback))` (ADR 0044), so each model call
|
|
51
|
-
* is charged and traced on its own, as today. A FallbackAdapter behind one
|
|
52
|
-
* shim would make a redirected call one span and one charge.
|
|
53
|
-
*/
|
|
54
|
-
import { BaseLlm } from '../adkPeer.ts';
|
|
55
|
-
import type { BaseLlmConnection, BaseLlmType, LlmRequest, LlmResponse } from '@google/adk';
|
|
56
|
-
import type { ModelAdapter, ModelRequest, ModelResponse } from './contract.ts';
|
|
57
|
-
import type { ModelRequestOptions } from './genaiMapping.ts';
|
|
58
|
-
export interface AdkShimOptions {
|
|
59
|
-
/**
|
|
60
|
-
* The model id ADK and the span see, and the request's `model`. Default
|
|
61
|
-
* the adapter's own `model`, which is what an adapter for one YAML id has.
|
|
62
|
-
*/
|
|
63
|
-
model?: string;
|
|
64
|
-
}
|
|
65
|
-
/** Builds the adapter for a model id: what a registered shim class calls when ADK constructs it. */
|
|
66
|
-
export type ModelAdapterFactory = (model: string) => ModelAdapter;
|
|
67
|
-
export declare class AdkShim extends BaseLlm {
|
|
68
|
-
/** The adapter every call runs through. */
|
|
69
|
-
readonly adapter: ModelAdapter;
|
|
70
|
-
constructor(adapter: ModelAdapter, options?: AdkShimOptions);
|
|
71
|
-
/**
|
|
72
|
-
* One model call: charged against the turn and traced as one llm.request
|
|
73
|
-
* span, then mapped to the contract and back. A call the turn refuses
|
|
74
|
-
* yields the refusal and never reaches the adapter.
|
|
75
|
-
*/
|
|
76
|
-
generateContentAsync(llmRequest: LlmRequest, stream?: boolean, abortSignal?: AbortSignal): AsyncGenerator<LlmResponse, void>;
|
|
77
|
-
/** Runs inside the span: the adapter's setLlmSpanAttribute calls land on it. */
|
|
78
|
-
private generateInner;
|
|
79
|
-
/**
|
|
80
|
-
* The ModelRequest one call hands the adapter: `llmRequestToModelRequest`.
|
|
81
|
-
* A provider's ADK class may extend it with what its adapter reads on the
|
|
82
|
-
* ADK path only, an agent setting the contract leaves out: ClaudeLlm's
|
|
83
|
-
* older reasoning spelling (ADR 0055), and the chat-completions shims'
|
|
84
|
-
* older generateContentConfig spelling (ADR 0057). It never removes or
|
|
85
|
-
* rewrites a contract field. The span records what it returns.
|
|
86
|
-
*/
|
|
87
|
-
protected toModelRequest(llmRequest: LlmRequest, options: ModelRequestOptions): ModelRequest;
|
|
88
|
-
/**
|
|
89
|
-
* One ModelResponse as the LlmResponse ADK sees: modelResponseToLlmResponse.
|
|
90
|
-
* It runs inside the llm.request span, so the tracer reads what it returns.
|
|
91
|
-
* A subclass that stands in for an ADK-path adapter overrides it to keep
|
|
92
|
-
* what that adapter wrote beyond the contract (GptLlm, ADR 0056; the
|
|
93
|
-
* chat-completions shims, ADR 0057).
|
|
94
|
-
*/
|
|
95
|
-
protected toLlmResponse(response: ModelResponse): LlmResponse;
|
|
96
|
-
/** Live/bidirectional connections are outside the model contract. Throws to say so. */
|
|
97
|
-
connect(_llmRequest: LlmRequest): Promise<BaseLlmConnection>;
|
|
98
|
-
}
|
|
99
|
-
/** A shim around `adapter`, under `model` (default the adapter's own id). */
|
|
100
|
-
export declare function adkShim(adapter: ModelAdapter, model?: string): AdkShim;
|
|
101
|
-
/**
|
|
102
|
-
* A shim class ADK's LLMRegistry can register: it answers for
|
|
103
|
-
* `supportedModels` and builds its adapter with `createAdapter(model)` when
|
|
104
|
-
* ADK constructs it for a model id. Pass the direct adapter's own pattern
|
|
105
|
-
* instances, since the registry keys on the pattern object (see
|
|
106
|
-
* lib/models/gatewayLlm.ts), so the shim replaces that entry rather than
|
|
107
|
-
* shadowing it.
|
|
108
|
-
*/
|
|
109
|
-
export declare function adkShimClass(supportedModels: Array<string | RegExp>, createAdapter: ModelAdapterFactory): BaseLlmType & (new (params: {
|
|
110
|
-
model: string;
|
|
111
|
-
}) => AdkShim);
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/models/adkShim.ts — any ModelAdapter on the engine's own contract
|
|
3
|
-
* (lib/models/contract.ts, ADR 0048) as an ADK BaseLlm, so an adapter can
|
|
4
|
-
* move onto the contract while ADK still runs every turn (ADR 0045).
|
|
5
|
-
*
|
|
6
|
-
* WHY this file exists:
|
|
7
|
-
* ADK calls `BaseLlm.generateContentAsync(llmRequest, stream, abortSignal)`
|
|
8
|
-
* and expects LlmResponses back. An adapter on the contract implements
|
|
9
|
-
* `generate(ModelRequest)` and yields ModelResponses. The shim is the one
|
|
10
|
-
* class between them: it maps the LlmRequest in and each ModelResponse out
|
|
11
|
-
* through lib/models/genaiMapping.ts, and does no translation of its own.
|
|
12
|
-
* The adapter tickets move one adapter at a time behind it, and ADK sees
|
|
13
|
-
* no change: ClaudeLlm (lib/models/claudeLlm.ts) is the shim around
|
|
14
|
-
* ClaudeAdapter. A provider's ADK class may extend `toModelRequest` with
|
|
15
|
-
* what its adapter reads on the ADK path only (ADR 0055).
|
|
16
|
-
*
|
|
17
|
-
* WHAT THE SHIM DOES ONCE, SO NO CONTRACT ADAPTER DOES IT (ADR 0053):
|
|
18
|
-
* - The turn's charge and the llm.request span. The call goes through
|
|
19
|
-
* `traceLlmGeneration` (lib/observability/tracer.ts) exactly as every
|
|
20
|
-
* ADK-path adapter's call does, with the adapter's `provider`, the
|
|
21
|
-
* shim's model id and the request, here the ModelRequest the shim hands
|
|
22
|
-
* the adapter (an ADK-path adapter maps its LlmRequest to one for a
|
|
23
|
-
* failed call's payload). So the step budget, a stopped
|
|
24
|
-
* turn's refusal (STEP_LIMIT, DEADLINE_EXCEEDED, CANCELED, as the same
|
|
25
|
-
* LlmResponse), the token charge and the span's attributes are what
|
|
26
|
-
* GptLlm and the chat-completions adapters produce. A refused
|
|
27
|
-
* call never reaches the adapter. The adapter decorates the open span
|
|
28
|
-
* with `setLlmSpanAttribute`, and never opens one of its own.
|
|
29
|
-
* - The abort signal. The request's `signal` aborts when the turn stops
|
|
30
|
-
* (`currentTurnSignal()`) or when the signal ADK passes aborts, whichever
|
|
31
|
-
* comes first; an adapter hands it to its provider call.
|
|
32
|
-
*
|
|
33
|
-
* TWO SEAMS FOR A SUBCLASS, the genai mapping by default:
|
|
34
|
-
* - `toLlmResponse` (ADR 0056) keeps what an ADK-path adapter wrote beyond
|
|
35
|
-
* the contract: GptLlm's usage and server-side tool record, and the
|
|
36
|
-
* chat-completions shims' usage and final shape. It runs inside the
|
|
37
|
-
* span, so the tracer reads what it returns.
|
|
38
|
-
* - `toModelRequest` (ADR 0057) carries what an adapter reads beside the
|
|
39
|
-
* contract: the chat-completions shims' older generateContentConfig
|
|
40
|
-
* spelling. The span records the request it returns.
|
|
41
|
-
*
|
|
42
|
-
* WHAT IT DOES NOT DO:
|
|
43
|
-
* - Repair an adapter that breaks the contract. A throw reaches ADK as a
|
|
44
|
-
* throw (as Gemini's does today), and every response is mapped as it
|
|
45
|
-
* comes, so a missing final or a response after it passes through.
|
|
46
|
-
* - Live connections. `connect()` is refused, as on every adapter but
|
|
47
|
-
* ADK's Gemini: live sessions are outside the contract.
|
|
48
|
-
*
|
|
49
|
-
* ONE SHIM PER LEAF ADAPTER. A fallback pair on the ADK path is
|
|
50
|
-
* `FallbackLlm(shim(primary), shim(fallback))` (ADR 0044), so each model call
|
|
51
|
-
* is charged and traced on its own, as today. A FallbackAdapter behind one
|
|
52
|
-
* shim would make a redirected call one span and one charge.
|
|
53
|
-
*/
|
|
54
|
-
import { BaseLlm } from '../adkPeer.js';
|
|
55
|
-
import { llmRequestToModelRequest, modelResponseToLlmResponse } from './genaiMapping.js';
|
|
56
|
-
import { traceLlmGeneration } from '../observability/tracer.js';
|
|
57
|
-
import { currentTurnSignal } from '../runtime/turnControl.js';
|
|
58
|
-
/** One signal that aborts when any of the given ones does; undefined when none is given. */
|
|
59
|
-
function eitherSignal(...candidates) {
|
|
60
|
-
const signals = [...new Set(candidates.filter((s) => s !== undefined))];
|
|
61
|
-
return signals.length <= 1 ? signals[0] : AbortSignal.any(signals);
|
|
62
|
-
}
|
|
63
|
-
export class AdkShim extends BaseLlm {
|
|
64
|
-
/** The adapter every call runs through. */
|
|
65
|
-
adapter;
|
|
66
|
-
constructor(adapter, options = {}) {
|
|
67
|
-
super({ model: options.model ?? adapter.model });
|
|
68
|
-
this.adapter = adapter;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* One model call: charged against the turn and traced as one llm.request
|
|
72
|
-
* span, then mapped to the contract and back. A call the turn refuses
|
|
73
|
-
* yields the refusal and never reaches the adapter.
|
|
74
|
-
*/
|
|
75
|
-
async *generateContentAsync(llmRequest, stream = false, abortSignal) {
|
|
76
|
-
const signal = eitherSignal(abortSignal, currentTurnSignal(), llmRequest.config?.abortSignal);
|
|
77
|
-
const request = this.toModelRequest(llmRequest, {
|
|
78
|
-
model: this.model,
|
|
79
|
-
stream,
|
|
80
|
-
...(signal ? { signal } : {}),
|
|
81
|
-
});
|
|
82
|
-
yield* traceLlmGeneration({ provider: this.adapter.provider, model: this.model, request }, this.generateInner(request));
|
|
83
|
-
}
|
|
84
|
-
/** Runs inside the span: the adapter's setLlmSpanAttribute calls land on it. */
|
|
85
|
-
async *generateInner(request) {
|
|
86
|
-
for await (const response of this.adapter.generate(request)) {
|
|
87
|
-
yield this.toLlmResponse(response);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* The ModelRequest one call hands the adapter: `llmRequestToModelRequest`.
|
|
92
|
-
* A provider's ADK class may extend it with what its adapter reads on the
|
|
93
|
-
* ADK path only, an agent setting the contract leaves out: ClaudeLlm's
|
|
94
|
-
* older reasoning spelling (ADR 0055), and the chat-completions shims'
|
|
95
|
-
* older generateContentConfig spelling (ADR 0057). It never removes or
|
|
96
|
-
* rewrites a contract field. The span records what it returns.
|
|
97
|
-
*/
|
|
98
|
-
toModelRequest(llmRequest, options) {
|
|
99
|
-
return llmRequestToModelRequest(llmRequest, options);
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* One ModelResponse as the LlmResponse ADK sees: modelResponseToLlmResponse.
|
|
103
|
-
* It runs inside the llm.request span, so the tracer reads what it returns.
|
|
104
|
-
* A subclass that stands in for an ADK-path adapter overrides it to keep
|
|
105
|
-
* what that adapter wrote beyond the contract (GptLlm, ADR 0056; the
|
|
106
|
-
* chat-completions shims, ADR 0057).
|
|
107
|
-
*/
|
|
108
|
-
toLlmResponse(response) {
|
|
109
|
-
return modelResponseToLlmResponse(response);
|
|
110
|
-
}
|
|
111
|
-
/** Live/bidirectional connections are outside the model contract. Throws to say so. */
|
|
112
|
-
async connect(_llmRequest) {
|
|
113
|
-
throw new Error(`${this.model} runs through the ADK shim, which does not support live bidirectional connections ` +
|
|
114
|
-
'(they are outside the model contract). Use a Gemini model for live/streaming sessions.');
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/** A shim around `adapter`, under `model` (default the adapter's own id). */
|
|
118
|
-
export function adkShim(adapter, model = adapter.model) {
|
|
119
|
-
return new AdkShim(adapter, { model });
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* A shim class ADK's LLMRegistry can register: it answers for
|
|
123
|
-
* `supportedModels` and builds its adapter with `createAdapter(model)` when
|
|
124
|
-
* ADK constructs it for a model id. Pass the direct adapter's own pattern
|
|
125
|
-
* instances, since the registry keys on the pattern object (see
|
|
126
|
-
* lib/models/gatewayLlm.ts), so the shim replaces that entry rather than
|
|
127
|
-
* shadowing it.
|
|
128
|
-
*/
|
|
129
|
-
export function adkShimClass(supportedModels, createAdapter) {
|
|
130
|
-
return class AdkShimFor extends AdkShim {
|
|
131
|
-
static supportedModels = supportedModels;
|
|
132
|
-
constructor({ model }) {
|
|
133
|
-
super(createAdapter(model), { model });
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
}
|