@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 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 SEGMENTOS_SENSIVEIS = /* @__PURE__ */ new Set([".env", ".git", "node_modules", ".theo"]);
797
- function ehProibidoEmQualquerProfundidade(path) {
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 SEGMENTOS_SENSIVEIS.has(s) || /^\.env\./.test(s);
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 = decidirEscopo(relative3, path, opts.allowAbsolute === true);
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 decidirEscopo(relative3, original, allowAbsolute) {
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 (ehProibidoEmQualquerProfundidade(relative3)) return refuse("forbidden_path");
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 askerDoContexto(context) {
1772
+ function askerFromContext(context) {
1773
1773
  if (typeof context !== "object" || context === null) return void 0;
1774
- const candidato = context.askUser;
1775
- return typeof candidato === "function" ? candidato : void 0;
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 = askerDoContexto(ctx?.context) ?? opts.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) && ehProibidoEmQualquerProfundidade(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 json = await res.json();
2772
- const results = json?.web?.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;