@theokit/sdk-tools 0.16.0 → 0.18.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/CHANGELOG.md +23 -0
- package/dist/index.cjs +75 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +74 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- `createReadFileTool` gains three ADDITIVE, opt-in Codex-grade capabilities (all default OFF, so existing
|
|
8
|
+
consumers are byte-identical): `lineNumbers` (render a `cat -n` `<n>\t<line>` view so the model can cite/
|
|
9
|
+
edit by line), `offset`/`limit` input params (page through a large file), and `allowAbsolute` (honor an
|
|
10
|
+
absolute path outside `projectRoot` — the Codex read-only "reads-anywhere" sandbox). Security: with
|
|
11
|
+
`allowAbsolute`, the secret guard now blocks `.env`/`.git`/`node_modules`/`.theo` at ANY path depth (not
|
|
12
|
+
just the project-relative first segment), closing an absolute-path exfiltration hole. Opt-in only.
|
|
13
|
+
|
|
14
|
+
## 0.17.0
|
|
15
|
+
|
|
16
|
+
### Minor Changes
|
|
17
|
+
|
|
18
|
+
- c98c40a: Add `createUpdatePlanTool` — a Codex-faithful `update_plan` built-in. The model posts a DECLARATIVE plan
|
|
19
|
+
(an ordered list of steps, each `pending | in_progress | completed`) and refreshes it as work proceeds.
|
|
20
|
+
Surface-agnostic by design: returns STRUCTURED `{ ok, explanation, steps, warning? }` so each surface
|
|
21
|
+
renders the checklist itself (no hard-coded glyphs). Follows Codex's "exactly one step in_progress"
|
|
22
|
+
invariant as a non-fatal `warning` (never rejects), so the agent self-corrects on the next update.
|
|
23
|
+
Distinct from the imperative `createTodolistTool` (add/complete by id) and `createPlanModeTool` (mode
|
|
24
|
+
toggle) — this is the declarative full-plan post.
|
|
25
|
+
|
|
3
26
|
## 0.16.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -1579,23 +1579,53 @@ function createQuestionTool(opts) {
|
|
|
1579
1579
|
}
|
|
1580
1580
|
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
1581
1581
|
var BINARY_PROBE_BYTES = 8 * 1024;
|
|
1582
|
+
var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([".env", ".git", "node_modules", ".theo"]);
|
|
1583
|
+
function isForbiddenAtAnyDepth(path) {
|
|
1584
|
+
const segs = path.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
1585
|
+
return segs.some((s) => {
|
|
1586
|
+
if (s === ".env.example") return false;
|
|
1587
|
+
return SENSITIVE_SEGMENTS.has(s) || /^\.env\./.test(s);
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
function forbiddenReadError(path$1, allowAbsolute) {
|
|
1591
|
+
if (isForbiddenPath(path$1)) {
|
|
1592
|
+
return JSON.stringify({ ok: false, error: "forbidden_path", path: path$1 });
|
|
1593
|
+
}
|
|
1594
|
+
if (allowAbsolute && path.isAbsolute(path$1) && isForbiddenAtAnyDepth(path$1)) {
|
|
1595
|
+
return JSON.stringify({ ok: false, error: "forbidden_path", path: path$1 });
|
|
1596
|
+
}
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
function renderView(content, opts) {
|
|
1600
|
+
const paginated = opts.offset !== void 0 || opts.limit !== void 0;
|
|
1601
|
+
if (!opts.lineNumbers && !paginated) return content;
|
|
1602
|
+
const lines = content.split("\n");
|
|
1603
|
+
const start = Math.max(1, opts.offset ?? 1);
|
|
1604
|
+
const end = opts.limit !== void 0 ? Math.min(lines.length, start - 1 + opts.limit) : lines.length;
|
|
1605
|
+
const slice = lines.slice(start - 1, end);
|
|
1606
|
+
return opts.lineNumbers ? slice.map((l, i) => `${start + i} ${l}`).join("\n") : slice.join("\n");
|
|
1607
|
+
}
|
|
1582
1608
|
function createReadFileTool(opts) {
|
|
1583
|
-
const { projectRoot, readTracker, filesystem: filesystem$1 } = opts;
|
|
1609
|
+
const { projectRoot, readTracker, filesystem: filesystem$1, lineNumbers, allowAbsolute } = opts;
|
|
1610
|
+
const numbered = lineNumbers === true ? " Returns a cat -n numbered view (`<n>\\t<line>`)." : "";
|
|
1611
|
+
const abs = allowAbsolute === true ? " Absolute paths outside the project are honored." : "";
|
|
1584
1612
|
return sdk.Tool.create({
|
|
1585
1613
|
name: "read_file",
|
|
1586
|
-
description: "Read a
|
|
1614
|
+
description: "Read a text file as UTF-8. ALWAYS read a file before you edit it (edit_file) or overwrite it (write_file), so your old_string / new content matches the real bytes exactly." + numbered + abs + " By default returns the whole file; use the optional offset (1-based first line) + limit to page through a large file, or search_text to locate a symbol. Refuses sensitive files (.env, .git/, node_modules/, .theo/, lock files) and binary files (null byte in the first 8 KB); caps at 5 MB. Returns { ok, content, size } or { ok: false, error }.",
|
|
1587
1615
|
inputSchema: zod.z.object({
|
|
1588
|
-
path: zod.z.string().min(1).describe("
|
|
1616
|
+
path: zod.z.string().min(1).describe("File path (project-relative; absolute when allowed)."),
|
|
1617
|
+
offset: zod.z.number().int().min(1).optional().describe("1-based first line to read (default 1)."),
|
|
1618
|
+
limit: zod.z.number().int().min(1).optional().describe("Max number of lines to read (default: all).")
|
|
1589
1619
|
}),
|
|
1590
|
-
handler: async ({ path }, ctx) => {
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
}
|
|
1620
|
+
handler: async ({ path, offset, limit }, ctx) => {
|
|
1621
|
+
const forbidden = forbiddenReadError(path, allowAbsolute === true);
|
|
1622
|
+
if (forbidden !== null) return forbidden;
|
|
1623
|
+
const view = { lineNumbers, offset, limit };
|
|
1594
1624
|
if (filesystem$1) {
|
|
1595
1625
|
const backend = await filesystem.resolveFilesystem(filesystem$1, ctx ?? {});
|
|
1596
|
-
return readViaBackend(backend, path, (mtimeMs) => readTracker?.record(path, mtimeMs));
|
|
1626
|
+
return readViaBackend(backend, path, view, (mtimeMs) => readTracker?.record(path, mtimeMs));
|
|
1597
1627
|
}
|
|
1598
|
-
const boundary = resolveBoundary(path, projectRoot);
|
|
1628
|
+
const boundary = resolveBoundary(path, projectRoot, allowAbsolute === true);
|
|
1599
1629
|
if ("error" in boundary) return boundary.error;
|
|
1600
1630
|
const opened = await openHandleSafe(boundary.absolutePath, path);
|
|
1601
1631
|
if ("error" in opened) return opened.error;
|
|
@@ -1603,6 +1633,7 @@ function createReadFileTool(opts) {
|
|
|
1603
1633
|
return await readContent(
|
|
1604
1634
|
opened.handle,
|
|
1605
1635
|
path,
|
|
1636
|
+
view,
|
|
1606
1637
|
(mtimeMs) => readTracker?.record(path, mtimeMs)
|
|
1607
1638
|
);
|
|
1608
1639
|
} finally {
|
|
@@ -1611,7 +1642,7 @@ function createReadFileTool(opts) {
|
|
|
1611
1642
|
}
|
|
1612
1643
|
});
|
|
1613
1644
|
}
|
|
1614
|
-
async function readViaBackend(backend, path, onRead) {
|
|
1645
|
+
async function readViaBackend(backend, path, view, onRead) {
|
|
1615
1646
|
try {
|
|
1616
1647
|
const stat2 = await backend.stat(path);
|
|
1617
1648
|
if (stat2.size > MAX_FILE_SIZE) {
|
|
@@ -1623,12 +1654,12 @@ async function readViaBackend(backend, path, onRead) {
|
|
|
1623
1654
|
limit: MAX_FILE_SIZE
|
|
1624
1655
|
});
|
|
1625
1656
|
}
|
|
1626
|
-
const
|
|
1627
|
-
if (
|
|
1657
|
+
const raw = await backend.readFile(path);
|
|
1658
|
+
if (raw.includes("\0")) {
|
|
1628
1659
|
return JSON.stringify({ ok: false, error: "binary_file", path, size: stat2.size });
|
|
1629
1660
|
}
|
|
1630
1661
|
onRead?.(stat2.mtimeMs);
|
|
1631
|
-
return JSON.stringify({ ok: true, content, size: stat2.size });
|
|
1662
|
+
return JSON.stringify({ ok: true, content: renderView(raw, view), size: stat2.size });
|
|
1632
1663
|
} catch (err) {
|
|
1633
1664
|
if (err instanceof filesystem.FileNotFoundError) {
|
|
1634
1665
|
return JSON.stringify({ ok: false, error: "not_found", path });
|
|
@@ -1639,14 +1670,17 @@ async function readViaBackend(backend, path, onRead) {
|
|
|
1639
1670
|
throw err;
|
|
1640
1671
|
}
|
|
1641
1672
|
}
|
|
1642
|
-
function resolveBoundary(path, projectRoot) {
|
|
1673
|
+
function resolveBoundary(path$1, projectRoot, allowAbsolute) {
|
|
1674
|
+
if (allowAbsolute && path.isAbsolute(path$1)) {
|
|
1675
|
+
return { absolutePath: path$1 };
|
|
1676
|
+
}
|
|
1643
1677
|
try {
|
|
1644
|
-
const absolutePath = safePathJoin(projectRoot, path);
|
|
1678
|
+
const absolutePath = safePathJoin(projectRoot, path$1);
|
|
1645
1679
|
assertNoSymlinkEscape(absolutePath, projectRoot);
|
|
1646
1680
|
return { absolutePath };
|
|
1647
1681
|
} catch (err) {
|
|
1648
1682
|
if (err instanceof PathTraversalError || err instanceof ForbiddenPathError) {
|
|
1649
|
-
return { error: JSON.stringify({ ok: false, error: "path_traversal", path }) };
|
|
1683
|
+
return { error: JSON.stringify({ ok: false, error: "path_traversal", path: path$1 }) };
|
|
1650
1684
|
}
|
|
1651
1685
|
throw err;
|
|
1652
1686
|
}
|
|
@@ -1663,7 +1697,7 @@ async function openHandleSafe(absolutePath, path) {
|
|
|
1663
1697
|
throw err;
|
|
1664
1698
|
}
|
|
1665
1699
|
}
|
|
1666
|
-
async function readContent(handle, path, onRead) {
|
|
1700
|
+
async function readContent(handle, path, view, onRead) {
|
|
1667
1701
|
const stat2 = await handle.stat();
|
|
1668
1702
|
if (stat2.size > MAX_FILE_SIZE) {
|
|
1669
1703
|
return JSON.stringify({
|
|
@@ -1677,9 +1711,9 @@ async function readContent(handle, path, onRead) {
|
|
|
1677
1711
|
if (await isBinaryProbe(handle, Number(stat2.size))) {
|
|
1678
1712
|
return JSON.stringify({ ok: false, error: "binary_file", path, size: stat2.size });
|
|
1679
1713
|
}
|
|
1680
|
-
const
|
|
1714
|
+
const raw = await handle.readFile({ encoding: "utf-8" });
|
|
1681
1715
|
onRead?.(stat2.mtimeMs);
|
|
1682
|
-
return JSON.stringify({ ok: true, content, size: stat2.size });
|
|
1716
|
+
return JSON.stringify({ ok: true, content: renderView(raw, view), size: stat2.size });
|
|
1683
1717
|
}
|
|
1684
1718
|
async function isBinaryProbe(handle, size) {
|
|
1685
1719
|
const probeLen = Math.min(BINARY_PROBE_BYTES, size);
|
|
@@ -2297,6 +2331,27 @@ function truncateOutput(output, opts) {
|
|
|
2297
2331
|
overflowPath
|
|
2298
2332
|
};
|
|
2299
2333
|
}
|
|
2334
|
+
var STATUS = ["pending", "in_progress", "completed"];
|
|
2335
|
+
var planStepSchema = zod.z.object({
|
|
2336
|
+
step: zod.z.string().min(1).max(100).describe("A short step, \u2264 ~7 words."),
|
|
2337
|
+
status: zod.z.enum(STATUS)
|
|
2338
|
+
});
|
|
2339
|
+
function createUpdatePlanTool() {
|
|
2340
|
+
return sdk.Tool.create({
|
|
2341
|
+
name: "update_plan",
|
|
2342
|
+
description: "Post or refresh a short plan so the user sees your progress on a multi-step task. Pass an ordered `plan` of steps, each with a status (pending | in_progress | completed); keep exactly one step in_progress at a time and mark steps completed as you finish. Returns { ok, steps, warning? } as a JSON string \u2014 the surface renders the checklist. A `warning` is returned (not an error) if the one-in_progress invariant is violated, so you can self-correct on the next update.",
|
|
2343
|
+
inputSchema: zod.z.object({
|
|
2344
|
+
explanation: zod.z.string().max(200).optional().describe("One line on what changed / why (optional)."),
|
|
2345
|
+
plan: zod.z.array(planStepSchema).min(1).describe("The ordered steps.")
|
|
2346
|
+
}),
|
|
2347
|
+
handler: ({ explanation, plan }) => {
|
|
2348
|
+
const inProgress = plan.filter((s) => s.status === "in_progress").length;
|
|
2349
|
+
const allDone = plan.every((s) => s.status === "completed");
|
|
2350
|
+
const warning = !allDone && inProgress !== 1 ? `keep exactly one step in_progress until all are completed \u2014 found ${inProgress}` : void 0;
|
|
2351
|
+
return JSON.stringify({ ok: true, explanation: explanation ?? null, steps: plan, warning });
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2354
|
+
}
|
|
2300
2355
|
var DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
2301
2356
|
var MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
2302
2357
|
function createWebFetchTool(opts) {
|
|
@@ -2627,6 +2682,7 @@ exports.createSearchTextTool = createSearchTextTool;
|
|
|
2627
2682
|
exports.createSessionArtifactStore = createSessionArtifactStore;
|
|
2628
2683
|
exports.createShellTool = createShellTool;
|
|
2629
2684
|
exports.createTodolistTool = createTodolistTool;
|
|
2685
|
+
exports.createUpdatePlanTool = createUpdatePlanTool;
|
|
2630
2686
|
exports.createWebFetchTool = createWebFetchTool;
|
|
2631
2687
|
exports.createWebSearchTool = createWebSearchTool;
|
|
2632
2688
|
exports.createWriteFileTool = createWriteFileTool;
|