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
@@ -23,45 +23,119 @@
23
23
  * resolution (LRU), so late registration can be masked by stale cache hits.
24
24
  */
25
25
  import { Gemini, LLMRegistry } from '@google/adk';
26
- import { DEFAULT_CLAUDE_MODEL, DEFAULT_GEMINI_MODEL, DEFAULT_GPT_MODEL, DEFAULT_GROK_MODEL, DEFAULT_OLLAMA_MODEL, } from "../config.js";
27
- import { traceLlmGeneration, setLlmSpanAttribute, } from "../observability/tracer.js";
28
- import { ClaudeLlm, registerClaudeLlm } from "./claudeLlm.js";
29
- import { GptLlm, registerGptLlm } from "./gptLlm.js";
30
- import { GrokLlm, registerGrokLlm } from "./grokLlm.js";
31
- import { OllamaLlm, registerOllamaLlm } from "./ollamaLlm.js";
32
- import { PROVIDERS, providerForModel, providerKeyPresent, } from "./providerMap.js";
26
+ import { endpointFromEnv, endpointProblems, mergeEndpoint, platformModel, providerReady } from './endpoints.js';
27
+ import { currentTurnSignal } from '../runtime/turnControl.js';
28
+ import { errorStatus, retryUntilFirstYield } from './retry.js';
29
+ import { DEFAULT_CLAUDE_MODEL, DEFAULT_GEMINI_MODEL, DEFAULT_GPT_MODEL, DEFAULT_GROK_MODEL, DEFAULT_OLLAMA_MODEL, } from '../config.js';
30
+ import { traceLlmGeneration, setLlmSpanAttribute, } from '../observability/tracer.js';
31
+ import { ClaudeLlm, registerClaudeLlm } from './claudeLlm.js';
32
+ import { GptLlm, registerGptLlm } from './gptLlm.js';
33
+ import { GrokLlm, registerGrokLlm } from './grokLlm.js';
34
+ import { OllamaLlm, registerOllamaLlm } from './ollamaLlm.js';
35
+ import { GatewayLlm, registerGatewayLlm } from './gatewayLlm.js';
36
+ import { gatewayConfig, gatewayProblem, gatewayUsable, planTransport, } from './gateway.js';
37
+ import { PROVIDERS, providerForModel, providerKeyPresent, } from './providerMap.js';
33
38
  export { providerForModel, providerKeyPresent, PROVIDERS };
39
+ export { endpointFromEnv, endpointLabel, endpointProblems, PLATFORMS_FOR, platformFromEnv, providerReady, } from './endpoints.js';
40
+ export { gatewayConfig, gatewayProblem, gatewayUsable, planTransport, GATEWAYS } from './gateway.js';
41
+ export { describeCapabilities, capabilitySummary } from './capabilities.js';
42
+ export { GatewayLlm };
34
43
  // ── TracedGemini ─────────────────────────────────────────────────────────────
35
44
  // ADK's built-in Gemini adapter, wrapped so Gemini calls emit the same
36
- // per-request llm.request spans (tokens, latency) as every other provider.
45
+ // per-request llm.request spans (tokens, latency) as every other provider,
46
+ // and get the shared transient-failure retries (lib/models/retry.ts).
37
47
  export class TracedGemini extends Gemini {
38
48
  // CRITICAL: reuse Gemini's exact regex instances. The LLMRegistry dict is
39
49
  // keyed by the regex OBJECT — registering the same instances REPLACES the
40
50
  // built-in Gemini entries instead of adding shadowed duplicates.
41
51
  static supportedModels = Gemini.supportedModels;
42
- async *generateContentAsync(llmRequest, stream) {
43
- yield* traceLlmGeneration({ provider: 'gemini', model: this.model, llmRequest }, this.tagAndGenerate(llmRequest, stream));
52
+ /**
53
+ * The Gemini platform (ADR 0023) applies to every construction, the
54
+ * LLMRegistry's included: on Vertex AI the client authenticates with
55
+ * Google Application Default Credentials for the configured project and
56
+ * location, and an AI Studio key (the environment's or a caller's) is not
57
+ * sent. `endpoint` overrides the environment's (the credentials plug point).
58
+ */
59
+ constructor(params = {}) {
60
+ const { endpoint: given, ...rest } = params;
61
+ const e = given ?? endpointFromEnv('gemini');
62
+ const model = rest.model ? platformModel(e, rest.model) : rest.model;
63
+ super(e.platform === 'vertex'
64
+ ? { ...rest, model, apiKey: e.apiKey, vertexai: true, project: e.project, location: e.location }
65
+ : { ...rest, model, ...(e.apiKey && !rest.apiKey ? { apiKey: e.apiKey } : {}) });
66
+ }
67
+ async *generateContentAsync(llmRequest, stream, abortSignal) {
68
+ yield* traceLlmGeneration({ provider: 'gemini', model: this.model, llmRequest }, this.tagAndGenerate(llmRequest, stream, abortSignal ?? currentTurnSignal()));
44
69
  }
45
70
  /** Tags the llm.request span (web_search = Gemini grounding), then
46
- * delegates to ADK's Gemini. Runs inside the span context. */
47
- async *tagAndGenerate(llmRequest, stream) {
71
+ * delegates to ADK's Gemini. Runs inside the span context. The abort
72
+ * signal is forwarded so a canceled turn stops the request in flight
73
+ * (ADK's Gemini puts it on the genai request config). */
74
+ async *tagAndGenerate(llmRequest, stream, abortSignal) {
48
75
  const hasGrounding = (llmRequest.config?.tools ?? []).some((t) => t && (t.googleSearch || t.googleSearchRetrieval));
49
76
  if (hasGrounding)
50
77
  setLlmSpanAttribute('llm.web_search.native', true);
51
- yield* super.generateContentAsync(llmRequest, stream);
78
+ // Retries live here, not in genai's own httpOptions.retryOptions: that
79
+ // hook (p-retry 4, client-level only) ignores the abort signal — it
80
+ // sleeps and re-sends after a canceled turn — honours no Retry-After,
81
+ // does not retry Node's "fetch failed" resets, and replaces a 4xx's
82
+ // error body (e.g. "API key not valid") with a bare statusText. Wrapping
83
+ // the call keeps genai's ApiError intact and applies the same policy as
84
+ // every other adapter. A retry is only made while nothing has been
85
+ // yielded, so a stream that fails mid-reply is surfaced, not replayed.
86
+ // ADK's request preprocessing is idempotent, so re-sending the same
87
+ // llmRequest is safe. Both the registry class and resolveModel()'s
88
+ // per-request instances are TracedGemini, so both get this.
89
+ try {
90
+ yield* retryUntilFirstYield(() => super.generateContentAsync(llmRequest, stream, abortSignal), {
91
+ signal: abortSignal,
92
+ onRetry: ({ retries }) => setLlmSpanAttribute('llm.retries', retries),
93
+ });
94
+ }
95
+ catch (err) {
96
+ // genai's ApiError carries the status; put it where the ledger reads.
97
+ const status = errorStatus(err);
98
+ if (status !== undefined)
99
+ setLlmSpanAttribute('llm.http_status', status);
100
+ throw err;
101
+ }
52
102
  }
53
103
  }
54
104
  /** Availability report without side effects (used by demos and key gates). */
55
105
  export function providerStatuses() {
106
+ const gw = gatewayUsable() ? gatewayConfig() : null;
56
107
  return Object.keys(PROVIDERS).map((provider) => {
57
- const available = providerKeyPresent(provider);
108
+ const label = PROVIDERS[provider].label;
109
+ if (providerReady(provider)) {
110
+ return { provider, label, available: true, transport: 'direct' };
111
+ }
112
+ // A cloud platform that is not fully configured is not covered by the
113
+ // gateway: the deployment chose that platform on purpose.
114
+ let cloudProblem;
115
+ try {
116
+ const e = endpointFromEnv(provider);
117
+ if (e.platform !== 'direct')
118
+ cloudProblem = endpointProblems(provider, e).join('; ');
119
+ }
120
+ catch (err) {
121
+ cloudProblem = err instanceof Error ? err.message : String(err);
122
+ }
123
+ if (cloudProblem)
124
+ return { provider, label, available: false, reason: cloudProblem };
125
+ if (gw && provider !== 'ollama') {
126
+ return {
127
+ provider,
128
+ label,
129
+ available: true,
130
+ transport: 'gateway',
131
+ gateway: gw.gateway.id,
132
+ };
133
+ }
58
134
  return {
59
135
  provider,
60
- label: PROVIDERS[provider].label,
61
- available,
62
- ...(available
63
- ? {}
64
- : { reason: `${PROVIDERS[provider].keyEnv} not set` }),
136
+ label,
137
+ available: false,
138
+ reason: `${PROVIDERS[provider].keyEnv} not set`,
65
139
  };
66
140
  });
67
141
  }
@@ -72,29 +146,57 @@ const REGISTRARS = {
72
146
  xai: registerGrokLlm,
73
147
  ollama: registerOllamaLlm,
74
148
  };
149
+ /**
150
+ * The direct adapter's OWN supportedModels per provider. A gateway stand-in
151
+ * must register under these exact regex instances so it REPLACES the
152
+ * direct entry (the LLMRegistry dict is keyed by regex object) instead of
153
+ * sitting behind it unmatched. Gemini's are ADK's built-in patterns, which
154
+ * are registered at import time — replacing them is how TracedGemini works
155
+ * too.
156
+ */
157
+ const PATTERNS = {
158
+ gemini: Gemini.supportedModels,
159
+ anthropic: ClaudeLlm.supportedModels,
160
+ openai: GptLlm.supportedModels,
161
+ xai: GrokLlm.supportedModels,
162
+ };
75
163
  let providersRegistered = false;
76
164
  /**
77
165
  * Registers every adapter whose credentials exist into the ADK LLMRegistry
78
166
  * (Ollama unconditionally — local needs no key; Gemini always, since the
79
167
  * registry needs a fallback class, and a missing Gemini key surfaces as a
80
- * clear API error at call time). Returns the per-provider statuses so
81
- * entrypoints can log which models are routable.
168
+ * clear API error at call time). When MODEL_GATEWAY is configured, a
169
+ * provider whose direct key is ABSENT is served by the gateway stand-in
170
+ * instead (lib/models/gateway.ts — the fallback rule). Returns the
171
+ * per-provider statuses so entrypoints can log which models are routable.
82
172
  */
83
173
  export function registerAvailableProviders(log) {
84
174
  const statuses = providerStatuses();
85
175
  if (!providersRegistered) {
86
176
  providersRegistered = true;
87
177
  for (const status of statuses) {
88
- if (status.available || status.provider === 'gemini') {
178
+ if (status.transport === 'gateway' && status.provider !== 'ollama') {
179
+ registerGatewayLlm(PATTERNS[status.provider]);
180
+ }
181
+ else if (status.available || status.provider === 'gemini') {
89
182
  REGISTRARS[status.provider]();
90
183
  }
91
184
  }
92
185
  }
93
186
  if (log) {
187
+ const problem = gatewayProblem();
188
+ if (problem)
189
+ log(`⚠ ${problem} — gateway ignored`);
94
190
  for (const s of statuses) {
95
- log(s.available
96
- ? `✓ ${s.label} — active`
97
- : `⚠ ${s.label} disabled (${s.reason}) — ${modelHint(s.provider)} models unavailable`);
191
+ if (!s.available) {
192
+ log(`⚠ ${s.label} disabled (${s.reason}) — ${modelHint(s.provider)} models unavailable`);
193
+ }
194
+ else if (s.transport === 'gateway') {
195
+ log(`◇ ${s.label} — via gateway:${s.gateway} (${modelHint(s.provider)} served without native search)`);
196
+ }
197
+ else {
198
+ log(`✓ ${s.label} — active`);
199
+ }
98
200
  }
99
201
  }
100
202
  return statuses;
@@ -137,17 +239,26 @@ export function resolveModel(model, options = {}) {
137
239
  const apiKey = providerForModel(model ?? DEFAULT_MODEL_FOR[normalizeProvider(options.defaultProvider)]) === normalizeProvider(options.defaultProvider)
138
240
  ? options.apiKey
139
241
  : undefined;
140
- switch (providerForModel(resolved)) {
242
+ // The fallback rule (lib/models/gateway.ts): the direct adapter whenever
243
+ // the provider's key — from env, or the caller's own BYOK key — is
244
+ // present; the gateway stand-in only when it is absent and a gateway is
245
+ // configured. The gateway key is server env only, never a request header.
246
+ const provider = providerForModel(resolved);
247
+ const endpoint = options.endpoint ? mergeEndpoint(provider, options.endpoint) : undefined;
248
+ if (planTransport(resolved, { callerKey: !!apiKey || !!endpoint }).transport === 'gateway') {
249
+ return new GatewayLlm({ model: resolved });
250
+ }
251
+ switch (provider) {
141
252
  case 'ollama':
142
253
  return new OllamaLlm({ model: resolved });
143
254
  case 'anthropic':
144
- return new ClaudeLlm({ model: resolved, apiKey });
255
+ return new ClaudeLlm({ model: resolved, apiKey, endpoint });
145
256
  case 'openai':
146
- return new GptLlm({ model: resolved, apiKey });
257
+ return new GptLlm({ model: resolved, apiKey, endpoint });
147
258
  case 'xai':
148
259
  return new GrokLlm({ model: resolved, apiKey });
149
260
  case 'gemini':
150
- return new TracedGemini({ model: resolved, apiKey });
261
+ return new TracedGemini({ model: resolved, apiKey, endpoint });
151
262
  }
152
263
  }
153
264
  function normalizeProvider(provider) {
@@ -0,0 +1,146 @@
1
+ /**
2
+ * lib/models/retry.ts — the one retry policy for transient provider failures.
3
+ *
4
+ * WHY this file exists:
5
+ * Before it, resilience was an accident of which SDK an adapter used.
6
+ * Claude and GPT/Grok inherited their vendor SDKs' two retries; Gemini —
7
+ * the default provider — got none (`@google/genai` does a single fetch
8
+ * unless retryOptions is set, and ADK's Gemini never sets it); the
9
+ * chat-completions path (Ollama, gateways) was one bare `fetch`. A 503
10
+ * "high demand" from Google failed the whole turn. One policy here, used by
11
+ * every adapter that does not get it from its SDK, keeps the behaviour the
12
+ * same whichever provider an agent's YAML names.
13
+ *
14
+ * THE POLICY:
15
+ * - Bounded: MODEL_RETRY_MAX_ATTEMPTS total attempts (default 3, clamped
16
+ * 1..10; 1 disables retries). Matches the vendor SDKs' 1 + 2 retries.
17
+ * - Retried: HTTP 408/409/425/429/500/502/503/504, and connection-level
18
+ * failures that say nothing about the request (ECONNRESET, ETIMEDOUT,
19
+ * EPIPE, ECONNABORTED, EAI_AGAIN, undici socket/connect-timeout). Every
20
+ * other 4xx is the request's own fault and is never repeated.
21
+ * - NOT retried: ECONNREFUSED / ENOTFOUND. Against Ollama on localhost a
22
+ * refused connection means the server is not running, and the adapter's
23
+ * own message already says how to start it; retrying only delays that
24
+ * diagnosis by seconds, and no daemon restarts inside our backoff window.
25
+ * Against a remote gateway it means a wrong host or port — configuration,
26
+ * not load. Response-wait timeouts (undici headers/body timeout) are not
27
+ * retried either: a request that already ran for minutes is better
28
+ * reported than repeated at the same cost.
29
+ * - Backoff: full jitter, uniform in [0, min(8 s, 500 ms * 2^(n-1))], so
30
+ * callers that failed together do not come back together.
31
+ * - Retry-After (seconds, HTTP date, or OpenAI's retry-after-ms; Google's
32
+ * RetryInfo `retryDelay` in an error body) replaces the jittered delay
33
+ * when present. Above 60 s the call is NOT retried at all: per-minute
34
+ * quota windows reset inside a minute, so a longer wait signals a daily
35
+ * or hard quota that waiting will not fix — surface it now.
36
+ * - Cancellation: the turn's AbortSignal (lib/runtime/turnControl.ts) is
37
+ * checked before every retry and wakes the backoff sleep, so a canceled
38
+ * or timed-out turn never sleeps past its abort and never re-sends.
39
+ *
40
+ * THE STREAMING RULE:
41
+ * A retry is only ever made BEFORE the caller has received any response
42
+ * bytes. A failure after the first partial has been yielded would, if
43
+ * retried, replay text the user has already seen (and that ADK may have
44
+ * already turned into events). `fetchWithRetry` retries the request and
45
+ * hands back the Response before its body is read; `retryUntilFirstYield`
46
+ * retries a generator only while it has yielded nothing.
47
+ *
48
+ * Retries run inside one `llm.request` span and one step-budget charge: a
49
+ * retried call is still one logical model call. The count lands on the span
50
+ * as `llm.retries` (via the onRetry hook each adapter wires to
51
+ * setLlmSpanAttribute).
52
+ */
53
+ export declare const RETRYABLE_STATUSES: ReadonlySet<number>;
54
+ export declare const RETRYABLE_NETWORK_CODES: ReadonlySet<string>;
55
+ export interface RetryPolicy {
56
+ /** Total attempts including the first; 1 = no retries. */
57
+ maxAttempts: number;
58
+ /** Backoff ceiling for attempt 1; doubles per attempt. */
59
+ baseDelayMs: number;
60
+ /** Backoff ceiling never grows past this. */
61
+ maxDelayMs: number;
62
+ /** A Retry-After longer than this is not waited out — the call fails. */
63
+ maxRetryAfterMs: number;
64
+ }
65
+ export declare const DEFAULT_RETRY_POLICY: Readonly<RetryPolicy>;
66
+ export declare const MAX_ATTEMPTS_ENV = "MODEL_RETRY_MAX_ATTEMPTS";
67
+ /**
68
+ * Process-wide overrides for the policy (tests shrink the delays; an
69
+ * embedding app can tune them). Returns a function restoring the previous
70
+ * overrides. The env var still sets maxAttempts unless overridden here.
71
+ */
72
+ export declare function setRetryPolicyOverrides(next: Partial<RetryPolicy>): () => void;
73
+ /** The effective policy: defaults ← MODEL_RETRY_MAX_ATTEMPTS ← overrides. */
74
+ export declare function retryPolicy(env?: NodeJS.ProcessEnv): RetryPolicy;
75
+ /** What one failed attempt says about trying again. */
76
+ export interface RetryDecision {
77
+ retryable: boolean;
78
+ /** HTTP status, when the failure had one. */
79
+ status?: number;
80
+ /** Server-requested wait, when it sent one. */
81
+ retryAfterMs?: number;
82
+ }
83
+ export declare function isRetryableStatus(status: number): boolean;
84
+ /** The system/undici error code anywhere on an error's cause chain. */
85
+ export declare function networkErrorCode(err: unknown): string | undefined;
86
+ /** A numeric HTTP status carried on an error (genai ApiError, SDK errors). */
87
+ export declare function errorStatus(err: unknown): number | undefined;
88
+ /** Classifies a non-2xx HTTP response. */
89
+ export declare function classifyResponse(res: Pick<Response, 'status' | 'headers'>): RetryDecision;
90
+ /** Classifies a thrown error (an HTTP error from an SDK, or a network one). */
91
+ export declare function classifyError(err: unknown): RetryDecision;
92
+ /**
93
+ * Parses a Retry-After value: delta-seconds ("30", "1.5") or an HTTP date.
94
+ * Returns milliseconds (never negative), or undefined when unparseable.
95
+ */
96
+ export declare function parseRetryAfter(value: string | null | undefined, now?: number): number | undefined;
97
+ /** Retry-After from response headers, preferring OpenAI's retry-after-ms. */
98
+ export declare function retryAfterFromHeaders(headers: Pick<Headers, 'get'> | undefined, now?: number): number | undefined;
99
+ /**
100
+ * Google RPC's RetryInfo, which genai only exposes inside the error message
101
+ * (its ApiError drops the response headers): `"retryDelay": "37s"`.
102
+ */
103
+ export declare function googleRetryDelayMs(message: string): number | undefined;
104
+ /** Full-jitter backoff for the given (1-based) failed attempt. */
105
+ export declare function backoffDelayMs(attempt: number, policy?: RetryPolicy, random?: () => number): number;
106
+ /**
107
+ * How long to wait before attempt `attempt + 1`, or undefined to stop:
108
+ * not retryable, attempts exhausted, the turn aborted, or the server asked
109
+ * for a wait beyond the cap.
110
+ */
111
+ export declare function nextRetryDelay(attempt: number, decision: RetryDecision, policy?: RetryPolicy, signal?: AbortSignal, random?: () => number): number | undefined;
112
+ /**
113
+ * Sleeps `ms`, waking early if `signal` aborts. Resolves true when the full
114
+ * wait elapsed, false when it was cut short by an abort.
115
+ */
116
+ export declare function sleepUnlessAborted(ms: number, signal?: AbortSignal): Promise<boolean>;
117
+ export interface RetryOptions {
118
+ signal?: AbortSignal;
119
+ policy?: RetryPolicy;
120
+ /** Called before each wait; `retries` counts retries made so far + 1. */
121
+ onRetry?: (info: {
122
+ retries: number;
123
+ delayMs: number;
124
+ decision: RetryDecision;
125
+ }) => void;
126
+ /** Injectable for tests. */
127
+ random?: () => number;
128
+ }
129
+ /**
130
+ * `fetch` with the policy applied to the REQUEST. Returns the first 2xx, or
131
+ * the last non-2xx Response once retrying stops (body unread, so the caller
132
+ * builds its own error from it). Throws the last error when the final
133
+ * attempt failed at the network level. The body of a successful response is
134
+ * never touched here — reading (and streaming) it is the caller's, and a
135
+ * failure there is not retried (see THE STREAMING RULE above).
136
+ */
137
+ export declare function fetchWithRetry(url: string, init: RequestInit, opts?: RetryOptions): Promise<{
138
+ response: Response;
139
+ retries: number;
140
+ }>;
141
+ /**
142
+ * Re-runs a generator factory while it fails BEFORE its first yield. Once
143
+ * anything has been yielded a failure is rethrown untouched: retrying then
144
+ * would duplicate output the caller has already passed on.
145
+ */
146
+ export declare function retryUntilFirstYield<T>(start: (attempt: number) => AsyncGenerator<T, void>, opts?: RetryOptions): AsyncGenerator<T, void>;