@theokit/sdk-tools 0.4.0 → 0.6.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 +20 -0
- package/dist/index.cjs +44 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +43 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6d65983: `createWebFetchTool` gains a redirect policy + injection seam.
|
|
8
|
+
|
|
9
|
+
- **`maxRedirects?: number`** — caps redirect hops (each SSRF-screened). Default 5 (unchanged). Set `0` to BLOCK ALL redirects (strict no-redirect policy for untrusted, model-chosen URLs).
|
|
10
|
+
- **Distinct `redirect_blocked` error** — a refused redirect now returns `{ ok:false, error:'redirect_blocked' }`, split from `ssrf_blocked` (a blocked private/reserved host). New exported `RedirectBlockedError`; `screenedFetch` throws it on redirect-limit exhaustion (was `SsrfBlockedError("too many redirects")` — a minor, more-precise error refinement).
|
|
11
|
+
- **Injectable `fetchImpl?` / `lookup?`** — drive the tool's redirect + SSRF paths deterministically in tests with no real network/DNS (the seam `screenedFetch` already had, now on the tool surface).
|
|
12
|
+
|
|
13
|
+
Additive + backward-compatible: absent options ⇒ today's behavior; `ssrf_blocked`/`invalid_url`/`timeout`/`too_large` codes + return shape unchanged. Lets a consumer (theocode) replace an app-side SSRF/redirect wrapper with `createWebFetchTool({ maxRedirects: 0 })`.
|
|
14
|
+
|
|
15
|
+
## 0.5.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 6dc0e26: Add `withShellExitGuidance` — a guidance wrapper for `shell_exec` soft failures.
|
|
20
|
+
|
|
21
|
+
`injectGuidance`/`withDefaultGuidance` inject an actionable `guidance` hint only on `{ ok:false, error }` results (by design). But `shell_exec` returns `{ ok:true, exit_code }` — a non-zero `exit_code` is a SOFT failure (the tool ran, the command failed) that the ok:false-only injector does not cover. `withShellExitGuidance(tool)` wraps `shell_exec` so a `{ ok:true, exit_code≠0 }` result gains a `guidance` hint ("The command exited N. Read the stderr above, fix the cause, then retry."). ADDITIVE, IDEMPOTENT, NEVER-THROW; a no-op for any other tool, for `exit_code 0`, and for non-JSON output. Composes after `withDefaultGuidance` (disjoint domains — no double-injection). Lets consumers drop app-side shell-exit guidance reimplementations.
|
|
22
|
+
|
|
3
23
|
## 0.4.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -721,6 +721,15 @@ var SsrfBlockedError = class extends sdk.ConfigurationError {
|
|
|
721
721
|
);
|
|
722
722
|
}
|
|
723
723
|
};
|
|
724
|
+
var RedirectBlockedError = class extends sdk.ConfigurationError {
|
|
725
|
+
name = "RedirectBlockedError";
|
|
726
|
+
constructor(url, detail) {
|
|
727
|
+
super(
|
|
728
|
+
`Blocked redirect for "${url}"${detail ? ` (${detail})` : ""}: redirect-hop limit exceeded (redirect policy).`,
|
|
729
|
+
{ code: "redirect_blocked" }
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
724
733
|
function v4ToInt(ip) {
|
|
725
734
|
const parts = ip.split(".");
|
|
726
735
|
return (Number(parts[0]) << 24 | Number(parts[1]) << 16 | Number(parts[2]) << 8 | Number(parts[3])) >>> 0;
|
|
@@ -844,7 +853,7 @@ async function screenedFetch(url, options = {}) {
|
|
|
844
853
|
if (next === void 0) return res;
|
|
845
854
|
current = next;
|
|
846
855
|
}
|
|
847
|
-
throw new
|
|
856
|
+
throw new RedirectBlockedError(url, "too many redirects");
|
|
848
857
|
}
|
|
849
858
|
var DEFAULT_BUDGET = 8e3;
|
|
850
859
|
var DEFAULT_MAX_DEPTH = 4;
|
|
@@ -1029,6 +1038,28 @@ function withToolResultGuidance(tool, guidance) {
|
|
|
1029
1038
|
function withDefaultGuidance(tool) {
|
|
1030
1039
|
return withToolResultGuidance(tool, DEFAULT_TOOL_GUIDANCE);
|
|
1031
1040
|
}
|
|
1041
|
+
function withShellExitGuidance(tool) {
|
|
1042
|
+
if (tool.name !== "shell_exec") return tool;
|
|
1043
|
+
return {
|
|
1044
|
+
name: tool.name,
|
|
1045
|
+
description: tool.description,
|
|
1046
|
+
inputSchema: tool.inputSchema,
|
|
1047
|
+
handler: async (input) => {
|
|
1048
|
+
const out = await tool.handler(input);
|
|
1049
|
+
let parsed;
|
|
1050
|
+
try {
|
|
1051
|
+
parsed = JSON.parse(out);
|
|
1052
|
+
} catch {
|
|
1053
|
+
return out;
|
|
1054
|
+
}
|
|
1055
|
+
if (isRecord(parsed) && parsed.ok === true && typeof parsed.exit_code === "number" && parsed.exit_code !== 0 && !("guidance" in parsed)) {
|
|
1056
|
+
const guidance = `The command exited ${parsed.exit_code}. Read the stderr above, fix the cause, then retry.`;
|
|
1057
|
+
return JSON.stringify({ ...parsed, guidance });
|
|
1058
|
+
}
|
|
1059
|
+
return out;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1032
1063
|
var DEFAULT_MAX_ENTRIES = 500;
|
|
1033
1064
|
function createListDirTool(opts) {
|
|
1034
1065
|
const { projectRoot, max = DEFAULT_MAX_ENTRIES } = opts;
|
|
@@ -1747,6 +1778,9 @@ var MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
|
1747
1778
|
function createWebFetchTool(opts) {
|
|
1748
1779
|
const defaultTimeoutMs = opts?.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS4;
|
|
1749
1780
|
const allowPrivateHosts = opts?.allowPrivateHosts ?? false;
|
|
1781
|
+
const maxRedirects = opts?.maxRedirects;
|
|
1782
|
+
const fetchImpl = opts?.fetchImpl;
|
|
1783
|
+
const lookup = opts?.lookup;
|
|
1750
1784
|
return sdk.defineTool({
|
|
1751
1785
|
name: "web_fetch",
|
|
1752
1786
|
description: "Fetch the contents of a URL via HTTP/HTTPS. Use only for URLs the user provided or that you are confident help with the task; never invent or guess URLs. Rejects non-http(s) URLs and is SSRF-guarded by default (private/loopback/link-local/cloud-metadata hosts are refused with an ssrf_blocked error). The response body is capped at 1 MB. Returns { ok, content, status_code, content_type } or { ok: false, error }.",
|
|
@@ -1776,7 +1810,10 @@ function createWebFetchTool(opts) {
|
|
|
1776
1810
|
try {
|
|
1777
1811
|
const response = await screenedFetch(url, {
|
|
1778
1812
|
signal: controller.signal,
|
|
1779
|
-
allowPrivateHosts
|
|
1813
|
+
allowPrivateHosts,
|
|
1814
|
+
...maxRedirects !== void 0 ? { maxRedirects } : {},
|
|
1815
|
+
...fetchImpl ? { fetchImpl } : {},
|
|
1816
|
+
...lookup ? { lookup } : {}
|
|
1780
1817
|
});
|
|
1781
1818
|
clearTimeout(timer);
|
|
1782
1819
|
const contentLength = response.headers.get("content-length");
|
|
@@ -1809,6 +1846,9 @@ function createWebFetchTool(opts) {
|
|
|
1809
1846
|
});
|
|
1810
1847
|
} catch (err) {
|
|
1811
1848
|
clearTimeout(timer);
|
|
1849
|
+
if (err instanceof RedirectBlockedError) {
|
|
1850
|
+
return JSON.stringify({ ok: false, error: "redirect_blocked", url, reason: err.message });
|
|
1851
|
+
}
|
|
1812
1852
|
if (err instanceof SsrfBlockedError) {
|
|
1813
1853
|
return JSON.stringify({ ok: false, error: "ssrf_blocked", url, reason: err.message });
|
|
1814
1854
|
}
|
|
@@ -1941,6 +1981,7 @@ async function isBinaryFile(absolutePath) {
|
|
|
1941
1981
|
|
|
1942
1982
|
exports.CatastrophicCommandError = CatastrophicCommandError;
|
|
1943
1983
|
exports.DEFAULT_TOOL_GUIDANCE = DEFAULT_TOOL_GUIDANCE;
|
|
1984
|
+
exports.RedirectBlockedError = RedirectBlockedError;
|
|
1944
1985
|
exports.SsrfBlockedError = SsrfBlockedError;
|
|
1945
1986
|
exports.buildEnvContext = buildEnvContext;
|
|
1946
1987
|
exports.buildRepoMap = buildRepoMap;
|
|
@@ -1978,6 +2019,7 @@ exports.todoItemsToPlanNodes = todoItemsToPlanNodes;
|
|
|
1978
2019
|
exports.truncateOutput = truncateOutput;
|
|
1979
2020
|
exports.withDefaultGuidance = withDefaultGuidance;
|
|
1980
2021
|
exports.withDescription = withDescription;
|
|
2022
|
+
exports.withShellExitGuidance = withShellExitGuidance;
|
|
1981
2023
|
exports.withToolResultGuidance = withToolResultGuidance;
|
|
1982
2024
|
//# sourceMappingURL=index.cjs.map
|
|
1983
2025
|
//# sourceMappingURL=index.cjs.map
|