@opencode/codemode 0.0.0-dev-19959 → 0.0.0-dev-19962

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.
@@ -6,7 +6,7 @@ import { checkStringLength } from "./limits.js";
6
6
  import { locate, materialize } from "./errors.js";
7
7
  import { primitivePrototype } from "./intrinsics.js";
8
8
  import { globals } from "./globals.js";
9
- import { assign, Callable, define, get, hostCursor, IteratorObj, has, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
9
+ import { assign, Callable, define, get, hostCursor, IteratorObj, has, hidden, hasPrototype, keys, Native, parseArrayIndex, Arr, Fn, GeneratorObj, Obj, PromiseObj, record, remove, set, coerceToNumber, coerceToString, } from "./objects.js";
10
10
  import { preserveConsumerError } from "./callback.js";
11
11
  import { Pending, resolvePromise, resolvePromiseValue } from "./promises.js";
12
12
  import { containsOpaqueReference, describeValue, rejectCircularInsertion, typeofValue } from "./references.js";
@@ -318,7 +318,16 @@ class Frame {
318
318
  }).pipe(Effect.ensuring(Effect.sync(() => self.scopes.pop())));
319
319
  }
320
320
  createFunction(node, name = node.type === "ArrowFunctionExpression" ? "" : (node.id?.name ?? "")) {
321
- return new Fn(this.ctx.builtins.Function, name, node.params, node.body, this.scopes.capture(), node.async, node.generator);
321
+ const builtins = this.ctx.builtins;
322
+ const fn = new Fn(builtins.Function, name, node.params, node.body, this.scopes.capture(), node.async, node.generator);
323
+ // Each generator function gets its own prototype, so `g() instanceof g` holds as in JS.
324
+ if (node.generator)
325
+ define(fn, "prototype", new Obj(node.async ? builtins.AsyncGenerator : builtins.Generator), hidden);
326
+ // The body of a named function expression sees its own name, read-only.
327
+ if (node.type === "FunctionExpression" && node.id) {
328
+ fn.capturedScopes.push(new Map([[node.id.name, { mutable: false, value: fn, initialized: true }]]));
329
+ }
330
+ return fn;
322
331
  }
323
332
  // NamedEvaluation: an anonymous function definition takes the name of what it is assigned to.
324
333
  evaluateNamed(node, name) {
@@ -327,11 +336,12 @@ class Frame {
327
336
  }
328
337
  return this.evaluateExpression(node);
329
338
  }
339
+ // Repeated `function` declarations and `var` clashes are legal: the last declaration wins.
330
340
  hoistFunctions(statements) {
331
341
  for (const node of statements) {
332
342
  if (node.type !== "FunctionDeclaration")
333
343
  continue;
334
- this.scopes.declare(node.id.name, this.createFunction(node), true, node);
344
+ this.scopes.current().set(node.id.name, { mutable: true, value: this.createFunction(node), initialized: true });
335
345
  }
336
346
  }
337
347
  // Hoisted `var` bindings start undefined, or copy a same-named parameter. Function bodies hoist
@@ -1373,13 +1383,14 @@ class Frame {
1373
1383
  return yield* invocation.evaluateExpression(fn.body);
1374
1384
  });
1375
1385
  if (fn.generator)
1376
- return Effect.succeed(this.createGenerator(invocation, run, fn.async));
1386
+ return Effect.succeed(this.createGenerator(invocation, run, fn));
1377
1387
  if (!fn.async)
1378
1388
  return run;
1379
1389
  return this.ctx.pending.createWithSelf((self) => Effect.flatMap(run, (value) => resolvePromiseValue(invocation.ctx, value, self)));
1380
1390
  });
1381
1391
  }
1382
- createGenerator(invocation, run, asynchronous) {
1392
+ createGenerator(invocation, run, fn) {
1393
+ const asynchronous = fn.async;
1383
1394
  const state = { started: false, completed: false, draining: false, pending: [], pendingIndex: 0 };
1384
1395
  invocation.generatorState = state;
1385
1396
  invocation.generatorAsync = asynchronous;
@@ -1438,8 +1449,8 @@ class Frame {
1438
1449
  }
1439
1450
  return Deferred.await(request.response);
1440
1451
  };
1441
- const generator = new GeneratorObj(asynchronous ? builtins.AsyncGenerator : builtins.Generator, asynchronous, request);
1442
- return generator;
1452
+ const proto = get(fn, "prototype");
1453
+ return new GeneratorObj(proto instanceof Obj ? proto : builtins.Generator, asynchronous, request);
1443
1454
  }
1444
1455
  completeGeneratorRequests(state, asynchronous) {
1445
1456
  const self = this;
@@ -1751,9 +1762,11 @@ class Frame {
1751
1762
  }
1752
1763
  evaluateDeleteExpression(argument) {
1753
1764
  const target = argument.type === "ChainExpression" ? argument.expression : argument;
1754
- if (target.type !== "MemberExpression") {
1765
+ if (target.type === "Identifier")
1755
1766
  throw typeError("Only data fields may be deleted.", argument);
1756
- }
1767
+ // `delete <non-reference>` evaluates the operand and is true, as in JS.
1768
+ if (target.type !== "MemberExpression")
1769
+ return Effect.map(this.evaluateExpression(target), () => true);
1757
1770
  return Effect.map(this.getMemberReference(target), (reference) => {
1758
1771
  if (reference === OptionalShortCircuit)
1759
1772
  return true;
@@ -87,10 +87,10 @@ export declare abstract class Callable extends Opaque {
87
87
  export declare class Fn extends Callable {
88
88
  readonly parameters: ReadonlyArray<Pattern>;
89
89
  readonly body: BlockStatement | Expression;
90
- readonly capturedScopes: ReadonlyArray<Map<string, Binding>>;
90
+ readonly capturedScopes: Array<Map<string, Binding>>;
91
91
  readonly async: boolean;
92
92
  readonly generator: boolean;
93
- constructor(proto: Obj, name: string, parameters: ReadonlyArray<Pattern>, body: BlockStatement | Expression, capturedScopes: ReadonlyArray<Map<string, Binding>>, async: boolean, generator: boolean);
93
+ constructor(proto: Obj, name: string, parameters: ReadonlyArray<Pattern>, body: BlockStatement | Expression, capturedScopes: Array<Map<string, Binding>>, async: boolean, generator: boolean);
94
94
  }
95
95
  export type NativeCall<R> = (thisValue: Value, args: Array<Value>) => Effect.Effect<Value, unknown, R>;
96
96
  export type NativeConstruct<R> = (args: Array<Value>, newTarget: Callable) => Effect.Effect<Value, unknown, R>;
@@ -166,7 +166,7 @@ export const mapGlobal = (ctx) => {
166
166
  const target = self(thisValue, "forEach");
167
167
  const apply = applyCollectionCallback(ctx, args[0], "Map.forEach");
168
168
  return Effect.gen(function* () {
169
- for (const [key, item] of Array.from(target.map.entries()))
169
+ for (const [key, item] of target.map.entries())
170
170
  yield* apply([item, key, target]);
171
171
  return undefined;
172
172
  });
@@ -356,7 +356,7 @@ export const setGlobal = (ctx) => {
356
356
  const target = self(thisValue, "forEach");
357
357
  const apply = applyCollectionCallback(ctx, args[0], "Set.forEach");
358
358
  return Effect.gen(function* () {
359
- for (const item of Array.from(target.set.values()))
359
+ for (const item of target.set.values())
360
360
  yield* apply([item, item, target]);
361
361
  return undefined;
362
362
  });
@@ -109,6 +109,24 @@ const termForms = (term) => {
109
109
  forms.push(term.slice(0, -1));
110
110
  return forms;
111
111
  };
112
+ const rank = (entries, query) => {
113
+ const terms = tokenize(query).map(termForms);
114
+ return entries
115
+ .map((entry) => {
116
+ const path = entry.description.path.toLowerCase();
117
+ const description = entry.description.description.toLowerCase();
118
+ const score = terms.reduce((total, forms) => total +
119
+ (forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
120
+ (forms.some((form) => entry.pathWords.includes(form)) ? 12 : 0) +
121
+ (forms.some((form) => path.includes(form)) ? 8 : 0) +
122
+ (forms.some((form) => description.includes(form)) ? 4 : 0) +
123
+ (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0), 0);
124
+ return { entry, score };
125
+ })
126
+ .filter(({ score }) => terms.length === 0 || score > 0)
127
+ .sort((left, right) => right.score - left.score || compareText(left.entry.description.path, right.entry.description.path))
128
+ .map(({ entry }) => entry);
129
+ };
112
130
  const makeSearchTool = (searchIndex) => ({
113
131
  _tag: "CodeModeTool",
114
132
  description: "Search available tools",
@@ -133,24 +151,7 @@ const makeSearchTool = (searchIndex) => ({
133
151
  const exact = pathQuery === ""
134
152
  ? undefined
135
153
  : scoped.find((entry) => entry.description.path === pathQuery || toolExpression(entry.description.path) === trimmed);
136
- const terms = tokenize(query).map(termForms);
137
- const ranked = exact !== undefined
138
- ? [exact]
139
- : scoped
140
- .map((entry) => {
141
- const path = entry.description.path.toLowerCase();
142
- const description = entry.description.description.toLowerCase();
143
- const score = terms.reduce((total, forms) => total +
144
- (forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
145
- (forms.some((form) => entry.pathWords.includes(form)) ? 12 : 0) +
146
- (forms.some((form) => path.includes(form)) ? 8 : 0) +
147
- (forms.some((form) => description.includes(form)) ? 4 : 0) +
148
- (forms.some((form) => entry.searchText.includes(form)) ? 2 : 0), 0);
149
- return { entry, score };
150
- })
151
- .filter(({ score }) => terms.length === 0 || score > 0)
152
- .sort((left, right) => right.score - left.score || compareText(left.entry.description.path, right.entry.description.path))
153
- .map(({ entry }) => entry);
154
+ const ranked = exact !== undefined ? [exact] : rank(scoped, query);
154
155
  const items = ranked.slice(offset, offset + (request.limit ?? defaultSearchLimit)).map(({ description }) => ({
155
156
  ...description,
156
157
  path: toolExpression(description.path),
@@ -207,13 +208,16 @@ const namespaceKeys = (root, path) => {
207
208
  }
208
209
  return Array.from(node.children.keys());
209
210
  };
210
- const resolve = (root, path) => {
211
+ const resolve = (root, path, index) => {
211
212
  const segments = canonicalSegments(path);
212
213
  const node = lookup(root, segments);
213
214
  if (node === undefined) {
214
- throw new ToolRuntimeError("UnknownTool", `Unknown tool '${segments.join(".")}'.`, [
215
- "The tool may have been removed or renamed. Use search to find available tools.",
216
- ]);
215
+ const name = segments.join(".");
216
+ const ns = segments.length > 1 && root.children.has(segments[0]) ? segments[0] : undefined;
217
+ const closest = rank(ns ? index.filter((entry) => entry.description.path.startsWith(`${ns}.`)) : index, ns ? segments.slice(1).join(" ") : name)[0];
218
+ throw new ToolRuntimeError("UnknownTool", closest
219
+ ? `Unknown tool '${name}'. Did you mean ${toolExpression(closest.description.path)}?`
220
+ : `Unknown tool '${name}'.`, ["Use search to find available tools."]);
217
221
  }
218
222
  if (node.tool === undefined) {
219
223
  throw new ToolRuntimeError("UnknownTool", `Tool '${segments.join(".")}' is not callable.`);
@@ -277,7 +281,7 @@ export const make = (prepared, maxToolCalls, hooks = {}) => {
277
281
  // Models often write `tools.search(...)` for the bare `search(...)`; honor it unless a tool owns that path.
278
282
  if (segments.length === 1 && segments[0] === "search" && lookup(root, segments) === undefined)
279
283
  return executeTool("search", makeSearchTool(prepared.searchIndex), args);
280
- return executeTool(segments.join("."), resolve(root, path), args);
284
+ return executeTool(segments.join("."), resolve(root, path, prepared.searchIndex), args);
281
285
  }),
282
286
  };
283
287
  };
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-19959",
4
+ "version": "0.0.0-dev-19962",
5
5
  "description": "Effect-native confined code execution over schema-described tools",
6
6
  "type": "module",
7
7
  "license": "MIT",