pi-ast-sgrep 1.4.0 → 2.0.0
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 +42 -12
- package/dist/code-mode.d.ts +18 -10
- package/dist/code-mode.js +126 -42
- package/dist/codemode/connector.d.ts +37 -9
- package/dist/codemode/connector.js +29 -23
- package/dist/codemode/dispatch.d.ts +2 -3
- package/dist/codemode/dispatch.js +109 -131
- package/dist/codemode/index.d.ts +8 -9
- package/dist/codemode/index.js +7 -8
- package/dist/codemode/native.d.ts +6 -9
- package/dist/codemode/native.js +10 -1
- package/dist/codemode/runner.d.ts +37 -0
- package/dist/codemode/runner.js +242 -0
- package/dist/codemode/sandbox-worker.d.ts +1 -0
- package/dist/codemode/sandbox-worker.js +204 -0
- package/dist/codemode/session-pool.d.ts +1 -0
- package/dist/codemode/session-pool.js +94 -29
- package/dist/codemode/types.d.ts +2 -2
- package/dist/codemode/types.js +2 -2
- package/dist/codemode/worker.d.ts +3 -0
- package/dist/codemode/worker.js +99 -42
- package/dist/index.d.ts +6 -1
- package/dist/index.js +192 -106
- package/dist/present.d.ts +67 -0
- package/dist/present.js +166 -0
- package/dist/runtime.d.ts +16 -2
- package/dist/runtime.js +399 -131
- package/package.json +6 -14
- package/dist/codemode/sandbox.d.ts +0 -25
- package/dist/codemode/sandbox.js +0 -192
- package/skills/ast-sgrep/SKILL.md +0 -73
- package/skills/ast-sgrep/references/query-guide.md +0 -22
|
@@ -8,6 +8,20 @@ import { mkdtemp, writeFile, rm } from "node:fs/promises";
|
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
const MAX_WAVE = 32;
|
|
11
|
+
const MUTATING_TOOLS = new Set(["index_repo"]);
|
|
12
|
+
const abortError = () => Object.assign(new Error("codemode aborted"), { name: "AbortError" });
|
|
13
|
+
function rejectWave(wave, cause) {
|
|
14
|
+
for (const item of wave)
|
|
15
|
+
item.reject(cause);
|
|
16
|
+
}
|
|
17
|
+
function sharedBatchOptions(wave) {
|
|
18
|
+
const signal = wave[0]?.options?.signal;
|
|
19
|
+
return signal && wave.every((item) => item.options?.signal === signal) ? { signal } : undefined;
|
|
20
|
+
}
|
|
21
|
+
function isSharedAbort(cause, options) {
|
|
22
|
+
return options?.signal !== undefined
|
|
23
|
+
&& (options.signal.aborted || (cause instanceof Error && cause.name === "AbortError"));
|
|
24
|
+
}
|
|
11
25
|
/**
|
|
12
26
|
* Wraps a host so Promise.all([asgrep.search, asgrep.defs, …]) collapses into
|
|
13
27
|
* one microtask wave. Prefers sticky serve → one-shot batch → overlapped spawn.
|
|
@@ -17,7 +31,7 @@ export function createCodemodeDispatcher(host) {
|
|
|
17
31
|
let scheduled = false;
|
|
18
32
|
let stats = emptyStats();
|
|
19
33
|
const flush = async () => {
|
|
20
|
-
const wave = pending;
|
|
34
|
+
const wave = pending.filter((item) => !item.settled);
|
|
21
35
|
pending = [];
|
|
22
36
|
scheduled = false;
|
|
23
37
|
if (wave.length === 0)
|
|
@@ -32,7 +46,9 @@ export function createCodemodeDispatcher(host) {
|
|
|
32
46
|
}
|
|
33
47
|
// Chunk oversized waves (batch max = 32).
|
|
34
48
|
for (let offset = 0; offset < wave.length; offset += MAX_WAVE) {
|
|
35
|
-
const chunk = wave.slice(offset, offset + MAX_WAVE);
|
|
49
|
+
const chunk = wave.slice(offset, offset + MAX_WAVE).filter((item) => !item.settled);
|
|
50
|
+
if (chunk.length === 0)
|
|
51
|
+
continue;
|
|
36
52
|
await settleWave(host, chunk, stats);
|
|
37
53
|
}
|
|
38
54
|
}
|
|
@@ -41,8 +57,28 @@ export function createCodemodeDispatcher(host) {
|
|
|
41
57
|
}
|
|
42
58
|
};
|
|
43
59
|
const enqueue = (item) => new Promise((resolve, reject) => {
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
const signal = item.options?.signal;
|
|
61
|
+
const cleanup = () => signal?.removeEventListener("abort", onAbort);
|
|
62
|
+
item.resolve = (value) => {
|
|
63
|
+
if (item.settled)
|
|
64
|
+
return;
|
|
65
|
+
item.settled = true;
|
|
66
|
+
cleanup();
|
|
67
|
+
resolve(value);
|
|
68
|
+
};
|
|
69
|
+
item.reject = (reason) => {
|
|
70
|
+
if (item.settled)
|
|
71
|
+
return;
|
|
72
|
+
item.settled = true;
|
|
73
|
+
cleanup();
|
|
74
|
+
reject(reason);
|
|
75
|
+
};
|
|
76
|
+
const onAbort = () => item.reject(abortError());
|
|
77
|
+
if (signal?.aborted) {
|
|
78
|
+
item.reject(abortError());
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
46
82
|
pending.push(item);
|
|
47
83
|
if (!scheduled) {
|
|
48
84
|
scheduled = true;
|
|
@@ -53,20 +89,11 @@ export function createCodemodeDispatcher(host) {
|
|
|
53
89
|
});
|
|
54
90
|
const dispatchHost = {
|
|
55
91
|
call(tool, args, context, options) {
|
|
56
|
-
const item = { tool, args, context, resolve: () => undefined, reject: () => undefined };
|
|
57
|
-
if (options)
|
|
58
|
-
item.options = options;
|
|
59
|
-
return enqueue(item);
|
|
60
|
-
},
|
|
61
|
-
run(args, context, options) {
|
|
62
|
-
// Legacy argv: still coalesce, but tool inference is best-effort only.
|
|
63
|
-
const tool = toolFromArgs(args);
|
|
64
|
-
const callArgs = argsObjectFromArgv(args);
|
|
65
92
|
const item = {
|
|
66
93
|
tool,
|
|
67
|
-
args
|
|
68
|
-
argv: args,
|
|
94
|
+
args,
|
|
69
95
|
context,
|
|
96
|
+
settled: false,
|
|
70
97
|
resolve: () => undefined,
|
|
71
98
|
reject: () => undefined,
|
|
72
99
|
};
|
|
@@ -91,7 +118,7 @@ async function settleOne(host, item, stats) {
|
|
|
91
118
|
return;
|
|
92
119
|
}
|
|
93
120
|
// N=1 without sticky: direct CLI (batch-of-1 is tempfile + protocol for no gain).
|
|
94
|
-
const args =
|
|
121
|
+
const args = argvFor(item.tool, item.args);
|
|
95
122
|
item.resolve(await host.run(args, item.context, item.options));
|
|
96
123
|
}
|
|
97
124
|
catch (err) {
|
|
@@ -100,43 +127,65 @@ async function settleOne(host, item, stats) {
|
|
|
100
127
|
}
|
|
101
128
|
async function settleWave(host, wave, stats) {
|
|
102
129
|
if (host.sticky) {
|
|
130
|
+
const transportOptions = sharedBatchOptions(wave);
|
|
103
131
|
try {
|
|
104
132
|
const calls = wave.map((item, index) => ({
|
|
105
133
|
id: String(index),
|
|
106
134
|
tool: item.tool,
|
|
107
135
|
args: item.args,
|
|
108
136
|
}));
|
|
109
|
-
const batch = await host.sticky.batch(calls,
|
|
137
|
+
const batch = await host.sticky.batch(calls, transportOptions);
|
|
110
138
|
stats.stickyCalls += wave.length;
|
|
111
139
|
settleFromBatch(wave, batch);
|
|
112
140
|
return;
|
|
113
141
|
}
|
|
114
142
|
catch (cause) {
|
|
143
|
+
if (isSharedAbort(cause, transportOptions)) {
|
|
144
|
+
rejectWave(wave, cause);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (wave.some((item) => MUTATING_TOOLS.has(item.tool))) {
|
|
148
|
+
// The worker may have committed a mutation before its transport died.
|
|
149
|
+
// Replaying the wave through another transport would be ambiguous.
|
|
150
|
+
rejectWave(wave, cause);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
115
153
|
// Sticky died mid-program — fall through to one-shot / spawn.
|
|
116
|
-
void cause;
|
|
117
154
|
}
|
|
118
155
|
}
|
|
156
|
+
const batchWave = wave.filter((item) => !item.settled);
|
|
157
|
+
if (batchWave.length === 0)
|
|
158
|
+
return;
|
|
119
159
|
if (host.runBatch) {
|
|
160
|
+
const transportOptions = sharedBatchOptions(batchWave);
|
|
120
161
|
try {
|
|
121
|
-
const calls =
|
|
162
|
+
const calls = batchWave.map((item, index) => ({
|
|
122
163
|
id: String(index),
|
|
123
164
|
tool: item.tool,
|
|
124
165
|
args: item.args,
|
|
125
166
|
}));
|
|
126
|
-
const batch = await host.runBatch(calls,
|
|
127
|
-
stats.batchedCalls +=
|
|
128
|
-
settleFromBatch(
|
|
167
|
+
const batch = await host.runBatch(calls, batchWave[0].context, transportOptions);
|
|
168
|
+
stats.batchedCalls += batchWave.length;
|
|
169
|
+
settleFromBatch(batchWave, batch);
|
|
129
170
|
return;
|
|
130
171
|
}
|
|
131
172
|
catch (cause) {
|
|
173
|
+
if (isSharedAbort(cause, transportOptions)) {
|
|
174
|
+
rejectWave(batchWave, cause);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (batchWave.some((item) => MUTATING_TOOLS.has(item.tool))) {
|
|
178
|
+
rejectWave(batchWave, cause);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
132
181
|
// Transport failure only — do NOT re-run when per-call ok:false.
|
|
133
|
-
void cause;
|
|
134
182
|
}
|
|
135
183
|
}
|
|
136
|
-
|
|
137
|
-
|
|
184
|
+
const spawnWave = batchWave.filter((item) => !item.settled);
|
|
185
|
+
stats.parallelSpawnCalls += spawnWave.length;
|
|
186
|
+
await Promise.all(spawnWave.map(async (item) => {
|
|
138
187
|
try {
|
|
139
|
-
const args =
|
|
188
|
+
const args = argvFor(item.tool, item.args);
|
|
140
189
|
item.resolve(await host.run(args, item.context, item.options));
|
|
141
190
|
}
|
|
142
191
|
catch (err) {
|
|
@@ -170,122 +219,51 @@ function emptyStats() {
|
|
|
170
219
|
wallMs: 0,
|
|
171
220
|
};
|
|
172
221
|
}
|
|
173
|
-
|
|
222
|
+
const ARGV_SPEC = {
|
|
223
|
+
search: { form: "capsule", key: "query" },
|
|
224
|
+
semantic: { form: "semantic" },
|
|
225
|
+
chain: { form: "chain" },
|
|
226
|
+
defs: { form: "capsule", key: "symbol", prefix: "defs" },
|
|
227
|
+
callers: { form: "capsule", key: "symbol", prefix: "callers" },
|
|
228
|
+
imports: { form: "capsule", key: "module", prefix: "imports" },
|
|
229
|
+
index_status: { form: "status" },
|
|
230
|
+
index_repo: { form: "index_repo" },
|
|
231
|
+
};
|
|
232
|
+
function argStr(args, key) {
|
|
233
|
+
return String(args[key] ?? "");
|
|
234
|
+
}
|
|
174
235
|
export function argvFor(tool, args) {
|
|
236
|
+
const spec = ARGV_SPEC[tool];
|
|
237
|
+
if (!spec)
|
|
238
|
+
throw new Error(`codemode tool has no direct CLI fallback: ${tool}`);
|
|
239
|
+
if (spec.form === "status")
|
|
240
|
+
return ["status", ".", "--json"];
|
|
241
|
+
if (spec.form === "index_repo") {
|
|
242
|
+
const command = args.force === true ? "reindex" : "index";
|
|
243
|
+
const paths = Array.isArray(args.paths)
|
|
244
|
+
? args.paths.filter((path) => typeof path === "string")
|
|
245
|
+
: [];
|
|
246
|
+
return [command, ".", "--json", ...paths.flatMap((path) => ["--path", path])];
|
|
247
|
+
}
|
|
175
248
|
const limit = num(args.limit, 8);
|
|
249
|
+
if (spec.form === "chain") {
|
|
250
|
+
return ["chain", argStr(args, "query"), ".", "--json", "--limit", String(limit)];
|
|
251
|
+
}
|
|
176
252
|
const excerpt = num(args.excerpt_lines ?? args.excerptLines, 0);
|
|
177
253
|
const capsule = ["--json", "--format", "agent-capsule", "--limit", String(limit), "--excerpt-lines", String(excerpt)];
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return [...capsule, String(args.query ?? ""), "."];
|
|
181
|
-
case "semantic":
|
|
182
|
-
return ["semantic", String(args.query ?? ""), ".", ...capsule];
|
|
183
|
-
case "chain":
|
|
184
|
-
return ["chain", String(args.query ?? ""), ".", "--json", "--limit", String(limit)];
|
|
185
|
-
case "defs":
|
|
186
|
-
return [...capsule, `defs:${String(args.symbol ?? "")}`, "."];
|
|
187
|
-
case "callers":
|
|
188
|
-
return [...capsule, `callers:${String(args.symbol ?? "")}`, "."];
|
|
189
|
-
case "imports":
|
|
190
|
-
return [...capsule, `imports:${String(args.module ?? "")}`, "."];
|
|
191
|
-
case "index_status":
|
|
192
|
-
return ["status", ".", "--json"];
|
|
193
|
-
case "index_repo":
|
|
194
|
-
return [args.force === true ? "reindex" : "index", ".", "--json"];
|
|
195
|
-
case "catalog_search":
|
|
196
|
-
case "catalog_describe":
|
|
197
|
-
// No CLI equivalent — sticky/batch only.
|
|
198
|
-
return ["status", ".", "--json"];
|
|
199
|
-
default:
|
|
200
|
-
return [...capsule, String(args.query ?? ""), "."];
|
|
254
|
+
if (spec.form === "semantic") {
|
|
255
|
+
return ["semantic", argStr(args, "query"), ".", ...capsule];
|
|
201
256
|
}
|
|
257
|
+
// capsule (+ optional prefix for defs/callers/imports)
|
|
258
|
+
const raw = argStr(args, spec.key);
|
|
259
|
+
const token = spec.prefix ? `${spec.prefix}:${raw}` : raw;
|
|
260
|
+
return [...capsule, token, "."];
|
|
202
261
|
}
|
|
203
262
|
function num(value, fallback) {
|
|
204
263
|
if (typeof value === "number" && Number.isFinite(value))
|
|
205
264
|
return Math.trunc(value);
|
|
206
265
|
return fallback;
|
|
207
266
|
}
|
|
208
|
-
function toolFromArgs(args) {
|
|
209
|
-
if (args[0] === "semantic")
|
|
210
|
-
return "semantic";
|
|
211
|
-
if (args[0] === "chain")
|
|
212
|
-
return "chain";
|
|
213
|
-
if (args[0] === "status")
|
|
214
|
-
return "index_status";
|
|
215
|
-
if (args[0] === "index" || args[0] === "reindex")
|
|
216
|
-
return "index_repo";
|
|
217
|
-
if (args[0] === "codemode-batch" || args[0] === "codemode-serve")
|
|
218
|
-
return "search";
|
|
219
|
-
const query = args.length >= 2 ? args[args.length - 2] : "";
|
|
220
|
-
// Only classify prefix forms when the whole query is a navigator, so
|
|
221
|
-
// search({ query: "defs: auth in login" }) stays search.
|
|
222
|
-
if (/^defs:\s*\S+$/.test(query))
|
|
223
|
-
return "defs";
|
|
224
|
-
if (/^callers:\s*\S+$/.test(query))
|
|
225
|
-
return "callers";
|
|
226
|
-
if (/^imports:\s*\S+$/.test(query))
|
|
227
|
-
return "imports";
|
|
228
|
-
return "search";
|
|
229
|
-
}
|
|
230
|
-
function argsObjectFromArgv(args) {
|
|
231
|
-
if (args[0] === "status")
|
|
232
|
-
return {};
|
|
233
|
-
if (args[0] === "index")
|
|
234
|
-
return { force: false };
|
|
235
|
-
if (args[0] === "reindex")
|
|
236
|
-
return { force: true };
|
|
237
|
-
if (args[0] === "semantic" || args[0] === "chain") {
|
|
238
|
-
const query = args[1] ?? "";
|
|
239
|
-
const limit = readFlag(args, "--limit");
|
|
240
|
-
const excerptLines = readFlag(args, "--excerpt-lines");
|
|
241
|
-
const out = { query };
|
|
242
|
-
if (limit !== undefined)
|
|
243
|
-
out.limit = limit;
|
|
244
|
-
if (excerptLines !== undefined)
|
|
245
|
-
out.excerpt_lines = excerptLines;
|
|
246
|
-
return out;
|
|
247
|
-
}
|
|
248
|
-
const query = args.length >= 2 ? args[args.length - 2] : "";
|
|
249
|
-
const limit = readFlag(args, "--limit");
|
|
250
|
-
const excerptLines = readFlag(args, "--excerpt-lines");
|
|
251
|
-
if (/^defs:\s*/.test(query)) {
|
|
252
|
-
const out = { symbol: query.replace(/^defs:\s*/, "").trim() };
|
|
253
|
-
if (limit !== undefined)
|
|
254
|
-
out.limit = limit;
|
|
255
|
-
if (excerptLines !== undefined)
|
|
256
|
-
out.excerpt_lines = excerptLines;
|
|
257
|
-
return out;
|
|
258
|
-
}
|
|
259
|
-
if (/^callers:\s*/.test(query)) {
|
|
260
|
-
const out = { symbol: query.replace(/^callers:\s*/, "").trim() };
|
|
261
|
-
if (limit !== undefined)
|
|
262
|
-
out.limit = limit;
|
|
263
|
-
if (excerptLines !== undefined)
|
|
264
|
-
out.excerpt_lines = excerptLines;
|
|
265
|
-
return out;
|
|
266
|
-
}
|
|
267
|
-
if (/^imports:\s*/.test(query)) {
|
|
268
|
-
const out = { module: query.replace(/^imports:\s*/, "").trim() };
|
|
269
|
-
if (limit !== undefined)
|
|
270
|
-
out.limit = limit;
|
|
271
|
-
if (excerptLines !== undefined)
|
|
272
|
-
out.excerpt_lines = excerptLines;
|
|
273
|
-
return out;
|
|
274
|
-
}
|
|
275
|
-
const out = { query, format: "capsule" };
|
|
276
|
-
if (limit !== undefined)
|
|
277
|
-
out.limit = limit;
|
|
278
|
-
if (excerptLines !== undefined)
|
|
279
|
-
out.excerpt_lines = excerptLines;
|
|
280
|
-
return out;
|
|
281
|
-
}
|
|
282
|
-
function readFlag(args, flag) {
|
|
283
|
-
const idx = args.indexOf(flag);
|
|
284
|
-
if (idx < 0 || idx + 1 >= args.length)
|
|
285
|
-
return undefined;
|
|
286
|
-
const n = Number(args[idx + 1]);
|
|
287
|
-
return Number.isFinite(n) ? n : undefined;
|
|
288
|
-
}
|
|
289
267
|
export function asEnvelope(value, command) {
|
|
290
268
|
if (value &&
|
|
291
269
|
typeof value === "object" &&
|
package/dist/codemode/index.d.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Code Mode for ast-sgrep in Pi.
|
|
3
3
|
*
|
|
4
|
-
* Pattern (Cloudflare / Anthropic PTC / OpenAI PTC):
|
|
5
|
-
* the model writes JavaScript that calls typed `asgrep.*` methods
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `Promise.all` and are coalesced into sticky serve / one warm batch process.
|
|
4
|
+
* Pattern (Cloudflare / Anthropic PTC / OpenAI PTC / OpenCode):
|
|
5
|
+
* the model writes JavaScript that calls typed `asgrep.*` methods. Intermediate
|
|
6
|
+
* results stay in the program; only the shaped return value re-enters the model
|
|
7
|
+
* context. Parallel calls use `Promise.all` against one warm in-process session.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Code Mode and MCP are sibling front ends on the same core — pick one per
|
|
10
|
+
* client. They never import each other. Do not install both for the same agent.
|
|
12
11
|
*/
|
|
13
|
-
export { createAsgrepConnector, type AsgrepConnector, type ConnectorHost, type ConnectorBundle, } from "./connector.js";
|
|
14
|
-
export { runCodemode, normalizeCode, type CodemodeRunResult } from "./
|
|
12
|
+
export { createAsgrepConnector, type AsgrepConnector, type ConnectorHost, type DispatchSurface, type ConnectorBundle, } from "./connector.js";
|
|
13
|
+
export { runCodemode, normalizeCode, type CodemodeRunResult, type CodemodeRunSuccess, type CodemodeRunFailure } from "./runner.js";
|
|
15
14
|
export { CODEMODE_TYPES_FOR_MODEL, type SearchArgs, type ChainArgs } from "./types.js";
|
|
16
15
|
export { createCodemodeDispatcher, runNativeBatch, argvFor, asEnvelope, type DispatchStats, type BatchCapableHost, type StickyWorker, type BatchResult, } from "./dispatch.js";
|
|
17
16
|
export { startStickyWorker, runBatchViaStdin } from "./worker.js";
|
package/dist/codemode/index.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Code Mode for ast-sgrep in Pi.
|
|
3
3
|
*
|
|
4
|
-
* Pattern (Cloudflare / Anthropic PTC / OpenAI PTC):
|
|
5
|
-
* the model writes JavaScript that calls typed `asgrep.*` methods
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `Promise.all` and are coalesced into sticky serve / one warm batch process.
|
|
4
|
+
* Pattern (Cloudflare / Anthropic PTC / OpenAI PTC / OpenCode):
|
|
5
|
+
* the model writes JavaScript that calls typed `asgrep.*` methods. Intermediate
|
|
6
|
+
* results stay in the program; only the shaped return value re-enters the model
|
|
7
|
+
* context. Parallel calls use `Promise.all` against one warm in-process session.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Code Mode and MCP are sibling front ends on the same core — pick one per
|
|
10
|
+
* client. They never import each other. Do not install both for the same agent.
|
|
12
11
|
*/
|
|
13
12
|
export { createAsgrepConnector, } from "./connector.js";
|
|
14
|
-
export { runCodemode, normalizeCode } from "./
|
|
13
|
+
export { runCodemode, normalizeCode } from "./runner.js";
|
|
15
14
|
export { CODEMODE_TYPES_FOR_MODEL } from "./types.js";
|
|
16
15
|
export { createCodemodeDispatcher, runNativeBatch, argvFor, asEnvelope, } from "./dispatch.js";
|
|
17
16
|
export { startStickyWorker, runBatchViaStdin } from "./worker.js";
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* 2. `@ast-sgrep/<platform>/ast-sgrep-codemode.node` via launcher (release install)
|
|
10
10
|
* 3. Local `extension/native/` / cargo `target/release` (dev builds)
|
|
11
11
|
*/
|
|
12
|
+
export declare const CODEMODE_BINDING_VERSION = "2.0.0";
|
|
12
13
|
export type NativeSessionConfig = {
|
|
13
14
|
root?: string;
|
|
14
15
|
indexPath?: string;
|
|
@@ -34,22 +35,18 @@ export type NativeBatchResponse = {
|
|
|
34
35
|
mode: string;
|
|
35
36
|
};
|
|
36
37
|
export type NativeSession = {
|
|
37
|
-
call(tool: string, args?: Record<string, unknown
|
|
38
|
+
call(tool: string, args?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown>;
|
|
39
|
+
/** Sync bounded metadata/symbol lookup; omitted on older addons. Throws if busy. */
|
|
40
|
+
callNow?(tool: string, args?: Record<string, unknown>): unknown;
|
|
41
|
+
batch(calls: NativeBatchCall[], signal?: AbortSignal): Promise<NativeBatchResponse>;
|
|
38
42
|
readonly callCount: number;
|
|
39
43
|
readonly root: string;
|
|
40
44
|
};
|
|
41
45
|
export type CodemodeNativeBinding = {
|
|
42
46
|
Session: new (config?: NativeSessionConfig) => NativeSession;
|
|
43
|
-
batch(request: {
|
|
44
|
-
root?: string;
|
|
45
|
-
indexPath?: string;
|
|
46
|
-
useEmbed?: boolean;
|
|
47
|
-
limit?: number;
|
|
48
|
-
parallelMode?: string;
|
|
49
|
-
calls: NativeBatchCall[];
|
|
50
|
-
}): NativeBatchResponse;
|
|
51
47
|
bindingVersion(): string;
|
|
52
48
|
isNative(): boolean;
|
|
49
|
+
asyncApiVersion(): number;
|
|
53
50
|
};
|
|
54
51
|
/** Load the NAPI binding once. Returns null if unavailable on this host. */
|
|
55
52
|
export declare function loadCodemodeNative(): CodemodeNativeBinding | null;
|
package/dist/codemode/native.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createRequire } from "node:module";
|
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { dirname, join } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
+
export const CODEMODE_BINDING_VERSION = "2.0.0";
|
|
16
17
|
let cached;
|
|
17
18
|
function platformTriple() {
|
|
18
19
|
const { platform, arch } = process;
|
|
@@ -57,6 +58,9 @@ function candidatePaths() {
|
|
|
57
58
|
// Workspace release output
|
|
58
59
|
join(here, "..", "..", "..", "..", "target", "release"),
|
|
59
60
|
];
|
|
61
|
+
const cargoTarget = process.env.CARGO_TARGET_DIR;
|
|
62
|
+
if (cargoTarget)
|
|
63
|
+
dirs.push(join(cargoTarget, "release"));
|
|
60
64
|
const out = [];
|
|
61
65
|
const override = process.env.ASGREP_CODEMODE_NAPI_PATH;
|
|
62
66
|
if (override)
|
|
@@ -89,7 +93,12 @@ export function loadCodemodeNative() {
|
|
|
89
93
|
continue;
|
|
90
94
|
try {
|
|
91
95
|
const binding = require(path);
|
|
92
|
-
if (typeof binding?.isNative === "function" &&
|
|
96
|
+
if (typeof binding?.isNative === "function" &&
|
|
97
|
+
binding.isNative() &&
|
|
98
|
+
typeof binding.bindingVersion === "function" &&
|
|
99
|
+
binding.bindingVersion() === CODEMODE_BINDING_VERSION &&
|
|
100
|
+
typeof binding.asyncApiVersion === "function" &&
|
|
101
|
+
binding.asyncApiVersion() === 1) {
|
|
93
102
|
cached = binding;
|
|
94
103
|
return cached;
|
|
95
104
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AsgrepConnector } from "./connector.js";
|
|
2
|
+
import type { DispatchStats } from "./dispatch.js";
|
|
3
|
+
/** Closed sum: success|failure — `ok:true` with `error` (or `ok:false` without) is unrepresentable. */
|
|
4
|
+
export type CodemodeRunSuccess = {
|
|
5
|
+
ok: true;
|
|
6
|
+
result: unknown;
|
|
7
|
+
logs: string[];
|
|
8
|
+
code: string;
|
|
9
|
+
stats?: DispatchStats;
|
|
10
|
+
wallMs: number;
|
|
11
|
+
};
|
|
12
|
+
export type CodemodeRunFailure = {
|
|
13
|
+
ok: false;
|
|
14
|
+
result: null;
|
|
15
|
+
error: string;
|
|
16
|
+
logs: string[];
|
|
17
|
+
code: string;
|
|
18
|
+
stats?: DispatchStats;
|
|
19
|
+
wallMs: number;
|
|
20
|
+
};
|
|
21
|
+
export type CodemodeRunResult = CodemodeRunSuccess | CodemodeRunFailure;
|
|
22
|
+
/** Strip markdown fences and normalize to an async IIFE expression. */
|
|
23
|
+
export declare function normalizeCode(raw: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Run model-generated JavaScript against the typed `asgrep` connector.
|
|
26
|
+
*
|
|
27
|
+
* Model-generated code is not trusted with the extension host's ambient Node
|
|
28
|
+
* authority. A dedicated worker contains CPU/microtask denial of service; its
|
|
29
|
+
* VM hides `process`, module loading, and host constructors, with a JSON bridge
|
|
30
|
+
* as the only exposed capability. This is not an OS sandbox, so deployments
|
|
31
|
+
* requiring adversarial-code isolation should still restrict the Pi process.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runCodemode(rawCode: string, asgrep: AsgrepConnector, options?: {
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
stats?: () => DispatchStats;
|
|
37
|
+
}): Promise<CodemodeRunResult>;
|