@theokit/sdk-tools 0.12.0 → 0.14.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/dist/index.cjs +167 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -1
- package/dist/index.d.ts +57 -1
- package/dist/index.js +164 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,7 @@ var fs = require('fs');
|
|
|
8
8
|
var pathSafety = require('@theokit/sdk/path-safety');
|
|
9
9
|
var persistence = require('@theokit/sdk/persistence');
|
|
10
10
|
var child_process = require('child_process');
|
|
11
|
+
var interactive = require('@theokit/sdk/interactive');
|
|
11
12
|
var promises$1 = require('dns/promises');
|
|
12
13
|
var net = require('net');
|
|
13
14
|
var filesystem = require('@theokit/sdk/filesystem');
|
|
@@ -265,6 +266,90 @@ function createSessionArtifactStore(options) {
|
|
|
265
266
|
}
|
|
266
267
|
return { write, read, has, list, path };
|
|
267
268
|
}
|
|
269
|
+
|
|
270
|
+
// src/internal/context-match.ts
|
|
271
|
+
var ContextMatchError = class extends Error {
|
|
272
|
+
reason;
|
|
273
|
+
constructor(reason, message) {
|
|
274
|
+
super(message);
|
|
275
|
+
this.name = "ContextMatchError";
|
|
276
|
+
this.reason = reason;
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
function normalizeUnicode(s) {
|
|
280
|
+
return s.replace(/[‐-―−]/g, "-").replace(/[‘-‛]/g, "'").replace(/[“-‟]/g, '"').replace(/[ - ]/g, " ");
|
|
281
|
+
}
|
|
282
|
+
var LINE_MATCH_LADDER = [
|
|
283
|
+
(s) => s,
|
|
284
|
+
// exact
|
|
285
|
+
(s) => s.replace(/\s+$/, ""),
|
|
286
|
+
// rstrip (ignore trailing whitespace)
|
|
287
|
+
(s) => s.trim(),
|
|
288
|
+
// trim (ignore leading + trailing)
|
|
289
|
+
(s) => normalizeUnicode(s).trim()
|
|
290
|
+
// unicode + trim (loosest)
|
|
291
|
+
];
|
|
292
|
+
function matchesAt(lines, pattern, i, norm) {
|
|
293
|
+
for (let p = 0; p < pattern.length; p++) {
|
|
294
|
+
const lineAt = lines[i + p];
|
|
295
|
+
const patAt = pattern[p];
|
|
296
|
+
if (lineAt === void 0 || patAt === void 0 || norm(lineAt) !== norm(patAt)) return false;
|
|
297
|
+
}
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
function findHits(lines, pattern, norm) {
|
|
301
|
+
const hits = [];
|
|
302
|
+
for (let i = 0; i + pattern.length <= lines.length; i++) {
|
|
303
|
+
if (matchesAt(lines, pattern, i, norm)) hits.push(i);
|
|
304
|
+
}
|
|
305
|
+
return hits;
|
|
306
|
+
}
|
|
307
|
+
function seekUniqueLineMatch(lines, pattern, find) {
|
|
308
|
+
if (pattern.length === 0 || pattern.length > lines.length) return null;
|
|
309
|
+
for (const norm of LINE_MATCH_LADDER) {
|
|
310
|
+
const hits = findHits(lines, pattern, norm);
|
|
311
|
+
if (hits.length === 1) return hits[0] ?? null;
|
|
312
|
+
if (hits.length > 1) {
|
|
313
|
+
throw new ContextMatchError(
|
|
314
|
+
"ambiguous",
|
|
315
|
+
`target text is ambiguous (multiple matches); include more context:
|
|
316
|
+
${find}`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
function replaceUnique(content, find, replace) {
|
|
323
|
+
if (find === "") {
|
|
324
|
+
throw new ContextMatchError(
|
|
325
|
+
"empty",
|
|
326
|
+
"empty find is not a valid target; provide the text to replace"
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const first = content.indexOf(find);
|
|
330
|
+
if (first !== -1) {
|
|
331
|
+
if (content.indexOf(find, first + 1) !== -1) {
|
|
332
|
+
throw new ContextMatchError(
|
|
333
|
+
"ambiguous",
|
|
334
|
+
`target text is ambiguous (multiple matches); include more context:
|
|
335
|
+
${find}`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return content.slice(0, first) + replace + content.slice(first + find.length);
|
|
339
|
+
}
|
|
340
|
+
const lines = content.split("\n");
|
|
341
|
+
const at = seekUniqueLineMatch(lines, find.split("\n"), find);
|
|
342
|
+
if (at === null) {
|
|
343
|
+
throw new ContextMatchError("not_found", `target text not found:
|
|
344
|
+
${find}`);
|
|
345
|
+
}
|
|
346
|
+
const patternLen = find.split("\n").length;
|
|
347
|
+
const matchedCrlf = patternLen > 0 && lines.slice(at, at + patternLen).every((l) => l.endsWith("\r"));
|
|
348
|
+
const replaceLines = matchedCrlf ? replace.split("\n").map((l) => l.endsWith("\r") ? l : `${l}\r`) : replace.split("\n");
|
|
349
|
+
return [...lines.slice(0, at), ...replaceLines, ...lines.slice(at + patternLen)].join("\n");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/edit-file.ts
|
|
268
353
|
function createEditFileTool(opts) {
|
|
269
354
|
const { projectRoot } = opts;
|
|
270
355
|
return sdk.Tool.create({
|
|
@@ -306,26 +391,36 @@ function createEditFileTool(opts) {
|
|
|
306
391
|
const exactIdx = content.indexOf(old_string);
|
|
307
392
|
if (exactIdx !== -1) {
|
|
308
393
|
await promises.copyFile(absolutePath, `${absolutePath}.bak`);
|
|
309
|
-
const
|
|
310
|
-
await promises.writeFile(absolutePath,
|
|
394
|
+
const result = content.slice(0, exactIdx) + new_string + content.slice(exactIdx + old_string.length);
|
|
395
|
+
await promises.writeFile(absolutePath, result, "utf-8");
|
|
311
396
|
return JSON.stringify({ ok: true, replacements: 1 });
|
|
312
397
|
}
|
|
313
398
|
const normalizedContent = normalizeWhitespace(content);
|
|
314
399
|
const normalizedOld = normalizeWhitespace(old_string);
|
|
315
400
|
const normalizedIdx = normalizedContent.indexOf(normalizedOld);
|
|
316
|
-
if (normalizedIdx
|
|
317
|
-
|
|
401
|
+
if (normalizedIdx !== -1) {
|
|
402
|
+
const span = findOriginalSpan(
|
|
403
|
+
content,
|
|
404
|
+
normalizedContent,
|
|
405
|
+
normalizedIdx,
|
|
406
|
+
normalizedOld.length
|
|
407
|
+
);
|
|
408
|
+
await promises.copyFile(absolutePath, `${absolutePath}.bak`);
|
|
409
|
+
const result = content.slice(0, span.start) + new_string + content.slice(span.end);
|
|
410
|
+
await promises.writeFile(absolutePath, result, "utf-8");
|
|
411
|
+
return JSON.stringify({ ok: true, replacements: 1 });
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const result = replaceUnique(content, old_string, new_string);
|
|
415
|
+
await promises.copyFile(absolutePath, `${absolutePath}.bak`);
|
|
416
|
+
await promises.writeFile(absolutePath, result, "utf-8");
|
|
417
|
+
return JSON.stringify({ ok: true, replacements: 1 });
|
|
418
|
+
} catch (err) {
|
|
419
|
+
if (err instanceof ContextMatchError) {
|
|
420
|
+
return JSON.stringify({ ok: false, error: "no_match", path });
|
|
421
|
+
}
|
|
422
|
+
throw err;
|
|
318
423
|
}
|
|
319
|
-
const span = findOriginalSpan(
|
|
320
|
-
content,
|
|
321
|
-
normalizedContent,
|
|
322
|
-
normalizedIdx,
|
|
323
|
-
normalizedOld.length
|
|
324
|
-
);
|
|
325
|
-
await promises.copyFile(absolutePath, `${absolutePath}.bak`);
|
|
326
|
-
const result = content.slice(0, span.start) + new_string + content.slice(span.end);
|
|
327
|
-
await promises.writeFile(absolutePath, result, "utf-8");
|
|
328
|
-
return JSON.stringify({ ok: true, replacements: 1 });
|
|
329
424
|
}
|
|
330
425
|
});
|
|
331
426
|
}
|
|
@@ -597,6 +692,60 @@ function globToRegex(pattern) {
|
|
|
597
692
|
}
|
|
598
693
|
return new RegExp(`^${regexStr}$`);
|
|
599
694
|
}
|
|
695
|
+
function toErrorJson(err) {
|
|
696
|
+
if (err instanceof interactive.InteractiveUnavailableError) {
|
|
697
|
+
return JSON.stringify({ ok: false, error: "interactive_unavailable" });
|
|
698
|
+
}
|
|
699
|
+
if (err instanceof interactive.NoSuchSessionError) {
|
|
700
|
+
return JSON.stringify({ ok: false, error: "no_such_session" });
|
|
701
|
+
}
|
|
702
|
+
throw err;
|
|
703
|
+
}
|
|
704
|
+
function createInteractiveShellTool(opts) {
|
|
705
|
+
const { interactive: interactive$1 } = opts;
|
|
706
|
+
return sdk.Tool.create({
|
|
707
|
+
name: "interactive_shell",
|
|
708
|
+
description: "Start an interactive shell session for a command that PROMPTS for input or is a REPL (python, node, `git rebase -i`, a `read` prompt) \u2014 NOT for one-shot commands (use shell_exec). Returns a session_id; drive it with write_stdin, reading the incremental output each step. Returns { ok, session_id, output } or { ok: false, error }.",
|
|
709
|
+
inputSchema: zod.z.object({
|
|
710
|
+
command: zod.z.string().min(1).describe("Command to run interactively, e.g. 'python3' or 'bash -i'."),
|
|
711
|
+
yield_time_ms: zod.z.number().int().positive().optional().describe("How long to wait for startup output before returning (clamped by the backend).")
|
|
712
|
+
}),
|
|
713
|
+
handler: async ({ command, yield_time_ms }, ctx) => {
|
|
714
|
+
try {
|
|
715
|
+
const backend = await interactive.resolveInteractive(interactive$1, ctx ?? {});
|
|
716
|
+
const { sessionId, output } = await backend.startInteractive(command, {
|
|
717
|
+
yieldMs: yield_time_ms
|
|
718
|
+
});
|
|
719
|
+
return JSON.stringify({ ok: true, session_id: sessionId, output });
|
|
720
|
+
} catch (err) {
|
|
721
|
+
return toErrorJson(err);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
function createWriteStdinTool(opts) {
|
|
727
|
+
const { interactive: interactive$1 } = opts;
|
|
728
|
+
return sdk.Tool.create({
|
|
729
|
+
name: "write_stdin",
|
|
730
|
+
description: "Write input to a live interactive session (from interactive_shell) and read the output it produces during the wait window. Include a trailing newline to submit a line. Returns { ok, output, alive } (alive:false means the session exited) or { ok: false, error }.",
|
|
731
|
+
inputSchema: zod.z.object({
|
|
732
|
+
session_id: zod.z.string().min(1).describe("The session_id returned by interactive_shell."),
|
|
733
|
+
input: zod.z.string().describe("Text to write to stdin (add a trailing '\\n' to submit a line)."),
|
|
734
|
+
yield_time_ms: zod.z.number().int().positive().optional().describe("How long to wait for output before returning (clamped by the backend).")
|
|
735
|
+
}),
|
|
736
|
+
handler: async ({ session_id, input, yield_time_ms }, ctx) => {
|
|
737
|
+
try {
|
|
738
|
+
const backend = await interactive.resolveInteractive(interactive$1, ctx ?? {});
|
|
739
|
+
const { output, alive } = await backend.writeStdin(session_id, input, {
|
|
740
|
+
yieldMs: yield_time_ms
|
|
741
|
+
});
|
|
742
|
+
return JSON.stringify({ ok: true, output, alive });
|
|
743
|
+
} catch (err) {
|
|
744
|
+
return toErrorJson(err);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
}
|
|
600
749
|
var CatastrophicCommandError = class extends sdk.ConfigurationError {
|
|
601
750
|
name = "CatastrophicCommandError";
|
|
602
751
|
constructor(reason) {
|
|
@@ -2250,6 +2399,7 @@ async function isBinaryFile(absolutePath) {
|
|
|
2250
2399
|
}
|
|
2251
2400
|
|
|
2252
2401
|
exports.CatastrophicCommandError = CatastrophicCommandError;
|
|
2402
|
+
exports.ContextMatchError = ContextMatchError;
|
|
2253
2403
|
exports.DEFAULT_TOOL_GUIDANCE = DEFAULT_TOOL_GUIDANCE;
|
|
2254
2404
|
exports.ReadTracker = ReadTracker;
|
|
2255
2405
|
exports.ReasoningTools = ReasoningTools;
|
|
@@ -2265,6 +2415,7 @@ exports.createEditFileTool = createEditFileTool;
|
|
|
2265
2415
|
exports.createGenericHttpSearchAdapter = createGenericHttpSearchAdapter;
|
|
2266
2416
|
exports.createGitDiffTool = createGitDiffTool;
|
|
2267
2417
|
exports.createGlobTool = createGlobTool;
|
|
2418
|
+
exports.createInteractiveShellTool = createInteractiveShellTool;
|
|
2268
2419
|
exports.createListDirTool = createListDirTool;
|
|
2269
2420
|
exports.createPlanModeTool = createPlanModeTool;
|
|
2270
2421
|
exports.createQuestionTool = createQuestionTool;
|
|
@@ -2277,6 +2428,7 @@ exports.createTodolistTool = createTodolistTool;
|
|
|
2277
2428
|
exports.createWebFetchTool = createWebFetchTool;
|
|
2278
2429
|
exports.createWebSearchTool = createWebSearchTool;
|
|
2279
2430
|
exports.createWriteFileTool = createWriteFileTool;
|
|
2431
|
+
exports.createWriteStdinTool = createWriteStdinTool;
|
|
2280
2432
|
exports.denyCatastrophicCommands = denyCatastrophicCommands;
|
|
2281
2433
|
exports.formatCode = formatCode;
|
|
2282
2434
|
exports.formatDiff = formatDiff;
|
|
@@ -2286,6 +2438,7 @@ exports.injectGuidance = injectGuidance;
|
|
|
2286
2438
|
exports.isBlockedIp = isBlockedIp;
|
|
2287
2439
|
exports.isCommandAllowed = isCommandAllowed;
|
|
2288
2440
|
exports.renderToolList = renderToolList;
|
|
2441
|
+
exports.replaceUnique = replaceUnique;
|
|
2289
2442
|
exports.resolveAndScreen = resolveAndScreen;
|
|
2290
2443
|
exports.screenedFetch = screenedFetch;
|
|
2291
2444
|
exports.todoItemsToPlanNodes = todoItemsToPlanNodes;
|