@namzu/sdk 57.1.0 → 58.1.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 +72 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +2 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/file-definitions.d.ts +35 -4
- package/dist/agents/file-definitions.d.ts.map +1 -1
- package/dist/agents/file-definitions.js +98 -8
- package/dist/agents/file-definitions.js.map +1 -1
- package/dist/authorization/protected-paths.d.ts +9 -0
- package/dist/authorization/protected-paths.d.ts.map +1 -1
- package/dist/authorization/protected-paths.js +29 -3
- package/dist/authorization/protected-paths.js.map +1 -1
- package/dist/compaction/context-window.d.ts +1 -1
- package/dist/compaction/context-window.d.ts.map +1 -1
- package/dist/compaction/context-window.js +8 -1
- package/dist/compaction/context-window.js.map +1 -1
- package/dist/compaction/plan.d.ts.map +1 -1
- package/dist/compaction/plan.js +2 -2
- package/dist/compaction/plan.js.map +1 -1
- package/dist/config/runtime.d.ts +10 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +2 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/mcp/client.d.ts +8 -0
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +75 -2
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/constants/mcp/index.d.ts +2 -0
- package/dist/constants/mcp/index.d.ts.map +1 -1
- package/dist/constants/mcp/index.js +2 -0
- package/dist/constants/mcp/index.js.map +1 -1
- package/dist/constants/plugin/index.d.ts +2 -0
- package/dist/constants/plugin/index.d.ts.map +1 -1
- package/dist/constants/plugin/index.js +2 -0
- package/dist/constants/plugin/index.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +25 -4
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +2 -1
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +2 -2
- package/dist/persona/assembler.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/lifecycle.d.ts +8 -1
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +9 -2
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/shell-hook-http.d.ts +39 -0
- package/dist/plugin/shell-hook-http.d.ts.map +1 -0
- package/dist/plugin/shell-hook-http.js +163 -0
- package/dist/plugin/shell-hook-http.js.map +1 -0
- package/dist/plugin/shell-hook.d.ts +93 -11
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +223 -39
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/public-runtime.d.ts +2 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -0
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +2 -0
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/executor.d.ts +1 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +34 -9
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +2 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +25 -1
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +47 -10
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +18 -8
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.js +33 -9
- package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +7 -5
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +70 -3
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts +2 -0
- package/dist/runtime/query/path-escalation.d.ts.map +1 -1
- package/dist/runtime/query/path-escalation.js +7 -4
- package/dist/runtime/query/path-escalation.js.map +1 -1
- package/dist/runtime/query/plugin-hooks.js +2 -2
- package/dist/runtime/query/plugin-hooks.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts +2 -0
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +25 -0
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +3 -2
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +32 -13
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/session/retention/archive.js +1 -1
- package/dist/session/retention/archive.js.map +1 -1
- package/dist/session/workspace/driver.d.ts +6 -0
- package/dist/session/workspace/driver.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.js +28 -0
- package/dist/session/workspace/git-worktree.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/loader.d.ts +7 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +53 -0
- package/dist/skills/loader.js.map +1 -1
- package/dist/skills/registry.d.ts +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +2 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/tools/builtins/bash-cwd.d.ts +52 -0
- package/dist/tools/builtins/bash-cwd.d.ts.map +1 -0
- package/dist/tools/builtins/bash-cwd.js +127 -0
- package/dist/tools/builtins/bash-cwd.js.map +1 -0
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +41 -6
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/bounded-file-read.d.ts +10 -0
- package/dist/tools/builtins/bounded-file-read.d.ts.map +1 -0
- package/dist/tools/builtins/bounded-file-read.js +67 -0
- package/dist/tools/builtins/bounded-file-read.js.map +1 -0
- package/dist/tools/builtins/grep.d.ts +6 -1
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +187 -24
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts +1 -0
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +113 -28
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/read-media.d.ts +68 -0
- package/dist/tools/builtins/read-media.d.ts.map +1 -0
- package/dist/tools/builtins/read-media.js +397 -0
- package/dist/tools/builtins/read-media.js.map +1 -0
- package/dist/tools/builtins/search-tools.js +1 -1
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/builtins/skill.d.ts +4 -2
- package/dist/tools/builtins/skill.d.ts.map +1 -1
- package/dist/tools/builtins/skill.js +61 -2
- package/dist/tools/builtins/skill.js.map +1 -1
- package/dist/tools/builtins/sleep-guard.d.ts +15 -0
- package/dist/tools/builtins/sleep-guard.d.ts.map +1 -0
- package/dist/tools/builtins/sleep-guard.js +33 -0
- package/dist/tools/builtins/sleep-guard.js.map +1 -0
- package/dist/tools/builtins/view-image.d.ts +23 -1
- package/dist/tools/builtins/view-image.d.ts.map +1 -1
- package/dist/tools/builtins/view-image.js +66 -58
- package/dist/tools/builtins/view-image.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +31 -0
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/toolsets/manager.js +3 -3
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +6 -2
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/agent/task.js.map +1 -1
- package/dist/types/connector/mcp.d.ts +46 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +1 -1
- package/dist/types/plugin/index.d.ts +72 -5
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +12 -1
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/session/sub-session.d.ts +6 -2
- package/dist/types/session/sub-session.d.ts.map +1 -1
- package/dist/types/skills/index.d.ts +36 -0
- package/dist/types/skills/index.d.ts.map +1 -1
- package/dist/types/skills/index.js +9 -0
- package/dist/types/skills/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +22 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/workspace/ref.d.ts +2 -0
- package/dist/types/workspace/ref.d.ts.map +1 -1
- package/dist/utils/frontmatter.d.ts +12 -0
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +6 -0
- package/dist/utils/frontmatter.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +2 -0
- package/src/agents/file-definitions.ts +126 -6
- package/src/authorization/protected-paths.ts +30 -2
- package/src/compaction/context-window.ts +10 -0
- package/src/compaction/plan.ts +12 -2
- package/src/config/runtime.ts +2 -0
- package/src/connector/mcp/client.ts +87 -1
- package/src/constants/mcp/index.ts +3 -0
- package/src/constants/plugin/index.ts +2 -0
- package/src/manager/agent/lifecycle.ts +24 -5
- package/src/manager/session/turn-recorder.ts +2 -1
- package/src/persona/assembler.ts +2 -2
- package/src/plugin/index.ts +5 -0
- package/src/plugin/lifecycle.ts +16 -2
- package/src/plugin/shell-hook-http.ts +181 -0
- package/src/plugin/shell-hook.ts +386 -89
- package/src/public-runtime.ts +2 -0
- package/src/public-tools.ts +2 -0
- package/src/public-types.ts +5 -0
- package/src/runtime/query/executor.ts +40 -10
- package/src/runtime/query/index.ts +33 -0
- package/src/runtime/query/iteration/index.ts +51 -11
- package/src/runtime/query/iteration/phases/compaction-breaker.ts +38 -9
- package/src/runtime/query/iteration/phases/compaction.ts +27 -5
- package/src/runtime/query/iteration/phases/context.ts +2 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +74 -3
- package/src/runtime/query/path-escalation.ts +11 -5
- package/src/runtime/query/plugin-hooks.ts +2 -2
- package/src/runtime/query/prepare-turn.ts +33 -0
- package/src/runtime/query/review-policy.ts +45 -16
- package/src/session/retention/archive.ts +1 -1
- package/src/session/workspace/driver.ts +6 -0
- package/src/session/workspace/git-worktree.ts +31 -0
- package/src/skills/index.ts +6 -1
- package/src/skills/loader.ts +54 -0
- package/src/skills/registry.ts +7 -6
- package/src/tools/builtins/bash-cwd.ts +156 -0
- package/src/tools/builtins/bash.ts +79 -34
- package/src/tools/builtins/bounded-file-read.ts +80 -0
- package/src/tools/builtins/grep.ts +198 -28
- package/src/tools/builtins/read-file.ts +151 -30
- package/src/tools/builtins/read-media.ts +483 -0
- package/src/tools/builtins/search-tools.ts +1 -1
- package/src/tools/builtins/skill.ts +91 -2
- package/src/tools/builtins/sleep-guard.ts +31 -0
- package/src/tools/builtins/view-image.ts +74 -62
- package/src/tools/file-read-tracker.ts +30 -0
- package/src/toolsets/manager.ts +3 -3
- package/src/types/agent/query.ts +2 -0
- package/src/types/agent/task.ts +6 -2
- package/src/types/connector/mcp.ts +48 -0
- package/src/types/hitl/index.ts +1 -1
- package/src/types/plugin/index.ts +102 -16
- package/src/types/session/sub-session.ts +6 -2
- package/src/types/skills/index.ts +48 -0
- package/src/types/tool/index.ts +22 -0
- package/src/types/workspace/ref.ts +2 -0
- package/src/utils/frontmatter.ts +21 -0
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stat } from 'node:fs/promises'
|
|
2
2
|
import { extname } from 'node:path'
|
|
3
3
|
import { z } from 'zod'
|
|
4
|
-
import type { ToolContext } from '../../types/tool/index.js'
|
|
4
|
+
import type { ToolContext, ToolResult } from '../../types/tool/index.js'
|
|
5
5
|
import type { ToolFacts } from '../../types/tool/presentation.js'
|
|
6
6
|
import { defineTool } from '../defineTool.js'
|
|
7
7
|
import { resolveWithinAnyReal, toolRoots } from '../paths.js'
|
|
8
8
|
import { resolveRetainedOutput } from '../retained-output.js'
|
|
9
9
|
import { looksBinary } from '../text-window.js'
|
|
10
|
+
import { TEXT_MAX_INPUT_BYTES, readBoundedChunks, readBoundedFile } from './bounded-file-read.js'
|
|
10
11
|
import { fingerprintContent } from './content-fingerprint.js'
|
|
12
|
+
import {
|
|
13
|
+
type MediaRead,
|
|
14
|
+
NOTEBOOK_MAX_BYTES,
|
|
15
|
+
PDF_MAX_BYTES,
|
|
16
|
+
readNotebook,
|
|
17
|
+
readPdfPages,
|
|
18
|
+
} from './read-media.js'
|
|
11
19
|
import { type RenderedRead, humanBytes, renderCappedRead } from './read-render.js'
|
|
20
|
+
import { MAX_BYTES as IMAGE_MAX_BYTES, inspectImageBytes } from './view-image.js'
|
|
12
21
|
|
|
13
22
|
const inputSchema = z.object({
|
|
14
23
|
path: z.string().describe('Path to the file to read (absolute or relative)'),
|
|
@@ -35,6 +44,12 @@ const inputSchema = z.object({
|
|
|
35
44
|
.optional()
|
|
36
45
|
.describe('Starting line number (0-indexed). Defaults to 0 (beginning of file).'),
|
|
37
46
|
limit: z.coerce.number().optional().describe('Maximum number of lines to read'),
|
|
47
|
+
pages: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe(
|
|
51
|
+
'For a PDF: the pages to render as images, one page ("3") or one range ("1-5"), at most 20 pages per request. Required for a PDF of more than 10 pages.',
|
|
52
|
+
),
|
|
38
53
|
allow_large: z
|
|
39
54
|
.boolean()
|
|
40
55
|
.optional()
|
|
@@ -46,7 +61,7 @@ const inputSchema = z.object({
|
|
|
46
61
|
export const ReadFileTool = defineTool({
|
|
47
62
|
name: 'read',
|
|
48
63
|
description:
|
|
49
|
-
'Reads a file and returns its contents with line numbers. Supports readRange ([start,end], 1-indexed inclusive) or offset/limit for large files. Without a window it returns the first 2000 lines and says so — pass offset/limit to continue. A file over 256 KiB is refused unless a window is named, and a result over about 25,000 tokens is cut to a first page that names the next call; pass allow_large only when you need all of it.',
|
|
64
|
+
'Reads a file and returns its contents with line numbers. Also reads images (PNG, JPEG, WebP), which come back as pictures; PDFs, whose pages come back as images (pass pages, for example "1-5", at most 20 pages per request; a PDF of more than 10 pages needs it); and Jupyter notebooks (.ipynb), which come back as cells with their outputs. Supports readRange ([start,end], 1-indexed inclusive) or offset/limit for large files. Without a window it returns the first 2000 lines and says so — pass offset/limit to continue. A file over 256 KiB is refused unless a window is named, and a result over about 25,000 tokens is cut to a first page that names the next call; pass allow_large only when you need all of it.',
|
|
50
65
|
inputSchema,
|
|
51
66
|
category: 'filesystem',
|
|
52
67
|
// Declared so a path outside the turn's roots can be reviewed before the
|
|
@@ -78,8 +93,17 @@ export const ReadFileTool = defineTool({
|
|
|
78
93
|
const retained = await resolveRetainedOutput(context.retainedOutputDirectory, input.path)
|
|
79
94
|
// Sandbox-aware: route through sandbox.readFile() when available
|
|
80
95
|
if (context.sandbox && retained === undefined) {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
96
|
+
const sandbox = context.sandbox
|
|
97
|
+
const buffer = await readBoundedChunks(
|
|
98
|
+
(offset, length) =>
|
|
99
|
+
sandbox.readFile(input.path, { offset, length, signal: context.abortSignal }),
|
|
100
|
+
PDF_MAX_BYTES,
|
|
101
|
+
context.abortSignal,
|
|
102
|
+
(prefix) => inputLimit(input.path, prefix),
|
|
103
|
+
)
|
|
104
|
+
const media = await readMediaFile(buffer, input.path, input, context, true)
|
|
105
|
+
if (media) return media
|
|
106
|
+
const binaryGuidance = describeStructuredBinaryRead(input.path)
|
|
83
107
|
if (binaryGuidance) {
|
|
84
108
|
return {
|
|
85
109
|
success: false,
|
|
@@ -121,15 +145,22 @@ export const ReadFileTool = defineTool({
|
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
const filePath = retained ?? (await resolveWithinAnyReal(toolRoots(context), input.path))
|
|
148
|
+
// Pictures, PDFs and notebooks are not text: they have their own size
|
|
149
|
+
// limits and are never numbered, so the text cap below does not apply.
|
|
150
|
+
const namesMediaFile = namesMedia(filePath)
|
|
124
151
|
// Refused on the size alone, before the file is in memory, when no window
|
|
125
152
|
// was named: the cap exists so a huge file is never loaded to be refused.
|
|
126
|
-
if (input.readRange === undefined && input.limit === undefined) {
|
|
153
|
+
if (!namesMediaFile && input.readRange === undefined && input.limit === undefined) {
|
|
127
154
|
const { size } = await stat(filePath)
|
|
128
155
|
const early = renderCappedRead('', size, input, capOptions(input, context))
|
|
129
156
|
if (!early.ok) return refusedRead(filePath, size, early.message, false)
|
|
130
157
|
}
|
|
131
|
-
const buffer = await
|
|
132
|
-
|
|
158
|
+
const buffer = await readBoundedFile(filePath, PDF_MAX_BYTES, context.abortSignal, (prefix) =>
|
|
159
|
+
inputLimit(filePath, prefix),
|
|
160
|
+
)
|
|
161
|
+
const media = await readMediaFile(buffer, filePath, input, context, false)
|
|
162
|
+
if (media) return media
|
|
163
|
+
const binaryGuidance = describeStructuredBinaryRead(filePath)
|
|
133
164
|
if (binaryGuidance) {
|
|
134
165
|
return {
|
|
135
166
|
success: false,
|
|
@@ -172,6 +203,117 @@ export const ReadFileTool = defineTool({
|
|
|
172
203
|
},
|
|
173
204
|
})
|
|
174
205
|
|
|
206
|
+
type MediaKind = 'image' | 'pdf' | 'notebook'
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* What a file is by its bytes, never by its name: a text file called `.png` is
|
|
210
|
+
* still text. A notebook is JSON, so only its extension can name it.
|
|
211
|
+
*/
|
|
212
|
+
function mediaKindOf(path: string, buffer: Buffer): MediaKind | undefined {
|
|
213
|
+
if (buffer.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))) return 'image'
|
|
214
|
+
if (buffer[0] === 0xff && buffer[1] === 0xd8) return 'image'
|
|
215
|
+
if (buffer.toString('ascii', 0, 4) === 'RIFF' && buffer.toString('ascii', 8, 12) === 'WEBP')
|
|
216
|
+
return 'image'
|
|
217
|
+
if (buffer.subarray(0, 4).toString('utf8') === '%PDF') return 'pdf'
|
|
218
|
+
if (extname(path).toLowerCase() === '.ipynb') return 'notebook'
|
|
219
|
+
return undefined
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** A name that suggests a file the text cap should not judge. */
|
|
223
|
+
function namesMedia(path: string): boolean {
|
|
224
|
+
return ['.png', '.jpg', '.jpeg', '.webp', '.pdf', '.ipynb'].includes(extname(path).toLowerCase())
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function inputLimit(path: string, prefix: Buffer): number {
|
|
228
|
+
const kind = mediaKindOf(path, prefix)
|
|
229
|
+
return kind ? mediaLimit(kind) : TEXT_MAX_INPUT_BYTES
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function mediaLimit(kind: MediaKind): number {
|
|
233
|
+
return kind === 'image' ? IMAGE_MAX_BYTES : kind === 'pdf' ? PDF_MAX_BYTES : NOTEBOOK_MAX_BYTES
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function mediaFailure(path: string, bytes: number, message: string, sandboxed: boolean) {
|
|
237
|
+
return {
|
|
238
|
+
success: false as const,
|
|
239
|
+
output: message,
|
|
240
|
+
error: message,
|
|
241
|
+
data: { path, ...(sandboxed ? { sandboxed: true } : {}) },
|
|
242
|
+
facts: { kind: 'read' as const, path, bytes },
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function tooLargeMedia(path: string, bytes: number, kind: MediaKind) {
|
|
247
|
+
const what = kind === 'image' ? 'Image' : kind === 'pdf' ? 'PDF' : 'Notebook'
|
|
248
|
+
return mediaFailure(
|
|
249
|
+
path,
|
|
250
|
+
bytes,
|
|
251
|
+
`${what} file (${humanBytes(bytes)}) exceeds the maximum size this tool can read (${humanBytes(mediaLimit(kind))}).`,
|
|
252
|
+
false,
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* An image, a PDF or a notebook, as what the model can use: the picture itself,
|
|
258
|
+
* the pages of the PDF as pictures, or the notebook's cells with their output.
|
|
259
|
+
* Undefined for any other file, which is read as text.
|
|
260
|
+
*/
|
|
261
|
+
async function readMediaFile(
|
|
262
|
+
buffer: Buffer,
|
|
263
|
+
path: string,
|
|
264
|
+
input: { readonly pages?: string | undefined },
|
|
265
|
+
context: ToolContext,
|
|
266
|
+
sandboxed: boolean,
|
|
267
|
+
): Promise<ToolResult | undefined> {
|
|
268
|
+
const kind = mediaKindOf(path, buffer)
|
|
269
|
+
if (!kind) return undefined
|
|
270
|
+
if (buffer.length > mediaLimit(kind)) return tooLargeMedia(path, buffer.length, kind)
|
|
271
|
+
if (kind === 'image') {
|
|
272
|
+
try {
|
|
273
|
+
const result = await inspectImageBytes(
|
|
274
|
+
{ path, bytes: buffer, sandboxed },
|
|
275
|
+
context.abortSignal,
|
|
276
|
+
)
|
|
277
|
+
return {
|
|
278
|
+
...result,
|
|
279
|
+
facts: { kind: 'read' as const, path, bytes: buffer.length },
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
return mediaFailure(
|
|
283
|
+
path,
|
|
284
|
+
buffer.length,
|
|
285
|
+
error instanceof Error ? error.message : String(error),
|
|
286
|
+
sandboxed,
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const media: MediaRead =
|
|
291
|
+
kind === 'pdf'
|
|
292
|
+
? await readPdfPages(buffer, input.pages, path, context.abortSignal)
|
|
293
|
+
: readNotebook(buffer, path)
|
|
294
|
+
if (!media.ok) return mediaFailure(path, buffer.length, media.message, sandboxed)
|
|
295
|
+
const text = media.parts
|
|
296
|
+
.filter((part) => part.type === 'text')
|
|
297
|
+
.map((part) => part.text)
|
|
298
|
+
.join('\n')
|
|
299
|
+
const hasImage = media.parts.some((part) => part.type === 'image')
|
|
300
|
+
return {
|
|
301
|
+
success: true as const,
|
|
302
|
+
output: text,
|
|
303
|
+
...(hasImage
|
|
304
|
+
? {
|
|
305
|
+
content: media.parts.map((part) =>
|
|
306
|
+
part.type === 'text'
|
|
307
|
+
? { type: 'text' as const, text: part.text }
|
|
308
|
+
: { type: 'image' as const, mediaType: part.mediaType, data: part.data },
|
|
309
|
+
),
|
|
310
|
+
}
|
|
311
|
+
: {}),
|
|
312
|
+
data: { path, ...(sandboxed ? { sandboxed: true } : {}), ...media.facts },
|
|
313
|
+
facts: { kind: 'read' as const, path, bytes: buffer.length },
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
175
317
|
function capOptions(
|
|
176
318
|
input: { path: string; allow_large?: boolean | undefined },
|
|
177
319
|
context: ToolContext,
|
|
@@ -315,35 +457,14 @@ function unchangedRead(
|
|
|
315
457
|
}
|
|
316
458
|
}
|
|
317
459
|
|
|
318
|
-
function describeStructuredBinaryRead(path: string
|
|
460
|
+
function describeStructuredBinaryRead(path: string): string | null {
|
|
319
461
|
const ext = extname(path).toLowerCase()
|
|
320
|
-
const image = buffer.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))
|
|
321
|
-
? 'PNG'
|
|
322
|
-
: buffer[0] === 0xff && buffer[1] === 0xd8
|
|
323
|
-
? 'JPEG'
|
|
324
|
-
: buffer.toString('ascii', 0, 4) === 'RIFF' && buffer.toString('ascii', 8, 12) === 'WEBP'
|
|
325
|
-
? 'WebP'
|
|
326
|
-
: null
|
|
327
|
-
if (image) {
|
|
328
|
-
return [
|
|
329
|
-
`The file "${path}" is a ${image} raster image, not UTF-8 text.`,
|
|
330
|
-
'Use an available view_image tool with this path to inspect the actual pixels. Reading binary bytes as text does not provide visual evidence.',
|
|
331
|
-
'If image inspection is unavailable, open the file in an application and use an available screenshot tool; report the limitation if neither can show the image.',
|
|
332
|
-
].join('\n')
|
|
333
|
-
}
|
|
334
462
|
if (ext === '.docx') return buildStructuredBinaryGuidance(path, 'DOCX', 'python-docx')
|
|
335
463
|
if (ext === '.pptx') return buildStructuredBinaryGuidance(path, 'PPTX', 'python-pptx')
|
|
336
464
|
if (ext === '.xlsx') return buildStructuredBinaryGuidance(path, 'XLSX', 'openpyxl')
|
|
337
|
-
if (ext === '.pdf' || startsWithPdfHeader(buffer)) {
|
|
338
|
-
return buildStructuredBinaryGuidance(path, 'PDF', 'pdftotext or PyMuPDF')
|
|
339
|
-
}
|
|
340
465
|
return null
|
|
341
466
|
}
|
|
342
467
|
|
|
343
|
-
function startsWithPdfHeader(buffer: Buffer): boolean {
|
|
344
|
-
return buffer.length >= 4 && buffer.subarray(0, 4).toString('utf8') === '%PDF'
|
|
345
|
-
}
|
|
346
|
-
|
|
347
468
|
function buildStructuredBinaryGuidance(path: string, format: string, extractor: string): string {
|
|
348
469
|
return [
|
|
349
470
|
`The file "${path}" is a ${format} document package, not UTF-8 text.`,
|