@arnilo/prism 0.0.96 → 0.1.1
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 +290 -2
- package/README.md +17 -3
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-loops.d.ts +5 -0
- package/dist/agent-loops.js +99 -14
- package/dist/agent-run-lifecycle.d.ts +5 -2
- package/dist/agent-run-lifecycle.js +18 -2
- package/dist/agent-run-state.d.ts +27 -1
- package/dist/agent-run-state.js +113 -7
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +1255 -129
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +44 -0
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.d.ts +67 -0
- package/dist/context-budget.js +288 -0
- package/dist/contracts.d.ts +590 -8
- package/dist/contracts.js +142 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.d.ts +50 -0
- package/dist/conversations.js +98 -0
- package/dist/credentials.d.ts +22 -2
- package/dist/credentials.js +18 -3
- package/dist/devices.d.ts +94 -0
- package/dist/devices.js +138 -0
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +79 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +265 -0
- package/dist/index.d.ts +94 -72
- package/dist/index.js +48 -36
- package/dist/input.d.ts +10 -1
- package/dist/input.js +152 -52
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +13 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +202 -0
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +46 -1
- package/dist/providers/openai-compatible.js +123 -53
- package/dist/providers/openai-primitives.js +10 -7
- 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.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +55 -11
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +8 -2
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +195 -21
- package/dist/skill-disclosure.d.ts +35 -0
- package/dist/skill-disclosure.js +101 -0
- package/dist/skill-load.d.ts +25 -0
- package/dist/skill-load.js +112 -0
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +20 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +280 -35
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +37 -2
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/thinking.js +4 -1
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +352 -0
- package/dist/tool-result-fold.d.ts +40 -0
- package/dist/tool-result-fold.js +176 -0
- package/dist/tools.d.ts +8 -3
- package/dist/tools.js +248 -13
- package/docs/0.1.0-readiness.md +215 -0
- package/docs/a2a.md +33 -2
- package/docs/acp.md +152 -0
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +225 -0
- package/docs/agent-events.md +34 -3
- package/docs/agent-identity.md +144 -0
- package/docs/agent-loops.md +17 -2
- package/docs/agent-session-runtime.md +21 -4
- package/docs/browser-automation.md +5 -0
- package/docs/caveman.md +129 -0
- package/docs/cli-rpc.md +3 -6
- package/docs/coding-agent-tools.md +229 -25
- package/docs/coding-security.md +77 -11
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +20 -1
- package/docs/compaction-observational-memory.md +52 -8
- package/docs/context-and-skills.md +94 -7
- package/docs/contribution-registries.md +1 -0
- package/docs/conversations.md +135 -0
- package/docs/credential-storage.md +34 -1
- package/docs/credentials-and-redaction.md +11 -1
- package/docs/database-persistence.md +27 -7
- package/docs/device-adapters.md +97 -0
- package/docs/enterprise-postgres-state.md +178 -0
- package/docs/evaluations.md +14 -1
- package/docs/extensions.md +4 -1
- package/docs/forge-integration.md +113 -0
- package/docs/guardrails.md +16 -2
- package/docs/host-security.md +35 -4
- package/docs/index.md +69 -37
- package/docs/input-and-prompt-assembly.md +8 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/mcp-tools.md +62 -5
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +427 -2
- package/docs/model-routing.md +111 -0
- package/docs/multimodal-content.md +8 -5
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/observability.md +2 -0
- package/docs/openapi-tools.md +56 -0
- package/docs/performance.md +282 -0
- package/docs/policy-and-audit.md +171 -0
- package/docs/ponytail.md +127 -0
- package/docs/postgres-persistence.md +8 -4
- package/docs/process-sessions.md +147 -0
- package/docs/provider-caching.md +13 -1
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +43 -2
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/ai-sdk.md +24 -7
- package/docs/providers/alibaba.md +179 -0
- package/docs/providers/anthropic.md +93 -0
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +89 -0
- package/docs/providers/ollama.md +166 -0
- package/docs/providers/openai-compatible.md +31 -2
- package/docs/providers/openai.md +24 -5
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +68 -4
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +362 -208
- package/docs/resource-loading.md +3 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +44 -6
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +41 -2
- package/docs/sqlite-persistence.md +11 -3
- package/docs/structured-output.md +7 -1
- package/docs/supervisors.md +8 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +5 -0
- package/docs/work-artifacts-and-review.md +102 -0
- package/docs/work-connectors.md +32 -0
- package/docs/work-tools.md +137 -0
- package/docs/workflows.md +6 -0
- package/docs/working-and-semantic-memory.md +40 -7
- package/package.json +30 -7
- package/templates/init/providers.json +22 -0
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// stores already satisfy. Mirrors the assertion shape already repeated across
|
|
6
6
|
// src/__tests__/session-stores.test.ts and node-session-store-jsonl.test.ts so
|
|
7
7
|
// adapter authors do not re-derive them. Throws plain Error; no test runner.
|
|
8
|
-
import { isSessionAppendConflict } from "../contracts.js";
|
|
8
|
+
import { HARD_MAX_SESSION_SEARCH_LIMIT, HARD_MAX_SESSION_SEARCH_QUERY_BYTES, isSessionAppendConflict, resolveSessionSearchQuery, } from "../contracts.js";
|
|
9
9
|
/**
|
|
10
10
|
* Assert that a `SessionStore` implementation satisfies the core adapter
|
|
11
11
|
* contract: round-trip append/list, duplicate-entry-id rejection,
|
|
@@ -63,6 +63,9 @@ export async function assertSessionStoreConforms(store, options = {}) {
|
|
|
63
63
|
throw new Error(`readBranchPath must return the ancestor chain root→leaf in order; got ${JSON.stringify(ids)}`);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
if (options.exerciseSearchSessions && typeof store.searchSessions === "function") {
|
|
67
|
+
await assertSessionStoreSearchSessions(store);
|
|
68
|
+
}
|
|
66
69
|
await assertSessionStoreBranchIsolation(store, options);
|
|
67
70
|
if (options.exerciseConcurrentParentAppend) {
|
|
68
71
|
await assertConcurrentParentAppendAllowed(store, sessionId, now, make);
|
|
@@ -104,6 +107,38 @@ export async function runSessionStoreConformance(factory, options = {}) {
|
|
|
104
107
|
label: "dup",
|
|
105
108
|
}, { idempotencyKey: "reopen-idem", expectedParentId: parent?.id }), (error) => isSessionAppendConflict(error) && error.conflict.idempotencyDuplicate === true, "Restarted store must still deduplicate an exact idempotency retry");
|
|
106
109
|
}
|
|
110
|
+
async function assertSessionStoreSearchSessions(store) {
|
|
111
|
+
const search = store.searchSessions;
|
|
112
|
+
await reject(() => search({ limit: 0 }), (error) => error instanceof TypeError, "searchSessions must reject non-positive limit");
|
|
113
|
+
await reject(() => search({ limit: Number.NaN }), (error) => error instanceof TypeError, "searchSessions must reject NaN limit");
|
|
114
|
+
await reject(() => search({ limit: HARD_MAX_SESSION_SEARCH_LIMIT + 1 }), (error) => error instanceof TypeError, "searchSessions must reject oversize limit");
|
|
115
|
+
await reject(() => search({ query: "x".repeat(HARD_MAX_SESSION_SEARCH_QUERY_BYTES + 1) }), (error) => error instanceof TypeError, "searchSessions must reject oversize query string");
|
|
116
|
+
const empty = await search(resolveSessionSearchQuery({
|
|
117
|
+
workspaceRoot: "__prism_conformance_empty__",
|
|
118
|
+
limit: 5,
|
|
119
|
+
}));
|
|
120
|
+
if (!Array.isArray(empty.items)) {
|
|
121
|
+
throw new Error("searchSessions must return a PersistencePage with an items array");
|
|
122
|
+
}
|
|
123
|
+
const resolved = resolveSessionSearchQuery({ limit: 1 });
|
|
124
|
+
const page = await search(resolved);
|
|
125
|
+
if (page.items.length > resolved.limit) {
|
|
126
|
+
throw new Error(`searchSessions must honor limit; got ${page.items.length} items for limit ${resolved.limit}`);
|
|
127
|
+
}
|
|
128
|
+
for (const hit of page.items) {
|
|
129
|
+
if (typeof hit.sessionId !== "string" || !hit.sessionId) {
|
|
130
|
+
throw new Error("searchSessions hits must include a non-empty sessionId");
|
|
131
|
+
}
|
|
132
|
+
if ("credential" in hit || "apiKey" in hit || "password" in hit) {
|
|
133
|
+
throw new Error("searchSessions hits must not include credential fields");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const unscoped = await search({ limit: 100 });
|
|
137
|
+
const owned = await search({ tenantId: "__missing_tenant__", limit: 100 });
|
|
138
|
+
if (owned.items.length > unscoped.items.length) {
|
|
139
|
+
throw new Error("searchSessions ownership filter must not return more hits than an unscoped search");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
107
142
|
async function assertSessionStoreBranchIsolation(store, options) {
|
|
108
143
|
const sessionId = options.sessionId ?? "conformance";
|
|
109
144
|
const otherSessionId = options.otherSessionId ?? `${sessionId}-other`;
|
|
@@ -120,7 +155,7 @@ async function assertSessionStoreBranchIsolation(store, options) {
|
|
|
120
155
|
throw new Error("SessionStore leaked entries across session ids");
|
|
121
156
|
}
|
|
122
157
|
}
|
|
123
|
-
async function assertConcurrentParentAppendAllowed(store, sessionId,
|
|
158
|
+
async function assertConcurrentParentAppendAllowed(store, sessionId, _now, make) {
|
|
124
159
|
const forkRoot = make("fork-root");
|
|
125
160
|
await store.append(forkRoot);
|
|
126
161
|
const childA = make("fork-a", forkRoot.id);
|
|
@@ -26,15 +26,38 @@ export async function assertToolDispatchConforms(registry, options) {
|
|
|
26
26
|
// 1. unknown tool → blocked "unknown_tool".
|
|
27
27
|
await assertToolBlocked({ call: call("does-not-exist", {}), context: baseContext, ...shared, ...pickPolicy(options) }, "unknown_tool");
|
|
28
28
|
// 2. denied tool (filter) → blocked "tool_denied".
|
|
29
|
-
await assertToolBlocked({
|
|
29
|
+
await assertToolBlocked({
|
|
30
|
+
call: call(options.tool.name, options.validArgs),
|
|
31
|
+
context: baseContext,
|
|
32
|
+
...shared,
|
|
33
|
+
...pickPolicy(options),
|
|
34
|
+
filter: { deny: [options.tool.name] },
|
|
35
|
+
}, "tool_denied");
|
|
30
36
|
// 3. invalid (non-object) arguments → blocked "invalid_arguments".
|
|
31
37
|
await assertToolBlocked({ call: call(options.tool.name, "not-an-object"), context: baseContext, ...shared, ...pickPolicy(options) }, "invalid_arguments");
|
|
32
38
|
// 4. permission denial → blocked "permission_denied".
|
|
33
|
-
await assertToolBlocked({
|
|
39
|
+
await assertToolBlocked({
|
|
40
|
+
call: call(options.tool.name, options.validArgs),
|
|
41
|
+
context: baseContext,
|
|
42
|
+
...shared,
|
|
43
|
+
...pickPolicy(options),
|
|
44
|
+
permission: denyAllPermission,
|
|
45
|
+
}, "permission_denied");
|
|
34
46
|
// 5. validator failure → blocked "validation_failed".
|
|
35
|
-
await assertToolBlocked({
|
|
47
|
+
await assertToolBlocked({
|
|
48
|
+
call: call(options.tool.name, options.validArgs),
|
|
49
|
+
context: baseContext,
|
|
50
|
+
...shared,
|
|
51
|
+
...pickPolicy(options),
|
|
52
|
+
validate: alwaysInvalidValidator,
|
|
53
|
+
}, "validation_failed");
|
|
36
54
|
// 6. valid call → executes (tool_execution_started), no error, no blocked event.
|
|
37
|
-
const probe = await dispatchAndCollect({
|
|
55
|
+
const probe = await dispatchAndCollect({
|
|
56
|
+
call: call(options.tool.name, options.validArgs),
|
|
57
|
+
context: baseContext,
|
|
58
|
+
...shared,
|
|
59
|
+
...pickPolicy(options),
|
|
60
|
+
});
|
|
38
61
|
if (probe.result.error)
|
|
39
62
|
throw new Error(`Valid tool call was not executed: ${probe.result.error.message}`);
|
|
40
63
|
if (!probe.events.some((event) => event.type === "tool_execution_started")) {
|
|
@@ -69,7 +92,9 @@ export async function dispatchAndCollect(probe) {
|
|
|
69
92
|
permission: probe.permission,
|
|
70
93
|
validate: probe.validate,
|
|
71
94
|
secrets: probe.secrets,
|
|
72
|
-
emit: (event) => {
|
|
95
|
+
emit: (event) => {
|
|
96
|
+
events.push(event);
|
|
97
|
+
},
|
|
73
98
|
});
|
|
74
99
|
return { result, events };
|
|
75
100
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AgentIdentity, ToolEffectKey, ToolEffectStore } from "../contracts.js";
|
|
2
|
+
export interface ToolEffectStoreConformanceOptions {
|
|
3
|
+
readonly identity?: AgentIdentity;
|
|
4
|
+
readonly ownership?: ToolEffectKey["ownership"];
|
|
5
|
+
readonly key?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Assert core claim/CAS, duplicate, reconciliation, and cleanup semantics without a test framework. */
|
|
8
|
+
export declare function assertToolEffectStoreConforms(factory: () => ToolEffectStore | Promise<ToolEffectStore>, options?: ToolEffectStoreConformanceOptions): Promise<void>;
|
|
9
|
+
export declare function runToolEffectStoreConformance(factory: () => ToolEffectStore | Promise<ToolEffectStore>, options?: ToolEffectStoreConformanceOptions): Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Assert core claim/CAS, duplicate, reconciliation, and cleanup semantics without a test framework. */
|
|
2
|
+
export async function assertToolEffectStoreConforms(factory, options = {}) {
|
|
3
|
+
const store = await factory();
|
|
4
|
+
const identity = options.identity ?? testIdentity();
|
|
5
|
+
const ownership = options.ownership ?? { tenantId: identity.tenantId };
|
|
6
|
+
const base = key(identity, ownership, options.key ?? "prism:tool-effect:v1:conformance");
|
|
7
|
+
const first = await store.begin(base);
|
|
8
|
+
if (first.outcome !== "acquired" || first.record.status !== "pending" || !first.record.claimToken) {
|
|
9
|
+
throw new Error("store must acquire an absent tool effect as pending with a claim token");
|
|
10
|
+
}
|
|
11
|
+
const duplicate = await store.begin(base);
|
|
12
|
+
if (duplicate.outcome !== "existing" || duplicate.record.status !== "pending") {
|
|
13
|
+
throw new Error("store must return existing pending effects without a second claim");
|
|
14
|
+
}
|
|
15
|
+
const dispatched = await store.markDispatched(transition(base, first.record));
|
|
16
|
+
if (dispatched.status !== "dispatched" || dispatched.version !== first.record.version + 1) {
|
|
17
|
+
throw new Error("store must transition a claimed pending effect to dispatched");
|
|
18
|
+
}
|
|
19
|
+
const result = { toolCallId: base.toolCallId, name: base.toolName, value: { ok: true } };
|
|
20
|
+
const completed = await store.complete({ ...transition(base, dispatched), result });
|
|
21
|
+
if (completed.status !== "completed" || completed.result?.toolCallId !== base.toolCallId) {
|
|
22
|
+
throw new Error("store must retain a completed bounded result");
|
|
23
|
+
}
|
|
24
|
+
const replay = await store.begin(base);
|
|
25
|
+
if (replay.outcome !== "existing" || replay.record.status !== "completed") {
|
|
26
|
+
throw new Error("store must preserve completed duplicate state");
|
|
27
|
+
}
|
|
28
|
+
await expectReject(() => store.markDispatched(transition(base, dispatched)), "store must reject stale claim/version transitions");
|
|
29
|
+
const unknownKey = key(identity, ownership, `${base.key}:unknown`, "call-unknown");
|
|
30
|
+
const unknownPending = await store.begin(unknownKey);
|
|
31
|
+
const unknownDispatched = await store.markDispatched(transition(unknownKey, unknownPending.record));
|
|
32
|
+
const unknown = await store.markUnknown({ ...transition(unknownKey, unknownDispatched), failure: { code: "test" } });
|
|
33
|
+
if (unknown.status !== "unknown")
|
|
34
|
+
throw new Error("store must mark dispatched effects unknown");
|
|
35
|
+
const resolved = await store.resolveUnknown({
|
|
36
|
+
...unknownKey,
|
|
37
|
+
expectedVersion: unknown.version,
|
|
38
|
+
status: "failed_terminal",
|
|
39
|
+
failure: { code: "test" },
|
|
40
|
+
});
|
|
41
|
+
if (resolved.status !== "failed_terminal")
|
|
42
|
+
throw new Error("store must CAS-resolve unknown effects");
|
|
43
|
+
const cleanup = await store.cleanup({ ownership, before: new Date(Date.now() + 60_000).toISOString(), limit: 100 });
|
|
44
|
+
if (cleanup.deleted < 1)
|
|
45
|
+
throw new Error("store cleanup must remove terminal effects");
|
|
46
|
+
}
|
|
47
|
+
export async function runToolEffectStoreConformance(factory, options = {}) {
|
|
48
|
+
await assertToolEffectStoreConforms(factory, options);
|
|
49
|
+
}
|
|
50
|
+
function key(identity, ownership, value, toolCallId = "call") {
|
|
51
|
+
return {
|
|
52
|
+
identity,
|
|
53
|
+
ownership,
|
|
54
|
+
key: value,
|
|
55
|
+
sessionId: "session",
|
|
56
|
+
runId: "run",
|
|
57
|
+
toolCallId,
|
|
58
|
+
toolName: "effect.tool",
|
|
59
|
+
argumentsHash: "a".repeat(64),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function transition(base, record) {
|
|
63
|
+
if (!record.claimToken)
|
|
64
|
+
throw new Error("store transition record lacks claim token");
|
|
65
|
+
return { ...base, claimToken: record.claimToken, expectedVersion: record.version };
|
|
66
|
+
}
|
|
67
|
+
async function expectReject(run, message) {
|
|
68
|
+
try {
|
|
69
|
+
await run();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
throw new Error(message);
|
|
75
|
+
}
|
|
76
|
+
function testIdentity() {
|
|
77
|
+
return {
|
|
78
|
+
tenantId: "tenant",
|
|
79
|
+
principal: { kind: "service", id: "conformance" },
|
|
80
|
+
scopes: ["tools:execute"],
|
|
81
|
+
issuedAt: "2026-01-01T00:00:00.000Z",
|
|
82
|
+
verified: true,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=tool-effect-store-conformance.js.map
|
package/dist/thinking.js
CHANGED
|
@@ -47,7 +47,10 @@ export function applyThinkingLevel(options, level, family = "reasoning_effort")
|
|
|
47
47
|
if (!normalized || family === "noop")
|
|
48
48
|
return options ?? {};
|
|
49
49
|
const patch = thinkingCompatFor(family, normalized);
|
|
50
|
-
if (family === "openai_reasoning" &&
|
|
50
|
+
if (family === "openai_reasoning" &&
|
|
51
|
+
options?.compat?.reasoning &&
|
|
52
|
+
typeof options.compat.reasoning === "object" &&
|
|
53
|
+
!Array.isArray(options.compat.reasoning)) {
|
|
51
54
|
return mergeProviderRequestOptions(options, {
|
|
52
55
|
compat: {
|
|
53
56
|
reasoning: {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JsonObject, ToolEffectKey, ToolEffectStore } from "./contracts.js";
|
|
2
|
+
export type ToolEffectErrorCode = "ERR_PRISM_TOOL_EFFECT_REQUIRED" | "ERR_PRISM_TOOL_EFFECT_CONFLICT" | "ERR_PRISM_TOOL_EFFECT_UNKNOWN" | "ERR_PRISM_TOOL_EFFECT_COMPLETED" | "ERR_PRISM_TOOL_EFFECT_LIMIT";
|
|
3
|
+
export declare class ToolEffectError extends Error {
|
|
4
|
+
readonly code: ToolEffectErrorCode;
|
|
5
|
+
constructor(code: ToolEffectErrorCode, message: string);
|
|
6
|
+
}
|
|
7
|
+
/** Stable JSON representation for an already-validated tool arguments object. */
|
|
8
|
+
export declare function canonicalToolEffectJson(value: unknown): string;
|
|
9
|
+
export declare function toolEffectArgumentsHash(argumentsValue: JsonObject): string;
|
|
10
|
+
/** Derives the only core-authoritative key. Callers never supply this from model input. */
|
|
11
|
+
export declare function deriveToolEffectKey(input: Omit<ToolEffectKey, "key" | "signal">): string;
|
|
12
|
+
/** In-process reference. Use a durable adapter for cross-replica claims. */
|
|
13
|
+
export declare function createMemoryToolEffectStore(options?: {
|
|
14
|
+
readonly now?: () => number;
|
|
15
|
+
}): ToolEffectStore;
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { assertIdentityActive, assertIdentityMatchesOwnership } from "./identity.js";
|
|
3
|
+
export class ToolEffectError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.name = "ToolEffectError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const DEFAULT_CLAIM_TTL_MS = 15 * 60_000;
|
|
12
|
+
const HARD_CLAIM_TTL_MS = 60 * 60_000;
|
|
13
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
14
|
+
const HARD_MAX_ATTEMPTS = 10;
|
|
15
|
+
const DEFAULT_CLEANUP_LIMIT = 100;
|
|
16
|
+
const HARD_CLEANUP_LIMIT = 500;
|
|
17
|
+
const MAX_EFFECT_KEY_BYTES = 96;
|
|
18
|
+
const MAX_TOOL_NAME_BYTES = 512;
|
|
19
|
+
const MAX_IDENTIFIER_BYTES = 512;
|
|
20
|
+
const MAX_RESULT_BYTES = 64 * 1024;
|
|
21
|
+
const MAX_REFERENCE_BYTES = 1024;
|
|
22
|
+
const MAX_RECORD_BYTES = 128 * 1024;
|
|
23
|
+
/** Stable JSON representation for an already-validated tool arguments object. */
|
|
24
|
+
export function canonicalToolEffectJson(value) {
|
|
25
|
+
return JSON.stringify(canonical(value));
|
|
26
|
+
}
|
|
27
|
+
export function toolEffectArgumentsHash(argumentsValue) {
|
|
28
|
+
return createHash("sha256").update(canonicalToolEffectJson(argumentsValue)).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
/** Derives the only core-authoritative key. Callers never supply this from model input. */
|
|
31
|
+
export function deriveToolEffectKey(input) {
|
|
32
|
+
const value = canonicalToolEffectJson({
|
|
33
|
+
tenantId: input.ownership.tenantId,
|
|
34
|
+
accountId: input.ownership.accountId ?? null,
|
|
35
|
+
userId: input.ownership.userId ?? null,
|
|
36
|
+
principalId: input.identity.principal.id,
|
|
37
|
+
sessionId: input.sessionId,
|
|
38
|
+
runId: input.runId,
|
|
39
|
+
toolCallId: input.toolCallId,
|
|
40
|
+
toolName: input.toolName,
|
|
41
|
+
argumentsHash: input.argumentsHash,
|
|
42
|
+
});
|
|
43
|
+
return `prism:tool-effect:v1:${createHash("sha256").update(value).digest("hex")}`;
|
|
44
|
+
}
|
|
45
|
+
/** In-process reference. Use a durable adapter for cross-replica claims. */
|
|
46
|
+
export function createMemoryToolEffectStore(options = {}) {
|
|
47
|
+
const records = new Map();
|
|
48
|
+
const now = options.now ?? Date.now;
|
|
49
|
+
function current(input) {
|
|
50
|
+
throwIfAborted(input.signal);
|
|
51
|
+
validateKey(input);
|
|
52
|
+
const found = records.get(recordKey(input));
|
|
53
|
+
if (!found)
|
|
54
|
+
return undefined;
|
|
55
|
+
assertMatches(found, input);
|
|
56
|
+
const expired = expire(found, now());
|
|
57
|
+
if (expired !== found)
|
|
58
|
+
records.set(recordKey(input), expired);
|
|
59
|
+
return expired;
|
|
60
|
+
}
|
|
61
|
+
function save(record, identity) {
|
|
62
|
+
const frozen = freezeRecord(record);
|
|
63
|
+
assertRecordSize(frozen);
|
|
64
|
+
records.set(recordKey({ identity, key: frozen.key }), frozen);
|
|
65
|
+
return frozen;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
async get(input) {
|
|
69
|
+
return current(input);
|
|
70
|
+
},
|
|
71
|
+
async begin(input) {
|
|
72
|
+
const existing = current(input);
|
|
73
|
+
const timestamp = now();
|
|
74
|
+
const ttl = claimTtl(input.claimTtlMs);
|
|
75
|
+
const attempts = maxAttempts(input.maxAttempts);
|
|
76
|
+
if (!existing)
|
|
77
|
+
return {
|
|
78
|
+
outcome: "acquired",
|
|
79
|
+
record: save(claim(input, 1, timestamp, ttl), input.identity),
|
|
80
|
+
};
|
|
81
|
+
if (existing.status === "failed_retryable" && existing.attempt < attempts) {
|
|
82
|
+
return {
|
|
83
|
+
outcome: "acquired",
|
|
84
|
+
record: save(claim(input, existing.attempt + 1, timestamp, ttl, existing), input.identity),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { outcome: "existing", record: existing };
|
|
88
|
+
},
|
|
89
|
+
async markDispatched(input) {
|
|
90
|
+
const record = requireClaim(current(input), input, ["pending"]);
|
|
91
|
+
return save({
|
|
92
|
+
...record,
|
|
93
|
+
status: "dispatched",
|
|
94
|
+
version: record.version + 1,
|
|
95
|
+
updatedAt: timestamp(now()),
|
|
96
|
+
}, input.identity);
|
|
97
|
+
},
|
|
98
|
+
async complete(input) {
|
|
99
|
+
const record = requireClaim(current(input), input, ["dispatched"]);
|
|
100
|
+
const result = input.result === undefined ? undefined : validateResult(input.result, input);
|
|
101
|
+
const resultRef = input.resultRef === undefined ? undefined : validateReference(input.resultRef);
|
|
102
|
+
return save({
|
|
103
|
+
...withoutClaim(record),
|
|
104
|
+
status: "completed",
|
|
105
|
+
version: record.version + 1,
|
|
106
|
+
...(result === undefined ? {} : { result }),
|
|
107
|
+
...(resultRef === undefined ? {} : { resultRef }),
|
|
108
|
+
updatedAt: timestamp(now()),
|
|
109
|
+
}, input.identity);
|
|
110
|
+
},
|
|
111
|
+
async fail(input) {
|
|
112
|
+
const record = requireClaim(current(input), input, ["pending", "dispatched"]);
|
|
113
|
+
return save({
|
|
114
|
+
...withoutClaim(record),
|
|
115
|
+
status: input.status,
|
|
116
|
+
version: record.version + 1,
|
|
117
|
+
failure: validateFailure(input.failure),
|
|
118
|
+
updatedAt: timestamp(now()),
|
|
119
|
+
}, input.identity);
|
|
120
|
+
},
|
|
121
|
+
async markUnknown(input) {
|
|
122
|
+
const record = requireClaim(current(input), input, ["dispatched"]);
|
|
123
|
+
return save({
|
|
124
|
+
...withoutClaim(record),
|
|
125
|
+
status: "unknown",
|
|
126
|
+
version: record.version + 1,
|
|
127
|
+
...(input.failure === undefined ? {} : { failure: validateFailure(input.failure) }),
|
|
128
|
+
updatedAt: timestamp(now()),
|
|
129
|
+
}, input.identity);
|
|
130
|
+
},
|
|
131
|
+
async resolveUnknown(input) {
|
|
132
|
+
const record = current(input);
|
|
133
|
+
if (!record || record.status !== "unknown" || record.version !== input.expectedVersion)
|
|
134
|
+
throw conflict();
|
|
135
|
+
const result = input.result === undefined ? undefined : validateResult(input.result, input);
|
|
136
|
+
const resultRef = input.resultRef === undefined ? undefined : validateReference(input.resultRef);
|
|
137
|
+
return save({
|
|
138
|
+
...record,
|
|
139
|
+
status: input.status,
|
|
140
|
+
version: record.version + 1,
|
|
141
|
+
...(result === undefined ? {} : { result }),
|
|
142
|
+
...(resultRef === undefined ? {} : { resultRef }),
|
|
143
|
+
...(input.failure === undefined ? {} : { failure: validateFailure(input.failure) }),
|
|
144
|
+
updatedAt: timestamp(now()),
|
|
145
|
+
}, input.identity);
|
|
146
|
+
},
|
|
147
|
+
async cleanup(input) {
|
|
148
|
+
throwIfAborted(input.signal);
|
|
149
|
+
const before = Date.parse(input.before);
|
|
150
|
+
if (!Number.isFinite(before))
|
|
151
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", "cleanup boundary is invalid");
|
|
152
|
+
validateOwnership(input.ownership);
|
|
153
|
+
const limit = cleanupLimit(input.limit);
|
|
154
|
+
let deleted = 0;
|
|
155
|
+
for (const [id, record] of records) {
|
|
156
|
+
throwIfAborted(input.signal);
|
|
157
|
+
if (deleted >= limit)
|
|
158
|
+
break;
|
|
159
|
+
if (!sameOwnership(record, input.ownership) || !isTerminal(record.status) || Date.parse(record.updatedAt) >= before)
|
|
160
|
+
continue;
|
|
161
|
+
records.delete(id);
|
|
162
|
+
deleted += 1;
|
|
163
|
+
}
|
|
164
|
+
return { deleted };
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function canonical(value) {
|
|
169
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
170
|
+
return value;
|
|
171
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
172
|
+
return value;
|
|
173
|
+
if (Array.isArray(value))
|
|
174
|
+
return value.map(canonical);
|
|
175
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
176
|
+
const out = {};
|
|
177
|
+
for (const key of Object.keys(value).sort())
|
|
178
|
+
out[key] = canonical(value[key]);
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", "tool effect values must be JSON");
|
|
182
|
+
}
|
|
183
|
+
function claim(input, attempt, currentTime, ttl, previous) {
|
|
184
|
+
return {
|
|
185
|
+
...owner(input.ownership),
|
|
186
|
+
key: input.key,
|
|
187
|
+
sessionId: input.sessionId,
|
|
188
|
+
runId: input.runId,
|
|
189
|
+
toolCallId: input.toolCallId,
|
|
190
|
+
toolName: input.toolName,
|
|
191
|
+
argumentsHash: input.argumentsHash,
|
|
192
|
+
status: "pending",
|
|
193
|
+
attempt,
|
|
194
|
+
version: (previous?.version ?? 0) + 1,
|
|
195
|
+
claimToken: randomUUID(),
|
|
196
|
+
createdAt: previous?.createdAt ?? timestamp(currentTime),
|
|
197
|
+
updatedAt: timestamp(currentTime),
|
|
198
|
+
expiresAt: timestamp(currentTime + ttl),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function expire(record, currentTime) {
|
|
202
|
+
if ((record.status !== "pending" && record.status !== "dispatched") || !record.expiresAt || Date.parse(record.expiresAt) > currentTime)
|
|
203
|
+
return record;
|
|
204
|
+
const status = record.status === "pending" ? "failed_retryable" : "unknown";
|
|
205
|
+
return freezeRecord({
|
|
206
|
+
...withoutClaim(record),
|
|
207
|
+
status,
|
|
208
|
+
version: record.version + 1,
|
|
209
|
+
failure: { code: "ERR_PRISM_TOOL_EFFECT_EXPIRED" },
|
|
210
|
+
updatedAt: timestamp(currentTime),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function requireClaim(record, input, statuses) {
|
|
214
|
+
if (!record || !statuses.includes(record.status) || record.version !== input.expectedVersion || record.claimToken !== input.claimToken)
|
|
215
|
+
throw conflict();
|
|
216
|
+
return record;
|
|
217
|
+
}
|
|
218
|
+
function withoutClaim(record) {
|
|
219
|
+
const { claimToken: _claimToken, expiresAt: _expiresAt, ...rest } = record;
|
|
220
|
+
return rest;
|
|
221
|
+
}
|
|
222
|
+
function validateKey(input) {
|
|
223
|
+
assertIdentityActive(input.identity);
|
|
224
|
+
validateOwnership(input.ownership);
|
|
225
|
+
assertIdentityMatchesOwnership(input.identity, input.ownership);
|
|
226
|
+
if (input.ownership.tenantId !== input.identity.tenantId ||
|
|
227
|
+
input.ownership.accountId !== input.identity.accountId ||
|
|
228
|
+
input.ownership.userId !== input.identity.userId)
|
|
229
|
+
throw conflict();
|
|
230
|
+
validateText(input.key, MAX_EFFECT_KEY_BYTES, "effect key");
|
|
231
|
+
validateText(input.sessionId, MAX_IDENTIFIER_BYTES, "session id");
|
|
232
|
+
validateText(input.runId, MAX_IDENTIFIER_BYTES, "run id");
|
|
233
|
+
validateText(input.toolCallId, MAX_IDENTIFIER_BYTES, "tool call id");
|
|
234
|
+
validateText(input.toolName, MAX_TOOL_NAME_BYTES, "tool name");
|
|
235
|
+
if (!/^[a-f0-9]{64}$/.test(input.argumentsHash))
|
|
236
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", "arguments hash is invalid");
|
|
237
|
+
}
|
|
238
|
+
function validateOwnership(ownership) {
|
|
239
|
+
if (!ownership.tenantId?.trim())
|
|
240
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_CONFLICT", "tool effect ownership is required");
|
|
241
|
+
for (const value of [ownership.tenantId, ownership.accountId, ownership.userId]) {
|
|
242
|
+
if (value !== undefined)
|
|
243
|
+
validateText(value, MAX_IDENTIFIER_BYTES, "ownership");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function assertMatches(record, input) {
|
|
247
|
+
if (record.key !== input.key ||
|
|
248
|
+
record.sessionId !== input.sessionId ||
|
|
249
|
+
record.runId !== input.runId ||
|
|
250
|
+
record.toolCallId !== input.toolCallId ||
|
|
251
|
+
record.toolName !== input.toolName ||
|
|
252
|
+
record.argumentsHash !== input.argumentsHash ||
|
|
253
|
+
!sameOwnership(record, input.ownership)) {
|
|
254
|
+
throw conflict();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function validateResult(result, input) {
|
|
258
|
+
if (result.toolCallId !== input.toolCallId || result.name !== input.toolName)
|
|
259
|
+
throw conflict();
|
|
260
|
+
return jsonSnapshot(result, MAX_RESULT_BYTES, "tool result");
|
|
261
|
+
}
|
|
262
|
+
function validateReference(reference) {
|
|
263
|
+
validateText(reference, MAX_REFERENCE_BYTES, "effect reference");
|
|
264
|
+
return reference;
|
|
265
|
+
}
|
|
266
|
+
function validateFailure(failure) {
|
|
267
|
+
validateText(failure.code, 128, "effect failure code");
|
|
268
|
+
return Object.freeze({
|
|
269
|
+
...failure,
|
|
270
|
+
...(failure.reference === undefined ? {} : { reference: validateReference(failure.reference) }),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function assertRecordSize(record) {
|
|
274
|
+
void jsonSnapshot(record, MAX_RECORD_BYTES, "tool effect record");
|
|
275
|
+
}
|
|
276
|
+
function jsonSnapshot(value, maxBytes, label) {
|
|
277
|
+
let text;
|
|
278
|
+
try {
|
|
279
|
+
text = JSON.stringify(value);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", `${label} must be JSON serializable`);
|
|
283
|
+
}
|
|
284
|
+
if (text === undefined || Buffer.byteLength(text) > maxBytes)
|
|
285
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", `${label} exceeds limits`);
|
|
286
|
+
return JSON.parse(text);
|
|
287
|
+
}
|
|
288
|
+
function freezeRecord(record) {
|
|
289
|
+
return freezeJson(jsonSnapshot(record, MAX_RECORD_BYTES, "tool effect record"));
|
|
290
|
+
}
|
|
291
|
+
function freezeJson(value) {
|
|
292
|
+
if (!value || typeof value !== "object")
|
|
293
|
+
return value;
|
|
294
|
+
for (const child of Object.values(value))
|
|
295
|
+
freezeJson(child);
|
|
296
|
+
return Object.freeze(value);
|
|
297
|
+
}
|
|
298
|
+
function owner(ownership) {
|
|
299
|
+
return {
|
|
300
|
+
tenantId: ownership.tenantId,
|
|
301
|
+
...(ownership.accountId === undefined ? {} : { accountId: ownership.accountId }),
|
|
302
|
+
...(ownership.userId === undefined ? {} : { userId: ownership.userId }),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function sameOwnership(left, right) {
|
|
306
|
+
return left.tenantId === right.tenantId && left.accountId === right.accountId && left.userId === right.userId;
|
|
307
|
+
}
|
|
308
|
+
function recordKey(input) {
|
|
309
|
+
return JSON.stringify([
|
|
310
|
+
input.identity.tenantId,
|
|
311
|
+
input.identity.accountId ?? "",
|
|
312
|
+
input.identity.userId ?? "",
|
|
313
|
+
input.identity.principal.id,
|
|
314
|
+
input.key,
|
|
315
|
+
]);
|
|
316
|
+
}
|
|
317
|
+
function isTerminal(status) {
|
|
318
|
+
return status === "completed" || status === "failed_terminal";
|
|
319
|
+
}
|
|
320
|
+
function timestamp(value) {
|
|
321
|
+
return new Date(value).toISOString();
|
|
322
|
+
}
|
|
323
|
+
function claimTtl(value) {
|
|
324
|
+
const ttl = value ?? DEFAULT_CLAIM_TTL_MS;
|
|
325
|
+
if (!Number.isSafeInteger(ttl) || ttl < 1 || ttl > HARD_CLAIM_TTL_MS)
|
|
326
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", "claim TTL exceeds limits");
|
|
327
|
+
return ttl;
|
|
328
|
+
}
|
|
329
|
+
function maxAttempts(value) {
|
|
330
|
+
const attempts = value ?? DEFAULT_MAX_ATTEMPTS;
|
|
331
|
+
if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > HARD_MAX_ATTEMPTS)
|
|
332
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", "effect attempts exceed limits");
|
|
333
|
+
return attempts;
|
|
334
|
+
}
|
|
335
|
+
function cleanupLimit(value) {
|
|
336
|
+
const limit = value ?? DEFAULT_CLEANUP_LIMIT;
|
|
337
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > HARD_CLEANUP_LIMIT)
|
|
338
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", "cleanup limit exceeds limits");
|
|
339
|
+
return limit;
|
|
340
|
+
}
|
|
341
|
+
function validateText(value, maxBytes, label) {
|
|
342
|
+
if (typeof value !== "string" || !value.trim() || Buffer.byteLength(value) > maxBytes)
|
|
343
|
+
throw new ToolEffectError("ERR_PRISM_TOOL_EFFECT_LIMIT", `${label} is required and bounded`);
|
|
344
|
+
}
|
|
345
|
+
function conflict() {
|
|
346
|
+
return new ToolEffectError("ERR_PRISM_TOOL_EFFECT_CONFLICT", "tool effect transition conflict");
|
|
347
|
+
}
|
|
348
|
+
function throwIfAborted(signal) {
|
|
349
|
+
if (signal?.aborted)
|
|
350
|
+
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=tool-effects.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Message, ToolResult } from "./contracts.js";
|
|
2
|
+
export declare const DEFAULT_TOOL_RESULT_FOLD_MIN_AGE_TURNS = 2;
|
|
3
|
+
export declare const DEFAULT_TOOL_RESULT_FOLD_MIN_BYTES = 4096;
|
|
4
|
+
export declare const DEFAULT_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES = 512;
|
|
5
|
+
export declare const HARD_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES = 4096;
|
|
6
|
+
export declare const TOOL_RESULT_FOLD_TURN_METADATA_KEY: "prismToolResultTurn";
|
|
7
|
+
export interface ToolResultFoldInput {
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
readonly runId: string;
|
|
10
|
+
readonly turn: number;
|
|
11
|
+
readonly toolCallId: string;
|
|
12
|
+
readonly toolName: string;
|
|
13
|
+
readonly text: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ToolResultFoldOptions {
|
|
16
|
+
readonly minAgeTurns?: number;
|
|
17
|
+
readonly minBytes?: number;
|
|
18
|
+
readonly maxSummaryBytes?: number;
|
|
19
|
+
readonly summarize: (input: ToolResultFoldInput) => Promise<string> | string;
|
|
20
|
+
}
|
|
21
|
+
export interface ResolvedToolResultFoldOptions {
|
|
22
|
+
readonly minAgeTurns: number;
|
|
23
|
+
readonly minBytes: number;
|
|
24
|
+
readonly maxSummaryBytes: number;
|
|
25
|
+
readonly summarize: (input: ToolResultFoldInput) => Promise<string> | string;
|
|
26
|
+
}
|
|
27
|
+
export interface FoldToolResultsContext {
|
|
28
|
+
readonly sessionId: string;
|
|
29
|
+
readonly runId: string;
|
|
30
|
+
readonly turn: number;
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
/** Run overrides agent; disabled when neither supplies `summarize`. */
|
|
34
|
+
export declare function resolveToolResultFold(run?: ToolResultFoldOptions, agent?: ToolResultFoldOptions): ResolvedToolResultFoldOptions | undefined;
|
|
35
|
+
/** Projection-only fold for history tool messages; does not mutate the input array. */
|
|
36
|
+
export declare function foldToolResultHistory(history: readonly Message[], options: ResolvedToolResultFoldOptions, context: FoldToolResultsContext): Promise<readonly Message[]>;
|
|
37
|
+
/** Projection-only fold for in-flight tool results before message conversion. */
|
|
38
|
+
export declare function foldToolResults(results: readonly ToolResult[], options: ResolvedToolResultFoldOptions, context: FoldToolResultsContext): Promise<readonly ToolResult[]>;
|
|
39
|
+
export declare function formatFoldedToolResult(summary: string): string;
|
|
40
|
+
export declare function foldedToolResultHeader(toolName: string, toolCallId: string, summary: string): string;
|