@arnilo/prism 0.0.16 → 0.0.17
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 +20 -0
- package/dist/agent-run-state.js +13 -1
- package/dist/agents.js +42 -10
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +12 -0
- package/dist/cli-runner.d.ts +1 -5
- package/dist/cli-runner.js +5 -28
- package/dist/context-budget.js +6 -3
- package/dist/contracts.d.ts +13 -0
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/credentials.d.ts +7 -1
- package/dist/credentials.js +6 -2
- package/dist/event-multiplexer.js +17 -1
- package/dist/extensions.d.ts +7 -1
- package/dist/extensions.js +64 -6
- package/dist/feedback.js +1 -1
- package/dist/guardrails.js +9 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/input.js +11 -4
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/providers/openai-compatible.d.ts +42 -1
- package/dist/providers/openai-compatible.js +109 -47
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.js +21 -7
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +3 -9
- package/dist/session-stores.js +15 -11
- package/docs/agent-events.md +2 -1
- package/docs/agent-session-runtime.md +2 -2
- package/docs/cli-rpc.md +1 -5
- package/docs/coding-agent-tools.md +2 -0
- package/docs/compaction-and-retry.md +3 -1
- package/docs/contribution-registries.md +1 -0
- package/docs/credentials-and-redaction.md +1 -1
- package/docs/extensions.md +1 -1
- package/docs/guardrails.md +13 -2
- package/docs/index.md +2 -2
- package/docs/input-and-prompt-assembly.md +3 -3
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +11 -0
- package/docs/providers/openai-compatible.md +28 -1
- package/docs/public-contracts.md +1 -1
- package/docs/release-and-install.md +36 -15
- package/docs/session-stores.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.17] - 2026-07-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Extension lifecycle: `ExtensionKernel.load()` returns `LoadedExtension[]` dispose handles; contribution/provider/model registries gain `unregister(...)`; a failed `setup` unwinds its partial registrations.
|
|
7
|
+
- `MemoryCredentialStoreOptions.allowProviderFallback` for strict provider-scoped credential resolution; `createMemoryCheckpointStore` `maxRecords`/`maxValueBytes` bounds; `ShellToolOptions.envAllowlist` (coding-agent); `ErrorInfo.retryAfterMs` plus `retryAfterMs`-aware `createDefaultRetryPolicy` with `jitter`/`random` options; guardrail `steer_rejected` event; `httpStatusError` provider transport helper wired into anthropic, google, kimi, openai, opencode-go, and the shared OpenAI-compatible transport.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Durable runs: run-state load now bounds against the 1 MiB hard cap (states saved with a raised `maxStateBytes` resume correctly); agent fingerprint also covers instructions, system-prompt contributions, and skills; resume-after-interrupt is explicit implicit-approval.
|
|
11
|
+
- Retry/backpressure: HTTP provider errors carry numeric codes and `Retry-After` hints; default retry policy applies ±25% jitter.
|
|
12
|
+
- `input_assembly` middleware runs unconditionally (both plain and context-budget paths, any `InputBuilder`); memory session store rejects cross-session `expectedParentId`; context-budget eviction is O(n) instead of O(n²).
|
|
13
|
+
- Guardrails: `interrupt` errors name the stage; `guardrail_failed` records carry the underlying error message in `metadata.error`; steer `block`/`tripwire` drops the message and emits `steer_rejected` instead of failing the run.
|
|
14
|
+
- Default prompt builder omits the `Available tools:` text for tool-capable models (`capabilities.tools === true`).
|
|
15
|
+
- Middleware registry throws on double `next()` and diagnoses conflicting `next(v)` + return; event multiplexer keeps sorted delivery while a consumer is parked; batched run-ledger dead counters removed.
|
|
16
|
+
|
|
17
|
+
### Breaking (minor, pre-1.0)
|
|
18
|
+
- CLI: `--config`, `--resource`, `--extension`, `--tool` are rejected (`<flag> is not supported in this build`); the dead `CliOptions.config/resources/extensions/tools` fields are removed.
|
|
19
|
+
- `ExtensionKernel.load()` resolves to `LoadedExtension[]` instead of `void`.
|
|
20
|
+
|
|
21
|
+
See [docs/migration.md](docs/migration.md) for the full 0.0.16 → 0.0.17 notes.
|
|
22
|
+
|
|
3
23
|
## [0.0.16] - 2026-07-26
|
|
4
24
|
|
|
5
25
|
### Added
|
package/dist/agent-run-state.js
CHANGED
|
@@ -9,16 +9,25 @@ const MAX_PROPERTIES = 256;
|
|
|
9
9
|
export function agentFingerprint(agent, revision) {
|
|
10
10
|
const config = agent.config;
|
|
11
11
|
const tools = !config.tools ? [] : "list" in config.tools ? config.tools.list() : config.tools;
|
|
12
|
+
const skills = !config.skills ? [] : "list" in config.skills ? config.skills.list() : config.skills;
|
|
12
13
|
const guardrails = [
|
|
13
14
|
...(config.guardrails?.input ?? []),
|
|
14
15
|
...(config.guardrails?.output ?? []),
|
|
15
16
|
...(config.guardrails?.toolInput ?? []),
|
|
16
17
|
...(config.guardrails?.toolOutput ?? []),
|
|
17
18
|
];
|
|
19
|
+
const systemPrompt = config.systemPrompt === false || config.systemPrompt === undefined
|
|
20
|
+
? (config.systemPrompt ?? null)
|
|
21
|
+
: (Array.isArray(config.systemPrompt) ? config.systemPrompt : [config.systemPrompt]).map((c) => ({ id: c.id, text: c.text }));
|
|
18
22
|
const value = JSON.stringify({
|
|
19
23
|
id: config.id ?? config.name ?? "agent",
|
|
20
24
|
revision,
|
|
21
25
|
model: config.model,
|
|
26
|
+
// Instructions/prompt text shapes agent behavior as much as the tool set; a change
|
|
27
|
+
// without a definitionRevision bump must not resume stale durable runs silently.
|
|
28
|
+
instructions: config.instructions ?? null,
|
|
29
|
+
systemPrompt,
|
|
30
|
+
skills: skills.map((skill) => ({ name: skill.name, instructions: skill.instructions, toolNames: skill.toolNames })),
|
|
22
31
|
tools: tools.map((tool) => ({ name: tool.name, parameters: tool.parameters, exclusive: tool.exclusive })),
|
|
23
32
|
guardrails: guardrails.map((guardrail) => ({ name: guardrail.name, stage: guardrail.stage, revision: guardrail.revision })),
|
|
24
33
|
loop: typeof config.loop === "object" && config.loop && "strategy" in config.loop
|
|
@@ -111,7 +120,10 @@ export function parseAgentRunState(value, version) {
|
|
|
111
120
|
!state.deadlineAt) {
|
|
112
121
|
throw new AgentRunStateError("Malformed agent run state");
|
|
113
122
|
}
|
|
114
|
-
|
|
123
|
+
// Load bounds against the hard cap, not the default: the configured maxStateBytes is a
|
|
124
|
+
// save-side policy knob, while the load-side bound is only a DoS ceiling. States saved
|
|
125
|
+
// with a raised maxStateBytes must remain resumable.
|
|
126
|
+
return boundState({ ...state, version }, HARD_MAX_AGENT_RUN_STATE_BYTES);
|
|
115
127
|
}
|
|
116
128
|
function boundState(state, maxBytes) {
|
|
117
129
|
checkShape(state, 0);
|
package/dist/agents.js
CHANGED
|
@@ -346,15 +346,17 @@ class RuntimeAgentSession {
|
|
|
346
346
|
redactor: this.activeRedactor,
|
|
347
347
|
emit: (event) => this.emit(event),
|
|
348
348
|
});
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
// Input-guardrail decision table:
|
|
350
|
+
// - interrupt + durable + fresh run → suspend for approval.
|
|
351
|
+
// - interrupt + durable + resumed run → proceed: resuming IS the operator approval.
|
|
352
|
+
// - interrupt without durable, or block/tripwire → fail via assertGuardrailsAllowed.
|
|
353
|
+
const approvedByResume = resumed !== undefined && inputGuardrails.terminal?.action === "interrupt" && this.activeDurable !== undefined;
|
|
354
|
+
if (inputGuardrails.terminal?.action === "interrupt" && this.activeDurable && !approvedByResume) {
|
|
355
|
+
const interruption = { kind: "input_guardrail", reason: inputGuardrails.terminal.reason ?? "Input requires approval" };
|
|
356
|
+
throw new AgentRunSuspended(await this.suspendDurable({ runId, model, limits, interruption, messages: inputMessages }), interruption);
|
|
354
357
|
}
|
|
355
|
-
|
|
358
|
+
if (inputGuardrails.terminal && !approvedByResume)
|
|
356
359
|
assertGuardrailsAllowed(inputGuardrails);
|
|
357
|
-
}
|
|
358
360
|
for (const message of inputMessages)
|
|
359
361
|
await this.appendMessage(message, runId);
|
|
360
362
|
await this.autoCompact(runId, options, controller.signal, inputMessages);
|
|
@@ -1049,7 +1051,22 @@ class RuntimeAgentSession {
|
|
|
1049
1051
|
redactor: this.activeRedactor,
|
|
1050
1052
|
emit: (event) => this.emit(event),
|
|
1051
1053
|
});
|
|
1052
|
-
|
|
1054
|
+
// Mid-run steer: a terminal decision drops the message (never enters history or
|
|
1055
|
+
// the session store) and the run continues. Run-start input blocking still fails
|
|
1056
|
+
// the run — only the blast radius of steered input is narrowed.
|
|
1057
|
+
const terminal = inputGuardrails.terminal;
|
|
1058
|
+
if (terminal) {
|
|
1059
|
+
if (terminal.action === "interrupt")
|
|
1060
|
+
throw new GuardrailError(terminal);
|
|
1061
|
+
this.emit({
|
|
1062
|
+
type: "steer_rejected",
|
|
1063
|
+
sessionId: this.id,
|
|
1064
|
+
runId,
|
|
1065
|
+
message: this.activeRedactor ? this.activeRedactor.redact(message) : message,
|
|
1066
|
+
record: terminal,
|
|
1067
|
+
});
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1053
1070
|
this.history.push(message);
|
|
1054
1071
|
await this.appendMessage(message, runId);
|
|
1055
1072
|
}
|
|
@@ -1324,11 +1341,25 @@ function withoutTrailingInput(messages, input) {
|
|
|
1324
1341
|
const next = [...messages];
|
|
1325
1342
|
for (let i = input.length - 1; i >= 0; i -= 1) {
|
|
1326
1343
|
const last = next.at(-1);
|
|
1327
|
-
if (last &&
|
|
1344
|
+
if (last && stableMessageKey(last) === stableMessageKey(input[i]))
|
|
1328
1345
|
next.pop();
|
|
1329
1346
|
}
|
|
1330
1347
|
return next;
|
|
1331
1348
|
}
|
|
1349
|
+
// Key-order-insensitive comparison: a redacted-then-reassembled message with reordered
|
|
1350
|
+
// keys must still dedupe against the trailing input, or auto-compaction duplicates it.
|
|
1351
|
+
function stableMessageKey(value) {
|
|
1352
|
+
if (Array.isArray(value))
|
|
1353
|
+
return `[${value.map(stableMessageKey).join(",")}]`;
|
|
1354
|
+
if (value !== null && typeof value === "object") {
|
|
1355
|
+
const record = value;
|
|
1356
|
+
return `{${Object.keys(record)
|
|
1357
|
+
.sort()
|
|
1358
|
+
.map((key) => `${JSON.stringify(key)}:${stableMessageKey(record[key])}`)
|
|
1359
|
+
.join(",")}}`;
|
|
1360
|
+
}
|
|
1361
|
+
return JSON.stringify(value) ?? "null";
|
|
1362
|
+
}
|
|
1332
1363
|
function bridgeAbort(signal, controller) {
|
|
1333
1364
|
if (!signal)
|
|
1334
1365
|
return () => undefined;
|
|
@@ -1346,9 +1377,10 @@ function throwIfAbortedSignal(signal) {
|
|
|
1346
1377
|
if (signal?.aborted)
|
|
1347
1378
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
1348
1379
|
}
|
|
1380
|
+
const jsonTextEncoder = new TextEncoder();
|
|
1349
1381
|
function jsonBytes(value) {
|
|
1350
1382
|
try {
|
|
1351
|
-
return
|
|
1383
|
+
return jsonTextEncoder.encode(JSON.stringify(value)).byteLength;
|
|
1352
1384
|
}
|
|
1353
1385
|
catch {
|
|
1354
1386
|
throw new TypeError("Provider request or event must be JSON-serializable for run limits");
|
package/dist/checkpoints.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export declare class CheckpointConflictError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
export interface MemoryCheckpointStoreOptions {
|
|
8
8
|
readonly maxPageSize?: number;
|
|
9
|
+
/** Capacity bound: least-recently-saved records are evicted past this count (default 10_000). */
|
|
10
|
+
readonly maxRecords?: number;
|
|
11
|
+
/** Per-record bound on the JSON-encoded value in bytes (default 1 MiB); oversized values are rejected. */
|
|
12
|
+
readonly maxValueBytes?: number;
|
|
9
13
|
}
|
|
10
14
|
/** In-process reference implementation of the generic checkpoint contract. */
|
|
11
15
|
export declare function createMemoryCheckpointStore(options?: MemoryCheckpointStoreOptions): CheckpointStore;
|
package/dist/checkpoints.js
CHANGED
|
@@ -6,10 +6,14 @@ export class CheckpointConflictError extends Error {
|
|
|
6
6
|
this.name = "CheckpointConflictError";
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
const DEFAULT_MAX_CHECKPOINT_RECORDS = 10_000;
|
|
10
|
+
const DEFAULT_MAX_CHECKPOINT_VALUE_BYTES = 1024 * 1024;
|
|
9
11
|
/** In-process reference implementation of the generic checkpoint contract. */
|
|
10
12
|
export function createMemoryCheckpointStore(options = {}) {
|
|
11
13
|
const records = new Map();
|
|
12
14
|
const maxPageSize = Math.max(1, options.maxPageSize ?? 500);
|
|
15
|
+
const maxRecords = Math.max(1, options.maxRecords ?? DEFAULT_MAX_CHECKPOINT_RECORDS);
|
|
16
|
+
const maxValueBytes = Math.max(1, options.maxValueBytes ?? DEFAULT_MAX_CHECKPOINT_VALUE_BYTES);
|
|
13
17
|
return {
|
|
14
18
|
async saveCheckpoint(input) {
|
|
15
19
|
throwIfAborted(input.signal);
|
|
@@ -32,6 +36,9 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
32
36
|
}
|
|
33
37
|
const now = new Date().toISOString();
|
|
34
38
|
const value = cloneJson(input.value, "Checkpoint value");
|
|
39
|
+
if (new TextEncoder().encode(JSON.stringify(value)).length > maxValueBytes) {
|
|
40
|
+
throw new RangeError(`Checkpoint value exceeds maxValueBytes (${maxValueBytes})`);
|
|
41
|
+
}
|
|
35
42
|
const metadata = input.metadata === undefined ? undefined : cloneJson(input.metadata, "Checkpoint metadata");
|
|
36
43
|
const record = {
|
|
37
44
|
namespace: input.namespace,
|
|
@@ -45,7 +52,12 @@ export function createMemoryCheckpointStore(options = {}) {
|
|
|
45
52
|
updatedAt: now,
|
|
46
53
|
...(metadata === undefined ? {} : { metadata }),
|
|
47
54
|
};
|
|
55
|
+
// delete+set moves an updated record to the end, keeping Map order = least- to
|
|
56
|
+
// most-recently-saved, so capacity eviction is O(1) off the front.
|
|
57
|
+
records.delete(id);
|
|
48
58
|
records.set(id, record);
|
|
59
|
+
while (records.size > maxRecords)
|
|
60
|
+
records.delete(records.keys().next().value);
|
|
49
61
|
return record;
|
|
50
62
|
},
|
|
51
63
|
async loadCheckpoint(input) {
|
package/dist/cli-runner.d.ts
CHANGED
|
@@ -9,10 +9,6 @@ export interface CliOptions {
|
|
|
9
9
|
readonly provider?: string;
|
|
10
10
|
readonly model?: string;
|
|
11
11
|
readonly session?: string;
|
|
12
|
-
readonly config: readonly string[];
|
|
13
|
-
readonly resources: readonly string[];
|
|
14
|
-
readonly extensions: readonly string[];
|
|
15
|
-
readonly tools: readonly string[];
|
|
16
12
|
readonly system?: string;
|
|
17
13
|
readonly context: readonly string[];
|
|
18
14
|
readonly compact?: number;
|
|
@@ -67,7 +63,7 @@ export interface CliRuntime {
|
|
|
67
63
|
/** Working directory for relative `prism init` destinations (tests). */
|
|
68
64
|
readonly cwd?: string;
|
|
69
65
|
}
|
|
70
|
-
export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --
|
|
66
|
+
export declare const usage = "Usage: prism [--mode print|json|rpc] [-p prompt] [options]\n prism init <dir> [--provider <name>] [--with-workflows] [--with-evals] [--force]\n\nOptions:\n -p, --prompt <text> Prompt to run in print/json mode\n --provider <name> Explicit provider id (mock is built in for smoke tests)\n --model <name> Explicit model name\n --session <id> Session id\n --system <text> System instructions\n --context <text> Context text\n --compact <entries> Auto-compaction threshold\n --max-tool-rounds <n> Maximum tool rounds\n --discover Enable workspace contribution discovery (opt-in)\n --discover-kinds <csv> Kinds to discover (default: skill; skill,tool,context,instructions)\n --no-discovery Disable discovery even if --discover is set\n --agents-config <path> App config root holding agents/<name>/AGENT.md bundles (opt-in)\n --no-agents-md Skip auto-loading <workspaceRoot>/AGENTS.md\n --no-system-md Skip auto-loading the global SYSTEM.md layer\n --agents-md-file <path> Read AGENTS.md from <path> instead (trust-gated, source: app)\n --system-md-file <path> Read SYSTEM.md from <path> instead (source: user)\n -h, --help Show this help\n";
|
|
71
67
|
export declare function parseCliArgs(argv: readonly string[]): CliOptions;
|
|
72
68
|
export declare function runCli(argv: readonly string[], runtime: CliRuntime): Promise<number>;
|
|
73
69
|
export declare function runPromptMode(session: AgentSession, options: CliOptions, stdout: Writable, mode: "print" | "json"): Promise<void>;
|
package/dist/cli-runner.js
CHANGED
|
@@ -19,10 +19,6 @@ Options:
|
|
|
19
19
|
--provider <name> Explicit provider id (mock is built in for smoke tests)
|
|
20
20
|
--model <name> Explicit model name
|
|
21
21
|
--session <id> Session id
|
|
22
|
-
--config <path> Explicit config path (recorded, not auto-loaded)
|
|
23
|
-
--resource <uri> Explicit resource URI (recorded, not auto-loaded)
|
|
24
|
-
--extension <name> Explicit extension name (recorded, not auto-loaded)
|
|
25
|
-
--tool <name> Explicit tool name (recorded, not auto-enabled)
|
|
26
22
|
--system <text> System instructions
|
|
27
23
|
--context <text> Context text
|
|
28
24
|
--compact <entries> Auto-compaction threshold
|
|
@@ -43,10 +39,6 @@ const valueFlags = new Set([
|
|
|
43
39
|
"--provider",
|
|
44
40
|
"--model",
|
|
45
41
|
"--session",
|
|
46
|
-
"--config",
|
|
47
|
-
"--resource",
|
|
48
|
-
"--extension",
|
|
49
|
-
"--tool",
|
|
50
42
|
"--system",
|
|
51
43
|
"--context",
|
|
52
44
|
"--compact",
|
|
@@ -59,6 +51,9 @@ const valueFlags = new Set([
|
|
|
59
51
|
"--agents-config",
|
|
60
52
|
]);
|
|
61
53
|
const boolFlags = new Set(["--discover", "--no-discovery", "--no-agents-md", "--no-system-md"]);
|
|
54
|
+
// Known-but-inert flags: parsed by earlier builds, never wired to any behavior.
|
|
55
|
+
// Rejected loudly (rather than silently ignored) until a CLI-harness plan wires them.
|
|
56
|
+
const unsupportedFlags = new Set(["--config", "--resource", "--extension", "--tool"]);
|
|
62
57
|
const ALL_KINDS = ["skill", "tool", "context", "instructions"];
|
|
63
58
|
export function parseCliArgs(argv) {
|
|
64
59
|
let mode = "print";
|
|
@@ -78,10 +73,6 @@ export function parseCliArgs(argv) {
|
|
|
78
73
|
let agentsMdFile;
|
|
79
74
|
let systemMdFile;
|
|
80
75
|
let agentsConfig;
|
|
81
|
-
const config = [];
|
|
82
|
-
const resources = [];
|
|
83
|
-
const extensions = [];
|
|
84
|
-
const tools = [];
|
|
85
76
|
const context = [];
|
|
86
77
|
const instructions = [];
|
|
87
78
|
const injectorFiles = [];
|
|
@@ -92,6 +83,8 @@ export function parseCliArgs(argv) {
|
|
|
92
83
|
continue;
|
|
93
84
|
}
|
|
94
85
|
const name = flag === "-p" ? "--prompt" : flag;
|
|
86
|
+
if (unsupportedFlags.has(name))
|
|
87
|
+
throw new CliUsageError(`${name} is not supported in this build`);
|
|
95
88
|
if (boolFlags.has(name)) {
|
|
96
89
|
switch (name) {
|
|
97
90
|
case "--discover":
|
|
@@ -133,18 +126,6 @@ export function parseCliArgs(argv) {
|
|
|
133
126
|
case "--session":
|
|
134
127
|
session = value;
|
|
135
128
|
break;
|
|
136
|
-
case "--config":
|
|
137
|
-
config.push(value);
|
|
138
|
-
break;
|
|
139
|
-
case "--resource":
|
|
140
|
-
resources.push(value);
|
|
141
|
-
break;
|
|
142
|
-
case "--extension":
|
|
143
|
-
extensions.push(value);
|
|
144
|
-
break;
|
|
145
|
-
case "--tool":
|
|
146
|
-
tools.push(value);
|
|
147
|
-
break;
|
|
148
129
|
case "--system":
|
|
149
130
|
system = value;
|
|
150
131
|
break;
|
|
@@ -183,10 +164,6 @@ export function parseCliArgs(argv) {
|
|
|
183
164
|
provider,
|
|
184
165
|
model,
|
|
185
166
|
session,
|
|
186
|
-
config,
|
|
187
|
-
resources,
|
|
188
|
-
extensions,
|
|
189
|
-
tools,
|
|
190
167
|
system,
|
|
191
168
|
context,
|
|
192
169
|
compact,
|
package/dist/context-budget.js
CHANGED
|
@@ -61,16 +61,19 @@ export function applyContextBudget(options) {
|
|
|
61
61
|
const skills = [...(options.skills ?? [])].filter((skill) => skill.instructions);
|
|
62
62
|
const tools = options.tools ? [...options.tools] : undefined;
|
|
63
63
|
const omitted = [];
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
// Measure once, then subtract each dropped item's own estimate (dropNext computes it
|
|
65
|
+
// with the same estimators) — avoids an O(n²) re-scan of the full keep-set per drop.
|
|
66
|
+
const kept = measureAll(groups, context, skills, tools);
|
|
67
|
+
while (overBudget(kept, budget)) {
|
|
66
68
|
const drop = dropNext(groups, context, skills, layout);
|
|
67
69
|
if (!drop) {
|
|
68
70
|
throw new ContextBudgetError();
|
|
69
71
|
}
|
|
72
|
+
kept.tokens -= drop.tokenEstimate;
|
|
73
|
+
kept.bytes -= drop.byteLength;
|
|
70
74
|
if (omitted.length < HARD_MAX_CONTEXT_BUDGET_OMISSIONS)
|
|
71
75
|
omitted.push(drop);
|
|
72
76
|
}
|
|
73
|
-
const kept = cost();
|
|
74
77
|
const reportOmissions = omitted.slice(0, DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS);
|
|
75
78
|
return {
|
|
76
79
|
groups,
|
package/dist/contracts.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface ErrorInfo {
|
|
|
15
15
|
readonly name?: string;
|
|
16
16
|
readonly message: string;
|
|
17
17
|
readonly code?: string | number;
|
|
18
|
+
/** Provider backpressure hint (e.g. from a `Retry-After` header); retry policies
|
|
19
|
+
* honor it capped at their own `maxDelayMs`. */
|
|
20
|
+
readonly retryAfterMs?: number;
|
|
18
21
|
readonly cause?: unknown;
|
|
19
22
|
}
|
|
20
23
|
export type { AudioContent, DocumentContent, FileContent } from "./content.js";
|
|
@@ -749,6 +752,13 @@ export type AgentEvent = {
|
|
|
749
752
|
readonly sessionId: string;
|
|
750
753
|
readonly runId: string;
|
|
751
754
|
readonly size: number;
|
|
755
|
+
} | {
|
|
756
|
+
/** A steered message was dropped by a terminal input guardrail; the run continues without it. */
|
|
757
|
+
readonly type: "steer_rejected";
|
|
758
|
+
readonly sessionId: string;
|
|
759
|
+
readonly runId: string;
|
|
760
|
+
readonly message: Message;
|
|
761
|
+
readonly record: GuardrailRecord;
|
|
752
762
|
} | {
|
|
753
763
|
readonly type: "event_subscriber_overflow";
|
|
754
764
|
readonly sessionId: string;
|
|
@@ -937,6 +947,9 @@ export interface PromptBuildRequest {
|
|
|
937
947
|
readonly context?: readonly ContextBlock[];
|
|
938
948
|
readonly skills?: readonly Skill[];
|
|
939
949
|
readonly tools?: readonly ToolDefinition[];
|
|
950
|
+
/** Model being prompted; lets builders adapt composition to declared capabilities
|
|
951
|
+
* (e.g. the default builder omits the `Available tools:` text for tool-capable models). */
|
|
952
|
+
readonly model?: ModelConfig;
|
|
940
953
|
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
941
954
|
readonly signal?: AbortSignal;
|
|
942
955
|
}
|
package/dist/contributions.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { type ProviderRegistry } from "./providers.js";
|
|
|
4
4
|
import { type DuplicateRegistrationOptions } from "./registry-options.js";
|
|
5
5
|
export interface ContributionRegistry<T> {
|
|
6
6
|
register(key: string, contribution: T): void;
|
|
7
|
+
/** Remove a contribution; returns false when the key was not registered. */
|
|
8
|
+
unregister(key: string): boolean;
|
|
7
9
|
get(key: string): T | undefined;
|
|
8
10
|
resolve(key: string): T;
|
|
9
11
|
list(): readonly T[];
|
package/dist/contributions.js
CHANGED
|
@@ -9,6 +9,9 @@ export function createContributionRegistry(options = {}) {
|
|
|
9
9
|
assertCanRegister(byKey, key, label, key, options.duplicate);
|
|
10
10
|
byKey.set(key, contribution);
|
|
11
11
|
},
|
|
12
|
+
unregister(key) {
|
|
13
|
+
return byKey.delete(key);
|
|
14
|
+
},
|
|
12
15
|
get(key) {
|
|
13
16
|
return byKey.get(key);
|
|
14
17
|
},
|
package/dist/credentials.d.ts
CHANGED
|
@@ -10,7 +10,13 @@ export interface MemoryCredentialStore extends CredentialResolver {
|
|
|
10
10
|
delete(request: Pick<CredentialRequest, "name" | "provider">): boolean;
|
|
11
11
|
clear(): void;
|
|
12
12
|
}
|
|
13
|
-
export
|
|
13
|
+
export interface MemoryCredentialStoreOptions {
|
|
14
|
+
/** When true (default, backward compatible), a provider-scoped request falls back to a
|
|
15
|
+
* providerless record of the same name — that record is then served to EVERY provider.
|
|
16
|
+
* Set false for exact-match-only resolution (strict provider scoping). */
|
|
17
|
+
readonly allowProviderFallback?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function createMemoryCredentialStore(initial?: readonly CredentialRecord[], options?: MemoryCredentialStoreOptions): MemoryCredentialStore;
|
|
14
20
|
export declare function createChainedCredentialResolver(resolvers: readonly CredentialResolver[]): CredentialResolver;
|
|
15
21
|
export declare function createExplicitCredentialResolver(sources: readonly CredentialResolverSource[]): CredentialResolver;
|
|
16
22
|
export declare function createEnvCredentialResolver(env: Readonly<Record<string, string | undefined>>, map: Readonly<Record<string, string>>): CredentialResolver;
|
package/dist/credentials.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export function createMemoryCredentialStore(initial = []) {
|
|
1
|
+
export function createMemoryCredentialStore(initial = [], options = {}) {
|
|
2
2
|
const records = new Map();
|
|
3
|
+
const allowProviderFallback = options.allowProviderFallback ?? true;
|
|
3
4
|
const key = (name, provider) => `${provider ?? ""}\u0000${name}`;
|
|
4
5
|
const store = {
|
|
5
6
|
set(record) {
|
|
@@ -12,7 +13,10 @@ export function createMemoryCredentialStore(initial = []) {
|
|
|
12
13
|
records.clear();
|
|
13
14
|
},
|
|
14
15
|
resolve(request) {
|
|
15
|
-
|
|
16
|
+
const exact = records.get(key(request.name, request.provider));
|
|
17
|
+
if (exact !== undefined || !allowProviderFallback || request.provider === undefined)
|
|
18
|
+
return exact;
|
|
19
|
+
return records.get(key(request.name));
|
|
16
20
|
},
|
|
17
21
|
};
|
|
18
22
|
for (const record of initial)
|
|
@@ -13,10 +13,21 @@ export function createEventMultiplexer(options = {}) {
|
|
|
13
13
|
isClosed = true;
|
|
14
14
|
else
|
|
15
15
|
options.signal?.addEventListener("abort", abort, { once: true });
|
|
16
|
+
// With compare set, publish always enqueues and the parked consumer is woken with
|
|
17
|
+
// this token instead of a value, so every event drains through the sorted queue —
|
|
18
|
+
// delivery order follows the comparator even when the consumer is caught up.
|
|
19
|
+
const WAKE = Symbol("wake");
|
|
20
|
+
const wakeWaiter = () => {
|
|
21
|
+
if (!waiter)
|
|
22
|
+
return;
|
|
23
|
+
const resolve = waiter;
|
|
24
|
+
waiter = undefined;
|
|
25
|
+
resolve({ value: WAKE, done: false });
|
|
26
|
+
};
|
|
16
27
|
function publish(event) {
|
|
17
28
|
if (isClosed)
|
|
18
29
|
return;
|
|
19
|
-
if (waiter) {
|
|
30
|
+
if (!options.compare && waiter) {
|
|
20
31
|
const resolve = waiter;
|
|
21
32
|
waiter = undefined;
|
|
22
33
|
resolve({ value: event, done: false });
|
|
@@ -24,6 +35,7 @@ export function createEventMultiplexer(options = {}) {
|
|
|
24
35
|
}
|
|
25
36
|
if (queue.length < maxQueuedEvents) {
|
|
26
37
|
queue.push(event);
|
|
38
|
+
wakeWaiter();
|
|
27
39
|
return;
|
|
28
40
|
}
|
|
29
41
|
droppedEvents += 1;
|
|
@@ -41,6 +53,7 @@ export function createEventMultiplexer(options = {}) {
|
|
|
41
53
|
if (overflow === "drop_newest") {
|
|
42
54
|
if (notice !== undefined)
|
|
43
55
|
queue[queue.length - 1] = notice;
|
|
56
|
+
wakeWaiter();
|
|
44
57
|
return;
|
|
45
58
|
}
|
|
46
59
|
queue.shift();
|
|
@@ -50,6 +63,7 @@ export function createEventMultiplexer(options = {}) {
|
|
|
50
63
|
queue.shift();
|
|
51
64
|
}
|
|
52
65
|
queue.push(event);
|
|
66
|
+
wakeWaiter();
|
|
53
67
|
}
|
|
54
68
|
function observe(source, map) {
|
|
55
69
|
if (isClosed)
|
|
@@ -115,6 +129,8 @@ export function createEventMultiplexer(options = {}) {
|
|
|
115
129
|
});
|
|
116
130
|
if (next.done)
|
|
117
131
|
return;
|
|
132
|
+
if (next.value === WAKE)
|
|
133
|
+
continue;
|
|
118
134
|
yield next.value;
|
|
119
135
|
}
|
|
120
136
|
}
|
package/dist/extensions.d.ts
CHANGED
|
@@ -26,11 +26,17 @@ export interface ExtensionEventBus {
|
|
|
26
26
|
on(type: ExtensionLifecycleEventName | string, handler: ExtensionEventHandler): () => void;
|
|
27
27
|
emit(event: ExtensionEvent): Promise<void>;
|
|
28
28
|
}
|
|
29
|
+
export interface LoadedExtension {
|
|
30
|
+
readonly name: string;
|
|
31
|
+
/** Remove this extension's registry contributions and middleware/event subscriptions.
|
|
32
|
+
* Best-effort and idempotent; side effects outside the registries are NOT unwound. */
|
|
33
|
+
dispose(): void;
|
|
34
|
+
}
|
|
29
35
|
export interface ExtensionKernel {
|
|
30
36
|
readonly registries: ContributionRegistries;
|
|
31
37
|
readonly middleware: MiddlewareRegistry;
|
|
32
38
|
readonly events: ExtensionEventBus;
|
|
33
|
-
load(extensions: readonly Extension[]): Promise<
|
|
39
|
+
load(extensions: readonly Extension[]): Promise<LoadedExtension[]>;
|
|
34
40
|
}
|
|
35
41
|
export declare function createExtensionEventBus(options?: Pick<ExtensionKernelOptions, "errorPolicy" | "secrets">): ExtensionEventBus;
|
|
36
42
|
export declare function createExtensionKernel(options?: ExtensionKernelOptions): ExtensionKernel;
|
package/dist/extensions.js
CHANGED
|
@@ -45,90 +45,148 @@ export function createExtensionKernel(options = {}) {
|
|
|
45
45
|
const middleware = options.middleware ?? createMiddlewareRegistry({ ...options, onError: events.emit });
|
|
46
46
|
const errorPolicy = options.errorPolicy ?? "event";
|
|
47
47
|
const secrets = options.secrets ?? [];
|
|
48
|
-
|
|
48
|
+
// Per-extension tracked API: every registration records an undo so a dispose handle
|
|
49
|
+
// (or a failed setup) can unwind exactly what that extension added.
|
|
50
|
+
const createApi = (track) => ({
|
|
49
51
|
registries,
|
|
50
52
|
middleware,
|
|
51
|
-
on
|
|
53
|
+
on(type, handler) {
|
|
54
|
+
const off = events.on(type, handler);
|
|
55
|
+
track?.(off);
|
|
56
|
+
return off;
|
|
57
|
+
},
|
|
52
58
|
emit: events.emit,
|
|
53
|
-
use
|
|
59
|
+
use(hook, mw) {
|
|
60
|
+
const off = middleware.use(hook, mw);
|
|
61
|
+
track?.(off);
|
|
62
|
+
return off;
|
|
63
|
+
},
|
|
54
64
|
registerProvider(provider) {
|
|
55
65
|
registries.providers.register(provider);
|
|
66
|
+
track?.(() => registries.providers.unregister(provider.id));
|
|
56
67
|
},
|
|
57
68
|
registerModel(model) {
|
|
58
69
|
registries.models.register(model);
|
|
70
|
+
track?.(() => registries.models.unregister(model.provider, model.model));
|
|
59
71
|
},
|
|
60
72
|
registerTool(tool) {
|
|
61
73
|
registries.tools.register(tool.name, tool);
|
|
74
|
+
track?.(() => registries.tools.unregister(tool.name));
|
|
62
75
|
},
|
|
63
76
|
registerContextProvider(provider) {
|
|
64
77
|
registries.contextProviders.register(provider.name, provider);
|
|
78
|
+
track?.(() => registries.contextProviders.unregister(provider.name));
|
|
65
79
|
},
|
|
66
80
|
registerSkill(skill) {
|
|
67
81
|
registries.skills.register(skill.name, skill);
|
|
82
|
+
track?.(() => registries.skills.unregister(skill.name));
|
|
68
83
|
},
|
|
69
84
|
registerCommand(command) {
|
|
70
85
|
registries.commands.register(command.name, command);
|
|
86
|
+
track?.(() => registries.commands.unregister(command.name));
|
|
71
87
|
},
|
|
72
88
|
registerAgent(agent) {
|
|
73
89
|
registries.agents.register(agent.name, agent);
|
|
90
|
+
track?.(() => registries.agents.unregister(agent.name));
|
|
74
91
|
},
|
|
75
92
|
registerInputBuilder(builder) {
|
|
76
93
|
registries.inputBuilders.register(builder.name, builder);
|
|
94
|
+
track?.(() => registries.inputBuilders.unregister(builder.name));
|
|
77
95
|
},
|
|
78
96
|
registerPromptBuilder(builder) {
|
|
79
97
|
registries.promptBuilders.register(builder.name, builder);
|
|
98
|
+
track?.(() => registries.promptBuilders.unregister(builder.name));
|
|
80
99
|
},
|
|
81
100
|
registerCompactionStrategy(strategy) {
|
|
82
101
|
registries.compactionStrategies.register(strategy.name, strategy);
|
|
102
|
+
track?.(() => registries.compactionStrategies.unregister(strategy.name));
|
|
83
103
|
},
|
|
84
104
|
registerRetryPolicy(policy) {
|
|
85
105
|
registries.retryPolicies.register(policy.name, policy);
|
|
106
|
+
track?.(() => registries.retryPolicies.unregister(policy.name));
|
|
86
107
|
},
|
|
87
108
|
registerStoreFactory(factory) {
|
|
88
109
|
registries.storeFactories.register(factory.name, factory);
|
|
110
|
+
track?.(() => registries.storeFactories.unregister(factory.name));
|
|
89
111
|
},
|
|
90
112
|
registerResourceLoader(key, loader) {
|
|
91
113
|
registries.resourceLoaders.register(key, loader);
|
|
114
|
+
track?.(() => registries.resourceLoaders.unregister(key));
|
|
92
115
|
},
|
|
93
116
|
registerSettingsProvider(key, provider) {
|
|
94
117
|
registries.settingsProviders.register(key, provider);
|
|
118
|
+
track?.(() => registries.settingsProviders.unregister(key));
|
|
95
119
|
},
|
|
96
120
|
registerCredentialResolver(key, resolver) {
|
|
97
121
|
registries.credentialResolvers.register(key, resolver);
|
|
122
|
+
track?.(() => registries.credentialResolvers.unregister(key));
|
|
98
123
|
},
|
|
99
124
|
registerProviderPackage(providerPackage) {
|
|
100
125
|
registries.providerPackages.register(providerPackage.name, providerPackage);
|
|
126
|
+
track?.(() => registries.providerPackages.unregister(providerPackage.name));
|
|
101
127
|
},
|
|
102
128
|
registerAuthMethod(method) {
|
|
103
|
-
|
|
129
|
+
const key = authMethodKey(method);
|
|
130
|
+
registries.authMethods.register(key, method);
|
|
131
|
+
track?.(() => registries.authMethods.unregister(key));
|
|
104
132
|
},
|
|
105
133
|
registerProviderRequestPolicy(policy) {
|
|
106
134
|
registries.providerRequestPolicies.register(policy.name, policy);
|
|
135
|
+
track?.(() => registries.providerRequestPolicies.unregister(policy.name));
|
|
107
136
|
},
|
|
108
137
|
registerSystemPromptContribution(contribution) {
|
|
109
|
-
|
|
138
|
+
const key = systemPromptContributionKey(contribution);
|
|
139
|
+
registries.systemPromptContributions.register(key, contribution);
|
|
140
|
+
track?.(() => registries.systemPromptContributions.unregister(key));
|
|
110
141
|
},
|
|
111
142
|
registerInstructionInjector(injector) {
|
|
112
143
|
registries.instructionInjectors.register(injector.name, injector);
|
|
144
|
+
track?.(() => registries.instructionInjectors.unregister(injector.name));
|
|
113
145
|
},
|
|
146
|
+
});
|
|
147
|
+
const unwind = (undo) => {
|
|
148
|
+
for (const fn of undo.reverse()) {
|
|
149
|
+
try {
|
|
150
|
+
fn();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// best-effort: one stuck undo must not block the rest
|
|
154
|
+
}
|
|
155
|
+
}
|
|
114
156
|
};
|
|
115
157
|
return {
|
|
116
158
|
registries,
|
|
117
159
|
middleware,
|
|
118
160
|
events,
|
|
119
161
|
async load(extensions) {
|
|
162
|
+
const loaded = [];
|
|
120
163
|
for (const extension of extensions) {
|
|
164
|
+
const undo = [];
|
|
121
165
|
try {
|
|
122
166
|
await assertPermission(options.permission, { kind: "extension", action: "setup", target: extension.name });
|
|
123
167
|
await assertExtensionLoadPolicy(options.loadPolicy, extension);
|
|
124
|
-
await extension.setup(
|
|
168
|
+
await extension.setup(createApi((fn) => undo.push(fn)));
|
|
125
169
|
}
|
|
126
170
|
catch (error) {
|
|
171
|
+
// A failed setup must not leave partial contributions behind.
|
|
172
|
+
unwind(undo);
|
|
127
173
|
if (errorPolicy === "throw")
|
|
128
174
|
throw error;
|
|
129
175
|
await events.emit(extensionError(error, extension.name, secrets));
|
|
176
|
+
continue;
|
|
130
177
|
}
|
|
178
|
+
let disposed = false;
|
|
179
|
+
loaded.push({
|
|
180
|
+
name: extension.name,
|
|
181
|
+
dispose() {
|
|
182
|
+
if (disposed)
|
|
183
|
+
return;
|
|
184
|
+
disposed = true;
|
|
185
|
+
unwind(undo);
|
|
186
|
+
},
|
|
187
|
+
});
|
|
131
188
|
}
|
|
189
|
+
return loaded;
|
|
132
190
|
},
|
|
133
191
|
};
|
|
134
192
|
}
|