@opencode/codemode 2.0.10 → 2.0.12

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 CHANGED
@@ -13,8 +13,8 @@ The idea of code mode was originally introduced by Cloudflare. See
13
13
 
14
14
  ## How it differs from JavaScript
15
15
 
16
- - **Only supported APIs are available.** Programs can use the provided tools and supported JavaScript built-ins. APIs
17
- such as `fetch`, timers, `process`, filesystem access, imports, and modules are unavailable.
16
+ - **Only supported APIs are available.** Programs can use the provided tools, supported JavaScript built-ins, and the
17
+ globals the host adds through extensions. Timers, `process`, filesystem access, imports, and modules are unavailable.
18
18
  - **Unfinished work is interrupted.** Tool calls and async functions start when called. When the program finishes,
19
19
  anything still running is interrupted. Unhandled rejections from un-awaited promises are returned as warnings.
20
20
  - **REPL-style results.** Without an explicit `return`, the final top-level expression becomes the result. `undefined`
@@ -94,11 +94,19 @@ receive `{ extension, name, args }`. An `after` hook also receives how the call
94
94
  `failure` with its error, or `interrupted`). A failing `before` hook denies the call, and the program catches the
95
95
  failure as a thrown error.
96
96
 
97
- ### `Values`
97
+ ### `Extension.make`
98
98
 
99
- `Values` exports the runtime's non-JSON value classes: `Values.URL`, `Values.URLSearchParams`, `Values.Date`,
100
- `Values.RegExp`, `Values.Map`, `Values.Set`, and `Values.Promise`. The interpreter recognizes these by class; a
101
- program's `new URL(...)` is a `Values.URL` wrapping the host `URL`. `Values.isValue` narrows to the data-like kinds.
99
+ Extensions are host functions a program calls directly as globals, such as `fetch`. Unlike tools they are not in the
100
+ catalog, not counted against `maxToolCalls`, and not described to the model; the host decides what they mean.
101
+
102
+ ```ts
103
+ const web = Extension.make({ name: "web", globals: { fetch: (url: string) => globalThis.fetch(url) } })
104
+ const runtime = CodeMode.make({ tools, extensions: [web] })
105
+ ```
106
+
107
+ Every value crossing in either direction is converted, never shared: arguments come in as copies, results go out as
108
+ copies, and a function inside a result is callable the same way. A global that shadows a built-in or another
109
+ extension throws at `CodeMode.make`.
102
110
 
103
111
  ### OpenAPI tools
104
112
 
package/dist/data.js CHANGED
@@ -2,7 +2,7 @@ export * as Data from "./data.js";
2
2
  import { Effect } from "effect";
3
3
  import { MAX_VALUE_DEPTH } from "./interpreter/limits.js";
4
4
  import { invalidData, rangeError, typeError } from "./interpreter/model.js";
5
- import { Arr, Bytes, Callable, define, ErrorObj, get, keys, Obj, PromiseObj, record, SetObj, URLSearchParamsObj, } from "./interpreter/objects.js";
5
+ import { Arr, Bytes, Callable, define, ErrorObj, get, keys, Obj, PromiseObj, record, SetObj, URLSearchParamsObj, HeadersObj, } from "./interpreter/objects.js";
6
6
  import { typeofValue } from "./interpreter/references.js";
7
7
  /**
8
8
  * What `JSON.stringify` would serialize for a program value, as host JSON: `toJSON` is honored, functions and
@@ -42,6 +42,8 @@ const walk = (ctx, value, replacer, boundary) => {
42
42
  }
43
43
  if (boundary && value instanceof URLSearchParamsObj)
44
44
  return value.params.toString();
45
+ if (value instanceof HeadersObj)
46
+ return Object.fromEntries(value.headers);
45
47
  const target = boundary && value instanceof SetObj ? new Arr(ctx.builtins.Array, [...value.set]) : value;
46
48
  if (stack.has(target))
47
49
  throw typeError("Converting circular structure to JSON.");
@@ -14,6 +14,8 @@ export declare const preserveConsumerError: <A, R>(cursor: IteratorCursor<R>, ef
14
14
  * default hint as "string", like their `Symbol.toPrimitive`.
15
15
  */
16
16
  export declare const toPrimitive: <R>(ctx: Interpreter<R>, value: unknown, hint: "number" | "string" | "default") => Effect.Effect<unknown, unknown, R>;
17
+ /** Invoke(value, name): calls the method the value would find through its prototype. */
18
+ export declare const invoke: <R>(ctx: Interpreter<R>, value: unknown, name: string, label: string) => Effect.Effect<unknown, unknown, R>;
17
19
  export declare const toPrimitiveString: <R>(ctx: Interpreter<R>, value: unknown) => Effect.Effect<string, unknown, R>;
18
20
  export declare const toPrimitiveNumber: <R>(ctx: Interpreter<R>, value: unknown) => Effect.Effect<number, unknown, R>;
19
21
  export declare const isSupportedCallback: (value: unknown) => value is Callable;
@@ -1,5 +1,6 @@
1
1
  import { Effect, Exit } from "effect";
2
2
  import { coerceToNumber, coerceToString } from "../stdlib/value.js";
3
+ import { primitivePrototype } from "./intrinsics.js";
3
4
  import { typeError } from "./model.js";
4
5
  import { Callable, get, Native, DateObj, Obj } from "./objects.js";
5
6
  import { typeofValue } from "./references.js";
@@ -27,6 +28,13 @@ export const toPrimitive = (ctx, value, hint) => {
27
28
  throw typeError("Cannot convert object to primitive value.");
28
29
  });
29
30
  };
31
+ /** Invoke(value, name): calls the method the value would find through its prototype. */
32
+ export const invoke = (ctx, value, name, label) => {
33
+ const target = value instanceof Obj ? value : primitivePrototype(ctx.builtins, value);
34
+ if (target === undefined)
35
+ throw typeError(`${label} called on null or undefined.`);
36
+ return ctx.call(get(target, name), value, []);
37
+ };
30
38
  export const toPrimitiveString = (ctx, value) => Effect.map(toPrimitive(ctx, value, "string"), coerceToString);
31
39
  export const toPrimitiveNumber = (ctx, value) => Effect.map(toPrimitive(ctx, value, "number"), coerceToNumber);
32
40
  // The single acceptance list for callbacks: collections, sort, string replacers,
@@ -150,6 +150,7 @@ export const errorGlobal = (type, ctx) => {
150
150
  methods(builtins, prototype, [
151
151
  ["toString", 0, (thisValue) => errorToString(receiver(Obj, thisValue, "Error.prototype.toString"))],
152
152
  ]);
153
+ methods(builtins, ctor, [["isError", 1, (_, args) => args[0] instanceof ErrorObj]]);
153
154
  }
154
155
  return ctor;
155
156
  };
@@ -5,7 +5,7 @@ import { createErrorValue, isErrorType } from "./intrinsics.js";
5
5
  import { MAX_VALUE_DEPTH } from "./limits.js";
6
6
  import { PendingThrow, Throw, typeError } from "./model.js";
7
7
  import { fn } from "./native.js";
8
- import { Callable, define, entries, get, has, hidden, keys, Arr, Bytes, DateObj, ErrorObj, GeneratorObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, } from "./objects.js";
8
+ import { Callable, define, entries, get, has, hidden, keys, Arr, Bytes, DateObj, ErrorObj, GeneratorObj, IteratorObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, HeadersObj, } from "./objects.js";
9
9
  import { describeValue } from "./references.js";
10
10
  /**
11
11
  * The global bindings of one run's extensions. Everything crossing the boundary is converted: plain data and
@@ -32,6 +32,8 @@ export const extensionGlobals = (ctx, extensions) => {
32
32
  return new URL(value.url.href);
33
33
  if (value instanceof URLSearchParamsObj)
34
34
  return new URLSearchParams(value.params);
35
+ if (value instanceof HeadersObj)
36
+ return new Headers(value.headers);
35
37
  const next = (item) => toHost(item, label, depth + 1, seen);
36
38
  if (value instanceof MapObj)
37
39
  return new Map([...value.map].map(([key, item]) => [next(key), next(item)]));
@@ -40,6 +42,7 @@ export const extensionGlobals = (ctx, extensions) => {
40
42
  if (!(value instanceof Obj) ||
41
43
  value instanceof Callable ||
42
44
  value instanceof GeneratorObj ||
45
+ value instanceof IteratorObj ||
43
46
  value instanceof PromiseObj) {
44
47
  throw typeError(`${label} contains ${describeValue(value)}, which cannot be passed to an extension.`);
45
48
  }
@@ -114,6 +117,8 @@ export const extensionGlobals = (ctx, extensions) => {
114
117
  if (value instanceof URLSearchParams) {
115
118
  return new URLSearchParamsObj(builtins.URLSearchParams, new URLSearchParams(value));
116
119
  }
120
+ if (value instanceof Headers)
121
+ return new HeadersObj(builtins.Headers, new Headers(value));
117
122
  if (value instanceof Map) {
118
123
  const wrapped = new MapObj(builtins.Map);
119
124
  for (const [key, item] of value)
@@ -11,6 +11,8 @@ import { objectGlobal } from "../stdlib/object.js";
11
11
  import { regexpGlobal } from "../stdlib/regexp.js";
12
12
  import { stringGlobal } from "../stdlib/string.js";
13
13
  import { uriGlobal, urlGlobal, urlSearchParamsGlobal } from "../stdlib/url.js";
14
+ import { headersGlobal } from "../stdlib/headers.js";
15
+ import { iteratorGlobals } from "../stdlib/iterator.js";
14
16
  import { coercion } from "../stdlib/value.js";
15
17
  import { base64Global, cryptoGlobal } from "../stdlib/web.js";
16
18
  import { ToolReference } from "../tool-runtime.js";
@@ -71,6 +73,7 @@ const table = {
71
73
  Set: (ctx) => setGlobal(ctx),
72
74
  URL: (ctx) => urlGlobal(ctx),
73
75
  URLSearchParams: (ctx) => urlSearchParamsGlobal(ctx),
76
+ Headers: (ctx) => headersGlobal(ctx),
74
77
  Uint8Array: (ctx) => uint8ArrayGlobal(ctx),
75
78
  TextEncoder: (ctx) => textEncoderGlobal(ctx),
76
79
  TextDecoder: (ctx) => textDecoderGlobal(ctx),
@@ -88,5 +91,6 @@ export const globalNames = new Set(Object.keys(table));
88
91
  /** The immutable global bindings of every program, in declaration order. */
89
92
  export const globals = (ctx) => {
90
93
  generatorGlobals(ctx);
94
+ iteratorGlobals(ctx);
91
95
  return Object.entries(table).map(([name, factory]) => [name, factory(ctx)]);
92
96
  };
@@ -2,7 +2,7 @@ import type { Program } from "acorn";
2
2
  import { Effect } from "effect";
3
3
  import { type Json } from "../data.js";
4
4
  import { type ToolRuntime } from "../tool-runtime.js";
5
- import type { Builtins } from "./intrinsics.js";
5
+ import { type Builtins } from "./intrinsics.js";
6
6
  import { PromiseObj } from "./objects.js";
7
7
  import { Pending } from "./promises.js";
8
8
  /** One program execution: the tool bridge, promise scheduler, captured logs, and the global scope built once. */
@@ -23,6 +23,12 @@ export declare class Interpreter<R> {
23
23
  call(callable: unknown, thisValue: unknown, args: Array<unknown>): Effect.Effect<unknown, unknown, R>;
24
24
  await(promise: PromiseObj): Effect.Effect<unknown, unknown, never>;
25
25
  iterate(value: unknown): Effect.Effect<{
26
+ next: Effect.Effect<{
27
+ done: boolean;
28
+ value: any;
29
+ }, never, never>;
30
+ close: Effect.Effect<void, never, never>;
31
+ }, never, never> | Effect.Effect<{
26
32
  next: Effect.Effect<{
27
33
  done: boolean;
28
34
  value: unknown;
@@ -4,8 +4,9 @@ import { ToolReference } from "../tool-runtime.js";
4
4
  import { AsyncIteratorSymbol, CallSite, GeneratorReturn, IteratorSymbol, OptionalShortCircuit, invalidData, Throw, rangeError, typeError, unsupportedSyntax, } from "./model.js";
5
5
  import { checkStringLength } from "./limits.js";
6
6
  import { locate, materialize } from "./errors.js";
7
+ import { primitivePrototype } from "./intrinsics.js";
7
8
  import { globals } from "./globals.js";
8
- import { assign, Callable, define, get, has, hasPrototype, keys, Native, parseArrayIndex, Arr, Bytes, DateObj, Fn, GeneratorObj, MapObj, Obj, PromiseObj, SetObj, URLSearchParamsObj, record, remove, set, } from "./objects.js";
9
+ import { assign, Callable, define, get, has, hasPrototype, keys, Native, parseArrayIndex, Arr, Bytes, DateObj, Fn, GeneratorObj, IteratorObj, MapObj, Obj, PromiseObj, SetObj, URLSearchParamsObj, HeadersObj, record, remove, set, } from "./objects.js";
9
10
  import { preserveConsumerError } from "./callback.js";
10
11
  import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
11
12
  import { containsOpaqueReference, describeValue, rejectCircularInsertion, typeofValue } from "./references.js";
@@ -27,21 +28,27 @@ const loopExit = (result, labels) => {
27
28
  return result;
28
29
  return undefined;
29
30
  };
30
- const calleeDescription = (callee) => {
31
- if (callee?.type === "Identifier")
32
- return callee.name;
33
- if (callee?.type === "MemberExpression") {
34
- const object = callee.object;
35
- const property = callee.property;
36
- const key = !callee.computed && property.type === "Identifier"
37
- ? property.name
38
- : property.type === "Literal" && typeof property.value === "string"
39
- ? property.value
40
- : undefined;
41
- if (object.type === "Identifier" && key !== undefined)
42
- return `${object.name}.${key}`;
43
- }
44
- return "The called value";
31
+ // Native engines name the callee (`search(...).catch is not a function`), including call chains. Returns
32
+ // undefined when a link cannot be named, so a chain is either named completely or not at all.
33
+ const calleeDescription = (node) => {
34
+ if (node?.type === "Identifier")
35
+ return node.name;
36
+ if (node?.type === "CallExpression") {
37
+ const target = calleeDescription(node.callee);
38
+ return target === undefined ? undefined : `${target}(...)`;
39
+ }
40
+ if (node?.type !== "MemberExpression")
41
+ return undefined;
42
+ const property = node.property;
43
+ const key = !node.computed && property.type === "Identifier"
44
+ ? property.name
45
+ : property.type === "Literal" && typeof property.value === "string"
46
+ ? property.value
47
+ : undefined;
48
+ if (key === undefined)
49
+ return undefined;
50
+ const object = calleeDescription(node.object);
51
+ return object === undefined ? undefined : `${object}.${key}`;
45
52
  };
46
53
  // OrdinaryHasInstance: walk the left operand's chain looking for the constructor's `prototype`.
47
54
  const instanceofValue = (lhs, rhs, node) => {
@@ -477,10 +484,10 @@ class Frame {
477
484
  if (declared?.lexical)
478
485
  self.predeclarePattern(declared.pattern, declared.mutable, left);
479
486
  const right = yield* self.evaluateExpression(node.right);
480
- const iterator = yield* self.customIterator(right, node, awaiting);
481
- const cursor = iterator === undefined ? yield* self.iterate(right, node) : undefined;
487
+ const cursor = self.hostCursor(right);
488
+ const iterator = cursor === undefined ? yield* self.customIterator(right, node, awaiting) : undefined;
482
489
  if (iterator === undefined && cursor === undefined) {
483
- throw invalidData(`${awaiting ? "for await...of" : "for...of"} requires an array, string, Map, Set, or URLSearchParams, or custom iterator value.`, node);
490
+ throw invalidData(`${awaiting ? "for await...of" : "for...of"} requires an array, string, Map, Set, URLSearchParams, or Headers, or custom iterator value.`, node);
484
491
  }
485
492
  const close = () => iterator
486
493
  ? self.closeIterator(iterator, node, awaiting)
@@ -550,6 +557,18 @@ class Frame {
550
557
  });
551
558
  }
552
559
  iterate(value, node) {
560
+ const cursor = this.hostCursor(value);
561
+ if (cursor !== undefined)
562
+ return Effect.succeed(cursor);
563
+ const self = this;
564
+ return Effect.map(this.customIterator(value, node, false), (iterator) => iterator === undefined
565
+ ? undefined
566
+ : {
567
+ next: self.nextIteratorResult(iterator, node, false),
568
+ close: Effect.suspend(() => self.closeIterator(iterator, node, false)),
569
+ });
570
+ }
571
+ hostCursor(value) {
553
572
  const iterator = value instanceof Arr
554
573
  ? value.items[Symbol.iterator]()
555
574
  : typeof value === "string"
@@ -560,29 +579,26 @@ class Frame {
560
579
  ? value.set.values()
561
580
  : value instanceof URLSearchParamsObj
562
581
  ? value.params.entries()
563
- : value instanceof Bytes
564
- ? value.bytes.values()
565
- : undefined;
566
- if (iterator !== undefined) {
567
- const proto = this.ctx.builtins.Array;
568
- return Effect.succeed({
569
- next: Effect.sync(() => {
570
- const step = iterator.next();
571
- return {
572
- done: Boolean(step.done),
573
- value: Array.isArray(step.value) ? new Arr(proto, step.value) : step.value,
574
- };
575
- }),
576
- close: Effect.void,
577
- });
578
- }
579
- const self = this;
580
- return Effect.map(this.customIterator(value, node, false), (iterator) => iterator === undefined
581
- ? undefined
582
- : {
583
- next: self.nextIteratorResult(iterator, node, false),
584
- close: Effect.suspend(() => self.closeIterator(iterator, node, false)),
585
- });
582
+ : value instanceof HeadersObj
583
+ ? value.headers.entries()
584
+ : value instanceof Bytes
585
+ ? value.bytes.values()
586
+ : value instanceof IteratorObj
587
+ ? value.iterator
588
+ : undefined;
589
+ if (iterator === undefined)
590
+ return undefined;
591
+ const proto = this.ctx.builtins.Array;
592
+ return {
593
+ next: Effect.sync(() => {
594
+ const step = iterator.next();
595
+ return {
596
+ done: Boolean(step.done),
597
+ value: Array.isArray(step.value) ? new Arr(proto, step.value) : step.value,
598
+ };
599
+ }),
600
+ close: Effect.void,
601
+ };
586
602
  }
587
603
  customIterator(value, node, allowAsync = true) {
588
604
  if (!(value instanceof Obj))
@@ -1016,7 +1032,7 @@ class Frame {
1016
1032
  // `new` itself is supported, so a non-constructible callee is a TypeError like JS rather than
1017
1033
  // unsupported syntax. Built-ins like Number are real constructors in JS, so do not claim
1018
1034
  // otherwise; say `new` is unsupported for them and point at the plain call.
1019
- const name = calleeDescription(node.callee);
1035
+ const name = calleeDescription(node.callee) ?? "The called value";
1020
1036
  const message = callee instanceof Fn
1021
1037
  ? `${name} cannot be constructed: user-defined constructors and classes are not supported. Call it as a function that returns a plain object instead.`
1022
1038
  : callee instanceof Native
@@ -1303,7 +1319,7 @@ class Frame {
1303
1319
  if (callable instanceof Native) {
1304
1320
  return yield* self.native(() => callable.call(thisValue, args), node);
1305
1321
  }
1306
- throw typeError(`${calleeDescription(callee)} is not a function.`, callee ?? node);
1322
+ throw typeError(`${calleeDescription(callee) ?? "The called value"} is not a function.`, callee ?? node);
1307
1323
  });
1308
1324
  }
1309
1325
  // Built-ins throw without a location, synchronously or inside their Effect; the call site supplies it.
@@ -1513,6 +1529,7 @@ class Frame {
1513
1529
  value instanceof MapObj ||
1514
1530
  value instanceof SetObj ||
1515
1531
  value instanceof URLSearchParamsObj ||
1532
+ value instanceof HeadersObj ||
1516
1533
  value instanceof Bytes) {
1517
1534
  const cursor = yield* self.iterate(value, node);
1518
1535
  if (!cursor)
@@ -1702,20 +1719,17 @@ class Frame {
1702
1719
  }
1703
1720
  if (objectValue instanceof Obj)
1704
1721
  return { target: objectValue, key, receiver: objectValue };
1705
- // Primitives read through their wrapper prototype without being boxed; strings own length and indexes.
1706
- const builtins = self.ctx.builtins;
1722
+ // Strings own length and indexes; every other primitive property reads through the wrapper prototype.
1707
1723
  if (typeof objectValue === "string") {
1708
1724
  if (key === "length")
1709
1725
  return { value: objectValue.length };
1710
1726
  const index = typeof key === "symbol" ? undefined : parseArrayIndex(key);
1711
1727
  if (index !== undefined)
1712
1728
  return { value: objectValue[index] };
1713
- return { target: builtins.String, key, receiver: objectValue };
1714
1729
  }
1715
- if (typeof objectValue === "number")
1716
- return { target: builtins.Number, key, receiver: objectValue };
1717
- if (typeof objectValue === "boolean")
1718
- return { target: builtins.Boolean, key, receiver: objectValue };
1730
+ const proto = primitivePrototype(self.ctx.builtins, objectValue);
1731
+ if (proto !== undefined)
1732
+ return { target: proto, key, receiver: objectValue };
1719
1733
  if (objectValue === null || objectValue === undefined) {
1720
1734
  throw typeError(`Cannot read properties of ${objectValue} (reading '${String(key)}').`, objectNode);
1721
1735
  }
@@ -2,12 +2,14 @@ import { ErrorObj, Obj } from "./objects.js";
2
2
  export declare const errorTypes: readonly ["Error", "TypeError", "RangeError", "SyntaxError", "ReferenceError", "EvalError", "URIError", "AggregateError"];
3
3
  export type ErrorType = (typeof errorTypes)[number];
4
4
  export declare const isErrorType: (name: string) => name is ErrorType;
5
- declare const builtins: readonly ["Object", "Function", "Array", "String", "Number", "Boolean", "Date", "RegExp", "Map", "Set", "URL", "URLSearchParams", "Uint8Array", "TextEncoder", "TextDecoder", "Promise", "Iterator", "AsyncIterator", "Generator", "AsyncGenerator"];
5
+ declare const builtins: readonly ["Object", "Function", "Array", "String", "Number", "Boolean", "Date", "RegExp", "Map", "Set", "URL", "URLSearchParams", "Headers", "Uint8Array", "TextEncoder", "TextDecoder", "Promise", "Iterator", "AsyncIterator", "Generator", "AsyncGenerator"];
6
6
  /**
7
7
  * The built-in prototype objects of one runtime, allocated empty in dependency order. The globals populate them
8
8
  * and attach their constructors when the runtime is built.
9
9
  */
10
10
  export type Builtins = Readonly<Record<(typeof builtins)[number] | ErrorType, Obj>>;
11
11
  export declare const createErrorValue: (prototype: Obj, message: string | undefined) => ErrorObj;
12
+ /** The prototype a primitive reads its methods from without being boxed; none for null, undefined, and symbols. */
13
+ export declare const primitivePrototype: (builtins: Builtins, value: unknown) => Obj | undefined;
12
14
  export declare const createBuiltins: () => Builtins;
13
15
  export {};
@@ -24,6 +24,7 @@ const builtins = [
24
24
  "Set",
25
25
  "URL",
26
26
  "URLSearchParams",
27
+ "Headers",
27
28
  "Uint8Array",
28
29
  "TextEncoder",
29
30
  "TextDecoder",
@@ -39,6 +40,16 @@ export const createErrorValue = (prototype, message) => {
39
40
  define(value, "message", message, hidden);
40
41
  return value;
41
42
  };
43
+ /** The prototype a primitive reads its methods from without being boxed; none for null, undefined, and symbols. */
44
+ export const primitivePrototype = (builtins, value) => {
45
+ if (typeof value === "string")
46
+ return builtins.String;
47
+ if (typeof value === "number")
48
+ return builtins.Number;
49
+ if (typeof value === "boolean")
50
+ return builtins.Boolean;
51
+ return undefined;
52
+ };
42
53
  export const createBuiltins = () => {
43
54
  const object = new Obj(null);
44
55
  // Function.prototype is itself callable and returns undefined.
@@ -68,6 +79,7 @@ export const createBuiltins = () => {
68
79
  Set: plain(),
69
80
  URL: plain(),
70
81
  URLSearchParams: plain(),
82
+ Headers: plain(),
71
83
  Uint8Array: plain(),
72
84
  TextEncoder: plain(),
73
85
  TextDecoder: plain(),
@@ -52,9 +52,10 @@ export const uriError = failure("URIError");
52
52
  export const supportedSyntaxMessage = "This is a restricted JavaScript-like language. Supported: plain and async functions, data literals, destructuring, standard control flow, await and Promise, and built-ins such as Array, Object, Math, JSON, Date, RegExp, Map, Set, and URL. Unsupported: classes, this, getters/setters, tagged templates, BigInt, and custom Symbols. Use plain functions and data objects instead.";
53
53
  export const unsupportedSyntax = (kind, node) => new PendingThrow("SyntaxError", `Syntax '${kind}' is not supported. ${supportedSyntaxMessage}`, node, "UnsupportedSyntax", [supportedSyntaxMessage]);
54
54
  export const isRecord = (value) => typeof value === "object" && value !== null;
55
+ // Acorn lines are 1-based and its columns are 0-based. Diagnostics use 1-based columns of the submitted source.
55
56
  export const sourceLocation = (node) => ({
56
- line: Math.max(1, (node.loc?.start.line ?? 2) - 1),
57
- column: Math.max(1, (node.loc?.start.column ?? 4) - 3),
57
+ line: node.loc?.start.line ?? 1,
58
+ column: (node.loc?.start.column ?? 0) + 1,
58
59
  });
59
60
  export const formatLocation = (node) => {
60
61
  if (!node?.loc)
@@ -81,6 +81,11 @@ export declare class GeneratorObj extends Obj {
81
81
  readonly request: (kind: GeneratorRequestKind, value: unknown) => Effect.Effect<unknown, unknown, unknown>;
82
82
  constructor(proto: Obj, asynchronous: boolean, request: (kind: GeneratorRequestKind, value: unknown) => Effect.Effect<unknown, unknown, unknown>);
83
83
  }
84
+ /** A built-in collection iterator: live over the host collection, yielding program values. */
85
+ export declare class IteratorObj extends Obj {
86
+ readonly iterator: IteratorObject<unknown>;
87
+ constructor(proto: Obj, iterator: IteratorObject<unknown>);
88
+ }
84
89
  export declare class DateObj extends Obj {
85
90
  time: number;
86
91
  constructor(proto: Obj, time: number);
@@ -99,6 +104,10 @@ export declare class URLSearchParamsObj extends Obj {
99
104
  readonly params: URLSearchParams;
100
105
  constructor(proto: Obj, params: URLSearchParams);
101
106
  }
107
+ export declare class HeadersObj extends Obj {
108
+ readonly headers: Headers;
109
+ constructor(proto: Obj, headers: Headers);
110
+ }
102
111
  export declare class URLObj extends Obj {
103
112
  readonly url: URL;
104
113
  readonly searchParams: URLSearchParamsObj;
@@ -110,7 +119,7 @@ export declare class Bytes extends Obj {
110
119
  constructor(proto: Obj, bytes: Uint8Array);
111
120
  }
112
121
  /** Built-in objects that wrap a host value; data-like, but never plain data. */
113
- export declare const isWrapper: (value: unknown) => value is DateObj | RegExpObj | MapObj | SetObj | URLObj | URLSearchParamsObj | Bytes;
122
+ export declare const isWrapper: (value: unknown) => value is DateObj | RegExpObj | MapObj | SetObj | URLObj | URLSearchParamsObj | HeadersObj | Bytes;
114
123
  export declare const parseArrayIndex: (key: string | number) => number | undefined;
115
124
  /** The own property under `key`, including an array's live indexes and `length`. */
116
125
  export declare const own: (target: Obj, key: PropertyKey) => Slot | undefined;
@@ -78,6 +78,14 @@ export class GeneratorObj extends Obj {
78
78
  this.request = request;
79
79
  }
80
80
  }
81
+ /** A built-in collection iterator: live over the host collection, yielding program values. */
82
+ export class IteratorObj extends Obj {
83
+ iterator;
84
+ constructor(proto, iterator) {
85
+ super(proto);
86
+ this.iterator = iterator;
87
+ }
88
+ }
81
89
  export class DateObj extends Obj {
82
90
  time;
83
91
  constructor(proto, time) {
@@ -105,6 +113,13 @@ export class URLSearchParamsObj extends Obj {
105
113
  this.params = params;
106
114
  }
107
115
  }
116
+ export class HeadersObj extends Obj {
117
+ headers;
118
+ constructor(proto, headers) {
119
+ super(proto);
120
+ this.headers = headers;
121
+ }
122
+ }
108
123
  export class URLObj extends Obj {
109
124
  url;
110
125
  searchParams;
@@ -129,6 +144,7 @@ export const isWrapper = (value) => value instanceof DateObj ||
129
144
  value instanceof SetObj ||
130
145
  value instanceof URLObj ||
131
146
  value instanceof URLSearchParamsObj ||
147
+ value instanceof HeadersObj ||
132
148
  value instanceof Bytes;
133
149
  const MAX_ARRAY_INDEX = 4_294_967_295;
134
150
  export const parseArrayIndex = (key) => {
@@ -1,9 +1,10 @@
1
1
  import { ToolReference } from "../tool-runtime.js";
2
2
  import { invalidData } from "./model.js";
3
- import { Callable, getOwn, isWrapper, ownKeys, Arr, Bytes, DateObj, GeneratorObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, } from "./objects.js";
3
+ import { Callable, getOwn, isWrapper, ownKeys, Arr, Bytes, DateObj, GeneratorObj, IteratorObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, HeadersObj, } from "./objects.js";
4
4
  /** Values that cannot cross the data boundary. */
5
5
  export const isRuntimeReference = (value) => value instanceof Callable ||
6
6
  value instanceof GeneratorObj ||
7
+ value instanceof IteratorObj ||
7
8
  value instanceof ToolReference ||
8
9
  value instanceof PromiseObj ||
9
10
  isWrapper(value);
@@ -63,10 +64,14 @@ export const describeValue = (value) => {
63
64
  return "a URL";
64
65
  if (value instanceof URLSearchParamsObj)
65
66
  return "a URLSearchParams";
67
+ if (value instanceof HeadersObj)
68
+ return "a Headers";
66
69
  if (value instanceof Bytes)
67
70
  return "a Uint8Array";
68
71
  if (value instanceof GeneratorObj)
69
72
  return "a generator";
73
+ if (value instanceof IteratorObj)
74
+ return "an iterator";
70
75
  if (isRuntimeReference(value))
71
76
  return "a function";
72
77
  if (typeof value === "object")
@@ -1,10 +1,10 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, methods, prototypeFrom, receiver } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength, MAX_ARRAY_LENGTH } from "../interpreter/limits.js";
4
- import { invalidData, rangeError, typeError } from "../interpreter/model.js";
5
- import { get, Arr, GeneratorObj, Obj } from "../interpreter/objects.js";
4
+ import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
+ import { define, get, hidden, Arr, GeneratorObj, IteratorObj, Obj } from "../interpreter/objects.js";
6
6
  import { describeValue, rejectCircularInsertion } from "../interpreter/references.js";
7
- import { applyCollectionCallback, preserveConsumerError } from "../interpreter/callback.js";
7
+ import { applyCollectionCallback, invoke, preserveConsumerError } from "../interpreter/callback.js";
8
8
  import { compareText } from "../tool-runtime.js";
9
9
  import { coerceToNumber, coerceToString } from "./value.js";
10
10
  const arrayLikeSource = (source) => {
@@ -317,12 +317,21 @@ export const arrayGlobal = (ctx) => {
317
317
  return target;
318
318
  },
319
319
  ],
320
- ["keys", 0, (thisValue) => wrap(Array.from(self(thisValue, "keys").items.keys()))],
321
- ["values", 0, (thisValue) => wrap([...self(thisValue, "values").items])],
320
+ [
321
+ "toLocaleString",
322
+ 0,
323
+ (thisValue) => Effect.map(Effect.forEach(self(thisValue, "toLocaleString").items, (item) => item === null || item === undefined
324
+ ? Effect.succeed("")
325
+ : Effect.map(invoke(ctx, item, "toLocaleString", "Array.prototype.toLocaleString"), coerceToString)), (parts) => parts.join(",")),
326
+ ],
327
+ ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").items.keys())],
328
+ ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").items.values())],
322
329
  [
323
330
  "entries",
324
331
  0,
325
- (thisValue) => wrap(Array.from(self(thisValue, "entries").items.entries(), ([index, item]) => wrap([index, item]))),
332
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
333
+ .items.entries()
334
+ .map(([index, item]) => wrap([index, item]))),
326
335
  ],
327
336
  iterate("map", 1, (target, receiver, apply) => Effect.gen(function* () {
328
337
  const length = target.length;
@@ -461,5 +470,6 @@ export const arrayGlobal = (ctx) => {
461
470
  return accumulator;
462
471
  })),
463
472
  ]);
473
+ define(proto, IteratorSymbol, get(proto, "values"), hidden);
464
474
  return array;
465
475
  };
@@ -1,8 +1,8 @@
1
1
  import { Effect } from "effect";
2
2
  import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
3
3
  import { constructor, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
4
- import { rangeError, syntaxError, typeError } from "../interpreter/model.js";
5
- import { defineAccessor, get, Arr, Bytes, Obj } from "../interpreter/objects.js";
4
+ import { IteratorSymbol, rangeError, syntaxError, typeError } from "../interpreter/model.js";
5
+ import { define, defineAccessor, get, hidden, Arr, Bytes, IteratorObj, Obj } from "../interpreter/objects.js";
6
6
  import { describeValue } from "../interpreter/references.js";
7
7
  import { coerceToNumber, coerceToString } from "./value.js";
8
8
  /** The bytes a Uint8Array, array, or other iterable of numbers describes; the host array clamps each value. */
@@ -152,14 +152,17 @@ export const uint8ArrayGlobal = (ctx) => {
152
152
  ["toString", 0, (thisValue) => self(thisValue, "toString").bytes.join(",")],
153
153
  ["toBase64", 0, (thisValue) => self(thisValue, "toBase64").bytes.toBase64()],
154
154
  ["toHex", 0, (thisValue) => self(thisValue, "toHex").bytes.toHex()],
155
- ["keys", 0, (thisValue) => wrapAll(Array.from(self(thisValue, "keys").bytes.keys()))],
156
- ["values", 0, (thisValue) => wrapAll(Array.from(self(thisValue, "values").bytes.values()))],
155
+ ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").bytes.keys())],
156
+ ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").bytes.values())],
157
157
  [
158
158
  "entries",
159
159
  0,
160
- (thisValue) => wrapAll(Array.from(self(thisValue, "entries").bytes.entries(), ([index, byte]) => wrapAll([index, byte]))),
160
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
161
+ .bytes.entries()
162
+ .map(([index, byte]) => wrapAll([index, byte]))),
161
163
  ],
162
164
  ]);
165
+ define(proto, IteratorSymbol, get(proto, "values"), hidden);
163
166
  return uint8Array;
164
167
  };
165
168
  export const textEncoderGlobal = (ctx) => {
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, fn, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
- import { invalidData, typeError } from "../interpreter/model.js";
4
- import { define, defineAccessor, get, getOwn, hidden, isWrapper, Arr, MapObj, Obj, PromiseObj, SetObj, } from "../interpreter/objects.js";
3
+ import { invalidData, IteratorSymbol, typeError } from "../interpreter/model.js";
4
+ import { define, defineAccessor, get, getOwn, hidden, isWrapper, Arr, IteratorObj, MapObj, Obj, PromiseObj, SetObj, } from "../interpreter/objects.js";
5
5
  import { describeValue, isRuntimeReference } from "../interpreter/references.js";
6
6
  import { applyCollectionCallback, isSupportedCallback, preserveConsumerError, toPrimitiveNumber, toPrimitiveString, } from "../interpreter/callback.js";
7
7
  const coerceGroupByPropertyKey = (ctx, value) => {
@@ -123,6 +123,31 @@ export const mapGlobal = (ctx) => {
123
123
  return target;
124
124
  },
125
125
  ],
126
+ [
127
+ "getOrInsert",
128
+ 2,
129
+ (thisValue, args) => {
130
+ const target = self(thisValue, "getOrInsert").map;
131
+ if (!target.has(args[0]))
132
+ target.set(args[0], args[1]);
133
+ return target.get(args[0]);
134
+ },
135
+ ],
136
+ [
137
+ "getOrInsertComputed",
138
+ 2,
139
+ (thisValue, args) => {
140
+ const target = self(thisValue, "getOrInsertComputed").map;
141
+ const apply = applyCollectionCallback(ctx, args[1], "Map.getOrInsertComputed");
142
+ if (target.has(args[0]))
143
+ return target.get(args[0]);
144
+ // The callback sees the stored key (-0 is +0) and its result wins over anything it inserted itself.
145
+ return Effect.map(apply([args[0] === 0 ? 0 : args[0]]), (value) => {
146
+ target.set(args[0], value);
147
+ return value;
148
+ });
149
+ },
150
+ ],
126
151
  ["delete", 1, (thisValue, args) => self(thisValue, "delete").map.delete(args[0])],
127
152
  [
128
153
  "clear",
@@ -132,12 +157,14 @@ export const mapGlobal = (ctx) => {
132
157
  return undefined;
133
158
  },
134
159
  ],
135
- ["keys", 0, (thisValue) => wrap(Array.from(self(thisValue, "keys").map.keys()))],
136
- ["values", 0, (thisValue) => wrap(Array.from(self(thisValue, "values").map.values()))],
160
+ ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").map.keys())],
161
+ ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").map.values())],
137
162
  [
138
163
  "entries",
139
164
  0,
140
- (thisValue) => wrap(Array.from(self(thisValue, "entries").map.entries(), ([key, item]) => wrap([key, item]))),
165
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
166
+ .map.entries()
167
+ .map(([key, item]) => wrap([key, item]))),
141
168
  ],
142
169
  [
143
170
  "forEach",
@@ -153,6 +180,7 @@ export const mapGlobal = (ctx) => {
153
180
  },
154
181
  ],
155
182
  ]);
183
+ define(proto, IteratorSymbol, get(proto, "entries"), hidden);
156
184
  return map;
157
185
  };
158
186
  const loadSetRecord = (ctx, source, name) => {
@@ -187,6 +215,8 @@ const loadSetRecord = (ctx, source, name) => {
187
215
  size: Math.max(Math.trunc(size), 0),
188
216
  has: (item) => Effect.map(ctx.call(has, source, [item]), Boolean),
189
217
  keys: () => Effect.flatMap(ctx.call(keys, source, []), (result) => {
218
+ if (result instanceof IteratorObj)
219
+ return Effect.succeed(result.iterator);
190
220
  if (result instanceof Arr)
191
221
  return Effect.succeed(result.items);
192
222
  throw typeError(`Set.${name} expected 'keys' to return an iterator.`);
@@ -313,12 +343,14 @@ export const setGlobal = (ctx) => {
313
343
  return undefined;
314
344
  },
315
345
  ],
316
- ["keys", 0, (thisValue) => wrap(Array.from(self(thisValue, "keys").set.values()))],
317
- ["values", 0, (thisValue) => wrap(Array.from(self(thisValue, "values").set.values()))],
346
+ ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").set.values())],
347
+ ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").set.values())],
318
348
  [
319
349
  "entries",
320
350
  0,
321
- (thisValue) => wrap(Array.from(self(thisValue, "entries").set.values(), (item) => wrap([item, item]))),
351
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
352
+ .set.values()
353
+ .map((item) => wrap([item, item]))),
322
354
  ],
323
355
  [
324
356
  "forEach",
@@ -341,5 +373,6 @@ export const setGlobal = (ctx) => {
341
373
  operation("isSupersetOf"),
342
374
  operation("isDisjointFrom"),
343
375
  ]);
376
+ define(proto, IteratorSymbol, get(proto, "values"), hidden);
344
377
  return set;
345
378
  };
@@ -1,5 +1,5 @@
1
1
  import { methods } from "../interpreter/native.js";
2
- import { entries, get, Arr, Bytes, DateObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, } from "../interpreter/objects.js";
2
+ import { entries, get, Arr, Bytes, DateObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, HeadersObj, } from "../interpreter/objects.js";
3
3
  import { containsOpaqueReference, isRuntimeReference } from "../interpreter/references.js";
4
4
  import { coerceToString } from "./value.js";
5
5
  const consoleMethods = ["log", "info", "debug", "warn", "error", "dir", "table"];
@@ -53,6 +53,8 @@ const formatConsoleValue = (value, seen, depth) => {
53
53
  return coerceToString(value);
54
54
  if (value instanceof URLSearchParamsObj)
55
55
  return coerceToString(value);
56
+ if (value instanceof HeadersObj)
57
+ return `Headers ${JSON.stringify(Object.fromEntries(value.headers))}`;
56
58
  if (value instanceof Bytes)
57
59
  return `Uint8Array(${value.bytes.length}) [${value.bytes.join(",")}]`;
58
60
  if (depth > MAX_CONSOLE_DEPTH)
@@ -92,6 +92,11 @@ export const dateGlobal = (ctx) => {
92
92
  ["toTimeString", 0, (thisValue) => new Date(self(thisValue, "toTimeString").time).toTimeString()],
93
93
  ["toUTCString", 0, (thisValue) => new Date(self(thisValue, "toUTCString").time).toUTCString()],
94
94
  ["toGMTString", 0, (thisValue) => new Date(self(thisValue, "toGMTString").time).toUTCString()],
95
+ ...["toLocaleString", "toLocaleDateString", "toLocaleTimeString"].map((name) => [
96
+ name,
97
+ 0,
98
+ (thisValue) => new Date(self(thisValue, name).time)[name]("en-US", { timeZone: "UTC" }),
99
+ ]),
95
100
  ...getters.map((name) => [name, 0, (thisValue) => new Date(self(thisValue, name).time)[name]()]),
96
101
  ...setters.map(([name, length]) => [
97
102
  name,
@@ -0,0 +1,2 @@
1
+ import type { Interpreter } from "../interpreter/interpreter.js";
2
+ export declare const headersGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
@@ -0,0 +1,127 @@
1
+ import { Effect } from "effect";
2
+ import { constructor, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
+ import { IteratorSymbol, typeError } from "../interpreter/model.js";
4
+ import { define, entries, get, hidden, Arr, HeadersObj, IteratorObj, Obj } from "../interpreter/objects.js";
5
+ import { applyCollectionCallback } from "../interpreter/callback.js";
6
+ import { isRuntimeReference } from "../interpreter/references.js";
7
+ import { coerceToString } from "./value.js";
8
+ import { readPairs } from "./url.js";
9
+ // The host validates header names and values and throws its own TypeError; the program gets one of its own.
10
+ const attempt = (run) => {
11
+ try {
12
+ return run();
13
+ }
14
+ catch (error) {
15
+ throw typeError(error instanceof Error ? error.message : String(error));
16
+ }
17
+ };
18
+ const constructHeaders = (ctx, init, proto) => {
19
+ const wrap = (headers) => new HeadersObj(proto, headers);
20
+ if (init === undefined)
21
+ return Effect.succeed(wrap(new Headers()));
22
+ return Effect.gen(function* () {
23
+ const pairs = init instanceof Obj ? yield* readPairs(ctx, init, "new Headers(...)") : undefined;
24
+ if (pairs !== undefined)
25
+ return wrap(attempt(() => new Headers(pairs)));
26
+ if (!(init instanceof Obj) || isRuntimeReference(init)) {
27
+ throw typeError("new Headers(...) expects a record of names to values, iterable [name, value] pairs, or Headers.");
28
+ }
29
+ return wrap(attempt(() => new Headers(Object.fromEntries(entries(init).map(([key, value]) => [key, coerceToString(value)])))));
30
+ });
31
+ };
32
+ export const headersGlobal = (ctx) => {
33
+ const builtins = ctx.builtins;
34
+ const proto = builtins.Headers;
35
+ const headers = constructor(builtins, proto, {
36
+ name: "Headers",
37
+ call: requiresNew("Headers"),
38
+ construct: (args, newTarget) => constructHeaders(ctx, args[0], prototypeFrom(newTarget, proto)),
39
+ });
40
+ const self = (thisValue, name) => receiver(HeadersObj, thisValue, `Headers.prototype.${name}`);
41
+ const wrap = (items) => new Arr(builtins.Array, items);
42
+ const arg = (args, index) => coerceToString(args[index]);
43
+ const requireArgs = (name, args, count) => {
44
+ if (args.length < count)
45
+ throw typeError(`Headers.${name} requires ${count} argument${count === 1 ? "" : "s"}.`);
46
+ };
47
+ methods(builtins, proto, [
48
+ [
49
+ "append",
50
+ 2,
51
+ (thisValue, args) => {
52
+ requireArgs("append", args, 2);
53
+ const target = self(thisValue, "append").headers;
54
+ return attempt(() => target.append(arg(args, 0), arg(args, 1)));
55
+ },
56
+ ],
57
+ [
58
+ "delete",
59
+ 1,
60
+ (thisValue, args) => {
61
+ requireArgs("delete", args, 1);
62
+ const target = self(thisValue, "delete").headers;
63
+ return attempt(() => target.delete(arg(args, 0)));
64
+ },
65
+ ],
66
+ [
67
+ "get",
68
+ 1,
69
+ (thisValue, args) => {
70
+ requireArgs("get", args, 1);
71
+ const target = self(thisValue, "get").headers;
72
+ return attempt(() => target.get(arg(args, 0)));
73
+ },
74
+ ],
75
+ ["getSetCookie", 0, (thisValue) => wrap(self(thisValue, "getSetCookie").headers.getSetCookie())],
76
+ [
77
+ "has",
78
+ 1,
79
+ (thisValue, args) => {
80
+ requireArgs("has", args, 1);
81
+ const target = self(thisValue, "has").headers;
82
+ return attempt(() => target.has(arg(args, 0)));
83
+ },
84
+ ],
85
+ [
86
+ "set",
87
+ 2,
88
+ (thisValue, args) => {
89
+ requireArgs("set", args, 2);
90
+ const target = self(thisValue, "set").headers;
91
+ return attempt(() => target.set(arg(args, 0), arg(args, 1)));
92
+ },
93
+ ],
94
+ // Iterator.from because Bun's Headers typings predate iterator helpers; the runtime iterators already have them.
95
+ [
96
+ "keys",
97
+ 0,
98
+ (thisValue) => new IteratorObj(builtins.Iterator, Iterator.from(self(thisValue, "keys").headers.keys())),
99
+ ],
100
+ [
101
+ "values",
102
+ 0,
103
+ (thisValue) => new IteratorObj(builtins.Iterator, Iterator.from(self(thisValue, "values").headers.values())),
104
+ ],
105
+ [
106
+ "entries",
107
+ 0,
108
+ (thisValue) => new IteratorObj(builtins.Iterator, Iterator.from(self(thisValue, "entries").headers.entries()).map(([key, value]) => wrap([key, value]))),
109
+ ],
110
+ [
111
+ "forEach",
112
+ 1,
113
+ (thisValue, args) => {
114
+ requireArgs("forEach", args, 1);
115
+ const target = self(thisValue, "forEach");
116
+ const apply = applyCollectionCallback(ctx, args[0], "Headers.forEach");
117
+ return Effect.gen(function* () {
118
+ for (const [key, value] of Array.from(target.headers.entries()))
119
+ yield* apply([value, key, target]);
120
+ return undefined;
121
+ });
122
+ },
123
+ ],
124
+ ]);
125
+ define(proto, IteratorSymbol, get(proto, "entries"), hidden);
126
+ return headers;
127
+ };
@@ -0,0 +1,2 @@
1
+ import type { Interpreter } from "../interpreter/interpreter.js";
2
+ export declare const iteratorGlobals: <R>(ctx: Interpreter<R>) => void;
@@ -0,0 +1,17 @@
1
+ import { methods, receiver } from "../interpreter/native.js";
2
+ import { IteratorObj, record } from "../interpreter/objects.js";
3
+ // Every built-in collection iterator shares the Iterator prototype; JS gives each collection its own, which is only
4
+ // observable through getPrototypeOf.
5
+ export const iteratorGlobals = (ctx) => {
6
+ const builtins = ctx.builtins;
7
+ methods(builtins, builtins.Iterator, [
8
+ [
9
+ "next",
10
+ 0,
11
+ (thisValue) => {
12
+ const step = receiver(IteratorObj, thisValue, "Iterator.prototype.next").iterator.next();
13
+ return record(builtins.Object, { value: step.value, done: Boolean(step.done) });
14
+ },
15
+ ],
16
+ ]);
17
+ };
@@ -50,6 +50,7 @@ export const numberGlobal = (ctx) => {
50
50
  };
51
51
  methods(builtins, builtins.Number, [
52
52
  ["toFixed", 1, (thisValue, args) => self(thisValue, "toFixed").toFixed(optNum("toFixed", args[0]))],
53
+ ["toLocaleString", 0, (thisValue) => self(thisValue, "toLocaleString").toLocaleString("en-US")],
53
54
  [
54
55
  "toExponential",
55
56
  1,
@@ -3,7 +3,7 @@ import { constructor, methods, receiver } from "../interpreter/native.js";
3
3
  import { AsyncIteratorSymbol, invalidData, IteratorSymbol, rangeError, typeError, } from "../interpreter/model.js";
4
4
  import { Callable, define, entries, enumerableKeys, getOwn, hasOwn, hasPrototype, hidden, keys, own, Arr, Bytes, DateObj, ErrorObj, Obj, PromiseObj, RegExpObj, set, } 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
9
  import { coerceToString } from "./value.js";
@@ -161,7 +161,7 @@ export const objectGlobal = (ctx) => {
161
161
  true,
162
162
  ],
163
163
  ["toString", 0, (thisValue) => `[object ${classTag(thisValue)}]`],
164
- ["toLocaleString", 0, (thisValue) => `[object ${classTag(thisValue)}]`],
164
+ ["toLocaleString", 0, (thisValue) => invoke(ctx, thisValue, "toString", "Object.prototype.toLocaleString")],
165
165
  [
166
166
  "valueOf",
167
167
  0,
@@ -1,8 +1,8 @@
1
1
  import { Effect } from "effect";
2
- import { constructor, methods } from "../interpreter/native.js";
2
+ import { constructor, fn, methods } from "../interpreter/native.js";
3
3
  import { checkArrayLength, checkStringLength } from "../interpreter/limits.js";
4
- import { invalidData, rangeError, typeError } from "../interpreter/model.js";
5
- import { Arr, PromiseObj, RegExpObj, record } from "../interpreter/objects.js";
4
+ import { invalidData, IteratorSymbol, rangeError, typeError } from "../interpreter/model.js";
5
+ import { define, hidden, Arr, IteratorObj, PromiseObj, RegExpObj, record } 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";
@@ -130,6 +130,8 @@ export const stringGlobal = (ctx) => {
130
130
  simple("valueOf", 0, (value) => value),
131
131
  simple("toLowerCase", 0, (value) => value.toLowerCase()),
132
132
  simple("toUpperCase", 0, (value) => value.toUpperCase()),
133
+ simple("toLocaleLowerCase", 0, (value) => value.toLowerCase()),
134
+ simple("toLocaleUpperCase", 0, (value) => value.toUpperCase()),
133
135
  simple("trim", 0, (value) => value.trim()),
134
136
  simple("trimStart", 0, (value) => value.trimStart()),
135
137
  simple("trimLeft", 0, (value) => value.trimStart()),
@@ -232,5 +234,6 @@ export const stringGlobal = (ctx) => {
232
234
  return joined;
233
235
  }),
234
236
  ]);
237
+ define(builtins.String, IteratorSymbol, fn(builtins, "[Symbol.iterator]", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "[Symbol.iterator]")[Symbol.iterator]())), hidden);
235
238
  return string;
236
239
  };
@@ -1,6 +1,12 @@
1
+ import { Effect } from "effect";
1
2
  import type { Interpreter } from "../interpreter/interpreter.js";
2
3
  type UriFunction = "encodeURI" | "encodeURIComponent" | "decodeURI" | "decodeURIComponent";
3
4
  export declare const uriGlobal: <R>(ctx: Interpreter<R>, name: UriFunction) => import("../interpreter/objects.js").Native<R>;
4
5
  export declare const urlGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
6
+ /**
7
+ * Reads a synchronous iterable of `[name, value]` pairs as strings; `undefined` when `init` is not iterable. As in
8
+ * WebIDL, the whole sequence is converted before any pair's length is checked.
9
+ */
10
+ export declare const readPairs: <R>(ctx: Interpreter<R>, init: unknown, label: string) => Effect.Effect<Array<[string, string]> | undefined, unknown, R>;
5
11
  export declare const urlSearchParamsGlobal: <R>(ctx: Interpreter<R>) => import("../interpreter/objects.js").Native<R>;
6
12
  export {};
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { constructor, fn, methods, prototypeFrom, receiver, requiresNew } from "../interpreter/native.js";
3
- import { PendingThrow, typeError, uriError } from "../interpreter/model.js";
4
- import { defineAccessor, entries, isWrapper, Arr, Obj, URLObj, URLSearchParamsObj } from "../interpreter/objects.js";
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
5
  import { isRuntimeReference } from "../interpreter/references.js";
6
6
  import { applyCollectionCallback, preserveConsumerError } from "../interpreter/callback.js";
7
7
  import { coerceToString } from "./value.js";
@@ -98,11 +98,10 @@ export const urlGlobal = (ctx) => {
98
98
  ]);
99
99
  return url;
100
100
  };
101
- const readPair = (ctx, value) => Effect.gen(function* () {
101
+ const readPair = (ctx, value, label) => Effect.gen(function* () {
102
102
  const cursor = yield* ctx.iterate(value);
103
- if (cursor === undefined) {
104
- throw typeError("new URLSearchParams(...) expects iterable [name, value] pairs.");
105
- }
103
+ if (cursor === undefined)
104
+ throw typeError(`${label} expects iterable [name, value] pairs.`);
106
105
  const items = [];
107
106
  while (true) {
108
107
  const step = yield* cursor.next;
@@ -111,6 +110,25 @@ const readPair = (ctx, value) => Effect.gen(function* () {
111
110
  items.push(yield* preserveConsumerError(cursor, Effect.sync(() => coerceToString(step.value))));
112
111
  }
113
112
  });
113
+ /**
114
+ * Reads a synchronous iterable of `[name, value]` pairs as strings; `undefined` when `init` is not iterable. As in
115
+ * WebIDL, the whole sequence is converted before any pair's length is checked.
116
+ */
117
+ export const readPairs = (ctx, init, label) => Effect.gen(function* () {
118
+ const cursor = yield* ctx.iterate(init);
119
+ if (cursor === undefined)
120
+ return undefined;
121
+ const pairs = [];
122
+ while (true) {
123
+ const step = yield* cursor.next;
124
+ if (step.done) {
125
+ if (pairs.some((entry) => entry.length !== 2))
126
+ throw typeError(`${label} expects iterable [name, value] pairs.`);
127
+ return pairs;
128
+ }
129
+ pairs.push(yield* preserveConsumerError(cursor, readPair(ctx, step.value, label)));
130
+ }
131
+ });
114
132
  const constructURLSearchParams = (ctx, init, proto) => {
115
133
  const wrap = (params) => new URLSearchParamsObj(proto, params);
116
134
  if (init === undefined)
@@ -123,20 +141,9 @@ const constructURLSearchParams = (ctx, init, proto) => {
123
141
  return Effect.succeed(wrap(new URLSearchParams(coerceToString(init))));
124
142
  }
125
143
  return Effect.gen(function* () {
126
- const cursor = yield* ctx.iterate(init);
127
- if (cursor !== undefined) {
128
- const pairs = [];
129
- while (true) {
130
- const step = yield* cursor.next;
131
- if (step.done) {
132
- if (pairs.some((entry) => entry.length !== 2)) {
133
- throw typeError("new URLSearchParams(...) expects iterable [name, value] pairs.");
134
- }
135
- return wrap(new URLSearchParams(pairs.map((entry) => [entry[0] ?? "", entry[1] ?? ""])));
136
- }
137
- pairs.push(yield* preserveConsumerError(cursor, readPair(ctx, step.value)));
138
- }
139
- }
144
+ const pairs = yield* readPairs(ctx, init, "new URLSearchParams(...)");
145
+ if (pairs !== undefined)
146
+ return wrap(new URLSearchParams(pairs));
140
147
  if (isRuntimeReference(init)) {
141
148
  throw typeError("new URLSearchParams(...) expects a query string, data object, or synchronous iterable pairs.");
142
149
  }
@@ -230,12 +237,14 @@ export const urlSearchParamsGlobal = (ctx) => {
230
237
  return undefined;
231
238
  },
232
239
  ],
233
- ["keys", 0, (thisValue) => wrap(Array.from(self(thisValue, "keys").params.keys()))],
234
- ["values", 0, (thisValue) => wrap(Array.from(self(thisValue, "values").params.values()))],
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())],
235
242
  [
236
243
  "entries",
237
244
  0,
238
- (thisValue) => wrap(Array.from(self(thisValue, "entries").params.entries(), ([key, value]) => wrap([key, value]))),
245
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
246
+ .params.entries()
247
+ .map(([key, value]) => wrap([key, value]))),
239
248
  ],
240
249
  ["toString", 0, (thisValue) => self(thisValue, "toString").params.toString()],
241
250
  [
@@ -253,5 +262,6 @@ export const urlSearchParamsGlobal = (ctx) => {
253
262
  },
254
263
  ],
255
264
  ]);
265
+ define(proto, IteratorSymbol, get(proto, "entries"), hidden);
256
266
  return searchParams;
257
267
  };
@@ -1,6 +1,6 @@
1
1
  import { fn } from "../interpreter/native.js";
2
2
  import { typeError } from "../interpreter/model.js";
3
- import { get, isWrapper, Arr, Bytes, DateObj, ErrorObj, MapObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, } from "../interpreter/objects.js";
3
+ import { get, isWrapper, Arr, Bytes, DateObj, ErrorObj, MapObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, HeadersObj, } from "../interpreter/objects.js";
4
4
  export const compoundOperators = new Set(["+=", "-=", "*=", "/=", "%=", "**=", "&=", "|=", "^=", "<<=", ">>=", ">>>="]);
5
5
  /** The built-in string form of a value, without consulting program-defined `toString` methods. */
6
6
  export const coerceToString = (value) => {
@@ -20,6 +20,8 @@ export const coerceToString = (value) => {
20
20
  return value.url.href;
21
21
  if (value instanceof URLSearchParamsObj)
22
22
  return value.params.toString();
23
+ if (value instanceof HeadersObj)
24
+ return "[object Headers]";
23
25
  if (value instanceof Bytes)
24
26
  return value.bytes.join(",");
25
27
  if (value instanceof ErrorObj) {
@@ -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. */
@@ -136,6 +136,7 @@ const makeSearchTool = (searchIndex) => ({
136
136
  const description = entry.description.description.toLowerCase();
137
137
  const score = terms.reduce((total, forms) => total +
138
138
  (forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
139
+ (forms.some((form) => entry.pathWords.includes(form)) ? 12 : 0) +
139
140
  (forms.some((form) => path.includes(form)) ? 8 : 0) +
140
141
  (forms.some((form) => description.includes(form)) ? 4 : 0) +
141
142
  (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0), 0);
@@ -163,6 +164,7 @@ export const searchSignature = (() => {
163
164
  })();
164
165
  const toSearchEntry = (visible) => ({
165
166
  description: describeTool(visible),
167
+ pathWords: tokenize(visible.path),
166
168
  searchText: [
167
169
  visible.path,
168
170
  visible.tool.description,
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.10",
4
+ "version": "2.0.12",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",