@earendil-works/pi-agent-core 0.85.1 → 0.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +16 -6
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +64 -22
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/agent.d.ts +5 -3
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +15 -6
  8. package/dist/agent.js.map +1 -1
  9. package/dist/harness/config.d.ts +1 -1
  10. package/dist/harness/config.d.ts.map +1 -1
  11. package/dist/harness/config.js +10 -2
  12. package/dist/harness/config.js.map +1 -1
  13. package/dist/harness/env/nodejs.d.ts +2 -1
  14. package/dist/harness/env/nodejs.d.ts.map +1 -1
  15. package/dist/harness/env/nodejs.js +98 -27
  16. package/dist/harness/env/nodejs.js.map +1 -1
  17. package/dist/harness/execution/tools.d.ts +1 -1
  18. package/dist/harness/execution/tools.d.ts.map +1 -1
  19. package/dist/harness/execution/tools.js +0 -2
  20. package/dist/harness/execution/tools.js.map +1 -1
  21. package/dist/harness/messages.d.ts.map +1 -1
  22. package/dist/harness/messages.js +1 -0
  23. package/dist/harness/messages.js.map +1 -1
  24. package/dist/harness/pico3/bash.d.ts +10 -0
  25. package/dist/harness/pico3/bash.d.ts.map +1 -0
  26. package/dist/harness/pico3/bash.js +25 -0
  27. package/dist/harness/pico3/bash.js.map +1 -0
  28. package/dist/harness/pico3/bounded.d.ts +21 -0
  29. package/dist/harness/pico3/bounded.d.ts.map +1 -0
  30. package/dist/harness/pico3/bounded.js +98 -0
  31. package/dist/harness/pico3/bounded.js.map +1 -0
  32. package/dist/harness/pico3/chord.d.ts +41 -0
  33. package/dist/harness/pico3/chord.d.ts.map +1 -0
  34. package/dist/harness/pico3/chord.js +130 -0
  35. package/dist/harness/pico3/chord.js.map +1 -0
  36. package/dist/harness/pico3/context.d.ts +16 -0
  37. package/dist/harness/pico3/context.d.ts.map +1 -0
  38. package/dist/harness/pico3/context.js +88 -0
  39. package/dist/harness/pico3/context.js.map +1 -0
  40. package/dist/harness/pico3/harness.d.ts +197 -0
  41. package/dist/harness/pico3/harness.d.ts.map +1 -0
  42. package/dist/harness/pico3/harness.js +654 -0
  43. package/dist/harness/pico3/harness.js.map +1 -0
  44. package/dist/harness/pico3/hooks.d.ts +10 -0
  45. package/dist/harness/pico3/hooks.d.ts.map +1 -0
  46. package/dist/harness/pico3/hooks.js +33 -0
  47. package/dist/harness/pico3/hooks.js.map +1 -0
  48. package/dist/harness/pico3/index.d.ts +19 -0
  49. package/dist/harness/pico3/index.d.ts.map +1 -0
  50. package/dist/harness/pico3/index.js +15 -0
  51. package/dist/harness/pico3/index.js.map +1 -0
  52. package/dist/harness/pico3/jsonl.d.ts +58 -0
  53. package/dist/harness/pico3/jsonl.d.ts.map +1 -0
  54. package/dist/harness/pico3/jsonl.js +325 -0
  55. package/dist/harness/pico3/jsonl.js.map +1 -0
  56. package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
  57. package/dist/harness/pico3/kinds/collapse.d.ts.map +1 -0
  58. package/dist/harness/pico3/kinds/collapse.js +191 -0
  59. package/dist/harness/pico3/kinds/collapse.js.map +1 -0
  60. package/dist/harness/pico3/kinds/entries.d.ts +78 -0
  61. package/dist/harness/pico3/kinds/entries.d.ts.map +1 -0
  62. package/dist/harness/pico3/kinds/entries.js +13 -0
  63. package/dist/harness/pico3/kinds/entries.js.map +1 -0
  64. package/dist/harness/pico3/kinds/frames.d.ts +9 -0
  65. package/dist/harness/pico3/kinds/frames.d.ts.map +1 -0
  66. package/dist/harness/pico3/kinds/frames.js +75 -0
  67. package/dist/harness/pico3/kinds/frames.js.map +1 -0
  68. package/dist/harness/pico3/kinds/generation.d.ts +94 -0
  69. package/dist/harness/pico3/kinds/generation.d.ts.map +1 -0
  70. package/dist/harness/pico3/kinds/generation.js +504 -0
  71. package/dist/harness/pico3/kinds/generation.js.map +1 -0
  72. package/dist/harness/pico3/kinds/job.d.ts +44 -0
  73. package/dist/harness/pico3/kinds/job.d.ts.map +1 -0
  74. package/dist/harness/pico3/kinds/job.js +143 -0
  75. package/dist/harness/pico3/kinds/job.js.map +1 -0
  76. package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
  77. package/dist/harness/pico3/kinds/plugin.d.ts.map +1 -0
  78. package/dist/harness/pico3/kinds/plugin.js +35 -0
  79. package/dist/harness/pico3/kinds/plugin.js.map +1 -0
  80. package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
  81. package/dist/harness/pico3/kinds/post-tools.d.ts.map +1 -0
  82. package/dist/harness/pico3/kinds/post-tools.js +145 -0
  83. package/dist/harness/pico3/kinds/post-tools.js.map +1 -0
  84. package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
  85. package/dist/harness/pico3/kinds/task-api.d.ts.map +1 -0
  86. package/dist/harness/pico3/kinds/task-api.js +45 -0
  87. package/dist/harness/pico3/kinds/task-api.js.map +1 -0
  88. package/dist/harness/pico3/kinds/tool.d.ts +43 -0
  89. package/dist/harness/pico3/kinds/tool.d.ts.map +1 -0
  90. package/dist/harness/pico3/kinds/tool.js +373 -0
  91. package/dist/harness/pico3/kinds/tool.js.map +1 -0
  92. package/dist/harness/pico3/membrane.d.ts +23 -0
  93. package/dist/harness/pico3/membrane.d.ts.map +1 -0
  94. package/dist/harness/pico3/membrane.js +141 -0
  95. package/dist/harness/pico3/membrane.js.map +1 -0
  96. package/dist/harness/pico3/memory.d.ts +53 -0
  97. package/dist/harness/pico3/memory.d.ts.map +1 -0
  98. package/dist/harness/pico3/memory.js +264 -0
  99. package/dist/harness/pico3/memory.js.map +1 -0
  100. package/dist/harness/pico3/scheduler.d.ts +46 -0
  101. package/dist/harness/pico3/scheduler.d.ts.map +1 -0
  102. package/dist/harness/pico3/scheduler.js +438 -0
  103. package/dist/harness/pico3/scheduler.js.map +1 -0
  104. package/dist/harness/pico3/session.d.ts +263 -0
  105. package/dist/harness/pico3/session.d.ts.map +1 -0
  106. package/dist/harness/pico3/session.js +1347 -0
  107. package/dist/harness/pico3/session.js.map +1 -0
  108. package/dist/harness/pico3/system.d.ts +126 -0
  109. package/dist/harness/pico3/system.d.ts.map +1 -0
  110. package/dist/harness/pico3/system.js +244 -0
  111. package/dist/harness/pico3/system.js.map +1 -0
  112. package/dist/harness/pico3/types.d.ts +973 -0
  113. package/dist/harness/pico3/types.d.ts.map +1 -0
  114. package/dist/harness/pico3/types.js +96 -0
  115. package/dist/harness/pico3/types.js.map +1 -0
  116. package/dist/harness/pico3/view.d.ts +34 -0
  117. package/dist/harness/pico3/view.d.ts.map +1 -0
  118. package/dist/harness/pico3/view.js +417 -0
  119. package/dist/harness/pico3/view.js.map +1 -0
  120. package/dist/harness/runtime/drive/boundary.d.ts.map +1 -1
  121. package/dist/harness/runtime/drive/boundary.js +6 -3
  122. package/dist/harness/runtime/drive/boundary.js.map +1 -1
  123. package/dist/harness/runtime/drive/response.d.ts.map +1 -1
  124. package/dist/harness/runtime/drive/response.js +4 -4
  125. package/dist/harness/runtime/drive/response.js.map +1 -1
  126. package/dist/harness/runtime/drive/retry.d.ts +2 -2
  127. package/dist/harness/runtime/drive/retry.d.ts.map +1 -1
  128. package/dist/harness/runtime/drive/retry.js +3 -6
  129. package/dist/harness/runtime/drive/retry.js.map +1 -1
  130. package/dist/harness/runtime/drive/structural.d.ts.map +1 -1
  131. package/dist/harness/runtime/drive/structural.js +5 -5
  132. package/dist/harness/runtime/drive/structural.js.map +1 -1
  133. package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -1
  134. package/dist/harness/runtime/drive/tool-placement.js +2 -27
  135. package/dist/harness/runtime/drive/tool-placement.js.map +1 -1
  136. package/dist/harness/runtime/drive/tools.d.ts.map +1 -1
  137. package/dist/harness/runtime/drive/tools.js.map +1 -1
  138. package/dist/harness/session/fork-policy.d.ts +20 -12
  139. package/dist/harness/session/fork-policy.d.ts.map +1 -1
  140. package/dist/harness/session/fork-policy.js +45 -12
  141. package/dist/harness/session/fork-policy.js.map +1 -1
  142. package/dist/harness/session/fork.d.ts +0 -2
  143. package/dist/harness/session/fork.d.ts.map +1 -1
  144. package/dist/harness/session/fork.js +20 -68
  145. package/dist/harness/session/fork.js.map +1 -1
  146. package/dist/harness/session/in-memory-storage-state.d.ts +5 -5
  147. package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -1
  148. package/dist/harness/session/in-memory-storage-state.js +99 -39
  149. package/dist/harness/session/in-memory-storage-state.js.map +1 -1
  150. package/dist/harness/session/index.d.ts +1 -1
  151. package/dist/harness/session/index.d.ts.map +1 -1
  152. package/dist/harness/session/index.js +1 -1
  153. package/dist/harness/session/index.js.map +1 -1
  154. package/dist/harness/session/jsonl/fork.d.ts +38 -0
  155. package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
  156. package/dist/harness/session/jsonl/fork.js +264 -0
  157. package/dist/harness/session/jsonl/fork.js.map +1 -0
  158. package/dist/harness/session/jsonl/io.d.ts +14 -0
  159. package/dist/harness/session/jsonl/io.d.ts.map +1 -0
  160. package/dist/harness/session/jsonl/io.js +86 -0
  161. package/dist/harness/session/jsonl/io.js.map +1 -0
  162. package/dist/harness/session/jsonl/legacy-v3.d.ts +33 -8
  163. package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -1
  164. package/dist/harness/session/jsonl/legacy-v3.js +285 -206
  165. package/dist/harness/session/jsonl/legacy-v3.js.map +1 -1
  166. package/dist/harness/session/jsonl/repo.d.ts +2 -1
  167. package/dist/harness/session/jsonl/repo.d.ts.map +1 -1
  168. package/dist/harness/session/jsonl/repo.js +36 -20
  169. package/dist/harness/session/jsonl/repo.js.map +1 -1
  170. package/dist/harness/session/jsonl/storage.d.ts +4 -5
  171. package/dist/harness/session/jsonl/storage.d.ts.map +1 -1
  172. package/dist/harness/session/jsonl/storage.js +39 -113
  173. package/dist/harness/session/jsonl/storage.js.map +1 -1
  174. package/dist/harness/session/memory.d.ts +3 -5
  175. package/dist/harness/session/memory.d.ts.map +1 -1
  176. package/dist/harness/session/memory.js +9 -14
  177. package/dist/harness/session/memory.js.map +1 -1
  178. package/dist/harness/session/testing/conformance/session-repo.d.ts +2 -0
  179. package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -1
  180. package/dist/harness/session/testing/conformance/session-repo.js +220 -1
  181. package/dist/harness/session/testing/conformance/session-repo.js.map +1 -1
  182. package/dist/harness/session/testing/index.d.ts +1 -1
  183. package/dist/harness/session/testing/index.d.ts.map +1 -1
  184. package/dist/harness/session/testing/index.js +1 -1
  185. package/dist/harness/session/testing/index.js.map +1 -1
  186. package/dist/harness/session/types.d.ts +1 -0
  187. package/dist/harness/session/types.d.ts.map +1 -1
  188. package/dist/harness/session/types.js.map +1 -1
  189. package/dist/harness/types.d.ts +14 -0
  190. package/dist/harness/types.d.ts.map +1 -1
  191. package/dist/harness/types.js.map +1 -1
  192. package/dist/proxy.d.ts +2 -2
  193. package/dist/proxy.d.ts.map +1 -1
  194. package/dist/proxy.js.map +1 -1
  195. package/dist/types.d.ts +30 -14
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/types.js.map +1 -1
  198. package/package.json +10 -6
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collapse.js","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/collapse.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { estimateContextTokens } from \"@earendil-works/pi-ai/utils/estimate\";\nimport { effectiveTools } from \"../system.ts\";\nimport {\n\ttype Completion,\n\ttype CoreKind,\n\ttype CoreTx,\n\ttype Entry,\n\ttype HookInfo,\n\ttype HookResult,\n\ttype Id,\n\ttype ModelRef,\n\ttype RequestMessage,\n\ttype RetryPolicy,\n\ttype Runtime,\n\ttype Step,\n\ttype SystemMessage,\n\ttype Task,\n\ttype ThinkingLevel,\n\ttoStored,\n} from \"../types.ts\";\nimport { retryDecision } from \"./generation.ts\";\n\nconst estimate = (messages: RequestMessage[]) =>\n\testimateContextTokens(\n\t\tmessages.filter((message): message is Exclude<RequestMessage, SystemMessage> => message.role !== \"system\"),\n\t).tokens;\n\nexport type CollapseInput = { reason: \"threshold\" | \"manual\" | \"overflow\"; through: Id; instructions?: string };\ntype Base = {\n\texpectedHead: Id | null;\n\tinstructions?: string;\n\tmodel: ModelRef;\n\tthinkingLevel: ThinkingLevel;\n\tretry: RetryPolicy;\n\tattempt: number;\n};\nexport type CollapseCheckpoint =\n\t| ({ phase: \"summarizing\" } & Base)\n\t| ({ phase: \"retrying\"; untilMs: number; lastError: string } & Base)\n\t| ({ phase: \"prepared\"; summary: string } & Base);\nexport type CollapseFailure = {\n\treason: \"stale\" | \"declined\" | \"provider\" | \"retries_exhausted\" | \"no_model\";\n\tdetail: string;\n};\nexport interface CollapseHooks {\n\tbeforeCollapse(\n\t\treason: \"threshold\" | \"manual\" | \"overflow\",\n\t\tthrough: Id,\n\t\tentries: Entry[],\n\t\tinfo: HookInfo,\n\t\tctx: Context,\n\t): HookResult<{ decline: true } | { instructions?: string; summary?: string }>;\n}\nexport const collapseConfig = { rewindable: { threshold: 0 as number, keepRecent: 20_000 as number } } as const;\n\ntype CollapseTask = Task<CollapseInput, CollapseCheckpoint>;\ntype CollapseRuntime = Runtime<CollapseHooks, CoreTx>;\ntype CollapseStep = Step<CollapseCheckpoint, { summary: Id }, CollapseFailure, CoreTx>;\nconst failed = (reason: CollapseFailure[\"reason\"], detail: string): Completion<{ summary: Id }, CollapseFailure> => ({\n\tstatus: \"failed\",\n\tfailure: { reason, detail },\n});\nconst baseOf = (checkpoint: CollapseCheckpoint): Base => {\n\tconst { phase: _phase, ...rest } = checkpoint;\n\tconst {\n\t\tuntilMs: _untilMs,\n\t\tlastError: _lastError,\n\t\tsummary: _summary,\n\t\t...base\n\t} = rest as Base & {\n\t\tuntilMs?: number;\n\t\tlastError?: string;\n\t\tsummary?: string;\n\t};\n\treturn base;\n};\n\nexport const collapse: CoreKind<\n\tCollapseInput,\n\tCollapseCheckpoint,\n\t{ summary: Id },\n\tCollapseFailure,\n\tnull,\n\tCollapseHooks,\n\ttypeof collapseConfig\n> = {\n\tname: \"pi.collapse\",\n\tconfig: collapseConfig,\n\tinflight: [\"summarizing\"],\n\n\tasync initial(task, runtime, ctx) {\n\t\tconst state = await runtime.rewindable(task.conversationId, ctx);\n\t\tif (state.model === undefined) return { done: () => failed(\"no_model\", \"no model configured\") };\n\t\tconst sticky = await runtime.sticky(task.conversationId, ctx);\n\t\tconst head = await runtime.newestEntry(task.conversationId, { withHead: true }, ctx);\n\t\tconst { entries } = await runtime.context(task.conversationId, task.input.through, ctx);\n\t\tlet decision: { decline: true } | { instructions?: string; summary?: string } = {};\n\t\tawait runtime.hooks.each(\n\t\t\tctx,\n\t\t\t(hook, api) => hook.beforeCollapse?.(task.input.reason, task.input.through, entries, api, ctx),\n\t\t\t(value) => {\n\t\t\t\tif (value === undefined) return;\n\t\t\t\tdecision = value;\n\t\t\t\treturn true;\n\t\t\t},\n\t\t);\n\t\tif (\"decline\" in decision) return { done: () => failed(\"declined\", \"declined by beforeCollapse\") };\n\t\tconst choice: { instructions?: string; summary?: string } = decision;\n\t\tconst base: Base = {\n\t\t\texpectedHead: head?.id ?? null,\n\t\t\t...((choice.instructions ?? task.input.instructions)\n\t\t\t\t? { instructions: choice.instructions ?? task.input.instructions }\n\t\t\t\t: {}),\n\t\t\tmodel: state.model,\n\t\t\tthinkingLevel: state.thinkingLevel,\n\t\t\tretry: sticky.retry,\n\t\t\tattempt: 1,\n\t\t};\n\t\tif (choice.summary !== undefined) {\n\t\t\treturn {\n\t\t\t\tnext: async (tx, current) =>\n\t\t\t\t\t(await headMoved(tx, current.conversationId, base))\n\t\t\t\t\t\t? failed(\"stale\", \"head moved during beforeCollapse\")\n\t\t\t\t\t\t: { phase: \"prepared\", summary: choice.summary!, ...base },\n\t\t\t};\n\t\t}\n\t\treturn summarizeNow(task, base, runtime, ctx, \"check head\");\n\t},\n\n\tphases: {\n\t\tasync summarizing(task, runtime) {\n\t\t\treturn afterFailure(baseOf(task.checkpoint), null, \"interrupted\", runtime);\n\t\t},\n\t\tasync retrying(task, runtime, ctx) {\n\t\t\tawait runtime.sleep(task.checkpoint.untilMs, ctx);\n\t\t\treturn summarizeNow(task, { ...baseOf(task.checkpoint), attempt: task.checkpoint.attempt + 1 }, runtime, ctx);\n\t\t},\n\t\tasync prepared(task, runtime) {\n\t\t\tconst { summary } = task.checkpoint;\n\t\t\tconst base = baseOf(task.checkpoint);\n\t\t\treturn {\n\t\t\t\tdone: async (tx, current) => {\n\t\t\t\t\tconst { head, entries } = await tx.context(current.conversationId);\n\t\t\t\t\tif ((head?.id ?? null) !== base.expectedHead) return failed(\"stale\", \"head moved during collapse\");\n\t\t\t\t\tconst retained = entries.find((entry) => entry.id > task.input.through);\n\t\t\t\t\tconst id = tx.appendEntry(current.conversationId, {\n\t\t\t\t\t\tkind: \"pi.summary\",\n\t\t\t\t\t\tdata: { through: task.input.through },\n\t\t\t\t\t\tmodel: [{ role: \"user\", content: summary, timestamp: runtime.now() }],\n\t\t\t\t\t\thead: retained?.id ?? \"self\",\n\t\t\t\t\t});\n\t\t\t\t\treturn { status: \"completed\", result: { summary: id } };\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t},\n\n\tasync abort() {\n\t\treturn async () => null;\n\t},\n};\n\nconst headMoved = async (tx: Pick<CoreTx, \"newestEntry\">, conversationId: Id, base: Base) =>\n\t((await tx.newestEntry(conversationId, { withHead: true }))?.id ?? null) !== base.expectedHead;\n\nasync function summarizeNow(\n\ttask: CollapseTask,\n\tbase: Base,\n\truntime: CollapseRuntime,\n\tctx: Context,\n\tcheck?: \"check head\",\n): Promise<CollapseStep> {\n\tconst stale = await runtime.commit(async (tx, current) => {\n\t\tif (check && (await headMoved(tx, current.conversationId, base))) return true;\n\t\ttx.checkpoint({ phase: \"summarizing\", ...base });\n\t\treturn false;\n\t}, ctx);\n\tif (stale) return { done: () => failed(\"stale\", \"head moved during beforeCollapse\") };\n\tconst model = runtime.models.resolve(base.model);\n\tif (model === undefined) return { done: () => failed(\"no_model\", \"model unavailable\") };\n\tconst { messages } = await runtime.context(task.conversationId, task.input.through, ctx);\n\tconst tools = effectiveTools(messages);\n\tconst request: RequestMessage[] = [\n\t\t...messages,\n\t\t...(tools.length > 0\n\t\t\t? [toStored({ role: \"system\", content: \"\", toolsRemoved: tools, timestamp: runtime.now() } as SystemMessage)]\n\t\t\t: []),\n\t\t{\n\t\t\trole: \"user\",\n\t\t\tcontent: `${base.instructions ?? \"Summarize the conversation so far for continuation.\"}\\n\\nRespond with the summary only.`,\n\t\t\ttimestamp: runtime.now(),\n\t\t},\n\t];\n\tlet message: AssistantMessage | undefined;\n\ttry {\n\t\tfor await (const event of runtime.models.stream(\n\t\t\tmodel,\n\t\t\t{ messages: request, thinkingLevel: base.thinkingLevel },\n\t\t\tctx,\n\t\t)) {\n\t\t\tif (event.type === \"done\") {\n\t\t\t\tmessage = event.message;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (event.type === \"error\") {\n\t\t\t\tmessage = event.error;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif (ctx.abortSignal?.aborted) throw error;\n\t\treturn afterFailure(base, null, String(error), runtime);\n\t}\n\tif (message === undefined) return afterFailure(base, null, \"no message\", runtime);\n\tif (message.content.some((content) => content.type === \"toolCall\")) {\n\t\treturn afterFailure(base, { ...message, stopReason: \"error\" }, \"summarizer returned tool calls\", runtime);\n\t}\n\tif (message.stopReason === \"error\")\n\t\treturn afterFailure(base, message, message.errorMessage ?? \"provider error\", runtime);\n\tconst summary = message.content\n\t\t.filter((content): content is { type: \"text\"; text: string } => content.type === \"text\")\n\t\t.map((content) => content.text)\n\t\t.join(\"\");\n\treturn { next: { phase: \"prepared\", summary, ...base } };\n}\n\nfunction afterFailure(\n\tbase: Base,\n\tmessage: AssistantMessage | null,\n\tdetail: string,\n\truntime: CollapseRuntime,\n): CollapseStep {\n\tconst decision = retryDecision(base, message, runtime.now());\n\tif (decision.kind === \"fail\") return { done: () => failed(decision.reason, detail) };\n\treturn {\n\t\tnext: (tx, current) => {\n\t\t\ttx.emit({\n\t\t\t\ttype: \"compaction.retrying\",\n\t\t\t\ttaskId: current.id,\n\t\t\t\tattempt: base.attempt,\n\t\t\t\tretryAt: decision.untilMs,\n\t\t\t\terror: detail,\n\t\t\t});\n\t\t\treturn { phase: \"retrying\", ...base, untilMs: decision.untilMs, lastError: detail };\n\t\t},\n\t};\n}\n\nexport function chooseThrough(entries: Entry[], keepRecent: number): Id | undefined {\n\tconst exchanges: { last: Id; tokens: number }[] = [];\n\tlet open: { last: Id; tokens: number } | undefined;\n\tfor (const entry of entries) {\n\t\tconst role = entry.model?.[0]?.role;\n\t\tconst tokens = estimate((entry.model ?? []) as RequestMessage[]);\n\t\tif (role === \"assistant\") {\n\t\t\topen = { last: entry.id, tokens };\n\t\t\texchanges.push(open);\n\t\t} else if (role === \"toolResult\" && open) {\n\t\t\topen.last = entry.id;\n\t\t\topen.tokens += tokens;\n\t\t} else {\n\t\t\topen = undefined;\n\t\t\texchanges.push({ last: entry.id, tokens });\n\t\t}\n\t}\n\tlet retained = 0;\n\tlet index = exchanges.length - 1;\n\twhile (index >= 0 && retained + exchanges[index]!.tokens <= keepRecent) retained += exchanges[index--]!.tokens;\n\tif (index < 0) return undefined;\n\tif (index === exchanges.length - 1) return exchanges[index - 1]?.last;\n\treturn exchanges[index]!.last;\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import type { AssistantMessage, ToolResultMessage, UserMessage } from "@earendil-works/pi-ai";
2
+ import type { Entry, EntryKind, Id, JsonValue, SystemMessage, ToolControl } from "../types.ts";
3
+ export type UserEntry = Entry & {
4
+ kind: "pi.user";
5
+ model: [UserMessage];
6
+ data?: {
7
+ continuation: true;
8
+ from: Id;
9
+ };
10
+ };
11
+ export type AssistantEntry = Entry & {
12
+ kind: "pi.assistant";
13
+ model: [AssistantMessage];
14
+ data: {
15
+ attempt: number;
16
+ };
17
+ };
18
+ export type ToolResultEntry = Entry & {
19
+ kind: "pi.tool_result";
20
+ model: [ToolResultMessage];
21
+ data: {
22
+ details?: JsonValue;
23
+ diagnostics?: JsonValue;
24
+ control?: ToolControl;
25
+ truncated?: {
26
+ bytes: number;
27
+ lines: number;
28
+ };
29
+ };
30
+ };
31
+ export type SystemEntry = Entry & {
32
+ kind: "pi.system";
33
+ model: [SystemMessage];
34
+ data: {
35
+ baseline: boolean;
36
+ };
37
+ };
38
+ export type NoticeEntry = Entry & {
39
+ kind: "pi.notice";
40
+ model: [UserMessage];
41
+ };
42
+ export type UsageEntry = Entry & {
43
+ kind: "pi.usage";
44
+ data: {
45
+ attempt: number;
46
+ usage?: AssistantMessage["usage"];
47
+ error: string;
48
+ };
49
+ };
50
+ export type SummaryEntry = Entry & {
51
+ kind: "pi.summary";
52
+ model: [UserMessage];
53
+ data: {
54
+ through: Id;
55
+ };
56
+ head: Id;
57
+ };
58
+ export type HandoffEntry = Entry & {
59
+ kind: "pi.handoff";
60
+ model: [UserMessage];
61
+ head: Id;
62
+ };
63
+ export type ResetEntry = Entry & {
64
+ kind: "pi.reset";
65
+ head: Id;
66
+ };
67
+ export declare const entries: {
68
+ readonly user: EntryKind<UserEntry>;
69
+ readonly assistant: EntryKind<AssistantEntry>;
70
+ readonly toolResult: EntryKind<ToolResultEntry>;
71
+ readonly system: EntryKind<SystemEntry>;
72
+ readonly notice: EntryKind<NoticeEntry>;
73
+ readonly usage: EntryKind<UsageEntry>;
74
+ readonly summary: EntryKind<SummaryEntry>;
75
+ readonly handoff: EntryKind<HandoffEntry>;
76
+ readonly reset: EntryKind<ResetEntry>;
77
+ };
78
+ //# sourceMappingURL=entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC9F,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/F,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE;QAAE,YAAY,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AACnH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AACpH,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IACrC,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,CAAC,iBAAiB,CAAC,CAAC;IAC3B,IAAI,EAAE;QACL,OAAO,CAAC,EAAE,SAAS,CAAC;QACpB,WAAW,CAAC,EAAE,SAAS,CAAC;QACxB,OAAO,CAAC,EAAE,WAAW,CAAC;QACtB,SAAS,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;KAC7C,CAAC;CACF,CAAC;AACF,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,aAAa,CAAC,CAAC;IAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC;AAC7G,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAA;CAAE,CAAC;AAC9E,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG;IAChC,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5E,CAAC;AACF,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,EAAE,CAAA;KAAE,CAAC;IAAC,IAAI,EAAE,EAAE,CAAA;CAAE,CAAC;AACjH,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC;IAAC,IAAI,EAAE,EAAE,CAAA;CAAE,CAAC;AAC1F,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,EAAE,CAAA;CAAE,CAAC;AAKhE,eAAO,MAAM,OAAO;;;;;;;;;;CAUV,CAAC","sourcesContent":["import type { AssistantMessage, ToolResultMessage, UserMessage } from \"@earendil-works/pi-ai\";\nimport type { Entry, EntryKind, Id, JsonValue, SystemMessage, ToolControl } from \"../types.ts\";\n\nexport type UserEntry = Entry & { kind: \"pi.user\"; model: [UserMessage]; data?: { continuation: true; from: Id } };\nexport type AssistantEntry = Entry & { kind: \"pi.assistant\"; model: [AssistantMessage]; data: { attempt: number } };\nexport type ToolResultEntry = Entry & {\n\tkind: \"pi.tool_result\";\n\tmodel: [ToolResultMessage];\n\tdata: {\n\t\tdetails?: JsonValue;\n\t\tdiagnostics?: JsonValue;\n\t\tcontrol?: ToolControl;\n\t\ttruncated?: { bytes: number; lines: number };\n\t};\n};\nexport type SystemEntry = Entry & { kind: \"pi.system\"; model: [SystemMessage]; data: { baseline: boolean } };\nexport type NoticeEntry = Entry & { kind: \"pi.notice\"; model: [UserMessage] };\nexport type UsageEntry = Entry & {\n\tkind: \"pi.usage\";\n\tdata: { attempt: number; usage?: AssistantMessage[\"usage\"]; error: string };\n};\nexport type SummaryEntry = Entry & { kind: \"pi.summary\"; model: [UserMessage]; data: { through: Id }; head: Id };\nexport type HandoffEntry = Entry & { kind: \"pi.handoff\"; model: [UserMessage]; head: Id };\nexport type ResetEntry = Entry & { kind: \"pi.reset\"; head: Id };\n\nconst coreEntry = <E extends Entry>(kind: string): EntryKind<E> =>\n\tObject.freeze({ kind, is: (entry: Entry | undefined): entry is E => entry?.kind === kind });\n\nexport const entries = {\n\tuser: coreEntry<UserEntry>(\"pi.user\"),\n\tassistant: coreEntry<AssistantEntry>(\"pi.assistant\"),\n\ttoolResult: coreEntry<ToolResultEntry>(\"pi.tool_result\"),\n\tsystem: coreEntry<SystemEntry>(\"pi.system\"),\n\tnotice: coreEntry<NoticeEntry>(\"pi.notice\"),\n\tusage: coreEntry<UsageEntry>(\"pi.usage\"),\n\tsummary: coreEntry<SummaryEntry>(\"pi.summary\"),\n\thandoff: coreEntry<HandoffEntry>(\"pi.handoff\"),\n\treset: coreEntry<ResetEntry>(\"pi.reset\"),\n} as const;\n"]}
@@ -0,0 +1,13 @@
1
+ const coreEntry = (kind) => Object.freeze({ kind, is: (entry) => entry?.kind === kind });
2
+ export const entries = {
3
+ user: coreEntry("pi.user"),
4
+ assistant: coreEntry("pi.assistant"),
5
+ toolResult: coreEntry("pi.tool_result"),
6
+ system: coreEntry("pi.system"),
7
+ notice: coreEntry("pi.notice"),
8
+ usage: coreEntry("pi.usage"),
9
+ summary: coreEntry("pi.summary"),
10
+ handoff: coreEntry("pi.handoff"),
11
+ reset: coreEntry("pi.reset"),
12
+ };
13
+ //# sourceMappingURL=entries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.js","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/entries.ts"],"names":[],"mappings":"AAyBA,MAAM,SAAS,GAAG,CAAkB,IAAY,EAAgB,EAAE,CACjE,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,KAAwB,EAAc,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC;AAE7F,MAAM,CAAC,MAAM,OAAO,GAAG;IACtB,IAAI,EAAE,SAAS,CAAY,SAAS,CAAC;IACrC,SAAS,EAAE,SAAS,CAAiB,cAAc,CAAC;IACpD,UAAU,EAAE,SAAS,CAAkB,gBAAgB,CAAC;IACxD,MAAM,EAAE,SAAS,CAAc,WAAW,CAAC;IAC3C,MAAM,EAAE,SAAS,CAAc,WAAW,CAAC;IAC3C,KAAK,EAAE,SAAS,CAAa,UAAU,CAAC;IACxC,OAAO,EAAE,SAAS,CAAe,YAAY,CAAC;IAC9C,OAAO,EAAE,SAAS,CAAe,YAAY,CAAC;IAC9C,KAAK,EAAE,SAAS,CAAa,UAAU,CAAC;CAC/B,CAAC","sourcesContent":["import type { AssistantMessage, ToolResultMessage, UserMessage } from \"@earendil-works/pi-ai\";\nimport type { Entry, EntryKind, Id, JsonValue, SystemMessage, ToolControl } from \"../types.ts\";\n\nexport type UserEntry = Entry & { kind: \"pi.user\"; model: [UserMessage]; data?: { continuation: true; from: Id } };\nexport type AssistantEntry = Entry & { kind: \"pi.assistant\"; model: [AssistantMessage]; data: { attempt: number } };\nexport type ToolResultEntry = Entry & {\n\tkind: \"pi.tool_result\";\n\tmodel: [ToolResultMessage];\n\tdata: {\n\t\tdetails?: JsonValue;\n\t\tdiagnostics?: JsonValue;\n\t\tcontrol?: ToolControl;\n\t\ttruncated?: { bytes: number; lines: number };\n\t};\n};\nexport type SystemEntry = Entry & { kind: \"pi.system\"; model: [SystemMessage]; data: { baseline: boolean } };\nexport type NoticeEntry = Entry & { kind: \"pi.notice\"; model: [UserMessage] };\nexport type UsageEntry = Entry & {\n\tkind: \"pi.usage\";\n\tdata: { attempt: number; usage?: AssistantMessage[\"usage\"]; error: string };\n};\nexport type SummaryEntry = Entry & { kind: \"pi.summary\"; model: [UserMessage]; data: { through: Id }; head: Id };\nexport type HandoffEntry = Entry & { kind: \"pi.handoff\"; model: [UserMessage]; head: Id };\nexport type ResetEntry = Entry & { kind: \"pi.reset\"; head: Id };\n\nconst coreEntry = <E extends Entry>(kind: string): EntryKind<E> =>\n\tObject.freeze({ kind, is: (entry: Entry | undefined): entry is E => entry?.kind === kind });\n\nexport const entries = {\n\tuser: coreEntry<UserEntry>(\"pi.user\"),\n\tassistant: coreEntry<AssistantEntry>(\"pi.assistant\"),\n\ttoolResult: coreEntry<ToolResultEntry>(\"pi.tool_result\"),\n\tsystem: coreEntry<SystemEntry>(\"pi.system\"),\n\tnotice: coreEntry<NoticeEntry>(\"pi.notice\"),\n\tusage: coreEntry<UsageEntry>(\"pi.usage\"),\n\tsummary: coreEntry<SummaryEntry>(\"pi.summary\"),\n\thandoff: coreEntry<HandoffEntry>(\"pi.handoff\"),\n\treset: coreEntry<ResetEntry>(\"pi.reset\"),\n} as const;\n"]}
@@ -0,0 +1,9 @@
1
+ import type { AssistantMessage, AssistantMessageFrame } from "@earendil-works/pi-ai";
2
+ /**
3
+ * Apply one encoded frame to the tracked output. Same switch as pi-ai's
4
+ * reduceAssistantMessageFrames (the test oracle); no buffering, no second copy.
5
+ */
6
+ export declare function applyFrame(o: {
7
+ message?: AssistantMessage;
8
+ }, frame: AssistantMessageFrame): void;
9
+ //# sourceMappingURL=frames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/frames.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,gBAAgB,EAChB,qBAAqB,EAIrB,MAAM,uBAAuB,CAAC;AAE/B;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,gBAAgB,CAAA;CAAE,EAAE,KAAK,EAAE,qBAAqB,GAAG,IAAI,CAuDhG","sourcesContent":["import type {\n\tAssistantMessage,\n\tAssistantMessageFrame,\n\tTextContent,\n\tThinkingContent,\n\tToolCall,\n} from \"@earendil-works/pi-ai\";\n\n/**\n * Apply one encoded frame to the tracked output. Same switch as pi-ai's\n * reduceAssistantMessageFrames (the test oracle); no buffering, no second copy.\n */\nexport function applyFrame(o: { message?: AssistantMessage }, frame: AssistantMessageFrame): void {\n\tif (frame.type === \"start\") {\n\t\to.message = frame.partial; // the encoder's clone\n\t\treturn;\n\t}\n\tconst m = o.message;\n\tif (m === undefined) throw new Error(`${frame.type} before start`);\n\tconst c = m.content;\n\tswitch (frame.type) {\n\t\tcase \"text_start\":\n\t\t\tc[frame.contentIndex] = { ...frame.content };\n\t\t\treturn;\n\t\tcase \"text_delta\":\n\t\t\t(c[frame.contentIndex] as TextContent).text += frame.delta;\n\t\t\treturn;\n\t\tcase \"text_end\": {\n\t\t\tconst b = c[frame.contentIndex] as TextContent;\n\t\t\tb.text = frame.content;\n\t\t\tdelete b.textSignature;\n\t\t\tif (frame.textSignature !== undefined) b.textSignature = frame.textSignature;\n\t\t\treturn;\n\t\t}\n\t\tcase \"thinking_start\":\n\t\t\tc[frame.contentIndex] = { ...frame.content };\n\t\t\treturn;\n\t\tcase \"thinking_delta\":\n\t\t\t(c[frame.contentIndex] as ThinkingContent).thinking += frame.delta;\n\t\t\treturn;\n\t\tcase \"thinking_end\": {\n\t\t\tconst b = c[frame.contentIndex] as ThinkingContent;\n\t\t\tb.thinking = frame.content;\n\t\t\tdelete b.thinkingSignature;\n\t\t\tdelete b.redacted;\n\t\t\tif (frame.thinkingSignature !== undefined) b.thinkingSignature = frame.thinkingSignature;\n\t\t\tif (frame.redacted !== undefined) b.redacted = frame.redacted;\n\t\t\treturn;\n\t\t}\n\t\tcase \"toolcall_start\":\n\t\t\tc[frame.contentIndex] = { ...frame.toolCall };\n\t\t\treturn;\n\t\tcase \"toolcall_checkpoint\":\n\t\t\t(c[frame.contentIndex] as ToolCall).arguments = safeParse(frame.json);\n\t\t\treturn;\n\t\tcase \"toolcall_delta\":\n\t\t\treturn; // arguments materialise at checkpoint/end; deltas only feed the encoder's own json buffer\n\t\tcase \"toolcall_end\": {\n\t\t\tconst b = c[frame.contentIndex] as ToolCall;\n\t\t\tb.id = frame.id;\n\t\t\tb.name = frame.name;\n\t\t\tb.arguments = frame.arguments;\n\t\t\tif (frame.thoughtSignature !== undefined) b.thoughtSignature = frame.thoughtSignature;\n\t\t\tif (frame.namespace !== undefined) b.namespace = frame.namespace;\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nfunction safeParse(json: string): ToolCall[\"arguments\"] {\n\ttry {\n\t\treturn JSON.parse(json);\n\t} catch {\n\t\treturn {};\n\t}\n}\n"]}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Apply one encoded frame to the tracked output. Same switch as pi-ai's
3
+ * reduceAssistantMessageFrames (the test oracle); no buffering, no second copy.
4
+ */
5
+ export function applyFrame(o, frame) {
6
+ if (frame.type === "start") {
7
+ o.message = frame.partial; // the encoder's clone
8
+ return;
9
+ }
10
+ const m = o.message;
11
+ if (m === undefined)
12
+ throw new Error(`${frame.type} before start`);
13
+ const c = m.content;
14
+ switch (frame.type) {
15
+ case "text_start":
16
+ c[frame.contentIndex] = { ...frame.content };
17
+ return;
18
+ case "text_delta":
19
+ c[frame.contentIndex].text += frame.delta;
20
+ return;
21
+ case "text_end": {
22
+ const b = c[frame.contentIndex];
23
+ b.text = frame.content;
24
+ delete b.textSignature;
25
+ if (frame.textSignature !== undefined)
26
+ b.textSignature = frame.textSignature;
27
+ return;
28
+ }
29
+ case "thinking_start":
30
+ c[frame.contentIndex] = { ...frame.content };
31
+ return;
32
+ case "thinking_delta":
33
+ c[frame.contentIndex].thinking += frame.delta;
34
+ return;
35
+ case "thinking_end": {
36
+ const b = c[frame.contentIndex];
37
+ b.thinking = frame.content;
38
+ delete b.thinkingSignature;
39
+ delete b.redacted;
40
+ if (frame.thinkingSignature !== undefined)
41
+ b.thinkingSignature = frame.thinkingSignature;
42
+ if (frame.redacted !== undefined)
43
+ b.redacted = frame.redacted;
44
+ return;
45
+ }
46
+ case "toolcall_start":
47
+ c[frame.contentIndex] = { ...frame.toolCall };
48
+ return;
49
+ case "toolcall_checkpoint":
50
+ c[frame.contentIndex].arguments = safeParse(frame.json);
51
+ return;
52
+ case "toolcall_delta":
53
+ return; // arguments materialise at checkpoint/end; deltas only feed the encoder's own json buffer
54
+ case "toolcall_end": {
55
+ const b = c[frame.contentIndex];
56
+ b.id = frame.id;
57
+ b.name = frame.name;
58
+ b.arguments = frame.arguments;
59
+ if (frame.thoughtSignature !== undefined)
60
+ b.thoughtSignature = frame.thoughtSignature;
61
+ if (frame.namespace !== undefined)
62
+ b.namespace = frame.namespace;
63
+ return;
64
+ }
65
+ }
66
+ }
67
+ function safeParse(json) {
68
+ try {
69
+ return JSON.parse(json);
70
+ }
71
+ catch {
72
+ return {};
73
+ }
74
+ }
75
+ //# sourceMappingURL=frames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.js","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/frames.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,CAAiC,EAAE,KAA4B,EAAQ;IACjG,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,sBAAsB;QACjD,OAAO;IACR,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;IACpB,IAAI,CAAC,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,eAAe,CAAC,CAAC;IACnE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;IACpB,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,YAAY;YAChB,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;YAC7C,OAAO;QACR,KAAK,YAAY;YACf,CAAC,CAAC,KAAK,CAAC,YAAY,CAAiB,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC;YAC3D,OAAO;QACR,KAAK,UAAU,EAAE,CAAC;YACjB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,CAAgB,CAAC;YAC/C,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC;YACvB,OAAO,CAAC,CAAC,aAAa,CAAC;YACvB,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;gBAAE,CAAC,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;YAC7E,OAAO;QACR,CAAC;QACD,KAAK,gBAAgB;YACpB,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;YAC7C,OAAO;QACR,KAAK,gBAAgB;YACnB,CAAC,CAAC,KAAK,CAAC,YAAY,CAAqB,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC;YACnE,OAAO;QACR,KAAK,cAAc,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,CAAoB,CAAC;YACnD,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC;YAC3B,OAAO,CAAC,CAAC,iBAAiB,CAAC;YAC3B,OAAO,CAAC,CAAC,QAAQ,CAAC;YAClB,IAAI,KAAK,CAAC,iBAAiB,KAAK,SAAS;gBAAE,CAAC,CAAC,iBAAiB,GAAG,KAAK,CAAC,iBAAiB,CAAC;YACzF,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;gBAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;YAC9D,OAAO;QACR,CAAC;QACD,KAAK,gBAAgB;YACpB,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;YAC9C,OAAO;QACR,KAAK,qBAAqB;YACxB,CAAC,CAAC,KAAK,CAAC,YAAY,CAAc,CAAC,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACtE,OAAO;QACR,KAAK,gBAAgB;YACpB,OAAO,CAAC,0FAA0F;QACnG,KAAK,cAAc,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,CAAa,CAAC;YAC5C,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;YAChB,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;YACpB,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;YAC9B,IAAI,KAAK,CAAC,gBAAgB,KAAK,SAAS;gBAAE,CAAC,CAAC,gBAAgB,GAAG,KAAK,CAAC,gBAAgB,CAAC;YACtF,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;gBAAE,CAAC,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;YACjE,OAAO;QACR,CAAC;IACF,CAAC;AAAA,CACD;AAED,SAAS,SAAS,CAAC,IAAY,EAAyB;IACvD,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD","sourcesContent":["import type {\n\tAssistantMessage,\n\tAssistantMessageFrame,\n\tTextContent,\n\tThinkingContent,\n\tToolCall,\n} from \"@earendil-works/pi-ai\";\n\n/**\n * Apply one encoded frame to the tracked output. Same switch as pi-ai's\n * reduceAssistantMessageFrames (the test oracle); no buffering, no second copy.\n */\nexport function applyFrame(o: { message?: AssistantMessage }, frame: AssistantMessageFrame): void {\n\tif (frame.type === \"start\") {\n\t\to.message = frame.partial; // the encoder's clone\n\t\treturn;\n\t}\n\tconst m = o.message;\n\tif (m === undefined) throw new Error(`${frame.type} before start`);\n\tconst c = m.content;\n\tswitch (frame.type) {\n\t\tcase \"text_start\":\n\t\t\tc[frame.contentIndex] = { ...frame.content };\n\t\t\treturn;\n\t\tcase \"text_delta\":\n\t\t\t(c[frame.contentIndex] as TextContent).text += frame.delta;\n\t\t\treturn;\n\t\tcase \"text_end\": {\n\t\t\tconst b = c[frame.contentIndex] as TextContent;\n\t\t\tb.text = frame.content;\n\t\t\tdelete b.textSignature;\n\t\t\tif (frame.textSignature !== undefined) b.textSignature = frame.textSignature;\n\t\t\treturn;\n\t\t}\n\t\tcase \"thinking_start\":\n\t\t\tc[frame.contentIndex] = { ...frame.content };\n\t\t\treturn;\n\t\tcase \"thinking_delta\":\n\t\t\t(c[frame.contentIndex] as ThinkingContent).thinking += frame.delta;\n\t\t\treturn;\n\t\tcase \"thinking_end\": {\n\t\t\tconst b = c[frame.contentIndex] as ThinkingContent;\n\t\t\tb.thinking = frame.content;\n\t\t\tdelete b.thinkingSignature;\n\t\t\tdelete b.redacted;\n\t\t\tif (frame.thinkingSignature !== undefined) b.thinkingSignature = frame.thinkingSignature;\n\t\t\tif (frame.redacted !== undefined) b.redacted = frame.redacted;\n\t\t\treturn;\n\t\t}\n\t\tcase \"toolcall_start\":\n\t\t\tc[frame.contentIndex] = { ...frame.toolCall };\n\t\t\treturn;\n\t\tcase \"toolcall_checkpoint\":\n\t\t\t(c[frame.contentIndex] as ToolCall).arguments = safeParse(frame.json);\n\t\t\treturn;\n\t\tcase \"toolcall_delta\":\n\t\t\treturn; // arguments materialise at checkpoint/end; deltas only feed the encoder's own json buffer\n\t\tcase \"toolcall_end\": {\n\t\t\tconst b = c[frame.contentIndex] as ToolCall;\n\t\t\tb.id = frame.id;\n\t\t\tb.name = frame.name;\n\t\t\tb.arguments = frame.arguments;\n\t\t\tif (frame.thoughtSignature !== undefined) b.thoughtSignature = frame.thoughtSignature;\n\t\t\tif (frame.namespace !== undefined) b.namespace = frame.namespace;\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nfunction safeParse(json: string): ToolCall[\"arguments\"] {\n\ttry {\n\t\treturn JSON.parse(json);\n\t} catch {\n\t\treturn {};\n\t}\n}\n"]}
@@ -0,0 +1,94 @@
1
+ import type { Context } from "@earendil-works/chord";
2
+ import type { AssistantMessage, DeferredHandle } from "@earendil-works/pi-ai";
3
+ import { type SystemInstructionsHooks } from "../system.ts";
4
+ import { type CoreKind, type HookInfo, type HookResult, type Id, type ModelRef, type RequestMessage, type RetryPolicy, type Stored, type ThinkingLevel } from "../types.ts";
5
+ export type GenerationInput = {
6
+ inputs: Id[];
7
+ };
8
+ /** Captured once at `prepared`; carried unchanged except `attempt`. */
9
+ type Prep = {
10
+ cutoff: Id;
11
+ system: Id | null;
12
+ model: ModelRef;
13
+ thinkingLevel: ThinkingLevel;
14
+ tools: string[];
15
+ retry: RetryPolicy;
16
+ attempt: number;
17
+ };
18
+ export type GenerationCheckpoint = ({
19
+ phase: "prepared";
20
+ } & Prep) | ({
21
+ phase: "requesting";
22
+ } & Prep) | ({
23
+ phase: "retrying";
24
+ untilMs: number;
25
+ lastError: string;
26
+ } & Prep) | ({
27
+ phase: "deferred";
28
+ handle: Stored<DeferredHandle>;
29
+ pollAt: number;
30
+ } & Prep);
31
+ export type GenerationResult = {
32
+ assistant: Id;
33
+ tools: Id[];
34
+ postTools?: Id;
35
+ successor?: Id;
36
+ };
37
+ export type GenerationFailure = {
38
+ reason: "provider" | "overflow" | "retries_exhausted" | "no_model";
39
+ detail: string;
40
+ assistant?: Id;
41
+ };
42
+ /** Display-only assistant entries (provider error, aborted partial) carry the message here, never in `model`. */
43
+ export type DisplayAssistantData = {
44
+ attempt: number;
45
+ display: Stored<AssistantMessage>;
46
+ reason: "error" | "aborted";
47
+ };
48
+ /** Hook points this kind calls. Register with `h.hooks(namespace, kinds.generation, { … })`. */
49
+ export interface GenerationHooks extends SystemInstructionsHooks {
50
+ /** Before every request (also on retry and recovery). Chain. */
51
+ beforeRequest(request: {
52
+ messages: RequestMessage[];
53
+ }, info: HookInfo & {
54
+ cutoff: Id;
55
+ }, ctx: Context): HookResult<{
56
+ messages: RequestMessage[];
57
+ }>;
58
+ /** Every terminal provider message, before classification. Observer. */
59
+ afterResponse(message: AssistantMessage, info: HookInfo & {
60
+ attempt: number;
61
+ }, ctx: Context): void | Promise<void>;
62
+ /** On a final answer with nothing queued. First `{ continue }` wins and starts a continuation. */
63
+ onYield(answer: AssistantMessage, info: HookInfo, ctx: Context): HookResult<{
64
+ continue: string;
65
+ }>;
66
+ }
67
+ /** Configuration this kind reads, with defaults. `model` has none: absent → failed/no_model. */
68
+ export declare const generationConfig: {
69
+ readonly rewindable: {
70
+ readonly model: ModelRef | undefined;
71
+ readonly thinkingLevel: ThinkingLevel;
72
+ readonly selectedTools: string[];
73
+ readonly profile: string;
74
+ };
75
+ readonly sticky: {
76
+ readonly retry: RetryPolicy;
77
+ };
78
+ };
79
+ export declare const generation: CoreKind<GenerationInput, GenerationCheckpoint, GenerationResult, GenerationFailure, {
80
+ assistant?: Id;
81
+ }, GenerationHooks, typeof generationConfig>;
82
+ type RetryDecision = {
83
+ kind: "retry";
84
+ untilMs: number;
85
+ } | {
86
+ kind: "fail";
87
+ reason: "provider" | "retries_exhausted";
88
+ };
89
+ export declare function retryDecision(cp: {
90
+ retry: RetryPolicy;
91
+ attempt: number;
92
+ }, message: AssistantMessage | null, now: number): RetryDecision;
93
+ export {};
94
+ //# sourceMappingURL=generation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generation.d.ts","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/generation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport type {\n\tAssistantMessage,\n\tAssistantMessageEvent,\n\tAssistantMessageFrame,\n\tDeferredHandle,\n\tToolCall,\n} from \"@earendil-works/pi-ai\";\nimport { AssistantMessageFrameEncoder, isRetryableAssistantError } from \"@earendil-works/pi-ai\";\nimport { estimateContextTokens } from \"@earendil-works/pi-ai/utils/estimate\";\nimport { planManagedEntry, prepareDraft, type SystemInstructionsHooks, sameSnapshot, takeSnapshot } from \"../system.ts\";\nimport {\n\ttype Closure,\n\ttype Completion,\n\ttype CoreKind,\n\ttype CoreTx,\n\ttype HookInfo,\n\ttype HookResult,\n\ttype Id,\n\ttype ModelRef,\n\ttype RequestMessage,\n\ttype RetryPolicy,\n\ttype Runtime,\n\ttype Step,\n\ttype Stored,\n\ttype Task,\n\ttype ThinkingLevel,\n\ttoStored,\n} from \"../types.ts\";\nimport { chooseThrough } from \"./collapse.ts\";\nimport { applyFrame } from \"./frames.ts\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type GenerationInput = { inputs: Id[] };\n/** Captured once at `prepared`; carried unchanged except `attempt`. */\ntype Prep = {\n\tcutoff: Id;\n\tsystem: Id | null;\n\tmodel: ModelRef;\n\tthinkingLevel: ThinkingLevel;\n\ttools: string[];\n\tretry: RetryPolicy;\n\tattempt: number;\n};\nexport type GenerationCheckpoint =\n\t| ({ phase: \"prepared\" } & Prep)\n\t| ({ phase: \"requesting\" } & Prep)\n\t| ({ phase: \"retrying\"; untilMs: number; lastError: string } & Prep)\n\t| ({ phase: \"deferred\"; handle: Stored<DeferredHandle>; pollAt: number } & Prep);\nexport type GenerationResult = { assistant: Id; tools: Id[]; postTools?: Id; successor?: Id };\nexport type GenerationFailure = {\n\treason: \"provider\" | \"overflow\" | \"retries_exhausted\" | \"no_model\";\n\tdetail: string;\n\tassistant?: Id;\n};\n\n/** Display-only assistant entries (provider error, aborted partial) carry the message here, never in `model`. */\nexport type DisplayAssistantData = { attempt: number; display: Stored<AssistantMessage>; reason: \"error\" | \"aborted\" };\n\n/** Hook points this kind calls. Register with `h.hooks(namespace, kinds.generation, { … })`. */\nexport interface GenerationHooks extends SystemInstructionsHooks {\n\t/** Before every request (also on retry and recovery). Chain. */\n\tbeforeRequest(\n\t\trequest: { messages: RequestMessage[] },\n\t\tinfo: HookInfo & { cutoff: Id },\n\t\tctx: Context,\n\t): HookResult<{ messages: RequestMessage[] }>;\n\t/** Every terminal provider message, before classification. Observer. */\n\tafterResponse(message: AssistantMessage, info: HookInfo & { attempt: number }, ctx: Context): void | Promise<void>;\n\t/** On a final answer with nothing queued. First `{ continue }` wins and starts a continuation. */\n\tonYield(answer: AssistantMessage, info: HookInfo, ctx: Context): HookResult<{ continue: string }>;\n}\n\n/** Configuration this kind reads, with defaults. `model` has none: absent → failed/no_model. */\nexport const generationConfig = {\n\trewindable: {\n\t\tmodel: undefined as ModelRef | undefined,\n\t\tthinkingLevel: \"off\" as ThinkingLevel,\n\t\tselectedTools: [] as string[],\n\t\tprofile: \"default\" as string,\n\t},\n\tsticky: { retry: { enabled: true, maxRetries: 3, baseDelayMs: 2000, maxAgentDelayMs: 60_000 } as RetryPolicy },\n} as const;\n\ntype G = Task<GenerationInput, GenerationCheckpoint>;\ntype Rt = Runtime<GenerationHooks, CoreTx>;\ntype S = Step<GenerationCheckpoint, GenerationResult, GenerationFailure, CoreTx>;\ntype Out = Closure<GenerationResult, GenerationFailure, CoreTx>;\n\nconst fail = (\n\treason: GenerationFailure[\"reason\"],\n\tdetail: string,\n\tassistant?: Id,\n): Completion<GenerationResult, GenerationFailure> => ({\n\tstatus: \"failed\",\n\tfailure: { reason, detail, ...(assistant === undefined ? {} : { assistant }) },\n});\n/** Every terminal generation failure settles its group and admits queued triggers at the final boundary. */\nconst failStep = (task: G, reason: GenerationFailure[\"reason\"], detail: string): S => ({\n\tdone: async (tx, current) => {\n\t\tconst head = await tx.newestEntry(current.conversationId, { withHead: true });\n\t\ttx.emit({ type: \"generation.failed\", taskId: current.id, reason, detail });\n\t\tawait settleFailedTurn(tx, current, task.input.inputs, detail, head?.id);\n\t\treturn fail(reason, detail);\n\t},\n});\n/** pi-ai drops system messages and aborted/error assistant messages before sending; estimate what is sent. */\nconst estimate = (messages: RequestMessage[]) =>\n\testimateContextTokens(\n\t\tmessages.filter(\n\t\t\t(m) =>\n\t\t\t\tm.role !== \"system\" &&\n\t\t\t\t!(m.role === \"assistant\" && (m.stopReason === \"aborted\" || m.stopReason === \"error\")),\n\t\t) as never,\n\t).tokens;\nconst emptyUsage = (): AssistantMessage[\"usage\"] => ({\n\tinput: 0,\n\toutput: 0,\n\tcacheRead: 0,\n\tcacheWrite: 0,\n\ttotalTokens: 0,\n\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n});\n\n// ---------------------------------------------------------------------------\n// The kind\n// ---------------------------------------------------------------------------\n\nexport const generation: CoreKind<\n\tGenerationInput,\n\tGenerationCheckpoint,\n\tGenerationResult,\n\tGenerationFailure,\n\t{ assistant?: Id },\n\tGenerationHooks,\n\ttypeof generationConfig\n> = {\n\tname: \"pi.generation\",\n\tturn: true,\n\tconfig: generationConfig,\n\tinflight: [\"requesting\"],\n\n\t// Step 1 (§12.5): snapshot S on the line; systemInstructions off the line; then in one\n\t// commit re-take S′, retry if it moved, else append the managed entry and checkpoint\n\t// `prepared` with the cutoff. Runs once per generation; never on recovery.\n\tasync initial(task, rt, ctx) {\n\t\tconst { snapshot, canonical, seed } = await rt.commit(\n\t\t\t(tx, current) => takeSnapshot(tx, current.conversationId, rt.registries.sections, rt.registries.tools),\n\t\t\tctx,\n\t\t);\n\t\tif (snapshot.settings.model === undefined) return failStep(task, \"no_model\", \"no model configured\");\n\t\tconst retry = (await rt.sticky(task.conversationId, ctx)).retry;\n\n\t\tconst warnings: string[] = [];\n\t\tconst { desired, tools } = await prepareDraft(\n\t\t\trt,\n\t\t\trt.registries.sections,\n\t\t\tcanonical,\n\t\t\tseed,\n\t\t\tsnapshot.settings,\n\t\t\t(message) => warnings.push(message),\n\t\t\tctx,\n\t\t);\n\t\tconst model = snapshot.settings.model as unknown as ModelRef;\n\t\tconst thinkingLevel = snapshot.settings.thinkingLevel as ThinkingLevel;\n\n\t\treturn {\n\t\t\tnext: async (tx, current) => {\n\t\t\t\tconst c = current.conversationId;\n\t\t\t\tconst again = await takeSnapshot(tx, c, rt.registries.sections, rt.registries.tools);\n\t\t\t\tif (!sameSnapshot(again.snapshot, snapshot)) return \"retry\";\n\t\t\t\tconst plan = await planManagedEntry(tx, c, snapshot, canonical, desired, tools, rt.now());\n\t\t\t\tfor (const message of warnings) tx.emit({ type: \"warning\", source: \"generation\", message });\n\t\t\t\tconst system = plan === undefined ? null : tx.appendEntry(c, { kind: \"pi.system\", ...plan });\n\t\t\t\tconst cutoff = system ?? (await tx.newestEntry(c))!.id;\n\t\t\t\ttx.sticky(c).turn = { tools: [] }; // a new generation starts a fresh turn view\n\t\t\t\treturn {\n\t\t\t\t\tphase: \"prepared\",\n\t\t\t\t\tcutoff,\n\t\t\t\t\tsystem,\n\t\t\t\t\tmodel,\n\t\t\t\t\tthinkingLevel,\n\t\t\t\t\ttools: tools.map((t) => t.name),\n\t\t\t\t\tretry,\n\t\t\t\t\tattempt: 0,\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t},\n\n\tphases: {\n\t\t// Step 2: derive the request from the cutoff (hooks rerun), overflow check, write the\n\t\t// in-flight checkpoint, call the provider. Reached fresh from `initial`/`retrying`,\n\t\t// and again on recovery of `prepared` (step 1 never reruns).\n\t\tasync prepared(task, rt, ctx) {\n\t\t\tconst cp = task.checkpoint;\n\t\t\tconst model = rt.models.resolve(cp.model);\n\t\t\tif (model === undefined)\n\t\t\t\treturn failStep(task, \"no_model\", `model ${cp.model.provider}/${cp.model.modelId} unavailable`);\n\t\t\tconst derived = await derive(task, cp, rt, ctx);\n\t\t\tif (estimate(derived.messages) > model.contextWindow - model.maxTokens) return overflow(task, rt);\n\t\t\tconst attempt = cp.attempt + 1;\n\t\t\tawait rt.commit((tx) => {\n\t\t\t\ttx.checkpoint({ ...cp, phase: \"requesting\", attempt });\n\t\t\t\ttx.emit({ type: \"generation.started\", taskId: task.id, attempt });\n\t\t\t}, ctx); // before the effect\n\t\t\tconst { terminal, deferred } = await stream(cp, model, derived.messages, rt, ctx);\n\t\t\tif (deferred !== undefined) {\n\t\t\t\tconst pollAt = rt.now() + (deferred.pollAfterMs ?? 5000);\n\t\t\t\treturn {\n\t\t\t\t\tnext: (tx) => {\n\t\t\t\t\t\ttx.emit({ type: \"generation.deferred\", taskId: task.id, pollAt });\n\t\t\t\t\t\treturn { ...cp, phase: \"deferred\", attempt, handle: toStored(deferred), pollAt };\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn classify(task, { ...cp, attempt }, terminal!, rt, ctx);\n\t\t},\n\n\t\t// Only reached by the scheduler after a crash: the call may have happened and there\n\t\t// is no result lookup. Count it as a failed attempt and retry per policy.\n\t\tasync requesting(task, rt) {\n\t\t\tconst cp = task.checkpoint;\n\t\t\tconst decision = retryDecision(cp, null, rt.now());\n\t\t\tif (decision.kind === \"fail\") return failStep(task, decision.reason, \"interrupted\");\n\t\t\treturn {\n\t\t\t\tnext: (tx, current) => {\n\t\t\t\t\ttx.appendEntry(current.conversationId, {\n\t\t\t\t\t\tkind: \"pi.usage\",\n\t\t\t\t\t\tdata: { attempt: cp.attempt, error: \"interrupted\" },\n\t\t\t\t\t});\n\t\t\t\t\ttx.emit({\n\t\t\t\t\t\ttype: \"generation.retrying\",\n\t\t\t\t\t\ttaskId: current.id,\n\t\t\t\t\t\tattempt: cp.attempt,\n\t\t\t\t\t\tretryAt: decision.untilMs,\n\t\t\t\t\t\terror: \"interrupted\",\n\t\t\t\t\t});\n\t\t\t\t\treturn { ...cp, phase: \"retrying\", untilMs: decision.untilMs, lastError: \"interrupted\" };\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\t// Durable backoff, then back to step 2. Resets the streaming view.\n\t\tasync retrying(task, rt, ctx) {\n\t\t\tconst cp = task.checkpoint;\n\t\t\tawait rt.sleep(cp.untilMs, ctx);\n\t\t\tconst { untilMs: _u, lastError: _e, ...prep } = cp;\n\t\t\treturn {\n\t\t\t\tnext: (tx, current) => {\n\t\t\t\t\ttx.sticky(current.conversationId).turn.message = undefined;\n\t\t\t\t\treturn { ...prep, phase: \"prepared\" };\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\n\t\t// Provider-side async. Poll; not the retry backoff. Same after a crash: the handle is durable.\n\t\tasync deferred(task, rt, ctx) {\n\t\t\tconst cp = task.checkpoint;\n\t\t\tconst model = rt.models.resolve(cp.model);\n\t\t\tif (model === undefined) return failStep(task, \"no_model\", \"model disappeared\");\n\t\t\tawait rt.sleep(cp.pollAt, ctx);\n\t\t\tconst result = await rt.models.fetchDeferred(model, cp.handle, ctx);\n\t\t\tif (\"deferred\" in result && result.deferred !== undefined) {\n\t\t\t\tconst pollAt = rt.now() + (result.deferred.pollAfterMs ?? 5000);\n\t\t\t\treturn {\n\t\t\t\t\tnext: (tx) => {\n\t\t\t\t\t\ttx.emit({ type: \"generation.deferred\", taskId: task.id, pollAt });\n\t\t\t\t\t\treturn { ...cp, handle: toStored(result.deferred), pollAt };\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst message = result as AssistantMessage;\n\t\t\tawait rt.commit((tx, current) => {\n\t\t\t\ttx.sticky(current.conversationId).turn.message = toStored(message);\n\t\t\t}, ctx);\n\t\t\tconst { handle: _handle, pollAt: _pollAt, ...prep } = cp;\n\t\t\treturn classify(task, prep, message, rt, ctx);\n\t\t},\n\t},\n\n\tasync abort(task, rt, ctx) {\n\t\tconst cp = task.checkpoint;\n\t\tif (cp?.phase === \"deferred\") {\n\t\t\tconst model = rt.models.resolve(cp.model);\n\t\t\tif (model) await rt.models.cancelDeferred(model, cp.handle as DeferredHandle, ctx).catch(() => {});\n\t\t}\n\t\treturn async (tx, current) => {\n\t\t\tconst partial = tx.sticky(current.conversationId).turn.message;\n\t\t\tlet assistant: Id | undefined;\n\t\t\t// Display-only: the partial goes in `data`, never in `model`, so it cannot enter a request.\n\t\t\tif (partial && partial.content.length > 0)\n\t\t\t\tassistant = tx.appendEntry(current.conversationId, {\n\t\t\t\t\tkind: \"pi.assistant\",\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tattempt: cp?.attempt ?? 0,\n\t\t\t\t\t\tdisplay: { ...toStored(partial), stopReason: \"aborted\" },\n\t\t\t\t\t\treason: \"aborted\",\n\t\t\t\t\t} satisfies DisplayAssistantData,\n\t\t\t\t});\n\t\t\ttx.sticky(current.conversationId).turn = { tools: [] };\n\t\t\tawait tx.resolveInputs(task.input.inputs, { status: \"unanswered\", reason: \"aborted\" });\n\t\t\ttx.emit({ type: \"turn.ended\", inputs: task.input.inputs, status: \"unanswered\", reason: \"aborted\" });\n\t\t\treturn assistant === undefined ? {} : { assistant };\n\t\t};\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Request derivation and streaming\n// ---------------------------------------------------------------------------\n\nasync function derive(task: G, cp: Prep, rt: Rt, ctx: Context): Promise<{ messages: RequestMessage[] }> {\n\tconst { messages } = await rt.context(task.conversationId, cp.cutoff, ctx);\n\tlet request = { messages };\n\tawait rt.hooks.each(\n\t\tctx,\n\t\t(h, api) => h.beforeRequest?.(request, { ...api, cutoff: cp.cutoff }, ctx),\n\t\t(value) => {\n\t\t\tif (value !== undefined) request = value;\n\t\t},\n\t);\n\treturn request;\n}\n\n/** Stream, coalescing frames into the turn view. Flush on size or time. */\nasync function stream(\n\tcp: Prep,\n\tmodel: NonNullable<ReturnType<Rt[\"models\"][\"resolve\"]>>,\n\tmessages: RequestMessage[],\n\trt: Rt,\n\tctx: Context,\n): Promise<{ terminal?: AssistantMessage; deferred?: DeferredHandle }> {\n\tconst encoder = new AssistantMessageFrameEncoder();\n\tlet pending: AssistantMessageFrame[] = [];\n\tlet pendingBytes = 0;\n\tlet lastFlush = rt.now();\n\tlet terminal: AssistantMessage | undefined;\n\tlet deferred: DeferredHandle | undefined;\n\tlet flushedContent = false;\n\tconst flush = async () => {\n\t\tconst batch = pending;\n\t\tif (batch.length === 0) return;\n\t\tpending = [];\n\t\tpendingBytes = 0;\n\t\tlastFlush = rt.now();\n\t\tif (batch.some((frame) => \"delta\" in frame)) flushedContent = true;\n\t\tawait rt.commit((tx, current) => {\n\t\t\tconst turn = tx.sticky(current.conversationId).turn;\n\t\t\tfor (const f of batch) applyFrame(turn as { message?: AssistantMessage }, f);\n\t\t}, ctx);\n\t};\n\ttry {\n\t\tconst iterator = rt.models\n\t\t\t.stream(model, { messages, thinkingLevel: cp.thinkingLevel }, ctx)\n\t\t\t[Symbol.asyncIterator]();\n\t\tlet pull = iterator.next();\n\t\tfor (;;) {\n\t\t\tconst selected: { kind: \"event\"; result: IteratorResult<AssistantMessageEvent> } | { kind: \"flush\" } =\n\t\t\t\tpending.length === 0\n\t\t\t\t\t? { kind: \"event\", result: await pull }\n\t\t\t\t\t: await Promise.race([\n\t\t\t\t\t\t\tpull.then((result) => ({ kind: \"event\" as const, result })),\n\t\t\t\t\t\t\trt.sleep(lastFlush + 100, ctx).then(() => ({ kind: \"flush\" as const })),\n\t\t\t\t\t\t]);\n\t\t\tif (selected.kind === \"flush\") {\n\t\t\t\tawait flush();\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (selected.result.done) break;\n\t\t\tconst event = selected.result.value;\n\t\t\tif (event.type === \"done\") {\n\t\t\t\tencoder.encode(event);\n\t\t\t\tif (event.reason === \"deferred\" && event.message.deferred !== undefined) deferred = event.message.deferred;\n\t\t\t\telse terminal = event.message;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (event.type === \"error\") {\n\t\t\t\tencoder.encode(event);\n\t\t\t\tterminal = event.error;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tconst frame = encoder.encode(event);\n\t\t\tpull = iterator.next();\n\t\t\tif (frame === undefined) continue;\n\t\t\tpending.push(frame);\n\t\t\tpendingBytes += \"delta\" in frame ? frame.delta.length : 64;\n\t\t\tif (pendingBytes >= 256 || (!flushedContent && \"delta\" in frame)) await flush();\n\t\t}\n\t} catch (error) {\n\t\tif (ctx.abortSignal?.aborted) throw error;\n\t\tterminal = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: emptyUsage(),\n\t\t\tstopReason: \"error\",\n\t\t\terrorMessage: String(error),\n\t\t\ttimestamp: rt.now(),\n\t\t};\n\t}\n\tif (pending.length > 0) await flush();\n\tif (terminal === undefined && deferred === undefined) throw new Error(\"stream ended without a terminal message\");\n\treturn { terminal, deferred };\n}\n\n// ---------------------------------------------------------------------------\n// Steps 3–5: classify the terminal message.\n// ---------------------------------------------------------------------------\n\nasync function classify(task: G, cp: Prep, message: AssistantMessage, rt: Rt, ctx: Context): Promise<S> {\n\tawait rt.hooks.each(ctx, (h, api) => h.afterResponse?.(message, { ...api, attempt: cp.attempt }, ctx));\n\n\tif (message.stopReason === \"error\") {\n\t\tconst decision = retryDecision(cp, message, rt.now());\n\t\tif (decision.kind === \"retry\") {\n\t\t\treturn {\n\t\t\t\tnext: (tx, current) => {\n\t\t\t\t\ttx.appendEntry(current.conversationId, {\n\t\t\t\t\t\tkind: \"pi.usage\",\n\t\t\t\t\t\tdata: {\n\t\t\t\t\t\t\tattempt: cp.attempt,\n\t\t\t\t\t\t\tusage: toStored(message.usage),\n\t\t\t\t\t\t\terror: message.errorMessage ?? \"provider error\",\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\ttx.emit({\n\t\t\t\t\t\ttype: \"generation.retrying\",\n\t\t\t\t\t\ttaskId: current.id,\n\t\t\t\t\t\tattempt: cp.attempt,\n\t\t\t\t\t\tretryAt: decision.untilMs,\n\t\t\t\t\t\terror: message.errorMessage ?? \"provider error\",\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...cp,\n\t\t\t\t\t\tphase: \"retrying\",\n\t\t\t\t\t\tuntilMs: decision.untilMs,\n\t\t\t\t\t\tlastError: message.errorMessage ?? \"provider error\",\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t\treturn { done: terminalError(task, cp, message, decision.reason) };\n\t}\n\tif (message.stopReason === \"aborted\") return { done: terminalError(task, cp, message, \"provider\") };\n\n\tconst calls = message.content.filter((c): c is ToolCall => c.type === \"toolCall\");\n\tconst stored = toStored(message);\n\tif (calls.length > 0) {\n\t\treturn {\n\t\t\tdone: async (tx, current) => {\n\t\t\t\tconst c = current.conversationId;\n\t\t\t\tconst collapseThrough = await thresholdCollapseThrough(tx, c, message); // read BEFORE the append\n\t\t\t\tconst assistant = tx.appendEntry(c, {\n\t\t\t\t\tkind: \"pi.assistant\",\n\t\t\t\t\tmodel: [stored],\n\t\t\t\t\tdata: { attempt: cp.attempt },\n\t\t\t\t});\n\t\t\t\tconst turn = tx.sticky(c).turn;\n\t\t\t\tturn.message = undefined;\n\t\t\t\tturn.tools = calls.map((call) => ({\n\t\t\t\t\tcallId: call.id,\n\t\t\t\t\tname: call.name,\n\t\t\t\t\targs: toStored(call.arguments),\n\t\t\t\t\tstatus: \"pending\",\n\t\t\t\t}));\n\t\t\t\tconst tools = calls.map((call, index) =>\n\t\t\t\t\ttx.createTask({ kind: \"pi.tool\", input: { assistant, call: toStored(call), offered: cp.tools, index } }),\n\t\t\t\t);\n\t\t\t\tconst postTools = tx.createTask({\n\t\t\t\t\tkind: \"pi.post_tools\",\n\t\t\t\t\tafter: tools,\n\t\t\t\t\tinput: { inputs: task.input.inputs, assistant, tools },\n\t\t\t\t});\n\t\t\t\tif (collapseThrough !== undefined)\n\t\t\t\t\ttx.createTask({ kind: \"pi.collapse\", input: { reason: \"threshold\", through: collapseThrough } });\n\t\t\t\ttx.emit({ type: \"generation.completed\", taskId: current.id, entry: assistant, toolCalls: calls.length });\n\t\t\t\treturn { status: \"completed\", result: { assistant, tools, postTools } };\n\t\t\t},\n\t\t};\n\t}\n\n\tlet continueText: string | undefined;\n\tawait rt.hooks.each(\n\t\tctx,\n\t\t(h, api) => h.onYield?.(message, api, ctx),\n\t\t(value) => {\n\t\t\tif (value === undefined) return;\n\t\t\tcontinueText = value.continue;\n\t\t\treturn true;\n\t\t},\n\t);\n\treturn {\n\t\tdone: async (tx, current) => {\n\t\t\tconst c = current.conversationId;\n\t\t\tconst collapseThrough = await thresholdCollapseThrough(tx, c, message); // read BEFORE the append\n\t\t\tconst head = await tx.newestEntry(c, { withHead: true });\n\t\t\tconst assistant = tx.appendEntry(c, { kind: \"pi.assistant\", model: [stored], data: { attempt: cp.attempt } });\n\t\t\ttx.emit({ type: \"generation.completed\", taskId: current.id, entry: assistant, toolCalls: 0 });\n\t\t\ttx.sticky(c).turn = { tools: [] };\n\t\t\tif (collapseThrough !== undefined)\n\t\t\t\ttx.createTask({ kind: \"pi.collapse\", input: { reason: \"threshold\", through: collapseThrough } });\n\t\t\tconst { triggers, terminated } = await tx.boundary(c, \"final\", head?.id);\n\t\t\tif (continueText !== undefined && triggers.length === 0 && !terminated) {\n\t\t\t\ttx.appendEntry(c, {\n\t\t\t\t\tkind: \"pi.user\",\n\t\t\t\t\tmodel: [{ role: \"user\", content: continueText, timestamp: rt.now() }],\n\t\t\t\t\tdata: { continuation: true, from: assistant },\n\t\t\t\t});\n\t\t\t\tconst successor = tx.createTask({ kind: \"pi.generation\", input: { inputs: task.input.inputs } });\n\t\t\t\treturn { status: \"completed\", result: { assistant, tools: [], successor } };\n\t\t\t}\n\t\t\tawait tx.resolveInputs(task.input.inputs, { status: \"done\", answer: assistant });\n\t\t\ttx.emit({ type: \"turn.ended\", inputs: task.input.inputs, status: \"done\", answer: assistant });\n\t\t\tif (triggers.length > 0) {\n\t\t\t\tconst successor = tx.createTask({ kind: \"pi.generation\", input: { inputs: triggers } });\n\t\t\t\ttx.emit({ type: \"turn.started\", inputs: triggers });\n\t\t\t\treturn { status: \"completed\", result: { assistant, tools: [], successor } };\n\t\t\t}\n\t\t\treturn { status: \"completed\", result: { assistant, tools: [] } };\n\t\t},\n\t};\n}\n\n/** Provider error / aborted stop: a display-only assistant entry (no `model`), inputs unanswered. */\nfunction terminalError(task: G, cp: Prep, message: AssistantMessage, reason: \"provider\" | \"retries_exhausted\"): Out {\n\treturn async (tx, current) => {\n\t\tconst head = await tx.newestEntry(current.conversationId, { withHead: true });\n\t\tconst assistant = tx.appendEntry(current.conversationId, {\n\t\t\tkind: \"pi.assistant\",\n\t\t\tdata: {\n\t\t\t\tattempt: cp.attempt,\n\t\t\t\tdisplay: toStored(message),\n\t\t\t\treason: message.stopReason === \"aborted\" ? \"aborted\" : \"error\",\n\t\t\t} satisfies DisplayAssistantData,\n\t\t});\n\t\ttx.emit({\n\t\t\ttype: \"generation.failed\",\n\t\t\ttaskId: current.id,\n\t\t\treason,\n\t\t\tdetail: message.errorMessage ?? \"provider error\",\n\t\t\tentry: assistant,\n\t\t});\n\t\tawait settleFailedTurn(tx, current, task.input.inputs, message.errorMessage ?? \"provider error\", head?.id);\n\t\treturn fail(reason, message.errorMessage ?? \"provider error\", assistant);\n\t};\n}\n\nasync function settleFailedTurn(\n\ttx: CoreTx,\n\tcurrent: Task,\n\tinputs: readonly Id[],\n\tdetail: string,\n\theadBoundary: Id | undefined,\n): Promise<void> {\n\ttx.sticky(current.conversationId).turn = { tools: [] };\n\tawait tx.resolveInputs(inputs, { status: \"unanswered\", reason: \"failed\", detail });\n\ttx.emit({ type: \"turn.ended\", inputs: [...inputs], status: \"unanswered\", reason: \"failed\", detail });\n\tconst { triggers } = await tx.boundary(current.conversationId, \"final\", headBoundary);\n\tif (triggers.length > 0) {\n\t\ttx.createTask({ kind: \"pi.generation\", conversationId: current.conversationId, input: { inputs: triggers } });\n\t\ttx.emit({ type: \"turn.started\", inputs: triggers });\n\t}\n}\n\ntype RetryDecision = { kind: \"retry\"; untilMs: number } | { kind: \"fail\"; reason: \"provider\" | \"retries_exhausted\" };\nexport function retryDecision(\n\tcp: { retry: RetryPolicy; attempt: number },\n\tmessage: AssistantMessage | null,\n\tnow: number,\n): RetryDecision {\n\tif (message !== null && !isRetryableAssistantError(message)) return { kind: \"fail\", reason: \"provider\" };\n\tif (!cp.retry.enabled) return { kind: \"fail\", reason: \"provider\" };\n\tif (cp.attempt > cp.retry.maxRetries) return { kind: \"fail\", reason: \"retries_exhausted\" };\n\tconst delay = cp.retry.baseDelayMs * 2 ** Math.max(0, cp.attempt - 1);\n\tconst safeDelay = Number.isSafeInteger(delay) ? delay : Number.MAX_SAFE_INTEGER;\n\treturn { kind: \"retry\", untilMs: now + Math.min(safeDelay, cp.retry.maxAgentDelayMs ?? 60_000) };\n}\n\n// ---------------------------------------------------------------------------\n// Collapse triggers\n// ---------------------------------------------------------------------------\n\n/** Read-side of threshold collapse: computed before the assistant is appended, applied after. */\nasync function thresholdCollapseThrough(\n\ttx: CoreTx,\n\tconversationId: Id,\n\tmessage: AssistantMessage,\n): Promise<Id | undefined> {\n\tif ((await tx.tasks({ conversationId, kind: \"pi.collapse\", status: [\"pending\", \"running\"] })).length > 0)\n\t\treturn undefined;\n\tconst state = tx.rewindable(conversationId);\n\tif (state.threshold <= 0) return undefined;\n\tconst { entries, messages } = await tx.context(conversationId);\n\tconst used = Math.max(\n\t\t(message.usage?.input ?? 0) + (message.usage?.output ?? 0),\n\t\testimate([...messages, toStored(message)]),\n\t);\n\tif (used <= state.threshold) return undefined;\n\treturn chooseThrough(entries, state.keepRecent);\n}\n\nfunction overflow(task: G, rt: Rt): S {\n\treturn {\n\t\tdone: async (tx, current) => {\n\t\t\tconst c = current.conversationId;\n\t\t\tconst state = tx.rewindable(c);\n\t\t\tconst { entries, head } = await tx.context(c);\n\t\t\tconst through = chooseThrough(entries, state.keepRecent);\n\t\t\ttx.sticky(c).turn = { tools: [] };\n\t\t\tif (through === undefined) {\n\t\t\t\tawait tx.write(c, {\n\t\t\t\t\tkind: \"pi.notice\",\n\t\t\t\t\tmodel: [{ role: \"user\", content: \"Context too large; nothing to compact.\", timestamp: rt.now() }],\n\t\t\t\t});\n\t\t\t\ttx.emit({\n\t\t\t\t\ttype: \"generation.failed\",\n\t\t\t\t\ttaskId: current.id,\n\t\t\t\t\treason: \"overflow\",\n\t\t\t\t\tdetail: \"request exceeds context window and nothing is collapsible\",\n\t\t\t\t});\n\t\t\t\tawait settleFailedTurn(tx, current, task.input.inputs, \"overflow\", head?.id);\n\t\t\t\treturn fail(\"overflow\", \"request exceeds context window and nothing is collapsible\");\n\t\t\t}\n\t\t\tconst collapse = tx.createTask({ kind: \"pi.collapse\", input: { reason: \"overflow\", through } });\n\t\t\ttx.createTask({ kind: \"pi.generation\", after: [collapse], input: { inputs: task.input.inputs } });\n\t\t\ttx.emit({\n\t\t\t\ttype: \"generation.failed\",\n\t\t\t\ttaskId: current.id,\n\t\t\t\treason: \"overflow\",\n\t\t\t\tdetail: `collapsing through ${through}`,\n\t\t\t});\n\t\t\treturn fail(\"overflow\", `collapsing through ${through}`);\n\t\t},\n\t};\n}\n"]}