@opencode/codemode 2.0.3 → 2.0.5
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 +5 -2
- package/dist/codemode.d.ts +7 -2
- package/dist/codemode.js +15 -2
- package/dist/data.d.ts +14 -21
- package/dist/data.js +72 -140
- package/dist/extension.d.ts +11 -0
- package/dist/extension.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interpreter/callback.d.ts +20 -0
- package/dist/interpreter/{runner.js → callback.js} +14 -14
- package/dist/interpreter/errors.d.ts +12 -5
- package/dist/interpreter/errors.js +66 -54
- package/dist/interpreter/execute.d.ts +2 -2
- package/dist/interpreter/execute.js +15 -15
- package/dist/interpreter/extensions.d.ts +8 -0
- package/dist/interpreter/extensions.js +160 -0
- package/dist/interpreter/generators.d.ts +2 -3
- package/dist/interpreter/generators.js +10 -10
- package/dist/interpreter/globals.d.ts +4 -12
- package/dist/interpreter/globals.js +56 -49
- package/dist/interpreter/interpreter.d.ts +34 -0
- package/dist/interpreter/{runtime.js → interpreter.js} +250 -215
- package/dist/interpreter/intrinsics.d.ts +5 -5
- package/dist/interpreter/intrinsics.js +16 -10
- package/dist/interpreter/limits.d.ts +10 -0
- package/dist/interpreter/limits.js +19 -0
- package/dist/interpreter/model.d.ts +27 -14
- package/dist/interpreter/model.js +22 -14
- package/dist/interpreter/native.d.ts +12 -13
- package/dist/interpreter/native.js +17 -16
- package/dist/interpreter/objects.d.ts +55 -48
- package/dist/interpreter/objects.js +64 -44
- package/dist/interpreter/promises.d.ts +13 -15
- package/dist/interpreter/promises.js +89 -85
- package/dist/interpreter/references.d.ts +1 -2
- package/dist/interpreter/references.js +18 -16
- package/dist/interpreter/scope.js +6 -6
- package/dist/openapi/types.d.ts +1 -1
- package/dist/stdlib/array.d.ts +3 -4
- package/dist/stdlib/array.js +115 -110
- package/dist/stdlib/bytes.d.ts +10 -0
- package/dist/stdlib/bytes.js +228 -0
- package/dist/stdlib/collections.d.ts +4 -4
- package/dist/stdlib/collections.js +89 -89
- package/dist/stdlib/console.d.ts +5 -3
- package/dist/stdlib/console.js +30 -37
- package/dist/stdlib/date.d.ts +2 -2
- package/dist/stdlib/date.js +35 -35
- package/dist/stdlib/json.d.ts +3 -3
- package/dist/stdlib/json.js +29 -67
- package/dist/stdlib/math.d.ts +3 -3
- package/dist/stdlib/math.js +17 -21
- package/dist/stdlib/number.d.ts +3 -3
- package/dist/stdlib/number.js +32 -36
- package/dist/stdlib/object.d.ts +5 -5
- package/dist/stdlib/object.js +59 -62
- package/dist/stdlib/regexp.d.ts +7 -8
- package/dist/stdlib/regexp.js +40 -48
- package/dist/stdlib/string.d.ts +2 -2
- package/dist/stdlib/string.js +110 -88
- package/dist/stdlib/url.d.ts +4 -6
- package/dist/stdlib/url.js +86 -90
- package/dist/stdlib/value.d.ts +3 -3
- package/dist/stdlib/value.js +26 -24
- package/dist/stdlib/web.d.ts +4 -4
- package/dist/stdlib/web.js +22 -8
- package/dist/tool-runtime.d.ts +38 -19
- package/dist/tool-runtime.js +70 -48
- package/package.json +1 -1
- package/dist/interpreter/runner.d.ts +0 -28
- package/dist/interpreter/runtime.d.ts +0 -19
package/dist/tool-runtime.js
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
import { Cause, Effect, Exit, Formatter, Schema } from "effect";
|
|
2
|
-
import { fromData, toData, ToolRuntimeError } from "./data.js";
|
|
3
2
|
import { toolError } from "./tool-error.js";
|
|
4
3
|
import { decodeInput as decodeToolInput, decodeOutput as decodeToolOutput, identifierSegment, inputProperties, inputTypeScript, isEmptyInput, outputTypeScript, } from "./tool-schema.js";
|
|
5
4
|
import { isNamespace } from "./namespace.js";
|
|
6
5
|
import { isTool } from "./tool.js";
|
|
7
6
|
export const compareText = (left, right) => (left < right ? -1 : left > right ? 1 : 0);
|
|
7
|
+
/** Runs `before`, then `run`, then `after` with how it ended, including when interrupted. */
|
|
8
|
+
export const hooked = (call, before, after, run) => {
|
|
9
|
+
const observed = after === undefined
|
|
10
|
+
? run
|
|
11
|
+
: Effect.onExit(run, (exit) => {
|
|
12
|
+
if (Exit.isSuccess(exit))
|
|
13
|
+
return after(call, { status: "success", value: exit.value });
|
|
14
|
+
if (Cause.hasInterruptsOnly(exit.cause))
|
|
15
|
+
return after(call, { status: "interrupted" });
|
|
16
|
+
return after(call, { status: "failure", error: Cause.squash(exit.cause) });
|
|
17
|
+
});
|
|
18
|
+
return before === undefined ? observed : Effect.andThen(before(call), observed);
|
|
19
|
+
};
|
|
8
20
|
const defaultSearchLimit = 10;
|
|
9
21
|
const PositiveInt = Schema.Int.check(Schema.isGreaterThan(0));
|
|
10
22
|
const NonNegativeInt = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
@@ -68,13 +80,22 @@ const flattenTools = (node, path = [], namespaces = []) => {
|
|
|
68
80
|
...Array.from(node.children).flatMap(([name, child]) => flattenTools(child, [...path, name], next)),
|
|
69
81
|
];
|
|
70
82
|
};
|
|
71
|
-
const describeTool = (visible) =>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
const describeTool = (visible) => {
|
|
84
|
+
let signature;
|
|
85
|
+
return {
|
|
86
|
+
path: visible.path,
|
|
87
|
+
description: visible.tool.description,
|
|
88
|
+
get signature() {
|
|
89
|
+
// Search ranks paths and descriptions first; only returned matches need their schemas rendered.
|
|
90
|
+
// Joining the final fragments avoids retaining the rendering's intermediate string ropes in JSC.
|
|
91
|
+
return (signature ??= [
|
|
92
|
+
toolExpression(visible.path),
|
|
93
|
+
isEmptyInput(visible.tool) ? "()" : `(input: ${inputTypeScript(visible.tool, true)})`,
|
|
94
|
+
`: Promise<${outputTypeScript(visible.tool, true)}>`,
|
|
95
|
+
].join(""));
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
};
|
|
78
99
|
const tokenize = (query) => query
|
|
79
100
|
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
80
101
|
.toLowerCase()
|
|
@@ -153,12 +174,20 @@ const toSearchEntry = (visible) => ({
|
|
|
153
174
|
});
|
|
154
175
|
export const prepare = (tools) => {
|
|
155
176
|
const root = toolTrie(tools);
|
|
156
|
-
|
|
157
|
-
|
|
177
|
+
let searchIndex;
|
|
178
|
+
let catalog;
|
|
158
179
|
return {
|
|
159
180
|
root,
|
|
160
|
-
catalog
|
|
161
|
-
|
|
181
|
+
get catalog() {
|
|
182
|
+
return (catalog ??= this.searchIndex.map((entry) => entry.description));
|
|
183
|
+
},
|
|
184
|
+
get searchIndex() {
|
|
185
|
+
// Executing known tools only needs the trie. Render discovery when it is actually read,
|
|
186
|
+
// ordering after canonical-path collisions settle so instruction bytes stay deterministic.
|
|
187
|
+
return (searchIndex ??= flattenTools(root)
|
|
188
|
+
.sort((left, right) => compareText(left.path, right.path))
|
|
189
|
+
.map(toSearchEntry));
|
|
190
|
+
},
|
|
162
191
|
};
|
|
163
192
|
};
|
|
164
193
|
const lookup = (root, segments) => segments.reduce((node, segment) => node?.children.get(segment), root);
|
|
@@ -183,49 +212,36 @@ const resolve = (root, path) => {
|
|
|
183
212
|
}
|
|
184
213
|
return node.tool;
|
|
185
214
|
};
|
|
215
|
+
export class ToolRuntimeError extends Error {
|
|
216
|
+
kind;
|
|
217
|
+
suggestions;
|
|
218
|
+
constructor(kind, message, suggestions = []) {
|
|
219
|
+
super(message);
|
|
220
|
+
this.kind = kind;
|
|
221
|
+
this.suggestions = suggestions;
|
|
222
|
+
this.name = "ToolRuntimeError";
|
|
223
|
+
}
|
|
224
|
+
}
|
|
186
225
|
/** Per-execution call state over tools prepared once for the runtime. */
|
|
187
|
-
export const make = (prepared,
|
|
226
|
+
export const make = (prepared, maxToolCalls, hooks = {}) => {
|
|
188
227
|
const calls = [];
|
|
189
228
|
const root = prepared.root;
|
|
190
|
-
const searchTool = makeSearchTool(prepared.searchIndex);
|
|
191
|
-
const observeEnd = (effect, call) => {
|
|
192
|
-
const onEnd = hooks?.onToolCallEnd;
|
|
193
|
-
if (onEnd === undefined)
|
|
194
|
-
return effect;
|
|
195
|
-
const startedAt = Date.now();
|
|
196
|
-
return effect.pipe(Effect.onExit((exit) => {
|
|
197
|
-
const durationMs = Date.now() - startedAt;
|
|
198
|
-
if (Exit.isSuccess(exit))
|
|
199
|
-
return onEnd({ ...call, durationMs, outcome: "success" });
|
|
200
|
-
if (Cause.hasInterruptsOnly(exit.cause))
|
|
201
|
-
return onEnd({ ...call, durationMs, outcome: "interrupted" });
|
|
202
|
-
const error = Cause.squash(exit.cause);
|
|
203
|
-
const message = error instanceof Error ? error.message : Cause.pretty(exit.cause);
|
|
204
|
-
return onEnd({ ...call, durationMs, outcome: "failure", message });
|
|
205
|
-
}));
|
|
206
|
-
};
|
|
207
229
|
const recordCall = (call) => {
|
|
208
230
|
if (maxToolCalls !== undefined && calls.length >= maxToolCalls) {
|
|
209
231
|
throw new ToolRuntimeError("ToolCallLimitExceeded", `Execution exceeded its tool-call limit of ${maxToolCalls}.`);
|
|
210
232
|
}
|
|
211
233
|
calls.push(call);
|
|
212
234
|
};
|
|
213
|
-
const executeTool = (name, tool,
|
|
214
|
-
const normalized =
|
|
235
|
+
const executeTool = (name, tool, args) => Effect.gen(function* () {
|
|
236
|
+
const normalized = args.length === 0 ? [{}] : args;
|
|
215
237
|
if (normalized.length !== 1)
|
|
216
238
|
throw new ToolRuntimeError("InvalidToolInput", `Tool '${name}' expects at most one input object.`);
|
|
217
239
|
const input = yield* Effect.try({
|
|
218
240
|
try: () => decodeToolInput(tool, normalized[0]),
|
|
219
241
|
catch: (cause) => new ToolRuntimeError("InvalidToolInput", `Invalid input for tool '${name}': ${String(cause)}`, name === "search" ? [] : ["The signature may have changed. Use search to get the current signature."]),
|
|
220
242
|
});
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return calls.length - 1;
|
|
224
|
-
});
|
|
225
|
-
const call = { index, name, input };
|
|
226
|
-
return yield* observeEnd(Effect.gen(function* () {
|
|
227
|
-
if (hooks?.onToolCallStart !== undefined)
|
|
228
|
-
yield* hooks.onToolCallStart(call);
|
|
243
|
+
yield* Effect.sync(() => recordCall({ name }));
|
|
244
|
+
return yield* hooked({ name, input }, hooks["tool.before"], hooks["tool.after"], Effect.gen(function* () {
|
|
229
245
|
const raw = yield* Effect.suspend(() => tool.execute(input)).pipe(Effect.catchCause((cause) => {
|
|
230
246
|
if (Cause.hasInterruptsOnly(cause))
|
|
231
247
|
return Effect.interrupt;
|
|
@@ -233,21 +249,27 @@ export const make = (prepared, prototypes, maxToolCalls, hooks) => {
|
|
|
233
249
|
.map((error) => (error.cause ? Formatter.format(error) : error.message || error.name))
|
|
234
250
|
.join("\n")));
|
|
235
251
|
}));
|
|
252
|
+
// The same round trip a tool result would take through text: exact JSON.stringify semantics.
|
|
236
253
|
return yield* Effect.try({
|
|
237
|
-
try: () =>
|
|
254
|
+
try: () => {
|
|
255
|
+
const text = JSON.stringify(decodeToolOutput(tool, raw));
|
|
256
|
+
return text === undefined ? undefined : JSON.parse(text);
|
|
257
|
+
},
|
|
238
258
|
catch: (cause) => new ToolRuntimeError("InvalidToolOutput", `Invalid output from tool '${name}': ${cause}`),
|
|
239
259
|
});
|
|
240
|
-
})
|
|
260
|
+
}));
|
|
241
261
|
});
|
|
242
262
|
return {
|
|
243
263
|
calls,
|
|
264
|
+
hooks,
|
|
244
265
|
keys: (path) => namespaceKeys(root, path),
|
|
245
|
-
search: (args) => Effect.suspend(() => executeTool("search",
|
|
246
|
-
execute: (path, args) => Effect.
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
266
|
+
search: (args) => Effect.suspend(() => executeTool("search", makeSearchTool(prepared.searchIndex), args)),
|
|
267
|
+
execute: (path, args) => Effect.suspend(() => {
|
|
268
|
+
const segments = canonicalSegments(path);
|
|
269
|
+
// Models often write `tools.search(...)` for the bare `search(...)`; honor it unless a tool owns that path.
|
|
270
|
+
if (segments.length === 1 && segments[0] === "search" && lookup(root, segments) === undefined)
|
|
271
|
+
return executeTool("search", makeSearchTool(prepared.searchIndex), args);
|
|
272
|
+
return executeTool(segments.join("."), resolve(root, path), args);
|
|
251
273
|
}),
|
|
252
274
|
};
|
|
253
275
|
};
|
package/package.json
CHANGED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
2
|
-
import type { Prototypes } from "./intrinsics.js";
|
|
3
|
-
import { type AstNode } from "./model.js";
|
|
4
|
-
import { Callable, ProgramPromise } from "./objects.js";
|
|
5
|
-
export type IteratorCursor<R> = {
|
|
6
|
-
readonly next: Effect.Effect<{
|
|
7
|
-
readonly done: boolean;
|
|
8
|
-
readonly value: unknown;
|
|
9
|
-
}, unknown, R>;
|
|
10
|
-
readonly close: Effect.Effect<void, unknown, R>;
|
|
11
|
-
};
|
|
12
|
-
/** Everything a native function needs from the realm: calling back into the program and its intrinsic objects. */
|
|
13
|
-
export type Runner<R> = {
|
|
14
|
-
readonly invokeCallable: (callable: unknown, thisValue: unknown, args: Array<unknown>, node: AstNode) => Effect.Effect<unknown, unknown, R>;
|
|
15
|
-
readonly settlePromise: (promise: ProgramPromise) => Effect.Effect<unknown, unknown, never>;
|
|
16
|
-
readonly syncIterator: (value: unknown, node: AstNode) => Effect.Effect<IteratorCursor<R> | undefined, unknown, R>;
|
|
17
|
-
readonly prototypes: Prototypes;
|
|
18
|
-
};
|
|
19
|
-
export declare const preserveConsumerError: <A, R>(cursor: IteratorCursor<R>, effect: Effect.Effect<A, unknown, R>) => Effect.Effect<A, unknown, R>;
|
|
20
|
-
/**
|
|
21
|
-
* ToPrimitive: calls `valueOf`/`toString` in hint order and returns the first primitive result. Dates treat the
|
|
22
|
-
* default hint as "string", like their `Symbol.toPrimitive`.
|
|
23
|
-
*/
|
|
24
|
-
export declare const toPrimitive: <R>(runner: Runner<R>, value: unknown, hint: "number" | "string" | "default", node: AstNode) => Effect.Effect<unknown, unknown, R>;
|
|
25
|
-
export declare const toPrimitiveString: <R>(runner: Runner<R>, value: unknown, node: AstNode) => Effect.Effect<string, unknown, R>;
|
|
26
|
-
export declare const toPrimitiveNumber: <R>(runner: Runner<R>, value: unknown, node: AstNode) => Effect.Effect<number, unknown, R>;
|
|
27
|
-
export declare const isSupportedCallback: (value: unknown) => value is Callable;
|
|
28
|
-
export declare const applyCollectionCallback: <R>(runner: Runner<R>, callback: unknown, name: string, node: AstNode) => ((args: Array<unknown>) => Effect.Effect<unknown, unknown, R>);
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Program } from "acorn";
|
|
2
|
-
import { Effect } from "effect";
|
|
3
|
-
import type { Prototypes } from "./intrinsics.js";
|
|
4
|
-
import { type Host } from "./globals.js";
|
|
5
|
-
import { type Runner } from "./runner.js";
|
|
6
|
-
import { PromiseRuntime } from "./promises.js";
|
|
7
|
-
/** One program execution: the tool bridge, promise scheduler, captured logs, and the global scope built once. */
|
|
8
|
-
export declare class Runtime<R> {
|
|
9
|
-
readonly executeTool: (path: ReadonlyArray<string>, args: Array<unknown>) => Effect.Effect<unknown, unknown, R>;
|
|
10
|
-
readonly search: (args: Array<unknown>) => Effect.Effect<unknown, unknown, R>;
|
|
11
|
-
readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>;
|
|
12
|
-
readonly promises: PromiseRuntime<R>;
|
|
13
|
-
readonly prototypes: Prototypes;
|
|
14
|
-
readonly logs: Array<string>;
|
|
15
|
-
readonly runner: Runner<R>;
|
|
16
|
-
private readonly root;
|
|
17
|
-
constructor(executeTool: (path: ReadonlyArray<string>, args: Array<unknown>) => Effect.Effect<unknown, unknown, R>, search: (args: Array<unknown>) => Effect.Effect<unknown, unknown, R>, toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>, promises: PromiseRuntime<R>, prototypes: Prototypes, logs?: Array<string>, extraGlobals?: (host: Host<R>) => ReadonlyArray<readonly [string, unknown]>);
|
|
18
|
-
run(program: Program): Effect.Effect<unknown, unknown, R>;
|
|
19
|
-
}
|