melchizedek-agents 1.0.2 → 1.1.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 -2
- package/CHANGELOG.md +333 -0
- package/DOCUMENTATION.md +99 -39
- package/README.md +1 -0
- package/config/agents/README.md +2 -2
- package/config/agents/examples/ares.yaml +6 -10
- package/config/agents/examples/assistant.yaml +6 -6
- package/config/agents/examples/augustin.yaml +9 -15
- package/config/agents/examples/cartographers.yaml +6 -6
- package/config/agents/examples/council.yaml +6 -6
- package/config/agents/examples/critic.yaml +16 -16
- package/config/agents/examples/harness.yaml +4 -4
- package/config/agents/examples/image_production.yaml +33 -32
- package/config/agents/examples/librarian.yaml +4 -4
- package/config/agents/examples/patient_advocate.yaml +6 -10
- package/config/agents/examples/pipeline.yaml +20 -19
- package/config/agents/examples/research.yaml +36 -43
- package/config/agents/examples/scribe.yaml +31 -32
- package/config/agents/examples/scriptorium.yaml +6 -6
- package/config/agents/examples/structured_critic.yaml +82 -0
- package/config/agents/examples/style_council.yaml +9 -11
- package/config/agents/examples/syndicate.yaml +3 -5
- package/config/agents/examples/tutor.yaml +2 -2
- package/config/agents/examples/weather.yaml +2 -2
- package/config/agents/syndicate.schema.json +1979 -0
- package/config/agents/syndicateSchema.yaml +109 -72
- package/config/agents/templates/README.md +1 -1
- package/config/agents/templates/account_memory.yaml +4 -4
- package/config/agents/templates/case_desk.yaml +6 -6
- package/config/agents/templates/conversational.yaml +2 -2
- package/config/agents/templates/draft_review.yaml +6 -6
- package/config/agents/templates/intake_extractor.yaml +61 -60
- package/config/agents/templates/research_brief.yaml +6 -6
- package/config/agents/templates/research_desk.yaml +6 -6
- package/config/agents/templates/review_panel.yaml +8 -8
- package/config/agents/templates/support_triage.yaml +30 -29
- package/config/agents/templates/systems_operator.yaml +21 -5
- package/db/migrations/0014_durable_runs.sql +31 -0
- package/dist/lib/a2a/app.d.ts +15 -0
- package/dist/lib/a2a/app.js +53 -5
- package/dist/lib/a2a/executor.d.ts +7 -1
- package/dist/lib/a2a/executor.js +34 -4
- package/dist/lib/a2a/oauthSetup.d.ts +90 -0
- package/dist/lib/a2a/oauthSetup.js +205 -0
- package/dist/lib/agentDialect.d.ts +66 -0
- package/dist/lib/agentDialect.js +134 -0
- package/dist/lib/compile.d.ts +10 -5
- package/dist/lib/compile.js +69 -37
- package/dist/lib/compileNative.js +2 -0
- package/dist/lib/dispatch.d.ts +10 -5
- package/dist/lib/dispatch.js +10 -5
- package/dist/lib/doctor.d.ts +34 -1
- package/dist/lib/doctor.js +47 -3
- package/dist/lib/loadSyndicate.d.ts +45 -2
- package/dist/lib/loadSyndicate.js +22 -2
- package/dist/lib/memory/namespace.d.ts +6 -5
- package/dist/lib/memory/namespace.js +6 -5
- package/dist/lib/memory/providers.js +3 -4
- package/dist/lib/models/capabilities.d.ts +16 -1
- package/dist/lib/models/capabilities.js +55 -1
- package/dist/lib/observability/tracer.d.ts +8 -1
- package/dist/lib/observability/tracer.js +33 -5
- package/dist/lib/runtime/approvals.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +18 -1
- package/dist/lib/runtime/native/agentLoop.js +105 -11
- package/dist/lib/runtime/native/checkpoint.d.ts +164 -0
- package/dist/lib/runtime/native/checkpoint.js +365 -0
- package/dist/lib/runtime/native/delegate.d.ts +141 -18
- package/dist/lib/runtime/native/delegate.js +235 -39
- package/dist/lib/runtime/native/interrupts.d.ts +101 -2
- package/dist/lib/runtime/native/interrupts.js +201 -1
- package/dist/lib/runtime/native/request.d.ts +13 -3
- package/dist/lib/runtime/native/request.js +28 -4
- package/dist/lib/runtime/native/telemetry.js +10 -15
- package/dist/lib/runtime/questions.d.ts +7 -4
- package/dist/lib/runtime/sessions.d.ts +1 -1
- package/dist/lib/runtime/syndicateTurn.d.ts +7 -1
- package/dist/lib/runtime/syndicateTurn.js +74 -22
- package/dist/lib/runtime/turnControl.d.ts +15 -0
- package/dist/lib/runtime/turnControl.js +10 -0
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +7 -2
- package/dist/lib/storage/postgres/taskQueue.d.ts +14 -2
- package/dist/lib/storage/postgres/taskQueue.js +20 -3
- package/dist/lib/storage/postgres/taskStore.d.ts +26 -2
- package/dist/lib/storage/postgres/taskStore.js +74 -8
- package/dist/lib/syndicateSchema.d.ts +278 -0
- package/dist/lib/syndicateSchema.js +182 -15
- package/dist/lib/tools/auth.d.ts +7 -5
- package/dist/lib/tools/auth.js +12 -4
- package/dist/lib/tools/credentialEnv.d.ts +28 -0
- package/dist/lib/tools/credentialEnv.js +52 -0
- package/dist/lib/tools/credentialStore.js +9 -2
- package/dist/lib/tools/mcpToolFactory.d.ts +25 -2
- package/dist/lib/tools/mcpToolFactory.js +179 -21
- package/dist/lib/tools/oauthHosts.d.ts +71 -0
- package/dist/lib/tools/oauthHosts.js +154 -0
- package/dist/lib/tools/oauthTools.d.ts +233 -0
- package/dist/lib/tools/oauthTools.js +421 -0
- package/dist/lib/tools/openapiTools.d.ts +4 -6
- package/dist/lib/tools/openapiTools.js +40 -36
- package/dist/lib/tools/taskTools.d.ts +30 -5
- package/dist/lib/tools/taskTools.js +74 -10
- package/dist/lib/workflow.d.ts +8 -4
- package/dist/lib/workflow.js +8 -4
- package/dist/lib/workflowConfig.d.ts +6 -0
- package/dist/scripts/a2a_server.d.ts +36 -0
- package/dist/scripts/a2a_server.js +122 -1
- package/dist/scripts/assistant_worker.d.ts +10 -0
- package/dist/scripts/assistant_worker.js +69 -19
- package/dist/scripts/doctor.d.ts +4 -1
- package/dist/scripts/doctor.js +5 -2
- package/dist/scripts/yaml_codemod.d.ts +52 -0
- package/dist/scripts/yaml_codemod.js +570 -0
- package/package.json +11 -7
- package/skills/briefs/melchizedek-author.md +7 -5
- package/skills/briefs/melchizedek-models.md +4 -3
- package/skills/briefs/melchizedek.md +1 -0
- package/skills/melchizedek/SKILL.md +1 -0
- package/skills/melchizedek-author/SKILL.md +7 -4
- package/skills/melchizedek-author/assets/minimal.yaml +13 -13
- package/skills/melchizedek-models/SKILL.md +6 -3
package/dist/lib/dispatch.d.ts
CHANGED
|
@@ -26,11 +26,16 @@
|
|
|
26
26
|
* be logged, traced, and streamed to a waiting user as progress. In DELEGATE
|
|
27
27
|
* mode the choice is only ever implicit in a tool call.
|
|
28
28
|
*
|
|
29
|
-
* ── Why the router
|
|
30
|
-
* The classifier holds an `outputSchema`, and an agent holding one
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* ── Why the router is tool-less ────────────────────────────────────────────
|
|
30
|
+
* The classifier holds an `outputSchema`, and an agent holding one ends its
|
|
31
|
+
* turn on that JSON. Its JSON names a route for code to run, so it holds no
|
|
32
|
+
* subagent tools: dispatch happens in code where it belongs. This is a choice
|
|
33
|
+
* of method, not a constraint of the engine: an orchestrator may hold an
|
|
34
|
+
* `outputSchema` beside its subagent tools, delegate, and answer in that
|
|
35
|
+
* schema itself (ADR 0109; ADK refused the combination, the engine does not).
|
|
36
|
+
* Plan-dispatch is for a turn whose answer IS the specialist's; a schema on a
|
|
37
|
+
* delegating orchestrator is for a turn whose answer is the orchestrator's
|
|
38
|
+
* own structured judgment of what its team returned.
|
|
34
39
|
*
|
|
35
40
|
* ── Fail-static ───────────────────────────────────────────────────────────
|
|
36
41
|
* Every failure mode here resolves to `default_route` and answers the user.
|
package/dist/lib/dispatch.js
CHANGED
|
@@ -26,11 +26,16 @@
|
|
|
26
26
|
* be logged, traced, and streamed to a waiting user as progress. In DELEGATE
|
|
27
27
|
* mode the choice is only ever implicit in a tool call.
|
|
28
28
|
*
|
|
29
|
-
* ── Why the router
|
|
30
|
-
* The classifier holds an `outputSchema`, and an agent holding one
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* ── Why the router is tool-less ────────────────────────────────────────────
|
|
30
|
+
* The classifier holds an `outputSchema`, and an agent holding one ends its
|
|
31
|
+
* turn on that JSON. Its JSON names a route for code to run, so it holds no
|
|
32
|
+
* subagent tools: dispatch happens in code where it belongs. This is a choice
|
|
33
|
+
* of method, not a constraint of the engine: an orchestrator may hold an
|
|
34
|
+
* `outputSchema` beside its subagent tools, delegate, and answer in that
|
|
35
|
+
* schema itself (ADR 0109; ADK refused the combination, the engine does not).
|
|
36
|
+
* Plan-dispatch is for a turn whose answer IS the specialist's; a schema on a
|
|
37
|
+
* delegating orchestrator is for a turn whose answer is the orchestrator's
|
|
38
|
+
* own structured judgment of what its team returned.
|
|
34
39
|
*
|
|
35
40
|
* ── Fail-static ───────────────────────────────────────────────────────────
|
|
36
41
|
* Every failure mode here resolves to `default_route` and answers the user.
|
package/dist/lib/doctor.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* from the YAMLs and the environment: every syndicate the loader can see,
|
|
8
8
|
* every agent's model, the provider that id routes to, whether that path
|
|
9
9
|
* is funded (direct key, gateway, or local), and which declared
|
|
10
|
-
* server-side tools the path will drop
|
|
10
|
+
* server-side tools the path will drop, and which tools need an OAuth
|
|
11
|
+
* grant before they work (ADR 0112). Then it says exactly what to set,
|
|
11
12
|
* grouped by how much each variable unlocks.
|
|
12
13
|
*
|
|
13
14
|
* WHAT IT NEVER DOES:
|
|
@@ -46,6 +47,34 @@ export interface DoctorRow {
|
|
|
46
47
|
gaps: CapabilityGap[];
|
|
47
48
|
}
|
|
48
49
|
export type VerdictState = 'ready' | 'ready-local' | 'via-gateway' | 'blocked';
|
|
50
|
+
/**
|
|
51
|
+
* A tool that sends an OAuth token (ADR 0112): which agent holds it, the
|
|
52
|
+
* provider, and the grant. An authorization_code grant needs each user's
|
|
53
|
+
* consent before the tool works for them; a client_credentials grant needs
|
|
54
|
+
* its variables set. Variable names only, never a value.
|
|
55
|
+
*/
|
|
56
|
+
export interface DoctorGrant {
|
|
57
|
+
agent: string;
|
|
58
|
+
/** `openapi <spec>` or `mcp <url>`. */
|
|
59
|
+
tools: string;
|
|
60
|
+
provider: string;
|
|
61
|
+
grant: 'authorization_code' | 'client_credentials';
|
|
62
|
+
scopes: string[];
|
|
63
|
+
/** The variables the grant reads that are not set here. */
|
|
64
|
+
missingEnv: string[];
|
|
65
|
+
/** Why the operator's OAuth host allowlist refuses this grant here (ADR 0114); empty when it is permitted. */
|
|
66
|
+
hostProblems: string[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The server's OAuth setup under this environment (ADR 0114): which of its
|
|
70
|
+
* variables are set, and what is wrong, names only. Present when a
|
|
71
|
+
* syndicate declares a grant or any of the variables is set.
|
|
72
|
+
*/
|
|
73
|
+
export interface DoctorOAuth {
|
|
74
|
+
/** Which of MELCHIZEDEK_CREDENTIAL_KEY, OAUTH_REDIRECT_URI and MELCHIZEDEK_OAUTH_HOSTS are set. */
|
|
75
|
+
set: Record<string, boolean>;
|
|
76
|
+
problems: string[];
|
|
77
|
+
}
|
|
49
78
|
export interface DoctorSyndicate {
|
|
50
79
|
/** Path relative to the agents dir, e.g. "examples/council.yaml". */
|
|
51
80
|
file: string;
|
|
@@ -75,6 +104,8 @@ export interface DoctorSyndicate {
|
|
|
75
104
|
declared: boolean;
|
|
76
105
|
issue?: string;
|
|
77
106
|
};
|
|
107
|
+
/** The tools that need an OAuth grant (ADR 0112), when any do. */
|
|
108
|
+
grants?: DoctorGrant[];
|
|
78
109
|
}
|
|
79
110
|
export interface Unlock {
|
|
80
111
|
env: string;
|
|
@@ -108,6 +139,8 @@ export interface DoctorResult {
|
|
|
108
139
|
/** Every provider not on its default endpoint: a cloud platform or a proxy (ADR 0023). */
|
|
109
140
|
endpoints: DoctorEndpoint[];
|
|
110
141
|
counts: Record<VerdictState, number>;
|
|
142
|
+
/** The OAuth setup, when any syndicate declares a grant or its variables are set. */
|
|
143
|
+
oauth?: DoctorOAuth;
|
|
111
144
|
}
|
|
112
145
|
export interface DoctorEndpoint {
|
|
113
146
|
provider: ProviderId;
|
package/dist/lib/doctor.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* from the YAMLs and the environment: every syndicate the loader can see,
|
|
8
8
|
* every agent's model, the provider that id routes to, whether that path
|
|
9
9
|
* is funded (direct key, gateway, or local), and which declared
|
|
10
|
-
* server-side tools the path will drop
|
|
10
|
+
* server-side tools the path will drop, and which tools need an OAuth
|
|
11
|
+
* grant before they work (ADR 0112). Then it says exactly what to set,
|
|
11
12
|
* grouped by how much each variable unlocks.
|
|
12
13
|
*
|
|
13
14
|
* WHAT IT NEVER DOES:
|
|
@@ -33,6 +34,10 @@ import { credentialSource, endpointFromEnv, endpointLabel, endpointProblems, PLA
|
|
|
33
34
|
import { GATEWAY_ENV, GATEWAY_KEY_ENV, GATEWAYS, gatewayConfig, gatewayProblem, gatewayUsable, } from './models/gateway.js';
|
|
34
35
|
import { PROVIDERS } from './models/providerMap.js';
|
|
35
36
|
import { describeRuntime } from './runtime/runtimeFlag.js';
|
|
37
|
+
import { agentOAuthGrants, oauthEnvNames } from './tools/oauthTools.js';
|
|
38
|
+
import { grantHostProblems, OAUTH_REDIRECT_URI_ENV, oauthEnvProblems } from './a2a/oauthSetup.js';
|
|
39
|
+
import { CREDENTIAL_KEY_ENV } from './tools/credentialCipher.js';
|
|
40
|
+
import { OAUTH_HOSTS_ENV } from './tools/oauthHosts.js';
|
|
36
41
|
function resolvable(module) {
|
|
37
42
|
try {
|
|
38
43
|
createRequire(import.meta.url).resolve(module);
|
|
@@ -160,6 +165,18 @@ export function tierOf(rows) {
|
|
|
160
165
|
return 'multi-provider';
|
|
161
166
|
return providers[0] === 'ollama' ? 'keyless' : providers[0];
|
|
162
167
|
}
|
|
168
|
+
/** The OAuth grants one agent's tools declare, with the variables not set here. */
|
|
169
|
+
function grantsOf(agent, prefix) {
|
|
170
|
+
return agentOAuthGrants(agent, prefix).map((use) => ({
|
|
171
|
+
agent: use.agent,
|
|
172
|
+
tools: use.tools,
|
|
173
|
+
provider: use.oauth2.provider,
|
|
174
|
+
grant: use.oauth2.grant,
|
|
175
|
+
scopes: [...(use.oauth2.scopes ?? [])],
|
|
176
|
+
missingEnv: oauthEnvNames(use.oauth2).filter((name) => !process.env[name]?.trim()),
|
|
177
|
+
hostProblems: grantHostProblems(use),
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
163
180
|
/** The first problem, without the file prefix the doctor already shows. */
|
|
164
181
|
function firstProblem(err) {
|
|
165
182
|
if (err instanceof SyndicateValidationError)
|
|
@@ -179,6 +196,8 @@ function needsOf(agent, delegates = false) {
|
|
|
179
196
|
function walk(config, prefix, opts, rows) {
|
|
180
197
|
const orch = config.orchestrator;
|
|
181
198
|
const orchModel = orch?.model;
|
|
199
|
+
if (orch)
|
|
200
|
+
opts.grants.push(...grantsOf(orch, prefix));
|
|
182
201
|
if (orch && orchModel) {
|
|
183
202
|
// In DELEGATE mode the orchestrator calls its subagents as tools; under
|
|
184
203
|
// plan-dispatch it is a tool-less classifier and code runs the route; in
|
|
@@ -218,6 +237,7 @@ function walk(config, prefix, opts, rows) {
|
|
|
218
237
|
}
|
|
219
238
|
continue;
|
|
220
239
|
}
|
|
240
|
+
opts.grants.push(...grantsOf(sub, prefix));
|
|
221
241
|
// A subagent without its own model inherits the orchestrator's.
|
|
222
242
|
const model = sub.model ?? orchModel;
|
|
223
243
|
if (!model)
|
|
@@ -283,7 +303,8 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
|
|
|
283
303
|
const config = load(file);
|
|
284
304
|
const rows = [];
|
|
285
305
|
const nestedErrors = [];
|
|
286
|
-
|
|
306
|
+
const grants = [];
|
|
307
|
+
walk(config, '', { load, seen: new Set([file, path.basename(file)]), nestedErrors, grants }, rows);
|
|
287
308
|
const memory = config.memory_system === 'long-term'
|
|
288
309
|
? {
|
|
289
310
|
namespace: config.memory_namespace ?? LEGACY_MEMORY_APP_NAME,
|
|
@@ -302,6 +323,7 @@ export function diagnoseSyndicate(file, agentsDir, scripts) {
|
|
|
302
323
|
...(runCommand ? { runCommand } : {}),
|
|
303
324
|
...(nestedErrors.length ? { error: nestedErrors.join('\n') } : {}),
|
|
304
325
|
...(memory ? { memory } : {}),
|
|
326
|
+
...(grants.length ? { grants } : {}),
|
|
305
327
|
};
|
|
306
328
|
}
|
|
307
329
|
catch (err) {
|
|
@@ -398,7 +420,11 @@ export function runDoctor(options = {}) {
|
|
|
398
420
|
for (const s of syndicates)
|
|
399
421
|
counts[s.verdict.state]++;
|
|
400
422
|
flagMemoryNamespaces(syndicates);
|
|
401
|
-
|
|
423
|
+
// The server's OAuth setup (ADR 0114): the key, the redirect URI and the allowlist, names only.
|
|
424
|
+
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
|
+
const set = Object.fromEntries([CREDENTIAL_KEY_ENV, OAUTH_REDIRECT_URI_ENV, OAUTH_HOSTS_ENV].map((name) => [name, !!process.env[name]?.trim()]));
|
|
426
|
+
const oauth = grants.length || Object.values(set).some(Boolean) ? { set, problems: oauthEnvProblems(grants) } : undefined;
|
|
427
|
+
return { agentsDir, runtime: runtimeReport(), syndicates, unlocks, gateway, endpoints: endpointRows(), counts, ...(oauth ? { oauth } : {}) };
|
|
402
428
|
}
|
|
403
429
|
// ── Rendering ────────────────────────────────────────────────────────────────
|
|
404
430
|
const ANSI = {
|
|
@@ -464,6 +490,14 @@ export function renderDoctor(result, opts = {}) {
|
|
|
464
490
|
? `gateway ${c.red}✗${c.reset} ${result.gateway.problem}`
|
|
465
491
|
: `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}`);
|
|
466
492
|
}
|
|
493
|
+
if (result.oauth) {
|
|
494
|
+
// Names only: which variables are set, never their values (ADR 0114).
|
|
495
|
+
const set = Object.entries(result.oauth.set).map(([name, on]) => `${name} ${on ? 'set' : 'unset'}`).join(' · ');
|
|
496
|
+
const mark = result.oauth.problems.length ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
|
|
497
|
+
lines.push(`oauth ${mark} ${c.dim}${set}${c.reset}`);
|
|
498
|
+
for (const p of result.oauth.problems)
|
|
499
|
+
lines.push(` ${c.red}✗ ${p}${c.reset}`);
|
|
500
|
+
}
|
|
467
501
|
lines.push('');
|
|
468
502
|
// The table: one line per syndicate (name, verdict, run command), then
|
|
469
503
|
// one indented line per agent.
|
|
@@ -490,6 +524,16 @@ export function renderDoctor(result, opts = {}) {
|
|
|
490
524
|
if (s.memory?.issue) {
|
|
491
525
|
lines.push(` ${c.yellow}⚠ memory: ${s.memory.issue}${c.reset}`);
|
|
492
526
|
}
|
|
527
|
+
for (const g of s.grants ?? []) {
|
|
528
|
+
const scopes = g.scopes.length ? ` ${c.dim}[${g.scopes.join(' ')}]${c.reset}` : '';
|
|
529
|
+
const what = g.grant === 'authorization_code'
|
|
530
|
+
? `needs a grant: each user connects ${g.provider} (consent) before ${g.tools} works for them`
|
|
531
|
+
: `needs a grant: the server's own ${g.provider} token (client_credentials) for ${g.tools}`;
|
|
532
|
+
const missing = g.missingEnv.length ? ` ${c.red}✗ ${g.missingEnv.join(', ')} not set${c.reset}` : '';
|
|
533
|
+
lines.push(` ${c.cyan}⚿ ${g.agent}${c.reset} ${what}${scopes}${missing}`);
|
|
534
|
+
for (const p of g.hostProblems ?? [])
|
|
535
|
+
lines.push(` ${c.red}✗ ${p}${c.reset}`);
|
|
536
|
+
}
|
|
493
537
|
if (s.declaredTier && s.declaredTier !== s.tier) {
|
|
494
538
|
lines.push(` ${c.yellow}⚠ header says "tier: ${s.declaredTier}" but the models say ${s.tier}${c.reset}`);
|
|
495
539
|
}
|
|
@@ -2,6 +2,7 @@ import type { DispatchConfig } from './dispatch.ts';
|
|
|
2
2
|
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
|
+
import type { McpAuthConfig } from './tools/oauthTools.ts';
|
|
5
6
|
import type { ContextConfig, ExampleConfig } from './compile.ts';
|
|
6
7
|
export type { OpenApiConfig } from './tools/openapiTools.ts';
|
|
7
8
|
export type { SkillsConfig } from './tools/skillToolset.ts';
|
|
@@ -91,7 +92,9 @@ export interface AgentYamlConfig {
|
|
|
91
92
|
outputKey?: string;
|
|
92
93
|
/**
|
|
93
94
|
* Model generation config (temperature, topP, safety, thinking, etc.)
|
|
94
|
-
* Maps to LlmAgentConfig.generateContentConfig.
|
|
95
|
+
* Maps to LlmAgentConfig.generateContentConfig. In YAML a v1 spelling,
|
|
96
|
+
* deprecated (ADR 0115): write `sampling`, `output` and `reasoning`. It
|
|
97
|
+
* stays the engine form the loader hands downstream.
|
|
95
98
|
*/
|
|
96
99
|
generateContentConfig?: GenerateContentConfig;
|
|
97
100
|
/**
|
|
@@ -102,12 +105,45 @@ export interface AgentYamlConfig {
|
|
|
102
105
|
reasoning?: ReasoningSetting;
|
|
103
106
|
/**
|
|
104
107
|
* Output schema for structured JSON responses.
|
|
105
|
-
* Maps to LlmAgentConfig.outputSchema.
|
|
108
|
+
* Maps to LlmAgentConfig.outputSchema. In YAML a v1 spelling, deprecated
|
|
109
|
+
* (ADR 0115): write `output.schema`. It stays the engine form.
|
|
106
110
|
*/
|
|
107
111
|
outputSchema?: Record<string, unknown>;
|
|
112
|
+
/**
|
|
113
|
+
* Sampling, provider-neutral (v2): replaces generateContentConfig's
|
|
114
|
+
* temperature, topP, maxOutputTokens and stopSequences. The loader folds it
|
|
115
|
+
* into generateContentConfig (lib/agentDialect.ts toEngineAgent), so a
|
|
116
|
+
* loaded config never carries it. Cannot be combined with the v1 key it replaces.
|
|
117
|
+
*/
|
|
118
|
+
sampling?: {
|
|
119
|
+
temperature?: number;
|
|
120
|
+
top_p?: number;
|
|
121
|
+
max_output_tokens?: number;
|
|
122
|
+
stop?: string[];
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Structured output (v2): `schema` replaces outputSchema, `mime` replaces
|
|
126
|
+
* generateContentConfig.responseMimeType. Folded into those by the loader.
|
|
127
|
+
*/
|
|
128
|
+
output?: {
|
|
129
|
+
schema?: Record<string, unknown>;
|
|
130
|
+
mime?: 'application/json' | 'text/plain';
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Per-provider instruction (v2), keyed by provider id (lib/models/providerMap.ts):
|
|
134
|
+
* `instruction` replaces the agent's instruction, `instruction_append` is
|
|
135
|
+
* appended after a blank line, for the provider of the model the agent runs
|
|
136
|
+
* on. Applied at compile time, before the skills index; not for fallback_model.
|
|
137
|
+
*/
|
|
138
|
+
model_overrides?: Partial<Record<'gemini' | 'anthropic' | 'openai' | 'xai' | 'moonshot' | 'ollama', {
|
|
139
|
+
instruction?: string;
|
|
140
|
+
instruction_append?: string;
|
|
141
|
+
}>>;
|
|
108
142
|
mcp_server_url?: string;
|
|
109
143
|
/** The MCP server's tools this agent may use; the rest are not exposed. */
|
|
110
144
|
mcp_tools?: string[];
|
|
145
|
+
/** The OAuth grant the MCP server takes (ADR 0112): each user's, or the server's own. */
|
|
146
|
+
mcp_auth?: McpAuthConfig;
|
|
111
147
|
/** Answers when `model` fails provider-side, or its provider's circuit is open (ADR 0044). */
|
|
112
148
|
fallback_model?: string;
|
|
113
149
|
/**
|
|
@@ -216,6 +252,8 @@ export interface SyndicateYamlConfig {
|
|
|
216
252
|
memory_retention_days?: number;
|
|
217
253
|
/** Optional hard limit on the turn's model calls, subagents included (LLM -> Tool cycles). */
|
|
218
254
|
max_steps?: number;
|
|
255
|
+
/** Delegated calls from one model step that run at once (ADR 0116). Default 4. */
|
|
256
|
+
max_concurrency?: number;
|
|
219
257
|
/**
|
|
220
258
|
* The nested `yaml_reference:` syndicates this one reaches, raw, keyed by
|
|
221
259
|
* reference: written by the registry publisher (`bundleReferences`) so a
|
|
@@ -251,6 +289,11 @@ export interface LoadSyndicateOptions {
|
|
|
251
289
|
* with the public example of the same name (ADR 0018).
|
|
252
290
|
*/
|
|
253
291
|
shippedFallback?: boolean;
|
|
292
|
+
/**
|
|
293
|
+
* Where load-time warnings go (the deprecated v1 spellings of ADR 0115).
|
|
294
|
+
* Default console.warn. A warning names the file and key paths, never a value.
|
|
295
|
+
*/
|
|
296
|
+
onWarning?: (message: string) => void;
|
|
254
297
|
}
|
|
255
298
|
/**
|
|
256
299
|
* Parse CLI variable bindings into a VariableMap.
|
|
@@ -4,6 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import { createClient } from '@supabase/supabase-js';
|
|
5
5
|
import { hasSupabaseCredentials } from './persistence/supabaseProvider.js';
|
|
6
6
|
import { validateSyndicateConfig } from './syndicateSchema.js';
|
|
7
|
+
import { v1SpellingsOf } from './agentDialect.js';
|
|
7
8
|
// ── Variable Injection (private) ──────────────────────────
|
|
8
9
|
const TOKEN_RE = /\{\{(\w+)\}\}/g;
|
|
9
10
|
const FULL_TOKEN_RE = /^\{\{(\w+)\}\}$/;
|
|
@@ -87,6 +88,7 @@ function resolveAndValidate(raw, label, options) {
|
|
|
87
88
|
...bindings,
|
|
88
89
|
};
|
|
89
90
|
const interpolated = isPlainObject(raw) ? interpolateDeep(raw, merged) : raw;
|
|
91
|
+
warnV1Spellings(interpolated, label, options.onWarning);
|
|
90
92
|
const unresolved = findUnresolvedTokens(interpolated);
|
|
91
93
|
if (unresolved.length > 0) {
|
|
92
94
|
console.warn(`⚠ Unresolved template variables: ${unresolved.map((t) => `{{${t}}}`).join(', ')}`);
|
|
@@ -99,6 +101,24 @@ function resolveAndValidate(raw, label, options) {
|
|
|
99
101
|
}
|
|
100
102
|
return config;
|
|
101
103
|
}
|
|
104
|
+
/** Labels already warned about, so a server loading one file per request warns once per process. */
|
|
105
|
+
const warnedV1 = new Set();
|
|
106
|
+
/**
|
|
107
|
+
* One line per label when a file still uses the v1 spellings ADR 0115
|
|
108
|
+
* deprecates. Key paths and the label only: never a value (a prompt, a key).
|
|
109
|
+
*/
|
|
110
|
+
function warnV1Spellings(raw, label, onWarning = console.warn) {
|
|
111
|
+
if (warnedV1.has(label))
|
|
112
|
+
return;
|
|
113
|
+
const paths = v1SpellingsOf(raw);
|
|
114
|
+
if (!paths.length)
|
|
115
|
+
return;
|
|
116
|
+
warnedV1.add(label);
|
|
117
|
+
const fix = label.startsWith('registry:') || label.startsWith('bundled:')
|
|
118
|
+
? 'Run npx melchizedek-codemod on the file it was published from, and republish'
|
|
119
|
+
: `Run npx melchizedek-codemod ${label} to rewrite them`;
|
|
120
|
+
onWarning(`⚠ ${label}: generateContentConfig and outputSchema are v1 spellings, deprecated (ADR 0115): ${paths.join(', ')}. ${fix}.`);
|
|
121
|
+
}
|
|
102
122
|
function applyOverrides(config, overrides) {
|
|
103
123
|
return { ...config, ...overrides };
|
|
104
124
|
}
|
|
@@ -227,9 +247,9 @@ export function collectGuards(config, loadNested = nestedLoader(config), seen =
|
|
|
227
247
|
return [...names];
|
|
228
248
|
}
|
|
229
249
|
export function loadSyndicate(filename, options = {}) {
|
|
230
|
-
const { bindings = {}, overrides } = options;
|
|
250
|
+
const { bindings = {}, overrides, onWarning } = options;
|
|
231
251
|
const { raw, label, filePath } = readSyndicateFile(filename, options);
|
|
232
|
-
return anchorSpecPaths(resolveAndValidate(raw, label, { bindings, overrides }), path.dirname(filePath));
|
|
252
|
+
return anchorSpecPaths(resolveAndValidate(raw, label, { bindings, overrides, onWarning }), path.dirname(filePath));
|
|
233
253
|
}
|
|
234
254
|
/**
|
|
235
255
|
* An `openapi:` spec path is written relative to the syndicate file, so a
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* lib/memory/namespace.ts — memory always resolves to the ROOT syndicate's
|
|
3
3
|
* namespace (ADR 0020 item 3).
|
|
4
4
|
*
|
|
5
|
-
* WHY: a DELEGATE subagent runs on its own session row under
|
|
6
|
-
* `appName = <
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* WHY: a DELEGATE subagent runs on its own session row, filed under its
|
|
6
|
+
* agent path (`appName = <app>/<caller>/<subagent>`, ADR 0111; the
|
|
7
|
+
* subagent's name alone in a conversation stored before), and its memory
|
|
8
|
+
* tools search with the session's app name. Facts are written under the
|
|
9
|
+
* root syndicate's namespace, so a `load_memory` declared on a subagent
|
|
10
|
+
* would search the child's own key, find nothing, and say so without error. Wrapping the
|
|
10
11
|
* service the runtime receives pins every search and ingestion to one
|
|
11
12
|
* namespace, whatever app name the caller carries.
|
|
12
13
|
*
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
* lib/memory/namespace.ts — memory always resolves to the ROOT syndicate's
|
|
3
3
|
* namespace (ADR 0020 item 3).
|
|
4
4
|
*
|
|
5
|
-
* WHY: a DELEGATE subagent runs on its own session row under
|
|
6
|
-
* `appName = <
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* WHY: a DELEGATE subagent runs on its own session row, filed under its
|
|
6
|
+
* agent path (`appName = <app>/<caller>/<subagent>`, ADR 0111; the
|
|
7
|
+
* subagent's name alone in a conversation stored before), and its memory
|
|
8
|
+
* tools search with the session's app name. Facts are written under the
|
|
9
|
+
* root syndicate's namespace, so a `load_memory` declared on a subagent
|
|
10
|
+
* would search the child's own key, find nothing, and say so without error. Wrapping the
|
|
10
11
|
* service the runtime receives pins every search and ingestion to one
|
|
11
12
|
* namespace, whatever app name the caller carries.
|
|
12
13
|
*
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import { endpointFromEnv } from '../models/endpoints.js';
|
|
32
32
|
import { GoogleGenAI } from '@google/genai';
|
|
33
|
-
import {
|
|
33
|
+
import { SpanStatusCode } from '@opentelemetry/api';
|
|
34
34
|
import { llmRequestToModelRequest, modelResponseToLlmResponse } from '../models/genaiMapping.js';
|
|
35
35
|
import { EMBEDDING_DIMENSIONS, EMBEDDING_MODEL, MEMORY_EXTRACTION_MODEL } from '../config.js';
|
|
36
36
|
import { providerForModel } from '../models/providerMap.js';
|
|
37
37
|
import { resolveModel } from '../models/registry.js';
|
|
38
|
-
import {
|
|
38
|
+
import { startEngineSpan, traceLlmGeneration } from '../observability/tracer.js';
|
|
39
39
|
import { trimTrailingSlashes } from '../models/urls.js';
|
|
40
40
|
/**
|
|
41
41
|
* Extraction on any model id. The key passed here reaches only a model of
|
|
@@ -86,8 +86,7 @@ export function modelExtractor(opts = {}) {
|
|
|
86
86
|
* a reasoning step.
|
|
87
87
|
*/
|
|
88
88
|
async function traced(provider, model, count, fn) {
|
|
89
|
-
|
|
90
|
-
const span = trace.getTracer('melchizedek-tracer').startSpan('llm.request');
|
|
89
|
+
const span = startEngineSpan('melchizedek-tracer', 'llm.request');
|
|
91
90
|
span.setAttribute('llm.provider', provider);
|
|
92
91
|
span.setAttribute('llm.model', model);
|
|
93
92
|
span.setAttribute('llm.purpose', 'memory_embedding');
|
|
@@ -55,7 +55,7 @@ export declare function describeCapabilities(model: string, tools?: readonly str
|
|
|
55
55
|
* a funded direct path with no dropped tool is the quiet default.
|
|
56
56
|
*/
|
|
57
57
|
export declare function capabilitySummary(agentName: string, r: CapabilityReport): string | undefined;
|
|
58
|
-
export declare const CAPABILITIES: readonly ['delegation', 'memory_tools', 'structured_output', 'thinking_with_tools', 'streaming', 'vision', 'native_search'];
|
|
58
|
+
export declare const CAPABILITIES: readonly ['delegation', 'memory_tools', 'structured_output', 'structured_output_with_tools', 'thinking_with_tools', 'streaming', 'vision', 'native_search'];
|
|
59
59
|
export type Capability = (typeof CAPABILITIES)[number];
|
|
60
60
|
export declare const CAPABILITY_LABELS: Record<Capability, string>;
|
|
61
61
|
export type Support = 'supported' | 'degraded' | 'unsupported';
|
|
@@ -74,6 +74,21 @@ export declare function capabilityOf(model: string, capability: Capability, opts
|
|
|
74
74
|
}): CapabilityCell & {
|
|
75
75
|
row: MatrixRow;
|
|
76
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Whether an outputSchema goes in the same request as the agent's tools, as
|
|
79
|
+
* the provider's own structured-output field, on the path `model` takes
|
|
80
|
+
* (ADR 0109). True only where the `structured_output_with_tools` cell is
|
|
81
|
+
* supported and the model's generation takes it: Claude from Opus 4.8,
|
|
82
|
+
* Sonnet 5 and Haiku 5.5 on (`output_config.format`), every OpenAI id
|
|
83
|
+
* (`text.format`), Gemini 2 and later on Vertex AI (`responseJsonSchema`).
|
|
84
|
+
* Elsewhere the native loop declares set_model_response beside the tools
|
|
85
|
+
* (lib/runtime/native/request.ts), which works on every path. A path that
|
|
86
|
+
* cannot be planned (a malformed gateway setting) answers false, the form
|
|
87
|
+
* that works everywhere.
|
|
88
|
+
*/
|
|
89
|
+
export declare function outputSchemaBesideTools(model: string, opts?: {
|
|
90
|
+
callerKey?: boolean;
|
|
91
|
+
}): boolean;
|
|
77
92
|
/** What one agent's YAML asks of its model. */
|
|
78
93
|
export interface AgentNeedsInput {
|
|
79
94
|
tools?: readonly string[];
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import { PROVIDERS } from './providerMap.js';
|
|
21
21
|
import { planTransport } from './gateway.js';
|
|
22
22
|
import { nativeSearchOn, PLATFORMS_FOR, platformFromEnv } from './endpoints.js';
|
|
23
|
-
import { claudeUrlImagesOn } from './claudeModels.js';
|
|
23
|
+
import { claudeGeneration, claudeUrlImagesOn } from './claudeModels.js';
|
|
24
24
|
/** The platform a direct path uses (ADR 0023); a misconfigured one reads as direct here, the doctor reports it. */
|
|
25
25
|
function directPlatform(provider) {
|
|
26
26
|
try {
|
|
@@ -117,6 +117,7 @@ export const CAPABILITIES = [
|
|
|
117
117
|
'delegation',
|
|
118
118
|
'memory_tools',
|
|
119
119
|
'structured_output',
|
|
120
|
+
'structured_output_with_tools',
|
|
120
121
|
'thinking_with_tools',
|
|
121
122
|
'streaming',
|
|
122
123
|
'vision',
|
|
@@ -126,6 +127,7 @@ export const CAPABILITY_LABELS = {
|
|
|
126
127
|
delegation: 'delegation (subagents as tools)',
|
|
127
128
|
memory_tools: 'memory tools (load_memory)',
|
|
128
129
|
structured_output: 'structured output (outputSchema)',
|
|
130
|
+
structured_output_with_tools: 'structured output beside tools (an outputSchema on an agent that calls tools or delegates)',
|
|
129
131
|
thinking_with_tools: 'thinking with tool use',
|
|
130
132
|
streaming: 'token streaming',
|
|
131
133
|
vision: 'image input',
|
|
@@ -138,12 +140,14 @@ const nativeSearch = (row) => SERVER_SIDE_TOOLS.web_search.includes(row)
|
|
|
138
140
|
? ok()
|
|
139
141
|
: unsupported('no native search on this path; the web_search sentinel is dropped (use web_extract)');
|
|
140
142
|
const responsesReasoningNote = (ids) => `encrypted reasoning items are replayed verbatim within the turn's tool loop, with store: false (ADR 0050); ${ids}`;
|
|
143
|
+
const setModelResponseNote = (why) => `the schema travels as a set_model_response tool beside the agent's tools, so the model is asked, not held, to answer in it: the turn ends when it calls the tool, and its arguments are the answer (${why}; ADR 0109)`;
|
|
141
144
|
const CHAT_THINKING_NOTE = "reasoning: is the lever, sent as reasoning_effort in the model's word, a thinking budget as the level that covers it (ADR 0047); the reasoning is not carried between tool steps, so the model re-reasons each step";
|
|
142
145
|
export const CAPABILITY_MATRIX = {
|
|
143
146
|
gemini: {
|
|
144
147
|
delegation: ok(),
|
|
145
148
|
memory_tools: ok(),
|
|
146
149
|
structured_output: ok(),
|
|
150
|
+
structured_output_with_tools: degraded(setModelResponseNote('on the Gemini API; Gemini 2 and later on Vertex AI take responseJsonSchema beside the tools in one request')),
|
|
147
151
|
thinking_with_tools: ok(),
|
|
148
152
|
streaming: ok(),
|
|
149
153
|
vision: ok(),
|
|
@@ -153,6 +157,7 @@ export const CAPABILITY_MATRIX = {
|
|
|
153
157
|
delegation: ok(),
|
|
154
158
|
memory_tools: ok(),
|
|
155
159
|
structured_output: ok('test', 'output_config.format (json_schema) from Opus 4.8, Sonnet 5 and Haiku 5.5 on; a forced tool call on Claude 4.6 and earlier and Opus 4.7, offered under tool_choice auto when thinking is on (ADR 0049)'),
|
|
160
|
+
structured_output_with_tools: ok('test', 'output_config.format beside the tools in one request from Opus 4.8, Sonnet 5 and Haiku 5.5 on; Claude 4.6 and earlier and Opus 4.7 get a set_model_response tool beside them (ADR 0109)'),
|
|
156
161
|
thinking_with_tools: ok('test', "a thinking budget on Claude 4.6 and earlier, adaptive thinking with output_config.effort after (ADR 0049); signed thinking blocks are replayed verbatim within the turn's tool loop (ADR 0046), and where the model binds them to the conversation (Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5) under drop_block, so a block whose history changed is dropped rather than rejected; with a budget, a step answering another model's tool call runs without thinking"),
|
|
157
162
|
streaming: ok(),
|
|
158
163
|
vision: ok('test', 'user-turn images only'),
|
|
@@ -162,6 +167,7 @@ export const CAPABILITY_MATRIX = {
|
|
|
162
167
|
delegation: ok(),
|
|
163
168
|
memory_tools: ok(),
|
|
164
169
|
structured_output: ok(),
|
|
170
|
+
structured_output_with_tools: ok('test', 'text.format (strict json_schema) beside the tools in one request (ADR 0109)'),
|
|
165
171
|
thinking_with_tools: ok('test', responsesReasoningNote('reasoning ids only (o-series, gpt-5*)')),
|
|
166
172
|
streaming: ok(),
|
|
167
173
|
vision: ok('test', 'user-turn images only'),
|
|
@@ -171,6 +177,7 @@ export const CAPABILITY_MATRIX = {
|
|
|
171
177
|
delegation: ok(),
|
|
172
178
|
memory_tools: ok(),
|
|
173
179
|
structured_output: ok(),
|
|
180
|
+
structured_output_with_tools: degraded(setModelResponseNote('the schema is not yet sent beside tools on xAI')),
|
|
174
181
|
thinking_with_tools: ok('test', responsesReasoningNote('grok-4.5, grok-4.6 and grok-4.7; other grok ids re-reason each step')),
|
|
175
182
|
streaming: ok(),
|
|
176
183
|
vision: ok('test', 'user-turn images only'),
|
|
@@ -180,6 +187,7 @@ export const CAPABILITY_MATRIX = {
|
|
|
180
187
|
delegation: ok(),
|
|
181
188
|
memory_tools: ok(),
|
|
182
189
|
structured_output: ok('test', 'strict json_schema; kimi-k2.6 is documented as unstable on complex schemas ($ref, oneOf)'),
|
|
190
|
+
structured_output_with_tools: degraded(setModelResponseNote('the schema is not yet sent beside tools on Moonshot')),
|
|
183
191
|
thinking_with_tools: ok('test', "reasoning_content is sent back on the turn's tool-loop assistant messages for the same model (ADR 0046) on kimi-k3, kimi-k2.6 and kimi-k2.7-code; earlier turns' reasoning is not, which K3 and K2.7 Code also ask for; effort travels as reasoning_effort (K3) or a thinking switch (K2.x)"),
|
|
184
192
|
streaming: ok(),
|
|
185
193
|
vision: ok('test', 'user-turn images only, sent as base64 (Moonshot takes no public image URLs)'),
|
|
@@ -189,6 +197,7 @@ export const CAPABILITY_MATRIX = {
|
|
|
189
197
|
delegation: ok(),
|
|
190
198
|
memory_tools: ok(),
|
|
191
199
|
structured_output: ok('test', 'json_schema, enforced by grammar-constrained decoding from Ollama 0.5.0 (an older server ignores it); not enforced on Ollama Cloud (ADR 0096)'),
|
|
200
|
+
structured_output_with_tools: degraded(setModelResponseNote('grammar-constrained decoding to the schema would hold back the tool calls')),
|
|
192
201
|
thinking_with_tools: degraded(CHAT_THINKING_NOTE),
|
|
193
202
|
streaming: ok(),
|
|
194
203
|
vision: ok('test', 'needs a vision model, e.g. ollama/qwen3-vl:8b'),
|
|
@@ -198,6 +207,7 @@ export const CAPABILITY_MATRIX = {
|
|
|
198
207
|
delegation: ok(),
|
|
199
208
|
memory_tools: ok(),
|
|
200
209
|
structured_output: ok('test', 'strict json_schema; upstream support varies by model'),
|
|
210
|
+
structured_output_with_tools: degraded(setModelResponseNote('upstream support for a schema beside tools varies by model')),
|
|
201
211
|
thinking_with_tools: degraded(CHAT_THINKING_NOTE),
|
|
202
212
|
streaming: ok(),
|
|
203
213
|
vision: ok('test', 'upstream model must accept images'),
|
|
@@ -215,6 +225,45 @@ export function capabilityOf(model, capability, opts = {}) {
|
|
|
215
225
|
}
|
|
216
226
|
return { row, ...CAPABILITY_MATRIX[row][capability] };
|
|
217
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Whether an outputSchema goes in the same request as the agent's tools, as
|
|
230
|
+
* the provider's own structured-output field, on the path `model` takes
|
|
231
|
+
* (ADR 0109). True only where the `structured_output_with_tools` cell is
|
|
232
|
+
* supported and the model's generation takes it: Claude from Opus 4.8,
|
|
233
|
+
* Sonnet 5 and Haiku 5.5 on (`output_config.format`), every OpenAI id
|
|
234
|
+
* (`text.format`), Gemini 2 and later on Vertex AI (`responseJsonSchema`).
|
|
235
|
+
* Elsewhere the native loop declares set_model_response beside the tools
|
|
236
|
+
* (lib/runtime/native/request.ts), which works on every path. A path that
|
|
237
|
+
* cannot be planned (a malformed gateway setting) answers false, the form
|
|
238
|
+
* that works everywhere.
|
|
239
|
+
*/
|
|
240
|
+
export function outputSchemaBesideTools(model, opts = {}) {
|
|
241
|
+
let cell;
|
|
242
|
+
try {
|
|
243
|
+
cell = capabilityOf(model, 'structured_output_with_tools', opts);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (cell.support !== 'supported')
|
|
249
|
+
return false;
|
|
250
|
+
switch (cell.row) {
|
|
251
|
+
case 'anthropic':
|
|
252
|
+
return claudeGeneration(model).structuredOutput === 'output_format';
|
|
253
|
+
case 'gemini':
|
|
254
|
+
return geminiMajorVersion(model) >= 2;
|
|
255
|
+
default:
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** A Gemini id's major version (a Vertex AI resource name's last segment), or 0 when it is not a `gemini-<n>` id. Read without a pattern. */
|
|
260
|
+
function geminiMajorVersion(model) {
|
|
261
|
+
const name = model.slice(model.lastIndexOf('/') + 1);
|
|
262
|
+
if (!name.startsWith('gemini-'))
|
|
263
|
+
return 0;
|
|
264
|
+
const major = Number.parseInt(name.slice('gemini-'.length), 10);
|
|
265
|
+
return Number.isFinite(major) ? major : 0;
|
|
266
|
+
}
|
|
218
267
|
export function requiredCapabilities(agent) {
|
|
219
268
|
const tools = agent.tools ?? [];
|
|
220
269
|
const needs = new Set();
|
|
@@ -224,6 +273,8 @@ export function requiredCapabilities(agent) {
|
|
|
224
273
|
needs.add('memory_tools');
|
|
225
274
|
if (agent.outputSchema)
|
|
226
275
|
needs.add('structured_output');
|
|
276
|
+
if (agent.outputSchema && (tools.length > 0 || agent.delegates))
|
|
277
|
+
needs.add('structured_output_with_tools');
|
|
227
278
|
const thinking = agent.generateContentConfig?.thinkingConfig;
|
|
228
279
|
const thinks = agent.reasoning !== undefined
|
|
229
280
|
? agent.reasoning !== 'none' && !(typeof agent.reasoning === 'object' && agent.reasoning.budget_tokens === 0)
|
|
@@ -256,6 +307,9 @@ export function capabilityGaps(model, agent, opts = {}) {
|
|
|
256
307
|
export function platformCell(provider, platform, capability) {
|
|
257
308
|
if (platform === 'direct')
|
|
258
309
|
return undefined;
|
|
310
|
+
if (capability === 'structured_output_with_tools' && provider === 'gemini' && platform === 'vertex') {
|
|
311
|
+
return ok('test', 'responseJsonSchema beside the tools in one request on Gemini 2 and later; an earlier id gets set_model_response (ADR 0109)');
|
|
312
|
+
}
|
|
259
313
|
if (capability === 'vision' && provider === 'anthropic' && !claudeUrlImagesOn(platform)) {
|
|
260
314
|
return degraded(`user-turn images inline (base64) only; an image given by URL is dropped, since ${PLATFORM_LABEL[platform]} takes no URL image source`);
|
|
261
315
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SpanContext } from '@opentelemetry/api';
|
|
1
|
+
import type { Span, SpanContext, SpanOptions } from '@opentelemetry/api';
|
|
2
2
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
3
3
|
import type { LlmResponse } from '../models/genaiMapping.ts';
|
|
4
4
|
import type { TurnEvent as Event } from '../runtime/events.ts';
|
|
@@ -43,6 +43,13 @@ export declare function takeTraceStats(traceId: string): TraceStats | undefined;
|
|
|
43
43
|
/** Name of the agent of the nearest enclosing agent span (ADK's `invoke_agent`, the loop's `agent.invoke`), or null. */
|
|
44
44
|
export declare function agentForSpan(spanId: string | undefined): string | null;
|
|
45
45
|
export declare function initializeTracing(): void;
|
|
46
|
+
/**
|
|
47
|
+
* Starts a span on the named tracer, starting the tracer first; in a turn
|
|
48
|
+
* that opted out of tracing, a non-recording span and no tracer. Every span
|
|
49
|
+
* the engine opens inside a turn goes through here or checks turnUntraced
|
|
50
|
+
* itself.
|
|
51
|
+
*/
|
|
52
|
+
export declare function startEngineSpan(tracerName: string, name: string, options?: SpanOptions): Span;
|
|
46
53
|
export interface TraceMetadata {
|
|
47
54
|
syndicateName: string;
|
|
48
55
|
bindings?: Record<string, any>;
|