ai-runtime-engine 1.3.0 → 2.7.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/CHANGELOG.md +578 -0
- package/dist/agents/admit.d.ts +69 -0
- package/dist/agents/admit.js +129 -0
- package/dist/agents/definition.d.ts +36 -0
- package/dist/agents/definition.js +9 -0
- package/dist/agents/envelope.d.ts +53 -0
- package/dist/agents/envelope.js +68 -0
- package/dist/agents/finding.d.ts +79 -0
- package/dist/agents/finding.js +80 -0
- package/dist/agents/task.d.ts +60 -0
- package/dist/agents/task.js +32 -0
- package/dist/agents/worker.d.ts +68 -0
- package/dist/agents/worker.js +256 -0
- package/dist/capabilities/capability.d.ts +117 -0
- package/dist/capabilities/capability.js +66 -0
- package/dist/capabilities/registry.d.ts +139 -0
- package/dist/capabilities/registry.js +413 -0
- package/dist/capabilities/vocabulary.d.ts +32 -0
- package/dist/capabilities/vocabulary.js +34 -0
- package/dist/cli/cli.js +55 -4
- package/dist/cli/commands/cleanup.js +29 -27
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +38 -8
- package/dist/cli/commands/executions.js +34 -25
- package/dist/cli/commands/info.d.ts +1 -0
- package/dist/cli/commands/info.js +11 -9
- package/dist/cli/commands/init.js +19 -0
- package/dist/cli/commands/inspect.d.ts +40 -1
- package/dist/cli/commands/inspect.js +157 -2
- package/dist/cli/commands/mcp.d.ts +45 -0
- package/dist/cli/commands/mcp.js +148 -0
- package/dist/cli/commands/route.js +21 -0
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.js +21 -2
- package/dist/cli/commands/skills.d.ts +2 -0
- package/dist/cli/commands/skills.js +29 -7
- package/dist/cli/interactive/ansi.d.ts +41 -0
- package/dist/cli/interactive/ansi.js +43 -0
- package/dist/cli/interactive/complete.d.ts +10 -0
- package/dist/cli/interactive/complete.js +19 -0
- package/dist/cli/interactive/repl.d.ts +3 -0
- package/dist/cli/interactive/repl.js +91 -13
- package/dist/cli/interactive/session.d.ts +8 -0
- package/dist/cli/interactive/session.js +73 -2
- package/dist/cli/render.d.ts +7 -0
- package/dist/cli/render.js +10 -0
- package/dist/cli/runtimeSession.d.ts +11 -0
- package/dist/cli/runtimeSession.js +17 -0
- package/dist/config/defaults.d.ts +3 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.js +2 -2
- package/dist/context/lossVerifier.d.ts +24 -0
- package/dist/context/lossVerifier.js +45 -0
- package/dist/context/summarize.d.ts +19 -0
- package/dist/context/summarize.js +53 -0
- package/dist/core/fallback/fallback.d.ts +5 -0
- package/dist/core/fallback/fallback.js +3 -1
- package/dist/core/router/router.d.ts +3 -0
- package/dist/core/router/router.js +1 -0
- package/dist/executions/execution.d.ts +13 -2
- package/dist/generation/generateAdapter.d.ts +14 -0
- package/dist/generation/generateAdapter.js +38 -0
- package/dist/generation/generateSkill.d.ts +26 -0
- package/dist/generation/generateSkill.js +51 -0
- package/dist/index.d.ts +43 -4
- package/dist/index.js +26 -2
- package/dist/mcp/client.d.ts +70 -0
- package/dist/mcp/client.js +221 -0
- package/dist/mcp/manager.d.ts +151 -0
- package/dist/mcp/manager.js +493 -0
- package/dist/mcp/protocol.d.ts +216 -0
- package/dist/mcp/protocol.js +149 -0
- package/dist/mcp/toolAdapter.d.ts +44 -0
- package/dist/mcp/toolAdapter.js +94 -0
- package/dist/mcp/transport.d.ts +109 -0
- package/dist/mcp/transport.js +383 -0
- package/dist/memory/embedders/hash.d.ts +12 -0
- package/dist/memory/embedders/hash.js +31 -0
- package/dist/memory/embedders/http.d.ts +25 -0
- package/dist/memory/embedders/http.js +48 -0
- package/dist/memory/memory.d.ts +19 -2
- package/dist/memory/memory.js +75 -11
- package/dist/memory/semantic.d.ts +17 -0
- package/dist/memory/semantic.js +29 -0
- package/dist/orchestration/budget.d.ts +30 -0
- package/dist/orchestration/budget.js +40 -0
- package/dist/orchestration/executor.d.ts +39 -1
- package/dist/orchestration/executor.js +64 -4
- package/dist/orchestration/orchestrator.d.ts +29 -1
- package/dist/orchestration/orchestrator.js +89 -8
- package/dist/orchestration/plan.d.ts +15 -1
- package/dist/orchestration/plan.js +23 -4
- package/dist/orchestration/planner.d.ts +19 -1
- package/dist/orchestration/planner.js +25 -5
- package/dist/plugin/ai.d.ts +4 -0
- package/dist/plugin/ai.js +9 -0
- package/dist/runtime/config.js +50 -6
- package/dist/runtime/intent/aiClassifier.d.ts +19 -0
- package/dist/runtime/intent/aiClassifier.js +74 -0
- package/dist/runtime/models/modelProfile.d.ts +61 -0
- package/dist/runtime/models/modelProfile.js +139 -0
- package/dist/runtime/planning/deriveCapabilities.d.ts +95 -0
- package/dist/runtime/planning/deriveCapabilities.js +146 -0
- package/dist/runtime/policy.d.ts +10 -0
- package/dist/runtime/policy.js +9 -2
- package/dist/runtime/runtime.d.ts +173 -0
- package/dist/runtime/runtime.js +705 -50
- package/dist/runtime/types.d.ts +88 -2
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +24 -0
- package/dist/skills/registry.d.ts +16 -1
- package/dist/skills/registry.js +21 -1
- package/dist/skills/skill.d.ts +6 -1
- package/dist/store/area.d.ts +15 -1
- package/dist/store/area.js +19 -8
- package/dist/store/crypto.d.ts +21 -0
- package/dist/store/crypto.js +49 -0
- package/dist/store/paths.d.ts +5 -1
- package/dist/store/paths.js +6 -0
- package/dist/store/store.d.ts +15 -3
- package/dist/store/store.js +28 -7
- package/dist/telemetry/sinks/otlp.d.ts +31 -0
- package/dist/telemetry/sinks/otlp.js +76 -0
- package/dist/tools/builtins/filesystem.js +1 -0
- package/dist/tools/builtins/git.js +1 -0
- package/dist/tools/builtins/shell.js +1 -0
- package/dist/tools/permissions.d.ts +28 -0
- package/dist/tools/permissions.js +72 -0
- package/dist/tools/registry.d.ts +18 -2
- package/dist/tools/registry.js +22 -2
- package/dist/tools/tool.d.ts +4 -0
- package/dist/types.d.ts +5 -1
- package/dist/util/flatten.d.ts +11 -0
- package/dist/util/flatten.js +18 -0
- package/dist/util/semaphore.d.ts +19 -0
- package/dist/util/semaphore.js +60 -0
- package/package.json +24 -9
|
@@ -9,6 +9,16 @@ import { SETUP_CANDIDATES } from '../commands/setup.js';
|
|
|
9
9
|
import { gatherInfo, renderInfo } from '../commands/info.js';
|
|
10
10
|
import { gatherDoctor, renderDoctor } from '../commands/doctor.js';
|
|
11
11
|
import { planCleanup } from '../commands/cleanup.js';
|
|
12
|
+
import { actionCapabilityRows, renderActionCapabilities, renderCapabilityGaps } from '../commands/inspect.js';
|
|
13
|
+
import { renderMcpList, renderMcpDetail } from '../commands/mcp.js';
|
|
14
|
+
import { candidatesFrom, deriveCapabilitiesOffline } from '../../runtime/planning/deriveCapabilities.js';
|
|
15
|
+
import { displaySafe } from '../render.js';
|
|
16
|
+
/** Top-level slash commands, for REPL tab-completion (Phase 21b). Kept in sync with the `handle` dispatch. */
|
|
17
|
+
export const SLASH_COMMANDS = [
|
|
18
|
+
'help', 'status', 'info', 'doctor', 'cleanup', 'mode', 'compare', 'models', 'config', 'providers', 'tools', 'capabilities', 'mcp',
|
|
19
|
+
'skills', 'memory', 'conversations', 'executions', 'resume', 'resume-execution', 'pause', 'cancel', 'approve',
|
|
20
|
+
'deny', 'learning', 'feedback', 'permissions', 'budget', 'stream', 'dry-run', 'clear', 'exit', 'quit',
|
|
21
|
+
];
|
|
12
22
|
const HELP = [
|
|
13
23
|
'Commands:',
|
|
14
24
|
' /help show this help',
|
|
@@ -24,6 +34,10 @@ const HELP = [
|
|
|
24
34
|
' /providers/<id> [models|capabilities|health] inspect one provider',
|
|
25
35
|
' /models list all known models',
|
|
26
36
|
' /tools list registered tools',
|
|
37
|
+
' /capabilities list action capabilities (what the runtime can DO) + providers',
|
|
38
|
+
' /capabilities <goal> which capabilities a goal needs, and what is missing (offline)',
|
|
39
|
+
' /mcp list MCP servers (state, tools, grant)',
|
|
40
|
+
' /mcp/<id> inspect one MCP server (protocol, tools, diagnostics)',
|
|
27
41
|
' /skills [discover] list available skills; discover scans the workspace',
|
|
28
42
|
' /permissions show the current tool permissions',
|
|
29
43
|
' /feedback <text> record feedback on the last result (e.g. "that worked")',
|
|
@@ -43,6 +57,7 @@ const HELP = [
|
|
|
43
57
|
' /config show the resolved configuration',
|
|
44
58
|
' /dry-run toggle dry-run (plan only, no changes)',
|
|
45
59
|
' /stream toggle token-by-token streaming of answers',
|
|
60
|
+
' /budget show the call/cost budget (AI_MAX_CALLS / AI_MAX_COST_USD)',
|
|
46
61
|
' /clear clear the screen',
|
|
47
62
|
' /exit leave the session',
|
|
48
63
|
'',
|
|
@@ -67,6 +82,27 @@ export class ReplSession {
|
|
|
67
82
|
this.viewCache = new ProviderViewCache(this.runtime.ai);
|
|
68
83
|
return this.viewCache;
|
|
69
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* MCP servers in the REPL. The session's Runtime is already connected (the REPL connects eagerly at
|
|
87
|
+
* start), so this NEVER opens a second Runtime — that would spawn duplicate stdio children and give
|
|
88
|
+
* `close()` the wrong one to tear down. It reuses the exact formatters the `mcp` command prints.
|
|
89
|
+
*/
|
|
90
|
+
mcpView(target) {
|
|
91
|
+
if (!this.runtime.hasMcpServers())
|
|
92
|
+
return { lines: renderMcpList({}, []) };
|
|
93
|
+
const grants = this.runtime.permissions().mcp.servers;
|
|
94
|
+
if (!target) {
|
|
95
|
+
const lines = renderMcpList(grants, this.runtime.mcp().list());
|
|
96
|
+
const warnings = this.runtime.mcpWarningsList();
|
|
97
|
+
if (warnings.length)
|
|
98
|
+
lines.push('', 'Warnings:', ...warnings.map((w) => ` ! ${w}`));
|
|
99
|
+
return { lines };
|
|
100
|
+
}
|
|
101
|
+
const status = this.runtime.mcp().status(target);
|
|
102
|
+
if (!status)
|
|
103
|
+
return { lines: [`no MCP server '${displaySafe(target, 40)}' — /mcp lists them`] };
|
|
104
|
+
return { lines: renderMcpDetail(grants, status, this.runtime.mcp().diagnostics(target), this.runtime.mcp().tools(target)) };
|
|
105
|
+
}
|
|
70
106
|
async handle(raw) {
|
|
71
107
|
const line = raw.trim();
|
|
72
108
|
if (!line)
|
|
@@ -109,6 +145,24 @@ export class ReplSession {
|
|
|
109
145
|
return this.config();
|
|
110
146
|
case 'providers':
|
|
111
147
|
return this.providers(parts[1], args[0]);
|
|
148
|
+
case 'capabilities': {
|
|
149
|
+
// `capabilities --actions` connects (inspect.ts); without this the same question reported
|
|
150
|
+
// different MCP availability on the two surfaces. connectMcp is idempotent.
|
|
151
|
+
if (this.runtime.hasMcpServers())
|
|
152
|
+
await this.runtime.connectMcp();
|
|
153
|
+
// Both forms, and never a truncated goal: `/capabilities run the tests` and
|
|
154
|
+
// `/capabilities/run the tests` must mean the same thing.
|
|
155
|
+
const goal = [...parts.slice(1), ...args].join(' ').trim();
|
|
156
|
+
if (!goal)
|
|
157
|
+
return { lines: renderActionCapabilities(actionCapabilityRows(this.runtime)) };
|
|
158
|
+
const required = deriveCapabilitiesOffline(goal, candidatesFrom(this.runtime.capabilities().list()));
|
|
159
|
+
if (!required.length)
|
|
160
|
+
return { lines: [`no action capability matched "${displaySafe(goal, 80)}" — /capabilities lists everything this runtime can do.`] };
|
|
161
|
+
return { lines: renderCapabilityGaps(this.runtime.capabilityReport(required)) };
|
|
162
|
+
}
|
|
163
|
+
case 'mcp':
|
|
164
|
+
// `/mcp/<id>` and `/mcp <id>` both address one server.
|
|
165
|
+
return this.mcpView(parts[1] ?? args[0]);
|
|
112
166
|
case 'tools':
|
|
113
167
|
return { lines: ['Tools:', ...this.runtime.tools().map((t) => ` ${t.id.padEnd(14)} ${t.description}`)] };
|
|
114
168
|
case 'skills': {
|
|
@@ -139,6 +193,14 @@ export class ReplSession {
|
|
|
139
193
|
case 'stream':
|
|
140
194
|
this.streaming = !this.streaming;
|
|
141
195
|
return { lines: [`streaming ${this.streaming ? 'ON — answers render token-by-token' : 'OFF'}`] };
|
|
196
|
+
case 'budget':
|
|
197
|
+
return {
|
|
198
|
+
lines: [
|
|
199
|
+
`call budget (AI_MAX_CALLS): ${process.env.AI_MAX_CALLS ?? '(unset — no limit)'}`,
|
|
200
|
+
`cost budget (AI_MAX_COST_USD): ${process.env.AI_MAX_COST_USD ?? '(unset — no limit)'}`,
|
|
201
|
+
'over budget: notify-and-wait by default; add --partial (one-shot) to run the phases that fit and pause.',
|
|
202
|
+
],
|
|
203
|
+
};
|
|
142
204
|
case 'memory':
|
|
143
205
|
return this.memory(args);
|
|
144
206
|
case 'conversations':
|
|
@@ -182,17 +244,25 @@ export class ReplSession {
|
|
|
182
244
|
const execId = result.execution?.id;
|
|
183
245
|
lines.push(execId ? `(approval required — /approve ${execId} to proceed, or /deny ${execId})` : '(approval required — approve the execution via /executions then /approve <id>)');
|
|
184
246
|
}
|
|
247
|
+
if (result.status === 'waiting_for_budget') {
|
|
248
|
+
const execId = result.execution?.id;
|
|
249
|
+
lines.push(execId ? `(over budget — raise AI_MAX_CALLS and /resume-execution ${execId}, or re-run with more budget)` : '(over budget — raise the call budget and re-run, or add --partial)');
|
|
250
|
+
}
|
|
185
251
|
if (result.status === 'failed')
|
|
186
252
|
lines.push('(did not complete)');
|
|
187
253
|
if (result.clarification)
|
|
188
254
|
lines.push(`? ${result.clarification.question}`);
|
|
189
255
|
if (result.memory?.captured)
|
|
190
256
|
lines.push('(remembered)');
|
|
257
|
+
// Phase 3.3: the ONE gap renderer, shared with `ai-runtime run` and both capabilities surfaces.
|
|
258
|
+
// Appended AFTER the existing advisory tail so every positional assertion above is untouched.
|
|
259
|
+
if (result.capabilityGaps)
|
|
260
|
+
lines.push(...renderCapabilityGaps(result.capabilityGaps));
|
|
191
261
|
if (this.conversationId && result.response?.text)
|
|
192
262
|
this.runtime.conversations.append(this.conversationId, 'assistant', result.response.text, result.runId);
|
|
193
263
|
return { lines };
|
|
194
264
|
}
|
|
195
|
-
memory(args) {
|
|
265
|
+
async memory(args) {
|
|
196
266
|
if (!this.runtime.memory.enabled)
|
|
197
267
|
return { lines: ['memory is disabled (stateless mode).'] };
|
|
198
268
|
const sub = args[0];
|
|
@@ -200,7 +270,8 @@ export class ReplSession {
|
|
|
200
270
|
const q = args.slice(1).join(' ');
|
|
201
271
|
if (!q)
|
|
202
272
|
return { lines: ['usage: /memory search <query>'] };
|
|
203
|
-
|
|
273
|
+
// Semantic ranking when an embedder is configured; degrades to BM25 internally.
|
|
274
|
+
const hits = await this.runtime.memory.searchSemantic(q, { limit: 10 });
|
|
204
275
|
return { lines: hits.length ? hits.map((r) => ` ${r.id} [${r.scope}] ${r.text}`) : ['no matches.'] };
|
|
205
276
|
}
|
|
206
277
|
if (sub === 'delete') {
|
package/dist/cli/render.d.ts
CHANGED
|
@@ -11,3 +11,10 @@ export declare function print(line: string): void;
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function printChunk(chunk: string): void;
|
|
13
13
|
export declare function printError(line: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Clamp a source- or MODEL-controlled string before it reaches the terminal — the terminal twin of
|
|
16
|
+
* `promptSafe`. LOAD-BEARING: `print` redacts secrets but strips no control characters and bounds no
|
|
17
|
+
* length, and a gap's `capabilityId` originates in the model's plan JSON — `sanitizeSegment` restricts
|
|
18
|
+
* its charset but sets no maximum, and the `register` gap hint interpolates it.
|
|
19
|
+
*/
|
|
20
|
+
export declare function displaySafe(raw: string, max?: number): string;
|
package/dist/cli/render.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* directly from a command.
|
|
5
5
|
*/
|
|
6
6
|
import { redactString } from '../security/redact.js';
|
|
7
|
+
import { flattenClamp } from '../util/flatten.js';
|
|
7
8
|
export function print(line) {
|
|
8
9
|
// eslint-disable-next-line no-console
|
|
9
10
|
console.log(redactString(line));
|
|
@@ -20,3 +21,12 @@ export function printError(line) {
|
|
|
20
21
|
// eslint-disable-next-line no-console
|
|
21
22
|
console.error(redactString(line));
|
|
22
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Clamp a source- or MODEL-controlled string before it reaches the terminal — the terminal twin of
|
|
26
|
+
* `promptSafe`. LOAD-BEARING: `print` redacts secrets but strips no control characters and bounds no
|
|
27
|
+
* length, and a gap's `capabilityId` originates in the model's plan JSON — `sanitizeSegment` restricts
|
|
28
|
+
* its charset but sets no maximum, and the `register` gap hint interpolates it.
|
|
29
|
+
*/
|
|
30
|
+
export function displaySafe(raw, max = 80) {
|
|
31
|
+
return flattenClamp(raw, max);
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One place where a CLI command borrows a Runtime and gives it back.
|
|
3
|
+
*
|
|
4
|
+
* A `Runtime` can own OS resources — today MCP stdio child processes. A one-shot command that returns
|
|
5
|
+
* without releasing them does not merely leak: a live child keeps the event loop alive, so the CLI never
|
|
6
|
+
* exits. `withRuntime` guarantees the release on every path, including a thrown error.
|
|
7
|
+
*/
|
|
8
|
+
import { Runtime } from '../runtime/runtime.js';
|
|
9
|
+
export declare function withRuntime<T>(opts: {
|
|
10
|
+
config?: string;
|
|
11
|
+
}, fn: (rt: Runtime) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One place where a CLI command borrows a Runtime and gives it back.
|
|
3
|
+
*
|
|
4
|
+
* A `Runtime` can own OS resources — today MCP stdio child processes. A one-shot command that returns
|
|
5
|
+
* without releasing them does not merely leak: a live child keeps the event loop alive, so the CLI never
|
|
6
|
+
* exits. `withRuntime` guarantees the release on every path, including a thrown error.
|
|
7
|
+
*/
|
|
8
|
+
import { Runtime } from '../runtime/runtime.js';
|
|
9
|
+
export async function withRuntime(opts, fn) {
|
|
10
|
+
const rt = await Runtime.load({ ...(opts.config ? { config: opts.config } : {}) });
|
|
11
|
+
try {
|
|
12
|
+
return await fn(rt);
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
await rt.close();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -24,9 +24,11 @@ export interface ResolvedConfig {
|
|
|
24
24
|
privacy: ResolvedPrivacy;
|
|
25
25
|
telemetry: {
|
|
26
26
|
enabled: boolean;
|
|
27
|
-
sink: 'memory' | 'file';
|
|
27
|
+
sink: 'memory' | 'file' | 'otlp';
|
|
28
28
|
storePrompts: false;
|
|
29
29
|
path?: string;
|
|
30
|
+
endpoint?: string;
|
|
31
|
+
headersEnv?: string;
|
|
30
32
|
};
|
|
31
33
|
learning: {
|
|
32
34
|
enabled: boolean;
|
package/dist/config/defaults.js
CHANGED
|
@@ -33,6 +33,8 @@ export function resolveConfig(cfg) {
|
|
|
33
33
|
sink: cfg.telemetry?.sink ?? 'memory',
|
|
34
34
|
storePrompts: false,
|
|
35
35
|
...(cfg.telemetry?.path !== undefined ? { path: cfg.telemetry.path } : {}),
|
|
36
|
+
...(cfg.telemetry?.endpoint !== undefined ? { endpoint: cfg.telemetry.endpoint } : {}),
|
|
37
|
+
...(cfg.telemetry?.headersEnv !== undefined ? { headersEnv: cfg.telemetry.headersEnv } : {}),
|
|
36
38
|
};
|
|
37
39
|
return {
|
|
38
40
|
providers: cfg.providers,
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -6,4 +6,5 @@
|
|
|
6
6
|
import type { RouterConfig } from '../types.js';
|
|
7
7
|
/** Canonical strategy names. Exported for reuse by the runtime-config layer (no duplication). */
|
|
8
8
|
export declare const STRATEGIES: readonly ["best", "fastest", "cheapest", "highest-quality", "local-only", "cloud-only", "privacy-first", "provider-specific", "fallback-only"];
|
|
9
|
+
export declare const KEY_LIKE: RegExp;
|
|
9
10
|
export declare function parseConfig(raw: unknown): RouterConfig;
|
package/dist/config/schema.js
CHANGED
|
@@ -10,7 +10,7 @@ export const STRATEGIES = ['best', 'fastest', 'cheapest', 'highest-quality', 'lo
|
|
|
10
10
|
const EVIDENCE = ['unsupported', 'unknown', 'inferred', 'documented', 'verified'];
|
|
11
11
|
const GROUPS = ['input', 'output', 'intelligence', 'agent'];
|
|
12
12
|
const KINDS = ['openai-compatible', 'gemini', 'groq', 'anthropic', 'ollama', 'custom', 'mock'];
|
|
13
|
-
const KEY_LIKE = /^(sk-|gsk_|Bearer\s|[A-Za-z0-9_-]{40,}$)/;
|
|
13
|
+
export const KEY_LIKE = /^(sk-|gsk_|Bearer\s|[A-Za-z0-9_-]{40,}$)/;
|
|
14
14
|
const capabilityRequirement = z.object({
|
|
15
15
|
group: z.enum(GROUPS),
|
|
16
16
|
key: z.string().min(1),
|
|
@@ -64,7 +64,7 @@ const routerConfig = z
|
|
|
64
64
|
.object({ allowCloud: z.boolean().optional(), allowLocal: z.boolean().optional(), sensitiveDataAllowedOnCloud: z.boolean().optional(), treatUnknownAsSensitive: z.boolean().optional(), defaultSensitivity: z.enum(['low', 'high', 'unknown']).optional() })
|
|
65
65
|
.strict()
|
|
66
66
|
.optional(),
|
|
67
|
-
telemetry: z.object({ enabled: z.boolean().optional(), sink: z.enum(['memory', 'file']).optional(), storePrompts: z.literal(false).optional(), path: z.string().optional() }).strict().optional(),
|
|
67
|
+
telemetry: z.object({ enabled: z.boolean().optional(), sink: z.enum(['memory', 'file', 'otlp']).optional(), storePrompts: z.literal(false).optional(), path: z.string().optional(), endpoint: z.string().optional(), headersEnv: z.string().refine((v) => v === undefined || !KEY_LIKE.test(v), { message: 'headersEnv must be an env-var NAME, not a header/token value' }).optional() }).strict().optional(),
|
|
68
68
|
tasks: z.array(taskDefinition).optional(),
|
|
69
69
|
})
|
|
70
70
|
.strict();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-based context-loss verification (Phase 16) — an OPT-IN rung ON TOP OF the deterministic
|
|
3
|
+
* `validate()` in the compiler. After the sync `compileContext` runs, this asks a model "does the compiled
|
|
4
|
+
* context drop anything task-critical that was in the originals?" and returns a single check that the caller
|
|
5
|
+
* APPENDS to `validation.checks`. It NEVER blocks: any budget/parse/model problem yields an `ok:true`
|
|
6
|
+
* `inconclusive` check, and the deterministic `validation.ok` is untouched. Both the originals and the
|
|
7
|
+
* compiled text are `wrapUntrusted`-fenced — they are data to inspect, never instructions.
|
|
8
|
+
*/
|
|
9
|
+
import type { AI } from '../plugin/ai.js';
|
|
10
|
+
import type { ContextBlock, CompiledContext } from './compiler.js';
|
|
11
|
+
export interface VerifyLossInput {
|
|
12
|
+
ai: AI;
|
|
13
|
+
/** The ORIGINAL blocks handed to the compiler (pre-compression). */
|
|
14
|
+
blocks: ContextBlock[];
|
|
15
|
+
/** The compiler's output (its `system` string is what will actually be sent). */
|
|
16
|
+
compiled: CompiledContext;
|
|
17
|
+
}
|
|
18
|
+
/** The check appended to `ContextValidation.checks`. `ok:false` means the model flagged task-critical loss. */
|
|
19
|
+
export type LossCheck = {
|
|
20
|
+
name: 'model-loss-verify';
|
|
21
|
+
ok: boolean;
|
|
22
|
+
why?: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function verifyContextLoss(input: VerifyLossInput): Promise<LossCheck>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-based context-loss verification (Phase 16) — an OPT-IN rung ON TOP OF the deterministic
|
|
3
|
+
* `validate()` in the compiler. After the sync `compileContext` runs, this asks a model "does the compiled
|
|
4
|
+
* context drop anything task-critical that was in the originals?" and returns a single check that the caller
|
|
5
|
+
* APPENDS to `validation.checks`. It NEVER blocks: any budget/parse/model problem yields an `ok:true`
|
|
6
|
+
* `inconclusive` check, and the deterministic `validation.ok` is untouched. Both the originals and the
|
|
7
|
+
* compiled text are `wrapUntrusted`-fenced — they are data to inspect, never instructions.
|
|
8
|
+
*/
|
|
9
|
+
import { wrapUntrusted } from '../tools/untrusted.js';
|
|
10
|
+
import { extractJson } from '../util/extractJson.js';
|
|
11
|
+
export async function verifyContextLoss(input) {
|
|
12
|
+
const inconclusive = (why) => ({ name: 'model-loss-verify', ok: true, why: `inconclusive: ${why}` });
|
|
13
|
+
try {
|
|
14
|
+
// Only critical/high blocks carry task-critical information worth verifying.
|
|
15
|
+
const important = input.blocks.filter((b) => b.retention === 'critical' || b.retention === 'high');
|
|
16
|
+
if (important.length === 0)
|
|
17
|
+
return inconclusive('no task-critical blocks to verify');
|
|
18
|
+
const originals = important.map((b) => `[${b.retention}:${b.id}] ${b.resolveContent ? b.resolveContent() : b.text}`).join('\n');
|
|
19
|
+
const prompt = `You verify whether a COMPILED model context dropped anything task-critical from the ORIGINAL blocks.\n\n` +
|
|
20
|
+
`ORIGINAL task-critical blocks:\n${wrapUntrusted('context-originals', originals)}\n\n` +
|
|
21
|
+
`COMPILED context that will be sent:\n${wrapUntrusted('context-compiled', input.compiled.system)}\n\n` +
|
|
22
|
+
`Reply ONLY with JSON: {"lost": true|false, "items": ["<short phrase>", ...]}. Set "lost" true ONLY if ` +
|
|
23
|
+
`something task-critical present in the originals is ABSENT or corrupted in the compiled context.`;
|
|
24
|
+
const result = await input.ai.run({
|
|
25
|
+
task: 'chat',
|
|
26
|
+
input: { text: prompt },
|
|
27
|
+
system: 'You are a context-loss verifier. Respond only with the requested JSON.',
|
|
28
|
+
output: { format: 'json' },
|
|
29
|
+
requirements: { intelligence: ['reasoning'], output: ['json'] },
|
|
30
|
+
});
|
|
31
|
+
if (!result.ok || !result.response)
|
|
32
|
+
return inconclusive('verifier run did not complete');
|
|
33
|
+
const verdict = (result.response.json ?? extractJson(result.response.text ?? ''));
|
|
34
|
+
if (typeof verdict?.lost !== 'boolean')
|
|
35
|
+
return inconclusive('verifier response was not parseable JSON');
|
|
36
|
+
if (!verdict.lost)
|
|
37
|
+
return { name: 'model-loss-verify', ok: true, why: 'model found no task-critical loss' };
|
|
38
|
+
const items = Array.isArray(verdict.items) ? verdict.items.filter((x) => typeof x === 'string').slice(0, 5) : [];
|
|
39
|
+
return { name: 'model-loss-verify', ok: false, why: items.length ? `model flagged omissions: ${items.join('; ')}` : 'model flagged task-critical loss' };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// A verifier problem must never break a run — report it as inconclusive, leave validation.ok alone.
|
|
43
|
+
return inconclusive('verifier error');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in abstractive summarization pre-pass (Phase 16) — runs BEFORE `compileContext`. For non-critical
|
|
3
|
+
* blocks (`high`/`normal`) whose estimate exceeds their fair share of the budget, it asks a model for a
|
|
4
|
+
* shorter text and swaps it in. `critical` and `discardable` blocks are NEVER summarized (critical must stay
|
|
5
|
+
* verbatim; discardable is dropped by the compiler anyway). Originals are never mutated — a new block array
|
|
6
|
+
* is returned. Any model failure, or a "summary" that does not actually shrink the block, keeps the original
|
|
7
|
+
* (the compiler's deterministic truncation then handles it, exactly as before). Content is `wrapUntrusted`-
|
|
8
|
+
* fenced. Absent config ⇒ this never runs and the compiler sees the original blocks.
|
|
9
|
+
*/
|
|
10
|
+
import type { AI } from '../plugin/ai.js';
|
|
11
|
+
import type { ContextBlock } from './compiler.js';
|
|
12
|
+
import { TokenEstimator } from './tokens.js';
|
|
13
|
+
export interface SummarizeInput {
|
|
14
|
+
ai: AI;
|
|
15
|
+
blocks: ContextBlock[];
|
|
16
|
+
budgetTokens: number;
|
|
17
|
+
estimator?: TokenEstimator;
|
|
18
|
+
}
|
|
19
|
+
export declare function summarizeOverBudget(input: SummarizeInput): Promise<ContextBlock[]>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in abstractive summarization pre-pass (Phase 16) — runs BEFORE `compileContext`. For non-critical
|
|
3
|
+
* blocks (`high`/`normal`) whose estimate exceeds their fair share of the budget, it asks a model for a
|
|
4
|
+
* shorter text and swaps it in. `critical` and `discardable` blocks are NEVER summarized (critical must stay
|
|
5
|
+
* verbatim; discardable is dropped by the compiler anyway). Originals are never mutated — a new block array
|
|
6
|
+
* is returned. Any model failure, or a "summary" that does not actually shrink the block, keeps the original
|
|
7
|
+
* (the compiler's deterministic truncation then handles it, exactly as before). Content is `wrapUntrusted`-
|
|
8
|
+
* fenced. Absent config ⇒ this never runs and the compiler sees the original blocks.
|
|
9
|
+
*/
|
|
10
|
+
import { TokenEstimator } from './tokens.js';
|
|
11
|
+
import { wrapUntrusted } from '../tools/untrusted.js';
|
|
12
|
+
export async function summarizeOverBudget(input) {
|
|
13
|
+
const estimator = input.estimator ?? new TokenEstimator();
|
|
14
|
+
const compressible = input.blocks.filter((b) => b.retention === 'high' || b.retention === 'normal');
|
|
15
|
+
if (compressible.length === 0)
|
|
16
|
+
return input.blocks;
|
|
17
|
+
// Fair share: the budget split across the compressible blocks. Only those exceeding it are summarized.
|
|
18
|
+
const share = Math.max(1, Math.floor(Math.max(0, input.budgetTokens) / compressible.length));
|
|
19
|
+
const out = [];
|
|
20
|
+
for (const b of input.blocks) {
|
|
21
|
+
if (b.retention !== 'high' && b.retention !== 'normal') {
|
|
22
|
+
out.push(b); // critical stays verbatim; discardable is the compiler's to drop
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const full = b.resolveContent ? b.resolveContent() : b.text;
|
|
26
|
+
if (estimator.estimate(full) <= share) {
|
|
27
|
+
out.push(b); // already within its share — nothing to gain
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const prompt = `Summarize the following content to about ${share} tokens or fewer, preserving EVERY identifier, ` +
|
|
32
|
+
`file path, number, and task-critical fact. Output ONLY the summary text.\n\n${wrapUntrusted(`block:${b.id}`, full)}`;
|
|
33
|
+
const result = await input.ai.run({
|
|
34
|
+
task: 'chat',
|
|
35
|
+
input: { text: prompt },
|
|
36
|
+
system: 'You compress context while preserving task-critical facts and identifiers. Output only the summary text.',
|
|
37
|
+
requirements: { intelligence: ['reasoning'], output: ['text'] },
|
|
38
|
+
});
|
|
39
|
+
const summary = result.ok ? (result.response?.text ?? '').trim() : '';
|
|
40
|
+
// Accept only a summary that genuinely shrinks the block; otherwise keep the original.
|
|
41
|
+
if (summary && estimator.estimate(summary) < estimator.estimate(full)) {
|
|
42
|
+
out.push({ ...b, text: summary, ...(b.resolveContent ? { resolveContent: () => summary } : {}) });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
out.push(b);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
out.push(b); // model failure → keep the original; the compiler truncates deterministically as before
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
@@ -11,6 +11,7 @@ import type { RequestTemplate } from '../router/normalize.js';
|
|
|
11
11
|
import { AIError } from './errors.js';
|
|
12
12
|
import type { BudgetTracker } from '../policies/budget.js';
|
|
13
13
|
import type { Clock } from '../../util/clock.js';
|
|
14
|
+
import type { KeyedSemaphore } from '../../util/semaphore.js';
|
|
14
15
|
export interface FallbackInput {
|
|
15
16
|
ranked: ScoredCandidate[];
|
|
16
17
|
template: RequestTemplate;
|
|
@@ -28,6 +29,10 @@ export interface FallbackInput {
|
|
|
28
29
|
budget?: BudgetTracker;
|
|
29
30
|
/** Estimated USD cost of a candidate call, consulted before spending (used with `budget`). */
|
|
30
31
|
costOf?: (providerId: string, model: string) => Promise<number>;
|
|
32
|
+
/** Phase 19: optional per-provider in-flight limiter, SHARED across concurrent router calls. Bounds how
|
|
33
|
+
* many calls to a given provider run at once (this fallback walks candidates sequentially, so it only
|
|
34
|
+
* bites under fan-out). Absent / unlimited ⇒ a zero-overhead pass-through. */
|
|
35
|
+
providerLimiter?: KeyedSemaphore;
|
|
31
36
|
}
|
|
32
37
|
export type FallbackResult = {
|
|
33
38
|
ok: true;
|
|
@@ -34,7 +34,9 @@ export async function runWithFallback(input) {
|
|
|
34
34
|
tried += 1;
|
|
35
35
|
const started = clock.now();
|
|
36
36
|
const request = buildRequest(input.template, model.id, input.signal);
|
|
37
|
-
const outcome =
|
|
37
|
+
const outcome = input.providerLimiter
|
|
38
|
+
? await input.providerLimiter.run(providerId, () => executeOnce(provider, request, input.onDelta))
|
|
39
|
+
: await executeOnce(provider, request, input.onDelta);
|
|
38
40
|
const latencyMs = clock.now() - started;
|
|
39
41
|
input.budget?.recordCall(estCost);
|
|
40
42
|
if (outcome.ok) {
|
|
@@ -14,6 +14,7 @@ import type { PerformanceStore } from '../../learning/performanceStore.js';
|
|
|
14
14
|
import type { CapabilityOverlay } from '../capabilities/overlay.js';
|
|
15
15
|
import type { Clock } from '../../util/clock.js';
|
|
16
16
|
import type { RunRequest, RunResult } from '../../types.js';
|
|
17
|
+
import type { KeyedSemaphore } from '../../util/semaphore.js';
|
|
17
18
|
export interface RouterDeps {
|
|
18
19
|
registry: ProviderRegistry;
|
|
19
20
|
tasks: TaskRegistry;
|
|
@@ -23,6 +24,8 @@ export interface RouterDeps {
|
|
|
23
24
|
performance?: PerformanceStore;
|
|
24
25
|
overlay?: CapabilityOverlay;
|
|
25
26
|
clock?: Clock;
|
|
27
|
+
/** Phase 19: shared per-provider in-flight limiter, forwarded to fallback (bounds concurrent calls to a provider). */
|
|
28
|
+
providerLimiter?: KeyedSemaphore;
|
|
26
29
|
}
|
|
27
30
|
export declare class Router {
|
|
28
31
|
private readonly deps;
|
|
@@ -144,6 +144,7 @@ export class Router {
|
|
|
144
144
|
...(template.stream && req.onDelta ? { onDelta: req.onDelta } : {}),
|
|
145
145
|
validate: (response) => validateResponse({ response, ...(template.output ? { output: template.output } : {}), ...(template.tools ? { tools: template.tools } : {}) }),
|
|
146
146
|
...(budget ? { budget, costOf } : {}),
|
|
147
|
+
...(this.deps.providerLimiter ? { providerLimiter: this.deps.providerLimiter } : {}),
|
|
147
148
|
});
|
|
148
149
|
baseReport.attempts = fb.attempts;
|
|
149
150
|
const fallbackCount = Math.max(0, fb.attempts.length - 1);
|
|
@@ -24,11 +24,22 @@ export interface Checkpoint {
|
|
|
24
24
|
skillVersions: Record<string, string>;
|
|
25
25
|
completedSteps: string[];
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
27
|
+
/** A plan's estimated vs available model-call budget (Phase 22), carried on a budget-paused execution. */
|
|
28
|
+
export interface BudgetInfo {
|
|
29
|
+
/** Estimated model calls the plan needs (its skill steps). */
|
|
30
|
+
estCalls: number;
|
|
31
|
+
/** The call budget in force (`policy.maxCalls`). */
|
|
32
|
+
maxCalls: number;
|
|
33
|
+
/** Steps already completed (0 on a notify-before-start pause). */
|
|
34
|
+
completedSteps: number;
|
|
35
|
+
totalSteps: number;
|
|
36
|
+
}
|
|
37
|
+
/** What an execution is waiting for (approval / clarification / budget), persisted so a restart resumes it. */
|
|
28
38
|
export interface PendingInput {
|
|
29
|
-
kind: 'approval' | 'clarification';
|
|
39
|
+
kind: 'approval' | 'clarification' | 'budget';
|
|
30
40
|
question?: string;
|
|
31
41
|
action?: string;
|
|
42
|
+
budget?: BudgetInfo;
|
|
32
43
|
}
|
|
33
44
|
export interface Execution {
|
|
34
45
|
id: string;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { type OpenApiAnalysis } from '../discovery/openapi.js';
|
|
8
8
|
import type { ProviderConfig } from '../types.js';
|
|
9
|
+
import type { AI } from '../plugin/ai.js';
|
|
9
10
|
export interface GenerateOptions {
|
|
10
11
|
id?: string;
|
|
11
12
|
apiKeyEnv?: string;
|
|
@@ -15,3 +16,16 @@ export interface GenerateOptions {
|
|
|
15
16
|
/** Turn an OpenAPI analysis into a ProviderConfig. Throws if the spec yielded no usable mapping. */
|
|
16
17
|
export declare function generateProviderConfig(analysis: OpenApiAnalysis, overrides?: GenerateOptions): ProviderConfig;
|
|
17
18
|
export declare function generateProviderConfigFromFile(specPath: string, overrides?: GenerateOptions): ProviderConfig;
|
|
19
|
+
export interface SuggestResult {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
config?: ProviderConfig;
|
|
22
|
+
error?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* AI-assisted adapter rung (Phase 21a) — an opt-in gap-filler on top of the DETERMINISTIC
|
|
26
|
+
* `generateProviderConfig`. When the spec doesn't enumerate models, a model proposes a likely model-id
|
|
27
|
+
* list; the OUTPUT is STILL produced by `generateProviderConfig` (a validated `ProviderConfig`, never code),
|
|
28
|
+
* so a bad suggestion can only propose model-id strings the operator then confirms — it can never inject an
|
|
29
|
+
* unvalidated config. Any model/parse failure falls back to the deterministic result (or its error).
|
|
30
|
+
*/
|
|
31
|
+
export declare function suggestProviderConfig(analysis: OpenApiAnalysis, ai: AI, overrides?: GenerateOptions): Promise<SuggestResult>;
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { AIError } from '../core/fallback/errors.js';
|
|
8
8
|
import { analyzeOpenApiFile } from '../discovery/openapi.js';
|
|
9
|
+
import { extractJson } from '../util/extractJson.js';
|
|
10
|
+
import { wrapUntrusted } from '../tools/untrusted.js';
|
|
9
11
|
/** Turn an OpenAPI analysis into a ProviderConfig. Throws if the spec yielded no usable mapping. */
|
|
10
12
|
export function generateProviderConfig(analysis, overrides = {}) {
|
|
11
13
|
const base = analysis.suggestedProviderConfig;
|
|
@@ -28,3 +30,39 @@ export function generateProviderConfig(analysis, overrides = {}) {
|
|
|
28
30
|
export function generateProviderConfigFromFile(specPath, overrides = {}) {
|
|
29
31
|
return generateProviderConfig(analyzeOpenApiFile(specPath), overrides);
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* AI-assisted adapter rung (Phase 21a) — an opt-in gap-filler on top of the DETERMINISTIC
|
|
35
|
+
* `generateProviderConfig`. When the spec doesn't enumerate models, a model proposes a likely model-id
|
|
36
|
+
* list; the OUTPUT is STILL produced by `generateProviderConfig` (a validated `ProviderConfig`, never code),
|
|
37
|
+
* so a bad suggestion can only propose model-id strings the operator then confirms — it can never inject an
|
|
38
|
+
* unvalidated config. Any model/parse failure falls back to the deterministic result (or its error).
|
|
39
|
+
*/
|
|
40
|
+
export async function suggestProviderConfig(analysis, ai, overrides = {}) {
|
|
41
|
+
let modelList = Array.isArray(overrides.models) ? overrides.models : [];
|
|
42
|
+
// Only consult the model when we actually have a gap to fill (no explicit models, none in the spec).
|
|
43
|
+
const specModels = analysis.suggestedProviderConfig?.models;
|
|
44
|
+
const needsModels = modelList.length === 0 && (specModels === undefined || specModels === 'auto');
|
|
45
|
+
if (needsModels) {
|
|
46
|
+
try {
|
|
47
|
+
const prompt = `An OpenAPI spec describes an AI provider. Suggest the model IDs it most likely exposes for chat/completions. ` +
|
|
48
|
+
`Reply ONLY with JSON: {"models": ["<model-id>", ...]}.\n\n` +
|
|
49
|
+
wrapUntrusted('openapi-summary', `title: ${analysis.title ?? '(unknown)'}\nbaseUrl: ${analysis.baseUrl ?? '(unknown)'}\nwireShape: ${analysis.wireShape}\nendpoints: ${analysis.endpoints.map((e) => e.path).slice(0, 20).join(', ')}`);
|
|
50
|
+
const run = await ai.run({ task: 'suggest-provider-config', input: prompt, output: { format: 'json' }, requirements: { intelligence: ['reasoning'], output: ['json'] } });
|
|
51
|
+
if (run.ok && run.response) {
|
|
52
|
+
const parsed = (run.response.json ?? extractJson(run.response.text ?? ''));
|
|
53
|
+
if (Array.isArray(parsed?.models))
|
|
54
|
+
modelList = parsed.models.filter((m) => typeof m === 'string' && m.trim().length > 0);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
/* a suggestion failure just falls through to the deterministic path */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const config = generateProviderConfig(analysis, { ...overrides, ...(modelList.length && !overrides.models ? { models: modelList } : {}) });
|
|
63
|
+
return { ok: true, config };
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill scaffolding (Phase 21a) — the generative seam for skills, mirroring `generateAdapter`'s
|
|
3
|
+
* "emit validated CONFIG/manifest, never code" rule. A model drafts a `*.skill.yaml` MANIFEST (pure data,
|
|
4
|
+
* parsed by the existing `parseManifest`); we then validate it three ways before offering it:
|
|
5
|
+
* 1. `parseManifest` (shape),
|
|
6
|
+
* 2. every step names an AVAILABLE tool (a `validatePlan`-style membership check — no invented tools),
|
|
7
|
+
* 3. a dry `manifestToSkill` compile (it must not throw).
|
|
8
|
+
* Nothing is written here — the caller writes it only on explicit confirm into the consented
|
|
9
|
+
* `.ai-runtime/skills/` directory. A drafted manifest that fails any check is refused with a reason.
|
|
10
|
+
*/
|
|
11
|
+
import type { AI } from '../plugin/ai.js';
|
|
12
|
+
import { type SkillManifest } from '../skills/manifest.js';
|
|
13
|
+
export interface GenerateSkillInput {
|
|
14
|
+
goal: string;
|
|
15
|
+
/** Tool ids the drafted manifest may use (the host's registered tools). */
|
|
16
|
+
tools: string[];
|
|
17
|
+
ai: AI;
|
|
18
|
+
}
|
|
19
|
+
export interface GenerateSkillResult {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
manifest?: SkillManifest;
|
|
22
|
+
/** The manifest serialized as `*.skill.yaml`, ready to write on confirm. */
|
|
23
|
+
yaml?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function generateSkillManifest(input: GenerateSkillInput): Promise<GenerateSkillResult>;
|