@anthropic-ai/sdk 0.115.0 → 0.117.1
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 +64 -0
- package/client.d.mts +3 -3
- package/client.d.mts.map +1 -1
- package/client.d.ts +3 -3
- package/client.d.ts.map +1 -1
- package/client.js +1 -1
- package/client.js.map +1 -1
- package/client.mjs +1 -1
- package/client.mjs.map +1 -1
- package/internal/constants.d.mts.map +1 -1
- package/internal/constants.d.ts.map +1 -1
- package/internal/constants.js +0 -5
- package/internal/constants.js.map +1 -1
- package/internal/constants.mjs +0 -5
- package/internal/constants.mjs.map +1 -1
- package/lib/BetaMessageStream.d.mts.map +1 -1
- package/lib/BetaMessageStream.d.ts.map +1 -1
- package/lib/BetaMessageStream.js +12 -5
- package/lib/BetaMessageStream.js.map +1 -1
- package/lib/BetaMessageStream.mjs +12 -5
- package/lib/BetaMessageStream.mjs.map +1 -1
- package/lib/MessageStream.d.mts.map +1 -1
- package/lib/MessageStream.d.ts.map +1 -1
- package/lib/MessageStream.js +9 -4
- package/lib/MessageStream.js.map +1 -1
- package/lib/MessageStream.mjs +9 -4
- package/lib/MessageStream.mjs.map +1 -1
- package/lib/environments/worker.d.mts.map +1 -1
- package/lib/environments/worker.d.ts.map +1 -1
- package/lib/environments/worker.js +29 -3
- package/lib/environments/worker.js.map +1 -1
- package/lib/environments/worker.mjs +29 -3
- package/lib/environments/worker.mjs.map +1 -1
- package/lib/sessions/accumulate.js +1 -0
- package/lib/sessions/accumulate.js.map +1 -1
- package/lib/sessions/accumulate.mjs +1 -0
- package/lib/sessions/accumulate.mjs.map +1 -1
- package/lib/tools/BetaToolRunner.d.mts.map +1 -1
- package/lib/tools/BetaToolRunner.d.ts.map +1 -1
- package/lib/tools/BetaToolRunner.js +11 -0
- package/lib/tools/BetaToolRunner.js.map +1 -1
- package/lib/tools/BetaToolRunner.mjs +11 -0
- package/lib/tools/BetaToolRunner.mjs.map +1 -1
- package/package.json +9 -15
- package/resources/beta/agents/agents.d.mts +25 -3
- package/resources/beta/agents/agents.d.mts.map +1 -1
- package/resources/beta/agents/agents.d.ts +25 -3
- package/resources/beta/agents/agents.d.ts.map +1 -1
- package/resources/beta/agents/agents.js.map +1 -1
- package/resources/beta/agents/agents.mjs.map +1 -1
- package/resources/beta/agents/index.d.mts +1 -1
- package/resources/beta/agents/index.d.mts.map +1 -1
- package/resources/beta/agents/index.d.ts +1 -1
- package/resources/beta/agents/index.d.ts.map +1 -1
- package/resources/beta/agents/index.js.map +1 -1
- package/resources/beta/agents/index.mjs.map +1 -1
- package/resources/beta/beta.d.mts +28 -10
- package/resources/beta/beta.d.mts.map +1 -1
- package/resources/beta/beta.d.ts +28 -10
- package/resources/beta/beta.d.ts.map +1 -1
- package/resources/beta/beta.js.map +1 -1
- package/resources/beta/beta.mjs.map +1 -1
- package/resources/beta/deployments.d.mts +16 -1
- package/resources/beta/deployments.d.mts.map +1 -1
- package/resources/beta/deployments.d.ts +16 -1
- package/resources/beta/deployments.d.ts.map +1 -1
- package/resources/beta/dreams.d.mts +46 -6
- package/resources/beta/dreams.d.mts.map +1 -1
- package/resources/beta/dreams.d.ts +46 -6
- package/resources/beta/dreams.d.ts.map +1 -1
- package/resources/beta/index.d.mts +5 -5
- package/resources/beta/index.d.mts.map +1 -1
- package/resources/beta/index.d.ts +5 -5
- package/resources/beta/index.d.ts.map +1 -1
- package/resources/beta/index.js.map +1 -1
- package/resources/beta/index.mjs.map +1 -1
- package/resources/beta/messages/messages.d.mts +51 -0
- package/resources/beta/messages/messages.d.mts.map +1 -1
- package/resources/beta/messages/messages.d.ts +51 -0
- package/resources/beta/messages/messages.d.ts.map +1 -1
- package/resources/beta/messages/messages.js +2 -23
- package/resources/beta/messages/messages.js.map +1 -1
- package/resources/beta/messages/messages.mjs +2 -23
- package/resources/beta/messages/messages.mjs.map +1 -1
- package/resources/beta/sessions/events.d.mts +60 -10
- package/resources/beta/sessions/events.d.mts.map +1 -1
- package/resources/beta/sessions/events.d.ts +60 -10
- package/resources/beta/sessions/events.d.ts.map +1 -1
- package/resources/beta/sessions/events.js.map +1 -1
- package/resources/beta/sessions/events.mjs.map +1 -1
- package/resources/beta/sessions/index.d.mts +2 -2
- package/resources/beta/sessions/index.d.mts.map +1 -1
- package/resources/beta/sessions/index.d.ts +2 -2
- package/resources/beta/sessions/index.d.ts.map +1 -1
- package/resources/beta/sessions/index.js.map +1 -1
- package/resources/beta/sessions/index.mjs.map +1 -1
- package/resources/beta/sessions/sessions.d.mts +101 -6
- package/resources/beta/sessions/sessions.d.mts.map +1 -1
- package/resources/beta/sessions/sessions.d.ts +101 -6
- package/resources/beta/sessions/sessions.d.ts.map +1 -1
- package/resources/beta/sessions/sessions.js.map +1 -1
- package/resources/beta/sessions/sessions.mjs.map +1 -1
- package/resources/beta/sessions/threads/threads.d.mts +17 -5
- package/resources/beta/sessions/threads/threads.d.mts.map +1 -1
- package/resources/beta/sessions/threads/threads.d.ts +17 -5
- package/resources/beta/sessions/threads/threads.d.ts.map +1 -1
- package/resources/beta/sessions/threads/threads.js.map +1 -1
- package/resources/beta/sessions/threads/threads.mjs.map +1 -1
- package/resources/beta/user-profiles.d.mts +5 -5
- package/resources/beta/user-profiles.d.ts +5 -5
- package/resources/beta/webhooks.d.mts +11 -2
- package/resources/beta/webhooks.d.mts.map +1 -1
- package/resources/beta/webhooks.d.ts +11 -2
- package/resources/beta/webhooks.d.ts.map +1 -1
- package/resources/index.d.mts +1 -1
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +1 -1
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js.map +1 -1
- package/resources/index.mjs.map +1 -1
- package/resources/messages/messages.d.mts +52 -1
- package/resources/messages/messages.d.mts.map +1 -1
- package/resources/messages/messages.d.ts +52 -1
- package/resources/messages/messages.d.ts.map +1 -1
- package/resources/messages/messages.js +2 -23
- package/resources/messages/messages.js.map +1 -1
- package/resources/messages/messages.mjs +2 -23
- package/resources/messages/messages.mjs.map +1 -1
- package/src/client.ts +6 -2
- package/src/internal/constants.ts +0 -5
- package/src/lib/BetaMessageStream.ts +15 -5
- package/src/lib/MessageStream.ts +11 -4
- package/src/lib/environments/worker.ts +28 -3
- package/src/lib/sessions/accumulate.ts +1 -0
- package/src/lib/tools/BetaToolRunner.ts +11 -0
- package/src/resources/beta/agents/agents.ts +29 -2
- package/src/resources/beta/agents/index.ts +1 -0
- package/src/resources/beta/beta.ts +43 -1
- package/src/resources/beta/deployments.ts +19 -0
- package/src/resources/beta/dreams.ts +54 -5
- package/src/resources/beta/index.ts +11 -0
- package/src/resources/beta/messages/messages.ts +53 -23
- package/src/resources/beta/sessions/events.ts +95 -9
- package/src/resources/beta/sessions/index.ts +7 -0
- package/src/resources/beta/sessions/sessions.ts +127 -3
- package/src/resources/beta/sessions/threads/threads.ts +21 -5
- package/src/resources/beta/user-profiles.ts +5 -5
- package/src/resources/beta/webhooks.ts +16 -1
- package/src/resources/index.ts +2 -0
- package/src/resources/messages/messages.ts +53 -25
- package/src/tools/agent-toolset/fs-util.ts +43 -23
- package/src/tools/agent-toolset/node.browser.ts +14 -0
- package/src/tools/agent-toolset/node.ts +20 -9
- package/src/tools/agent-toolset/skills.ts +127 -31
- package/src/version.ts +1 -1
- package/tools/agent-toolset/fs-util.d.mts +14 -2
- package/tools/agent-toolset/fs-util.d.mts.map +1 -1
- package/tools/agent-toolset/fs-util.d.ts +14 -2
- package/tools/agent-toolset/fs-util.d.ts.map +1 -1
- package/tools/agent-toolset/fs-util.js +46 -24
- package/tools/agent-toolset/fs-util.js.map +1 -1
- package/tools/agent-toolset/fs-util.mjs +45 -24
- package/tools/agent-toolset/fs-util.mjs.map +1 -1
- package/tools/agent-toolset/node.browser.d.mts +9 -0
- package/tools/agent-toolset/node.browser.d.mts.map +1 -1
- package/tools/agent-toolset/node.browser.d.ts +9 -0
- package/tools/agent-toolset/node.browser.d.ts.map +1 -1
- package/tools/agent-toolset/node.browser.js +13 -1
- package/tools/agent-toolset/node.browser.js.map +1 -1
- package/tools/agent-toolset/node.browser.mjs +11 -0
- package/tools/agent-toolset/node.browser.mjs.map +1 -1
- package/tools/agent-toolset/node.d.mts +9 -0
- package/tools/agent-toolset/node.d.mts.map +1 -1
- package/tools/agent-toolset/node.d.ts +9 -0
- package/tools/agent-toolset/node.d.ts.map +1 -1
- package/tools/agent-toolset/node.js +19 -7
- package/tools/agent-toolset/node.js.map +1 -1
- package/tools/agent-toolset/node.mjs +17 -6
- package/tools/agent-toolset/node.mjs.map +1 -1
- package/tools/agent-toolset/skills.d.mts +26 -3
- package/tools/agent-toolset/skills.d.mts.map +1 -1
- package/tools/agent-toolset/skills.d.ts +26 -3
- package/tools/agent-toolset/skills.d.ts.map +1 -1
- package/tools/agent-toolset/skills.js +114 -29
- package/tools/agent-toolset/skills.js.map +1 -1
- package/tools/agent-toolset/skills.mjs +113 -30
- package/tools/agent-toolset/skills.mjs.map +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -86,7 +86,7 @@ export class Messages extends APIResource {
|
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
let timeout = (this._client as any)._options.timeout as number | null;
|
|
89
|
+
let timeout = options?.timeout ?? ((this._client as any)._options.timeout as number | null);
|
|
90
90
|
if (!body.stream && timeout == null) {
|
|
91
91
|
const maxNonstreamingTokens = MODEL_NONSTREAMING_TOKENS[body.model] ?? undefined;
|
|
92
92
|
timeout = this._client.calculateNonstreamingTimeout(body.max_tokens, maxNonstreamingTokens);
|
|
@@ -1272,8 +1272,6 @@ export type Model =
|
|
|
1272
1272
|
| 'claude-opus-4-5-20251101'
|
|
1273
1273
|
| 'claude-sonnet-4-5'
|
|
1274
1274
|
| 'claude-sonnet-4-5-20250929'
|
|
1275
|
-
| 'claude-opus-4-1'
|
|
1276
|
-
| 'claude-opus-4-1-20250805'
|
|
1277
1275
|
| (string & {});
|
|
1278
1276
|
|
|
1279
1277
|
export interface OutputConfig {
|
|
@@ -1304,28 +1302,7 @@ export interface OutputTokensDetails {
|
|
|
1304
1302
|
}
|
|
1305
1303
|
const DEPRECATED_MODELS: {
|
|
1306
1304
|
[K in Model]?: string;
|
|
1307
|
-
} = {
|
|
1308
|
-
'claude-1.3': 'November 6th, 2024',
|
|
1309
|
-
'claude-1.3-100k': 'November 6th, 2024',
|
|
1310
|
-
'claude-instant-1.1': 'November 6th, 2024',
|
|
1311
|
-
'claude-instant-1.1-100k': 'November 6th, 2024',
|
|
1312
|
-
'claude-instant-1.2': 'November 6th, 2024',
|
|
1313
|
-
'claude-3-sonnet-20240229': 'July 21st, 2025',
|
|
1314
|
-
'claude-3-opus-20240229': 'January 5th, 2026',
|
|
1315
|
-
'claude-2.1': 'July 21st, 2025',
|
|
1316
|
-
'claude-2.0': 'July 21st, 2025',
|
|
1317
|
-
'claude-3-7-sonnet-latest': 'February 19th, 2026',
|
|
1318
|
-
'claude-3-7-sonnet-20250219': 'February 19th, 2026',
|
|
1319
|
-
'claude-3-5-haiku-latest': 'February 19th, 2026',
|
|
1320
|
-
'claude-3-5-haiku-20241022': 'February 19th, 2026',
|
|
1321
|
-
'claude-opus-4-0': 'June 15th, 2026',
|
|
1322
|
-
'claude-opus-4-20250514': 'June 15th, 2026',
|
|
1323
|
-
'claude-sonnet-4-0': 'June 15th, 2026',
|
|
1324
|
-
'claude-sonnet-4-20250514': 'June 15th, 2026',
|
|
1325
|
-
'claude-opus-4-1': 'August 5th, 2026',
|
|
1326
|
-
'claude-opus-4-1-20250805': 'August 5th, 2026',
|
|
1327
|
-
'claude-mythos-preview': 'June 30th, 2026',
|
|
1328
|
-
};
|
|
1305
|
+
} = {};
|
|
1329
1306
|
|
|
1330
1307
|
const MODELS_TO_WARN_WITH_THINKING_ENABLED: Model[] = ['claude-mythos-preview', 'claude-opus-4-6'];
|
|
1331
1308
|
|
|
@@ -1444,12 +1421,28 @@ export type RawMessageStreamEvent =
|
|
|
1444
1421
|
| RawContentBlockStopEvent;
|
|
1445
1422
|
|
|
1446
1423
|
export interface RedactedThinkingBlock {
|
|
1424
|
+
/**
|
|
1425
|
+
* The contents of this redacted thinking block, returned when portions of the
|
|
1426
|
+
* model's thinking were safety-redacted. This field is opaque and encrypted, with
|
|
1427
|
+
* no readable content.
|
|
1428
|
+
*
|
|
1429
|
+
* Pass `redacted_thinking` blocks back to the API unchanged when continuing a
|
|
1430
|
+
* multi-turn conversation.
|
|
1431
|
+
*
|
|
1432
|
+
* See
|
|
1433
|
+
* [extended thinking](https://platform.claude.com/docs/en/build-with-claude/extended-thinking#redacted-thinking-blocks)
|
|
1434
|
+
* for details.
|
|
1435
|
+
*/
|
|
1447
1436
|
data: string;
|
|
1448
1437
|
|
|
1449
1438
|
type: 'redacted_thinking';
|
|
1450
1439
|
}
|
|
1451
1440
|
|
|
1452
1441
|
export interface RedactedThinkingBlockParam {
|
|
1442
|
+
/**
|
|
1443
|
+
* The `data` value of this redacted thinking block, exactly as returned by the API
|
|
1444
|
+
* in a previous response. Opaque and encrypted; pass it back unchanged.
|
|
1445
|
+
*/
|
|
1453
1446
|
data: string;
|
|
1454
1447
|
|
|
1455
1448
|
type: 'redacted_thinking';
|
|
@@ -1584,6 +1577,11 @@ export interface ServerToolUseBlockParam {
|
|
|
1584
1577
|
}
|
|
1585
1578
|
|
|
1586
1579
|
export interface SignatureDelta {
|
|
1580
|
+
/**
|
|
1581
|
+
* The `signature` for this thinking block: an opaque value used to verify that the
|
|
1582
|
+
* block was generated by Claude when it is passed back to the API. Delivered in a
|
|
1583
|
+
* `signature_delta` event just before the block's `content_block_stop` event.
|
|
1584
|
+
*/
|
|
1587
1585
|
signature: string;
|
|
1588
1586
|
|
|
1589
1587
|
type: 'signature_delta';
|
|
@@ -1767,16 +1765,41 @@ export interface TextEditorCodeExecutionViewResultBlockParam {
|
|
|
1767
1765
|
}
|
|
1768
1766
|
|
|
1769
1767
|
export interface ThinkingBlock {
|
|
1768
|
+
/**
|
|
1769
|
+
* A value used to verify that this thinking block was generated by Claude when it
|
|
1770
|
+
* is passed back to the API.
|
|
1771
|
+
*
|
|
1772
|
+
* This is an opaque field and should not be interpreted or parsed. When passing
|
|
1773
|
+
* thinking blocks back to the API (required when using tools with extended
|
|
1774
|
+
* thinking), pass them back exactly as received, with this field intact.
|
|
1775
|
+
*
|
|
1776
|
+
* See
|
|
1777
|
+
* [extended thinking](https://platform.claude.com/docs/en/build-with-claude/extended-thinking)
|
|
1778
|
+
* for details.
|
|
1779
|
+
*/
|
|
1770
1780
|
signature: string;
|
|
1771
1781
|
|
|
1782
|
+
/**
|
|
1783
|
+
* The text of Claude's thinking process for this block.
|
|
1784
|
+
*/
|
|
1772
1785
|
thinking: string;
|
|
1773
1786
|
|
|
1774
1787
|
type: 'thinking';
|
|
1775
1788
|
}
|
|
1776
1789
|
|
|
1777
1790
|
export interface ThinkingBlockParam {
|
|
1791
|
+
/**
|
|
1792
|
+
* The `signature` value of this thinking block, exactly as returned by the API in
|
|
1793
|
+
* a previous response. Used to verify that the block was generated by Claude.
|
|
1794
|
+
*
|
|
1795
|
+
* Thinking blocks must be passed back unmodified and in their original order; a
|
|
1796
|
+
* modified block results in a 400 `invalid_request_error`.
|
|
1797
|
+
*/
|
|
1778
1798
|
signature: string;
|
|
1779
1799
|
|
|
1800
|
+
/**
|
|
1801
|
+
* The `thinking` text of this block as returned by the API.
|
|
1802
|
+
*/
|
|
1780
1803
|
thinking: string;
|
|
1781
1804
|
|
|
1782
1805
|
type: 'thinking';
|
|
@@ -1837,6 +1860,11 @@ export interface ThinkingConfigEnabled {
|
|
|
1837
1860
|
export type ThinkingConfigParam = ThinkingConfigEnabled | ThinkingConfigDisabled | ThinkingConfigAdaptive;
|
|
1838
1861
|
|
|
1839
1862
|
export interface ThinkingDelta {
|
|
1863
|
+
/**
|
|
1864
|
+
* The incremental `thinking` text for this content block. Concatenate the
|
|
1865
|
+
* `thinking` values of successive `thinking_delta` events to assemble the block's
|
|
1866
|
+
* full `thinking` value.
|
|
1867
|
+
*/
|
|
1840
1868
|
thinking: string;
|
|
1841
1869
|
|
|
1842
1870
|
type: 'thinking_delta';
|
|
@@ -24,12 +24,27 @@ async function realpathOrSelf(p: string): Promise<string> {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Matches Linux MAXSYMLINKS, the threshold at which `realpath` itself reports ELOOP. */
|
|
28
|
+
const MAX_SYMLINK_HOPS = 40;
|
|
29
|
+
|
|
30
|
+
/** The `code` of a Node system error, or `undefined` for anything else. */
|
|
31
|
+
export function errnoCode(err: unknown): string | undefined {
|
|
32
|
+
const code = (err as { code?: unknown } | null)?.code;
|
|
33
|
+
return typeof code === 'string' ? code : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
/**
|
|
28
37
|
* Fully resolve `abs`: `realpath` the longest existing ancestor and re-append
|
|
29
38
|
* the rest, but never re-append a component that is itself a symlink — read the
|
|
30
39
|
* link and continue from its target instead. This handles paths being created
|
|
31
40
|
* (write/edit) without letting a symlink leaf (e.g. a dangling one pointing
|
|
32
41
|
* outside a confinement root) slip through unresolved.
|
|
42
|
+
*
|
|
43
|
+
* Returns a symlink-free path or throws an errno-carrying error (`ELOOP` for a
|
|
44
|
+
* cycle or more than {@link MAX_SYMLINK_HOPS} links, the `lstat`/`realpath`
|
|
45
|
+
* error for an unreadable component); it never returns `abs` unresolved. Only
|
|
46
|
+
* symlink hops count against the cap, so any depth of not-yet-existing
|
|
47
|
+
* directories still resolves.
|
|
33
48
|
*/
|
|
34
49
|
export async function canonicalize(abs: string): Promise<string> {
|
|
35
50
|
const tail: string[] = [];
|
|
@@ -39,28 +54,24 @@ export async function canonicalize(abs: string): Promise<string> {
|
|
|
39
54
|
let real: string;
|
|
40
55
|
try {
|
|
41
56
|
real = await fs.realpath(prefix);
|
|
42
|
-
} catch {
|
|
43
|
-
let isLink
|
|
57
|
+
} catch (realpathErr) {
|
|
58
|
+
let isLink: boolean;
|
|
44
59
|
try {
|
|
45
60
|
isLink = (await fs.lstat(prefix)).isSymbolicLink();
|
|
46
|
-
} catch {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// have returned ELOOP — so a cycle of unresolvable links terminates.
|
|
54
|
-
if (++hops > 40) {
|
|
55
|
-
throw new ToolError(`path ${JSON.stringify(abs)} has too many levels of symbolic links`);
|
|
56
|
-
}
|
|
57
|
-
prefix = path.resolve(path.dirname(prefix), await fs.readlink(prefix));
|
|
61
|
+
} catch (lstatErr) {
|
|
62
|
+
const code = errnoCode(lstatErr);
|
|
63
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') throw lstatErr;
|
|
64
|
+
const parent = path.dirname(prefix);
|
|
65
|
+
if (parent === prefix) throw lstatErr;
|
|
66
|
+
tail.push(path.basename(prefix));
|
|
67
|
+
prefix = parent;
|
|
58
68
|
continue;
|
|
59
69
|
}
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
if (!isLink) throw realpathErr;
|
|
71
|
+
if (++hops > MAX_SYMLINK_HOPS) {
|
|
72
|
+
throw Object.assign(new Error('too many levels of symbolic links'), { code: 'ELOOP' });
|
|
73
|
+
}
|
|
74
|
+
prefix = path.resolve(path.dirname(prefix), await fs.readlink(prefix));
|
|
64
75
|
continue;
|
|
65
76
|
}
|
|
66
77
|
return tail.length ? path.join(real, ...tail.reverse()) : real;
|
|
@@ -76,6 +87,9 @@ export async function canonicalize(abs: string): Promise<string> {
|
|
|
76
87
|
* leaf, even a dangling one) is resolved before the confinement check, and the
|
|
77
88
|
* resolved path is what the caller then operates on, so a symlink inside `root`
|
|
78
89
|
* that points outside it can neither pass the check nor be followed afterwards.
|
|
90
|
+
* `..` is collapsed lexically before any symlink is followed. A path that cannot
|
|
91
|
+
* be resolved (symlink loop, unreadable component) is rejected with a
|
|
92
|
+
* `ToolError` naming `p`, never the host's absolute path.
|
|
79
93
|
*
|
|
80
94
|
* Residual TOCTOU: a component could still be swapped for a symlink between this
|
|
81
95
|
* call and the eventual `fs` operation. Closing that fully needs per-component
|
|
@@ -91,7 +105,12 @@ export async function confineToRoot(
|
|
|
91
105
|
const realRoot = await realpathOrSelf(path.resolve(root));
|
|
92
106
|
const abs = path.resolve(realRoot, p);
|
|
93
107
|
if (allowOutside) return abs;
|
|
94
|
-
|
|
108
|
+
let real: string;
|
|
109
|
+
try {
|
|
110
|
+
real = await canonicalize(abs);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
throw new ToolError(fsErrorMessage(err, `path ${JSON.stringify(p)}`));
|
|
113
|
+
}
|
|
95
114
|
if (real !== realRoot && !real.startsWith(realRoot + path.sep)) {
|
|
96
115
|
throw new ToolError(`path ${JSON.stringify(p)} escapes workdir`);
|
|
97
116
|
}
|
|
@@ -124,11 +143,12 @@ export async function atomicWriteFile(targetPath: string, content: string): Prom
|
|
|
124
143
|
/**
|
|
125
144
|
* Map a thrown filesystem error to a consistent, language-independent message,
|
|
126
145
|
* so the model sees the same wording regardless of the runtime (Node's raw
|
|
127
|
-
* `ENOENT: no such file...` text would otherwise leak through).
|
|
128
|
-
*
|
|
146
|
+
* `ENOENT: no such file...` text would otherwise leak through). Codes we don't
|
|
147
|
+
* special-case render as the bare code, never Node's message, which embeds the
|
|
148
|
+
* host's absolute path.
|
|
129
149
|
*/
|
|
130
150
|
export function fsErrorMessage(err: unknown, file: string): string {
|
|
131
|
-
const code = (err
|
|
151
|
+
const code = errnoCode(err);
|
|
132
152
|
switch (code) {
|
|
133
153
|
case 'ENOENT':
|
|
134
154
|
return `${file}: no such file or directory`;
|
|
@@ -149,6 +169,6 @@ export function fsErrorMessage(err: unknown, file: string): string {
|
|
|
149
169
|
case 'ENFILE':
|
|
150
170
|
return `${file}: too many open files`;
|
|
151
171
|
default:
|
|
152
|
-
return `${file}: ${
|
|
172
|
+
return `${file}: ${code !== undefined ? `i/o error (${code})` : 'i/o error'}`;
|
|
153
173
|
}
|
|
154
174
|
}
|
|
@@ -44,6 +44,20 @@ export function resolvePath(_ctx: AgentToolContext, _p: string): Promise<string>
|
|
|
44
44
|
return nodeOnly('resolvePath');
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* A bash command exceeded its `timeoutMs`. Carries the timeout so a caller can
|
|
49
|
+
* tell it apart from an abort without matching on the message text.
|
|
50
|
+
*/
|
|
51
|
+
export class BashTimeoutError extends AnthropicError {
|
|
52
|
+
readonly timeoutMs: number;
|
|
53
|
+
|
|
54
|
+
constructor(timeoutMs: number) {
|
|
55
|
+
super(`bash command timed out after ${timeoutMs}ms`);
|
|
56
|
+
this.name = 'BashTimeoutError';
|
|
57
|
+
this.timeoutMs = timeoutMs;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
export class BashSession {
|
|
48
62
|
constructor(_dir: string, _env?: NodeJS.ProcessEnv) {
|
|
49
63
|
nodeOnly('BashSession');
|
|
@@ -55,6 +55,20 @@ const GREP_OUTPUT_LIMIT = 100 * 1024;
|
|
|
55
55
|
const GREP_MAX_LINE_LENGTH = 2000;
|
|
56
56
|
const GLOB_RESULT_LIMIT = 200;
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* A bash command exceeded its `timeoutMs`. Carries the timeout so a caller can
|
|
60
|
+
* tell it apart from an abort without matching on the message text.
|
|
61
|
+
*/
|
|
62
|
+
export class BashTimeoutError extends AnthropicError {
|
|
63
|
+
readonly timeoutMs: number;
|
|
64
|
+
|
|
65
|
+
constructor(timeoutMs: number) {
|
|
66
|
+
super(`bash command timed out after ${timeoutMs}ms`);
|
|
67
|
+
this.name = 'BashTimeoutError';
|
|
68
|
+
this.timeoutMs = timeoutMs;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
const ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
59
73
|
|
|
60
74
|
// `fs.glob` is Node 22+. `@types/node` may still target an older line, so the
|
|
@@ -271,9 +285,9 @@ export class BashSession {
|
|
|
271
285
|
}
|
|
272
286
|
const timeoutMs = opts.timeoutMs ?? BASH_DEFAULT_TIMEOUT_MS;
|
|
273
287
|
const signal = opts.signal;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
288
|
+
// Reject with the signal's own reason, so a caller telling a user cancel
|
|
289
|
+
// apart from an `AbortSignal.timeout()` sees the platform's name intact.
|
|
290
|
+
signal?.throwIfAborted();
|
|
277
291
|
this.#buf = '';
|
|
278
292
|
this.#truncated = false;
|
|
279
293
|
// Per-call nonce so a command that prints a fixed marker can't spoof the
|
|
@@ -298,14 +312,11 @@ export class BashSession {
|
|
|
298
312
|
await Promise.race([
|
|
299
313
|
sentinelSeen,
|
|
300
314
|
new Promise<never>((_, reject) => {
|
|
301
|
-
timer = setTimeout(
|
|
302
|
-
() => reject(new AnthropicError(`bash command timed out after ${timeoutMs}ms`)),
|
|
303
|
-
timeoutMs,
|
|
304
|
-
);
|
|
315
|
+
timer = setTimeout(() => reject(new BashTimeoutError(timeoutMs)), timeoutMs);
|
|
305
316
|
}),
|
|
306
317
|
new Promise<never>((_, reject) => {
|
|
307
318
|
if (!signal) return;
|
|
308
|
-
onAbort = () => reject(
|
|
319
|
+
onAbort = () => reject(signal.reason);
|
|
309
320
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
310
321
|
}),
|
|
311
322
|
]);
|
|
@@ -459,7 +470,7 @@ export function betaReadTool(ctx: AgentToolContext): BetaRunnableTool {
|
|
|
459
470
|
if (e instanceof ToolError) throw e;
|
|
460
471
|
throw new ToolError(`read: ${fsErrorMessage(e, file_path)}`);
|
|
461
472
|
}
|
|
462
|
-
if (!view_range) return data;
|
|
473
|
+
if (!view_range?.length) return data;
|
|
463
474
|
if (view_range.length !== 2) throw new ToolError('read: view_range must be [start_line, end_line]');
|
|
464
475
|
const [startLine, endLine] = view_range as [number, number];
|
|
465
476
|
const lines = data.split('\n');
|
|
@@ -15,7 +15,7 @@ import { pipeline } from 'node:stream/promises';
|
|
|
15
15
|
import type { Anthropic } from '../../client';
|
|
16
16
|
import { AnthropicError } from '../../core/error';
|
|
17
17
|
import { loggerFor } from '../../internal/utils/log';
|
|
18
|
-
import { DIR_CREATE_MODE } from './fs-util';
|
|
18
|
+
import { DIR_CREATE_MODE, errnoCode } from './fs-util';
|
|
19
19
|
import type { AgentToolContext } from './node';
|
|
20
20
|
|
|
21
21
|
const execFileAsync = promisify(execFile);
|
|
@@ -115,8 +115,8 @@ export async function resolveSkillVersion(
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
/** Reject archive members that are absolute or contain a `..` component. */
|
|
118
|
-
function assertSafeMemberNames(names: string): void {
|
|
119
|
-
for (const raw of names
|
|
118
|
+
function assertSafeMemberNames(names: string[]): void {
|
|
119
|
+
for (const raw of names) {
|
|
120
120
|
const entry = raw.trim();
|
|
121
121
|
if (!entry) continue;
|
|
122
122
|
if (path.isAbsolute(entry) || entry.split(/[\\/]/).includes('..')) {
|
|
@@ -125,19 +125,77 @@ function assertSafeMemberNames(names: string): void {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
const INCONSISTENT_LISTING = 'skill archive listing is inconsistent; refusing to extract';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Type chars (first byte of each `ls`-style line from `unzip -Z` / `tar -tvf`)
|
|
132
|
+
* that denote a regular file or directory. `zipinfo` prints `?` for entries
|
|
133
|
+
* with no Unix type bits, which `unzip` extracts as regular files; GNU tar
|
|
134
|
+
* prints `C` for contiguous files. Everything else — `l` symlink, `h`
|
|
135
|
+
* hardlink, `b`/`c` device, `p` fifo, `s` socket, unknown tar types — is a
|
|
136
|
+
* special member.
|
|
137
|
+
*/
|
|
138
|
+
const PLAIN_TYPE_CHARS = { unzip: new Set(['-', 'd', '?']), tar: new Set(['-', 'd', 'C']) };
|
|
139
|
+
|
|
140
|
+
function listingLines(listing: string): string[] {
|
|
141
|
+
const lines = listing.split('\n');
|
|
142
|
+
if (lines[lines.length - 1] === '') lines.pop();
|
|
143
|
+
return lines;
|
|
144
|
+
}
|
|
145
|
+
|
|
128
146
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* `
|
|
133
|
-
*
|
|
147
|
+
* A special member is excluded by handing its listed name back to the CLI as
|
|
148
|
+
* a pattern, so the name must be byte-identical to what is stored. `tar`,
|
|
149
|
+
* `bsdtar` and `unzip` print bytes they cannot show literally as `\ooo`, `^X`
|
|
150
|
+
* or `#U` escapes, or as raw non-ASCII; any such name cannot be excluded
|
|
151
|
+
* reliably. A leading `-` would let `unzip` parse the pattern as an option.
|
|
134
152
|
*/
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
153
|
+
function canExcludeVerbatim(cmd: 'unzip' | 'tar', name: string): boolean {
|
|
154
|
+
return /^[\x20-\x7E]+$/.test(name) && !/[\\^#]/.test(name) && !(cmd === 'unzip' && name.startsWith('-'));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Pair an archive's name listing (`unzip -Z1` / `tar -tf`) with its typed
|
|
159
|
+
* listing (`unzip -Z --h --t` / `tar -tvf`) and split the members into plain
|
|
160
|
+
* (regular file or directory) and special (everything else). Special members
|
|
161
|
+
* are excluded from extraction rather than rejected; the archive is refused
|
|
162
|
+
* only when the two listings disagree in length or a special member's name
|
|
163
|
+
* cannot be passed back to the CLI verbatim (see {@link canExcludeVerbatim}).
|
|
164
|
+
*/
|
|
165
|
+
export function classifyArchiveListing(
|
|
166
|
+
cmd: 'unzip' | 'tar',
|
|
167
|
+
names: string,
|
|
168
|
+
typed: string,
|
|
169
|
+
): { plain: string[]; special: string[] } {
|
|
170
|
+
const nameLines = listingLines(names);
|
|
171
|
+
const typedLines = listingLines(typed);
|
|
172
|
+
if (nameLines.length !== typedLines.length) throw new AnthropicError(INCONSISTENT_LISTING);
|
|
173
|
+
const plain: string[] = [];
|
|
174
|
+
const special: string[] = [];
|
|
175
|
+
nameLines.forEach((name, i) => {
|
|
176
|
+
if (PLAIN_TYPE_CHARS[cmd].has(typedLines[i]!.charAt(0))) {
|
|
177
|
+
plain.push(name);
|
|
178
|
+
return;
|
|
140
179
|
}
|
|
180
|
+
if (!canExcludeVerbatim(cmd, name)) {
|
|
181
|
+
throw new AnthropicError(
|
|
182
|
+
`refusing to extract archive: cannot safely exclude member ${JSON.stringify(name)}`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
special.push(name);
|
|
186
|
+
});
|
|
187
|
+
return { plain, special };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Walk `dir` with `lstat` semantics and reject anything that is not a regular
|
|
192
|
+
* file or directory. Never follows a link and never descends into anything
|
|
193
|
+
* but a real directory.
|
|
194
|
+
*/
|
|
195
|
+
export async function assertOnlyPlainEntries(dir: string): Promise<void> {
|
|
196
|
+
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
|
|
197
|
+
if (entry.isDirectory()) await assertOnlyPlainEntries(path.join(dir, entry.name));
|
|
198
|
+
else if (!entry.isFile()) throw new AnthropicError(INCONSISTENT_LISTING);
|
|
141
199
|
}
|
|
142
200
|
}
|
|
143
201
|
|
|
@@ -152,7 +210,7 @@ async function runArchiveTool(cmd: 'unzip' | 'tar', args: string[]): Promise<str
|
|
|
152
210
|
const { stdout } = await execFileAsync(cmd, args);
|
|
153
211
|
return stdout;
|
|
154
212
|
} catch (e) {
|
|
155
|
-
if (
|
|
213
|
+
if (errnoCode(e) === 'ENOENT') {
|
|
156
214
|
throw new AnthropicError(
|
|
157
215
|
`skill extraction requires the \`${cmd}\` command, but it was not found on PATH`,
|
|
158
216
|
);
|
|
@@ -162,17 +220,17 @@ async function runArchiveTool(cmd: 'unzip' | 'tar', args: string[]): Promise<str
|
|
|
162
220
|
}
|
|
163
221
|
|
|
164
222
|
/**
|
|
165
|
-
* The single top-level directory shared by every entry in
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
223
|
+
* The single top-level directory shared by every entry in an archive listing,
|
|
224
|
+
* or `''` if entries don't all live under one common directory. Skill bundles
|
|
225
|
+
* are packaged wrapped in one directory named after the skill (e.g.
|
|
226
|
+
* `pdf/SKILL.md`, `pdf/scripts/...`); the extractor strips it so contents land
|
|
227
|
+
* directly in the skill's dir instead of a redundant nested `<skill>/<skill>/`
|
|
228
|
+
* level. A flat or multi-root archive yields `''`.
|
|
171
229
|
*/
|
|
172
|
-
function archiveTopDir(
|
|
230
|
+
function archiveTopDir(names: string[]): string {
|
|
173
231
|
let top: string | undefined;
|
|
174
232
|
let nested = false;
|
|
175
|
-
for (const raw of
|
|
233
|
+
for (const raw of names) {
|
|
176
234
|
// Drop `.` / empty segments so a `./pdf/...`-style listing (e.g. from
|
|
177
235
|
// `tar -C dir .`) is treated the same as `pdf/...`.
|
|
178
236
|
const parts = raw
|
|
@@ -195,9 +253,14 @@ function archiveTopDir(listing: string): string {
|
|
|
195
253
|
* to `unzip`/`tar` — consistent with the rest of the toolset, which already
|
|
196
254
|
* invokes `bash` and `rg`. Both `unzip` and `tar` must be available on `PATH`; a
|
|
197
255
|
* missing binary surfaces as a clear error (see {@link runArchiveTool}). Refuses
|
|
198
|
-
* any member that would escape `dest` (zip-slip / tar-slip)
|
|
199
|
-
*
|
|
200
|
-
*
|
|
256
|
+
* any member that would escape `dest` (zip-slip / tar-slip): skill archives
|
|
257
|
+
* come from the API, but skills can be third-party. Members that are not a
|
|
258
|
+
* regular file or directory (symlink, hardlink, device, fifo) are excluded
|
|
259
|
+
* from extraction rather than rejected; an archive whose special members
|
|
260
|
+
* cannot be excluded reliably is refused (see {@link classifyArchiveListing}).
|
|
261
|
+
* `tar` matches exclusions unanchored, so a plain member sharing a special
|
|
262
|
+
* member's name may be dropped too. The staging tree is verified to hold only
|
|
263
|
+
* regular files and directories before anything is promoted into `dest`.
|
|
201
264
|
*
|
|
202
265
|
* The skill bundle's single wrapper directory is stripped: the archive is
|
|
203
266
|
* extracted into a staging dir and the wrapper's contents are promoted into
|
|
@@ -215,6 +278,7 @@ export async function extractSkillArchive(resp: Response, dest: string): Promise
|
|
|
215
278
|
fssync.createWriteStream(tmp),
|
|
216
279
|
);
|
|
217
280
|
const stage = path.join(path.dirname(dest), `.skill-stage-${process.pid}-${Date.now()}`);
|
|
281
|
+
const excludeFile = path.join(path.dirname(dest), `.skill-exclude-${process.pid}-${Date.now()}`);
|
|
218
282
|
try {
|
|
219
283
|
// Sniff the first bytes: zip archives start with "PK\x03\x04"; treat
|
|
220
284
|
// anything else as a tar.* archive (`tar -xf` autodetects gzip/bzip2/xz).
|
|
@@ -224,25 +288,57 @@ export async function extractSkillArchive(resp: Response, dest: string): Promise
|
|
|
224
288
|
const archiveCmd = isZip ? 'unzip' : 'tar';
|
|
225
289
|
// List first, validate, then extract — `tar`/`unzip` will happily write a
|
|
226
290
|
// `../` member (or follow a symlink member) outside `-C`/`-d` otherwise.
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
291
|
+
const names = await runArchiveTool(archiveCmd, isZip ? ['-Z1', tmp] : ['-tf', tmp]);
|
|
292
|
+
const typed = await runArchiveTool(archiveCmd, isZip ? ['-Z', '--h', '--t', tmp] : ['-tvf', tmp]);
|
|
293
|
+
const { plain, special } = classifyArchiveListing(archiveCmd, names, typed);
|
|
294
|
+
assertSafeMemberNames([...plain, ...special]);
|
|
295
|
+
const top = archiveTopDir(plain);
|
|
231
296
|
await fs.mkdir(stage, { recursive: true, mode: DIR_CREATE_MODE });
|
|
232
|
-
|
|
297
|
+
// `unzip` exits non-zero when every member is excluded, so only run the
|
|
298
|
+
// extractor when there is something to extract.
|
|
299
|
+
if (plain.length > 0) {
|
|
300
|
+
await runArchiveTool(archiveCmd, await extractArgs(archiveCmd, tmp, stage, special, excludeFile));
|
|
301
|
+
}
|
|
302
|
+
await assertOnlyPlainEntries(stage);
|
|
233
303
|
// Promote the wrapper's contents (or the staged tree itself, if the
|
|
234
304
|
// archive wasn't wrapped) into the already-created empty `dest`. `stage`
|
|
235
305
|
// is a sibling of `dest`, so each rename stays on one filesystem.
|
|
236
306
|
const srcRoot = top ? path.join(stage, top) : stage;
|
|
237
|
-
|
|
307
|
+
const entries = await fs.readdir(srcRoot).catch((e: unknown) => {
|
|
308
|
+
throw errnoCode(e) === 'ENOENT' ? new AnthropicError(INCONSISTENT_LISTING) : e;
|
|
309
|
+
});
|
|
310
|
+
for (const entry of entries) {
|
|
238
311
|
await fs.rename(path.join(srcRoot, entry), path.join(dest, entry));
|
|
239
312
|
}
|
|
240
313
|
} finally {
|
|
241
314
|
await fs.rm(tmp, { force: true });
|
|
315
|
+
await fs.rm(excludeFile, { force: true });
|
|
242
316
|
await fs.rm(stage, { recursive: true, force: true });
|
|
243
317
|
}
|
|
244
318
|
}
|
|
245
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Arguments that extract `archive` into `stage` while excluding every member
|
|
322
|
+
* in `special`. Names are glob-escaped because both CLIs treat exclusions as
|
|
323
|
+
* patterns; `tar` reads them from `excludeFile`, `unzip` takes them after
|
|
324
|
+
* `-x`, which must follow `-d` so no pattern is parsed as an option.
|
|
325
|
+
*/
|
|
326
|
+
async function extractArgs(
|
|
327
|
+
cmd: 'unzip' | 'tar',
|
|
328
|
+
archive: string,
|
|
329
|
+
stage: string,
|
|
330
|
+
special: string[],
|
|
331
|
+
excludeFile: string,
|
|
332
|
+
): Promise<string[]> {
|
|
333
|
+
const patterns = special.map((name) => name.replace(/[*?[\\]/g, '\\$&'));
|
|
334
|
+
if (cmd === 'unzip') {
|
|
335
|
+
return ['-oq', archive, '-d', stage, ...(patterns.length > 0 ? ['-x', ...patterns] : [])];
|
|
336
|
+
}
|
|
337
|
+
if (patterns.length === 0) return ['-xf', archive, '-C', stage];
|
|
338
|
+
await fs.writeFile(excludeFile, patterns.join('\n') + '\n', { flag: 'wx', mode: 0o600 });
|
|
339
|
+
return ['-xf', archive, '-C', stage, '-X', excludeFile];
|
|
340
|
+
}
|
|
341
|
+
|
|
246
342
|
/** Read the first `n` bytes of `file`. */
|
|
247
343
|
async function readHead(file: string, n: number): Promise<Buffer> {
|
|
248
344
|
const handle = await fs.open(file, 'r');
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.117.1'; // x-release-please-version
|
|
@@ -8,12 +8,20 @@
|
|
|
8
8
|
export declare const DIR_CREATE_MODE = 493;
|
|
9
9
|
/** Mode for files the file tools create. */
|
|
10
10
|
export declare const FILE_CREATE_MODE = 420;
|
|
11
|
+
/** The `code` of a Node system error, or `undefined` for anything else. */
|
|
12
|
+
export declare function errnoCode(err: unknown): string | undefined;
|
|
11
13
|
/**
|
|
12
14
|
* Fully resolve `abs`: `realpath` the longest existing ancestor and re-append
|
|
13
15
|
* the rest, but never re-append a component that is itself a symlink — read the
|
|
14
16
|
* link and continue from its target instead. This handles paths being created
|
|
15
17
|
* (write/edit) without letting a symlink leaf (e.g. a dangling one pointing
|
|
16
18
|
* outside a confinement root) slip through unresolved.
|
|
19
|
+
*
|
|
20
|
+
* Returns a symlink-free path or throws an errno-carrying error (`ELOOP` for a
|
|
21
|
+
* cycle or more than {@link MAX_SYMLINK_HOPS} links, the `lstat`/`realpath`
|
|
22
|
+
* error for an unreadable component); it never returns `abs` unresolved. Only
|
|
23
|
+
* symlink hops count against the cap, so any depth of not-yet-existing
|
|
24
|
+
* directories still resolves.
|
|
17
25
|
*/
|
|
18
26
|
export declare function canonicalize(abs: string): Promise<string>;
|
|
19
27
|
/**
|
|
@@ -25,6 +33,9 @@ export declare function canonicalize(abs: string): Promise<string>;
|
|
|
25
33
|
* leaf, even a dangling one) is resolved before the confinement check, and the
|
|
26
34
|
* resolved path is what the caller then operates on, so a symlink inside `root`
|
|
27
35
|
* that points outside it can neither pass the check nor be followed afterwards.
|
|
36
|
+
* `..` is collapsed lexically before any symlink is followed. A path that cannot
|
|
37
|
+
* be resolved (symlink loop, unreadable component) is rejected with a
|
|
38
|
+
* `ToolError` naming `p`, never the host's absolute path.
|
|
28
39
|
*
|
|
29
40
|
* Residual TOCTOU: a component could still be swapped for a symlink between this
|
|
30
41
|
* call and the eventual `fs` operation. Closing that fully needs per-component
|
|
@@ -43,8 +54,9 @@ export declare function atomicWriteFile(targetPath: string, content: string): Pr
|
|
|
43
54
|
/**
|
|
44
55
|
* Map a thrown filesystem error to a consistent, language-independent message,
|
|
45
56
|
* so the model sees the same wording regardless of the runtime (Node's raw
|
|
46
|
-
* `ENOENT: no such file...` text would otherwise leak through).
|
|
47
|
-
*
|
|
57
|
+
* `ENOENT: no such file...` text would otherwise leak through). Codes we don't
|
|
58
|
+
* special-case render as the bare code, never Node's message, which embeds the
|
|
59
|
+
* host's absolute path.
|
|
48
60
|
*/
|
|
49
61
|
export declare function fsErrorMessage(err: unknown, file: string): string;
|
|
50
62
|
//# sourceMappingURL=fs-util.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs-util.d.mts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"fs-util.d.mts","sourceRoot":"","sources":["../../src/tools/agent-toolset/fs-util.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,uFAAuF;AACvF,eAAO,MAAM,eAAe,MAAQ,CAAC;AACrC,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AActC,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GAChC,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBxF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwBjE"}
|