melchizedek-agents 1.1.0 → 1.3.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.
- package/.env.example +19 -0
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +373 -0
- package/DOCUMENTATION.md +193 -21
- package/ONBOARDING.md +378 -0
- package/QUICKSTART.md +15 -2
- package/README.md +26 -3
- package/config/agents/examples/connectors.yaml +82 -0
- package/config/agents/examples/council.yaml +1 -1
- package/config/agents/examples/critic.yaml +8 -8
- package/config/agents/examples/image_production.yaml +4 -3
- package/config/agents/syndicate.schema.json +389 -33
- package/config/agents/syndicateSchema.yaml +18 -6
- package/config/agents/templates/intake_extractor.yaml +3 -2
- package/config/agents/templates/systems_operator.yaml +5 -2
- package/dist/lib/a2a/app.d.ts +12 -0
- package/dist/lib/a2a/app.js +25 -11
- package/dist/lib/a2a/executor.js +14 -6
- package/dist/lib/a2a/oauthSetup.d.ts +16 -0
- package/dist/lib/a2a/oauthSetup.js +77 -8
- package/dist/lib/agentDialect.d.ts +18 -8
- package/dist/lib/agentDialect.js +36 -19
- package/dist/lib/chatgpt/adapter.d.ts +50 -0
- package/dist/lib/chatgpt/adapter.js +107 -0
- package/dist/lib/chatgpt/cli.d.ts +23 -0
- package/dist/lib/chatgpt/cli.js +77 -0
- package/dist/lib/chatgpt/oauth.d.ts +103 -0
- package/dist/lib/chatgpt/oauth.js +448 -0
- package/dist/lib/chatgpt/state.d.ts +98 -0
- package/dist/lib/chatgpt/state.js +145 -0
- package/dist/lib/chatgpt/store.d.ts +24 -0
- package/dist/lib/chatgpt/store.js +94 -0
- package/dist/lib/compile.d.ts +38 -7
- package/dist/lib/compile.js +97 -62
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +18 -1
- package/dist/lib/dispatch.js +6 -2
- package/dist/lib/doctor.d.ts +107 -0
- package/dist/lib/doctor.js +217 -7
- package/dist/lib/loadSyndicate.d.ts +15 -0
- package/dist/lib/mcp/server.d.ts +195 -0
- package/dist/lib/mcp/server.js +564 -0
- package/dist/lib/memory/supabaseMemoryService.js +2 -2
- package/dist/lib/models/adapterResolver.js +6 -1
- package/dist/lib/models/capabilities.d.ts +9 -0
- package/dist/lib/models/capabilities.js +24 -0
- package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
- package/dist/lib/models/chatCompletionsAdapter.js +19 -5
- package/dist/lib/models/claudeAdapter.js +4 -0
- package/dist/lib/models/contract.d.ts +5 -2
- package/dist/lib/models/gateway.d.ts +3 -2
- package/dist/lib/models/gateway.js +8 -4
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/onboarding.d.ts +135 -0
- package/dist/lib/onboarding.js +578 -0
- package/dist/lib/runtime/approvals.d.ts +2 -0
- package/dist/lib/runtime/approvals.js +4 -3
- package/dist/lib/runtime/credentials.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
- package/dist/lib/runtime/native/agentLoop.js +2 -0
- package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
- package/dist/lib/runtime/native/checkpoint.js +13 -19
- package/dist/lib/runtime/native/delegate.d.ts +82 -1
- package/dist/lib/runtime/native/delegate.js +157 -2
- package/dist/lib/runtime/native/interrupts.d.ts +88 -5
- package/dist/lib/runtime/native/interrupts.js +220 -30
- package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
- package/dist/lib/runtime/syndicateTurn.js +324 -77
- package/dist/lib/runtime/turnControl.d.ts +41 -0
- package/dist/lib/session/transcript.d.ts +9 -0
- package/dist/lib/session/transcript.js +20 -1
- package/dist/lib/skills.d.ts +18 -0
- package/dist/lib/skills.js +73 -1
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +1 -1
- package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
- package/dist/lib/storage/postgres/taskQueue.js +10 -3
- package/dist/lib/syndicateSchema.d.ts +184 -9
- package/dist/lib/syndicateSchema.js +143 -29
- package/dist/lib/tools/auth.d.ts +13 -1
- package/dist/lib/tools/auth.js +10 -2
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialUses.d.ts +98 -0
- package/dist/lib/tools/credentialUses.js +120 -0
- package/dist/lib/tools/generateImageTool.js +2 -1
- package/dist/lib/tools/inspectImageTool.js +1 -1
- package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
- package/dist/lib/tools/mcpToolFactory.js +72 -26
- package/dist/lib/tools/mcpTransports.d.ts +12 -0
- package/dist/lib/tools/mcpTransports.js +11 -0
- package/dist/lib/tools/oauthConsent.d.ts +18 -3
- package/dist/lib/tools/oauthConsent.js +57 -15
- package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
- package/dist/lib/tools/oauthDiscovery.js +616 -0
- package/dist/lib/tools/oauthHosts.d.ts +2 -0
- package/dist/lib/tools/oauthHosts.js +2 -2
- package/dist/lib/tools/oauthTools.d.ts +54 -13
- package/dist/lib/tools/oauthTools.js +106 -23
- package/dist/lib/tools/openapiTools.d.ts +7 -0
- package/dist/lib/tools/openapiTools.js +49 -3
- package/dist/lib/tools/taskTools.d.ts +32 -2
- package/dist/lib/tools/taskTools.js +93 -33
- package/dist/lib/tools/xApiSearchTool.js +2 -2
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +10 -7
- package/dist/lib/workflow.js +10 -7
- package/dist/scripts/a2a_server.d.ts +2 -2
- package/dist/scripts/a2a_server.js +19 -2
- package/dist/scripts/assistant_worker.d.ts +2 -0
- package/dist/scripts/assistant_worker.js +24 -5
- package/dist/scripts/doctor.d.ts +4 -2
- package/dist/scripts/doctor.js +6 -4
- package/dist/scripts/mcp_server.d.ts +12 -0
- package/dist/scripts/mcp_server.js +345 -0
- package/dist/scripts/setup.d.ts +25 -0
- package/dist/scripts/setup.js +181 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/syndicate_chat.js +12 -3
- package/dist/scripts/yaml_codemod.d.ts +12 -2
- package/dist/scripts/yaml_codemod.js +115 -33
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-models.md +4 -2
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek-models/SKILL.md +6 -2
- package/skills/melchizedek-onboard/SKILL.md +71 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +47 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
import { remoteAgentOwnTool } from './a2a/remoteAgent.js';
|
|
36
36
|
import { compileSpec, compileSubagentSpec, workflowAgentSpecs } from './compile.js';
|
|
37
37
|
import { resolveAdapter } from './models/registry.js';
|
|
38
|
-
import { subagentTool, workflowSubagentTool } from './runtime/native/delegate.js';
|
|
38
|
+
import { nestedDispatchWalk, subagentTool, workflowSubagentTool } from './runtime/native/delegate.js';
|
|
39
39
|
import { unsupportedOnNative } from './runtime/runtimeFlag.js';
|
|
40
40
|
export { UnsupportedOnRuntimeError, unsupportedOnNative } from './runtime/runtimeFlag.js';
|
|
41
41
|
import { instructionToolOf, toolOf, toolsetOf } from './tools/tool.js';
|
|
@@ -108,6 +108,8 @@ export function compileNative(spec) {
|
|
|
108
108
|
tools.push(subagentTool(compileNative(entry.agent)));
|
|
109
109
|
else if (entry.kind === 'workflow')
|
|
110
110
|
tools.push(workflowSubagentTool(workflowSubagentOf(compileNativeWorkflow(entry.workflow))));
|
|
111
|
+
else if (entry.kind === 'dispatch')
|
|
112
|
+
tools.push(workflowSubagentTool(dispatchSubagentOf(entry.dispatch)));
|
|
111
113
|
else if (entry.kind === 'remote')
|
|
112
114
|
tools.push(remoteAgentOwnTool({ name: entry.name, description: entry.description, url: entry.url }));
|
|
113
115
|
else
|
|
@@ -182,6 +184,8 @@ export function compileNativeWorkflow(spec) {
|
|
|
182
184
|
const workflows = new Map();
|
|
183
185
|
for (const { yaml, workflow } of spec.workflows)
|
|
184
186
|
workflows.set(yaml.name, compileNativeWorkflow(workflow));
|
|
187
|
+
for (const { yaml, dispatch } of spec.dispatches ?? [])
|
|
188
|
+
workflows.set(yaml.name, dispatchSubagentOf(dispatch));
|
|
185
189
|
refuseUnrunnableNodes(graph, spec.resolveTool);
|
|
186
190
|
return { name: spec.name, description: spec.description, graph, agents, resolveTool: spec.resolveTool, workflows };
|
|
187
191
|
}
|
|
@@ -214,6 +218,19 @@ export function workflowSubagentOf(workflow) {
|
|
|
214
218
|
}),
|
|
215
219
|
};
|
|
216
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* A nested dispatch syndicate as the delegated subagent the native loop
|
|
223
|
+
* calls, or the node a walk runs (ADR 0120): one call runs it as its own
|
|
224
|
+
* turn (nestedDispatchWalk) on the child session lib/runtime/native/delegate.ts
|
|
225
|
+
* (or lib/workflow/turn.ts) opened.
|
|
226
|
+
*/
|
|
227
|
+
export function dispatchSubagentOf(dispatch) {
|
|
228
|
+
return {
|
|
229
|
+
name: dispatch.name,
|
|
230
|
+
...(dispatch.description ? { description: dispatch.description } : {}),
|
|
231
|
+
walk: (run) => nestedDispatchWalk({ name: dispatch.name, config: dispatch.config, compile: dispatch.compile }, run),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
217
234
|
/** One subagent entry (a dispatch route) for the native loop: compileSubagentSpec, then compileNative. */
|
|
218
235
|
export async function compileNativeSubagent(subCfg, opts = {}) {
|
|
219
236
|
return compileNative(await compileSubagentSpec(subCfg, opts));
|
package/dist/lib/dispatch.js
CHANGED
|
@@ -73,6 +73,8 @@ function matchSubagent(candidate, subagents) {
|
|
|
73
73
|
return undefined;
|
|
74
74
|
return subagents.find(sub => norm(sub.name) === target);
|
|
75
75
|
}
|
|
76
|
+
/** What a subagent name can look like: one token, linear to test. */
|
|
77
|
+
const ROUTE_SHAPED = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
76
78
|
/**
|
|
77
79
|
* Turn the router's raw output into a route that definitely exists.
|
|
78
80
|
*
|
|
@@ -108,7 +110,8 @@ export function resolveRoute(rawOutput, config) {
|
|
|
108
110
|
parsed = JSON.parse(unfence(rawOutput));
|
|
109
111
|
}
|
|
110
112
|
catch {
|
|
111
|
-
|
|
113
|
+
// The reason reaches the server log: the size, never the text (the router can echo the person).
|
|
114
|
+
return defaultResolution(`router output was not JSON (${rawOutput.trim().length} chars)`);
|
|
112
115
|
}
|
|
113
116
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
114
117
|
return defaultResolution('router output was not a JSON object');
|
|
@@ -120,7 +123,8 @@ export function resolveRoute(rawOutput, config) {
|
|
|
120
123
|
}
|
|
121
124
|
const matched = matchSubagent(rawRoute, subagents);
|
|
122
125
|
if (!matched) {
|
|
123
|
-
|
|
126
|
+
// A route-shaped value is named; anything else is model text and is only measured.
|
|
127
|
+
return defaultResolution(ROUTE_SHAPED.test(rawRoute) ? `router chose unknown route '${rawRoute}'` : `router chose an unknown route (${rawRoute.length} chars)`);
|
|
124
128
|
}
|
|
125
129
|
const rawReason = record[reasonKey];
|
|
126
130
|
return {
|
package/dist/lib/doctor.d.ts
CHANGED
|
@@ -75,6 +75,21 @@ export interface DoctorOAuth {
|
|
|
75
75
|
set: Record<string, boolean>;
|
|
76
76
|
problems: string[];
|
|
77
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* The static credential variables the syndicates send, under the operator's
|
|
80
|
+
* credential host allowlist (ADR 0122). Present when any syndicate sends one
|
|
81
|
+
* or MELCHIZEDEK_CREDENTIAL_HOSTS is set. Names and hosts only.
|
|
82
|
+
*/
|
|
83
|
+
export interface DoctorCredentials {
|
|
84
|
+
/** Whether MELCHIZEDEK_CREDENTIAL_HOSTS is set. */
|
|
85
|
+
set: boolean;
|
|
86
|
+
/** Every variable the syndicates send (bearer_env, api_key.env, client_secret_env), sorted. */
|
|
87
|
+
envs: string[];
|
|
88
|
+
/** With no allowlist: the variables sent wherever the YAML says, named. Not a --check failure. */
|
|
89
|
+
warning?: string;
|
|
90
|
+
/** A malformed allowlist, and each use it refuses (`file: agent · tools · role: why`). A --check failure. */
|
|
91
|
+
problems: string[];
|
|
92
|
+
}
|
|
78
93
|
export interface DoctorSyndicate {
|
|
79
94
|
/** Path relative to the agents dir, e.g. "examples/council.yaml". */
|
|
80
95
|
file: string;
|
|
@@ -106,6 +121,11 @@ export interface DoctorSyndicate {
|
|
|
106
121
|
};
|
|
107
122
|
/** The tools that need an OAuth grant (ADR 0112), when any do. */
|
|
108
123
|
grants?: DoctorGrant[];
|
|
124
|
+
/** The static credential variables its tools send (ADR 0122), and what the allowlist refuses; when it sends any. */
|
|
125
|
+
credentials?: {
|
|
126
|
+
envs: string[];
|
|
127
|
+
problems: string[];
|
|
128
|
+
};
|
|
109
129
|
}
|
|
110
130
|
export interface Unlock {
|
|
111
131
|
env: string;
|
|
@@ -141,6 +161,66 @@ export interface DoctorResult {
|
|
|
141
161
|
counts: Record<VerdictState, number>;
|
|
142
162
|
/** The OAuth setup, when any syndicate declares a grant or its variables are set. */
|
|
143
163
|
oauth?: DoctorOAuth;
|
|
164
|
+
/** The credential host allowlist (ADR 0122), when any syndicate sends a static credential or it is set. */
|
|
165
|
+
credentials?: DoctorCredentials;
|
|
166
|
+
/** How each provider would be reached here: the providers line, as data. */
|
|
167
|
+
providers: ProviderPath[];
|
|
168
|
+
/** How the A2A server would authenticate and bill, when any of its variables is set. */
|
|
169
|
+
serving?: DoctorServing;
|
|
170
|
+
/** A stored Sign in with ChatGPT credential (ADR 0126), when one is present or its variables are set. */
|
|
171
|
+
chatgpt?: DoctorChatGpt;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* A Sign in with ChatGPT credential (ADR 0126): where it is, whether it is
|
|
175
|
+
* signed in and carries OpenAI ids here, and what stops it. Never a token.
|
|
176
|
+
*/
|
|
177
|
+
export interface DoctorChatGpt {
|
|
178
|
+
file: string;
|
|
179
|
+
/** False when MELCHIZEDEK_CHATGPT_SIGNIN=off. */
|
|
180
|
+
enabled: boolean;
|
|
181
|
+
present: boolean;
|
|
182
|
+
signedIn: boolean;
|
|
183
|
+
/** OpenAI ids run on it here (no OPENAI_API_KEY, OpenAI on its own API). */
|
|
184
|
+
routesOpenAi: boolean;
|
|
185
|
+
/** Epoch milliseconds the stored access token expires (it refreshes on use). */
|
|
186
|
+
expiresAt?: number;
|
|
187
|
+
/** A served configuration on it, or a file that cannot be used. `--check` fails on any. */
|
|
188
|
+
problems: string[];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* How one provider would be reached under this environment (the doctor's
|
|
192
|
+
* providers line): funded or not, directly or through the gateway, and on
|
|
193
|
+
* which platform. `melchizedek-setup --auto` reads its levels from this.
|
|
194
|
+
*/
|
|
195
|
+
export interface ProviderPath {
|
|
196
|
+
provider: ProviderId;
|
|
197
|
+
label: string;
|
|
198
|
+
funded: boolean;
|
|
199
|
+
transport: 'direct' | 'gateway';
|
|
200
|
+
/** The gateway id when transport is 'gateway'. */
|
|
201
|
+
gateway?: string;
|
|
202
|
+
/** `local` for Ollama; a cloud platform (vertex, bedrock, azure) or `direct` otherwise. */
|
|
203
|
+
platform: Platform | 'local';
|
|
204
|
+
keyEnv: string | null;
|
|
205
|
+
/** `chatgpt-signin` when a stored Sign in with ChatGPT funds the provider instead of its key (ADR 0126). */
|
|
206
|
+
credential?: 'chatgpt-signin';
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The A2A server's identity and billing settings (scripts/a2a_server.ts),
|
|
210
|
+
* names and presence only: which `A2A_AUTH` mode, whether the secret and the
|
|
211
|
+
* mode's own variables are set, and who pays (`A2A_KEY_MODE`).
|
|
212
|
+
*/
|
|
213
|
+
export interface DoctorServing {
|
|
214
|
+
/** A2A_AUTH as set, or `secret` (the server's default). */
|
|
215
|
+
auth: string;
|
|
216
|
+
/** True when A2A_AUTH is set explicitly. */
|
|
217
|
+
authDeclared: boolean;
|
|
218
|
+
/** A2A_KEY_MODE as set, or `server`. */
|
|
219
|
+
keyMode: string;
|
|
220
|
+
/** Which of the serving variables are set (never their values). */
|
|
221
|
+
set: Record<string, boolean>;
|
|
222
|
+
/** What the server would refuse to start on, by variable name. */
|
|
223
|
+
problems: string[];
|
|
144
224
|
}
|
|
145
225
|
export interface DoctorEndpoint {
|
|
146
226
|
provider: ProviderId;
|
|
@@ -166,6 +246,16 @@ export interface DoctorEndpoint {
|
|
|
166
246
|
export declare function runtimeReport(env?: NodeJS.ProcessEnv): DoctorRuntime;
|
|
167
247
|
/** One row per provider that is not on its vendor's public API with an env key. */
|
|
168
248
|
export declare function endpointRows(): DoctorEndpoint[];
|
|
249
|
+
/** One status per provider, probed with an id of its own prefix (the providers line). */
|
|
250
|
+
export declare function providerPaths(): ProviderPath[];
|
|
251
|
+
/** The serving variables the doctor reports by presence. */
|
|
252
|
+
export declare const SERVING_ENV: readonly ['A2A_AUTH', 'A2A_SERVER_SECRET', 'A2A_CALLERS', 'A2A_JWT_ISSUER', 'A2A_JWT_AUDIENCE', 'A2A_JWT_JWKS_URL', 'A2A_JWT_SECRET', 'A2A_TRUSTED_USER_HEADER', 'A2A_KEY_MODE', 'PUBLIC_URL'];
|
|
253
|
+
/**
|
|
254
|
+
* How the A2A server would authenticate callers and who would pay, from the
|
|
255
|
+
* environment; undefined when none of its variables is set. The checks are
|
|
256
|
+
* the ones scripts/a2a_server.ts stops on, reported by name.
|
|
257
|
+
*/
|
|
258
|
+
export declare function servingReport(env?: NodeJS.ProcessEnv): DoctorServing | undefined;
|
|
169
259
|
export declare const CONSOLE_URLS: Record<ProviderId, string>;
|
|
170
260
|
/**
|
|
171
261
|
* Every syndicate file the loader can see: the root (this deployment's
|
|
@@ -194,6 +284,23 @@ export declare function runDoctor(options?: {
|
|
|
194
284
|
agentsDir?: string;
|
|
195
285
|
scripts?: Record<string, string>;
|
|
196
286
|
}): DoctorResult;
|
|
287
|
+
/**
|
|
288
|
+
* The Sign in with ChatGPT line (ADR 0126), when a credential file is present
|
|
289
|
+
* or one of its variables is set. `served`: a serving variable is set, so
|
|
290
|
+
* this configuration is meant for melchizedek-serve, which refuses to start
|
|
291
|
+
* while the sign-in is the OpenAI path.
|
|
292
|
+
*/
|
|
293
|
+
export declare function chatGptReport(served: boolean, env?: NodeJS.ProcessEnv): DoctorChatGpt | undefined;
|
|
294
|
+
/**
|
|
295
|
+
* Why `melchizedek-doctor --check` exits non-zero, one line each; empty when
|
|
296
|
+
* it passes. A blocked syndicate, a runtime problem, a `MODEL_GATEWAY` that
|
|
297
|
+
* names no gateway (named with the accepted values only: the value may be a
|
|
298
|
+
* key pasted into the wrong variable), the OAuth setup's and the credential
|
|
299
|
+
* allowlist's problems, and each grant whose hosts the OAuth host allowlist
|
|
300
|
+
* refuses (ADR 0114), and the Sign in with ChatGPT line's problems (ADR 0126).
|
|
301
|
+
* Names and hosts only, never a value.
|
|
302
|
+
*/
|
|
303
|
+
export declare function checkProblems(result: DoctorResult): string[];
|
|
197
304
|
export declare function renderDoctor(result: DoctorResult, opts?: {
|
|
198
305
|
color?: boolean;
|
|
199
306
|
}): string;
|
package/dist/lib/doctor.js
CHANGED
|
@@ -34,10 +34,16 @@ import { credentialSource, endpointFromEnv, endpointLabel, endpointProblems, PLA
|
|
|
34
34
|
import { GATEWAY_ENV, GATEWAY_KEY_ENV, GATEWAYS, gatewayConfig, gatewayProblem, gatewayUsable, } from './models/gateway.js';
|
|
35
35
|
import { PROVIDERS } from './models/providerMap.js';
|
|
36
36
|
import { describeRuntime } from './runtime/runtimeFlag.js';
|
|
37
|
+
import { CHATGPT_SIGNIN_ENV, CHATGPT_SIGNIN_FILE_ENV, chatGptSignInRoutesOpenAi, chatGptSignInStatus } from './chatgpt/state.js';
|
|
37
38
|
import { agentOAuthGrants, oauthEnvNames } from './tools/oauthTools.js';
|
|
38
39
|
import { grantHostProblems, OAUTH_REDIRECT_URI_ENV, oauthEnvProblems } from './a2a/oauthSetup.js';
|
|
39
40
|
import { CREDENTIAL_KEY_ENV } from './tools/credentialCipher.js';
|
|
40
41
|
import { OAUTH_HOSTS_ENV } from './tools/oauthHosts.js';
|
|
42
|
+
import { CREDENTIAL_HOSTS_ENV, credentialHosts } from './tools/credentialHosts.js';
|
|
43
|
+
import { syndicateCredentialUses } from './tools/credentialUses.js';
|
|
44
|
+
import { credentialHostEnvProblems, unboundCredentialWarning } from './a2a/oauthSetup.js';
|
|
45
|
+
/** The `gateway.id` the doctor reports when `MODEL_GATEWAY` names no known gateway; the value itself is never kept. */
|
|
46
|
+
const UNRECOGNISED_GATEWAY = 'unrecognised';
|
|
41
47
|
function resolvable(module) {
|
|
42
48
|
try {
|
|
43
49
|
createRequire(import.meta.url).resolve(module);
|
|
@@ -100,6 +106,70 @@ export function endpointRows() {
|
|
|
100
106
|
}
|
|
101
107
|
return rows;
|
|
102
108
|
}
|
|
109
|
+
/** One status per provider, probed with an id of its own prefix (the providers line). */
|
|
110
|
+
export function providerPaths() {
|
|
111
|
+
return Object.keys(PROVIDERS).map((p) => {
|
|
112
|
+
const r = describeCapabilities(PROBE_ID[p]);
|
|
113
|
+
return {
|
|
114
|
+
provider: p,
|
|
115
|
+
label: PROVIDERS[p].label,
|
|
116
|
+
funded: r.funded,
|
|
117
|
+
transport: r.transport,
|
|
118
|
+
...(r.gateway ? { gateway: r.gateway } : {}),
|
|
119
|
+
platform: p === 'ollama' ? 'local' : (r.platform ?? 'direct'),
|
|
120
|
+
keyEnv: r.keyEnv,
|
|
121
|
+
...(p === 'openai' && r.funded && r.transport === 'direct' && chatGptSignInRoutesOpenAi() ? { credential: 'chatgpt-signin' } : {}),
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/** The serving variables the doctor reports by presence. */
|
|
126
|
+
export const SERVING_ENV = [
|
|
127
|
+
'A2A_AUTH',
|
|
128
|
+
'A2A_SERVER_SECRET',
|
|
129
|
+
'A2A_CALLERS',
|
|
130
|
+
'A2A_JWT_ISSUER',
|
|
131
|
+
'A2A_JWT_AUDIENCE',
|
|
132
|
+
'A2A_JWT_JWKS_URL',
|
|
133
|
+
'A2A_JWT_SECRET',
|
|
134
|
+
'A2A_TRUSTED_USER_HEADER',
|
|
135
|
+
'A2A_KEY_MODE',
|
|
136
|
+
'PUBLIC_URL',
|
|
137
|
+
];
|
|
138
|
+
/**
|
|
139
|
+
* How the A2A server would authenticate callers and who would pay, from the
|
|
140
|
+
* environment; undefined when none of its variables is set. The checks are
|
|
141
|
+
* the ones scripts/a2a_server.ts stops on, reported by name.
|
|
142
|
+
*/
|
|
143
|
+
export function servingReport(env = process.env) {
|
|
144
|
+
const has = (name) => !!env[name]?.trim();
|
|
145
|
+
const set = Object.fromEntries(SERVING_ENV.map((name) => [name, has(name)]));
|
|
146
|
+
if (!Object.values(set).some(Boolean))
|
|
147
|
+
return undefined;
|
|
148
|
+
// A value outside the known modes is reported as `invalid`, never echoed:
|
|
149
|
+
// a secret pasted into the wrong variable must not reach the terminal.
|
|
150
|
+
const rawAuth = (env.A2A_AUTH ?? '').trim().toLowerCase() || 'secret';
|
|
151
|
+
const rawKeyMode = (env.A2A_KEY_MODE ?? '').trim().toLowerCase() || 'server';
|
|
152
|
+
const auth = ['secret', 'callers', 'jwt', 'header'].includes(rawAuth) ? rawAuth : 'invalid';
|
|
153
|
+
const keyMode = rawKeyMode === 'server' || rawKeyMode === 'byok' ? rawKeyMode : 'invalid';
|
|
154
|
+
const problems = [];
|
|
155
|
+
if (auth === 'invalid')
|
|
156
|
+
problems.push('A2A_AUTH must be secret, callers, jwt or header');
|
|
157
|
+
if (auth === 'callers' && !has('A2A_CALLERS'))
|
|
158
|
+
problems.push('A2A_AUTH=callers needs A2A_CALLERS');
|
|
159
|
+
if (auth === 'jwt' && (!has('A2A_JWT_ISSUER') || !has('A2A_JWT_AUDIENCE')))
|
|
160
|
+
problems.push('A2A_AUTH=jwt needs A2A_JWT_ISSUER and A2A_JWT_AUDIENCE');
|
|
161
|
+
if (auth === 'jwt' && !has('A2A_JWT_JWKS_URL') && !has('A2A_JWT_SECRET'))
|
|
162
|
+
problems.push('A2A_AUTH=jwt needs A2A_JWT_JWKS_URL or A2A_JWT_SECRET');
|
|
163
|
+
if (auth === 'header' && (!has('A2A_TRUSTED_USER_HEADER') || !has('A2A_SERVER_SECRET')))
|
|
164
|
+
problems.push('A2A_AUTH=header needs A2A_TRUSTED_USER_HEADER and A2A_SERVER_SECRET');
|
|
165
|
+
if (keyMode !== 'server' && keyMode !== 'byok')
|
|
166
|
+
problems.push("A2A_KEY_MODE must be 'server' or 'byok'");
|
|
167
|
+
if (has('PUBLIC_URL') && !has('A2A_AUTH'))
|
|
168
|
+
problems.push('PUBLIC_URL is set, so A2A_AUTH must be set explicitly (with A2A_SERVED_AGENTS and A2A_TRUST_PROXY)');
|
|
169
|
+
if (auth === 'secret' && !has('A2A_SERVER_SECRET') && keyMode === 'byok')
|
|
170
|
+
problems.push('A2A_KEY_MODE=byok without A2A_SERVER_SECRET (or caller tokens) serves unauthenticated, loopback only');
|
|
171
|
+
return { auth, authDeclared: has('A2A_AUTH'), keyMode, set, problems };
|
|
172
|
+
}
|
|
103
173
|
// ── Where to get a key ───────────────────────────────────────────────────────
|
|
104
174
|
export const CONSOLE_URLS = {
|
|
105
175
|
gemini: 'https://aistudio.google.com',
|
|
@@ -305,6 +375,17 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
|
|
|
305
375
|
const nestedErrors = [];
|
|
306
376
|
const grants = [];
|
|
307
377
|
walk(config, '', { load, seen: new Set([file, path.basename(file)]), nestedErrors, grants }, rows);
|
|
378
|
+
// Static credentials and the credential host allowlist (ADR 0122); a nested file that cannot load is reported above.
|
|
379
|
+
const loadQuiet = (ref) => {
|
|
380
|
+
try {
|
|
381
|
+
return load(ref);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return {};
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
const credentialEnvs = [...new Set(syndicateCredentialUses([config], loadQuiet).map((u) => u.env))].sort();
|
|
388
|
+
const credentials = credentialEnvs.length ? { envs: credentialEnvs, problems: credentialHostEnvProblems([config], process.env, loadQuiet) } : undefined;
|
|
308
389
|
const memory = config.memory_system === 'long-term'
|
|
309
390
|
? {
|
|
310
391
|
namespace: config.memory_namespace ?? LEGACY_MEMORY_APP_NAME,
|
|
@@ -324,6 +405,7 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
|
|
|
324
405
|
...(nestedErrors.length ? { error: nestedErrors.join('\n') } : {}),
|
|
325
406
|
...(memory ? { memory } : {}),
|
|
326
407
|
...(grants.length ? { grants } : {}),
|
|
408
|
+
...(credentials ? { credentials } : {}),
|
|
327
409
|
};
|
|
328
410
|
}
|
|
329
411
|
catch (err) {
|
|
@@ -405,8 +487,9 @@ export function runDoctor(options = {}) {
|
|
|
405
487
|
const rawGateway = (process.env[GATEWAY_ENV] ?? '').trim();
|
|
406
488
|
const gateway = rawGateway
|
|
407
489
|
? {
|
|
408
|
-
|
|
409
|
-
|
|
490
|
+
// An unrecognised value is never echoed: it may be a key pasted into the wrong variable.
|
|
491
|
+
id: cfg?.gateway.id ?? UNRECOGNISED_GATEWAY,
|
|
492
|
+
label: cfg?.gateway.label ?? 'an unrecognised gateway',
|
|
410
493
|
usable: gatewayUsable(),
|
|
411
494
|
...(gatewayProblem() ? { problem: gatewayProblem() } : {}),
|
|
412
495
|
}
|
|
@@ -424,7 +507,68 @@ export function runDoctor(options = {}) {
|
|
|
424
507
|
const grants = syndicates.flatMap((s) => s.grants ?? []).map((g) => ({ agent: g.agent, tools: g.tools, oauth2: { provider: g.provider, grant: g.grant, token_url: '' } }));
|
|
425
508
|
const set = Object.fromEntries([CREDENTIAL_KEY_ENV, OAUTH_REDIRECT_URI_ENV, OAUTH_HOSTS_ENV].map((name) => [name, !!process.env[name]?.trim()]));
|
|
426
509
|
const oauth = grants.length || Object.values(set).some(Boolean) ? { set, problems: oauthEnvProblems(grants) } : undefined;
|
|
427
|
-
|
|
510
|
+
// The credential host allowlist (ADR 0122): unbound variables named, refusals listed, names and hosts only.
|
|
511
|
+
const credentialSet = !!process.env[CREDENTIAL_HOSTS_ENV]?.trim();
|
|
512
|
+
const envs = [...new Set(syndicates.flatMap((s) => s.credentials?.envs ?? []))].sort();
|
|
513
|
+
let credentials;
|
|
514
|
+
if (envs.length || credentialSet) {
|
|
515
|
+
const problems = [];
|
|
516
|
+
try {
|
|
517
|
+
credentialHosts();
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
problems.push(err instanceof Error ? err.message : `${CREDENTIAL_HOSTS_ENV} is malformed`);
|
|
521
|
+
}
|
|
522
|
+
if (!problems.length)
|
|
523
|
+
for (const s of syndicates)
|
|
524
|
+
for (const p of s.credentials?.problems ?? [])
|
|
525
|
+
problems.push(`${s.file}: ${p}`);
|
|
526
|
+
const warning = unboundCredentialWarning(envs);
|
|
527
|
+
credentials = { set: credentialSet, envs, ...(warning ? { warning } : {}), problems };
|
|
528
|
+
}
|
|
529
|
+
const serving = servingReport();
|
|
530
|
+
const chatgpt = chatGptReport(!!serving);
|
|
531
|
+
return {
|
|
532
|
+
agentsDir,
|
|
533
|
+
runtime: runtimeReport(),
|
|
534
|
+
syndicates,
|
|
535
|
+
unlocks,
|
|
536
|
+
gateway,
|
|
537
|
+
endpoints: endpointRows(),
|
|
538
|
+
counts,
|
|
539
|
+
...(oauth ? { oauth } : {}),
|
|
540
|
+
...(credentials ? { credentials } : {}),
|
|
541
|
+
providers: providerPaths(),
|
|
542
|
+
...(serving ? { serving } : {}),
|
|
543
|
+
...(chatgpt ? { chatgpt } : {}),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* The Sign in with ChatGPT line (ADR 0126), when a credential file is present
|
|
548
|
+
* or one of its variables is set. `served`: a serving variable is set, so
|
|
549
|
+
* this configuration is meant for melchizedek-serve, which refuses to start
|
|
550
|
+
* while the sign-in is the OpenAI path.
|
|
551
|
+
*/
|
|
552
|
+
export function chatGptReport(served, env = process.env) {
|
|
553
|
+
const status = chatGptSignInStatus(env);
|
|
554
|
+
if (!status.present && !env[CHATGPT_SIGNIN_FILE_ENV]?.trim() && !env[CHATGPT_SIGNIN_ENV]?.trim())
|
|
555
|
+
return undefined;
|
|
556
|
+
const routesOpenAi = chatGptSignInRoutesOpenAi(env);
|
|
557
|
+
const problems = [];
|
|
558
|
+
if (status.problem)
|
|
559
|
+
problems.push(status.problem);
|
|
560
|
+
if (served && routesOpenAi) {
|
|
561
|
+
problems.push(`Sign in with ChatGPT is local only, and it is the OpenAI path in this served configuration: melchizedek-serve and melchizedek-worker refuse to start. Set OPENAI_API_KEY (or Azure OpenAI), or ${CHATGPT_SIGNIN_ENV}=off for the served process.`);
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
file: status.file,
|
|
565
|
+
enabled: status.enabled,
|
|
566
|
+
present: status.present,
|
|
567
|
+
signedIn: status.signedIn,
|
|
568
|
+
routesOpenAi,
|
|
569
|
+
...(status.expiresAt ? { expiresAt: status.expiresAt } : {}),
|
|
570
|
+
problems,
|
|
571
|
+
};
|
|
428
572
|
}
|
|
429
573
|
// ── Rendering ────────────────────────────────────────────────────────────────
|
|
430
574
|
const ANSI = {
|
|
@@ -445,6 +589,37 @@ const PROBE_ID = {
|
|
|
445
589
|
moonshot: 'kimi-x',
|
|
446
590
|
ollama: 'ollama/x',
|
|
447
591
|
};
|
|
592
|
+
/**
|
|
593
|
+
* Why `melchizedek-doctor --check` exits non-zero, one line each; empty when
|
|
594
|
+
* it passes. A blocked syndicate, a runtime problem, a `MODEL_GATEWAY` that
|
|
595
|
+
* names no gateway (named with the accepted values only: the value may be a
|
|
596
|
+
* key pasted into the wrong variable), the OAuth setup's and the credential
|
|
597
|
+
* allowlist's problems, and each grant whose hosts the OAuth host allowlist
|
|
598
|
+
* refuses (ADR 0114), and the Sign in with ChatGPT line's problems (ADR 0126).
|
|
599
|
+
* Names and hosts only, never a value.
|
|
600
|
+
*/
|
|
601
|
+
export function checkProblems(result) {
|
|
602
|
+
const problems = [];
|
|
603
|
+
if (result.counts.blocked > 0)
|
|
604
|
+
problems.push(`${result.counts.blocked} syndicate(s) blocked`);
|
|
605
|
+
if (result.runtime.problem)
|
|
606
|
+
problems.push(result.runtime.problem);
|
|
607
|
+
if (result.gateway?.id === UNRECOGNISED_GATEWAY) {
|
|
608
|
+
problems.push(`gateway: ${GATEWAY_ENV} is set to an unrecognised value (not shown); it must be one of: ${Object.keys(GATEWAYS).join(', ')}`);
|
|
609
|
+
}
|
|
610
|
+
for (const p of result.oauth?.problems ?? [])
|
|
611
|
+
problems.push(`oauth: ${p}`);
|
|
612
|
+
for (const p of result.credentials?.problems ?? [])
|
|
613
|
+
problems.push(`credentials: ${p}`);
|
|
614
|
+
for (const p of result.chatgpt?.problems ?? [])
|
|
615
|
+
problems.push(`chatgpt: ${p}`);
|
|
616
|
+
for (const s of result.syndicates) {
|
|
617
|
+
for (const g of s.grants ?? [])
|
|
618
|
+
for (const p of g.hostProblems ?? [])
|
|
619
|
+
problems.push(`${s.file}: ${g.agent} · ${g.provider}: ${p}`);
|
|
620
|
+
}
|
|
621
|
+
return problems;
|
|
622
|
+
}
|
|
448
623
|
function pad(s, n) {
|
|
449
624
|
return s.length >= n ? s.slice(0, n - 1) + '…' : s.padEnd(n);
|
|
450
625
|
}
|
|
@@ -471,11 +646,10 @@ export function renderDoctor(result, opts = {}) {
|
|
|
471
646
|
// Provider line: what is funded, and how. Each provider is probed with an
|
|
472
647
|
// id of its own prefix, so the line names ITS key (a fallthrough once put
|
|
473
648
|
// xAI's key beside Moonshot).
|
|
474
|
-
const providerBits =
|
|
475
|
-
const r = describeCapabilities(PROBE_ID[p]);
|
|
649
|
+
const providerBits = (result.providers ?? providerPaths()).map((r) => {
|
|
476
650
|
const mark = !r.funded ? `${c.red}✗${c.reset}` : r.transport === 'gateway' ? `${c.yellow}◇${c.reset}` : `${c.green}✓${c.reset}`;
|
|
477
|
-
const how = !r.funded ? `${c.dim}${r.keyEnv} not set${c.reset}` : r.transport === 'gateway' ? `${c.dim}via gateway:${r.gateway}${c.reset}` :
|
|
478
|
-
return `${mark} ${
|
|
651
|
+
const how = !r.funded ? `${c.dim}${r.keyEnv} not set${c.reset}` : r.transport === 'gateway' ? `${c.dim}via gateway:${r.gateway}${c.reset}` : r.platform === 'local' ? `${c.dim}local${c.reset}` : r.credential === 'chatgpt-signin' ? `${c.dim}ChatGPT sign-in, local only${c.reset}` : `${c.dim}${r.platform}${c.reset}`;
|
|
652
|
+
return `${mark} ${r.label} ${how}`;
|
|
479
653
|
});
|
|
480
654
|
lines.push('providers ' + providerBits.join(' '));
|
|
481
655
|
for (const e of result.endpoints ?? []) {
|
|
@@ -490,6 +664,32 @@ export function renderDoctor(result, opts = {}) {
|
|
|
490
664
|
? `gateway ${c.red}✗${c.reset} ${result.gateway.problem}`
|
|
491
665
|
: `gateway ${c.yellow}◇${c.reset} ${result.gateway.label} fills in for any provider whose direct key is absent ${c.dim}(native search is lost on that path)${c.reset}`);
|
|
492
666
|
}
|
|
667
|
+
if (result.serving) {
|
|
668
|
+
// Names only: the A2A identity mode, who pays, and what the server would refuse.
|
|
669
|
+
const sv = result.serving;
|
|
670
|
+
const mark = sv.problems.length ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
671
|
+
const set = Object.entries(sv.set).filter(([, on]) => on).map(([name]) => name).join(', ');
|
|
672
|
+
lines.push(`serving ${mark} A2A_AUTH=${sv.auth}${sv.authDeclared ? '' : ' (default)'} · A2A_KEY_MODE=${sv.keyMode} ${c.dim}· set: ${set || 'none'}${c.reset}`);
|
|
673
|
+
for (const p of sv.problems)
|
|
674
|
+
lines.push(` ${c.red}✗ ${p}${c.reset}`);
|
|
675
|
+
}
|
|
676
|
+
if (result.chatgpt) {
|
|
677
|
+
// Presence and the file's path, never a token (ADR 0126).
|
|
678
|
+
const g = result.chatgpt;
|
|
679
|
+
const mark = g.problems.length ? `${c.red}✗${c.reset}` : g.routesOpenAi ? `${c.green}✓${c.reset}` : `${c.dim}·${c.reset}`;
|
|
680
|
+
const state = !g.enabled
|
|
681
|
+
? `${CHATGPT_SIGNIN_ENV}=off, ignored`
|
|
682
|
+
: !g.present
|
|
683
|
+
? 'not signed in'
|
|
684
|
+
: !g.signedIn
|
|
685
|
+
? 'signed out (run melchizedek-setup --chatgpt-signin)'
|
|
686
|
+
: g.routesOpenAi
|
|
687
|
+
? 'signed in · carries OpenAI ids · local only'
|
|
688
|
+
: 'signed in · not used (OPENAI_API_KEY or another OpenAI endpoint wins)';
|
|
689
|
+
lines.push(`chatgpt ${mark} ${state} ${c.dim}· ${g.file}${c.reset}`);
|
|
690
|
+
for (const p of g.problems)
|
|
691
|
+
lines.push(` ${c.red}✗ ${p}${c.reset}`);
|
|
692
|
+
}
|
|
493
693
|
if (result.oauth) {
|
|
494
694
|
// Names only: which variables are set, never their values (ADR 0114).
|
|
495
695
|
const set = Object.entries(result.oauth.set).map(([name, on]) => `${name} ${on ? 'set' : 'unset'}`).join(' · ');
|
|
@@ -498,6 +698,16 @@ export function renderDoctor(result, opts = {}) {
|
|
|
498
698
|
for (const p of result.oauth.problems)
|
|
499
699
|
lines.push(` ${c.red}✗ ${p}${c.reset}`);
|
|
500
700
|
}
|
|
701
|
+
if (result.credentials) {
|
|
702
|
+
// Names only: which variables are sent, never their values (ADR 0122).
|
|
703
|
+
const cr = result.credentials;
|
|
704
|
+
const mark = cr.problems.length ? `${c.red}✗${c.reset}` : cr.warning ? `${c.yellow}⚠${c.reset}` : `${c.green}✓${c.reset}`;
|
|
705
|
+
lines.push(`credentials ${mark} ${c.dim}${CREDENTIAL_HOSTS_ENV} ${cr.set ? 'set' : 'unset'}${cr.envs.length ? ` · sent: ${cr.envs.join(', ')}` : ''}${c.reset}`);
|
|
706
|
+
if (cr.warning)
|
|
707
|
+
lines.push(` ${c.yellow}⚠ ${cr.warning}${c.reset}`);
|
|
708
|
+
for (const p of cr.problems)
|
|
709
|
+
lines.push(` ${c.red}✗ ${p}${c.reset}`);
|
|
710
|
+
}
|
|
501
711
|
lines.push('');
|
|
502
712
|
// The table: one line per syndicate (name, verdict, run command), then
|
|
503
713
|
// one indented line per agent.
|
|
@@ -3,6 +3,8 @@ export type { DispatchConfig } from './dispatch.ts';
|
|
|
3
3
|
import type { SkillsConfig } from './tools/skillToolset.ts';
|
|
4
4
|
import type { OpenApiConfig } from './tools/openapiTools.ts';
|
|
5
5
|
import type { McpAuthConfig } from './tools/oauthTools.ts';
|
|
6
|
+
import type { McpTransportKind } from './tools/mcpTransports.ts';
|
|
7
|
+
import type { McpServerConfig } from './tools/mcpToolFactory.ts';
|
|
6
8
|
import type { ContextConfig, ExampleConfig } from './compile.ts';
|
|
7
9
|
export type { OpenApiConfig } from './tools/openapiTools.ts';
|
|
8
10
|
export type { SkillsConfig } from './tools/skillToolset.ts';
|
|
@@ -129,6 +131,15 @@ export interface AgentYamlConfig {
|
|
|
129
131
|
schema?: Record<string, unknown>;
|
|
130
132
|
mime?: 'application/json' | 'text/plain';
|
|
131
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* Which tools the model may call (v2, ADR 0117): `auto`, `none`,
|
|
136
|
+
* `required`, or `{ name }` for one tool. Replaces
|
|
137
|
+
* generateContentConfig.toolConfig.functionCallingConfig; folded into it
|
|
138
|
+
* by the loader (lib/agentDialect.ts toEngineAgent).
|
|
139
|
+
*/
|
|
140
|
+
tool_choice?: 'auto' | 'none' | 'required' | {
|
|
141
|
+
name: string;
|
|
142
|
+
};
|
|
132
143
|
/**
|
|
133
144
|
* Per-provider instruction (v2), keyed by provider id (lib/models/providerMap.ts):
|
|
134
145
|
* `instruction` replaces the agent's instruction, `instruction_append` is
|
|
@@ -144,6 +155,10 @@ export interface AgentYamlConfig {
|
|
|
144
155
|
mcp_tools?: string[];
|
|
145
156
|
/** The OAuth grant the MCP server takes (ADR 0112): each user's, or the server's own. */
|
|
146
157
|
mcp_auth?: McpAuthConfig;
|
|
158
|
+
/** How mcp_server_url is reached: auto (the default), streamable_http or sse (ADR 0124). */
|
|
159
|
+
mcp_transport?: McpTransportKind;
|
|
160
|
+
/** Several MCP servers, each with its tools, grant and transport (ADR 0124). Not with mcp_server_url. */
|
|
161
|
+
mcp_servers?: McpServerConfig[];
|
|
147
162
|
/** Answers when `model` fails provider-side, or its provider's circuit is open (ADR 0044). */
|
|
148
163
|
fallback_model?: string;
|
|
149
164
|
/**
|