omk-agent-core 0.90.7 → 0.90.9
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/README.md +97 -1
- package/dist/agent-loop.d.ts +25 -2
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +524 -189
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +29 -7
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +81 -46
- package/dist/agent.js.map +1 -1
- package/dist/builtin-tool-resource-claims.d.ts +19 -0
- package/dist/builtin-tool-resource-claims.d.ts.map +1 -0
- package/dist/builtin-tool-resource-claims.js +200 -0
- package/dist/builtin-tool-resource-claims.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/node-resource-resolver.d.ts +42 -0
- package/dist/node-resource-resolver.d.ts.map +1 -0
- package/dist/node-resource-resolver.js +149 -0
- package/dist/node-resource-resolver.js.map +1 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +2 -0
- package/dist/node.js.map +1 -1
- package/dist/parallel-tool-batch.d.ts +12 -1
- package/dist/parallel-tool-batch.d.ts.map +1 -1
- package/dist/parallel-tool-batch.js +71 -49
- package/dist/parallel-tool-batch.js.map +1 -1
- package/dist/path-segments.d.ts +21 -1
- package/dist/path-segments.d.ts.map +1 -1
- package/dist/path-segments.js +91 -9
- package/dist/path-segments.js.map +1 -1
- package/dist/plain-data.d.ts +7 -0
- package/dist/plain-data.d.ts.map +1 -0
- package/dist/plain-data.js +70 -0
- package/dist/plain-data.js.map +1 -0
- package/dist/tool-dag-scheduler.d.ts +86 -0
- package/dist/tool-dag-scheduler.d.ts.map +1 -0
- package/dist/tool-dag-scheduler.js +171 -0
- package/dist/tool-dag-scheduler.js.map +1 -0
- package/dist/tool-execution-boundary.d.ts +52 -0
- package/dist/tool-execution-boundary.d.ts.map +1 -0
- package/dist/tool-execution-boundary.js +185 -0
- package/dist/tool-execution-boundary.js.map +1 -0
- package/dist/tool-resource-claims.d.ts +31 -0
- package/dist/tool-resource-claims.d.ts.map +1 -0
- package/dist/tool-resource-claims.js +128 -0
- package/dist/tool-resource-claims.js.map +1 -0
- package/dist/tool-timeout.d.ts +96 -0
- package/dist/tool-timeout.d.ts.map +1 -0
- package/dist/tool-timeout.js +173 -0
- package/dist/tool-timeout.js.map +1 -0
- package/dist/tool-transcript-integrity.d.ts +65 -0
- package/dist/tool-transcript-integrity.d.ts.map +1 -0
- package/dist/tool-transcript-integrity.js +223 -0
- package/dist/tool-transcript-integrity.js.map +1 -0
- package/dist/types.d.ts +219 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +50 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, browser-safe transcript integrity inspector and repair for the tool-call
|
|
3
|
+
* transcripts produced by the agent loop.
|
|
4
|
+
*
|
|
5
|
+
* The inspector detects five classes of structural corruption:
|
|
6
|
+
* - `missing_result`: a tool call with no matching tool result
|
|
7
|
+
* - `duplicate_result`: two or more tool results for the same call id
|
|
8
|
+
* - `orphan_result`: a tool result whose call id was never emitted
|
|
9
|
+
* - `duplicate_call_id`: two or more tool calls sharing an id
|
|
10
|
+
* - `interleaved_non_result`: a non-result message breaks the contiguous run of
|
|
11
|
+
* results that must follow an assistant message's tool calls (including a
|
|
12
|
+
* result that arrives before its call, after a user/custom boundary, or at the
|
|
13
|
+
* start of the transcript)
|
|
14
|
+
*
|
|
15
|
+
* IDs are accounted globally, so an orphan or duplicate is caught regardless of
|
|
16
|
+
* where it appears. Repair is intentionally conservative: it only appends
|
|
17
|
+
* synthetic results for unambiguous missing tail calls. Any duplicate, orphan,
|
|
18
|
+
* interleaving, or mid-transcript gap fails closed.
|
|
19
|
+
*
|
|
20
|
+
* This module uses no platform APIs (no `process`, fs, or timers) so it is safe
|
|
21
|
+
* to run in a browser.
|
|
22
|
+
*/
|
|
23
|
+
import { createImmutableSnapshot } from "./plain-data.js";
|
|
24
|
+
import { createToolResultEnvelope } from "./types.js";
|
|
25
|
+
/** Thrown by {@link repairTranscriptIntegrity} when a transcript cannot be safely repaired. */
|
|
26
|
+
export class TranscriptIntegrityError extends Error {
|
|
27
|
+
report;
|
|
28
|
+
constructor(message, report) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "TranscriptIntegrityError";
|
|
31
|
+
this.report = report;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isAssistantMessage(message) {
|
|
35
|
+
return message.role === "assistant";
|
|
36
|
+
}
|
|
37
|
+
function isToolResultMessage(message) {
|
|
38
|
+
return message.role === "toolResult";
|
|
39
|
+
}
|
|
40
|
+
function isToolCallBlock(block) {
|
|
41
|
+
return block?.type === "toolCall";
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Inspect a transcript and report every detected integrity issue. Pure: does not
|
|
45
|
+
* mutate the input and uses no platform APIs.
|
|
46
|
+
*
|
|
47
|
+
* IDs are accounted globally first (catching duplicates, orphans, and missing
|
|
48
|
+
* results anywhere in the transcript), then a single left-to-right pass checks
|
|
49
|
+
* the ordering invariant that an assistant message's tool calls must be followed
|
|
50
|
+
* by a contiguous run of their results with no intervening non-result message.
|
|
51
|
+
*/
|
|
52
|
+
export function inspectTranscriptIntegrity(messages) {
|
|
53
|
+
const issues = [];
|
|
54
|
+
const callIdCount = new Map();
|
|
55
|
+
const resultIdCount = new Map();
|
|
56
|
+
const callToolName = new Map();
|
|
57
|
+
for (const message of messages) {
|
|
58
|
+
if (isAssistantMessage(message)) {
|
|
59
|
+
for (const block of message.content) {
|
|
60
|
+
if (isToolCallBlock(block)) {
|
|
61
|
+
callIdCount.set(block.id, (callIdCount.get(block.id) ?? 0) + 1);
|
|
62
|
+
if (!callToolName.has(block.id)) {
|
|
63
|
+
callToolName.set(block.id, block.name);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (isToolResultMessage(message)) {
|
|
69
|
+
resultIdCount.set(message.toolCallId, (resultIdCount.get(message.toolCallId) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
for (const [id, count] of callIdCount) {
|
|
73
|
+
if (count > 1) {
|
|
74
|
+
issues.push({ kind: "duplicate_call_id", toolCallId: id, toolName: callToolName.get(id) });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const [id, count] of resultIdCount) {
|
|
78
|
+
if (count > 1) {
|
|
79
|
+
issues.push({ kind: "duplicate_result", toolCallId: id });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const [id] of resultIdCount) {
|
|
83
|
+
if (!callIdCount.has(id)) {
|
|
84
|
+
issues.push({ kind: "orphan_result", toolCallId: id });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const [id] of callIdCount) {
|
|
88
|
+
if (!resultIdCount.has(id)) {
|
|
89
|
+
issues.push({ kind: "missing_result", toolCallId: id, toolName: callToolName.get(id) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const pending = new Map();
|
|
93
|
+
let resultsRegion = false;
|
|
94
|
+
for (const message of messages) {
|
|
95
|
+
if (isAssistantMessage(message)) {
|
|
96
|
+
const calls = message.content.filter(isToolCallBlock);
|
|
97
|
+
if (calls.length > 0) {
|
|
98
|
+
// A new assistant message with tool calls while previous calls are
|
|
99
|
+
// still unresolved means a non-result interrupted the prior region.
|
|
100
|
+
if (pending.size > 0) {
|
|
101
|
+
for (const [id, name] of pending) {
|
|
102
|
+
issues.push({ kind: "interleaved_non_result", toolCallId: id, toolName: name });
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
for (const call of calls) {
|
|
107
|
+
pending.set(call.id, call.name);
|
|
108
|
+
}
|
|
109
|
+
resultsRegion = true;
|
|
110
|
+
}
|
|
111
|
+
else if (resultsRegion && pending.size > 0) {
|
|
112
|
+
for (const [id, name] of pending) {
|
|
113
|
+
issues.push({ kind: "interleaved_non_result", toolCallId: id, toolName: name });
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (isToolResultMessage(message)) {
|
|
119
|
+
if (resultsRegion && pending.has(message.toolCallId)) {
|
|
120
|
+
pending.delete(message.toolCallId);
|
|
121
|
+
if (pending.size === 0) {
|
|
122
|
+
resultsRegion = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (callIdCount.has(message.toolCallId)) {
|
|
126
|
+
// Known call id arriving outside its contiguous results region:
|
|
127
|
+
// orphan-at-start, after-user, or assistant -> non-result -> result.
|
|
128
|
+
issues.push({ kind: "interleaved_non_result", toolCallId: message.toolCallId });
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
// Unknown-id orphans are already reported above; do not double report.
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// user or custom non-result message: breaks an open results region.
|
|
135
|
+
if (resultsRegion && pending.size > 0) {
|
|
136
|
+
for (const [id, name] of pending) {
|
|
137
|
+
issues.push({ kind: "interleaved_non_result", toolCallId: id, toolName: name });
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const frozenIssues = Object.freeze(issues.map((issue) => Object.freeze({ ...issue })));
|
|
144
|
+
return Object.freeze({ ok: frozenIssues.length === 0, issues: frozenIssues });
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create a synthetic terminal tool result. Shared by transcript repair and the
|
|
148
|
+
* agent-loop abort closure so the disposition of an unresolved call is encoded
|
|
149
|
+
* identically everywhere. The `details.omk` envelope marks the artifact as
|
|
150
|
+
* synthetic (`executionStarted: false`): it closes the provider transcript and
|
|
151
|
+
* never claims the tool actually ran.
|
|
152
|
+
*/
|
|
153
|
+
export function createSyntheticToolResult(toolCallId, toolName, reason, timestamp = Date.now(), disposition = "aborted") {
|
|
154
|
+
const envelope = createToolResultEnvelope({
|
|
155
|
+
synthetic: true,
|
|
156
|
+
disposition,
|
|
157
|
+
reason,
|
|
158
|
+
executionStarted: false,
|
|
159
|
+
});
|
|
160
|
+
return createImmutableSnapshot({
|
|
161
|
+
role: "toolResult",
|
|
162
|
+
toolCallId,
|
|
163
|
+
toolName,
|
|
164
|
+
content: [{ type: "text", text: reason }],
|
|
165
|
+
details: { omk: envelope },
|
|
166
|
+
isError: true,
|
|
167
|
+
timestamp,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Repair a transcript by appending synthetic results for unambiguous missing
|
|
172
|
+
* tail calls. Fails closed (throws {@link TranscriptIntegrityError}) for any
|
|
173
|
+
* duplicate, orphan, interleaving, or mid-transcript gap. Idempotent: a second
|
|
174
|
+
* call on already-repaired input returns an equal copy with no new messages.
|
|
175
|
+
*/
|
|
176
|
+
export function repairTranscriptIntegrity(messages, reason) {
|
|
177
|
+
const report = inspectTranscriptIntegrity(messages);
|
|
178
|
+
for (const issue of report.issues) {
|
|
179
|
+
if (issue.kind !== "missing_result") {
|
|
180
|
+
throw new TranscriptIntegrityError(`Cannot repair transcript: detected ${issue.kind} for tool call ${issue.toolCallId}`, report);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const missingIds = new Set(report.issues.map((issue) => issue.toolCallId));
|
|
184
|
+
if (missingIds.size === 0) {
|
|
185
|
+
return messages.slice();
|
|
186
|
+
}
|
|
187
|
+
let lastAssistantIndex = -1;
|
|
188
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
189
|
+
if (isAssistantMessage(messages[i])) {
|
|
190
|
+
lastAssistantIndex = i;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (lastAssistantIndex === -1) {
|
|
195
|
+
throw new TranscriptIntegrityError("Cannot repair transcript: missing results without an assistant message", report);
|
|
196
|
+
}
|
|
197
|
+
const lastAssistant = messages[lastAssistantIndex];
|
|
198
|
+
if (!isAssistantMessage(lastAssistant)) {
|
|
199
|
+
throw new TranscriptIntegrityError("Cannot repair transcript: trailing message is not assistant", report);
|
|
200
|
+
}
|
|
201
|
+
const tailCalls = lastAssistant.content.filter(isToolCallBlock);
|
|
202
|
+
const tailCallIds = new Set(tailCalls.map((call) => call.id));
|
|
203
|
+
for (const id of missingIds) {
|
|
204
|
+
if (!tailCallIds.has(id)) {
|
|
205
|
+
throw new TranscriptIntegrityError(`Cannot repair transcript: missing result for ${id} is not in the trailing assistant message`, report);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
for (let i = lastAssistantIndex + 1; i < messages.length; i++) {
|
|
209
|
+
const message = messages[i];
|
|
210
|
+
if (!isToolResultMessage(message) || !tailCallIds.has(message.toolCallId)) {
|
|
211
|
+
throw new TranscriptIntegrityError("Cannot repair transcript: non-result message after the trailing assistant message", report);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const text = reason ?? "Tool result missing; synthesized by transcript repair";
|
|
215
|
+
const repaired = messages.slice();
|
|
216
|
+
for (const call of tailCalls) {
|
|
217
|
+
if (missingIds.has(call.id)) {
|
|
218
|
+
repaired.push(createSyntheticToolResult(call.id, call.name, text));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return repaired;
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=tool-transcript-integrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-transcript-integrity.js","sourceRoot":"","sources":["../src/tool-transcript-integrity.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure, browser-safe transcript integrity inspector and repair for the tool-call\n * transcripts produced by the agent loop.\n *\n * The inspector detects five classes of structural corruption:\n * - `missing_result`: a tool call with no matching tool result\n * - `duplicate_result`: two or more tool results for the same call id\n * - `orphan_result`: a tool result whose call id was never emitted\n * - `duplicate_call_id`: two or more tool calls sharing an id\n * - `interleaved_non_result`: a non-result message breaks the contiguous run of\n * results that must follow an assistant message's tool calls (including a\n * result that arrives before its call, after a user/custom boundary, or at the\n * start of the transcript)\n *\n * IDs are accounted globally, so an orphan or duplicate is caught regardless of\n * where it appears. Repair is intentionally conservative: it only appends\n * synthetic results for unambiguous missing tail calls. Any duplicate, orphan,\n * interleaving, or mid-transcript gap fails closed.\n *\n * This module uses no platform APIs (no `process`, fs, or timers) so it is safe\n * to run in a browser.\n */\n\nimport type { AssistantMessage, ToolCall, ToolResultMessage } from \"omk-ai\";\nimport { createImmutableSnapshot } from \"./plain-data.ts\";\nimport { type AgentMessage, createToolResultEnvelope } from \"./types.ts\";\n\nexport type TranscriptIntegrityIssueKind =\n\t| \"missing_result\"\n\t| \"duplicate_result\"\n\t| \"orphan_result\"\n\t| \"duplicate_call_id\"\n\t| \"interleaved_non_result\";\n\nexport interface TranscriptIntegrityIssue {\n\treadonly kind: TranscriptIntegrityIssueKind;\n\treadonly toolCallId: string;\n\treadonly toolName?: string;\n}\n\nexport interface TranscriptIntegrityReport {\n\treadonly ok: boolean;\n\treadonly issues: readonly TranscriptIntegrityIssue[];\n}\n\n/** Thrown by {@link repairTranscriptIntegrity} when a transcript cannot be safely repaired. */\nexport class TranscriptIntegrityError extends Error {\n\treadonly report: TranscriptIntegrityReport;\n\tconstructor(message: string, report: TranscriptIntegrityReport) {\n\t\tsuper(message);\n\t\tthis.name = \"TranscriptIntegrityError\";\n\t\tthis.report = report;\n\t}\n}\n\nfunction isAssistantMessage(message: AgentMessage): message is AssistantMessage {\n\treturn message.role === \"assistant\";\n}\n\nfunction isToolResultMessage(message: AgentMessage): message is ToolResultMessage {\n\treturn message.role === \"toolResult\";\n}\n\nfunction isToolCallBlock(block: unknown): block is ToolCall {\n\treturn (block as { type?: string } | null)?.type === \"toolCall\";\n}\n\n/**\n * Inspect a transcript and report every detected integrity issue. Pure: does not\n * mutate the input and uses no platform APIs.\n *\n * IDs are accounted globally first (catching duplicates, orphans, and missing\n * results anywhere in the transcript), then a single left-to-right pass checks\n * the ordering invariant that an assistant message's tool calls must be followed\n * by a contiguous run of their results with no intervening non-result message.\n */\nexport function inspectTranscriptIntegrity(messages: readonly AgentMessage[]): TranscriptIntegrityReport {\n\tconst issues: TranscriptIntegrityIssue[] = [];\n\n\tconst callIdCount = new Map<string, number>();\n\tconst resultIdCount = new Map<string, number>();\n\tconst callToolName = new Map<string, string>();\n\n\tfor (const message of messages) {\n\t\tif (isAssistantMessage(message)) {\n\t\t\tfor (const block of message.content) {\n\t\t\t\tif (isToolCallBlock(block)) {\n\t\t\t\t\tcallIdCount.set(block.id, (callIdCount.get(block.id) ?? 0) + 1);\n\t\t\t\t\tif (!callToolName.has(block.id)) {\n\t\t\t\t\t\tcallToolName.set(block.id, block.name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isToolResultMessage(message)) {\n\t\t\tresultIdCount.set(message.toolCallId, (resultIdCount.get(message.toolCallId) ?? 0) + 1);\n\t\t}\n\t}\n\n\tfor (const [id, count] of callIdCount) {\n\t\tif (count > 1) {\n\t\t\tissues.push({ kind: \"duplicate_call_id\", toolCallId: id, toolName: callToolName.get(id) });\n\t\t}\n\t}\n\tfor (const [id, count] of resultIdCount) {\n\t\tif (count > 1) {\n\t\t\tissues.push({ kind: \"duplicate_result\", toolCallId: id });\n\t\t}\n\t}\n\tfor (const [id] of resultIdCount) {\n\t\tif (!callIdCount.has(id)) {\n\t\t\tissues.push({ kind: \"orphan_result\", toolCallId: id });\n\t\t}\n\t}\n\tfor (const [id] of callIdCount) {\n\t\tif (!resultIdCount.has(id)) {\n\t\t\tissues.push({ kind: \"missing_result\", toolCallId: id, toolName: callToolName.get(id) });\n\t\t}\n\t}\n\n\tconst pending = new Map<string, string>();\n\tlet resultsRegion = false;\n\n\tfor (const message of messages) {\n\t\tif (isAssistantMessage(message)) {\n\t\t\tconst calls = message.content.filter(isToolCallBlock);\n\t\t\tif (calls.length > 0) {\n\t\t\t\t// A new assistant message with tool calls while previous calls are\n\t\t\t\t// still unresolved means a non-result interrupted the prior region.\n\t\t\t\tif (pending.size > 0) {\n\t\t\t\t\tfor (const [id, name] of pending) {\n\t\t\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: id, toolName: name });\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfor (const call of calls) {\n\t\t\t\t\tpending.set(call.id, call.name);\n\t\t\t\t}\n\t\t\t\tresultsRegion = true;\n\t\t\t} else if (resultsRegion && pending.size > 0) {\n\t\t\t\tfor (const [id, name] of pending) {\n\t\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: id, toolName: name });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else if (isToolResultMessage(message)) {\n\t\t\tif (resultsRegion && pending.has(message.toolCallId)) {\n\t\t\t\tpending.delete(message.toolCallId);\n\t\t\t\tif (pending.size === 0) {\n\t\t\t\t\tresultsRegion = false;\n\t\t\t\t}\n\t\t\t} else if (callIdCount.has(message.toolCallId)) {\n\t\t\t\t// Known call id arriving outside its contiguous results region:\n\t\t\t\t// orphan-at-start, after-user, or assistant -> non-result -> result.\n\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: message.toolCallId });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t// Unknown-id orphans are already reported above; do not double report.\n\t\t} else {\n\t\t\t// user or custom non-result message: breaks an open results region.\n\t\t\tif (resultsRegion && pending.size > 0) {\n\t\t\t\tfor (const [id, name] of pending) {\n\t\t\t\t\tissues.push({ kind: \"interleaved_non_result\", toolCallId: id, toolName: name });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst frozenIssues = Object.freeze(issues.map((issue) => Object.freeze({ ...issue })));\n\treturn Object.freeze({ ok: frozenIssues.length === 0, issues: frozenIssues });\n}\n\n/**\n * Create a synthetic terminal tool result. Shared by transcript repair and the\n * agent-loop abort closure so the disposition of an unresolved call is encoded\n * identically everywhere. The `details.omk` envelope marks the artifact as\n * synthetic (`executionStarted: false`): it closes the provider transcript and\n * never claims the tool actually ran.\n */\nexport function createSyntheticToolResult(\n\ttoolCallId: string,\n\ttoolName: string,\n\treason: string,\n\ttimestamp: number = Date.now(),\n\tdisposition: \"aborted\" | \"skipped\" = \"aborted\",\n): ToolResultMessage {\n\tconst envelope = createToolResultEnvelope({\n\t\tsynthetic: true,\n\t\tdisposition,\n\t\treason,\n\t\texecutionStarted: false,\n\t});\n\treturn createImmutableSnapshot({\n\t\trole: \"toolResult\",\n\t\ttoolCallId,\n\t\ttoolName,\n\t\tcontent: [{ type: \"text\", text: reason }],\n\t\tdetails: { omk: envelope },\n\t\tisError: true,\n\t\ttimestamp,\n\t});\n}\n\n/**\n * Repair a transcript by appending synthetic results for unambiguous missing\n * tail calls. Fails closed (throws {@link TranscriptIntegrityError}) for any\n * duplicate, orphan, interleaving, or mid-transcript gap. Idempotent: a second\n * call on already-repaired input returns an equal copy with no new messages.\n */\nexport function repairTranscriptIntegrity(messages: readonly AgentMessage[], reason?: string): AgentMessage[] {\n\tconst report = inspectTranscriptIntegrity(messages);\n\n\tfor (const issue of report.issues) {\n\t\tif (issue.kind !== \"missing_result\") {\n\t\t\tthrow new TranscriptIntegrityError(\n\t\t\t\t`Cannot repair transcript: detected ${issue.kind} for tool call ${issue.toolCallId}`,\n\t\t\t\treport,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst missingIds = new Set(report.issues.map((issue) => issue.toolCallId));\n\tif (missingIds.size === 0) {\n\t\treturn messages.slice();\n\t}\n\n\tlet lastAssistantIndex = -1;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tif (isAssistantMessage(messages[i])) {\n\t\t\tlastAssistantIndex = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (lastAssistantIndex === -1) {\n\t\tthrow new TranscriptIntegrityError(\n\t\t\t\"Cannot repair transcript: missing results without an assistant message\",\n\t\t\treport,\n\t\t);\n\t}\n\n\tconst lastAssistant = messages[lastAssistantIndex];\n\tif (!isAssistantMessage(lastAssistant)) {\n\t\tthrow new TranscriptIntegrityError(\"Cannot repair transcript: trailing message is not assistant\", report);\n\t}\n\tconst tailCalls = lastAssistant.content.filter(isToolCallBlock);\n\tconst tailCallIds = new Set(tailCalls.map((call) => call.id));\n\tfor (const id of missingIds) {\n\t\tif (!tailCallIds.has(id)) {\n\t\t\tthrow new TranscriptIntegrityError(\n\t\t\t\t`Cannot repair transcript: missing result for ${id} is not in the trailing assistant message`,\n\t\t\t\treport,\n\t\t\t);\n\t\t}\n\t}\n\n\tfor (let i = lastAssistantIndex + 1; i < messages.length; i++) {\n\t\tconst message = messages[i];\n\t\tif (!isToolResultMessage(message) || !tailCallIds.has(message.toolCallId)) {\n\t\t\tthrow new TranscriptIntegrityError(\n\t\t\t\t\"Cannot repair transcript: non-result message after the trailing assistant message\",\n\t\t\t\treport,\n\t\t\t);\n\t\t}\n\t}\n\n\tconst text = reason ?? \"Tool result missing; synthesized by transcript repair\";\n\tconst repaired = messages.slice();\n\tfor (const call of tailCalls) {\n\t\tif (missingIds.has(call.id)) {\n\t\t\trepaired.push(createSyntheticToolResult(call.id, call.name, text));\n\t\t}\n\t}\n\treturn repaired;\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,20 @@ export type StreamFn = (...args: Parameters<typeof streamSimple>) => ReturnType<
|
|
|
19
19
|
* while tool-result message artifacts are emitted later in assistant source order.
|
|
20
20
|
*/
|
|
21
21
|
export type ToolExecutionMode = "sequential" | "parallel";
|
|
22
|
+
/**
|
|
23
|
+
* Tool-call scheduler selection for a single assistant turn's tool batch.
|
|
24
|
+
*
|
|
25
|
+
* - `"waves-v1"` (default): the original contiguous-wave scheduler
|
|
26
|
+
* (`partitionToolBatchWaves`). Established behavior and the rollback target.
|
|
27
|
+
* - `"dag-v2"`: the deterministic resource-claim DAG scheduler
|
|
28
|
+
* (`scheduleDagLevels`). Active only when explicitly selected. It resolves
|
|
29
|
+
* per-call resource claims and groups conflict-free calls into source-index
|
|
30
|
+
* DAG levels so a conflicting call no longer head-of-line-blocks independent
|
|
31
|
+
* later calls (e.g. `write x, write x, write y` schedules as `[[0, 2], [1]]`).
|
|
32
|
+
* Final tool results are buffered globally and emitted in original source
|
|
33
|
+
* order.
|
|
34
|
+
*/
|
|
35
|
+
export type ToolSchedulerKind = "waves-v1" | "dag-v2";
|
|
22
36
|
/**
|
|
23
37
|
* Controls how many queued user messages are injected when the agent loop reaches a queue drain point.
|
|
24
38
|
*
|
|
@@ -220,17 +234,69 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
220
234
|
* Default: "parallel"
|
|
221
235
|
*/
|
|
222
236
|
toolExecution?: ToolExecutionMode;
|
|
237
|
+
/**
|
|
238
|
+
* Default per-tool execution timeout in milliseconds applied to every tool
|
|
239
|
+
* call that does not resolve a more specific timeout.
|
|
240
|
+
*
|
|
241
|
+
* This is deliberately named to avoid colliding with the inherited
|
|
242
|
+
* {@link SimpleStreamOptions.timeoutMs} (the provider request timeout).
|
|
243
|
+
* Absent, non-finite, or non-positive values disable the tool timeout and
|
|
244
|
+
* preserve the current unbounded execution behavior.
|
|
245
|
+
*/
|
|
246
|
+
toolTimeoutMs?: number;
|
|
247
|
+
/**
|
|
248
|
+
* Per-tool-name execution timeouts in milliseconds. An entry here overrides
|
|
249
|
+
* {@link toolTimeoutMs} for that tool name, and is itself overridden by a
|
|
250
|
+
* per-tool {@link AgentTool.timeoutMs}. Absent, non-finite, or non-positive
|
|
251
|
+
* entries disable the timeout for that name.
|
|
252
|
+
*/
|
|
253
|
+
toolTimeouts?: Record<string, number>;
|
|
254
|
+
/**
|
|
255
|
+
* Tool-call scheduler. Defaults to `"waves-v1"` (the established
|
|
256
|
+
* contiguous-wave scheduler). Set to `"dag-v2"` to opt into the
|
|
257
|
+
* deterministic resource-claim DAG scheduler. The v1 path and its exports
|
|
258
|
+
* are unchanged when this is unset or `"waves-v1"`.
|
|
259
|
+
*/
|
|
260
|
+
toolScheduler?: ToolSchedulerKind;
|
|
261
|
+
/**
|
|
262
|
+
* dag-v2 only. Optional positive width cap. When set, each DAG level is
|
|
263
|
+
* split into deterministic contiguous chunks of at most this many calls
|
|
264
|
+
* (preserving source order) so a wide conflict-free level does not fan out
|
|
265
|
+
* unbounded. Absent, non-finite, or non-positive values leave each level
|
|
266
|
+
* whole. No effect on the default `"waves-v1"` scheduler.
|
|
267
|
+
*/
|
|
268
|
+
maxToolConcurrency?: number;
|
|
269
|
+
/**
|
|
270
|
+
* dag-v2 only. When `false` (default), an extension/custom tool with
|
|
271
|
+
* `executionMode: "parallel"` and no resource claims is treated as freely
|
|
272
|
+
* parallel (compatibility). When `true`, such tools are treated as
|
|
273
|
+
* exclusive (run alone). Unknown tools and bash are always exclusive
|
|
274
|
+
* regardless of this flag. No effect on the default `"waves-v1"` scheduler.
|
|
275
|
+
*/
|
|
276
|
+
strictExtensionClaims?: boolean;
|
|
223
277
|
/** Working directory for path-scoped parallel tool batch checks (read/write/edit). */
|
|
224
278
|
cwd?: string;
|
|
279
|
+
/**
|
|
280
|
+
* dag-v2 only. Optional platform identity resolver used to detect
|
|
281
|
+
* symlink/hardlink/drive/UNC path aliases when scheduling resource claims.
|
|
282
|
+
* Absent, the scheduler uses lexical canonicalization only (browser-safe).
|
|
283
|
+
* Resolver failure isolates the call as exclusive (fail closed).
|
|
284
|
+
*/
|
|
285
|
+
resourceKeyResolver?: ResourceKeyResolver;
|
|
286
|
+
/**
|
|
287
|
+
* Execution policy defaults for every tool call in this run. Only
|
|
288
|
+
* `lateSettlement` is consumed today; it defaults to `"audit"`.
|
|
289
|
+
*/
|
|
290
|
+
toolExecutionPolicy?: Partial<ToolExecutionPolicy>;
|
|
225
291
|
/**
|
|
226
292
|
* Called before a tool is executed, after arguments have been validated.
|
|
227
293
|
*
|
|
228
294
|
* Return `{ block: true }` to prevent execution. The loop emits an error tool result instead.
|
|
229
|
-
* The hook
|
|
295
|
+
* The runtime bounds the hook by the parent abort signal and ignores a late settlement.
|
|
230
296
|
*/
|
|
231
297
|
beforeToolCall?: (context: BeforeToolCallContext, signal?: AbortSignal) => Promise<BeforeToolCallResult | undefined>;
|
|
232
298
|
/**
|
|
233
|
-
* Called after
|
|
299
|
+
* Called after execution but skipped for an immutable committed timeout/abort terminal.
|
|
234
300
|
*
|
|
235
301
|
* Return an `AfterToolCallResult` to override parts of the executed tool result:
|
|
236
302
|
* - `content` replaces the full content array
|
|
@@ -239,7 +305,7 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
239
305
|
* - `terminate` replaces the early-termination hint
|
|
240
306
|
*
|
|
241
307
|
* Any omitted fields keep their original values. No deep merge is performed.
|
|
242
|
-
* The hook
|
|
308
|
+
* The runtime bounds the hook by the parent abort signal and ignores a late settlement.
|
|
243
309
|
*/
|
|
244
310
|
afterToolCall?: (context: AfterToolCallContext, signal?: AbortSignal) => Promise<AfterToolCallResult | undefined>;
|
|
245
311
|
}
|
|
@@ -317,6 +383,45 @@ export interface AgentToolResult<T> {
|
|
|
317
383
|
}
|
|
318
384
|
/** Callback used by tools to stream partial execution updates. */
|
|
319
385
|
export type AgentToolUpdateCallback<T = any> = (partialResult: AgentToolResult<T>) => void;
|
|
386
|
+
/** Access mode used by a tool resource claim. */
|
|
387
|
+
export type ResourceAccess = "read" | "write" | "exclusive";
|
|
388
|
+
/** Access modes valid for path claims and built-in read/write resources. */
|
|
389
|
+
export type ToolResourceAccess = Extract<ResourceAccess, "read" | "write">;
|
|
390
|
+
/**
|
|
391
|
+
* A resource touched by one tool call.
|
|
392
|
+
*
|
|
393
|
+
* Path claims conflict on lexical parent/child overlap. Other claim kinds
|
|
394
|
+
* conflict when their keys are equal. `exclusive` access conflicts with every
|
|
395
|
+
* call, regardless of resource kind or key.
|
|
396
|
+
*/
|
|
397
|
+
export type ToolResourceClaim = {
|
|
398
|
+
kind: "path";
|
|
399
|
+
key: string;
|
|
400
|
+
access: ToolResourceAccess;
|
|
401
|
+
/** Canonical real-path identity (symlink-resolved), when a resolver is injected. */
|
|
402
|
+
realKey?: string;
|
|
403
|
+
/** `dev:ino` identity for an existing file, when a resolver is injected. */
|
|
404
|
+
inodeKey?: string;
|
|
405
|
+
} | {
|
|
406
|
+
kind: "session" | "terminal" | "network" | "global";
|
|
407
|
+
key: string;
|
|
408
|
+
access: ResourceAccess;
|
|
409
|
+
};
|
|
410
|
+
/**
|
|
411
|
+
* Resource contract returned by {@link AgentTool.resourceClaims}.
|
|
412
|
+
*
|
|
413
|
+
* Return `"exclusive"` when the call must run alone, or a non-empty claim
|
|
414
|
+
* list. The dag-v2 scheduler fails closed to exclusive when a resolver throws,
|
|
415
|
+
* rejects, or returns an empty or malformed claim list.
|
|
416
|
+
*/
|
|
417
|
+
export type ToolResourceClaims = readonly ToolResourceClaim[] | "exclusive";
|
|
418
|
+
/** Context passed to {@link AgentTool.resourceClaims}. */
|
|
419
|
+
export interface ToolResourceClaimsContext {
|
|
420
|
+
/** Working directory used by the scheduler for this call batch. */
|
|
421
|
+
cwd: string;
|
|
422
|
+
/** Provider-supplied id of the tool call being scheduled. */
|
|
423
|
+
toolCallId: string;
|
|
424
|
+
}
|
|
320
425
|
/** Tool definition used by the agent runtime. */
|
|
321
426
|
export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any> extends Tool<TParameters> {
|
|
322
427
|
/** Human-readable label for UI display. */
|
|
@@ -336,7 +441,105 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
336
441
|
* If omitted, the default execution mode applies.
|
|
337
442
|
*/
|
|
338
443
|
executionMode?: ToolExecutionMode;
|
|
444
|
+
/**
|
|
445
|
+
* dag-v2 resource contract for this tool call.
|
|
446
|
+
*
|
|
447
|
+
* The resolver receives the raw call arguments and scheduler context before
|
|
448
|
+
* execution starts. It may return synchronously or asynchronously. Throwing,
|
|
449
|
+
* rejecting, returning malformed data, or returning no usable claim fails
|
|
450
|
+
* closed to exclusive scheduling. This field has no effect on waves-v1.
|
|
451
|
+
*/
|
|
452
|
+
resourceClaims?: (args: unknown, context: ToolResourceClaimsContext) => ToolResourceClaims | Promise<ToolResourceClaims>;
|
|
453
|
+
/**
|
|
454
|
+
* Optional per-call execution timeout in milliseconds.
|
|
455
|
+
*
|
|
456
|
+
* When positive, the tool's `execute` promise is raced against this timeout at
|
|
457
|
+
* the shared execution chokepoint. If the timeout elapses first, the loop
|
|
458
|
+
* commits an immediate terminal timeout result and aborts the tool's child
|
|
459
|
+
* `AbortSignal`, so a tool that ignores the signal cannot stall the run.
|
|
460
|
+
*
|
|
461
|
+
* Precedence: this value overrides `AgentLoopConfig.toolTimeouts[name]`, which
|
|
462
|
+
* overrides `AgentLoopConfig.toolTimeoutMs`. Absent, non-finite, or
|
|
463
|
+
* non-positive values disable the timeout and preserve current behavior.
|
|
464
|
+
*/
|
|
465
|
+
timeoutMs?: number;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Terminal disposition of one tool call in a transcript (six-state model).
|
|
469
|
+
*
|
|
470
|
+
* - `completed`: the tool ran and returned a result.
|
|
471
|
+
* - `failed`: the tool ran and threw/rejected, or could not be prepared.
|
|
472
|
+
* - `blocked`: a policy hook prevented execution.
|
|
473
|
+
* - `aborted`: the run's abort terminated the call (started or unstarted).
|
|
474
|
+
* - `timeout`: the per-call timeout terminated the call.
|
|
475
|
+
* - `skipped`: the scheduler closed the call without ever starting it.
|
|
476
|
+
*/
|
|
477
|
+
export type ToolCallDisposition = "completed" | "failed" | "blocked" | "aborted" | "timeout" | "skipped";
|
|
478
|
+
/** Schema tag for the {@link ToolResultEnvelope} attached to terminal tool results. */
|
|
479
|
+
export declare const TOOL_RESULT_ENVELOPE_SCHEMA: "tool-result/v2";
|
|
480
|
+
/**
|
|
481
|
+
* Model-invisible disposition envelope stamped at `details.omk` on every
|
|
482
|
+
* terminal tool result. `synthetic` means the result artifact was fabricated
|
|
483
|
+
* by the runtime and does not represent the tool's returned details;
|
|
484
|
+
* `executionStarted` records whether `AgentTool.execute` actually began.
|
|
485
|
+
*/
|
|
486
|
+
export interface ToolResultEnvelope {
|
|
487
|
+
schema: typeof TOOL_RESULT_ENVELOPE_SCHEMA;
|
|
488
|
+
synthetic: boolean;
|
|
489
|
+
disposition: ToolCallDisposition;
|
|
490
|
+
reason?: string;
|
|
491
|
+
timeoutMs?: number;
|
|
492
|
+
executionStarted: boolean;
|
|
493
|
+
}
|
|
494
|
+
/** Input accepted by the executor-owned {@link createToolResultEnvelope} boundary. */
|
|
495
|
+
export type ToolResultEnvelopeInput = Omit<ToolResultEnvelope, "schema">;
|
|
496
|
+
/** Compatibility wrapper used when original tool details are not a plain object. */
|
|
497
|
+
export interface WrappedToolResultDetails {
|
|
498
|
+
readonly originalDetails: unknown;
|
|
499
|
+
readonly omk: ToolResultEnvelope;
|
|
339
500
|
}
|
|
501
|
+
/** Validate both the v2 shape and disposition-specific executor invariants. */
|
|
502
|
+
export declare function isToolResultEnvelope(value: unknown): value is ToolResultEnvelope;
|
|
503
|
+
/**
|
|
504
|
+
* Construct and freeze a validated executor envelope. Invalid combinations
|
|
505
|
+
* throw at the executor boundary instead of being persisted into a transcript.
|
|
506
|
+
*/
|
|
507
|
+
export declare function createToolResultEnvelope(input: ToolResultEnvelopeInput): ToolResultEnvelope;
|
|
508
|
+
/**
|
|
509
|
+
* Per-call execution policy (ALG-004 §6.2). `lateSettlement` controls whether
|
|
510
|
+
* a real tool promise settling after its terminal cause is surfaced as an
|
|
511
|
+
* audit-only event (`"audit"`, default) or silently dropped (`"ignore"`).
|
|
512
|
+
* Terminal results are immutable either way.
|
|
513
|
+
*/
|
|
514
|
+
export interface ToolExecutionPolicy {
|
|
515
|
+
timeoutMs?: number;
|
|
516
|
+
cancelSiblingsOnFatal?: boolean;
|
|
517
|
+
lateSettlement: "audit" | "ignore";
|
|
518
|
+
}
|
|
519
|
+
/** Committed disposition for a tool call terminated by timeout or parent abort. */
|
|
520
|
+
export type ToolTimeoutDisposition = "timeout" | "aborted";
|
|
521
|
+
/** Identity keys resolved for one raw path by a {@link ResourceKeyResolver}. */
|
|
522
|
+
export interface ResolvedResourceKeys {
|
|
523
|
+
/** Canonical lexical key (slash-normalized, dot-collapsed, drive/UNC normalized). */
|
|
524
|
+
lexicalKey: string;
|
|
525
|
+
/** Canonical real path: nearest existing ancestor realpath + non-existing suffix. */
|
|
526
|
+
realKey?: string;
|
|
527
|
+
/** `dev:ino` identity for an existing file (symlink target/hardlink aware). */
|
|
528
|
+
inodeKey?: string;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Optional platform identity resolver injected into the dag-v2 scheduler
|
|
532
|
+
* (§5.5 stage 2). The shared agent package stays browser-safe; a Node
|
|
533
|
+
* implementation lives in `omk-agent-core/node`. The raw path reaches the
|
|
534
|
+
* resolver before cwd-relative lexical canonicalization so Node-only tilde and
|
|
535
|
+
* platform aliases remain visible. Returning `null`, throwing, rejecting, or
|
|
536
|
+
* returning malformed keys isolates the call as exclusive.
|
|
537
|
+
*/
|
|
538
|
+
export interface ResourceKeyResolver {
|
|
539
|
+
resolvePath(rawPath: string, cwd: string): Promise<ResolvedResourceKeys | null> | ResolvedResourceKeys | null;
|
|
540
|
+
}
|
|
541
|
+
/** How a tool's real promise eventually settled after its terminal cause won. */
|
|
542
|
+
export type ToolLateSettlementOutcome = "resolved" | "rejected";
|
|
340
543
|
/** Context snapshot passed into the low-level agent loop. */
|
|
341
544
|
export interface AgentContext {
|
|
342
545
|
/** System prompt included with the request. */
|
|
@@ -349,9 +552,9 @@ export interface AgentContext {
|
|
|
349
552
|
/**
|
|
350
553
|
* Events emitted by the Agent for UI updates.
|
|
351
554
|
*
|
|
352
|
-
* `agent_end` is the last event emitted for a run,
|
|
353
|
-
*
|
|
354
|
-
*
|
|
555
|
+
* `agent_end` is the last event emitted for a run, and its subscribers remain
|
|
556
|
+
* part of run settlement. `tool_execution_update` delivery is observation-only:
|
|
557
|
+
* subscriber promises are detached and cannot delay timeout/abort terminality.
|
|
355
558
|
*/
|
|
356
559
|
export type AgentEvent = {
|
|
357
560
|
type: "agent_start";
|
|
@@ -378,18 +581,24 @@ export type AgentEvent = {
|
|
|
378
581
|
type: "tool_execution_start";
|
|
379
582
|
toolCallId: string;
|
|
380
583
|
toolName: string;
|
|
381
|
-
args:
|
|
584
|
+
args: unknown;
|
|
382
585
|
} | {
|
|
383
586
|
type: "tool_execution_update";
|
|
384
587
|
toolCallId: string;
|
|
385
588
|
toolName: string;
|
|
386
|
-
args:
|
|
387
|
-
partialResult:
|
|
589
|
+
args: unknown;
|
|
590
|
+
partialResult: unknown;
|
|
388
591
|
} | {
|
|
389
592
|
type: "tool_execution_end";
|
|
390
593
|
toolCallId: string;
|
|
391
594
|
toolName: string;
|
|
392
|
-
result:
|
|
595
|
+
result: unknown;
|
|
393
596
|
isError: boolean;
|
|
597
|
+
} | {
|
|
598
|
+
type: "tool_execution_late_settlement";
|
|
599
|
+
toolCallId: string;
|
|
600
|
+
toolName: string;
|
|
601
|
+
disposition: ToolTimeoutDisposition;
|
|
602
|
+
outcome: ToolLateSettlementOutcome;
|
|
394
603
|
};
|
|
395
604
|
//# sourceMappingURL=types.d.ts.map
|