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.
Files changed (246) hide show
  1. package/.env.example +272 -0
  2. package/AGENT_SETUP.md +39 -23
  3. package/CHANGELOG.md +623 -0
  4. package/DOCUMENTATION.md +429 -109
  5. package/QUICKSTART.md +92 -18
  6. package/README.md +45 -5
  7. package/config/agents/README.md +46 -1
  8. package/config/agents/examples/ares.yaml +117 -40
  9. package/config/agents/examples/assistant.yaml +159 -0
  10. package/config/agents/examples/augustin.yaml +154 -51
  11. package/config/agents/examples/cartographers.yaml +91 -23
  12. package/config/agents/examples/claude.yaml +45 -6
  13. package/config/agents/examples/council.yaml +23 -8
  14. package/config/agents/examples/critic.yaml +57 -21
  15. package/config/agents/examples/delegation.yaml +76 -9
  16. package/config/agents/examples/hierarchical.yaml +55 -8
  17. package/config/agents/examples/image_production.yaml +54 -16
  18. package/config/agents/examples/librarian.yaml +70 -14
  19. package/config/agents/examples/model_zoo.yaml +105 -29
  20. package/config/agents/examples/patient_advocate.yaml +102 -34
  21. package/config/agents/examples/research.yaml +250 -0
  22. package/config/agents/examples/scribe.yaml +242 -0
  23. package/config/agents/examples/scriptorium.yaml +87 -24
  24. package/config/agents/examples/style_council.yaml +31 -9
  25. package/config/agents/examples/syndicate.yaml +29 -10
  26. package/config/agents/examples/tutor.yaml +10 -3
  27. package/config/agents/syndicateSchema.yaml +44 -9
  28. package/config/agents/templates/README.md +58 -0
  29. package/config/agents/templates/account_memory.yaml +153 -0
  30. package/config/agents/templates/case_desk.yaml +188 -0
  31. package/config/agents/templates/conversational.yaml +110 -0
  32. package/config/agents/templates/draft_review.yaml +130 -0
  33. package/config/agents/templates/intake_extractor.yaml +152 -0
  34. package/config/agents/templates/research_brief.yaml +169 -0
  35. package/config/agents/templates/research_desk.yaml +152 -0
  36. package/config/agents/templates/review_panel.yaml +188 -0
  37. package/config/agents/templates/support_triage.yaml +273 -0
  38. package/config/agents/templates/systems_operator.yaml +157 -0
  39. package/db/hardening.sql +202 -0
  40. package/db/memory_v2.sql +77 -0
  41. package/db/migrations/0001_base.sql +167 -0
  42. package/db/migrations/0002_erase_scope.sql +172 -0
  43. package/db/migrations/0003_postgres_storage.sql +58 -0
  44. package/db/migrations/0004_usage.sql +94 -0
  45. package/db/migrations/0005_agent_registry.sql +200 -0
  46. package/db/migrations/0006_task_leases.sql +25 -0
  47. package/db/migrations/0007_memory_commit.sql +134 -0
  48. package/db/migrations/0008_memory_retention.sql +37 -0
  49. package/db/migrations/0009_task_queue.sql +51 -0
  50. package/db/migrations/0010_erase_complete.sql +180 -0
  51. package/db/telemetry.sql +401 -0
  52. package/demo/README.md +77 -0
  53. package/demo/a2a_demo.mjs +95 -0
  54. package/dist/lib/a2a/app.d.ts +253 -0
  55. package/dist/lib/a2a/app.js +751 -0
  56. package/dist/lib/a2a/executor.d.ts +167 -0
  57. package/dist/lib/a2a/executor.js +548 -0
  58. package/dist/lib/a2a/identity.d.ts +122 -0
  59. package/dist/lib/a2a/identity.js +254 -0
  60. package/dist/lib/a2a/policy.d.ts +110 -0
  61. package/dist/lib/a2a/policy.js +178 -0
  62. package/dist/lib/a2a/redisLimits.d.ts +26 -0
  63. package/dist/lib/a2a/redisLimits.js +31 -0
  64. package/dist/lib/a2a/remoteAgent.d.ts +91 -0
  65. package/dist/lib/a2a/remoteAgent.js +268 -0
  66. package/dist/lib/a2a/turnLock.d.ts +28 -0
  67. package/dist/lib/a2a/turnLock.js +76 -0
  68. package/dist/lib/compile.d.ts +11 -1
  69. package/dist/lib/compile.js +109 -8
  70. package/dist/lib/config.d.ts +7 -7
  71. package/dist/lib/config.js +7 -7
  72. package/dist/lib/doctor.d.ts +145 -0
  73. package/dist/lib/doctor.js +517 -0
  74. package/dist/lib/grounding.d.ts +2 -2
  75. package/dist/lib/grounding.js +2 -2
  76. package/dist/lib/guards/index.d.ts +37 -0
  77. package/dist/lib/guards/index.js +66 -0
  78. package/dist/lib/guards/science.d.ts +117 -0
  79. package/dist/lib/guards/science.js +444 -0
  80. package/dist/lib/index.d.ts +37 -2
  81. package/dist/lib/index.js +35 -4
  82. package/dist/lib/loadEnv.d.ts +23 -11
  83. package/dist/lib/loadEnv.js +77 -29
  84. package/dist/lib/loadSyndicate.d.ts +97 -5
  85. package/dist/lib/loadSyndicate.js +176 -76
  86. package/dist/lib/memory/erase.d.ts +24 -0
  87. package/dist/lib/memory/erase.js +47 -0
  88. package/dist/lib/memory/namespace.d.ts +37 -0
  89. package/dist/lib/memory/namespace.js +74 -0
  90. package/dist/lib/memory/providers.d.ts +99 -0
  91. package/dist/lib/memory/providers.js +240 -0
  92. package/dist/lib/memory/store.d.ts +74 -0
  93. package/dist/lib/memory/store.js +108 -0
  94. package/dist/lib/memory/supabaseMemoryService.d.ts +69 -9
  95. package/dist/lib/memory/supabaseMemoryService.js +239 -125
  96. package/dist/lib/models/capabilities.d.ts +103 -0
  97. package/dist/lib/models/capabilities.js +291 -0
  98. package/dist/lib/models/claudeLlm.d.ts +10 -1
  99. package/dist/lib/models/claudeLlm.js +57 -27
  100. package/dist/lib/models/endpoints.d.ts +100 -0
  101. package/dist/lib/models/endpoints.js +282 -0
  102. package/dist/lib/models/gateway.d.ts +107 -0
  103. package/dist/lib/models/gateway.js +157 -0
  104. package/dist/lib/models/gatewayLlm.d.ts +57 -0
  105. package/dist/lib/models/gatewayLlm.js +105 -0
  106. package/dist/lib/models/gptLlm.d.ts +51 -2
  107. package/dist/lib/models/gptLlm.js +169 -26
  108. package/dist/lib/models/grokLlm.d.ts +19 -10
  109. package/dist/lib/models/grokLlm.js +29 -14
  110. package/dist/lib/models/ollamaLlm.d.ts +26 -0
  111. package/dist/lib/models/ollamaLlm.js +43 -1
  112. package/dist/lib/models/openAiCompatibleLlm.d.ts +67 -2
  113. package/dist/lib/models/openAiCompatibleLlm.js +200 -37
  114. package/dist/lib/models/providerMap.js +9 -2
  115. package/dist/lib/models/registry.d.ts +42 -5
  116. package/dist/lib/models/registry.js +140 -29
  117. package/dist/lib/models/retry.d.ts +146 -0
  118. package/dist/lib/models/retry.js +305 -0
  119. package/dist/lib/models/schemaNormalize.d.ts +20 -0
  120. package/dist/lib/models/schemaNormalize.js +43 -0
  121. package/dist/lib/net/addressGuard.d.ts +60 -0
  122. package/dist/lib/net/addressGuard.js +216 -0
  123. package/dist/lib/observability/embeddings.d.ts +11 -2
  124. package/dist/lib/observability/embeddings.js +18 -14
  125. package/dist/lib/observability/metrics.d.ts +36 -0
  126. package/dist/lib/observability/metrics.js +86 -0
  127. package/dist/lib/observability/redact.d.ts +29 -0
  128. package/dist/lib/observability/redact.js +102 -0
  129. package/dist/lib/observability/supabaseSpanExporter.d.ts +2 -0
  130. package/dist/lib/observability/supabaseSpanExporter.js +22 -6
  131. package/dist/lib/observability/tracer.d.ts +10 -0
  132. package/dist/lib/observability/tracer.js +70 -3
  133. package/dist/lib/persistence/supabaseProvider.d.ts +11 -0
  134. package/dist/lib/persistence/supabaseProvider.js +32 -6
  135. package/dist/lib/registry.d.ts +87 -0
  136. package/dist/lib/registry.js +146 -0
  137. package/dist/lib/runtime/approvals.d.ts +44 -0
  138. package/dist/lib/runtime/approvals.js +67 -0
  139. package/dist/lib/runtime/syndicateTurn.d.ts +233 -0
  140. package/dist/lib/runtime/syndicateTurn.js +597 -0
  141. package/dist/lib/runtime/turnControl.d.ts +90 -0
  142. package/dist/lib/runtime/turnControl.js +131 -0
  143. package/dist/lib/session/supabaseSessionService.js +1 -1
  144. package/dist/lib/session/transcript.d.ts +9 -1
  145. package/dist/lib/session/transcript.js +15 -7
  146. package/dist/lib/skills.d.ts +50 -0
  147. package/dist/lib/skills.js +138 -0
  148. package/dist/lib/storage/postgres/index.d.ts +98 -0
  149. package/dist/lib/storage/postgres/index.js +146 -0
  150. package/dist/lib/storage/postgres/memoryStore.d.ts +11 -0
  151. package/dist/lib/storage/postgres/memoryStore.js +82 -0
  152. package/dist/lib/storage/postgres/sessionService.d.ts +51 -0
  153. package/dist/lib/storage/postgres/sessionService.js +225 -0
  154. package/dist/lib/storage/postgres/taskQueue.d.ts +22 -0
  155. package/dist/lib/storage/postgres/taskQueue.js +101 -0
  156. package/dist/lib/storage/postgres/taskStore.d.ts +55 -0
  157. package/dist/lib/storage/postgres/taskStore.js +168 -0
  158. package/dist/lib/storage/schema.d.ts +27 -0
  159. package/dist/lib/storage/schema.js +50 -0
  160. package/dist/lib/storage/schemaVersion.d.ts +25 -0
  161. package/dist/lib/storage/schemaVersion.js +46 -0
  162. package/dist/lib/syndicateSchema.d.ts +275 -0
  163. package/dist/lib/syndicateSchema.js +534 -0
  164. package/dist/lib/toolRegistry.d.ts +15 -0
  165. package/dist/lib/toolRegistry.js +59 -10
  166. package/dist/lib/tools/collectionsSearchTool.js +1 -1
  167. package/dist/lib/tools/mcpServe.d.ts +45 -0
  168. package/dist/lib/tools/mcpServe.js +88 -0
  169. package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
  170. package/dist/lib/tools/mcpToolFactory.js +41 -21
  171. package/dist/lib/tools/science/corrections.d.ts +42 -0
  172. package/dist/lib/tools/science/corrections.js +115 -0
  173. package/dist/lib/tools/science/europepmc.d.ts +51 -0
  174. package/dist/lib/tools/science/europepmc.js +127 -0
  175. package/dist/lib/tools/science/http.d.ts +31 -0
  176. package/dist/lib/tools/science/http.js +104 -0
  177. package/dist/lib/tools/science/openalex.d.ts +30 -0
  178. package/dist/lib/tools/science/openalex.js +81 -0
  179. package/dist/lib/tools/science/trials.d.ts +58 -0
  180. package/dist/lib/tools/science/trials.js +203 -0
  181. package/dist/lib/tools/science/types.d.ts +69 -0
  182. package/dist/lib/tools/science/types.js +48 -0
  183. package/dist/lib/tools/scienceTools.d.ts +100 -0
  184. package/dist/lib/tools/scienceTools.js +277 -0
  185. package/dist/lib/tools/taskTools.d.ts +204 -0
  186. package/dist/lib/tools/taskTools.js +374 -0
  187. package/dist/lib/tools/toolContract.d.ts +17 -4
  188. package/dist/lib/tools/toolContract.js +13 -5
  189. package/dist/lib/tools/webExtractTool.d.ts +13 -10
  190. package/dist/lib/tools/webExtractTool.js +23 -55
  191. package/dist/lib/tools/webSearchTool.d.ts +2 -1
  192. package/dist/lib/tools/webSearchTool.js +4 -3
  193. package/dist/lib/tools/wikiTools.d.ts +2 -2
  194. package/dist/lib/tools/wikiTools.js +12 -12
  195. package/dist/lib/tools/xApiSearchTool.d.ts +194 -0
  196. package/dist/lib/tools/xApiSearchTool.js +583 -0
  197. package/dist/lib/tools/xSearchTool.js +1 -1
  198. package/dist/lib/wiki/agentRun.js +1 -1
  199. package/dist/lib/wiki/builder.js +21 -6
  200. package/dist/lib/wiki/dive.js +2 -2
  201. package/dist/lib/wiki/entities.d.ts +2 -2
  202. package/dist/lib/wiki/entities.js +7 -7
  203. package/dist/lib/wiki/extract.js +5 -1
  204. package/dist/lib/wiki/fill.js +5 -5
  205. package/dist/lib/wiki/format.d.ts +7 -5
  206. package/dist/lib/wiki/format.js +2 -2
  207. package/dist/lib/wiki/graph.js +2 -2
  208. package/dist/lib/wiki/lint.js +2 -2
  209. package/dist/lib/wiki/markdown.d.ts +1 -1
  210. package/dist/lib/wiki/markdown.js +6 -1
  211. package/dist/lib/wiki/pipeline.js +2 -2
  212. package/dist/lib/wiki/vault.js +2 -2
  213. package/dist/scripts/a2a_server.d.ts +17 -11
  214. package/dist/scripts/a2a_server.js +405 -1028
  215. package/dist/scripts/assistant_worker.d.ts +33 -0
  216. package/dist/scripts/assistant_worker.js +193 -0
  217. package/dist/scripts/db.d.ts +26 -0
  218. package/dist/scripts/db.js +179 -0
  219. package/dist/scripts/doctor.d.ts +23 -0
  220. package/dist/scripts/doctor.js +90 -0
  221. package/dist/scripts/init.d.ts +43 -0
  222. package/dist/scripts/init.js +152 -0
  223. package/dist/scripts/registry.d.ts +25 -0
  224. package/dist/scripts/registry.js +182 -0
  225. package/dist/scripts/skills_install.d.ts +7 -0
  226. package/dist/scripts/skills_install.js +137 -0
  227. package/dist/scripts/syndicate_chat.js +159 -276
  228. package/package.json +90 -16
  229. package/skills/README.md +74 -0
  230. package/skills/briefs/README.md +16 -0
  231. package/skills/briefs/_shared.md +16 -0
  232. package/skills/briefs/melchizedek-author.md +60 -0
  233. package/skills/briefs/melchizedek-memory.md +47 -0
  234. package/skills/briefs/melchizedek-models.md +47 -0
  235. package/skills/briefs/melchizedek-scribe.md +37 -0
  236. package/skills/briefs/melchizedek-serve.md +56 -0
  237. package/skills/briefs/melchizedek.md +69 -0
  238. package/skills/briefs/skills-readme.md +33 -0
  239. package/skills/melchizedek/SKILL.md +159 -0
  240. package/skills/melchizedek-author/SKILL.md +99 -0
  241. package/skills/melchizedek-author/templates/minimal.yaml +60 -0
  242. package/skills/melchizedek-memory/SKILL.md +138 -0
  243. package/skills/melchizedek-models/SKILL.md +137 -0
  244. package/skills/melchizedek-scribe/SKILL.md +72 -0
  245. package/skills/melchizedek-scribe/templates/brief.md +27 -0
  246. 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[];
@@ -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 "./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 { X_SEARCH } from "./tools/xSearchTool.js";
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
- const TOOL_MAP = {
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 "../models/providerMap.js";
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() {