melchizedek-agents 0.9.6 → 0.15.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 (118) hide show
  1. package/AGENT_SETUP.md +20 -9
  2. package/DOCUMENTATION.md +56 -2
  3. package/QUICKSTART.md +37 -8
  4. package/README.md +23 -3
  5. package/config/agents/README.md +12 -1
  6. package/config/agents/examples/ares.yaml +1 -0
  7. package/config/agents/examples/assistant.yaml +109 -0
  8. package/config/agents/examples/augustin.yaml +62 -20
  9. package/config/agents/examples/cartographers.yaml +1 -0
  10. package/config/agents/examples/claude.yaml +1 -0
  11. package/config/agents/examples/council.yaml +1 -0
  12. package/config/agents/examples/critic.yaml +1 -0
  13. package/config/agents/examples/delegation.yaml +1 -0
  14. package/config/agents/examples/hierarchical.yaml +1 -0
  15. package/config/agents/examples/image_production.yaml +1 -0
  16. package/config/agents/examples/librarian.yaml +1 -0
  17. package/config/agents/examples/model_zoo.yaml +4 -3
  18. package/config/agents/examples/patient_advocate.yaml +1 -0
  19. package/config/agents/examples/research.yaml +209 -0
  20. package/config/agents/examples/scribe.yaml +210 -0
  21. package/config/agents/examples/scriptorium.yaml +1 -0
  22. package/config/agents/examples/style_council.yaml +1 -0
  23. package/config/agents/examples/syndicate.yaml +1 -0
  24. package/config/agents/examples/tutor.yaml +1 -0
  25. package/config/agents/syndicateSchema.yaml +5 -0
  26. package/config/agents/templates/README.md +53 -0
  27. package/config/agents/templates/account_memory.yaml +95 -0
  28. package/config/agents/templates/case_desk.yaml +117 -0
  29. package/config/agents/templates/conversational.yaml +81 -0
  30. package/config/agents/templates/draft_review.yaml +103 -0
  31. package/config/agents/templates/intake_extractor.yaml +135 -0
  32. package/config/agents/templates/research_brief.yaml +123 -0
  33. package/config/agents/templates/research_desk.yaml +108 -0
  34. package/config/agents/templates/review_panel.yaml +153 -0
  35. package/config/agents/templates/support_triage.yaml +206 -0
  36. package/config/agents/templates/systems_operator.yaml +106 -0
  37. package/dist/lib/compile.js +18 -0
  38. package/dist/lib/config.d.ts +5 -5
  39. package/dist/lib/config.js +5 -5
  40. package/dist/lib/doctor.d.ts +98 -0
  41. package/dist/lib/doctor.js +370 -0
  42. package/dist/lib/guards/index.d.ts +28 -0
  43. package/dist/lib/guards/index.js +51 -0
  44. package/dist/lib/guards/science.d.ts +117 -0
  45. package/dist/lib/guards/science.js +444 -0
  46. package/dist/lib/loadSyndicate.d.ts +30 -2
  47. package/dist/lib/loadSyndicate.js +52 -8
  48. package/dist/lib/models/capabilities.d.ts +53 -0
  49. package/dist/lib/models/capabilities.js +81 -0
  50. package/dist/lib/models/gateway.d.ts +107 -0
  51. package/dist/lib/models/gateway.js +156 -0
  52. package/dist/lib/models/gatewayLlm.d.ts +57 -0
  53. package/dist/lib/models/gatewayLlm.js +105 -0
  54. package/dist/lib/models/gptLlm.d.ts +29 -1
  55. package/dist/lib/models/gptLlm.js +92 -3
  56. package/dist/lib/models/grokLlm.d.ts +6 -6
  57. package/dist/lib/models/grokLlm.js +7 -7
  58. package/dist/lib/models/openAiCompatibleLlm.d.ts +7 -0
  59. package/dist/lib/models/openAiCompatibleLlm.js +15 -1
  60. package/dist/lib/models/registry.d.ts +18 -2
  61. package/dist/lib/models/registry.js +63 -12
  62. package/dist/lib/observability/tracer.d.ts +10 -0
  63. package/dist/lib/observability/tracer.js +37 -0
  64. package/dist/lib/skills.d.ts +50 -0
  65. package/dist/lib/skills.js +138 -0
  66. package/dist/lib/toolRegistry.js +16 -0
  67. package/dist/lib/tools/mcpServe.d.ts +46 -0
  68. package/dist/lib/tools/mcpServe.js +89 -0
  69. package/dist/lib/tools/mcpToolFactory.d.ts +1 -0
  70. package/dist/lib/tools/mcpToolFactory.js +29 -1
  71. package/dist/lib/tools/science/corrections.d.ts +42 -0
  72. package/dist/lib/tools/science/corrections.js +115 -0
  73. package/dist/lib/tools/science/europepmc.d.ts +51 -0
  74. package/dist/lib/tools/science/europepmc.js +127 -0
  75. package/dist/lib/tools/science/http.d.ts +31 -0
  76. package/dist/lib/tools/science/http.js +104 -0
  77. package/dist/lib/tools/science/openalex.d.ts +30 -0
  78. package/dist/lib/tools/science/openalex.js +81 -0
  79. package/dist/lib/tools/science/trials.d.ts +58 -0
  80. package/dist/lib/tools/science/trials.js +203 -0
  81. package/dist/lib/tools/science/types.d.ts +69 -0
  82. package/dist/lib/tools/science/types.js +48 -0
  83. package/dist/lib/tools/scienceTools.d.ts +100 -0
  84. package/dist/lib/tools/scienceTools.js +277 -0
  85. package/dist/lib/tools/taskTools.d.ts +155 -0
  86. package/dist/lib/tools/taskTools.js +342 -0
  87. package/dist/lib/tools/webExtractTool.d.ts +6 -0
  88. package/dist/lib/tools/webExtractTool.js +8 -4
  89. package/dist/lib/tools/xApiSearchTool.d.ts +194 -0
  90. package/dist/lib/tools/xApiSearchTool.js +583 -0
  91. package/dist/lib/tools/xPacket.d.ts +102 -0
  92. package/dist/lib/tools/xPacket.js +226 -0
  93. package/dist/scripts/a2a_server.js +67 -1
  94. package/dist/scripts/assistant_worker.d.ts +31 -0
  95. package/dist/scripts/assistant_worker.js +163 -0
  96. package/dist/scripts/doctor.d.ts +21 -0
  97. package/dist/scripts/doctor.js +47 -0
  98. package/dist/scripts/skills_install.d.ts +7 -0
  99. package/dist/scripts/skills_install.js +137 -0
  100. package/package.json +17 -5
  101. package/skills/README.md +74 -0
  102. package/skills/briefs/README.md +16 -0
  103. package/skills/briefs/_shared.md +16 -0
  104. package/skills/briefs/melchizedek-author.md +60 -0
  105. package/skills/briefs/melchizedek-memory.md +45 -0
  106. package/skills/briefs/melchizedek-models.md +47 -0
  107. package/skills/briefs/melchizedek-scribe.md +37 -0
  108. package/skills/briefs/melchizedek-serve.md +52 -0
  109. package/skills/briefs/melchizedek.md +69 -0
  110. package/skills/briefs/skills-readme.md +33 -0
  111. package/skills/melchizedek/SKILL.md +159 -0
  112. package/skills/melchizedek-author/SKILL.md +99 -0
  113. package/skills/melchizedek-author/templates/minimal.yaml +60 -0
  114. package/skills/melchizedek-memory/SKILL.md +136 -0
  115. package/skills/melchizedek-models/SKILL.md +137 -0
  116. package/skills/melchizedek-scribe/SKILL.md +72 -0
  117. package/skills/melchizedek-scribe/templates/brief.md +27 -0
  118. package/skills/melchizedek-serve/SKILL.md +110 -0
@@ -0,0 +1,81 @@
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
+ /**
23
+ * Server-side tool sentinels and the providers whose DIRECT adapter
24
+ * honours them natively. Anything not listed is a client-side function
25
+ * tool and travels on every path.
26
+ */
27
+ export const SERVER_SIDE_TOOLS = {
28
+ web_search: ['gemini', 'anthropic', 'openai', 'xai'],
29
+ google_search: ['gemini'],
30
+ x_search: ['xai'],
31
+ collections_search: ['xai'],
32
+ };
33
+ export function describeCapabilities(model, tools = [], opts = {}) {
34
+ const plan = planTransport(model, opts);
35
+ const native = [];
36
+ const dropped = [];
37
+ const portable = [];
38
+ for (const name of tools) {
39
+ const providers = SERVER_SIDE_TOOLS[name];
40
+ if (!providers) {
41
+ portable.push(name);
42
+ }
43
+ else if (plan.transport === 'direct' && providers.includes(plan.provider)) {
44
+ native.push(name);
45
+ }
46
+ else {
47
+ dropped.push(name);
48
+ }
49
+ }
50
+ return {
51
+ model,
52
+ provider: plan.provider,
53
+ providerLabel: PROVIDERS[plan.provider].label,
54
+ transport: plan.transport,
55
+ ...(plan.gateway ? { gateway: plan.gateway.id } : {}),
56
+ funded: plan.funded,
57
+ keyEnv: plan.keyEnv,
58
+ native,
59
+ dropped,
60
+ portable,
61
+ };
62
+ }
63
+ /**
64
+ * One line for a startup log, or undefined when there is nothing to say —
65
+ * a funded direct path with no dropped tool is the quiet default.
66
+ */
67
+ export function capabilitySummary(agentName, r) {
68
+ if (!r.funded) {
69
+ return `${agentName}: ${r.model} has no route — set ${r.keyEnv ?? 'a provider key'} (or a MODEL_GATEWAY).`;
70
+ }
71
+ const via = r.transport === 'gateway' ? ` via gateway:${r.gateway}` : '';
72
+ if (r.dropped.length === 0) {
73
+ return via ? `${agentName}: ${r.model}${via}.` : undefined;
74
+ }
75
+ const why = r.transport === 'gateway'
76
+ ? 'a gateway cannot enable upstream native search'
77
+ : r.provider === 'ollama'
78
+ ? 'a local model has no native search'
79
+ : `${r.providerLabel} has no native ${r.dropped.join('/')}`;
80
+ return `${agentName}: ${r.model}${via} — dropped ${r.dropped.join(', ')} (${why}).`;
81
+ }
@@ -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 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,156 @@
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 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 { PROVIDERS, providerForModel, providerKeyPresent } from "./providerMap.js";
41
+ export const GATEWAY_ENV = 'MODEL_GATEWAY';
42
+ export const GATEWAY_KEY_ENV = 'MODEL_GATEWAY_API_KEY';
43
+ export const GATEWAY_BASE_URL_ENV = 'MODEL_GATEWAY_BASE_URL';
44
+ export const GATEWAY_MODEL_MAP_ENV = 'MODEL_GATEWAY_MODEL_MAP';
45
+ export const GATEWAYS = {
46
+ vercel: {
47
+ id: 'vercel',
48
+ label: 'Vercel AI Gateway',
49
+ baseUrl: 'https://ai-gateway.vercel.sh/v1',
50
+ consoleUrl: 'https://vercel.com/ai-gateway',
51
+ slugs: { gemini: 'google', anthropic: 'anthropic', openai: 'openai', xai: 'xai' },
52
+ },
53
+ openrouter: {
54
+ id: 'openrouter',
55
+ label: 'OpenRouter',
56
+ baseUrl: 'https://openrouter.ai/api/v1',
57
+ consoleUrl: 'https://openrouter.ai/keys',
58
+ slugs: { gemini: 'google', anthropic: 'anthropic', openai: 'openai', xai: 'x-ai' },
59
+ },
60
+ };
61
+ /**
62
+ * The configured gateway, or null when MODEL_GATEWAY is unset. A set but
63
+ * unknown value also yields null — `gatewayProblem()` says why, so the
64
+ * doctor and the registry log can name the mistake instead of routing
65
+ * silently to nothing.
66
+ */
67
+ export function gatewayConfig() {
68
+ const id = (process.env[GATEWAY_ENV] ?? '').trim().toLowerCase();
69
+ if (!id)
70
+ return null;
71
+ const gateway = GATEWAYS[id];
72
+ if (!gateway)
73
+ return null;
74
+ const override = (process.env[GATEWAY_BASE_URL_ENV] ?? '').trim();
75
+ return {
76
+ gateway,
77
+ baseUrl: (override || gateway.baseUrl).replace(/\/+$/, ''),
78
+ keyPresent: !!process.env[GATEWAY_KEY_ENV],
79
+ };
80
+ }
81
+ /** A human-readable misconfiguration, or undefined when the gateway is
82
+ * either off or usable. */
83
+ export function gatewayProblem() {
84
+ const id = (process.env[GATEWAY_ENV] ?? '').trim();
85
+ if (!id)
86
+ return undefined;
87
+ const cfg = gatewayConfig();
88
+ if (!cfg) {
89
+ return `${GATEWAY_ENV}=${id} is not a known gateway (expected one of: ${Object.keys(GATEWAYS).join(', ')})`;
90
+ }
91
+ if (!cfg.keyPresent)
92
+ return `${GATEWAY_ENV}=${cfg.gateway.id} is set but ${GATEWAY_KEY_ENV} is not`;
93
+ return undefined;
94
+ }
95
+ /** True when a gateway is configured well enough to serve requests. */
96
+ export function gatewayUsable() {
97
+ const cfg = gatewayConfig();
98
+ return !!cfg && cfg.keyPresent;
99
+ }
100
+ /** Parses "a=b,c=d" into a map; malformed pairs are skipped. */
101
+ function modelMapFromEnv() {
102
+ const raw = process.env[GATEWAY_MODEL_MAP_ENV] ?? '';
103
+ const map = {};
104
+ for (const pair of raw.split(',')) {
105
+ const eq = pair.indexOf('=');
106
+ if (eq <= 0)
107
+ continue;
108
+ const from = pair.slice(0, eq).trim();
109
+ const to = pair.slice(eq + 1).trim();
110
+ if (from && to)
111
+ map[from] = to;
112
+ }
113
+ return map;
114
+ }
115
+ /**
116
+ * The id a gateway expects for a YAML model id.
117
+ *
118
+ * Gateways prefix every id with the upstream provider's slug and spell
119
+ * Anthropic's version segment with a dot ("claude-sonnet-4-6" is
120
+ * "anthropic/claude-sonnet-4.6" on both Vercel and OpenRouter). Gemini,
121
+ * GPT and Grok ids already carry dots, so only the prefix is added. An id
122
+ * the mapper gets wrong is fixed in MODEL_GATEWAY_MODEL_MAP, not in code.
123
+ */
124
+ export function gatewayWireModel(model, gateway) {
125
+ const override = modelMapFromEnv()[model];
126
+ if (override)
127
+ return override;
128
+ if (model.includes('/'))
129
+ return model; // already provider-qualified
130
+ const provider = providerForModel(model);
131
+ if (provider === 'ollama')
132
+ return model;
133
+ const slug = gateway.slugs[provider];
134
+ const wire = provider === 'anthropic' ? model.replace(/-(\d+)-(\d+)/, '-$1.$2') : model;
135
+ return `${slug}/${wire}`;
136
+ }
137
+ /**
138
+ * Where a model id will be served from under the current environment.
139
+ *
140
+ * `callerKey` is the A2A BYOK case: a per-request key the caller sent for
141
+ * its own provider counts as that provider's key being present, so a BYOK
142
+ * request never falls through to the operator's gateway.
143
+ */
144
+ export function planTransport(model, opts = {}) {
145
+ const provider = providerForModel(model);
146
+ const keyEnv = PROVIDERS[provider].keyEnv;
147
+ const direct = providerKeyPresent(provider) || !!opts.callerKey;
148
+ if (direct || provider === 'ollama') {
149
+ return { provider, transport: 'direct', funded: true, keyEnv };
150
+ }
151
+ const cfg = gatewayConfig();
152
+ if (cfg && cfg.keyPresent) {
153
+ return { provider, transport: 'gateway', gateway: cfg.gateway, funded: true, keyEnv };
154
+ }
155
+ return { provider, transport: 'direct', funded: false, keyEnv };
156
+ }
@@ -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 ymir's
15
+ * per-agent cost trends 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;
@@ -0,0 +1,105 @@
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 ymir's
15
+ * per-agent cost trends 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 { LLMRegistry } from '@google/adk';
31
+ import { OpenAiCompatibleLlm } from "./openAiCompatibleLlm.js";
32
+ import { GATEWAY_ENV, GATEWAY_KEY_ENV, GATEWAY_MODEL_MAP_ENV, gatewayConfig, gatewayWireModel, } from "./gateway.js";
33
+ import { providerForModel } from "./providerMap.js";
34
+ export class GatewayLlm extends OpenAiCompatibleLlm {
35
+ /** Never registered directly — see gatewayClassFor(). */
36
+ static supportedModels = [];
37
+ cfg;
38
+ constructor({ model }) {
39
+ super({ model });
40
+ this.cfg = gatewayConfig();
41
+ }
42
+ /** Attribution stays with the upstream provider named by the YAML id. */
43
+ providerId() {
44
+ return providerForModel(this.model);
45
+ }
46
+ transport() {
47
+ return this.cfg ? `gateway:${this.cfg.gateway.id}` : 'gateway';
48
+ }
49
+ endpointUrl() {
50
+ const base = this.cfg?.baseUrl ?? '';
51
+ return `${base}/chat/completions`;
52
+ }
53
+ headers() {
54
+ return { Authorization: `Bearer ${process.env[GATEWAY_KEY_ENV] ?? ''}` };
55
+ }
56
+ wireModelName() {
57
+ return this.cfg ? gatewayWireModel(this.model, this.cfg.gateway) : this.model;
58
+ }
59
+ missingRequirement() {
60
+ if (!this.cfg) {
61
+ return {
62
+ errorCode: 'GATEWAY_NOT_CONFIGURED',
63
+ errorMessage: `${GATEWAY_ENV} is not set to a known gateway, so ${this.model} has no route.`,
64
+ };
65
+ }
66
+ if (!this.cfg.keyPresent) {
67
+ return {
68
+ errorCode: 'GATEWAY_KEY_MISSING',
69
+ errorMessage: `${GATEWAY_ENV}=${this.cfg.gateway.id} but ${GATEWAY_KEY_ENV} is not set.`,
70
+ };
71
+ }
72
+ return undefined;
73
+ }
74
+ // webSearchBodyFields() stays at the base default (null): a gateway has no
75
+ // uniform switch for upstream native search, so the tool is dropped and
76
+ // the loss is reported (llm.capability.dropped, the doctor, the startup log).
77
+ httpError(status, detail) {
78
+ const label = this.cfg?.gateway.label ?? 'gateway';
79
+ const hint = status === 404 || status === 400
80
+ ? ` If the model id is the problem, map it with ${GATEWAY_MODEL_MAP_ENV}=${this.model}=<gateway id>.`
81
+ : '';
82
+ return {
83
+ errorCode: 'GATEWAY_HTTP_ERROR',
84
+ errorMessage: `${label} returned ${status} for ${this.wireModelName()}: ${detail.slice(0, 4000)}.${hint}`,
85
+ };
86
+ }
87
+ extraBodyFields(_llmRequest) {
88
+ return {};
89
+ }
90
+ }
91
+ /**
92
+ * A GatewayLlm subclass that answers for the given model patterns. Pass the
93
+ * direct adapter's OWN `supportedModels` array so the registry entry
94
+ * REPLACES that adapter's (the dict is keyed by regex object) rather than
95
+ * adding a second, shadowed match.
96
+ */
97
+ export function gatewayClassFor(patterns) {
98
+ return class GatewayLlmFor extends GatewayLlm {
99
+ static supportedModels = patterns;
100
+ };
101
+ }
102
+ /** Registers a gateway stand-in for the given patterns. */
103
+ export function registerGatewayLlm(patterns) {
104
+ LLMRegistry.register(gatewayClassFor(patterns));
105
+ }
@@ -27,10 +27,15 @@
27
27
  *
28
28
  * Response `output` items map back:
29
29
  * 'reasoning' summary → { text, thought: true } partial (display-only)
30
- * 'message' output_text → text part
30
+ * 'message' output_text → text part (2nd+ message item opens a new paragraph)
31
31
  * 'function_call' → functionCall part
32
+ * '*_call' / 'custom_tool_call' (server-side: web_search, x_search…)
33
+ * → customMetadata['responses.server_tool_calls'],
34
+ * never a functionCall (ADK must not run them)
32
35
  * usage.input_tokens / output_tokens / output_tokens_details.reasoning_tokens
33
36
  * → LlmResponse.usageMetadata, so token telemetry works like every provider.
37
+ * xAI's server-side tool counters → customMetadata['responses.server_tool_usage']
38
+ * and llm.server_tools.* span attributes.
34
39
  *
35
40
  * STREAMING (ADK stream=true, i.e. RunConfig streamingMode: SSE):
36
41
  * The request is sent with { stream: true }; SSE deltas
@@ -59,6 +64,29 @@ export declare function streamEventDelta(ev: any): {
59
64
  thought: boolean;
60
65
  text: string;
61
66
  } | null;
67
+ /** One tool call the vendor ran on its own side inside a single Responses
68
+ * call. It never comes back to ADK as a functionCall, so without this
69
+ * record a searched answer and a recalled one look identical in a trace. */
70
+ export interface ServerToolCall {
71
+ name: string;
72
+ args: Record<string, unknown>;
73
+ status?: string;
74
+ /** URLs the vendor reports the call returned (web search only). */
75
+ sources?: string[];
76
+ }
77
+ /**
78
+ * The server-side tool calls in a Responses `output` array. Two shapes,
79
+ * both verified against a live xAI response (2026-09-25):
80
+ * web_search_call → { status, action: { type:'search', query, sources:[{url}] } }
81
+ * custom_tool_call → { name:'x_keyword_search'|'x_semantic_search', input:'<json>', status }
82
+ * Any other `*_call` item (code_interpreter_call, file_search_call…) is
83
+ * recorded by its type with its `action` as args. `custom_tool_call` is
84
+ * server-side here because this adapter never declares a client custom tool.
85
+ */
86
+ export declare function extractServerToolCalls(output: unknown): ServerToolCall[];
87
+ /** xAI's server-side tool counters off `usage` (num_server_side_tools_used +
88
+ * server_side_tool_usage_details), non-zero entries only; {} for OpenAI. */
89
+ export declare function serverToolUsage(usage: any): Record<string, number>;
62
90
  export declare class GptLlm extends BaseLlm {
63
91
  /** gpt-* and o-series ids route here after registration. */
64
92
  static readonly supportedModels: Array<string | RegExp>;