@opencode/codemode 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +5 -2
  2. package/dist/codemode.d.ts +7 -2
  3. package/dist/codemode.js +15 -2
  4. package/dist/data.d.ts +14 -21
  5. package/dist/data.js +72 -140
  6. package/dist/extension.d.ts +11 -0
  7. package/dist/extension.js +9 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/interpreter/callback.d.ts +20 -0
  11. package/dist/interpreter/{runner.js → callback.js} +14 -14
  12. package/dist/interpreter/errors.d.ts +12 -5
  13. package/dist/interpreter/errors.js +66 -54
  14. package/dist/interpreter/execute.d.ts +2 -2
  15. package/dist/interpreter/execute.js +15 -15
  16. package/dist/interpreter/extensions.d.ts +8 -0
  17. package/dist/interpreter/extensions.js +160 -0
  18. package/dist/interpreter/generators.d.ts +2 -3
  19. package/dist/interpreter/generators.js +10 -10
  20. package/dist/interpreter/globals.d.ts +4 -12
  21. package/dist/interpreter/globals.js +56 -49
  22. package/dist/interpreter/interpreter.d.ts +34 -0
  23. package/dist/interpreter/{runtime.js → interpreter.js} +250 -215
  24. package/dist/interpreter/intrinsics.d.ts +5 -5
  25. package/dist/interpreter/intrinsics.js +16 -10
  26. package/dist/interpreter/limits.d.ts +10 -0
  27. package/dist/interpreter/limits.js +19 -0
  28. package/dist/interpreter/model.d.ts +27 -14
  29. package/dist/interpreter/model.js +22 -14
  30. package/dist/interpreter/native.d.ts +12 -13
  31. package/dist/interpreter/native.js +17 -16
  32. package/dist/interpreter/objects.d.ts +55 -48
  33. package/dist/interpreter/objects.js +64 -44
  34. package/dist/interpreter/promises.d.ts +13 -15
  35. package/dist/interpreter/promises.js +89 -85
  36. package/dist/interpreter/references.d.ts +1 -2
  37. package/dist/interpreter/references.js +18 -16
  38. package/dist/interpreter/scope.js +6 -6
  39. package/dist/openapi/types.d.ts +1 -1
  40. package/dist/stdlib/array.d.ts +3 -4
  41. package/dist/stdlib/array.js +115 -110
  42. package/dist/stdlib/bytes.d.ts +10 -0
  43. package/dist/stdlib/bytes.js +228 -0
  44. package/dist/stdlib/collections.d.ts +4 -4
  45. package/dist/stdlib/collections.js +89 -89
  46. package/dist/stdlib/console.d.ts +5 -3
  47. package/dist/stdlib/console.js +30 -37
  48. package/dist/stdlib/date.d.ts +2 -2
  49. package/dist/stdlib/date.js +35 -35
  50. package/dist/stdlib/json.d.ts +3 -3
  51. package/dist/stdlib/json.js +29 -67
  52. package/dist/stdlib/math.d.ts +3 -3
  53. package/dist/stdlib/math.js +17 -21
  54. package/dist/stdlib/number.d.ts +3 -3
  55. package/dist/stdlib/number.js +32 -36
  56. package/dist/stdlib/object.d.ts +5 -5
  57. package/dist/stdlib/object.js +59 -62
  58. package/dist/stdlib/regexp.d.ts +7 -8
  59. package/dist/stdlib/regexp.js +40 -48
  60. package/dist/stdlib/string.d.ts +2 -2
  61. package/dist/stdlib/string.js +110 -88
  62. package/dist/stdlib/url.d.ts +4 -6
  63. package/dist/stdlib/url.js +86 -90
  64. package/dist/stdlib/value.d.ts +3 -3
  65. package/dist/stdlib/value.js +26 -24
  66. package/dist/stdlib/web.d.ts +4 -4
  67. package/dist/stdlib/web.js +22 -8
  68. package/dist/tool-runtime.d.ts +38 -19
  69. package/dist/tool-runtime.js +70 -48
  70. package/package.json +1 -1
  71. package/dist/interpreter/runner.d.ts +0 -28
  72. package/dist/interpreter/runtime.d.ts +0 -19
@@ -1,18 +1,18 @@
1
1
  import { Effect } from "effect";
2
2
  import { ToolError } from "../tool-error.js";
3
- import { toData, ToolRuntimeError } from "../data.js";
4
- import { formatLocation, InterpreterRuntimeError, ProgramThrow, sourceLocation } from "./model.js";
3
+ import { ToolRuntimeError } from "../tool-runtime.js";
4
+ import { formatLocation, PendingThrow, Throw, sourceLocation, typeError } from "./model.js";
5
5
  import { containsRuntimeReference } from "./references.js";
6
6
  import { createErrorValue, isErrorType } from "./intrinsics.js";
7
7
  import { constructor, methods, prototypeFrom, receiver } from "./native.js";
8
- import { define, get, hidden, ProgramArray, ProgramError, ProgramObject, } from "./objects.js";
9
- import {} from "./runner.js";
8
+ import { define, get, hidden, Arr, ErrorObj, Obj } from "./objects.js";
9
+ import { formatValue } from "../stdlib/console.js";
10
10
  import { coerceToString } from "../stdlib/value.js";
11
11
  export const normalizeError = (error) => {
12
- if (error instanceof InterpreterRuntimeError) {
12
+ if (error instanceof PendingThrow) {
13
13
  return {
14
14
  kind: error.kind,
15
- message: `${error.message}${formatLocation(error.node)}`,
15
+ message: `${error.type}: ${error.message}${formatLocation(error.node)}`,
16
16
  ...(error.node?.loc ? { location: sourceLocation(error.node) } : {}),
17
17
  ...(error.suggestions ? { suggestions: error.suggestions } : {}),
18
18
  };
@@ -27,8 +27,11 @@ export const normalizeError = (error) => {
27
27
  if (error instanceof ToolError) {
28
28
  return { kind: "ToolFailure", message: error.message };
29
29
  }
30
- if (error instanceof ProgramThrow) {
30
+ if (error instanceof Throw) {
31
31
  const value = error.value;
32
+ if (value instanceof ErrorObj) {
33
+ return value.host ? normalizeError(value.host) : { kind: "ExecutionFailure", message: errorToString(value) };
34
+ }
32
35
  let message;
33
36
  if (containsRuntimeReference(value)) {
34
37
  // Never expose runtime reference internals through thrown values.
@@ -37,16 +40,8 @@ export const normalizeError = (error) => {
37
40
  else if (typeof value === "string") {
38
41
  message = value;
39
42
  }
40
- else if (value instanceof ProgramObject && typeof get(value, "message") === "string") {
41
- message = get(value, "message");
42
- }
43
43
  else {
44
- try {
45
- message = JSON.stringify(toData(value, "Thrown value")) ?? String(value);
46
- }
47
- catch {
48
- message = String(value);
49
- }
44
+ message = formatValue(value);
50
45
  }
51
46
  return { kind: "ExecutionFailure", message: `Uncaught: ${message}` };
52
47
  }
@@ -67,68 +62,85 @@ export const normalizeError = (error) => {
67
62
  message: String(error),
68
63
  };
69
64
  };
70
- export const caughtErrorValue = (runner, thrown) => {
71
- if (thrown instanceof ProgramThrow)
65
+ /**
66
+ * Gives a failure the source location of the expression that raised it, keeping the first one attached. Host errors
67
+ * that escape a built-in become the equivalent program error here.
68
+ */
69
+ export const locate = (error, node) => {
70
+ if (error instanceof PendingThrow) {
71
+ if (error.node === undefined && node)
72
+ error.node = node;
73
+ return error;
74
+ }
75
+ if (error instanceof Error && !(error instanceof ToolError) && !(error instanceof ToolRuntimeError)) {
76
+ return new PendingThrow(isErrorType(error.name) ? error.name : "Error", error.message, node);
77
+ }
78
+ return error;
79
+ };
80
+ /** The program value a handler receives for a failure; one failure always yields the same value. */
81
+ export const materialize = (ctx, thrown) => {
82
+ if (thrown instanceof Throw)
72
83
  return thrown.value;
73
- const prototypes = runner.prototypes;
74
- if (thrown instanceof InterpreterRuntimeError)
75
- return createErrorValue(prototypes[thrown.type], thrown.message);
84
+ const builtins = ctx.builtins;
85
+ if (thrown instanceof PendingThrow) {
86
+ if (thrown.value === undefined) {
87
+ thrown.value = createErrorValue(builtins[thrown.type], thrown.message);
88
+ thrown.value.host = thrown;
89
+ }
90
+ return thrown.value;
91
+ }
76
92
  const type = thrown instanceof Error && isErrorType(thrown.name) ? thrown.name : "Error";
77
- return createErrorValue(prototypes[type], normalizeError(thrown).message);
93
+ return createErrorValue(builtins[type], normalizeError(thrown).message);
78
94
  };
79
- export const createAggregateErrorValue = (runner, errors, message, proto = runner.prototypes.AggregateError) => {
95
+ /** Error.prototype.toString: `name: message`, omitting whichever side is empty. */
96
+ const errorToString = (self) => {
97
+ const name = get(self, "name");
98
+ const message = get(self, "message");
99
+ const shownName = name === undefined ? "Error" : coerceToString(name);
100
+ const shownMessage = message === undefined ? "" : coerceToString(message);
101
+ if (shownMessage === "")
102
+ return shownName;
103
+ if (shownName === "")
104
+ return shownMessage;
105
+ return `${shownName}: ${shownMessage}`;
106
+ };
107
+ export const createAggregateErrorValue = (ctx, errors, message, proto = ctx.builtins.AggregateError) => {
80
108
  const value = createErrorValue(proto, message);
81
- define(value, "errors", new ProgramArray(runner.prototypes.Array, errors), { ...hidden });
109
+ define(value, "errors", new Arr(ctx.builtins.Array, errors), { ...hidden });
82
110
  return value;
83
111
  };
84
- const constructAggregateErrorValue = (runner, args, proto, node) => Effect.gen(function* () {
85
- const cursor = yield* runner.syncIterator(args[0], node);
86
- if (cursor === undefined) {
87
- throw new InterpreterRuntimeError("new AggregateError(...) expects a synchronous iterable of errors.", node);
88
- }
112
+ const constructAggregateErrorValue = (ctx, args, proto) => Effect.gen(function* () {
113
+ const cursor = yield* ctx.iterate(args[0]);
114
+ if (cursor === undefined)
115
+ throw typeError("new AggregateError(...) expects a synchronous iterable of errors.");
89
116
  const errors = [];
90
117
  while (true) {
91
118
  const step = yield* cursor.next;
92
119
  if (step.done) {
93
- return createAggregateErrorValue(runner, errors, args[1] === undefined ? "" : coerceToString(args[1]), proto);
120
+ return createAggregateErrorValue(ctx, errors, args[1] === undefined ? "" : coerceToString(args[1]), proto);
94
121
  }
95
122
  errors.push(step.value);
96
123
  }
97
124
  });
98
125
  /** An error constructor such as `Error` or `TypeError`; callable with or without `new`, like JS. */
99
- export const errorGlobal = (type, runner) => {
100
- const protos = runner.prototypes;
101
- const prototype = protos[type];
102
- const construct = (args, newTarget, node) => {
126
+ export const errorGlobal = (type, ctx) => {
127
+ const builtins = ctx.builtins;
128
+ const prototype = builtins[type];
129
+ const construct = (args, newTarget) => {
103
130
  const proto = prototypeFrom(newTarget, prototype);
104
131
  return type === "AggregateError"
105
- ? constructAggregateErrorValue(runner, args, proto, node)
132
+ ? constructAggregateErrorValue(ctx, args, proto)
106
133
  : Effect.sync(() => createErrorValue(proto, args[0] === undefined ? undefined : coerceToString(args[0])));
107
134
  };
108
- const ctor = constructor(protos, prototype, {
135
+ const ctor = constructor(builtins, prototype, {
109
136
  name: type,
110
137
  length: type === "AggregateError" ? 2 : 1,
111
- call: (_, args, node) => construct(args, ctor, node),
138
+ call: (_, args) => construct(args, ctor),
112
139
  construct,
113
140
  });
114
141
  if (type === "Error") {
115
- methods(protos, prototype, [
116
- [
117
- "toString",
118
- 0,
119
- (thisValue, _, node) => {
120
- const self = receiver(ProgramObject, thisValue, "Error.prototype.toString", node);
121
- const name = get(self, "name");
122
- const message = get(self, "message");
123
- const shownName = name === undefined ? "Error" : coerceToString(name);
124
- const shownMessage = message === undefined ? "" : coerceToString(message);
125
- if (shownMessage === "")
126
- return shownName;
127
- if (shownName === "")
128
- return shownMessage;
129
- return `${shownName}: ${shownMessage}`;
130
- },
131
- ],
142
+ methods(builtins, prototype, [
143
+ ["toString", 0, (thisValue) => errorToString(receiver(Obj, thisValue, "Error.prototype.toString"))],
132
144
  ]);
133
145
  }
134
146
  return ctor;
@@ -1,5 +1,5 @@
1
1
  import { Effect } from "effect";
2
2
  import type { ResolvedExecutionLimits, Result } from "../codemode.js";
3
3
  import { ToolRuntime } from "../tool-runtime.js";
4
- import type { Host } from "./globals.js";
5
- export declare const executeProgram: <R>(code: string, prepared: ToolRuntime.Prepared<R>, limits: ResolvedExecutionLimits, hooks: ToolRuntime.ToolCallHooks<R>, extraGlobals?: (host: Host<R>) => ReadonlyArray<readonly [string, unknown]>) => Effect.Effect<Result, never, R>;
4
+ import { Interpreter } from "./interpreter.js";
5
+ export declare const executeProgram: <R>(code: string, prepared: ToolRuntime.Prepared<R>, limits: ResolvedExecutionLimits, hooks: ToolRuntime.Hooks<R>, globals?: (ctx: Interpreter<R>) => ReadonlyArray<readonly [string, unknown]>) => Effect.Effect<Result, never, R>;
@@ -3,14 +3,14 @@ import { Cause, Effect, Scope } from "effect";
3
3
  // #transpile: conditional import — full typescript on node/bun, an identity
4
4
  // pass-through on workerd (the compiler is ~11 MiB and can't init there).
5
5
  import { transpile } from "#transpile";
6
- import { toData } from "../data.js";
6
+ import { toBoundary } from "../data.js";
7
7
  import { ToolRuntime } from "../tool-runtime.js";
8
8
  import { normalizeError } from "./errors.js";
9
- import { createPrototypes } from "./intrinsics.js";
10
- import { InterpreterRuntimeError } from "./model.js";
11
- import { PromiseRuntime } from "./promises.js";
12
- import { Runtime } from "./runtime.js";
13
- export const executeProgram = (code, prepared, limits, hooks, extraGlobals) => {
9
+ import { createBuiltins } from "./intrinsics.js";
10
+ import { PendingThrow } from "./model.js";
11
+ import { Pending } from "./promises.js";
12
+ import { Interpreter } from "./interpreter.js";
13
+ export const executeProgram = (code, prepared, limits, hooks, globals) => {
14
14
  if (code.trim().length === 0) {
15
15
  return Effect.succeed({
16
16
  ok: false,
@@ -20,19 +20,19 @@ export const executeProgram = (code, prepared, limits, hooks, extraGlobals) => {
20
20
  }
21
21
  // Allocate execution state inside suspension so reused Effects never share it.
22
22
  return Effect.suspend(() => {
23
- const prototypes = createPrototypes();
24
- const tools = ToolRuntime.make(prepared, prototypes, limits.maxToolCalls, hooks);
23
+ const builtins = createBuiltins();
24
+ const tools = ToolRuntime.make(prepared, limits.maxToolCalls, hooks);
25
25
  const logs = [];
26
26
  const logged = () => (logs.length > 0 ? { logs: [...logs] } : {});
27
27
  // Set only after copy-out so timeouts cannot report invalid values as completed.
28
28
  let returned;
29
29
  const base = Effect.acquireUseRelease(Scope.make("parallel"), (scope) => Effect.gen(function* () {
30
30
  const program = parseProgram(code);
31
- const promises = new PromiseRuntime(scope, prototypes.Promise);
32
- const value = yield* new Runtime(tools.execute, tools.search, tools.keys, promises, prototypes, logs, extraGlobals).run(program);
33
- const result = toData(value, "Execution result", "result");
34
- returned = { value: result, promises };
35
- const warnings = yield* promises.interrupt();
31
+ const pending = new Pending(scope, builtins.Promise);
32
+ const ctx = new Interpreter({ tools, pending, builtins, logs, globals });
33
+ const result = (yield* toBoundary(ctx, yield* ctx.run(program))) ?? null;
34
+ returned = { value: result, pending };
35
+ const warnings = yield* pending.interrupt();
36
36
  return {
37
37
  ok: true,
38
38
  value: result,
@@ -64,7 +64,7 @@ export const executeProgram = (code, prepared, limits, hooks, extraGlobals) => {
64
64
  kind: "TimeoutExceeded",
65
65
  message: `The program returned, but background work was still running at the ${timeoutMs}ms timeout and was interrupted. Await all started promises.`,
66
66
  },
67
- ...returned.promises.diagnostics(),
67
+ ...returned.pending.diagnostics(),
68
68
  ],
69
69
  ...logged(),
70
70
  toolCalls: tools.calls,
@@ -84,7 +84,7 @@ export const executeProgram = (code, prepared, limits, hooks, extraGlobals) => {
84
84
  const parseProgram = (code) => {
85
85
  const transpiled = transpile(`async function __codemode__() {\n${code}\n}`);
86
86
  if (transpiled.error !== undefined) {
87
- throw new InterpreterRuntimeError(`Failed to parse TypeScript: ${transpiled.error}`, undefined, "ParseError");
87
+ throw new PendingThrow("SyntaxError", `Failed to parse TypeScript: ${transpiled.error}`, undefined, "ParseError");
88
88
  }
89
89
  const bodyStart = transpiled.outputText.indexOf("{") + 1;
90
90
  const bodyEnd = transpiled.outputText.lastIndexOf("}");
@@ -0,0 +1,8 @@
1
+ import type { Extension } from "../extension.js";
2
+ import type { Interpreter } from "./interpreter.js";
3
+ /**
4
+ * The global bindings of one run's extensions. Everything crossing the boundary is converted: plain data and
5
+ * built-in wrappers are copied, a host function becomes a program function whose calls cross the same way, and a
6
+ * host Promise becomes a program promise.
7
+ */
8
+ export declare const extensionGlobals: <R>(ctx: Interpreter<R>, extensions: ReadonlyArray<Extension>) => ReadonlyArray<readonly [string, unknown]>;
@@ -0,0 +1,160 @@
1
+ import { Effect } from "effect";
2
+ import { coerceToString } from "../stdlib/value.js";
3
+ import { hooked } from "../tool-runtime.js";
4
+ import { createErrorValue, isErrorType } from "./intrinsics.js";
5
+ import { MAX_VALUE_DEPTH } from "./limits.js";
6
+ import { Throw, typeError } from "./model.js";
7
+ import { fn } from "./native.js";
8
+ import { Callable, define, entries, get, Arr, Bytes, DateObj, ErrorObj, GeneratorObj, MapObj, Obj, PromiseObj, RegExpObj, SetObj, URLObj, URLSearchParamsObj, } from "./objects.js";
9
+ import { describeValue } from "./references.js";
10
+ /**
11
+ * The global bindings of one run's extensions. Everything crossing the boundary is converted: plain data and
12
+ * built-in wrappers are copied, a host function becomes a program function whose calls cross the same way, and a
13
+ * host Promise becomes a program promise.
14
+ */
15
+ export const extensionGlobals = (ctx, extensions) => {
16
+ const builtins = ctx.builtins;
17
+ const toHost = (value, label, depth = 0, seen = new Set()) => {
18
+ if (depth > MAX_VALUE_DEPTH)
19
+ throw typeError(`${label} exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`);
20
+ if (value === null || typeof value !== "object") {
21
+ if (isPrimitive(value))
22
+ return value;
23
+ throw typeError(`${label} contains ${describeValue(value)}, which cannot be passed to an extension.`);
24
+ }
25
+ if (value instanceof Bytes)
26
+ return new Uint8Array(value.bytes);
27
+ if (value instanceof DateObj)
28
+ return new Date(value.time);
29
+ if (value instanceof RegExpObj)
30
+ return new RegExp(value.regex.source, value.regex.flags);
31
+ if (value instanceof URLObj)
32
+ return new URL(value.url.href);
33
+ if (value instanceof URLSearchParamsObj)
34
+ return new URLSearchParams(value.params);
35
+ const next = (item) => toHost(item, label, depth + 1, seen);
36
+ if (value instanceof MapObj)
37
+ return new Map([...value.map].map(([key, item]) => [next(key), next(item)]));
38
+ if (value instanceof SetObj)
39
+ return new Set([...value.set].map(next));
40
+ if (!(value instanceof Obj) ||
41
+ value instanceof Callable ||
42
+ value instanceof GeneratorObj ||
43
+ value instanceof PromiseObj) {
44
+ throw typeError(`${label} contains ${describeValue(value)}, which cannot be passed to an extension.`);
45
+ }
46
+ if (value instanceof ErrorObj) {
47
+ const name = coerceToString(get(value, "name"));
48
+ const message = get(value, "message");
49
+ const text = message === undefined ? "" : coerceToString(message);
50
+ return name === "AggregateError" ? new AggregateError([], text) : new (hostErrors.get(name) ?? Error)(text);
51
+ }
52
+ if (seen.has(value))
53
+ throw typeError(`${label} contains a circular value.`);
54
+ seen.add(value);
55
+ const copied = value instanceof Arr
56
+ ? value.items.map(next)
57
+ : Object.fromEntries(entries(value)
58
+ .filter(([key]) => key !== "__proto__")
59
+ .map(([key, item]) => [key, next(item)]));
60
+ seen.delete(value);
61
+ return copied;
62
+ };
63
+ const fromHost = (value, label, depth = 0, seen = new Set()) => {
64
+ if (depth > MAX_VALUE_DEPTH)
65
+ throw typeError(`${label} exceeds the maximum value depth of ${MAX_VALUE_DEPTH}.`);
66
+ if (isPrimitive(value))
67
+ return value;
68
+ if (typeof value === "function")
69
+ return wrap(value, label);
70
+ if (value !== null && typeof value === "object") {
71
+ if (value instanceof Date)
72
+ return new DateObj(builtins.Date, value.getTime());
73
+ if (value instanceof RegExp)
74
+ return new RegExpObj(builtins.RegExp, value.source, value.flags);
75
+ if (value instanceof Uint8Array)
76
+ return new Bytes(builtins.Uint8Array, new Uint8Array(value));
77
+ if (value instanceof ArrayBuffer)
78
+ return new Bytes(builtins.Uint8Array, new Uint8Array(value.slice(0)));
79
+ if (value instanceof Error) {
80
+ return createErrorValue(builtins[isErrorType(value.name) ? value.name : "Error"], value.message);
81
+ }
82
+ if (value instanceof URL)
83
+ return new URLObj(builtins.URL, builtins.URLSearchParams, new URL(value.href));
84
+ if (value instanceof URLSearchParams) {
85
+ return new URLSearchParamsObj(builtins.URLSearchParams, new URLSearchParams(value));
86
+ }
87
+ const next = (item, path) => fromHost(item, path, depth + 1, seen);
88
+ if (value instanceof Map) {
89
+ const wrapped = new MapObj(builtins.Map);
90
+ for (const [key, item] of value)
91
+ wrapped.map.set(next(key, label), next(item, label));
92
+ return wrapped;
93
+ }
94
+ if (value instanceof Set) {
95
+ const wrapped = new SetObj(builtins.Set);
96
+ for (const item of value)
97
+ wrapped.set.add(next(item, label));
98
+ return wrapped;
99
+ }
100
+ if (seen.has(value))
101
+ throw typeError(`${label} produced a circular value.`);
102
+ seen.add(value);
103
+ if (Array.isArray(value)) {
104
+ const copied = new Arr(builtins.Array, value.map((item, index) => next(item, `${label}[${index}]`)));
105
+ seen.delete(value);
106
+ return copied;
107
+ }
108
+ const prototype = Object.getPrototypeOf(value);
109
+ if (prototype === Object.prototype || prototype === null) {
110
+ const copied = new Obj(builtins.Object);
111
+ for (const [key, item] of Object.entries(value))
112
+ define(copied, key, next(item, `${label}.${key}`));
113
+ seen.delete(value);
114
+ return copied;
115
+ }
116
+ }
117
+ throw typeError(`${label} produced ${describeHost(value)}, which the program cannot hold.`);
118
+ };
119
+ // A host function as a program function. Arguments cross in; a global's call runs inside the host's extension
120
+ // hooks, which see the host's own error on failure; then whatever came back, or was thrown, crosses out so the
121
+ // program catches a copy. Functions inside results are part of a value's API and skip the hooks.
122
+ const wrap = (value, label, describe) => fn(builtins, value.name, value.length, (_, values) => {
123
+ const args = values.map((item, index) => toHost(item, `Argument ${index + 1} to ${label}`));
124
+ const hooks = ctx.tools.hooks;
125
+ const settle = (run) => (describe === undefined
126
+ ? run
127
+ : hooked(describe(args), hooks["extension.before"], hooks["extension.after"], run)).pipe(Effect.mapError((reason) => new Throw(fromHost(reason, label))), Effect.map((settled) => fromHost(settled, label)));
128
+ let result;
129
+ try {
130
+ result = value.apply(undefined, args);
131
+ }
132
+ catch (reason) {
133
+ return settle(Effect.fail(reason));
134
+ }
135
+ if (!(result instanceof Promise))
136
+ return settle(Effect.succeed(result));
137
+ return ctx.pending.create(settle(Effect.tryPromise({ try: () => result, catch: (reason) => reason })));
138
+ });
139
+ return extensions.flatMap((extension) => Object.entries(extension.globals).map(([name, value]) => [name, wrap(value, name, (args) => ({ extension: extension.name, name, args }))]));
140
+ };
141
+ const hostErrors = new Map([
142
+ ["TypeError", TypeError],
143
+ ["RangeError", RangeError],
144
+ ["SyntaxError", SyntaxError],
145
+ ["ReferenceError", ReferenceError],
146
+ ["EvalError", EvalError],
147
+ ["URIError", URIError],
148
+ ]);
149
+ // The primitives the interpreter operates on; symbols and BigInts are not among them.
150
+ const isPrimitive = (value) => value === null ||
151
+ value === undefined ||
152
+ typeof value === "string" ||
153
+ typeof value === "number" ||
154
+ typeof value === "boolean";
155
+ const describeHost = (value) => {
156
+ if (typeof value !== "object" || value === null)
157
+ return `a ${typeof value}`;
158
+ const name = value.constructor?.name;
159
+ return name === undefined || name === "" ? "an object" : `a ${name}`;
160
+ };
@@ -1,4 +1,3 @@
1
- import type { PromiseRuntime } from "./promises.js";
2
- import type { Runner } from "./runner.js";
1
+ import type { Interpreter } from "./interpreter.js";
3
2
  /** `next`/`return`/`throw` on the generator prototypes; async generators answer with promises. */
4
- export declare const generatorGlobals: <R>(runner: Runner<R>, promises: PromiseRuntime<R>) => void;
3
+ export declare const generatorGlobals: <R>(ctx: Interpreter<R>) => void;
@@ -1,24 +1,24 @@
1
1
  import { Effect } from "effect";
2
2
  import { fn, methods, receiver } from "./native.js";
3
3
  import { AsyncIteratorSymbol, IteratorSymbol } from "./model.js";
4
- import { define, hidden, ProgramGenerator } from "./objects.js";
4
+ import { define, hidden, GeneratorObj } from "./objects.js";
5
5
  /** `next`/`return`/`throw` on the generator prototypes; async generators answer with promises. */
6
- export const generatorGlobals = (runner, promises) => {
7
- const protos = runner.prototypes;
6
+ export const generatorGlobals = (ctx) => {
7
+ const builtins = ctx.builtins;
8
8
  const install = (asynchronous) => {
9
- const proto = asynchronous ? protos.AsyncGenerator : protos.Generator;
9
+ const proto = asynchronous ? builtins.AsyncGenerator : builtins.Generator;
10
10
  const label = asynchronous ? "AsyncGenerator" : "Generator";
11
11
  const request = (kind) => [
12
12
  kind,
13
13
  1,
14
- (thisValue, args, node) => {
15
- const generator = receiver(ProgramGenerator, thisValue, `${label}.prototype.${kind}`, node);
16
- const requested = generator.request(kind, args[0], node);
17
- return generator.asynchronous ? promises.create(requested) : requested;
14
+ (thisValue, args) => {
15
+ const generator = receiver(GeneratorObj, thisValue, `${label}.prototype.${kind}`);
16
+ const requested = generator.request(kind, args[0]);
17
+ return generator.asynchronous ? ctx.pending.create(requested) : requested;
18
18
  },
19
19
  ];
20
- methods(protos, proto, [request("next"), request("return"), request("throw")]);
21
- define(asynchronous ? protos.AsyncIterator : protos.Iterator, asynchronous ? AsyncIteratorSymbol : IteratorSymbol, fn(protos, asynchronous ? "[Symbol.asyncIterator]" : "[Symbol.iterator]", 0, (thisValue) => thisValue), hidden);
20
+ methods(builtins, proto, [request("next"), request("return"), request("throw")]);
21
+ define(asynchronous ? builtins.AsyncIterator : builtins.Iterator, asynchronous ? AsyncIteratorSymbol : IteratorSymbol, fn(builtins, asynchronous ? "[Symbol.asyncIterator]" : "[Symbol.iterator]", 0, (thisValue) => thisValue), hidden);
22
22
  };
23
23
  install(false);
24
24
  install(true);
@@ -1,13 +1,5 @@
1
- import { Effect } from "effect";
2
- import { type PromiseRuntime } from "./promises.js";
3
- import type { Runner } from "./runner.js";
4
- /** What the built-in globals need from the interpreter that owns them. */
5
- export type Host<R> = {
6
- readonly runner: Runner<R>;
7
- readonly promises: PromiseRuntime<R>;
8
- readonly search: (args: Array<unknown>) => Effect.Effect<unknown, unknown, R>;
9
- readonly toolKeys: (path: ReadonlyArray<string>) => ReadonlyArray<string>;
10
- readonly logs: Array<string>;
11
- };
1
+ import type { Interpreter } from "./interpreter.js";
2
+ /** Names bound in every program before extensions apply. */
3
+ export declare const globalNames: ReadonlySet<string>;
12
4
  /** The immutable global bindings of every program, in declaration order. */
13
- export declare const globals: <R>(host: Host<R>) => ReadonlyArray<readonly [string, unknown]>;
5
+ export declare const globals: <R>(ctx: Interpreter<R>) => ReadonlyArray<readonly [string, unknown]>;
@@ -1,5 +1,6 @@
1
1
  import { Effect } from "effect";
2
2
  import { arrayGlobal } from "../stdlib/array.js";
3
+ import { textDecoderGlobal, textEncoderGlobal, uint8ArrayGlobal } from "../stdlib/bytes.js";
3
4
  import { mapGlobal, setGlobal } from "../stdlib/collections.js";
4
5
  import { consoleGlobal } from "../stdlib/console.js";
5
6
  import { dateGlobal } from "../stdlib/date.js";
@@ -16,70 +17,76 @@ import { ToolReference } from "../tool-runtime.js";
16
17
  import { errorGlobal } from "./errors.js";
17
18
  import { errorTypes } from "./intrinsics.js";
18
19
  import { constants, constructor, native } from "./native.js";
19
- import { AsyncIteratorSymbol, InterpreterRuntimeError, IteratorSymbol } from "./model.js";
20
+ import { AsyncIteratorSymbol, IteratorSymbol, typeError } from "./model.js";
20
21
  import { generatorGlobals } from "./generators.js";
21
22
  import { promiseGlobal } from "./promises.js";
22
23
  // Function.prototype.constructor exists so `fn.constructor === Function` holds; dynamic code is unsupported.
23
- const functionGlobal = (runner) => {
24
- const reject = (_, __, node) => Effect.sync(() => {
25
- throw new InterpreterRuntimeError("The Function constructor is not supported; write the function inline.", node);
24
+ const functionGlobal = (ctx) => {
25
+ const reject = () => Effect.sync(() => {
26
+ throw typeError("The Function constructor is not supported; write the function inline.");
26
27
  });
27
- return constructor(runner.prototypes, runner.prototypes.Function, {
28
+ return constructor(ctx.builtins, ctx.builtins.Function, {
28
29
  name: "Function",
29
30
  length: 1,
30
31
  call: reject,
31
- construct: (args, _, node) => reject(undefined, args, node),
32
+ construct: reject,
32
33
  });
33
34
  };
34
- const symbolGlobal = (runner) => {
35
- const symbol = native(runner.prototypes, {
35
+ const symbolGlobal = (ctx) => {
36
+ const symbol = native(ctx.builtins, {
36
37
  name: "Symbol",
37
- call: (_, __, node) => Effect.sync(() => {
38
- throw new InterpreterRuntimeError("Symbol is not callable; only Symbol.asyncIterator and Symbol.iterator are available.", node);
38
+ call: () => Effect.sync(() => {
39
+ throw typeError("Symbol is not callable; only Symbol.asyncIterator and Symbol.iterator are available.");
39
40
  }),
40
41
  callback: false,
41
42
  });
42
43
  constants(symbol, { asyncIterator: AsyncIteratorSymbol, iterator: IteratorSymbol });
43
44
  return symbol;
44
45
  };
46
+ // A table rather than a list so the names are known before any runtime exists.
47
+ const table = {
48
+ tools: () => new ToolReference([]),
49
+ search: (ctx) => native(ctx.builtins, { name: "search", call: (_, args) => ctx.tool(ctx.tools.search, args), callback: false }),
50
+ undefined: () => undefined,
51
+ NaN: () => NaN,
52
+ Infinity: () => Infinity,
53
+ Object: (ctx) => objectGlobal(ctx),
54
+ Function: (ctx) => functionGlobal(ctx),
55
+ Array: (ctx) => arrayGlobal(ctx),
56
+ Math: (ctx) => mathGlobal(ctx),
57
+ JSON: (ctx) => jsonGlobal(ctx),
58
+ console: (ctx) => consoleGlobal(ctx),
59
+ Promise: (ctx) => promiseGlobal(ctx),
60
+ Symbol: (ctx) => symbolGlobal(ctx),
61
+ Number: (ctx) => numberGlobal(ctx),
62
+ String: (ctx) => stringGlobal(ctx),
63
+ Boolean: (ctx) => booleanGlobal(ctx),
64
+ parseInt: (ctx) => coercion(ctx, "parseInt", 2),
65
+ parseFloat: (ctx) => coercion(ctx, "parseFloat"),
66
+ isFinite: (ctx) => coercion(ctx, "isFinite"),
67
+ isNaN: (ctx) => coercion(ctx, "isNaN"),
68
+ Date: (ctx) => dateGlobal(ctx),
69
+ RegExp: (ctx) => regexpGlobal(ctx),
70
+ Map: (ctx) => mapGlobal(ctx),
71
+ Set: (ctx) => setGlobal(ctx),
72
+ URL: (ctx) => urlGlobal(ctx),
73
+ URLSearchParams: (ctx) => urlSearchParamsGlobal(ctx),
74
+ Uint8Array: (ctx) => uint8ArrayGlobal(ctx),
75
+ TextEncoder: (ctx) => textEncoderGlobal(ctx),
76
+ TextDecoder: (ctx) => textDecoderGlobal(ctx),
77
+ encodeURI: (ctx) => uriGlobal(ctx, "encodeURI"),
78
+ encodeURIComponent: (ctx) => uriGlobal(ctx, "encodeURIComponent"),
79
+ decodeURI: (ctx) => uriGlobal(ctx, "decodeURI"),
80
+ decodeURIComponent: (ctx) => uriGlobal(ctx, "decodeURIComponent"),
81
+ atob: (ctx) => base64Global(ctx, "atob"),
82
+ btoa: (ctx) => base64Global(ctx, "btoa"),
83
+ crypto: (ctx) => cryptoGlobal(ctx),
84
+ ...Object.fromEntries(errorTypes.map((type) => [type, (ctx) => errorGlobal(type, ctx)])),
85
+ };
86
+ /** Names bound in every program before extensions apply. */
87
+ export const globalNames = new Set(Object.keys(table));
45
88
  /** The immutable global bindings of every program, in declaration order. */
46
- export const globals = (host) => {
47
- const runner = host.runner;
48
- generatorGlobals(runner, host.promises);
49
- return [
50
- ["tools", new ToolReference([])],
51
- ["search", native(runner.prototypes, { name: "search", call: (_, args) => host.search(args), callback: false })],
52
- ["undefined", undefined],
53
- ["NaN", NaN],
54
- ["Infinity", Infinity],
55
- ["Object", objectGlobal(runner, host.toolKeys)],
56
- ["Function", functionGlobal(runner)],
57
- ["Array", arrayGlobal(runner)],
58
- ["Math", mathGlobal(runner)],
59
- ["JSON", jsonGlobal(runner)],
60
- ["console", consoleGlobal(runner, host.logs)],
61
- ["Promise", promiseGlobal(runner, host.promises)],
62
- ["Symbol", symbolGlobal(runner)],
63
- ["Number", numberGlobal(runner)],
64
- ["String", stringGlobal(runner)],
65
- ["Boolean", booleanGlobal(runner)],
66
- ["parseInt", coercion(runner, "parseInt", 2)],
67
- ["parseFloat", coercion(runner, "parseFloat")],
68
- ["isFinite", coercion(runner, "isFinite")],
69
- ["isNaN", coercion(runner, "isNaN")],
70
- ["Date", dateGlobal(runner)],
71
- ["RegExp", regexpGlobal(runner)],
72
- ["Map", mapGlobal(runner)],
73
- ["Set", setGlobal(runner)],
74
- ["URL", urlGlobal(runner)],
75
- ["URLSearchParams", urlSearchParamsGlobal(runner)],
76
- ["encodeURI", uriGlobal(runner, "encodeURI")],
77
- ["encodeURIComponent", uriGlobal(runner, "encodeURIComponent")],
78
- ["decodeURI", uriGlobal(runner, "decodeURI")],
79
- ["decodeURIComponent", uriGlobal(runner, "decodeURIComponent")],
80
- ["atob", base64Global(runner, "atob")],
81
- ["btoa", base64Global(runner, "btoa")],
82
- ["crypto", cryptoGlobal(runner)],
83
- ...errorTypes.map((type) => [type, errorGlobal(type, runner)]),
84
- ];
89
+ export const globals = (ctx) => {
90
+ generatorGlobals(ctx);
91
+ return Object.entries(table).map(([name, factory]) => [name, factory(ctx)]);
85
92
  };