@opencode/codemode 2.0.12 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/data.d.ts +5 -4
  2. package/dist/interpreter/callback.d.ts +10 -15
  3. package/dist/interpreter/callback.js +15 -7
  4. package/dist/interpreter/errors.d.ts +3 -3
  5. package/dist/interpreter/errors.js +1 -2
  6. package/dist/interpreter/execute.d.ts +2 -1
  7. package/dist/interpreter/extensions.d.ts +2 -1
  8. package/dist/interpreter/extensions.js +7 -35
  9. package/dist/interpreter/globals.d.ts +2 -1
  10. package/dist/interpreter/globals.js +1 -1
  11. package/dist/interpreter/interpreter.d.ts +8 -19
  12. package/dist/interpreter/interpreter.js +96 -98
  13. package/dist/interpreter/intrinsics.d.ts +3 -3
  14. package/dist/interpreter/intrinsics.js +2 -0
  15. package/dist/interpreter/model.d.ts +7 -8
  16. package/dist/interpreter/model.js +0 -1
  17. package/dist/interpreter/native.d.ts +5 -5
  18. package/dist/interpreter/objects.d.ts +148 -47
  19. package/dist/interpreter/objects.js +224 -26
  20. package/dist/interpreter/promises.d.ts +7 -7
  21. package/dist/interpreter/promises.js +32 -11
  22. package/dist/interpreter/references.d.ts +8 -7
  23. package/dist/interpreter/references.js +10 -46
  24. package/dist/interpreter/scope.d.ts +5 -4
  25. package/dist/stdlib/array.d.ts +2 -1
  26. package/dist/stdlib/array.js +29 -51
  27. package/dist/stdlib/bytes.js +14 -24
  28. package/dist/stdlib/collections.js +27 -30
  29. package/dist/stdlib/console.d.ts +2 -2
  30. package/dist/stdlib/console.js +6 -50
  31. package/dist/stdlib/date.js +1 -2
  32. package/dist/stdlib/headers.js +10 -22
  33. package/dist/stdlib/iterator.d.ts +1 -1
  34. package/dist/stdlib/iterator.js +294 -6
  35. package/dist/stdlib/json.js +3 -5
  36. package/dist/stdlib/math.js +5 -17
  37. package/dist/stdlib/number.js +8 -21
  38. package/dist/stdlib/object.d.ts +3 -4
  39. package/dist/stdlib/object.js +5 -16
  40. package/dist/stdlib/regexp.d.ts +3 -3
  41. package/dist/stdlib/regexp.js +1 -2
  42. package/dist/stdlib/string.js +10 -20
  43. package/dist/stdlib/url.d.ts +2 -1
  44. package/dist/stdlib/url.js +6 -16
  45. package/dist/stdlib/value.d.ts +0 -3
  46. package/dist/stdlib/value.js +2 -74
  47. package/dist/stdlib/web.js +1 -2
  48. package/dist/tool-runtime.js +38 -28
  49. package/package.json +1 -1
@@ -1,32 +1,20 @@
1
1
  import { Effect } from "effect";
2
2
  import { constants, methods } from "../interpreter/native.js";
3
3
  import { typeError } from "../interpreter/model.js";
4
- import { Obj } from "../interpreter/objects.js";
4
+ import { Obj, coerceToNumber } from "../interpreter/objects.js";
5
5
  import { preserveConsumerError } from "../interpreter/callback.js";
6
6
  // Validate only the arguments a method consumes; like JS, extras are ignored
7
7
  // (so built-ins work as callbacks receiving (element, index, array)).
8
- const number = (name, args, index) => {
9
- if (index >= args.length)
10
- return Number.NaN;
11
- const arg = args[index];
12
- if (typeof arg !== "number")
13
- throw typeError(`Math.${name} expects number arguments.`);
14
- return arg;
15
- };
16
- const unary = (name, op) => [name, 1, (_, args) => op(number(name, args, 0))];
8
+ const unary = (name, op) => [name, 1, (_, args) => op(coerceToNumber(args[0]))];
17
9
  const binary = (name, op) => [
18
10
  name,
19
11
  2,
20
- (_, args) => op(number(name, args, 0), number(name, args, 1)),
12
+ (_, args) => op(coerceToNumber(args[0]), coerceToNumber(args[1])),
21
13
  ];
22
14
  const variadic = (name, op) => [
23
15
  name,
24
16
  2,
25
- (_, args) => op(...args.map((arg) => {
26
- if (typeof arg !== "number")
27
- throw typeError(`Math.${name} expects number arguments.`);
28
- return arg;
29
- })),
17
+ (_, args) => op(...args.map(coerceToNumber)),
30
18
  ];
31
19
  export const mathGlobal = (ctx) => {
32
20
  const builtins = ctx.builtins;
@@ -91,7 +79,7 @@ export const mathGlobal = (ctx) => {
91
79
  const step = yield* cursor.next;
92
80
  if (step.done)
93
81
  return Math.sumPrecise(numbers);
94
- yield* preserveConsumerError(cursor, Effect.sync(() => {
82
+ yield* preserveConsumerError(cursor.close, Effect.sync(() => {
95
83
  if (typeof step.value !== "number") {
96
84
  throw typeError("Math.sumPrecise expects an iterable of numbers.");
97
85
  }
@@ -1,6 +1,7 @@
1
1
  import { constructor, constants, methods } from "../interpreter/native.js";
2
+ import { coerceToNumber, coerceToString } from "../interpreter/objects.js";
2
3
  import { rangeError, typeError } from "../interpreter/model.js";
3
- import { coercion, coerceToString } from "./value.js";
4
+ import { coercion } from "./value.js";
4
5
  export const numberGlobal = (ctx) => {
5
6
  const builtins = ctx.builtins;
6
7
  const number = constructor(builtins, builtins.Number, {
@@ -27,11 +28,7 @@ export const numberGlobal = (ctx) => {
27
28
  "parseInt",
28
29
  2,
29
30
  (_, args) => {
30
- const radix = args[1];
31
- if (radix !== undefined && typeof radix !== "number") {
32
- throw typeError("Number.parseInt expects a numeric radix.");
33
- }
34
- return parseInt(coerceToString(args[0]), radix);
31
+ return parseInt(coerceToString(args[0]), coerceToNumber(args[1]));
35
32
  },
36
33
  ],
37
34
  ["parseFloat", 1, (_, args) => parseFloat(coerceToString(args[0]))],
@@ -41,27 +38,17 @@ export const numberGlobal = (ctx) => {
41
38
  return thisValue;
42
39
  throw typeError(`Number.prototype.${name} requires that 'this' be a Number.`);
43
40
  };
44
- const optNum = (name, arg) => {
45
- if (arg === undefined)
46
- return undefined;
47
- if (typeof arg !== "number")
48
- throw typeError(`Number.${name} expects a number argument.`);
49
- return arg;
50
- };
41
+ const optNum = (arg) => (arg === undefined ? undefined : coerceToNumber(arg));
51
42
  methods(builtins, builtins.Number, [
52
- ["toFixed", 1, (thisValue, args) => self(thisValue, "toFixed").toFixed(optNum("toFixed", args[0]))],
43
+ ["toFixed", 1, (thisValue, args) => self(thisValue, "toFixed").toFixed(optNum(args[0]))],
53
44
  ["toLocaleString", 0, (thisValue) => self(thisValue, "toLocaleString").toLocaleString("en-US")],
54
- [
55
- "toExponential",
56
- 1,
57
- (thisValue, args) => self(thisValue, "toExponential").toExponential(optNum("toExponential", args[0])),
58
- ],
45
+ ["toExponential", 1, (thisValue, args) => self(thisValue, "toExponential").toExponential(optNum(args[0]))],
59
46
  [
60
47
  "toPrecision",
61
48
  1,
62
49
  (thisValue, args) => {
63
50
  const value = self(thisValue, "toPrecision");
64
- const digits = optNum("toPrecision", args[0]);
51
+ const digits = optNum(args[0]);
65
52
  return digits === undefined ? value.toString() : value.toPrecision(digits);
66
53
  },
67
54
  ],
@@ -70,7 +57,7 @@ export const numberGlobal = (ctx) => {
70
57
  1,
71
58
  (thisValue, args) => {
72
59
  const value = self(thisValue, "toString");
73
- const radix = optNum("toString", args[0]);
60
+ const radix = optNum(args[0]);
74
61
  if (radix !== undefined && (radix < 2 || radix > 36)) {
75
62
  throw rangeError("Number.toString radix must be between 2 and 36.");
76
63
  }
@@ -1,7 +1,6 @@
1
1
  import { type AstNode } from "../interpreter/model.js";
2
- import { Obj } from "../interpreter/objects.js";
2
+ import { Obj, type Value } from "../interpreter/objects.js";
3
3
  import type { Interpreter } from "../interpreter/interpreter.js";
4
- export declare const enumerableSource: <R>(ctx: Interpreter<R>, label: string, value: unknown, node?: AstNode) => Obj;
5
- export declare const objectAssign: <R>(ctx: Interpreter<R>, args: Array<unknown>) => unknown;
6
- export declare const classTag: (value: unknown) => string;
4
+ export declare const enumerableSource: <R>(ctx: Interpreter<R>, label: string, value: Value, node?: AstNode) => Obj;
5
+ export declare const objectAssign: <R>(ctx: Interpreter<R>, args: Array<Value>) => Value;
7
6
  export declare const objectGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
@@ -1,12 +1,11 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, methods, receiver } from "../interpreter/native.js";
3
3
  import { AsyncIteratorSymbol, invalidData, IteratorSymbol, rangeError, typeError, } from "../interpreter/model.js";
4
- import { Callable, define, entries, enumerableKeys, getOwn, hasOwn, hasPrototype, hidden, keys, own, Arr, Bytes, DateObj, ErrorObj, Obj, PromiseObj, RegExpObj, set, } from "../interpreter/objects.js";
4
+ import { define, entries, enumerableKeys, getOwn, hasOwn, hasPrototype, hidden, keys, own, Arr, Obj, PromiseObj, set, coerceToString, } from "../interpreter/objects.js";
5
5
  import { containsOpaqueReference, describeValue, rejectCircularInsertion } from "../interpreter/references.js";
6
6
  import { invoke, preserveConsumerError } from "../interpreter/callback.js";
7
7
  import { ToolReference } from "../tool-runtime.js";
8
8
  import { groupBy } from "./collections.js";
9
- import { coerceToString } from "./value.js";
10
9
  // ToObject for enumeration.
11
10
  export const enumerableSource = (ctx, label, value, node) => {
12
11
  if (value === null || value === undefined) {
@@ -57,7 +56,7 @@ const objectFromEntries = (ctx, source) => {
57
56
  const step = yield* cursor.next;
58
57
  if (step.done)
59
58
  return out;
60
- yield* preserveConsumerError(cursor, Effect.sync(() => {
59
+ yield* preserveConsumerError(cursor.close, Effect.sync(() => {
61
60
  if (!(step.value instanceof Obj) || containsOpaqueReference(step.value)) {
62
61
  throw typeError("Object.fromEntries expects [key, value] entry objects.");
63
62
  }
@@ -66,23 +65,13 @@ const objectFromEntries = (ctx, source) => {
66
65
  }
67
66
  });
68
67
  };
69
- export const classTag = (value) => {
68
+ const classTag = (value) => {
70
69
  if (value === null)
71
70
  return "Null";
72
71
  if (value === undefined)
73
72
  return "Undefined";
74
- if (value instanceof Arr)
75
- return "Array";
76
- if (value instanceof Callable)
77
- return "Function";
78
- if (value instanceof ErrorObj)
79
- return "Error";
80
- if (value instanceof DateObj)
81
- return "Date";
82
- if (value instanceof RegExpObj)
83
- return "RegExp";
84
- if (value instanceof Bytes)
85
- return "Uint8Array";
73
+ if (value instanceof Obj)
74
+ return value.tag;
86
75
  if (typeof value === "string")
87
76
  return "String";
88
77
  if (typeof value === "number")
@@ -1,7 +1,7 @@
1
1
  import type { Builtins } from "../interpreter/intrinsics.js";
2
- import { Arr, Obj, RegExpObj } from "../interpreter/objects.js";
2
+ import { Arr, Obj, RegExpObj, type Value } from "../interpreter/objects.js";
3
3
  import type { Interpreter } from "../interpreter/interpreter.js";
4
- export declare const toHostRegex: (arg: unknown, method: string, extraFlags?: string) => RegExp;
4
+ export declare const toHostRegex: (arg: Value, method: string, extraFlags?: string) => RegExp;
5
5
  export declare const matchToValue: (builtins: Builtins, match: RegExpMatchArray) => Arr;
6
- export declare const constructRegExp: (builtins: Builtins, args: Array<unknown>, proto?: Obj) => RegExpObj;
6
+ export declare const constructRegExp: (builtins: Builtins, args: Array<Value>, proto?: Obj) => RegExpObj;
7
7
  export declare const regexpGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
@@ -1,8 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, methods, prototypeFrom, receiver } from "../interpreter/native.js";
3
3
  import { syntaxError, typeError } from "../interpreter/model.js";
4
- import { define, defineAccessor, Arr, Obj, RegExpObj, record } from "../interpreter/objects.js";
5
- import { coerceToNumber, coerceToString } from "./value.js";
4
+ import { define, defineAccessor, Arr, Obj, RegExpObj, record, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
5
  const flagProperties = [
7
6
  "hasIndices",
8
7
  "global",
@@ -2,11 +2,11 @@ import { Effect } from "effect";
2
2
  import { constructor, fn, methods } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
4
4
  import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
- import { define, hidden, Arr, IteratorObj, PromiseObj, RegExpObj, record } from "../interpreter/objects.js";
5
+ import { define, hidden, Arr, hostIterator, RegExpObj, record, coerceToNumber, coerceToString, } from "../interpreter/objects.js";
6
6
  import { containsOpaqueReference, typeofValue } from "../interpreter/references.js";
7
7
  import { applyCollectionCallback, isSupportedCallback } from "../interpreter/callback.js";
8
8
  import { matchToValue, toHostRegex } from "./regexp.js";
9
- import { coerceToNumber, coerceToString, coercion } from "./value.js";
9
+ import { coercion } from "./value.js";
10
10
  // console is intercepted by the interpreter before reaching here.
11
11
  const requireDataArgument = (name, index, arg) => {
12
12
  if (containsOpaqueReference(arg)) {
@@ -23,17 +23,16 @@ const replaceWithCallback = (ctx, value, name, args) => {
23
23
  const builtins = ctx.builtins;
24
24
  const apply = applyCollectionCallback(ctx, args[1], `String.${name}`);
25
25
  const matches = [];
26
+ // The host calls back with (match, ...captures, offset, string, groups?); only groups is not already a Value.
26
27
  const collect = (...callbackArgs) => {
27
28
  const match = callbackArgs[0];
28
- const groups = callbackArgs[callbackArgs.length - 1];
29
- const hasGroups = groups !== null && typeof groups === "object";
29
+ const hasGroups = typeof callbackArgs.at(-1) === "object";
30
30
  const offset = callbackArgs[callbackArgs.length - (hasGroups ? 3 : 2)];
31
31
  if (typeof match !== "string" || typeof offset !== "number") {
32
32
  throw typeError(`String.${name} produced an invalid replacement match.`);
33
33
  }
34
- if (hasGroups)
35
- callbackArgs[callbackArgs.length - 1] = record(builtins.Object, groups);
36
- matches.push({ match, offset, args: callbackArgs });
34
+ const args = callbackArgs.map((arg) => (typeof arg === "object" ? record(builtins.Object, arg) : arg));
35
+ matches.push({ match, offset, args });
37
36
  return match;
38
37
  };
39
38
  const pattern = args[0];
@@ -57,7 +56,7 @@ const replaceWithCallback = (ctx, value, name, args) => {
57
56
  let end = 0;
58
57
  for (const match of matches) {
59
58
  const replacement = yield* apply(match.args);
60
- output.push(value.slice(end, match.offset), replacement instanceof PromiseObj ? "[object Promise]" : coerceToString(replacement));
59
+ output.push(value.slice(end, match.offset), coerceToString(replacement));
61
60
  end = match.offset + match.match.length;
62
61
  }
63
62
  output.push(value.slice(end));
@@ -74,12 +73,7 @@ export const stringGlobal = (ctx) => {
74
73
  const codeUnits = (name, op) => [
75
74
  name,
76
75
  1,
77
- (_, args) => op(...args.map((arg) => {
78
- if (typeof arg !== "number") {
79
- throw typeError(`String.${name} expects number arguments.`);
80
- }
81
- return arg;
82
- })),
76
+ (_, args) => op(...args.map(coerceToNumber)),
83
77
  ];
84
78
  methods(builtins, string, [
85
79
  codeUnits("fromCharCode", String.fromCharCode),
@@ -103,11 +97,7 @@ export const stringGlobal = (ctx) => {
103
97
  throw typeError(`String.${name} cannot take a regular expression; use regex.test(string) or String.search instead.`);
104
98
  }
105
99
  };
106
- const simple = (name, length, op) => [
107
- name,
108
- length,
109
- (thisValue, args) => op(self(thisValue, name), args),
110
- ];
100
+ const simple = (name, length, op) => [name, length, (thisValue, args) => op(self(thisValue, name), args)];
111
101
  const replace = (name) => simple(name, 2, (value, args) => {
112
102
  if (isSupportedCallback(args[1]))
113
103
  return replaceWithCallback(ctx, value, name, args);
@@ -234,6 +224,6 @@ export const stringGlobal = (ctx) => {
234
224
  return joined;
235
225
  }),
236
226
  ]);
237
- define(builtins.String, IteratorSymbol, fn(builtins, "[Symbol.iterator]", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "[Symbol.iterator]")[Symbol.iterator]())), hidden);
227
+ define(builtins.String, IteratorSymbol, fn(builtins, "[Symbol.iterator]", 0, (thisValue) => hostIterator(builtins, self(thisValue, "[Symbol.iterator]")[Symbol.iterator]())), hidden);
238
228
  return string;
239
229
  };
@@ -1,4 +1,5 @@
1
1
  import { Effect } from "effect";
2
+ import { type Value } from "../interpreter/objects.js";
2
3
  import type { Interpreter } from "../interpreter/interpreter.js";
3
4
  type UriFunction = "encodeURI" | "encodeURIComponent" | "decodeURI" | "decodeURIComponent";
4
5
  export declare const uriGlobal: <R>(ctx: Interpreter<R>, name: UriFunction) => import("../interpreter/objects.js").Native<R>;
@@ -7,6 +8,6 @@ export declare const urlGlobal: <R>(ctx: Interpreter<R>) => import("../interpret
7
8
  * Reads a synchronous iterable of `[name, value]` pairs as strings; `undefined` when `init` is not iterable. As in
8
9
  * WebIDL, the whole sequence is converted before any pair's length is checked.
9
10
  */
10
- export declare const readPairs: <R>(ctx: Interpreter<R>, init: unknown, label: string) => Effect.Effect<Array<[string, string]> | undefined, unknown, R>;
11
+ export declare const readPairs: <R>(ctx: Interpreter<R>, init: Value, label: string) => Effect.Effect<Array<[string, string]> | undefined, unknown, R>;
11
12
  export declare const urlSearchParamsGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
12
13
  export {};
@@ -1,10 +1,8 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, fn, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
3
  import { IteratorSymbol, PendingThrow, typeError, uriError } from "../interpreter/model.js";
4
- import { define, defineAccessor, entries, get, hidden, isWrapper, Arr, IteratorObj, Obj, URLObj, URLSearchParamsObj, } from "../interpreter/objects.js";
5
- import { isRuntimeReference } from "../interpreter/references.js";
4
+ import { define, defineAccessor, entries, get, hidden, Arr, hostIterator, Obj, URLObj, URLSearchParamsObj, coerceToString, isRuntimeReference, } from "../interpreter/objects.js";
6
5
  import { applyCollectionCallback, preserveConsumerError } from "../interpreter/callback.js";
7
- import { coerceToString } from "./value.js";
8
6
  const urlProperties = [
9
7
  "href",
10
8
  "origin",
@@ -107,7 +105,7 @@ const readPair = (ctx, value, label) => Effect.gen(function* () {
107
105
  const step = yield* cursor.next;
108
106
  if (step.done)
109
107
  return items;
110
- items.push(yield* preserveConsumerError(cursor, Effect.sync(() => coerceToString(step.value))));
108
+ items.push(yield* preserveConsumerError(cursor.close, Effect.sync(() => coerceToString(step.value))));
111
109
  }
112
110
  });
113
111
  /**
@@ -126,7 +124,7 @@ export const readPairs = (ctx, init, label) => Effect.gen(function* () {
126
124
  throw typeError(`${label} expects iterable [name, value] pairs.`);
127
125
  return pairs;
128
126
  }
129
- pairs.push(yield* preserveConsumerError(cursor, readPair(ctx, step.value, label)));
127
+ pairs.push(yield* preserveConsumerError(cursor.close, readPair(ctx, step.value, label)));
130
128
  }
131
129
  });
132
130
  const constructURLSearchParams = (ctx, init, proto) => {
@@ -147,8 +145,6 @@ const constructURLSearchParams = (ctx, init, proto) => {
147
145
  if (isRuntimeReference(init)) {
148
146
  throw typeError("new URLSearchParams(...) expects a query string, data object, or synchronous iterable pairs.");
149
147
  }
150
- if (isWrapper(init))
151
- return wrap(new URLSearchParams());
152
148
  if (!(init instanceof Obj)) {
153
149
  throw typeError("new URLSearchParams(...) expects a query string, data object, iterable pairs, or URLSearchParams.");
154
150
  }
@@ -237,15 +233,9 @@ export const urlSearchParamsGlobal = (ctx) => {
237
233
  return undefined;
238
234
  },
239
235
  ],
240
- ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").params.keys())],
241
- ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").params.values())],
242
- [
243
- "entries",
244
- 0,
245
- (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
246
- .params.entries()
247
- .map(([key, value]) => wrap([key, value]))),
248
- ],
236
+ ["keys", 0, (thisValue) => hostIterator(builtins, self(thisValue, "keys").params.keys())],
237
+ ["values", 0, (thisValue) => hostIterator(builtins, self(thisValue, "values").params.values())],
238
+ ["entries", 0, (thisValue) => hostIterator(builtins, self(thisValue, "entries").iterator(builtins))],
249
239
  ["toString", 0, (thisValue) => self(thisValue, "toString").params.toString()],
250
240
  [
251
241
  "forEach",
@@ -1,9 +1,6 @@
1
1
  import { type Native } from "../interpreter/objects.js";
2
2
  import type { Interpreter } from "../interpreter/interpreter.js";
3
3
  export declare const compoundOperators: Set<string>;
4
- /** The built-in string form of a value, without consulting program-defined `toString` methods. */
5
- export declare const coerceToString: (value: unknown) => string;
6
- export declare const coerceToNumber: (value: unknown) => number;
7
4
  export type Coercion = "Number" | "String" | "Boolean" | "parseInt" | "parseFloat" | "isFinite" | "isNaN";
8
5
  /** A global coercion function such as `Number` or `parseInt`. */
9
6
  export declare const coercion: <R>(ctx: Interpreter<R>, name: Coercion, length?: number) => Native<R>;
@@ -1,59 +1,6 @@
1
1
  import { fn } from "../interpreter/native.js";
2
- import { typeError } from "../interpreter/model.js";
3
- import { get, isWrapper, Arr, Bytes, DateObj, ErrorObj, MapObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, HeadersObj, } from "../interpreter/objects.js";
2
+ import { coerceToNumber, coerceToString } from "../interpreter/objects.js";
4
3
  export const compoundOperators = new Set(["+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>="]);
5
- /** The built-in string form of a value, without consulting program-defined `toString` methods. */
6
- export const coerceToString = (value) => {
7
- if (value === null)
8
- return "null";
9
- if (value === undefined)
10
- return "undefined";
11
- if (value instanceof DateObj)
12
- return Number.isFinite(value.time) ? new Date(value.time).toISOString() : "Invalid Date";
13
- if (value instanceof RegExpObj)
14
- return `/${value.regex.source}/${value.regex.flags}`;
15
- if (value instanceof MapObj)
16
- return "[object Map]";
17
- if (value instanceof SetObj)
18
- return "[object Set]";
19
- if (value instanceof URLObj)
20
- return value.url.href;
21
- if (value instanceof URLSearchParamsObj)
22
- return value.params.toString();
23
- if (value instanceof HeadersObj)
24
- return "[object Headers]";
25
- if (value instanceof Bytes)
26
- return value.bytes.join(",");
27
- if (value instanceof ErrorObj) {
28
- // Match Error.prototype.toString: "name: message", or just one when the other is empty.
29
- const name = get(value, "name");
30
- const message = get(value, "message");
31
- const shownName = typeof name === "string" ? name : "Error";
32
- const shownMessage = typeof message === "string" ? message : "";
33
- if (shownMessage === "")
34
- return shownName;
35
- if (shownName === "")
36
- return shownMessage;
37
- return `${shownName}: ${shownMessage}`;
38
- }
39
- if (value instanceof Arr) {
40
- return value.items.map((item) => (item === null || item === undefined ? "" : coerceToString(item))).join(",");
41
- }
42
- if (typeof value === "object")
43
- return "[object Object]";
44
- return String(value);
45
- };
46
- export const coerceToNumber = (value) => {
47
- if (value instanceof DateObj)
48
- return value.time;
49
- if (value instanceof Bytes)
50
- return Number(coerceToString(value));
51
- if (isWrapper(value))
52
- return Number.NaN;
53
- if (value instanceof Arr)
54
- return Number(coerceToString(value));
55
- return value !== null && typeof value === "object" ? Number.NaN : Number(value);
56
- };
57
4
  const coerce = (ctx, name, args) => {
58
5
  // Native: Number() is 0 and String() is "", unlike their undefined-argument forms; the
59
6
  // other coercers match native through the undefined-argument path below.
@@ -64,21 +11,6 @@ const coerce = (ctx, name, args) => {
64
11
  return "";
65
12
  }
66
13
  const raw = args[0];
67
- if (isWrapper(raw)) {
68
- if (name === "Boolean")
69
- return true;
70
- if (name === "Number")
71
- return coerceToNumber(raw);
72
- if (name === "String")
73
- return coerceToString(raw);
74
- if (name === "isFinite")
75
- return Number.isFinite(coerceToNumber(raw));
76
- if (name === "isNaN")
77
- return Number.isNaN(coerceToNumber(raw));
78
- if (name === "parseInt")
79
- return parseInt(coerceToString(raw));
80
- return parseFloat(coerceToString(raw));
81
- }
82
14
  if (name === "Number")
83
15
  return coerceToNumber(raw);
84
16
  if (name === "Boolean")
@@ -88,11 +20,7 @@ const coerce = (ctx, name, args) => {
88
20
  if (name === "isNaN")
89
21
  return Number.isNaN(coerceToNumber(raw));
90
22
  if (name === "parseInt") {
91
- const radix = args[1];
92
- if (radix !== undefined && typeof radix !== "number") {
93
- throw typeError("parseInt expects a numeric radix.");
94
- }
95
- return parseInt(coerceToString(raw), radix);
23
+ return parseInt(coerceToString(raw), coerceToNumber(args[1]));
96
24
  }
97
25
  if (name === "parseFloat")
98
26
  return parseFloat(coerceToString(raw));
@@ -1,8 +1,7 @@
1
1
  import { fn, methods } from "../interpreter/native.js";
2
2
  import { typeError } from "../interpreter/model.js";
3
- import { Bytes, Obj } from "../interpreter/objects.js";
3
+ import { Bytes, Obj, coerceToString } from "../interpreter/objects.js";
4
4
  import { describeValue } from "../interpreter/references.js";
5
- import { coerceToString } from "./value.js";
6
5
  // WebIDL DOMString conversion: a missing argument is a TypeError, anything else stringifies. Invalid input is a
7
6
  // TypeError as well; browsers throw a DOMException named InvalidCharacterError, which CodeMode does not have.
8
7
  export const base64Global = (ctx, name) => fn(ctx.builtins, name, 1, (_, args) => {
@@ -90,7 +90,7 @@ const describeTool = (visible) => {
90
90
  // Joining the final fragments avoids retaining the rendering's intermediate string ropes in JSC.
91
91
  return (signature ??= [
92
92
  toolExpression(visible.path),
93
- isEmptyInput(visible.tool) ? "()" : `(input: ${inputTypeScript(visible.tool, true)})`,
93
+ isEmptyInput(visible.tool) ? "()" : `(${inputTypeScript(visible.tool, true)})`,
94
94
  `: Promise<${outputTypeScript(visible.tool, true)}>`,
95
95
  ].join(""));
96
96
  },
@@ -109,6 +109,24 @@ const termForms = (term) => {
109
109
  forms.push(term.slice(0, -1));
110
110
  return forms;
111
111
  };
112
+ const rank = (entries, query) => {
113
+ const terms = tokenize(query).map(termForms);
114
+ return entries
115
+ .map((entry) => {
116
+ const path = entry.description.path.toLowerCase();
117
+ const description = entry.description.description.toLowerCase();
118
+ const score = terms.reduce((total, forms) => total +
119
+ (forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
120
+ (forms.some((form) => entry.pathWords.includes(form)) ? 12 : 0) +
121
+ (forms.some((form) => path.includes(form)) ? 8 : 0) +
122
+ (forms.some((form) => description.includes(form)) ? 4 : 0) +
123
+ (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0), 0);
124
+ return { entry, score };
125
+ })
126
+ .filter(({ score }) => terms.length === 0 || score > 0)
127
+ .sort((left, right) => right.score - left.score || compareText(left.entry.description.path, right.entry.description.path))
128
+ .map(({ entry }) => entry);
129
+ };
112
130
  const makeSearchTool = (searchIndex) => ({
113
131
  _tag: "CodeModeTool",
114
132
  description: "Search available tools",
@@ -118,33 +136,22 @@ const makeSearchTool = (searchIndex) => ({
118
136
  const request = input;
119
137
  const query = request.query ?? "";
120
138
  const offset = request.offset ?? 0;
121
- const scoped = request.namespace === undefined
139
+ let ns = request.namespace;
140
+ if (ns !== undefined && !searchIndex.some((entry) => entry.description.path.startsWith("tools."))) {
141
+ if (ns === "tools")
142
+ ns = undefined;
143
+ else if (ns.startsWith("tools."))
144
+ ns = ns.slice("tools.".length);
145
+ }
146
+ const scoped = ns === undefined
122
147
  ? searchIndex
123
- : searchIndex.filter((entry) => entry.description.path === request.namespace ||
124
- entry.description.path.startsWith(`${request.namespace}.`));
148
+ : searchIndex.filter((entry) => entry.description.path === ns || entry.description.path.startsWith(`${ns}.`));
125
149
  const trimmed = query.trim();
126
150
  const pathQuery = trimmed.startsWith("tools.") ? trimmed.slice("tools.".length) : trimmed;
127
151
  const exact = pathQuery === ""
128
152
  ? undefined
129
153
  : scoped.find((entry) => entry.description.path === pathQuery || toolExpression(entry.description.path) === trimmed);
130
- const terms = tokenize(query).map(termForms);
131
- const ranked = exact !== undefined
132
- ? [exact]
133
- : scoped
134
- .map((entry) => {
135
- const path = entry.description.path.toLowerCase();
136
- const description = entry.description.description.toLowerCase();
137
- const score = terms.reduce((total, forms) => total +
138
- (forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
139
- (forms.some((form) => entry.pathWords.includes(form)) ? 12 : 0) +
140
- (forms.some((form) => path.includes(form)) ? 8 : 0) +
141
- (forms.some((form) => description.includes(form)) ? 4 : 0) +
142
- (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0), 0);
143
- return { entry, score };
144
- })
145
- .filter(({ score }) => terms.length === 0 || score > 0)
146
- .sort((left, right) => right.score - left.score || compareText(left.entry.description.path, right.entry.description.path))
147
- .map(({ entry }) => entry);
154
+ const ranked = exact !== undefined ? [exact] : rank(scoped, query);
148
155
  const items = ranked.slice(offset, offset + (request.limit ?? defaultSearchLimit)).map(({ description }) => ({
149
156
  ...description,
150
157
  path: toolExpression(description.path),
@@ -160,7 +167,7 @@ const makeSearchTool = (searchIndex) => ({
160
167
  /** Exact callable signature of the built-in `search` function, for host-owned instructions. */
161
168
  export const searchSignature = (() => {
162
169
  const tool = makeSearchTool([]);
163
- return `search(input: ${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`;
170
+ return `search(${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`;
164
171
  })();
165
172
  const toSearchEntry = (visible) => ({
166
173
  description: describeTool(visible),
@@ -201,13 +208,16 @@ const namespaceKeys = (root, path) => {
201
208
  }
202
209
  return Array.from(node.children.keys());
203
210
  };
204
- const resolve = (root, path) => {
211
+ const resolve = (root, path, index) => {
205
212
  const segments = canonicalSegments(path);
206
213
  const node = lookup(root, segments);
207
214
  if (node === undefined) {
208
- throw new ToolRuntimeError("UnknownTool", `Unknown tool '${segments.join(".")}'.`, [
209
- "The tool may have been removed or renamed. Use search to find available tools.",
210
- ]);
215
+ const name = segments.join(".");
216
+ const ns = segments.length > 1 && root.children.has(segments[0]) ? segments[0] : undefined;
217
+ const closest = rank(ns ? index.filter((entry) => entry.description.path.startsWith(`${ns}.`)) : index, ns ? segments.slice(1).join(" ") : name)[0];
218
+ throw new ToolRuntimeError("UnknownTool", closest
219
+ ? `Unknown tool '${name}'. Did you mean ${toolExpression(closest.description.path)}?`
220
+ : `Unknown tool '${name}'.`, ["Use search to find available tools."]);
211
221
  }
212
222
  if (node.tool === undefined) {
213
223
  throw new ToolRuntimeError("UnknownTool", `Tool '${segments.join(".")}' is not callable.`);
@@ -271,7 +281,7 @@ export const make = (prepared, maxToolCalls, hooks = {}) => {
271
281
  // Models often write `tools.search(...)` for the bare `search(...)`; honor it unless a tool owns that path.
272
282
  if (segments.length === 1 && segments[0] === "search" && lookup(root, segments) === undefined)
273
283
  return executeTool("search", makeSearchTool(prepared.searchIndex), args);
274
- return executeTool(segments.join("."), resolve(root, path), args);
284
+ return executeTool(segments.join("."), resolve(root, path, prepared.searchIndex), args);
275
285
  }),
276
286
  };
277
287
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/codemode",
4
- "version": "2.0.12",
4
+ "version": "2.0.14",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",