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,291 @@
1
+ /**
2
+ * lib/models/capabilities.ts — what an agent keeps and loses on the path
3
+ * its model will actually take.
4
+ *
5
+ * WHY this file exists:
6
+ * A model id is the whole routing decision, and some of what an agent
7
+ * declares only exists on one provider: Gemini grounding, xAI's x_search
8
+ * and Collections, the server-side web_search sentinel that every cloud
9
+ * adapter turns into its provider's native search. Until now the only
10
+ * defence against losing one was a one-time console.warn inside an
11
+ * adapter. This module states the loss BEFORE a request is made, per
12
+ * agent, on the RESOLVED transport (direct or gateway), so the doctor
13
+ * (lib/doctor.ts), the A2A startup log (lib/compile.ts) and the ledger
14
+ * (llm.capability.dropped) all say the same thing.
15
+ *
16
+ * It knows nothing about gateways beyond planTransport(); a future
17
+ * transport that restores a native feature changes the table here, not
18
+ * the callers.
19
+ */
20
+ import { PROVIDERS } from './providerMap.js';
21
+ import { planTransport } from './gateway.js';
22
+ import { nativeSearchOn, PLATFORMS_FOR, platformFromEnv } from './endpoints.js';
23
+ /** The platform a direct path uses (ADR 0023); a misconfigured one reads as direct here, the doctor reports it. */
24
+ function directPlatform(provider) {
25
+ try {
26
+ return platformFromEnv(provider);
27
+ }
28
+ catch {
29
+ return 'direct';
30
+ }
31
+ }
32
+ /**
33
+ * Server-side tool sentinels and the providers whose DIRECT adapter
34
+ * honours them natively. Anything not listed is a client-side function
35
+ * tool and travels on every path.
36
+ */
37
+ export const SERVER_SIDE_TOOLS = {
38
+ web_search: ['gemini', 'anthropic', 'openai', 'xai'],
39
+ google_search: ['gemini'],
40
+ x_search: ['xai'],
41
+ collections_search: ['xai'],
42
+ };
43
+ export function describeCapabilities(model, tools = [], opts = {}) {
44
+ const plan = planTransport(model, opts);
45
+ const platform = plan.transport === 'direct' && plan.provider !== 'ollama' ? directPlatform(plan.provider) : undefined;
46
+ const native = [];
47
+ const dropped = [];
48
+ const portable = [];
49
+ for (const name of tools) {
50
+ const providers = SERVER_SIDE_TOOLS[name];
51
+ if (!providers) {
52
+ portable.push(name);
53
+ }
54
+ else if (plan.transport === 'direct' && providers.includes(plan.provider) && nativeSearchOn(plan.provider, platform ?? 'direct')) {
55
+ native.push(name);
56
+ }
57
+ else {
58
+ dropped.push(name);
59
+ }
60
+ }
61
+ return {
62
+ model,
63
+ provider: plan.provider,
64
+ providerLabel: PROVIDERS[plan.provider].label,
65
+ transport: plan.transport,
66
+ ...(plan.gateway ? { gateway: plan.gateway.id } : {}),
67
+ ...(platform && platform !== 'direct' ? { platform } : {}),
68
+ funded: plan.funded,
69
+ keyEnv: plan.keyEnv,
70
+ native,
71
+ dropped,
72
+ portable,
73
+ };
74
+ }
75
+ /**
76
+ * One line for a startup log, or undefined when there is nothing to say —
77
+ * a funded direct path with no dropped tool is the quiet default.
78
+ */
79
+ export function capabilitySummary(agentName, r) {
80
+ if (!r.funded) {
81
+ return `${agentName}: ${r.model} has no route — set ${r.keyEnv ?? 'a provider key'} (or a MODEL_GATEWAY).`;
82
+ }
83
+ const via = r.transport === 'gateway' ? ` via gateway:${r.gateway}` : '';
84
+ if (r.dropped.length === 0) {
85
+ return via ? `${agentName}: ${r.model}${via}.` : undefined;
86
+ }
87
+ const why = r.transport === 'gateway'
88
+ ? 'a gateway cannot enable upstream native search'
89
+ : r.provider === 'ollama'
90
+ ? 'a local model has no native search'
91
+ : r.platform
92
+ ? `native search is not sent to ${r.providerLabel} on ${r.platform}`
93
+ : `${r.providerLabel} has no native ${r.dropped.join('/')}`;
94
+ return `${agentName}: ${r.model}${via} — dropped ${r.dropped.join(', ')} (${why}).`;
95
+ }
96
+ // ── The capability matrix (ADR 0019) ─────────────────────────────────────────
97
+ //
98
+ // "Multi-model" promises that any provider can run any role, orchestrators
99
+ // included. This table states, per resolved path (a direct provider, or the
100
+ // gateway transport), what an agent can rely on. Each cell describes what the
101
+ // ADAPTER SENDS, not how a given model behaves once it receives the request:
102
+ //
103
+ // supported the feature reaches the provider in its native form
104
+ // degraded it reaches the provider in a weaker form; `note` names the loss
105
+ // unsupported the adapter does not send it; `note` says what happens instead
106
+ //
107
+ // `evidence: 'test'` cells are asserted against the real outgoing request body
108
+ // in tests/capabilityMatrix.test.ts, so changing an adapter without changing
109
+ // its row fails a test. `evidence: 'adk'` cells are ADK's own Gemini adapter,
110
+ // which this repo does not build requests for.
111
+ export const CAPABILITIES = [
112
+ 'delegation',
113
+ 'memory_tools',
114
+ 'structured_output',
115
+ 'thinking_with_tools',
116
+ 'streaming',
117
+ 'vision',
118
+ 'native_search',
119
+ ];
120
+ export const CAPABILITY_LABELS = {
121
+ delegation: 'delegation (subagents as tools)',
122
+ memory_tools: 'memory tools (load_memory)',
123
+ structured_output: 'structured output (outputSchema)',
124
+ thinking_with_tools: 'thinking with tool use',
125
+ streaming: 'token streaming',
126
+ vision: 'image input',
127
+ native_search: 'native web search',
128
+ };
129
+ const ok = (evidence = 'test', note) => note ? { support: 'supported', note, evidence } : { support: 'supported', evidence };
130
+ const degraded = (note) => ({ support: 'degraded', note, evidence: 'test' });
131
+ const unsupported = (note) => ({ support: 'unsupported', note, evidence: 'test' });
132
+ const nativeSearch = (row) => SERVER_SIDE_TOOLS.web_search.includes(row)
133
+ ? ok(row === 'gemini' ? 'adk' : 'test')
134
+ : unsupported('no native search on this path; the web_search sentinel is dropped (use web_extract)');
135
+ const RESPONSES_REASONING_NOTE = 'reasoning is requested, but reasoning items are not carried across tool calls, so the model re-reasons each step';
136
+ const CHAT_THINKING_NOTE = 'thinkingConfig budgets are ignored on chat-completions; generateContentConfig.reasoningEffort is the lever';
137
+ export const CAPABILITY_MATRIX = {
138
+ gemini: {
139
+ delegation: ok('adk'),
140
+ memory_tools: ok('adk'),
141
+ structured_output: ok('adk'),
142
+ thinking_with_tools: ok('adk'),
143
+ streaming: ok('adk'),
144
+ vision: ok('adk'),
145
+ native_search: nativeSearch('gemini'),
146
+ },
147
+ anthropic: {
148
+ delegation: ok(),
149
+ memory_tools: ok(),
150
+ structured_output: ok('test', 'sent as a forced tool call; with a thinking budget the tool is offered under tool_choice auto'),
151
+ thinking_with_tools: unsupported('signed thinking blocks are not replayed on tool loops, which Anthropic requires; give a thinking Claude agent no tools'),
152
+ streaming: ok(),
153
+ vision: unsupported('image parts are dropped from the request; route image work to a Gemini, GPT or vision Ollama agent'),
154
+ native_search: nativeSearch('anthropic'),
155
+ },
156
+ openai: {
157
+ delegation: ok(),
158
+ memory_tools: ok(),
159
+ structured_output: ok(),
160
+ thinking_with_tools: degraded(RESPONSES_REASONING_NOTE),
161
+ streaming: ok(),
162
+ vision: ok('test', 'user-turn images only'),
163
+ native_search: nativeSearch('openai'),
164
+ },
165
+ xai: {
166
+ delegation: ok(),
167
+ memory_tools: ok(),
168
+ structured_output: ok(),
169
+ thinking_with_tools: degraded(RESPONSES_REASONING_NOTE),
170
+ streaming: ok(),
171
+ vision: ok('test', 'user-turn images only'),
172
+ native_search: nativeSearch('xai'),
173
+ },
174
+ ollama: {
175
+ delegation: ok(),
176
+ memory_tools: ok(),
177
+ structured_output: degraded('JSON mode only (json_object): the output is JSON but the schema is not enforced'),
178
+ thinking_with_tools: degraded(CHAT_THINKING_NOTE),
179
+ streaming: ok(),
180
+ vision: ok('test', 'needs a vision model, e.g. ollama/qwen3-vl:8b'),
181
+ native_search: nativeSearch('ollama'),
182
+ },
183
+ gateway: {
184
+ delegation: ok(),
185
+ memory_tools: ok(),
186
+ structured_output: ok('test', 'strict json_schema; upstream support varies by model'),
187
+ thinking_with_tools: degraded(CHAT_THINKING_NOTE),
188
+ streaming: ok(),
189
+ vision: ok('test', 'upstream model must accept images'),
190
+ native_search: unsupported('a gateway cannot enable upstream native search; the web_search sentinel is dropped'),
191
+ },
192
+ };
193
+ /** The matrix cell for a model on the path it will actually take. */
194
+ export function capabilityOf(model, capability, opts = {}) {
195
+ const plan = planTransport(model, opts);
196
+ const row = plan.transport === 'gateway' ? 'gateway' : plan.provider;
197
+ if (row !== 'gateway' && row !== 'ollama') {
198
+ const cell = platformCell(row, directPlatform(row), capability);
199
+ if (cell)
200
+ return { row, ...cell };
201
+ }
202
+ return { row, ...CAPABILITY_MATRIX[row][capability] };
203
+ }
204
+ export function requiredCapabilities(agent) {
205
+ const tools = agent.tools ?? [];
206
+ const needs = new Set();
207
+ if (agent.delegates)
208
+ needs.add('delegation');
209
+ if (tools.includes('load_memory'))
210
+ needs.add('memory_tools');
211
+ if (agent.outputSchema)
212
+ needs.add('structured_output');
213
+ const thinking = agent.generateContentConfig?.thinkingConfig;
214
+ const thinks = !!thinking && (thinking.thinkingBudget ?? 0) !== 0;
215
+ if (thinks && (tools.length > 0 || agent.delegates))
216
+ needs.add('thinking_with_tools');
217
+ if (tools.includes('web_search'))
218
+ needs.add('native_search');
219
+ return [...needs];
220
+ }
221
+ /** The capabilities an agent needs that its resolved path does not fully give it. */
222
+ export function capabilityGaps(model, agent, opts = {}) {
223
+ const gaps = [];
224
+ for (const capability of requiredCapabilities(agent)) {
225
+ const cell = capabilityOf(model, capability, opts);
226
+ if (cell.support === 'supported')
227
+ continue;
228
+ gaps.push({ capability, support: cell.support, row: cell.row, ...(cell.note ? { note: cell.note } : {}) });
229
+ }
230
+ return gaps;
231
+ }
232
+ // ── Cloud platforms (ADR 0023) ───────────────────────────────────────────────
233
+ //
234
+ // On Vertex AI, Bedrock and Azure OpenAI the adapter sends the same request
235
+ // as on the provider's own API (the vendor SDK's platform client speaks the
236
+ // same dialect), so every cell is the provider's row, except where the
237
+ // adapter deliberately sends less. Native search is the one such cell today.
238
+ /** The cell that differs from the provider's row on a platform, or undefined. */
239
+ export function platformCell(provider, platform, capability) {
240
+ if (platform === 'direct' || capability !== 'native_search')
241
+ return undefined;
242
+ if (nativeSearchOn(provider, platform))
243
+ return undefined;
244
+ return unsupported(`not sent on ${PLATFORM_LABEL[platform]}; the web_search sentinel is dropped (use web_extract)`);
245
+ }
246
+ const PLATFORM_LABEL = {
247
+ direct: 'the provider API',
248
+ vertex: 'Vertex AI',
249
+ bedrock: 'Bedrock',
250
+ azure: 'Azure OpenAI',
251
+ };
252
+ const SUPPORT_MARK = { supported: '✓', degraded: '◐', unsupported: '✗' };
253
+ /** The matrix as a Markdown table plus notes, for documentation and the doctor. */
254
+ export function renderCapabilityMatrix() {
255
+ const rows = Object.keys(CAPABILITY_MATRIX);
256
+ const label = (r) => (r === 'gateway' ? 'Gateway (any id)' : PROVIDERS[r].label);
257
+ const lines = [];
258
+ lines.push(`| Capability | ${rows.map(label).join(' | ')} |`);
259
+ lines.push(`|---|${rows.map(() => '---').join('|')}|`);
260
+ const notes = [];
261
+ for (const cap of CAPABILITIES) {
262
+ const cells = rows.map((r) => {
263
+ const cell = CAPABILITY_MATRIX[r][cap];
264
+ if (!cell.note)
265
+ return SUPPORT_MARK[cell.support];
266
+ notes.push(`${label(r)} · ${CAPABILITY_LABELS[cap]}: ${cell.note}.`);
267
+ return `${SUPPORT_MARK[cell.support]}${notes.length}`;
268
+ });
269
+ lines.push(`| ${CAPABILITY_LABELS[cap]} | ${cells.join(' | ')} |`);
270
+ }
271
+ lines.push('');
272
+ lines.push('✓ supported · ◐ degraded · ✗ unsupported. Gemini cells are ADK\'s own adapter; every other cell is asserted against the request the adapter sends.');
273
+ lines.push('');
274
+ notes.forEach((n, i) => lines.push(`${i + 1}. ${n}`));
275
+ lines.push('');
276
+ lines.push('**Cloud platforms** (ADR 0023): the same adapter and request as the provider\'s own API, except as listed. These paths are tested against mocks, not against the live clouds.');
277
+ lines.push('');
278
+ lines.push('| Path | Differs from the provider row |');
279
+ lines.push('|---|---|');
280
+ for (const provider of Object.keys(PLATFORMS_FOR)) {
281
+ for (const platform of PLATFORMS_FOR[provider]) {
282
+ if (platform === 'direct')
283
+ continue;
284
+ const diffs = CAPABILITIES.map((cap) => [cap, platformCell(provider, platform, cap)])
285
+ .filter(([, cell]) => cell)
286
+ .map(([cap, cell]) => `${CAPABILITY_LABELS[cap]}: ${SUPPORT_MARK[cell.support]} ${cell.note ?? ''}`.trim());
287
+ lines.push(`| ${PROVIDERS[provider].label} on ${PLATFORM_LABEL[platform]} | ${diffs.join('; ') || 'nothing'} |`);
288
+ }
289
+ }
290
+ return lines.join('\n');
291
+ }
@@ -48,6 +48,7 @@
48
48
  import { BaseLlm } from '@google/adk';
49
49
  import type { LlmRequest, LlmResponse } from '@google/adk';
50
50
  import type { BaseLlmConnection } from '@google/adk';
51
+ import type { ProviderEndpoint } from './endpoints.ts';
51
52
  /** ADK toolsDict → Anthropic tool definitions (lowercase schemas; the
52
53
  * web_search sentinel becomes Anthropic's native server tool). */
53
54
  export declare function buildAnthropicTools(llmRequest: LlmRequest): any[];
@@ -58,9 +59,17 @@ export declare class ClaudeLlm extends BaseLlm {
58
59
  */
59
60
  static readonly supportedModels: Array<string | RegExp>;
60
61
  private apiKey?;
61
- constructor({ model, apiKey }: {
62
+ private endpoint?;
63
+ private searchDropWarned;
64
+ /**
65
+ * `endpoint` (ADR 0023): where the request goes — Anthropic's API (or a
66
+ * proxy at its base URL), Bedrock, or Vertex AI. Default: the environment's
67
+ * (`ANTHROPIC_PLATFORM`).
68
+ */
69
+ constructor({ model, apiKey, endpoint }: {
62
70
  model: string;
63
71
  apiKey?: string;
72
+ endpoint?: ProviderEndpoint;
64
73
  });
65
74
  /**
66
75
  * Main generation method called by the ADK Runner for every turn.
@@ -46,9 +46,11 @@
46
46
  * translated to Anthropic's tool_use / tool_result blocks.
47
47
  */
48
48
  import { BaseLlm, LLMRegistry } from '@google/adk';
49
- import { traceLlmGeneration, setLlmSpanAttribute, } from "../observability/tracer.js";
50
- import { wantsWebSearch, isWebSearchSentinel, } from "../tools/webSearchTool.js";
51
- import { toLowercaseJsonSchema } from "./schemaNormalize.js";
49
+ import { traceLlmGeneration, setLlmSpanAttribute, } from '../observability/tracer.js';
50
+ import { wantsWebSearch, isWebSearchSentinel, } from '../tools/webSearchTool.js';
51
+ import { providerRequestOptions } from '../runtime/turnControl.js';
52
+ import { toLowercaseJsonSchema, toolDeclarationFor } from './schemaNormalize.js';
53
+ import { claudeClientSpec, endpointFromEnv, instantiateClient, nativeSearchOn, platformModel, SdkMissingError } from './endpoints.js';
52
54
  /** Anthropic's minimum extended-thinking budget. */
53
55
  /** Name of the synthetic tool that carries an ADK outputSchema answer. */
54
56
  const STRUCTURED_OUTPUT_TOOL = 'structured_output';
@@ -61,12 +63,12 @@ export function buildAnthropicTools(llmRequest) {
61
63
  for (const [, tool] of Object.entries(llmRequest.toolsDict ?? {})) {
62
64
  if (isWebSearchSentinel(tool))
63
65
  continue; // added as a server tool below
64
- const t = tool;
65
- if (t.name && t.description) {
66
+ const decl = toolDeclarationFor(tool);
67
+ if (decl) {
66
68
  anthropicTools.push({
67
- name: t.name,
68
- description: t.description,
69
- input_schema: toLowercaseJsonSchema(t.parameters ?? { type: 'object', properties: {} }),
69
+ name: decl.name,
70
+ description: decl.description,
71
+ input_schema: decl.parameters,
70
72
  });
71
73
  }
72
74
  }
@@ -89,9 +91,17 @@ export class ClaudeLlm extends BaseLlm {
89
91
  */
90
92
  static supportedModels = [/^claude-.+/];
91
93
  apiKey;
92
- constructor({ model, apiKey }) {
94
+ endpoint;
95
+ searchDropWarned = false;
96
+ /**
97
+ * `endpoint` (ADR 0023): where the request goes — Anthropic's API (or a
98
+ * proxy at its base URL), Bedrock, or Vertex AI. Default: the environment's
99
+ * (`ANTHROPIC_PLATFORM`).
100
+ */
101
+ constructor({ model, apiKey, endpoint }) {
93
102
  super({ model });
94
103
  this.apiKey = apiKey;
104
+ this.endpoint = endpoint;
95
105
  }
96
106
  /**
97
107
  * Main generation method called by the ADK Runner for every turn.
@@ -105,29 +115,36 @@ export class ClaudeLlm extends BaseLlm {
105
115
  yield* traceLlmGeneration({ provider: 'anthropic', model: this.model, llmRequest }, this.generateInner(llmRequest, stream));
106
116
  }
107
117
  async *generateInner(llmRequest, stream) {
108
- const apiKey = this.apiKey || process.env.ANTHROPIC_API_KEY;
109
- if (!apiKey) {
118
+ let endpoint;
119
+ try {
120
+ endpoint = this.endpoint ?? endpointFromEnv('anthropic');
121
+ }
122
+ catch (err) {
123
+ yield { errorCode: 'ENDPOINT_MISCONFIGURED', errorMessage: err.message };
124
+ return;
125
+ }
126
+ const spec = claudeClientSpec(endpoint, this.apiKey || endpoint.apiKey || process.env.ANTHROPIC_API_KEY);
127
+ if ('error' in spec) {
110
128
  yield {
111
- errorCode: 'MISSING_API_KEY',
112
- errorMessage: 'ANTHROPIC_API_KEY is not set in environment.',
129
+ errorCode: endpoint.platform === 'direct' ? 'MISSING_API_KEY' : 'ENDPOINT_MISCONFIGURED',
130
+ errorMessage: spec.error,
113
131
  };
114
132
  return;
115
133
  }
116
- // Dynamic import — only loads @anthropic-ai/sdk when actually called,
117
- // so the framework boots without the SDK installed for Gemini-only users.
118
- let Anthropic;
134
+ // Dynamic import — only loads the SDK (Anthropic's, or its Bedrock or
135
+ // Vertex AI client) when actually called, so the framework boots without
136
+ // it for users of other providers. All three share the Messages API.
137
+ let client;
119
138
  try {
120
- const mod = await import('@anthropic-ai/sdk');
121
- Anthropic = mod.default ?? mod.Anthropic;
139
+ client = await instantiateClient(spec);
122
140
  }
123
- catch {
141
+ catch (err) {
124
142
  yield {
125
- errorCode: 'SDK_NOT_INSTALLED',
126
- errorMessage: 'The @anthropic-ai/sdk package is not installed. Run: npm install @anthropic-ai/sdk',
143
+ errorCode: err instanceof SdkMissingError ? 'SDK_NOT_INSTALLED' : 'ENDPOINT_MISCONFIGURED',
144
+ errorMessage: err.message,
127
145
  };
128
146
  return;
129
147
  }
130
- const client = new Anthropic({ apiKey });
131
148
  // ── Translate ADK Contents → Anthropic messages ──────────────────────────
132
149
  const systemParts = [];
133
150
  const messages = [];
@@ -186,9 +203,22 @@ export class ClaudeLlm extends BaseLlm {
186
203
  systemParts.unshift(text);
187
204
  }
188
205
  // ── Build Anthropic tool definitions from ADK toolsDict ──────────────────
189
- const anthropicTools = buildAnthropicTools(llmRequest);
206
+ let anthropicTools = buildAnthropicTools(llmRequest);
190
207
  if (wantsWebSearch(llmRequest)) {
191
- setLlmSpanAttribute('llm.web_search.native', true);
208
+ if (nativeSearchOn('anthropic', endpoint.platform)) {
209
+ setLlmSpanAttribute('llm.web_search.native', true);
210
+ }
211
+ else {
212
+ // Not sent off Anthropic's own API (lib/models/endpoints.ts); the
213
+ // doctor and the capability matrix state this before any request.
214
+ anthropicTools = anthropicTools.filter((t) => t.type !== 'web_search_20250305');
215
+ setLlmSpanAttribute('llm.web_search.omitted', true);
216
+ setLlmSpanAttribute('llm.capability.dropped', 'web_search');
217
+ if (!this.searchDropWarned) {
218
+ this.searchDropWarned = true;
219
+ console.warn(`⚠ web_search is not sent to Claude on ${endpoint.platform}; the agent answers without it (use web_extract).`);
220
+ }
221
+ }
192
222
  }
193
223
  // ── Extended thinking (Gemini thinkingConfig → Anthropic thinking) ───────
194
224
  const cfg = llmRequest.config ?? {};
@@ -219,7 +249,7 @@ export class ClaudeLlm extends BaseLlm {
219
249
  : undefined;
220
250
  const allTools = structuredTool ? [...anthropicTools, structuredTool] : anthropicTools;
221
251
  const requestBase = {
222
- model: this.model,
252
+ model: platformModel(endpoint, this.model),
223
253
  max_tokens: maxTokens,
224
254
  system: systemParts.join('\n\n') || undefined,
225
255
  messages,
@@ -231,7 +261,7 @@ export class ClaudeLlm extends BaseLlm {
231
261
  try {
232
262
  if (stream) {
233
263
  // Streaming path
234
- const streamResponse = await client.messages.stream(requestBase);
264
+ const streamResponse = await client.messages.stream(requestBase, providerRequestOptions());
235
265
  for await (const chunk of streamResponse) {
236
266
  if (chunk.type === 'content_block_delta') {
237
267
  if (chunk.delta.type === 'text_delta') {
@@ -266,7 +296,7 @@ export class ClaudeLlm extends BaseLlm {
266
296
  }
267
297
  else {
268
298
  // Non-streaming path
269
- const response = await client.messages.create(requestBase);
299
+ const response = await client.messages.create(requestBase, providerRequestOptions());
270
300
  // Thinking first, as a display-only partial.
271
301
  const thinkingText = (response.content ?? [])
272
302
  .filter((b) => b.type === 'thinking')
@@ -0,0 +1,100 @@
1
+ /**
2
+ * lib/models/endpoints.ts — where each provider's requests go and how they
3
+ * authenticate (ADR 0023).
4
+ *
5
+ * The model id still picks the provider (lib/models/providerMap.ts). This
6
+ * module picks the provider's PLATFORM: its own public API, or the cloud an
7
+ * enterprise reaches it through, plus a base URL for an internal proxy and a
8
+ * map from the YAML id to the id (or deployment) that platform knows.
9
+ *
10
+ * provider platforms credential on a cloud platform
11
+ * gemini direct | vertex Google Application Default Credentials
12
+ * anthropic direct | bedrock | vertex AWS credential chain / Google ADC
13
+ * openai direct | azure AZURE_OPENAI_API_KEY, else Entra ID
14
+ * xai, ollama direct only
15
+ *
16
+ * Configuration comes from the environment (`endpointFromEnv`) or, per
17
+ * request, from the server's `credentials` plug point, which may return an
18
+ * API key or a partial endpoint merged over the environment's.
19
+ *
20
+ * The Vertex AI, Bedrock and Azure paths are built on each vendor SDK's own
21
+ * client for that platform and are tested against mocks; they have not been
22
+ * run against the live clouds from this repository. The doctor says so.
23
+ */
24
+ import type { ProviderId } from './providerMap.ts';
25
+ export type Platform = 'direct' | 'vertex' | 'bedrock' | 'azure';
26
+ export declare const PLATFORMS_FOR: Record<ProviderId, readonly Platform[]>;
27
+ export interface ProviderEndpoint {
28
+ platform: Platform;
29
+ /** direct: an OpenAI- or Anthropic-compatible proxy in front of the vendor API. */
30
+ baseURL?: string;
31
+ /** A static key: the environment's, a caller's, or one a secret manager returned. */
32
+ apiKey?: string;
33
+ /** A bearer-token source called per client (Entra ID, a secret manager). openai/azure only. */
34
+ token?: () => Promise<string>;
35
+ /** vertex: the Google Cloud project and location (region, or `global`). */
36
+ project?: string;
37
+ location?: string;
38
+ /** bedrock: the AWS region. */
39
+ region?: string;
40
+ /** YAML model id → the id or deployment name this platform uses. Unlisted ids pass through. */
41
+ models?: Record<string, string>;
42
+ }
43
+ /** The platform the environment selects for a provider (default direct). */
44
+ export declare function platformFromEnv(provider: ProviderId, env?: NodeJS.ProcessEnv): Platform;
45
+ /** A provider's endpoint as the environment configures it. */
46
+ export declare function endpointFromEnv(provider: ProviderId, env?: NodeJS.ProcessEnv): ProviderEndpoint;
47
+ /** Azure OpenAI's v1 API, which the plain OpenAI client speaks. */
48
+ export declare function azureBaseURL(endpoint: string): string;
49
+ /** The id to send: the platform's name for a YAML model id. */
50
+ export declare function platformModel(endpoint: ProviderEndpoint | undefined, model: string): string;
51
+ /** What is missing for an endpoint to be usable; empty when complete. */
52
+ export declare function endpointProblems(provider: ProviderId, e: ProviderEndpoint): string[];
53
+ /**
54
+ * True when a provider can be called: its key is present on the direct
55
+ * platform, or its cloud platform is fully configured (the cloud's own
56
+ * credential chain is then trusted to authenticate; the doctor says it was
57
+ * not checked).
58
+ */
59
+ export declare function providerReady(provider: ProviderId): boolean;
60
+ /** Merge a per-request endpoint (the credentials plug point) over the environment's. */
61
+ export declare function mergeEndpoint(provider: ProviderId, override: Partial<ProviderEndpoint> | undefined): ProviderEndpoint;
62
+ /** The label for logs and the doctor: "Claude on Bedrock (us-east-1)". */
63
+ export declare function endpointLabel(provider: ProviderId, e: ProviderEndpoint): string;
64
+ /** How a cloud platform authenticates, for the doctor. */
65
+ export declare function credentialSource(provider: ProviderId, e: ProviderEndpoint): string;
66
+ /** The vendor SDK package a platform needs, beyond the direct one. */
67
+ export declare const PLATFORM_SDK: Partial<Record<`${ProviderId}/${Platform}`, string>>;
68
+ /** Which SDK class to construct, with what options. */
69
+ export interface ClientSpec {
70
+ module: string;
71
+ /** Export names tried in order (default export first for the direct SDKs). */
72
+ exportNames: string[];
73
+ options: Record<string, unknown>;
74
+ }
75
+ export type SdkImporter = (module: string) => Promise<any>;
76
+ /**
77
+ * Replace how vendor SDKs are imported: for tests (a mock Bedrock client)
78
+ * and for bundlers that need static imports. Undefined restores the default.
79
+ */
80
+ export declare function setSdkImporter(importer: SdkImporter | undefined): void;
81
+ /** Import a spec's module and construct its client. Throws SdkMissingError when absent. */
82
+ export declare function instantiateClient(spec: ClientSpec): Promise<any>;
83
+ export declare class SdkMissingError extends Error {
84
+ readonly module: string;
85
+ constructor(module: string);
86
+ }
87
+ /** The Anthropic client for an endpoint, or why it cannot be built. */
88
+ export declare function claudeClientSpec(e: ProviderEndpoint, apiKey: string | undefined): ClientSpec | {
89
+ error: string;
90
+ };
91
+ /** The scope Azure OpenAI tokens are issued for. */
92
+ export declare const AZURE_COGNITIVE_SCOPE = "https://cognitiveservices.azure.com/.default";
93
+ /**
94
+ * A bearer-token source from Entra ID (DefaultAzureCredential: managed
95
+ * identity, workload identity, the Azure CLI...), shared by every client in
96
+ * the process so tokens are cached and refreshed once.
97
+ */
98
+ export declare function entraTokenSource(): () => Promise<string>;
99
+ /** Platforms whose adapter does not send the provider's native web search. */
100
+ export declare function nativeSearchOn(provider: ProviderId, platform: Platform): boolean;