@opencode/codemode 0.0.0-dev-19436 → 0.0.0-dev-19438

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.
@@ -136,7 +136,7 @@ export declare const Result: Schema.Union<readonly [Schema.Struct<{
136
136
  export type Result = typeof Result.Type;
137
137
  /** Reusable confined runtime over explicit tools. */
138
138
  export type Runtime<R = never> = {
139
- readonly catalog: () => ReadonlyArray<ToolDescription>;
139
+ readonly catalog: ReadonlyArray<ToolDescription>;
140
140
  readonly execute: (code: string) => Effect.Effect<Result, never, R>;
141
141
  };
142
142
  /** Executes one Effect-native CodeMode program without constructing a reusable runtime. */
package/dist/codemode.js CHANGED
@@ -60,7 +60,7 @@ export const make = (options = {}) => {
60
60
  const prepared = ToolRuntime.prepare((options.tools ?? {}));
61
61
  const limits = resolveExecutionLimits(options.limits);
62
62
  return {
63
- catalog: () => prepared.catalog,
63
+ catalog: prepared.catalog,
64
64
  execute: (code) => executeProgram(code, prepared, limits, options),
65
65
  };
66
66
  };
@@ -12,6 +12,9 @@ export declare class PromiseRuntime<R> {
12
12
  private readonly failures;
13
13
  private nextID;
14
14
  constructor(scope: Scope.Scope);
15
+ createWithSelf(body: (self: {
16
+ promise?: Values.Promise;
17
+ }) => Effect.Effect<unknown, unknown, R>): Effect.Effect<Values.Promise, never, R>;
15
18
  create(effect: Effect.Effect<unknown, unknown, R>): Effect.Effect<Values.Promise, never, R>;
16
19
  markObserved(promise: Values.Promise): void;
17
20
  await(promise: Values.Promise): Effect.Effect<Exit.Exit<unknown, unknown>>;
@@ -22,6 +22,14 @@ export class PromiseRuntime {
22
22
  constructor(scope) {
23
23
  this.scope = scope;
24
24
  }
25
+ // Resolution bodies need the promise's own identity to reject `resolve(promise)` self-resolution.
26
+ createWithSelf(body) {
27
+ const self = {};
28
+ return Effect.map(this.create(body(self)), (promise) => {
29
+ self.promise = promise;
30
+ return promise;
31
+ });
32
+ }
25
33
  create(effect) {
26
34
  return Effect.suspend(() => {
27
35
  // Allocate before forking so reruns get distinct IDs and diagnostics retain creation order.
@@ -103,11 +111,7 @@ export const resolvePromiseValue = (runner, value, node, own) => {
103
111
  export const resolvePromise = (runner, promises, value, node) => {
104
112
  if (value instanceof Values.Promise)
105
113
  return Effect.succeed(value);
106
- const box = {};
107
- return Effect.map(promises.create(resolvePromiseValue(runner, value, node, box)), (promise) => {
108
- box.promise = promise;
109
- return promise;
110
- });
114
+ return promises.createWithSelf((self) => resolvePromiseValue(runner, value, node, self));
111
115
  };
112
116
  const promiseStatics = ["all", "allSettled", "race", "any", "resolve", "reject"];
113
117
  const invokePromiseMethod = (runner, promises, name, args, node) => {
@@ -184,9 +188,7 @@ const constructPromise = (runner, promises, executor, node) => {
184
188
  }
185
189
  return Effect.gen(function* () {
186
190
  const deferred = Deferred.makeUnsafe();
187
- const box = {};
188
- const promise = yield* promises.create(Effect.flatMap(Deferred.await(deferred), (value) => resolvePromiseValue(runner, value, node, box)));
189
- box.promise = promise;
191
+ const promise = yield* promises.createWithSelf((self) => Effect.flatMap(Deferred.await(deferred), (value) => resolvePromiseValue(runner, value, node, self)));
190
192
  const resolve = capability("resolve", (value) => Deferred.doneUnsafe(deferred, Exit.succeed(value)));
191
193
  const reject = capability("reject", (value) => Deferred.doneUnsafe(deferred, Exit.fail(new ProgramThrow(value))));
192
194
  const executed = yield* Effect.exit(runner.invokeFunction(executor, [resolve, reject]));
@@ -223,20 +225,15 @@ const reactionExit = (promises, source) => Effect.gen(function* () {
223
225
  return exit;
224
226
  });
225
227
  const chainReaction = (runner, promises, source, onFulfilled, onRejected, method, node) => {
226
- const box = {};
227
- const body = Effect.gen(function* () {
228
+ return promises.createWithSelf((self) => Effect.gen(function* () {
228
229
  const exit = yield* reactionExit(promises, source);
229
230
  const handler = Exit.isSuccess(exit) ? onFulfilled : onRejected;
230
231
  if (handler === undefined)
231
232
  return yield* exit;
232
233
  const input = Exit.isSuccess(exit) ? exit.value : caughtErrorValue(Cause.squash(exit.cause));
233
234
  const result = yield* applyCollectionCallback(runner, handler, method, node)([input]);
234
- return yield* resolvePromiseValue(runner, result, node, box);
235
- });
236
- return Effect.map(promises.create(body), (derived) => {
237
- box.promise = derived;
238
- return derived;
239
- });
235
+ return yield* resolvePromiseValue(runner, result, node, self);
236
+ }));
240
237
  };
241
238
  const chainFinally = (runner, promises, source, cleanup, method, node) => promises.create(Effect.gen(function* () {
242
239
  const exit = yield* reactionExit(promises, source);
@@ -19,6 +19,20 @@ import { uriArgument, urlMethods, urlProperties, urlSearchParamsMethods, urlWrit
19
19
  import { enumerableSource } from "../stdlib/object.js";
20
20
  import { coerceToNumber, coerceToString, compoundOperators, errorBrandName } from "../stdlib/value.js";
21
21
  import { Values } from "../values.js";
22
+ // What a loop does with its body's result: exit with a StatementResult, or undefined to keep iterating.
23
+ // Unlabelled break ends this loop; a label the loop does not carry propagates outward.
24
+ const loopExit = (result, labels) => {
25
+ if (result.kind === "return")
26
+ return result;
27
+ if (result.kind === "break") {
28
+ if (result.label !== undefined && !labels?.has(result.label))
29
+ return result;
30
+ return { kind: "none" };
31
+ }
32
+ if (result.kind === "continue" && result.label !== undefined && !labels?.has(result.label))
33
+ return result;
34
+ return undefined;
35
+ };
22
36
  const calleeDescription = (callee) => {
23
37
  if (callee?.type === "Identifier")
24
38
  return callee.name;
@@ -93,6 +107,53 @@ const collectPatternNames = (pattern, out = []) => {
93
107
  }
94
108
  return out;
95
109
  };
110
+ // `var` names declared anywhere in a function body except inside nested functions, which own theirs.
111
+ const collectVarNames = (node, out = []) => {
112
+ if (!node)
113
+ return out;
114
+ switch (node.type) {
115
+ case "VariableDeclaration":
116
+ if (node.kind === "var")
117
+ for (const declaration of node.declarations)
118
+ collectPatternNames(declaration.id, out);
119
+ break;
120
+ case "BlockStatement":
121
+ for (const statement of node.body)
122
+ collectVarNames(statement, out);
123
+ break;
124
+ case "IfStatement":
125
+ collectVarNames(node.consequent, out);
126
+ collectVarNames(node.alternate, out);
127
+ break;
128
+ case "ForStatement":
129
+ if (node.init?.type === "VariableDeclaration")
130
+ collectVarNames(node.init, out);
131
+ collectVarNames(node.body, out);
132
+ break;
133
+ case "ForInStatement":
134
+ case "ForOfStatement":
135
+ if (node.left.type === "VariableDeclaration")
136
+ collectVarNames(node.left, out);
137
+ collectVarNames(node.body, out);
138
+ break;
139
+ case "WhileStatement":
140
+ case "DoWhileStatement":
141
+ case "LabeledStatement":
142
+ collectVarNames(node.body, out);
143
+ break;
144
+ case "SwitchStatement":
145
+ for (const item of node.cases)
146
+ for (const statement of item.consequent)
147
+ collectVarNames(statement, out);
148
+ break;
149
+ case "TryStatement":
150
+ collectVarNames(node.block, out);
151
+ collectVarNames(node.handler?.body, out);
152
+ collectVarNames(node.finalizer, out);
153
+ break;
154
+ }
155
+ return out;
156
+ };
96
157
  const loopDeclaration = (left, statement) => {
97
158
  if (left.type !== "VariableDeclaration")
98
159
  return undefined;
@@ -169,6 +230,7 @@ class Frame {
169
230
  return Effect.gen(function* () {
170
231
  self.predeclareLexical(program.body);
171
232
  self.hoistFunctions(program.body);
233
+ self.hoistVars(program.body);
172
234
  let value = undefined;
173
235
  for (const [index, statement] of program.body.entries()) {
174
236
  if (index === program.body.length - 1 && statement.type === "ExpressionStatement") {
@@ -276,6 +338,18 @@ class Frame {
276
338
  this.scopes.declare(node.id.name, this.createFunction(node), true, node);
277
339
  }
278
340
  }
341
+ // Hoisted `var` bindings start undefined, or copy a same-named parameter. Function bodies hoist
342
+ // into their own scope above the parameters so closures in parameter defaults keep seeing outer names.
343
+ hoistVars(statements, parameters) {
344
+ const scope = this.scopes.current();
345
+ for (const statement of statements) {
346
+ for (const name of collectVarNames(statement)) {
347
+ if (scope.has(name))
348
+ continue;
349
+ scope.set(name, { mutable: true, value: parameters?.get(name)?.value, initialized: true });
350
+ }
351
+ }
352
+ }
279
353
  predeclareLexical(statements) {
280
354
  for (const statement of statements) {
281
355
  if (statement.type !== "VariableDeclaration")
@@ -352,20 +426,9 @@ class Frame {
352
426
  const self = this;
353
427
  return Effect.gen(function* () {
354
428
  while (yield* self.evaluateExpression(node.test)) {
355
- const result = yield* self.evaluateStatement(node.body);
356
- if (result.kind === "continue") {
357
- if (result.label !== undefined && !labels?.has(result.label))
358
- return result;
359
- continue;
360
- }
361
- if (result.kind === "break") {
362
- if (result.label !== undefined && !labels?.has(result.label))
363
- return result;
364
- return { kind: "none" };
365
- }
366
- if (result.kind === "return") {
367
- return result;
368
- }
429
+ const exit = loopExit(yield* self.evaluateStatement(node.body), labels);
430
+ if (exit !== undefined)
431
+ return exit;
369
432
  }
370
433
  return { kind: "none" };
371
434
  });
@@ -374,20 +437,9 @@ class Frame {
374
437
  const self = this;
375
438
  return Effect.gen(function* () {
376
439
  do {
377
- const result = yield* self.evaluateStatement(node.body);
378
- if (result.kind === "continue") {
379
- if (result.label !== undefined && !labels?.has(result.label))
380
- return result;
381
- continue;
382
- }
383
- if (result.kind === "break") {
384
- if (result.label !== undefined && !labels?.has(result.label))
385
- return result;
386
- return { kind: "none" };
387
- }
388
- if (result.kind === "return") {
389
- return result;
390
- }
440
+ const exit = loopExit(yield* self.evaluateStatement(node.body), labels);
441
+ if (exit !== undefined)
442
+ return exit;
391
443
  } while (yield* self.evaluateExpression(node.test));
392
444
  return { kind: "none" };
393
445
  });
@@ -422,24 +474,13 @@ class Frame {
422
474
  };
423
475
  nextIteration();
424
476
  while (testNode ? yield* self.evaluateExpression(testNode) : true) {
425
- const result = yield* self.evaluateStatement(node.body);
426
- if (result.kind === "return") {
427
- return result;
428
- }
429
- if (result.kind === "break") {
430
- if (result.label !== undefined && !labels?.has(result.label))
431
- return result;
432
- return { kind: "none" };
433
- }
434
- if (result.kind === "continue" && result.label !== undefined && !labels?.has(result.label))
435
- return result;
477
+ const exit = loopExit(yield* self.evaluateStatement(node.body), labels);
478
+ if (exit !== undefined)
479
+ return exit;
436
480
  nextIteration();
437
481
  if (updateNode) {
438
482
  yield* self.evaluateExpression(updateNode);
439
483
  }
440
- if (result.kind === "continue") {
441
- continue;
442
- }
443
484
  }
444
485
  return { kind: "none" };
445
486
  }).pipe(Effect.ensuring(Effect.sync(() => self.scopes.pop())));
@@ -470,18 +511,20 @@ class Frame {
470
511
  }
471
512
  const assignment = left.type === "VariableDeclaration" ? undefined : left;
472
513
  const evaluateBody = (value) => Effect.gen(function* () {
473
- if (declared) {
514
+ if (declared?.lexical) {
474
515
  self.scopes.push();
475
- if (declared.lexical)
476
- self.predeclarePattern(declared.pattern, declared.mutable, left);
477
- yield* self.declarePattern(declared.pattern, value, declared.mutable, left, declared.lexical);
516
+ self.predeclarePattern(declared.pattern, declared.mutable, left);
517
+ yield* self.declarePattern(declared.pattern, value, declared.mutable, left, true);
518
+ }
519
+ else if (declared) {
520
+ yield* self.assignPattern(declared.pattern, value, left);
478
521
  }
479
522
  else if (assignment) {
480
523
  yield* self.assignPattern(assignment, value, left);
481
524
  }
482
525
  return yield* self.evaluateStatement(node.body);
483
526
  }).pipe(Effect.ensuring(Effect.sync(() => {
484
- if (declared)
527
+ if (declared?.lexical)
485
528
  self.scopes.pop();
486
529
  })));
487
530
  while (true) {
@@ -499,20 +542,10 @@ class Frame {
499
542
  }
500
543
  return yield* Effect.failCause(bodyExit.cause);
501
544
  }
502
- const result = bodyExit.value;
503
- if (result.kind === "return") {
545
+ const exit = loopExit(bodyExit.value, labels);
546
+ if (exit !== undefined) {
504
547
  yield* close();
505
- return result;
506
- }
507
- if (result.kind === "break") {
508
- yield* close();
509
- if (result.label !== undefined && !labels?.has(result.label))
510
- return result;
511
- return { kind: "none" };
512
- }
513
- if (result.kind === "continue" && result.label !== undefined && !labels?.has(result.label)) {
514
- yield* close();
515
- return result;
548
+ return exit;
516
549
  }
517
550
  }
518
551
  }).pipe(Effect.ensuring(Effect.sync(() => {
@@ -691,33 +724,25 @@ class Frame {
691
724
  const assignmentName = left.type === "Identifier" ? left.name : undefined;
692
725
  for (const key of keys) {
693
726
  const result = yield* Effect.gen(function* () {
694
- if (declared) {
727
+ if (declared?.lexical) {
695
728
  self.scopes.push();
696
- if (declared.lexical)
697
- self.predeclarePattern(declared.pattern, declared.mutable, left);
698
- yield* self.declarePattern(declared.pattern, key, declared.mutable, left, declared.lexical);
729
+ self.predeclarePattern(declared.pattern, declared.mutable, left);
730
+ yield* self.declarePattern(declared.pattern, key, declared.mutable, left, true);
731
+ }
732
+ else if (declared) {
733
+ yield* self.assignPattern(declared.pattern, key, left);
699
734
  }
700
735
  else if (assignmentName) {
701
736
  self.scopes.set(assignmentName, key, left);
702
737
  }
703
738
  return yield* self.evaluateStatement(node.body);
704
739
  }).pipe(Effect.ensuring(Effect.sync(() => {
705
- if (declared)
740
+ if (declared?.lexical)
706
741
  self.scopes.pop();
707
742
  })));
708
- if (result.kind === "return") {
709
- return result;
710
- }
711
- if (result.kind === "break") {
712
- if (result.label !== undefined && !labels?.has(result.label))
713
- return result;
714
- return { kind: "none" };
715
- }
716
- if (result.kind === "continue") {
717
- if (result.label !== undefined && !labels?.has(result.label))
718
- return result;
719
- continue;
720
- }
743
+ const exit = loopExit(result, labels);
744
+ if (exit !== undefined)
745
+ return exit;
721
746
  }
722
747
  return { kind: "none" };
723
748
  }).pipe(Effect.ensuring(Effect.sync(() => {
@@ -798,8 +823,14 @@ class Frame {
798
823
  throw new InterpreterRuntimeError("Unsupported variable declaration shape.", declaration);
799
824
  }
800
825
  const init = declaration.init;
826
+ // `var x` alone is a no-op: the binding was hoisted on function entry.
827
+ if (kind === "var") {
828
+ if (init)
829
+ yield* self.assignPattern(declaration.id, yield* self.evaluateExpression(init), declaration);
830
+ continue;
831
+ }
801
832
  const value = init ? yield* self.evaluateExpression(init) : undefined;
802
- yield* self.declarePattern(declaration.id, value, kind !== "const", declaration, kind !== "var");
833
+ yield* self.declarePattern(declaration.id, value, kind !== "const", declaration, true);
803
834
  }
804
835
  });
805
836
  }
@@ -1357,6 +1388,8 @@ class Frame {
1357
1388
  yield* invocation.declarePattern(parameter, args[index], true, parameter, true);
1358
1389
  }
1359
1390
  if (fn.body.type === "BlockStatement") {
1391
+ invocation.scopes.push();
1392
+ invocation.hoistVars(fn.body.body, paramScope);
1360
1393
  const result = yield* invocation.evaluateStatement(fn.body);
1361
1394
  return result.kind === "return" ? result.value : undefined;
1362
1395
  }
@@ -97,13 +97,6 @@ const constructObject = (args, node) => {
97
97
  return first;
98
98
  throw new InterpreterRuntimeError(`Object(${typeof first}) wrapper objects are not supported; use the primitive value directly.`, node);
99
99
  };
100
- // Tool references are not data; only Object.keys(tools) reads them, for tool names.
101
- const rejectTools = (name, args, node) => {
102
- if (!(args[0] instanceof ToolReference))
103
- return;
104
- throw new InterpreterRuntimeError(`Object.${name}(...) cannot read tool references: they are not plain data. Use Object.keys(tools) for names, or search({ query }) for signatures.`, node, "InvalidDataValue");
105
- };
106
- const objectStatic = (name, impl) => sync(`Object.${name}`, impl);
107
100
  // Object constructs identically with or without new, like JS. Only `keys` copies its result into the
108
101
  // program; `values`, `entries`, `assign`, and `fromEntries` hand back the program's own values.
109
102
  export const objectGlobal = (runner, toolKeys) => new HostFunction({
@@ -115,22 +108,19 @@ export const objectGlobal = (runner, toolKeys) => new HostFunction({
115
108
  keys: sync("Object.keys", (args, node) => toProgram(args[0] instanceof ToolReference
116
109
  ? [...toolKeys(args[0].path)]
117
110
  : Object.keys(enumerableSource("Object.keys(...)", args[0], node)), "Object.keys result")),
118
- values: objectStatic("values", (args, node) => Object.values(enumerableSource("Object.values(...)", args[0], node))),
119
- entries: objectStatic("entries", (args, node) => Object.entries(enumerableSource("Object.entries(...)", args[0], node)).map(([key, item]) => [key, item])),
120
- hasOwn: objectStatic("hasOwn", (args, node) => Object.hasOwn(enumerableSource("Object.hasOwn(...)", args[0], node), args[1] === AsyncIteratorSymbol || args[1] === IteratorSymbol ? args[1] : String(args[1]))),
121
- is: objectStatic("is", (args, node) => {
111
+ values: sync("Object.values", (args, node) => Object.values(enumerableSource("Object.values(...)", args[0], node))),
112
+ entries: sync("Object.entries", (args, node) => Object.entries(enumerableSource("Object.entries(...)", args[0], node)).map(([key, item]) => [key, item])),
113
+ hasOwn: sync("Object.hasOwn", (args, node) => Object.hasOwn(enumerableSource("Object.hasOwn(...)", args[0], node), args[1] === AsyncIteratorSymbol || args[1] === IteratorSymbol ? args[1] : String(args[1]))),
114
+ is: sync("Object.is", (args, node) => {
122
115
  if (containsOpaqueReference(args[0]) || containsOpaqueReference(args[1])) {
123
116
  throw new InterpreterRuntimeError("Object.is requires data values.", node, "InvalidDataValue");
124
117
  }
125
118
  return Object.is(args[0], args[1]);
126
119
  }),
127
- assign: objectStatic("assign", objectAssign),
120
+ assign: sync("Object.assign", objectAssign),
128
121
  fromEntries: new HostFunction({
129
122
  name: "Object.fromEntries",
130
- call: (args, node) => Effect.suspend(() => {
131
- rejectTools("fromEntries", args, node);
132
- return objectFromEntries(runner, args[0], node);
133
- }),
123
+ call: (args, node) => Effect.suspend(() => objectFromEntries(runner, args[0], node)),
134
124
  }),
135
125
  groupBy: groupBy(runner, "Object"),
136
126
  },
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": "0.0.0-dev-19436",
4
+ "version": "0.0.0-dev-19438",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",