@theokit/sdk-tools 0.26.2 → 0.27.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 +38 -0
- package/dist/index.cjs +95 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -15
- package/dist/index.d.ts +69 -15
- package/dist/index.js +95 -15
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.27.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c18c655: `createViewImageTool` reaches consumers.
|
|
8
|
+
|
|
9
|
+
The tool was committed on 2026-08-14, three days after `0.26.3` went to the registry, and no version
|
|
10
|
+
was cut — so the published `0.26.3` and the source at `0.26.3` were different packages. Anyone
|
|
11
|
+
resolving the range got a build without the image tool, and `@theokit/agents` could not forward what
|
|
12
|
+
its dependency did not ship.
|
|
13
|
+
|
|
14
|
+
The measurable cost: TheoCode wrote `packages/agent/src/tools/view-image.ts` by hand — the only local
|
|
15
|
+
tool in a ten-tool registry where the other nine are framework built-ins.
|
|
16
|
+
|
|
17
|
+
Note for consumers migrating off a hand-rolled equivalent: this `toModelOutput` returns the image
|
|
18
|
+
block ALONE on success, and leaves a failure as text so the model can read the reason and retry.
|
|
19
|
+
An implementation that also emitted a leading text line will change what the model receives.
|
|
20
|
+
|
|
21
|
+
## 0.26.3
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 8790f70: Refuse a `workspace:` range before it can reach npm.
|
|
26
|
+
|
|
27
|
+
Five of this repo's twelve publishable packages declare internal dependencies as `workspace:^`, which
|
|
28
|
+
is correct on disk and becomes an unrecoverable defect if the publish goes out through a tool that
|
|
29
|
+
does not rewrite it: `pnpm` resolves the protocol while packing, `npm` ships the manifest verbatim.
|
|
30
|
+
A version published that way fails to install for everyone and cannot be corrected — only
|
|
31
|
+
deprecated.
|
|
32
|
+
|
|
33
|
+
Every publishable package now runs the guard in `prepublishOnly`, so it fires whichever way the
|
|
34
|
+
publish is invoked, and `pnpm release` runs it once across the repo before `changeset publish`.
|
|
35
|
+
|
|
36
|
+
Note for anyone reading a published manifest: the `prepublishOnly` entry points at a path inside
|
|
37
|
+
this repository. It never runs for a consumer — the hook only fires when the package itself is
|
|
38
|
+
published — and guarding the entry point that a hand-run `npm publish` actually uses was worth the
|
|
39
|
+
cosmetic wart of shipping the line.
|
|
40
|
+
|
|
3
41
|
## 0.26.2
|
|
4
42
|
|
|
5
43
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -793,12 +793,12 @@ function checkPathScope(path, projectRoot) {
|
|
|
793
793
|
throw err;
|
|
794
794
|
}
|
|
795
795
|
}
|
|
796
|
-
var
|
|
797
|
-
function
|
|
796
|
+
var SENSITIVE_SEGMENTS = /* @__PURE__ */ new Set([".env", ".git", "node_modules", ".theo"]);
|
|
797
|
+
function isForbiddenAtAnyDepth(path) {
|
|
798
798
|
const segs = path.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
799
799
|
return segs.some((s) => {
|
|
800
800
|
if (s === ".env.example") return false;
|
|
801
|
-
return
|
|
801
|
+
return SENSITIVE_SEGMENTS.has(s) || /^\.env\./.test(s);
|
|
802
802
|
});
|
|
803
803
|
}
|
|
804
804
|
|
|
@@ -1586,7 +1586,7 @@ function createListDirTool(opts) {
|
|
|
1586
1586
|
}),
|
|
1587
1587
|
handler: async ({ path }, ctx) => {
|
|
1588
1588
|
const relative3 = path === "" || path === "." ? "." : path;
|
|
1589
|
-
const verdict =
|
|
1589
|
+
const verdict = decideScope(relative3, path, opts.allowAbsolute === true);
|
|
1590
1590
|
if (verdict.error !== void 0) return verdict.error;
|
|
1591
1591
|
if (verdict.absoluteRoot !== void 0) {
|
|
1592
1592
|
return listViaLocalFs(verdict.absoluteRoot, ".", path, max);
|
|
@@ -1599,14 +1599,14 @@ function createListDirTool(opts) {
|
|
|
1599
1599
|
}
|
|
1600
1600
|
});
|
|
1601
1601
|
}
|
|
1602
|
-
function
|
|
1602
|
+
function decideScope(relative3, original, allowAbsolute) {
|
|
1603
1603
|
const refuse = (error) => ({
|
|
1604
1604
|
error: JSON.stringify({ ok: false, error, path: original })
|
|
1605
1605
|
});
|
|
1606
1606
|
if (relative3 !== "." && pathSafety.isForbiddenPath(relative3)) return refuse("forbidden_path");
|
|
1607
1607
|
if (!path.isAbsolute(relative3)) return {};
|
|
1608
1608
|
if (!allowAbsolute) return refuse("path_traversal");
|
|
1609
|
-
if (
|
|
1609
|
+
if (isForbiddenAtAnyDepth(relative3)) return refuse("forbidden_path");
|
|
1610
1610
|
return { absoluteRoot: relative3 };
|
|
1611
1611
|
}
|
|
1612
1612
|
async function listViaLocalFs(projectRoot, relative3, originalPath, max) {
|
|
@@ -1769,10 +1769,10 @@ function createPlanModeTool(options) {
|
|
|
1769
1769
|
}
|
|
1770
1770
|
|
|
1771
1771
|
// src/question.ts
|
|
1772
|
-
function
|
|
1772
|
+
function askerFromContext(context) {
|
|
1773
1773
|
if (typeof context !== "object" || context === null) return void 0;
|
|
1774
|
-
const
|
|
1775
|
-
return typeof
|
|
1774
|
+
const candidate = context.askUser;
|
|
1775
|
+
return typeof candidate === "function" ? candidate : void 0;
|
|
1776
1776
|
}
|
|
1777
1777
|
function createQuestionTool(opts) {
|
|
1778
1778
|
const timeoutMs = opts.timeoutMs ?? 3e5;
|
|
@@ -1787,7 +1787,7 @@ function createQuestionTool(opts) {
|
|
|
1787
1787
|
required: ["question"]
|
|
1788
1788
|
},
|
|
1789
1789
|
handler: async (input, ctx) => {
|
|
1790
|
-
const askUser =
|
|
1790
|
+
const askUser = askerFromContext(ctx?.context) ?? opts.askUser;
|
|
1791
1791
|
if (askUser === void 0) {
|
|
1792
1792
|
return JSON.stringify({
|
|
1793
1793
|
ok: false,
|
|
@@ -1824,7 +1824,7 @@ function forbiddenReadError(path$1, allowAbsolute) {
|
|
|
1824
1824
|
if (pathSafety.isForbiddenPath(path$1)) {
|
|
1825
1825
|
return JSON.stringify({ ok: false, error: "forbidden_path", path: path$1 });
|
|
1826
1826
|
}
|
|
1827
|
-
if (allowAbsolute && path.isAbsolute(path$1) &&
|
|
1827
|
+
if (allowAbsolute && path.isAbsolute(path$1) && isForbiddenAtAnyDepth(path$1)) {
|
|
1828
1828
|
return JSON.stringify({ ok: false, error: "forbidden_path", path: path$1 });
|
|
1829
1829
|
}
|
|
1830
1830
|
return null;
|
|
@@ -2627,6 +2627,86 @@ function createUpdatePlanTool() {
|
|
|
2627
2627
|
}
|
|
2628
2628
|
});
|
|
2629
2629
|
}
|
|
2630
|
+
var MEDIA_TYPES = /* @__PURE__ */ new Map([
|
|
2631
|
+
[".png", "image/png"],
|
|
2632
|
+
[".jpg", "image/jpeg"],
|
|
2633
|
+
[".jpeg", "image/jpeg"],
|
|
2634
|
+
[".gif", "image/gif"],
|
|
2635
|
+
[".webp", "image/webp"]
|
|
2636
|
+
]);
|
|
2637
|
+
var DEFAULT_MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
2638
|
+
var json = (result) => JSON.stringify(result);
|
|
2639
|
+
function createViewImageTool(options) {
|
|
2640
|
+
const { projectRoot } = options;
|
|
2641
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_IMAGE_BYTES;
|
|
2642
|
+
return sdk.Tool.create({
|
|
2643
|
+
name: options.name ?? "view_image",
|
|
2644
|
+
description: options.description ?? "Read an image file from the project and show it to the model. Supports png, jpeg, gif and webp.",
|
|
2645
|
+
inputSchema: zod.z.object({
|
|
2646
|
+
path: zod.z.string().describe("Path to the image, relative to the project root.")
|
|
2647
|
+
}),
|
|
2648
|
+
handler: (input) => {
|
|
2649
|
+
const refused = checkPathScope(input.path, projectRoot);
|
|
2650
|
+
if (refused !== null) return refused;
|
|
2651
|
+
if (isForbiddenAtAnyDepth(input.path)) {
|
|
2652
|
+
return json({ ok: false, error: "path_traversal", path: input.path });
|
|
2653
|
+
}
|
|
2654
|
+
const mediaType = MEDIA_TYPES.get(path.extname(input.path).toLowerCase());
|
|
2655
|
+
if (mediaType === void 0) {
|
|
2656
|
+
return json({
|
|
2657
|
+
ok: false,
|
|
2658
|
+
error: "unsupported_image_type",
|
|
2659
|
+
path: input.path,
|
|
2660
|
+
supported: [...MEDIA_TYPES.keys()]
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
const absolute = pathSafety.safePathJoin(projectRoot, input.path);
|
|
2664
|
+
let bytes;
|
|
2665
|
+
try {
|
|
2666
|
+
bytes = fs.statSync(absolute).size;
|
|
2667
|
+
} catch {
|
|
2668
|
+
return json({ ok: false, error: "not_found", path: input.path });
|
|
2669
|
+
}
|
|
2670
|
+
if (bytes > maxBytes) {
|
|
2671
|
+
return json({
|
|
2672
|
+
ok: false,
|
|
2673
|
+
error: "image_too_large",
|
|
2674
|
+
path: input.path,
|
|
2675
|
+
bytes,
|
|
2676
|
+
limit_bytes: maxBytes
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
return json({
|
|
2680
|
+
ok: true,
|
|
2681
|
+
path: input.path,
|
|
2682
|
+
media_type: mediaType,
|
|
2683
|
+
bytes,
|
|
2684
|
+
data: fs.readFileSync(absolute).toString("base64")
|
|
2685
|
+
});
|
|
2686
|
+
},
|
|
2687
|
+
/**
|
|
2688
|
+
* Turn a successful read into an image block.
|
|
2689
|
+
*
|
|
2690
|
+
* A failed read stays TEXT on purpose: the model needs to read "not_found" and try another path,
|
|
2691
|
+
* and an error is not something to look at.
|
|
2692
|
+
*/
|
|
2693
|
+
toModelOutput: (output) => {
|
|
2694
|
+
let result;
|
|
2695
|
+
try {
|
|
2696
|
+
result = JSON.parse(output);
|
|
2697
|
+
} catch {
|
|
2698
|
+
return output;
|
|
2699
|
+
}
|
|
2700
|
+
if (result.ok !== true) return output;
|
|
2701
|
+
return [
|
|
2702
|
+
{
|
|
2703
|
+
type: "image",
|
|
2704
|
+
source: { type: "base64", media_type: result.media_type, data: result.data }
|
|
2705
|
+
}
|
|
2706
|
+
];
|
|
2707
|
+
}
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2630
2710
|
var DEFAULT_TIMEOUT_MS4 = 3e4;
|
|
2631
2711
|
var MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
2632
2712
|
function createWebFetchTool(opts) {
|
|
@@ -2768,8 +2848,8 @@ function createBraveWebSearchAdapter(opts = {}) {
|
|
|
2768
2848
|
headers: { "X-Subscription-Token": apiKey, Accept: "application/json" }
|
|
2769
2849
|
});
|
|
2770
2850
|
if (!res.ok) throw new Error(`brave_search_failed: HTTP ${res.status}`);
|
|
2771
|
-
const
|
|
2772
|
-
const results =
|
|
2851
|
+
const json2 = await res.json();
|
|
2852
|
+
const results = json2?.web?.results ?? [];
|
|
2773
2853
|
return results.map((r) => ({
|
|
2774
2854
|
title: String(r?.title ?? ""),
|
|
2775
2855
|
url: String(r?.url ?? ""),
|
|
@@ -2931,6 +3011,7 @@ async function isBinaryFile(absolutePath) {
|
|
|
2931
3011
|
|
|
2932
3012
|
exports.CatastrophicCommandError = CatastrophicCommandError;
|
|
2933
3013
|
exports.ContextMatchError = ContextMatchError;
|
|
3014
|
+
exports.DEFAULT_MAX_IMAGE_BYTES = DEFAULT_MAX_IMAGE_BYTES;
|
|
2934
3015
|
exports.DEFAULT_TOOL_GUIDANCE = DEFAULT_TOOL_GUIDANCE;
|
|
2935
3016
|
exports.ReadTracker = ReadTracker;
|
|
2936
3017
|
exports.ReasoningTools = ReasoningTools;
|
|
@@ -2959,6 +3040,7 @@ exports.createSessionArtifactStore = createSessionArtifactStore;
|
|
|
2959
3040
|
exports.createShellTool = createShellTool;
|
|
2960
3041
|
exports.createTodolistTool = createTodolistTool;
|
|
2961
3042
|
exports.createUpdatePlanTool = createUpdatePlanTool;
|
|
3043
|
+
exports.createViewImageTool = createViewImageTool;
|
|
2962
3044
|
exports.createWebFetchTool = createWebFetchTool;
|
|
2963
3045
|
exports.createWebSearchTool = createWebSearchTool;
|
|
2964
3046
|
exports.createWriteFileTool = createWriteFileTool;
|