@opencode/codemode 0.0.0-dev-19567 → 0.0.0-dev-19570

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.
@@ -5,7 +5,11 @@ import type { DiagnosticKind } from "../codemode.js";
5
5
  import type { ProgramError } from "./objects.js";
6
6
  /** Any parsed node; the interpreter narrows on `type` and reads `loc` for diagnostics. */
7
7
  export type AstNode = Node;
8
- export declare const CallSite: Context.Reference<Node | undefined>;
8
+ /** The program call a built-in is running under: where to locate failures born inside it, and how deep the stack is there. */
9
+ export declare const CallSite: Context.Reference<{
10
+ readonly node?: AstNode;
11
+ readonly depth: number;
12
+ }>;
9
13
  export type Binding = {
10
14
  mutable: boolean;
11
15
  value: unknown;
@@ -1,5 +1,8 @@
1
1
  import { Context } from "effect";
2
- export const CallSite = Context.Reference("codemode/CallSite", { defaultValue: () => undefined });
2
+ /** The program call a built-in is running under: where to locate failures born inside it, and how deep the stack is there. */
3
+ export const CallSite = Context.Reference("codemode/CallSite", {
4
+ defaultValue: () => ({ depth: 0 }),
5
+ });
3
6
  export const AsyncIteratorSymbol = Symbol("codemode.async-iterator");
4
7
  export const IteratorSymbol = Symbol("codemode.iterator");
5
8
  export const IteratorSymbols = [AsyncIteratorSymbol, IteratorSymbol];
@@ -35,7 +35,7 @@ export class PromiseRuntime {
35
35
  return Effect.flatMap(CallSite, (site) => {
36
36
  // Allocate before forking so reruns get distinct IDs and diagnostics retain creation order.
37
37
  const id = this.nextID++;
38
- const body = Effect.catchDefect(effect, (defect) => Effect.die(locate(defect, site)));
38
+ const body = Effect.catchDefect(effect, (defect) => Effect.die(locate(defect, site.node)));
39
39
  return Effect.map(Effect.forkIn(body, this.scope, { startImmediately: true }), (fiber) => {
40
40
  const promise = new ProgramPromise(this.proto, fiber);
41
41
  this.active.add(promise);
@@ -175,15 +175,20 @@ export class Runtime {
175
175
  return this.root.run(program);
176
176
  }
177
177
  }
178
+ const MAX_CALL_DEPTH = 10_000;
178
179
  /** One activation: the top-level program or a single function call, evaluating against its own scope chain. */
179
180
  class Frame {
180
181
  runtime;
181
182
  scopes;
183
+ depth;
182
184
  generatorState;
183
185
  generatorAsync = false;
184
- constructor(runtime, scopes) {
186
+ constructor(runtime, scopes,
187
+ /** Nested call depth; resets when an await resumes, since the continuation runs from the job queue. */
188
+ depth = 0) {
185
189
  this.runtime = runtime;
186
190
  this.scopes = scopes;
191
+ this.depth = depth;
187
192
  }
188
193
  run(program) {
189
194
  const self = this;
@@ -523,7 +528,7 @@ class Frame {
523
528
  })));
524
529
  }
525
530
  awaitValue(value) {
526
- return Effect.flatMap(resolvePromise(this.runtime.runner, this.runtime.promises, value), (promise) => this.settlePromise(promise));
531
+ return Effect.flatMap(resolvePromise(this.runtime.runner, this.runtime.promises, value), (promise) => Effect.ensuring(this.settlePromise(promise), Effect.sync(() => (this.depth = 0))));
527
532
  }
528
533
  awaitAsyncFromSyncValue(iterator, value, node, closeOnRejection) {
529
534
  const self = this;
@@ -1280,7 +1285,7 @@ class Frame {
1280
1285
  return yield* self.createToolCallPromise(callable.path, args);
1281
1286
  }
1282
1287
  if (callable instanceof ProgramFunction)
1283
- return yield* self.invokeFunction(callable, args);
1288
+ return yield* self.invokeFunction(callable, args, node);
1284
1289
  if (callable instanceof NativeFunction) {
1285
1290
  return yield* self.native(() => callable.call(thisValue, args), node);
1286
1291
  }
@@ -1289,7 +1294,7 @@ class Frame {
1289
1294
  }
1290
1295
  // Built-ins throw without a location, synchronously or inside their Effect; the call site supplies it.
1291
1296
  native(body, node) {
1292
- return Effect.provideService(Effect.catchDefect(Effect.suspend(body), (defect) => Effect.die(locate(defect, node))), CallSite, node);
1297
+ return Effect.provideService(Effect.catchDefect(Effect.suspend(body), (defect) => Effect.die(locate(defect, node))), CallSite, { node, depth: this.depth });
1293
1298
  }
1294
1299
  evaluateCallArguments(argNodes) {
1295
1300
  const self = this;
@@ -1315,37 +1320,43 @@ class Frame {
1315
1320
  return args;
1316
1321
  });
1317
1322
  }
1318
- invokeFunction(fn, args) {
1323
+ // A callback invoked by a built-in runs below the call that invoked the built-in, so the deeper of the two counts.
1324
+ invokeFunction(fn, args, node) {
1319
1325
  const self = this;
1320
- const invocation = new Frame(this.runtime, new ScopeStack([...fn.capturedScopes, new Map()]));
1321
- const run = Effect.gen(function* () {
1322
- // Seed all parameters first so defaults cannot fall through to same-named outer bindings.
1323
- const paramScope = invocation.scopes.current();
1324
- for (const parameter of fn.parameters) {
1325
- for (const name of collectPatternNames(parameter)) {
1326
- paramScope.set(name, { mutable: true, value: undefined, initialized: false });
1326
+ return Effect.flatMap(CallSite, (site) => {
1327
+ const depth = Math.max(self.depth, site.depth) + 1;
1328
+ if (depth > MAX_CALL_DEPTH)
1329
+ throw rangeError("Maximum call stack size exceeded", node);
1330
+ const invocation = new Frame(this.runtime, new ScopeStack([...fn.capturedScopes, new Map()]), depth);
1331
+ const run = Effect.gen(function* () {
1332
+ // Seed all parameters first so defaults cannot fall through to same-named outer bindings.
1333
+ const paramScope = invocation.scopes.current();
1334
+ for (const parameter of fn.parameters) {
1335
+ for (const name of collectPatternNames(parameter)) {
1336
+ paramScope.set(name, { mutable: true, value: undefined, initialized: false });
1337
+ }
1327
1338
  }
1328
- }
1329
- for (const [index, parameter] of fn.parameters.entries()) {
1330
- if (parameter.type === "RestElement") {
1331
- yield* invocation.declarePattern(parameter.argument, new ProgramArray(self.runtime.prototypes.Array, args.slice(index)), true, parameter, true);
1332
- break;
1339
+ for (const [index, parameter] of fn.parameters.entries()) {
1340
+ if (parameter.type === "RestElement") {
1341
+ yield* invocation.declarePattern(parameter.argument, new ProgramArray(self.runtime.prototypes.Array, args.slice(index)), true, parameter, true);
1342
+ break;
1343
+ }
1344
+ yield* invocation.declarePattern(parameter, args[index], true, parameter, true);
1333
1345
  }
1334
- yield* invocation.declarePattern(parameter, args[index], true, parameter, true);
1335
- }
1336
- if (fn.body.type === "BlockStatement") {
1337
- invocation.scopes.push();
1338
- invocation.hoistVars(fn.body.body, paramScope);
1339
- const result = yield* invocation.evaluateStatement(fn.body);
1340
- return result.kind === "return" ? result.value : undefined;
1341
- }
1342
- return yield* invocation.evaluateExpression(fn.body);
1346
+ if (fn.body.type === "BlockStatement") {
1347
+ invocation.scopes.push();
1348
+ invocation.hoistVars(fn.body.body, paramScope);
1349
+ const result = yield* invocation.evaluateStatement(fn.body);
1350
+ return result.kind === "return" ? result.value : undefined;
1351
+ }
1352
+ return yield* invocation.evaluateExpression(fn.body);
1353
+ });
1354
+ if (fn.generator)
1355
+ return Effect.succeed(this.createGenerator(invocation, run, fn.async));
1356
+ if (!fn.async)
1357
+ return run;
1358
+ return this.runtime.promises.createWithSelf((self) => Effect.flatMap(run, (value) => resolvePromiseValue(invocation.runtime.runner, value, self)));
1343
1359
  });
1344
- if (fn.generator)
1345
- return Effect.succeed(this.createGenerator(invocation, run, fn.async));
1346
- if (!fn.async)
1347
- return run;
1348
- return this.runtime.promises.createWithSelf((self) => Effect.flatMap(run, (value) => resolvePromiseValue(invocation.runtime.runner, value, self)));
1349
1360
  }
1350
1361
  createGenerator(invocation, run, asynchronous) {
1351
1362
  const state = { started: false, completed: false, draining: false, pending: [], pendingIndex: 0 };
@@ -3,7 +3,7 @@ import type { Prototypes } from "./interpreter/intrinsics.js";
3
3
  import { type Namespace } from "./namespace.js";
4
4
  import { type Tool } from "./tool.js";
5
5
  import type { Tools } from "./tools.js";
6
- export declare const compareText: (left: string, right: string) => 1 | -1 | 0;
6
+ export declare const compareText: (left: string, right: string) => 0 | 1 | -1;
7
7
  export type Services<T> = ServicesOf<T, []>;
8
8
  type ServicesOf<T, Depth extends ReadonlyArray<unknown>> = Depth["length"] extends 8 ? never : T extends {
9
9
  readonly _tag: "CodeModeTool";
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-19567",
4
+ "version": "0.0.0-dev-19570",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",