melchizedek-agents 0.16.2 → 0.18.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 +40 -3
- package/AGENT_SETUP.md +4 -2
- package/CHANGELOG.md +246 -0
- package/DOCUMENTATION.md +311 -15
- package/QUICKSTART.md +2 -2
- package/README.md +125 -149
- package/config/agents/examples/harness.yaml +135 -0
- package/config/agents/examples/model_zoo.yaml +23 -9
- package/config/agents/examples/pipeline.yaml +120 -0
- package/config/agents/examples/specs/open-meteo-forecast.json +77 -0
- package/config/agents/examples/specs/open-meteo-geocoding.json +53 -0
- package/config/agents/examples/weather.yaml +59 -0
- package/config/agents/syndicateSchema.yaml +125 -1
- package/db/hardening.sql +4 -3
- package/db/migrations/0011_erase_expired.sql +198 -0
- package/db/migrations/0012_audit_log.sql +98 -0
- package/dist/lib/a2a/app.d.ts +27 -0
- package/dist/lib/a2a/app.js +101 -5
- package/dist/lib/a2a/executor.d.ts +34 -4
- package/dist/lib/a2a/executor.js +109 -9
- package/dist/lib/compile.d.ts +41 -3
- package/dist/lib/compile.js +162 -17
- package/dist/lib/config.d.ts +24 -0
- package/dist/lib/config.js +24 -0
- package/dist/lib/doctor.js +17 -4
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +1 -0
- package/dist/lib/loadEnv.d.ts +5 -0
- package/dist/lib/loadEnv.js +7 -0
- package/dist/lib/loadSyndicate.d.ts +50 -0
- package/dist/lib/loadSyndicate.js +19 -3
- package/dist/lib/memory/erase.d.ts +5 -0
- package/dist/lib/memory/erase.js +5 -0
- package/dist/lib/memory/providers.d.ts +27 -0
- package/dist/lib/memory/providers.js +28 -0
- package/dist/lib/models/capabilities.js +10 -0
- package/dist/lib/models/endpoints.d.ts +1 -1
- package/dist/lib/models/endpoints.js +4 -1
- package/dist/lib/models/fallback.d.ts +41 -0
- package/dist/lib/models/fallback.js +105 -0
- package/dist/lib/models/gateway.d.ts +2 -2
- package/dist/lib/models/gateway.js +4 -4
- package/dist/lib/models/kimiLlm.d.ts +123 -0
- package/dist/lib/models/kimiLlm.js +171 -0
- package/dist/lib/models/providerMap.d.ts +2 -2
- package/dist/lib/models/providerMap.js +4 -1
- package/dist/lib/models/registry.d.ts +1 -1
- package/dist/lib/models/registry.js +11 -3
- package/dist/lib/net/redirects.d.ts +14 -0
- package/dist/lib/net/redirects.js +94 -0
- package/dist/lib/observability/audit.d.ts +21 -0
- package/dist/lib/observability/audit.js +39 -0
- package/dist/lib/observability/metrics.d.ts +6 -0
- package/dist/lib/observability/tracer.d.ts +14 -1
- package/dist/lib/observability/tracer.js +16 -2
- package/dist/lib/persistence/supabaseProvider.d.ts +2 -6
- package/dist/lib/persistence/supabaseProvider.js +2 -31
- package/dist/lib/runtime/questions.d.ts +55 -0
- package/dist/lib/runtime/questions.js +107 -0
- package/dist/lib/runtime/syndicateTurn.d.ts +45 -5
- package/dist/lib/runtime/syndicateTurn.js +165 -17
- package/dist/lib/storage/postgres/index.d.ts +9 -0
- package/dist/lib/storage/postgres/index.js +13 -0
- package/dist/lib/storage/rlsStatus.d.ts +45 -0
- package/dist/lib/storage/rlsStatus.js +69 -0
- package/dist/lib/syndicateSchema.d.ts +177 -0
- package/dist/lib/syndicateSchema.js +343 -3
- package/dist/lib/toolRegistry.js +9 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +26 -0
- package/dist/lib/tools/mcpToolFactory.js +61 -4
- package/dist/lib/tools/openapiTools.d.ts +52 -0
- package/dist/lib/tools/openapiTools.js +225 -0
- package/dist/lib/tools/skillToolset.d.ts +135 -0
- package/dist/lib/tools/skillToolset.js +247 -0
- package/dist/lib/tools/urlContextTool.d.ts +29 -0
- package/dist/lib/tools/urlContextTool.js +39 -0
- package/dist/lib/workflow.d.ts +72 -0
- package/dist/lib/workflow.js +180 -0
- package/dist/lib/workflowConfig.d.ts +95 -0
- package/dist/lib/workflowConfig.js +93 -0
- package/dist/scripts/a2a_server.js +51 -8
- package/dist/scripts/db.d.ts +9 -0
- package/dist/scripts/db.js +30 -15
- package/dist/scripts/syndicate_chat.js +39 -5
- package/package.json +6 -3
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-author.md +8 -4
- package/skills/briefs/melchizedek-memory.md +1 -1
- package/skills/briefs/melchizedek-models.md +1 -1
- package/skills/briefs/melchizedek-scribe.md +4 -4
- package/skills/briefs/melchizedek-serve.md +1 -1
- package/skills/briefs/melchizedek.md +4 -1
- package/skills/melchizedek/SKILL.md +4 -1
- package/skills/melchizedek-author/SKILL.md +7 -3
- package/skills/melchizedek-memory/SKILL.md +1 -1
- package/skills/melchizedek-models/SKILL.md +1 -1
- package/skills/melchizedek-scribe/SKILL.md +3 -3
- package/skills/melchizedek-serve/SKILL.md +1 -1
- /package/skills/melchizedek-author/{templates → assets}/minimal.yaml +0 -0
- /package/skills/melchizedek-scribe/{templates → assets}/brief.md +0 -0
package/dist/lib/a2a/executor.js
CHANGED
|
@@ -19,6 +19,7 @@ import { turnLockKey } from './turnLock.js';
|
|
|
19
19
|
import { ingestTurnMemory, runSyndicateTurn } from '../runtime/syndicateTurn.js';
|
|
20
20
|
import { approvalResponsePart, describeApproval, pendingApproval } from '../runtime/approvals.js';
|
|
21
21
|
import { declaresApprovals } from '../compile.js';
|
|
22
|
+
import { describeInput } from '../runtime/syndicateTurn.js';
|
|
22
23
|
export const requestContextStorage = new AsyncLocalStorage();
|
|
23
24
|
/** The app name every A2A session and memory silo is stored under. */
|
|
24
25
|
export const A2A_APP_NAME = 'melchizedek-a2a';
|
|
@@ -72,6 +73,9 @@ export function describeFailedTurn(result) {
|
|
|
72
73
|
if (result.failedStage === 'dispatch' && result.route) {
|
|
73
74
|
return `Error: [${error.code}] ${result.route.route} failed to answer — ${describeTurnError(error)}`;
|
|
74
75
|
}
|
|
76
|
+
if (result.failedStage === 'workflow') {
|
|
77
|
+
return `Error: [${error.code}] The workflow failed — ${describeTurnError(error)}`;
|
|
78
|
+
}
|
|
75
79
|
return `Error: [${error.code}] The agent run failed — ${describeTurnError(error)}`;
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
@@ -118,28 +122,64 @@ export function a2aPartsToMessage(rawParts) {
|
|
|
118
122
|
return { parts };
|
|
119
123
|
}
|
|
120
124
|
const NO_USAGE = { llmCalls: 0, inputTokens: 0, outputTokens: 0, thinkingTokens: 0 };
|
|
121
|
-
/** Concurrency
|
|
125
|
+
/** Concurrency caps and in-flight registry shared by all executors. */
|
|
122
126
|
export class TaskLimiter {
|
|
123
127
|
max;
|
|
128
|
+
perScope;
|
|
129
|
+
perCaller;
|
|
124
130
|
running = new Map();
|
|
131
|
+
/** taskId → the keys it counts against, so release frees exactly those. */
|
|
132
|
+
keysOf = new Map();
|
|
133
|
+
counts = new Map();
|
|
125
134
|
draining = false;
|
|
126
135
|
idleWaiters = [];
|
|
127
|
-
constructor(max) {
|
|
136
|
+
constructor(max, limits = {}) {
|
|
128
137
|
this.max = max > 0 ? max : Infinity;
|
|
138
|
+
this.perScope = limits.perScope && limits.perScope > 0 ? limits.perScope : Infinity;
|
|
139
|
+
this.perCaller = limits.perCaller && limits.perCaller > 0 ? limits.perCaller : Infinity;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Why a task for this scope and caller may not start now, or undefined.
|
|
143
|
+
* Checked before acquire() so the refusal can name the cap that was hit.
|
|
144
|
+
*/
|
|
145
|
+
refusal(who = {}) {
|
|
146
|
+
if (this.draining)
|
|
147
|
+
return 'The server is shutting down; retry shortly.';
|
|
148
|
+
if (this.running.size >= this.max)
|
|
149
|
+
return `The server is at its limit of ${this.max} concurrent tasks; retry shortly.`;
|
|
150
|
+
if (who.scopeKey && (this.counts.get(`s:${who.scopeKey}`) ?? 0) >= this.perScope) {
|
|
151
|
+
return `This user already has ${this.perScope} tasks running, the most allowed at once; retry when one finishes.`;
|
|
152
|
+
}
|
|
153
|
+
if (who.caller && (this.counts.get(`c:${who.caller}`) ?? 0) >= this.perCaller) {
|
|
154
|
+
return `This caller already has ${this.perCaller} tasks running, the most allowed at once; retry when one finishes.`;
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
129
157
|
}
|
|
130
158
|
get inFlight() {
|
|
131
159
|
return this.running.size;
|
|
132
160
|
}
|
|
133
|
-
/** Reserve a slot. Returns undefined when
|
|
134
|
-
acquire(taskId) {
|
|
135
|
-
if (this.
|
|
161
|
+
/** Reserve a slot. Returns undefined when any cap is reached or the server is shutting down. */
|
|
162
|
+
acquire(taskId, who = {}) {
|
|
163
|
+
if (this.refusal(who))
|
|
136
164
|
return undefined;
|
|
137
165
|
const controller = new AbortController();
|
|
138
166
|
this.running.set(taskId, controller);
|
|
167
|
+
const keys = [who.scopeKey && `s:${who.scopeKey}`, who.caller && `c:${who.caller}`].filter((k) => !!k);
|
|
168
|
+
for (const k of keys)
|
|
169
|
+
this.counts.set(k, (this.counts.get(k) ?? 0) + 1);
|
|
170
|
+
this.keysOf.set(taskId, keys);
|
|
139
171
|
return controller;
|
|
140
172
|
}
|
|
141
173
|
release(taskId) {
|
|
142
174
|
this.running.delete(taskId);
|
|
175
|
+
for (const k of this.keysOf.get(taskId) ?? []) {
|
|
176
|
+
const n = (this.counts.get(k) ?? 1) - 1;
|
|
177
|
+
if (n > 0)
|
|
178
|
+
this.counts.set(k, n);
|
|
179
|
+
else
|
|
180
|
+
this.counts.delete(k);
|
|
181
|
+
}
|
|
182
|
+
this.keysOf.delete(taskId);
|
|
143
183
|
if (this.running.size === 0)
|
|
144
184
|
for (const w of this.idleWaiters.splice(0))
|
|
145
185
|
w();
|
|
@@ -209,6 +249,37 @@ function publishWorking(eventBus, taskId, contextId, text) {
|
|
|
209
249
|
metadata: undefined,
|
|
210
250
|
}));
|
|
211
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* A workflow paused on an `ask_user` node (lib/workflow.ts): the task ends
|
|
254
|
+
* input-required with the question, and a data part carrying it. The
|
|
255
|
+
* answer is simply the next message on the conversation; its text becomes
|
|
256
|
+
* the node's output and the graph resumes where it waited.
|
|
257
|
+
*/
|
|
258
|
+
function publishInputRequest(eventBus, taskId, contextId, input) {
|
|
259
|
+
const message = statusMessage(taskId, contextId, `Input needed: ${describeInput(input)}`);
|
|
260
|
+
message.parts.push({
|
|
261
|
+
content: {
|
|
262
|
+
$case: 'data',
|
|
263
|
+
value: {
|
|
264
|
+
type: 'input_request',
|
|
265
|
+
interrupt_id: input.id,
|
|
266
|
+
node: input.node,
|
|
267
|
+
message: input.message,
|
|
268
|
+
...(input.payload !== undefined ? { payload: input.payload } : {}),
|
|
269
|
+
...(input.schema !== undefined ? { schema: input.schema } : {}),
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
metadata: undefined,
|
|
273
|
+
filename: '',
|
|
274
|
+
mediaType: 'application/json',
|
|
275
|
+
});
|
|
276
|
+
eventBus.publish(AgentEvent.statusUpdate({
|
|
277
|
+
taskId,
|
|
278
|
+
contextId,
|
|
279
|
+
status: { state: TaskState.TASK_STATE_INPUT_REQUIRED, message, timestamp: new Date().toISOString() },
|
|
280
|
+
metadata: undefined,
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
212
283
|
/**
|
|
213
284
|
* The turn paused on a gated tool call (ADR 0028): the task ends
|
|
214
285
|
* input-required, saying what is to be approved, with a data part a client
|
|
@@ -332,6 +403,8 @@ export class SyndicateExecutor {
|
|
|
332
403
|
// Reports the task once: to the task log and metrics, and its spend to
|
|
333
404
|
// the policy. Never throws into the task.
|
|
334
405
|
let reported = false;
|
|
406
|
+
// The turn's trace (the last root span started: a dispatch turn's answer).
|
|
407
|
+
let traceId;
|
|
335
408
|
const report = async (ctx, status, reason, usage = NO_USAGE) => {
|
|
336
409
|
if (reported)
|
|
337
410
|
return;
|
|
@@ -347,6 +420,9 @@ export class SyndicateExecutor {
|
|
|
347
420
|
}
|
|
348
421
|
try {
|
|
349
422
|
this.opts.onTaskEnd?.({
|
|
423
|
+
taskId,
|
|
424
|
+
traceId,
|
|
425
|
+
...(ctx?.traceparent ? { callerTraceId: ctx.traceparent.split('-')[1] } : {}),
|
|
350
426
|
agentId,
|
|
351
427
|
syndicate: config.syndicate_name,
|
|
352
428
|
caller: ctx?.caller,
|
|
@@ -360,6 +436,21 @@ export class SyndicateExecutor {
|
|
|
360
436
|
catch {
|
|
361
437
|
/* a broken log sink must not fail the task */
|
|
362
438
|
}
|
|
439
|
+
try {
|
|
440
|
+
this.opts.onAudit?.({
|
|
441
|
+
event: 'task.end',
|
|
442
|
+
outcome: status,
|
|
443
|
+
caller: ctx?.caller,
|
|
444
|
+
scopeHash: scopeKey ? createHash('sha256').update(scopeKey).digest('hex').slice(0, 12) : undefined,
|
|
445
|
+
sourceIp: ctx?.sourceIp,
|
|
446
|
+
agentId,
|
|
447
|
+
taskId,
|
|
448
|
+
detail: { syndicate: config.syndicate_name, ...(reason ? { reason } : {}), ...(traceId ? { traceId } : {}), durationMs: Date.now() - started, llmCalls: usage.llmCalls },
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
/* nor a broken audit sink */
|
|
453
|
+
}
|
|
363
454
|
};
|
|
364
455
|
let ctxForReport;
|
|
365
456
|
try {
|
|
@@ -408,11 +499,10 @@ export class SyndicateExecutor {
|
|
|
408
499
|
return;
|
|
409
500
|
}
|
|
410
501
|
}
|
|
411
|
-
|
|
502
|
+
const who = { scopeKey: ctx.scopeKey, caller: ctx.caller };
|
|
503
|
+
slot = this.opts.limiter.acquire(taskId, who);
|
|
412
504
|
if (!slot) {
|
|
413
|
-
const why = this.opts.limiter.
|
|
414
|
-
? 'The server is shutting down; retry shortly.'
|
|
415
|
-
: `The server is at its limit of ${this.opts.limiter.max} concurrent tasks; retry shortly.`;
|
|
505
|
+
const why = this.opts.limiter.refusal(who) ?? 'The server is at capacity; retry shortly.';
|
|
416
506
|
warn(`Task ${short} rejected — ${why}`);
|
|
417
507
|
publishFinal(eventBus, taskId, contextId, 'rejected', why);
|
|
418
508
|
await report(ctx, 'rejected', 'capacity');
|
|
@@ -471,6 +561,10 @@ export class SyndicateExecutor {
|
|
|
471
561
|
taskId,
|
|
472
562
|
configHash: this.configHashFor(),
|
|
473
563
|
attributes: surfaceAttributes(ctx),
|
|
564
|
+
traceparent: ctx.traceparent,
|
|
565
|
+
onSpanStart: (ids) => {
|
|
566
|
+
traceId = ids.traceId;
|
|
567
|
+
},
|
|
474
568
|
},
|
|
475
569
|
events: {
|
|
476
570
|
onProgress: (text) => publishWorking(eventBus, taskId, contextId, text),
|
|
@@ -497,6 +591,12 @@ export class SyndicateExecutor {
|
|
|
497
591
|
await report(ctx, 'input-required', 'approval', u);
|
|
498
592
|
return;
|
|
499
593
|
}
|
|
594
|
+
if (result.status === 'input-required' && result.input) {
|
|
595
|
+
log(`⏸ Task ${short} waiting for input from ${result.input.node} after ${spent}`);
|
|
596
|
+
publishInputRequest(eventBus, taskId, contextId, result.input);
|
|
597
|
+
await report(ctx, 'input-required', 'input', u);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
500
600
|
if (result.status === 'failed') {
|
|
501
601
|
warn(`✗ Task ${short} failed [${result.error?.code}] after ${spent}`);
|
|
502
602
|
publishFinal(eventBus, taskId, contextId, 'failed', describeFailedTurn(result));
|
package/dist/lib/compile.d.ts
CHANGED
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
* dispatches to is identical to the one DELEGATE would have wrapped.
|
|
28
28
|
* Contract and rationale: lib/dispatch.ts.
|
|
29
29
|
*/
|
|
30
|
-
import { FunctionTool, LlmAgent } from '@google/adk';
|
|
31
|
-
import type {
|
|
30
|
+
import { BaseLlm, FunctionTool, LlmAgent } from '@google/adk';
|
|
31
|
+
import type { BaseTool } from '@google/adk';
|
|
32
32
|
import type { SubagentYamlConfig, SyndicateYamlConfig } from './loadSyndicate.ts';
|
|
33
|
+
import type { SkillsConfig } from './tools/skillToolset.ts';
|
|
33
34
|
export interface CompileOptions {
|
|
34
35
|
/**
|
|
35
36
|
* Turns the YAML `model` string into what LlmAgent receives. Default:
|
|
@@ -48,7 +49,23 @@ export interface CompileOptions {
|
|
|
48
49
|
onUnknownTool?: (name: string) => void;
|
|
49
50
|
/** Progress/diagnostic line sink (nested loads, MCP discovery). */
|
|
50
51
|
log?: (message: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Node settings for an agent compiled as a workflow node (lib/workflow.ts):
|
|
54
|
+
* ADK's `retryConfig` and `timeout`, which an LlmAgent takes only at
|
|
55
|
+
* construction. Called with the agent's name; undefined means none.
|
|
56
|
+
*/
|
|
57
|
+
nodeConfig?: (agentName: string) => Record<string, unknown> | undefined;
|
|
51
58
|
}
|
|
59
|
+
/** An agent's `context:` block: compact a long conversation into a summary (ADR 0033). */
|
|
60
|
+
export interface ContextConfig {
|
|
61
|
+
/** Compact when the last request's prompt passed this many tokens. */
|
|
62
|
+
compact_after_tokens: number;
|
|
63
|
+
/** Events kept verbatim after the summary. Default 6. */
|
|
64
|
+
keep_recent_events?: number;
|
|
65
|
+
/** The model that writes the summary. Default: the agent's own. */
|
|
66
|
+
summary_model?: string;
|
|
67
|
+
}
|
|
68
|
+
export declare const DEFAULT_KEEP_RECENT_EVENTS = 6;
|
|
52
69
|
/**
|
|
53
70
|
* A copy of a FunctionTool that runs only after a person approves the call
|
|
54
71
|
* (ADR 0028). ADK's own gate does the work: the call raises an
|
|
@@ -57,8 +74,29 @@ export interface CompileOptions {
|
|
|
57
74
|
* ungated for every other agent that lists the same tool.
|
|
58
75
|
*/
|
|
59
76
|
export declare function requireApprovalOn(tool: FunctionTool): FunctionTool;
|
|
60
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* A copy of an OpenAPI tool (lib/tools/openapiTools.ts) that runs only after
|
|
79
|
+
* a person approves the call: the same confirmation interrupt FunctionTool's
|
|
80
|
+
* own gate raises, so the turn pauses and resumes exactly as ADR 0028 says.
|
|
81
|
+
*/
|
|
82
|
+
export declare function requireApprovalOnBaseTool<T extends BaseTool>(tool: T): T;
|
|
83
|
+
/** True when any agent of a syndicate may pause for approval: a gated tool, or skill scripts. */
|
|
61
84
|
export declare function declaresApprovals(config: SyndicateYamlConfig): boolean;
|
|
85
|
+
/** An agent's `examples:` entry: one exchange the model should imitate. */
|
|
86
|
+
export interface ExampleConfig {
|
|
87
|
+
input: string;
|
|
88
|
+
output: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* ADK's ExampleTool from YAML pairs: never called by the model, it adds the
|
|
92
|
+
* exchanges to every request's instruction as few-shot examples.
|
|
93
|
+
*/
|
|
94
|
+
export declare function examplesTool(examples: ExampleConfig[] | undefined): unknown[];
|
|
95
|
+
/** True when a turn running this agent directly may pause for a person (ADR 0028). */
|
|
96
|
+
export declare function agentGates(agent: {
|
|
97
|
+
require_approval?: string[];
|
|
98
|
+
skills?: SkillsConfig;
|
|
99
|
+
} | undefined): boolean;
|
|
62
100
|
/**
|
|
63
101
|
* Builds ONE runnable agent from a subagent entry. A `yaml_reference` entry
|
|
64
102
|
* compiles the nested syndicate's whole graph under this entry's name and
|
package/dist/lib/compile.js
CHANGED
|
@@ -27,13 +27,18 @@
|
|
|
27
27
|
* dispatches to is identical to the one DELEGATE would have wrapped.
|
|
28
28
|
* Contract and rationale: lib/dispatch.ts.
|
|
29
29
|
*/
|
|
30
|
-
import { AgentTool, FunctionTool, LlmAgent } from '@google/adk';
|
|
30
|
+
import { AgentTool, BaseLlm, BuiltInCodeExecutor, ExampleTool, FunctionTool, LLMRegistry, LlmAgent, LlmSummarizer, TokenBasedContextCompactor } from '@google/adk';
|
|
31
|
+
import { relative } from 'node:path';
|
|
31
32
|
import { isDispatchSyndicate } from './dispatch.js';
|
|
32
33
|
import { loadSyndicate, nestedLoader } from './loadSyndicate.js';
|
|
33
34
|
import { resolveTools as resolveNamedTools } from './toolRegistry.js';
|
|
34
35
|
import { createMcpTools } from './tools/mcpToolFactory.js';
|
|
35
36
|
import { capabilitySummary, describeCapabilities } from './models/capabilities.js';
|
|
37
|
+
import { FallbackLlm } from './models/fallback.js';
|
|
38
|
+
import { resolveModel as resolveRegistryModel } from './models/registry.js';
|
|
36
39
|
import { remoteAgentTool } from './a2a/remoteAgent.js';
|
|
40
|
+
import { buildSkillHarness } from './tools/skillToolset.js';
|
|
41
|
+
import { buildOpenApiTools, isOpenApiTool, namesTool } from './tools/openapiTools.js';
|
|
37
42
|
/** generateContentConfig as every entrypoint has always sent it to ADK. */
|
|
38
43
|
function withServerSideToolInvocations(generateContentConfig) {
|
|
39
44
|
const cfg = (generateContentConfig ?? {});
|
|
@@ -68,6 +73,36 @@ function passthroughFields(cfg) {
|
|
|
68
73
|
out.disallowTransferToPeers = cfg.disallowTransferToPeers;
|
|
69
74
|
return out;
|
|
70
75
|
}
|
|
76
|
+
export const DEFAULT_KEEP_RECENT_EVENTS = 6;
|
|
77
|
+
/**
|
|
78
|
+
* The LlmAgent fields the engine builds from YAML rather than passing
|
|
79
|
+
* through (ADR 0033): `code_execution: gemini` (Gemini's server-side
|
|
80
|
+
* sandbox runs the model's Python; nothing runs on this host), `context:`
|
|
81
|
+
* (ADK's token-based compactor with an LLM summarizer, so a long
|
|
82
|
+
* conversation is summarized instead of overflowing the window), and
|
|
83
|
+
* `mode: task` (the agent works until it calls finish_task; on a workflow
|
|
84
|
+
* node, its arguments become the node's output).
|
|
85
|
+
*/
|
|
86
|
+
function executionFields(cfg, opts) {
|
|
87
|
+
const out = {};
|
|
88
|
+
if (cfg.code_execution === 'gemini')
|
|
89
|
+
out.codeExecutor = new BuiltInCodeExecutor();
|
|
90
|
+
if (cfg.mode)
|
|
91
|
+
out.mode = cfg.mode;
|
|
92
|
+
if (cfg.context) {
|
|
93
|
+
const resolve = opts.resolveModel ?? ((m) => m);
|
|
94
|
+
const summaryModel = resolve(cfg.context.summary_model ?? cfg.model);
|
|
95
|
+
const llm = typeof summaryModel === 'string' || !summaryModel ? LLMRegistry.newLlm(String(summaryModel ?? cfg.model)) : summaryModel;
|
|
96
|
+
out.contextCompactors = [
|
|
97
|
+
new TokenBasedContextCompactor({
|
|
98
|
+
tokenThreshold: cfg.context.compact_after_tokens,
|
|
99
|
+
eventRetentionSize: cfg.context.keep_recent_events ?? DEFAULT_KEEP_RECENT_EVENTS,
|
|
100
|
+
summarizer: new LlmSummarizer({ llm }),
|
|
101
|
+
}),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
71
106
|
/**
|
|
72
107
|
* Says, once per compiled agent, what its resolved path cannot honour — a
|
|
73
108
|
* dropped server-side tool, a gateway stand-in, or no route at all. Quiet
|
|
@@ -95,17 +130,46 @@ export function requireApprovalOn(tool) {
|
|
|
95
130
|
Object.defineProperty(gated, 'requireConfirmation', { value: true, enumerable: false });
|
|
96
131
|
return gated;
|
|
97
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* A copy of an OpenAPI tool (lib/tools/openapiTools.ts) that runs only after
|
|
135
|
+
* a person approves the call: the same confirmation interrupt FunctionTool's
|
|
136
|
+
* own gate raises, so the turn pauses and resumes exactly as ADR 0028 says.
|
|
137
|
+
*/
|
|
138
|
+
export function requireApprovalOnBaseTool(tool) {
|
|
139
|
+
const gated = Object.create(tool);
|
|
140
|
+
Object.defineProperty(gated, 'runAsync', {
|
|
141
|
+
value: async (request) => {
|
|
142
|
+
const ctx = request.toolContext;
|
|
143
|
+
if (!ctx.toolConfirmation) {
|
|
144
|
+
ctx.requestConfirmation({ hint: `Approval is required before ${tool.name} runs.`, payload: request.args });
|
|
145
|
+
ctx.actions.skipSummarization = true;
|
|
146
|
+
return { error: 'This call requires approval, please approve or reject.' };
|
|
147
|
+
}
|
|
148
|
+
if (!ctx.toolConfirmation.confirmed)
|
|
149
|
+
return { error: 'This call was rejected.' };
|
|
150
|
+
return tool.runAsync(request);
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
return gated;
|
|
154
|
+
}
|
|
98
155
|
function gateTools(tools, names, agentName) {
|
|
99
156
|
if (!names?.length)
|
|
100
157
|
return tools;
|
|
101
158
|
const wanted = new Set(names);
|
|
102
159
|
const out = tools.map((t) => {
|
|
103
|
-
const
|
|
104
|
-
|
|
160
|
+
const tool = t;
|
|
161
|
+
const name = tool?.name;
|
|
162
|
+
if (!name)
|
|
163
|
+
return t;
|
|
164
|
+
// An OpenAPI operation may be named as the YAML named it (its operationId).
|
|
165
|
+
const match = [...wanted].find((w) => w === name || (isOpenApiTool(t) && namesTool(w, { name, operation: tool.operation })));
|
|
166
|
+
if (!match)
|
|
105
167
|
return t;
|
|
106
|
-
wanted.delete(
|
|
168
|
+
wanted.delete(match);
|
|
169
|
+
if (isOpenApiTool(t))
|
|
170
|
+
return requireApprovalOnBaseTool(t);
|
|
107
171
|
if (!(t instanceof FunctionTool)) {
|
|
108
|
-
throw new Error(`${agentName}: '${name}' cannot require approval — only function tools from the registry can be gated (ADR 0028).`);
|
|
172
|
+
throw new Error(`${agentName}: '${name}' cannot require approval — only function tools from the registry and OpenAPI operations can be gated (ADR 0028).`);
|
|
109
173
|
}
|
|
110
174
|
return requireApprovalOn(t);
|
|
111
175
|
});
|
|
@@ -116,15 +180,61 @@ function gateTools(tools, names, agentName) {
|
|
|
116
180
|
}
|
|
117
181
|
return out;
|
|
118
182
|
}
|
|
119
|
-
/** True when any agent of a syndicate
|
|
183
|
+
/** True when any agent of a syndicate may pause for approval: a gated tool, or skill scripts. */
|
|
120
184
|
export function declaresApprovals(config) {
|
|
121
|
-
return
|
|
185
|
+
return agentGates(config.orchestrator) || (config.subagents ?? []).some((s) => agentGates(s));
|
|
122
186
|
}
|
|
123
|
-
|
|
124
|
-
|
|
187
|
+
/**
|
|
188
|
+
* ADK's ExampleTool from YAML pairs: never called by the model, it adds the
|
|
189
|
+
* exchanges to every request's instruction as few-shot examples.
|
|
190
|
+
*/
|
|
191
|
+
export function examplesTool(examples) {
|
|
192
|
+
if (!examples?.length)
|
|
193
|
+
return [];
|
|
194
|
+
return [
|
|
195
|
+
new ExampleTool(examples.map((e) => ({ input: { role: 'user', parts: [{ text: e.input }] }, output: [{ role: 'model', parts: [{ text: e.output }] }] }))),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* An agent with `fallback_model:` gets its model wrapped (lib/models/fallback.ts):
|
|
200
|
+
* the fallback answers a provider-side failure, and a provider that keeps
|
|
201
|
+
* failing is skipped for a cooldown. A model id string is resolved to its
|
|
202
|
+
* adapter first, so the wrapper always holds two adapters.
|
|
203
|
+
*/
|
|
204
|
+
function withFallback(primary, fallbackId, resolve, opts) {
|
|
205
|
+
if (!fallbackId)
|
|
206
|
+
return primary;
|
|
207
|
+
const asLlm = (m) => (m instanceof BaseLlm ? m : resolveRegistryModel(String(m)));
|
|
208
|
+
return new FallbackLlm(asLlm(primary), asLlm(resolve(fallbackId)), opts.log ?? ((m) => console.warn(m)));
|
|
209
|
+
}
|
|
210
|
+
async function resolveAgentTools(toolNames, mcpServerUrl, opts, openapi, examples, mcpAllowed) {
|
|
211
|
+
const tools = [...resolveNamedTools(toolNames, opts.onUnknownTool), ...examplesTool(examples)];
|
|
212
|
+
// OpenAPI operations become tools here, so require_approval can name them.
|
|
213
|
+
for (const entry of openapi ?? []) {
|
|
214
|
+
const built = await buildOpenApiTools(entry);
|
|
215
|
+
opts.log?.(`openapi · ${relative(process.cwd(), entry.spec) || entry.spec}: ${built.map((t) => t.name).join(', ') || '(no operations)'}`);
|
|
216
|
+
for (const t of built) {
|
|
217
|
+
if (tools.some((existing) => existing?.name === t.name))
|
|
218
|
+
throw new Error(`openapi ${entry.spec}: tool '${t.name}' collides with another tool; set a prefix`);
|
|
219
|
+
tools.push(t);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
125
222
|
if (mcpServerUrl) {
|
|
126
223
|
opts.log?.(`Loading MCP tools: ${mcpServerUrl}`);
|
|
127
|
-
const
|
|
224
|
+
const offered = await createMcpTools(mcpServerUrl);
|
|
225
|
+
// mcp_tools: only the named tools are exposed (a server's list is its
|
|
226
|
+
// own to change); a name the server does not offer is reported, and a
|
|
227
|
+
// gate on it fails the compile in gateTools.
|
|
228
|
+
const allowed = mcpAllowed ? new Set(mcpAllowed) : undefined;
|
|
229
|
+
const mcpTools = allowed ? offered.filter((t) => allowed.has(t.name)) : offered;
|
|
230
|
+
if (allowed) {
|
|
231
|
+
const missing = [...allowed].filter((n) => !offered.some((t) => t.name === n));
|
|
232
|
+
if (missing.length)
|
|
233
|
+
opts.log?.(`MCP ${mcpServerUrl} does not offer ${missing.map((n) => `'${n}'`).join(', ')} (mcp_tools)`);
|
|
234
|
+
const hidden = offered.length - mcpTools.length;
|
|
235
|
+
if (hidden)
|
|
236
|
+
opts.log?.(`MCP ${mcpServerUrl}: ${hidden} tool(s) not in mcp_tools are not exposed`);
|
|
237
|
+
}
|
|
128
238
|
for (const mcpTool of mcpTools) {
|
|
129
239
|
if (!tools.some((t) => t.name === mcpTool.name))
|
|
130
240
|
tools.push(mcpTool);
|
|
@@ -132,6 +242,28 @@ async function resolveAgentTools(toolNames, mcpServerUrl, opts) {
|
|
|
132
242
|
}
|
|
133
243
|
return tools;
|
|
134
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* An agent's `skills:` block becomes one toolset among its tools and one
|
|
247
|
+
* block appended to its instruction (the frontmatter index, always in view).
|
|
248
|
+
* The registry tools a skill may unlock (`skills.tools`) are resolved here,
|
|
249
|
+
* by name, and handed to the toolset; they reach the model only after a
|
|
250
|
+
* skill naming them is loaded (lib/tools/skillToolset.ts).
|
|
251
|
+
*/
|
|
252
|
+
async function withSkills(instruction, tools, skills, agentName, opts) {
|
|
253
|
+
if (!skills)
|
|
254
|
+
return { instruction, tools };
|
|
255
|
+
const unlockable = resolveNamedTools(skills.tools, opts.onUnknownTool);
|
|
256
|
+
const harness = await buildSkillHarness(skills, unlockable);
|
|
257
|
+
const count = Object.keys(harness.skills).length;
|
|
258
|
+
opts.log?.(`skills · ${agentName}: ${count} skill${count === 1 ? '' : 's'} from ${skills.dir}${skills.scripts === 'local' ? ' · scripts run after approval' : ''}`);
|
|
259
|
+
for (const problem of harness.problems)
|
|
260
|
+
opts.log?.(`⚠ skills · ${skills.dir}: not loaded — ${problem}`);
|
|
261
|
+
return { instruction: `${instruction.trimEnd()}\n\n${harness.instruction}`, tools: [...tools, harness.toolset] };
|
|
262
|
+
}
|
|
263
|
+
/** True when a turn running this agent directly may pause for a person (ADR 0028). */
|
|
264
|
+
export function agentGates(agent) {
|
|
265
|
+
return !!agent?.require_approval?.length || agent?.skills?.scripts === 'local';
|
|
266
|
+
}
|
|
135
267
|
/**
|
|
136
268
|
* Builds ONE runnable agent from a subagent entry. A `yaml_reference` entry
|
|
137
269
|
* compiles the nested syndicate's whole graph under this entry's name and
|
|
@@ -154,18 +286,21 @@ export async function compileSubagent(subCfg, opts = {}) {
|
|
|
154
286
|
}
|
|
155
287
|
return compileGraph(nested, opts, subCfg.name, subCfg.description);
|
|
156
288
|
}
|
|
157
|
-
const
|
|
289
|
+
const gated = gateTools(await resolveAgentTools(subCfg.tools, subCfg.mcp_server_url, opts, subCfg.openapi, subCfg.examples, subCfg.mcp_tools), subCfg.require_approval, subCfg.name);
|
|
290
|
+
const { instruction, tools } = await withSkills(subCfg.instruction ?? '', gated, subCfg.skills, subCfg.name, opts);
|
|
158
291
|
const resolveModel = opts.resolveModel ?? ((m) => m);
|
|
159
292
|
logCapabilities(opts, subCfg.name, subCfg.model, subCfg.tools);
|
|
160
293
|
return new LlmAgent({
|
|
161
294
|
name: subCfg.name,
|
|
162
295
|
description: subCfg.description,
|
|
163
|
-
model: resolveModel(subCfg.model),
|
|
164
|
-
instruction
|
|
296
|
+
model: withFallback(resolveModel(subCfg.model), subCfg.fallback_model, resolveModel, opts),
|
|
297
|
+
instruction,
|
|
165
298
|
tools: tools.length > 0 ? tools : undefined,
|
|
166
299
|
outputSchema: subCfg.outputSchema,
|
|
167
300
|
generateContentConfig: withServerSideToolInvocations(subCfg.generateContentConfig),
|
|
168
301
|
...passthroughFields(subCfg),
|
|
302
|
+
...executionFields(subCfg, opts),
|
|
303
|
+
...(opts.nodeConfig?.(subCfg.name) ?? {}),
|
|
169
304
|
});
|
|
170
305
|
}
|
|
171
306
|
/**
|
|
@@ -178,6 +313,14 @@ export async function compileGraph(config, opts = {}, overrideName, overrideDesc
|
|
|
178
313
|
// A registry definition carries its nested syndicates (ADR 0018 item 6).
|
|
179
314
|
if (config.bundled_references && !opts.loadNested)
|
|
180
315
|
opts = { ...opts, loadNested: nestedLoader(config) };
|
|
316
|
+
// A graph has no orchestrator-with-tools shape to build: its agents are
|
|
317
|
+
// nodes (lib/workflow.ts). Nested as a yaml_reference, only its
|
|
318
|
+
// orchestrator runs, since ADK cannot yet make a Workflow a subagent.
|
|
319
|
+
if (config.workflow && !overrideName) {
|
|
320
|
+
throw new Error(`${config.syndicate_name}: a workflow syndicate is compiled with compileWorkflow (lib/workflow.ts), not compileGraph`);
|
|
321
|
+
}
|
|
322
|
+
if (config.workflow)
|
|
323
|
+
opts.log?.(`${config.syndicate_name}: nested as a subagent, so only its orchestrator runs (a Workflow cannot be a subagent yet)`);
|
|
181
324
|
const compiledTools = isDispatchSyndicate(config)
|
|
182
325
|
? []
|
|
183
326
|
: await Promise.all((config.subagents ?? []).map(async (subCfg) => {
|
|
@@ -190,17 +333,19 @@ export async function compileGraph(config, opts = {}, overrideName, overrideDesc
|
|
|
190
333
|
// Orchestrator tools are registry names only — no entrypoint has ever
|
|
191
334
|
// attached an MCP server to an orchestrator, and this compiler preserves
|
|
192
335
|
// that exactly rather than widening the contract in passing.
|
|
193
|
-
compiledTools.push(...gateTools(await resolveAgentTools(config.orchestrator.tools, undefined, opts), config.orchestrator.require_approval, overrideName || config.orchestrator.name));
|
|
336
|
+
compiledTools.push(...gateTools(await resolveAgentTools(config.orchestrator.tools, undefined, opts, config.orchestrator.openapi, config.orchestrator.examples), config.orchestrator.require_approval, overrideName || config.orchestrator.name));
|
|
337
|
+
const { instruction, tools: orchestratorTools } = await withSkills(config.orchestrator.instruction, compiledTools, config.orchestrator.skills, overrideName || config.orchestrator.name, opts);
|
|
194
338
|
const resolveModel = opts.resolveModel ?? ((m) => m);
|
|
195
339
|
logCapabilities(opts, overrideName || config.orchestrator.name, config.orchestrator.model, config.orchestrator.tools);
|
|
196
340
|
return new LlmAgent({
|
|
197
341
|
name: overrideName || config.orchestrator.name,
|
|
198
342
|
description: overrideDescription || config.orchestrator.description,
|
|
199
|
-
model: resolveModel(config.orchestrator.model),
|
|
200
|
-
instruction
|
|
201
|
-
tools:
|
|
343
|
+
model: withFallback(resolveModel(config.orchestrator.model), config.orchestrator.fallback_model, resolveModel, opts),
|
|
344
|
+
instruction,
|
|
345
|
+
tools: orchestratorTools.length > 0 ? orchestratorTools : undefined,
|
|
202
346
|
outputSchema: config.orchestrator.outputSchema,
|
|
203
347
|
generateContentConfig: withServerSideToolInvocations(config.orchestrator.generateContentConfig),
|
|
204
348
|
...passthroughFields(config.orchestrator),
|
|
349
|
+
...executionFields(config.orchestrator, opts),
|
|
205
350
|
});
|
|
206
351
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -59,6 +59,30 @@ export declare const DEFAULT_GROK_MODEL = "grok-4.7";
|
|
|
59
59
|
* accept the param and never receive it (lib/models/grokLlm.ts).
|
|
60
60
|
*/
|
|
61
61
|
export declare const DEFAULT_GROK_REASONING_EFFORT = "medium";
|
|
62
|
+
/**
|
|
63
|
+
* Default Moonshot model used when a kimi-* model is requested but no
|
|
64
|
+
* specific identifier is provided. Requires MOONSHOT_API_KEY
|
|
65
|
+
* (lib/models/kimiLlm.ts). kimi-k3 is the flagship, priced like a mid-tier
|
|
66
|
+
* closed model ($3 / $15, Claude Sonnet 4.6's price; Sonnet 5.5 is cheaper);
|
|
67
|
+
* kimi-k2.6 is the general tier at a quarter of that (the family table and
|
|
68
|
+
* the cost note are in the adapter's header).
|
|
69
|
+
*/
|
|
70
|
+
export declare const DEFAULT_KIMI_MODEL = "kimi-k3";
|
|
71
|
+
/**
|
|
72
|
+
* Reasoning effort sent with kimi-k3 requests: 'low' | 'high' | 'max'
|
|
73
|
+
* (Moonshot's own default is 'max'; K3 always thinks and has no off switch).
|
|
74
|
+
* Pinned to HIGH — one step below max, which is the benchmark setting and
|
|
75
|
+
* the slowest; an agent sets generateContentConfig.reasoningEffort to
|
|
76
|
+
* override. Source: platform.moonshot.ai › Guides › Reasoning effort.
|
|
77
|
+
*/
|
|
78
|
+
export declare const DEFAULT_KIMI_REASONING_EFFORT = "high";
|
|
79
|
+
/**
|
|
80
|
+
* Model calls one turn may make, subagents included, when the syndicate sets
|
|
81
|
+
* no `max_steps`. A runaway tool loop stops here instead of at the provider's
|
|
82
|
+
* bill. Every shipped example that sets a cap sets 12 to 30; a syndicate that
|
|
83
|
+
* needs more raises its own `max_steps` (ADR 0039).
|
|
84
|
+
*/
|
|
85
|
+
export declare const DEFAULT_MAX_STEPS = 50;
|
|
62
86
|
/**
|
|
63
87
|
* Default open-weight model, served locally by Ollama (lib/models/ollamaLlm.ts).
|
|
64
88
|
* qwen3:8b is the smallest pulled model that supports tool calling — the
|
package/dist/lib/config.js
CHANGED
|
@@ -60,6 +60,30 @@ export const DEFAULT_GROK_MODEL = 'grok-4.7';
|
|
|
60
60
|
* accept the param and never receive it (lib/models/grokLlm.ts).
|
|
61
61
|
*/
|
|
62
62
|
export const DEFAULT_GROK_REASONING_EFFORT = 'medium';
|
|
63
|
+
/**
|
|
64
|
+
* Default Moonshot model used when a kimi-* model is requested but no
|
|
65
|
+
* specific identifier is provided. Requires MOONSHOT_API_KEY
|
|
66
|
+
* (lib/models/kimiLlm.ts). kimi-k3 is the flagship, priced like a mid-tier
|
|
67
|
+
* closed model ($3 / $15, Claude Sonnet 4.6's price; Sonnet 5.5 is cheaper);
|
|
68
|
+
* kimi-k2.6 is the general tier at a quarter of that (the family table and
|
|
69
|
+
* the cost note are in the adapter's header).
|
|
70
|
+
*/
|
|
71
|
+
export const DEFAULT_KIMI_MODEL = 'kimi-k3';
|
|
72
|
+
/**
|
|
73
|
+
* Reasoning effort sent with kimi-k3 requests: 'low' | 'high' | 'max'
|
|
74
|
+
* (Moonshot's own default is 'max'; K3 always thinks and has no off switch).
|
|
75
|
+
* Pinned to HIGH — one step below max, which is the benchmark setting and
|
|
76
|
+
* the slowest; an agent sets generateContentConfig.reasoningEffort to
|
|
77
|
+
* override. Source: platform.moonshot.ai › Guides › Reasoning effort.
|
|
78
|
+
*/
|
|
79
|
+
export const DEFAULT_KIMI_REASONING_EFFORT = 'high';
|
|
80
|
+
/**
|
|
81
|
+
* Model calls one turn may make, subagents included, when the syndicate sets
|
|
82
|
+
* no `max_steps`. A runaway tool loop stops here instead of at the provider's
|
|
83
|
+
* bill. Every shipped example that sets a cap sets 12 to 30; a syndicate that
|
|
84
|
+
* needs more raises its own `max_steps` (ADR 0039).
|
|
85
|
+
*/
|
|
86
|
+
export const DEFAULT_MAX_STEPS = 50;
|
|
63
87
|
/**
|
|
64
88
|
* Default open-weight model, served locally by Ollama (lib/models/ollamaLlm.ts).
|
|
65
89
|
* qwen3:8b is the smallest pulled model that supports tool calling — the
|
package/dist/lib/doctor.js
CHANGED
|
@@ -84,6 +84,7 @@ export const CONSOLE_URLS = {
|
|
|
84
84
|
anthropic: 'https://console.anthropic.com',
|
|
85
85
|
openai: 'https://platform.openai.com/api-keys',
|
|
86
86
|
xai: 'https://console.x.ai',
|
|
87
|
+
moonshot: 'https://platform.moonshot.ai',
|
|
87
88
|
ollama: 'https://ollama.com',
|
|
88
89
|
};
|
|
89
90
|
// ── Discovery ────────────────────────────────────────────────────────────────
|
|
@@ -162,8 +163,9 @@ function walk(config, prefix, opts, rows) {
|
|
|
162
163
|
const orchModel = orch?.model;
|
|
163
164
|
if (orch && orchModel) {
|
|
164
165
|
// In DELEGATE mode the orchestrator calls its subagents as tools; under
|
|
165
|
-
// plan-dispatch it is a tool-less classifier and code runs the route
|
|
166
|
-
|
|
166
|
+
// plan-dispatch it is a tool-less classifier and code runs the route; in
|
|
167
|
+
// a workflow it is one node of the graph.
|
|
168
|
+
const delegates = !config.dispatch && !config.workflow && (config.subagents ?? []).length > 0;
|
|
167
169
|
rows.push({
|
|
168
170
|
agent: prefix + orch.name,
|
|
169
171
|
role: 'orchestrator',
|
|
@@ -390,6 +392,15 @@ const ANSI = {
|
|
|
390
392
|
red: '\x1b[31m',
|
|
391
393
|
cyan: '\x1b[36m',
|
|
392
394
|
};
|
|
395
|
+
/** One id per provider, in that provider's own prefix, for the providers line. */
|
|
396
|
+
const PROBE_ID = {
|
|
397
|
+
gemini: 'gemini-x',
|
|
398
|
+
anthropic: 'claude-x',
|
|
399
|
+
openai: 'gpt-x',
|
|
400
|
+
xai: 'grok-x',
|
|
401
|
+
moonshot: 'kimi-x',
|
|
402
|
+
ollama: 'ollama/x',
|
|
403
|
+
};
|
|
393
404
|
function pad(s, n) {
|
|
394
405
|
return s.length >= n ? s.slice(0, n - 1) + '…' : s.padEnd(n);
|
|
395
406
|
}
|
|
@@ -405,9 +416,11 @@ export function renderDoctor(result, opts = {}) {
|
|
|
405
416
|
: `${c.green}${text}${c.reset}`;
|
|
406
417
|
lines.push(`${c.bold}melchizedek doctor${c.reset} ${c.dim}· ${result.agentsDir}${c.reset}`);
|
|
407
418
|
lines.push('');
|
|
408
|
-
// Provider line: what is funded, and how.
|
|
419
|
+
// Provider line: what is funded, and how. Each provider is probed with an
|
|
420
|
+
// id of its own prefix, so the line names ITS key (a fallthrough once put
|
|
421
|
+
// xAI's key beside Moonshot).
|
|
409
422
|
const providerBits = Object.keys(PROVIDERS).map((p) => {
|
|
410
|
-
const r = describeCapabilities(p
|
|
423
|
+
const r = describeCapabilities(PROBE_ID[p]);
|
|
411
424
|
const mark = !r.funded ? `${c.red}✗${c.reset}` : r.transport === 'gateway' ? `${c.yellow}◇${c.reset}` : `${c.green}✓${c.reset}`;
|
|
412
425
|
const how = !r.funded ? `${c.dim}${r.keyEnv} not set${c.reset}` : r.transport === 'gateway' ? `${c.dim}via gateway:${r.gateway}${c.reset}` : p === 'ollama' ? `${c.dim}local${c.reset}` : `${c.dim}${r.platform ?? 'direct'}${c.reset}`;
|
|
413
426
|
return `${mark} ${PROVIDERS[p].label} ${how}`;
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type { PendingApproval } from './runtime/approvals.ts';
|
|
|
19
19
|
export type { DrainedRun, MessagePart, TurnUsage, RouteDecision, SyndicateTurnOptions, SyndicateTurnResult, TraceOptions, TurnEvents, TurnStage, } from './runtime/syndicateTurn.ts';
|
|
20
20
|
export { compileGraph, compileSubagent } from './compile.ts';
|
|
21
21
|
export type { CompileOptions } from './compile.ts';
|
|
22
|
+
export { compileWorkflow, isWorkflowSyndicate, describeInput } from './workflow.ts';
|
|
23
|
+
export type { WorkflowConfig, WorkflowNodeYaml, EdgeElement, PendingInput, CompiledWorkflow } from './workflow.ts';
|
|
22
24
|
export { createA2AApp, compileAgentCard, currentRequestContext } from './a2a/app.ts';
|
|
23
25
|
export type { A2AApp, A2AAppOptions, RequestIdentity } from './a2a/app.ts';
|
|
24
26
|
export type { A2AContext } from './a2a/executor.ts';
|