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,282 @@
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
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
25
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
26
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
27
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
28
+ });
29
+ }
30
+ return path;
31
+ };
32
+ import { PROVIDERS, providerKeyPresent } from './providerMap.js';
33
+ export const PLATFORMS_FOR = {
34
+ gemini: ['direct', 'vertex'],
35
+ anthropic: ['direct', 'bedrock', 'vertex'],
36
+ openai: ['direct', 'azure'],
37
+ xai: ['direct'],
38
+ ollama: ['direct'],
39
+ };
40
+ const PLATFORM_ENV = {
41
+ gemini: 'GEMINI_PLATFORM',
42
+ anthropic: 'ANTHROPIC_PLATFORM',
43
+ openai: 'OPENAI_PLATFORM',
44
+ };
45
+ const MODEL_MAP_ENV = {
46
+ gemini: 'GEMINI_MODEL_MAP',
47
+ anthropic: 'ANTHROPIC_MODEL_MAP',
48
+ openai: 'OPENAI_MODEL_MAP',
49
+ };
50
+ const trimmed = (v) => (v && v.trim() ? v.trim() : undefined);
51
+ function parseModelMap(raw, name) {
52
+ if (!trimmed(raw))
53
+ return undefined;
54
+ let parsed;
55
+ try {
56
+ parsed = JSON.parse(raw);
57
+ }
58
+ catch {
59
+ throw new Error(`${name} must be a JSON object of model id → platform id.`);
60
+ }
61
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || Object.values(parsed).some((v) => typeof v !== 'string')) {
62
+ throw new Error(`${name} must be a JSON object of model id → platform id.`);
63
+ }
64
+ return parsed;
65
+ }
66
+ /** The platform the environment selects for a provider (default direct). */
67
+ export function platformFromEnv(provider, env = process.env) {
68
+ const name = PLATFORM_ENV[provider];
69
+ const raw = name ? trimmed(env[name])?.toLowerCase() : undefined;
70
+ // @google/genai's own switch selects Vertex too, so honour it rather than
71
+ // run a Vertex-configured process against AI Studio.
72
+ if (!raw && provider === 'gemini' && /^(1|true)$/i.test(env.GOOGLE_GENAI_USE_VERTEXAI ?? ''))
73
+ return 'vertex';
74
+ if (!raw)
75
+ return 'direct';
76
+ if (!PLATFORMS_FOR[provider].includes(raw)) {
77
+ throw new Error(`${name}=${raw} is not a platform for ${PROVIDERS[provider].label}; use one of ${PLATFORMS_FOR[provider].join(', ')}.`);
78
+ }
79
+ return raw;
80
+ }
81
+ /** A provider's endpoint as the environment configures it. */
82
+ export function endpointFromEnv(provider, env = process.env) {
83
+ const platform = platformFromEnv(provider, env);
84
+ const mapEnv = MODEL_MAP_ENV[provider];
85
+ const models = mapEnv ? parseModelMap(env[mapEnv], mapEnv) : undefined;
86
+ const base = { platform, ...(models ? { models } : {}) };
87
+ switch (`${provider}/${platform}`) {
88
+ case 'gemini/vertex':
89
+ return { ...base, project: trimmed(env.GOOGLE_CLOUD_PROJECT), location: trimmed(env.GOOGLE_CLOUD_LOCATION) };
90
+ case 'anthropic/vertex':
91
+ return {
92
+ ...base,
93
+ project: trimmed(env.ANTHROPIC_VERTEX_PROJECT_ID) ?? trimmed(env.GOOGLE_CLOUD_PROJECT),
94
+ location: trimmed(env.CLOUD_ML_REGION) ?? trimmed(env.GOOGLE_CLOUD_LOCATION),
95
+ };
96
+ case 'anthropic/bedrock':
97
+ return { ...base, region: trimmed(env.AWS_REGION) ?? trimmed(env.AWS_DEFAULT_REGION) };
98
+ case 'anthropic/direct':
99
+ return { ...base, ...(trimmed(env.ANTHROPIC_BASE_URL) ? { baseURL: trimmed(env.ANTHROPIC_BASE_URL) } : {}) };
100
+ case 'openai/direct':
101
+ return { ...base, ...(trimmed(env.OPENAI_BASE_URL) ? { baseURL: trimmed(env.OPENAI_BASE_URL) } : {}) };
102
+ case 'openai/azure': {
103
+ const endpoint = trimmed(env.AZURE_OPENAI_ENDPOINT);
104
+ const key = trimmed(env.AZURE_OPENAI_API_KEY);
105
+ return {
106
+ ...base,
107
+ ...(endpoint ? { baseURL: azureBaseURL(endpoint) } : {}),
108
+ ...(key ? { apiKey: key } : {}),
109
+ };
110
+ }
111
+ default:
112
+ return base;
113
+ }
114
+ }
115
+ /** Azure OpenAI's v1 API, which the plain OpenAI client speaks. */
116
+ export function azureBaseURL(endpoint) {
117
+ const root = endpoint.replace(/\/+$/, '').replace(/\/openai(\/v1)?$/, '');
118
+ return `${root}/openai/v1/`;
119
+ }
120
+ /** The id to send: the platform's name for a YAML model id. */
121
+ export function platformModel(endpoint, model) {
122
+ return endpoint?.models?.[model] ?? model;
123
+ }
124
+ /** What is missing for an endpoint to be usable; empty when complete. */
125
+ export function endpointProblems(provider, e) {
126
+ const problems = [];
127
+ switch (`${provider}/${e.platform}`) {
128
+ case 'gemini/vertex':
129
+ case 'anthropic/vertex':
130
+ if (!e.project)
131
+ problems.push(provider === 'gemini' ? 'GOOGLE_CLOUD_PROJECT not set' : 'ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) not set');
132
+ if (!e.location)
133
+ problems.push(provider === 'gemini' ? 'GOOGLE_CLOUD_LOCATION not set' : 'CLOUD_ML_REGION (or GOOGLE_CLOUD_LOCATION) not set');
134
+ break;
135
+ case 'anthropic/bedrock':
136
+ if (!e.region)
137
+ problems.push('AWS_REGION not set');
138
+ break;
139
+ case 'openai/azure':
140
+ if (!e.baseURL)
141
+ problems.push('AZURE_OPENAI_ENDPOINT not set');
142
+ break;
143
+ }
144
+ return problems;
145
+ }
146
+ /**
147
+ * True when a provider can be called: its key is present on the direct
148
+ * platform, or its cloud platform is fully configured (the cloud's own
149
+ * credential chain is then trusted to authenticate; the doctor says it was
150
+ * not checked).
151
+ */
152
+ export function providerReady(provider) {
153
+ let e;
154
+ try {
155
+ e = endpointFromEnv(provider);
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ if (e.platform === 'direct')
161
+ return providerKeyPresent(provider);
162
+ return endpointProblems(provider, e).length === 0;
163
+ }
164
+ /** Merge a per-request endpoint (the credentials plug point) over the environment's. */
165
+ export function mergeEndpoint(provider, override) {
166
+ const fromEnv = endpointFromEnv(provider);
167
+ if (!override)
168
+ return fromEnv;
169
+ if (override.platform && override.platform !== fromEnv.platform) {
170
+ // A different platform shares nothing with the environment's settings.
171
+ return { ...override, platform: override.platform };
172
+ }
173
+ return { ...fromEnv, ...override, platform: fromEnv.platform };
174
+ }
175
+ /** The label for logs and the doctor: "Claude on Bedrock (us-east-1)". */
176
+ export function endpointLabel(provider, e) {
177
+ const where = e.platform === 'vertex'
178
+ ? `Vertex AI${e.project ? ` (${e.project}, ${e.location ?? '?'})` : ''}`
179
+ : e.platform === 'bedrock'
180
+ ? `Bedrock${e.region ? ` (${e.region})` : ''}`
181
+ : e.platform === 'azure'
182
+ ? `Azure OpenAI${e.baseURL ? ` (${new URL(e.baseURL).host})` : ''}`
183
+ : e.baseURL
184
+ ? `proxy ${safeHost(e.baseURL)}`
185
+ : 'direct';
186
+ return `${PROVIDERS[provider].label} on ${where}`;
187
+ }
188
+ function safeHost(url) {
189
+ try {
190
+ return new URL(url).host;
191
+ }
192
+ catch {
193
+ return '(invalid URL)';
194
+ }
195
+ }
196
+ /** How a cloud platform authenticates, for the doctor. */
197
+ export function credentialSource(provider, e) {
198
+ if (e.platform === 'vertex')
199
+ return 'Google Application Default Credentials';
200
+ if (e.platform === 'bedrock')
201
+ return 'the AWS credential chain';
202
+ if (e.platform === 'azure')
203
+ return e.apiKey ? 'AZURE_OPENAI_API_KEY' : e.token ? 'a token callback' : 'Entra ID (@azure/identity)';
204
+ return PROVIDERS[provider].keyEnv ?? 'none (local)';
205
+ }
206
+ /** The vendor SDK package a platform needs, beyond the direct one. */
207
+ export const PLATFORM_SDK = {
208
+ 'anthropic/bedrock': '@anthropic-ai/bedrock-sdk',
209
+ 'anthropic/vertex': '@anthropic-ai/vertex-sdk',
210
+ };
211
+ let sdkImporter = (module) => import(__rewriteRelativeImportExtension(module));
212
+ /**
213
+ * Replace how vendor SDKs are imported: for tests (a mock Bedrock client)
214
+ * and for bundlers that need static imports. Undefined restores the default.
215
+ */
216
+ export function setSdkImporter(importer) {
217
+ sdkImporter = importer ?? ((module) => import(__rewriteRelativeImportExtension(module)));
218
+ }
219
+ /** Import a spec's module and construct its client. Throws SdkMissingError when absent. */
220
+ export async function instantiateClient(spec) {
221
+ let mod;
222
+ try {
223
+ mod = await sdkImporter(spec.module);
224
+ }
225
+ catch {
226
+ throw new SdkMissingError(spec.module);
227
+ }
228
+ const Ctor = spec.exportNames.map((n) => mod?.[n]).find((c) => typeof c === 'function');
229
+ if (!Ctor)
230
+ throw new Error(`${spec.module} exports none of ${spec.exportNames.join(', ')}.`);
231
+ return new Ctor(spec.options);
232
+ }
233
+ export class SdkMissingError extends Error {
234
+ module;
235
+ constructor(module) {
236
+ super(`The ${module} package is not installed. Run: npm install ${module}`);
237
+ this.module = module;
238
+ }
239
+ }
240
+ /** The Anthropic client for an endpoint, or why it cannot be built. */
241
+ export function claudeClientSpec(e, apiKey) {
242
+ const problems = endpointProblems('anthropic', e);
243
+ if (problems.length)
244
+ return { error: `Claude on ${e.platform}: ${problems.join('; ')}.` };
245
+ switch (e.platform) {
246
+ case 'bedrock':
247
+ return { module: '@anthropic-ai/bedrock-sdk', exportNames: ['AnthropicBedrock', 'default'], options: { awsRegion: e.region } };
248
+ case 'vertex':
249
+ return { module: '@anthropic-ai/vertex-sdk', exportNames: ['AnthropicVertex', 'default'], options: { projectId: e.project, region: e.location } };
250
+ default:
251
+ if (!apiKey)
252
+ return { error: 'ANTHROPIC_API_KEY is not set in environment.' };
253
+ return { module: '@anthropic-ai/sdk', exportNames: ['default', 'Anthropic'], options: { apiKey, ...(e.baseURL ? { baseURL: e.baseURL } : {}) } };
254
+ }
255
+ }
256
+ /** The scope Azure OpenAI tokens are issued for. */
257
+ export const AZURE_COGNITIVE_SCOPE = 'https://cognitiveservices.azure.com/.default';
258
+ let entraProvider;
259
+ /**
260
+ * A bearer-token source from Entra ID (DefaultAzureCredential: managed
261
+ * identity, workload identity, the Azure CLI...), shared by every client in
262
+ * the process so tokens are cached and refreshed once.
263
+ */
264
+ export function entraTokenSource() {
265
+ return async () => {
266
+ entraProvider ??= sdkImporter('@azure/identity').then((m) => m.getBearerTokenProvider(new m.DefaultAzureCredential(), AZURE_COGNITIVE_SCOPE), () => {
267
+ entraProvider = undefined;
268
+ throw new Error('Azure OpenAI without AZURE_OPENAI_API_KEY authenticates with Entra ID through @azure/identity, which is not installed. Run: npm install @azure/identity');
269
+ });
270
+ return (await entraProvider)();
271
+ };
272
+ }
273
+ /** Platforms whose adapter does not send the provider's native web search. */
274
+ export function nativeSearchOn(provider, platform) {
275
+ if (platform === 'direct')
276
+ return true;
277
+ // Gemini grounding is a Vertex AI feature as much as an AI Studio one.
278
+ // Anthropic's server-side web search and OpenAI's web_search tool are not
279
+ // sent on Bedrock, Claude-on-Vertex or Azure: availability there varies by
280
+ // region and version and has not been verified from this repository.
281
+ return provider === 'gemini';
282
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * lib/models/gateway.ts — the optional "one key" transport.
3
+ *
4
+ * WHY this file exists:
5
+ * A newcomer should be able to run every starter-pack syndicate with ONE
6
+ * credential when they choose to. A hosted gateway (Vercel AI Gateway,
7
+ * OpenRouter) fronts OpenAI, Anthropic, Google and xAI behind a single
8
+ * OpenAI-compatible endpoint, so a model id whose direct key is absent can
9
+ * still be served. This module owns that decision and nothing else.
10
+ *
11
+ * THE RULE (plans/model-access-tiers.md, ADR 0012):
12
+ * Direct adapters are canonical. A model id routes to its provider's own
13
+ * adapter whenever that provider's key is present. Only when the key is
14
+ * ABSENT and a gateway is configured does the id route through the
15
+ * gateway. Ollama never does. So the gateway is a fallback, never a
16
+ * preempt: adding a direct key beside the gateway key silently upgrades
17
+ * that provider's agents back to native fidelity (Gemini grounding, xAI
18
+ * search, Anthropic's server-side web_search) with no YAML change.
19
+ *
20
+ * WHAT A GATEWAY LOSES — reported, never silent:
21
+ * Every server-side tool sentinel (web_search, google_search, x_search,
22
+ * collections_search) is dropped on the gateway path, because a
23
+ * chat-completions endpoint has no uniform way to enable a provider's
24
+ * native search. lib/models/capabilities.ts turns that into a per-agent
25
+ * report the doctor, the A2A startup log and the ledger all read.
26
+ *
27
+ * WHY a leaf (imports providerMap and endpoints only):
28
+ * The registry, the gateway adapter, the capability report and the
29
+ * doctor all need this decision; keeping it free of adapter imports
30
+ * avoids the cycle model-routing warns about.
31
+ *
32
+ * Env (deployment config, never YAML):
33
+ * MODEL_GATEWAY vercel | openrouter (unset = off, the default)
34
+ * MODEL_GATEWAY_API_KEY the gateway's key
35
+ * MODEL_GATEWAY_BASE_URL optional endpoint override (self-hosted proxies
36
+ * that speak the same dialect, e.g. LiteLLM)
37
+ * MODEL_GATEWAY_MODEL_MAP optional "from=to,from=to" wire-name overrides
38
+ * for ids the default mapper gets wrong
39
+ */
40
+ import type { ProviderId } from './providerMap.ts';
41
+ export type GatewayId = 'vercel' | 'openrouter';
42
+ /** Providers a gateway can front (everything cloud; never Ollama). */
43
+ export type CloudProviderId = Exclude<ProviderId, 'ollama'>;
44
+ export interface GatewayInfo {
45
+ id: GatewayId;
46
+ label: string;
47
+ /** OpenAI-compatible base URL (chat/completions is appended). */
48
+ baseUrl: string;
49
+ /** Where the operator creates the key. */
50
+ consoleUrl: string;
51
+ /** The gateway's slug for each upstream provider, e.g. "x-ai" vs "xai". */
52
+ slugs: Record<CloudProviderId, string>;
53
+ }
54
+ export declare const GATEWAY_ENV = "MODEL_GATEWAY";
55
+ export declare const GATEWAY_KEY_ENV = "MODEL_GATEWAY_API_KEY";
56
+ export declare const GATEWAY_BASE_URL_ENV = "MODEL_GATEWAY_BASE_URL";
57
+ export declare const GATEWAY_MODEL_MAP_ENV = "MODEL_GATEWAY_MODEL_MAP";
58
+ export declare const GATEWAYS: Record<GatewayId, GatewayInfo>;
59
+ export interface GatewayConfig {
60
+ gateway: GatewayInfo;
61
+ /** Effective base URL (env override or the gateway's default). */
62
+ baseUrl: string;
63
+ keyPresent: boolean;
64
+ }
65
+ /**
66
+ * The configured gateway, or null when MODEL_GATEWAY is unset. A set but
67
+ * unknown value also yields null — `gatewayProblem()` says why, so the
68
+ * doctor and the registry log can name the mistake instead of routing
69
+ * silently to nothing.
70
+ */
71
+ export declare function gatewayConfig(): GatewayConfig | null;
72
+ /** A human-readable misconfiguration, or undefined when the gateway is
73
+ * either off or usable. */
74
+ export declare function gatewayProblem(): string | undefined;
75
+ /** True when a gateway is configured well enough to serve requests. */
76
+ export declare function gatewayUsable(): boolean;
77
+ /**
78
+ * The id a gateway expects for a YAML model id.
79
+ *
80
+ * Gateways prefix every id with the upstream provider's slug and spell
81
+ * Anthropic's version segment with a dot ("claude-sonnet-4-6" is
82
+ * "anthropic/claude-sonnet-4.6" on both Vercel and OpenRouter). Gemini,
83
+ * GPT and Grok ids already carry dots, so only the prefix is added. An id
84
+ * the mapper gets wrong is fixed in MODEL_GATEWAY_MODEL_MAP, not in code.
85
+ */
86
+ export declare function gatewayWireModel(model: string, gateway: GatewayInfo): string;
87
+ export type Transport = 'direct' | 'gateway';
88
+ export interface TransportPlan {
89
+ provider: ProviderId;
90
+ transport: Transport;
91
+ /** Set when transport is 'gateway'. */
92
+ gateway?: GatewayInfo;
93
+ /** True when this transport has a credential (or needs none). */
94
+ funded: boolean;
95
+ /** The direct key env, for the "set X to unlock" message. */
96
+ keyEnv: string | null;
97
+ }
98
+ /**
99
+ * Where a model id will be served from under the current environment.
100
+ *
101
+ * `callerKey` is the A2A BYOK case: a per-request key the caller sent for
102
+ * its own provider counts as that provider's key being present, so a BYOK
103
+ * request never falls through to the operator's gateway.
104
+ */
105
+ export declare function planTransport(model: string, opts?: {
106
+ callerKey?: boolean;
107
+ }): TransportPlan;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * lib/models/gateway.ts — the optional "one key" transport.
3
+ *
4
+ * WHY this file exists:
5
+ * A newcomer should be able to run every starter-pack syndicate with ONE
6
+ * credential when they choose to. A hosted gateway (Vercel AI Gateway,
7
+ * OpenRouter) fronts OpenAI, Anthropic, Google and xAI behind a single
8
+ * OpenAI-compatible endpoint, so a model id whose direct key is absent can
9
+ * still be served. This module owns that decision and nothing else.
10
+ *
11
+ * THE RULE (plans/model-access-tiers.md, ADR 0012):
12
+ * Direct adapters are canonical. A model id routes to its provider's own
13
+ * adapter whenever that provider's key is present. Only when the key is
14
+ * ABSENT and a gateway is configured does the id route through the
15
+ * gateway. Ollama never does. So the gateway is a fallback, never a
16
+ * preempt: adding a direct key beside the gateway key silently upgrades
17
+ * that provider's agents back to native fidelity (Gemini grounding, xAI
18
+ * search, Anthropic's server-side web_search) with no YAML change.
19
+ *
20
+ * WHAT A GATEWAY LOSES — reported, never silent:
21
+ * Every server-side tool sentinel (web_search, google_search, x_search,
22
+ * collections_search) is dropped on the gateway path, because a
23
+ * chat-completions endpoint has no uniform way to enable a provider's
24
+ * native search. lib/models/capabilities.ts turns that into a per-agent
25
+ * report the doctor, the A2A startup log and the ledger all read.
26
+ *
27
+ * WHY a leaf (imports providerMap and endpoints only):
28
+ * The registry, the gateway adapter, the capability report and the
29
+ * doctor all need this decision; keeping it free of adapter imports
30
+ * avoids the cycle model-routing warns about.
31
+ *
32
+ * Env (deployment config, never YAML):
33
+ * MODEL_GATEWAY vercel | openrouter (unset = off, the default)
34
+ * MODEL_GATEWAY_API_KEY the gateway's key
35
+ * MODEL_GATEWAY_BASE_URL optional endpoint override (self-hosted proxies
36
+ * that speak the same dialect, e.g. LiteLLM)
37
+ * MODEL_GATEWAY_MODEL_MAP optional "from=to,from=to" wire-name overrides
38
+ * for ids the default mapper gets wrong
39
+ */
40
+ import { providerReady } from './endpoints.js';
41
+ import { PROVIDERS, providerForModel } from './providerMap.js';
42
+ export const GATEWAY_ENV = 'MODEL_GATEWAY';
43
+ export const GATEWAY_KEY_ENV = 'MODEL_GATEWAY_API_KEY';
44
+ export const GATEWAY_BASE_URL_ENV = 'MODEL_GATEWAY_BASE_URL';
45
+ export const GATEWAY_MODEL_MAP_ENV = 'MODEL_GATEWAY_MODEL_MAP';
46
+ export const GATEWAYS = {
47
+ vercel: {
48
+ id: 'vercel',
49
+ label: 'Vercel AI Gateway',
50
+ baseUrl: 'https://ai-gateway.vercel.sh/v1',
51
+ consoleUrl: 'https://vercel.com/ai-gateway',
52
+ slugs: { gemini: 'google', anthropic: 'anthropic', openai: 'openai', xai: 'xai' },
53
+ },
54
+ openrouter: {
55
+ id: 'openrouter',
56
+ label: 'OpenRouter',
57
+ baseUrl: 'https://openrouter.ai/api/v1',
58
+ consoleUrl: 'https://openrouter.ai/keys',
59
+ slugs: { gemini: 'google', anthropic: 'anthropic', openai: 'openai', xai: 'x-ai' },
60
+ },
61
+ };
62
+ /**
63
+ * The configured gateway, or null when MODEL_GATEWAY is unset. A set but
64
+ * unknown value also yields null — `gatewayProblem()` says why, so the
65
+ * doctor and the registry log can name the mistake instead of routing
66
+ * silently to nothing.
67
+ */
68
+ export function gatewayConfig() {
69
+ const id = (process.env[GATEWAY_ENV] ?? '').trim().toLowerCase();
70
+ if (!id)
71
+ return null;
72
+ const gateway = GATEWAYS[id];
73
+ if (!gateway)
74
+ return null;
75
+ const override = (process.env[GATEWAY_BASE_URL_ENV] ?? '').trim();
76
+ return {
77
+ gateway,
78
+ baseUrl: (override || gateway.baseUrl).replace(/\/+$/, ''),
79
+ keyPresent: !!process.env[GATEWAY_KEY_ENV],
80
+ };
81
+ }
82
+ /** A human-readable misconfiguration, or undefined when the gateway is
83
+ * either off or usable. */
84
+ export function gatewayProblem() {
85
+ const id = (process.env[GATEWAY_ENV] ?? '').trim();
86
+ if (!id)
87
+ return undefined;
88
+ const cfg = gatewayConfig();
89
+ if (!cfg) {
90
+ return `${GATEWAY_ENV}=${id} is not a known gateway (expected one of: ${Object.keys(GATEWAYS).join(', ')})`;
91
+ }
92
+ if (!cfg.keyPresent)
93
+ return `${GATEWAY_ENV}=${cfg.gateway.id} is set but ${GATEWAY_KEY_ENV} is not`;
94
+ return undefined;
95
+ }
96
+ /** True when a gateway is configured well enough to serve requests. */
97
+ export function gatewayUsable() {
98
+ const cfg = gatewayConfig();
99
+ return !!cfg && cfg.keyPresent;
100
+ }
101
+ /** Parses "a=b,c=d" into a map; malformed pairs are skipped. */
102
+ function modelMapFromEnv() {
103
+ const raw = process.env[GATEWAY_MODEL_MAP_ENV] ?? '';
104
+ const map = {};
105
+ for (const pair of raw.split(',')) {
106
+ const eq = pair.indexOf('=');
107
+ if (eq <= 0)
108
+ continue;
109
+ const from = pair.slice(0, eq).trim();
110
+ const to = pair.slice(eq + 1).trim();
111
+ if (from && to)
112
+ map[from] = to;
113
+ }
114
+ return map;
115
+ }
116
+ /**
117
+ * The id a gateway expects for a YAML model id.
118
+ *
119
+ * Gateways prefix every id with the upstream provider's slug and spell
120
+ * Anthropic's version segment with a dot ("claude-sonnet-4-6" is
121
+ * "anthropic/claude-sonnet-4.6" on both Vercel and OpenRouter). Gemini,
122
+ * GPT and Grok ids already carry dots, so only the prefix is added. An id
123
+ * the mapper gets wrong is fixed in MODEL_GATEWAY_MODEL_MAP, not in code.
124
+ */
125
+ export function gatewayWireModel(model, gateway) {
126
+ const override = modelMapFromEnv()[model];
127
+ if (override)
128
+ return override;
129
+ if (model.includes('/'))
130
+ return model; // already provider-qualified
131
+ const provider = providerForModel(model);
132
+ if (provider === 'ollama')
133
+ return model;
134
+ const slug = gateway.slugs[provider];
135
+ const wire = provider === 'anthropic' ? model.replace(/-(\d+)-(\d+)/, '-$1.$2') : model;
136
+ return `${slug}/${wire}`;
137
+ }
138
+ /**
139
+ * Where a model id will be served from under the current environment.
140
+ *
141
+ * `callerKey` is the A2A BYOK case: a per-request key the caller sent for
142
+ * its own provider counts as that provider's key being present, so a BYOK
143
+ * request never falls through to the operator's gateway.
144
+ */
145
+ export function planTransport(model, opts = {}) {
146
+ const provider = providerForModel(model);
147
+ const keyEnv = PROVIDERS[provider].keyEnv;
148
+ const direct = providerReady(provider) || !!opts.callerKey;
149
+ if (direct || provider === 'ollama') {
150
+ return { provider, transport: 'direct', funded: true, keyEnv };
151
+ }
152
+ const cfg = gatewayConfig();
153
+ if (cfg && cfg.keyPresent) {
154
+ return { provider, transport: 'gateway', gateway: cfg.gateway, funded: true, keyEnv };
155
+ }
156
+ return { provider, transport: 'direct', funded: false, keyEnv };
157
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * lib/models/gatewayLlm.ts — the one-key fallback adapter.
3
+ *
4
+ * WHY this file exists:
5
+ * Serves any cloud model id through a hosted gateway's OpenAI-compatible
6
+ * chat-completions endpoint when the provider's direct key is absent
7
+ * (lib/models/gateway.ts owns that decision). It subclasses the
8
+ * chat-completions base the way Ollama does — not GptLlm, which speaks
9
+ * OpenAI's Responses API — because chat completions is the one dialect
10
+ * every gateway serves.
11
+ *
12
+ * WHAT STAYS TRUE THROUGH THE GATEWAY:
13
+ * - Attribution. providerId() is the YAML id's provider (anthropic,
14
+ * openai, gemini, xai), never "gateway", so the ledger and any per-agent
15
+ * cost view keep the same provider column (ADR 0009). The
16
+ * transport is a separate span attribute: llm.transport = gateway:<id>.
17
+ * - Tool calling, structured output, reasoning_effort, streaming and
18
+ * token accounting — all provided by the base class.
19
+ *
20
+ * WHAT IS LOST — and reported:
21
+ * Every server-side tool sentinel (web_search, google_search, x_search,
22
+ * collections_search). The base omits web_search with a warning and marks
23
+ * the span; lib/models/capabilities.ts names the loss per agent for the
24
+ * doctor and the A2A startup log.
25
+ *
26
+ * Registration: LLMRegistry keys on regex OBJECTS, so a gateway class must
27
+ * carry the SAME supportedModels instances as the direct adapter it stands
28
+ * in for (see gatewayClassFor in registry.ts).
29
+ */
30
+ import type { LlmRequest, LlmResponse } from '@google/adk';
31
+ import { OpenAiCompatibleLlm } from './openAiCompatibleLlm.ts';
32
+ export declare class GatewayLlm extends OpenAiCompatibleLlm {
33
+ /** Never registered directly — see gatewayClassFor(). */
34
+ static readonly supportedModels: Array<string | RegExp>;
35
+ private readonly cfg;
36
+ constructor({ model }: {
37
+ model: string;
38
+ });
39
+ /** Attribution stays with the upstream provider named by the YAML id. */
40
+ protected providerId(): string;
41
+ protected transport(): string;
42
+ protected endpointUrl(): string;
43
+ protected headers(): Record<string, string>;
44
+ protected wireModelName(): string;
45
+ protected missingRequirement(): LlmResponse | undefined;
46
+ protected httpError(status: number, detail: string): LlmResponse;
47
+ protected extraBodyFields(_llmRequest: LlmRequest): Record<string, unknown>;
48
+ }
49
+ /**
50
+ * A GatewayLlm subclass that answers for the given model patterns. Pass the
51
+ * direct adapter's OWN `supportedModels` array so the registry entry
52
+ * REPLACES that adapter's (the dict is keyed by regex object) rather than
53
+ * adding a second, shadowed match.
54
+ */
55
+ export declare function gatewayClassFor(patterns: Array<string | RegExp>): typeof GatewayLlm;
56
+ /** Registers a gateway stand-in for the given patterns. */
57
+ export declare function registerGatewayLlm(patterns: Array<string | RegExp>): void;