@theokit/sdk-tools 0.5.0 → 0.7.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 +18 -0
- package/dist/index.cjs +33 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -6
- package/dist/index.d.ts +39 -6
- package/dist/index.js +33 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 5bd2f9c: @theokit/sdk: `shouldCompact`/`ShouldCompactInput` gains an optional `maxOutput` output-reserve term (`estimated >= contextWindow - buffer - (maxOutput ?? 0)`), defaulting to today's behavior. `AgentOptions.plugins` now also accepts an array of code `Plugin` objects (matching the runtime + docs), not only `{ enabled }`. @theokit/sdk-tools: `renderToolList` gains an optional `{ mode: "full" | "summary" | "names" }` — `"full"` (default) is the existing `<tools>` XML; `"summary"` renders `- name: <first sentence>`; `"names"` renders `- name`.
|
|
8
|
+
|
|
9
|
+
## 0.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 6d65983: `createWebFetchTool` gains a redirect policy + injection seam.
|
|
14
|
+
|
|
15
|
+
- **`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).
|
|
16
|
+
- **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).
|
|
17
|
+
- **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).
|
|
18
|
+
|
|
19
|
+
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 })`.
|
|
20
|
+
|
|
3
21
|
## 0.5.0
|
|
4
22
|
|
|
5
23
|
### 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;
|
|
@@ -972,7 +981,18 @@ function withDescription(tool, description) {
|
|
|
972
981
|
function esc(s) {
|
|
973
982
|
return String(s).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
974
983
|
}
|
|
975
|
-
function
|
|
984
|
+
function firstSentence(d) {
|
|
985
|
+
const m = d.trim().match(/\.\s+(?=[A-Z(]|$)/);
|
|
986
|
+
return m?.index == null ? d.trim() : d.trim().slice(0, m.index + 1);
|
|
987
|
+
}
|
|
988
|
+
function renderToolList(tools, options) {
|
|
989
|
+
const mode = options?.mode ?? "full";
|
|
990
|
+
if (mode === "summary") {
|
|
991
|
+
return tools.map((t) => `- ${t.name}: ${firstSentence(t.description)}`).join("\n");
|
|
992
|
+
}
|
|
993
|
+
if (mode === "names") {
|
|
994
|
+
return tools.map((t) => `- ${t.name}`).join("\n");
|
|
995
|
+
}
|
|
976
996
|
if (tools.length === 0) return "<tools></tools>";
|
|
977
997
|
const lines = ["<tools>"];
|
|
978
998
|
for (const t of tools) {
|
|
@@ -1769,6 +1789,9 @@ var MAX_BODY_BYTES = 1 * 1024 * 1024;
|
|
|
1769
1789
|
function createWebFetchTool(opts) {
|
|
1770
1790
|
const defaultTimeoutMs = opts?.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS4;
|
|
1771
1791
|
const allowPrivateHosts = opts?.allowPrivateHosts ?? false;
|
|
1792
|
+
const maxRedirects = opts?.maxRedirects;
|
|
1793
|
+
const fetchImpl = opts?.fetchImpl;
|
|
1794
|
+
const lookup = opts?.lookup;
|
|
1772
1795
|
return sdk.defineTool({
|
|
1773
1796
|
name: "web_fetch",
|
|
1774
1797
|
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 }.",
|
|
@@ -1798,7 +1821,10 @@ function createWebFetchTool(opts) {
|
|
|
1798
1821
|
try {
|
|
1799
1822
|
const response = await screenedFetch(url, {
|
|
1800
1823
|
signal: controller.signal,
|
|
1801
|
-
allowPrivateHosts
|
|
1824
|
+
allowPrivateHosts,
|
|
1825
|
+
...maxRedirects !== void 0 ? { maxRedirects } : {},
|
|
1826
|
+
...fetchImpl ? { fetchImpl } : {},
|
|
1827
|
+
...lookup ? { lookup } : {}
|
|
1802
1828
|
});
|
|
1803
1829
|
clearTimeout(timer);
|
|
1804
1830
|
const contentLength = response.headers.get("content-length");
|
|
@@ -1831,6 +1857,9 @@ function createWebFetchTool(opts) {
|
|
|
1831
1857
|
});
|
|
1832
1858
|
} catch (err) {
|
|
1833
1859
|
clearTimeout(timer);
|
|
1860
|
+
if (err instanceof RedirectBlockedError) {
|
|
1861
|
+
return JSON.stringify({ ok: false, error: "redirect_blocked", url, reason: err.message });
|
|
1862
|
+
}
|
|
1834
1863
|
if (err instanceof SsrfBlockedError) {
|
|
1835
1864
|
return JSON.stringify({ ok: false, error: "ssrf_blocked", url, reason: err.message });
|
|
1836
1865
|
}
|
|
@@ -1963,6 +1992,7 @@ async function isBinaryFile(absolutePath) {
|
|
|
1963
1992
|
|
|
1964
1993
|
exports.CatastrophicCommandError = CatastrophicCommandError;
|
|
1965
1994
|
exports.DEFAULT_TOOL_GUIDANCE = DEFAULT_TOOL_GUIDANCE;
|
|
1995
|
+
exports.RedirectBlockedError = RedirectBlockedError;
|
|
1966
1996
|
exports.SsrfBlockedError = SsrfBlockedError;
|
|
1967
1997
|
exports.buildEnvContext = buildEnvContext;
|
|
1968
1998
|
exports.buildRepoMap = buildRepoMap;
|