@opencode/codemode 2.0.11 → 2.0.13

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 (50) hide show
  1. package/dist/data.d.ts +5 -4
  2. package/dist/interpreter/callback.d.ts +11 -14
  3. package/dist/interpreter/callback.js +23 -7
  4. package/dist/interpreter/errors.d.ts +3 -3
  5. package/dist/interpreter/errors.js +2 -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 +9 -20
  12. package/dist/interpreter/interpreter.js +124 -122
  13. package/dist/interpreter/intrinsics.d.ts +4 -2
  14. package/dist/interpreter/intrinsics.js +12 -0
  15. package/dist/interpreter/model.d.ts +7 -8
  16. package/dist/interpreter/model.js +3 -3
  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 +37 -52
  27. package/dist/stdlib/bytes.js +14 -24
  28. package/dist/stdlib/collections.js +52 -30
  29. package/dist/stdlib/console.d.ts +2 -2
  30. package/dist/stdlib/console.js +6 -50
  31. package/dist/stdlib/date.js +6 -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 +9 -21
  38. package/dist/stdlib/object.d.ts +3 -4
  39. package/dist/stdlib/object.js +7 -18
  40. package/dist/stdlib/regexp.d.ts +3 -3
  41. package/dist/stdlib/regexp.js +1 -2
  42. package/dist/stdlib/string.js +12 -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.d.ts +2 -0
  49. package/dist/tool-runtime.js +39 -27
  50. package/package.json +1 -1
@@ -2,10 +2,10 @@ import { Effect } from "effect";
2
2
  import { methods } from "../interpreter/native.js";
3
3
  import { applyCollectionCallback } from "../interpreter/callback.js";
4
4
  import { checkStringLength } from "../interpreter/limits.js";
5
- import { syntaxError, typeError } from "../interpreter/model.js";
5
+ import { syntaxError } from "../interpreter/model.js";
6
6
  import { typeofValue } from "../interpreter/references.js";
7
7
  import { fromJson, toJson } from "../data.js";
8
- import { get, keys, Arr, Obj, record, remove, set } from "../interpreter/objects.js";
8
+ import { get, keys, Arr, Obj, coerceToString, record, remove, set } from "../interpreter/objects.js";
9
9
  export const jsonGlobal = (ctx) => {
10
10
  const json = new Obj(ctx.builtins.Object);
11
11
  methods(ctx.builtins, json, [
@@ -15,9 +15,7 @@ export const jsonGlobal = (ctx) => {
15
15
  return json;
16
16
  };
17
17
  const parse = (ctx, args) => {
18
- const text = args[0];
19
- if (typeof text !== "string")
20
- throw typeError("JSON.parse expects a string.");
18
+ const text = coerceToString(args[0]);
21
19
  const parsed = (() => {
22
20
  try {
23
21
  return fromJson(ctx, JSON.parse(text));
@@ -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,26 +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]))],
53
- [
54
- "toExponential",
55
- 1,
56
- (thisValue, args) => self(thisValue, "toExponential").toExponential(optNum("toExponential", args[0])),
57
- ],
43
+ ["toFixed", 1, (thisValue, args) => self(thisValue, "toFixed").toFixed(optNum(args[0]))],
44
+ ["toLocaleString", 0, (thisValue) => self(thisValue, "toLocaleString").toLocaleString("en-US")],
45
+ ["toExponential", 1, (thisValue, args) => self(thisValue, "toExponential").toExponential(optNum(args[0]))],
58
46
  [
59
47
  "toPrecision",
60
48
  1,
61
49
  (thisValue, args) => {
62
50
  const value = self(thisValue, "toPrecision");
63
- const digits = optNum("toPrecision", args[0]);
51
+ const digits = optNum(args[0]);
64
52
  return digits === undefined ? value.toString() : value.toPrecision(digits);
65
53
  },
66
54
  ],
@@ -69,7 +57,7 @@ export const numberGlobal = (ctx) => {
69
57
  1,
70
58
  (thisValue, args) => {
71
59
  const value = self(thisValue, "toString");
72
- const radix = optNum("toString", args[0]);
60
+ const radix = optNum(args[0]);
73
61
  if (radix !== undefined && (radix < 2 || radix > 36)) {
74
62
  throw rangeError("Number.toString radix must be between 2 and 36.");
75
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
- import { preserveConsumerError } from "../interpreter/callback.js";
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")
@@ -161,7 +150,7 @@ export const objectGlobal = (ctx) => {
161
150
  true,
162
151
  ],
163
152
  ["toString", 0, (thisValue) => `[object ${classTag(thisValue)}]`],
164
- ["toLocaleString", 0, (thisValue) => `[object ${classTag(thisValue)}]`],
153
+ ["toLocaleString", 0, (thisValue) => invoke(ctx, thisValue, "toString", "Object.prototype.toLocaleString")],
165
154
  [
166
155
  "valueOf",
167
156
  0,
@@ -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);
@@ -130,6 +120,8 @@ export const stringGlobal = (ctx) => {
130
120
  simple("valueOf", 0, (value) => value),
131
121
  simple("toLowerCase", 0, (value) => value.toLowerCase()),
132
122
  simple("toUpperCase", 0, (value) => value.toUpperCase()),
123
+ simple("toLocaleLowerCase", 0, (value) => value.toLowerCase()),
124
+ simple("toLocaleUpperCase", 0, (value) => value.toUpperCase()),
133
125
  simple("trim", 0, (value) => value.trim()),
134
126
  simple("trimStart", 0, (value) => value.trimStart()),
135
127
  simple("trimLeft", 0, (value) => value.trimStart()),
@@ -232,6 +224,6 @@ export const stringGlobal = (ctx) => {
232
224
  return joined;
233
225
  }),
234
226
  ]);
235
- 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);
236
228
  return string;
237
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) => {
@@ -66,6 +66,8 @@ export type Prepared<R = never> = {
66
66
  };
67
67
  export type SearchEntry = {
68
68
  readonly description: ToolDescription;
69
+ /** The path split into words, so `zones` matches `get_zones` as a word rather than as a substring of `timezones`. */
70
+ readonly pathWords: ReadonlyArray<string>;
69
71
  readonly searchText: string;
70
72
  };
71
73
  /** Exact callable signature of the built-in `search` function, for host-owned instructions. */
@@ -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,32 +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) => path.includes(form)) ? 8 : 0) +
140
- (forms.some((form) => description.includes(form)) ? 4 : 0) +
141
- (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0), 0);
142
- return { entry, score };
143
- })
144
- .filter(({ score }) => terms.length === 0 || score > 0)
145
- .sort((left, right) => right.score - left.score || compareText(left.entry.description.path, right.entry.description.path))
146
- .map(({ entry }) => entry);
154
+ const ranked = exact !== undefined ? [exact] : rank(scoped, query);
147
155
  const items = ranked.slice(offset, offset + (request.limit ?? defaultSearchLimit)).map(({ description }) => ({
148
156
  ...description,
149
157
  path: toolExpression(description.path),
@@ -159,10 +167,11 @@ const makeSearchTool = (searchIndex) => ({
159
167
  /** Exact callable signature of the built-in `search` function, for host-owned instructions. */
160
168
  export const searchSignature = (() => {
161
169
  const tool = makeSearchTool([]);
162
- return `search(input: ${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`;
170
+ return `search(${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`;
163
171
  })();
164
172
  const toSearchEntry = (visible) => ({
165
173
  description: describeTool(visible),
174
+ pathWords: tokenize(visible.path),
166
175
  searchText: [
167
176
  visible.path,
168
177
  visible.tool.description,
@@ -199,13 +208,16 @@ const namespaceKeys = (root, path) => {
199
208
  }
200
209
  return Array.from(node.children.keys());
201
210
  };
202
- const resolve = (root, path) => {
211
+ const resolve = (root, path, index) => {
203
212
  const segments = canonicalSegments(path);
204
213
  const node = lookup(root, segments);
205
214
  if (node === undefined) {
206
- throw new ToolRuntimeError("UnknownTool", `Unknown tool '${segments.join(".")}'.`, [
207
- "The tool may have been removed or renamed. Use search to find available tools.",
208
- ]);
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."]);
209
221
  }
210
222
  if (node.tool === undefined) {
211
223
  throw new ToolRuntimeError("UnknownTool", `Tool '${segments.join(".")}' is not callable.`);
@@ -269,7 +281,7 @@ export const make = (prepared, maxToolCalls, hooks = {}) => {
269
281
  // Models often write `tools.search(...)` for the bare `search(...)`; honor it unless a tool owns that path.
270
282
  if (segments.length === 1 && segments[0] === "search" && lookup(root, segments) === undefined)
271
283
  return executeTool("search", makeSearchTool(prepared.searchIndex), args);
272
- return executeTool(segments.join("."), resolve(root, path), args);
284
+ return executeTool(segments.join("."), resolve(root, path, prepared.searchIndex), args);
273
285
  }),
274
286
  };
275
287
  };