@claxedo/agent-runtime-contract 0.8.0 → 0.10.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.
Files changed (65) hide show
  1. package/README.md +27 -9
  2. package/dist/agent-runtime-event.d.ts +975 -0
  3. package/dist/availability.d.ts +3 -4
  4. package/dist/background-work.d.ts +10 -0
  5. package/dist/claims.d.ts +2 -0
  6. package/dist/connections.d.ts +66 -1
  7. package/dist/content.d.ts +72 -15
  8. package/dist/credential-broker-errors.d.ts +4 -0
  9. package/dist/custom-provider-headers.d.ts +12 -0
  10. package/dist/d1-bookmarks.d.ts +39 -0
  11. package/dist/daemon-ownership-snapshot.d.ts +40 -0
  12. package/dist/diagnostics.d.ts +24 -0
  13. package/dist/elicitation-pattern-worker.d.ts +6 -0
  14. package/dist/elicitation-pattern-worker.mjs +17 -0
  15. package/dist/elicitation-validation.d.ts +23 -0
  16. package/dist/elicitation.d.ts +51 -0
  17. package/dist/errors.d.ts +8 -1
  18. package/dist/event-cursor.d.ts +19 -0
  19. package/dist/events.d.ts +146 -216
  20. package/dist/files.d.ts +2 -0
  21. package/dist/first-turn-error.d.ts +12 -0
  22. package/dist/goals.d.ts +28 -0
  23. package/dist/harness-permission-modes.d.ts +76 -0
  24. package/dist/harness-table.d.ts +66 -4
  25. package/dist/harnesses.d.ts +19 -1
  26. package/dist/ids.d.ts +4 -0
  27. package/dist/index.d.ts +46 -1
  28. package/dist/index.mjs +2814 -95
  29. package/dist/mcp-app.d.ts +24 -0
  30. package/dist/message-page.d.ts +101 -0
  31. package/dist/permissions.d.ts +47 -0
  32. package/dist/pi-providers.d.ts +36 -0
  33. package/dist/prompt-title.d.ts +2 -0
  34. package/dist/provider-projection.d.ts +166 -0
  35. package/dist/raw-harness-event.d.ts +7 -0
  36. package/dist/recovery/actions.d.ts +20 -0
  37. package/dist/recovery/budgets.d.ts +16 -0
  38. package/dist/recovery/facts.d.ts +37 -0
  39. package/dist/recovery/operations.d.ts +66 -0
  40. package/dist/recovery/outcomes.d.ts +48 -0
  41. package/dist/recovery/payload-line.d.ts +19 -0
  42. package/dist/recovery/postconditions.d.ts +18 -0
  43. package/dist/recovery/requests.d.ts +44 -0
  44. package/dist/recovery/targets.d.ts +55 -0
  45. package/dist/recovery/validation.d.ts +9 -0
  46. package/dist/runtime-content.d.ts +34 -0
  47. package/dist/session-handoff.d.ts +4 -0
  48. package/dist/session-titles.d.ts +14 -0
  49. package/dist/sessions.d.ts +183 -5
  50. package/dist/stream-heartbeat.d.ts +11 -0
  51. package/dist/subagents.d.ts +92 -0
  52. package/dist/tool-header.d.ts +13 -0
  53. package/dist/tool-names.d.ts +5 -0
  54. package/dist/transcript-notice.d.ts +28 -0
  55. package/dist/turn-account.d.ts +26 -0
  56. package/dist/turn-error-classes.d.ts +10 -0
  57. package/dist/turn-fold.d.ts +111 -0
  58. package/dist/turn-fold.mjs +414 -0
  59. package/dist/turn-message-ids.d.ts +16 -0
  60. package/dist/turn-outline.d.ts +37 -0
  61. package/dist/turn-page.d.ts +58 -0
  62. package/dist/usage-streams.d.ts +19 -0
  63. package/dist/vendor-body.d.ts +31 -0
  64. package/dist/vendor-providers.d.ts +11 -0
  65. package/package.json +17 -3
@@ -0,0 +1,111 @@
1
+ import { type AgentAssistantMessage, type AgentContentPart } from "./content";
2
+ export type PartRef = {
3
+ messageId: string;
4
+ partId: string;
5
+ };
6
+ type WorkGroupTool = "bash" | "edit" | "webfetch";
7
+ export type PartGroup = {
8
+ key: string;
9
+ type: "part";
10
+ ref: PartRef;
11
+ answer?: true;
12
+ } | {
13
+ key: string;
14
+ type: "context";
15
+ refs: PartRef[];
16
+ } | {
17
+ key: string;
18
+ type: "work";
19
+ tool: WorkGroupTool;
20
+ refs: PartRef[];
21
+ } | {
22
+ key: string;
23
+ type: "agents";
24
+ refs: PartRef[];
25
+ };
26
+ type GroupablePart = {
27
+ messageId: string;
28
+ part: AgentContentPart;
29
+ };
30
+ export declare const EDIT_TOOL_NAMES: Set<string>;
31
+ export declare const WEB_TOOL_NAMES: Set<string>;
32
+ export declare function textIsPresent(text: string): boolean;
33
+ export declare function partHasText(part: object): boolean;
34
+ export declare function isHiddenTool(part: {
35
+ type: string;
36
+ tool?: string;
37
+ }): boolean;
38
+ export declare function isPendingQuestion(part: {
39
+ type: string;
40
+ tool?: string;
41
+ state?: {
42
+ status?: string;
43
+ };
44
+ }): boolean;
45
+ export declare function isSubagentToolPart(part: {
46
+ type: string;
47
+ tool?: string;
48
+ state?: {
49
+ input?: unknown;
50
+ };
51
+ }): boolean;
52
+ export declare function isGroupablePart(part: AgentContentPart, hasText: (part: AgentContentPart) => boolean, showReasoning: boolean): boolean;
53
+ export declare function groupParts(input: GroupablePart[]): PartGroup[];
54
+ export type FoldablePartLookup = (ref: PartRef) => FoldablePart | undefined;
55
+ type FoldablePart = {
56
+ type: string;
57
+ userOpen?: boolean;
58
+ };
59
+ export declare function assistantMessageSettled(message: AgentAssistantMessage): boolean;
60
+ export declare function answerPartRefs(groups: readonly PartGroup[], part: FoldablePartLookup): PartRef[];
61
+ export declare function countFoldableGroups(groups: readonly PartGroup[], part: FoldablePartLookup): number;
62
+ type TurnFoldStatus = {
63
+ foldableCount: number;
64
+ settled: boolean;
65
+ interrupted?: boolean;
66
+ errored?: boolean;
67
+ busy?: boolean;
68
+ foldWhenSettled?: boolean;
69
+ userChoice?: boolean;
70
+ };
71
+ type TurnFoldDecision = {
72
+ canFold: boolean;
73
+ folded: boolean;
74
+ explicit: boolean;
75
+ };
76
+ export declare function turnFoldDecision(status: TurnFoldStatus): TurnFoldDecision;
77
+ export declare function groupMembers(group: PartGroup): PartRef[];
78
+ export declare function foldedGroupKeys(decision: TurnFoldDecision, groups: readonly PartGroup[], part: FoldablePartLookup): ReadonlySet<string>;
79
+ type TurnPartRef = GroupablePart & {
80
+ messageIndex: number;
81
+ };
82
+ type TurnFoldShape = {
83
+ /** The assistant message the turn was interrupted at, or -1. */
84
+ interruptedMessageIndex: number;
85
+ /** The interruption is the message's own abort error rather than the runtime's cancelled outcome. */
86
+ harnessInterrupted: boolean;
87
+ /** The message that failed the turn; an abort is an interruption, not a failure. */
88
+ errorMessage: AgentAssistantMessage | undefined;
89
+ settled: boolean;
90
+ compaction: boolean;
91
+ refs: TurnPartRef[];
92
+ };
93
+ /** The assistant message a session's last turn was cancelled at, which that turn draws as an interruption. */
94
+ export declare function cancelledAssistantMessageId(lastTurn: {
95
+ status: string;
96
+ assistantMessageId?: string;
97
+ } | undefined): string | undefined;
98
+ export declare function turnInterruption(messages: readonly AgentAssistantMessage[], cancelledAssistantMessageId?: string): {
99
+ index: number;
100
+ harness: boolean;
101
+ };
102
+ export declare function turnFoldShape(input: {
103
+ assistantMessages: readonly AgentAssistantMessage[];
104
+ partsOf: (messageId: string) => readonly AgentContentPart[];
105
+ hasText: (part: AgentContentPart) => boolean;
106
+ showReasoning: boolean;
107
+ compaction: boolean;
108
+ cancelledAssistantMessageId?: string;
109
+ }): TurnFoldShape;
110
+ export declare function turnSegments(refs: readonly TurnPartRef[], shape: Pick<TurnFoldShape, "interruptedMessageIndex" | "compaction">): PartGroup[][];
111
+ export {};
@@ -0,0 +1,414 @@
1
+ // ../claxedo-helpers/src/guards.ts
2
+ function isRecord(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ function asRecord(value) {
6
+ return isRecord(value) ? value : undefined;
7
+ }
8
+ function isNonEmptyString(value) {
9
+ return typeof value === "string" && value.length > 0;
10
+ }
11
+ function nonEmptyString(value) {
12
+ return isNonEmptyString(value) ? value : undefined;
13
+ }
14
+
15
+ // src/tool-names.ts
16
+ var TOOL_NAME_ALIASES = {
17
+ agent: "task",
18
+ subagent: "task",
19
+ spawn_agent: "task",
20
+ spawnagent: "task",
21
+ create_subagent: "task",
22
+ mcp__claxedo__create_subagent: "task",
23
+ claxedo_create_subagent: "task",
24
+ command: "bash",
25
+ shell: "bash",
26
+ local_shell: "bash",
27
+ read_file: "read",
28
+ write_file: "write",
29
+ edit_file: "edit",
30
+ ls: "list",
31
+ askuserquestion: "question",
32
+ createplan: "exitplanmode",
33
+ web_search: "websearch"
34
+ };
35
+ function canonicalToolName(name) {
36
+ const lowered = name.toLowerCase();
37
+ return Object.hasOwn(TOOL_NAME_ALIASES, lowered) ? TOOL_NAME_ALIASES[lowered] : lowered;
38
+ }
39
+ var CLAXEDO_MCP_SERVER = "claxedo";
40
+ var CLAXEDO_TOOL_NAMES = [
41
+ "task_list",
42
+ "task_get",
43
+ "task_create",
44
+ "task_edit",
45
+ "task_start",
46
+ "session_create",
47
+ "sessions_list",
48
+ "session_get",
49
+ "session_transcript",
50
+ "session_send",
51
+ "session_cancel_turn",
52
+ "session_handoff",
53
+ "session_rename",
54
+ "session_delete",
55
+ "session_changes",
56
+ "documents_list",
57
+ "documents_open",
58
+ "subagent_capabilities",
59
+ "create_subagent",
60
+ "subagent_status",
61
+ "subagent_list",
62
+ "subagent_cancel",
63
+ "sessions_board",
64
+ "permission_reply",
65
+ "question_reply",
66
+ "question_reject",
67
+ "wait_for_attention",
68
+ "workspaces_list",
69
+ "workspace_status",
70
+ "workspace_checkpoint",
71
+ "workspace_restore",
72
+ "workspace_lifecycle",
73
+ "app_plugin_create",
74
+ "app_plugin_check",
75
+ "app_plugin_add",
76
+ "app_plugin_guide",
77
+ "ui_show",
78
+ "ui_guide"
79
+ ];
80
+ var CLAXEDO_TOOL_NAME_SET = new Set(CLAXEDO_TOOL_NAMES);
81
+ function isClaxedoToolName(name) {
82
+ return CLAXEDO_TOOL_NAME_SET.has(name);
83
+ }
84
+ function claxedoToolName(tool, input) {
85
+ const lowered = tool.toLowerCase();
86
+ for (const server of ["claxedo-mcp", CLAXEDO_MCP_SERVER]) {
87
+ const wrapped = `mcp__${server}__`;
88
+ if (lowered.startsWith(wrapped))
89
+ return lowered.slice(wrapped.length) || undefined;
90
+ const joined = `${server}_`;
91
+ if (lowered.startsWith(joined)) {
92
+ const name = lowered.slice(joined.length);
93
+ if (isClaxedoToolName(name) || server === "claxedo-mcp" && name)
94
+ return name;
95
+ }
96
+ if (typeof input?.server === "string" && input.server.toLowerCase() === server) {
97
+ return nonEmptyString(input.tool)?.toLowerCase() ?? lowered;
98
+ }
99
+ }
100
+ return;
101
+ }
102
+
103
+ // src/transcript-notice.ts
104
+ var SEVERITIES = new Set(["info", "warn", "error"]);
105
+
106
+ // src/content.ts
107
+ function isSubagentSpawnToolName(toolName) {
108
+ return canonicalToolName(toolName) === "task";
109
+ }
110
+ var FILE_PART_URL_SCHEMES = new Set(["data", "file", "http", "https"]);
111
+
112
+ // src/turn-fold.ts
113
+ var CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"]);
114
+ var STANDALONE_TOOLS = new Set(["question"]);
115
+ var EDIT_TOOL_NAMES = new Set(["edit", "write", "apply_patch"]);
116
+ var WEB_TOOL_NAMES = new Set(["webfetch", "websearch"]);
117
+ var HIDDEN_TOOLS = new Set(["todowrite"]);
118
+ var ANSWER_TOOLS = new Set(["ui_show"]);
119
+ var GROUPABLE_PART_TYPES = new Set(["compaction", "handoff", "notice", "text", "reasoning", "tool", "file"]);
120
+ var NON_BLANK = /\S/;
121
+ function textIsPresent(text) {
122
+ return NON_BLANK.test(text);
123
+ }
124
+ function partHasText(part) {
125
+ return "text" in part && typeof part.text === "string" && textIsPresent(part.text);
126
+ }
127
+ function isHiddenTool(part) {
128
+ return part.type === "tool" && !!part.tool && HIDDEN_TOOLS.has(canonicalToolName(part.tool));
129
+ }
130
+ function producedImage(part) {
131
+ const state = part.state;
132
+ if (state.status !== "completed")
133
+ return false;
134
+ return !!state.attachments?.some((file) => file.mime.startsWith("image/"));
135
+ }
136
+ function isStandaloneTool(part) {
137
+ return part.type === "tool" && !!part.tool && STANDALONE_TOOLS.has(canonicalToolName(part.tool));
138
+ }
139
+ function isPendingQuestion(part) {
140
+ if (!isStandaloneTool(part))
141
+ return false;
142
+ const status = part.state?.status;
143
+ return status === "pending" || status === "running";
144
+ }
145
+ function isContextGroupTool(part) {
146
+ if (part.type !== "tool" || !CONTEXT_GROUP_TOOLS.has(canonicalToolName(part.tool)))
147
+ return false;
148
+ return !isClaxedoToolPart(part) && !producedImage(part);
149
+ }
150
+ function isWorkGroupTool(part) {
151
+ if (part.type !== "tool")
152
+ return false;
153
+ if (CONTEXT_GROUP_TOOLS.has(canonicalToolName(part.tool)) || isHiddenTool(part) || isStandaloneTool(part))
154
+ return false;
155
+ return !isSubagentToolPart(part) && !isClaxedoToolPart(part);
156
+ }
157
+ function isClaxedoToolPart(part) {
158
+ if (part.type !== "tool" || !part.tool)
159
+ return false;
160
+ return claxedoToolName(part.tool, asRecord(part.state?.input)) !== undefined;
161
+ }
162
+ function isAnswerToolPart(part) {
163
+ if (part.type !== "tool")
164
+ return false;
165
+ return ANSWER_TOOLS.has(claxedoToolName(part.tool, asRecord(part.state.input)) ?? "");
166
+ }
167
+ function isSubagentToolPart(part) {
168
+ if (part.type !== "tool")
169
+ return false;
170
+ if (part.tool && isSubagentSpawnToolName(part.tool))
171
+ return true;
172
+ const input = part.state?.input;
173
+ return typeof input === "object" && input !== null && input.intent === "task";
174
+ }
175
+ function spawnFailed(part) {
176
+ return part.type === "tool" && part.state.status === "error";
177
+ }
178
+ function isGroupablePart(part, hasText, showReasoning) {
179
+ if (part.type === "tool") {
180
+ if (isHiddenTool(part))
181
+ return false;
182
+ return !isPendingQuestion(part);
183
+ }
184
+ if (part.type === "text")
185
+ return hasText(part);
186
+ if (part.type === "reasoning")
187
+ return showReasoning && hasText(part);
188
+ return GROUPABLE_PART_TYPES.has(part.type);
189
+ }
190
+ function partRef(item) {
191
+ return { messageId: item.messageId, partId: item.part.id };
192
+ }
193
+ function workGroupTool(slice) {
194
+ if (slice.some((item) => item.part.type === "tool" && EDIT_TOOL_NAMES.has(canonicalToolName(item.part.tool))))
195
+ return "edit";
196
+ if (slice.some((item) => item.part.type === "tool" && WEB_TOOL_NAMES.has(canonicalToolName(item.part.tool))))
197
+ return "webfetch";
198
+ return "bash";
199
+ }
200
+ function partGroup(item) {
201
+ const group = { key: `part:${item.messageId}:${item.part.id}`, type: "part", ref: partRef(item) };
202
+ return isAnswerToolPart(item.part) ? { ...group, answer: true } : group;
203
+ }
204
+ function groupParts(input) {
205
+ const parts = input.filter((item) => !isPendingQuestion(item.part));
206
+ const result = [];
207
+ let contextStart = -1;
208
+ let workStart = -1;
209
+ let taskStart = -1;
210
+ let thoughtStart = -1;
211
+ const flushContext = (end) => {
212
+ if (contextStart < 0)
213
+ return;
214
+ const first = parts[contextStart];
215
+ if (!first) {
216
+ contextStart = -1;
217
+ return;
218
+ }
219
+ result.push({
220
+ key: `context:${first.part.id}`,
221
+ type: "context",
222
+ refs: parts.slice(contextStart, end + 1).map(partRef)
223
+ });
224
+ contextStart = -1;
225
+ };
226
+ const flushWork = (end) => {
227
+ if (workStart < 0)
228
+ return;
229
+ const slice = parts.slice(workStart, end + 1);
230
+ const first = parts[workStart];
231
+ if (!first) {
232
+ workStart = -1;
233
+ return;
234
+ }
235
+ if (slice.filter((item) => item.part.type === "tool").length >= 2) {
236
+ result.push({
237
+ key: `work:${first.part.id}`,
238
+ type: "work",
239
+ tool: workGroupTool(slice),
240
+ refs: slice.map(partRef)
241
+ });
242
+ } else {
243
+ result.push(...slice.map(partGroup));
244
+ }
245
+ workStart = -1;
246
+ };
247
+ const flushTask = (end) => {
248
+ if (taskStart < 0)
249
+ return;
250
+ const slice = parts.slice(taskStart, end + 1);
251
+ const first = parts[taskStart];
252
+ if (!first) {
253
+ taskStart = -1;
254
+ return;
255
+ }
256
+ result.push({ key: `agents:${first.part.id}`, type: "agents", refs: slice.map(partRef) });
257
+ taskStart = -1;
258
+ };
259
+ const flushThoughts = (end) => {
260
+ if (thoughtStart < 0)
261
+ return;
262
+ result.push(...parts.slice(thoughtStart, end + 1).map(partGroup));
263
+ thoughtStart = -1;
264
+ };
265
+ parts.forEach((item, index) => {
266
+ if (item.part.type === "reasoning") {
267
+ if (thoughtStart < 0)
268
+ thoughtStart = index;
269
+ return;
270
+ }
271
+ const runStart = thoughtStart < 0 ? index : thoughtStart;
272
+ const isContext = isContextGroupTool(item.part);
273
+ const isWork = isWorkGroupTool(item.part);
274
+ const isTask = !isClaxedoToolPart(item.part) && isSubagentToolPart(item.part) && !spawnFailed(item.part);
275
+ if (isContext) {
276
+ flushWork(runStart - 1);
277
+ flushTask(runStart - 1);
278
+ if (contextStart < 0)
279
+ contextStart = runStart;
280
+ thoughtStart = -1;
281
+ return;
282
+ }
283
+ if (isWork) {
284
+ flushContext(runStart - 1);
285
+ flushTask(runStart - 1);
286
+ if (workStart < 0)
287
+ workStart = runStart;
288
+ thoughtStart = -1;
289
+ return;
290
+ }
291
+ flushContext(runStart - 1);
292
+ flushWork(runStart - 1);
293
+ if (!isTask || thoughtStart >= 0)
294
+ flushTask(runStart - 1);
295
+ flushThoughts(index - 1);
296
+ if (isTask) {
297
+ if (taskStart < 0)
298
+ taskStart = index;
299
+ return;
300
+ }
301
+ result.push(partGroup(item));
302
+ });
303
+ const runEnd = (thoughtStart < 0 ? parts.length : thoughtStart) - 1;
304
+ flushContext(runEnd);
305
+ flushWork(runEnd);
306
+ flushTask(runEnd);
307
+ flushThoughts(parts.length - 1);
308
+ return result;
309
+ }
310
+ var FOLD_MINIMUM = 2;
311
+ var NO_KEYS = new Set;
312
+ function assistantMessageSettled(message) {
313
+ return typeof message.time.completed === "number" || !!message.error;
314
+ }
315
+ function holdsTool(group, part) {
316
+ return group.type !== "part" || !group.answer && part(group.ref)?.type === "tool";
317
+ }
318
+ function answerGroups(groups, part) {
319
+ const lastTool = groups.findLastIndex((group) => holdsTool(group, part));
320
+ return groups.slice(lastTool + 1).filter((group) => group.type === "part" && part(group.ref)?.type === "text");
321
+ }
322
+ function answerGroupKeys(groups, part) {
323
+ return new Set(answerGroups(groups, part).map((group) => group.key));
324
+ }
325
+ function answerPartRefs(groups, part) {
326
+ return answerGroups(groups, part).flatMap(groupMembers);
327
+ }
328
+ function isFoldableGroup(group, part, answerKeys) {
329
+ if (group.type !== "part")
330
+ return true;
331
+ if (group.answer || answerKeys.has(group.key))
332
+ return false;
333
+ const resolved = part(group.ref);
334
+ if (!resolved)
335
+ return false;
336
+ return resolved.type === "text" || resolved.type === "reasoning" || resolved.type === "tool";
337
+ }
338
+ function countFoldableGroups(groups, part) {
339
+ const answerKeys = answerGroupKeys(groups, part);
340
+ return groups.reduce((count, group) => isFoldableGroup(group, part, answerKeys) ? count + 1 : count, 0);
341
+ }
342
+ function turnFoldDecision(status) {
343
+ const running = !!status.busy && !status.errored;
344
+ const canFold = !running && status.foldWhenSettled !== false && status.settled && status.foldableCount >= FOLD_MINIMUM;
345
+ const explainsItself = !!status.interrupted || !!status.errored;
346
+ return {
347
+ canFold,
348
+ folded: canFold ? status.userChoice ?? !explainsItself : false,
349
+ explicit: status.userChoice !== undefined
350
+ };
351
+ }
352
+ function groupMembers(group) {
353
+ return group.type === "part" ? [group.ref] : group.refs;
354
+ }
355
+ function foldedGroupKeys(decision, groups, part) {
356
+ if (!decision.folded)
357
+ return NO_KEYS;
358
+ const answerKeys = answerGroupKeys(groups, part);
359
+ return new Set(groups.filter((group) => isFoldableGroup(group, part, answerKeys) && (decision.explicit || !groupMembers(group).some((ref) => part(ref)?.userOpen))).map((group) => group.key));
360
+ }
361
+ function assistantMessageInterrupted(message) {
362
+ return message.error?.name === "MessageAbortedError";
363
+ }
364
+ function cancelledAssistantMessageId(lastTurn) {
365
+ return lastTurn?.status === "cancelled" ? lastTurn.assistantMessageId : undefined;
366
+ }
367
+ function turnInterruption(messages, cancelledAssistantMessageId2) {
368
+ const harnessIndex = messages.findIndex(assistantMessageInterrupted);
369
+ if (harnessIndex !== -1 || cancelledAssistantMessageId2 === undefined)
370
+ return { index: harnessIndex, harness: harnessIndex !== -1 };
371
+ return { index: messages.findIndex((message) => message.id === cancelledAssistantMessageId2), harness: false };
372
+ }
373
+ function turnFoldShape(input) {
374
+ const messages = input.assistantMessages;
375
+ const interruption = turnInterruption(messages, input.cancelledAssistantMessageId);
376
+ const interrupted = interruption.index !== -1;
377
+ return {
378
+ interruptedMessageIndex: interruption.index,
379
+ harnessInterrupted: interruption.harness,
380
+ errorMessage: messages.find((message) => message.error && message.error.name !== "MessageAbortedError"),
381
+ settled: messages.some(assistantMessageSettled),
382
+ compaction: input.compaction,
383
+ refs: messages.flatMap((message, messageIndex) => input.partsOf(message.id).filter((part) => isGroupablePart(part, input.hasText, input.showReasoning) && !(interrupted && part.type === "tool" && (part.state.status === "pending" || part.state.status === "running"))).map((part) => ({ messageId: message.id, messageIndex, part })))
384
+ };
385
+ }
386
+ function turnSegments(refs, shape) {
387
+ if (shape.interruptedMessageIndex === -1 || shape.compaction)
388
+ return [groupParts([...refs])];
389
+ return [
390
+ groupParts(refs.filter((ref) => ref.messageIndex <= shape.interruptedMessageIndex)),
391
+ groupParts(refs.filter((ref) => ref.messageIndex > shape.interruptedMessageIndex))
392
+ ];
393
+ }
394
+ export {
395
+ turnSegments,
396
+ turnInterruption,
397
+ turnFoldShape,
398
+ turnFoldDecision,
399
+ textIsPresent,
400
+ partHasText,
401
+ isSubagentToolPart,
402
+ isPendingQuestion,
403
+ isHiddenTool,
404
+ isGroupablePart,
405
+ groupParts,
406
+ groupMembers,
407
+ foldedGroupKeys,
408
+ countFoldableGroups,
409
+ cancelledAssistantMessageId,
410
+ assistantMessageSettled,
411
+ answerPartRefs,
412
+ WEB_TOOL_NAMES,
413
+ EDIT_TOOL_NAMES
414
+ };
@@ -0,0 +1,16 @@
1
+ /** The id of the reply that answers `userMessageId`. */
2
+ export declare function assistantMessageIdForTurn(userMessageId: string): string;
3
+ /**
4
+ * The user message `assistantMessageId` answers, or `undefined` when the id
5
+ * was not minted by {@link assistantMessageIdForTurn}. Callers that require a
6
+ * parent must treat `undefined` as a contract violation to report, never as a
7
+ * cue to invent one: an id outside the convention names no user message, and
8
+ * substituting the session's own id fabricates a turn that does not exist.
9
+ */
10
+ export declare function userMessageIdForAssistantReply(assistantMessageId: string): string | undefined;
11
+ /**
12
+ * Mints message ids that sort by creation time on the minting clock. Other
13
+ * minters (a task's first prompt, a subagent, a server-side `msg_<uuid>`) do
14
+ * not, so a reader orders a session's messages by `time.created`, not by id.
15
+ */
16
+ export declare function createMessageIds(clock?: () => number): () => string;
@@ -0,0 +1,37 @@
1
+ export declare const TURN_OUTLINE_LIMIT = 500;
2
+ export declare const TURN_OUTLINE_SNIPPET_LENGTH = 120;
3
+ /** One turn as a transcript's outline lists it: what a nav rail needs, read from the turn's user message alone. */
4
+ export type TurnOutlineEntry = {
5
+ id: string;
6
+ createdAt: number;
7
+ title?: string;
8
+ user?: string;
9
+ };
10
+ /** The newest turns of a session, oldest first; `complete` is false when turns older than the window exist. */
11
+ export type TurnOutline = {
12
+ turns: TurnOutlineEntry[];
13
+ complete: boolean;
14
+ };
15
+ export type OutlineUserRow = {
16
+ id: string;
17
+ created_at: number | null;
18
+ title: string | null;
19
+ };
20
+ export type OutlineTextRow = {
21
+ message_id: string;
22
+ text: string | null;
23
+ synthetic: number | null;
24
+ ignored: number | null;
25
+ };
26
+ /**
27
+ * The rows a store reads for the outline's window, all from its user messages:
28
+ * the users oldest first, and their text parts in transcript order, each
29
+ * already cut to the snippet length by the store.
30
+ */
31
+ type OutlineRows = {
32
+ users: readonly OutlineUserRow[];
33
+ texts: readonly OutlineTextRow[];
34
+ complete: boolean;
35
+ };
36
+ export declare function foldTurnOutline(rows: OutlineRows, snippetLength?: number): TurnOutline;
37
+ export {};
@@ -0,0 +1,58 @@
1
+ import type { AgentMessage } from "./content";
2
+ import type { TurnOutline } from "./turn-outline";
3
+ export declare const TURN_PAGE_TURN_CAP = 24;
4
+ export declare const TURN_PAGE_BYTE_CAP: number;
5
+ export type ReaderSettings = {
6
+ reasoning: boolean;
7
+ shell: boolean;
8
+ edit: boolean;
9
+ };
10
+ export type TurnPageQuery = ReaderSettings & {
11
+ rows: number;
12
+ cols: number;
13
+ };
14
+ export type TurnPageRequest = TurnPageQuery & {
15
+ /** The assistant message the session's last turn was cancelled at, which that turn draws as an interruption. */
16
+ cancelledAssistantMessageId?: string;
17
+ /** Read the turns before this cursor; absent reads from the newest turn. */
18
+ before?: string;
19
+ };
20
+ export type PageTurn = {
21
+ messages: AgentMessage[];
22
+ /** Pages back from this turn's user message; absent on a session's first turn. */
23
+ cursor?: string;
24
+ };
25
+ /** Consecutive turns of a session, oldest first; the first turn's cursor reads what comes before the page. */
26
+ export type TurnPage = {
27
+ turns: PageTurn[];
28
+ };
29
+ export type FirstRead<Session> = {
30
+ session: Session;
31
+ outline: TurnOutline;
32
+ page?: TurnPage;
33
+ };
34
+ /** A read named some but not all of its query parameters, or one of them out of range; every producer answers it with a 400. */
35
+ export declare class TurnPageQueryError extends Error {
36
+ readonly name = "TurnPageQueryError";
37
+ }
38
+ /** One whole turn ending before `before` (the newest turn without it), and the cursor at its user message when older turns exist. */
39
+ export type TurnRead = (before?: string) => Promise<{
40
+ messages: AgentMessage[];
41
+ nextCursor?: string;
42
+ }> | {
43
+ messages: AgentMessage[];
44
+ nextCursor?: string;
45
+ };
46
+ type Query = (name: string) => string | undefined;
47
+ export declare function parseTurnPageQuery(query: Query): TurnPageQuery | undefined;
48
+ export declare function parseOlderTurnPageQuery(query: Query): TurnPageQuery & {
49
+ before: string;
50
+ };
51
+ export declare function projectTurn(messages: AgentMessage[], settings: ReaderSettings): PageTurn;
52
+ type LineRequest = Pick<TurnPageRequest, "cols" | "reasoning" | "cancelledAssistantMessageId">;
53
+ /** An estimate for choosing how many turns a page sends, never a layout. */
54
+ export declare function estimateTurnLines(messages: readonly AgentMessage[], request: LineRequest): number;
55
+ /** The caps decide how many turns are sent; a message or part is never trimmed. */
56
+ export declare function readTurnPage(read: TurnRead, request: TurnPageRequest): Promise<TurnPage>;
57
+ export declare function readFirstRead<Session>(session: Session, outline: TurnOutline, read: TurnRead, request: TurnPageRequest | undefined): Promise<FirstRead<Session>>;
58
+ export {};
@@ -0,0 +1,19 @@
1
+ import type { RuntimeTokenUsage, RuntimeUsageObservation } from "./events";
2
+ /**
3
+ * A turn's usage as independent streams: each scope's running usage, and the
4
+ * last observation each scope applied, so a re-emitted one is not applied twice.
5
+ * The turn's usage is the sum over every scope.
6
+ */
7
+ export type UsageStreams = {
8
+ streams: Map<string, RuntimeTokenUsage>;
9
+ lastObservationKeys: Map<string, string>;
10
+ };
11
+ export declare function unknownTokenUsage(): RuntimeTokenUsage;
12
+ export declare function addTokenUsage(previous: RuntimeTokenUsage, next: RuntimeTokenUsage): RuntimeTokenUsage;
13
+ export declare function usageStreamsTotal(streams: ReadonlyMap<string, RuntimeTokenUsage>): RuntimeTokenUsage;
14
+ export declare function usageObservationKey(observation: RuntimeUsageObservation): string;
15
+ export declare function isRepeatedUsageObservation(state: UsageStreams, observation: RuntimeUsageObservation): boolean;
16
+ /** Applies one observation to its scope; a repeat of the scope's last observation changes nothing and returns false. */
17
+ export declare function recordUsageObservation(state: UsageStreams, observation: RuntimeUsageObservation): boolean;
18
+ /** A turn's usage from its observations in the order they were reported. */
19
+ export declare function foldUsageObservations(observations: Iterable<RuntimeUsageObservation>): RuntimeTokenUsage;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * What a model vendor's response body is.
3
+ *
4
+ * Only a JSON document is the vendor API's own answer. An HTML document is the
5
+ * edge in front of the API refusing the client: chatgpt.com answers requests
6
+ * from Cloudflare Worker egress with Cloudflare's challenge page (403,
7
+ * text/html, "Please try again later", observed 2026-10-15), and that page says
8
+ * nothing about the credential, the model or the account's limits. Anything
9
+ * else is prose a gateway, SDK or harness wrote.
10
+ */
11
+ export type VendorBody = {
12
+ kind: "api";
13
+ document: Record<string, unknown>;
14
+ } | {
15
+ kind: "page";
16
+ } | {
17
+ kind: "text";
18
+ };
19
+ export declare function vendorBody(text: string): VendorBody;
20
+ /**
21
+ * The HTTP status a vendor answered, as pi-ai's error states it ("OpenAI API
22
+ * error (401): {…}") or an SDK's error leads with ("401 {…}", "401: …"): the
23
+ * one fact about a refusal that is the vendor's own and not prose around it.
24
+ */
25
+ export declare function vendorHttpStatus(text: string): number | undefined;
26
+ /**
27
+ * The turn error a harness reports in place of an edge's page. The page itself
28
+ * never reaches a transcript: it is kilobytes of markup, and its "try again
29
+ * later" reads as a rate limit it is not.
30
+ */
31
+ export declare const EDGE_REFUSED_MESSAGE = "The model provider refused the connection from this host and answered with a block page instead of its API; the account itself was not refused";