melchizedek-agents 0.9.6 → 0.16.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 +272 -0
- package/AGENT_SETUP.md +39 -23
- package/CHANGELOG.md +623 -0
- package/DOCUMENTATION.md +429 -109
- package/QUICKSTART.md +92 -18
- package/README.md +45 -5
- package/config/agents/README.md +46 -1
- package/config/agents/examples/ares.yaml +117 -40
- package/config/agents/examples/assistant.yaml +159 -0
- package/config/agents/examples/augustin.yaml +154 -51
- package/config/agents/examples/cartographers.yaml +91 -23
- package/config/agents/examples/claude.yaml +45 -6
- package/config/agents/examples/council.yaml +23 -8
- package/config/agents/examples/critic.yaml +57 -21
- package/config/agents/examples/delegation.yaml +76 -9
- package/config/agents/examples/hierarchical.yaml +55 -8
- package/config/agents/examples/image_production.yaml +54 -16
- package/config/agents/examples/librarian.yaml +70 -14
- package/config/agents/examples/model_zoo.yaml +105 -29
- package/config/agents/examples/patient_advocate.yaml +102 -34
- package/config/agents/examples/research.yaml +250 -0
- package/config/agents/examples/scribe.yaml +242 -0
- package/config/agents/examples/scriptorium.yaml +87 -24
- package/config/agents/examples/style_council.yaml +31 -9
- package/config/agents/examples/syndicate.yaml +29 -10
- package/config/agents/examples/tutor.yaml +10 -3
- package/config/agents/syndicateSchema.yaml +44 -9
- package/config/agents/templates/README.md +58 -0
- package/config/agents/templates/account_memory.yaml +153 -0
- package/config/agents/templates/case_desk.yaml +188 -0
- package/config/agents/templates/conversational.yaml +110 -0
- package/config/agents/templates/draft_review.yaml +130 -0
- package/config/agents/templates/intake_extractor.yaml +152 -0
- package/config/agents/templates/research_brief.yaml +169 -0
- package/config/agents/templates/research_desk.yaml +152 -0
- package/config/agents/templates/review_panel.yaml +188 -0
- package/config/agents/templates/support_triage.yaml +273 -0
- package/config/agents/templates/systems_operator.yaml +157 -0
- package/db/hardening.sql +202 -0
- package/db/memory_v2.sql +77 -0
- package/db/migrations/0001_base.sql +167 -0
- package/db/migrations/0002_erase_scope.sql +172 -0
- package/db/migrations/0003_postgres_storage.sql +58 -0
- package/db/migrations/0004_usage.sql +94 -0
- package/db/migrations/0005_agent_registry.sql +200 -0
- package/db/migrations/0006_task_leases.sql +25 -0
- package/db/migrations/0007_memory_commit.sql +134 -0
- package/db/migrations/0008_memory_retention.sql +37 -0
- package/db/migrations/0009_task_queue.sql +51 -0
- package/db/migrations/0010_erase_complete.sql +180 -0
- package/db/telemetry.sql +401 -0
- package/demo/README.md +77 -0
- package/demo/a2a_demo.mjs +95 -0
- package/dist/lib/a2a/app.d.ts +253 -0
- package/dist/lib/a2a/app.js +751 -0
- package/dist/lib/a2a/executor.d.ts +167 -0
- package/dist/lib/a2a/executor.js +548 -0
- package/dist/lib/a2a/identity.d.ts +122 -0
- package/dist/lib/a2a/identity.js +254 -0
- package/dist/lib/a2a/policy.d.ts +110 -0
- package/dist/lib/a2a/policy.js +178 -0
- package/dist/lib/a2a/redisLimits.d.ts +26 -0
- package/dist/lib/a2a/redisLimits.js +31 -0
- package/dist/lib/a2a/remoteAgent.d.ts +91 -0
- package/dist/lib/a2a/remoteAgent.js +268 -0
- package/dist/lib/a2a/turnLock.d.ts +28 -0
- package/dist/lib/a2a/turnLock.js +76 -0
- package/dist/lib/compile.d.ts +11 -1
- package/dist/lib/compile.js +109 -8
- package/dist/lib/config.d.ts +7 -7
- package/dist/lib/config.js +7 -7
- package/dist/lib/doctor.d.ts +145 -0
- package/dist/lib/doctor.js +517 -0
- package/dist/lib/grounding.d.ts +2 -2
- package/dist/lib/grounding.js +2 -2
- package/dist/lib/guards/index.d.ts +37 -0
- package/dist/lib/guards/index.js +66 -0
- package/dist/lib/guards/science.d.ts +117 -0
- package/dist/lib/guards/science.js +444 -0
- package/dist/lib/index.d.ts +37 -2
- package/dist/lib/index.js +35 -4
- package/dist/lib/loadEnv.d.ts +23 -11
- package/dist/lib/loadEnv.js +77 -29
- package/dist/lib/loadSyndicate.d.ts +97 -5
- package/dist/lib/loadSyndicate.js +176 -76
- package/dist/lib/memory/erase.d.ts +24 -0
- package/dist/lib/memory/erase.js +47 -0
- package/dist/lib/memory/namespace.d.ts +37 -0
- package/dist/lib/memory/namespace.js +74 -0
- package/dist/lib/memory/providers.d.ts +99 -0
- package/dist/lib/memory/providers.js +240 -0
- package/dist/lib/memory/store.d.ts +74 -0
- package/dist/lib/memory/store.js +108 -0
- package/dist/lib/memory/supabaseMemoryService.d.ts +69 -9
- package/dist/lib/memory/supabaseMemoryService.js +239 -125
- package/dist/lib/models/capabilities.d.ts +103 -0
- package/dist/lib/models/capabilities.js +291 -0
- package/dist/lib/models/claudeLlm.d.ts +10 -1
- package/dist/lib/models/claudeLlm.js +57 -27
- package/dist/lib/models/endpoints.d.ts +100 -0
- package/dist/lib/models/endpoints.js +282 -0
- package/dist/lib/models/gateway.d.ts +107 -0
- package/dist/lib/models/gateway.js +157 -0
- package/dist/lib/models/gatewayLlm.d.ts +57 -0
- package/dist/lib/models/gatewayLlm.js +105 -0
- package/dist/lib/models/gptLlm.d.ts +51 -2
- package/dist/lib/models/gptLlm.js +169 -26
- package/dist/lib/models/grokLlm.d.ts +19 -10
- package/dist/lib/models/grokLlm.js +29 -14
- package/dist/lib/models/ollamaLlm.d.ts +26 -0
- package/dist/lib/models/ollamaLlm.js +43 -1
- package/dist/lib/models/openAiCompatibleLlm.d.ts +67 -2
- package/dist/lib/models/openAiCompatibleLlm.js +200 -37
- package/dist/lib/models/providerMap.js +9 -2
- package/dist/lib/models/registry.d.ts +42 -5
- package/dist/lib/models/registry.js +140 -29
- package/dist/lib/models/retry.d.ts +146 -0
- package/dist/lib/models/retry.js +305 -0
- package/dist/lib/models/schemaNormalize.d.ts +20 -0
- package/dist/lib/models/schemaNormalize.js +43 -0
- package/dist/lib/net/addressGuard.d.ts +60 -0
- package/dist/lib/net/addressGuard.js +216 -0
- package/dist/lib/observability/embeddings.d.ts +11 -2
- package/dist/lib/observability/embeddings.js +18 -14
- package/dist/lib/observability/metrics.d.ts +36 -0
- package/dist/lib/observability/metrics.js +86 -0
- package/dist/lib/observability/redact.d.ts +29 -0
- package/dist/lib/observability/redact.js +102 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +2 -0
- package/dist/lib/observability/supabaseSpanExporter.js +22 -6
- package/dist/lib/observability/tracer.d.ts +10 -0
- package/dist/lib/observability/tracer.js +70 -3
- package/dist/lib/persistence/supabaseProvider.d.ts +11 -0
- package/dist/lib/persistence/supabaseProvider.js +32 -6
- package/dist/lib/registry.d.ts +87 -0
- package/dist/lib/registry.js +146 -0
- package/dist/lib/runtime/approvals.d.ts +44 -0
- package/dist/lib/runtime/approvals.js +67 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +233 -0
- package/dist/lib/runtime/syndicateTurn.js +597 -0
- package/dist/lib/runtime/turnControl.d.ts +90 -0
- package/dist/lib/runtime/turnControl.js +131 -0
- package/dist/lib/session/supabaseSessionService.js +1 -1
- package/dist/lib/session/transcript.d.ts +9 -1
- package/dist/lib/session/transcript.js +15 -7
- package/dist/lib/skills.d.ts +50 -0
- package/dist/lib/skills.js +138 -0
- package/dist/lib/storage/postgres/index.d.ts +98 -0
- package/dist/lib/storage/postgres/index.js +146 -0
- package/dist/lib/storage/postgres/memoryStore.d.ts +11 -0
- package/dist/lib/storage/postgres/memoryStore.js +82 -0
- package/dist/lib/storage/postgres/sessionService.d.ts +51 -0
- package/dist/lib/storage/postgres/sessionService.js +225 -0
- package/dist/lib/storage/postgres/taskQueue.d.ts +22 -0
- package/dist/lib/storage/postgres/taskQueue.js +101 -0
- package/dist/lib/storage/postgres/taskStore.d.ts +55 -0
- package/dist/lib/storage/postgres/taskStore.js +168 -0
- package/dist/lib/storage/schema.d.ts +27 -0
- package/dist/lib/storage/schema.js +50 -0
- package/dist/lib/storage/schemaVersion.d.ts +25 -0
- package/dist/lib/storage/schemaVersion.js +46 -0
- package/dist/lib/syndicateSchema.d.ts +275 -0
- package/dist/lib/syndicateSchema.js +534 -0
- package/dist/lib/toolRegistry.d.ts +15 -0
- package/dist/lib/toolRegistry.js +59 -10
- package/dist/lib/tools/collectionsSearchTool.js +1 -1
- package/dist/lib/tools/mcpServe.d.ts +45 -0
- package/dist/lib/tools/mcpServe.js +88 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
- package/dist/lib/tools/mcpToolFactory.js +41 -21
- package/dist/lib/tools/science/corrections.d.ts +42 -0
- package/dist/lib/tools/science/corrections.js +115 -0
- package/dist/lib/tools/science/europepmc.d.ts +51 -0
- package/dist/lib/tools/science/europepmc.js +127 -0
- package/dist/lib/tools/science/http.d.ts +31 -0
- package/dist/lib/tools/science/http.js +104 -0
- package/dist/lib/tools/science/openalex.d.ts +30 -0
- package/dist/lib/tools/science/openalex.js +81 -0
- package/dist/lib/tools/science/trials.d.ts +58 -0
- package/dist/lib/tools/science/trials.js +203 -0
- package/dist/lib/tools/science/types.d.ts +69 -0
- package/dist/lib/tools/science/types.js +48 -0
- package/dist/lib/tools/scienceTools.d.ts +100 -0
- package/dist/lib/tools/scienceTools.js +277 -0
- package/dist/lib/tools/taskTools.d.ts +204 -0
- package/dist/lib/tools/taskTools.js +374 -0
- package/dist/lib/tools/toolContract.d.ts +17 -4
- package/dist/lib/tools/toolContract.js +13 -5
- package/dist/lib/tools/webExtractTool.d.ts +13 -10
- package/dist/lib/tools/webExtractTool.js +23 -55
- package/dist/lib/tools/webSearchTool.d.ts +2 -1
- package/dist/lib/tools/webSearchTool.js +4 -3
- package/dist/lib/tools/wikiTools.d.ts +2 -2
- package/dist/lib/tools/wikiTools.js +12 -12
- package/dist/lib/tools/xApiSearchTool.d.ts +194 -0
- package/dist/lib/tools/xApiSearchTool.js +583 -0
- package/dist/lib/tools/xSearchTool.js +1 -1
- package/dist/lib/wiki/agentRun.js +1 -1
- package/dist/lib/wiki/builder.js +21 -6
- package/dist/lib/wiki/dive.js +2 -2
- package/dist/lib/wiki/entities.d.ts +2 -2
- package/dist/lib/wiki/entities.js +7 -7
- package/dist/lib/wiki/extract.js +5 -1
- package/dist/lib/wiki/fill.js +5 -5
- package/dist/lib/wiki/format.d.ts +7 -5
- package/dist/lib/wiki/format.js +2 -2
- package/dist/lib/wiki/graph.js +2 -2
- package/dist/lib/wiki/lint.js +2 -2
- package/dist/lib/wiki/markdown.d.ts +1 -1
- package/dist/lib/wiki/markdown.js +6 -1
- package/dist/lib/wiki/pipeline.js +2 -2
- package/dist/lib/wiki/vault.js +2 -2
- package/dist/scripts/a2a_server.d.ts +17 -11
- package/dist/scripts/a2a_server.js +405 -1028
- package/dist/scripts/assistant_worker.d.ts +33 -0
- package/dist/scripts/assistant_worker.js +193 -0
- package/dist/scripts/db.d.ts +26 -0
- package/dist/scripts/db.js +179 -0
- package/dist/scripts/doctor.d.ts +23 -0
- package/dist/scripts/doctor.js +90 -0
- package/dist/scripts/init.d.ts +43 -0
- package/dist/scripts/init.js +152 -0
- package/dist/scripts/registry.d.ts +25 -0
- package/dist/scripts/registry.js +182 -0
- package/dist/scripts/skills_install.d.ts +7 -0
- package/dist/scripts/skills_install.js +137 -0
- package/dist/scripts/syndicate_chat.js +159 -276
- package/package.json +90 -16
- package/skills/README.md +74 -0
- package/skills/briefs/README.md +16 -0
- package/skills/briefs/_shared.md +16 -0
- package/skills/briefs/melchizedek-author.md +60 -0
- package/skills/briefs/melchizedek-memory.md +47 -0
- package/skills/briefs/melchizedek-models.md +47 -0
- package/skills/briefs/melchizedek-scribe.md +37 -0
- package/skills/briefs/melchizedek-serve.md +56 -0
- package/skills/briefs/melchizedek.md +69 -0
- package/skills/briefs/skills-readme.md +33 -0
- package/skills/melchizedek/SKILL.md +159 -0
- package/skills/melchizedek-author/SKILL.md +99 -0
- package/skills/melchizedek-author/templates/minimal.yaml +60 -0
- package/skills/melchizedek-memory/SKILL.md +138 -0
- package/skills/melchizedek-models/SKILL.md +137 -0
- package/skills/melchizedek-scribe/SKILL.md +72 -0
- package/skills/melchizedek-scribe/templates/brief.md +27 -0
- package/skills/melchizedek-serve/SKILL.md +119 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/syndicateSchema.ts — the syndicate YAML contract, as a validator.
|
|
3
|
+
*
|
|
4
|
+
* WHY this file exists:
|
|
5
|
+
* A syndicate YAML is the framework's primary authoring surface, and the
|
|
6
|
+
* loader used to type-cast it (`parse(...) as SyndicateYamlConfig`). A typo
|
|
7
|
+
* either crashed deep inside the compiler (`orchestator:` → "cannot read
|
|
8
|
+
* 'model' of undefined") or, worse, was accepted and silently changed
|
|
9
|
+
* behaviour (`memory_system: internl-only` disabled memory in the A2A
|
|
10
|
+
* server). Here the shape the interfaces in lib/loadSyndicate.ts declare is
|
|
11
|
+
* written once as a zod schema, checked at load, and every problem is
|
|
12
|
+
* reported with its key path and, for a misspelt key, the key it was meant
|
|
13
|
+
* to be.
|
|
14
|
+
*
|
|
15
|
+
* The same schema emits config/agents/syndicate.schema.json (npm run
|
|
16
|
+
* schema:gen) so an editor can flag the typo before a run does; a test
|
|
17
|
+
* keeps the committed file equal to the generated one.
|
|
18
|
+
*
|
|
19
|
+
* Strictness is deliberate at the syndicate, agent and dispatch levels — the
|
|
20
|
+
* places a misspelt key silently does nothing — and deliberately ABSENT inside
|
|
21
|
+
* `generateContentConfig` and `outputSchema`, which pass through to provider
|
|
22
|
+
* SDKs whose fields this repo does not own.
|
|
23
|
+
*/
|
|
24
|
+
import { z } from 'zod';
|
|
25
|
+
// ── Leaf rules ───────────────────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* ADK's own rule (validateAgentName in @google/adk base_agent): checked here
|
|
28
|
+
* so the error names the YAML key instead of surfacing from a constructor
|
|
29
|
+
* mid-compile.
|
|
30
|
+
*/
|
|
31
|
+
const AGENT_NAME_RE = /^[\p{ID_Start}$_][\p{ID_Continue}$_-]*$/u;
|
|
32
|
+
const agentName = z
|
|
33
|
+
.string()
|
|
34
|
+
.regex(AGENT_NAME_RE, 'must be a valid identifier (letters, digits, _ and -; not starting with a digit)')
|
|
35
|
+
.refine((n) => n !== 'user', "'user' is reserved by ADK for the end user's input")
|
|
36
|
+
.describe('Unique agent name within the tree. A valid identifier; cannot be "user".');
|
|
37
|
+
export const MEMORY_SYSTEMS = ['internal-only', 'session-only', 'long-term'];
|
|
38
|
+
const thinkingConfig = z
|
|
39
|
+
.looseObject({
|
|
40
|
+
thinkingBudget: z.number().int().optional().describe('Reasoning token budget. 0 = off, -1 = dynamic.'),
|
|
41
|
+
includeThoughts: z.boolean().optional().describe('Stream the thinking trace in the response.'),
|
|
42
|
+
})
|
|
43
|
+
.describe('Gemini thinking controls (generateContentConfig.thinkingConfig).');
|
|
44
|
+
/**
|
|
45
|
+
* Loose on purpose: provider-specific fields (e.g. `toolConfig`) pass through
|
|
46
|
+
* to the SDK. Only the documented fields are typed, so `temperature: hot`
|
|
47
|
+
* still fails while an unlisted knob does not.
|
|
48
|
+
*/
|
|
49
|
+
const generateContentConfig = z
|
|
50
|
+
.looseObject({
|
|
51
|
+
temperature: z.number().optional(),
|
|
52
|
+
topP: z.number().optional(),
|
|
53
|
+
topK: z.number().optional(),
|
|
54
|
+
maxOutputTokens: z.number().int().positive().optional(),
|
|
55
|
+
stopSequences: z.array(z.string()).optional(),
|
|
56
|
+
candidateCount: z.number().int().positive().optional(),
|
|
57
|
+
presencePenalty: z.number().optional(),
|
|
58
|
+
frequencyPenalty: z.number().optional(),
|
|
59
|
+
seed: z.number().int().optional(),
|
|
60
|
+
responseMimeType: z.string().optional(),
|
|
61
|
+
safetySettings: z
|
|
62
|
+
.array(z.looseObject({ category: z.string(), threshold: z.string() }))
|
|
63
|
+
.optional(),
|
|
64
|
+
thinkingConfig: thinkingConfig.optional(),
|
|
65
|
+
})
|
|
66
|
+
.describe('Model generation config (@google/genai GenerateContentConfig). Do not set tools here.');
|
|
67
|
+
// ── Agents ───────────────────────────────────────────────────────────────────
|
|
68
|
+
const agentFields = {
|
|
69
|
+
name: agentName,
|
|
70
|
+
description: z
|
|
71
|
+
.string()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe('One line other agents read to decide when to delegate here.'),
|
|
74
|
+
model: z
|
|
75
|
+
.string()
|
|
76
|
+
.min(1)
|
|
77
|
+
.describe('Model id; its prefix picks the provider (gemini-*, claude-*, gpt-*/o*, grok-*, ollama/*).'),
|
|
78
|
+
instruction: z.string().min(1).describe('System prompt / persona.'),
|
|
79
|
+
globalInstruction: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe('Instruction applied to every agent in the tree; only the root agent\'s value takes effect.'),
|
|
83
|
+
tools: z
|
|
84
|
+
.array(z.string().min(1))
|
|
85
|
+
.optional()
|
|
86
|
+
.describe('Named tools from lib/toolRegistry.ts (e.g. web_search, web_extract, wiki_search).'),
|
|
87
|
+
require_approval: z
|
|
88
|
+
.array(z.string().min(1))
|
|
89
|
+
.optional()
|
|
90
|
+
.describe('Tools from this agent\'s `tools` that run only after a person approves the exact call (ADR 0028): the A2A task ends input-required until the caller answers approve or reject. Allowed on the orchestrator and on plan-dispatch routes.'),
|
|
91
|
+
includeContents: z
|
|
92
|
+
.enum(['default', 'none'])
|
|
93
|
+
.optional()
|
|
94
|
+
.describe('"default" = include conversation history, "none" = stateless.'),
|
|
95
|
+
disallowTransferToParent: z.boolean().optional(),
|
|
96
|
+
disallowTransferToPeers: z.boolean().optional(),
|
|
97
|
+
outputKey: z.string().optional().describe('Session-state key the final reply is saved under.'),
|
|
98
|
+
generateContentConfig: generateContentConfig.optional(),
|
|
99
|
+
outputSchema: z
|
|
100
|
+
.record(z.string(), z.unknown())
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('JSON Schema for structured output. Cannot be combined with AgentTool delegation.'),
|
|
103
|
+
mcp_server_url: z
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe('MCP server (SSE) whose tools are discovered at runtime and merged with `tools`.'),
|
|
107
|
+
orchestration: z
|
|
108
|
+
.strictObject({
|
|
109
|
+
role: z.enum(['primary', 'sub-agent']).optional(),
|
|
110
|
+
delegates: z.array(z.string()).optional(),
|
|
111
|
+
})
|
|
112
|
+
.optional(),
|
|
113
|
+
};
|
|
114
|
+
export const agentSchema = z
|
|
115
|
+
.strictObject(agentFields)
|
|
116
|
+
.describe('The root agent (ADK LlmAgentConfig).');
|
|
117
|
+
/**
|
|
118
|
+
* Model and instruction are optional at the type level because a nested
|
|
119
|
+
* (`yaml_reference`) or remote (`a2a_agent_url`) subagent brings its own; the
|
|
120
|
+
* "an ordinary subagent needs an instruction" rule is checked in
|
|
121
|
+
* crossFieldProblems so it can name the alternative keys in its message.
|
|
122
|
+
*/
|
|
123
|
+
export const subagentSchema = z
|
|
124
|
+
.strictObject({
|
|
125
|
+
...agentFields,
|
|
126
|
+
description: z
|
|
127
|
+
.string()
|
|
128
|
+
.min(1)
|
|
129
|
+
.describe('Required: the orchestrator reads it to decide when to call this subagent.'),
|
|
130
|
+
model: agentFields.model.optional().describe('Model id. Inherits the orchestrator\'s when omitted.'),
|
|
131
|
+
instruction: agentFields.instruction.optional(),
|
|
132
|
+
yaml_reference: z
|
|
133
|
+
.string()
|
|
134
|
+
.min(1)
|
|
135
|
+
.optional()
|
|
136
|
+
.describe('A whole nested syndicate (filename under the agents dir) used as this subagent.'),
|
|
137
|
+
a2a_agent_url: z
|
|
138
|
+
.string()
|
|
139
|
+
.min(1)
|
|
140
|
+
.optional()
|
|
141
|
+
.describe('A remote agent over A2A: the server base URL or its agent-card URL. Needs no model or instruction; credentials come from A2A_AGENT_TOKENS, never YAML.'),
|
|
142
|
+
})
|
|
143
|
+
.describe('A subagent: an inline agent, a nested syndicate (yaml_reference), or a remote A2A agent (a2a_agent_url).');
|
|
144
|
+
// ── Dispatch ─────────────────────────────────────────────────────────────────
|
|
145
|
+
const routeOverride = z.strictObject({
|
|
146
|
+
route: z.string().min(1).describe('Subagent to run on a match.'),
|
|
147
|
+
pattern: z.string().min(1).describe('JS regular expression source tested against the raw message.'),
|
|
148
|
+
flags: z.string().optional().describe('Regex flags. Default "i".'),
|
|
149
|
+
reason: z.string().optional().describe('Shown to the waiting user as the [STATUS] route note.'),
|
|
150
|
+
});
|
|
151
|
+
const dispatchSchema = z
|
|
152
|
+
.strictObject({
|
|
153
|
+
default_route: z
|
|
154
|
+
.string()
|
|
155
|
+
.min(1)
|
|
156
|
+
.describe('Subagent used whenever routing yields no usable answer. Must name a declared subagent.'),
|
|
157
|
+
route_key: z.string().optional().describe('JSON property holding the chosen route. Default "route".'),
|
|
158
|
+
reason_key: z.string().optional().describe('JSON property holding the justification. Default "reason".'),
|
|
159
|
+
route_overrides: z.array(routeOverride).optional(),
|
|
160
|
+
})
|
|
161
|
+
.describe('Opts into PLAN-DISPATCH orchestration (lib/dispatch.ts).');
|
|
162
|
+
// ── Syndicate ────────────────────────────────────────────────────────────────
|
|
163
|
+
export const syndicateSchema = z
|
|
164
|
+
.strictObject({
|
|
165
|
+
syndicate_name: z.string().min(1).describe('Display name for this syndicate.'),
|
|
166
|
+
orchestrator: agentSchema,
|
|
167
|
+
subagents: z
|
|
168
|
+
.array(subagentSchema)
|
|
169
|
+
.optional()
|
|
170
|
+
.describe('The orchestrator\'s team. Omit it (or write `subagents: []`) for a single-agent syndicate.'),
|
|
171
|
+
dispatch: dispatchSchema.optional(),
|
|
172
|
+
variables: z
|
|
173
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
|
|
174
|
+
.optional()
|
|
175
|
+
.describe('Defaults for {{token}} interpolation. {{current_date}} is built in.'),
|
|
176
|
+
memory_system: z
|
|
177
|
+
.enum(MEMORY_SYSTEMS)
|
|
178
|
+
.optional()
|
|
179
|
+
.describe('Persistence and semantic-memory layer.'),
|
|
180
|
+
guards: z
|
|
181
|
+
.array(z.string().min(1))
|
|
182
|
+
.optional()
|
|
183
|
+
.describe('Post-answer guards by name (lib/guards/index.ts).'),
|
|
184
|
+
memory_namespace: z
|
|
185
|
+
.string()
|
|
186
|
+
.regex(/^[A-Za-z0-9._-]{1,96}$/, 'letters, digits, ".", "_" and "-" only (max 96)')
|
|
187
|
+
.optional()
|
|
188
|
+
.describe('Where long-term memory is stored: the syndicate name plus a generated id, written once and never recomputed (ADR 0020). Syndicates that declare the same namespace share memory.'),
|
|
189
|
+
memory_extraction_rules: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe('Domain rules appended to the fact-extraction prompt (long-term memory only).'),
|
|
193
|
+
memory_extraction_model: z
|
|
194
|
+
.string()
|
|
195
|
+
.min(1)
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("The model that distils this syndicate's turns into memory records; default MEMORY_EXTRACTION_MODEL (ADR 0020)."),
|
|
198
|
+
memory_retention_days: z
|
|
199
|
+
.number()
|
|
200
|
+
.int()
|
|
201
|
+
.positive()
|
|
202
|
+
.optional()
|
|
203
|
+
.describe('Days a memory fact is kept in this syndicate\'s namespace; older facts are deleted daily. Needs memory_namespace (ADR 0020).'),
|
|
204
|
+
max_steps: z
|
|
205
|
+
.number()
|
|
206
|
+
.int()
|
|
207
|
+
.positive()
|
|
208
|
+
.optional()
|
|
209
|
+
.describe('Hard cap on runner loops (LLM → tool cycles).'),
|
|
210
|
+
bundled_references: z
|
|
211
|
+
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
212
|
+
.optional()
|
|
213
|
+
.describe('Written by the registry publisher, not by hand: every nested yaml_reference this syndicate reaches, as it was when published, so a registry version is one unit (ADR 0018). Nested references load from here instead of from files.'),
|
|
214
|
+
})
|
|
215
|
+
.describe('A Melchizedek syndicate definition (lib/loadSyndicate.ts).');
|
|
216
|
+
// ── Did-you-mean ─────────────────────────────────────────────────────────────
|
|
217
|
+
/** Optimal-string-alignment distance: a transposition ("modle") costs 1. */
|
|
218
|
+
function editDistance(a, b) {
|
|
219
|
+
const d = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)));
|
|
220
|
+
for (let i = 1; i <= a.length; i++) {
|
|
221
|
+
for (let j = 1; j <= b.length; j++) {
|
|
222
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
223
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
224
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
225
|
+
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return d[a.length][b.length];
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The closest candidate, if it is close enough to be a typo. Case and
|
|
233
|
+
* snake/camel drift (`output_key` for `outputKey`) count as an exact match.
|
|
234
|
+
*/
|
|
235
|
+
export function suggest(input, candidates) {
|
|
236
|
+
const norm = (s) => s.replace(/[_-]/g, '').toLowerCase();
|
|
237
|
+
const exact = candidates.find((c) => norm(c) === norm(input));
|
|
238
|
+
if (exact)
|
|
239
|
+
return exact;
|
|
240
|
+
let best;
|
|
241
|
+
let bestD = Infinity;
|
|
242
|
+
for (const c of candidates) {
|
|
243
|
+
const dist = editDistance(input.toLowerCase(), c.toLowerCase());
|
|
244
|
+
if (dist < bestD) {
|
|
245
|
+
best = c;
|
|
246
|
+
bestD = dist;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// The second bound stops a one-letter name "suggesting" another one-letter
|
|
250
|
+
// name: replacing every character is not a typo.
|
|
251
|
+
return best !== undefined &&
|
|
252
|
+
bestD <= Math.max(2, Math.floor(best.length / 3)) &&
|
|
253
|
+
bestD < Math.max(input.length, best.length)
|
|
254
|
+
? best
|
|
255
|
+
: undefined;
|
|
256
|
+
}
|
|
257
|
+
function formatPath(p) {
|
|
258
|
+
let out = '';
|
|
259
|
+
for (const seg of p) {
|
|
260
|
+
if (typeof seg === 'number')
|
|
261
|
+
out += `[${seg}]`;
|
|
262
|
+
else
|
|
263
|
+
out += out ? `.${String(seg)}` : String(seg);
|
|
264
|
+
}
|
|
265
|
+
return out || '(root)';
|
|
266
|
+
}
|
|
267
|
+
/** The strict object whose keys are valid at `p`, for did-you-mean. */
|
|
268
|
+
function knownKeysAt(p) {
|
|
269
|
+
const key = p.map((s) => (typeof s === 'number' ? '#' : String(s))).join('/');
|
|
270
|
+
switch (key) {
|
|
271
|
+
case '':
|
|
272
|
+
return Object.keys(syndicateSchema.shape);
|
|
273
|
+
case 'orchestrator':
|
|
274
|
+
return Object.keys(agentSchema.shape);
|
|
275
|
+
case 'subagents/#':
|
|
276
|
+
return Object.keys(subagentSchema.shape);
|
|
277
|
+
case 'dispatch':
|
|
278
|
+
return Object.keys(dispatchSchema.shape);
|
|
279
|
+
case 'dispatch/route_overrides/#':
|
|
280
|
+
return Object.keys(routeOverride.shape);
|
|
281
|
+
case 'orchestrator/orchestration':
|
|
282
|
+
case 'subagents/#/orchestration':
|
|
283
|
+
return ['role', 'delegates'];
|
|
284
|
+
default:
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function valueAt(root, p) {
|
|
289
|
+
let cur = root;
|
|
290
|
+
for (const seg of p) {
|
|
291
|
+
if (cur === null || typeof cur !== 'object')
|
|
292
|
+
return undefined;
|
|
293
|
+
cur = cur[seg];
|
|
294
|
+
}
|
|
295
|
+
return cur;
|
|
296
|
+
}
|
|
297
|
+
function describeValue(v) {
|
|
298
|
+
if (v === null)
|
|
299
|
+
return 'null';
|
|
300
|
+
if (Array.isArray(v))
|
|
301
|
+
return 'a list';
|
|
302
|
+
if (typeof v === 'object')
|
|
303
|
+
return 'a mapping';
|
|
304
|
+
if (typeof v === 'string')
|
|
305
|
+
return JSON.stringify(v.length > 40 ? `${v.slice(0, 40)}…` : v);
|
|
306
|
+
return String(v);
|
|
307
|
+
}
|
|
308
|
+
function problemsFromIssues(issues, raw) {
|
|
309
|
+
const out = [];
|
|
310
|
+
for (const issue of issues) {
|
|
311
|
+
const p = issue.path;
|
|
312
|
+
if (issue.code === 'unrecognized_keys') {
|
|
313
|
+
const known = knownKeysAt(p);
|
|
314
|
+
for (const k of issue.keys) {
|
|
315
|
+
const hint = suggest(k, known);
|
|
316
|
+
out.push({
|
|
317
|
+
path: [...p, k],
|
|
318
|
+
message: `unknown key${hint ? ` (did you mean "${hint}"?)` : ''}`,
|
|
319
|
+
typo: true,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const got = valueAt(raw, p);
|
|
325
|
+
if (issue.code === 'invalid_type' && got === undefined) {
|
|
326
|
+
out.push({ path: p, message: 'required' });
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (issue.code === 'invalid_value') {
|
|
330
|
+
const values = issue.values.map(String);
|
|
331
|
+
const hint = typeof got === 'string' ? suggest(got, values) : undefined;
|
|
332
|
+
out.push({
|
|
333
|
+
path: p,
|
|
334
|
+
message: `must be one of ${values.join(' | ')} (got ${describeValue(got)}` +
|
|
335
|
+
(hint ? ` — did you mean "${hint}"?)` : ')'),
|
|
336
|
+
});
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (issue.code === 'invalid_type') {
|
|
340
|
+
out.push({ path: p, message: `expected ${issue.expected}, got ${describeValue(got)}` });
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (issue.code === 'too_small' && issue.origin === 'string') {
|
|
344
|
+
out.push({ path: p, message: 'must not be empty' });
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (issue.code === 'too_small' && issue.origin === 'number') {
|
|
348
|
+
out.push({ path: p, message: `must be a positive integer (got ${describeValue(got)})` });
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
out.push({ path: p, message: issue.message });
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
}
|
|
355
|
+
const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
356
|
+
/** Same normalisation lib/dispatch.ts uses to match a route to a subagent. */
|
|
357
|
+
const routeNorm = (s) => s.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
358
|
+
/**
|
|
359
|
+
* Rules that span fields, which a JSON Schema an editor reads cannot express
|
|
360
|
+
* cleanly. Defensive about shape: runs on raw input alongside zod, so a file
|
|
361
|
+
* with a type error still gets its cross-field problems listed in one pass.
|
|
362
|
+
*/
|
|
363
|
+
function crossFieldProblems(raw) {
|
|
364
|
+
const out = [];
|
|
365
|
+
if (!isObj(raw))
|
|
366
|
+
return out;
|
|
367
|
+
// Retention prunes a namespace; on the shared default namespace it would
|
|
368
|
+
// delete other syndicates' facts.
|
|
369
|
+
if (raw.memory_retention_days !== undefined && typeof raw.memory_namespace !== 'string') {
|
|
370
|
+
out.push({
|
|
371
|
+
path: ['memory_retention_days'],
|
|
372
|
+
message: 'needs memory_namespace — retention applies to one namespace, never to the shared default (npm run doctor -- --fix-namespaces <file>)',
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
const subs = Array.isArray(raw.subagents) ? raw.subagents : [];
|
|
376
|
+
// Approval gates (ADR 0028): only tools the agent has, and only on agents
|
|
377
|
+
// the turn runs directly — a delegated subagent runs inside a tool call,
|
|
378
|
+
// where ADK swallows the pause and the gated tool silently never runs.
|
|
379
|
+
const gateProblems = (agent, path, allowed) => {
|
|
380
|
+
if (agent.require_approval === undefined)
|
|
381
|
+
return;
|
|
382
|
+
if (!allowed) {
|
|
383
|
+
out.push({
|
|
384
|
+
path: [...path, 'require_approval'],
|
|
385
|
+
message: 'approval gates run only on the orchestrator or a plan-dispatch route; a delegated subagent cannot pause the turn (ADR 0028)',
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const tools = Array.isArray(agent.tools) ? agent.tools : [];
|
|
390
|
+
(Array.isArray(agent.require_approval) ? agent.require_approval : []).forEach((name, j) => {
|
|
391
|
+
if (typeof name === 'string' && !tools.includes(name)) {
|
|
392
|
+
out.push({ path: [...path, 'require_approval', j], message: `'${name}' is not in this agent's tools` });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
if (isObj(raw.orchestrator))
|
|
397
|
+
gateProblems(raw.orchestrator, ['orchestrator'], true);
|
|
398
|
+
const dispatching = isObj(raw.dispatch);
|
|
399
|
+
subs.forEach((sub, i) => {
|
|
400
|
+
if (!isObj(sub))
|
|
401
|
+
return;
|
|
402
|
+
gateProblems(sub, ['subagents', i], dispatching);
|
|
403
|
+
const hasRef = typeof sub.yaml_reference === 'string';
|
|
404
|
+
const hasRemote = typeof sub.a2a_agent_url === 'string';
|
|
405
|
+
if (hasRef && hasRemote) {
|
|
406
|
+
out.push({
|
|
407
|
+
path: ['subagents', i, 'a2a_agent_url'],
|
|
408
|
+
message: 'cannot be combined with yaml_reference — a subagent is either a nested syndicate or a remote agent',
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (!hasRef && !hasRemote && sub.instruction === undefined) {
|
|
412
|
+
out.push({
|
|
413
|
+
path: ['subagents', i, 'instruction'],
|
|
414
|
+
message: 'required (or set yaml_reference / a2a_agent_url for a nested or remote agent)',
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (hasRemote && !/\{\{\w+\}\}/.test(sub.a2a_agent_url)) {
|
|
418
|
+
let ok = false;
|
|
419
|
+
try {
|
|
420
|
+
ok = /^https?:$/.test(new URL(sub.a2a_agent_url).protocol);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
ok = false;
|
|
424
|
+
}
|
|
425
|
+
if (!ok) {
|
|
426
|
+
out.push({ path: ['subagents', i, 'a2a_agent_url'], message: 'must be an http(s) URL' });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
// Names must be unique across the tree this file declares: two AgentTools
|
|
431
|
+
// with one name collide, and dispatch routes by name.
|
|
432
|
+
const seen = new Map();
|
|
433
|
+
const named = [
|
|
434
|
+
[['orchestrator', 'name'], isObj(raw.orchestrator) ? raw.orchestrator.name : undefined],
|
|
435
|
+
...subs.map((s, i) => [['subagents', i, 'name'], isObj(s) ? s.name : undefined]),
|
|
436
|
+
];
|
|
437
|
+
for (const [p, name] of named) {
|
|
438
|
+
if (typeof name !== 'string')
|
|
439
|
+
continue;
|
|
440
|
+
const first = seen.get(name);
|
|
441
|
+
if (first)
|
|
442
|
+
out.push({ path: p, message: `duplicate agent name "${name}" (also ${first})` });
|
|
443
|
+
else
|
|
444
|
+
seen.set(name, formatPath(p));
|
|
445
|
+
}
|
|
446
|
+
if (isObj(raw.dispatch) && typeof raw.dispatch.default_route === 'string') {
|
|
447
|
+
const names = subs.map((s) => (isObj(s) && typeof s.name === 'string' ? s.name : '')).filter(Boolean);
|
|
448
|
+
const target = routeNorm(raw.dispatch.default_route);
|
|
449
|
+
if (!names.some((n) => routeNorm(n) === target)) {
|
|
450
|
+
const hint = suggest(raw.dispatch.default_route, names);
|
|
451
|
+
out.push({
|
|
452
|
+
path: ['dispatch', 'default_route'],
|
|
453
|
+
message: `"${raw.dispatch.default_route}" is not a declared subagent` +
|
|
454
|
+
(hint ? ` (did you mean "${hint}"?)` : names.length ? ` (declared: ${names.join(', ')})` : ''),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return out;
|
|
459
|
+
}
|
|
460
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
461
|
+
/**
|
|
462
|
+
* Thrown by validateSyndicateConfig. `problems` holds one `<file>: <path> —
|
|
463
|
+
* <what>` line per problem (the message lists them all); `issues` holds the
|
|
464
|
+
* same lines without the file, for callers that already show it.
|
|
465
|
+
*/
|
|
466
|
+
export class SyndicateValidationError extends Error {
|
|
467
|
+
file;
|
|
468
|
+
issues;
|
|
469
|
+
problems;
|
|
470
|
+
constructor(issues, file) {
|
|
471
|
+
const problems = issues.map((i) => (file ? `${file}: ${i}` : i));
|
|
472
|
+
super(problems.length === 1
|
|
473
|
+
? problems[0]
|
|
474
|
+
: `${problems.length} problems in syndicate config:\n ${problems.join('\n ')}`);
|
|
475
|
+
this.name = 'SyndicateValidationError';
|
|
476
|
+
this.file = file;
|
|
477
|
+
this.issues = issues;
|
|
478
|
+
this.problems = problems;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Validate a parsed syndicate and return it typed, or throw ONE error that
|
|
483
|
+
* lists every problem as `<file>: <key.path> — <what is wrong>`.
|
|
484
|
+
*
|
|
485
|
+
* Runs on the interpolated config: a `{{token}}` inside a string is still a
|
|
486
|
+
* string, and a full-token number (`max_steps: "{{n}}"`) is checked as the
|
|
487
|
+
* value it resolved to.
|
|
488
|
+
*/
|
|
489
|
+
export function validateSyndicateConfig(raw, file) {
|
|
490
|
+
if (!isObj(raw)) {
|
|
491
|
+
throw new SyndicateValidationError([`(root) — expected a mapping with syndicate_name, orchestrator and subagents, got ${describeValue(raw)}`], file);
|
|
492
|
+
}
|
|
493
|
+
const result = syndicateSchema.safeParse(raw);
|
|
494
|
+
const problems = [
|
|
495
|
+
...(result.success ? [] : problemsFromIssues(result.error.issues, raw)),
|
|
496
|
+
...crossFieldProblems(raw),
|
|
497
|
+
];
|
|
498
|
+
if (problems.length > 0) {
|
|
499
|
+
const ordered = [...problems.filter((p) => p.typo), ...problems.filter((p) => !p.typo)];
|
|
500
|
+
throw new SyndicateValidationError(ordered.map((p) => `${formatPath(p.path)} — ${p.message}`), file);
|
|
501
|
+
}
|
|
502
|
+
// A single-agent syndicate may omit `subagents`; callers always get a list.
|
|
503
|
+
if (raw.subagents === undefined)
|
|
504
|
+
raw.subagents = [];
|
|
505
|
+
return raw;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* The syndicate contract as JSON Schema (draft-07: the widest editor
|
|
509
|
+
* support), for `# yaml-language-server: $schema=` and other tooling.
|
|
510
|
+
* Cross-field rules zod expresses as code are added back where JSON Schema
|
|
511
|
+
* can say them, so an editor flags a subagent with no instruction too.
|
|
512
|
+
*/
|
|
513
|
+
export function syndicateJsonSchema() {
|
|
514
|
+
const schema = z.toJSONSchema(syndicateSchema, {
|
|
515
|
+
target: 'draft-7',
|
|
516
|
+
override: (ctx) => {
|
|
517
|
+
// `\p{ID_Start}` needs the regex `u` flag, which JSON Schema validators
|
|
518
|
+
// do not all apply; the ASCII form only over-warns on non-Latin names,
|
|
519
|
+
// and still catches the common mistake (a space in a name).
|
|
520
|
+
if (ctx.jsonSchema.pattern === AGENT_NAME_RE.source) {
|
|
521
|
+
ctx.jsonSchema.pattern = '^[A-Za-z_$][A-Za-z0-9_$-]*$';
|
|
522
|
+
}
|
|
523
|
+
if (ctx.zodSchema === subagentSchema) {
|
|
524
|
+
ctx.jsonSchema.anyOf = [
|
|
525
|
+
{ required: ['instruction'] },
|
|
526
|
+
{ required: ['yaml_reference'] },
|
|
527
|
+
{ required: ['a2a_agent_url'] },
|
|
528
|
+
];
|
|
529
|
+
ctx.jsonSchema.not = { required: ['yaml_reference', 'a2a_agent_url'] };
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
return { ...schema, title: 'Melchizedek syndicate' };
|
|
534
|
+
}
|
|
@@ -13,3 +13,18 @@
|
|
|
13
13
|
* callers can log in their own format.
|
|
14
14
|
*/
|
|
15
15
|
export declare function resolveTools(toolNames?: string[], onUnknown?: (name: string) => void): any[];
|
|
16
|
+
/**
|
|
17
|
+
* Make a tool resolvable by name from a syndicate YAML's `tools:` list.
|
|
18
|
+
*
|
|
19
|
+
* For package consumers: the registry is otherwise closed (YAML can name
|
|
20
|
+
* only what is registered, never load code), and editing this file under
|
|
21
|
+
* node_modules is not an option. Registering is the same deliberate act of
|
|
22
|
+
* exposure as listing a tool above — it happens in your code, where a
|
|
23
|
+
* reviewer reads it. Pass a `defineTool` contract (lib/tools/toolContract.ts)
|
|
24
|
+
* or a ready ADK tool. Replacing a built-in requires `{ override: true }`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function registerTool(name: string, tool: unknown, options?: {
|
|
27
|
+
override?: boolean;
|
|
28
|
+
}): void;
|
|
29
|
+
/** Names a YAML can declare under `tools:` right now. */
|
|
30
|
+
export declare function registeredToolNames(): string[];
|
package/dist/lib/toolRegistry.js
CHANGED
|
@@ -8,14 +8,17 @@
|
|
|
8
8
|
* here means a newly added tool is available to every entrypoint at once.
|
|
9
9
|
*/
|
|
10
10
|
import { GOOGLE_SEARCH, LOAD_MEMORY, PRELOAD_MEMORY, } from '@google/adk';
|
|
11
|
-
import { COLLECTIONS_SEARCH } from
|
|
12
|
-
import { generateImageTool } from
|
|
13
|
-
import { inspectImageTool } from
|
|
14
|
-
import { toFunctionTool } from
|
|
15
|
-
import { WEB_SEARCH } from
|
|
16
|
-
import { webExtractTool } from
|
|
17
|
-
import { WIKI_AGENT_TOOL_CONTRACTS } from
|
|
18
|
-
import {
|
|
11
|
+
import { COLLECTIONS_SEARCH } from './tools/collectionsSearchTool.js';
|
|
12
|
+
import { generateImageTool } from './tools/generateImageTool.js';
|
|
13
|
+
import { inspectImageTool } from './tools/inspectImageTool.js';
|
|
14
|
+
import { toFunctionTool } from './tools/toolContract.js';
|
|
15
|
+
import { WEB_SEARCH } from './tools/webSearchTool.js';
|
|
16
|
+
import { webExtractTool } from './tools/webExtractTool.js';
|
|
17
|
+
import { WIKI_AGENT_TOOL_CONTRACTS } from './tools/wikiTools.js';
|
|
18
|
+
import { SCIENCE_TOOL_CONTRACTS } from './tools/scienceTools.js';
|
|
19
|
+
import { TASK_TOOL_CONTRACTS } from './tools/taskTools.js';
|
|
20
|
+
import { X_SEARCH } from './tools/xSearchTool.js';
|
|
21
|
+
import { xApiSearchTool } from './tools/xApiSearchTool.js';
|
|
19
22
|
// Knowledge-bundle tools, derived from their contracts so the YAML names
|
|
20
23
|
// can never drift from the definitions. The agentic composites
|
|
21
24
|
// (wiki_query/wiki_garden) are deliberately absent: a syndicate reaches
|
|
@@ -24,8 +27,23 @@ const WIKI_TOOLS = Object.fromEntries(WIKI_AGENT_TOOL_CONTRACTS.map((contract) =
|
|
|
24
27
|
contract.name,
|
|
25
28
|
toFunctionTool(contract),
|
|
26
29
|
]));
|
|
27
|
-
|
|
30
|
+
// Science tools (lib/tools/scienceTools.ts): read-only literature and
|
|
31
|
+
// registry lookups, derived from their contracts the same way, so the YAML
|
|
32
|
+
// name IS the contract name. research.yaml declares them.
|
|
33
|
+
const SCIENCE_TOOLS = Object.fromEntries(SCIENCE_TOOL_CONTRACTS.map((contract) => [contract.name, toFunctionTool(contract)]));
|
|
34
|
+
// Task list + background-job queue (lib/tools/taskTools.ts): a single-user
|
|
35
|
+
// JSON file by default, per-caller lists on Postgres (setTaskBackend). The
|
|
36
|
+
// tools only write the queue; scripts/assistant_worker.ts runs the jobs.
|
|
37
|
+
// assistant.yaml declares them.
|
|
38
|
+
const TASK_TOOLS = Object.fromEntries(TASK_TOOL_CONTRACTS.map((contract) => [contract.name, toFunctionTool(contract)]));
|
|
39
|
+
// The built-in tools. The wiki build reads these keys from this literal.
|
|
40
|
+
// A plain object literal inherits from Object.prototype, so a YAML naming
|
|
41
|
+
// `constructor` or `toString` resolved to a prototype function instead of
|
|
42
|
+
// the unknown-tool warning; TOOL_MAP below is the null-prototype copy.
|
|
43
|
+
const BUILTIN_TOOLS = {
|
|
28
44
|
...WIKI_TOOLS,
|
|
45
|
+
...SCIENCE_TOOLS,
|
|
46
|
+
...TASK_TOOLS,
|
|
29
47
|
// Provider-agnostic web search: routes to the model's NATIVE search
|
|
30
48
|
// (Gemini grounding / Anthropic / OpenAI / xAI); omitted with a warning
|
|
31
49
|
// for local models. Prefer this in new YAMLs.
|
|
@@ -35,6 +53,9 @@ const TOOL_MAP = {
|
|
|
35
53
|
// augustin.yaml and librarian-style research agents declare it.
|
|
36
54
|
web_extract: webExtractTool,
|
|
37
55
|
x_search: X_SEARCH,
|
|
56
|
+
// X API v2 recent search as a client-side contract, photos transcribed
|
|
57
|
+
// inline — runs on every provider; needs X_BEARER_TOKEN in the server env.
|
|
58
|
+
x_api_search: xApiSearchTool,
|
|
38
59
|
// xAI-only: semantic search over hosted Collections (XAI_COLLECTION_IDS).
|
|
39
60
|
collections_search: COLLECTIONS_SEARCH,
|
|
40
61
|
// Gemini-only ADK grounding tool, kept for backward compatibility.
|
|
@@ -44,6 +65,9 @@ const TOOL_MAP = {
|
|
|
44
65
|
load_memory: LOAD_MEMORY,
|
|
45
66
|
preload_memory: PRELOAD_MEMORY,
|
|
46
67
|
};
|
|
68
|
+
// Null-prototype copy (as GUARD_MAP is): resolution and registration go
|
|
69
|
+
// through this map, never the literal above.
|
|
70
|
+
const TOOL_MAP = Object.assign(Object.create(null), BUILTIN_TOOLS);
|
|
47
71
|
/**
|
|
48
72
|
* Resolve an array of tool-name strings to live ADK tool instances.
|
|
49
73
|
* Unknown names are skipped; `onUnknown` (if provided) is invoked for each so
|
|
@@ -52,7 +76,7 @@ const TOOL_MAP = {
|
|
|
52
76
|
export function resolveTools(toolNames = [], onUnknown) {
|
|
53
77
|
return toolNames
|
|
54
78
|
.map((name) => {
|
|
55
|
-
const tool = TOOL_MAP[name];
|
|
79
|
+
const tool = Object.prototype.hasOwnProperty.call(TOOL_MAP, name) ? TOOL_MAP[name] : undefined;
|
|
56
80
|
if (tool === undefined) {
|
|
57
81
|
onUnknown?.(name);
|
|
58
82
|
return null;
|
|
@@ -61,3 +85,28 @@ export function resolveTools(toolNames = [], onUnknown) {
|
|
|
61
85
|
})
|
|
62
86
|
.filter(Boolean);
|
|
63
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Make a tool resolvable by name from a syndicate YAML's `tools:` list.
|
|
90
|
+
*
|
|
91
|
+
* For package consumers: the registry is otherwise closed (YAML can name
|
|
92
|
+
* only what is registered, never load code), and editing this file under
|
|
93
|
+
* node_modules is not an option. Registering is the same deliberate act of
|
|
94
|
+
* exposure as listing a tool above — it happens in your code, where a
|
|
95
|
+
* reviewer reads it. Pass a `defineTool` contract (lib/tools/toolContract.ts)
|
|
96
|
+
* or a ready ADK tool. Replacing a built-in requires `{ override: true }`.
|
|
97
|
+
*/
|
|
98
|
+
export function registerTool(name, tool, options = {}) {
|
|
99
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(name)) {
|
|
100
|
+
throw new Error(`registerTool: '${name}' is not a valid tool name`);
|
|
101
|
+
}
|
|
102
|
+
if (Object.prototype.hasOwnProperty.call(TOOL_MAP, name) && !options.override) {
|
|
103
|
+
throw new Error(`registerTool: '${name}' is already registered (pass { override: true } to replace it)`);
|
|
104
|
+
}
|
|
105
|
+
const t = tool;
|
|
106
|
+
const isContract = !!t && typeof t === 'object' && 'schema' in t && typeof t.execute === 'function' && !('runAsync' in t);
|
|
107
|
+
TOOL_MAP[name] = isContract ? toFunctionTool(tool) : tool;
|
|
108
|
+
}
|
|
109
|
+
/** Names a YAML can declare under `tools:` right now. */
|
|
110
|
+
export function registeredToolNames() {
|
|
111
|
+
return Object.keys(TOOL_MAP).sort();
|
|
112
|
+
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* web_search / x_search).
|
|
32
32
|
*/
|
|
33
33
|
import { BaseTool } from '@google/adk';
|
|
34
|
-
import { providerForModel } from
|
|
34
|
+
import { providerForModel } from '../models/providerMap.js';
|
|
35
35
|
export const COLLECTIONS_SEARCH_TOOL_NAME = 'collections_search';
|
|
36
36
|
export class CollectionsSearchTool extends BaseTool {
|
|
37
37
|
constructor() {
|