ai-runtime-engine 1.2.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 +602 -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 +56 -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 +2 -0
- package/dist/cli/commands/run.js +36 -4
- 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 +105 -16
- package/dist/cli/interactive/session.d.ts +12 -1
- package/dist/cli/interactive/session.js +83 -5
- package/dist/cli/render.d.ts +13 -0
- package/dist/cli/render.js +18 -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 +8 -0
- package/dist/core/fallback/fallback.js +3 -1
- package/dist/core/router/executor.d.ts +6 -1
- package/dist/core/router/executor.js +9 -2
- package/dist/core/router/normalize.d.ts +2 -0
- package/dist/core/router/request.js +2 -0
- package/dist/core/router/router.d.ts +3 -0
- package/dist/core/router/router.js +7 -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 +44 -5
- 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/providers/httpClient.d.ts +25 -1
- package/dist/providers/httpClient.js +93 -0
- package/dist/providers/httpProvider.d.ts +1 -0
- package/dist/providers/httpProvider.js +67 -1
- package/dist/providers/mock/mockProvider.d.ts +3 -0
- package/dist/providers/mock/mockProvider.js +54 -0
- package/dist/providers/mock/scenarios.d.ts +7 -0
- package/dist/providers/provider.d.ts +6 -0
- package/dist/providers/wire/anthropicWire.js +34 -0
- package/dist/providers/wire/openaiWire.js +30 -0
- package/dist/providers/wire/types.d.ts +16 -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 +723 -50
- package/dist/runtime/types.d.ts +94 -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 +11 -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")',
|
|
@@ -42,6 +56,8 @@ const HELP = [
|
|
|
42
56
|
' /cancel <id> cancel an execution',
|
|
43
57
|
' /config show the resolved configuration',
|
|
44
58
|
' /dry-run toggle dry-run (plan only, no changes)',
|
|
59
|
+
' /stream toggle token-by-token streaming of answers',
|
|
60
|
+
' /budget show the call/cost budget (AI_MAX_CALLS / AI_MAX_COST_USD)',
|
|
45
61
|
' /clear clear the screen',
|
|
46
62
|
' /exit leave the session',
|
|
47
63
|
'',
|
|
@@ -53,8 +69,10 @@ export class ReplSession {
|
|
|
53
69
|
viewCache;
|
|
54
70
|
conversationId;
|
|
55
71
|
dryRunMode = false;
|
|
56
|
-
|
|
72
|
+
streaming;
|
|
73
|
+
constructor(runtime, opts = {}) {
|
|
57
74
|
this.runtime = runtime;
|
|
75
|
+
this.streaming = opts.streaming ?? false;
|
|
58
76
|
}
|
|
59
77
|
currentMode() {
|
|
60
78
|
return this.mode;
|
|
@@ -64,6 +82,27 @@ export class ReplSession {
|
|
|
64
82
|
this.viewCache = new ProviderViewCache(this.runtime.ai);
|
|
65
83
|
return this.viewCache;
|
|
66
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
|
+
}
|
|
67
106
|
async handle(raw) {
|
|
68
107
|
const line = raw.trim();
|
|
69
108
|
if (!line)
|
|
@@ -106,6 +145,24 @@ export class ReplSession {
|
|
|
106
145
|
return this.config();
|
|
107
146
|
case 'providers':
|
|
108
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]);
|
|
109
166
|
case 'tools':
|
|
110
167
|
return { lines: ['Tools:', ...this.runtime.tools().map((t) => ` ${t.id.padEnd(14)} ${t.description}`)] };
|
|
111
168
|
case 'skills': {
|
|
@@ -133,6 +190,17 @@ export class ReplSession {
|
|
|
133
190
|
case 'dryrun':
|
|
134
191
|
this.dryRunMode = !this.dryRunMode;
|
|
135
192
|
return { lines: [`dry-run ${this.dryRunMode ? 'ON — plans will be shown, nothing executed' : 'OFF'}`] };
|
|
193
|
+
case 'stream':
|
|
194
|
+
this.streaming = !this.streaming;
|
|
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
|
+
};
|
|
136
204
|
case 'memory':
|
|
137
205
|
return this.memory(args);
|
|
138
206
|
case 'conversations':
|
|
@@ -161,9 +229,10 @@ export class ReplSession {
|
|
|
161
229
|
this.conversationId = this.runtime.conversations.start();
|
|
162
230
|
this.runtime.conversations.append(this.conversationId, 'user', input);
|
|
163
231
|
}
|
|
164
|
-
const result = await this.runtime.run({ input, mode: forceMode ?? this.mode, ...(this.dryRunMode ? { dryRun: true } : {}) });
|
|
232
|
+
const result = await this.runtime.run({ input, mode: forceMode ?? this.mode, ...(this.dryRunMode ? { dryRun: true } : {}), ...(this.streaming ? { stream: true } : {}) });
|
|
165
233
|
const lines = [];
|
|
166
|
-
|
|
234
|
+
// When the answer already streamed live (response.streamed), don't reprint it.
|
|
235
|
+
if (result.response?.text && !result.response.streamed)
|
|
167
236
|
lines.push(result.response.text);
|
|
168
237
|
else if (result.response?.json !== undefined)
|
|
169
238
|
lines.push(JSON.stringify(result.response.json, null, 2));
|
|
@@ -175,17 +244,25 @@ export class ReplSession {
|
|
|
175
244
|
const execId = result.execution?.id;
|
|
176
245
|
lines.push(execId ? `(approval required — /approve ${execId} to proceed, or /deny ${execId})` : '(approval required — approve the execution via /executions then /approve <id>)');
|
|
177
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
|
+
}
|
|
178
251
|
if (result.status === 'failed')
|
|
179
252
|
lines.push('(did not complete)');
|
|
180
253
|
if (result.clarification)
|
|
181
254
|
lines.push(`? ${result.clarification.question}`);
|
|
182
255
|
if (result.memory?.captured)
|
|
183
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));
|
|
184
261
|
if (this.conversationId && result.response?.text)
|
|
185
262
|
this.runtime.conversations.append(this.conversationId, 'assistant', result.response.text, result.runId);
|
|
186
263
|
return { lines };
|
|
187
264
|
}
|
|
188
|
-
memory(args) {
|
|
265
|
+
async memory(args) {
|
|
189
266
|
if (!this.runtime.memory.enabled)
|
|
190
267
|
return { lines: ['memory is disabled (stateless mode).'] };
|
|
191
268
|
const sub = args[0];
|
|
@@ -193,7 +270,8 @@ export class ReplSession {
|
|
|
193
270
|
const q = args.slice(1).join(' ');
|
|
194
271
|
if (!q)
|
|
195
272
|
return { lines: ['usage: /memory search <query>'] };
|
|
196
|
-
|
|
273
|
+
// Semantic ranking when an embedder is configured; degrades to BM25 internally.
|
|
274
|
+
const hits = await this.runtime.memory.searchSemantic(q, { limit: 10 });
|
|
197
275
|
return { lines: hits.length ? hits.map((r) => ` ${r.id} [${r.scope}] ${r.text}`) : ['no matches.'] };
|
|
198
276
|
}
|
|
199
277
|
if (sub === 'delete') {
|
package/dist/cli/render.d.ts
CHANGED
|
@@ -4,4 +4,17 @@
|
|
|
4
4
|
* directly from a command.
|
|
5
5
|
*/
|
|
6
6
|
export declare function print(line: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Write a streamed chunk WITHOUT a trailing newline, redacted like every other egress. (Redaction is
|
|
9
|
+
* per-chunk, matching the lifecycle emitter; a secret split across chunk boundaries is the known limit of
|
|
10
|
+
* any token stream — but model output never contains the env-var secrets the redactor tracks.)
|
|
11
|
+
*/
|
|
12
|
+
export declare function printChunk(chunk: string): void;
|
|
7
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,11 +4,29 @@
|
|
|
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));
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Write a streamed chunk WITHOUT a trailing newline, redacted like every other egress. (Redaction is
|
|
14
|
+
* per-chunk, matching the lifecycle emitter; a secret split across chunk boundaries is the known limit of
|
|
15
|
+
* any token stream — but model output never contains the env-var secrets the redactor tracks.)
|
|
16
|
+
*/
|
|
17
|
+
export function printChunk(chunk) {
|
|
18
|
+
process.stdout.write(redactString(chunk));
|
|
19
|
+
}
|
|
11
20
|
export function printError(line) {
|
|
12
21
|
// eslint-disable-next-line no-console
|
|
13
22
|
console.error(redactString(line));
|
|
14
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;
|
|
@@ -19,12 +20,19 @@ export interface FallbackInput {
|
|
|
19
20
|
signal?: AbortSignal;
|
|
20
21
|
clock?: Clock;
|
|
21
22
|
onAttempt?: (record: AttemptRecord) => void;
|
|
23
|
+
/** Phase-13 streaming: forwarded to each attempt's `executeOnce`; deltas ride this callback, the final
|
|
24
|
+
* aggregate rides the return value. Only meaningful when `template.stream` is set. */
|
|
25
|
+
onDelta?: (chunk: string) => void;
|
|
22
26
|
/** Optional Phase-6 validation. A failing report drops this candidate and continues (no poisoning). */
|
|
23
27
|
validate?: (response: AIResponse, model: string) => ValidationReport;
|
|
24
28
|
/** Optional spend guardrail. When it cannot afford the next call, the run STOPS with BUDGET. */
|
|
25
29
|
budget?: BudgetTracker;
|
|
26
30
|
/** Estimated USD cost of a candidate call, consulted before spending (used with `budget`). */
|
|
27
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;
|
|
28
36
|
}
|
|
29
37
|
export type FallbackResult = {
|
|
30
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) {
|
|
@@ -13,4 +13,9 @@ export type ExecOutcome = {
|
|
|
13
13
|
ok: false;
|
|
14
14
|
error: AIError;
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Run one attempt. When `onDelta` is supplied AND the request asked to stream AND the provider supports
|
|
18
|
+
* `executeStream`, the answer streams token-by-token; otherwise the normal single-shot `execute()` runs.
|
|
19
|
+
* Either way the resolved `AIResponse` is the full aggregate.
|
|
20
|
+
*/
|
|
21
|
+
export declare function executeOnce(provider: AIProvider, request: AIRequest, onDelta?: (chunk: string) => void): Promise<ExecOutcome>;
|
|
@@ -4,9 +4,16 @@
|
|
|
4
4
|
* raw vendor error.
|
|
5
5
|
*/
|
|
6
6
|
import { AIError, toAIError } from '../fallback/errors.js';
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Run one attempt. When `onDelta` is supplied AND the request asked to stream AND the provider supports
|
|
9
|
+
* `executeStream`, the answer streams token-by-token; otherwise the normal single-shot `execute()` runs.
|
|
10
|
+
* Either way the resolved `AIResponse` is the full aggregate.
|
|
11
|
+
*/
|
|
12
|
+
export async function executeOnce(provider, request, onDelta) {
|
|
8
13
|
try {
|
|
9
|
-
const response =
|
|
14
|
+
const response = onDelta && request.stream && provider.executeStream
|
|
15
|
+
? await provider.executeStream(request, onDelta)
|
|
16
|
+
: await provider.execute(request);
|
|
10
17
|
return { ok: true, response };
|
|
11
18
|
}
|
|
12
19
|
catch (e) {
|
|
@@ -22,6 +22,8 @@ export interface RequestTemplate {
|
|
|
22
22
|
params?: AIRequest['params'];
|
|
23
23
|
timeoutMs: number;
|
|
24
24
|
sensitivity: Sensitivity;
|
|
25
|
+
/** Stream the answer token-by-token (Phase 13). Set only for text output; never for JSON. */
|
|
26
|
+
stream?: boolean;
|
|
25
27
|
}
|
|
26
28
|
export interface NormalizeResult {
|
|
27
29
|
task: NormalizedTask;
|
|
@@ -15,6 +15,8 @@ export function buildRequest(template, model, signal) {
|
|
|
15
15
|
req.tools = template.tools;
|
|
16
16
|
if (template.params !== undefined)
|
|
17
17
|
req.params = template.params;
|
|
18
|
+
if (template.stream)
|
|
19
|
+
req.stream = true;
|
|
18
20
|
if (signal !== undefined)
|
|
19
21
|
req.signal = signal;
|
|
20
22
|
return req;
|
|
@@ -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;
|
|
@@ -31,6 +31,11 @@ export class Router {
|
|
|
31
31
|
// Phase 1 — normalize
|
|
32
32
|
const { task, template } = normalize(req, tasks, config);
|
|
33
33
|
const pin = { provider: req.provider, model: req.model };
|
|
34
|
+
// Phase 13 — streaming is opt-in and TEXT-ONLY: a JSON/structured request never streams (partial
|
|
35
|
+
// JSON is useless). The delta callback rides `req.onDelta`; the final aggregate rides the return value.
|
|
36
|
+
const wantsJsonOut = template.output?.format === 'json' || template.output?.format === 'structured_output';
|
|
37
|
+
if (req.stream && req.onDelta && !wantsJsonOut)
|
|
38
|
+
template.stream = true;
|
|
34
39
|
// Team policy (org-level guardrails) merged over per-run constraints.
|
|
35
40
|
const policy = config.policy;
|
|
36
41
|
if (policy.requireLocal)
|
|
@@ -136,8 +141,10 @@ export class Router {
|
|
|
136
141
|
maxFallbacks: config.maxFallbacks,
|
|
137
142
|
clock: this.clock,
|
|
138
143
|
onAttempt,
|
|
144
|
+
...(template.stream && req.onDelta ? { onDelta: req.onDelta } : {}),
|
|
139
145
|
validate: (response) => validateResponse({ response, ...(template.output ? { output: template.output } : {}), ...(template.tools ? { tools: template.tools } : {}) }),
|
|
140
146
|
...(budget ? { budget, costOf } : {}),
|
|
147
|
+
...(this.deps.providerLimiter ? { providerLimiter: this.deps.providerLimiter } : {}),
|
|
141
148
|
});
|
|
142
149
|
baseReport.attempts = fb.attempts;
|
|
143
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>;
|