@earendil-works/pi-agent-core 0.85.0 → 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.
- package/README.md +16 -6
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +64 -22
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +5 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -6
- package/dist/agent.js.map +1 -1
- package/dist/harness/config.d.ts +1 -1
- package/dist/harness/config.d.ts.map +1 -1
- package/dist/harness/config.js +10 -2
- package/dist/harness/config.js.map +1 -1
- package/dist/harness/env/nodejs.d.ts +2 -1
- package/dist/harness/env/nodejs.d.ts.map +1 -1
- package/dist/harness/env/nodejs.js +98 -27
- package/dist/harness/env/nodejs.js.map +1 -1
- package/dist/harness/execution/tools.d.ts +1 -1
- package/dist/harness/execution/tools.d.ts.map +1 -1
- package/dist/harness/execution/tools.js +0 -2
- package/dist/harness/execution/tools.js.map +1 -1
- package/dist/harness/messages.d.ts.map +1 -1
- package/dist/harness/messages.js +1 -0
- package/dist/harness/messages.js.map +1 -1
- package/dist/harness/pico3/bash.d.ts +10 -0
- package/dist/harness/pico3/bash.d.ts.map +1 -0
- package/dist/harness/pico3/bash.js +25 -0
- package/dist/harness/pico3/bash.js.map +1 -0
- package/dist/harness/pico3/bounded.d.ts +21 -0
- package/dist/harness/pico3/bounded.d.ts.map +1 -0
- package/dist/harness/pico3/bounded.js +98 -0
- package/dist/harness/pico3/bounded.js.map +1 -0
- package/dist/harness/pico3/chord.d.ts +41 -0
- package/dist/harness/pico3/chord.d.ts.map +1 -0
- package/dist/harness/pico3/chord.js +130 -0
- package/dist/harness/pico3/chord.js.map +1 -0
- package/dist/harness/pico3/context.d.ts +16 -0
- package/dist/harness/pico3/context.d.ts.map +1 -0
- package/dist/harness/pico3/context.js +88 -0
- package/dist/harness/pico3/context.js.map +1 -0
- package/dist/harness/pico3/harness.d.ts +197 -0
- package/dist/harness/pico3/harness.d.ts.map +1 -0
- package/dist/harness/pico3/harness.js +654 -0
- package/dist/harness/pico3/harness.js.map +1 -0
- package/dist/harness/pico3/hooks.d.ts +10 -0
- package/dist/harness/pico3/hooks.d.ts.map +1 -0
- package/dist/harness/pico3/hooks.js +33 -0
- package/dist/harness/pico3/hooks.js.map +1 -0
- package/dist/harness/pico3/index.d.ts +19 -0
- package/dist/harness/pico3/index.d.ts.map +1 -0
- package/dist/harness/pico3/index.js +15 -0
- package/dist/harness/pico3/index.js.map +1 -0
- package/dist/harness/pico3/jsonl.d.ts +58 -0
- package/dist/harness/pico3/jsonl.d.ts.map +1 -0
- package/dist/harness/pico3/jsonl.js +325 -0
- package/dist/harness/pico3/jsonl.js.map +1 -0
- package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
- package/dist/harness/pico3/kinds/collapse.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/collapse.js +191 -0
- package/dist/harness/pico3/kinds/collapse.js.map +1 -0
- package/dist/harness/pico3/kinds/entries.d.ts +78 -0
- package/dist/harness/pico3/kinds/entries.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/entries.js +13 -0
- package/dist/harness/pico3/kinds/entries.js.map +1 -0
- package/dist/harness/pico3/kinds/frames.d.ts +9 -0
- package/dist/harness/pico3/kinds/frames.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/frames.js +75 -0
- package/dist/harness/pico3/kinds/frames.js.map +1 -0
- package/dist/harness/pico3/kinds/generation.d.ts +94 -0
- package/dist/harness/pico3/kinds/generation.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/generation.js +504 -0
- package/dist/harness/pico3/kinds/generation.js.map +1 -0
- package/dist/harness/pico3/kinds/job.d.ts +44 -0
- package/dist/harness/pico3/kinds/job.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/job.js +143 -0
- package/dist/harness/pico3/kinds/job.js.map +1 -0
- package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
- package/dist/harness/pico3/kinds/plugin.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/plugin.js +35 -0
- package/dist/harness/pico3/kinds/plugin.js.map +1 -0
- package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
- package/dist/harness/pico3/kinds/post-tools.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/post-tools.js +145 -0
- package/dist/harness/pico3/kinds/post-tools.js.map +1 -0
- package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
- package/dist/harness/pico3/kinds/task-api.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/task-api.js +45 -0
- package/dist/harness/pico3/kinds/task-api.js.map +1 -0
- package/dist/harness/pico3/kinds/tool.d.ts +43 -0
- package/dist/harness/pico3/kinds/tool.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/tool.js +373 -0
- package/dist/harness/pico3/kinds/tool.js.map +1 -0
- package/dist/harness/pico3/membrane.d.ts +23 -0
- package/dist/harness/pico3/membrane.d.ts.map +1 -0
- package/dist/harness/pico3/membrane.js +141 -0
- package/dist/harness/pico3/membrane.js.map +1 -0
- package/dist/harness/pico3/memory.d.ts +53 -0
- package/dist/harness/pico3/memory.d.ts.map +1 -0
- package/dist/harness/pico3/memory.js +264 -0
- package/dist/harness/pico3/memory.js.map +1 -0
- package/dist/harness/pico3/scheduler.d.ts +46 -0
- package/dist/harness/pico3/scheduler.d.ts.map +1 -0
- package/dist/harness/pico3/scheduler.js +438 -0
- package/dist/harness/pico3/scheduler.js.map +1 -0
- package/dist/harness/pico3/session.d.ts +263 -0
- package/dist/harness/pico3/session.d.ts.map +1 -0
- package/dist/harness/pico3/session.js +1347 -0
- package/dist/harness/pico3/session.js.map +1 -0
- package/dist/harness/pico3/system.d.ts +126 -0
- package/dist/harness/pico3/system.d.ts.map +1 -0
- package/dist/harness/pico3/system.js +244 -0
- package/dist/harness/pico3/system.js.map +1 -0
- package/dist/harness/pico3/types.d.ts +973 -0
- package/dist/harness/pico3/types.d.ts.map +1 -0
- package/dist/harness/pico3/types.js +96 -0
- package/dist/harness/pico3/types.js.map +1 -0
- package/dist/harness/pico3/view.d.ts +34 -0
- package/dist/harness/pico3/view.d.ts.map +1 -0
- package/dist/harness/pico3/view.js +417 -0
- package/dist/harness/pico3/view.js.map +1 -0
- package/dist/harness/runtime/drive/boundary.d.ts.map +1 -1
- package/dist/harness/runtime/drive/boundary.js +6 -3
- package/dist/harness/runtime/drive/boundary.js.map +1 -1
- package/dist/harness/runtime/drive/response.d.ts.map +1 -1
- package/dist/harness/runtime/drive/response.js +4 -4
- package/dist/harness/runtime/drive/response.js.map +1 -1
- package/dist/harness/runtime/drive/retry.d.ts +2 -2
- package/dist/harness/runtime/drive/retry.d.ts.map +1 -1
- package/dist/harness/runtime/drive/retry.js +3 -6
- package/dist/harness/runtime/drive/retry.js.map +1 -1
- package/dist/harness/runtime/drive/structural.d.ts.map +1 -1
- package/dist/harness/runtime/drive/structural.js +5 -5
- package/dist/harness/runtime/drive/structural.js.map +1 -1
- package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -1
- package/dist/harness/runtime/drive/tool-placement.js +2 -27
- package/dist/harness/runtime/drive/tool-placement.js.map +1 -1
- package/dist/harness/runtime/drive/tools.d.ts.map +1 -1
- package/dist/harness/runtime/drive/tools.js.map +1 -1
- package/dist/harness/session/fork-policy.d.ts +20 -12
- package/dist/harness/session/fork-policy.d.ts.map +1 -1
- package/dist/harness/session/fork-policy.js +45 -12
- package/dist/harness/session/fork-policy.js.map +1 -1
- package/dist/harness/session/fork.d.ts +0 -2
- package/dist/harness/session/fork.d.ts.map +1 -1
- package/dist/harness/session/fork.js +20 -68
- package/dist/harness/session/fork.js.map +1 -1
- package/dist/harness/session/in-memory-storage-state.d.ts +5 -5
- package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -1
- package/dist/harness/session/in-memory-storage-state.js +99 -39
- package/dist/harness/session/in-memory-storage-state.js.map +1 -1
- package/dist/harness/session/index.d.ts +1 -1
- package/dist/harness/session/index.d.ts.map +1 -1
- package/dist/harness/session/index.js +1 -1
- package/dist/harness/session/index.js.map +1 -1
- package/dist/harness/session/jsonl/fork.d.ts +38 -0
- package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
- package/dist/harness/session/jsonl/fork.js +264 -0
- package/dist/harness/session/jsonl/fork.js.map +1 -0
- package/dist/harness/session/jsonl/io.d.ts +14 -0
- package/dist/harness/session/jsonl/io.d.ts.map +1 -0
- package/dist/harness/session/jsonl/io.js +86 -0
- package/dist/harness/session/jsonl/io.js.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts +33 -8
- package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -1
- package/dist/harness/session/jsonl/legacy-v3.js +285 -206
- package/dist/harness/session/jsonl/legacy-v3.js.map +1 -1
- package/dist/harness/session/jsonl/repo.d.ts +2 -1
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -1
- package/dist/harness/session/jsonl/repo.js +36 -20
- package/dist/harness/session/jsonl/repo.js.map +1 -1
- package/dist/harness/session/jsonl/storage.d.ts +4 -5
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -1
- package/dist/harness/session/jsonl/storage.js +39 -113
- package/dist/harness/session/jsonl/storage.js.map +1 -1
- package/dist/harness/session/memory.d.ts +3 -5
- package/dist/harness/session/memory.d.ts.map +1 -1
- package/dist/harness/session/memory.js +9 -14
- package/dist/harness/session/memory.js.map +1 -1
- package/dist/harness/session/testing/conformance/session-repo.d.ts +2 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -1
- package/dist/harness/session/testing/conformance/session-repo.js +220 -1
- package/dist/harness/session/testing/conformance/session-repo.js.map +1 -1
- package/dist/harness/session/testing/index.d.ts +1 -1
- package/dist/harness/session/testing/index.d.ts.map +1 -1
- package/dist/harness/session/testing/index.js +1 -1
- package/dist/harness/session/testing/index.js.map +1 -1
- package/dist/harness/session/types.d.ts +1 -0
- package/dist/harness/session/types.d.ts.map +1 -1
- package/dist/harness/session/types.js.map +1 -1
- package/dist/harness/types.d.ts +14 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/proxy.d.ts +2 -2
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +30 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -6
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import type { ToolCall } from "@earendil-works/pi-ai";
|
|
3
|
+
import { type AnyToolDeclaration, type BeforeToolApi, type CoreKind, type HookInfo, type HookResult, type Id, type Step, type Stored, type ToolControl, type ToolResult } from "../types.ts";
|
|
4
|
+
export type StoredToolCall = Stored<ToolCall>;
|
|
5
|
+
export type ToolInput = {
|
|
6
|
+
assistant: Id;
|
|
7
|
+
call: StoredToolCall;
|
|
8
|
+
offered: string[];
|
|
9
|
+
index: number;
|
|
10
|
+
};
|
|
11
|
+
export type ToolCheckpoint = {
|
|
12
|
+
phase: "started";
|
|
13
|
+
replay: "safe" | "unsafe";
|
|
14
|
+
call: StoredToolCall;
|
|
15
|
+
};
|
|
16
|
+
export type ToolTaskResult = {
|
|
17
|
+
entry: Id;
|
|
18
|
+
control?: ToolControl;
|
|
19
|
+
};
|
|
20
|
+
/** Hook points this kind calls. Register with `h.hooks(namespace, kinds.tool, { … })`. */
|
|
21
|
+
export interface ToolHooks {
|
|
22
|
+
/** Before invocation, before `started`. Chain on `call`; `{ block }` stops it. A throw blocks. */
|
|
23
|
+
beforeTool(call: StoredToolCall, api: BeforeToolApi, ctx: Context): HookResult<{
|
|
24
|
+
call?: StoredToolCall;
|
|
25
|
+
block?: string;
|
|
26
|
+
}>;
|
|
27
|
+
/** After the tool returns; may replace the result. Chain. */
|
|
28
|
+
afterTool(call: StoredToolCall, result: ToolResult, api: HookInfo & {
|
|
29
|
+
readonly callId: string;
|
|
30
|
+
}, ctx: Context): HookResult<ToolResult>;
|
|
31
|
+
}
|
|
32
|
+
/** Validate arguments with the tool's real schema (TypeBox 1.x). */
|
|
33
|
+
export declare function invalid(declaration: AnyToolDeclaration, call: StoredToolCall): string | undefined;
|
|
34
|
+
export declare const tool: CoreKind<ToolInput, ToolCheckpoint, ToolTaskResult, never, {
|
|
35
|
+
entry: Id;
|
|
36
|
+
}, ToolHooks>;
|
|
37
|
+
export declare const DEFAULT_BOUNDS: {
|
|
38
|
+
maxBytes: number;
|
|
39
|
+
maxLines: number;
|
|
40
|
+
retain: "head";
|
|
41
|
+
};
|
|
42
|
+
export type { Step };
|
|
43
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAGlB,KAAK,QAAQ,EAEb,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,EAAE,EAIP,KAAK,IAAI,EACT,KAAK,MAAM,EAGX,KAAK,WAAW,EAChB,KAAK,UAAU,EAGf,MAAM,aAAa,CAAC;AAGrB,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC9C,MAAM,MAAM,SAAS,GAAG;IACvB,SAAS,EAAE,EAAE,CAAC;IACd,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AACF,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AACnG,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,EAAE,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAClE,4FAA0F;AAC1F,MAAM,WAAW,SAAS;IACzB,kGAAkG;IAClG,UAAU,CACT,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,aAAa,EAClB,GAAG,EAAE,OAAO,GACV,UAAU,CAAC;QAAE,IAAI,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,6DAA6D;IAC7D,SAAS,CACR,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,QAAQ,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAC3C,GAAG,EAAE,OAAO,GACV,UAAU,CAAC,UAAU,CAAC,CAAC;CAC1B;AAWD,oEAAoE;AACpE,wBAAgB,OAAO,CAAC,WAAW,EAAE,kBAAkB,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CAGjG;AAID,eAAO,MAAM,IAAI,EAAE,QAAQ,CAAC,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,EAAE;IAAE,KAAK,EAAE,EAAE,CAAA;CAAE,EAAE,SAAS,CA8GrG,CAAC;AAgEF,eAAO,MAAM,cAAc;;;;CAAkE,CAAC;AAwO9F,YAAY,EAAE,IAAI,EAAE,CAAC","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport type { ToolCall } from \"@earendil-works/pi-ai\";\nimport type { TSchema } from \"typebox\";\nimport { Errors } from \"typebox/value\";\nimport { Bounded } from \"../bounded.ts\";\nimport {\n\ttype AnyToolDeclaration,\n\ttype BeforeToolApi,\n\ttype Closure,\n\ttype Completion,\n\ttype CoreKind,\n\ttype CoreTx,\n\ttype HookInfo,\n\ttype HookResult,\n\ttype Id,\n\ttype JsonObject,\n\ttype JsonValue,\n\ttype Runtime,\n\ttype Step,\n\ttype Stored,\n\ttype Task,\n\ttype ToolApi,\n\ttype ToolControl,\n\ttype ToolResult,\n\ttype ToolSlot,\n\ttoStored,\n} from \"../types.ts\";\nimport { taskApi } from \"./task-api.ts\";\n\nexport type StoredToolCall = Stored<ToolCall>;\nexport type ToolInput = {\n\tassistant: Id;\n\tcall: StoredToolCall;\n\toffered: string[];\n\tindex: number; // position of the call in the assistant message; also this tool's slot in sticky.turn.tools\n};\nexport type ToolCheckpoint = { phase: \"started\"; replay: \"safe\" | \"unsafe\"; call: StoredToolCall };\nexport type ToolTaskResult = { entry: Id; control?: ToolControl };\n/** Hook points this kind calls. Register with `h.hooks(namespace, kinds.tool, { … })`. */\nexport interface ToolHooks {\n\t/** Before invocation, before `started`. Chain on `call`; `{ block }` stops it. A throw blocks. */\n\tbeforeTool(\n\t\tcall: StoredToolCall,\n\t\tapi: BeforeToolApi,\n\t\tctx: Context,\n\t): HookResult<{ call?: StoredToolCall; block?: string }>;\n\t/** After the tool returns; may replace the result. Chain. */\n\tafterTool(\n\t\tcall: StoredToolCall,\n\t\tresult: ToolResult,\n\t\tapi: HookInfo & { readonly callId: string },\n\t\tctx: Context,\n\t): HookResult<ToolResult>;\n}\n\ntype T = Task<ToolInput, ToolCheckpoint>;\ntype Rt = Runtime<ToolHooks, CoreTx>;\ntype Out = Closure<ToolTaskResult, never, CoreTx>;\n\nconst synthetic = (text: string, code: string): ToolResult => ({\n\tcontent: [{ type: \"text\", text }],\n\tisError: true,\n\tdiagnostics: [{ severity: \"error\", message: text, code }],\n});\n/** Validate arguments with the tool's real schema (TypeBox 1.x). */\nexport function invalid(declaration: AnyToolDeclaration, call: StoredToolCall): string | undefined {\n\tconst errors = [...Errors(declaration.parameters as TSchema, call.arguments)];\n\treturn errors.length === 0 ? undefined : errors.map((e) => `${e.instancePath || \"/\"}: ${e.message}`).join(\"; \");\n}\nconst sameIdentity = (a: StoredToolCall, b: StoredToolCall) =>\n\ta.id === b.id && a.name === b.name && a.namespace === b.namespace;\n\nexport const tool: CoreKind<ToolInput, ToolCheckpoint, ToolTaskResult, never, { entry: Id }, ToolHooks> = {\n\tname: \"pi.tool\",\n\tturn: true,\n\tinflight: [\"started\"],\n\n\t// Offered-set check, lookup, validate, beforeTool, validate again; then write `started`\n\t// (the in-flight checkpoint) and invoke. A crash before `started` reruns all of this.\n\tasync initial(task, rt, ctx) {\n\t\tconst call = task.input.call;\n\t\tif (!task.input.offered.includes(call.name))\n\t\t\treturn {\n\t\t\t\tdone: close(task, synthetic(`tool ${call.name} was not offered`, \"not_offered\"), undefined, rt.now()),\n\t\t\t};\n\t\tconst declaration = rt.tools.get(call.name);\n\t\tif (declaration === undefined)\n\t\t\treturn {\n\t\t\t\tdone: close(task, synthetic(`tool ${call.name} is not registered`, \"missing_tool\"), undefined, rt.now()),\n\t\t\t};\n\t\tconst bad = invalid(declaration, call);\n\t\tif (bad !== undefined)\n\t\t\treturn {\n\t\t\t\tdone: close(task, synthetic(`invalid arguments: ${bad}`, \"invalid_arguments\"), declaration, rt.now()),\n\t\t\t};\n\t\tconst hooked = await beforeTool(task, rt, call, ctx);\n\t\tif (\"block\" in hooked)\n\t\t\treturn { done: close(task, synthetic(`blocked: ${hooked.block}`, \"blocked\"), declaration, rt.now()) };\n\t\tif (!sameIdentity(call, hooked.call))\n\t\t\treturn { done: close(task, synthetic(\"blocked: call identity changed\", \"blocked\"), declaration, rt.now()) };\n\t\tconst badFinal = invalid(declaration, hooked.call);\n\t\tif (badFinal !== undefined)\n\t\t\treturn {\n\t\t\t\tdone: close(\n\t\t\t\t\ttask,\n\t\t\t\t\tsynthetic(`invalid arguments after hook: ${badFinal}`, \"invalid_arguments\"),\n\t\t\t\t\tdeclaration,\n\t\t\t\t\trt.now(),\n\t\t\t\t),\n\t\t\t};\n\t\tconst final = toStored(hooked.call);\n\t\tawait rt.commit((tx) => {\n\t\t\ttx.checkpoint({ phase: \"started\", replay: declaration.replay ?? \"unsafe\", call: final });\n\t\t\tconst slot = tx.toolSlot(task);\n\t\t\tslot.status = \"running\";\n\t\t\tdelete slot.waitingOn;\n\t\t\ttx.emit({ type: \"tool.started\", taskId: task.id, callId: final.id, name: final.name });\n\t\t}, ctx); // durable before the effect\n\t\treturn { done: await invoke(task, final, declaration, rt, ctx) };\n\t},\n\n\tphases: {\n\t\t// Only entered by the scheduler after reopen. The stored final call is the evidence; beforeTool never reruns.\n\t\tasync started(task, rt, ctx) {\n\t\t\tconst cp = task.checkpoint;\n\t\t\tconst declaration = rt.tools.get(cp.call.name);\n\t\t\tif (declaration === undefined)\n\t\t\t\treturn {\n\t\t\t\t\tdone: close(\n\t\t\t\t\t\ttask,\n\t\t\t\t\t\tsynthetic(`tool ${cp.call.name} unavailable after restart`, \"unavailable\"),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\trt.now(),\n\t\t\t\t\t\tcp.call,\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\tif (cp.replay !== \"safe\" || (declaration.replay ?? \"unsafe\") !== \"safe\")\n\t\t\t\treturn {\n\t\t\t\t\tdone: close(\n\t\t\t\t\t\ttask,\n\t\t\t\t\t\tsynthetic(`tool ${cp.call.name} was interrupted`, \"interrupted\"),\n\t\t\t\t\t\tdeclaration,\n\t\t\t\t\t\trt.now(),\n\t\t\t\t\t\tcp.call,\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\tif (invalid(declaration, cp.call) !== undefined)\n\t\t\t\treturn {\n\t\t\t\t\tdone: close(\n\t\t\t\t\t\ttask,\n\t\t\t\t\t\tsynthetic(`tool ${cp.call.name} was interrupted; arguments no longer validate`, \"interrupted\"),\n\t\t\t\t\t\tdeclaration,\n\t\t\t\t\t\trt.now(),\n\t\t\t\t\t\tcp.call,\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\treturn { done: await invoke(task, cp.call, declaration, rt, ctx) };\n\t\t},\n\t},\n\n\tasync abort(task, rt, ctx) {\n\t\tfor (const conversationId of task.owns) {\n\t\t\tconst children = await rt.commit((tx) => tx.tasks({ conversationId, status: [\"pending\", \"running\"] }), ctx);\n\t\t\tfor (const child of children) if (!child.background) await rt.abortTask(child.id, ctx);\n\t\t}\n\t\treturn (tx, current) => {\n\t\t\tconst call = task.checkpoint?.call ?? task.input.call;\n\t\t\tconst entry = tx.appendEntry(current.conversationId, {\n\t\t\t\tkind: \"pi.tool_result\",\n\t\t\t\tmodel: [toMessage(call, synthetic(\"tool aborted\", \"aborted\"), rt.now())],\n\t\t\t\tdata: { diagnostics: [{ severity: \"error\", message: \"aborted\", code: \"aborted\" }] },\n\t\t\t});\n\t\t\tconst slot = tx.sticky(current.conversationId).turn.tools[task.input.index];\n\t\t\tif (slot !== undefined) {\n\t\t\t\tslot.status = \"aborted\";\n\t\t\t\tslot.entry = entry;\n\t\t\t\tdelete slot.waitingOn;\n\t\t\t}\n\t\t\ttx.emit({ type: \"tool.aborted\", taskId: current.id, callId: call.id, entry });\n\t\t\treturn { entry };\n\t\t};\n\t},\n};\n\n/** The one point where a throwing handler blocks rather than being skipped. */\nasync function beforeTool(\n\ttask: T,\n\trt: Rt,\n\tcall: StoredToolCall,\n\tctx: Context,\n): Promise<{ call: StoredToolCall } | { block: string }> {\n\tlet current = call;\n\tfor (const binding of rt.hooks.handlers()) {\n\t\tfunction memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;\n\t\tfunction memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;\n\t\tfunction memo<T extends JsonValue>(\n\t\t\tname: string,\n\t\t\tcandidateOrContext: T | Context,\n\t\t\tcontext?: Context,\n\t\t): Promise<T | undefined> {\n\t\t\tconst targetContext = context ?? (candidateOrContext as Context);\n\t\t\treturn rt.commit((tx) => {\n\t\t\t\ttx.plugins(binding.namespace);\n\t\t\t\tconst slot = tx.toolSlot(task);\n\t\t\t\tif (!Object.hasOwn(slot, \"memos\")) slot.memos = {};\n\t\t\t\tconst memos = slot.memos!;\n\t\t\t\tconst key = `hook:${binding.namespace.id}:${name}`;\n\t\t\t\tif (Object.hasOwn(memos, key)) return structuredClone(memos[key]) as T;\n\t\t\t\tif (context === undefined) return undefined;\n\t\t\t\tconst candidate = structuredClone(candidateOrContext as T);\n\t\t\t\tmemos[key] = candidate;\n\t\t\t\treturn candidate;\n\t\t\t}, targetContext);\n\t\t}\n\t\tconst api: BeforeToolApi = {\n\t\t\t...binding.api,\n\t\t\tcallId: call.id,\n\t\t\twaiting: (context) =>\n\t\t\t\trt.commit((tx, currentTask) => {\n\t\t\t\t\ttx.plugins(binding.namespace);\n\t\t\t\t\tconst slot = tx.toolSlot(currentTask as T);\n\t\t\t\t\tif (slot.waitingOn === binding.namespace.id) return;\n\t\t\t\t\tslot.waitingOn = binding.namespace.id;\n\t\t\t\t\ttx.emit({\n\t\t\t\t\t\ttype: \"tool.waiting\",\n\t\t\t\t\t\ttaskId: currentTask.id,\n\t\t\t\t\t\tcallId: call.id,\n\t\t\t\t\t\ton: binding.namespace.id,\n\t\t\t\t\t});\n\t\t\t\t}, context),\n\t\t\tmemo,\n\t\t\temit: (name, data, context) => rt.commit((tx) => tx.emit(binding.namespace, name, data), context),\n\t\t};\n\t\ttry {\n\t\t\tconst value = await binding.handlers.beforeTool?.(current, api, ctx);\n\t\t\tif (!value) continue;\n\t\t\tif (value.block !== undefined) return { block: value.block };\n\t\t\tif (value.call !== undefined) current = value.call;\n\t\t} catch (error) {\n\t\t\tif (ctx.abortSignal?.aborted) throw error;\n\t\t\treturn { block: `hook threw: ${String(error)}` };\n\t\t}\n\t}\n\treturn { call: current };\n}\n\nexport const DEFAULT_BOUNDS = { maxBytes: 64 * 1024, maxLines: 200, retain: \"head\" as const };\n\nasync function invoke(\n\ttask: T,\n\tcall: StoredToolCall,\n\tdeclaration: AnyToolDeclaration,\n\trt: Rt,\n\tctx: Context,\n): Promise<Out> {\n\t// The kernel owns the stream: one bounded buffer, one throttle, one flush path. Flush failures are not swallowed.\n\tconst b = { ...DEFAULT_BOUNDS, ...declaration.output };\n\tconst buffer = new Bounded(b.maxBytes, b.maxLines, b.retain);\n\tlet streamed = false;\n\tlet lastFlush = 0;\n\tlet flushing: Promise<void> = Promise.resolve();\n\tlet flushError: unknown;\n\tconst flush = () => {\n\t\tlastFlush = rt.now();\n\t\tconst text = buffer.text();\n\t\tflushing = flushing\n\t\t\t.then(() =>\n\t\t\t\trt.commit((tx) => {\n\t\t\t\t\ttx.toolSlot(task).output = text;\n\t\t\t\t}, ctx),\n\t\t\t)\n\t\t\t.catch((e) => {\n\t\t\t\tflushError ??= e;\n\t\t\t});\n\t};\n\tconst api = {\n\t\t...taskApi(task, rt),\n\t\t// Only the free fields; identity (callId/name/args/output) is protected.\n\t\tprogress: (update: (slot: Pick<ToolSlot, \"progress\" | \"details\" | \"continuedBy\">) => void, c: Context) =>\n\t\t\trt.commit((tx) => {\n\t\t\t\tconst slot = tx.toolSlot(task);\n\t\t\t\tconst free: Pick<ToolSlot, \"progress\" | \"details\" | \"continuedBy\"> = {\n\t\t\t\t\tprogress: slot.progress,\n\t\t\t\t\tdetails: slot.details,\n\t\t\t\t\tcontinuedBy: slot.continuedBy,\n\t\t\t\t};\n\t\t\t\tupdate(free);\n\t\t\t\tif (free.progress !== undefined) slot.progress = free.progress;\n\t\t\t\telse delete slot.progress;\n\t\t\t\tif (free.details !== undefined) slot.details = toStored(free.details);\n\t\t\t\telse delete slot.details;\n\t\t\t\tif (free.continuedBy !== undefined) slot.continuedBy = free.continuedBy;\n\t\t\t\telse delete slot.continuedBy;\n\t\t\t}, c),\n\t\tstream(chunk: string | Uint8Array) {\n\t\t\tstreamed = true;\n\t\t\tbuffer.push(typeof chunk === \"string\" ? new TextEncoder().encode(chunk) : chunk);\n\t\t\tif (rt.now() - lastFlush >= 100) flush();\n\t\t},\n\t};\n\tfunction memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;\n\tfunction memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;\n\tfunction memo<T extends JsonValue>(\n\t\tname: string,\n\t\tcandidateOrContext: T | Context,\n\t\tcontext?: Context,\n\t): Promise<T | undefined> {\n\t\tconst targetContext = context ?? (candidateOrContext as Context);\n\t\treturn rt.commit((tx) => {\n\t\t\tconst slot = tx.toolSlot(task);\n\t\t\tif (!Object.hasOwn(slot, \"memos\")) slot.memos = {};\n\t\t\tconst memos = slot.memos!;\n\t\t\tconst key = `tool:${name}`;\n\t\t\tif (Object.hasOwn(memos, key)) return structuredClone(memos[key]) as T;\n\t\t\tif (context === undefined) return undefined;\n\t\t\tconst candidate = structuredClone(candidateOrContext as T);\n\t\t\tmemos[key] = candidate;\n\t\t\treturn candidate;\n\t\t}, targetContext);\n\t}\n\tconst toolApi: typeof api & Pick<ToolApi, \"callId\" | \"memo\"> = { ...api, callId: call.id, memo };\n\tlet result: ToolResult;\n\ttry {\n\t\tresult = await declaration.execute(call.arguments as never, toolApi, ctx);\n\t} catch (error) {\n\t\tif (ctx.abortSignal?.aborted) throw error;\n\t\tresult = synthetic(`tool threw: ${String(error)}`, \"threw\");\n\t}\n\tif (streamed) {\n\t\tflush();\n\t\tawait flushing;\n\t\tif (flushError !== undefined) throw flushError; // a persistence failure is never hidden\n\t\tif (result.content === undefined)\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tcontent: [{ type: \"text\", text: buffer.text() }],\n\t\t\t\t...(buffer.dropped\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdiagnostics: [\n\t\t\t\t\t\t\t\t...(result.diagnostics ?? []),\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tseverity: \"warn\" as const,\n\t\t\t\t\t\t\t\t\tcode: \"truncated\",\n\t\t\t\t\t\t\t\t\tmessage: `${buffer.droppedBytes} bytes / ${buffer.droppedLines} lines dropped (${b.retain} ${b.maxBytes} retained)`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t};\n\t}\n\tlet final = result;\n\tawait rt.hooks.each(\n\t\tctx,\n\t\t(h, api) => h.afterTool?.(call, final, { ...api, callId: call.id }, ctx),\n\t\t(value) => {\n\t\t\tif (value !== undefined) final = value;\n\t\t},\n\t);\n\treturn close(\n\t\ttask,\n\t\tfinal,\n\t\tdeclaration,\n\t\trt.now(),\n\t\tcall,\n\t\tstreamed ? { bytes: buffer.droppedBytes, lines: buffer.droppedLines } : undefined,\n\t);\n}\n\n/** Closure: bound the output, store the exact model message under the stored final call, rest as strict-JSON data. */\nfunction close(\n\ttask: T,\n\traw: ToolResult,\n\tdeclaration: AnyToolDeclaration | undefined,\n\tnow: number,\n\tcall: StoredToolCall = task.input.call,\n\tstreamTruncated?: { bytes: number; lines: number },\n): Out {\n\treturn (tx, current) => {\n\t\tconst bounded = bound({ ...raw, content: raw.content ?? [] }, declaration?.output);\n\t\tconst result = bounded.result;\n\t\tconst bytes = (bounded.truncated?.bytes ?? 0) + (streamTruncated?.bytes ?? 0);\n\t\tconst lines = (bounded.truncated?.lines ?? 0) + (streamTruncated?.lines ?? 0);\n\t\tconst truncated = bytes > 0 || lines > 0 ? { bytes, lines } : undefined;\n\t\tconst data: JsonObject = {};\n\t\tif (result.details !== undefined) data.details = toStored(result.details);\n\t\tif (result.diagnostics !== undefined) data.diagnostics = toStored(result.diagnostics);\n\t\tif (result.control !== undefined) data.control = toStored(result.control);\n\t\tif (truncated !== undefined) data.truncated = truncated;\n\t\tconst entry = tx.appendEntry(current.conversationId, {\n\t\t\tkind: \"pi.tool_result\",\n\t\t\tmodel: [toMessage(call, result, now)],\n\t\t\tdata,\n\t\t});\n\t\tconst slot = tx.toolSlot(task);\n\t\tslot.status = result.isError === true ? \"error\" : \"done\";\n\t\tslot.entry = entry;\n\t\tdelete slot.waitingOn;\n\t\ttx.emit({\n\t\t\ttype: \"tool.finished\",\n\t\t\ttaskId: current.id,\n\t\t\tcallId: call.id,\n\t\t\tentry,\n\t\t\tisError: result.isError === true,\n\t\t\t...(result.control === undefined ? {} : { control: result.control }),\n\t\t});\n\t\tif (truncated !== undefined)\n\t\t\ttx.emit({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tsource: \"tool\",\n\t\t\t\tmessage: `tool output truncated: ${truncated.bytes} bytes / ${truncated.lines} lines dropped`,\n\t\t\t});\n\t\tconst completion: Completion<ToolTaskResult, never> = {\n\t\t\tstatus: \"completed\",\n\t\t\tresult: { entry, ...(result.control === undefined ? {} : { control: toStored(result.control) }) },\n\t\t};\n\t\treturn completion;\n\t};\n}\n\nfunction toMessage(call: StoredToolCall, result: ToolResult, now: number) {\n\treturn toStored({\n\t\trole: \"toolResult\" as const,\n\t\ttoolCallId: call.id,\n\t\ttoolName: call.name,\n\t\tcontent: result.content ?? [],\n\t\tisError: result.isError ?? false,\n\t\ttimestamp: now,\n\t});\n}\n\nfunction bound(\n\tresult: ToolResult,\n\tdeclared: AnyToolDeclaration[\"output\"],\n): { result: ToolResult; truncated?: { bytes: number; lines: number } } {\n\tconst bounds = { ...DEFAULT_BOUNDS, ...declared };\n\tconst blocks = result.content ?? [];\n\tlet text = blocks\n\t\t.filter((block) => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\");\n\tlet droppedLines = 0;\n\tlet droppedBytes = 0;\n\tconst lines = text.split(\"\\n\");\n\tif (lines.length > bounds.maxLines) {\n\t\tdroppedLines = lines.length - bounds.maxLines;\n\t\ttext = (bounds.retain === \"head\" ? lines.slice(0, bounds.maxLines) : lines.slice(-bounds.maxLines)).join(\"\\n\");\n\t}\n\tconst bytes = new TextEncoder().encode(text);\n\tif (bytes.length > bounds.maxBytes) {\n\t\tdroppedBytes = bytes.length - bounds.maxBytes;\n\t\ttext = new TextDecoder().decode(\n\t\t\tbounds.retain === \"head\" ? bytes.subarray(0, bounds.maxBytes) : bytes.subarray(-bounds.maxBytes),\n\t\t);\n\t}\n\tif (droppedBytes === 0 && droppedLines === 0) return { result };\n\tlet textIndex = -1;\n\tfor (let index = 0; index < blocks.length; index++) {\n\t\tif (blocks[index]!.type !== \"text\") continue;\n\t\tif (bounds.retain === \"head\" && textIndex >= 0) continue;\n\t\ttextIndex = index;\n\t}\n\tconst content: NonNullable<ToolResult[\"content\"]> = [];\n\tfor (let index = 0; index < blocks.length; index++) {\n\t\tconst block = blocks[index]!;\n\t\tif (block.type !== \"text\") content.push(block);\n\t\telse if (index === textIndex) content.push({ ...block, text });\n\t}\n\tconst note = {\n\t\tseverity: \"warn\" as const,\n\t\tcode: \"truncated\",\n\t\tmessage: `output truncated: ${droppedLines} lines, ${droppedBytes} bytes dropped`,\n\t};\n\treturn {\n\t\tresult: { ...result, content, diagnostics: [...(result.diagnostics ?? []), note] },\n\t\ttruncated: { bytes: droppedBytes, lines: droppedLines },\n\t};\n}\n\nexport type { Step };\n"]}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { Errors } from "typebox/value";
|
|
2
|
+
import { Bounded } from "../bounded.js";
|
|
3
|
+
import { toStored, } from "../types.js";
|
|
4
|
+
import { taskApi } from "./task-api.js";
|
|
5
|
+
const synthetic = (text, code) => ({
|
|
6
|
+
content: [{ type: "text", text }],
|
|
7
|
+
isError: true,
|
|
8
|
+
diagnostics: [{ severity: "error", message: text, code }],
|
|
9
|
+
});
|
|
10
|
+
/** Validate arguments with the tool's real schema (TypeBox 1.x). */
|
|
11
|
+
export function invalid(declaration, call) {
|
|
12
|
+
const errors = [...Errors(declaration.parameters, call.arguments)];
|
|
13
|
+
return errors.length === 0 ? undefined : errors.map((e) => `${e.instancePath || "/"}: ${e.message}`).join("; ");
|
|
14
|
+
}
|
|
15
|
+
const sameIdentity = (a, b) => a.id === b.id && a.name === b.name && a.namespace === b.namespace;
|
|
16
|
+
export const tool = {
|
|
17
|
+
name: "pi.tool",
|
|
18
|
+
turn: true,
|
|
19
|
+
inflight: ["started"],
|
|
20
|
+
// Offered-set check, lookup, validate, beforeTool, validate again; then write `started`
|
|
21
|
+
// (the in-flight checkpoint) and invoke. A crash before `started` reruns all of this.
|
|
22
|
+
async initial(task, rt, ctx) {
|
|
23
|
+
const call = task.input.call;
|
|
24
|
+
if (!task.input.offered.includes(call.name))
|
|
25
|
+
return {
|
|
26
|
+
done: close(task, synthetic(`tool ${call.name} was not offered`, "not_offered"), undefined, rt.now()),
|
|
27
|
+
};
|
|
28
|
+
const declaration = rt.tools.get(call.name);
|
|
29
|
+
if (declaration === undefined)
|
|
30
|
+
return {
|
|
31
|
+
done: close(task, synthetic(`tool ${call.name} is not registered`, "missing_tool"), undefined, rt.now()),
|
|
32
|
+
};
|
|
33
|
+
const bad = invalid(declaration, call);
|
|
34
|
+
if (bad !== undefined)
|
|
35
|
+
return {
|
|
36
|
+
done: close(task, synthetic(`invalid arguments: ${bad}`, "invalid_arguments"), declaration, rt.now()),
|
|
37
|
+
};
|
|
38
|
+
const hooked = await beforeTool(task, rt, call, ctx);
|
|
39
|
+
if ("block" in hooked)
|
|
40
|
+
return { done: close(task, synthetic(`blocked: ${hooked.block}`, "blocked"), declaration, rt.now()) };
|
|
41
|
+
if (!sameIdentity(call, hooked.call))
|
|
42
|
+
return { done: close(task, synthetic("blocked: call identity changed", "blocked"), declaration, rt.now()) };
|
|
43
|
+
const badFinal = invalid(declaration, hooked.call);
|
|
44
|
+
if (badFinal !== undefined)
|
|
45
|
+
return {
|
|
46
|
+
done: close(task, synthetic(`invalid arguments after hook: ${badFinal}`, "invalid_arguments"), declaration, rt.now()),
|
|
47
|
+
};
|
|
48
|
+
const final = toStored(hooked.call);
|
|
49
|
+
await rt.commit((tx) => {
|
|
50
|
+
tx.checkpoint({ phase: "started", replay: declaration.replay ?? "unsafe", call: final });
|
|
51
|
+
const slot = tx.toolSlot(task);
|
|
52
|
+
slot.status = "running";
|
|
53
|
+
delete slot.waitingOn;
|
|
54
|
+
tx.emit({ type: "tool.started", taskId: task.id, callId: final.id, name: final.name });
|
|
55
|
+
}, ctx); // durable before the effect
|
|
56
|
+
return { done: await invoke(task, final, declaration, rt, ctx) };
|
|
57
|
+
},
|
|
58
|
+
phases: {
|
|
59
|
+
// Only entered by the scheduler after reopen. The stored final call is the evidence; beforeTool never reruns.
|
|
60
|
+
async started(task, rt, ctx) {
|
|
61
|
+
const cp = task.checkpoint;
|
|
62
|
+
const declaration = rt.tools.get(cp.call.name);
|
|
63
|
+
if (declaration === undefined)
|
|
64
|
+
return {
|
|
65
|
+
done: close(task, synthetic(`tool ${cp.call.name} unavailable after restart`, "unavailable"), undefined, rt.now(), cp.call),
|
|
66
|
+
};
|
|
67
|
+
if (cp.replay !== "safe" || (declaration.replay ?? "unsafe") !== "safe")
|
|
68
|
+
return {
|
|
69
|
+
done: close(task, synthetic(`tool ${cp.call.name} was interrupted`, "interrupted"), declaration, rt.now(), cp.call),
|
|
70
|
+
};
|
|
71
|
+
if (invalid(declaration, cp.call) !== undefined)
|
|
72
|
+
return {
|
|
73
|
+
done: close(task, synthetic(`tool ${cp.call.name} was interrupted; arguments no longer validate`, "interrupted"), declaration, rt.now(), cp.call),
|
|
74
|
+
};
|
|
75
|
+
return { done: await invoke(task, cp.call, declaration, rt, ctx) };
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
async abort(task, rt, ctx) {
|
|
79
|
+
for (const conversationId of task.owns) {
|
|
80
|
+
const children = await rt.commit((tx) => tx.tasks({ conversationId, status: ["pending", "running"] }), ctx);
|
|
81
|
+
for (const child of children)
|
|
82
|
+
if (!child.background)
|
|
83
|
+
await rt.abortTask(child.id, ctx);
|
|
84
|
+
}
|
|
85
|
+
return (tx, current) => {
|
|
86
|
+
const call = task.checkpoint?.call ?? task.input.call;
|
|
87
|
+
const entry = tx.appendEntry(current.conversationId, {
|
|
88
|
+
kind: "pi.tool_result",
|
|
89
|
+
model: [toMessage(call, synthetic("tool aborted", "aborted"), rt.now())],
|
|
90
|
+
data: { diagnostics: [{ severity: "error", message: "aborted", code: "aborted" }] },
|
|
91
|
+
});
|
|
92
|
+
const slot = tx.sticky(current.conversationId).turn.tools[task.input.index];
|
|
93
|
+
if (slot !== undefined) {
|
|
94
|
+
slot.status = "aborted";
|
|
95
|
+
slot.entry = entry;
|
|
96
|
+
delete slot.waitingOn;
|
|
97
|
+
}
|
|
98
|
+
tx.emit({ type: "tool.aborted", taskId: current.id, callId: call.id, entry });
|
|
99
|
+
return { entry };
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
/** The one point where a throwing handler blocks rather than being skipped. */
|
|
104
|
+
async function beforeTool(task, rt, call, ctx) {
|
|
105
|
+
let current = call;
|
|
106
|
+
for (const binding of rt.hooks.handlers()) {
|
|
107
|
+
function memo(name, candidateOrContext, context) {
|
|
108
|
+
const targetContext = context ?? candidateOrContext;
|
|
109
|
+
return rt.commit((tx) => {
|
|
110
|
+
tx.plugins(binding.namespace);
|
|
111
|
+
const slot = tx.toolSlot(task);
|
|
112
|
+
if (!Object.hasOwn(slot, "memos"))
|
|
113
|
+
slot.memos = {};
|
|
114
|
+
const memos = slot.memos;
|
|
115
|
+
const key = `hook:${binding.namespace.id}:${name}`;
|
|
116
|
+
if (Object.hasOwn(memos, key))
|
|
117
|
+
return structuredClone(memos[key]);
|
|
118
|
+
if (context === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
const candidate = structuredClone(candidateOrContext);
|
|
121
|
+
memos[key] = candidate;
|
|
122
|
+
return candidate;
|
|
123
|
+
}, targetContext);
|
|
124
|
+
}
|
|
125
|
+
const api = {
|
|
126
|
+
...binding.api,
|
|
127
|
+
callId: call.id,
|
|
128
|
+
waiting: (context) => rt.commit((tx, currentTask) => {
|
|
129
|
+
tx.plugins(binding.namespace);
|
|
130
|
+
const slot = tx.toolSlot(currentTask);
|
|
131
|
+
if (slot.waitingOn === binding.namespace.id)
|
|
132
|
+
return;
|
|
133
|
+
slot.waitingOn = binding.namespace.id;
|
|
134
|
+
tx.emit({
|
|
135
|
+
type: "tool.waiting",
|
|
136
|
+
taskId: currentTask.id,
|
|
137
|
+
callId: call.id,
|
|
138
|
+
on: binding.namespace.id,
|
|
139
|
+
});
|
|
140
|
+
}, context),
|
|
141
|
+
memo,
|
|
142
|
+
emit: (name, data, context) => rt.commit((tx) => tx.emit(binding.namespace, name, data), context),
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const value = await binding.handlers.beforeTool?.(current, api, ctx);
|
|
146
|
+
if (!value)
|
|
147
|
+
continue;
|
|
148
|
+
if (value.block !== undefined)
|
|
149
|
+
return { block: value.block };
|
|
150
|
+
if (value.call !== undefined)
|
|
151
|
+
current = value.call;
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (ctx.abortSignal?.aborted)
|
|
155
|
+
throw error;
|
|
156
|
+
return { block: `hook threw: ${String(error)}` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { call: current };
|
|
160
|
+
}
|
|
161
|
+
export const DEFAULT_BOUNDS = { maxBytes: 64 * 1024, maxLines: 200, retain: "head" };
|
|
162
|
+
async function invoke(task, call, declaration, rt, ctx) {
|
|
163
|
+
// The kernel owns the stream: one bounded buffer, one throttle, one flush path. Flush failures are not swallowed.
|
|
164
|
+
const b = { ...DEFAULT_BOUNDS, ...declaration.output };
|
|
165
|
+
const buffer = new Bounded(b.maxBytes, b.maxLines, b.retain);
|
|
166
|
+
let streamed = false;
|
|
167
|
+
let lastFlush = 0;
|
|
168
|
+
let flushing = Promise.resolve();
|
|
169
|
+
let flushError;
|
|
170
|
+
const flush = () => {
|
|
171
|
+
lastFlush = rt.now();
|
|
172
|
+
const text = buffer.text();
|
|
173
|
+
flushing = flushing
|
|
174
|
+
.then(() => rt.commit((tx) => {
|
|
175
|
+
tx.toolSlot(task).output = text;
|
|
176
|
+
}, ctx))
|
|
177
|
+
.catch((e) => {
|
|
178
|
+
flushError ??= e;
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
const api = {
|
|
182
|
+
...taskApi(task, rt),
|
|
183
|
+
// Only the free fields; identity (callId/name/args/output) is protected.
|
|
184
|
+
progress: (update, c) => rt.commit((tx) => {
|
|
185
|
+
const slot = tx.toolSlot(task);
|
|
186
|
+
const free = {
|
|
187
|
+
progress: slot.progress,
|
|
188
|
+
details: slot.details,
|
|
189
|
+
continuedBy: slot.continuedBy,
|
|
190
|
+
};
|
|
191
|
+
update(free);
|
|
192
|
+
if (free.progress !== undefined)
|
|
193
|
+
slot.progress = free.progress;
|
|
194
|
+
else
|
|
195
|
+
delete slot.progress;
|
|
196
|
+
if (free.details !== undefined)
|
|
197
|
+
slot.details = toStored(free.details);
|
|
198
|
+
else
|
|
199
|
+
delete slot.details;
|
|
200
|
+
if (free.continuedBy !== undefined)
|
|
201
|
+
slot.continuedBy = free.continuedBy;
|
|
202
|
+
else
|
|
203
|
+
delete slot.continuedBy;
|
|
204
|
+
}, c),
|
|
205
|
+
stream(chunk) {
|
|
206
|
+
streamed = true;
|
|
207
|
+
buffer.push(typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk);
|
|
208
|
+
if (rt.now() - lastFlush >= 100)
|
|
209
|
+
flush();
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
function memo(name, candidateOrContext, context) {
|
|
213
|
+
const targetContext = context ?? candidateOrContext;
|
|
214
|
+
return rt.commit((tx) => {
|
|
215
|
+
const slot = tx.toolSlot(task);
|
|
216
|
+
if (!Object.hasOwn(slot, "memos"))
|
|
217
|
+
slot.memos = {};
|
|
218
|
+
const memos = slot.memos;
|
|
219
|
+
const key = `tool:${name}`;
|
|
220
|
+
if (Object.hasOwn(memos, key))
|
|
221
|
+
return structuredClone(memos[key]);
|
|
222
|
+
if (context === undefined)
|
|
223
|
+
return undefined;
|
|
224
|
+
const candidate = structuredClone(candidateOrContext);
|
|
225
|
+
memos[key] = candidate;
|
|
226
|
+
return candidate;
|
|
227
|
+
}, targetContext);
|
|
228
|
+
}
|
|
229
|
+
const toolApi = { ...api, callId: call.id, memo };
|
|
230
|
+
let result;
|
|
231
|
+
try {
|
|
232
|
+
result = await declaration.execute(call.arguments, toolApi, ctx);
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (ctx.abortSignal?.aborted)
|
|
236
|
+
throw error;
|
|
237
|
+
result = synthetic(`tool threw: ${String(error)}`, "threw");
|
|
238
|
+
}
|
|
239
|
+
if (streamed) {
|
|
240
|
+
flush();
|
|
241
|
+
await flushing;
|
|
242
|
+
if (flushError !== undefined)
|
|
243
|
+
throw flushError; // a persistence failure is never hidden
|
|
244
|
+
if (result.content === undefined)
|
|
245
|
+
result = {
|
|
246
|
+
...result,
|
|
247
|
+
content: [{ type: "text", text: buffer.text() }],
|
|
248
|
+
...(buffer.dropped
|
|
249
|
+
? {
|
|
250
|
+
diagnostics: [
|
|
251
|
+
...(result.diagnostics ?? []),
|
|
252
|
+
{
|
|
253
|
+
severity: "warn",
|
|
254
|
+
code: "truncated",
|
|
255
|
+
message: `${buffer.droppedBytes} bytes / ${buffer.droppedLines} lines dropped (${b.retain} ${b.maxBytes} retained)`,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
}
|
|
259
|
+
: {}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
let final = result;
|
|
263
|
+
await rt.hooks.each(ctx, (h, api) => h.afterTool?.(call, final, { ...api, callId: call.id }, ctx), (value) => {
|
|
264
|
+
if (value !== undefined)
|
|
265
|
+
final = value;
|
|
266
|
+
});
|
|
267
|
+
return close(task, final, declaration, rt.now(), call, streamed ? { bytes: buffer.droppedBytes, lines: buffer.droppedLines } : undefined);
|
|
268
|
+
}
|
|
269
|
+
/** Closure: bound the output, store the exact model message under the stored final call, rest as strict-JSON data. */
|
|
270
|
+
function close(task, raw, declaration, now, call = task.input.call, streamTruncated) {
|
|
271
|
+
return (tx, current) => {
|
|
272
|
+
const bounded = bound({ ...raw, content: raw.content ?? [] }, declaration?.output);
|
|
273
|
+
const result = bounded.result;
|
|
274
|
+
const bytes = (bounded.truncated?.bytes ?? 0) + (streamTruncated?.bytes ?? 0);
|
|
275
|
+
const lines = (bounded.truncated?.lines ?? 0) + (streamTruncated?.lines ?? 0);
|
|
276
|
+
const truncated = bytes > 0 || lines > 0 ? { bytes, lines } : undefined;
|
|
277
|
+
const data = {};
|
|
278
|
+
if (result.details !== undefined)
|
|
279
|
+
data.details = toStored(result.details);
|
|
280
|
+
if (result.diagnostics !== undefined)
|
|
281
|
+
data.diagnostics = toStored(result.diagnostics);
|
|
282
|
+
if (result.control !== undefined)
|
|
283
|
+
data.control = toStored(result.control);
|
|
284
|
+
if (truncated !== undefined)
|
|
285
|
+
data.truncated = truncated;
|
|
286
|
+
const entry = tx.appendEntry(current.conversationId, {
|
|
287
|
+
kind: "pi.tool_result",
|
|
288
|
+
model: [toMessage(call, result, now)],
|
|
289
|
+
data,
|
|
290
|
+
});
|
|
291
|
+
const slot = tx.toolSlot(task);
|
|
292
|
+
slot.status = result.isError === true ? "error" : "done";
|
|
293
|
+
slot.entry = entry;
|
|
294
|
+
delete slot.waitingOn;
|
|
295
|
+
tx.emit({
|
|
296
|
+
type: "tool.finished",
|
|
297
|
+
taskId: current.id,
|
|
298
|
+
callId: call.id,
|
|
299
|
+
entry,
|
|
300
|
+
isError: result.isError === true,
|
|
301
|
+
...(result.control === undefined ? {} : { control: result.control }),
|
|
302
|
+
});
|
|
303
|
+
if (truncated !== undefined)
|
|
304
|
+
tx.emit({
|
|
305
|
+
type: "warning",
|
|
306
|
+
source: "tool",
|
|
307
|
+
message: `tool output truncated: ${truncated.bytes} bytes / ${truncated.lines} lines dropped`,
|
|
308
|
+
});
|
|
309
|
+
const completion = {
|
|
310
|
+
status: "completed",
|
|
311
|
+
result: { entry, ...(result.control === undefined ? {} : { control: toStored(result.control) }) },
|
|
312
|
+
};
|
|
313
|
+
return completion;
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function toMessage(call, result, now) {
|
|
317
|
+
return toStored({
|
|
318
|
+
role: "toolResult",
|
|
319
|
+
toolCallId: call.id,
|
|
320
|
+
toolName: call.name,
|
|
321
|
+
content: result.content ?? [],
|
|
322
|
+
isError: result.isError ?? false,
|
|
323
|
+
timestamp: now,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function bound(result, declared) {
|
|
327
|
+
const bounds = { ...DEFAULT_BOUNDS, ...declared };
|
|
328
|
+
const blocks = result.content ?? [];
|
|
329
|
+
let text = blocks
|
|
330
|
+
.filter((block) => block.type === "text")
|
|
331
|
+
.map((block) => block.text)
|
|
332
|
+
.join("");
|
|
333
|
+
let droppedLines = 0;
|
|
334
|
+
let droppedBytes = 0;
|
|
335
|
+
const lines = text.split("\n");
|
|
336
|
+
if (lines.length > bounds.maxLines) {
|
|
337
|
+
droppedLines = lines.length - bounds.maxLines;
|
|
338
|
+
text = (bounds.retain === "head" ? lines.slice(0, bounds.maxLines) : lines.slice(-bounds.maxLines)).join("\n");
|
|
339
|
+
}
|
|
340
|
+
const bytes = new TextEncoder().encode(text);
|
|
341
|
+
if (bytes.length > bounds.maxBytes) {
|
|
342
|
+
droppedBytes = bytes.length - bounds.maxBytes;
|
|
343
|
+
text = new TextDecoder().decode(bounds.retain === "head" ? bytes.subarray(0, bounds.maxBytes) : bytes.subarray(-bounds.maxBytes));
|
|
344
|
+
}
|
|
345
|
+
if (droppedBytes === 0 && droppedLines === 0)
|
|
346
|
+
return { result };
|
|
347
|
+
let textIndex = -1;
|
|
348
|
+
for (let index = 0; index < blocks.length; index++) {
|
|
349
|
+
if (blocks[index].type !== "text")
|
|
350
|
+
continue;
|
|
351
|
+
if (bounds.retain === "head" && textIndex >= 0)
|
|
352
|
+
continue;
|
|
353
|
+
textIndex = index;
|
|
354
|
+
}
|
|
355
|
+
const content = [];
|
|
356
|
+
for (let index = 0; index < blocks.length; index++) {
|
|
357
|
+
const block = blocks[index];
|
|
358
|
+
if (block.type !== "text")
|
|
359
|
+
content.push(block);
|
|
360
|
+
else if (index === textIndex)
|
|
361
|
+
content.push({ ...block, text });
|
|
362
|
+
}
|
|
363
|
+
const note = {
|
|
364
|
+
severity: "warn",
|
|
365
|
+
code: "truncated",
|
|
366
|
+
message: `output truncated: ${droppedLines} lines, ${droppedBytes} bytes dropped`,
|
|
367
|
+
};
|
|
368
|
+
return {
|
|
369
|
+
result: { ...result, content, diagnostics: [...(result.diagnostics ?? []), note] },
|
|
370
|
+
truncated: { bytes: droppedBytes, lines: droppedLines },
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool.js","sourceRoot":"","sources":["../../../../src/harness/pico3/kinds/tool.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport type { ToolCall } from \"@earendil-works/pi-ai\";\nimport type { TSchema } from \"typebox\";\nimport { Errors } from \"typebox/value\";\nimport { Bounded } from \"../bounded.ts\";\nimport {\n\ttype AnyToolDeclaration,\n\ttype BeforeToolApi,\n\ttype Closure,\n\ttype Completion,\n\ttype CoreKind,\n\ttype CoreTx,\n\ttype HookInfo,\n\ttype HookResult,\n\ttype Id,\n\ttype JsonObject,\n\ttype JsonValue,\n\ttype Runtime,\n\ttype Step,\n\ttype Stored,\n\ttype Task,\n\ttype ToolApi,\n\ttype ToolControl,\n\ttype ToolResult,\n\ttype ToolSlot,\n\ttoStored,\n} from \"../types.ts\";\nimport { taskApi } from \"./task-api.ts\";\n\nexport type StoredToolCall = Stored<ToolCall>;\nexport type ToolInput = {\n\tassistant: Id;\n\tcall: StoredToolCall;\n\toffered: string[];\n\tindex: number; // position of the call in the assistant message; also this tool's slot in sticky.turn.tools\n};\nexport type ToolCheckpoint = { phase: \"started\"; replay: \"safe\" | \"unsafe\"; call: StoredToolCall };\nexport type ToolTaskResult = { entry: Id; control?: ToolControl };\n/** Hook points this kind calls. Register with `h.hooks(namespace, kinds.tool, { … })`. */\nexport interface ToolHooks {\n\t/** Before invocation, before `started`. Chain on `call`; `{ block }` stops it. A throw blocks. */\n\tbeforeTool(\n\t\tcall: StoredToolCall,\n\t\tapi: BeforeToolApi,\n\t\tctx: Context,\n\t): HookResult<{ call?: StoredToolCall; block?: string }>;\n\t/** After the tool returns; may replace the result. Chain. */\n\tafterTool(\n\t\tcall: StoredToolCall,\n\t\tresult: ToolResult,\n\t\tapi: HookInfo & { readonly callId: string },\n\t\tctx: Context,\n\t): HookResult<ToolResult>;\n}\n\ntype T = Task<ToolInput, ToolCheckpoint>;\ntype Rt = Runtime<ToolHooks, CoreTx>;\ntype Out = Closure<ToolTaskResult, never, CoreTx>;\n\nconst synthetic = (text: string, code: string): ToolResult => ({\n\tcontent: [{ type: \"text\", text }],\n\tisError: true,\n\tdiagnostics: [{ severity: \"error\", message: text, code }],\n});\n/** Validate arguments with the tool's real schema (TypeBox 1.x). */\nexport function invalid(declaration: AnyToolDeclaration, call: StoredToolCall): string | undefined {\n\tconst errors = [...Errors(declaration.parameters as TSchema, call.arguments)];\n\treturn errors.length === 0 ? undefined : errors.map((e) => `${e.instancePath || \"/\"}: ${e.message}`).join(\"; \");\n}\nconst sameIdentity = (a: StoredToolCall, b: StoredToolCall) =>\n\ta.id === b.id && a.name === b.name && a.namespace === b.namespace;\n\nexport const tool: CoreKind<ToolInput, ToolCheckpoint, ToolTaskResult, never, { entry: Id }, ToolHooks> = {\n\tname: \"pi.tool\",\n\tturn: true,\n\tinflight: [\"started\"],\n\n\t// Offered-set check, lookup, validate, beforeTool, validate again; then write `started`\n\t// (the in-flight checkpoint) and invoke. A crash before `started` reruns all of this.\n\tasync initial(task, rt, ctx) {\n\t\tconst call = task.input.call;\n\t\tif (!task.input.offered.includes(call.name))\n\t\t\treturn {\n\t\t\t\tdone: close(task, synthetic(`tool ${call.name} was not offered`, \"not_offered\"), undefined, rt.now()),\n\t\t\t};\n\t\tconst declaration = rt.tools.get(call.name);\n\t\tif (declaration === undefined)\n\t\t\treturn {\n\t\t\t\tdone: close(task, synthetic(`tool ${call.name} is not registered`, \"missing_tool\"), undefined, rt.now()),\n\t\t\t};\n\t\tconst bad = invalid(declaration, call);\n\t\tif (bad !== undefined)\n\t\t\treturn {\n\t\t\t\tdone: close(task, synthetic(`invalid arguments: ${bad}`, \"invalid_arguments\"), declaration, rt.now()),\n\t\t\t};\n\t\tconst hooked = await beforeTool(task, rt, call, ctx);\n\t\tif (\"block\" in hooked)\n\t\t\treturn { done: close(task, synthetic(`blocked: ${hooked.block}`, \"blocked\"), declaration, rt.now()) };\n\t\tif (!sameIdentity(call, hooked.call))\n\t\t\treturn { done: close(task, synthetic(\"blocked: call identity changed\", \"blocked\"), declaration, rt.now()) };\n\t\tconst badFinal = invalid(declaration, hooked.call);\n\t\tif (badFinal !== undefined)\n\t\t\treturn {\n\t\t\t\tdone: close(\n\t\t\t\t\ttask,\n\t\t\t\t\tsynthetic(`invalid arguments after hook: ${badFinal}`, \"invalid_arguments\"),\n\t\t\t\t\tdeclaration,\n\t\t\t\t\trt.now(),\n\t\t\t\t),\n\t\t\t};\n\t\tconst final = toStored(hooked.call);\n\t\tawait rt.commit((tx) => {\n\t\t\ttx.checkpoint({ phase: \"started\", replay: declaration.replay ?? \"unsafe\", call: final });\n\t\t\tconst slot = tx.toolSlot(task);\n\t\t\tslot.status = \"running\";\n\t\t\tdelete slot.waitingOn;\n\t\t\ttx.emit({ type: \"tool.started\", taskId: task.id, callId: final.id, name: final.name });\n\t\t}, ctx); // durable before the effect\n\t\treturn { done: await invoke(task, final, declaration, rt, ctx) };\n\t},\n\n\tphases: {\n\t\t// Only entered by the scheduler after reopen. The stored final call is the evidence; beforeTool never reruns.\n\t\tasync started(task, rt, ctx) {\n\t\t\tconst cp = task.checkpoint;\n\t\t\tconst declaration = rt.tools.get(cp.call.name);\n\t\t\tif (declaration === undefined)\n\t\t\t\treturn {\n\t\t\t\t\tdone: close(\n\t\t\t\t\t\ttask,\n\t\t\t\t\t\tsynthetic(`tool ${cp.call.name} unavailable after restart`, \"unavailable\"),\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\trt.now(),\n\t\t\t\t\t\tcp.call,\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\tif (cp.replay !== \"safe\" || (declaration.replay ?? \"unsafe\") !== \"safe\")\n\t\t\t\treturn {\n\t\t\t\t\tdone: close(\n\t\t\t\t\t\ttask,\n\t\t\t\t\t\tsynthetic(`tool ${cp.call.name} was interrupted`, \"interrupted\"),\n\t\t\t\t\t\tdeclaration,\n\t\t\t\t\t\trt.now(),\n\t\t\t\t\t\tcp.call,\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\tif (invalid(declaration, cp.call) !== undefined)\n\t\t\t\treturn {\n\t\t\t\t\tdone: close(\n\t\t\t\t\t\ttask,\n\t\t\t\t\t\tsynthetic(`tool ${cp.call.name} was interrupted; arguments no longer validate`, \"interrupted\"),\n\t\t\t\t\t\tdeclaration,\n\t\t\t\t\t\trt.now(),\n\t\t\t\t\t\tcp.call,\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\treturn { done: await invoke(task, cp.call, declaration, rt, ctx) };\n\t\t},\n\t},\n\n\tasync abort(task, rt, ctx) {\n\t\tfor (const conversationId of task.owns) {\n\t\t\tconst children = await rt.commit((tx) => tx.tasks({ conversationId, status: [\"pending\", \"running\"] }), ctx);\n\t\t\tfor (const child of children) if (!child.background) await rt.abortTask(child.id, ctx);\n\t\t}\n\t\treturn (tx, current) => {\n\t\t\tconst call = task.checkpoint?.call ?? task.input.call;\n\t\t\tconst entry = tx.appendEntry(current.conversationId, {\n\t\t\t\tkind: \"pi.tool_result\",\n\t\t\t\tmodel: [toMessage(call, synthetic(\"tool aborted\", \"aborted\"), rt.now())],\n\t\t\t\tdata: { diagnostics: [{ severity: \"error\", message: \"aborted\", code: \"aborted\" }] },\n\t\t\t});\n\t\t\tconst slot = tx.sticky(current.conversationId).turn.tools[task.input.index];\n\t\t\tif (slot !== undefined) {\n\t\t\t\tslot.status = \"aborted\";\n\t\t\t\tslot.entry = entry;\n\t\t\t\tdelete slot.waitingOn;\n\t\t\t}\n\t\t\ttx.emit({ type: \"tool.aborted\", taskId: current.id, callId: call.id, entry });\n\t\t\treturn { entry };\n\t\t};\n\t},\n};\n\n/** The one point where a throwing handler blocks rather than being skipped. */\nasync function beforeTool(\n\ttask: T,\n\trt: Rt,\n\tcall: StoredToolCall,\n\tctx: Context,\n): Promise<{ call: StoredToolCall } | { block: string }> {\n\tlet current = call;\n\tfor (const binding of rt.hooks.handlers()) {\n\t\tfunction memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;\n\t\tfunction memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;\n\t\tfunction memo<T extends JsonValue>(\n\t\t\tname: string,\n\t\t\tcandidateOrContext: T | Context,\n\t\t\tcontext?: Context,\n\t\t): Promise<T | undefined> {\n\t\t\tconst targetContext = context ?? (candidateOrContext as Context);\n\t\t\treturn rt.commit((tx) => {\n\t\t\t\ttx.plugins(binding.namespace);\n\t\t\t\tconst slot = tx.toolSlot(task);\n\t\t\t\tif (!Object.hasOwn(slot, \"memos\")) slot.memos = {};\n\t\t\t\tconst memos = slot.memos!;\n\t\t\t\tconst key = `hook:${binding.namespace.id}:${name}`;\n\t\t\t\tif (Object.hasOwn(memos, key)) return structuredClone(memos[key]) as T;\n\t\t\t\tif (context === undefined) return undefined;\n\t\t\t\tconst candidate = structuredClone(candidateOrContext as T);\n\t\t\t\tmemos[key] = candidate;\n\t\t\t\treturn candidate;\n\t\t\t}, targetContext);\n\t\t}\n\t\tconst api: BeforeToolApi = {\n\t\t\t...binding.api,\n\t\t\tcallId: call.id,\n\t\t\twaiting: (context) =>\n\t\t\t\trt.commit((tx, currentTask) => {\n\t\t\t\t\ttx.plugins(binding.namespace);\n\t\t\t\t\tconst slot = tx.toolSlot(currentTask as T);\n\t\t\t\t\tif (slot.waitingOn === binding.namespace.id) return;\n\t\t\t\t\tslot.waitingOn = binding.namespace.id;\n\t\t\t\t\ttx.emit({\n\t\t\t\t\t\ttype: \"tool.waiting\",\n\t\t\t\t\t\ttaskId: currentTask.id,\n\t\t\t\t\t\tcallId: call.id,\n\t\t\t\t\t\ton: binding.namespace.id,\n\t\t\t\t\t});\n\t\t\t\t}, context),\n\t\t\tmemo,\n\t\t\temit: (name, data, context) => rt.commit((tx) => tx.emit(binding.namespace, name, data), context),\n\t\t};\n\t\ttry {\n\t\t\tconst value = await binding.handlers.beforeTool?.(current, api, ctx);\n\t\t\tif (!value) continue;\n\t\t\tif (value.block !== undefined) return { block: value.block };\n\t\t\tif (value.call !== undefined) current = value.call;\n\t\t} catch (error) {\n\t\t\tif (ctx.abortSignal?.aborted) throw error;\n\t\t\treturn { block: `hook threw: ${String(error)}` };\n\t\t}\n\t}\n\treturn { call: current };\n}\n\nexport const DEFAULT_BOUNDS = { maxBytes: 64 * 1024, maxLines: 200, retain: \"head\" as const };\n\nasync function invoke(\n\ttask: T,\n\tcall: StoredToolCall,\n\tdeclaration: AnyToolDeclaration,\n\trt: Rt,\n\tctx: Context,\n): Promise<Out> {\n\t// The kernel owns the stream: one bounded buffer, one throttle, one flush path. Flush failures are not swallowed.\n\tconst b = { ...DEFAULT_BOUNDS, ...declaration.output };\n\tconst buffer = new Bounded(b.maxBytes, b.maxLines, b.retain);\n\tlet streamed = false;\n\tlet lastFlush = 0;\n\tlet flushing: Promise<void> = Promise.resolve();\n\tlet flushError: unknown;\n\tconst flush = () => {\n\t\tlastFlush = rt.now();\n\t\tconst text = buffer.text();\n\t\tflushing = flushing\n\t\t\t.then(() =>\n\t\t\t\trt.commit((tx) => {\n\t\t\t\t\ttx.toolSlot(task).output = text;\n\t\t\t\t}, ctx),\n\t\t\t)\n\t\t\t.catch((e) => {\n\t\t\t\tflushError ??= e;\n\t\t\t});\n\t};\n\tconst api = {\n\t\t...taskApi(task, rt),\n\t\t// Only the free fields; identity (callId/name/args/output) is protected.\n\t\tprogress: (update: (slot: Pick<ToolSlot, \"progress\" | \"details\" | \"continuedBy\">) => void, c: Context) =>\n\t\t\trt.commit((tx) => {\n\t\t\t\tconst slot = tx.toolSlot(task);\n\t\t\t\tconst free: Pick<ToolSlot, \"progress\" | \"details\" | \"continuedBy\"> = {\n\t\t\t\t\tprogress: slot.progress,\n\t\t\t\t\tdetails: slot.details,\n\t\t\t\t\tcontinuedBy: slot.continuedBy,\n\t\t\t\t};\n\t\t\t\tupdate(free);\n\t\t\t\tif (free.progress !== undefined) slot.progress = free.progress;\n\t\t\t\telse delete slot.progress;\n\t\t\t\tif (free.details !== undefined) slot.details = toStored(free.details);\n\t\t\t\telse delete slot.details;\n\t\t\t\tif (free.continuedBy !== undefined) slot.continuedBy = free.continuedBy;\n\t\t\t\telse delete slot.continuedBy;\n\t\t\t}, c),\n\t\tstream(chunk: string | Uint8Array) {\n\t\t\tstreamed = true;\n\t\t\tbuffer.push(typeof chunk === \"string\" ? new TextEncoder().encode(chunk) : chunk);\n\t\t\tif (rt.now() - lastFlush >= 100) flush();\n\t\t},\n\t};\n\tfunction memo<T extends JsonValue>(name: string, candidate: T, context: Context): Promise<T>;\n\tfunction memo<T extends JsonValue>(name: string, context: Context): Promise<T | undefined>;\n\tfunction memo<T extends JsonValue>(\n\t\tname: string,\n\t\tcandidateOrContext: T | Context,\n\t\tcontext?: Context,\n\t): Promise<T | undefined> {\n\t\tconst targetContext = context ?? (candidateOrContext as Context);\n\t\treturn rt.commit((tx) => {\n\t\t\tconst slot = tx.toolSlot(task);\n\t\t\tif (!Object.hasOwn(slot, \"memos\")) slot.memos = {};\n\t\t\tconst memos = slot.memos!;\n\t\t\tconst key = `tool:${name}`;\n\t\t\tif (Object.hasOwn(memos, key)) return structuredClone(memos[key]) as T;\n\t\t\tif (context === undefined) return undefined;\n\t\t\tconst candidate = structuredClone(candidateOrContext as T);\n\t\t\tmemos[key] = candidate;\n\t\t\treturn candidate;\n\t\t}, targetContext);\n\t}\n\tconst toolApi: typeof api & Pick<ToolApi, \"callId\" | \"memo\"> = { ...api, callId: call.id, memo };\n\tlet result: ToolResult;\n\ttry {\n\t\tresult = await declaration.execute(call.arguments as never, toolApi, ctx);\n\t} catch (error) {\n\t\tif (ctx.abortSignal?.aborted) throw error;\n\t\tresult = synthetic(`tool threw: ${String(error)}`, \"threw\");\n\t}\n\tif (streamed) {\n\t\tflush();\n\t\tawait flushing;\n\t\tif (flushError !== undefined) throw flushError; // a persistence failure is never hidden\n\t\tif (result.content === undefined)\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tcontent: [{ type: \"text\", text: buffer.text() }],\n\t\t\t\t...(buffer.dropped\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdiagnostics: [\n\t\t\t\t\t\t\t\t...(result.diagnostics ?? []),\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tseverity: \"warn\" as const,\n\t\t\t\t\t\t\t\t\tcode: \"truncated\",\n\t\t\t\t\t\t\t\t\tmessage: `${buffer.droppedBytes} bytes / ${buffer.droppedLines} lines dropped (${b.retain} ${b.maxBytes} retained)`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t};\n\t}\n\tlet final = result;\n\tawait rt.hooks.each(\n\t\tctx,\n\t\t(h, api) => h.afterTool?.(call, final, { ...api, callId: call.id }, ctx),\n\t\t(value) => {\n\t\t\tif (value !== undefined) final = value;\n\t\t},\n\t);\n\treturn close(\n\t\ttask,\n\t\tfinal,\n\t\tdeclaration,\n\t\trt.now(),\n\t\tcall,\n\t\tstreamed ? { bytes: buffer.droppedBytes, lines: buffer.droppedLines } : undefined,\n\t);\n}\n\n/** Closure: bound the output, store the exact model message under the stored final call, rest as strict-JSON data. */\nfunction close(\n\ttask: T,\n\traw: ToolResult,\n\tdeclaration: AnyToolDeclaration | undefined,\n\tnow: number,\n\tcall: StoredToolCall = task.input.call,\n\tstreamTruncated?: { bytes: number; lines: number },\n): Out {\n\treturn (tx, current) => {\n\t\tconst bounded = bound({ ...raw, content: raw.content ?? [] }, declaration?.output);\n\t\tconst result = bounded.result;\n\t\tconst bytes = (bounded.truncated?.bytes ?? 0) + (streamTruncated?.bytes ?? 0);\n\t\tconst lines = (bounded.truncated?.lines ?? 0) + (streamTruncated?.lines ?? 0);\n\t\tconst truncated = bytes > 0 || lines > 0 ? { bytes, lines } : undefined;\n\t\tconst data: JsonObject = {};\n\t\tif (result.details !== undefined) data.details = toStored(result.details);\n\t\tif (result.diagnostics !== undefined) data.diagnostics = toStored(result.diagnostics);\n\t\tif (result.control !== undefined) data.control = toStored(result.control);\n\t\tif (truncated !== undefined) data.truncated = truncated;\n\t\tconst entry = tx.appendEntry(current.conversationId, {\n\t\t\tkind: \"pi.tool_result\",\n\t\t\tmodel: [toMessage(call, result, now)],\n\t\t\tdata,\n\t\t});\n\t\tconst slot = tx.toolSlot(task);\n\t\tslot.status = result.isError === true ? \"error\" : \"done\";\n\t\tslot.entry = entry;\n\t\tdelete slot.waitingOn;\n\t\ttx.emit({\n\t\t\ttype: \"tool.finished\",\n\t\t\ttaskId: current.id,\n\t\t\tcallId: call.id,\n\t\t\tentry,\n\t\t\tisError: result.isError === true,\n\t\t\t...(result.control === undefined ? {} : { control: result.control }),\n\t\t});\n\t\tif (truncated !== undefined)\n\t\t\ttx.emit({\n\t\t\t\ttype: \"warning\",\n\t\t\t\tsource: \"tool\",\n\t\t\t\tmessage: `tool output truncated: ${truncated.bytes} bytes / ${truncated.lines} lines dropped`,\n\t\t\t});\n\t\tconst completion: Completion<ToolTaskResult, never> = {\n\t\t\tstatus: \"completed\",\n\t\t\tresult: { entry, ...(result.control === undefined ? {} : { control: toStored(result.control) }) },\n\t\t};\n\t\treturn completion;\n\t};\n}\n\nfunction toMessage(call: StoredToolCall, result: ToolResult, now: number) {\n\treturn toStored({\n\t\trole: \"toolResult\" as const,\n\t\ttoolCallId: call.id,\n\t\ttoolName: call.name,\n\t\tcontent: result.content ?? [],\n\t\tisError: result.isError ?? false,\n\t\ttimestamp: now,\n\t});\n}\n\nfunction bound(\n\tresult: ToolResult,\n\tdeclared: AnyToolDeclaration[\"output\"],\n): { result: ToolResult; truncated?: { bytes: number; lines: number } } {\n\tconst bounds = { ...DEFAULT_BOUNDS, ...declared };\n\tconst blocks = result.content ?? [];\n\tlet text = blocks\n\t\t.filter((block) => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\");\n\tlet droppedLines = 0;\n\tlet droppedBytes = 0;\n\tconst lines = text.split(\"\\n\");\n\tif (lines.length > bounds.maxLines) {\n\t\tdroppedLines = lines.length - bounds.maxLines;\n\t\ttext = (bounds.retain === \"head\" ? lines.slice(0, bounds.maxLines) : lines.slice(-bounds.maxLines)).join(\"\\n\");\n\t}\n\tconst bytes = new TextEncoder().encode(text);\n\tif (bytes.length > bounds.maxBytes) {\n\t\tdroppedBytes = bytes.length - bounds.maxBytes;\n\t\ttext = new TextDecoder().decode(\n\t\t\tbounds.retain === \"head\" ? bytes.subarray(0, bounds.maxBytes) : bytes.subarray(-bounds.maxBytes),\n\t\t);\n\t}\n\tif (droppedBytes === 0 && droppedLines === 0) return { result };\n\tlet textIndex = -1;\n\tfor (let index = 0; index < blocks.length; index++) {\n\t\tif (blocks[index]!.type !== \"text\") continue;\n\t\tif (bounds.retain === \"head\" && textIndex >= 0) continue;\n\t\ttextIndex = index;\n\t}\n\tconst content: NonNullable<ToolResult[\"content\"]> = [];\n\tfor (let index = 0; index < blocks.length; index++) {\n\t\tconst block = blocks[index]!;\n\t\tif (block.type !== \"text\") content.push(block);\n\t\telse if (index === textIndex) content.push({ ...block, text });\n\t}\n\tconst note = {\n\t\tseverity: \"warn\" as const,\n\t\tcode: \"truncated\",\n\t\tmessage: `output truncated: ${droppedLines} lines, ${droppedBytes} bytes dropped`,\n\t};\n\treturn {\n\t\tresult: { ...result, content, diagnostics: [...(result.diagnostics ?? []), note] },\n\t\ttruncated: { bytes: droppedBytes, lines: droppedLines },\n\t};\n}\n\nexport type { Step };\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transaction-scoped revocable membrane over a Chord-tracked document.
|
|
3
|
+
*
|
|
4
|
+
* Every object reached through a document proxy is wrapped lazily; all wrappers of one
|
|
5
|
+
* transaction share one liveness flag; identity is preserved by a WeakMap. Mutations forward
|
|
6
|
+
* to the underlying Chord proxy so its tracker records ops. At transaction finish — success,
|
|
7
|
+
* callback failure, validation failure, storage failure — `revoke()` flips the flag and every
|
|
8
|
+
* retained wrapper, root or nested, throws on any operation.
|
|
9
|
+
*
|
|
10
|
+
* Assigning one wrapper into another (`a.list = b.list`) is rejected: Chord stores the value it
|
|
11
|
+
* is given, and a stored proxy is the classic footgun. Assign plain values; splice in place.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Membrane {
|
|
14
|
+
private alive;
|
|
15
|
+
private readonly wrappers;
|
|
16
|
+
private readonly isWrapper;
|
|
17
|
+
private readonly what;
|
|
18
|
+
constructor(what: string);
|
|
19
|
+
revoke(): void;
|
|
20
|
+
wrap<T extends object>(target: T): T;
|
|
21
|
+
private assertPlainInput;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=membrane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"membrane.d.ts","sourceRoot":"","sources":["../../../src/harness/pico3/membrane.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,qBAAa,QAAQ;IACpB,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAE9B,YAAY,IAAI,EAAE,MAAM,EAEvB;IAED,MAAM,SAEL;IAED,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAoFnC;IAED,OAAO,CAAC,gBAAgB;CAUxB","sourcesContent":["/**\n * A transaction-scoped revocable membrane over a Chord-tracked document.\n *\n * Every object reached through a document proxy is wrapped lazily; all wrappers of one\n * transaction share one liveness flag; identity is preserved by a WeakMap. Mutations forward\n * to the underlying Chord proxy so its tracker records ops. At transaction finish — success,\n * callback failure, validation failure, storage failure — `revoke()` flips the flag and every\n * retained wrapper, root or nested, throws on any operation.\n *\n * Assigning one wrapper into another (`a.list = b.list`) is rejected: Chord stores the value it\n * is given, and a stored proxy is the classic footgun. Assign plain values; splice in place.\n */\nexport class Membrane {\n\tprivate alive = true;\n\tprivate readonly wrappers = new WeakMap<object, object>();\n\tprivate readonly isWrapper = new WeakSet<object>();\n\tprivate readonly what: string;\n\n\tconstructor(what: string) {\n\t\tthis.what = what;\n\t}\n\n\trevoke() {\n\t\tthis.alive = false;\n\t}\n\n\twrap<T extends object>(target: T): T {\n\t\tconst hit = this.wrappers.get(target);\n\t\tif (hit !== undefined) return hit as T;\n\t\tconst m = this;\n\t\tconst dead = () => new TypeError(`document proxy (${m.what}) used outside its transaction`);\n\t\tconst out = (v: unknown): unknown => (typeof v === \"object\" && v !== null ? m.wrap(v) : v);\n\t\tconst inp = (value: unknown): unknown => {\n\t\t\tif (typeof value !== \"object\" || value === null) return value;\n\t\t\tm.assertPlainInput(value, new WeakSet());\n\t\t\treturn JSON.parse(JSON.stringify(value)) as unknown;\n\t\t};\n\t\tconst proxy = new Proxy(target, {\n\t\t\tget(t, key, receiver) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tconst v = Reflect.get(t, key, receiver === proxy ? t : receiver);\n\t\t\t\tif (typeof v === \"function\") {\n\t\t\t\t\t// Methods run against the tracked target. Callback arguments (array elements and\n\t\t\t\t\t// the array itself) must cross back through the membrane before user code sees them.\n\t\t\t\t\treturn (...args: unknown[]) => {\n\t\t\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\t\t\tconst safeArgs = args.map((arg) => {\n\t\t\t\t\t\t\tif (typeof arg !== \"function\") return inp(arg);\n\t\t\t\t\t\t\treturn function (this: unknown, ...callbackArgs: unknown[]) {\n\t\t\t\t\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\t\t\t\t\treturn Reflect.apply(arg, this, callbackArgs.map(out));\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn out(Reflect.apply(v, t, safeArgs));\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn out(v);\n\t\t\t},\n\t\t\tset(t, key, value) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tif (key === \"__proto__\") throw new TypeError(`document proxy (${m.what}) cannot change prototypes`);\n\t\t\t\treturn Reflect.set(t, key, inp(value) as never);\n\t\t\t},\n\t\t\tdeleteProperty(t, key) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.deleteProperty(t, key);\n\t\t\t},\n\t\t\thas(t, key) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.has(t, key);\n\t\t\t},\n\t\t\townKeys(t) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.ownKeys(t);\n\t\t\t},\n\t\t\tgetOwnPropertyDescriptor(t, key) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tconst d = Reflect.getOwnPropertyDescriptor(t, key);\n\t\t\t\tif (d === undefined) return undefined;\n\t\t\t\t// Descriptors must not leak a raw nested object.\n\t\t\t\tif (\"value\" in d) (d as { value: unknown }).value = out(d.value);\n\t\t\t\treturn d;\n\t\t\t},\n\t\t\tdefineProperty(t, key, descriptor) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tif (\"get\" in descriptor || \"set\" in descriptor)\n\t\t\t\t\tthrow new TypeError(`document proxy (${m.what}) cannot define accessors`);\n\t\t\t\tconst safe = \"value\" in descriptor ? { ...descriptor, value: inp(descriptor.value) } : descriptor;\n\t\t\t\treturn Reflect.defineProperty(t, key, safe);\n\t\t\t},\n\t\t\tgetPrototypeOf(t) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.getPrototypeOf(t);\n\t\t\t},\n\t\t\tsetPrototypeOf() {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tthrow new TypeError(`document proxy (${m.what}) cannot change prototypes`);\n\t\t\t},\n\t\t\tisExtensible(t) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.isExtensible(t);\n\t\t\t},\n\t\t\tpreventExtensions() {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tthrow new TypeError(`document proxy (${m.what}) cannot change extensibility`);\n\t\t\t},\n\t\t});\n\t\tthis.wrappers.set(target, proxy);\n\t\tthis.isWrapper.add(proxy);\n\t\treturn proxy as T;\n\t}\n\n\tprivate assertPlainInput(value: object, seen: WeakSet<object>): void {\n\t\tif (this.isWrapper.has(value))\n\t\t\tthrow new TypeError(`assigning a document proxy into a document (${this.what}); assign a plain value`);\n\t\tif (seen.has(value)) throw new TypeError(`assigning a cyclic value into a document (${this.what})`);\n\t\tseen.add(value);\n\t\tfor (const nested of Object.values(value)) {\n\t\t\tif (typeof nested === \"object\" && nested !== null) this.assertPlainInput(nested, seen);\n\t\t}\n\t\tseen.delete(value);\n\t}\n}\n"]}
|