@opencode/codemode 2.0.10 → 2.0.11

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.");
@@ -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
  };
@@ -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;
@@ -5,7 +5,7 @@ import { AsyncIteratorSymbol, CallSite, GeneratorReturn, IteratorSymbol, Optiona
5
5
  import { checkStringLength } from "./limits.js";
6
6
  import { locate, materialize } from "./errors.js";
7
7
  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";
8
+ 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
9
  import { preserveConsumerError } from "./callback.js";
10
10
  import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
11
11
  import { containsOpaqueReference, describeValue, rejectCircularInsertion, typeofValue } from "./references.js";
@@ -477,10 +477,10 @@ class Frame {
477
477
  if (declared?.lexical)
478
478
  self.predeclarePattern(declared.pattern, declared.mutable, left);
479
479
  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;
480
+ const cursor = self.hostCursor(right);
481
+ const iterator = cursor === undefined ? yield* self.customIterator(right, node, awaiting) : undefined;
482
482
  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);
483
+ throw invalidData(`${awaiting ? "for await...of" : "for...of"} requires an array, string, Map, Set, URLSearchParams, or Headers, or custom iterator value.`, node);
484
484
  }
485
485
  const close = () => iterator
486
486
  ? self.closeIterator(iterator, node, awaiting)
@@ -550,6 +550,18 @@ class Frame {
550
550
  });
551
551
  }
552
552
  iterate(value, node) {
553
+ const cursor = this.hostCursor(value);
554
+ if (cursor !== undefined)
555
+ return Effect.succeed(cursor);
556
+ const self = this;
557
+ return Effect.map(this.customIterator(value, node, false), (iterator) => iterator === undefined
558
+ ? undefined
559
+ : {
560
+ next: self.nextIteratorResult(iterator, node, false),
561
+ close: Effect.suspend(() => self.closeIterator(iterator, node, false)),
562
+ });
563
+ }
564
+ hostCursor(value) {
553
565
  const iterator = value instanceof Arr
554
566
  ? value.items[Symbol.iterator]()
555
567
  : typeof value === "string"
@@ -560,29 +572,26 @@ class Frame {
560
572
  ? value.set.values()
561
573
  : value instanceof URLSearchParamsObj
562
574
  ? 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
- });
575
+ : value instanceof HeadersObj
576
+ ? value.headers.entries()
577
+ : value instanceof Bytes
578
+ ? value.bytes.values()
579
+ : value instanceof IteratorObj
580
+ ? value.iterator
581
+ : undefined;
582
+ if (iterator === undefined)
583
+ return undefined;
584
+ const proto = this.ctx.builtins.Array;
585
+ return {
586
+ next: Effect.sync(() => {
587
+ const step = iterator.next();
588
+ return {
589
+ done: Boolean(step.done),
590
+ value: Array.isArray(step.value) ? new Arr(proto, step.value) : step.value,
591
+ };
592
+ }),
593
+ close: Effect.void,
594
+ };
586
595
  }
587
596
  customIterator(value, node, allowAsync = true) {
588
597
  if (!(value instanceof Obj))
@@ -1513,6 +1522,7 @@ class Frame {
1513
1522
  value instanceof MapObj ||
1514
1523
  value instanceof SetObj ||
1515
1524
  value instanceof URLSearchParamsObj ||
1525
+ value instanceof HeadersObj ||
1516
1526
  value instanceof Bytes) {
1517
1527
  const cursor = yield* self.iterate(value, node);
1518
1528
  if (!cursor)
@@ -2,7 +2,7 @@ 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.
@@ -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",
@@ -68,6 +69,7 @@ export const createBuiltins = () => {
68
69
  Set: plain(),
69
70
  URL: plain(),
70
71
  URLSearchParams: plain(),
72
+ Headers: plain(),
71
73
  Uint8Array: plain(),
72
74
  TextEncoder: plain(),
73
75
  TextDecoder: plain(),
@@ -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,8 +1,8 @@
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
7
  import { applyCollectionCallback, preserveConsumerError } from "../interpreter/callback.js";
8
8
  import { compareText } from "../tool-runtime.js";
@@ -317,12 +317,14 @@ 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
+ ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").items.keys())],
321
+ ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").items.values())],
322
322
  [
323
323
  "entries",
324
324
  0,
325
- (thisValue) => wrap(Array.from(self(thisValue, "entries").items.entries(), ([index, item]) => wrap([index, item]))),
325
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
326
+ .items.entries()
327
+ .map(([index, item]) => wrap([index, item]))),
326
328
  ],
327
329
  iterate("map", 1, (target, receiver, apply) => Effect.gen(function* () {
328
330
  const length = target.length;
@@ -461,5 +463,6 @@ export const arrayGlobal = (ctx) => {
461
463
  return accumulator;
462
464
  })),
463
465
  ]);
466
+ define(proto, IteratorSymbol, get(proto, "values"), hidden);
464
467
  return array;
465
468
  };
@@ -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) => {
@@ -132,12 +132,14 @@ export const mapGlobal = (ctx) => {
132
132
  return undefined;
133
133
  },
134
134
  ],
135
- ["keys", 0, (thisValue) => wrap(Array.from(self(thisValue, "keys").map.keys()))],
136
- ["values", 0, (thisValue) => wrap(Array.from(self(thisValue, "values").map.values()))],
135
+ ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").map.keys())],
136
+ ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").map.values())],
137
137
  [
138
138
  "entries",
139
139
  0,
140
- (thisValue) => wrap(Array.from(self(thisValue, "entries").map.entries(), ([key, item]) => wrap([key, item]))),
140
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
141
+ .map.entries()
142
+ .map(([key, item]) => wrap([key, item]))),
141
143
  ],
142
144
  [
143
145
  "forEach",
@@ -153,6 +155,7 @@ export const mapGlobal = (ctx) => {
153
155
  },
154
156
  ],
155
157
  ]);
158
+ define(proto, IteratorSymbol, get(proto, "entries"), hidden);
156
159
  return map;
157
160
  };
158
161
  const loadSetRecord = (ctx, source, name) => {
@@ -187,6 +190,8 @@ const loadSetRecord = (ctx, source, name) => {
187
190
  size: Math.max(Math.trunc(size), 0),
188
191
  has: (item) => Effect.map(ctx.call(has, source, [item]), Boolean),
189
192
  keys: () => Effect.flatMap(ctx.call(keys, source, []), (result) => {
193
+ if (result instanceof IteratorObj)
194
+ return Effect.succeed(result.iterator);
190
195
  if (result instanceof Arr)
191
196
  return Effect.succeed(result.items);
192
197
  throw typeError(`Set.${name} expected 'keys' to return an iterator.`);
@@ -313,12 +318,14 @@ export const setGlobal = (ctx) => {
313
318
  return undefined;
314
319
  },
315
320
  ],
316
- ["keys", 0, (thisValue) => wrap(Array.from(self(thisValue, "keys").set.values()))],
317
- ["values", 0, (thisValue) => wrap(Array.from(self(thisValue, "values").set.values()))],
321
+ ["keys", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "keys").set.values())],
322
+ ["values", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "values").set.values())],
318
323
  [
319
324
  "entries",
320
325
  0,
321
- (thisValue) => wrap(Array.from(self(thisValue, "entries").set.values(), (item) => wrap([item, item]))),
326
+ (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "entries")
327
+ .set.values()
328
+ .map((item) => wrap([item, item]))),
322
329
  ],
323
330
  [
324
331
  "forEach",
@@ -341,5 +348,6 @@ export const setGlobal = (ctx) => {
341
348
  operation("isSupersetOf"),
342
349
  operation("isDisjointFrom"),
343
350
  ]);
351
+ define(proto, IteratorSymbol, get(proto, "values"), hidden);
344
352
  return set;
345
353
  };
@@ -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)
@@ -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
+ };
@@ -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";
@@ -232,5 +232,6 @@ export const stringGlobal = (ctx) => {
232
232
  return joined;
233
233
  }),
234
234
  ]);
235
+ define(builtins.String, IteratorSymbol, fn(builtins, "[Symbol.iterator]", 0, (thisValue) => new IteratorObj(builtins.Iterator, self(thisValue, "[Symbol.iterator]")[Symbol.iterator]())), hidden);
235
236
  return string;
236
237
  };
@@ -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) {
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.11",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",