@yolk-sdk/codemode 0.1.0-canary.97 → 0.1.0-canary.99
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 +41 -11
- package/dist/index.d.mts +3 -3
- package/dist/store.d.mts +39 -2
- package/dist/store.d.mts.map +1 -1
- package/dist/store.mjs.map +1 -1
- package/dist/tool.d.mts +31 -3
- package/dist/tool.d.mts.map +1 -1
- package/dist/tool.mjs +112 -5
- package/dist/tool.mjs.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +13 -2
- package/src/store.ts +42 -0
- package/src/tool.ts +221 -6
package/README.md
CHANGED
|
@@ -55,8 +55,9 @@ const program = Effect.gen(function* () {
|
|
|
55
55
|
- Scripts call tools as `await tools.<id>(args)`. Every nested call runs through the resolution's
|
|
56
56
|
normal execute path (input decoding, enablement, registration wrappers, same host context), gets
|
|
57
57
|
the id `<toolCallId>/<seq>`, and is recorded on the result's `nestedCalls` with status, duration,
|
|
58
|
-
truncated error, and usage.
|
|
59
|
-
|
|
58
|
+
truncated error, and usage. The record keeps up to `maxNestedCalls` calls, at most 4096
|
|
59
|
+
(`nestedToolCallMaxRecordedCalls`; argument byte budgets still apply), and per-status `counts`
|
|
60
|
+
over every call. Nested results never reach the model; only the script output and return value do.
|
|
60
61
|
- A call resolves to `structuredContent` for tools with an output schema and to the text content
|
|
61
62
|
otherwise; error results, `beforeNestedCall` failures, and calls past `maxNestedCalls` reject
|
|
62
63
|
with an `Error` whose message is `tools.<id>: <text>`, or `tools["<name>"]: <text>` for a tool
|
|
@@ -93,20 +94,49 @@ const program = Effect.gen(function* () {
|
|
|
93
94
|
`ToolResultMessage`s carry no tool name: pair each with its assistant tool call's name.
|
|
94
95
|
- `beforeNestedCall({ call, context })` runs before each nested call; a failure rejects that call in
|
|
95
96
|
the script with `<label>: <message>` and records it as `error` without executing it.
|
|
97
|
+
- `afterNestedCall({ call, outcome, durationMs, context, result? })` runs after each admitted
|
|
98
|
+
nested call with `success`, `failure` (error result or unexpected failure), or `interrupted`
|
|
99
|
+
(cancelled, for example still running when the script ended). Calls rejected before running are
|
|
100
|
+
not reported. A failing hook (a failure, a defect, or a synchronous throw) is logged and never
|
|
101
|
+
changes the call's result.
|
|
96
102
|
- A nested call that is interrupted rejects with `<label> was cancelled.`; a defect rejects with
|
|
97
103
|
`<label> failed unexpectedly.` (the same label as above). If an executor misses its deadline, the tool returns a `timeout` failure
|
|
98
104
|
`timeoutMs` + 5 s after the start and aborts it.
|
|
99
105
|
|
|
106
|
+
## Re-execution and the tool ledger
|
|
107
|
+
|
|
108
|
+
Hosts that re-execute steps (Vercel Workflow's queue is at-least-once) pass a durable ledger to
|
|
109
|
+
`resolveTools(modules, context, { ledger: { store } })` (see `@yolk-sdk/agent/tools`). The
|
|
110
|
+
`codemode` call is itself ledgered, so a re-executed call never runs its script again:
|
|
111
|
+
|
|
112
|
+
- completed: the stored result is returned;
|
|
113
|
+
- still running elsewhere: the call waits for it (up to `maxWaitMs`/`deadline`);
|
|
114
|
+
- abandoned (the earlier execution crashed): an interrupted error result lists that script's
|
|
115
|
+
ledgered nested calls as applied, failed, or unknown (started, no recorded result), states that
|
|
116
|
+
they were not undone, and asks the model to verify. Hosts get the same entries in
|
|
117
|
+
`structuredContent.codemode` without calling the store again:
|
|
118
|
+
`{ ok: false, interrupted: true, interruptedCalls: { calls, complete, counts } }`, where each call
|
|
119
|
+
is `{ key, toolName, args, status: 'applied' | 'failed' | 'unknown' }` (`args` compact JSON cut
|
|
120
|
+
with a trailing `…`), bounded like `nestedCalls` (`maxNestedCalls` calls, 8 KiB of arguments per
|
|
121
|
+
call, 32 KiB in total; `complete: false` when cut, here or by the ledger), and `counts` covers
|
|
122
|
+
every entry. Types: `CodeModeInterruptedCalls`, `CodeModeInterruptedCall`,
|
|
123
|
+
`CodeModeInterruptedCallStatus`. When the ledger cannot list the nested calls, the result stays
|
|
124
|
+
interrupted, says they may already have been applied, and sets
|
|
125
|
+
`interruptedCallsUnavailable: true` instead of `interruptedCalls`.
|
|
126
|
+
|
|
127
|
+
Nested write calls are ledgered under `<toolCallId>/<seq>` and receive a stable `idempotencyKey`.
|
|
128
|
+
Calls the ledger policy skips (by default read-only calls) are not listed.
|
|
129
|
+
|
|
100
130
|
## Limits
|
|
101
131
|
|
|
102
|
-
| Limit | Default | Notes
|
|
103
|
-
| ------------------ | ------- |
|
|
104
|
-
| `timeoutMs` | 120000 | Clamped to `deadline(context)` minus 5 s, never below 1 s
|
|
105
|
-
| `memoryLimitBytes` | 64 MiB | QuickJS heap cap
|
|
106
|
-
| `maxNestedCalls` | 256 | Further calls reject
|
|
107
|
-
| `maxOutputChars` | 40000 | Head-and-tail cut with an omission marker
|
|
108
|
-
| `maxImages` | 8 | Later images are dropped with a note
|
|
109
|
-
| `maxImageBytes` | 4 MiB | Base64 characters of images in total
|
|
132
|
+
| Limit | Default | Notes |
|
|
133
|
+
| ------------------ | ------- | ------------------------------------------------------------------------ |
|
|
134
|
+
| `timeoutMs` | 120000 | Clamped to `deadline(context)` minus 5 s, never below 1 s |
|
|
135
|
+
| `memoryLimitBytes` | 64 MiB | QuickJS heap cap |
|
|
136
|
+
| `maxNestedCalls` | 256 | Further calls reject; also sizes the `nestedCalls` record (at most 4096) |
|
|
137
|
+
| `maxOutputChars` | 40000 | Head-and-tail cut with an omission marker |
|
|
138
|
+
| `maxImages` | 8 | Later images are dropped with a note |
|
|
139
|
+
| `maxImageBytes` | 4 MiB | Base64 characters of images in total |
|
|
110
140
|
|
|
111
141
|
## Classifier tool
|
|
112
142
|
|
|
@@ -130,7 +160,7 @@ permits. Nested-call records carry token usage; cost stays in the result's
|
|
|
130
160
|
- Where the tool is advertised (voice sessions do not get it in v1), deadlines, and limits.
|
|
131
161
|
- Next.js: `serverExternalPackages: ['@yolk-sdk/codemode', '@earendil-works/pi-codemode', 'quickjs-wasi']`.
|
|
132
162
|
- Vercel Workflow: run the code mode call inside the tool-batch step (`'use step'`), never inside a
|
|
133
|
-
`'use workflow'` function.
|
|
163
|
+
`'use workflow'` function, and supply a durable tool ledger: steps are at-least-once.
|
|
134
164
|
|
|
135
165
|
## Boundaries
|
|
136
166
|
|
package/dist/index.d.mts
CHANGED
|
@@ -3,6 +3,6 @@ import { CodeModeCatalogTool, CodeModeDescriptionInput, CodeModeListing, CodeMod
|
|
|
3
3
|
import { ClassifierConcurrencyLimiter, ClassifierToolParams, MakeClassifierToolOptions, classifierToolName, defaultClassifierProcessLimiter, defaultClassifierProcessMaxConcurrency, defaultClassifierToolMaxConcurrency, makeClassifierConcurrencyLimiter, makeClassifierTool } from "./classifier-tool.mjs";
|
|
4
4
|
import { CodeModeSearchHit, CodeModeSearchOptions, codeModeSearchTokens, defaultCodeModeSearchLimit, searchCodeModeTools } from "./search.mjs";
|
|
5
5
|
import { CodeModeCallSummary, CodeModeResultSegment, boundCodeModeSegments, codeModeResultSegments, codeModeSegmentsContent, defaultCodeModeMaxImageBytes, defaultCodeModeMaxImages, summarizeCodeModeCalls } from "./output.mjs";
|
|
6
|
-
import { CodeModeStructuredContent, CodeModeToolResultEntry, codeModeStoreFromToolResults, maxCodeModeStoreTotalChars, maxCodeModeStoreValueChars } from "./store.mjs";
|
|
7
|
-
import { CodeModeLimits, MakeCodeModeToolOptions, codeModeDeadlineMarginMs, codeModeMinimumTimeoutMs, codeModeToolName, defaultCodeModeLimits, makeCodeModeTool } from "./tool.mjs";
|
|
8
|
-
export { type ClassifierConcurrencyLimiter, ClassifierToolParams, type CodeModeCallSummary, type CodeModeCatalogTool, type CodeModeDescriptionInput, type CodeModeError, type CodeModeErrorKind, type CodeModeExecuteOptions, type CodeModeExecutionResult, type CodeModeExecutor, type CodeModeExecutorGlobal, type CodeModeExecutorTool, type CodeModeJsonSchema, type CodeModeLimits, type CodeModeListing, type CodeModeOutputItem, type CodeModeResultSegment, type CodeModeSearchHit, type CodeModeSearchOptions, type CodeModeStore, type CodeModeStoreWrites, type CodeModeStructuredContent, type CodeModeToolContext, type CodeModeToolExposure, type CodeModeToolResultEntry, type MakeClassifierToolOptions, type MakeCodeModeToolOptions, boundCodeModeSegments, classifierToolName, codeModeCatalog, codeModeDeadlineMarginMs, codeModeMinimumTimeoutMs, codeModeResultSegments, codeModeSearchTokens, codeModeSegmentsContent, codeModeStoreFromToolResults, codeModeToolName, defaultClassifierProcessLimiter, defaultClassifierProcessMaxConcurrency, defaultClassifierToolMaxConcurrency, defaultCodeModeInlineBudget, defaultCodeModeLimits, defaultCodeModeMaxImageBytes, defaultCodeModeMaxImages, defaultCodeModeSearchLimit, describeCodeModeTool, estimateCodeModeTokens, findCodeModeTool, makeClassifierConcurrencyLimiter, makeClassifierTool, makeCodeModeTool, maxCodeModeStoreTotalChars, maxCodeModeStoreValueChars, renderCodeModeDescription, searchCodeModeTools, selectCodeModeListing, summarizeCodeModeCalls };
|
|
6
|
+
import { CodeModeInterruptedCall, CodeModeInterruptedCallStatus, CodeModeInterruptedCalls, CodeModeStructuredContent, CodeModeToolResultEntry, codeModeStoreFromToolResults, maxCodeModeStoreTotalChars, maxCodeModeStoreValueChars } from "./store.mjs";
|
|
7
|
+
import { CodeModeAfterNestedCallInput, CodeModeLimits, CodeModeNestedCallOutcome, MakeCodeModeToolOptions, codeModeDeadlineMarginMs, codeModeMinimumTimeoutMs, codeModeToolName, defaultCodeModeLimits, makeCodeModeTool } from "./tool.mjs";
|
|
8
|
+
export { type ClassifierConcurrencyLimiter, ClassifierToolParams, type CodeModeAfterNestedCallInput, type CodeModeCallSummary, type CodeModeCatalogTool, type CodeModeDescriptionInput, type CodeModeError, type CodeModeErrorKind, type CodeModeExecuteOptions, type CodeModeExecutionResult, type CodeModeExecutor, type CodeModeExecutorGlobal, type CodeModeExecutorTool, type CodeModeInterruptedCall, type CodeModeInterruptedCallStatus, type CodeModeInterruptedCalls, type CodeModeJsonSchema, type CodeModeLimits, type CodeModeListing, type CodeModeNestedCallOutcome, type CodeModeOutputItem, type CodeModeResultSegment, type CodeModeSearchHit, type CodeModeSearchOptions, type CodeModeStore, type CodeModeStoreWrites, type CodeModeStructuredContent, type CodeModeToolContext, type CodeModeToolExposure, type CodeModeToolResultEntry, type MakeClassifierToolOptions, type MakeCodeModeToolOptions, boundCodeModeSegments, classifierToolName, codeModeCatalog, codeModeDeadlineMarginMs, codeModeMinimumTimeoutMs, codeModeResultSegments, codeModeSearchTokens, codeModeSegmentsContent, codeModeStoreFromToolResults, codeModeToolName, defaultClassifierProcessLimiter, defaultClassifierProcessMaxConcurrency, defaultClassifierToolMaxConcurrency, defaultCodeModeInlineBudget, defaultCodeModeLimits, defaultCodeModeMaxImageBytes, defaultCodeModeMaxImages, defaultCodeModeSearchLimit, describeCodeModeTool, estimateCodeModeTokens, findCodeModeTool, makeClassifierConcurrencyLimiter, makeClassifierTool, makeCodeModeTool, maxCodeModeStoreTotalChars, maxCodeModeStoreValueChars, renderCodeModeDescription, searchCodeModeTools, selectCodeModeListing, summarizeCodeModeCalls };
|
package/dist/store.d.mts
CHANGED
|
@@ -7,7 +7,44 @@ import { CodeModeStore, CodeModeStoreWrites } from "./executor.mjs";
|
|
|
7
7
|
type CodeModeStructuredContent = {
|
|
8
8
|
readonly codemode: {
|
|
9
9
|
readonly ok: boolean;
|
|
10
|
-
readonly storeWrites?: CodeModeStoreWrites;
|
|
10
|
+
readonly storeWrites?: CodeModeStoreWrites; /** Set on the result of a re-executed call whose earlier execution was abandoned. */
|
|
11
|
+
readonly interrupted?: true; /** With `interrupted`: the abandoned execution's ledgered nested calls. */
|
|
12
|
+
readonly interruptedCalls?: CodeModeInterruptedCalls;
|
|
13
|
+
/** With `interrupted`, instead of `interruptedCalls`: the nested calls could not be listed
|
|
14
|
+
* (the ledger's `list` failed), so any of them may have been applied.
|
|
15
|
+
*/
|
|
16
|
+
readonly interruptedCallsUnavailable?: true;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/** What an abandoned execution's ledgered nested call is known to have done: `applied` (recorded
|
|
20
|
+
* a result), `failed` (recorded a `ToolError` or an `isError` result), or `unknown` (started, no
|
|
21
|
+
* recorded outcome, so it may have been applied).
|
|
22
|
+
*/
|
|
23
|
+
type CodeModeInterruptedCallStatus = 'applied' | 'failed' | 'unknown';
|
|
24
|
+
/** One ledgered nested call of an abandoned execution. `key` is its ledger key
|
|
25
|
+
* (`<toolCallId>/<seq>`); `args` is compact JSON, cut with a trailing `…` (then no longer JSON).
|
|
26
|
+
*/
|
|
27
|
+
type CodeModeInterruptedCall = {
|
|
28
|
+
readonly key: string;
|
|
29
|
+
readonly toolName: string;
|
|
30
|
+
readonly args: string;
|
|
31
|
+
readonly status: CodeModeInterruptedCallStatus;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* `structuredContent.codemode.interruptedCalls`: plain JSON, in sequence order, bounded like
|
|
35
|
+
* `nestedCalls` (at most `limits.maxNestedCalls` calls; arguments within 8 KiB per call and
|
|
36
|
+
* 32 KiB in total). `complete: false` means calls were dropped or arguments cut, here or already
|
|
37
|
+
* in the ledger entry (`ToolLedgerEntry.argsTruncated`); `counts` covers every ledgered nested
|
|
38
|
+
* call, dropped ones included. Calls the ledger policy skips (by default read-only calls) never
|
|
39
|
+
* appear.
|
|
40
|
+
*/
|
|
41
|
+
type CodeModeInterruptedCalls = {
|
|
42
|
+
readonly calls: ReadonlyArray<CodeModeInterruptedCall>;
|
|
43
|
+
readonly complete: boolean;
|
|
44
|
+
readonly counts: {
|
|
45
|
+
readonly applied: number;
|
|
46
|
+
readonly failed: number;
|
|
47
|
+
readonly unknown: number;
|
|
11
48
|
};
|
|
12
49
|
};
|
|
13
50
|
/** Largest stored value: characters of JSON (256 KiB), as the pi engine counts them. */
|
|
@@ -36,5 +73,5 @@ declare const codeModeStoreFromToolResults: (entries: ReadonlyArray<CodeModeTool
|
|
|
36
73
|
readonly toolName?: string;
|
|
37
74
|
}) => CodeModeStore;
|
|
38
75
|
//#endregion
|
|
39
|
-
export { CodeModeStructuredContent, CodeModeToolResultEntry, codeModeStoreFromToolResults, maxCodeModeStoreTotalChars, maxCodeModeStoreValueChars };
|
|
76
|
+
export { CodeModeInterruptedCall, CodeModeInterruptedCallStatus, CodeModeInterruptedCalls, CodeModeStructuredContent, CodeModeToolResultEntry, codeModeStoreFromToolResults, maxCodeModeStoreTotalChars, maxCodeModeStoreValueChars };
|
|
40
77
|
//# sourceMappingURL=store.d.mts.map
|
package/dist/store.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.mts","names":[],"sources":["../src/store.ts"],"mappings":";;;;;AAQA;KAAY,yBAAA;EAAA,SACD,QAAA;IAAA,SACE,EAAA;IAAA,SACA,WAAA,GAAc,
|
|
1
|
+
{"version":3,"file":"store.d.mts","names":[],"sources":["../src/store.ts"],"mappings":";;;;;AAQA;KAAY,yBAAA;EAAA,SACD,QAAA;IAAA,SACE,EAAA;IAAA,SACA,WAAA,GAAc,mBAAA,EADd;IAAA,SAGA,WAAA,SAFc;IAAA,SAId,gBAAA,GAAmB,wBAAwB;IAA3C;;;IAAA,SAIA,2BAAA;EAAA;AAAA;;;;AAQ4B;KAA7B,6BAAA;;;;KAKA,uBAAA;EAAA,SACD,GAAA;EAAA,SACA,QAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA,EAAQ,6BAA6B;AAAA;AAAA;AAWhD;;;;;;;AAXgD,KAWpC,wBAAA;EAAA,SACD,KAAA,EAAO,aAAa,CAAC,uBAAA;EAAA,SACrB,QAAA;EAAA,SACA,MAAA;IAAA,SACE,OAAA;IAAA,SACA,MAAA;IAAA,SACA,OAAA;EAAA;AAAA;;cAmBA,0BAAA;AAAuC;AAAA,cAGvC,0BAAA;;KAGD,uBAAA;EAAA,SACD,QAAA;EAAA,SACA,MAAA;IAAA,SAAmB,iBAAA;EAAA;AAAA;;;;;;AAAiB;AAc/C;;;;;cAAa,4BAAA,GACX,OAAA,EAAS,aAAA,CAAc,uBAAA,GACvB,OAAA;EAAA,SAAoB,QAAA;AAAA,MACnB,aAAA"}
|
package/dist/store.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import { Option } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport type { CodeModeStore, CodeModeStoreWrites } from './executor.ts'\nimport { codeModeToolName } from './tool.ts'\n\n/** `structuredContent` of a code mode `ToolResult`. `storeWrites` is present only for successful\n * scripts that changed the store.\n */\nexport type CodeModeStructuredContent = {\n readonly codemode: {\n readonly ok: boolean\n readonly storeWrites?: CodeModeStoreWrites\n }\n}\n\nconst StoreWritesSchema = Schema.Struct({\n set: Schema.Record(Schema.String, Schema.Json),\n delete: Schema.Array(Schema.String)\n})\n\nconst CodeModeStructuredContentSchema = Schema.Struct({\n codemode: Schema.Struct({\n ok: Schema.Boolean,\n storeWrites: Schema.optionalKey(StoreWritesSchema)\n })\n})\n\nconst decodeStructuredContent = Schema.decodeUnknownOption(CodeModeStructuredContentSchema)\n\n/** Largest stored value: characters of JSON (256 KiB), as the pi engine counts them. */\nexport const maxCodeModeStoreValueChars = 256 * 1024\n\n/** Largest store: characters of keys plus JSON values (1 MiB), as the pi engine counts them. */\nexport const maxCodeModeStoreTotalChars = 1024 * 1024\n\n/** One prior tool result with the name of the tool that produced it. */\nexport type CodeModeToolResultEntry = {\n readonly toolName: string\n readonly result: { readonly structuredContent?: unknown }\n}\n\n/**\n * Rebuilds the code mode store from prior tool results, oldest first: applies the `storeWrites` of\n * each successful script of the code mode tool named `toolName` (default `codemode`) in order.\n * Results of other tools (even when their `structuredContent` looks like a code mode result),\n * failed scripts, and malformed contents are skipped. Transcript `ToolResultMessage`s carry no tool\n * name; pair each with the name of its assistant tool call.\n *\n * The rebuilt store keeps the engine bounds: a write whose JSON value exceeds 256 Ki characters, or\n * that would grow the store beyond 1 Mi characters (keys plus JSON values), is dropped and the key\n * keeps its previous value. Deletions always apply. Pass the value from `loadStore`.\n */\nexport const codeModeStoreFromToolResults = (\n entries: ReadonlyArray<CodeModeToolResultEntry>,\n options: { readonly toolName?: string } = {}\n): CodeModeStore => {\n const toolName = options.toolName ?? codeModeToolName\n const store = new Map<string, { readonly value: Schema.Json; readonly chars: number }>()\n let total = 0\n\n for (const entry of entries) {\n if (entry.toolName !== toolName) continue\n\n const content = decodeStructuredContent(entry.result.structuredContent)\n\n if (Option.isNone(content) || !content.value.codemode.ok) continue\n\n const writes = content.value.codemode.storeWrites\n\n if (writes === undefined) continue\n\n for (const key of writes.delete) {\n total -= store.get(key)?.chars ?? 0\n store.delete(key)\n }\n\n for (const [key, value] of Object.entries(writes.set)) {\n const json = JSON.stringify(value)\n\n if (json.length > maxCodeModeStoreValueChars) continue\n\n const chars = key.length + json.length\n const next = total - (store.get(key)?.chars ?? 0) + chars\n\n if (next > maxCodeModeStoreTotalChars) continue\n\n store.set(key, { value, chars })\n total = next\n }\n }\n\n return Object.fromEntries([...store].map(([key, stored]) => [key, stored.value]))\n}\n"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import { Option } from 'effect'\nimport * as Schema from 'effect/Schema'\nimport type { CodeModeStore, CodeModeStoreWrites } from './executor.ts'\nimport { codeModeToolName } from './tool.ts'\n\n/** `structuredContent` of a code mode `ToolResult`. `storeWrites` is present only for successful\n * scripts that changed the store.\n */\nexport type CodeModeStructuredContent = {\n readonly codemode: {\n readonly ok: boolean\n readonly storeWrites?: CodeModeStoreWrites\n /** Set on the result of a re-executed call whose earlier execution was abandoned. */\n readonly interrupted?: true\n /** With `interrupted`: the abandoned execution's ledgered nested calls. */\n readonly interruptedCalls?: CodeModeInterruptedCalls\n /** With `interrupted`, instead of `interruptedCalls`: the nested calls could not be listed\n * (the ledger's `list` failed), so any of them may have been applied.\n */\n readonly interruptedCallsUnavailable?: true\n }\n}\n\n/** What an abandoned execution's ledgered nested call is known to have done: `applied` (recorded\n * a result), `failed` (recorded a `ToolError` or an `isError` result), or `unknown` (started, no\n * recorded outcome, so it may have been applied).\n */\nexport type CodeModeInterruptedCallStatus = 'applied' | 'failed' | 'unknown'\n\n/** One ledgered nested call of an abandoned execution. `key` is its ledger key\n * (`<toolCallId>/<seq>`); `args` is compact JSON, cut with a trailing `…` (then no longer JSON).\n */\nexport type CodeModeInterruptedCall = {\n readonly key: string\n readonly toolName: string\n readonly args: string\n readonly status: CodeModeInterruptedCallStatus\n}\n\n/**\n * `structuredContent.codemode.interruptedCalls`: plain JSON, in sequence order, bounded like\n * `nestedCalls` (at most `limits.maxNestedCalls` calls; arguments within 8 KiB per call and\n * 32 KiB in total). `complete: false` means calls were dropped or arguments cut, here or already\n * in the ledger entry (`ToolLedgerEntry.argsTruncated`); `counts` covers every ledgered nested\n * call, dropped ones included. Calls the ledger policy skips (by default read-only calls) never\n * appear.\n */\nexport type CodeModeInterruptedCalls = {\n readonly calls: ReadonlyArray<CodeModeInterruptedCall>\n readonly complete: boolean\n readonly counts: {\n readonly applied: number\n readonly failed: number\n readonly unknown: number\n }\n}\n\nconst StoreWritesSchema = Schema.Struct({\n set: Schema.Record(Schema.String, Schema.Json),\n delete: Schema.Array(Schema.String)\n})\n\nconst CodeModeStructuredContentSchema = Schema.Struct({\n codemode: Schema.Struct({\n ok: Schema.Boolean,\n storeWrites: Schema.optionalKey(StoreWritesSchema)\n })\n})\n\nconst decodeStructuredContent = Schema.decodeUnknownOption(CodeModeStructuredContentSchema)\n\n/** Largest stored value: characters of JSON (256 KiB), as the pi engine counts them. */\nexport const maxCodeModeStoreValueChars = 256 * 1024\n\n/** Largest store: characters of keys plus JSON values (1 MiB), as the pi engine counts them. */\nexport const maxCodeModeStoreTotalChars = 1024 * 1024\n\n/** One prior tool result with the name of the tool that produced it. */\nexport type CodeModeToolResultEntry = {\n readonly toolName: string\n readonly result: { readonly structuredContent?: unknown }\n}\n\n/**\n * Rebuilds the code mode store from prior tool results, oldest first: applies the `storeWrites` of\n * each successful script of the code mode tool named `toolName` (default `codemode`) in order.\n * Results of other tools (even when their `structuredContent` looks like a code mode result),\n * failed scripts, and malformed contents are skipped. Transcript `ToolResultMessage`s carry no tool\n * name; pair each with the name of its assistant tool call.\n *\n * The rebuilt store keeps the engine bounds: a write whose JSON value exceeds 256 Ki characters, or\n * that would grow the store beyond 1 Mi characters (keys plus JSON values), is dropped and the key\n * keeps its previous value. Deletions always apply. Pass the value from `loadStore`.\n */\nexport const codeModeStoreFromToolResults = (\n entries: ReadonlyArray<CodeModeToolResultEntry>,\n options: { readonly toolName?: string } = {}\n): CodeModeStore => {\n const toolName = options.toolName ?? codeModeToolName\n const store = new Map<string, { readonly value: Schema.Json; readonly chars: number }>()\n let total = 0\n\n for (const entry of entries) {\n if (entry.toolName !== toolName) continue\n\n const content = decodeStructuredContent(entry.result.structuredContent)\n\n if (Option.isNone(content) || !content.value.codemode.ok) continue\n\n const writes = content.value.codemode.storeWrites\n\n if (writes === undefined) continue\n\n for (const key of writes.delete) {\n total -= store.get(key)?.chars ?? 0\n store.delete(key)\n }\n\n for (const [key, value] of Object.entries(writes.set)) {\n const json = JSON.stringify(value)\n\n if (json.length > maxCodeModeStoreValueChars) continue\n\n const chars = key.length + json.length\n const next = total - (store.get(key)?.chars ?? 0) + chars\n\n if (next > maxCodeModeStoreTotalChars) continue\n\n store.set(key, { value, chars })\n total = next\n }\n }\n\n return Object.fromEntries([...store].map(([key, stored]) => [key, stored.value]))\n}\n"],"mappings":";;;;AAyDA,MAAM,oBAAoB,OAAO,OAAO;CACtC,KAAK,OAAO,OAAO,OAAO,QAAQ,OAAO,IAAI;CAC7C,QAAQ,OAAO,MAAM,OAAO,MAAM;AACpC,CAAC;AAED,MAAM,kCAAkC,OAAO,OAAO,EACpD,UAAU,OAAO,OAAO;CACtB,IAAI,OAAO;CACX,aAAa,OAAO,YAAY,iBAAiB;AACnD,CAAC,EACH,CAAC;AAED,MAAM,0BAA0B,OAAO,oBAAoB,+BAA+B;;AAG1F,MAAa,6BAA6B,MAAM;;AAGhD,MAAa,6BAA6B,OAAO;;;;;;;;;;;;AAmBjD,MAAa,gCACX,SACA,UAA0C,CAAC,MACzB;CAClB,MAAM,WAAW,QAAQ,YAAA;CACzB,MAAM,wBAAQ,IAAI,IAAqE;CACvF,IAAI,QAAQ;CAEZ,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,MAAM,aAAa,UAAU;EAEjC,MAAM,UAAU,wBAAwB,MAAM,OAAO,iBAAiB;EAEtE,IAAI,OAAO,OAAO,OAAO,KAAK,CAAC,QAAQ,MAAM,SAAS,IAAI;EAE1D,MAAM,SAAS,QAAQ,MAAM,SAAS;EAEtC,IAAI,WAAW,KAAA,GAAW;EAE1B,KAAK,MAAM,OAAO,OAAO,QAAQ;GAC/B,SAAS,MAAM,IAAI,GAAG,GAAG,SAAS;GAClC,MAAM,OAAO,GAAG;EAClB;EAEA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG,GAAG;GACrD,MAAM,OAAO,KAAK,UAAU,KAAK;GAEjC,IAAI,KAAK,SAAA,QAAqC;GAE9C,MAAM,QAAQ,IAAI,SAAS,KAAK;GAChC,MAAM,OAAO,SAAS,MAAM,IAAI,GAAG,GAAG,SAAS,KAAK;GAEpD,IAAI,OAAA,SAAmC;GAEvC,MAAM,IAAI,KAAK;IAAE;IAAO;GAAM,CAAC;GAC/B,QAAQ;EACV;CACF;CAEA,OAAO,OAAO,YAAY,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,KAAK,YAAY,CAAC,KAAK,OAAO,KAAK,CAAC,CAAC;AAClF"}
|
package/dist/tool.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CodeModeExecutor, CodeModeStore } from "./executor.mjs";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
-
import { ToolCall } from "@yolk-sdk/agent/protocol";
|
|
3
|
+
import { ToolCall, ToolResult } from "@yolk-sdk/agent/protocol";
|
|
4
4
|
import { ToolRegistration } from "@yolk-sdk/agent/tools";
|
|
5
5
|
import { ToolError } from "@yolk-sdk/agent/loop";
|
|
6
6
|
|
|
@@ -10,7 +10,11 @@ declare const codeModeToolName = "codemode";
|
|
|
10
10
|
/** Host-owned limits of one script. */
|
|
11
11
|
type CodeModeLimits = {
|
|
12
12
|
/** Overall deadline including nested calls. Default 120000 (clamped by `deadline`). */readonly timeoutMs?: number; /** VM heap cap. Default 64 MiB. */
|
|
13
|
-
readonly memoryLimitBytes?: number;
|
|
13
|
+
readonly memoryLimitBytes?: number;
|
|
14
|
+
/** Nested tool calls per script; further calls reject with an Error. Default 256. The
|
|
15
|
+
* `nestedCalls` record keeps up to this many calls, at most `nestedToolCallMaxRecordedCalls`
|
|
16
|
+
* (4096) whatever the limit (argument byte budgets still apply).
|
|
17
|
+
*/
|
|
14
18
|
readonly maxNestedCalls?: number; /** Model-visible result characters, cut head and tail with an omission marker. Default 40000. */
|
|
15
19
|
readonly maxOutputChars?: number; /** Images kept in the result, in order; later images are dropped with a note. Default 8. */
|
|
16
20
|
readonly maxImages?: number;
|
|
@@ -57,6 +61,25 @@ type MakeCodeModeToolOptions<Context> = {
|
|
|
57
61
|
readonly call: ToolCall;
|
|
58
62
|
readonly context: Context;
|
|
59
63
|
}) => Effect.Effect<void, string>;
|
|
64
|
+
/**
|
|
65
|
+
* Runs after each nested call that `beforeNestedCall` admitted, with its outcome: `success` (a
|
|
66
|
+
* result without `isError`), `failure` (an error result or an unexpected failure), or
|
|
67
|
+
* `interrupted` (cancelled, for example still running when the script ended). `durationMs`
|
|
68
|
+
* covers the execution; `result` is present when the call produced one. Calls rejected by
|
|
69
|
+
* `beforeNestedCall` or the call limit never ran and are not reported. The script waits for the
|
|
70
|
+
* hook before it sees the call settle. A failing hook (a failure, a defect, or a synchronous
|
|
71
|
+
* throw) is logged and never changes the call's result.
|
|
72
|
+
*/
|
|
73
|
+
readonly afterNestedCall?: (input: CodeModeAfterNestedCallInput<Context>) => Effect.Effect<void>;
|
|
74
|
+
};
|
|
75
|
+
/** Outcome of a nested call for `afterNestedCall`. */
|
|
76
|
+
type CodeModeNestedCallOutcome = 'success' | 'failure' | 'interrupted';
|
|
77
|
+
type CodeModeAfterNestedCallInput<Context> = {
|
|
78
|
+
readonly call: ToolCall;
|
|
79
|
+
readonly outcome: CodeModeNestedCallOutcome;
|
|
80
|
+
readonly durationMs: number;
|
|
81
|
+
readonly context: Context;
|
|
82
|
+
readonly result?: ToolResult;
|
|
60
83
|
};
|
|
61
84
|
/**
|
|
62
85
|
* The code mode tool: one registration (default name `codemode`, input `{ code }`) whose scripts
|
|
@@ -68,8 +91,13 @@ type MakeCodeModeToolOptions<Context> = {
|
|
|
68
91
|
*
|
|
69
92
|
* Access is `write`: scripts can call any write tool the resolution exposes to code mode; each
|
|
70
93
|
* nested call keeps its own access metadata and host wrappers.
|
|
94
|
+
*
|
|
95
|
+
* With a tool ledger (`resolveTools(..., { ledger })`) the code mode call itself is ledgered, so a
|
|
96
|
+
* re-executed call never runs its script again: a completed call returns its stored result, an
|
|
97
|
+
* in-flight one is awaited, and an abandoned one returns an interrupted result listing its
|
|
98
|
+
* ledgered nested calls. Nested write calls are ledgered under `<toolCallId>/<seq>`.
|
|
71
99
|
*/
|
|
72
100
|
declare const makeCodeModeTool: <Context>(options: MakeCodeModeToolOptions<Context>) => ToolRegistration<Context>;
|
|
73
101
|
//#endregion
|
|
74
|
-
export { CodeModeLimits, MakeCodeModeToolOptions, codeModeDeadlineMarginMs, codeModeMinimumTimeoutMs, codeModeToolName, defaultCodeModeLimits, makeCodeModeTool };
|
|
102
|
+
export { CodeModeAfterNestedCallInput, CodeModeLimits, CodeModeNestedCallOutcome, MakeCodeModeToolOptions, codeModeDeadlineMarginMs, codeModeMinimumTimeoutMs, codeModeToolName, defaultCodeModeLimits, makeCodeModeTool };
|
|
75
103
|
//# sourceMappingURL=tool.d.mts.map
|
package/dist/tool.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.d.mts","names":[],"sources":["../src/tool.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"tool.d.mts","names":[],"sources":["../src/tool.ts"],"mappings":";;;;;;;;cAsEa,gBAAA;AAAb;AAAA,KAGY,cAAA;kGAED,SAAA,WALkB;EAAA,SAOlB,gBAAA;EAJe;;;;EAAA,SASf,cAAA;EAAA,SAEA,cAAA,WAEA;EAAA,SAAA,SAAA;EAIa;AAAA;AAGxB;EAHwB,SAAb,aAAA;AAAA;AAAA,cAGE,qBAAA;EAAA;;;;;;;;cAUA,wBAAA;;cAGA,wBAAA;AAAA,KAED,uBAAA;EAAA,SACD,QAAA,EAAU,gBAAA,EAHR;EAAA,SAKF,IAAA;WAEA,YAAA;EAAA,SACA,MAAA,GAAS,cAAA;EANR;;;;EAAA,SAWD,QAAA,IAAY,OAAA,EAAS,OAAA;EAAA;;;;EAAA,SAKrB,SAAA,IAAa,OAAA,EAAS,OAAA,KAAY,MAAA,CAAO,MAAA,CAAO,aAAA,EAAe,SAAA;EASvD;;;;;;;EAAA,SADR,gBAAA,IAAoB,KAAA;IAAA,SAClB,IAAA,EAAM,QAAA;IAAA,SACN,OAAA,EAAS,OAAA;EAAA,MACd,MAAA,CAAO,MAAA;EAxBJ;;;;;;;;;EAAA,SAkCA,eAAA,IAAmB,KAAA,EAAO,4BAAA,CAA6B,OAAA,MAAa,MAAA,CAAO,MAAA;AAAA;;KAI1E,yBAAA;AAAA,KAEA,4BAAA;EAAA,SACD,IAAA,EAAM,QAAA;EAAA,SACN,OAAA,EAAS,yBAAA;EAAA,SACT,UAAA;EAAA,SACA,OAAA,EAAS,OAAA;EAAA,SACT,MAAA,GAAS,UAAA;AAAA;;;;;;;;;;AAXwE;AAI5F;;;;AAAqC;AAErC;cAskBa,gBAAA,YACX,OAAA,EAAS,uBAAA,CAAwB,OAAA,MAChC,gBAAA,CAAiB,OAAA"}
|
package/dist/tool.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { codeModeCatalog, describeCodeModeTool, findCodeModeTool, renderCodeModeDescription } from "./catalog.mjs";
|
|
2
2
|
import { searchCodeModeTools } from "./search.mjs";
|
|
3
|
-
import { codeModeResultSegments, codeModeSegmentsContent, defaultCodeModeMaxImageBytes } from "./output.mjs";
|
|
3
|
+
import { boundCodeModeSegments, codeModeResultSegments, codeModeSegmentsContent, defaultCodeModeMaxImageBytes } from "./output.mjs";
|
|
4
4
|
import { Cause, Clock, Data, Duration, Effect, Exit, Fiber, FiberSet, Option, Predicate, Scope } from "effect";
|
|
5
|
-
import { ToolCall, ToolResult, contentPartText,
|
|
5
|
+
import { ToolCall, ToolResult, boundNestedToolCallArgs, contentPartText, makeNestedToolCallRecorder, nestedToolCallMaxErrorChars, nestedToolCallRecordLimit, nestedToolCallResultFields, recordNestedToolCall, truncateCodePoints } from "@yolk-sdk/agent/protocol";
|
|
6
6
|
import * as Schema from "effect/Schema";
|
|
7
7
|
import { makeTool } from "@yolk-sdk/agent/tools";
|
|
8
8
|
import { ToolError } from "@yolk-sdk/agent/loop";
|
|
@@ -65,6 +65,7 @@ const recordedOutcome = (exit, durationMs) => {
|
|
|
65
65
|
if (result.usage !== void 0) outcome.usage = result.usage;
|
|
66
66
|
return outcome;
|
|
67
67
|
};
|
|
68
|
+
const hookOutcome = (exit) => Exit.isSuccess(exit) ? exit.value.isError === true ? "failure" : "success" : Cause.hasInterruptsOnly(exit.cause) ? "interrupted" : "failure";
|
|
68
69
|
const SearchOptions = Schema.Struct({
|
|
69
70
|
limit: Schema.optionalKey(Schema.Finite),
|
|
70
71
|
namespace: Schema.optionalKey(Schema.String)
|
|
@@ -137,7 +138,24 @@ const runScript = (input) => Effect.gen(function* () {
|
|
|
137
138
|
name: tool.name,
|
|
138
139
|
params
|
|
139
140
|
});
|
|
140
|
-
const
|
|
141
|
+
const afterNestedCall = options.afterNestedCall;
|
|
142
|
+
const run = afterNestedCall === void 0 ? nested.execute(nestedCall) : Effect.gen(function* () {
|
|
143
|
+
const started = yield* Clock.currentTimeMillis;
|
|
144
|
+
const afterNestedCallSafely = (hookInput) => Effect.suspend(() => afterNestedCall(hookInput)).pipe(Effect.exit, Effect.flatMap((hookExit) => Exit.isSuccess(hookExit) ? Effect.void : Effect.logWarning(`Code mode afterNestedCall failed for ${nestedCall.id}; ignored: ${Cause.pretty(hookExit.cause)}`)));
|
|
145
|
+
return yield* nested.execute(nestedCall).pipe(Effect.onExit((exit) => Effect.flatMap(Clock.currentTimeMillis, (finished) => afterNestedCallSafely(Exit.isSuccess(exit) ? {
|
|
146
|
+
call: nestedCall,
|
|
147
|
+
outcome: hookOutcome(exit),
|
|
148
|
+
durationMs: finished - started,
|
|
149
|
+
context: input.context,
|
|
150
|
+
result: exit.value
|
|
151
|
+
} : {
|
|
152
|
+
call: nestedCall,
|
|
153
|
+
outcome: hookOutcome(exit),
|
|
154
|
+
durationMs: finished - started,
|
|
155
|
+
context: input.context
|
|
156
|
+
}))));
|
|
157
|
+
});
|
|
158
|
+
const admitted = options.beforeNestedCall === void 0 ? run : options.beforeNestedCall({
|
|
141
159
|
call: nestedCall,
|
|
142
160
|
context: input.context
|
|
143
161
|
}).pipe(Effect.matchEffect({
|
|
@@ -146,7 +164,7 @@ const runScript = (input) => Effect.gen(function* () {
|
|
|
146
164
|
content: message,
|
|
147
165
|
isError: true
|
|
148
166
|
})),
|
|
149
|
-
onSuccess: () =>
|
|
167
|
+
onSuccess: () => run
|
|
150
168
|
}));
|
|
151
169
|
const execution = Effect.gen(function* () {
|
|
152
170
|
const started = yield* Clock.currentTimeMillis;
|
|
@@ -207,7 +225,7 @@ const runScript = (input) => Effect.gen(function* () {
|
|
|
207
225
|
args: record.args,
|
|
208
226
|
...record.outcome ?? { status: "cancelled" }
|
|
209
227
|
}));
|
|
210
|
-
const { nestedCalls, usage } = nestedToolCallResultFields(recorded.reduce(recordNestedToolCall,
|
|
228
|
+
const { nestedCalls, usage } = nestedToolCallResultFields(recorded.reduce(recordNestedToolCall, makeNestedToolCallRecorder({ maxCalls: limits.maxNestedCalls })));
|
|
211
229
|
const content = codeModeSegmentsContent(codeModeResultSegments({
|
|
212
230
|
result,
|
|
213
231
|
wallTimeMs: finishedAt - startedAt,
|
|
@@ -231,6 +249,89 @@ const runScript = (input) => Effect.gen(function* () {
|
|
|
231
249
|
return ToolResult.make(fields);
|
|
232
250
|
}), (scope, exit) => boundedWait(Scope.close(scope, exit)));
|
|
233
251
|
});
|
|
252
|
+
const argsPreviewChars = 200;
|
|
253
|
+
const interruptedCallStatus = (entry) => {
|
|
254
|
+
const outcome = entry.outcome;
|
|
255
|
+
if (outcome === void 0) return "unknown";
|
|
256
|
+
if (Predicate.isTagged(outcome, "Failed")) return "failed";
|
|
257
|
+
return outcome.result.isError === true ? "failed" : "applied";
|
|
258
|
+
};
|
|
259
|
+
const nestedEntryState = (entry) => {
|
|
260
|
+
const outcome = entry.outcome;
|
|
261
|
+
if (outcome === void 0) return "unknown: it started but never recorded a result, so it may have been applied";
|
|
262
|
+
if (Predicate.isTagged(outcome, "Failed")) return `completed with an error: ${truncateCodePoints(outcome.error.message, nestedToolCallMaxErrorChars)}`;
|
|
263
|
+
return outcome.result.isError === true ? "completed with an error result" : "applied";
|
|
264
|
+
};
|
|
265
|
+
/** The bounded, wire-safe `interruptedCalls` of an abandoned execution's nested ledger entries:
|
|
266
|
+
* the `nestedCalls` record bounds (`maxCalls`, 8 KiB of arguments per call, 32 KiB in total).
|
|
267
|
+
*/
|
|
268
|
+
const interruptedCalls = (nested, maxCalls) => {
|
|
269
|
+
const counts = {
|
|
270
|
+
applied: 0,
|
|
271
|
+
failed: 0,
|
|
272
|
+
unknown: 0
|
|
273
|
+
};
|
|
274
|
+
const calls = [];
|
|
275
|
+
let argsBytes = 0;
|
|
276
|
+
let complete = true;
|
|
277
|
+
for (const entry of nested) {
|
|
278
|
+
const status = interruptedCallStatus(entry);
|
|
279
|
+
counts[status]++;
|
|
280
|
+
if (calls.length >= maxCalls) {
|
|
281
|
+
complete = false;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const { args, bytes, truncated } = boundNestedToolCallArgs(entry.args, argsBytes);
|
|
285
|
+
if (truncated || entry.argsTruncated) complete = false;
|
|
286
|
+
argsBytes += bytes;
|
|
287
|
+
calls.push({
|
|
288
|
+
key: entry.key,
|
|
289
|
+
toolName: entry.toolName,
|
|
290
|
+
args,
|
|
291
|
+
status
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
calls,
|
|
296
|
+
complete,
|
|
297
|
+
counts
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* The result of a code mode call whose earlier execution was abandoned (claimed, lease expired,
|
|
302
|
+
* no result recorded). The script never runs again: the result lists that execution's ledgered
|
|
303
|
+
* nested calls as applied, failed, or unknown, and says they were not undone. The text is bounded
|
|
304
|
+
* by `maxOutputChars`; `structuredContent.codemode.interruptedCalls` carries the same entries for
|
|
305
|
+
* hosts, bounded like `nestedCalls`. When the ledger cannot list them, the result stays
|
|
306
|
+
* interrupted, says they may have been applied, and sets `interruptedCallsUnavailable` instead.
|
|
307
|
+
*/
|
|
308
|
+
const abandonedScriptResult = (limits) => ({ call, nested }) => {
|
|
309
|
+
const maxChars = limits.maxOutputChars;
|
|
310
|
+
const listing = nested === void 0 ? "Its ledgered nested tool calls could not be listed (the tool ledger is unavailable), so any of them may already have been applied." : nested.length === 0 ? "No ledgered nested tool calls were recorded for that execution." : [`Ledgered nested tool calls of that execution (they were not undone):`, ...nested.map((entry) => `- ${entry.key} ${entry.toolName} ${truncateCodePoints(entry.args, argsPreviewChars)}: ${nestedEntryState(entry)}`)].join("\n");
|
|
311
|
+
const text = [
|
|
312
|
+
`Script interrupted: an earlier execution of this ${call.name} call (${call.id}) started but never recorded a result. The script was not run again.`,
|
|
313
|
+
listing,
|
|
314
|
+
"Calls the ledger policy skips (by default read-only calls) are not listed. Verify the state these calls affect before repeating any work."
|
|
315
|
+
].join("\n\n");
|
|
316
|
+
const structuredContent = { codemode: nested === void 0 ? {
|
|
317
|
+
ok: false,
|
|
318
|
+
interrupted: true,
|
|
319
|
+
interruptedCallsUnavailable: true
|
|
320
|
+
} : {
|
|
321
|
+
ok: false,
|
|
322
|
+
interrupted: true,
|
|
323
|
+
interruptedCalls: interruptedCalls(nested, nestedToolCallRecordLimit(limits.maxNestedCalls))
|
|
324
|
+
} };
|
|
325
|
+
return ToolResult.make({
|
|
326
|
+
toolCallId: call.id,
|
|
327
|
+
content: codeModeSegmentsContent(boundCodeModeSegments([{
|
|
328
|
+
type: "text",
|
|
329
|
+
text
|
|
330
|
+
}], maxChars)),
|
|
331
|
+
isError: true,
|
|
332
|
+
structuredContent
|
|
333
|
+
});
|
|
334
|
+
};
|
|
234
335
|
/**
|
|
235
336
|
* The code mode tool: one registration (default name `codemode`, input `{ code }`) whose scripts
|
|
236
337
|
* call the other code-mode-callable tools of the same `resolveTools` resolution. Nested calls run
|
|
@@ -241,6 +342,11 @@ const runScript = (input) => Effect.gen(function* () {
|
|
|
241
342
|
*
|
|
242
343
|
* Access is `write`: scripts can call any write tool the resolution exposes to code mode; each
|
|
243
344
|
* nested call keeps its own access metadata and host wrappers.
|
|
345
|
+
*
|
|
346
|
+
* With a tool ledger (`resolveTools(..., { ledger })`) the code mode call itself is ledgered, so a
|
|
347
|
+
* re-executed call never runs its script again: a completed call returns its stored result, an
|
|
348
|
+
* in-flight one is awaited, and an abandoned one returns an interrupted result listing its
|
|
349
|
+
* ledgered nested calls. Nested write calls are ledgered under `<toolCallId>/<seq>`.
|
|
244
350
|
*/
|
|
245
351
|
const makeCodeModeTool = (options) => {
|
|
246
352
|
const name = options.name ?? "codemode";
|
|
@@ -265,6 +371,7 @@ const makeCodeModeTool = (options) => {
|
|
|
265
371
|
inlineBudget,
|
|
266
372
|
store
|
|
267
373
|
}),
|
|
374
|
+
abandonedResult: abandonedScriptResult(limits),
|
|
268
375
|
execute: ({ call, context, params, nested }) => nested === void 0 ? Effect.fail(new ToolError({
|
|
269
376
|
tool: name,
|
|
270
377
|
cause: "unavailable",
|
package/dist/tool.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool.mjs","names":[],"sources":["../src/tool.ts"],"sourcesContent":["import {\n Cause,\n Clock,\n Data,\n Duration,\n Effect,\n Exit,\n Fiber,\n FiberSet,\n Option,\n Predicate,\n Scope\n} from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { ToolError } from '@yolk-sdk/agent/loop'\nimport {\n emptyNestedToolCallRecorder,\n nestedToolCallResultFields,\n recordNestedToolCall,\n contentPartText,\n ToolCall,\n ToolResult,\n type AgentUsage,\n type Content,\n type NestedToolCallInput,\n type NestedToolCallStatus\n} from '@yolk-sdk/agent/protocol'\nimport {\n makeTool,\n type NestedToolExecutor,\n type ToolExecutionInput,\n type ToolRegistration\n} from '@yolk-sdk/agent/tools'\nimport {\n codeModeCatalog,\n defaultCodeModeInlineBudget,\n describeCodeModeTool,\n findCodeModeTool,\n renderCodeModeDescription,\n type CodeModeCatalogTool\n} from './catalog.ts'\nimport type {\n CodeModeExecutionResult,\n CodeModeExecutor,\n CodeModeExecutorGlobal,\n CodeModeExecutorTool,\n CodeModeStore\n} from './executor.ts'\nimport {\n codeModeResultSegments,\n codeModeSegmentsContent,\n defaultCodeModeMaxImageBytes,\n defaultCodeModeMaxImages\n} from './output.ts'\nimport { searchCodeModeTools } from './search.ts'\nimport type { CodeModeStructuredContent } from './store.ts'\n\n/** Default name of the code mode tool. */\nexport const codeModeToolName = 'codemode'\n\n/** Host-owned limits of one script. */\nexport type CodeModeLimits = {\n /** Overall deadline including nested calls. Default 120000 (clamped by `deadline`). */\n readonly timeoutMs?: number\n /** VM heap cap. Default 64 MiB. */\n readonly memoryLimitBytes?: number\n /** Nested tool calls per script; further calls reject with an Error. Default 256. */\n readonly maxNestedCalls?: number\n /** Model-visible result characters, cut head and tail with an omission marker. Default 40000. */\n readonly maxOutputChars?: number\n /** Images kept in the result, in order; later images are dropped with a note. Default 8. */\n readonly maxImages?: number\n /** Base64 characters of images kept in the result in total; an image that would exceed it is\n * dropped with a note. Default 4 MiB.\n */\n readonly maxImageBytes?: number\n}\n\nexport const defaultCodeModeLimits = {\n timeoutMs: 120_000,\n memoryLimitBytes: 64 * 1024 * 1024,\n maxNestedCalls: 256,\n maxOutputChars: 40_000,\n maxImages: defaultCodeModeMaxImages,\n maxImageBytes: defaultCodeModeMaxImageBytes\n} as const satisfies Required<CodeModeLimits>\n\n/** Kept between a host deadline and the script deadline, for result handling. */\nexport const codeModeDeadlineMarginMs = 5_000\n\n/** A deadline never clamps a script below this timeout. */\nexport const codeModeMinimumTimeoutMs = 1_000\n\nexport type MakeCodeModeToolOptions<Context> = {\n readonly executor: CodeModeExecutor\n /** Default `codemode`. */\n readonly name?: string\n /** Estimated tokens (four characters each) for listing nested tools. Default 3000. */\n readonly inlineBudget?: number\n readonly limits?: CodeModeLimits\n /**\n * Epoch milliseconds by which the tool call must end, for example the remaining function budget\n * of a Workflow step. The script timeout is clamped to it minus 5 s, but never below 1 s.\n */\n readonly deadline?: (context: Context) => number | undefined\n /**\n * The store scripts read with `load(key)`, usually rebuilt from the transcript with\n * `codeModeStoreFromToolResults`. Without it every script starts with an empty store.\n */\n readonly loadStore?: (context: Context) => Effect.Effect<CodeModeStore, ToolError>\n /**\n * Runs before each nested call executes, with the nested call (id `<toolCallId>/<seq>`) and the\n * host context. A failure rejects that call in the script with the message and records it as an\n * `error` without executing it. Host decorators around the `ToolExecutor` (outside\n * `ResolvedToolSet.execute`) never see nested calls: put per-call run-authority checks here or in\n * registration-level wrappers.\n */\n readonly beforeNestedCall?: (input: {\n readonly call: ToolCall\n readonly context: Context\n }) => Effect.Effect<void, string>\n}\n\nconst CodeModeParams = Schema.Struct({\n code: Schema.String.annotate({\n description:\n 'Raw JavaScript: the body of an async function. Top-level await and return work. Not a function declaration and not a module.'\n })\n})\n\ntype CodeModeParams = typeof CodeModeParams.Type\n\nclass CodeModeExecutorRejected extends Data.TaggedError('CodeModeExecutorRejected')<{\n readonly message: string\n}> {}\n\nconst errorMessage = (error: unknown) =>\n error instanceof Error ? error.message : Predicate.isString(error) ? error : 'unknown error'\n\nconst nestedText = (content: Content) =>\n Predicate.isString(content)\n ? content\n : content\n .map(contentPartText)\n .filter(text => text.length > 0)\n .join('\\n')\n\ntype NestedResolution =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly message: string }\n\n/** What a nested call resolves to: `structuredContent` for tools with an output schema (when\n * present), text content otherwise; error results reject with their text. A tool that declares an\n * output schema but returns no `structuredContent` resolves to its text: that is a tool bug, kept\n * readable rather than turned into a failure.\n */\nconst resolveNestedResult = (tool: CodeModeCatalogTool, result: ToolResult): NestedResolution => {\n const text = nestedText(result.content)\n\n if (result.isError === true) {\n return { ok: false, message: text.length > 0 ? text : `Tool ${tool.name} failed.` }\n }\n\n return tool.structured && result.structuredContent !== undefined\n ? { ok: true, value: result.structuredContent }\n : { ok: true, value: text }\n}\n\ntype CallOutcome = {\n status: NestedToolCallStatus\n durationMs: number\n error?: string\n usage?: AgentUsage\n}\n\ntype CallRecord = {\n readonly name: string\n readonly args: unknown\n readonly id: string\n outcome?: CallOutcome\n}\n\nconst recordedOutcome = (exit: Exit.Exit<ToolResult>, durationMs: number): CallOutcome => {\n if (Exit.isFailure(exit)) {\n return Cause.hasInterruptsOnly(exit.cause)\n ? { status: 'cancelled', durationMs }\n : { status: 'error', durationMs, error: 'The tool call failed unexpectedly.' }\n }\n\n const result = exit.value\n\n const outcome: CallOutcome =\n result.isError === true\n ? { status: 'error', durationMs, error: nestedText(result.content) }\n : { status: 'ok', durationMs }\n\n if (result.usage !== undefined) {\n outcome.usage = result.usage\n }\n\n return outcome\n}\n\n/** What `describeNamespace(name)` resolves to. */\ntype CodeModeNamespaceDescription = {\n readonly name: string\n description?: string\n readonly tools: ReadonlyArray<{ readonly name: string; readonly description: string }>\n}\n\nconst SearchOptions = Schema.Struct({\n limit: Schema.optionalKey(Schema.Finite),\n namespace: Schema.optionalKey(Schema.String)\n})\n\nconst decodeSearchOptions = Schema.decodeUnknownOption(SearchOptions)\n\n/** `searchTools`, `describeTool`, and `describeNamespace` over every nested tool. */\nconst discoveryGlobals = (\n catalog: ReadonlyArray<CodeModeCatalogTool>\n): ReadonlyArray<CodeModeExecutorGlobal> => [\n {\n name: 'searchTools',\n spread: true,\n execute: args => {\n const [query, options] = Array.isArray(args) ? args : []\n\n return Promise.resolve(\n searchCodeModeTools(\n catalog,\n Predicate.isString(query) ? query : '',\n Option.getOrElse(decodeSearchOptions(options ?? {}), () => ({}))\n )\n )\n }\n },\n {\n name: 'describeTool',\n execute: name => {\n const tool = Predicate.isString(name) ? findCodeModeTool(catalog, name) : undefined\n\n return Promise.resolve(tool === undefined ? undefined : describeCodeModeTool(tool))\n }\n },\n {\n name: 'describeNamespace',\n execute: name => {\n const tools = catalog.filter(tool => tool.namespace === name)\n\n if (!Predicate.isString(name) || tools.length === 0) return Promise.resolve(undefined)\n\n const description = tools.find(\n tool => tool.namespaceDescription !== undefined\n )?.namespaceDescription\n\n const namespace: CodeModeNamespaceDescription = {\n name,\n tools: tools.map(tool => ({ name: tool.identifier, description: tool.description }))\n }\n\n if (description !== undefined) namespace.description = description\n\n return Promise.resolve(namespace)\n }\n }\n]\n\nconst hasStoreWrites = (result: CodeModeExecutionResult) =>\n result.storeWrites !== undefined &&\n (Object.keys(result.storeWrites.set).length > 0 || result.storeWrites.delete.length > 0)\n\nconst timeoutFor = (limit: number, deadline: number | undefined, now: number) =>\n deadline === undefined\n ? limit\n : Math.max(codeModeMinimumTimeoutMs, Math.min(limit, deadline - now - codeModeDeadlineMarginMs))\n\n/**\n * Waits at most `codeModeDeadlineMarginMs` for an effect (interrupting nested fibers) to finish; it\n * keeps running detached when a nested call does not respond to interruption in time.\n */\nconst boundedWait = (effect: Effect.Effect<void>) =>\n Effect.forkDetach(effect).pipe(\n Effect.flatMap(fiber =>\n Fiber.await(fiber).pipe(\n Effect.timeoutOption(Duration.millis(codeModeDeadlineMarginMs)),\n Effect.interruptible\n )\n ),\n Effect.asVoid\n )\n\ntype RunInput<Context> = {\n readonly options: MakeCodeModeToolOptions<Context>\n readonly limits: Required<CodeModeLimits>\n readonly call: ToolCall\n readonly context: Context\n readonly code: string\n readonly nested: NestedToolExecutor\n}\n\nconst runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult, ToolError> =>\n Effect.gen(function* () {\n const { call, limits, nested, options } = input\n const store = options.loadStore === undefined ? {} : yield* options.loadStore(input.context)\n const startedAt = yield* Clock.currentTimeMillis\n const timeoutMs = timeoutFor(limits.timeoutMs, options.deadline?.(input.context), startedAt)\n const catalog = codeModeCatalog(nested.tools)\n\n return yield* Effect.acquireUseRelease(\n Scope.make(),\n scope =>\n Effect.gen(function* () {\n // Nested calls run as fibers of this tool call (same services, interrupted with it).\n const fibers = yield* FiberSet.make<ToolResult>().pipe(Scope.provide(scope))\n const runFork = yield* FiberSet.runtime(fibers)<never>()\n const records: Array<CallRecord> = []\n\n const callTool =\n (tool: CodeModeCatalogTool): CodeModeExecutorTool['execute'] =>\n (args, { signal }) => {\n if (signal.aborted) {\n return Promise.reject(new Error(`${tool.callLabel} was cancelled.`))\n }\n\n if (records.length >= limits.maxNestedCalls) {\n return Promise.reject(\n new Error(\n `${tool.callLabel}: Nested call limit reached: a script may make at most ${limits.maxNestedCalls} tool calls. Batch the work or return partial results.`\n )\n )\n }\n\n const params = args === undefined ? {} : args\n\n const record: CallRecord = {\n id: `${call.id}/${records.length + 1}`,\n name: tool.name,\n args: params\n }\n\n records.push(record)\n\n const nestedCall = ToolCall.make({ id: record.id, name: tool.name, params })\n\n const admitted =\n options.beforeNestedCall === undefined\n ? nested.execute(nestedCall)\n : options.beforeNestedCall({ call: nestedCall, context: input.context }).pipe(\n Effect.matchEffect({\n onFailure: message =>\n Effect.succeed(\n ToolResult.make({\n toolCallId: nestedCall.id,\n content: message,\n isError: true\n })\n ),\n onSuccess: () => nested.execute(nestedCall)\n })\n )\n\n const execution = Effect.gen(function* () {\n const started = yield* Clock.currentTimeMillis\n\n return yield* admitted.pipe(\n Effect.onExit(exit =>\n Effect.map(Clock.currentTimeMillis, finished => {\n record.outcome = recordedOutcome(exit, finished - started)\n })\n )\n )\n })\n\n return new Promise((resolve, reject) => {\n runFork(execution, { signal }).addObserver(exit => {\n if (Exit.isFailure(exit)) {\n reject(\n new Error(\n Cause.hasInterruptsOnly(exit.cause)\n ? `${tool.callLabel} was cancelled.`\n : `${tool.callLabel} failed unexpectedly.`\n )\n )\n\n return\n }\n\n const resolution = resolveNestedResult(tool, exit.value)\n\n if (resolution.ok) {\n resolve(resolution.value)\n } else {\n // The script sees which call failed; the nested-call record keeps the raw text.\n reject(new Error(`${tool.callLabel}: ${resolution.message}`))\n }\n })\n })\n }\n\n const tools: ReadonlyArray<CodeModeExecutorTool> = catalog.map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n callLabel: tool.callLabel,\n execute: callTool(tool)\n }))\n\n const result = yield* Effect.tryPromise({\n try: signal =>\n options.executor.execute(input.code, {\n tools,\n globals: discoveryGlobals(catalog),\n timeoutMs,\n memoryLimitBytes: limits.memoryLimitBytes,\n store,\n signal\n }),\n catch: error => new CodeModeExecutorRejected({ message: errorMessage(error) })\n }).pipe(\n Effect.catch(error =>\n Effect.succeed<CodeModeExecutionResult>({\n ok: false,\n error: {\n kind: 'sandbox',\n message: `The code mode executor failed: ${error.message}`\n },\n output: []\n })\n ),\n // Backstop for executors that miss their own deadline: interrupting aborts the signal.\n Effect.timeoutOption(Duration.millis(timeoutMs + codeModeDeadlineMarginMs)),\n Effect.map(\n Option.getOrElse((): CodeModeExecutionResult => ({\n ok: false,\n error: {\n kind: 'timeout',\n message: `Execution timed out after ${timeoutMs} ms (the executor did not stop in time)`\n },\n output: []\n }))\n )\n )\n\n // Calls still running when the script ended are cancelled and recorded as such.\n yield* boundedWait(FiberSet.clear(fibers))\n\n const finishedAt = yield* Clock.currentTimeMillis\n\n const recorded = records.map((record): NestedToolCallInput => ({\n id: record.id,\n name: record.name,\n args: record.args,\n // A call interrupted before it started has no outcome.\n ...(record.outcome ?? { status: 'cancelled' })\n }))\n\n const recorder = recorded.reduce(recordNestedToolCall, emptyNestedToolCallRecorder)\n const { nestedCalls, usage } = nestedToolCallResultFields(recorder)\n\n const content = codeModeSegmentsContent(\n codeModeResultSegments({\n result,\n wallTimeMs: finishedAt - startedAt,\n calls: recorded,\n maxChars: limits.maxOutputChars,\n maxImages: limits.maxImages,\n maxImageBytes: limits.maxImageBytes\n })\n )\n\n const structuredContent: CodeModeStructuredContent = {\n codemode:\n result.ok && result.storeWrites !== undefined && hasStoreWrites(result)\n ? { ok: true, storeWrites: result.storeWrites }\n : { ok: result.ok }\n }\n\n type ResultFields = {\n toolCallId: string\n content: Content\n isError?: boolean\n structuredContent: CodeModeStructuredContent\n nestedCalls: typeof nestedCalls\n usage?: AgentUsage\n }\n\n const fields: ResultFields = {\n toolCallId: call.id,\n content,\n structuredContent,\n nestedCalls\n }\n\n if (!result.ok) {\n fields.isError = true\n }\n\n if (usage !== undefined) {\n fields.usage = usage\n }\n\n return ToolResult.make(fields)\n }),\n (scope, exit) => boundedWait(Scope.close(scope, exit))\n )\n })\n\n/**\n * The code mode tool: one registration (default name `codemode`, input `{ code }`) whose scripts\n * call the other code-mode-callable tools of the same `resolveTools` resolution. Nested calls run\n * through the resolved execute path with the same host context (input decoding, enablement,\n * registration wrappers), get ids `<toolCallId>/<seq>`, and are recorded on the result's\n * `nestedCalls`. The resolved description lists the callable tools (see\n * `renderCodeModeDescription`).\n *\n * Access is `write`: scripts can call any write tool the resolution exposes to code mode; each\n * nested call keeps its own access metadata and host wrappers.\n */\nexport const makeCodeModeTool = <Context>(\n options: MakeCodeModeToolOptions<Context>\n): ToolRegistration<Context> => {\n const name = options.name ?? codeModeToolName\n const limits: Required<CodeModeLimits> = { ...defaultCodeModeLimits, ...options.limits }\n const inlineBudget = options.inlineBudget ?? defaultCodeModeInlineBudget\n const store = options.loadStore !== undefined\n\n return makeTool<Context, typeof CodeModeParams>({\n name,\n description: renderCodeModeDescription({ tools: [], inlineBudget, store }),\n parameters: CodeModeParams,\n access: 'write',\n nestedToolAccess: true,\n describe: ({ tools }) => renderCodeModeDescription({ tools, inlineBudget, store }),\n execute: ({\n call,\n context,\n params,\n nested\n }: ToolExecutionInput<Context> & { readonly params: CodeModeParams }) =>\n nested === undefined\n ? Effect.fail(\n new ToolError({\n tool: name,\n cause: 'unavailable',\n message: `${name} requires nested tool access; resolve it with resolveTools.`\n })\n )\n : runScript({ options, limits, call, context, code: params.code, nested })\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool.mjs","names":[],"sources":["../src/tool.ts"],"sourcesContent":["import {\n Cause,\n Clock,\n Data,\n Duration,\n Effect,\n Exit,\n Fiber,\n FiberSet,\n Option,\n Predicate,\n Scope\n} from 'effect'\nimport * as Schema from 'effect/Schema'\nimport { ToolError } from '@yolk-sdk/agent/loop'\nimport {\n boundNestedToolCallArgs,\n makeNestedToolCallRecorder,\n nestedToolCallMaxErrorChars,\n nestedToolCallRecordLimit,\n nestedToolCallResultFields,\n recordNestedToolCall,\n truncateCodePoints,\n contentPartText,\n ToolCall,\n ToolResult,\n type AgentUsage,\n type Content,\n type NestedToolCallInput,\n type NestedToolCallStatus\n} from '@yolk-sdk/agent/protocol'\nimport {\n makeTool,\n type NestedToolExecutor,\n type ToolExecutionInput,\n type ToolLedgerAbandonedInput,\n type ToolLedgerEntry,\n type ToolRegistration\n} from '@yolk-sdk/agent/tools'\nimport {\n codeModeCatalog,\n defaultCodeModeInlineBudget,\n describeCodeModeTool,\n findCodeModeTool,\n renderCodeModeDescription,\n type CodeModeCatalogTool\n} from './catalog.ts'\nimport type {\n CodeModeExecutionResult,\n CodeModeExecutor,\n CodeModeExecutorGlobal,\n CodeModeExecutorTool,\n CodeModeStore\n} from './executor.ts'\nimport {\n boundCodeModeSegments,\n codeModeResultSegments,\n codeModeSegmentsContent,\n defaultCodeModeMaxImageBytes,\n defaultCodeModeMaxImages\n} from './output.ts'\nimport { searchCodeModeTools } from './search.ts'\nimport type {\n CodeModeInterruptedCall,\n CodeModeInterruptedCalls,\n CodeModeInterruptedCallStatus,\n CodeModeStructuredContent\n} from './store.ts'\n\n/** Default name of the code mode tool. */\nexport const codeModeToolName = 'codemode'\n\n/** Host-owned limits of one script. */\nexport type CodeModeLimits = {\n /** Overall deadline including nested calls. Default 120000 (clamped by `deadline`). */\n readonly timeoutMs?: number\n /** VM heap cap. Default 64 MiB. */\n readonly memoryLimitBytes?: number\n /** Nested tool calls per script; further calls reject with an Error. Default 256. The\n * `nestedCalls` record keeps up to this many calls, at most `nestedToolCallMaxRecordedCalls`\n * (4096) whatever the limit (argument byte budgets still apply).\n */\n readonly maxNestedCalls?: number\n /** Model-visible result characters, cut head and tail with an omission marker. Default 40000. */\n readonly maxOutputChars?: number\n /** Images kept in the result, in order; later images are dropped with a note. Default 8. */\n readonly maxImages?: number\n /** Base64 characters of images kept in the result in total; an image that would exceed it is\n * dropped with a note. Default 4 MiB.\n */\n readonly maxImageBytes?: number\n}\n\nexport const defaultCodeModeLimits = {\n timeoutMs: 120_000,\n memoryLimitBytes: 64 * 1024 * 1024,\n maxNestedCalls: 256,\n maxOutputChars: 40_000,\n maxImages: defaultCodeModeMaxImages,\n maxImageBytes: defaultCodeModeMaxImageBytes\n} as const satisfies Required<CodeModeLimits>\n\n/** Kept between a host deadline and the script deadline, for result handling. */\nexport const codeModeDeadlineMarginMs = 5_000\n\n/** A deadline never clamps a script below this timeout. */\nexport const codeModeMinimumTimeoutMs = 1_000\n\nexport type MakeCodeModeToolOptions<Context> = {\n readonly executor: CodeModeExecutor\n /** Default `codemode`. */\n readonly name?: string\n /** Estimated tokens (four characters each) for listing nested tools. Default 3000. */\n readonly inlineBudget?: number\n readonly limits?: CodeModeLimits\n /**\n * Epoch milliseconds by which the tool call must end, for example the remaining function budget\n * of a Workflow step. The script timeout is clamped to it minus 5 s, but never below 1 s.\n */\n readonly deadline?: (context: Context) => number | undefined\n /**\n * The store scripts read with `load(key)`, usually rebuilt from the transcript with\n * `codeModeStoreFromToolResults`. Without it every script starts with an empty store.\n */\n readonly loadStore?: (context: Context) => Effect.Effect<CodeModeStore, ToolError>\n /**\n * Runs before each nested call executes, with the nested call (id `<toolCallId>/<seq>`) and the\n * host context. A failure rejects that call in the script with the message and records it as an\n * `error` without executing it. Host decorators around the `ToolExecutor` (outside\n * `ResolvedToolSet.execute`) never see nested calls: put per-call run-authority checks here or in\n * registration-level wrappers.\n */\n readonly beforeNestedCall?: (input: {\n readonly call: ToolCall\n readonly context: Context\n }) => Effect.Effect<void, string>\n /**\n * Runs after each nested call that `beforeNestedCall` admitted, with its outcome: `success` (a\n * result without `isError`), `failure` (an error result or an unexpected failure), or\n * `interrupted` (cancelled, for example still running when the script ended). `durationMs`\n * covers the execution; `result` is present when the call produced one. Calls rejected by\n * `beforeNestedCall` or the call limit never ran and are not reported. The script waits for the\n * hook before it sees the call settle. A failing hook (a failure, a defect, or a synchronous\n * throw) is logged and never changes the call's result.\n */\n readonly afterNestedCall?: (input: CodeModeAfterNestedCallInput<Context>) => Effect.Effect<void>\n}\n\n/** Outcome of a nested call for `afterNestedCall`. */\nexport type CodeModeNestedCallOutcome = 'success' | 'failure' | 'interrupted'\n\nexport type CodeModeAfterNestedCallInput<Context> = {\n readonly call: ToolCall\n readonly outcome: CodeModeNestedCallOutcome\n readonly durationMs: number\n readonly context: Context\n readonly result?: ToolResult\n}\n\nconst CodeModeParams = Schema.Struct({\n code: Schema.String.annotate({\n description:\n 'Raw JavaScript: the body of an async function. Top-level await and return work. Not a function declaration and not a module.'\n })\n})\n\ntype CodeModeParams = typeof CodeModeParams.Type\n\nclass CodeModeExecutorRejected extends Data.TaggedError('CodeModeExecutorRejected')<{\n readonly message: string\n}> {}\n\nconst errorMessage = (error: unknown) =>\n error instanceof Error ? error.message : Predicate.isString(error) ? error : 'unknown error'\n\nconst nestedText = (content: Content) =>\n Predicate.isString(content)\n ? content\n : content\n .map(contentPartText)\n .filter(text => text.length > 0)\n .join('\\n')\n\ntype NestedResolution =\n | { readonly ok: true; readonly value: unknown }\n | { readonly ok: false; readonly message: string }\n\n/** What a nested call resolves to: `structuredContent` for tools with an output schema (when\n * present), text content otherwise; error results reject with their text. A tool that declares an\n * output schema but returns no `structuredContent` resolves to its text: that is a tool bug, kept\n * readable rather than turned into a failure.\n */\nconst resolveNestedResult = (tool: CodeModeCatalogTool, result: ToolResult): NestedResolution => {\n const text = nestedText(result.content)\n\n if (result.isError === true) {\n return { ok: false, message: text.length > 0 ? text : `Tool ${tool.name} failed.` }\n }\n\n return tool.structured && result.structuredContent !== undefined\n ? { ok: true, value: result.structuredContent }\n : { ok: true, value: text }\n}\n\ntype CallOutcome = {\n status: NestedToolCallStatus\n durationMs: number\n error?: string\n usage?: AgentUsage\n}\n\ntype CallRecord = {\n readonly name: string\n readonly args: unknown\n readonly id: string\n outcome?: CallOutcome\n}\n\nconst recordedOutcome = (exit: Exit.Exit<ToolResult>, durationMs: number): CallOutcome => {\n if (Exit.isFailure(exit)) {\n return Cause.hasInterruptsOnly(exit.cause)\n ? { status: 'cancelled', durationMs }\n : { status: 'error', durationMs, error: 'The tool call failed unexpectedly.' }\n }\n\n const result = exit.value\n\n const outcome: CallOutcome =\n result.isError === true\n ? { status: 'error', durationMs, error: nestedText(result.content) }\n : { status: 'ok', durationMs }\n\n if (result.usage !== undefined) {\n outcome.usage = result.usage\n }\n\n return outcome\n}\n\nconst hookOutcome = (exit: Exit.Exit<ToolResult>): CodeModeNestedCallOutcome =>\n Exit.isSuccess(exit)\n ? exit.value.isError === true\n ? 'failure'\n : 'success'\n : Cause.hasInterruptsOnly(exit.cause)\n ? 'interrupted'\n : 'failure'\n\n/** What `describeNamespace(name)` resolves to. */\ntype CodeModeNamespaceDescription = {\n readonly name: string\n description?: string\n readonly tools: ReadonlyArray<{ readonly name: string; readonly description: string }>\n}\n\nconst SearchOptions = Schema.Struct({\n limit: Schema.optionalKey(Schema.Finite),\n namespace: Schema.optionalKey(Schema.String)\n})\n\nconst decodeSearchOptions = Schema.decodeUnknownOption(SearchOptions)\n\n/** `searchTools`, `describeTool`, and `describeNamespace` over every nested tool. */\nconst discoveryGlobals = (\n catalog: ReadonlyArray<CodeModeCatalogTool>\n): ReadonlyArray<CodeModeExecutorGlobal> => [\n {\n name: 'searchTools',\n spread: true,\n execute: args => {\n const [query, options] = Array.isArray(args) ? args : []\n\n return Promise.resolve(\n searchCodeModeTools(\n catalog,\n Predicate.isString(query) ? query : '',\n Option.getOrElse(decodeSearchOptions(options ?? {}), () => ({}))\n )\n )\n }\n },\n {\n name: 'describeTool',\n execute: name => {\n const tool = Predicate.isString(name) ? findCodeModeTool(catalog, name) : undefined\n\n return Promise.resolve(tool === undefined ? undefined : describeCodeModeTool(tool))\n }\n },\n {\n name: 'describeNamespace',\n execute: name => {\n const tools = catalog.filter(tool => tool.namespace === name)\n\n if (!Predicate.isString(name) || tools.length === 0) return Promise.resolve(undefined)\n\n const description = tools.find(\n tool => tool.namespaceDescription !== undefined\n )?.namespaceDescription\n\n const namespace: CodeModeNamespaceDescription = {\n name,\n tools: tools.map(tool => ({ name: tool.identifier, description: tool.description }))\n }\n\n if (description !== undefined) namespace.description = description\n\n return Promise.resolve(namespace)\n }\n }\n]\n\nconst hasStoreWrites = (result: CodeModeExecutionResult) =>\n result.storeWrites !== undefined &&\n (Object.keys(result.storeWrites.set).length > 0 || result.storeWrites.delete.length > 0)\n\nconst timeoutFor = (limit: number, deadline: number | undefined, now: number) =>\n deadline === undefined\n ? limit\n : Math.max(codeModeMinimumTimeoutMs, Math.min(limit, deadline - now - codeModeDeadlineMarginMs))\n\n/**\n * Waits at most `codeModeDeadlineMarginMs` for an effect (interrupting nested fibers) to finish; it\n * keeps running detached when a nested call does not respond to interruption in time.\n */\nconst boundedWait = (effect: Effect.Effect<void>) =>\n Effect.forkDetach(effect).pipe(\n Effect.flatMap(fiber =>\n Fiber.await(fiber).pipe(\n Effect.timeoutOption(Duration.millis(codeModeDeadlineMarginMs)),\n Effect.interruptible\n )\n ),\n Effect.asVoid\n )\n\ntype RunInput<Context> = {\n readonly options: MakeCodeModeToolOptions<Context>\n readonly limits: Required<CodeModeLimits>\n readonly call: ToolCall\n readonly context: Context\n readonly code: string\n readonly nested: NestedToolExecutor\n}\n\nconst runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult, ToolError> =>\n Effect.gen(function* () {\n const { call, limits, nested, options } = input\n const store = options.loadStore === undefined ? {} : yield* options.loadStore(input.context)\n const startedAt = yield* Clock.currentTimeMillis\n const timeoutMs = timeoutFor(limits.timeoutMs, options.deadline?.(input.context), startedAt)\n const catalog = codeModeCatalog(nested.tools)\n\n return yield* Effect.acquireUseRelease(\n Scope.make(),\n scope =>\n Effect.gen(function* () {\n // Nested calls run as fibers of this tool call (same services, interrupted with it).\n const fibers = yield* FiberSet.make<ToolResult>().pipe(Scope.provide(scope))\n const runFork = yield* FiberSet.runtime(fibers)<never>()\n const records: Array<CallRecord> = []\n\n const callTool =\n (tool: CodeModeCatalogTool): CodeModeExecutorTool['execute'] =>\n (args, { signal }) => {\n if (signal.aborted) {\n return Promise.reject(new Error(`${tool.callLabel} was cancelled.`))\n }\n\n if (records.length >= limits.maxNestedCalls) {\n return Promise.reject(\n new Error(\n `${tool.callLabel}: Nested call limit reached: a script may make at most ${limits.maxNestedCalls} tool calls. Batch the work or return partial results.`\n )\n )\n }\n\n const params = args === undefined ? {} : args\n\n const record: CallRecord = {\n id: `${call.id}/${records.length + 1}`,\n name: tool.name,\n args: params\n }\n\n records.push(record)\n\n const nestedCall = ToolCall.make({ id: record.id, name: tool.name, params })\n const afterNestedCall = options.afterNestedCall\n\n const run =\n afterNestedCall === undefined\n ? nested.execute(nestedCall)\n : Effect.gen(function* () {\n const started = yield* Clock.currentTimeMillis\n\n // Host observability never changes the call: a hook failure, defects and\n // synchronous throws included, is only logged.\n const afterNestedCallSafely = (\n hookInput: CodeModeAfterNestedCallInput<Context>\n ) =>\n Effect.suspend(() => afterNestedCall(hookInput)).pipe(\n Effect.exit,\n Effect.flatMap(hookExit =>\n Exit.isSuccess(hookExit)\n ? Effect.void\n : Effect.logWarning(\n `Code mode afterNestedCall failed for ${nestedCall.id}; ignored: ${Cause.pretty(hookExit.cause)}`\n )\n )\n )\n\n return yield* nested.execute(nestedCall).pipe(\n Effect.onExit(exit =>\n Effect.flatMap(Clock.currentTimeMillis, finished =>\n afterNestedCallSafely(\n Exit.isSuccess(exit)\n ? {\n call: nestedCall,\n outcome: hookOutcome(exit),\n durationMs: finished - started,\n context: input.context,\n result: exit.value\n }\n : {\n call: nestedCall,\n outcome: hookOutcome(exit),\n durationMs: finished - started,\n context: input.context\n }\n )\n )\n )\n )\n })\n\n const admitted =\n options.beforeNestedCall === undefined\n ? run\n : options.beforeNestedCall({ call: nestedCall, context: input.context }).pipe(\n Effect.matchEffect({\n onFailure: message =>\n Effect.succeed(\n ToolResult.make({\n toolCallId: nestedCall.id,\n content: message,\n isError: true\n })\n ),\n onSuccess: () => run\n })\n )\n\n const execution = Effect.gen(function* () {\n const started = yield* Clock.currentTimeMillis\n\n return yield* admitted.pipe(\n Effect.onExit(exit =>\n Effect.map(Clock.currentTimeMillis, finished => {\n record.outcome = recordedOutcome(exit, finished - started)\n })\n )\n )\n })\n\n return new Promise((resolve, reject) => {\n runFork(execution, { signal }).addObserver(exit => {\n if (Exit.isFailure(exit)) {\n reject(\n new Error(\n Cause.hasInterruptsOnly(exit.cause)\n ? `${tool.callLabel} was cancelled.`\n : `${tool.callLabel} failed unexpectedly.`\n )\n )\n\n return\n }\n\n const resolution = resolveNestedResult(tool, exit.value)\n\n if (resolution.ok) {\n resolve(resolution.value)\n } else {\n // The script sees which call failed; the nested-call record keeps the raw text.\n reject(new Error(`${tool.callLabel}: ${resolution.message}`))\n }\n })\n })\n }\n\n const tools: ReadonlyArray<CodeModeExecutorTool> = catalog.map(tool => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n callLabel: tool.callLabel,\n execute: callTool(tool)\n }))\n\n const result = yield* Effect.tryPromise({\n try: signal =>\n options.executor.execute(input.code, {\n tools,\n globals: discoveryGlobals(catalog),\n timeoutMs,\n memoryLimitBytes: limits.memoryLimitBytes,\n store,\n signal\n }),\n catch: error => new CodeModeExecutorRejected({ message: errorMessage(error) })\n }).pipe(\n Effect.catch(error =>\n Effect.succeed<CodeModeExecutionResult>({\n ok: false,\n error: {\n kind: 'sandbox',\n message: `The code mode executor failed: ${error.message}`\n },\n output: []\n })\n ),\n // Backstop for executors that miss their own deadline: interrupting aborts the signal.\n Effect.timeoutOption(Duration.millis(timeoutMs + codeModeDeadlineMarginMs)),\n Effect.map(\n Option.getOrElse((): CodeModeExecutionResult => ({\n ok: false,\n error: {\n kind: 'timeout',\n message: `Execution timed out after ${timeoutMs} ms (the executor did not stop in time)`\n },\n output: []\n }))\n )\n )\n\n // Calls still running when the script ended are cancelled and recorded as such.\n yield* boundedWait(FiberSet.clear(fibers))\n\n const finishedAt = yield* Clock.currentTimeMillis\n\n const recorded = records.map((record): NestedToolCallInput => ({\n id: record.id,\n name: record.name,\n args: record.args,\n // A call interrupted before it started has no outcome.\n ...(record.outcome ?? { status: 'cancelled' })\n }))\n\n const recorder = recorded.reduce(\n recordNestedToolCall,\n makeNestedToolCallRecorder({ maxCalls: limits.maxNestedCalls })\n )\n\n const { nestedCalls, usage } = nestedToolCallResultFields(recorder)\n\n const content = codeModeSegmentsContent(\n codeModeResultSegments({\n result,\n wallTimeMs: finishedAt - startedAt,\n calls: recorded,\n maxChars: limits.maxOutputChars,\n maxImages: limits.maxImages,\n maxImageBytes: limits.maxImageBytes\n })\n )\n\n const structuredContent: CodeModeStructuredContent = {\n codemode:\n result.ok && result.storeWrites !== undefined && hasStoreWrites(result)\n ? { ok: true, storeWrites: result.storeWrites }\n : { ok: result.ok }\n }\n\n type ResultFields = {\n toolCallId: string\n content: Content\n isError?: boolean\n structuredContent: CodeModeStructuredContent\n nestedCalls: typeof nestedCalls\n usage?: AgentUsage\n }\n\n const fields: ResultFields = {\n toolCallId: call.id,\n content,\n structuredContent,\n nestedCalls\n }\n\n if (!result.ok) {\n fields.isError = true\n }\n\n if (usage !== undefined) {\n fields.usage = usage\n }\n\n return ToolResult.make(fields)\n }),\n (scope, exit) => boundedWait(Scope.close(scope, exit))\n )\n })\n\nconst argsPreviewChars = 200\n\nconst interruptedCallStatus = (entry: ToolLedgerEntry): CodeModeInterruptedCallStatus => {\n const outcome = entry.outcome\n\n if (outcome === undefined) return 'unknown'\n\n if (Predicate.isTagged(outcome, 'Failed')) return 'failed'\n\n return outcome.result.isError === true ? 'failed' : 'applied'\n}\n\nconst nestedEntryState = (entry: ToolLedgerEntry) => {\n const outcome = entry.outcome\n\n if (outcome === undefined) {\n return 'unknown: it started but never recorded a result, so it may have been applied'\n }\n\n if (Predicate.isTagged(outcome, 'Failed'))\n return `completed with an error: ${truncateCodePoints(outcome.error.message, nestedToolCallMaxErrorChars)}`\n\n return outcome.result.isError === true ? 'completed with an error result' : 'applied'\n}\n\n/** The bounded, wire-safe `interruptedCalls` of an abandoned execution's nested ledger entries:\n * the `nestedCalls` record bounds (`maxCalls`, 8 KiB of arguments per call, 32 KiB in total).\n */\nconst interruptedCalls = (\n nested: ReadonlyArray<ToolLedgerEntry>,\n maxCalls: number\n): CodeModeInterruptedCalls => {\n const counts = { applied: 0, failed: 0, unknown: 0 }\n const calls: Array<CodeModeInterruptedCall> = []\n let argsBytes = 0\n let complete = true\n\n for (const entry of nested) {\n const status = interruptedCallStatus(entry)\n\n counts[status]++\n\n if (calls.length >= maxCalls) {\n complete = false\n continue\n }\n\n const { args, bytes, truncated } = boundNestedToolCallArgs(entry.args, argsBytes)\n\n // `argsTruncated`: the ledger already cut the arguments when the call was claimed.\n if (truncated || entry.argsTruncated) complete = false\n\n argsBytes += bytes\n calls.push({ key: entry.key, toolName: entry.toolName, args, status })\n }\n\n return { calls, complete, counts }\n}\n\n/**\n * The result of a code mode call whose earlier execution was abandoned (claimed, lease expired,\n * no result recorded). The script never runs again: the result lists that execution's ledgered\n * nested calls as applied, failed, or unknown, and says they were not undone. The text is bounded\n * by `maxOutputChars`; `structuredContent.codemode.interruptedCalls` carries the same entries for\n * hosts, bounded like `nestedCalls`. When the ledger cannot list them, the result stays\n * interrupted, says they may have been applied, and sets `interruptedCallsUnavailable` instead.\n */\nconst abandonedScriptResult =\n (limits: Required<CodeModeLimits>) =>\n ({ call, nested }: ToolLedgerAbandonedInput): ToolResult => {\n const maxChars = limits.maxOutputChars\n\n const listing =\n nested === undefined\n ? 'Its ledgered nested tool calls could not be listed (the tool ledger is unavailable), so any of them may already have been applied.'\n : nested.length === 0\n ? 'No ledgered nested tool calls were recorded for that execution.'\n : [\n `Ledgered nested tool calls of that execution (they were not undone):`,\n ...nested.map(\n entry =>\n `- ${entry.key} ${entry.toolName} ${truncateCodePoints(entry.args, argsPreviewChars)}: ${nestedEntryState(entry)}`\n )\n ].join('\\n')\n\n const text = [\n `Script interrupted: an earlier execution of this ${call.name} call (${call.id}) started but never recorded a result. The script was not run again.`,\n listing,\n 'Calls the ledger policy skips (by default read-only calls) are not listed. Verify the state these calls affect before repeating any work.'\n ].join('\\n\\n')\n\n const structuredContent: CodeModeStructuredContent = {\n codemode:\n nested === undefined\n ? { ok: false, interrupted: true, interruptedCallsUnavailable: true }\n : {\n ok: false,\n interrupted: true,\n interruptedCalls: interruptedCalls(\n nested,\n nestedToolCallRecordLimit(limits.maxNestedCalls)\n )\n }\n }\n\n return ToolResult.make({\n toolCallId: call.id,\n content: codeModeSegmentsContent(boundCodeModeSegments([{ type: 'text', text }], maxChars)),\n isError: true,\n structuredContent\n })\n }\n\n/**\n * The code mode tool: one registration (default name `codemode`, input `{ code }`) whose scripts\n * call the other code-mode-callable tools of the same `resolveTools` resolution. Nested calls run\n * through the resolved execute path with the same host context (input decoding, enablement,\n * registration wrappers), get ids `<toolCallId>/<seq>`, and are recorded on the result's\n * `nestedCalls`. The resolved description lists the callable tools (see\n * `renderCodeModeDescription`).\n *\n * Access is `write`: scripts can call any write tool the resolution exposes to code mode; each\n * nested call keeps its own access metadata and host wrappers.\n *\n * With a tool ledger (`resolveTools(..., { ledger })`) the code mode call itself is ledgered, so a\n * re-executed call never runs its script again: a completed call returns its stored result, an\n * in-flight one is awaited, and an abandoned one returns an interrupted result listing its\n * ledgered nested calls. Nested write calls are ledgered under `<toolCallId>/<seq>`.\n */\nexport const makeCodeModeTool = <Context>(\n options: MakeCodeModeToolOptions<Context>\n): ToolRegistration<Context> => {\n const name = options.name ?? codeModeToolName\n const limits: Required<CodeModeLimits> = { ...defaultCodeModeLimits, ...options.limits }\n const inlineBudget = options.inlineBudget ?? defaultCodeModeInlineBudget\n const store = options.loadStore !== undefined\n\n return makeTool<Context, typeof CodeModeParams>({\n name,\n description: renderCodeModeDescription({ tools: [], inlineBudget, store }),\n parameters: CodeModeParams,\n access: 'write',\n nestedToolAccess: true,\n describe: ({ tools }) => renderCodeModeDescription({ tools, inlineBudget, store }),\n abandonedResult: abandonedScriptResult(limits),\n execute: ({\n call,\n context,\n params,\n nested\n }: ToolExecutionInput<Context> & { readonly params: CodeModeParams }) =>\n nested === undefined\n ? Effect.fail(\n new ToolError({\n tool: name,\n cause: 'unavailable',\n message: `${name} requires nested tool access; resolve it with resolveTools.`\n })\n )\n : runScript({ options, limits, call, context, code: params.code, nested })\n })\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yolk-sdk/codemode",
|
|
3
|
-
"version": "0.1.0-canary.
|
|
3
|
+
"version": "0.1.0-canary.99",
|
|
4
4
|
"description": "Code mode for Yolk agents: one tool that runs model-written JavaScript calling the host's resolved tools, plus a Node executor on a confined QuickJS sandbox.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@earendil-works/pi-codemode": "1.0.0",
|
|
54
54
|
"effect": "4.0.0",
|
|
55
|
-
"@yolk-sdk/agent": "^0.1.0-canary.
|
|
55
|
+
"@yolk-sdk/agent": "^0.1.0-canary.99"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"build": "tsdown",
|
package/src/index.ts
CHANGED
|
@@ -51,7 +51,13 @@ export {
|
|
|
51
51
|
maxCodeModeStoreValueChars
|
|
52
52
|
} from './store.ts'
|
|
53
53
|
|
|
54
|
-
export type {
|
|
54
|
+
export type {
|
|
55
|
+
CodeModeInterruptedCall,
|
|
56
|
+
CodeModeInterruptedCalls,
|
|
57
|
+
CodeModeInterruptedCallStatus,
|
|
58
|
+
CodeModeStructuredContent,
|
|
59
|
+
CodeModeToolResultEntry
|
|
60
|
+
} from './store.ts'
|
|
55
61
|
|
|
56
62
|
export {
|
|
57
63
|
codeModeDeadlineMarginMs,
|
|
@@ -61,7 +67,12 @@ export {
|
|
|
61
67
|
makeCodeModeTool
|
|
62
68
|
} from './tool.ts'
|
|
63
69
|
|
|
64
|
-
export type {
|
|
70
|
+
export type {
|
|
71
|
+
CodeModeAfterNestedCallInput,
|
|
72
|
+
CodeModeLimits,
|
|
73
|
+
CodeModeNestedCallOutcome,
|
|
74
|
+
MakeCodeModeToolOptions
|
|
75
|
+
} from './tool.ts'
|
|
65
76
|
|
|
66
77
|
export {
|
|
67
78
|
ClassifierToolParams,
|
package/src/store.ts
CHANGED
|
@@ -10,6 +10,48 @@ export type CodeModeStructuredContent = {
|
|
|
10
10
|
readonly codemode: {
|
|
11
11
|
readonly ok: boolean
|
|
12
12
|
readonly storeWrites?: CodeModeStoreWrites
|
|
13
|
+
/** Set on the result of a re-executed call whose earlier execution was abandoned. */
|
|
14
|
+
readonly interrupted?: true
|
|
15
|
+
/** With `interrupted`: the abandoned execution's ledgered nested calls. */
|
|
16
|
+
readonly interruptedCalls?: CodeModeInterruptedCalls
|
|
17
|
+
/** With `interrupted`, instead of `interruptedCalls`: the nested calls could not be listed
|
|
18
|
+
* (the ledger's `list` failed), so any of them may have been applied.
|
|
19
|
+
*/
|
|
20
|
+
readonly interruptedCallsUnavailable?: true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** What an abandoned execution's ledgered nested call is known to have done: `applied` (recorded
|
|
25
|
+
* a result), `failed` (recorded a `ToolError` or an `isError` result), or `unknown` (started, no
|
|
26
|
+
* recorded outcome, so it may have been applied).
|
|
27
|
+
*/
|
|
28
|
+
export type CodeModeInterruptedCallStatus = 'applied' | 'failed' | 'unknown'
|
|
29
|
+
|
|
30
|
+
/** One ledgered nested call of an abandoned execution. `key` is its ledger key
|
|
31
|
+
* (`<toolCallId>/<seq>`); `args` is compact JSON, cut with a trailing `…` (then no longer JSON).
|
|
32
|
+
*/
|
|
33
|
+
export type CodeModeInterruptedCall = {
|
|
34
|
+
readonly key: string
|
|
35
|
+
readonly toolName: string
|
|
36
|
+
readonly args: string
|
|
37
|
+
readonly status: CodeModeInterruptedCallStatus
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `structuredContent.codemode.interruptedCalls`: plain JSON, in sequence order, bounded like
|
|
42
|
+
* `nestedCalls` (at most `limits.maxNestedCalls` calls; arguments within 8 KiB per call and
|
|
43
|
+
* 32 KiB in total). `complete: false` means calls were dropped or arguments cut, here or already
|
|
44
|
+
* in the ledger entry (`ToolLedgerEntry.argsTruncated`); `counts` covers every ledgered nested
|
|
45
|
+
* call, dropped ones included. Calls the ledger policy skips (by default read-only calls) never
|
|
46
|
+
* appear.
|
|
47
|
+
*/
|
|
48
|
+
export type CodeModeInterruptedCalls = {
|
|
49
|
+
readonly calls: ReadonlyArray<CodeModeInterruptedCall>
|
|
50
|
+
readonly complete: boolean
|
|
51
|
+
readonly counts: {
|
|
52
|
+
readonly applied: number
|
|
53
|
+
readonly failed: number
|
|
54
|
+
readonly unknown: number
|
|
13
55
|
}
|
|
14
56
|
}
|
|
15
57
|
|
package/src/tool.ts
CHANGED
|
@@ -14,9 +14,13 @@ import {
|
|
|
14
14
|
import * as Schema from 'effect/Schema'
|
|
15
15
|
import { ToolError } from '@yolk-sdk/agent/loop'
|
|
16
16
|
import {
|
|
17
|
-
|
|
17
|
+
boundNestedToolCallArgs,
|
|
18
|
+
makeNestedToolCallRecorder,
|
|
19
|
+
nestedToolCallMaxErrorChars,
|
|
20
|
+
nestedToolCallRecordLimit,
|
|
18
21
|
nestedToolCallResultFields,
|
|
19
22
|
recordNestedToolCall,
|
|
23
|
+
truncateCodePoints,
|
|
20
24
|
contentPartText,
|
|
21
25
|
ToolCall,
|
|
22
26
|
ToolResult,
|
|
@@ -29,6 +33,8 @@ import {
|
|
|
29
33
|
makeTool,
|
|
30
34
|
type NestedToolExecutor,
|
|
31
35
|
type ToolExecutionInput,
|
|
36
|
+
type ToolLedgerAbandonedInput,
|
|
37
|
+
type ToolLedgerEntry,
|
|
32
38
|
type ToolRegistration
|
|
33
39
|
} from '@yolk-sdk/agent/tools'
|
|
34
40
|
import {
|
|
@@ -47,13 +53,19 @@ import type {
|
|
|
47
53
|
CodeModeStore
|
|
48
54
|
} from './executor.ts'
|
|
49
55
|
import {
|
|
56
|
+
boundCodeModeSegments,
|
|
50
57
|
codeModeResultSegments,
|
|
51
58
|
codeModeSegmentsContent,
|
|
52
59
|
defaultCodeModeMaxImageBytes,
|
|
53
60
|
defaultCodeModeMaxImages
|
|
54
61
|
} from './output.ts'
|
|
55
62
|
import { searchCodeModeTools } from './search.ts'
|
|
56
|
-
import type {
|
|
63
|
+
import type {
|
|
64
|
+
CodeModeInterruptedCall,
|
|
65
|
+
CodeModeInterruptedCalls,
|
|
66
|
+
CodeModeInterruptedCallStatus,
|
|
67
|
+
CodeModeStructuredContent
|
|
68
|
+
} from './store.ts'
|
|
57
69
|
|
|
58
70
|
/** Default name of the code mode tool. */
|
|
59
71
|
export const codeModeToolName = 'codemode'
|
|
@@ -64,7 +76,10 @@ export type CodeModeLimits = {
|
|
|
64
76
|
readonly timeoutMs?: number
|
|
65
77
|
/** VM heap cap. Default 64 MiB. */
|
|
66
78
|
readonly memoryLimitBytes?: number
|
|
67
|
-
/** Nested tool calls per script; further calls reject with an Error. Default 256.
|
|
79
|
+
/** Nested tool calls per script; further calls reject with an Error. Default 256. The
|
|
80
|
+
* `nestedCalls` record keeps up to this many calls, at most `nestedToolCallMaxRecordedCalls`
|
|
81
|
+
* (4096) whatever the limit (argument byte budgets still apply).
|
|
82
|
+
*/
|
|
68
83
|
readonly maxNestedCalls?: number
|
|
69
84
|
/** Model-visible result characters, cut head and tail with an omission marker. Default 40000. */
|
|
70
85
|
readonly maxOutputChars?: number
|
|
@@ -119,6 +134,27 @@ export type MakeCodeModeToolOptions<Context> = {
|
|
|
119
134
|
readonly call: ToolCall
|
|
120
135
|
readonly context: Context
|
|
121
136
|
}) => Effect.Effect<void, string>
|
|
137
|
+
/**
|
|
138
|
+
* Runs after each nested call that `beforeNestedCall` admitted, with its outcome: `success` (a
|
|
139
|
+
* result without `isError`), `failure` (an error result or an unexpected failure), or
|
|
140
|
+
* `interrupted` (cancelled, for example still running when the script ended). `durationMs`
|
|
141
|
+
* covers the execution; `result` is present when the call produced one. Calls rejected by
|
|
142
|
+
* `beforeNestedCall` or the call limit never ran and are not reported. The script waits for the
|
|
143
|
+
* hook before it sees the call settle. A failing hook (a failure, a defect, or a synchronous
|
|
144
|
+
* throw) is logged and never changes the call's result.
|
|
145
|
+
*/
|
|
146
|
+
readonly afterNestedCall?: (input: CodeModeAfterNestedCallInput<Context>) => Effect.Effect<void>
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Outcome of a nested call for `afterNestedCall`. */
|
|
150
|
+
export type CodeModeNestedCallOutcome = 'success' | 'failure' | 'interrupted'
|
|
151
|
+
|
|
152
|
+
export type CodeModeAfterNestedCallInput<Context> = {
|
|
153
|
+
readonly call: ToolCall
|
|
154
|
+
readonly outcome: CodeModeNestedCallOutcome
|
|
155
|
+
readonly durationMs: number
|
|
156
|
+
readonly context: Context
|
|
157
|
+
readonly result?: ToolResult
|
|
122
158
|
}
|
|
123
159
|
|
|
124
160
|
const CodeModeParams = Schema.Struct({
|
|
@@ -201,6 +237,15 @@ const recordedOutcome = (exit: Exit.Exit<ToolResult>, durationMs: number): CallO
|
|
|
201
237
|
return outcome
|
|
202
238
|
}
|
|
203
239
|
|
|
240
|
+
const hookOutcome = (exit: Exit.Exit<ToolResult>): CodeModeNestedCallOutcome =>
|
|
241
|
+
Exit.isSuccess(exit)
|
|
242
|
+
? exit.value.isError === true
|
|
243
|
+
? 'failure'
|
|
244
|
+
: 'success'
|
|
245
|
+
: Cause.hasInterruptsOnly(exit.cause)
|
|
246
|
+
? 'interrupted'
|
|
247
|
+
: 'failure'
|
|
248
|
+
|
|
204
249
|
/** What `describeNamespace(name)` resolves to. */
|
|
205
250
|
type CodeModeNamespaceDescription = {
|
|
206
251
|
readonly name: string
|
|
@@ -341,10 +386,57 @@ const runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult,
|
|
|
341
386
|
records.push(record)
|
|
342
387
|
|
|
343
388
|
const nestedCall = ToolCall.make({ id: record.id, name: tool.name, params })
|
|
389
|
+
const afterNestedCall = options.afterNestedCall
|
|
390
|
+
|
|
391
|
+
const run =
|
|
392
|
+
afterNestedCall === undefined
|
|
393
|
+
? nested.execute(nestedCall)
|
|
394
|
+
: Effect.gen(function* () {
|
|
395
|
+
const started = yield* Clock.currentTimeMillis
|
|
396
|
+
|
|
397
|
+
// Host observability never changes the call: a hook failure, defects and
|
|
398
|
+
// synchronous throws included, is only logged.
|
|
399
|
+
const afterNestedCallSafely = (
|
|
400
|
+
hookInput: CodeModeAfterNestedCallInput<Context>
|
|
401
|
+
) =>
|
|
402
|
+
Effect.suspend(() => afterNestedCall(hookInput)).pipe(
|
|
403
|
+
Effect.exit,
|
|
404
|
+
Effect.flatMap(hookExit =>
|
|
405
|
+
Exit.isSuccess(hookExit)
|
|
406
|
+
? Effect.void
|
|
407
|
+
: Effect.logWarning(
|
|
408
|
+
`Code mode afterNestedCall failed for ${nestedCall.id}; ignored: ${Cause.pretty(hookExit.cause)}`
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
return yield* nested.execute(nestedCall).pipe(
|
|
414
|
+
Effect.onExit(exit =>
|
|
415
|
+
Effect.flatMap(Clock.currentTimeMillis, finished =>
|
|
416
|
+
afterNestedCallSafely(
|
|
417
|
+
Exit.isSuccess(exit)
|
|
418
|
+
? {
|
|
419
|
+
call: nestedCall,
|
|
420
|
+
outcome: hookOutcome(exit),
|
|
421
|
+
durationMs: finished - started,
|
|
422
|
+
context: input.context,
|
|
423
|
+
result: exit.value
|
|
424
|
+
}
|
|
425
|
+
: {
|
|
426
|
+
call: nestedCall,
|
|
427
|
+
outcome: hookOutcome(exit),
|
|
428
|
+
durationMs: finished - started,
|
|
429
|
+
context: input.context
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
)
|
|
434
|
+
)
|
|
435
|
+
})
|
|
344
436
|
|
|
345
437
|
const admitted =
|
|
346
438
|
options.beforeNestedCall === undefined
|
|
347
|
-
?
|
|
439
|
+
? run
|
|
348
440
|
: options.beforeNestedCall({ call: nestedCall, context: input.context }).pipe(
|
|
349
441
|
Effect.matchEffect({
|
|
350
442
|
onFailure: message =>
|
|
@@ -355,7 +447,7 @@ const runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult,
|
|
|
355
447
|
isError: true
|
|
356
448
|
})
|
|
357
449
|
),
|
|
358
|
-
onSuccess: () =>
|
|
450
|
+
onSuccess: () => run
|
|
359
451
|
})
|
|
360
452
|
)
|
|
361
453
|
|
|
@@ -455,7 +547,11 @@ const runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult,
|
|
|
455
547
|
...(record.outcome ?? { status: 'cancelled' })
|
|
456
548
|
}))
|
|
457
549
|
|
|
458
|
-
const recorder = recorded.reduce(
|
|
550
|
+
const recorder = recorded.reduce(
|
|
551
|
+
recordNestedToolCall,
|
|
552
|
+
makeNestedToolCallRecorder({ maxCalls: limits.maxNestedCalls })
|
|
553
|
+
)
|
|
554
|
+
|
|
459
555
|
const { nestedCalls, usage } = nestedToolCallResultFields(recorder)
|
|
460
556
|
|
|
461
557
|
const content = codeModeSegmentsContent(
|
|
@@ -506,6 +602,119 @@ const runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult,
|
|
|
506
602
|
)
|
|
507
603
|
})
|
|
508
604
|
|
|
605
|
+
const argsPreviewChars = 200
|
|
606
|
+
|
|
607
|
+
const interruptedCallStatus = (entry: ToolLedgerEntry): CodeModeInterruptedCallStatus => {
|
|
608
|
+
const outcome = entry.outcome
|
|
609
|
+
|
|
610
|
+
if (outcome === undefined) return 'unknown'
|
|
611
|
+
|
|
612
|
+
if (Predicate.isTagged(outcome, 'Failed')) return 'failed'
|
|
613
|
+
|
|
614
|
+
return outcome.result.isError === true ? 'failed' : 'applied'
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const nestedEntryState = (entry: ToolLedgerEntry) => {
|
|
618
|
+
const outcome = entry.outcome
|
|
619
|
+
|
|
620
|
+
if (outcome === undefined) {
|
|
621
|
+
return 'unknown: it started but never recorded a result, so it may have been applied'
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (Predicate.isTagged(outcome, 'Failed'))
|
|
625
|
+
return `completed with an error: ${truncateCodePoints(outcome.error.message, nestedToolCallMaxErrorChars)}`
|
|
626
|
+
|
|
627
|
+
return outcome.result.isError === true ? 'completed with an error result' : 'applied'
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/** The bounded, wire-safe `interruptedCalls` of an abandoned execution's nested ledger entries:
|
|
631
|
+
* the `nestedCalls` record bounds (`maxCalls`, 8 KiB of arguments per call, 32 KiB in total).
|
|
632
|
+
*/
|
|
633
|
+
const interruptedCalls = (
|
|
634
|
+
nested: ReadonlyArray<ToolLedgerEntry>,
|
|
635
|
+
maxCalls: number
|
|
636
|
+
): CodeModeInterruptedCalls => {
|
|
637
|
+
const counts = { applied: 0, failed: 0, unknown: 0 }
|
|
638
|
+
const calls: Array<CodeModeInterruptedCall> = []
|
|
639
|
+
let argsBytes = 0
|
|
640
|
+
let complete = true
|
|
641
|
+
|
|
642
|
+
for (const entry of nested) {
|
|
643
|
+
const status = interruptedCallStatus(entry)
|
|
644
|
+
|
|
645
|
+
counts[status]++
|
|
646
|
+
|
|
647
|
+
if (calls.length >= maxCalls) {
|
|
648
|
+
complete = false
|
|
649
|
+
continue
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const { args, bytes, truncated } = boundNestedToolCallArgs(entry.args, argsBytes)
|
|
653
|
+
|
|
654
|
+
// `argsTruncated`: the ledger already cut the arguments when the call was claimed.
|
|
655
|
+
if (truncated || entry.argsTruncated) complete = false
|
|
656
|
+
|
|
657
|
+
argsBytes += bytes
|
|
658
|
+
calls.push({ key: entry.key, toolName: entry.toolName, args, status })
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return { calls, complete, counts }
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* The result of a code mode call whose earlier execution was abandoned (claimed, lease expired,
|
|
666
|
+
* no result recorded). The script never runs again: the result lists that execution's ledgered
|
|
667
|
+
* nested calls as applied, failed, or unknown, and says they were not undone. The text is bounded
|
|
668
|
+
* by `maxOutputChars`; `structuredContent.codemode.interruptedCalls` carries the same entries for
|
|
669
|
+
* hosts, bounded like `nestedCalls`. When the ledger cannot list them, the result stays
|
|
670
|
+
* interrupted, says they may have been applied, and sets `interruptedCallsUnavailable` instead.
|
|
671
|
+
*/
|
|
672
|
+
const abandonedScriptResult =
|
|
673
|
+
(limits: Required<CodeModeLimits>) =>
|
|
674
|
+
({ call, nested }: ToolLedgerAbandonedInput): ToolResult => {
|
|
675
|
+
const maxChars = limits.maxOutputChars
|
|
676
|
+
|
|
677
|
+
const listing =
|
|
678
|
+
nested === undefined
|
|
679
|
+
? 'Its ledgered nested tool calls could not be listed (the tool ledger is unavailable), so any of them may already have been applied.'
|
|
680
|
+
: nested.length === 0
|
|
681
|
+
? 'No ledgered nested tool calls were recorded for that execution.'
|
|
682
|
+
: [
|
|
683
|
+
`Ledgered nested tool calls of that execution (they were not undone):`,
|
|
684
|
+
...nested.map(
|
|
685
|
+
entry =>
|
|
686
|
+
`- ${entry.key} ${entry.toolName} ${truncateCodePoints(entry.args, argsPreviewChars)}: ${nestedEntryState(entry)}`
|
|
687
|
+
)
|
|
688
|
+
].join('\n')
|
|
689
|
+
|
|
690
|
+
const text = [
|
|
691
|
+
`Script interrupted: an earlier execution of this ${call.name} call (${call.id}) started but never recorded a result. The script was not run again.`,
|
|
692
|
+
listing,
|
|
693
|
+
'Calls the ledger policy skips (by default read-only calls) are not listed. Verify the state these calls affect before repeating any work.'
|
|
694
|
+
].join('\n\n')
|
|
695
|
+
|
|
696
|
+
const structuredContent: CodeModeStructuredContent = {
|
|
697
|
+
codemode:
|
|
698
|
+
nested === undefined
|
|
699
|
+
? { ok: false, interrupted: true, interruptedCallsUnavailable: true }
|
|
700
|
+
: {
|
|
701
|
+
ok: false,
|
|
702
|
+
interrupted: true,
|
|
703
|
+
interruptedCalls: interruptedCalls(
|
|
704
|
+
nested,
|
|
705
|
+
nestedToolCallRecordLimit(limits.maxNestedCalls)
|
|
706
|
+
)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return ToolResult.make({
|
|
711
|
+
toolCallId: call.id,
|
|
712
|
+
content: codeModeSegmentsContent(boundCodeModeSegments([{ type: 'text', text }], maxChars)),
|
|
713
|
+
isError: true,
|
|
714
|
+
structuredContent
|
|
715
|
+
})
|
|
716
|
+
}
|
|
717
|
+
|
|
509
718
|
/**
|
|
510
719
|
* The code mode tool: one registration (default name `codemode`, input `{ code }`) whose scripts
|
|
511
720
|
* call the other code-mode-callable tools of the same `resolveTools` resolution. Nested calls run
|
|
@@ -516,6 +725,11 @@ const runScript = <Context>(input: RunInput<Context>): Effect.Effect<ToolResult,
|
|
|
516
725
|
*
|
|
517
726
|
* Access is `write`: scripts can call any write tool the resolution exposes to code mode; each
|
|
518
727
|
* nested call keeps its own access metadata and host wrappers.
|
|
728
|
+
*
|
|
729
|
+
* With a tool ledger (`resolveTools(..., { ledger })`) the code mode call itself is ledgered, so a
|
|
730
|
+
* re-executed call never runs its script again: a completed call returns its stored result, an
|
|
731
|
+
* in-flight one is awaited, and an abandoned one returns an interrupted result listing its
|
|
732
|
+
* ledgered nested calls. Nested write calls are ledgered under `<toolCallId>/<seq>`.
|
|
519
733
|
*/
|
|
520
734
|
export const makeCodeModeTool = <Context>(
|
|
521
735
|
options: MakeCodeModeToolOptions<Context>
|
|
@@ -532,6 +746,7 @@ export const makeCodeModeTool = <Context>(
|
|
|
532
746
|
access: 'write',
|
|
533
747
|
nestedToolAccess: true,
|
|
534
748
|
describe: ({ tools }) => renderCodeModeDescription({ tools, inlineBudget, store }),
|
|
749
|
+
abandonedResult: abandonedScriptResult(limits),
|
|
535
750
|
execute: ({
|
|
536
751
|
call,
|
|
537
752
|
context,
|