melchizedek-agents 0.18.0 → 0.19.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/.env.example +12 -0
- package/CHANGELOG.md +825 -0
- package/DOCUMENTATION.md +217 -59
- package/README.md +42 -1
- package/config/agents/examples/harness.yaml +3 -1
- package/config/agents/examples/model_zoo.yaml +10 -9
- package/config/agents/syndicateSchema.yaml +50 -16
- package/config/agents/templates/account_memory.yaml +2 -6
- package/config/agents/templates/case_desk.yaml +3 -9
- package/config/agents/templates/draft_review.yaml +3 -9
- package/config/agents/templates/intake_extractor.yaml +1 -3
- package/config/agents/templates/research_brief.yaml +3 -9
- package/config/agents/templates/research_desk.yaml +3 -9
- package/config/agents/templates/review_panel.yaml +4 -12
- package/config/agents/templates/support_triage.yaml +4 -12
- package/config/agents/templates/systems_operator.yaml +2 -6
- package/db/hardening.sql +7 -1
- package/db/migrations/0013_tool_credentials.sql +260 -0
- package/dist/lib/a2a/app.d.ts +70 -6
- package/dist/lib/a2a/app.js +107 -2
- package/dist/lib/a2a/executor.d.ts +20 -3
- package/dist/lib/a2a/executor.js +66 -7
- package/dist/lib/a2a/remoteAgent.d.ts +24 -9
- package/dist/lib/a2a/remoteAgent.js +31 -14
- package/dist/lib/compile.d.ts +121 -30
- package/dist/lib/compile.js +137 -124
- package/dist/lib/compileAdk.d.ts +29 -0
- package/dist/lib/compileAdk.js +132 -0
- package/dist/lib/compileNative.d.ts +85 -0
- package/dist/lib/compileNative.js +222 -0
- package/dist/lib/config.d.ts +6 -5
- package/dist/lib/config.js +6 -5
- package/dist/lib/doctor.js +1 -0
- package/dist/lib/index.d.ts +2 -1
- package/dist/lib/index.js +2 -1
- package/dist/lib/loadSyndicate.d.ts +18 -0
- package/dist/lib/memory/erase.d.ts +5 -3
- package/dist/lib/memory/erase.js +5 -2
- package/dist/lib/memory/namespace.d.ts +8 -1
- package/dist/lib/memory/namespace.js +12 -6
- package/dist/lib/memory/providers.js +2 -1
- package/dist/lib/memory/supabaseMemoryService.d.ts +37 -3
- package/dist/lib/memory/supabaseMemoryService.js +42 -2
- package/dist/lib/model.d.ts +47 -0
- package/dist/lib/model.js +37 -0
- package/dist/lib/models/adapterResolver.d.ts +113 -0
- package/dist/lib/models/adapterResolver.js +136 -0
- package/dist/lib/models/adkGeminiAdapter.d.ts +90 -0
- package/dist/lib/models/adkGeminiAdapter.js +367 -0
- package/dist/lib/models/adkShim.d.ts +111 -0
- package/dist/lib/models/adkShim.js +136 -0
- package/dist/lib/models/capabilities.d.ts +3 -0
- package/dist/lib/models/capabilities.js +20 -13
- package/dist/lib/models/chatCompletionsAdapter.d.ts +279 -0
- package/dist/lib/models/chatCompletionsAdapter.js +854 -0
- package/dist/lib/models/circuitBreaker.d.ts +42 -0
- package/dist/lib/models/circuitBreaker.js +70 -0
- package/dist/lib/models/claudeAdapter.d.ts +103 -0
- package/dist/lib/models/claudeAdapter.js +669 -0
- package/dist/lib/models/claudeLlm.d.ts +39 -62
- package/dist/lib/models/claudeLlm.js +44 -349
- package/dist/lib/models/claudeModels.d.ts +127 -0
- package/dist/lib/models/claudeModels.js +237 -0
- package/dist/lib/models/contract.d.ts +395 -0
- package/dist/lib/models/contract.js +39 -0
- package/dist/lib/models/endpoints.js +2 -1
- package/dist/lib/models/errorResponse.d.ts +50 -0
- package/dist/lib/models/errorResponse.js +48 -0
- package/dist/lib/models/fallback.d.ts +17 -10
- package/dist/lib/models/fallback.js +53 -42
- package/dist/lib/models/fallbackAdapter.d.ts +55 -0
- package/dist/lib/models/fallbackAdapter.js +62 -0
- package/dist/lib/models/gateway.js +2 -1
- package/dist/lib/models/gatewayAdapter.d.ts +51 -0
- package/dist/lib/models/gatewayAdapter.js +81 -0
- package/dist/lib/models/gatewayLlm.d.ts +12 -33
- package/dist/lib/models/gatewayLlm.js +14 -75
- package/dist/lib/models/geminiAdapter.d.ts +123 -0
- package/dist/lib/models/geminiAdapter.js +831 -0
- package/dist/lib/models/geminiState.d.ts +25 -0
- package/dist/lib/models/geminiState.js +25 -0
- package/dist/lib/models/genaiMapping.d.ts +263 -0
- package/dist/lib/models/genaiMapping.js +862 -0
- package/dist/lib/models/gptAdapter.d.ts +240 -0
- package/dist/lib/models/gptAdapter.js +804 -0
- package/dist/lib/models/gptLlm.d.ts +63 -123
- package/dist/lib/models/gptLlm.js +88 -602
- package/dist/lib/models/grokAdapter.d.ts +86 -0
- package/dist/lib/models/grokAdapter.js +142 -0
- package/dist/lib/models/grokLlm.d.ts +22 -45
- package/dist/lib/models/grokLlm.js +22 -67
- package/dist/lib/models/kimiAdapter.d.ts +157 -0
- package/dist/lib/models/kimiAdapter.js +203 -0
- package/dist/lib/models/kimiLlm.d.ts +15 -107
- package/dist/lib/models/kimiLlm.js +17 -153
- package/dist/lib/models/ollamaAdapter.d.ts +125 -0
- package/dist/lib/models/ollamaAdapter.js +155 -0
- package/dist/lib/models/ollamaLlm.d.ts +11 -107
- package/dist/lib/models/ollamaLlm.js +13 -151
- package/dist/lib/models/openAiCompatibleLlm.d.ts +73 -216
- package/dist/lib/models/openAiCompatibleLlm.js +109 -671
- package/dist/lib/models/providerState.d.ts +63 -0
- package/dist/lib/models/providerState.js +61 -0
- package/dist/lib/models/reasoning.d.ts +26 -0
- package/dist/lib/models/reasoning.js +59 -0
- package/dist/lib/models/registry.d.ts +58 -35
- package/dist/lib/models/registry.js +89 -118
- package/dist/lib/models/retry.d.ts +2 -1
- package/dist/lib/models/retry.js +3 -2
- package/dist/lib/models/schemaNormalize.d.ts +98 -0
- package/dist/lib/models/schemaNormalize.js +321 -1
- package/dist/lib/models/tracedGemini.d.ts +41 -0
- package/dist/lib/models/tracedGemini.js +98 -0
- package/dist/lib/models/urls.d.ts +11 -0
- package/dist/lib/models/urls.js +16 -0
- package/dist/lib/net/redirects.d.ts +21 -2
- package/dist/lib/net/redirects.js +11 -34
- package/dist/lib/observability/audit.d.ts +5 -1
- package/dist/lib/observability/lineage.d.ts +14 -0
- package/dist/lib/observability/lineage.js +33 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +10 -4
- package/dist/lib/observability/supabaseSpanExporter.js +17 -9
- package/dist/lib/observability/tracer.d.ts +26 -8
- package/dist/lib/observability/tracer.js +43 -18
- package/dist/lib/runtime/adkMemoryBridge.d.ts +46 -0
- package/dist/lib/runtime/adkMemoryBridge.js +68 -0
- package/dist/lib/runtime/adkSessionBridge.d.ts +78 -0
- package/dist/lib/runtime/adkSessionBridge.js +176 -0
- package/dist/lib/runtime/credentials.d.ts +59 -0
- package/dist/lib/runtime/credentials.js +62 -0
- package/dist/lib/runtime/events.d.ts +229 -0
- package/dist/lib/runtime/events.js +274 -0
- package/dist/lib/runtime/logging.d.ts +32 -0
- package/dist/lib/runtime/logging.js +55 -0
- package/dist/lib/runtime/memoryService.d.ts +72 -0
- package/dist/lib/runtime/memoryService.js +25 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +203 -0
- package/dist/lib/runtime/native/agentLoop.js +687 -0
- package/dist/lib/runtime/native/compaction.d.ts +103 -0
- package/dist/lib/runtime/native/compaction.js +191 -0
- package/dist/lib/runtime/native/delegate.d.ts +98 -0
- package/dist/lib/runtime/native/delegate.js +173 -0
- package/dist/lib/runtime/native/history.d.ts +83 -0
- package/dist/lib/runtime/native/history.js +312 -0
- package/dist/lib/runtime/native/interrupts.d.ts +126 -0
- package/dist/lib/runtime/native/interrupts.js +291 -0
- package/dist/lib/runtime/native/request.d.ts +215 -0
- package/dist/lib/runtime/native/request.js +585 -0
- package/dist/lib/runtime/native/selfCorrection.d.ts +106 -0
- package/dist/lib/runtime/native/selfCorrection.js +253 -0
- package/dist/lib/runtime/native/step.d.ts +148 -0
- package/dist/lib/runtime/native/step.js +239 -0
- package/dist/lib/runtime/native/taskMode.d.ts +64 -0
- package/dist/lib/runtime/native/taskMode.js +126 -0
- package/dist/lib/runtime/native/telemetry.d.ts +75 -0
- package/dist/lib/runtime/native/telemetry.js +299 -0
- package/dist/lib/runtime/native/tempState.d.ts +31 -0
- package/dist/lib/runtime/native/tempState.js +56 -0
- package/dist/lib/runtime/nativeTurn.d.ts +86 -0
- package/dist/lib/runtime/nativeTurn.js +107 -0
- package/dist/lib/runtime/questions.d.ts +29 -7
- package/dist/lib/runtime/questions.js +47 -32
- package/dist/lib/runtime/runtimeFlag.d.ts +30 -0
- package/dist/lib/runtime/runtimeFlag.js +50 -0
- package/dist/lib/runtime/sessions.d.ts +166 -0
- package/dist/lib/runtime/sessions.js +201 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +41 -5
- package/dist/lib/runtime/syndicateTurn.js +202 -32
- package/dist/lib/session/supabaseSessionService.d.ts +64 -17
- package/dist/lib/session/supabaseSessionService.js +148 -105
- package/dist/lib/session/transcript.d.ts +68 -22
- package/dist/lib/session/transcript.js +102 -28
- package/dist/lib/skills.js +33 -7
- package/dist/lib/storage/postgres/credentialStore.d.ts +20 -0
- package/dist/lib/storage/postgres/credentialStore.js +82 -0
- package/dist/lib/storage/postgres/index.d.ts +12 -0
- package/dist/lib/storage/postgres/index.js +6 -1
- package/dist/lib/storage/postgres/sessionService.d.ts +43 -15
- package/dist/lib/storage/postgres/sessionService.js +128 -66
- package/dist/lib/syndicateSchema.d.ts +47 -0
- package/dist/lib/syndicateSchema.js +72 -3
- package/dist/lib/toolRegistry.d.ts +15 -2
- package/dist/lib/toolRegistry.js +33 -17
- package/dist/lib/tools/adkTool.d.ts +81 -0
- package/dist/lib/tools/adkTool.js +268 -0
- package/dist/lib/tools/auth.d.ts +118 -0
- package/dist/lib/tools/auth.js +89 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +7 -6
- package/dist/lib/tools/collectionsSearchTool.js +11 -26
- package/dist/lib/tools/credentialCipher.d.ts +58 -0
- package/dist/lib/tools/credentialCipher.js +116 -0
- package/dist/lib/tools/credentialStore.d.ts +77 -0
- package/dist/lib/tools/credentialStore.js +246 -0
- package/dist/lib/tools/examples.d.ts +43 -0
- package/dist/lib/tools/examples.js +62 -0
- package/dist/lib/tools/generateImageTool.d.ts +15 -6
- package/dist/lib/tools/generateImageTool.js +19 -29
- package/dist/lib/tools/inspectImageTool.d.ts +9 -6
- package/dist/lib/tools/inspectImageTool.js +13 -17
- package/dist/lib/tools/mcpToolFactory.d.ts +42 -1
- package/dist/lib/tools/mcpToolFactory.js +46 -47
- package/dist/lib/tools/memoryTools.d.ts +61 -0
- package/dist/lib/tools/memoryTools.js +119 -0
- package/dist/lib/tools/nativeTools.d.ts +38 -0
- package/dist/lib/tools/nativeTools.js +43 -0
- package/dist/lib/tools/oauthConsent.d.ts +181 -0
- package/dist/lib/tools/oauthConsent.js +330 -0
- package/dist/lib/tools/openapi/call.d.ts +108 -0
- package/dist/lib/tools/openapi/call.js +295 -0
- package/dist/lib/tools/openapi/parse.d.ts +147 -0
- package/dist/lib/tools/openapi/parse.js +570 -0
- package/dist/lib/tools/openapiTools.d.ts +25 -17
- package/dist/lib/tools/openapiTools.js +124 -121
- package/dist/lib/tools/scienceTools.d.ts +14 -14
- package/dist/lib/tools/skillToolset.d.ts +58 -87
- package/dist/lib/tools/skillToolset.js +61 -142
- package/dist/lib/tools/skills/env.d.ts +58 -0
- package/dist/lib/tools/skills/env.js +165 -0
- package/dist/lib/tools/skills/executor.d.ts +114 -0
- package/dist/lib/tools/skills/executor.js +331 -0
- package/dist/lib/tools/skills/frontmatter.d.ts +79 -0
- package/dist/lib/tools/skills/frontmatter.js +215 -0
- package/dist/lib/tools/skills/loader.d.ts +91 -0
- package/dist/lib/tools/skills/loader.js +257 -0
- package/dist/lib/tools/skills/tools.d.ts +187 -0
- package/dist/lib/tools/skills/tools.js +386 -0
- package/dist/lib/tools/taskTools.d.ts +15 -15
- package/dist/lib/tools/tool.d.ts +354 -0
- package/dist/lib/tools/tool.js +304 -0
- package/dist/lib/tools/toolContract.d.ts +89 -33
- package/dist/lib/tools/toolContract.js +104 -82
- package/dist/lib/tools/urlContextTool.d.ts +3 -0
- package/dist/lib/tools/urlContextTool.js +5 -1
- package/dist/lib/tools/webExtractTool.d.ts +2 -2
- package/dist/lib/tools/webExtractTool.js +2 -1
- package/dist/lib/tools/webSearchTool.d.ts +6 -9
- package/dist/lib/tools/webSearchTool.js +14 -59
- package/dist/lib/tools/wikiTools.d.ts +19 -19
- package/dist/lib/tools/wikiTools.js +13 -9
- package/dist/lib/tools/xApiSearchTool.d.ts +2 -2
- package/dist/lib/tools/xApiSearchTool.js +2 -1
- package/dist/lib/tools/xSearchTool.d.ts +7 -9
- package/dist/lib/tools/xSearchTool.js +11 -62
- package/dist/lib/tools/xaiSearchParams.d.ts +41 -0
- package/dist/lib/tools/xaiSearchParams.js +126 -0
- package/dist/lib/wiki/agentRun.d.ts +12 -0
- package/dist/lib/wiki/agentRun.js +72 -34
- package/dist/lib/wiki/entities.d.ts +12 -0
- package/dist/lib/wiki/entities.js +45 -3
- package/dist/lib/workflow/agentNode.d.ts +136 -0
- package/dist/lib/workflow/agentNode.js +274 -0
- package/dist/lib/workflow/graph.d.ts +177 -0
- package/dist/lib/workflow/graph.js +458 -0
- package/dist/lib/workflow/nodeEvents.d.ts +50 -0
- package/dist/lib/workflow/nodeEvents.js +101 -0
- package/dist/lib/workflow/pause.d.ts +122 -0
- package/dist/lib/workflow/pause.js +180 -0
- package/dist/lib/workflow/resume.d.ts +165 -0
- package/dist/lib/workflow/resume.js +440 -0
- package/dist/lib/workflow/route.d.ts +58 -0
- package/dist/lib/workflow/route.js +69 -0
- package/dist/lib/workflow/scheduler.d.ts +415 -0
- package/dist/lib/workflow/scheduler.js +743 -0
- package/dist/lib/workflow/toolNode.d.ts +127 -0
- package/dist/lib/workflow/toolNode.js +236 -0
- package/dist/lib/workflow/turn.d.ts +118 -0
- package/dist/lib/workflow/turn.js +216 -0
- package/dist/lib/workflowConfig.d.ts +3 -2
- package/dist/lib/workflowConfig.js +3 -12
- package/dist/scripts/a2a_server.js +4 -3
- package/dist/scripts/assistant_worker.d.ts +2 -1
- package/dist/scripts/assistant_worker.js +9 -4
- package/dist/scripts/syndicate_chat.js +19 -10
- package/package.json +4 -1
- package/skills/briefs/melchizedek-author.md +3 -2
- package/skills/briefs/melchizedek-models.md +13 -9
- package/skills/briefs/melchizedek.md +3 -3
- package/skills/melchizedek/SKILL.md +2 -1
- package/skills/melchizedek-author/SKILL.md +2 -1
- package/skills/melchizedek-author/assets/minimal.yaml +2 -0
- package/skills/melchizedek-models/SKILL.md +23 -6
|
@@ -12,7 +12,27 @@
|
|
|
12
12
|
* Tools are declared once (YAML / MCP discovery) in the Gemini dialect, so
|
|
13
13
|
* every non-Gemini adapter runs its tool schemas through this function at
|
|
14
14
|
* request-build time. See DOCUMENTATION.md §7.1.
|
|
15
|
+
*
|
|
16
|
+
* The engine's own model contract (lib/models/contract.ts, ADR 0048) takes
|
|
17
|
+
* tools in its own shape instead: contractToolDeclaration() builds a
|
|
18
|
+
* ToolDeclaration from an own Tool (lib/tools/tool.ts), an ADK tool or a
|
|
19
|
+
* defineTool contract, converting Gemini's dialect once, where the tool
|
|
20
|
+
* enters, and nativeToolOf() names the server-side tools that declare
|
|
21
|
+
* nothing. The functions above stay as they are for the ADK path's adapters.
|
|
22
|
+
*
|
|
23
|
+
* zodInputJsonSchema() is the one place a zod schema becomes JSON Schema,
|
|
24
|
+
* for every surface: the declaration, the ADK FunctionTool and the MCP
|
|
25
|
+
* tools/list entry all derive from it.
|
|
26
|
+
*/
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
import type { JsonSchema, NativeTool, ToolDeclaration } from './contract.ts';
|
|
29
|
+
/**
|
|
30
|
+
* Standard JSON Schema for what a caller may send a zod schema: its input
|
|
31
|
+
* side (`io: 'input'`), so a field with a default is optional to the model,
|
|
32
|
+
* as it is to the schema. `$schema` is dropped: it is metadata about the
|
|
33
|
+
* document, noise on the wire.
|
|
15
34
|
*/
|
|
35
|
+
export declare function zodInputJsonSchema(schema: z.ZodType): Record<string, unknown>;
|
|
16
36
|
/**
|
|
17
37
|
* Deep-clones a Gemini/ADK-style JSON schema, lowercasing every `type` value
|
|
18
38
|
* ('OBJECT' → 'object', ['STRING','NULL'] → ['string','null']) while leaving
|
|
@@ -49,3 +69,81 @@ export declare function toolDeclarationFor(tool: unknown): {
|
|
|
49
69
|
* names the harness expects rather than improvised ones.
|
|
50
70
|
*/
|
|
51
71
|
export declare function toStrictJsonSchema(schema: unknown): Record<string, unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* A deep copy of `schema` with `visit` applied to every schema node of the
|
|
74
|
+
* copy, children first, following only the keywords that hold schemas (a
|
|
75
|
+
* property named `default` or `additionalProperties` is a property, not the
|
|
76
|
+
* keyword). Never mutates `schema`. Undefined for a schema that is not an
|
|
77
|
+
* object.
|
|
78
|
+
*/
|
|
79
|
+
export declare function mapSchemaNodes(schema: unknown, visit: (node: Record<string, unknown>) => void): Record<string, unknown> | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* A copy of `schema` as lowercase JSON Schema, the contract's one dialect
|
|
82
|
+
* (`JsonSchema`), from either dialect. With `strict`, every object node
|
|
83
|
+
* that has properties, at any depth (inside `items`, `anyOf`, `$defs`, …),
|
|
84
|
+
* lists all of them as required and sets `additionalProperties: false`: the
|
|
85
|
+
* form OpenAI's and Anthropic's strict modes demand. An object without
|
|
86
|
+
* properties (a map) is left open, so a strict provider refuses it instead
|
|
87
|
+
* of receiving a field the model can never fill. Never mutates `schema`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function toContractJsonSchema(schema: unknown, options?: {
|
|
90
|
+
strict?: boolean;
|
|
91
|
+
}): JsonSchema;
|
|
92
|
+
/**
|
|
93
|
+
* The parameters a zod schema declares, in the contract's dialect: its
|
|
94
|
+
* input side (zodInputJsonSchema) without the keywords the ADK path cannot
|
|
95
|
+
* carry (dropZodOnlyKeywords). defineTool's declaration() and
|
|
96
|
+
* contractToolDeclaration() both build from here, so a contract declares the
|
|
97
|
+
* same parameters on either runtime.
|
|
98
|
+
*/
|
|
99
|
+
export declare function zodToolParameters(schema: z.ZodType, options?: {
|
|
100
|
+
strict?: boolean;
|
|
101
|
+
}): JsonSchema;
|
|
102
|
+
/**
|
|
103
|
+
* The declaration a model receives for one client-side tool, in the
|
|
104
|
+
* contract's shape (ToolDeclaration, lib/models/contract.ts).
|
|
105
|
+
*
|
|
106
|
+
* - An own Tool (lib/tools/tool.ts), defineTool's among them: its own
|
|
107
|
+
* `declaration()`.
|
|
108
|
+
* - A plain defineTool-shaped contract (name, description, zod schema,
|
|
109
|
+
* execute): built directly from its zod schema (zodToolParameters), never
|
|
110
|
+
* through Gemini's uppercase dialect. The input side is declared, so a
|
|
111
|
+
* field with a default is optional; the `default` keyword and a boolean
|
|
112
|
+
* `additionalProperties` are left out, as the ADK path leaves them out,
|
|
113
|
+
* and a record's value schema is kept, as the ADK path keeps it. The
|
|
114
|
+
* contract and the FunctionTool that toFunctionTool() makes of it declare
|
|
115
|
+
* the same parameters.
|
|
116
|
+
* - An ADK tool (FunctionTool, AgentTool, the memory tools, MCP tools): read
|
|
117
|
+
* from its own `_getDeclaration()` (ADR 0019), `parameters` or else
|
|
118
|
+
* `parametersJsonSchema`, and converted from Gemini's dialect once, here.
|
|
119
|
+
* - A plain object with `name` and `parameters` (tests, hand-built tools).
|
|
120
|
+
*
|
|
121
|
+
* With `strict`, the parameters take the strict form (toContractJsonSchema)
|
|
122
|
+
* and the declaration carries `strict: true`. Returns undefined for a tool
|
|
123
|
+
* that declares nothing (a server-side tool: see nativeToolOf, or an
|
|
124
|
+
* InstructionTool such as `preload_memory`, which only writes into the
|
|
125
|
+
* instruction, as itself or as its ADK tool) and for one with no name.
|
|
126
|
+
*/
|
|
127
|
+
export declare function contractToolDeclaration(tool: unknown, options?: {
|
|
128
|
+
strict?: boolean;
|
|
129
|
+
}): ToolDeclaration | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* The NativeTool a tool object stands for, or undefined for any other tool.
|
|
132
|
+
* Recognised by marker, never by class or by shape, and every marker lives
|
|
133
|
+
* in the global symbol registry, so a second copy of a module (or of ADK)
|
|
134
|
+
* still matches (ADR 0062):
|
|
135
|
+
* - the engine's own marker (lib/tools/tool.ts, NATIVE_TOOL): a
|
|
136
|
+
* NativeToolMarker (lib/tools/nativeTools.ts) and the ADK sentinel made
|
|
137
|
+
* from it (lib/tools/webSearchTool.ts and its siblings);
|
|
138
|
+
* - Gemini code execution: ADK's built-in code executor, by ADK's marker
|
|
139
|
+
* (the agent's `codeExecutor`, from `code_execution: gemini`);
|
|
140
|
+
* - ADK's own GOOGLE_SEARCH and URL_CONTEXT, by ADK's marker for a tool
|
|
141
|
+
* the model runs, and their name.
|
|
142
|
+
* A client-side tool registered under one of those names carries no marker,
|
|
143
|
+
* so it stays a client-side tool, as does a tool that merely declares
|
|
144
|
+
* nothing. ADK's other in-model tools (Vertex AI Search, enterprise web
|
|
145
|
+
* search, Maps grounding, RAG retrieval) have no NativeTool: they are
|
|
146
|
+
* undefined here and declare nothing, so a caller building a request
|
|
147
|
+
* reports them as dropped.
|
|
148
|
+
*/
|
|
149
|
+
export declare function nativeToolOf(tool: unknown): NativeTool | undefined;
|
|
@@ -12,7 +12,38 @@
|
|
|
12
12
|
* Tools are declared once (YAML / MCP discovery) in the Gemini dialect, so
|
|
13
13
|
* every non-Gemini adapter runs its tool schemas through this function at
|
|
14
14
|
* request-build time. See DOCUMENTATION.md §7.1.
|
|
15
|
+
*
|
|
16
|
+
* The engine's own model contract (lib/models/contract.ts, ADR 0048) takes
|
|
17
|
+
* tools in its own shape instead: contractToolDeclaration() builds a
|
|
18
|
+
* ToolDeclaration from an own Tool (lib/tools/tool.ts), an ADK tool or a
|
|
19
|
+
* defineTool contract, converting Gemini's dialect once, where the tool
|
|
20
|
+
* enters, and nativeToolOf() names the server-side tools that declare
|
|
21
|
+
* nothing. The functions above stay as they are for the ADK path's adapters.
|
|
22
|
+
*
|
|
23
|
+
* zodInputJsonSchema() is the one place a zod schema becomes JSON Schema,
|
|
24
|
+
* for every surface: the declaration, the ADK FunctionTool and the MCP
|
|
25
|
+
* tools/list entry all derive from it.
|
|
15
26
|
*/
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
import { isInstructionTool, isTool, nativeToolMarkerOf } from '../tools/tool.js';
|
|
29
|
+
/**
|
|
30
|
+
* Standard JSON Schema for what a caller may send a zod schema: its input
|
|
31
|
+
* side (`io: 'input'`), so a field with a default is optional to the model,
|
|
32
|
+
* as it is to the schema. `$schema` is dropped: it is metadata about the
|
|
33
|
+
* document, noise on the wire.
|
|
34
|
+
*/
|
|
35
|
+
export function zodInputJsonSchema(schema) {
|
|
36
|
+
const { $schema: _drop, ...json } = z.toJSONSchema(schema, { io: 'input' });
|
|
37
|
+
// A record's `propertyNames: { type: 'string' }` says only that its keys
|
|
38
|
+
// are strings, which JSON keys always are. Gemini refuses the keyword
|
|
39
|
+
// with a 400 (live, 2026-10-08), so it is left out here, the one source
|
|
40
|
+
// every path reads, and the paths keep declaring the same parameters.
|
|
41
|
+
return mapSchemaNodes(json, (node) => {
|
|
42
|
+
const names = node.propertyNames;
|
|
43
|
+
if (isPlainObject(names) && Object.keys(names).length === 1 && names.type === 'string')
|
|
44
|
+
delete node.propertyNames;
|
|
45
|
+
}) ?? json;
|
|
46
|
+
}
|
|
16
47
|
/**
|
|
17
48
|
* Deep-clones a Gemini/ADK-style JSON schema, lowercasing every `type` value
|
|
18
49
|
* ('OBJECT' → 'object', ['STRING','NULL'] → ['string','null']) while leaving
|
|
@@ -72,7 +103,7 @@ function normalizeNode(node) {
|
|
|
72
103
|
* search sentinels) and for tools with no name.
|
|
73
104
|
*/
|
|
74
105
|
export function toolDeclarationFor(tool) {
|
|
75
|
-
if (!tool || typeof tool !== 'object')
|
|
106
|
+
if (!tool || typeof tool !== 'object' || nativeToolMarkerOf(tool))
|
|
76
107
|
return undefined;
|
|
77
108
|
const t = tool;
|
|
78
109
|
let decl;
|
|
@@ -133,3 +164,292 @@ function strictNode(node) {
|
|
|
133
164
|
function isPlainObject(v) {
|
|
134
165
|
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
135
166
|
}
|
|
167
|
+
// ── Tool declarations in the contract's shape (ADR 0048) ─────────────────────
|
|
168
|
+
/** Keywords whose value is one schema (`items` may also be a draft-4 tuple of them). */
|
|
169
|
+
const SUBSCHEMA_KEYWORDS = new Set([
|
|
170
|
+
'items', 'additionalItems', 'additionalProperties', 'unevaluatedItems', 'unevaluatedProperties',
|
|
171
|
+
'contains', 'propertyNames', 'not', 'if', 'then', 'else',
|
|
172
|
+
]);
|
|
173
|
+
/** Keywords whose value is a list of schemas. */
|
|
174
|
+
const SUBSCHEMA_LIST_KEYWORDS = new Set(['anyOf', 'oneOf', 'allOf', 'prefixItems']);
|
|
175
|
+
/** Keywords whose value maps names to schemas. */
|
|
176
|
+
const SUBSCHEMA_MAP_KEYWORDS = new Set(['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']);
|
|
177
|
+
/** Bounds Gemini's Schema spells as int64 strings (`'1'`); JSON Schema wants integers. */
|
|
178
|
+
const INTEGER_KEYWORDS = ['minLength', 'maxLength', 'minItems', 'maxItems', 'minProperties', 'maxProperties'];
|
|
179
|
+
/**
|
|
180
|
+
* What a declaration built from a zod schema leaves out, as the ADK path
|
|
181
|
+
* (toGeminiSchema) leaves it out, so a contract declares the same
|
|
182
|
+
* parameters whichever path resolves it: the `default` keyword (zod applies
|
|
183
|
+
* defaults at parse time, and the field is already optional), and an
|
|
184
|
+
* `additionalProperties` that is only `true` or `false`. An
|
|
185
|
+
* `additionalProperties` holding a schema, a record's value schema, is kept.
|
|
186
|
+
*/
|
|
187
|
+
function dropZodOnlyKeywords(node) {
|
|
188
|
+
delete node.default;
|
|
189
|
+
if (typeof node.additionalProperties === 'boolean')
|
|
190
|
+
delete node.additionalProperties;
|
|
191
|
+
}
|
|
192
|
+
/** ADK's built-ins that a model runs itself, by the NativeTool their name is. */
|
|
193
|
+
const ADK_IN_MODEL_NATIVE_TOOLS = ['google_search', 'url_context'];
|
|
194
|
+
/**
|
|
195
|
+
* ADK's markers, read through the global symbol registry so this module
|
|
196
|
+
* imports nothing from ADK: a tool the model runs itself (GOOGLE_SEARCH,
|
|
197
|
+
* ADK's URL_CONTEXT) and Gemini's built-in code executor.
|
|
198
|
+
*/
|
|
199
|
+
const ADK_IN_MODEL_TOOL = Symbol.for('google.adk.inModelTool');
|
|
200
|
+
const ADK_BUILT_IN_CODE_EXECUTOR = Symbol.for('google.adk.builtInCodeExecutor');
|
|
201
|
+
/** A deep copy of a JSON value, so a walk can change it without touching a tool's own schema. */
|
|
202
|
+
function cloneJson(value) {
|
|
203
|
+
if (Array.isArray(value))
|
|
204
|
+
return value.map(cloneJson);
|
|
205
|
+
if (isPlainObject(value))
|
|
206
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, cloneJson(v)]));
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Visits every schema node in place, children first. It follows only the
|
|
211
|
+
* keywords that hold schemas, so a property named `type`, `enum` or
|
|
212
|
+
* `default` is a property like any other, and data (`enum`, `const`,
|
|
213
|
+
* `default`, `examples`) is never read as a schema.
|
|
214
|
+
*/
|
|
215
|
+
function walkSchema(node, visit) {
|
|
216
|
+
if (!isPlainObject(node))
|
|
217
|
+
return; // boolean schemas, malformed nodes
|
|
218
|
+
for (const [key, value] of Object.entries(node)) {
|
|
219
|
+
if (SUBSCHEMA_KEYWORDS.has(key)) {
|
|
220
|
+
if (Array.isArray(value))
|
|
221
|
+
value.forEach((v) => walkSchema(v, visit));
|
|
222
|
+
else
|
|
223
|
+
walkSchema(value, visit);
|
|
224
|
+
}
|
|
225
|
+
else if (SUBSCHEMA_LIST_KEYWORDS.has(key) && Array.isArray(value)) {
|
|
226
|
+
value.forEach((v) => walkSchema(v, visit));
|
|
227
|
+
}
|
|
228
|
+
else if (SUBSCHEMA_MAP_KEYWORDS.has(key) && isPlainObject(value)) {
|
|
229
|
+
Object.values(value).forEach((v) => walkSchema(v, visit));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
visit(node);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* A deep copy of `schema` with `visit` applied to every schema node of the
|
|
236
|
+
* copy, children first, following only the keywords that hold schemas (a
|
|
237
|
+
* property named `default` or `additionalProperties` is a property, not the
|
|
238
|
+
* keyword). Never mutates `schema`. Undefined for a schema that is not an
|
|
239
|
+
* object.
|
|
240
|
+
*/
|
|
241
|
+
export function mapSchemaNodes(schema, visit) {
|
|
242
|
+
if (!isPlainObject(schema))
|
|
243
|
+
return undefined;
|
|
244
|
+
const root = cloneJson(schema);
|
|
245
|
+
walkSchema(root, visit);
|
|
246
|
+
return root;
|
|
247
|
+
}
|
|
248
|
+
/** Keywords that say what a node is about, not what it admits; they stay put when a nullable node is wrapped. */
|
|
249
|
+
const ANNOTATION_KEYWORDS = new Set(['title', 'description', 'default', 'examples', '$comment', 'deprecated', 'readOnly', 'writeOnly', '$defs', 'definitions']);
|
|
250
|
+
/** Keywords beside which a type that admits null still refuses it. */
|
|
251
|
+
const NULL_REFUSING_KEYWORDS = ['allOf', 'anyOf', 'oneOf', '$ref', 'not', 'const'];
|
|
252
|
+
/**
|
|
253
|
+
* One node from Gemini's dialect into lowercase JSON Schema: types
|
|
254
|
+
* lowercased, int64 bounds as integers, the strict form when asked, and
|
|
255
|
+
* OpenAPI's `nullable: true` turned into a schema that admits null. Strict
|
|
256
|
+
* comes before null, because admitting null may move the node's properties
|
|
257
|
+
* into an anyOf branch the walk has already passed. A node already in the
|
|
258
|
+
* standard dialect passes through unchanged.
|
|
259
|
+
*/
|
|
260
|
+
function toContractNode(node, fromZod, strict) {
|
|
261
|
+
delete node.$schema;
|
|
262
|
+
if (fromZod)
|
|
263
|
+
dropZodOnlyKeywords(node);
|
|
264
|
+
if (typeof node.type === 'string')
|
|
265
|
+
node.type = node.type.toLowerCase();
|
|
266
|
+
else if (Array.isArray(node.type))
|
|
267
|
+
node.type = node.type.map((t) => (typeof t === 'string' ? t.toLowerCase() : t));
|
|
268
|
+
for (const key of INTEGER_KEYWORDS) {
|
|
269
|
+
const bound = node[key];
|
|
270
|
+
if (typeof bound === 'string' && /^\d+$/.test(bound))
|
|
271
|
+
node[key] = Number(bound);
|
|
272
|
+
}
|
|
273
|
+
if (strict)
|
|
274
|
+
toStrictNode(node);
|
|
275
|
+
if (typeof node.nullable !== 'boolean')
|
|
276
|
+
return;
|
|
277
|
+
const nullable = node.nullable;
|
|
278
|
+
delete node.nullable;
|
|
279
|
+
if (nullable)
|
|
280
|
+
admitNull(node);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Make `node` admit null. A plain typed node gains `null` in its type (and
|
|
284
|
+
* its enum, which would otherwise refuse it); a bare `anyOf` gains a null
|
|
285
|
+
* branch; anything else ($ref, allOf, oneOf, const…) moves into an anyOf
|
|
286
|
+
* beside `{ type: 'null' }`, its annotations staying on the node.
|
|
287
|
+
*/
|
|
288
|
+
function admitNull(node) {
|
|
289
|
+
const composed = NULL_REFUSING_KEYWORDS.some((k) => k in node);
|
|
290
|
+
if (node.type !== undefined && !composed) {
|
|
291
|
+
const types = Array.isArray(node.type) ? node.type : [node.type];
|
|
292
|
+
if (!types.includes('null'))
|
|
293
|
+
node.type = typeof node.type === 'string' ? [node.type, 'null'] : [...types, 'null'];
|
|
294
|
+
if (Array.isArray(node.enum) && !node.enum.includes(null))
|
|
295
|
+
node.enum = [...node.enum, null];
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const constraints = Object.keys(node).filter((k) => !ANNOTATION_KEYWORDS.has(k));
|
|
299
|
+
if (constraints.length === 0)
|
|
300
|
+
return; // an unconstrained schema admits null already
|
|
301
|
+
if (constraints.length === 1 && constraints[0] === 'anyOf' && Array.isArray(node.anyOf)) {
|
|
302
|
+
if (!node.anyOf.some((s) => isPlainObject(s) && s.type === 'null'))
|
|
303
|
+
node.anyOf = [...node.anyOf, { type: 'null' }];
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// fromEntries defines each key, so an own `__proto__` from an untrusted
|
|
307
|
+
// (MCP) schema stays a key instead of setting the new object's prototype.
|
|
308
|
+
const inner = Object.fromEntries(constraints.map((key) => [key, node[key]]));
|
|
309
|
+
for (const key of constraints)
|
|
310
|
+
delete node[key];
|
|
311
|
+
node.anyOf = [inner, { type: 'null' }];
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The strict form of one node: an object with properties lists them all as
|
|
315
|
+
* required and allows no others. An optional property becomes required as
|
|
316
|
+
* it is, not widened to null: a contract's zod schema would refuse the null.
|
|
317
|
+
*/
|
|
318
|
+
function toStrictNode(node) {
|
|
319
|
+
if (!isPlainObject(node.properties))
|
|
320
|
+
return;
|
|
321
|
+
node.required = Object.keys(node.properties);
|
|
322
|
+
node.additionalProperties = false;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* A copy of `schema` as lowercase JSON Schema, the contract's one dialect
|
|
326
|
+
* (`JsonSchema`), from either dialect. With `strict`, every object node
|
|
327
|
+
* that has properties, at any depth (inside `items`, `anyOf`, `$defs`, …),
|
|
328
|
+
* lists all of them as required and sets `additionalProperties: false`: the
|
|
329
|
+
* form OpenAI's and Anthropic's strict modes demand. An object without
|
|
330
|
+
* properties (a map) is left open, so a strict provider refuses it instead
|
|
331
|
+
* of receiving a field the model can never fill. Never mutates `schema`.
|
|
332
|
+
*/
|
|
333
|
+
export function toContractJsonSchema(schema, options = {}) {
|
|
334
|
+
return contractSchema(schema, false, options.strict === true);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* The parameters a zod schema declares, in the contract's dialect: its
|
|
338
|
+
* input side (zodInputJsonSchema) without the keywords the ADK path cannot
|
|
339
|
+
* carry (dropZodOnlyKeywords). defineTool's declaration() and
|
|
340
|
+
* contractToolDeclaration() both build from here, so a contract declares the
|
|
341
|
+
* same parameters on either runtime.
|
|
342
|
+
*/
|
|
343
|
+
export function zodToolParameters(schema, options = {}) {
|
|
344
|
+
return contractSchema(zodInputJsonSchema(schema), true, options.strict === true);
|
|
345
|
+
}
|
|
346
|
+
function contractSchema(schema, fromZod, strict) {
|
|
347
|
+
return mapSchemaNodes(schema, (node) => toContractNode(node, fromZod, strict)) ?? { type: 'object', properties: {} };
|
|
348
|
+
}
|
|
349
|
+
/** A defineTool contract (lib/tools/toolContract.ts), told apart from an ADK tool, which has runAsync. */
|
|
350
|
+
function isToolContract(tool) {
|
|
351
|
+
const schema = tool.schema;
|
|
352
|
+
return !!schema && typeof schema.safeParse === 'function' && typeof tool.execute === 'function' && !('runAsync' in tool);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* The declaration a model receives for one client-side tool, in the
|
|
356
|
+
* contract's shape (ToolDeclaration, lib/models/contract.ts).
|
|
357
|
+
*
|
|
358
|
+
* - An own Tool (lib/tools/tool.ts), defineTool's among them: its own
|
|
359
|
+
* `declaration()`.
|
|
360
|
+
* - A plain defineTool-shaped contract (name, description, zod schema,
|
|
361
|
+
* execute): built directly from its zod schema (zodToolParameters), never
|
|
362
|
+
* through Gemini's uppercase dialect. The input side is declared, so a
|
|
363
|
+
* field with a default is optional; the `default` keyword and a boolean
|
|
364
|
+
* `additionalProperties` are left out, as the ADK path leaves them out,
|
|
365
|
+
* and a record's value schema is kept, as the ADK path keeps it. The
|
|
366
|
+
* contract and the FunctionTool that toFunctionTool() makes of it declare
|
|
367
|
+
* the same parameters.
|
|
368
|
+
* - An ADK tool (FunctionTool, AgentTool, the memory tools, MCP tools): read
|
|
369
|
+
* from its own `_getDeclaration()` (ADR 0019), `parameters` or else
|
|
370
|
+
* `parametersJsonSchema`, and converted from Gemini's dialect once, here.
|
|
371
|
+
* - A plain object with `name` and `parameters` (tests, hand-built tools).
|
|
372
|
+
*
|
|
373
|
+
* With `strict`, the parameters take the strict form (toContractJsonSchema)
|
|
374
|
+
* and the declaration carries `strict: true`. Returns undefined for a tool
|
|
375
|
+
* that declares nothing (a server-side tool: see nativeToolOf, or an
|
|
376
|
+
* InstructionTool such as `preload_memory`, which only writes into the
|
|
377
|
+
* instruction, as itself or as its ADK tool) and for one with no name.
|
|
378
|
+
*/
|
|
379
|
+
export function contractToolDeclaration(tool, options = {}) {
|
|
380
|
+
if (!isPlainObject(tool) || isInstructionTool(tool) || nativeToolMarkerOf(tool))
|
|
381
|
+
return undefined;
|
|
382
|
+
const strict = options.strict === true;
|
|
383
|
+
let name;
|
|
384
|
+
let description;
|
|
385
|
+
let parameters;
|
|
386
|
+
if (isTool(tool)) {
|
|
387
|
+
const decl = tool.declaration();
|
|
388
|
+
name = decl.name;
|
|
389
|
+
description = decl.description;
|
|
390
|
+
parameters = contractSchema(decl.parameters, false, strict);
|
|
391
|
+
}
|
|
392
|
+
else if (isToolContract(tool)) {
|
|
393
|
+
name = tool.name;
|
|
394
|
+
description = tool.description;
|
|
395
|
+
parameters = zodToolParameters(tool.schema, { strict });
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
let decl;
|
|
399
|
+
if (typeof tool._getDeclaration === 'function') {
|
|
400
|
+
try {
|
|
401
|
+
decl = tool._getDeclaration() ?? undefined;
|
|
402
|
+
}
|
|
403
|
+
catch {
|
|
404
|
+
decl = undefined;
|
|
405
|
+
}
|
|
406
|
+
// A tool that implements _getDeclaration and returns nothing declares nothing.
|
|
407
|
+
if (!decl)
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
name = decl ? decl.name ?? tool.name : tool.name;
|
|
411
|
+
description = decl ? decl.description ?? tool.description : tool.description;
|
|
412
|
+
const declared = decl ? decl.parameters ?? decl.parametersJsonSchema : tool.parameters;
|
|
413
|
+
parameters = contractSchema(declared, false, strict);
|
|
414
|
+
}
|
|
415
|
+
if (!name || typeof name !== 'string')
|
|
416
|
+
return undefined;
|
|
417
|
+
return {
|
|
418
|
+
name,
|
|
419
|
+
description: typeof description === 'string' ? description : '',
|
|
420
|
+
parameters,
|
|
421
|
+
...(strict ? { strict: true } : {}),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The NativeTool a tool object stands for, or undefined for any other tool.
|
|
426
|
+
* Recognised by marker, never by class or by shape, and every marker lives
|
|
427
|
+
* in the global symbol registry, so a second copy of a module (or of ADK)
|
|
428
|
+
* still matches (ADR 0062):
|
|
429
|
+
* - the engine's own marker (lib/tools/tool.ts, NATIVE_TOOL): a
|
|
430
|
+
* NativeToolMarker (lib/tools/nativeTools.ts) and the ADK sentinel made
|
|
431
|
+
* from it (lib/tools/webSearchTool.ts and its siblings);
|
|
432
|
+
* - Gemini code execution: ADK's built-in code executor, by ADK's marker
|
|
433
|
+
* (the agent's `codeExecutor`, from `code_execution: gemini`);
|
|
434
|
+
* - ADK's own GOOGLE_SEARCH and URL_CONTEXT, by ADK's marker for a tool
|
|
435
|
+
* the model runs, and their name.
|
|
436
|
+
* A client-side tool registered under one of those names carries no marker,
|
|
437
|
+
* so it stays a client-side tool, as does a tool that merely declares
|
|
438
|
+
* nothing. ADK's other in-model tools (Vertex AI Search, enterprise web
|
|
439
|
+
* search, Maps grounding, RAG retrieval) have no NativeTool: they are
|
|
440
|
+
* undefined here and declare nothing, so a caller building a request
|
|
441
|
+
* reports them as dropped.
|
|
442
|
+
*/
|
|
443
|
+
export function nativeToolOf(tool) {
|
|
444
|
+
if (!tool || typeof tool !== 'object')
|
|
445
|
+
return undefined;
|
|
446
|
+
const own = nativeToolMarkerOf(tool);
|
|
447
|
+
if (own)
|
|
448
|
+
return own;
|
|
449
|
+
const t = tool;
|
|
450
|
+
if (t[ADK_BUILT_IN_CODE_EXECUTOR] === true)
|
|
451
|
+
return 'code_execution';
|
|
452
|
+
if (t[ADK_IN_MODEL_TOOL] !== true)
|
|
453
|
+
return undefined;
|
|
454
|
+
return ADK_IN_MODEL_NATIVE_TOOLS.find((n) => n === t.name);
|
|
455
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/tracedGemini.ts — ADK's built-in Gemini, wrapped so Gemini calls
|
|
3
|
+
* emit the same per-request llm.request spans (tokens, latency) as every other
|
|
4
|
+
* provider, and get the shared transient-failure retries (lib/models/retry.ts).
|
|
5
|
+
*
|
|
6
|
+
* WHY its own module: two modules use it. lib/models/registry.ts registers it
|
|
7
|
+
* for every Gemini id on the ADK path and builds it in resolveModel, and the
|
|
8
|
+
* temporary AdkGeminiAdapter (lib/models/adkGeminiAdapter.ts) runs its calls
|
|
9
|
+
* through generateWithRetries. registry.ts also builds that adapter in
|
|
10
|
+
* resolveAdapter, so the class living in registry.ts would be an ESM import
|
|
11
|
+
* cycle. registry.ts re-exports it, so `TracedGemini` imports from there keep
|
|
12
|
+
* working.
|
|
13
|
+
*/
|
|
14
|
+
import { Gemini } from '@google/adk';
|
|
15
|
+
import type { GeminiParams, LlmRequest, LlmResponse } from '@google/adk';
|
|
16
|
+
import type { ProviderEndpoint } from './endpoints.ts';
|
|
17
|
+
export declare class TracedGemini extends Gemini {
|
|
18
|
+
#private;
|
|
19
|
+
static readonly supportedModels: Array<string | RegExp>;
|
|
20
|
+
/**
|
|
21
|
+
* The Gemini platform (ADR 0023) applies to every construction, the
|
|
22
|
+
* LLMRegistry's included: on Vertex AI the client authenticates with
|
|
23
|
+
* Google Application Default Credentials for the configured project and
|
|
24
|
+
* location, and an AI Studio key (the environment's or a caller's) is not
|
|
25
|
+
* sent. `endpoint` overrides the environment's (the credentials plug point).
|
|
26
|
+
*/
|
|
27
|
+
constructor(params?: GeminiParams & {
|
|
28
|
+
endpoint?: ProviderEndpoint;
|
|
29
|
+
});
|
|
30
|
+
generateContentAsync(llmRequest: LlmRequest, stream?: boolean, abortSignal?: AbortSignal): AsyncGenerator<LlmResponse, void>;
|
|
31
|
+
/**
|
|
32
|
+
* One call through ADK's Gemini with the shared retries, tagging whatever
|
|
33
|
+
* llm.request span is active (llm.web_search.native for Gemini grounding,
|
|
34
|
+
* llm.retries, llm.http_status). generateContentAsync opens that span
|
|
35
|
+
* around it; AdkGeminiAdapter (lib/models/adkGeminiAdapter.ts) runs it
|
|
36
|
+
* inside the span its own caller opens (ADR 0053). A failed call throws,
|
|
37
|
+
* as ADK's Gemini does. The abort signal is forwarded so a canceled turn stops the
|
|
38
|
+
* request in flight (ADK's Gemini puts it on the genai request config).
|
|
39
|
+
*/
|
|
40
|
+
generateWithRetries(llmRequest: LlmRequest, stream?: boolean, abortSignal?: AbortSignal): AsyncGenerator<LlmResponse, void>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/tracedGemini.ts — ADK's built-in Gemini, wrapped so Gemini calls
|
|
3
|
+
* emit the same per-request llm.request spans (tokens, latency) as every other
|
|
4
|
+
* provider, and get the shared transient-failure retries (lib/models/retry.ts).
|
|
5
|
+
*
|
|
6
|
+
* WHY its own module: two modules use it. lib/models/registry.ts registers it
|
|
7
|
+
* for every Gemini id on the ADK path and builds it in resolveModel, and the
|
|
8
|
+
* temporary AdkGeminiAdapter (lib/models/adkGeminiAdapter.ts) runs its calls
|
|
9
|
+
* through generateWithRetries. registry.ts also builds that adapter in
|
|
10
|
+
* resolveAdapter, so the class living in registry.ts would be an ESM import
|
|
11
|
+
* cycle. registry.ts re-exports it, so `TracedGemini` imports from there keep
|
|
12
|
+
* working.
|
|
13
|
+
*/
|
|
14
|
+
import { Gemini } from '@google/adk';
|
|
15
|
+
import { endpointFromEnv, platformModel } from './endpoints.js';
|
|
16
|
+
import { llmRequestToModelRequest } from './genaiMapping.js';
|
|
17
|
+
import { errorStatus, retryUntilFirstYield } from './retry.js';
|
|
18
|
+
import { setLlmSpanAttribute, traceLlmGeneration } from '../observability/tracer.js';
|
|
19
|
+
import { currentTurnSignal } from '../runtime/turnControl.js';
|
|
20
|
+
export class TracedGemini extends Gemini {
|
|
21
|
+
// CRITICAL: reuse Gemini's exact regex instances. The LLMRegistry dict is
|
|
22
|
+
// keyed by the regex OBJECT — registering the same instances REPLACES the
|
|
23
|
+
// built-in Gemini entries instead of adding shadowed duplicates.
|
|
24
|
+
static supportedModels = Gemini.supportedModels;
|
|
25
|
+
/**
|
|
26
|
+
* The Gemini platform (ADR 0023) applies to every construction, the
|
|
27
|
+
* LLMRegistry's included: on Vertex AI the client authenticates with
|
|
28
|
+
* Google Application Default Credentials for the configured project and
|
|
29
|
+
* location, and an AI Studio key (the environment's or a caller's) is not
|
|
30
|
+
* sent. `endpoint` overrides the environment's (the credentials plug point).
|
|
31
|
+
*/
|
|
32
|
+
constructor(params = {}) {
|
|
33
|
+
const { endpoint: given, ...rest } = params;
|
|
34
|
+
const e = given ?? endpointFromEnv('gemini');
|
|
35
|
+
const model = rest.model ? platformModel(e, rest.model) : rest.model;
|
|
36
|
+
super(e.platform === 'vertex'
|
|
37
|
+
? { ...rest, model, apiKey: e.apiKey, vertexai: true, project: e.project, location: e.location }
|
|
38
|
+
: { ...rest, model, ...(e.apiKey && !rest.apiKey ? { apiKey: e.apiKey } : {}) });
|
|
39
|
+
this.#vertex = e.platform === 'vertex';
|
|
40
|
+
}
|
|
41
|
+
/** True when the client runs on Vertex AI (ADR 0023). */
|
|
42
|
+
#vertex;
|
|
43
|
+
async *generateContentAsync(llmRequest, stream, abortSignal) {
|
|
44
|
+
yield* traceLlmGeneration({
|
|
45
|
+
provider: 'gemini',
|
|
46
|
+
model: this.model,
|
|
47
|
+
request: () => llmRequestToModelRequest(llmRequest, { model: llmRequest.model || this.model, stream }),
|
|
48
|
+
}, this.generateWithRetries(llmRequest, stream, abortSignal ?? currentTurnSignal()));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* One call through ADK's Gemini with the shared retries, tagging whatever
|
|
52
|
+
* llm.request span is active (llm.web_search.native for Gemini grounding,
|
|
53
|
+
* llm.retries, llm.http_status). generateContentAsync opens that span
|
|
54
|
+
* around it; AdkGeminiAdapter (lib/models/adkGeminiAdapter.ts) runs it
|
|
55
|
+
* inside the span its own caller opens (ADR 0053). A failed call throws,
|
|
56
|
+
* as ADK's Gemini does. The abort signal is forwarded so a canceled turn stops the
|
|
57
|
+
* request in flight (ADK's Gemini puts it on the genai request config).
|
|
58
|
+
*/
|
|
59
|
+
async *generateWithRetries(llmRequest, stream, abortSignal) {
|
|
60
|
+
// The compiler asks every agent's config for includeServerSideToolInvocations
|
|
61
|
+
// (lib/compile.ts), which the Gemini API needs beside function
|
|
62
|
+
// declarations. @google/genai refuses it on Vertex AI before any request
|
|
63
|
+
// is sent ("only supported in Gemini Developer API mode"), so on Vertex
|
|
64
|
+
// it is left off, and the call goes out as it did before the flag.
|
|
65
|
+
const toolConfig = llmRequest.config?.toolConfig;
|
|
66
|
+
if (this.#vertex && toolConfig && 'includeServerSideToolInvocations' in toolConfig) {
|
|
67
|
+
const { includeServerSideToolInvocations: _vertexRefuses, ...kept } = toolConfig;
|
|
68
|
+
llmRequest.config = { ...llmRequest.config, toolConfig: kept };
|
|
69
|
+
}
|
|
70
|
+
const hasGrounding = (llmRequest.config?.tools ?? []).some((t) => t && (t.googleSearch || t.googleSearchRetrieval));
|
|
71
|
+
if (hasGrounding)
|
|
72
|
+
setLlmSpanAttribute('llm.web_search.native', true);
|
|
73
|
+
// Retries live here, not in genai's own httpOptions.retryOptions: that
|
|
74
|
+
// hook (p-retry 4, client-level only) ignores the abort signal — it
|
|
75
|
+
// sleeps and re-sends after a canceled turn — honours no Retry-After,
|
|
76
|
+
// does not retry Node's "fetch failed" resets, and replaces a 4xx's
|
|
77
|
+
// error body (e.g. "API key not valid") with a bare statusText. Wrapping
|
|
78
|
+
// the call keeps genai's ApiError intact and applies the same policy as
|
|
79
|
+
// every other adapter. A retry is only made while nothing has been
|
|
80
|
+
// yielded, so a stream that fails mid-reply is surfaced, not replayed.
|
|
81
|
+
// ADK's request preprocessing is idempotent, so re-sending the same
|
|
82
|
+
// llmRequest is safe. The registry class, resolveModel()'s per-request
|
|
83
|
+
// instances and AdkGeminiAdapter's are all TracedGemini, so all get this.
|
|
84
|
+
try {
|
|
85
|
+
yield* retryUntilFirstYield(() => super.generateContentAsync(llmRequest, stream, abortSignal), {
|
|
86
|
+
signal: abortSignal,
|
|
87
|
+
onRetry: ({ retries }) => setLlmSpanAttribute('llm.retries', retries),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
// genai's ApiError carries the status; put it where the ledger reads.
|
|
92
|
+
const status = errorStatus(err);
|
|
93
|
+
if (status !== undefined)
|
|
94
|
+
setLlmSpanAttribute('llm.http_status', status);
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/urls.ts — small URL helpers shared by the adapters, the
|
|
3
|
+
* gateway, the endpoints module and the embeddings provider.
|
|
4
|
+
*
|
|
5
|
+
* WHY: `url.replace(/\/+$/, '')` backtracks quadratically on a long run of
|
|
6
|
+
* slashes followed by another character (CodeQL js/polynomial-redos), and
|
|
7
|
+
* a base URL can come from an environment variable or a caller's option.
|
|
8
|
+
* This scans from the end once.
|
|
9
|
+
*/
|
|
10
|
+
/** `url` without its trailing slashes, in one pass from the end. */
|
|
11
|
+
export declare function trimTrailingSlashes(url: string): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/models/urls.ts — small URL helpers shared by the adapters, the
|
|
3
|
+
* gateway, the endpoints module and the embeddings provider.
|
|
4
|
+
*
|
|
5
|
+
* WHY: `url.replace(/\/+$/, '')` backtracks quadratically on a long run of
|
|
6
|
+
* slashes followed by another character (CodeQL js/polynomial-redos), and
|
|
7
|
+
* a base URL can come from an environment variable or a caller's option.
|
|
8
|
+
* This scans from the end once.
|
|
9
|
+
*/
|
|
10
|
+
/** `url` without its trailing slashes, in one pass from the end. */
|
|
11
|
+
export function trimTrailingSlashes(url) {
|
|
12
|
+
let end = url.length;
|
|
13
|
+
while (end > 0 && url.charCodeAt(end - 1) === 47 /* '/' */)
|
|
14
|
+
end--;
|
|
15
|
+
return url.slice(0, end);
|
|
16
|
+
}
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/net/redirects.ts — follow HTTP redirects one hop at a time, each hop
|
|
3
|
+
* held to a policy, for code that makes its own requests.
|
|
4
|
+
*
|
|
5
|
+
* WHY: fetch follows redirects by default, so a guard that vets only the URL
|
|
6
|
+
* a request starts at is bypassed by any server that answers 302: an allowed
|
|
7
|
+
* public API with an open redirect can send a call to 169.254.169.254, and
|
|
8
|
+
* fetch forwards every header but Authorization and Cookie (an API key in a
|
|
9
|
+
* custom header crosses origins). web_extract follows redirects manually for
|
|
10
|
+
* this reason, and so do the OpenAPI tools (lib/tools/openapi/call.ts) and
|
|
11
|
+
* the MCP client (lib/tools/mcpToolFactory.ts), through this module.
|
|
12
|
+
*
|
|
13
|
+
* HOW: `fetchWithRedirectPolicy(input, init, policy, fetch)` sends every
|
|
14
|
+
* request with `redirect: 'manual'`; each hop's URL goes to
|
|
15
|
+
* `policy.hopProblem` before it is fetched, a hop to another origin keeps
|
|
16
|
+
* only content-negotiation headers, and a chain longer than MAX_REDIRECTS is
|
|
17
|
+
* an error. 303, and 301/302 after a POST, continue as a GET without a body,
|
|
18
|
+
* as browsers do. The policy is a parameter of the call: nothing is
|
|
19
|
+
* installed on globalThis.fetch (ADR 0067 retired the context-scoped
|
|
20
|
+
* wrapper ADR 0036 put there while ADK made the OpenAPI requests).
|
|
21
|
+
*/
|
|
1
22
|
export declare const MAX_REDIRECTS = 5;
|
|
2
23
|
export interface RedirectPolicy {
|
|
3
24
|
/** Why the request may not follow a redirect to `url`, or null to follow it. */
|
|
@@ -9,6 +30,4 @@ type Fetch = typeof globalThis.fetch;
|
|
|
9
30
|
* Throws when a hop is refused or the chain is too long.
|
|
10
31
|
*/
|
|
11
32
|
export declare function fetchWithRedirectPolicy(input: string | URL | Request, init: RequestInit | undefined, policy: RedirectPolicy, fetchImpl: Fetch): Promise<Response>;
|
|
12
|
-
/** Run `fn` with every globalThis.fetch it makes following redirects under `policy`. */
|
|
13
|
-
export declare function withRedirectGuard<T>(policy: RedirectPolicy, fn: () => Promise<T>): Promise<T>;
|
|
14
33
|
export {};
|