@zocomputer/agent-sdk 0.5.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/LICENSE +21 -0
- package/README.md +673 -0
- package/dist/attachments.js +52 -0
- package/dist/gateway-fetch.js +67 -0
- package/dist/index.js +4169 -0
- package/dist/initiator-auth.js +49 -0
- package/dist/platform/agent-sandbox/index.js +691 -0
- package/dist/platform/cloud-tools/image.js +498 -0
- package/dist/platform/cloud-tools/index.js +507 -0
- package/dist/platform/cloud-tools/web-search.js +87 -0
- package/dist/platform/runtime-ai/gateway.js +87 -0
- package/dist/platform/runtime-ai/index.js +91 -0
- package/dist/platform/runtime-ai/register.js +88 -0
- package/dist/platform/runtime-ai/session-fetch.js +54 -0
- package/dist/platform/runtime-auth/index.js +141 -0
- package/dist/state-files.js +287 -0
- package/dist/state-sandbox.js +383 -0
- package/dist/state.js +29 -0
- package/dist/steer-inbox.js +84 -0
- package/dist/steer.js +83 -0
- package/package.json +143 -0
- package/platform/agent-sandbox/api-client.ts +196 -0
- package/platform/agent-sandbox/index.ts +27 -0
- package/platform/agent-sandbox/pure.ts +83 -0
- package/platform/agent-sandbox/sftp.ts +141 -0
- package/platform/agent-sandbox/ssh-connection.ts +165 -0
- package/platform/agent-sandbox/ssh-exec.ts +98 -0
- package/platform/agent-sandbox/ssh-session.ts +487 -0
- package/platform/agent-sandbox/zo-backend.ts +88 -0
- package/platform/agent-sandbox/zo-sandbox.ts +39 -0
- package/platform/cloud-tools/image-path.ts +44 -0
- package/platform/cloud-tools/image.ts +225 -0
- package/platform/cloud-tools/index.ts +22 -0
- package/platform/cloud-tools/state-files.ts +368 -0
- package/platform/cloud-tools/tool-meta.ts +32 -0
- package/platform/cloud-tools/web-search.ts +15 -0
- package/platform/runtime-ai/gateway.ts +76 -0
- package/platform/runtime-ai/index.ts +20 -0
- package/platform/runtime-ai/register.ts +26 -0
- package/platform/runtime-ai/session-fetch.ts +124 -0
- package/platform/runtime-auth/index.ts +331 -0
- package/src/async-tasks.ts +273 -0
- package/src/attachments.ts +109 -0
- package/src/backgroundable.ts +88 -0
- package/src/bounded-output.ts +159 -0
- package/src/dir-conventions.ts +238 -0
- package/src/extract/cache.ts +40 -0
- package/src/extract/docx.ts +18 -0
- package/src/extract/pdf.ts +54 -0
- package/src/extract/sheet.ts +56 -0
- package/src/file-kind.ts +258 -0
- package/src/file-view.ts +80 -0
- package/src/gateway-fetch.ts +115 -0
- package/src/glob-match.ts +13 -0
- package/src/hooks.ts +213 -0
- package/src/index.ts +419 -0
- package/src/initiator-auth.ts +81 -0
- package/src/instructions.ts +224 -0
- package/src/list-files.ts +41 -0
- package/src/mock-model.ts +572 -0
- package/src/park-delivery.ts +247 -0
- package/src/path-locks.ts +52 -0
- package/src/read-file-content.ts +142 -0
- package/src/read-text.ts +40 -0
- package/src/redeliver.ts +155 -0
- package/src/run.ts +159 -0
- package/src/sandbox-io.ts +414 -0
- package/src/state-files.ts +460 -0
- package/src/state-sandbox.ts +710 -0
- package/src/state.ts +96 -0
- package/src/steer-inbox.ts +105 -0
- package/src/steer-tool.ts +83 -0
- package/src/steer.ts +146 -0
- package/src/task.ts +320 -0
- package/src/tools/bash.ts +143 -0
- package/src/tools/edit.ts +58 -0
- package/src/tools/glob.ts +56 -0
- package/src/tools/grep.ts +188 -0
- package/src/tools/read.ts +319 -0
- package/src/tools/tasks.ts +241 -0
- package/src/tools/webfetch.ts +368 -0
- package/src/tools/write.ts +42 -0
- package/src/walk.ts +112 -0
- package/src/watch-output.ts +104 -0
- package/src/web-fetch.ts +324 -0
- package/src/web-page.ts +179 -0
- package/src/workspace-io.ts +225 -0
- package/src/workspace.ts +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4169 @@
|
|
|
1
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/index.ts
|
|
2
|
+
import { join as join8 } from "node:path";
|
|
3
|
+
|
|
4
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/attachments.ts
|
|
5
|
+
var CHAT_ATTACHMENT_FIELD = "chatAttachment";
|
|
6
|
+
var DEFAULT_MAX_INLINE_IMAGE_BYTES = 3 * 1024 * 1024;
|
|
7
|
+
var DEFAULT_MAX_INLINE_MEDIA_BYTES = 10 * 1024 * 1024;
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function readChatAttachment(toolOutput) {
|
|
12
|
+
if (!isRecord(toolOutput))
|
|
13
|
+
return null;
|
|
14
|
+
const raw = toolOutput[CHAT_ATTACHMENT_FIELD];
|
|
15
|
+
if (!isRecord(raw))
|
|
16
|
+
return null;
|
|
17
|
+
if (typeof raw.dataUrl !== "string" || raw.dataUrl.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
if (typeof raw.mediaType !== "string" || raw.mediaType.length === 0)
|
|
20
|
+
return null;
|
|
21
|
+
const base = {
|
|
22
|
+
dataUrl: raw.dataUrl,
|
|
23
|
+
mediaType: raw.mediaType
|
|
24
|
+
};
|
|
25
|
+
switch (raw.kind) {
|
|
26
|
+
case "image":
|
|
27
|
+
return {
|
|
28
|
+
kind: "image",
|
|
29
|
+
...base,
|
|
30
|
+
filename: typeof raw.filename === "string" ? raw.filename : "image",
|
|
31
|
+
width: typeof raw.width === "number" ? raw.width : null,
|
|
32
|
+
height: typeof raw.height === "number" ? raw.height : null
|
|
33
|
+
};
|
|
34
|
+
case "video":
|
|
35
|
+
return {
|
|
36
|
+
kind: "video",
|
|
37
|
+
...base,
|
|
38
|
+
filename: typeof raw.filename === "string" ? raw.filename : "video"
|
|
39
|
+
};
|
|
40
|
+
case "audio":
|
|
41
|
+
return {
|
|
42
|
+
kind: "audio",
|
|
43
|
+
...base,
|
|
44
|
+
filename: typeof raw.filename === "string" ? raw.filename : "audio"
|
|
45
|
+
};
|
|
46
|
+
default:
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/async-tasks.ts
|
|
52
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
53
|
+
import { dirname } from "node:path";
|
|
54
|
+
function isRecord2(value) {
|
|
55
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
56
|
+
}
|
|
57
|
+
function isTask(value) {
|
|
58
|
+
if (!isRecord2(value))
|
|
59
|
+
return false;
|
|
60
|
+
if (typeof value.id !== "string" || typeof value.tool !== "string" || typeof value.label !== "string" || typeof value.startedAt !== "number" || typeof value.status !== "string") {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
switch (value.status) {
|
|
64
|
+
case "running":
|
|
65
|
+
return true;
|
|
66
|
+
case "done":
|
|
67
|
+
return typeof value.finishedAt === "number" && "result" in value;
|
|
68
|
+
case "error":
|
|
69
|
+
case "lost":
|
|
70
|
+
return typeof value.finishedAt === "number" && typeof value.error === "string";
|
|
71
|
+
default:
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
var MAX_TASKS = 100;
|
|
76
|
+
var REGISTRY_CACHE_KEY = Symbol.for("zocomputer.agent-sdk.task-registries");
|
|
77
|
+
function registryCache() {
|
|
78
|
+
const holder = globalThis;
|
|
79
|
+
holder[REGISTRY_CACHE_KEY] ??= new Map;
|
|
80
|
+
return holder[REGISTRY_CACHE_KEY];
|
|
81
|
+
}
|
|
82
|
+
function __resetTaskRegistryCacheForTests() {
|
|
83
|
+
registryCache().clear();
|
|
84
|
+
}
|
|
85
|
+
var STORE_POLL_MS = 500;
|
|
86
|
+
function createTaskRegistry(opts) {
|
|
87
|
+
const cache = registryCache();
|
|
88
|
+
const cached = cache.get(opts.storePath);
|
|
89
|
+
if (cached)
|
|
90
|
+
return cached;
|
|
91
|
+
const registry = buildTaskRegistry(opts);
|
|
92
|
+
cache.set(opts.storePath, registry);
|
|
93
|
+
return registry;
|
|
94
|
+
}
|
|
95
|
+
function buildTaskRegistry(opts) {
|
|
96
|
+
const { storePath } = opts;
|
|
97
|
+
const tasks = new Map;
|
|
98
|
+
const pending = new Map;
|
|
99
|
+
let counter = 0;
|
|
100
|
+
function listTasks() {
|
|
101
|
+
return [...tasks.values()].sort((a, b) => a.startedAt - b.startedAt);
|
|
102
|
+
}
|
|
103
|
+
function persist() {
|
|
104
|
+
mkdirSync(dirname(storePath), { recursive: true });
|
|
105
|
+
writeFileSync(storePath, JSON.stringify({ tasks: listTasks() }, null, 2), "utf8");
|
|
106
|
+
}
|
|
107
|
+
function readStoreTasks() {
|
|
108
|
+
if (!existsSync(storePath))
|
|
109
|
+
return [];
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(readFileSync(storePath, "utf8"));
|
|
112
|
+
if (!isRecord2(parsed) || !Array.isArray(parsed.tasks))
|
|
113
|
+
return [];
|
|
114
|
+
return parsed.tasks.filter(isTask);
|
|
115
|
+
} catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function loadPersisted() {
|
|
120
|
+
for (const saved of readStoreTasks()) {
|
|
121
|
+
const task = saved.status === "running" ? {
|
|
122
|
+
...saved,
|
|
123
|
+
status: "lost",
|
|
124
|
+
finishedAt: Date.now(),
|
|
125
|
+
error: "The agent restarted before this background task finished."
|
|
126
|
+
} : saved;
|
|
127
|
+
tasks.set(task.id, task);
|
|
128
|
+
const match = task.id.match(/^task_(\d+)$/);
|
|
129
|
+
const n = match ? Number(match[1]) : 0;
|
|
130
|
+
if (Number.isFinite(n))
|
|
131
|
+
counter = Math.max(counter, n);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
loadPersisted();
|
|
135
|
+
function storeTask(id) {
|
|
136
|
+
return readStoreTasks().find((task) => task.id === id);
|
|
137
|
+
}
|
|
138
|
+
function prune() {
|
|
139
|
+
if (tasks.size <= MAX_TASKS)
|
|
140
|
+
return;
|
|
141
|
+
const settled = [...tasks.values()].filter((t) => t.status !== "running").sort((a, b) => a.startedAt - b.startedAt);
|
|
142
|
+
for (const t of settled) {
|
|
143
|
+
if (tasks.size <= MAX_TASKS)
|
|
144
|
+
break;
|
|
145
|
+
tasks.delete(t.id);
|
|
146
|
+
pending.delete(t.id);
|
|
147
|
+
}
|
|
148
|
+
persist();
|
|
149
|
+
}
|
|
150
|
+
function spawnTask(tool, label, work) {
|
|
151
|
+
const id = `task_${++counter}`;
|
|
152
|
+
const startedAt = Date.now();
|
|
153
|
+
tasks.set(id, { id, tool, label, startedAt, status: "running" });
|
|
154
|
+
pending.set(id, work);
|
|
155
|
+
work.then((result) => {
|
|
156
|
+
tasks.set(id, {
|
|
157
|
+
id,
|
|
158
|
+
tool,
|
|
159
|
+
label,
|
|
160
|
+
startedAt,
|
|
161
|
+
status: "done",
|
|
162
|
+
finishedAt: Date.now(),
|
|
163
|
+
result
|
|
164
|
+
});
|
|
165
|
+
pending.delete(id);
|
|
166
|
+
persist();
|
|
167
|
+
}, (err) => {
|
|
168
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
169
|
+
tasks.set(id, {
|
|
170
|
+
id,
|
|
171
|
+
tool,
|
|
172
|
+
label,
|
|
173
|
+
startedAt,
|
|
174
|
+
status: "error",
|
|
175
|
+
finishedAt: Date.now(),
|
|
176
|
+
error
|
|
177
|
+
});
|
|
178
|
+
pending.delete(id);
|
|
179
|
+
persist();
|
|
180
|
+
});
|
|
181
|
+
prune();
|
|
182
|
+
persist();
|
|
183
|
+
return id;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
spawnTask,
|
|
187
|
+
updateTaskProgress(id, progress) {
|
|
188
|
+
const task = tasks.get(id);
|
|
189
|
+
if (!task || task.status !== "running")
|
|
190
|
+
return;
|
|
191
|
+
tasks.set(id, { ...task, progress });
|
|
192
|
+
},
|
|
193
|
+
listTasks,
|
|
194
|
+
getTask(id) {
|
|
195
|
+
return tasks.get(id) ?? storeTask(id);
|
|
196
|
+
},
|
|
197
|
+
async awaitTask(id, waitMs) {
|
|
198
|
+
const current = tasks.get(id);
|
|
199
|
+
if (current) {
|
|
200
|
+
if (current.status !== "running")
|
|
201
|
+
return current;
|
|
202
|
+
const work = pending.get(id);
|
|
203
|
+
if (work) {
|
|
204
|
+
await Promise.race([
|
|
205
|
+
work.then(() => {
|
|
206
|
+
return;
|
|
207
|
+
}, () => {
|
|
208
|
+
return;
|
|
209
|
+
}),
|
|
210
|
+
new Promise((resolve) => setTimeout(resolve, waitMs))
|
|
211
|
+
]);
|
|
212
|
+
}
|
|
213
|
+
return tasks.get(id);
|
|
214
|
+
}
|
|
215
|
+
const deadline = Date.now() + waitMs;
|
|
216
|
+
for (;; ) {
|
|
217
|
+
const saved = storeTask(id);
|
|
218
|
+
if (!saved || saved.status !== "running")
|
|
219
|
+
return saved;
|
|
220
|
+
const remaining = deadline - Date.now();
|
|
221
|
+
if (remaining <= 0)
|
|
222
|
+
return saved;
|
|
223
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(STORE_POLL_MS, remaining)));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/backgroundable.ts
|
|
230
|
+
import { z } from "zod";
|
|
231
|
+
function defineOp(cfg) {
|
|
232
|
+
return {
|
|
233
|
+
name: cfg.name,
|
|
234
|
+
description: cfg.description,
|
|
235
|
+
inputJsonSchema: z.toJSONSchema(cfg.inputSchema),
|
|
236
|
+
start(rawInput, extras) {
|
|
237
|
+
const parsed = cfg.inputSchema.safeParse(rawInput);
|
|
238
|
+
if (!parsed.success) {
|
|
239
|
+
throw new Error(`Invalid input for "${cfg.name}": ${parsed.error.message}`);
|
|
240
|
+
}
|
|
241
|
+
const started = cfg.run(parsed.data, extras);
|
|
242
|
+
if (started instanceof Promise)
|
|
243
|
+
return { label: cfg.label(parsed.data), work: started };
|
|
244
|
+
return { label: cfg.label(parsed.data), ...started };
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function truncate(s, max = 80) {
|
|
249
|
+
const oneLine = s.replace(/\s+/g, " ").trim();
|
|
250
|
+
return oneLine.length > max ? `${oneLine.slice(0, max)}…` : oneLine;
|
|
251
|
+
}
|
|
252
|
+
function createBashOp(runner) {
|
|
253
|
+
return defineOp({
|
|
254
|
+
name: "bash",
|
|
255
|
+
description: "Run a shell command in the background (git, bun, tests, builds, installs, dev servers). Same as the bash tool, but non-blocking.",
|
|
256
|
+
inputSchema: z.object({
|
|
257
|
+
command: z.string().min(1),
|
|
258
|
+
cwd: z.string().optional(),
|
|
259
|
+
timeout_ms: z.number().int().positive().optional()
|
|
260
|
+
}),
|
|
261
|
+
label: ({ command }) => truncate(command),
|
|
262
|
+
run: ({ command, cwd, timeout_ms }, extras) => {
|
|
263
|
+
const running = runner.startCommand(command, {
|
|
264
|
+
cwd,
|
|
265
|
+
timeoutMs: timeout_ms ?? 600000,
|
|
266
|
+
onOutput: extras?.onOutput
|
|
267
|
+
});
|
|
268
|
+
return { work: running.result, progress: running.progress };
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/bounded-output.ts
|
|
274
|
+
import { appendFileSync, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
275
|
+
import { dirname as dirname2 } from "node:path";
|
|
276
|
+
var HEAD_CHARS = 25000;
|
|
277
|
+
var TAIL_CHARS = 25000;
|
|
278
|
+
var TOOL_OUTPUT_DIRNAME = "tool-outputs";
|
|
279
|
+
var isHighSurrogate = (code) => code >= 55296 && code <= 56319;
|
|
280
|
+
var isLowSurrogate = (code) => code >= 56320 && code <= 57343;
|
|
281
|
+
var endsOnHighSurrogate = (text) => text.length > 0 && isHighSurrogate(text.charCodeAt(text.length - 1));
|
|
282
|
+
function takeTail(text, cap) {
|
|
283
|
+
if (text.length <= cap)
|
|
284
|
+
return text;
|
|
285
|
+
let start = text.length - cap;
|
|
286
|
+
if (isLowSurrogate(text.charCodeAt(start)))
|
|
287
|
+
start += 1;
|
|
288
|
+
return text.slice(start);
|
|
289
|
+
}
|
|
290
|
+
function createBoundedCapture(opts = {}) {
|
|
291
|
+
const headCap = opts.headChars ?? HEAD_CHARS;
|
|
292
|
+
const tailCap = opts.tailChars ?? TAIL_CHARS;
|
|
293
|
+
let head = "";
|
|
294
|
+
let tail = "";
|
|
295
|
+
let total = 0;
|
|
296
|
+
let overflowed = false;
|
|
297
|
+
let spill = opts.spillPath ? "none" : "failed";
|
|
298
|
+
let spillCarry = "";
|
|
299
|
+
const writeSpill = (chunk, first) => {
|
|
300
|
+
if (spill === "failed" || opts.spillPath === undefined)
|
|
301
|
+
return;
|
|
302
|
+
let text = spillCarry + chunk;
|
|
303
|
+
if (endsOnHighSurrogate(text)) {
|
|
304
|
+
spillCarry = text.slice(-1);
|
|
305
|
+
text = text.slice(0, -1);
|
|
306
|
+
} else {
|
|
307
|
+
spillCarry = "";
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
if (first) {
|
|
311
|
+
mkdirSync2(dirname2(opts.spillPath), { recursive: true });
|
|
312
|
+
writeFileSync2(opts.spillPath, text);
|
|
313
|
+
spill = "live";
|
|
314
|
+
} else {
|
|
315
|
+
appendFileSync(opts.spillPath, text);
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
spill = "failed";
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
return {
|
|
322
|
+
append(chunk) {
|
|
323
|
+
total += chunk.length;
|
|
324
|
+
if (!overflowed) {
|
|
325
|
+
const room = headCap - head.length;
|
|
326
|
+
if (chunk.length <= room) {
|
|
327
|
+
head += chunk;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
overflowed = true;
|
|
331
|
+
let cut = room;
|
|
332
|
+
if (cut > 0 && isHighSurrogate(chunk.charCodeAt(cut - 1)))
|
|
333
|
+
cut -= 1;
|
|
334
|
+
head += chunk.slice(0, cut);
|
|
335
|
+
let remainder = chunk.slice(cut);
|
|
336
|
+
if (endsOnHighSurrogate(head)) {
|
|
337
|
+
remainder = head.slice(-1) + remainder;
|
|
338
|
+
head = head.slice(0, -1);
|
|
339
|
+
}
|
|
340
|
+
writeSpill(head + remainder, true);
|
|
341
|
+
tail = takeTail(remainder, tailCap);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
writeSpill(chunk, false);
|
|
345
|
+
tail = takeTail(tail + chunk, tailCap);
|
|
346
|
+
},
|
|
347
|
+
snapshot() {
|
|
348
|
+
if (!overflowed) {
|
|
349
|
+
return { text: head, totalChars: total, truncated: false, spillPath: null };
|
|
350
|
+
}
|
|
351
|
+
if (head.length + tail.length === total) {
|
|
352
|
+
return { text: head + tail, totalChars: total, truncated: false, spillPath: null };
|
|
353
|
+
}
|
|
354
|
+
const where = spill === "live" ? `; full output: ${opts.spillLabel ?? opts.spillPath}` : "";
|
|
355
|
+
const marker = `
|
|
356
|
+
… [output truncated: showing first ${head.length} and last ${tail.length} of ${total} chars${where}]
|
|
357
|
+
`;
|
|
358
|
+
return {
|
|
359
|
+
text: `${head}${marker}${tail}`,
|
|
360
|
+
totalChars: total,
|
|
361
|
+
truncated: true,
|
|
362
|
+
spillPath: spill === "live" && opts.spillPath !== undefined ? opts.spillPath : null
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
latest() {
|
|
366
|
+
return overflowed ? tail : head;
|
|
367
|
+
},
|
|
368
|
+
totalChars() {
|
|
369
|
+
return total;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/dir-conventions.ts
|
|
375
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
376
|
+
import { join } from "node:path";
|
|
377
|
+
var DEFAULT_MAX_BYTES_PER_FILE = 16 * 1024;
|
|
378
|
+
var DEFAULT_MAX_FILES_PER_READ = 4;
|
|
379
|
+
var DEFAULT_MAX_SESSIONS = 100;
|
|
380
|
+
function normalizeRel(relPath) {
|
|
381
|
+
return relPath.split(/[\\/]+/).filter((s) => s.length > 0).join("/");
|
|
382
|
+
}
|
|
383
|
+
function dirChain(relPath) {
|
|
384
|
+
const segments = normalizeRel(relPath).split("/").filter((s) => s.length > 0);
|
|
385
|
+
const dirs = [];
|
|
386
|
+
for (let i = 0;i < segments.length - 1; i++) {
|
|
387
|
+
dirs.push(segments.slice(0, i + 1).join("/"));
|
|
388
|
+
}
|
|
389
|
+
return dirs;
|
|
390
|
+
}
|
|
391
|
+
function defaultLoadFile(absPath) {
|
|
392
|
+
try {
|
|
393
|
+
return readFileSync2(absPath, "utf8");
|
|
394
|
+
} catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
var TRACKER_CACHE_KEY = Symbol.for("zocomputer.agent-sdk.dir-conventions");
|
|
399
|
+
function trackerStateCache() {
|
|
400
|
+
const holder = globalThis;
|
|
401
|
+
holder[TRACKER_CACHE_KEY] ??= new Map;
|
|
402
|
+
return holder[TRACKER_CACHE_KEY];
|
|
403
|
+
}
|
|
404
|
+
function __resetDirConventionsCacheForTests() {
|
|
405
|
+
trackerStateCache().clear();
|
|
406
|
+
}
|
|
407
|
+
function createDirConventionsTracker(options) {
|
|
408
|
+
const {
|
|
409
|
+
workspaceRoot,
|
|
410
|
+
fileName = "AGENTS.md",
|
|
411
|
+
maxBytesPerFile = DEFAULT_MAX_BYTES_PER_FILE,
|
|
412
|
+
maxFilesPerRead = DEFAULT_MAX_FILES_PER_READ,
|
|
413
|
+
loadFile = defaultLoadFile,
|
|
414
|
+
maxSessions = DEFAULT_MAX_SESSIONS
|
|
415
|
+
} = options;
|
|
416
|
+
const cache = trackerStateCache();
|
|
417
|
+
const cacheKey = `${workspaceRoot}\x00${fileName}`;
|
|
418
|
+
let state = cache.get(cacheKey);
|
|
419
|
+
if (!state) {
|
|
420
|
+
state = { sessions: new Map };
|
|
421
|
+
cache.set(cacheKey, state);
|
|
422
|
+
}
|
|
423
|
+
const sessions = state.sessions;
|
|
424
|
+
function deliveredSet(sessionId) {
|
|
425
|
+
const existing = sessions.get(sessionId);
|
|
426
|
+
if (existing) {
|
|
427
|
+
sessions.delete(sessionId);
|
|
428
|
+
sessions.set(sessionId, existing);
|
|
429
|
+
return existing;
|
|
430
|
+
}
|
|
431
|
+
const set = new Set;
|
|
432
|
+
sessions.set(sessionId, set);
|
|
433
|
+
while (sessions.size > maxSessions) {
|
|
434
|
+
const oldest = sessions.keys().next().value;
|
|
435
|
+
if (oldest === undefined)
|
|
436
|
+
break;
|
|
437
|
+
sessions.delete(oldest);
|
|
438
|
+
}
|
|
439
|
+
return set;
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
async collect(sessionId, relPath, loadOverride) {
|
|
443
|
+
if (!sessionId)
|
|
444
|
+
return [];
|
|
445
|
+
const load = loadOverride ?? loadFile;
|
|
446
|
+
const delivered = deliveredSet(sessionId);
|
|
447
|
+
const normalizedRel = normalizeRel(relPath);
|
|
448
|
+
const found = [];
|
|
449
|
+
for (const dir of dirChain(normalizedRel)) {
|
|
450
|
+
if (delivered.has(dir))
|
|
451
|
+
continue;
|
|
452
|
+
const riderRel = `${dir}/${fileName}`;
|
|
453
|
+
if (riderRel === normalizedRel) {
|
|
454
|
+
delivered.add(dir);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
delivered.add(dir);
|
|
458
|
+
let loaded;
|
|
459
|
+
try {
|
|
460
|
+
loaded = await load(join(workspaceRoot, riderRel));
|
|
461
|
+
} catch {
|
|
462
|
+
delivered.delete(dir);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const content = loaded?.trim() ?? "";
|
|
466
|
+
if (content.length === 0)
|
|
467
|
+
continue;
|
|
468
|
+
found.push({ dir, path: riderRel, content });
|
|
469
|
+
}
|
|
470
|
+
if (found.length === 0)
|
|
471
|
+
return [];
|
|
472
|
+
const inlineFrom = Math.max(0, found.length - maxFilesPerRead);
|
|
473
|
+
return found.map(({ path, content }, index) => {
|
|
474
|
+
if (index < inlineFrom || Buffer.byteLength(content, "utf8") > maxBytesPerFile) {
|
|
475
|
+
return {
|
|
476
|
+
path,
|
|
477
|
+
note: `This directory has its own conventions — read ${path} before working here.`
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return { path, content };
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/instructions.ts
|
|
487
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
488
|
+
import { resolve } from "node:path";
|
|
489
|
+
import { defineDynamic, defineInstructions } from "eve/instructions";
|
|
490
|
+
function buildRepoConventionsMarkdown(workspaceRoot) {
|
|
491
|
+
let agents = "";
|
|
492
|
+
try {
|
|
493
|
+
agents = readFileSync3(resolve(workspaceRoot, "AGENTS.md"), "utf8").trim();
|
|
494
|
+
} catch {}
|
|
495
|
+
if (!agents)
|
|
496
|
+
return "";
|
|
497
|
+
return `## Repository conventions (root AGENTS.md)
|
|
498
|
+
|
|
499
|
+
These repo-wide conventions always apply. Nested directories add their own \`AGENTS.md\` — read those for the code you touch.
|
|
500
|
+
|
|
501
|
+
<root-agents-md>
|
|
502
|
+
${agents}
|
|
503
|
+
</root-agents-md>`;
|
|
504
|
+
}
|
|
505
|
+
function createRepoConventionsInstruction(opts) {
|
|
506
|
+
const { workspaceRoot } = opts;
|
|
507
|
+
return defineDynamic({
|
|
508
|
+
events: {
|
|
509
|
+
"session.started": () => defineInstructions({ markdown: buildRepoConventionsMarkdown(workspaceRoot) })
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
function createParallelToolsInstruction() {
|
|
514
|
+
const instruction = defineInstructions({
|
|
515
|
+
markdown: `## Parallel tool calls
|
|
516
|
+
|
|
517
|
+
Long-running work can continue in the **background** instead of blocking the turn. The \`bash\` tool does this automatically: if a command is still running after its short foreground wait, it returns a \`task_id\` and keeps the process alive. You can also use \`run_async\` when you already know the work should start in the background. After you start background work, decide whether your *next* action depends on its output:
|
|
518
|
+
|
|
519
|
+
- **Independent?** Keep working — read files, make edits, start other tasks — then \`check_tasks\` (non-blocking status + live output preview) or \`await_task\` (blocks for the result) when it's convenient.
|
|
520
|
+
- **Dependent?** Call \`await_task\` right away; treat it like a normal blocking call.
|
|
521
|
+
|
|
522
|
+
Guidance:
|
|
523
|
+
- Prefer plain \`bash\` for shell commands even when they might run long; it auto-returns a task handle if needed. Use \`run_async\` when you already know a command should start in the background and want to skip the foreground wait.
|
|
524
|
+
- You can have several tasks in flight at once. Each \`run_async\` returns a \`task_id\`; keep track of them.
|
|
525
|
+
- \`check_tasks\` shows status and live output previews for tasks that support progress. \`await_task\` returns the final output.
|
|
526
|
+
- For a long job where you only care about a specific signal — a failure line, a "listening on" banner — pass \`notify\` (\`{ pattern, reason }\`) to \`bash\` or \`run_async\` instead of polling: matching output is delivered to you as a message while you're idle. \`run_async\`'s \`notify_on_complete\` does the same when the task settles.
|
|
527
|
+
- When you do poll on wall-clock time (waiting on CI, a review, a deploy), keep any single blocking call under ~4 minutes — one sleep+check per call, not a whole retry loop in one call. Provider prompt caches expire after ~5 minutes of model inactivity, so one long silent call re-prices your entire context on the next step; returning between polls keeps it warm.
|
|
528
|
+
- Background task metadata and completed results persist across agent restarts. A task still running during a restart is reported as \`lost\`; start it again if its result still matters.
|
|
529
|
+
- Before finishing your turn, make sure any background task whose result matters has been awaited — don't end while relevant work is still running. If you're unsure what's still in flight, call \`check_tasks\`. A task you set a \`notify\` watcher on may keep running — its matches will reach you as messages.`
|
|
530
|
+
});
|
|
531
|
+
return defineDynamic({
|
|
532
|
+
events: {
|
|
533
|
+
"session.started": () => instruction
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
function buildWorkflowMarkdown(opts) {
|
|
538
|
+
const noun = opts?.workspaceNoun ?? "workspace";
|
|
539
|
+
const verify = opts?.verifyCommandHint ? ` (e.g. \`${opts.verifyCommandHint}\`)` : "";
|
|
540
|
+
return `## How to work
|
|
541
|
+
|
|
542
|
+
1. **Explore before you edit.** Find the relevant code with \`glob\`/\`grep\` and \`read\` it — match the ${noun}'s existing patterns instead of guessing.
|
|
543
|
+
2. **Read a file before editing it**, so your edits target the current text. Prefer \`edit\` for targeted changes; use \`write\` for new files or full rewrites.
|
|
544
|
+
3. **Follow the surrounding conventions.** Match the style, structure, and idioms of the code around your change rather than imposing your own.
|
|
545
|
+
4. **Verify your work.** After changing code, run the relevant checks${verify} and fix what you broke. Leave the ${noun} in a working state.
|
|
546
|
+
5. **Track multi-step work** with \`todo\`, and keep it current as you finish each step.
|
|
547
|
+
6. **Finish the job before ending your turn.** Reread your final message: if it promises work ("I'll…"), lays out next steps you could take now, or asks a question you could answer yourself with a tool call, do that work instead of stopping. End your turn only when the task is complete or you're blocked on something only the user can provide.`;
|
|
548
|
+
}
|
|
549
|
+
function createWorkflowInstruction(opts) {
|
|
550
|
+
const instruction = defineInstructions({ markdown: buildWorkflowMarkdown(opts) });
|
|
551
|
+
return defineDynamic({
|
|
552
|
+
events: {
|
|
553
|
+
"session.started": () => instruction
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
function buildCommunicationMarkdown() {
|
|
558
|
+
return `## Communicating
|
|
559
|
+
|
|
560
|
+
- **Lead with the outcome.** The first sentence of your final message answers "what happened" — what changed, what you found, whether it worked. Supporting detail and reasoning come after, for readers who want them.
|
|
561
|
+
- **Readable beats brief.** Shorten by dropping detail that doesn't change what the reader does next — not by compressing prose into fragments, arrow chains, or bare jargon. Write complete sentences and name the specific thing (the actual file, function, or command), not "the relevant helper".
|
|
562
|
+
- **Report, don't fix, when the user is diagnosing.** If they're describing a problem or asking a question, the deliverable is your assessment: investigate and report. Apply a fix only when they ask for one.
|
|
563
|
+
- **Act within scope without asking.** For reversible actions that follow from the task, decide and proceed — asking "Should I…?" stalls the work. Stop to ask only for destructive or hard-to-reverse actions, or genuine scope changes the user must decide.
|
|
564
|
+
- **Report outcomes faithfully.** If a check fails, say so and include the output; if you skipped a step, say that; when something is done and verified, state it plainly without hedging.`;
|
|
565
|
+
}
|
|
566
|
+
function createCommunicationInstruction() {
|
|
567
|
+
const instruction = defineInstructions({ markdown: buildCommunicationMarkdown() });
|
|
568
|
+
return defineDynamic({
|
|
569
|
+
events: {
|
|
570
|
+
"session.started": () => instruction
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
function buildHitlMarkdown() {
|
|
575
|
+
return `## Asking the user (ask_question)
|
|
576
|
+
|
|
577
|
+
Call \`ask_question\` only when you're genuinely blocked on a choice that is the user's to make — not for permission to proceed with a reasonable default you can pick yourself. When you do ask:
|
|
578
|
+
|
|
579
|
+
- **Offer \`options\` when the choices are enumerable** instead of asking open-ended; each option is \`{ id, label, description?, style? }\` and the user answers with one click.
|
|
580
|
+
- **Put your recommended option first** and mark it \`style: "primary"\`. Use \`style: "danger"\` for destructive or hard-to-reverse choices.
|
|
581
|
+
- **Use each option's \`description\`** for the trade-off the label can't carry.
|
|
582
|
+
- **Keep free text open** (\`allowFreeform: true\`) unless the answer must be exactly one of the options.
|
|
583
|
+
- **Ask independent questions together**: emit several \`ask_question\` calls in one response — they collect into a single prompt and you get all the answers at once, instead of making the user answer serial round-trips.`;
|
|
584
|
+
}
|
|
585
|
+
function createHitlInstruction() {
|
|
586
|
+
const instruction = defineInstructions({ markdown: buildHitlMarkdown() });
|
|
587
|
+
return defineDynamic({
|
|
588
|
+
events: {
|
|
589
|
+
"session.started": () => instruction
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
function buildSubagentMarkdown(workspaceNoun = "workspace", roster) {
|
|
594
|
+
const noun = workspaceNoun;
|
|
595
|
+
const rosterSection = roster && roster.length > 0 ? `
|
|
596
|
+
|
|
597
|
+
### Choosing a subagent
|
|
598
|
+
|
|
599
|
+
Beyond the clone, you have declared specialists — each is its own tool with the same \`{ message, outputSchema? }\` input:
|
|
600
|
+
|
|
601
|
+
${roster.map((entry) => `- **\`${entry.name}\`** — ${entry.when}.`).join(`
|
|
602
|
+
`)}
|
|
603
|
+
|
|
604
|
+
Prefer a specialist when its purpose or model tier matches the subtask; use the clone \`agent\` when none fits. A specialist that can edit shares the non-overlapping write-scope rule above; one that cannot write is safe to fan out freely.` : "";
|
|
605
|
+
return `## Delegating with the agent tool
|
|
606
|
+
|
|
607
|
+
\`agent\` runs a focused subtask in a **fresh copy of yourself** — same tools and instructions, same ${noun}, but a **blank conversation**: the child sees only the \`message\` you send, none of your history. It's how you parallelize.
|
|
608
|
+
|
|
609
|
+
- **Pack the message with everything the child needs**: the exact deliverable, relevant paths, constraints, and any context it can't discover cheaply. A vague delegation wastes the whole child run.
|
|
610
|
+
- **Fan out independent subtasks in parallel**: emit several \`agent\` calls in one response — they run concurrently and all results return before you continue. Fan out only work that's genuinely independent.
|
|
611
|
+
- **Give parallel children non-overlapping write scopes** (different files or directories). They share your ${noun} and see each other's writes; overlapping edits clobber.
|
|
612
|
+
- **Don't delegate trivia.** A subtask that one or two direct tool calls would answer is faster done yourself; delegation pays off for self-contained work with real depth (multi-file exploration, an isolated fix + verify, a report).
|
|
613
|
+
- Set \`outputSchema\` when you need structured output back instead of prose.${rosterSection}`;
|
|
614
|
+
}
|
|
615
|
+
function createSubagentInstruction(opts) {
|
|
616
|
+
const instruction = defineInstructions({
|
|
617
|
+
markdown: buildSubagentMarkdown(opts?.workspaceNoun, opts?.roster)
|
|
618
|
+
});
|
|
619
|
+
return defineDynamic({
|
|
620
|
+
events: {
|
|
621
|
+
"session.started": () => instruction
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/run.ts
|
|
627
|
+
import { spawn } from "node:child_process";
|
|
628
|
+
import { join as join2 } from "node:path";
|
|
629
|
+
var MAX_PREVIEW = 20000;
|
|
630
|
+
function previewOf(capture) {
|
|
631
|
+
const latest = capture.latest();
|
|
632
|
+
if (capture.totalChars() <= MAX_PREVIEW)
|
|
633
|
+
return latest;
|
|
634
|
+
return `… [earlier output truncated]
|
|
635
|
+
${latest.slice(-MAX_PREVIEW)}`;
|
|
636
|
+
}
|
|
637
|
+
function createCommandRunner(opts) {
|
|
638
|
+
const { workspace, spillDir } = opts;
|
|
639
|
+
function startCommand(command, runOpts = {}) {
|
|
640
|
+
const cwd = runOpts.cwd ? workspace.resolve(runOpts.cwd) : workspace.root;
|
|
641
|
+
const timeoutMs = runOpts.timeoutMs ?? 120000;
|
|
642
|
+
const child = spawn(command, { cwd, shell: true, env: process.env, detached: true });
|
|
643
|
+
const runId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
644
|
+
const captureFor = (stream) => {
|
|
645
|
+
const spillPath = join2(spillDir, `bash-${runId}-${stream}.log`);
|
|
646
|
+
return createBoundedCapture({ spillPath, spillLabel: workspace.relativize(spillPath) });
|
|
647
|
+
};
|
|
648
|
+
const stdoutCapture = captureFor("stdout");
|
|
649
|
+
const stderrCapture = captureFor("stderr");
|
|
650
|
+
let stdoutBytes = 0;
|
|
651
|
+
let stderrBytes = 0;
|
|
652
|
+
let timedOut = false;
|
|
653
|
+
let closed = false;
|
|
654
|
+
const killTree = (signal) => {
|
|
655
|
+
const pid = child.pid;
|
|
656
|
+
if (pid === undefined)
|
|
657
|
+
return;
|
|
658
|
+
try {
|
|
659
|
+
process.kill(-pid, signal);
|
|
660
|
+
} catch {
|
|
661
|
+
child.kill(signal);
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
const timer = setTimeout(() => {
|
|
665
|
+
timedOut = true;
|
|
666
|
+
killTree("SIGKILL");
|
|
667
|
+
}, timeoutMs);
|
|
668
|
+
const result = new Promise((resolvePromise) => {
|
|
669
|
+
child.stdout.on("data", (d) => {
|
|
670
|
+
const chunk = d.toString();
|
|
671
|
+
stdoutBytes += Buffer.byteLength(chunk);
|
|
672
|
+
stdoutCapture.append(chunk);
|
|
673
|
+
runOpts.onOutput?.(chunk);
|
|
674
|
+
});
|
|
675
|
+
child.stderr.on("data", (d) => {
|
|
676
|
+
const chunk = d.toString();
|
|
677
|
+
stderrBytes += Buffer.byteLength(chunk);
|
|
678
|
+
stderrCapture.append(chunk);
|
|
679
|
+
runOpts.onOutput?.(chunk);
|
|
680
|
+
});
|
|
681
|
+
child.on("close", (code) => {
|
|
682
|
+
closed = true;
|
|
683
|
+
clearTimeout(timer);
|
|
684
|
+
resolvePromise({
|
|
685
|
+
stdout: stdoutCapture.snapshot().text,
|
|
686
|
+
stderr: stderrCapture.snapshot().text,
|
|
687
|
+
exitCode: code,
|
|
688
|
+
timedOut
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
child.on("error", (err) => {
|
|
692
|
+
closed = true;
|
|
693
|
+
clearTimeout(timer);
|
|
694
|
+
resolvePromise({
|
|
695
|
+
stdout: stdoutCapture.snapshot().text,
|
|
696
|
+
stderr: `${stderrCapture.snapshot().text}${err.message}`,
|
|
697
|
+
exitCode: null,
|
|
698
|
+
timedOut
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
return {
|
|
703
|
+
result,
|
|
704
|
+
progress() {
|
|
705
|
+
return {
|
|
706
|
+
stdout: previewOf(stdoutCapture),
|
|
707
|
+
stderr: previewOf(stderrCapture),
|
|
708
|
+
stdoutBytes,
|
|
709
|
+
stderrBytes,
|
|
710
|
+
stdoutTruncated: stdoutCapture.totalChars() > MAX_PREVIEW,
|
|
711
|
+
stderrTruncated: stderrCapture.totalChars() > MAX_PREVIEW
|
|
712
|
+
};
|
|
713
|
+
},
|
|
714
|
+
kill() {
|
|
715
|
+
if (closed)
|
|
716
|
+
return;
|
|
717
|
+
killTree("SIGTERM");
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
startCommand,
|
|
723
|
+
runCommand: (command, runOpts) => startCommand(command, runOpts).result
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/steer-inbox.ts
|
|
728
|
+
import {
|
|
729
|
+
appendFileSync as appendFileSync2,
|
|
730
|
+
linkSync,
|
|
731
|
+
mkdirSync as mkdirSync3,
|
|
732
|
+
readFileSync as readFileSync4,
|
|
733
|
+
renameSync,
|
|
734
|
+
rmSync
|
|
735
|
+
} from "node:fs";
|
|
736
|
+
import { join as join3 } from "node:path";
|
|
737
|
+
|
|
738
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/steer.ts
|
|
739
|
+
var STEER_FIELD = "user_steer";
|
|
740
|
+
var STEER_WRAPPED_OUTPUT_FIELD = "steer_wrapped_output";
|
|
741
|
+
var STEER_DIRNAME = "steer";
|
|
742
|
+
var STEER_NOTE = "The user sent these messages while this tool was running. They take priority: address them now and adjust your current approach before continuing.";
|
|
743
|
+
function buildSteerPayload(messages) {
|
|
744
|
+
return { note: STEER_NOTE, messages: [...messages] };
|
|
745
|
+
}
|
|
746
|
+
function isRecord3(value) {
|
|
747
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
748
|
+
}
|
|
749
|
+
function isSteerMessage(value) {
|
|
750
|
+
return isRecord3(value) && typeof value.id === "string" && typeof value.text === "string" && typeof value.at === "number";
|
|
751
|
+
}
|
|
752
|
+
function attachSteerToOutput(output, messages) {
|
|
753
|
+
if (isRecord3(output)) {
|
|
754
|
+
const existing = readSteerMessages(output) ?? [];
|
|
755
|
+
return { ...output, [STEER_FIELD]: buildSteerPayload([...existing, ...messages]) };
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
[STEER_WRAPPED_OUTPUT_FIELD]: output,
|
|
759
|
+
[STEER_FIELD]: buildSteerPayload(messages)
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function stripSteerFromOutput(record) {
|
|
763
|
+
const { [STEER_FIELD]: _steer, ...rest } = record;
|
|
764
|
+
const keys = Object.keys(rest);
|
|
765
|
+
if (keys.length === 1 && keys[0] === STEER_WRAPPED_OUTPUT_FIELD) {
|
|
766
|
+
return rest[STEER_WRAPPED_OUTPUT_FIELD];
|
|
767
|
+
}
|
|
768
|
+
return rest;
|
|
769
|
+
}
|
|
770
|
+
function readSteerMessages(output) {
|
|
771
|
+
if (!isRecord3(output))
|
|
772
|
+
return null;
|
|
773
|
+
const payload = output[STEER_FIELD];
|
|
774
|
+
if (!isRecord3(payload) || !Array.isArray(payload.messages))
|
|
775
|
+
return null;
|
|
776
|
+
const messages = payload.messages.filter(isSteerMessage);
|
|
777
|
+
return messages.length > 0 ? messages : null;
|
|
778
|
+
}
|
|
779
|
+
function formatSteerText(messages) {
|
|
780
|
+
const lines = messages.map((message) => `- ${message.text}`);
|
|
781
|
+
return `[${STEER_FIELD}] ${STEER_NOTE}
|
|
782
|
+
${lines.join(`
|
|
783
|
+
`)}`;
|
|
784
|
+
}
|
|
785
|
+
function mergeSteerIntoModelOutput(output, messages) {
|
|
786
|
+
if (output.type === "text") {
|
|
787
|
+
return { type: "text", value: `${output.value}
|
|
788
|
+
|
|
789
|
+
${formatSteerText(messages)}` };
|
|
790
|
+
}
|
|
791
|
+
return { type: "json", value: attachSteerToOutput(output.value, messages) };
|
|
792
|
+
}
|
|
793
|
+
function serializeSteerLine(message) {
|
|
794
|
+
return JSON.stringify(message);
|
|
795
|
+
}
|
|
796
|
+
function parseSteerLine(line) {
|
|
797
|
+
const trimmed = line.trim();
|
|
798
|
+
if (trimmed === "")
|
|
799
|
+
return null;
|
|
800
|
+
try {
|
|
801
|
+
const parsed = JSON.parse(trimmed);
|
|
802
|
+
return isSteerMessage(parsed) ? parsed : null;
|
|
803
|
+
} catch {
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/steer-inbox.ts
|
|
809
|
+
var drainSequence = 0;
|
|
810
|
+
function createSteerInbox(options) {
|
|
811
|
+
const now = options.now ?? Date.now;
|
|
812
|
+
const newId = options.newId ?? (() => crypto.randomUUID());
|
|
813
|
+
const readFile = options.readFile ?? ((path) => readFileSync4(path, "utf8"));
|
|
814
|
+
const fileFor = (sessionId) => join3(options.dir, `${encodeURIComponent(sessionId)}.ndjson`);
|
|
815
|
+
function appendMessage(sessionId, message) {
|
|
816
|
+
mkdirSync3(options.dir, { recursive: true });
|
|
817
|
+
appendFileSync2(fileFor(sessionId), `${serializeSteerLine(message)}
|
|
818
|
+
`, "utf8");
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
append(sessionId, text) {
|
|
822
|
+
const message = { id: newId(), text, at: now() };
|
|
823
|
+
appendMessage(sessionId, message);
|
|
824
|
+
return message;
|
|
825
|
+
},
|
|
826
|
+
appendMessage,
|
|
827
|
+
drain(sessionId) {
|
|
828
|
+
const file = fileFor(sessionId);
|
|
829
|
+
const claimed = `${file}.drain-${process.pid}-${drainSequence++}`;
|
|
830
|
+
try {
|
|
831
|
+
renameSync(file, claimed);
|
|
832
|
+
} catch {
|
|
833
|
+
return [];
|
|
834
|
+
}
|
|
835
|
+
let raw;
|
|
836
|
+
try {
|
|
837
|
+
raw = readFile(claimed);
|
|
838
|
+
} catch {
|
|
839
|
+
try {
|
|
840
|
+
linkSync(claimed, file);
|
|
841
|
+
rmSync(claimed, { force: true });
|
|
842
|
+
} catch {
|
|
843
|
+
try {
|
|
844
|
+
appendFileSync2(file, readFileSync4(claimed));
|
|
845
|
+
rmSync(claimed, { force: true });
|
|
846
|
+
} catch {}
|
|
847
|
+
}
|
|
848
|
+
return [];
|
|
849
|
+
}
|
|
850
|
+
rmSync(claimed, { force: true });
|
|
851
|
+
return raw.split(`
|
|
852
|
+
`).map(parseSteerLine).filter((message) => message !== null);
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/steer-tool.ts
|
|
858
|
+
import { defineTool } from "eve/tools";
|
|
859
|
+
function withSteerDelivery(tool, inbox) {
|
|
860
|
+
const originalToModelOutput = tool.toModelOutput?.bind(tool);
|
|
861
|
+
const wrapped = {
|
|
862
|
+
...tool,
|
|
863
|
+
async execute(input, ctx) {
|
|
864
|
+
const output = await tool.execute(input, ctx);
|
|
865
|
+
const messages = inbox.drain(ctx.session.id);
|
|
866
|
+
if (messages.length === 0)
|
|
867
|
+
return output;
|
|
868
|
+
return attachSteerToOutput(output, messages);
|
|
869
|
+
},
|
|
870
|
+
...originalToModelOutput ? {
|
|
871
|
+
async toModelOutput(output) {
|
|
872
|
+
const messages = readSteerMessages(output);
|
|
873
|
+
if (!messages)
|
|
874
|
+
return originalToModelOutput(output);
|
|
875
|
+
const original = stripSteerFromOutput(output);
|
|
876
|
+
const narrowed = await originalToModelOutput(original);
|
|
877
|
+
return mergeSteerIntoModelOutput(narrowed, messages);
|
|
878
|
+
}
|
|
879
|
+
} : {}
|
|
880
|
+
};
|
|
881
|
+
return defineTool(wrapped);
|
|
882
|
+
}
|
|
883
|
+
function createSteerWrapper(inbox) {
|
|
884
|
+
if (!inbox)
|
|
885
|
+
return (tool) => tool;
|
|
886
|
+
return (tool) => withSteerDelivery(tool, inbox);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/bash.ts
|
|
890
|
+
import { defineTool as defineTool2 } from "eve/tools";
|
|
891
|
+
import { z as z2 } from "zod";
|
|
892
|
+
|
|
893
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/park-delivery.ts
|
|
894
|
+
function isRecord4(value) {
|
|
895
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
896
|
+
}
|
|
897
|
+
function clientContinuationToken(runtimeToken) {
|
|
898
|
+
const sep = runtimeToken.indexOf(":");
|
|
899
|
+
if (sep <= 0)
|
|
900
|
+
return runtimeToken;
|
|
901
|
+
const rest = runtimeToken.slice(sep + 1);
|
|
902
|
+
return rest.includes(":") ? rest : runtimeToken;
|
|
903
|
+
}
|
|
904
|
+
function createParkDeliveryState() {
|
|
905
|
+
const sessions = new Map;
|
|
906
|
+
function session(id) {
|
|
907
|
+
let state = sessions.get(id);
|
|
908
|
+
if (!state) {
|
|
909
|
+
state = { pending: new Map, delivered: new Set, parked: false, delivering: false };
|
|
910
|
+
sessions.set(id, state);
|
|
911
|
+
}
|
|
912
|
+
return state;
|
|
913
|
+
}
|
|
914
|
+
function drain(id, state) {
|
|
915
|
+
if (state.pending.size === 0 || !state.continuationToken || state.delivering)
|
|
916
|
+
return null;
|
|
917
|
+
const items = [...state.pending.values()];
|
|
918
|
+
state.pending.clear();
|
|
919
|
+
for (const item of items)
|
|
920
|
+
state.delivered.add(item.key);
|
|
921
|
+
state.delivering = true;
|
|
922
|
+
return { sessionId: id, continuationToken: state.continuationToken, items };
|
|
923
|
+
}
|
|
924
|
+
function enqueueAll(sessionId, items) {
|
|
925
|
+
const state = session(sessionId);
|
|
926
|
+
let queued = false;
|
|
927
|
+
for (const item of items) {
|
|
928
|
+
if (state.delivered.has(item.key) || state.pending.has(item.key))
|
|
929
|
+
continue;
|
|
930
|
+
state.pending.set(item.key, item);
|
|
931
|
+
queued = true;
|
|
932
|
+
}
|
|
933
|
+
if (!queued || !state.parked)
|
|
934
|
+
return null;
|
|
935
|
+
return drain(sessionId, state);
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
observe(event, meta) {
|
|
939
|
+
const state = session(meta.sessionId);
|
|
940
|
+
if (meta.continuationToken) {
|
|
941
|
+
state.continuationToken = clientContinuationToken(meta.continuationToken);
|
|
942
|
+
}
|
|
943
|
+
if (!isRecord4(event))
|
|
944
|
+
return null;
|
|
945
|
+
if (event.type === "session.completed" || event.type === "session.failed") {
|
|
946
|
+
sessions.delete(meta.sessionId);
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
if (event.type !== "session.waiting") {
|
|
950
|
+
state.parked = false;
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
state.parked = true;
|
|
954
|
+
return drain(meta.sessionId, state);
|
|
955
|
+
},
|
|
956
|
+
enqueue(sessionId, item) {
|
|
957
|
+
return enqueueAll(sessionId, [item]);
|
|
958
|
+
},
|
|
959
|
+
enqueueAll,
|
|
960
|
+
settle(request, ok) {
|
|
961
|
+
const state = session(request.sessionId);
|
|
962
|
+
state.delivering = false;
|
|
963
|
+
if (ok) {
|
|
964
|
+
if (state.parked)
|
|
965
|
+
return drain(request.sessionId, state);
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
for (const item of request.items) {
|
|
969
|
+
state.delivered.delete(item.key);
|
|
970
|
+
state.pending.set(item.key, item);
|
|
971
|
+
}
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
var BRIDGE_KEY = Symbol.for("zocomputer.agent-sdk.park-notification-bridge");
|
|
977
|
+
var MAX_QUEUED_PER_SESSION = 20;
|
|
978
|
+
function bridge() {
|
|
979
|
+
const holder = globalThis;
|
|
980
|
+
holder[BRIDGE_KEY] ??= { queued: new Map, handler: null };
|
|
981
|
+
return holder[BRIDGE_KEY];
|
|
982
|
+
}
|
|
983
|
+
function postParkNotification(sessionId, notification) {
|
|
984
|
+
const b = bridge();
|
|
985
|
+
if (b.handler) {
|
|
986
|
+
b.handler(sessionId, notification);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
const queue = b.queued.get(sessionId) ?? [];
|
|
990
|
+
if (queue.length >= MAX_QUEUED_PER_SESSION)
|
|
991
|
+
return;
|
|
992
|
+
queue.push(notification);
|
|
993
|
+
b.queued.set(sessionId, queue);
|
|
994
|
+
}
|
|
995
|
+
function setParkNotificationHandler(handler) {
|
|
996
|
+
const b = bridge();
|
|
997
|
+
b.handler = handler;
|
|
998
|
+
const queued = [...b.queued.entries()];
|
|
999
|
+
b.queued.clear();
|
|
1000
|
+
for (const [sessionId, notifications] of queued) {
|
|
1001
|
+
for (const notification of notifications)
|
|
1002
|
+
handler(sessionId, notification);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/watch-output.ts
|
|
1007
|
+
var DEFAULT_WATCH_DEBOUNCE_MS = 5000;
|
|
1008
|
+
var DEFAULT_MAX_WATCH_NOTIFICATIONS = 5;
|
|
1009
|
+
function createOutputWatcher(options) {
|
|
1010
|
+
const regex = new RegExp(options.pattern);
|
|
1011
|
+
const debounceMs = options.debounceMs ?? DEFAULT_WATCH_DEBOUNCE_MS;
|
|
1012
|
+
const maxNotifications = options.maxNotifications ?? DEFAULT_MAX_WATCH_NOTIFICATIONS;
|
|
1013
|
+
const now = options.now ?? Date.now;
|
|
1014
|
+
let buffer = "";
|
|
1015
|
+
let notifications = 0;
|
|
1016
|
+
let lastNotifiedAt = null;
|
|
1017
|
+
function emit(lines) {
|
|
1018
|
+
const matches = lines.filter((line) => regex.test(line));
|
|
1019
|
+
if (matches.length === 0)
|
|
1020
|
+
return null;
|
|
1021
|
+
if (notifications >= maxNotifications)
|
|
1022
|
+
return null;
|
|
1023
|
+
const at = now();
|
|
1024
|
+
if (lastNotifiedAt !== null && at - lastNotifiedAt < debounceMs)
|
|
1025
|
+
return null;
|
|
1026
|
+
notifications += 1;
|
|
1027
|
+
lastNotifiedAt = at;
|
|
1028
|
+
return matches;
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
feed(chunk) {
|
|
1032
|
+
buffer += chunk;
|
|
1033
|
+
const parts = buffer.split(`
|
|
1034
|
+
`);
|
|
1035
|
+
buffer = parts.pop() ?? "";
|
|
1036
|
+
return emit(parts);
|
|
1037
|
+
},
|
|
1038
|
+
flush() {
|
|
1039
|
+
if (buffer.length === 0)
|
|
1040
|
+
return null;
|
|
1041
|
+
const tail = buffer;
|
|
1042
|
+
buffer = "";
|
|
1043
|
+
const matches = [tail].filter((line) => regex.test(line));
|
|
1044
|
+
if (matches.length === 0)
|
|
1045
|
+
return null;
|
|
1046
|
+
if (notifications >= maxNotifications)
|
|
1047
|
+
return null;
|
|
1048
|
+
notifications += 1;
|
|
1049
|
+
lastNotifiedAt = now();
|
|
1050
|
+
return matches;
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function formatWatchNotification(opts) {
|
|
1055
|
+
return `Background task ${opts.taskId} (${opts.label}) — ${opts.reason}. ` + `Output matched your watch pattern:
|
|
1056
|
+
${opts.lines.join(`
|
|
1057
|
+
`)}`;
|
|
1058
|
+
}
|
|
1059
|
+
function formatCompletionNotification(opts) {
|
|
1060
|
+
const outcome = opts.status === "done" ? "finished" : `failed${opts.error ? `: ${opts.error}` : ""}`;
|
|
1061
|
+
return `Background task ${opts.taskId} (${opts.label}) ${outcome}. Call await_task to collect its result.`;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/bash.ts
|
|
1065
|
+
var DEFAULT_INTERACTIVE_HINT = "This is a piped shell with NO tty: avoid interactive or full-screen CLIs (a REPL, vim, an interactive installer/prompt) — those programs hang or degrade without a real terminal.";
|
|
1066
|
+
function createBashTool(opts) {
|
|
1067
|
+
const { workspace, runner, registry, noun } = opts;
|
|
1068
|
+
const interactiveHint = opts.interactiveHint ?? DEFAULT_INTERACTIVE_HINT;
|
|
1069
|
+
return defineTool2({
|
|
1070
|
+
description: `Run a shell command on the host, from the ${noun} root by default. Quick commands return normally. If the command is still running after foreground_ms, it keeps running in the background and returns a task_id plus current stdout/stderr; use check_tasks and await_task to monitor or collect the result. For a long-running command where you only care about a specific output signal (a failure line, a "listening on" banner), pass notify — if the command backgrounds, matching output is delivered to you as a message while you're idle, so you can keep working instead of polling. Use it for git, tests/builds/type-checks, ripgrep, dev servers, and anything the file tools don't cover. Very long output is truncated to its head and tail; the complete output is saved to a file named in the result — grep or read that file instead of re-running the command. This is a real shell on the user's machine with no sandbox and no undo — be careful with destructive commands. ` + interactiveHint,
|
|
1071
|
+
inputSchema: z2.object({
|
|
1072
|
+
command: z2.string().min(1).describe("The shell command to run."),
|
|
1073
|
+
cwd: z2.string().optional().describe(`Working directory, relative to the ${noun} root. Defaults to the ${noun} root.`),
|
|
1074
|
+
timeout_ms: z2.number().int().positive().optional().describe("Kill the command after this many milliseconds (default 600000)."),
|
|
1075
|
+
foreground_ms: z2.number().int().positive().optional().describe("How long to wait before returning a background task handle (default 2000)."),
|
|
1076
|
+
notify: z2.object({
|
|
1077
|
+
pattern: z2.string().min(1).describe("Regex matched against complete output lines (stdout and stderr)."),
|
|
1078
|
+
reason: z2.string().min(1).describe("Short phrase naming what you're watching for, e.g. 'test failures'."),
|
|
1079
|
+
debounce_ms: z2.number().int().positive().optional().describe("Minimum ms between match notifications (default 5000).")
|
|
1080
|
+
}).optional().describe("Watch the command's output if it backgrounds: matching lines are delivered to you as a message while you're idle. No effect on a command that completes in the foreground.")
|
|
1081
|
+
}),
|
|
1082
|
+
async execute({ command, cwd, timeout_ms, foreground_ms, notify }, ctx) {
|
|
1083
|
+
const watcher = notify ? createOutputWatcher({ pattern: notify.pattern, debounceMs: notify.debounce_ms }) : null;
|
|
1084
|
+
let feedLive = null;
|
|
1085
|
+
const buffered = [];
|
|
1086
|
+
const running = runner.startCommand(command, {
|
|
1087
|
+
cwd,
|
|
1088
|
+
timeoutMs: timeout_ms ?? 600000,
|
|
1089
|
+
onOutput: watcher ? (chunk) => {
|
|
1090
|
+
if (feedLive)
|
|
1091
|
+
feedLive(chunk);
|
|
1092
|
+
else
|
|
1093
|
+
buffered.push(chunk);
|
|
1094
|
+
} : undefined
|
|
1095
|
+
});
|
|
1096
|
+
const result = await Promise.race([
|
|
1097
|
+
running.result,
|
|
1098
|
+
new Promise((resolve2) => setTimeout(() => resolve2(null), foreground_ms ?? 2000))
|
|
1099
|
+
]);
|
|
1100
|
+
if (result !== null) {
|
|
1101
|
+
return {
|
|
1102
|
+
workdir: workspace.root,
|
|
1103
|
+
mode: "completed",
|
|
1104
|
+
exitCode: result.exitCode,
|
|
1105
|
+
timedOut: result.timedOut,
|
|
1106
|
+
stdout: result.stdout,
|
|
1107
|
+
stderr: result.stderr
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
const taskId = registry.spawnTask("bash", command, running.result);
|
|
1111
|
+
registry.updateTaskProgress(taskId, running.progress());
|
|
1112
|
+
const interval = setInterval(() => registry.updateTaskProgress(taskId, running.progress()), 500);
|
|
1113
|
+
running.result.finally(() => clearInterval(interval)).catch(() => {
|
|
1114
|
+
return;
|
|
1115
|
+
});
|
|
1116
|
+
if (watcher && notify) {
|
|
1117
|
+
const sessionId = ctx?.session?.id;
|
|
1118
|
+
let matchCount = 0;
|
|
1119
|
+
const post = (lines) => {
|
|
1120
|
+
if (!lines || !sessionId)
|
|
1121
|
+
return;
|
|
1122
|
+
matchCount += 1;
|
|
1123
|
+
postParkNotification(sessionId, {
|
|
1124
|
+
key: `${taskId}#watch${matchCount}`,
|
|
1125
|
+
text: formatWatchNotification({ taskId, label: command, reason: notify.reason, lines })
|
|
1126
|
+
});
|
|
1127
|
+
};
|
|
1128
|
+
feedLive = (chunk) => post(watcher.feed(chunk));
|
|
1129
|
+
for (const chunk of buffered.splice(0))
|
|
1130
|
+
feedLive(chunk);
|
|
1131
|
+
running.result.finally(() => post(watcher.flush())).catch(() => {
|
|
1132
|
+
return;
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
workdir: workspace.root,
|
|
1137
|
+
mode: "backgrounded",
|
|
1138
|
+
task_id: taskId,
|
|
1139
|
+
status: "running",
|
|
1140
|
+
progress: running.progress(),
|
|
1141
|
+
...watcher ? {
|
|
1142
|
+
watching: notify?.pattern,
|
|
1143
|
+
note: "Command is still running in the background; matching output will be delivered to you as a message while you're idle. Continue independent work, or call await_task if your next step needs the result."
|
|
1144
|
+
} : {
|
|
1145
|
+
note: "Command is still running in the background. Continue independent work, then call check_tasks for live output or await_task when you need the final result."
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/edit.ts
|
|
1153
|
+
import { defineTool as defineTool3 } from "eve/tools";
|
|
1154
|
+
import { z as z3 } from "zod";
|
|
1155
|
+
|
|
1156
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/path-locks.ts
|
|
1157
|
+
var LOCKS_KEY = Symbol.for("zocomputer.agent-sdk.path-locks");
|
|
1158
|
+
function lockChains() {
|
|
1159
|
+
const holder = globalThis;
|
|
1160
|
+
holder[LOCKS_KEY] ??= new Map;
|
|
1161
|
+
return holder[LOCKS_KEY];
|
|
1162
|
+
}
|
|
1163
|
+
async function withPathLock(path, fn) {
|
|
1164
|
+
const chains = lockChains();
|
|
1165
|
+
const prev = chains.get(path) ?? Promise.resolve();
|
|
1166
|
+
let release;
|
|
1167
|
+
const gate = new Promise((resolve2) => {
|
|
1168
|
+
release = resolve2;
|
|
1169
|
+
});
|
|
1170
|
+
const chained = prev.then(() => gate);
|
|
1171
|
+
chains.set(path, chained);
|
|
1172
|
+
await prev;
|
|
1173
|
+
try {
|
|
1174
|
+
return await fn();
|
|
1175
|
+
} finally {
|
|
1176
|
+
release();
|
|
1177
|
+
if (chains.get(path) === chained)
|
|
1178
|
+
chains.delete(path);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/workspace-io.ts
|
|
1183
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync7, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1184
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
1185
|
+
|
|
1186
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/glob-match.ts
|
|
1187
|
+
function globToRegExp(glob) {
|
|
1188
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
1189
|
+
const body = escaped.replace(/\*\*\/?/g, "\x00").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, "(?:.*/)?");
|
|
1190
|
+
return new RegExp(`^${body}$`);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/list-files.ts
|
|
1194
|
+
import { spawnSync } from "node:child_process";
|
|
1195
|
+
var MAX_BUFFER = 64 * 1024 * 1024;
|
|
1196
|
+
function gitPaths(root, args) {
|
|
1197
|
+
const res = spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: MAX_BUFFER });
|
|
1198
|
+
if (res.error || res.status !== 0)
|
|
1199
|
+
return null;
|
|
1200
|
+
return res.stdout.split("\x00").filter((path) => path.length > 0);
|
|
1201
|
+
}
|
|
1202
|
+
function listGitFiles(root, scope) {
|
|
1203
|
+
const spec = scope !== undefined && scope !== "." ? ["--", scope] : [];
|
|
1204
|
+
const files = gitPaths(root, [
|
|
1205
|
+
"ls-files",
|
|
1206
|
+
"--cached",
|
|
1207
|
+
"--others",
|
|
1208
|
+
"--exclude-standard",
|
|
1209
|
+
"-z",
|
|
1210
|
+
...spec
|
|
1211
|
+
]);
|
|
1212
|
+
if (files === null)
|
|
1213
|
+
return null;
|
|
1214
|
+
const deleted = gitPaths(root, ["ls-files", "--deleted", "-z", ...spec]);
|
|
1215
|
+
if (deleted === null || deleted.length === 0)
|
|
1216
|
+
return files;
|
|
1217
|
+
const gone = new Set(deleted);
|
|
1218
|
+
return files.filter((path) => !gone.has(path));
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/read-text.ts
|
|
1222
|
+
import { readFileSync as readFileSync5, statSync } from "node:fs";
|
|
1223
|
+
var MAX_SEARCH_FILE_BYTES = 1500000;
|
|
1224
|
+
var BINARY_SNIFF_BYTES = 8192;
|
|
1225
|
+
function readTextForSearch(abs, maxBytes = MAX_SEARCH_FILE_BYTES) {
|
|
1226
|
+
let size;
|
|
1227
|
+
try {
|
|
1228
|
+
const stats = statSync(abs);
|
|
1229
|
+
if (!stats.isFile())
|
|
1230
|
+
return { kind: "unreadable" };
|
|
1231
|
+
size = stats.size;
|
|
1232
|
+
} catch {
|
|
1233
|
+
return { kind: "unreadable" };
|
|
1234
|
+
}
|
|
1235
|
+
if (size > maxBytes)
|
|
1236
|
+
return { kind: "too-large", bytes: size };
|
|
1237
|
+
let buf;
|
|
1238
|
+
try {
|
|
1239
|
+
buf = readFileSync5(abs);
|
|
1240
|
+
} catch {
|
|
1241
|
+
return { kind: "unreadable" };
|
|
1242
|
+
}
|
|
1243
|
+
if (buf.subarray(0, BINARY_SNIFF_BYTES).includes(0))
|
|
1244
|
+
return { kind: "binary" };
|
|
1245
|
+
return { kind: "text", content: buf.toString("utf8") };
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/walk.ts
|
|
1249
|
+
import { readFileSync as readFileSync6, readdirSync } from "node:fs";
|
|
1250
|
+
import { join as join4, relative, sep } from "node:path";
|
|
1251
|
+
import ignore from "ignore";
|
|
1252
|
+
var ALWAYS_IGNORED = new Set([".git", ".jj", ".hg", ".svn", "node_modules"]);
|
|
1253
|
+
function loadGitignore(absDir, prefix) {
|
|
1254
|
+
let text;
|
|
1255
|
+
try {
|
|
1256
|
+
text = readFileSync6(join4(absDir, ".gitignore"), "utf8");
|
|
1257
|
+
} catch {
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
return { prefix, matcher: ignore().add(text) };
|
|
1261
|
+
}
|
|
1262
|
+
function isIgnored(scopes, relPath, isDir) {
|
|
1263
|
+
let ignored = false;
|
|
1264
|
+
for (const scope of scopes) {
|
|
1265
|
+
let sub;
|
|
1266
|
+
if (scope.prefix === "")
|
|
1267
|
+
sub = relPath;
|
|
1268
|
+
else if (relPath.startsWith(`${scope.prefix}/`))
|
|
1269
|
+
sub = relPath.slice(scope.prefix.length + 1);
|
|
1270
|
+
else
|
|
1271
|
+
continue;
|
|
1272
|
+
const verdict = scope.matcher.test(isDir ? `${sub}/` : sub);
|
|
1273
|
+
if (verdict.ignored)
|
|
1274
|
+
ignored = true;
|
|
1275
|
+
else if (verdict.unignored)
|
|
1276
|
+
ignored = false;
|
|
1277
|
+
}
|
|
1278
|
+
return ignored;
|
|
1279
|
+
}
|
|
1280
|
+
function* walkFiles(root, base = root) {
|
|
1281
|
+
const scopes = [];
|
|
1282
|
+
const relRoot = relative(base, root).split(sep).join("/");
|
|
1283
|
+
if (relRoot !== "" && !relRoot.startsWith("..")) {
|
|
1284
|
+
let absDir = base;
|
|
1285
|
+
let prefix = "";
|
|
1286
|
+
const own = loadGitignore(base, "");
|
|
1287
|
+
if (own !== null)
|
|
1288
|
+
scopes.push(own);
|
|
1289
|
+
const segments = relRoot.split("/");
|
|
1290
|
+
for (const segment of segments.slice(0, -1)) {
|
|
1291
|
+
absDir = join4(absDir, segment);
|
|
1292
|
+
prefix = prefix === "" ? segment : `${prefix}/${segment}`;
|
|
1293
|
+
const scope = loadGitignore(absDir, prefix);
|
|
1294
|
+
if (scope !== null)
|
|
1295
|
+
scopes.push(scope);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
const stack = [{ dir: root, rel: relRoot === "." ? "" : relRoot, scopes }];
|
|
1299
|
+
while (stack.length > 0) {
|
|
1300
|
+
const frame = stack.pop();
|
|
1301
|
+
if (frame === undefined)
|
|
1302
|
+
break;
|
|
1303
|
+
let entries;
|
|
1304
|
+
try {
|
|
1305
|
+
entries = readdirSync(frame.dir, { withFileTypes: true });
|
|
1306
|
+
} catch {
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
const own = loadGitignore(frame.dir, frame.rel);
|
|
1310
|
+
const active = own === null ? frame.scopes : [...frame.scopes, own];
|
|
1311
|
+
for (const entry of entries) {
|
|
1312
|
+
const rel = frame.rel === "" ? entry.name : `${frame.rel}/${entry.name}`;
|
|
1313
|
+
if (entry.isDirectory()) {
|
|
1314
|
+
if (ALWAYS_IGNORED.has(entry.name))
|
|
1315
|
+
continue;
|
|
1316
|
+
if (isIgnored(active, rel, true))
|
|
1317
|
+
continue;
|
|
1318
|
+
stack.push({ dir: join4(frame.dir, entry.name), rel, scopes: active });
|
|
1319
|
+
} else if (entry.isFile()) {
|
|
1320
|
+
if (entry.name === ".git")
|
|
1321
|
+
continue;
|
|
1322
|
+
if (isIgnored(active, rel, false))
|
|
1323
|
+
continue;
|
|
1324
|
+
yield rel;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/workspace.ts
|
|
1331
|
+
import { isAbsolute, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
1332
|
+
function resolveWithin(root, path) {
|
|
1333
|
+
const abs = isAbsolute(path) ? resolve2(path) : resolve2(root, path);
|
|
1334
|
+
if (abs !== root && !abs.startsWith(root + sep2)) {
|
|
1335
|
+
throw new Error(`Path escapes the workspace root (${root}): ${path}`);
|
|
1336
|
+
}
|
|
1337
|
+
return abs;
|
|
1338
|
+
}
|
|
1339
|
+
function relativizeWithin(root, abs) {
|
|
1340
|
+
const rel = relative2(root, abs);
|
|
1341
|
+
return rel === "" ? "." : rel.split(sep2).join("/");
|
|
1342
|
+
}
|
|
1343
|
+
function createWorkspace(root) {
|
|
1344
|
+
const abs = resolve2(root);
|
|
1345
|
+
return {
|
|
1346
|
+
root: abs,
|
|
1347
|
+
resolve: (path) => resolveWithin(abs, path),
|
|
1348
|
+
relativize: (path) => relativizeWithin(abs, path)
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/workspace-io.ts
|
|
1353
|
+
function createLocalIo(root) {
|
|
1354
|
+
return {
|
|
1355
|
+
async stat(abs) {
|
|
1356
|
+
try {
|
|
1357
|
+
const st = statSync2(abs);
|
|
1358
|
+
return { isFile: st.isFile(), size: st.size, mtimeMs: st.mtimeMs };
|
|
1359
|
+
} catch {
|
|
1360
|
+
return null;
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
async readFile(abs) {
|
|
1364
|
+
try {
|
|
1365
|
+
return readFileSync7(abs);
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
if (isMissingFileError(err))
|
|
1368
|
+
return null;
|
|
1369
|
+
throw err;
|
|
1370
|
+
}
|
|
1371
|
+
},
|
|
1372
|
+
async writeFile(abs, content) {
|
|
1373
|
+
mkdirSync4(dirname3(abs), { recursive: true });
|
|
1374
|
+
writeFileSync3(abs, content);
|
|
1375
|
+
},
|
|
1376
|
+
async listFiles(scope) {
|
|
1377
|
+
if (scope === undefined) {
|
|
1378
|
+
return listGitFiles(root) ?? walkFiles(root);
|
|
1379
|
+
}
|
|
1380
|
+
const rel = relativizeWithin(root, scope);
|
|
1381
|
+
return listGitFiles(root, rel) ?? walkFiles(scope, root);
|
|
1382
|
+
},
|
|
1383
|
+
async search(options) {
|
|
1384
|
+
return searchLocal(root, options);
|
|
1385
|
+
}
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function localIoProvider(root) {
|
|
1389
|
+
const io = createLocalIo(root);
|
|
1390
|
+
return () => io;
|
|
1391
|
+
}
|
|
1392
|
+
function isMissingFileError(err) {
|
|
1393
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
1394
|
+
}
|
|
1395
|
+
async function searchLocal(root, options) {
|
|
1396
|
+
const re = new RegExp(options.pattern, options.ignoreCase ? "i" : "");
|
|
1397
|
+
const globRe = options.glob ? globToRegExp(options.glob) : null;
|
|
1398
|
+
let candidates;
|
|
1399
|
+
if (options.scope !== undefined) {
|
|
1400
|
+
const rel = relativizeWithin(root, options.scope);
|
|
1401
|
+
let isFile = false;
|
|
1402
|
+
try {
|
|
1403
|
+
isFile = statSync2(options.scope).isFile();
|
|
1404
|
+
} catch {
|
|
1405
|
+
isFile = false;
|
|
1406
|
+
}
|
|
1407
|
+
candidates = isFile ? [rel] : listGitFiles(root, rel) ?? walkFiles(options.scope, root);
|
|
1408
|
+
} else {
|
|
1409
|
+
candidates = listGitFiles(root) ?? walkFiles(root);
|
|
1410
|
+
}
|
|
1411
|
+
const matches = [];
|
|
1412
|
+
let stopped = false;
|
|
1413
|
+
let skippedLargeFiles = 0;
|
|
1414
|
+
scan:
|
|
1415
|
+
for (const file of candidates) {
|
|
1416
|
+
if (globRe && !globRe.test(file))
|
|
1417
|
+
continue;
|
|
1418
|
+
const read = readTextForSearch(join5(root, file));
|
|
1419
|
+
if (read.kind === "too-large") {
|
|
1420
|
+
skippedLargeFiles += 1;
|
|
1421
|
+
continue;
|
|
1422
|
+
}
|
|
1423
|
+
if (read.kind !== "text")
|
|
1424
|
+
continue;
|
|
1425
|
+
const lines = read.content.split(`
|
|
1426
|
+
`);
|
|
1427
|
+
for (const [index, line] of lines.entries()) {
|
|
1428
|
+
if (!re.test(line))
|
|
1429
|
+
continue;
|
|
1430
|
+
matches.push({ file, line: index + 1, text: line });
|
|
1431
|
+
if (matches.length >= options.maxMatches) {
|
|
1432
|
+
stopped = "max-matches";
|
|
1433
|
+
break scan;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return { matches, stopped, skippedLargeFiles };
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/edit.ts
|
|
1441
|
+
function createEditTool(opts) {
|
|
1442
|
+
const { workspace, noun } = opts;
|
|
1443
|
+
const io = opts.io ?? localIoProvider(workspace.root);
|
|
1444
|
+
return defineTool3({
|
|
1445
|
+
description: "Replace an exact string in an existing file. By default old_string must occur exactly once — include enough surrounding context to make it unique. Set replace_all to replace every occurrence (e.g. renaming a symbol).",
|
|
1446
|
+
inputSchema: z3.object({
|
|
1447
|
+
path: z3.string().min(1).describe(`File path, relative to the ${noun} root.`),
|
|
1448
|
+
old_string: z3.string().min(1).describe("Exact text to replace; must currently exist in the file."),
|
|
1449
|
+
new_string: z3.string().describe("Text to replace it with."),
|
|
1450
|
+
replace_all: z3.boolean().optional().describe("Replace every occurrence instead of requiring a single match.")
|
|
1451
|
+
}),
|
|
1452
|
+
async execute({ path, old_string, new_string, replace_all }, ctx) {
|
|
1453
|
+
const abs = workspace.resolve(path);
|
|
1454
|
+
const rel = workspace.relativize(abs);
|
|
1455
|
+
const fio = io(ctx);
|
|
1456
|
+
return withPathLock(abs, async () => {
|
|
1457
|
+
const bytes = await fio.readFile(abs);
|
|
1458
|
+
if (bytes === null)
|
|
1459
|
+
throw new Error(`${rel} does not exist.`);
|
|
1460
|
+
const before = bytes.toString("utf8");
|
|
1461
|
+
const count = before.split(old_string).length - 1;
|
|
1462
|
+
if (count === 0)
|
|
1463
|
+
throw new Error(`old_string not found in ${rel}.`);
|
|
1464
|
+
if (count > 1 && !replace_all) {
|
|
1465
|
+
throw new Error(`old_string is not unique in ${rel} (${count} matches). Add surrounding context or set replace_all.`);
|
|
1466
|
+
}
|
|
1467
|
+
const after = before.split(old_string).join(new_string);
|
|
1468
|
+
await fio.writeFile(abs, after);
|
|
1469
|
+
return { ok: true, path: rel, replacements: count };
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/glob.ts
|
|
1476
|
+
import { defineTool as defineTool4 } from "eve/tools";
|
|
1477
|
+
import { z as z4 } from "zod";
|
|
1478
|
+
function createGlobTool(opts) {
|
|
1479
|
+
const { workspace, noun } = opts;
|
|
1480
|
+
const io = opts.io ?? localIoProvider(workspace.root);
|
|
1481
|
+
return defineTool4({
|
|
1482
|
+
description: `Find files in the ${noun} by glob pattern, returning ${noun}-relative paths. \`**\` spans directories, \`*\` matches within a path segment. A pattern without a leading \`**/\` is matched at any depth (so \`*.ts\` finds .ts files anywhere). Gitignored files and build/VCS dirs are skipped.`,
|
|
1483
|
+
inputSchema: z4.object({
|
|
1484
|
+
pattern: z4.string().min(1).describe("Glob pattern, e.g. `**/*.ts` or `src/tools/*.ts`."),
|
|
1485
|
+
limit: z4.number().int().positive().optional().describe("Max paths to return (default 500).")
|
|
1486
|
+
}),
|
|
1487
|
+
async execute({ pattern, limit }, ctx) {
|
|
1488
|
+
const normalized = pattern.startsWith("**/") || pattern.startsWith("/") ? pattern.replace(/^\//, "") : `**/${pattern}`;
|
|
1489
|
+
const re = globToRegExp(normalized);
|
|
1490
|
+
const max = limit ?? 500;
|
|
1491
|
+
const candidates = await io(ctx).listFiles();
|
|
1492
|
+
const files = [];
|
|
1493
|
+
let truncated = false;
|
|
1494
|
+
for (const file of candidates) {
|
|
1495
|
+
if (re.test(file)) {
|
|
1496
|
+
if (files.length >= max) {
|
|
1497
|
+
truncated = true;
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
files.push(file);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return {
|
|
1504
|
+
pattern,
|
|
1505
|
+
count: files.length,
|
|
1506
|
+
truncated,
|
|
1507
|
+
files,
|
|
1508
|
+
...truncated ? { note: `More matches exist beyond the first ${max}. Use a more specific pattern, or raise limit.` } : {}
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/grep.ts
|
|
1515
|
+
import { defineTool as defineTool5 } from "eve/tools";
|
|
1516
|
+
import { z as z5 } from "zod";
|
|
1517
|
+
import { join as join6 } from "node:path";
|
|
1518
|
+
var GREP_SPILL_MAX_MATCHES = 5000;
|
|
1519
|
+
var MATCH_TEXT_MAX_CHARS = 300;
|
|
1520
|
+
function createGrepTool(opts) {
|
|
1521
|
+
const { workspace, noun, spillDir } = opts;
|
|
1522
|
+
const io = opts.io ?? localIoProvider(workspace.root);
|
|
1523
|
+
return defineTool5({
|
|
1524
|
+
description: `Search ${noun} file contents by regular expression, returning matching lines with their file and line number. Scope with \`path\` (a file or directory) and/or a \`glob\` on the filename. Gitignored files, build/VCS dirs, binaries, and files over ~1.5 MB are skipped.${spillDir === undefined ? "" : " When more lines match than max_results, the collected matches are saved to a file named in the result (the note says whether that list is complete) — read or grep that file instead of re-searching."}`,
|
|
1525
|
+
inputSchema: z5.object({
|
|
1526
|
+
pattern: z5.string().min(1).describe("JavaScript regular expression to search for."),
|
|
1527
|
+
path: z5.string().optional().describe(`A file or directory (relative to the ${noun} root) to limit the search to.`),
|
|
1528
|
+
glob: z5.string().optional().describe("Only search files whose path matches this glob, e.g. `**/*.ts`."),
|
|
1529
|
+
ignore_case: z5.boolean().optional().describe("Case-insensitive match."),
|
|
1530
|
+
max_results: z5.number().int().positive().optional().describe("Max matching lines (default 200).")
|
|
1531
|
+
}),
|
|
1532
|
+
async execute({ pattern, path, glob, ignore_case, max_results }, ctx) {
|
|
1533
|
+
try {
|
|
1534
|
+
new RegExp(pattern, ignore_case ? "i" : "");
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1537
|
+
throw new Error(`Invalid regular expression: ${reason}`);
|
|
1538
|
+
}
|
|
1539
|
+
const max = max_results ?? 200;
|
|
1540
|
+
const fio = io(ctx);
|
|
1541
|
+
let scope;
|
|
1542
|
+
if (path) {
|
|
1543
|
+
const abs = workspace.resolve(path);
|
|
1544
|
+
const stat = await fio.stat(abs);
|
|
1545
|
+
if (stat === null) {
|
|
1546
|
+
throw new Error(`${workspace.relativize(abs)} does not exist.`);
|
|
1547
|
+
}
|
|
1548
|
+
scope = abs;
|
|
1549
|
+
}
|
|
1550
|
+
const cap = spillDir === undefined ? Math.min(max + 1, GREP_SPILL_MAX_MATCHES) : GREP_SPILL_MAX_MATCHES;
|
|
1551
|
+
const searched = await fio.search({
|
|
1552
|
+
pattern,
|
|
1553
|
+
ignoreCase: ignore_case ?? false,
|
|
1554
|
+
scope,
|
|
1555
|
+
glob,
|
|
1556
|
+
maxMatches: cap
|
|
1557
|
+
});
|
|
1558
|
+
const clip = (m) => ({
|
|
1559
|
+
file: m.file,
|
|
1560
|
+
line: m.line,
|
|
1561
|
+
text: m.text.slice(0, MATCH_TEXT_MAX_CHARS)
|
|
1562
|
+
});
|
|
1563
|
+
const matches = searched.matches.slice(0, max).map(clip);
|
|
1564
|
+
const skipped = searched.skippedLargeFiles === null ? {} : { skippedLargeFiles: searched.skippedLargeFiles };
|
|
1565
|
+
const hitHardBound = searched.stopped === "max-matches" && cap === GREP_SPILL_MAX_MATCHES;
|
|
1566
|
+
const floodCut = searched.stopped === "output-cap";
|
|
1567
|
+
if (searched.stopped === false && searched.matches.length <= max) {
|
|
1568
|
+
return { pattern, count: matches.length, truncated: false, ...skipped, matches };
|
|
1569
|
+
}
|
|
1570
|
+
if (spillDir === undefined && !hitHardBound && !floodCut) {
|
|
1571
|
+
return {
|
|
1572
|
+
pattern,
|
|
1573
|
+
count: matches.length,
|
|
1574
|
+
truncated: true,
|
|
1575
|
+
...skipped,
|
|
1576
|
+
matches,
|
|
1577
|
+
note: `Stopped at ${max} matching lines — more matches may exist. Narrow with path/glob or a more specific pattern, or raise max_results.`
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
const allLines = searched.matches.map((m) => `${m.file}:${m.line}: ${m.text.slice(0, MATCH_TEXT_MAX_CHARS)}`);
|
|
1581
|
+
let label = null;
|
|
1582
|
+
if (spillDir !== undefined) {
|
|
1583
|
+
const spillPath = join6(spillDir, `grep-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.txt`);
|
|
1584
|
+
try {
|
|
1585
|
+
await fio.writeFile(spillPath, allLines.join(`
|
|
1586
|
+
`) + `
|
|
1587
|
+
`);
|
|
1588
|
+
label = workspace.relativize(spillPath);
|
|
1589
|
+
} catch {}
|
|
1590
|
+
}
|
|
1591
|
+
const found = floodCut ? `Search output hit the transfer cap after ${allLines.length} matching lines — more matches may exist` : hitHardBound ? `Stopped scanning at ${GREP_SPILL_MAX_MATCHES} matching lines` : `Found ${allLines.length} matching lines`;
|
|
1592
|
+
const spillIsComplete = !floodCut && !hitHardBound;
|
|
1593
|
+
const where = label === null ? "Narrow with path/glob or a more specific pattern, or raise max_results." : `The ${spillIsComplete ? "complete list is" : "matches collected so far are"} at ${label} — read or grep that file, or narrow with path/glob.`;
|
|
1594
|
+
return {
|
|
1595
|
+
pattern,
|
|
1596
|
+
count: matches.length,
|
|
1597
|
+
totalMatches: allLines.length,
|
|
1598
|
+
truncated: true,
|
|
1599
|
+
...skipped,
|
|
1600
|
+
matches,
|
|
1601
|
+
note: `${found} — showing the first ${matches.length} here. ${where}`
|
|
1602
|
+
};
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/read.ts
|
|
1608
|
+
import { defineTool as defineTool6 } from "eve/tools";
|
|
1609
|
+
import { z as z6 } from "zod";
|
|
1610
|
+
import { basename } from "node:path";
|
|
1611
|
+
|
|
1612
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/file-kind.ts
|
|
1613
|
+
import { extname } from "node:path";
|
|
1614
|
+
function imageMediaType(format) {
|
|
1615
|
+
return `image/${format}`;
|
|
1616
|
+
}
|
|
1617
|
+
function videoMediaType(format) {
|
|
1618
|
+
switch (format) {
|
|
1619
|
+
case "mp4":
|
|
1620
|
+
return "video/mp4";
|
|
1621
|
+
case "mov":
|
|
1622
|
+
return "video/quicktime";
|
|
1623
|
+
case "webm":
|
|
1624
|
+
return "video/webm";
|
|
1625
|
+
case "mkv":
|
|
1626
|
+
return "video/x-matroska";
|
|
1627
|
+
case "avi":
|
|
1628
|
+
return "video/x-msvideo";
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function audioMediaType(format) {
|
|
1632
|
+
switch (format) {
|
|
1633
|
+
case "mp3":
|
|
1634
|
+
return "audio/mpeg";
|
|
1635
|
+
case "wav":
|
|
1636
|
+
return "audio/wav";
|
|
1637
|
+
case "ogg":
|
|
1638
|
+
return "audio/ogg";
|
|
1639
|
+
case "flac":
|
|
1640
|
+
return "audio/flac";
|
|
1641
|
+
case "m4a":
|
|
1642
|
+
return "audio/mp4";
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
var BINARY_SNIFF_BYTES2 = 8192;
|
|
1646
|
+
function startsWith(buf, bytes, at = 0) {
|
|
1647
|
+
if (buf.length < at + bytes.length)
|
|
1648
|
+
return false;
|
|
1649
|
+
for (const [i, b] of bytes.entries())
|
|
1650
|
+
if (buf[at + i] !== b)
|
|
1651
|
+
return false;
|
|
1652
|
+
return true;
|
|
1653
|
+
}
|
|
1654
|
+
var PDF_MAGIC = [37, 80, 68, 70, 45];
|
|
1655
|
+
var PNG_MAGIC = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
1656
|
+
var JPEG_MAGIC = [255, 216, 255];
|
|
1657
|
+
var GIF_MAGIC = [71, 73, 70, 56];
|
|
1658
|
+
var RIFF_MAGIC = [82, 73, 70, 70];
|
|
1659
|
+
var WEBP_TAG = [87, 69, 66, 80];
|
|
1660
|
+
var AVI_TAG = [65, 86, 73, 32];
|
|
1661
|
+
var WAVE_TAG = [87, 65, 86, 69];
|
|
1662
|
+
var FTYP_TAG = [102, 116, 121, 112];
|
|
1663
|
+
var EBML_MAGIC = [26, 69, 223, 163];
|
|
1664
|
+
var ID3_MAGIC = [73, 68, 51];
|
|
1665
|
+
var OGG_MAGIC = [79, 103, 103, 83];
|
|
1666
|
+
var FLAC_MAGIC = [102, 76, 97, 67];
|
|
1667
|
+
var ZIP_MAGIC = [80, 75, 3, 4];
|
|
1668
|
+
var CFB_MAGIC = [208, 207, 17, 224, 161, 177, 26, 225];
|
|
1669
|
+
var UTF16LE_BOM = [255, 254];
|
|
1670
|
+
var UTF16BE_BOM = [254, 255];
|
|
1671
|
+
var BMFF_IMAGE_BRANDS = new Set([
|
|
1672
|
+
"avif",
|
|
1673
|
+
"avis",
|
|
1674
|
+
"heic",
|
|
1675
|
+
"heix",
|
|
1676
|
+
"heim",
|
|
1677
|
+
"heis",
|
|
1678
|
+
"hevc",
|
|
1679
|
+
"hevx",
|
|
1680
|
+
"mif1",
|
|
1681
|
+
"msf1"
|
|
1682
|
+
]);
|
|
1683
|
+
var BMFF_AUDIO_BRANDS = new Set(["M4A ", "M4B ", "M4P "]);
|
|
1684
|
+
var BMFF_AUDIO_EXTENSIONS = new Set([".m4a", ".m4b", ".m4p"]);
|
|
1685
|
+
var BMFF_FTYP_SCAN_CAP = 256;
|
|
1686
|
+
function bmffBrands(buf) {
|
|
1687
|
+
const boxSize = buf.length >= 4 ? buf.readUInt32BE(0) : 0;
|
|
1688
|
+
const end = Math.min(boxSize, buf.length, BMFF_FTYP_SCAN_CAP);
|
|
1689
|
+
const brands = [buf.subarray(8, 12).toString("latin1")];
|
|
1690
|
+
for (let off = 16;off + 4 <= end; off += 4) {
|
|
1691
|
+
brands.push(buf.subarray(off, off + 4).toString("latin1"));
|
|
1692
|
+
}
|
|
1693
|
+
return brands;
|
|
1694
|
+
}
|
|
1695
|
+
function bmffKind(buf, path) {
|
|
1696
|
+
const brands = bmffBrands(buf);
|
|
1697
|
+
const major = brands[0] ?? "";
|
|
1698
|
+
const imageBrand = brands.find((brand) => BMFF_IMAGE_BRANDS.has(brand.toLowerCase()));
|
|
1699
|
+
if (imageBrand !== undefined) {
|
|
1700
|
+
return {
|
|
1701
|
+
kind: "binary",
|
|
1702
|
+
description: `an HEIF/AVIF image (brand "${imageBrand.trim()}") with no supported renderer — convert it to PNG or JPEG`
|
|
1703
|
+
};
|
|
1704
|
+
}
|
|
1705
|
+
if (brands.some((brand) => BMFF_AUDIO_BRANDS.has(brand))) {
|
|
1706
|
+
return { kind: "audio", format: "m4a" };
|
|
1707
|
+
}
|
|
1708
|
+
if (major.startsWith("qt"))
|
|
1709
|
+
return { kind: "video", format: "mov" };
|
|
1710
|
+
if (BMFF_AUDIO_EXTENSIONS.has(extname(path).toLowerCase())) {
|
|
1711
|
+
return { kind: "audio", format: "m4a" };
|
|
1712
|
+
}
|
|
1713
|
+
return { kind: "video", format: "mp4" };
|
|
1714
|
+
}
|
|
1715
|
+
function ebmlKind(buf) {
|
|
1716
|
+
const header = buf.subarray(0, 64).toString("latin1");
|
|
1717
|
+
if (header.includes("webm"))
|
|
1718
|
+
return { kind: "video", format: "webm" };
|
|
1719
|
+
return { kind: "video", format: "mkv" };
|
|
1720
|
+
}
|
|
1721
|
+
function zipKind(path) {
|
|
1722
|
+
switch (extname(path).toLowerCase()) {
|
|
1723
|
+
case ".docx":
|
|
1724
|
+
return { kind: "docx" };
|
|
1725
|
+
case ".xlsx":
|
|
1726
|
+
return { kind: "sheet", format: "xlsx" };
|
|
1727
|
+
case ".xlsm":
|
|
1728
|
+
return { kind: "sheet", format: "xlsm" };
|
|
1729
|
+
case ".ods":
|
|
1730
|
+
return { kind: "sheet", format: "ods" };
|
|
1731
|
+
case ".pptx":
|
|
1732
|
+
return { kind: "binary", description: "a PowerPoint deck (.pptx) with no text extractor" };
|
|
1733
|
+
case ".odt":
|
|
1734
|
+
return {
|
|
1735
|
+
kind: "binary",
|
|
1736
|
+
description: "an OpenDocument text file (.odt) with no text extractor"
|
|
1737
|
+
};
|
|
1738
|
+
case ".epub":
|
|
1739
|
+
return { kind: "binary", description: "an EPUB e-book with no text extractor" };
|
|
1740
|
+
default:
|
|
1741
|
+
return { kind: "binary", description: "a zip archive" };
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
function cfbKind(path) {
|
|
1745
|
+
switch (extname(path).toLowerCase()) {
|
|
1746
|
+
case ".xls":
|
|
1747
|
+
return { kind: "sheet", format: "xls" };
|
|
1748
|
+
case ".doc":
|
|
1749
|
+
return {
|
|
1750
|
+
kind: "binary",
|
|
1751
|
+
description: "a legacy Word document (.doc) with no text extractor — convert it to .docx"
|
|
1752
|
+
};
|
|
1753
|
+
case ".ppt":
|
|
1754
|
+
return {
|
|
1755
|
+
kind: "binary",
|
|
1756
|
+
description: "a legacy PowerPoint deck (.ppt) with no text extractor — convert it to .pptx"
|
|
1757
|
+
};
|
|
1758
|
+
default:
|
|
1759
|
+
return { kind: "binary", description: "a legacy Office (CFB) container" };
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
function detectFileKind(buf, path) {
|
|
1763
|
+
if (startsWith(buf, PDF_MAGIC))
|
|
1764
|
+
return { kind: "pdf" };
|
|
1765
|
+
if (startsWith(buf, PNG_MAGIC))
|
|
1766
|
+
return { kind: "image", format: "png" };
|
|
1767
|
+
if (startsWith(buf, JPEG_MAGIC))
|
|
1768
|
+
return { kind: "image", format: "jpeg" };
|
|
1769
|
+
if (startsWith(buf, GIF_MAGIC))
|
|
1770
|
+
return { kind: "image", format: "gif" };
|
|
1771
|
+
if (startsWith(buf, RIFF_MAGIC)) {
|
|
1772
|
+
if (startsWith(buf, WEBP_TAG, 8))
|
|
1773
|
+
return { kind: "image", format: "webp" };
|
|
1774
|
+
if (startsWith(buf, AVI_TAG, 8))
|
|
1775
|
+
return { kind: "video", format: "avi" };
|
|
1776
|
+
if (startsWith(buf, WAVE_TAG, 8))
|
|
1777
|
+
return { kind: "audio", format: "wav" };
|
|
1778
|
+
}
|
|
1779
|
+
if (startsWith(buf, FTYP_TAG, 4))
|
|
1780
|
+
return bmffKind(buf, path);
|
|
1781
|
+
if (startsWith(buf, EBML_MAGIC))
|
|
1782
|
+
return ebmlKind(buf);
|
|
1783
|
+
if (startsWith(buf, OGG_MAGIC))
|
|
1784
|
+
return { kind: "audio", format: "ogg" };
|
|
1785
|
+
if (startsWith(buf, FLAC_MAGIC))
|
|
1786
|
+
return { kind: "audio", format: "flac" };
|
|
1787
|
+
if (startsWith(buf, ID3_MAGIC))
|
|
1788
|
+
return { kind: "audio", format: "mp3" };
|
|
1789
|
+
if (buf.length >= 2 && buf[0] === 255 && ((buf[1] ?? 0) & 224) === 224 && extname(path).toLowerCase() === ".mp3") {
|
|
1790
|
+
return { kind: "audio", format: "mp3" };
|
|
1791
|
+
}
|
|
1792
|
+
if (startsWith(buf, ZIP_MAGIC))
|
|
1793
|
+
return zipKind(path);
|
|
1794
|
+
if (startsWith(buf, CFB_MAGIC))
|
|
1795
|
+
return cfbKind(path);
|
|
1796
|
+
if (startsWith(buf, UTF16LE_BOM))
|
|
1797
|
+
return { kind: "text", encoding: "utf16le" };
|
|
1798
|
+
if (startsWith(buf, UTF16BE_BOM))
|
|
1799
|
+
return { kind: "text", encoding: "utf16be" };
|
|
1800
|
+
if (buf.subarray(0, BINARY_SNIFF_BYTES2).includes(0)) {
|
|
1801
|
+
return { kind: "binary", description: "binary data (unrecognized format)" };
|
|
1802
|
+
}
|
|
1803
|
+
return { kind: "text", encoding: "utf8" };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/file-view.ts
|
|
1807
|
+
var READ_FILE_DEFAULT_LINE_LIMIT = 2000;
|
|
1808
|
+
var READ_FILE_MAX_LINE_CHARS = 2000;
|
|
1809
|
+
var READ_FILE_MAX_CONTENT_CHARS = 50000;
|
|
1810
|
+
var READ_FILE_MAX_BYTES = 1e7;
|
|
1811
|
+
function buildFileView(text, opts = {}) {
|
|
1812
|
+
const lines = text.split(`
|
|
1813
|
+
`);
|
|
1814
|
+
const start = opts.offset ? opts.offset - 1 : 0;
|
|
1815
|
+
const requestedEnd = Math.min(start + (opts.limit ?? READ_FILE_DEFAULT_LINE_LIMIT), lines.length);
|
|
1816
|
+
const parts = [];
|
|
1817
|
+
let chars = 0;
|
|
1818
|
+
let included = 0;
|
|
1819
|
+
let clippedLine = false;
|
|
1820
|
+
let budgetStopped = false;
|
|
1821
|
+
for (let i = start;i < requestedEnd; i++) {
|
|
1822
|
+
const raw = lines[i];
|
|
1823
|
+
if (raw === undefined)
|
|
1824
|
+
break;
|
|
1825
|
+
const clipped = raw.length > READ_FILE_MAX_LINE_CHARS ? `${raw.slice(0, READ_FILE_MAX_LINE_CHARS)}… [line truncated]` : raw;
|
|
1826
|
+
if (clipped !== raw)
|
|
1827
|
+
clippedLine = true;
|
|
1828
|
+
const numbered = `${String(i + 1).padStart(6)}|${clipped}`;
|
|
1829
|
+
if (included > 0 && chars + numbered.length + 1 > READ_FILE_MAX_CONTENT_CHARS) {
|
|
1830
|
+
budgetStopped = true;
|
|
1831
|
+
break;
|
|
1832
|
+
}
|
|
1833
|
+
parts.push(numbered);
|
|
1834
|
+
chars += numbered.length + 1;
|
|
1835
|
+
included += 1;
|
|
1836
|
+
}
|
|
1837
|
+
const endLine = start + included;
|
|
1838
|
+
const note = included === 0 && start >= lines.length ? `Offset ${start + 1} is past the end of the file (${lines.length} lines).` : endLine < lines.length ? `Showing lines ${start + 1}–${endLine} of ${lines.length}${budgetStopped ? " (output budget reached)" : ""}. Continue with offset=${endLine + 1}, or use grep to locate what you need.` : null;
|
|
1839
|
+
return {
|
|
1840
|
+
totalLines: lines.length,
|
|
1841
|
+
startLine: start + 1,
|
|
1842
|
+
endLine,
|
|
1843
|
+
content: parts.join(`
|
|
1844
|
+
`),
|
|
1845
|
+
truncated: budgetStopped || clippedLine,
|
|
1846
|
+
note
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/read-file-content.ts
|
|
1851
|
+
import { imageSize } from "image-size";
|
|
1852
|
+
|
|
1853
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/extract/cache.ts
|
|
1854
|
+
function createStatCache(limit) {
|
|
1855
|
+
const entries = new Map;
|
|
1856
|
+
return {
|
|
1857
|
+
async get(key, id, compute) {
|
|
1858
|
+
const hit = entries.get(key);
|
|
1859
|
+
if (hit !== undefined && hit.id.mtimeMs === id.mtimeMs && hit.id.size === id.size) {
|
|
1860
|
+
entries.delete(key);
|
|
1861
|
+
entries.set(key, hit);
|
|
1862
|
+
return hit.value;
|
|
1863
|
+
}
|
|
1864
|
+
const value = await compute();
|
|
1865
|
+
entries.delete(key);
|
|
1866
|
+
entries.set(key, { id, value });
|
|
1867
|
+
if (entries.size > limit) {
|
|
1868
|
+
const oldest = entries.keys().next();
|
|
1869
|
+
if (!oldest.done)
|
|
1870
|
+
entries.delete(oldest.value);
|
|
1871
|
+
}
|
|
1872
|
+
return value;
|
|
1873
|
+
},
|
|
1874
|
+
size: () => entries.size
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/extract/docx.ts
|
|
1879
|
+
import mammoth from "mammoth";
|
|
1880
|
+
async function extractDocx(buffer) {
|
|
1881
|
+
try {
|
|
1882
|
+
const result = await mammoth.extractRawText({ buffer });
|
|
1883
|
+
return { ok: true, text: result.value.replace(/\n+$/, `
|
|
1884
|
+
`) };
|
|
1885
|
+
} catch (error) {
|
|
1886
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/extract/pdf.ts
|
|
1891
|
+
import { openPdf } from "clawpdf";
|
|
1892
|
+
var PDF_EMPTY_PAGE_NOTE = "[no text on this page — likely scanned or image-only; rendered pages cannot be attached]";
|
|
1893
|
+
var PDF_PAGE_CAP = 200;
|
|
1894
|
+
async function extractPdf(bytes, options = {}) {
|
|
1895
|
+
const pageCap = options.pageCap ?? PDF_PAGE_CAP;
|
|
1896
|
+
let pdf;
|
|
1897
|
+
try {
|
|
1898
|
+
pdf = await openPdf(bytes);
|
|
1899
|
+
} catch (error) {
|
|
1900
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
1901
|
+
}
|
|
1902
|
+
try {
|
|
1903
|
+
const pages = pdf.pageCount;
|
|
1904
|
+
const extracted = Math.min(pages, pageCap);
|
|
1905
|
+
const parts = [];
|
|
1906
|
+
for (let n = 1;n <= extracted; n++) {
|
|
1907
|
+
const text = pdf.page(n).text().replaceAll(`\r
|
|
1908
|
+
`, `
|
|
1909
|
+
`).trim();
|
|
1910
|
+
parts.push(`=== page ${n} of ${pages} ===`);
|
|
1911
|
+
parts.push(text.length > 0 ? text : PDF_EMPTY_PAGE_NOTE);
|
|
1912
|
+
}
|
|
1913
|
+
if (extracted < pages) {
|
|
1914
|
+
parts.push(`[extraction stopped at the page cap — ${extracted} of ${pages} pages extracted]`);
|
|
1915
|
+
}
|
|
1916
|
+
return { ok: true, text: parts.join(`
|
|
1917
|
+
`), pages };
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
1920
|
+
} finally {
|
|
1921
|
+
await pdf[Symbol.asyncDispose]();
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/extract/sheet.ts
|
|
1926
|
+
import { read, utils } from "xlsx";
|
|
1927
|
+
var SHEET_ROW_CAP = 5000;
|
|
1928
|
+
function extractSheets(buffer, rowCap = SHEET_ROW_CAP) {
|
|
1929
|
+
let workbook;
|
|
1930
|
+
try {
|
|
1931
|
+
workbook = read(buffer, { type: "buffer", dense: true, sheetRows: rowCap + 1 });
|
|
1932
|
+
} catch (error) {
|
|
1933
|
+
return { ok: false, reason: error instanceof Error ? error.message : String(error) };
|
|
1934
|
+
}
|
|
1935
|
+
const parts = [];
|
|
1936
|
+
const sheets = [];
|
|
1937
|
+
for (const name of workbook.SheetNames) {
|
|
1938
|
+
const sheet = workbook.Sheets[name];
|
|
1939
|
+
if (sheet === undefined)
|
|
1940
|
+
continue;
|
|
1941
|
+
const ref = sheet["!fullref"] ?? sheet["!ref"];
|
|
1942
|
+
const range = typeof ref === "string" ? utils.decode_range(ref) : null;
|
|
1943
|
+
const rows = range === null ? 0 : range.e.r - range.s.r + 1;
|
|
1944
|
+
const cols = range === null ? 0 : range.e.c - range.s.c + 1;
|
|
1945
|
+
sheets.push({ name, rows, cols });
|
|
1946
|
+
parts.push(`=== sheet "${name}" (${rows} rows x ${cols} cols) ===`);
|
|
1947
|
+
if (rows === 0) {
|
|
1948
|
+
parts.push("[empty sheet]");
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
const tsv = utils.sheet_to_csv(sheet, { FS: "\t", blankrows: false }).replace(/\n+$/, "");
|
|
1952
|
+
const lines = tsv.split(`
|
|
1953
|
+
`);
|
|
1954
|
+
if (lines.length > rowCap) {
|
|
1955
|
+
parts.push(lines.slice(0, rowCap).join(`
|
|
1956
|
+
`));
|
|
1957
|
+
parts.push(`[sheet truncated at ${rowCap} rows]`);
|
|
1958
|
+
} else {
|
|
1959
|
+
parts.push(tsv);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
return { ok: true, text: parts.join(`
|
|
1963
|
+
`), sheets };
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/read-file-content.ts
|
|
1967
|
+
var EXTRACTION_CACHE_LIMIT = 20;
|
|
1968
|
+
var extractionCache = createStatCache(EXTRACTION_CACHE_LIMIT);
|
|
1969
|
+
function decodeText(buffer, encoding) {
|
|
1970
|
+
switch (encoding) {
|
|
1971
|
+
case "utf8":
|
|
1972
|
+
return buffer.toString("utf8");
|
|
1973
|
+
case "utf16le":
|
|
1974
|
+
return buffer.toString("utf16le").replace(/^\uFEFF/, "");
|
|
1975
|
+
case "utf16be": {
|
|
1976
|
+
const swapped = Buffer.from(buffer).swap16();
|
|
1977
|
+
return swapped.toString("utf16le").replace(/^\uFEFF/, "");
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
async function extractDocument(detected, buffer, path) {
|
|
1982
|
+
switch (detected.kind) {
|
|
1983
|
+
case "pdf": {
|
|
1984
|
+
const result = await extractPdf(buffer);
|
|
1985
|
+
if (!result.ok)
|
|
1986
|
+
throw new Error(`Could not extract text from PDF ${path}: ${result.reason}`);
|
|
1987
|
+
return { kind: "pdf", text: result.text, pages: result.pages };
|
|
1988
|
+
}
|
|
1989
|
+
case "docx": {
|
|
1990
|
+
const result = await extractDocx(buffer);
|
|
1991
|
+
if (!result.ok) {
|
|
1992
|
+
throw new Error(`Could not extract text from DOCX ${path}: ${result.reason}`);
|
|
1993
|
+
}
|
|
1994
|
+
return { kind: "docx", text: result.text };
|
|
1995
|
+
}
|
|
1996
|
+
case "sheet": {
|
|
1997
|
+
const result = extractSheets(buffer);
|
|
1998
|
+
if (!result.ok) {
|
|
1999
|
+
throw new Error(`Could not extract data from spreadsheet ${path}: ${result.reason}`);
|
|
2000
|
+
}
|
|
2001
|
+
return { kind: "sheet", format: detected.format, text: result.text, sheets: result.sheets };
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
async function loadFileContent(buffer, path, id) {
|
|
2006
|
+
const detected = detectFileKind(buffer, path);
|
|
2007
|
+
switch (detected.kind) {
|
|
2008
|
+
case "text":
|
|
2009
|
+
return { kind: "text", text: decodeText(buffer, detected.encoding) };
|
|
2010
|
+
case "pdf":
|
|
2011
|
+
case "docx":
|
|
2012
|
+
case "sheet":
|
|
2013
|
+
return extractionCache.get(path, id, () => extractDocument(detected, buffer, path));
|
|
2014
|
+
case "image": {
|
|
2015
|
+
let size = null;
|
|
2016
|
+
try {
|
|
2017
|
+
size = imageSize(buffer);
|
|
2018
|
+
} catch {
|
|
2019
|
+
size = null;
|
|
2020
|
+
}
|
|
2021
|
+
return {
|
|
2022
|
+
kind: "image",
|
|
2023
|
+
format: detected.format,
|
|
2024
|
+
width: size?.width ?? null,
|
|
2025
|
+
height: size?.height ?? null
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
case "video":
|
|
2029
|
+
return { kind: "video", format: detected.format };
|
|
2030
|
+
case "audio":
|
|
2031
|
+
return { kind: "audio", format: detected.format };
|
|
2032
|
+
case "binary":
|
|
2033
|
+
throw new Error(`${path} is ${detected.description} — read returns text only. ` + "Use bash (unzip -l, strings, xxd) to inspect it if needed.");
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/read.ts
|
|
2038
|
+
function buildMediaHint(attach, verb) {
|
|
2039
|
+
const kinds = ["image", "video", "audio"];
|
|
2040
|
+
const on = kinds.filter((kind) => attach[kind]);
|
|
2041
|
+
const off = kinds.filter((kind) => !attach[kind]);
|
|
2042
|
+
const list = (items) => items.join(" or ");
|
|
2043
|
+
if (on.length === 0) {
|
|
2044
|
+
return `${verb} media (images, video, audio) returns metadata only`;
|
|
2045
|
+
}
|
|
2046
|
+
const queued = `${verb} ${list(on)} files returns metadata and queues the file to appear as a viewable attachment on your next message`;
|
|
2047
|
+
return off.length === 0 ? queued : `${queued} (${list(off)} ${verb === "reading" ? "reads" : "fetches"} return metadata only)`;
|
|
2048
|
+
}
|
|
2049
|
+
function createReadTool(opts) {
|
|
2050
|
+
const { workspace, noun, attachImagesToChat, maxInlineImageBytes, dirConventions } = opts;
|
|
2051
|
+
const io = opts.io ?? localIoProvider(workspace.root);
|
|
2052
|
+
const attachVideoToChat = opts.attachVideoToChat ?? false;
|
|
2053
|
+
const attachAudioToChat = opts.attachAudioToChat ?? false;
|
|
2054
|
+
const maxInlineMediaBytes = opts.maxInlineMediaBytes ?? DEFAULT_MAX_INLINE_MEDIA_BYTES;
|
|
2055
|
+
const oversizeHint = opts.oversizeHint ?? "Use bash (head, sed -n, rg) to extract the part you need.";
|
|
2056
|
+
const imageUnavailableHint = opts.imageUnavailableHint ?? "If you need to see this image, ask the user to attach it to the chat.";
|
|
2057
|
+
const mediaUnavailableHint = opts.mediaUnavailableHint ?? "If you need its contents, extract what you can with bash (e.g. ffmpeg frames from a video, read as images) or ask the user about it.";
|
|
2058
|
+
const conventionsHint = dirConventions ? ` When a read first enters a directory with its own ${dirConventions.fileName} conventions file, the result includes it under directory_conventions (once per directory per session) — honor those conventions for work in that directory.` : "";
|
|
2059
|
+
const mediaHint = buildMediaHint({ image: attachImagesToChat, video: attachVideoToChat, audio: attachAudioToChat }, "reading");
|
|
2060
|
+
const editHint = opts.includeEditGuidance ?? true ? " Read a file before editing it so your edits target the current text." : "";
|
|
2061
|
+
return defineTool6({
|
|
2062
|
+
description: `Read a file from the ${noun}, returning line-numbered text. PDF, DOCX, and spreadsheet files (.xlsx, .xlsm, .xls, .ods) are converted to plain text (PDFs get per-page markers, spreadsheets render as TSV per sheet); ${mediaHint}.${editHint} Returns up to 2000 lines per call by default; page bigger files with offset/limit.` + conventionsHint,
|
|
2063
|
+
inputSchema: z6.object({
|
|
2064
|
+
path: z6.string().min(1).describe(`File path, relative to the ${noun} root.`),
|
|
2065
|
+
offset: z6.number().int().positive().optional().describe("1-based line to start reading from."),
|
|
2066
|
+
limit: z6.number().int().positive().optional().describe("Max number of lines to return.")
|
|
2067
|
+
}),
|
|
2068
|
+
async execute({ path, offset, limit }, ctx) {
|
|
2069
|
+
const abs = workspace.resolve(path);
|
|
2070
|
+
const rel = workspace.relativize(abs);
|
|
2071
|
+
const fio = io(ctx);
|
|
2072
|
+
const stat = await fio.stat(abs);
|
|
2073
|
+
if (stat === null)
|
|
2074
|
+
throw new Error(`${rel} does not exist.`);
|
|
2075
|
+
if (!stat.isFile) {
|
|
2076
|
+
throw new Error(`${rel} is not a regular file. Use glob to list a directory.`);
|
|
2077
|
+
}
|
|
2078
|
+
if (stat.size > READ_FILE_MAX_BYTES) {
|
|
2079
|
+
throw new Error(`${rel} is ${stat.size} bytes — too large to read (max ${READ_FILE_MAX_BYTES}). ` + oversizeHint);
|
|
2080
|
+
}
|
|
2081
|
+
const buffer = await fio.readFile(abs);
|
|
2082
|
+
if (buffer === null)
|
|
2083
|
+
throw new Error(`${rel} does not exist.`);
|
|
2084
|
+
const content = await loadFileContent(buffer, rel, {
|
|
2085
|
+
mtimeMs: stat.mtimeMs,
|
|
2086
|
+
size: stat.size
|
|
2087
|
+
});
|
|
2088
|
+
const riders = await (async () => {
|
|
2089
|
+
try {
|
|
2090
|
+
return await dirConventions?.tracker.collect(ctx?.session?.id, rel, async (absPath) => {
|
|
2091
|
+
const bytes = await fio.readFile(absPath);
|
|
2092
|
+
return bytes === null ? null : bytes.toString("utf8");
|
|
2093
|
+
}) ?? [];
|
|
2094
|
+
} catch {
|
|
2095
|
+
return [];
|
|
2096
|
+
}
|
|
2097
|
+
})();
|
|
2098
|
+
const conventions = riders.length > 0 ? { directory_conventions: riders } : {};
|
|
2099
|
+
switch (content.kind) {
|
|
2100
|
+
case "text":
|
|
2101
|
+
return {
|
|
2102
|
+
path: rel,
|
|
2103
|
+
...buildFileView(content.text, { offset, limit }),
|
|
2104
|
+
...conventions
|
|
2105
|
+
};
|
|
2106
|
+
case "pdf":
|
|
2107
|
+
return {
|
|
2108
|
+
path: rel,
|
|
2109
|
+
source: "pdf",
|
|
2110
|
+
pages: content.pages,
|
|
2111
|
+
...buildFileView(content.text, { offset, limit }),
|
|
2112
|
+
...conventions
|
|
2113
|
+
};
|
|
2114
|
+
case "docx":
|
|
2115
|
+
return {
|
|
2116
|
+
path: rel,
|
|
2117
|
+
source: "docx",
|
|
2118
|
+
...buildFileView(content.text, { offset, limit }),
|
|
2119
|
+
...conventions
|
|
2120
|
+
};
|
|
2121
|
+
case "sheet":
|
|
2122
|
+
return {
|
|
2123
|
+
path: rel,
|
|
2124
|
+
source: "sheet",
|
|
2125
|
+
format: content.format,
|
|
2126
|
+
sheets: content.sheets,
|
|
2127
|
+
...buildFileView(content.text, { offset, limit }),
|
|
2128
|
+
...conventions
|
|
2129
|
+
};
|
|
2130
|
+
case "image": {
|
|
2131
|
+
const meta = {
|
|
2132
|
+
path: rel,
|
|
2133
|
+
source: "image",
|
|
2134
|
+
format: content.format,
|
|
2135
|
+
width: content.width,
|
|
2136
|
+
height: content.height,
|
|
2137
|
+
bytes: stat.size
|
|
2138
|
+
};
|
|
2139
|
+
if (!attachImagesToChat || stat.size > maxInlineImageBytes) {
|
|
2140
|
+
const why = attachImagesToChat && stat.size > maxInlineImageBytes ? `too large to attach automatically (${stat.size} bytes, max ${maxInlineImageBytes})` : "cannot be returned as a tool result (text/json only), and image attachments are not enabled for this agent";
|
|
2141
|
+
return {
|
|
2142
|
+
...meta,
|
|
2143
|
+
note: `Image content ${why}. ${imageUnavailableHint}`,
|
|
2144
|
+
...conventions
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
const attachment = {
|
|
2148
|
+
kind: "image",
|
|
2149
|
+
dataUrl: `data:${imageMediaType(content.format)};base64,${buffer.toString("base64")}`,
|
|
2150
|
+
mediaType: imageMediaType(content.format),
|
|
2151
|
+
filename: basename(rel),
|
|
2152
|
+
width: content.width,
|
|
2153
|
+
height: content.height
|
|
2154
|
+
};
|
|
2155
|
+
return {
|
|
2156
|
+
...meta,
|
|
2157
|
+
note: "This image is queued and will be attached to your next message as a viewable image — no need to ask the user to attach it.",
|
|
2158
|
+
[CHAT_ATTACHMENT_FIELD]: attachment,
|
|
2159
|
+
...conventions
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
case "video":
|
|
2163
|
+
case "audio": {
|
|
2164
|
+
const kind = content.kind;
|
|
2165
|
+
const mediaType = kind === "video" ? videoMediaType(content.format) : audioMediaType(content.format);
|
|
2166
|
+
const meta = {
|
|
2167
|
+
path: rel,
|
|
2168
|
+
source: kind,
|
|
2169
|
+
format: content.format,
|
|
2170
|
+
mediaType,
|
|
2171
|
+
bytes: stat.size
|
|
2172
|
+
};
|
|
2173
|
+
const label = kind === "video" ? "Video" : "Audio";
|
|
2174
|
+
const enabled = kind === "video" ? attachVideoToChat : attachAudioToChat;
|
|
2175
|
+
if (!enabled || stat.size > maxInlineMediaBytes) {
|
|
2176
|
+
const why = enabled && stat.size > maxInlineMediaBytes ? `too large to attach automatically (${stat.size} bytes, max ${maxInlineMediaBytes})` : `cannot be returned as a tool result (text/json only), and ${kind} attachments are not enabled for this agent`;
|
|
2177
|
+
return {
|
|
2178
|
+
...meta,
|
|
2179
|
+
note: `${label} content ${why}. ${mediaUnavailableHint}`,
|
|
2180
|
+
...conventions
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
const attachment = {
|
|
2184
|
+
kind,
|
|
2185
|
+
dataUrl: `data:${mediaType};base64,${buffer.toString("base64")}`,
|
|
2186
|
+
mediaType,
|
|
2187
|
+
filename: basename(rel)
|
|
2188
|
+
};
|
|
2189
|
+
return {
|
|
2190
|
+
...meta,
|
|
2191
|
+
note: `This ${kind} file is queued and will be attached to your next message — no need to ask the user to attach it.`,
|
|
2192
|
+
[CHAT_ATTACHMENT_FIELD]: attachment,
|
|
2193
|
+
...conventions
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
toModelOutput(output) {
|
|
2199
|
+
if (typeof output === "object" && output !== null && CHAT_ATTACHMENT_FIELD in output) {
|
|
2200
|
+
const { [CHAT_ATTACHMENT_FIELD]: _omitted, ...rest } = output;
|
|
2201
|
+
return { type: "json", value: rest };
|
|
2202
|
+
}
|
|
2203
|
+
return { type: "json", value: output };
|
|
2204
|
+
}
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/tasks.ts
|
|
2209
|
+
import { defineDynamic as defineDynamic2, defineTool as defineTool7 } from "eve/tools";
|
|
2210
|
+
import { z as z7 } from "zod";
|
|
2211
|
+
var DEFAULT_WAIT_MS = 120000;
|
|
2212
|
+
function elapsedMs(task) {
|
|
2213
|
+
const end = task.status === "running" ? Date.now() : task.finishedAt;
|
|
2214
|
+
return end - task.startedAt;
|
|
2215
|
+
}
|
|
2216
|
+
function peek(task) {
|
|
2217
|
+
return {
|
|
2218
|
+
task_id: task.id,
|
|
2219
|
+
tool: task.tool,
|
|
2220
|
+
label: task.label,
|
|
2221
|
+
status: task.status,
|
|
2222
|
+
elapsedMs: elapsedMs(task),
|
|
2223
|
+
...task.progress !== undefined ? { progress: task.progress } : {},
|
|
2224
|
+
...task.status === "error" || task.status === "lost" ? { error: task.error } : {}
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
function full(task) {
|
|
2228
|
+
const base = {
|
|
2229
|
+
task_id: task.id,
|
|
2230
|
+
tool: task.tool,
|
|
2231
|
+
label: task.label,
|
|
2232
|
+
status: task.status,
|
|
2233
|
+
elapsedMs: elapsedMs(task)
|
|
2234
|
+
};
|
|
2235
|
+
if (task.status === "done")
|
|
2236
|
+
return { ...base, result: task.result };
|
|
2237
|
+
if (task.status === "error" || task.status === "lost")
|
|
2238
|
+
return { ...base, error: task.error };
|
|
2239
|
+
return { ...base, ...task.progress !== undefined ? { progress: task.progress } : {} };
|
|
2240
|
+
}
|
|
2241
|
+
function buildTasksToolset(opts) {
|
|
2242
|
+
const { registry, backgroundables } = opts;
|
|
2243
|
+
const [firstOp, ...restOps] = backgroundables;
|
|
2244
|
+
if (!firstOp)
|
|
2245
|
+
return null;
|
|
2246
|
+
const toolNames = [firstOp.name, ...restOps.map((o) => o.name)];
|
|
2247
|
+
const catalog = backgroundables.map((o) => `- ${o.name}: ${o.description}
|
|
2248
|
+
input: ${JSON.stringify(o.inputJsonSchema)}`).join(`
|
|
2249
|
+
`);
|
|
2250
|
+
const wrap = createSteerWrapper(opts.steerInbox ?? null);
|
|
2251
|
+
return {
|
|
2252
|
+
run_async: wrap(defineTool7({
|
|
2253
|
+
description: `Start a tool running in the BACKGROUND and return immediately with a task id, instead of blocking until it finishes. Use it for long work whose result your next step doesn't need yet (tests, builds, installs) so you can keep working in parallel; poll with check_tasks and collect the result with await_task. If your very next step needs the output, just call the tool directly instead. For work where you only care about a specific output signal, pass notify — matching lines are delivered to you as a message while you're idle, instead of you polling.
|
|
2254
|
+
|
|
2255
|
+
Backgroundable tools (pass \`input\` matching the tool's own schema):
|
|
2256
|
+
` + catalog,
|
|
2257
|
+
inputSchema: z7.object({
|
|
2258
|
+
tool: z7.enum(toolNames).describe("Which backgroundable tool to run."),
|
|
2259
|
+
input: z7.record(z7.string(), z7.unknown()).describe("Arguments for that tool — the same object you'd pass calling it directly."),
|
|
2260
|
+
notify: z7.object({
|
|
2261
|
+
pattern: z7.string().min(1).describe("Regex matched against complete output lines."),
|
|
2262
|
+
reason: z7.string().min(1).describe("Short phrase naming what you're watching for, e.g. 'build errors'."),
|
|
2263
|
+
debounce_ms: z7.number().int().positive().optional().describe("Minimum ms between match notifications (default 5000).")
|
|
2264
|
+
}).optional().describe("Watch the task's output: matching lines are delivered to you as a message while you're idle."),
|
|
2265
|
+
notify_on_complete: z7.boolean().optional().describe("Also deliver a message when the task settles (default false; await_task remains the primary way to collect results).")
|
|
2266
|
+
}),
|
|
2267
|
+
execute({ tool, input, notify, notify_on_complete }, ctx) {
|
|
2268
|
+
const op = backgroundables.find((o) => o.name === tool);
|
|
2269
|
+
if (!op)
|
|
2270
|
+
throw new Error(`Unknown backgroundable tool: ${tool}`);
|
|
2271
|
+
const sessionId = ctx?.session?.id;
|
|
2272
|
+
const watcher = notify ? createOutputWatcher({ pattern: notify.pattern, debounceMs: notify.debounce_ms }) : null;
|
|
2273
|
+
let post = null;
|
|
2274
|
+
const early = [];
|
|
2275
|
+
const { label, work, progress } = op.start(input, watcher ? {
|
|
2276
|
+
onOutput: (chunk) => {
|
|
2277
|
+
const matches = watcher.feed(chunk);
|
|
2278
|
+
if (!matches)
|
|
2279
|
+
return;
|
|
2280
|
+
if (post)
|
|
2281
|
+
post(matches);
|
|
2282
|
+
else
|
|
2283
|
+
early.push(matches);
|
|
2284
|
+
}
|
|
2285
|
+
} : undefined);
|
|
2286
|
+
const taskId = registry.spawnTask(tool, label, work);
|
|
2287
|
+
if (watcher && notify && sessionId) {
|
|
2288
|
+
let matchCount = 0;
|
|
2289
|
+
post = (lines) => {
|
|
2290
|
+
if (!lines || lines.length === 0)
|
|
2291
|
+
return;
|
|
2292
|
+
matchCount += 1;
|
|
2293
|
+
postParkNotification(sessionId, {
|
|
2294
|
+
key: `${taskId}#watch${matchCount}`,
|
|
2295
|
+
text: formatWatchNotification({ taskId, label, reason: notify.reason, lines })
|
|
2296
|
+
});
|
|
2297
|
+
};
|
|
2298
|
+
for (const batch of early.splice(0))
|
|
2299
|
+
post(batch);
|
|
2300
|
+
work.finally(() => post?.(watcher.flush())).catch(() => {
|
|
2301
|
+
return;
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
if (notify_on_complete && sessionId) {
|
|
2305
|
+
work.then(() => postParkNotification(sessionId, {
|
|
2306
|
+
key: `${taskId}#done`,
|
|
2307
|
+
text: formatCompletionNotification({ taskId, label, status: "done" })
|
|
2308
|
+
}), (err) => postParkNotification(sessionId, {
|
|
2309
|
+
key: `${taskId}#done`,
|
|
2310
|
+
text: formatCompletionNotification({
|
|
2311
|
+
taskId,
|
|
2312
|
+
label,
|
|
2313
|
+
status: "error",
|
|
2314
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2315
|
+
})
|
|
2316
|
+
}));
|
|
2317
|
+
}
|
|
2318
|
+
if (progress) {
|
|
2319
|
+
registry.updateTaskProgress(taskId, progress());
|
|
2320
|
+
const interval = setInterval(() => registry.updateTaskProgress(taskId, progress()), 500);
|
|
2321
|
+
work.finally(() => clearInterval(interval)).catch(() => {
|
|
2322
|
+
return;
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
return {
|
|
2326
|
+
task_id: taskId,
|
|
2327
|
+
tool,
|
|
2328
|
+
status: "running",
|
|
2329
|
+
...watcher ? { watching: notify?.pattern } : {},
|
|
2330
|
+
note: "Started in the background. If your next actions don't depend on this, keep working and call check_tasks / await_task later; otherwise call await_task now."
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
})),
|
|
2334
|
+
check_tasks: wrap(defineTool7({
|
|
2335
|
+
description: "List background tasks and their status without blocking; returns `runningCount` plus the task list. For tasks that support progress (notably bash), includes a live stdout/stderr preview. Call await_task to collect a task's final result.",
|
|
2336
|
+
inputSchema: z7.object({}),
|
|
2337
|
+
execute() {
|
|
2338
|
+
const tasks = registry.listTasks().map(peek);
|
|
2339
|
+
return { runningCount: tasks.filter((t) => t.status === "running").length, tasks };
|
|
2340
|
+
}
|
|
2341
|
+
})),
|
|
2342
|
+
await_task: wrap(defineTool7({
|
|
2343
|
+
description: "Block until a background task finishes (up to wait_ms), then return its full result. Use it when your next step needs the task's final output. If the wait elapses while it's still running, returns the running status plus any live progress so you can decide to keep waiting or move on.",
|
|
2344
|
+
inputSchema: z7.object({
|
|
2345
|
+
task_id: z7.string().min(1).describe("Task id returned by run_async or a backgrounded bash call."),
|
|
2346
|
+
wait_ms: z7.number().int().positive().optional().describe(`Max time to block in ms (default ${DEFAULT_WAIT_MS}).`)
|
|
2347
|
+
}),
|
|
2348
|
+
async execute({ task_id, wait_ms }) {
|
|
2349
|
+
const task = await registry.awaitTask(task_id, wait_ms ?? DEFAULT_WAIT_MS);
|
|
2350
|
+
if (!task)
|
|
2351
|
+
throw new Error(`No such task: ${task_id}`);
|
|
2352
|
+
return full(task);
|
|
2353
|
+
}
|
|
2354
|
+
}))
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
function createTasksTools(opts) {
|
|
2358
|
+
return defineDynamic2({
|
|
2359
|
+
events: {
|
|
2360
|
+
"session.started": () => buildTasksToolset(opts)
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/webfetch.ts
|
|
2366
|
+
import { defineTool as defineTool8 } from "eve/tools";
|
|
2367
|
+
import { z as z8 } from "zod";
|
|
2368
|
+
import { basename as basename2, join as join7 } from "node:path";
|
|
2369
|
+
|
|
2370
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/web-fetch.ts
|
|
2371
|
+
import { Parser } from "htmlparser2";
|
|
2372
|
+
import { parseHTML as parseHTML2 } from "linkedom";
|
|
2373
|
+
import TurndownService from "turndown";
|
|
2374
|
+
|
|
2375
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/web-page.ts
|
|
2376
|
+
import Defuddle from "defuddle";
|
|
2377
|
+
import { parseHTML } from "linkedom";
|
|
2378
|
+
var asField = (value) => {
|
|
2379
|
+
if (typeof value !== "string")
|
|
2380
|
+
return null;
|
|
2381
|
+
const trimmed = value.trim();
|
|
2382
|
+
return trimmed === "" ? null : trimmed;
|
|
2383
|
+
};
|
|
2384
|
+
function visibleTextLength(html) {
|
|
2385
|
+
return html.replace(/<(script|style|noscript)\b[\s\S]*?<\/\1\s*>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().length;
|
|
2386
|
+
}
|
|
2387
|
+
function extractMainContent(html, url) {
|
|
2388
|
+
let parsed;
|
|
2389
|
+
try {
|
|
2390
|
+
const { document } = parseHTML(html);
|
|
2391
|
+
parsed = new Defuddle(document, {
|
|
2392
|
+
url
|
|
2393
|
+
}).parse();
|
|
2394
|
+
} catch {
|
|
2395
|
+
return null;
|
|
2396
|
+
}
|
|
2397
|
+
const contentHtml = typeof parsed.content === "string" ? parsed.content : "";
|
|
2398
|
+
const extractedLength = visibleTextLength(contentHtml);
|
|
2399
|
+
if (extractedLength === 0)
|
|
2400
|
+
return null;
|
|
2401
|
+
if (extractedLength < 500 && extractedLength * 4 < visibleTextLength(html))
|
|
2402
|
+
return null;
|
|
2403
|
+
return {
|
|
2404
|
+
contentHtml,
|
|
2405
|
+
title: asField(parsed.title),
|
|
2406
|
+
author: asField(parsed.author),
|
|
2407
|
+
published: asField(parsed.published),
|
|
2408
|
+
site: asField(parsed.site)
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
function buildMetadataHeader(page) {
|
|
2412
|
+
const byline = [
|
|
2413
|
+
page.author === null ? null : `By ${page.author}`,
|
|
2414
|
+
page.site,
|
|
2415
|
+
page.published === null ? null : `Published ${page.published}`
|
|
2416
|
+
].filter((part) => part !== null).join(" · ");
|
|
2417
|
+
const lines = [];
|
|
2418
|
+
if (page.title !== null)
|
|
2419
|
+
lines.push(`# ${page.title}`);
|
|
2420
|
+
if (byline !== "")
|
|
2421
|
+
lines.push(`> ${byline}`);
|
|
2422
|
+
return lines.length === 0 ? null : lines.join(`
|
|
2423
|
+
|
|
2424
|
+
`);
|
|
2425
|
+
}
|
|
2426
|
+
var JS_RENDERED_DOMAIN_HINTS = [
|
|
2427
|
+
{
|
|
2428
|
+
suffixes: ["x.com", "twitter.com"],
|
|
2429
|
+
hint: "X (Twitter) renders posts with client-side JavaScript and blocks anonymous scraping, so a plain fetch can't see the post."
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
suffixes: ["reddit.com"],
|
|
2433
|
+
hint: "Reddit renders client-side — fetch the JSON view (append .json to the post URL) or the same path on old.reddit.com instead."
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
suffixes: ["linkedin.com"],
|
|
2437
|
+
hint: "LinkedIn requires login; anonymous fetches get a wall instead of the content."
|
|
2438
|
+
},
|
|
2439
|
+
{
|
|
2440
|
+
suffixes: ["instagram.com", "threads.net", "tiktok.com", "facebook.com"],
|
|
2441
|
+
hint: "This site renders client-side and gates content behind login, so a plain fetch can't see it."
|
|
2442
|
+
},
|
|
2443
|
+
{
|
|
2444
|
+
suffixes: ["youtube.com", "youtu.be"],
|
|
2445
|
+
hint: "YouTube pages are a client-rendered player; the video itself isn't fetchable as text."
|
|
2446
|
+
}
|
|
2447
|
+
];
|
|
2448
|
+
function jsRenderedDomainHint(url) {
|
|
2449
|
+
let hostname;
|
|
2450
|
+
try {
|
|
2451
|
+
hostname = new URL(url).hostname.toLowerCase();
|
|
2452
|
+
} catch {
|
|
2453
|
+
return null;
|
|
2454
|
+
}
|
|
2455
|
+
for (const { suffixes, hint } of JS_RENDERED_DOMAIN_HINTS) {
|
|
2456
|
+
for (const suffix of suffixes) {
|
|
2457
|
+
if (hostname === suffix || hostname.endsWith(`.${suffix}`))
|
|
2458
|
+
return hint;
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
return null;
|
|
2462
|
+
}
|
|
2463
|
+
var COLLAPSE_RENDERED_MAX_CHARS = 200;
|
|
2464
|
+
var COLLAPSE_HTML_MIN_CHARS = 2000;
|
|
2465
|
+
function buildContentCollapseNote(opts) {
|
|
2466
|
+
if (opts.renderedChars >= COLLAPSE_RENDERED_MAX_CHARS)
|
|
2467
|
+
return null;
|
|
2468
|
+
if (opts.documentTextChars >= COLLAPSE_RENDERED_MAX_CHARS)
|
|
2469
|
+
return null;
|
|
2470
|
+
if (opts.htmlChars < COLLAPSE_HTML_MIN_CHARS)
|
|
2471
|
+
return null;
|
|
2472
|
+
const base = `The page produced almost no readable text (${opts.renderedChars} chars from ${opts.htmlChars} chars of HTML) — its content likely renders with client-side JavaScript or sits behind a login or bot wall.`;
|
|
2473
|
+
const hint = jsRenderedDomainHint(opts.url);
|
|
2474
|
+
return hint === null ? base : `${base} ${hint}`;
|
|
2475
|
+
}
|
|
2476
|
+
function looksLikeRawHtmlOutput(rendered) {
|
|
2477
|
+
if (rendered.length === 0)
|
|
2478
|
+
return false;
|
|
2479
|
+
const tags = rendered.match(/<\/?[a-z][a-z0-9-]*(?:\s[^<>]*)?>/gi);
|
|
2480
|
+
if (tags === null || tags.length < 10)
|
|
2481
|
+
return false;
|
|
2482
|
+
const tagChars = tags.reduce((sum, tag) => sum + tag.length, 0);
|
|
2483
|
+
return tagChars / rendered.length > 0.1;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/web-fetch.ts
|
|
2487
|
+
var WEB_FETCH_MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
2488
|
+
var WEB_FETCH_DEFAULT_TIMEOUT_SECONDS = 30;
|
|
2489
|
+
var WEB_FETCH_PDF_DEFAULT_TIMEOUT_SECONDS = 60;
|
|
2490
|
+
var WEB_FETCH_MAX_TIMEOUT_SECONDS = 120;
|
|
2491
|
+
var BROWSER_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36";
|
|
2492
|
+
var FALLBACK_USER_AGENT = "agent-sdk";
|
|
2493
|
+
function acceptHeader(format) {
|
|
2494
|
+
switch (format) {
|
|
2495
|
+
case "markdown":
|
|
2496
|
+
return "text/markdown;q=1.0, text/x-markdown;q=0.9, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1";
|
|
2497
|
+
case "text":
|
|
2498
|
+
return "text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1";
|
|
2499
|
+
case "html":
|
|
2500
|
+
return "text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1";
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
function buildWebFetchHeaders(format, userAgent = BROWSER_USER_AGENT) {
|
|
2504
|
+
return {
|
|
2505
|
+
Accept: acceptHeader(format),
|
|
2506
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
2507
|
+
"User-Agent": userAgent
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
function convertHtmlToMarkdown(html) {
|
|
2511
|
+
const turndown = new TurndownService({
|
|
2512
|
+
headingStyle: "atx",
|
|
2513
|
+
hr: "---",
|
|
2514
|
+
bulletListMarker: "-",
|
|
2515
|
+
codeBlockStyle: "fenced",
|
|
2516
|
+
emDelimiter: "*"
|
|
2517
|
+
});
|
|
2518
|
+
turndown.remove(["script", "style", "meta", "link"]);
|
|
2519
|
+
const { document } = parseHTML2(`<x-turndown id="turndown-root">${html}</x-turndown>`);
|
|
2520
|
+
const root = document.getElementById("turndown-root");
|
|
2521
|
+
if (root === null)
|
|
2522
|
+
throw new Error("unreachable: the turndown-root wrapper always parses");
|
|
2523
|
+
return turndown.turndown(root);
|
|
2524
|
+
}
|
|
2525
|
+
var SKIPPED_HTML_TAGS = ["script", "style", "noscript", "iframe", "object", "embed"];
|
|
2526
|
+
function extractTextFromHtml(html) {
|
|
2527
|
+
let text = "";
|
|
2528
|
+
const skipStack = [];
|
|
2529
|
+
const parser = new Parser({
|
|
2530
|
+
onopentag(name) {
|
|
2531
|
+
if (skipStack.length > 0 || SKIPPED_HTML_TAGS.includes(name)) {
|
|
2532
|
+
skipStack.push(name);
|
|
2533
|
+
}
|
|
2534
|
+
},
|
|
2535
|
+
ontext(input) {
|
|
2536
|
+
if (skipStack.length === 0)
|
|
2537
|
+
text += input;
|
|
2538
|
+
},
|
|
2539
|
+
onclosetag(name) {
|
|
2540
|
+
if (skipStack.length > 0 && skipStack[skipStack.length - 1] === name) {
|
|
2541
|
+
skipStack.pop();
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
});
|
|
2545
|
+
parser.write(html);
|
|
2546
|
+
parser.end();
|
|
2547
|
+
return text.split(`
|
|
2548
|
+
`).map((line) => line.replace(/[ \t]+/g, " ").trim()).join(`
|
|
2549
|
+
`).replace(/\n{3,}/g, `
|
|
2550
|
+
|
|
2551
|
+
`).trim();
|
|
2552
|
+
}
|
|
2553
|
+
function isHtmlContentType(contentType) {
|
|
2554
|
+
const mime = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
2555
|
+
return mime === "text/html" || mime === "application/xhtml+xml";
|
|
2556
|
+
}
|
|
2557
|
+
function looksLikeHtml(content) {
|
|
2558
|
+
const trimmed = content.trimStart().slice(0, 512).toLowerCase();
|
|
2559
|
+
return trimmed.startsWith("<!doctype html") || trimmed.startsWith("<html") || /^<(!--|head|body|div|p|h[1-6]|span|a|ul|ol|li|table|script|style)\b/.test(trimmed);
|
|
2560
|
+
}
|
|
2561
|
+
function renderWebText(content, contentType, format, url) {
|
|
2562
|
+
const isHtml = isHtmlContentType(contentType) || looksLikeHtml(content);
|
|
2563
|
+
if (!isHtml || format === "html")
|
|
2564
|
+
return { text: content };
|
|
2565
|
+
const page = extractMainContent(content, url);
|
|
2566
|
+
const bodyHtml = page === null ? content : page.contentHtml;
|
|
2567
|
+
const converted = format === "markdown" ? convertHtmlToMarkdown(bodyHtml) : extractTextFromHtml(bodyHtml);
|
|
2568
|
+
const header = page === null ? null : buildMetadataHeader(page);
|
|
2569
|
+
const text = header === null ? converted : `${header}
|
|
2570
|
+
|
|
2571
|
+
${converted}`;
|
|
2572
|
+
const notes = [];
|
|
2573
|
+
const collapse = buildContentCollapseNote({
|
|
2574
|
+
url,
|
|
2575
|
+
renderedChars: converted.trim().length,
|
|
2576
|
+
htmlChars: content.length,
|
|
2577
|
+
documentTextChars: visibleTextLength(content)
|
|
2578
|
+
});
|
|
2579
|
+
if (collapse !== null)
|
|
2580
|
+
notes.push(collapse);
|
|
2581
|
+
if (format === "markdown" && looksLikeRawHtmlOutput(converted)) {
|
|
2582
|
+
notes.push('The markdown conversion left substantial raw HTML in the output — treat it as partially converted, or re-fetch with format "html" for the true page.');
|
|
2583
|
+
}
|
|
2584
|
+
return notes.length === 0 ? { text } : { text, note: notes.join(" ") };
|
|
2585
|
+
}
|
|
2586
|
+
function assertHttpUrl(url) {
|
|
2587
|
+
let parsed;
|
|
2588
|
+
try {
|
|
2589
|
+
parsed = new URL(url);
|
|
2590
|
+
} catch {
|
|
2591
|
+
throw new Error(`Not a valid URL: ${url}`);
|
|
2592
|
+
}
|
|
2593
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2594
|
+
throw new Error("URL must start with http:// or https://");
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
async function fetchWebResource(opts) {
|
|
2598
|
+
const { url, format, timeoutMs, pdfTimeoutMs } = opts;
|
|
2599
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
2600
|
+
assertHttpUrl(url);
|
|
2601
|
+
const controller = new AbortController;
|
|
2602
|
+
const startedAt = Date.now();
|
|
2603
|
+
let deadlineMs = timeoutMs;
|
|
2604
|
+
let timer = setTimeout(() => controller.abort(), deadlineMs);
|
|
2605
|
+
const timedOut = () => new Error(`Request timed out after ${deadlineMs / 1000}s: ${url}`);
|
|
2606
|
+
try {
|
|
2607
|
+
const headers = buildWebFetchHeaders(format);
|
|
2608
|
+
let response;
|
|
2609
|
+
try {
|
|
2610
|
+
response = await fetchImpl(url, { headers, signal: controller.signal });
|
|
2611
|
+
if (response.status === 403 && response.headers.get("cf-mitigated") === "challenge") {
|
|
2612
|
+
response = await fetchImpl(url, {
|
|
2613
|
+
headers: { ...headers, "User-Agent": FALLBACK_USER_AGENT },
|
|
2614
|
+
signal: controller.signal
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
} catch (error) {
|
|
2618
|
+
if (controller.signal.aborted)
|
|
2619
|
+
throw timedOut();
|
|
2620
|
+
throw error;
|
|
2621
|
+
}
|
|
2622
|
+
if (!response.ok) {
|
|
2623
|
+
throw new Error(`Request failed with status ${response.status}: ${url}`);
|
|
2624
|
+
}
|
|
2625
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
2626
|
+
const finalUrl = response.url === "" ? url : response.url;
|
|
2627
|
+
if (pdfTimeoutMs !== undefined && pdfTimeoutMs > deadlineMs && responseLooksLikePdf(contentType, finalUrl)) {
|
|
2628
|
+
clearTimeout(timer);
|
|
2629
|
+
deadlineMs = pdfTimeoutMs;
|
|
2630
|
+
const remaining = Math.max(deadlineMs - (Date.now() - startedAt), 0);
|
|
2631
|
+
timer = setTimeout(() => controller.abort(), remaining);
|
|
2632
|
+
}
|
|
2633
|
+
const contentLength = response.headers.get("content-length");
|
|
2634
|
+
if (contentLength !== null && Number.parseInt(contentLength, 10) > WEB_FETCH_MAX_RESPONSE_BYTES) {
|
|
2635
|
+
throw new Error(`Response too large (content-length exceeds the ${WEB_FETCH_MAX_RESPONSE_BYTES}-byte limit): ${url}`);
|
|
2636
|
+
}
|
|
2637
|
+
let arrayBuffer;
|
|
2638
|
+
try {
|
|
2639
|
+
arrayBuffer = await response.arrayBuffer();
|
|
2640
|
+
} catch (error) {
|
|
2641
|
+
if (controller.signal.aborted)
|
|
2642
|
+
throw timedOut();
|
|
2643
|
+
throw error;
|
|
2644
|
+
}
|
|
2645
|
+
if (arrayBuffer.byteLength > WEB_FETCH_MAX_RESPONSE_BYTES) {
|
|
2646
|
+
throw new Error(`Response too large (exceeds the ${WEB_FETCH_MAX_RESPONSE_BYTES}-byte limit): ${url}`);
|
|
2647
|
+
}
|
|
2648
|
+
return {
|
|
2649
|
+
body: Buffer.from(arrayBuffer),
|
|
2650
|
+
contentType,
|
|
2651
|
+
finalUrl
|
|
2652
|
+
};
|
|
2653
|
+
} finally {
|
|
2654
|
+
clearTimeout(timer);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
function responseLooksLikePdf(contentType, finalUrl) {
|
|
2658
|
+
const mime = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
2659
|
+
return mime === "application/pdf" || urlLooksLikePdf(finalUrl);
|
|
2660
|
+
}
|
|
2661
|
+
function resolveWebFetchTimeoutMs(timeoutSeconds, url) {
|
|
2662
|
+
const fallback = urlLooksLikePdf(url) ? WEB_FETCH_PDF_DEFAULT_TIMEOUT_SECONDS : WEB_FETCH_DEFAULT_TIMEOUT_SECONDS;
|
|
2663
|
+
const seconds = timeoutSeconds ?? fallback;
|
|
2664
|
+
return Math.min(Math.max(seconds, 1), WEB_FETCH_MAX_TIMEOUT_SECONDS) * 1000;
|
|
2665
|
+
}
|
|
2666
|
+
function urlLooksLikePdf(url) {
|
|
2667
|
+
if (url === undefined)
|
|
2668
|
+
return false;
|
|
2669
|
+
try {
|
|
2670
|
+
return new URL(url).pathname.toLowerCase().endsWith(".pdf");
|
|
2671
|
+
} catch {
|
|
2672
|
+
return false;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/webfetch.ts
|
|
2677
|
+
var SPILL_EXTENSION = {
|
|
2678
|
+
markdown: "md",
|
|
2679
|
+
text: "txt",
|
|
2680
|
+
html: "html"
|
|
2681
|
+
};
|
|
2682
|
+
function spillFilename(format, kind) {
|
|
2683
|
+
const runId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2684
|
+
const ext = kind === "extracted" ? "txt" : SPILL_EXTENSION[format];
|
|
2685
|
+
return `webfetch-${runId}.${ext}`;
|
|
2686
|
+
}
|
|
2687
|
+
function fetchedFilename(finalUrl, fallback) {
|
|
2688
|
+
try {
|
|
2689
|
+
const name = basename2(new URL(finalUrl).pathname);
|
|
2690
|
+
return name === "" || name === "/" ? fallback : name;
|
|
2691
|
+
} catch {
|
|
2692
|
+
return fallback;
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
var ZIP_MAGIC2 = [80, 75, 3, 4];
|
|
2696
|
+
function startsWithBytes(buf, magic) {
|
|
2697
|
+
if (buf.length < magic.length)
|
|
2698
|
+
return false;
|
|
2699
|
+
for (let i = 0;i < magic.length; i++) {
|
|
2700
|
+
if (buf[i] !== magic[i])
|
|
2701
|
+
return false;
|
|
2702
|
+
}
|
|
2703
|
+
return true;
|
|
2704
|
+
}
|
|
2705
|
+
var CONTENT_TYPE_EXTENSIONS = {
|
|
2706
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
|
|
2707
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
|
|
2708
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
|
|
2709
|
+
"application/msword": ".doc",
|
|
2710
|
+
"application/vnd.ms-excel": ".xls",
|
|
2711
|
+
"application/vnd.ms-powerpoint": ".ppt",
|
|
2712
|
+
"application/vnd.oasis.opendocument.text": ".odt",
|
|
2713
|
+
"application/vnd.oasis.opendocument.spreadsheet": ".ods",
|
|
2714
|
+
"application/vnd.oasis.opendocument.presentation": ".odp",
|
|
2715
|
+
"application/pdf": ".pdf",
|
|
2716
|
+
"video/mp4": ".mp4",
|
|
2717
|
+
"video/quicktime": ".mov",
|
|
2718
|
+
"video/webm": ".webm",
|
|
2719
|
+
"video/x-matroska": ".mkv",
|
|
2720
|
+
"video/x-msvideo": ".avi",
|
|
2721
|
+
"audio/mpeg": ".mp3",
|
|
2722
|
+
"audio/wav": ".wav",
|
|
2723
|
+
"audio/x-wav": ".wav",
|
|
2724
|
+
"audio/ogg": ".ogg",
|
|
2725
|
+
"audio/flac": ".flac",
|
|
2726
|
+
"audio/mp4": ".m4a"
|
|
2727
|
+
};
|
|
2728
|
+
var LEGACY_TO_MODERN_EXT = {
|
|
2729
|
+
".doc": ".docx",
|
|
2730
|
+
".xls": ".xlsx",
|
|
2731
|
+
".ppt": ".pptx"
|
|
2732
|
+
};
|
|
2733
|
+
function pathLabelForFetch(finalUrl, contentType, body) {
|
|
2734
|
+
const pathname = new URL(finalUrl).pathname;
|
|
2735
|
+
const extMatch = pathname.match(/(\.[a-z0-9]+)$/i);
|
|
2736
|
+
if (extMatch) {
|
|
2737
|
+
const captured = extMatch[1];
|
|
2738
|
+
if (!captured)
|
|
2739
|
+
return pathname;
|
|
2740
|
+
const currentExt = captured.toLowerCase();
|
|
2741
|
+
if (startsWithBytes(body, ZIP_MAGIC2)) {
|
|
2742
|
+
const modernExt = LEGACY_TO_MODERN_EXT[currentExt];
|
|
2743
|
+
if (modernExt) {
|
|
2744
|
+
return pathname.slice(0, -currentExt.length) + modernExt;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
return pathname;
|
|
2748
|
+
}
|
|
2749
|
+
const mime = contentType.split(";", 1)[0]?.trim().toLowerCase() ?? "";
|
|
2750
|
+
let ext = CONTENT_TYPE_EXTENSIONS[mime];
|
|
2751
|
+
if (!ext)
|
|
2752
|
+
return pathname;
|
|
2753
|
+
if (startsWithBytes(body, ZIP_MAGIC2)) {
|
|
2754
|
+
ext = LEGACY_TO_MODERN_EXT[ext] ?? ext;
|
|
2755
|
+
}
|
|
2756
|
+
return pathname + ext;
|
|
2757
|
+
}
|
|
2758
|
+
function createWebFetchTool(opts) {
|
|
2759
|
+
const { workspace, spillDir, attachImagesToChat, maxInlineImageBytes, fetchImpl } = opts;
|
|
2760
|
+
const imageUnavailableHint = opts.imageUnavailableHint ?? "If you need to see this image, ask the user to attach it to the chat.";
|
|
2761
|
+
const attachVideoToChat = opts.attachVideoToChat ?? false;
|
|
2762
|
+
const attachAudioToChat = opts.attachAudioToChat ?? false;
|
|
2763
|
+
const maxInlineMediaBytes = opts.maxInlineMediaBytes ?? DEFAULT_MAX_INLINE_MEDIA_BYTES;
|
|
2764
|
+
const bounded = (text, format, kind) => {
|
|
2765
|
+
const spillPath = join7(spillDir, spillFilename(format, kind));
|
|
2766
|
+
const capture = createBoundedCapture({
|
|
2767
|
+
spillPath,
|
|
2768
|
+
spillLabel: workspace.relativize(spillPath)
|
|
2769
|
+
});
|
|
2770
|
+
capture.append(text);
|
|
2771
|
+
const snapshot = capture.snapshot();
|
|
2772
|
+
return {
|
|
2773
|
+
content: snapshot.text,
|
|
2774
|
+
totalChars: snapshot.totalChars,
|
|
2775
|
+
truncated: snapshot.truncated
|
|
2776
|
+
};
|
|
2777
|
+
};
|
|
2778
|
+
const mediaHint = buildMediaHint({ image: attachImagesToChat, video: attachVideoToChat, audio: attachAudioToChat }, "fetching");
|
|
2779
|
+
return defineTool8({
|
|
2780
|
+
description: `Fetch a URL and return its content. HTML pages are reduced to their main content (boilerplate stripped, title/author/date header) and converted to readable markdown by default (set format to "text" for plain text or "html" for the raw page). Fetched PDF, DOCX, and spreadsheet files are converted to plain text; ${mediaHint}. Content over the in-context budget is truncated head+tail and the complete output is spilled to a file named in the truncation marker — read or grep that file instead of re-fetching. Default timeout ${WEB_FETCH_DEFAULT_TIMEOUT_SECONDS}s (${WEB_FETCH_PDF_DEFAULT_TIMEOUT_SECONDS}s for PDFs), max ${WEB_FETCH_MAX_TIMEOUT_SECONDS}s; responses over 5 MB error. Read-only: one HTTP GET, no side effects.`,
|
|
2781
|
+
inputSchema: z8.object({
|
|
2782
|
+
url: z8.string().min(1).describe("The URL to fetch. Must start with http:// or https://."),
|
|
2783
|
+
format: z8.enum(["markdown", "text", "html"]).optional().describe('How to render HTML responses: "markdown" (default), "text", or "html" (raw). Non-HTML content is unaffected.'),
|
|
2784
|
+
timeout: z8.number().int().positive().optional().describe(`Timeout in seconds (default ${WEB_FETCH_DEFAULT_TIMEOUT_SECONDS}, max ${WEB_FETCH_MAX_TIMEOUT_SECONDS}).`)
|
|
2785
|
+
}),
|
|
2786
|
+
async execute({ url, format, timeout }) {
|
|
2787
|
+
const renderFormat = format ?? "markdown";
|
|
2788
|
+
const fetched = await fetchWebResource({
|
|
2789
|
+
url,
|
|
2790
|
+
format: renderFormat,
|
|
2791
|
+
timeoutMs: resolveWebFetchTimeoutMs(timeout, url),
|
|
2792
|
+
...timeout === undefined ? { pdfTimeoutMs: WEB_FETCH_PDF_DEFAULT_TIMEOUT_SECONDS * 1000 } : {},
|
|
2793
|
+
...fetchImpl !== undefined ? { fetchImpl } : {}
|
|
2794
|
+
});
|
|
2795
|
+
const { body, contentType, finalUrl } = fetched;
|
|
2796
|
+
const redirect = finalUrl !== url ? { finalUrl } : {};
|
|
2797
|
+
const meta = { url, ...redirect, contentType };
|
|
2798
|
+
const label = pathLabelForFetch(finalUrl, contentType, body);
|
|
2799
|
+
const detected = detectFileKind(body, label);
|
|
2800
|
+
if (detected.kind === "binary") {
|
|
2801
|
+
throw new Error(`Fetched content is ${detected.description} — webfetch returns text and media metadata only. ` + "Use bash (curl -o) to download it if needed.");
|
|
2802
|
+
}
|
|
2803
|
+
const content = await loadFileContent(body, label, {
|
|
2804
|
+
mtimeMs: Date.now(),
|
|
2805
|
+
size: body.byteLength
|
|
2806
|
+
});
|
|
2807
|
+
switch (content.kind) {
|
|
2808
|
+
case "text": {
|
|
2809
|
+
const rendered = renderWebText(content.text, contentType, renderFormat, finalUrl);
|
|
2810
|
+
return {
|
|
2811
|
+
...meta,
|
|
2812
|
+
format: renderFormat,
|
|
2813
|
+
...rendered.note === undefined ? {} : { note: rendered.note },
|
|
2814
|
+
...bounded(rendered.text, renderFormat, "text")
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
case "pdf":
|
|
2818
|
+
return {
|
|
2819
|
+
...meta,
|
|
2820
|
+
source: "pdf",
|
|
2821
|
+
pages: content.pages,
|
|
2822
|
+
...bounded(content.text, renderFormat, "extracted")
|
|
2823
|
+
};
|
|
2824
|
+
case "docx":
|
|
2825
|
+
return {
|
|
2826
|
+
...meta,
|
|
2827
|
+
source: "docx",
|
|
2828
|
+
...bounded(content.text, renderFormat, "extracted")
|
|
2829
|
+
};
|
|
2830
|
+
case "sheet":
|
|
2831
|
+
return {
|
|
2832
|
+
...meta,
|
|
2833
|
+
source: "sheet",
|
|
2834
|
+
sheetFormat: content.format,
|
|
2835
|
+
sheets: content.sheets,
|
|
2836
|
+
...bounded(content.text, renderFormat, "extracted")
|
|
2837
|
+
};
|
|
2838
|
+
case "image": {
|
|
2839
|
+
const imageMeta = {
|
|
2840
|
+
...meta,
|
|
2841
|
+
source: "image",
|
|
2842
|
+
imageFormat: content.format,
|
|
2843
|
+
width: content.width,
|
|
2844
|
+
height: content.height,
|
|
2845
|
+
bytes: body.byteLength
|
|
2846
|
+
};
|
|
2847
|
+
if (!attachImagesToChat || body.byteLength > maxInlineImageBytes) {
|
|
2848
|
+
const why = attachImagesToChat && body.byteLength > maxInlineImageBytes ? `too large to attach automatically (${body.byteLength} bytes, max ${maxInlineImageBytes})` : "cannot be returned as a tool result (text/json only), and image attachments are not enabled for this agent";
|
|
2849
|
+
return {
|
|
2850
|
+
...imageMeta,
|
|
2851
|
+
note: `Image content ${why}. ${imageUnavailableHint}`
|
|
2852
|
+
};
|
|
2853
|
+
}
|
|
2854
|
+
const attachment = {
|
|
2855
|
+
kind: "image",
|
|
2856
|
+
dataUrl: `data:${imageMediaType(content.format)};base64,${body.toString("base64")}`,
|
|
2857
|
+
mediaType: imageMediaType(content.format),
|
|
2858
|
+
filename: fetchedFilename(finalUrl, "image"),
|
|
2859
|
+
width: content.width,
|
|
2860
|
+
height: content.height
|
|
2861
|
+
};
|
|
2862
|
+
return {
|
|
2863
|
+
...imageMeta,
|
|
2864
|
+
note: "This image is queued and will be attached to your next message as a viewable image — no need to ask the user to attach it.",
|
|
2865
|
+
[CHAT_ATTACHMENT_FIELD]: attachment
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
case "video":
|
|
2869
|
+
case "audio": {
|
|
2870
|
+
const kind = content.kind;
|
|
2871
|
+
const mediaType = kind === "video" ? videoMediaType(content.format) : audioMediaType(content.format);
|
|
2872
|
+
const mediaMeta = {
|
|
2873
|
+
...meta,
|
|
2874
|
+
source: kind,
|
|
2875
|
+
mediaFormat: content.format,
|
|
2876
|
+
mediaType,
|
|
2877
|
+
bytes: body.byteLength
|
|
2878
|
+
};
|
|
2879
|
+
const label2 = kind === "video" ? "Video" : "Audio";
|
|
2880
|
+
const enabled = kind === "video" ? attachVideoToChat : attachAudioToChat;
|
|
2881
|
+
if (!enabled || body.byteLength > maxInlineMediaBytes) {
|
|
2882
|
+
const why = enabled && body.byteLength > maxInlineMediaBytes ? `too large to attach automatically (${body.byteLength} bytes, max ${maxInlineMediaBytes})` : `cannot be returned as a tool result (text/json only), and ${kind} attachments are not enabled for this agent`;
|
|
2883
|
+
return {
|
|
2884
|
+
...mediaMeta,
|
|
2885
|
+
note: `${label2} content ${why}. Use bash (curl -o) to download it if you need to process it.`
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
const attachment = {
|
|
2889
|
+
kind,
|
|
2890
|
+
dataUrl: `data:${mediaType};base64,${body.toString("base64")}`,
|
|
2891
|
+
mediaType,
|
|
2892
|
+
filename: fetchedFilename(finalUrl, kind)
|
|
2893
|
+
};
|
|
2894
|
+
return {
|
|
2895
|
+
...mediaMeta,
|
|
2896
|
+
note: `This ${kind} file is queued and will be attached to your next message — no need to ask the user to attach it.`,
|
|
2897
|
+
[CHAT_ATTACHMENT_FIELD]: attachment
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
},
|
|
2902
|
+
toModelOutput(output) {
|
|
2903
|
+
if (typeof output === "object" && output !== null && CHAT_ATTACHMENT_FIELD in output) {
|
|
2904
|
+
const { [CHAT_ATTACHMENT_FIELD]: _omitted, ...rest } = output;
|
|
2905
|
+
return { type: "json", value: rest };
|
|
2906
|
+
}
|
|
2907
|
+
return { type: "json", value: output };
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/tools/write.ts
|
|
2913
|
+
import { defineTool as defineTool9 } from "eve/tools";
|
|
2914
|
+
import { z as z9 } from "zod";
|
|
2915
|
+
function createWriteTool(opts) {
|
|
2916
|
+
const { workspace, noun } = opts;
|
|
2917
|
+
const io = opts.io ?? localIoProvider(workspace.root);
|
|
2918
|
+
return defineTool9({
|
|
2919
|
+
description: `Write a complete file to the ${noun}, creating parent directories and overwriting any existing file. For a small change to an existing file, prefer edit so you don't have to reproduce the whole file.`,
|
|
2920
|
+
inputSchema: z9.object({
|
|
2921
|
+
path: z9.string().min(1).describe(`File path, relative to the ${noun} root.`),
|
|
2922
|
+
content: z9.string().describe("The full contents to write.")
|
|
2923
|
+
}),
|
|
2924
|
+
async execute({ path, content }, ctx) {
|
|
2925
|
+
const abs = workspace.resolve(path);
|
|
2926
|
+
const fio = io(ctx);
|
|
2927
|
+
return withPathLock(abs, async () => {
|
|
2928
|
+
const created = await fio.stat(abs) === null;
|
|
2929
|
+
await fio.writeFile(abs, content);
|
|
2930
|
+
return {
|
|
2931
|
+
ok: true,
|
|
2932
|
+
path: workspace.relativize(abs),
|
|
2933
|
+
created,
|
|
2934
|
+
bytes: Buffer.byteLength(content)
|
|
2935
|
+
};
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/sandbox-io.ts
|
|
2942
|
+
import ignore2 from "ignore";
|
|
2943
|
+
function shellSingleQuote(value) {
|
|
2944
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
2945
|
+
}
|
|
2946
|
+
function defaultResolveSession(ctx) {
|
|
2947
|
+
if (ctx === undefined) {
|
|
2948
|
+
throw new Error("Sandbox-backed workspace tools need an eve tool context (ctx.getSandbox); none was provided.");
|
|
2949
|
+
}
|
|
2950
|
+
return ctx.getSandbox();
|
|
2951
|
+
}
|
|
2952
|
+
function sandboxIoProvider(options) {
|
|
2953
|
+
const resolve3 = options.resolveSession ?? defaultResolveSession;
|
|
2954
|
+
return (ctx) => createSandboxIo({ root: options.root, session: () => resolve3(ctx) });
|
|
2955
|
+
}
|
|
2956
|
+
function createSandboxIo(opts) {
|
|
2957
|
+
const { root } = opts;
|
|
2958
|
+
let resolved = null;
|
|
2959
|
+
const session = () => {
|
|
2960
|
+
resolved ??= Promise.resolve(opts.session());
|
|
2961
|
+
return resolved;
|
|
2962
|
+
};
|
|
2963
|
+
async function run(command) {
|
|
2964
|
+
const sb = await session();
|
|
2965
|
+
return await sb.run({ command, workingDirectory: root });
|
|
2966
|
+
}
|
|
2967
|
+
return {
|
|
2968
|
+
async stat(abs) {
|
|
2969
|
+
const q = shellSingleQuote(abs);
|
|
2970
|
+
const result = await run(`stat -c '%s %Y %F' -- ${q} 2>/dev/null || stat -f '%z %m %HT' -- ${q}`);
|
|
2971
|
+
if (result.exitCode !== 0)
|
|
2972
|
+
return null;
|
|
2973
|
+
const match = /^(\d+)\s+(\d+)\s+(.+)$/.exec(result.stdout.trim());
|
|
2974
|
+
if (match === null)
|
|
2975
|
+
return null;
|
|
2976
|
+
const [, size, mtimeSec, kind] = match;
|
|
2977
|
+
if (size === undefined || mtimeSec === undefined || kind === undefined)
|
|
2978
|
+
return null;
|
|
2979
|
+
return {
|
|
2980
|
+
isFile: /regular/i.test(kind),
|
|
2981
|
+
size: Number(size),
|
|
2982
|
+
mtimeMs: Number(mtimeSec) * 1000
|
|
2983
|
+
};
|
|
2984
|
+
},
|
|
2985
|
+
async readFile(abs) {
|
|
2986
|
+
const sb = await session();
|
|
2987
|
+
const bytes = await sb.readBinaryFile({ path: abs });
|
|
2988
|
+
return bytes === null ? null : Buffer.from(bytes);
|
|
2989
|
+
},
|
|
2990
|
+
async writeFile(abs, content) {
|
|
2991
|
+
const sb = await session();
|
|
2992
|
+
const bytes = typeof content === "string" ? new TextEncoder().encode(content) : content;
|
|
2993
|
+
await sb.writeBinaryFile({ path: abs, content: bytes });
|
|
2994
|
+
},
|
|
2995
|
+
async listFiles(scope) {
|
|
2996
|
+
const rel = scope === undefined ? undefined : relativizeWithin(root, scope);
|
|
2997
|
+
const spec = rel === undefined || rel === "." ? "" : ` -- ${shellSingleQuote(rel)}`;
|
|
2998
|
+
const listed = await run(`git ls-files --cached --others --exclude-standard -z${spec}`);
|
|
2999
|
+
if (listed.exitCode === 0) {
|
|
3000
|
+
const files2 = listed.stdout.split("\x00").filter((p) => p.length > 0);
|
|
3001
|
+
const deleted = await run(`git ls-files --deleted -z${spec}`);
|
|
3002
|
+
if (deleted.exitCode !== 0)
|
|
3003
|
+
return files2;
|
|
3004
|
+
const gone = new Set(deleted.stdout.split("\x00").filter((p) => p.length > 0));
|
|
3005
|
+
return gone.size === 0 ? files2 : files2.filter((p) => !gone.has(p));
|
|
3006
|
+
}
|
|
3007
|
+
const start = rel === undefined || rel === "." ? "." : shellSingleQuote(rel);
|
|
3008
|
+
const prune = `\\( ${[...ALWAYS_IGNORED].map((dir) => `-name ${dir}`).join(" -o ")} \\) -prune`;
|
|
3009
|
+
const found = await run(`find ${start} ${prune} -o -type f -print`);
|
|
3010
|
+
if (found.exitCode !== 0) {
|
|
3011
|
+
throw new Error(`Could not list workspace files in the sandbox: ${found.stderr.trim() || `find exited ${found.exitCode}`}`);
|
|
3012
|
+
}
|
|
3013
|
+
const files = found.stdout.split(`
|
|
3014
|
+
`).map((line) => line.startsWith("./") ? line.slice(2) : line).filter((line) => line.length > 0);
|
|
3015
|
+
const sb = await session();
|
|
3016
|
+
const rootIgnore = await sb.readBinaryFile({ path: `${root}/.gitignore` });
|
|
3017
|
+
if (rootIgnore === null)
|
|
3018
|
+
return files;
|
|
3019
|
+
const matcher = ignore2().add(Buffer.from(rootIgnore).toString("utf8"));
|
|
3020
|
+
return files.filter((file) => !matcher.ignores(file));
|
|
3021
|
+
},
|
|
3022
|
+
async search(options) {
|
|
3023
|
+
const scopeRel = options.scope === undefined ? "." : relativizeWithin(root, options.scope);
|
|
3024
|
+
const viaRg = await runSearch(run, buildRipgrepCommand(options, scopeRel));
|
|
3025
|
+
if (viaRg.exitCode === 0 || viaRg.exitCode === 1) {
|
|
3026
|
+
return parseSearchOutput(viaRg.stdout, options.maxMatches, viaRg.flooded);
|
|
3027
|
+
}
|
|
3028
|
+
if (viaRg.exitCode !== 127) {
|
|
3029
|
+
throw new Error(`Search failed in the sandbox (rg exited ${viaRg.exitCode}): ${viaRg.stderr.trim()}`);
|
|
3030
|
+
}
|
|
3031
|
+
const viaGrep = await runSearch(run, buildPosixGrepCommand(options, scopeRel));
|
|
3032
|
+
if (viaGrep.exitCode === 0 || viaGrep.exitCode === 1) {
|
|
3033
|
+
const parsed = parseSearchOutput(viaGrep.stdout, Number.MAX_SAFE_INTEGER, viaGrep.flooded);
|
|
3034
|
+
const globRe = options.glob === undefined ? null : globToRegExp(options.glob);
|
|
3035
|
+
const sb = await session();
|
|
3036
|
+
const rootIgnore = await sb.readBinaryFile({ path: `${root}/.gitignore` });
|
|
3037
|
+
const matcher = rootIgnore === null ? null : ignore2().add(Buffer.from(rootIgnore).toString("utf8"));
|
|
3038
|
+
const kept = parsed.matches.filter((m) => (globRe === null || globRe.test(m.file)) && (matcher === null || !matcher.ignores(m.file)));
|
|
3039
|
+
return {
|
|
3040
|
+
matches: kept.slice(0, options.maxMatches),
|
|
3041
|
+
stopped: parsed.stopped === "output-cap" ? "output-cap" : kept.length >= options.maxMatches ? "max-matches" : parsed.stopped,
|
|
3042
|
+
skippedLargeFiles: null
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
throw new Error(`Search failed in the sandbox (grep exited ${viaGrep.exitCode}): ${viaGrep.stderr.trim()}`);
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
}
|
|
3049
|
+
var SEARCH_OUTPUT_CAP_BYTES = 10 * 1024 * 1024;
|
|
3050
|
+
var SEARCH_EXIT_SENTINEL = "__ZO_SEARCH_EXIT__";
|
|
3051
|
+
async function runSearch(run, command) {
|
|
3052
|
+
const wrapped = `{ ${command}; printf '\\n${SEARCH_EXIT_SENTINEL}:%d\\n' "$?"; } | head -c ${SEARCH_OUTPUT_CAP_BYTES}`;
|
|
3053
|
+
const result = await run(wrapped);
|
|
3054
|
+
const parsed = extractSearchExit(result.stdout);
|
|
3055
|
+
if (parsed.exitCode === null) {
|
|
3056
|
+
if (result.exitCode !== 0) {
|
|
3057
|
+
return { exitCode: result.exitCode, stdout: parsed.stdout, stderr: result.stderr, flooded: false };
|
|
3058
|
+
}
|
|
3059
|
+
return { exitCode: 0, stdout: parsed.stdout, stderr: result.stderr, flooded: true };
|
|
3060
|
+
}
|
|
3061
|
+
return {
|
|
3062
|
+
exitCode: parsed.exitCode,
|
|
3063
|
+
stdout: parsed.stdout,
|
|
3064
|
+
stderr: result.stderr,
|
|
3065
|
+
flooded: false
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
function extractSearchExit(stdout) {
|
|
3069
|
+
const match = new RegExp(`\\n?${SEARCH_EXIT_SENTINEL}:(\\d+)\\n?$`).exec(stdout);
|
|
3070
|
+
if (match === null || match[1] === undefined) {
|
|
3071
|
+
return { stdout, exitCode: null };
|
|
3072
|
+
}
|
|
3073
|
+
return { stdout: stdout.slice(0, match.index), exitCode: Number(match[1]) };
|
|
3074
|
+
}
|
|
3075
|
+
function buildRipgrepCommand(options, scopeRel) {
|
|
3076
|
+
const parts = [
|
|
3077
|
+
"rg",
|
|
3078
|
+
"--line-number",
|
|
3079
|
+
"--with-filename",
|
|
3080
|
+
"--no-heading",
|
|
3081
|
+
"--color=never",
|
|
3082
|
+
"--hidden",
|
|
3083
|
+
...[...ALWAYS_IGNORED].map((dir) => `--glob ${shellSingleQuote(`!**/${dir}`)}`),
|
|
3084
|
+
`--max-filesize ${MAX_SEARCH_FILE_BYTES}`
|
|
3085
|
+
];
|
|
3086
|
+
if (options.ignoreCase)
|
|
3087
|
+
parts.push("--ignore-case");
|
|
3088
|
+
if (options.glob !== undefined)
|
|
3089
|
+
parts.push(`--glob ${shellSingleQuote(options.glob)}`);
|
|
3090
|
+
parts.push(`--max-count ${options.maxMatches}`);
|
|
3091
|
+
parts.push(`--regexp ${shellSingleQuote(options.pattern)}`);
|
|
3092
|
+
parts.push("--", shellSingleQuote(scopeRel));
|
|
3093
|
+
return parts.join(" ");
|
|
3094
|
+
}
|
|
3095
|
+
function buildPosixGrepCommand(options, scopeRel) {
|
|
3096
|
+
const prune = `\\( ${[...ALWAYS_IGNORED].map((dir) => `-name ${dir}`).join(" -o ")} \\) -prune`;
|
|
3097
|
+
const filters = ["-type f", `-size -${MAX_SEARCH_FILE_BYTES}c`];
|
|
3098
|
+
const grep = [
|
|
3099
|
+
"grep",
|
|
3100
|
+
"-n",
|
|
3101
|
+
"-H",
|
|
3102
|
+
"-E",
|
|
3103
|
+
"--color=never",
|
|
3104
|
+
...options.ignoreCase ? ["-i"] : [],
|
|
3105
|
+
`-m ${options.maxMatches}`,
|
|
3106
|
+
"-e",
|
|
3107
|
+
shellSingleQuote(options.pattern),
|
|
3108
|
+
"--"
|
|
3109
|
+
].join(" ");
|
|
3110
|
+
return `find ${shellSingleQuote(scopeRel)} ${prune} -o ${filters.join(" ")} -exec ${grep} {} +`;
|
|
3111
|
+
}
|
|
3112
|
+
function parseSearchLine(line) {
|
|
3113
|
+
const s = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
3114
|
+
let from = 0;
|
|
3115
|
+
for (;; ) {
|
|
3116
|
+
const colon = s.indexOf(":", from);
|
|
3117
|
+
if (colon === -1)
|
|
3118
|
+
return null;
|
|
3119
|
+
if (colon === 0) {
|
|
3120
|
+
from = 1;
|
|
3121
|
+
continue;
|
|
3122
|
+
}
|
|
3123
|
+
let end = colon + 1;
|
|
3124
|
+
while (end < s.length && s.charCodeAt(end) >= 48 && s.charCodeAt(end) <= 57)
|
|
3125
|
+
end++;
|
|
3126
|
+
if (end > colon + 1 && s.charCodeAt(end) === 58) {
|
|
3127
|
+
return { file: s.slice(0, colon), lineNo: s.slice(colon + 1, end), text: s.slice(end + 1) };
|
|
3128
|
+
}
|
|
3129
|
+
from = colon + 1;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
function parseSearchOutput(stdout, maxMatches, flooded = false) {
|
|
3133
|
+
const matches = [];
|
|
3134
|
+
let stopped = flooded ? "output-cap" : false;
|
|
3135
|
+
for (const line of stdout.split(`
|
|
3136
|
+
`)) {
|
|
3137
|
+
if (line.length === 0)
|
|
3138
|
+
continue;
|
|
3139
|
+
const parsed = parseSearchLine(line);
|
|
3140
|
+
if (parsed === null)
|
|
3141
|
+
continue;
|
|
3142
|
+
const { file, lineNo, text } = parsed;
|
|
3143
|
+
matches.push({
|
|
3144
|
+
file: file.startsWith("./") ? file.slice(2) : file,
|
|
3145
|
+
line: Number(lineNo),
|
|
3146
|
+
text
|
|
3147
|
+
});
|
|
3148
|
+
if (matches.length >= maxMatches) {
|
|
3149
|
+
if (stopped === false)
|
|
3150
|
+
stopped = "max-matches";
|
|
3151
|
+
break;
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
return { matches, stopped, skippedLargeFiles: null };
|
|
3155
|
+
}
|
|
3156
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/hooks.ts
|
|
3157
|
+
import { Client } from "eve/client";
|
|
3158
|
+
import { defineHook } from "eve/hooks";
|
|
3159
|
+
|
|
3160
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/redeliver.ts
|
|
3161
|
+
function isRecord5(value) {
|
|
3162
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3163
|
+
}
|
|
3164
|
+
function redeliveryFromEvent(event) {
|
|
3165
|
+
if (!isRecord5(event) || event.type !== "action.result")
|
|
3166
|
+
return null;
|
|
3167
|
+
if (!isRecord5(event.data))
|
|
3168
|
+
return null;
|
|
3169
|
+
const result = event.data.result;
|
|
3170
|
+
if (!isRecord5(result) || result.kind !== "tool-result")
|
|
3171
|
+
return null;
|
|
3172
|
+
if (typeof result.callId !== "string" || result.callId.length === 0)
|
|
3173
|
+
return null;
|
|
3174
|
+
const attachment = readChatAttachment(result.output);
|
|
3175
|
+
if (!attachment)
|
|
3176
|
+
return null;
|
|
3177
|
+
return { toolCallId: result.callId, attachment };
|
|
3178
|
+
}
|
|
3179
|
+
function buildRedeliveryMessage(pending) {
|
|
3180
|
+
const names = pending.map((p) => p.attachment.filename).join(", ");
|
|
3181
|
+
return [
|
|
3182
|
+
{ type: "text", text: `Attached: ${names} (auto-attached from read).` },
|
|
3183
|
+
...pending.map((p) => ({
|
|
3184
|
+
type: "file",
|
|
3185
|
+
data: p.attachment.dataUrl,
|
|
3186
|
+
mediaType: p.attachment.mediaType,
|
|
3187
|
+
filename: p.attachment.filename
|
|
3188
|
+
}))
|
|
3189
|
+
];
|
|
3190
|
+
}
|
|
3191
|
+
function createRedeliveryState() {
|
|
3192
|
+
const core = createParkDeliveryState();
|
|
3193
|
+
function toRequest(request) {
|
|
3194
|
+
if (!request)
|
|
3195
|
+
return null;
|
|
3196
|
+
return {
|
|
3197
|
+
sessionId: request.sessionId,
|
|
3198
|
+
continuationToken: request.continuationToken,
|
|
3199
|
+
pending: request.items.map((item) => ({
|
|
3200
|
+
toolCallId: item.key,
|
|
3201
|
+
attachment: item.payload
|
|
3202
|
+
}))
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
return {
|
|
3206
|
+
observe(event, meta) {
|
|
3207
|
+
const request = toRequest(core.observe(event, meta));
|
|
3208
|
+
const found = redeliveryFromEvent(event);
|
|
3209
|
+
if (found) {
|
|
3210
|
+
core.enqueue(meta.sessionId, {
|
|
3211
|
+
key: found.toolCallId,
|
|
3212
|
+
payload: found.attachment
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3215
|
+
return request;
|
|
3216
|
+
},
|
|
3217
|
+
settle(request, ok) {
|
|
3218
|
+
core.settle({
|
|
3219
|
+
sessionId: request.sessionId,
|
|
3220
|
+
continuationToken: request.continuationToken,
|
|
3221
|
+
items: request.pending.map((p) => ({
|
|
3222
|
+
key: p.toolCallId,
|
|
3223
|
+
payload: p.attachment
|
|
3224
|
+
}))
|
|
3225
|
+
}, ok);
|
|
3226
|
+
}
|
|
3227
|
+
};
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/hooks.ts
|
|
3231
|
+
var RETRY_DELAYS_MS = [500, 2000, 5000];
|
|
3232
|
+
function buildDeliveryMessage(request) {
|
|
3233
|
+
const media = request.items.flatMap((item) => item.payload.kind === "media" ? [{ toolCallId: item.key, attachment: item.payload.attachment }] : []);
|
|
3234
|
+
const notes = request.items.flatMap((item) => item.payload.kind === "note" ? [item.payload.text] : []);
|
|
3235
|
+
const steers = request.items.flatMap((item) => item.payload.kind === "steer" ? [item.payload.message.text] : []);
|
|
3236
|
+
return [
|
|
3237
|
+
...steers.map((text) => ({ type: "text", text })),
|
|
3238
|
+
...media.length > 0 ? buildRedeliveryMessage(media) : [],
|
|
3239
|
+
...notes.map((text) => ({ type: "text", text }))
|
|
3240
|
+
];
|
|
3241
|
+
}
|
|
3242
|
+
function isRecord6(value) {
|
|
3243
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3244
|
+
}
|
|
3245
|
+
function isSessionWaiting(event) {
|
|
3246
|
+
return isRecord6(event) && event.type === "session.waiting";
|
|
3247
|
+
}
|
|
3248
|
+
function createParkDeliveryHook(options = {}) {
|
|
3249
|
+
const serverUrl = options.serverUrl ?? `http://127.0.0.1:${process.env.PORT ?? "2000"}`;
|
|
3250
|
+
const log = options.log ?? true;
|
|
3251
|
+
const steerInbox = options.steer ? createSteerInbox({ dir: options.steer.dir }) : null;
|
|
3252
|
+
const state = createParkDeliveryState();
|
|
3253
|
+
async function deliver(request) {
|
|
3254
|
+
const client = new Client({ host: serverUrl });
|
|
3255
|
+
const message = buildDeliveryMessage(request);
|
|
3256
|
+
for (let attempt = 0;; attempt++) {
|
|
3257
|
+
try {
|
|
3258
|
+
const session = client.session({
|
|
3259
|
+
sessionId: request.sessionId,
|
|
3260
|
+
continuationToken: request.continuationToken,
|
|
3261
|
+
streamIndex: 0
|
|
3262
|
+
});
|
|
3263
|
+
const response = await session.send({ message });
|
|
3264
|
+
if (response.sessionId !== request.sessionId) {
|
|
3265
|
+
throw new Error(`park delivery landed on ${response.sessionId} instead of ${request.sessionId} (continuation token mismatch)`);
|
|
3266
|
+
}
|
|
3267
|
+
await response.result();
|
|
3268
|
+
const next = state.settle(request, true);
|
|
3269
|
+
if (log) {
|
|
3270
|
+
const labels = request.items.map((item) => item.payload.kind === "media" ? item.payload.attachment.filename : item.key);
|
|
3271
|
+
console.log(`[agent-sdk] park delivery to ${request.sessionId}: ${labels.join(", ")}`);
|
|
3272
|
+
}
|
|
3273
|
+
if (next)
|
|
3274
|
+
deliver(next);
|
|
3275
|
+
return;
|
|
3276
|
+
} catch (error) {
|
|
3277
|
+
const delay = RETRY_DELAYS_MS[attempt];
|
|
3278
|
+
if (delay === undefined) {
|
|
3279
|
+
state.settle(request, false);
|
|
3280
|
+
if (log) {
|
|
3281
|
+
console.warn(`[agent-sdk] park delivery to ${request.sessionId} failed; will retry on next park:`, error);
|
|
3282
|
+
}
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
setParkNotificationHandler((sessionId, notification) => {
|
|
3290
|
+
const request = state.enqueue(sessionId, {
|
|
3291
|
+
key: notification.key,
|
|
3292
|
+
payload: { kind: "note", text: notification.text }
|
|
3293
|
+
});
|
|
3294
|
+
if (request)
|
|
3295
|
+
deliver(request);
|
|
3296
|
+
});
|
|
3297
|
+
return defineHook({
|
|
3298
|
+
events: {
|
|
3299
|
+
"*"(event, ctx) {
|
|
3300
|
+
const meta = {
|
|
3301
|
+
sessionId: ctx.session.id,
|
|
3302
|
+
continuationToken: ctx.channel.continuationToken
|
|
3303
|
+
};
|
|
3304
|
+
if (steerInbox && isSessionWaiting(event)) {
|
|
3305
|
+
const steers = steerInbox.drain(meta.sessionId);
|
|
3306
|
+
if (steers.length > 0) {
|
|
3307
|
+
const flush = state.enqueueAll(meta.sessionId, steers.map((message) => ({
|
|
3308
|
+
key: `steer:${message.id}`,
|
|
3309
|
+
payload: { kind: "steer", message }
|
|
3310
|
+
})));
|
|
3311
|
+
if (flush)
|
|
3312
|
+
deliver(flush);
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
const request = state.observe(event, meta);
|
|
3316
|
+
if (request)
|
|
3317
|
+
deliver(request);
|
|
3318
|
+
const found = redeliveryFromEvent(event);
|
|
3319
|
+
if (found) {
|
|
3320
|
+
state.enqueue(meta.sessionId, {
|
|
3321
|
+
key: found.toolCallId,
|
|
3322
|
+
payload: { kind: "media", attachment: found.attachment }
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/task.ts
|
|
3330
|
+
import { defineAgent } from "eve";
|
|
3331
|
+
import { defineDynamic as defineDynamic3, defineInstructions as defineInstructions2 } from "eve/instructions";
|
|
3332
|
+
var TASK_DISABLED_BUILTINS = ["ask_question"];
|
|
3333
|
+
function expectedTaskToolNames(options) {
|
|
3334
|
+
const parent = new Set(options.parentToolNames);
|
|
3335
|
+
const excluded = options.excludedParentTools ?? [];
|
|
3336
|
+
for (const name of excluded) {
|
|
3337
|
+
if (!parent.has(name)) {
|
|
3338
|
+
throw new Error(`excludedParentTools names "${name}", which is not a parent tool — a stale exclusion would silently weaken the manifest guard`);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
const names = new Set(options.parentToolNames.filter((name) => !excluded.includes(name)));
|
|
3342
|
+
for (const name of TASK_DISABLED_BUILTINS) {
|
|
3343
|
+
if (names.has(name)) {
|
|
3344
|
+
throw new Error(`"${name}" is both a parent tool and a task-disabled builtin — re-export or shim, not both`);
|
|
3345
|
+
}
|
|
3346
|
+
names.add(name);
|
|
3347
|
+
}
|
|
3348
|
+
return [...names].sort();
|
|
3349
|
+
}
|
|
3350
|
+
function createTaskChildTools(options) {
|
|
3351
|
+
const noun = options.workspaceNoun ?? "workspace";
|
|
3352
|
+
const workspace = createWorkspace(options.workspaceRoot);
|
|
3353
|
+
const conventionsFileName = options.conventionsFileName ?? "AGENTS.md";
|
|
3354
|
+
const dirConventions = options.injectDirConventions ?? true ? {
|
|
3355
|
+
tracker: createDirConventionsTracker({
|
|
3356
|
+
workspaceRoot: workspace.root,
|
|
3357
|
+
fileName: conventionsFileName
|
|
3358
|
+
}),
|
|
3359
|
+
fileName: conventionsFileName
|
|
3360
|
+
} : undefined;
|
|
3361
|
+
return {
|
|
3362
|
+
read: createReadTool({
|
|
3363
|
+
workspace,
|
|
3364
|
+
noun,
|
|
3365
|
+
attachImagesToChat: false,
|
|
3366
|
+
maxInlineImageBytes: 0,
|
|
3367
|
+
dirConventions,
|
|
3368
|
+
imageUnavailableHint: "Its pixels are not available in a delegated child session — report the image's path and metadata in your final message so the caller can view it.",
|
|
3369
|
+
mediaUnavailableHint: "Its bytes are not available in a delegated child session — use bash extraction if text will do, or report the file's path and metadata so the caller can handle it."
|
|
3370
|
+
}),
|
|
3371
|
+
webfetch: createWebFetchTool({
|
|
3372
|
+
workspace,
|
|
3373
|
+
spillDir: options.spillDir,
|
|
3374
|
+
attachImagesToChat: false,
|
|
3375
|
+
maxInlineImageBytes: 0,
|
|
3376
|
+
imageUnavailableHint: "Its pixels are not available in a delegated child session — report the image's URL in your final message so the caller can fetch it."
|
|
3377
|
+
})
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3380
|
+
var TASK_CHILD_TOOL_OVERRIDES = ["read", "webfetch"];
|
|
3381
|
+
function buildTaskMarkdown(opts) {
|
|
3382
|
+
const noun = opts?.workspaceNoun ?? "workspace";
|
|
3383
|
+
return `## Working as a delegated task
|
|
3384
|
+
|
|
3385
|
+
You are a delegated worker: a copy of the parent agent, handed one self-contained task in this ${noun}. Your **final message is your entire deliverable** — the caller sees nothing else you did, so make it complete and self-contained.
|
|
3386
|
+
|
|
3387
|
+
- **Do the task asked, completely.** Cite concrete paths and line references (\`src/parser.ts:42\`) for every claim about code, so the caller can jump straight to it.
|
|
3388
|
+
- **Decide, don't ask.** You cannot ask the user anything: make the reasonable call yourself and note it in your report. If you're genuinely blocked, report the blocker as your result — never guess silently.
|
|
3389
|
+
- **Stay in your write scope.** Touch only the files your task calls for; the caller may be running sibling workers in parallel with their own scopes, and overlapping edits clobber.
|
|
3390
|
+
- **Honor the requested thoroughness.** "quick" means the first solid result and stop; "very thorough" means check every plausible angle before concluding; "medium" sits between. Unspecified means medium.
|
|
3391
|
+
- **Delegate onward sparingly.** You have your own \`agent\` clone for genuinely independent subtasks, but never chain delegations more than one level deeper.
|
|
3392
|
+
- **Background tasks work, but \`notify\` doesn't.** You can \`run_async\` and \`await_task\`, but \`notify\` watchers queue matches that never deliver — you don't idle waiting for user input, so use \`await_task\` or \`check_tasks\` to poll instead.
|
|
3393
|
+
- **Report outcomes, not process.** Skip the narration of your work; include what changed, what you verified, and only what changes what the caller does next.`;
|
|
3394
|
+
}
|
|
3395
|
+
function createTaskInstruction(opts) {
|
|
3396
|
+
const instruction = defineInstructions2({ markdown: buildTaskMarkdown(opts) });
|
|
3397
|
+
return defineDynamic3({
|
|
3398
|
+
events: {
|
|
3399
|
+
"session.started": () => instruction
|
|
3400
|
+
}
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3403
|
+
function buildTaskDescription(options) {
|
|
3404
|
+
const noun = options.workspaceNoun ?? "workspace";
|
|
3405
|
+
const capability = options.capabilityNote ? ` ${options.capabilityNote}` : "";
|
|
3406
|
+
const blurb = options.modelBlurb ? ` About ${options.modelName}: ${options.modelBlurb}` : "";
|
|
3407
|
+
return `Delegate one self-contained subtask to a copy of this agent pinned to ${options.modelName} — same ${noun}, fresh conversation. It cannot ask the user anything: it decides and reports.${capability} ${options.use} Pack the message with everything the child needs (it sees none of your history), name the exact deliverable and the thoroughness you want ("quick", "medium", or "very thorough"), and give parallel children non-overlapping write scopes.${blurb}`;
|
|
3408
|
+
}
|
|
3409
|
+
function createTaskAgent(options) {
|
|
3410
|
+
return defineAgent({
|
|
3411
|
+
description: options.description ?? buildTaskDescription(options),
|
|
3412
|
+
model: options.model,
|
|
3413
|
+
...options.reasoning !== undefined ? { reasoning: options.reasoning } : {}
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3416
|
+
var GATEWAY_MODELS_URL = "https://ai-gateway.vercel.sh/v1/models";
|
|
3417
|
+
function isRecord7(value) {
|
|
3418
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3419
|
+
}
|
|
3420
|
+
function parseGatewayModelCatalog(value) {
|
|
3421
|
+
if (!isRecord7(value) || !Array.isArray(value.data))
|
|
3422
|
+
return null;
|
|
3423
|
+
const models = [];
|
|
3424
|
+
for (const entry of value.data) {
|
|
3425
|
+
if (!isRecord7(entry) || typeof entry.id !== "string")
|
|
3426
|
+
return null;
|
|
3427
|
+
models.push({
|
|
3428
|
+
id: entry.id,
|
|
3429
|
+
name: typeof entry.name === "string" ? entry.name : undefined,
|
|
3430
|
+
description: typeof entry.description === "string" ? entry.description : undefined
|
|
3431
|
+
});
|
|
3432
|
+
}
|
|
3433
|
+
return models;
|
|
3434
|
+
}
|
|
3435
|
+
async function fetchGatewayModelCatalog(options) {
|
|
3436
|
+
const url = options?.url ?? GATEWAY_MODELS_URL;
|
|
3437
|
+
const fetchImpl = options?.fetchImpl ?? fetch;
|
|
3438
|
+
const response = await fetchImpl(url);
|
|
3439
|
+
if (!response.ok) {
|
|
3440
|
+
throw new Error(`gateway model catalog fetch failed: ${response.status} ${url}`);
|
|
3441
|
+
}
|
|
3442
|
+
const parsed = parseGatewayModelCatalog(await response.json());
|
|
3443
|
+
if (parsed === null) {
|
|
3444
|
+
throw new Error(`gateway model catalog response has an unexpected shape: ${url}`);
|
|
3445
|
+
}
|
|
3446
|
+
return parsed;
|
|
3447
|
+
}
|
|
3448
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/mock-model.ts
|
|
3449
|
+
var STORY_SENTENCES = [
|
|
3450
|
+
"The lighthouse keeper counted the waves as they broke against the rocks.",
|
|
3451
|
+
"Every seventh wave carried a whisper from the old town beneath the sea.",
|
|
3452
|
+
"Marisol had kept the light burning for forty-one years without a single dark night.",
|
|
3453
|
+
"Tonight the fog rolled in thicker than she had ever seen it.",
|
|
3454
|
+
"Somewhere out past the shoals, a bell rang that no ship had carried in decades.",
|
|
3455
|
+
"She climbed the spiral stairs slowly, lantern in one hand, logbook in the other.",
|
|
3456
|
+
"The glass at the top of the tower was cold and streaked with salt.",
|
|
3457
|
+
"Below, the sea moved like a great animal turning in its sleep.",
|
|
3458
|
+
"She wrote the date in the logbook and then paused, pen hovering.",
|
|
3459
|
+
"The bell rang again, closer now, and the fog pressed against the windows."
|
|
3460
|
+
];
|
|
3461
|
+
function storyChunk(index) {
|
|
3462
|
+
const sentence = STORY_SENTENCES[index % STORY_SENTENCES.length] ?? "The story went on.";
|
|
3463
|
+
const paragraphBreak = index > 0 && index % 8 === 0 ? `
|
|
3464
|
+
|
|
3465
|
+
` : " ";
|
|
3466
|
+
return `${paragraphBreak}${sentence}`;
|
|
3467
|
+
}
|
|
3468
|
+
var delay = (ms) => ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve();
|
|
3469
|
+
function usageFor(outputTokens) {
|
|
3470
|
+
return {
|
|
3471
|
+
inputTokens: { total: 100, noCache: 100, cacheRead: 0, cacheWrite: 0 },
|
|
3472
|
+
outputTokens: { total: outputTokens, text: outputTokens, reasoning: 0 }
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
var MOCK_SCENARIOS = [
|
|
3476
|
+
"hitl",
|
|
3477
|
+
"parallel",
|
|
3478
|
+
"todo",
|
|
3479
|
+
"delegate",
|
|
3480
|
+
"fail",
|
|
3481
|
+
"burst",
|
|
3482
|
+
"markdown",
|
|
3483
|
+
"interleave",
|
|
3484
|
+
"empty"
|
|
3485
|
+
];
|
|
3486
|
+
function markdownChunks() {
|
|
3487
|
+
return [
|
|
3488
|
+
`## Streaming markdown stress
|
|
3489
|
+
|
|
3490
|
+
`,
|
|
3491
|
+
"A paragraph with **bold**, _italic_, `inline code`, and a [link](https://example.com).\n\n",
|
|
3492
|
+
"```ts\n",
|
|
3493
|
+
`export function keeper(light: number): string {
|
|
3494
|
+
`,
|
|
3495
|
+
` // the fence stays open across deltas
|
|
3496
|
+
`,
|
|
3497
|
+
" return `burning for ${light} years`;\n",
|
|
3498
|
+
`}
|
|
3499
|
+
`,
|
|
3500
|
+
"```\n\n",
|
|
3501
|
+
`| tide | bell | fog |
|
|
3502
|
+
`,
|
|
3503
|
+
`| --- | --- | --- |
|
|
3504
|
+
`,
|
|
3505
|
+
`| low | quiet | thin |
|
|
3506
|
+
`,
|
|
3507
|
+
`| high | ringing | thick |
|
|
3508
|
+
|
|
3509
|
+
`,
|
|
3510
|
+
`1. climb the stairs
|
|
3511
|
+
`,
|
|
3512
|
+
` - lantern in one hand
|
|
3513
|
+
`,
|
|
3514
|
+
` - logbook in the other
|
|
3515
|
+
`,
|
|
3516
|
+
`2. write the date
|
|
3517
|
+
`,
|
|
3518
|
+
` 1. pause, pen hovering
|
|
3519
|
+
`,
|
|
3520
|
+
` 2. listen for the bell
|
|
3521
|
+
|
|
3522
|
+
`,
|
|
3523
|
+
`> The sea moved like a great animal turning in its sleep.
|
|
3524
|
+
|
|
3525
|
+
`,
|
|
3526
|
+
`Unicode: emoji \uD83C\uDF0A\uD83D\uDD14, CJK 灯台守は波を数えた, RTL مرحبا, combining é ñ ü.
|
|
3527
|
+
|
|
3528
|
+
`,
|
|
3529
|
+
"A very long unbroken token: " + "abcdefghij".repeat(40) + `
|
|
3530
|
+
|
|
3531
|
+
`,
|
|
3532
|
+
`Done. ✅
|
|
3533
|
+
`
|
|
3534
|
+
];
|
|
3535
|
+
}
|
|
3536
|
+
function mockScenarioFrom(text) {
|
|
3537
|
+
const match = /\[mock:([a-z]+)\]/.exec(text);
|
|
3538
|
+
const name = match?.[1];
|
|
3539
|
+
return MOCK_SCENARIOS.includes(name ?? "") ? name : null;
|
|
3540
|
+
}
|
|
3541
|
+
function scriptStepFrom(prompt) {
|
|
3542
|
+
let step = 0;
|
|
3543
|
+
for (const message of prompt) {
|
|
3544
|
+
if (message.role === "user")
|
|
3545
|
+
step = 0;
|
|
3546
|
+
else if (message.role === "tool") {
|
|
3547
|
+
step += message.content.filter((part) => part.type === "tool-result").length;
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
return step;
|
|
3551
|
+
}
|
|
3552
|
+
function lastUserTextFrom(prompt) {
|
|
3553
|
+
return [...prompt].reverse().flatMap((message) => message.role === "user" ? message.content.flatMap((part) => part.type === "text" ? [part.text] : []) : [])[0];
|
|
3554
|
+
}
|
|
3555
|
+
function askQuestionCall(prompt, topic) {
|
|
3556
|
+
return {
|
|
3557
|
+
toolName: "ask_question",
|
|
3558
|
+
input: {
|
|
3559
|
+
prompt,
|
|
3560
|
+
options: [
|
|
3561
|
+
{
|
|
3562
|
+
id: "ship",
|
|
3563
|
+
label: `Ship the ${topic}`,
|
|
3564
|
+
style: "primary",
|
|
3565
|
+
description: "Proceed with the happy path."
|
|
3566
|
+
},
|
|
3567
|
+
{ id: "hold", label: "Hold for review" },
|
|
3568
|
+
{ id: "abort", label: "Abort the run", style: "danger", description: "Stops everything." }
|
|
3569
|
+
],
|
|
3570
|
+
allowFreeform: true
|
|
3571
|
+
}
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
function scriptActionFor(scenario, step, delegateToolName = "task_fast") {
|
|
3575
|
+
switch (scenario) {
|
|
3576
|
+
case "hitl":
|
|
3577
|
+
if (step === 0) {
|
|
3578
|
+
return {
|
|
3579
|
+
kind: "tool-calls",
|
|
3580
|
+
calls: [askQuestionCall("Mock HITL: how should this test proceed?", "change")]
|
|
3581
|
+
};
|
|
3582
|
+
}
|
|
3583
|
+
return { kind: "text", text: "Answer received — the mock turn resumed and finished cleanly." };
|
|
3584
|
+
case "parallel":
|
|
3585
|
+
if (step === 0) {
|
|
3586
|
+
return {
|
|
3587
|
+
kind: "tool-calls",
|
|
3588
|
+
calls: [
|
|
3589
|
+
askQuestionCall("Mock parallel HITL (1 of 2): which color?", "color"),
|
|
3590
|
+
askQuestionCall("Mock parallel HITL (2 of 2): which size?", "size")
|
|
3591
|
+
]
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
if (step === 1) {
|
|
3595
|
+
return {
|
|
3596
|
+
kind: "text",
|
|
3597
|
+
text: "Only one answer arrived — the parallel HITL scenario ended without the second."
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
return {
|
|
3601
|
+
kind: "text",
|
|
3602
|
+
text: "Both answers received — the parallel HITL scenario finished cleanly."
|
|
3603
|
+
};
|
|
3604
|
+
case "todo":
|
|
3605
|
+
if (step === 0) {
|
|
3606
|
+
return {
|
|
3607
|
+
kind: "tool-calls",
|
|
3608
|
+
calls: [
|
|
3609
|
+
{
|
|
3610
|
+
toolName: "todo",
|
|
3611
|
+
input: {
|
|
3612
|
+
todos: [
|
|
3613
|
+
{ content: "Survey the harbor charts", status: "completed", priority: "high" },
|
|
3614
|
+
{ content: "Polish the tower glass", status: "in_progress", priority: "medium" },
|
|
3615
|
+
{ content: "Refill the oil reserves", status: "pending", priority: "medium" },
|
|
3616
|
+
{ content: "Log the evening tide", status: "pending", priority: "low" }
|
|
3617
|
+
]
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
]
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
if (step === 1) {
|
|
3624
|
+
return {
|
|
3625
|
+
kind: "tool-calls",
|
|
3626
|
+
calls: [
|
|
3627
|
+
{
|
|
3628
|
+
toolName: "todo",
|
|
3629
|
+
input: {
|
|
3630
|
+
todos: [
|
|
3631
|
+
{ content: "Survey the harbor charts", status: "completed", priority: "high" },
|
|
3632
|
+
{ content: "Polish the tower glass", status: "completed", priority: "medium" },
|
|
3633
|
+
{ content: "Refill the oil reserves", status: "in_progress", priority: "medium" },
|
|
3634
|
+
{ content: "Log the evening tide", status: "cancelled", priority: "low" }
|
|
3635
|
+
]
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
]
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
return { kind: "text", text: "Todo list written and updated — checklist scenario complete." };
|
|
3642
|
+
case "delegate":
|
|
3643
|
+
if (step === 0) {
|
|
3644
|
+
return {
|
|
3645
|
+
kind: "tool-calls",
|
|
3646
|
+
calls: [
|
|
3647
|
+
{
|
|
3648
|
+
toolName: delegateToolName,
|
|
3649
|
+
input: {
|
|
3650
|
+
message: "Mock delegation: describe the lighthouse keeper's routine. Reply with a short report."
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
]
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
return { kind: "text", text: "The delegate reported back — delegation scenario complete." };
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
function toolInputFragments(inputJson, fragmentSize = 24) {
|
|
3660
|
+
if (inputJson.length === 0)
|
|
3661
|
+
return [];
|
|
3662
|
+
const fragments = [];
|
|
3663
|
+
for (let i = 0;i < inputJson.length; i += fragmentSize) {
|
|
3664
|
+
fragments.push(inputJson.slice(i, i + fragmentSize));
|
|
3665
|
+
}
|
|
3666
|
+
return fragments;
|
|
3667
|
+
}
|
|
3668
|
+
function createMockStoryModel(options = {}) {
|
|
3669
|
+
const chunkCount = options.chunkCount ?? 240;
|
|
3670
|
+
const chunkDelayMs = options.chunkDelayMs ?? 250;
|
|
3671
|
+
const burstChunks = options.burstChunks ?? 600;
|
|
3672
|
+
const delegateToolName = options.delegateToolName ?? "task_fast";
|
|
3673
|
+
const now = options.now ?? Date.now;
|
|
3674
|
+
return {
|
|
3675
|
+
specificationVersion: "v4",
|
|
3676
|
+
provider: "anthropic",
|
|
3677
|
+
modelId: "claude-sonnet-4-6",
|
|
3678
|
+
supportedUrls: {},
|
|
3679
|
+
async doGenerate() {
|
|
3680
|
+
return {
|
|
3681
|
+
content: [{ type: "text", text: "(mock model, non-streaming reply)" }],
|
|
3682
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
3683
|
+
usage: usageFor(10),
|
|
3684
|
+
warnings: []
|
|
3685
|
+
};
|
|
3686
|
+
},
|
|
3687
|
+
async doStream(callOptions) {
|
|
3688
|
+
const abortSignal = callOptions.abortSignal;
|
|
3689
|
+
const lastUserText = lastUserTextFrom(callOptions.prompt);
|
|
3690
|
+
const scenario = mockScenarioFrom(lastUserText ?? "");
|
|
3691
|
+
const step = scriptStepFrom(callOptions.prompt);
|
|
3692
|
+
const topic = (lastUserText ?? "an untitled request").slice(0, 60);
|
|
3693
|
+
const stream = new ReadableStream({
|
|
3694
|
+
async start(controller) {
|
|
3695
|
+
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
3696
|
+
controller.enqueue({
|
|
3697
|
+
type: "response-metadata",
|
|
3698
|
+
id: `mock-${now()}`,
|
|
3699
|
+
modelId: "claude-sonnet-4-6",
|
|
3700
|
+
timestamp: new Date(now())
|
|
3701
|
+
});
|
|
3702
|
+
if (scenario === "fail") {
|
|
3703
|
+
controller.enqueue({ type: "text-start", id: "t1" });
|
|
3704
|
+
for (let i = 0;i < 6; i++) {
|
|
3705
|
+
if (abortSignal?.aborted)
|
|
3706
|
+
break;
|
|
3707
|
+
await delay(chunkDelayMs);
|
|
3708
|
+
controller.enqueue({ type: "text-delta", id: "t1", delta: storyChunk(i) });
|
|
3709
|
+
}
|
|
3710
|
+
controller.enqueue({ type: "text-end", id: "t1" });
|
|
3711
|
+
controller.enqueue({
|
|
3712
|
+
type: "error",
|
|
3713
|
+
error: new Error("mock: injected mid-stream failure [mock:fail]")
|
|
3714
|
+
});
|
|
3715
|
+
controller.close();
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
if (scenario === "burst") {
|
|
3719
|
+
controller.enqueue({ type: "text-start", id: "t1" });
|
|
3720
|
+
controller.enqueue({
|
|
3721
|
+
type: "text-delta",
|
|
3722
|
+
id: "t1",
|
|
3723
|
+
delta: `**Burst: ${burstChunks} deltas, no pacing.**
|
|
3724
|
+
|
|
3725
|
+
`
|
|
3726
|
+
});
|
|
3727
|
+
for (let i = 0;i < burstChunks; i++) {
|
|
3728
|
+
if (abortSignal?.aborted)
|
|
3729
|
+
break;
|
|
3730
|
+
controller.enqueue({
|
|
3731
|
+
type: "text-delta",
|
|
3732
|
+
id: "t1",
|
|
3733
|
+
delta: i % 8 === 0 ? `${storyChunk(i)} [¶${i / 8 + 1}]` : storyChunk(i)
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
controller.enqueue({ type: "text-delta", id: "t1", delta: `
|
|
3737
|
+
|
|
3738
|
+
**Burst done.**` });
|
|
3739
|
+
controller.enqueue({ type: "text-end", id: "t1" });
|
|
3740
|
+
controller.enqueue({
|
|
3741
|
+
type: "finish",
|
|
3742
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
3743
|
+
usage: usageFor(burstChunks * 12)
|
|
3744
|
+
});
|
|
3745
|
+
controller.close();
|
|
3746
|
+
return;
|
|
3747
|
+
}
|
|
3748
|
+
if (scenario === "markdown") {
|
|
3749
|
+
controller.enqueue({ type: "text-start", id: "t1" });
|
|
3750
|
+
for (const chunk of markdownChunks()) {
|
|
3751
|
+
if (abortSignal?.aborted)
|
|
3752
|
+
break;
|
|
3753
|
+
await delay(Math.min(chunkDelayMs, 120));
|
|
3754
|
+
controller.enqueue({ type: "text-delta", id: "t1", delta: chunk });
|
|
3755
|
+
}
|
|
3756
|
+
controller.enqueue({ type: "text-end", id: "t1" });
|
|
3757
|
+
controller.enqueue({
|
|
3758
|
+
type: "finish",
|
|
3759
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
3760
|
+
usage: usageFor(400)
|
|
3761
|
+
});
|
|
3762
|
+
controller.close();
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
if (scenario === "interleave") {
|
|
3766
|
+
const blocks = [
|
|
3767
|
+
{
|
|
3768
|
+
kind: "reasoning",
|
|
3769
|
+
id: "r1",
|
|
3770
|
+
text: "First thought: check the tide tables before anything else."
|
|
3771
|
+
},
|
|
3772
|
+
{
|
|
3773
|
+
kind: "text",
|
|
3774
|
+
id: "t1",
|
|
3775
|
+
text: `The tide tables say low water at dusk.
|
|
3776
|
+
|
|
3777
|
+
That changes the plan.`
|
|
3778
|
+
},
|
|
3779
|
+
{
|
|
3780
|
+
kind: "reasoning",
|
|
3781
|
+
id: "r2",
|
|
3782
|
+
text: "Second thought: the bell only rings when the fog is thick."
|
|
3783
|
+
},
|
|
3784
|
+
{
|
|
3785
|
+
kind: "text",
|
|
3786
|
+
id: "t2",
|
|
3787
|
+
text: "So the keeper waits for the bell — interleave scenario complete."
|
|
3788
|
+
}
|
|
3789
|
+
];
|
|
3790
|
+
for (const block of blocks) {
|
|
3791
|
+
const startType = block.kind === "reasoning" ? "reasoning-start" : "text-start";
|
|
3792
|
+
const deltaType = block.kind === "reasoning" ? "reasoning-delta" : "text-delta";
|
|
3793
|
+
const endType = block.kind === "reasoning" ? "reasoning-end" : "text-end";
|
|
3794
|
+
controller.enqueue({ type: startType, id: block.id });
|
|
3795
|
+
for (const word of block.text.split(" ")) {
|
|
3796
|
+
if (abortSignal?.aborted)
|
|
3797
|
+
break;
|
|
3798
|
+
await delay(Math.min(chunkDelayMs, 80));
|
|
3799
|
+
controller.enqueue({ type: deltaType, id: block.id, delta: `${word} ` });
|
|
3800
|
+
}
|
|
3801
|
+
controller.enqueue({ type: endType, id: block.id });
|
|
3802
|
+
if (abortSignal?.aborted)
|
|
3803
|
+
break;
|
|
3804
|
+
}
|
|
3805
|
+
controller.enqueue({
|
|
3806
|
+
type: "finish",
|
|
3807
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
3808
|
+
usage: usageFor(120)
|
|
3809
|
+
});
|
|
3810
|
+
controller.close();
|
|
3811
|
+
return;
|
|
3812
|
+
}
|
|
3813
|
+
if (scenario === "empty") {
|
|
3814
|
+
controller.enqueue({
|
|
3815
|
+
type: "finish",
|
|
3816
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
3817
|
+
usage: usageFor(0)
|
|
3818
|
+
});
|
|
3819
|
+
controller.close();
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3822
|
+
if (scenario !== null) {
|
|
3823
|
+
const action = scriptActionFor(scenario, step, delegateToolName);
|
|
3824
|
+
controller.enqueue({ type: "reasoning-start", id: "r1" });
|
|
3825
|
+
for (const word of `Scripted ${scenario} scenario, step ${step}: deciding the next move.`.split(" ")) {
|
|
3826
|
+
if (abortSignal?.aborted)
|
|
3827
|
+
break;
|
|
3828
|
+
await delay(chunkDelayMs);
|
|
3829
|
+
controller.enqueue({ type: "reasoning-delta", id: "r1", delta: `${word} ` });
|
|
3830
|
+
}
|
|
3831
|
+
controller.enqueue({ type: "reasoning-end", id: "r1" });
|
|
3832
|
+
if (action.kind === "tool-calls") {
|
|
3833
|
+
for (const [callIndex, call] of action.calls.entries()) {
|
|
3834
|
+
const toolCallId = `mock-call-${scenario}-${step}-${callIndex}`;
|
|
3835
|
+
const inputJson = JSON.stringify(call.input);
|
|
3836
|
+
controller.enqueue({
|
|
3837
|
+
type: "tool-input-start",
|
|
3838
|
+
id: toolCallId,
|
|
3839
|
+
toolName: call.toolName
|
|
3840
|
+
});
|
|
3841
|
+
for (const fragment of toolInputFragments(inputJson)) {
|
|
3842
|
+
if (abortSignal?.aborted)
|
|
3843
|
+
break;
|
|
3844
|
+
await delay(Math.min(chunkDelayMs, 80));
|
|
3845
|
+
controller.enqueue({ type: "tool-input-delta", id: toolCallId, delta: fragment });
|
|
3846
|
+
}
|
|
3847
|
+
controller.enqueue({ type: "tool-input-end", id: toolCallId });
|
|
3848
|
+
controller.enqueue({
|
|
3849
|
+
type: "tool-call",
|
|
3850
|
+
toolCallId,
|
|
3851
|
+
toolName: call.toolName,
|
|
3852
|
+
input: inputJson
|
|
3853
|
+
});
|
|
3854
|
+
}
|
|
3855
|
+
controller.enqueue({
|
|
3856
|
+
type: "finish",
|
|
3857
|
+
finishReason: { unified: "tool-calls", raw: "tool_use" },
|
|
3858
|
+
usage: usageFor(50 * action.calls.length)
|
|
3859
|
+
});
|
|
3860
|
+
controller.close();
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
controller.enqueue({ type: "text-start", id: "t1" });
|
|
3864
|
+
for (const word of action.text.split(" ")) {
|
|
3865
|
+
if (abortSignal?.aborted)
|
|
3866
|
+
break;
|
|
3867
|
+
await delay(chunkDelayMs);
|
|
3868
|
+
controller.enqueue({ type: "text-delta", id: "t1", delta: `${word} ` });
|
|
3869
|
+
}
|
|
3870
|
+
controller.enqueue({ type: "text-end", id: "t1" });
|
|
3871
|
+
controller.enqueue({
|
|
3872
|
+
type: "finish",
|
|
3873
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
3874
|
+
usage: usageFor(action.text.length)
|
|
3875
|
+
});
|
|
3876
|
+
controller.close();
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
controller.enqueue({ type: "text-start", id: "t1" });
|
|
3880
|
+
controller.enqueue({
|
|
3881
|
+
type: "text-delta",
|
|
3882
|
+
id: "t1",
|
|
3883
|
+
delta: `**Story for: "${topic}…"**
|
|
3884
|
+
|
|
3885
|
+
`
|
|
3886
|
+
});
|
|
3887
|
+
for (let i = 0;i < chunkCount; i++) {
|
|
3888
|
+
if (abortSignal?.aborted)
|
|
3889
|
+
break;
|
|
3890
|
+
await delay(chunkDelayMs);
|
|
3891
|
+
controller.enqueue({
|
|
3892
|
+
type: "text-delta",
|
|
3893
|
+
id: "t1",
|
|
3894
|
+
delta: i % 8 === 0 ? `${storyChunk(i)} [${topic.slice(0, 12)}… ¶${i / 8 + 1}]` : storyChunk(i)
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
controller.enqueue({ type: "text-end", id: "t1" });
|
|
3898
|
+
controller.enqueue({
|
|
3899
|
+
type: "finish",
|
|
3900
|
+
finishReason: { unified: "stop", raw: "stop" },
|
|
3901
|
+
usage: usageFor(chunkCount * 12)
|
|
3902
|
+
});
|
|
3903
|
+
controller.close();
|
|
3904
|
+
}
|
|
3905
|
+
});
|
|
3906
|
+
return { stream };
|
|
3907
|
+
}
|
|
3908
|
+
};
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
// ../../var/folders/vh/qdfcqc514qj47bbvzslcwjsc0000gn/T/agent-sdk-publish-eDWqsZ/src/index.ts
|
|
3912
|
+
function createStdlib(options) {
|
|
3913
|
+
const noun = options.workspaceNoun ?? "workspace";
|
|
3914
|
+
const workspace2 = createWorkspace(options.workspaceRoot);
|
|
3915
|
+
const spillDir = join8(options.stateDir, TOOL_OUTPUT_DIRNAME);
|
|
3916
|
+
const runner = createCommandRunner({ workspace: workspace2, spillDir });
|
|
3917
|
+
const registry = createTaskRegistry({
|
|
3918
|
+
storePath: join8(options.stateDir, "tasks.json")
|
|
3919
|
+
});
|
|
3920
|
+
const backgroundables = [
|
|
3921
|
+
createBashOp(runner),
|
|
3922
|
+
...options.extraBackgroundables?.({ workspace: workspace2, runner }) ?? []
|
|
3923
|
+
];
|
|
3924
|
+
const conventionsFileName = options.conventionsFileName ?? "AGENTS.md";
|
|
3925
|
+
const dirConventions = options.injectDirConventions ?? true ? {
|
|
3926
|
+
tracker: createDirConventionsTracker({
|
|
3927
|
+
workspaceRoot: workspace2.root,
|
|
3928
|
+
fileName: conventionsFileName
|
|
3929
|
+
}),
|
|
3930
|
+
fileName: conventionsFileName
|
|
3931
|
+
} : undefined;
|
|
3932
|
+
const steerInbox = options.steer ? createSteerInbox({ dir: options.steer.dir }) : null;
|
|
3933
|
+
const steer2 = createSteerWrapper(steerInbox);
|
|
3934
|
+
return {
|
|
3935
|
+
workspace: workspace2,
|
|
3936
|
+
runner,
|
|
3937
|
+
registry,
|
|
3938
|
+
spillDir,
|
|
3939
|
+
backgroundables,
|
|
3940
|
+
steerInbox,
|
|
3941
|
+
tools: {
|
|
3942
|
+
read: steer2(createReadTool({
|
|
3943
|
+
workspace: workspace2,
|
|
3944
|
+
noun,
|
|
3945
|
+
attachImagesToChat: options.attachImagesToChat ?? true,
|
|
3946
|
+
maxInlineImageBytes: options.maxInlineImageBytes ?? DEFAULT_MAX_INLINE_IMAGE_BYTES,
|
|
3947
|
+
attachVideoToChat: options.attachVideoToChat ?? false,
|
|
3948
|
+
attachAudioToChat: options.attachAudioToChat ?? false,
|
|
3949
|
+
maxInlineMediaBytes: options.maxInlineMediaBytes ?? DEFAULT_MAX_INLINE_MEDIA_BYTES,
|
|
3950
|
+
dirConventions
|
|
3951
|
+
})),
|
|
3952
|
+
edit: steer2(createEditTool({ workspace: workspace2, noun })),
|
|
3953
|
+
write: steer2(createWriteTool({ workspace: workspace2, noun })),
|
|
3954
|
+
glob: steer2(createGlobTool({ workspace: workspace2, noun })),
|
|
3955
|
+
grep: steer2(createGrepTool({ workspace: workspace2, noun, spillDir })),
|
|
3956
|
+
bash: steer2(createBashTool({
|
|
3957
|
+
workspace: workspace2,
|
|
3958
|
+
runner,
|
|
3959
|
+
registry,
|
|
3960
|
+
noun,
|
|
3961
|
+
interactiveHint: options.bashInteractiveHint
|
|
3962
|
+
})),
|
|
3963
|
+
tasks: createTasksTools({ registry, backgroundables, steerInbox }),
|
|
3964
|
+
webfetch: steer2(createWebFetchTool({
|
|
3965
|
+
workspace: workspace2,
|
|
3966
|
+
spillDir,
|
|
3967
|
+
attachImagesToChat: options.attachImagesToChat ?? true,
|
|
3968
|
+
maxInlineImageBytes: options.maxInlineImageBytes ?? DEFAULT_MAX_INLINE_IMAGE_BYTES,
|
|
3969
|
+
attachVideoToChat: options.attachVideoToChat ?? false,
|
|
3970
|
+
attachAudioToChat: options.attachAudioToChat ?? false,
|
|
3971
|
+
maxInlineMediaBytes: options.maxInlineMediaBytes ?? DEFAULT_MAX_INLINE_MEDIA_BYTES
|
|
3972
|
+
}))
|
|
3973
|
+
},
|
|
3974
|
+
instructions: {
|
|
3975
|
+
parallelTools: createParallelToolsInstruction(),
|
|
3976
|
+
repoConventions: createRepoConventionsInstruction({ workspaceRoot: workspace2.root }),
|
|
3977
|
+
subagents: createSubagentInstruction({
|
|
3978
|
+
workspaceNoun: noun,
|
|
3979
|
+
roster: options.subagentRoster
|
|
3980
|
+
}),
|
|
3981
|
+
workflow: createWorkflowInstruction({
|
|
3982
|
+
workspaceNoun: noun,
|
|
3983
|
+
verifyCommandHint: options.verifyCommandHint
|
|
3984
|
+
}),
|
|
3985
|
+
communication: createCommunicationInstruction(),
|
|
3986
|
+
hitl: createHitlInstruction()
|
|
3987
|
+
}
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
function createSandboxFileTools(options) {
|
|
3991
|
+
const noun = options.workspaceNoun ?? "workspace";
|
|
3992
|
+
const workspace2 = createWorkspace(options.workspaceRoot);
|
|
3993
|
+
const io = sandboxIoProvider({
|
|
3994
|
+
root: workspace2.root,
|
|
3995
|
+
...options.resolveSession !== undefined ? { resolveSession: options.resolveSession } : {}
|
|
3996
|
+
});
|
|
3997
|
+
const conventionsFileName = options.conventionsFileName ?? "AGENTS.md";
|
|
3998
|
+
const dirConventions = options.injectDirConventions ?? true ? {
|
|
3999
|
+
tracker: createDirConventionsTracker({
|
|
4000
|
+
workspaceRoot: workspace2.root,
|
|
4001
|
+
fileName: conventionsFileName
|
|
4002
|
+
}),
|
|
4003
|
+
fileName: conventionsFileName
|
|
4004
|
+
} : undefined;
|
|
4005
|
+
return {
|
|
4006
|
+
workspace: workspace2,
|
|
4007
|
+
io,
|
|
4008
|
+
tools: {
|
|
4009
|
+
read: createReadTool({
|
|
4010
|
+
workspace: workspace2,
|
|
4011
|
+
noun,
|
|
4012
|
+
io,
|
|
4013
|
+
attachImagesToChat: options.attachImagesToChat ?? false,
|
|
4014
|
+
maxInlineImageBytes: options.maxInlineImageBytes ?? DEFAULT_MAX_INLINE_IMAGE_BYTES,
|
|
4015
|
+
attachVideoToChat: options.attachVideoToChat ?? false,
|
|
4016
|
+
attachAudioToChat: options.attachAudioToChat ?? false,
|
|
4017
|
+
maxInlineMediaBytes: options.maxInlineMediaBytes ?? DEFAULT_MAX_INLINE_MEDIA_BYTES,
|
|
4018
|
+
dirConventions
|
|
4019
|
+
}),
|
|
4020
|
+
edit: createEditTool({ workspace: workspace2, noun, io }),
|
|
4021
|
+
write: createWriteTool({ workspace: workspace2, noun, io }),
|
|
4022
|
+
glob: createGlobTool({ workspace: workspace2, noun, io }),
|
|
4023
|
+
grep: createGrepTool({
|
|
4024
|
+
workspace: workspace2,
|
|
4025
|
+
noun,
|
|
4026
|
+
io,
|
|
4027
|
+
...options.spillDir !== undefined ? { spillDir: options.spillDir } : {}
|
|
4028
|
+
})
|
|
4029
|
+
}
|
|
4030
|
+
};
|
|
4031
|
+
}
|
|
4032
|
+
export {
|
|
4033
|
+
withSteerDelivery,
|
|
4034
|
+
walkFiles,
|
|
4035
|
+
videoMediaType,
|
|
4036
|
+
toolInputFragments,
|
|
4037
|
+
stripSteerFromOutput,
|
|
4038
|
+
shellSingleQuote,
|
|
4039
|
+
setParkNotificationHandler,
|
|
4040
|
+
serializeSteerLine,
|
|
4041
|
+
searchLocal,
|
|
4042
|
+
scriptStepFrom,
|
|
4043
|
+
scriptActionFor,
|
|
4044
|
+
sandboxIoProvider,
|
|
4045
|
+
resolveWithin,
|
|
4046
|
+
resolveWebFetchTimeoutMs,
|
|
4047
|
+
renderWebText,
|
|
4048
|
+
relativizeWithin,
|
|
4049
|
+
redeliveryFromEvent,
|
|
4050
|
+
readTextForSearch,
|
|
4051
|
+
readSteerMessages,
|
|
4052
|
+
readChatAttachment,
|
|
4053
|
+
postParkNotification,
|
|
4054
|
+
parseSteerLine,
|
|
4055
|
+
parseSearchOutput,
|
|
4056
|
+
parseGatewayModelCatalog,
|
|
4057
|
+
mockScenarioFrom,
|
|
4058
|
+
mergeSteerIntoModelOutput,
|
|
4059
|
+
markdownChunks,
|
|
4060
|
+
looksLikeHtml,
|
|
4061
|
+
localIoProvider,
|
|
4062
|
+
loadFileContent,
|
|
4063
|
+
listGitFiles,
|
|
4064
|
+
lastUserTextFrom,
|
|
4065
|
+
isHtmlContentType,
|
|
4066
|
+
imageMediaType,
|
|
4067
|
+
globToRegExp,
|
|
4068
|
+
formatWatchNotification,
|
|
4069
|
+
formatSteerText,
|
|
4070
|
+
formatCompletionNotification,
|
|
4071
|
+
fetchWebResource,
|
|
4072
|
+
fetchGatewayModelCatalog,
|
|
4073
|
+
extractTextFromHtml,
|
|
4074
|
+
extractSheets,
|
|
4075
|
+
extractSearchExit,
|
|
4076
|
+
extractPdf,
|
|
4077
|
+
extractDocx,
|
|
4078
|
+
expectedTaskToolNames,
|
|
4079
|
+
dirChain,
|
|
4080
|
+
detectFileKind,
|
|
4081
|
+
defineOp,
|
|
4082
|
+
createWriteTool,
|
|
4083
|
+
createWorkspace,
|
|
4084
|
+
createWorkflowInstruction,
|
|
4085
|
+
createWebFetchTool,
|
|
4086
|
+
createTasksTools,
|
|
4087
|
+
createTaskRegistry,
|
|
4088
|
+
createTaskInstruction,
|
|
4089
|
+
createTaskChildTools,
|
|
4090
|
+
createTaskAgent,
|
|
4091
|
+
createSubagentInstruction,
|
|
4092
|
+
createSteerWrapper,
|
|
4093
|
+
createSteerInbox,
|
|
4094
|
+
createStdlib,
|
|
4095
|
+
createStatCache,
|
|
4096
|
+
createSandboxIo,
|
|
4097
|
+
createSandboxFileTools,
|
|
4098
|
+
createRepoConventionsInstruction,
|
|
4099
|
+
createRedeliveryState,
|
|
4100
|
+
createReadTool,
|
|
4101
|
+
createParkDeliveryState,
|
|
4102
|
+
createParkDeliveryHook,
|
|
4103
|
+
createParallelToolsInstruction,
|
|
4104
|
+
createOutputWatcher,
|
|
4105
|
+
createMockStoryModel,
|
|
4106
|
+
createLocalIo,
|
|
4107
|
+
createHitlInstruction,
|
|
4108
|
+
createGrepTool,
|
|
4109
|
+
createGlobTool,
|
|
4110
|
+
createEditTool,
|
|
4111
|
+
createDirConventionsTracker,
|
|
4112
|
+
createCommunicationInstruction,
|
|
4113
|
+
createCommandRunner,
|
|
4114
|
+
createBoundedCapture,
|
|
4115
|
+
createBashTool,
|
|
4116
|
+
createBashOp,
|
|
4117
|
+
convertHtmlToMarkdown,
|
|
4118
|
+
clientContinuationToken,
|
|
4119
|
+
buildWorkflowMarkdown,
|
|
4120
|
+
buildWebFetchHeaders,
|
|
4121
|
+
buildTasksToolset,
|
|
4122
|
+
buildTaskMarkdown,
|
|
4123
|
+
buildTaskDescription,
|
|
4124
|
+
buildSubagentMarkdown,
|
|
4125
|
+
buildSteerPayload,
|
|
4126
|
+
buildRepoConventionsMarkdown,
|
|
4127
|
+
buildRedeliveryMessage,
|
|
4128
|
+
buildHitlMarkdown,
|
|
4129
|
+
buildFileView,
|
|
4130
|
+
buildCommunicationMarkdown,
|
|
4131
|
+
audioMediaType,
|
|
4132
|
+
attachSteerToOutput,
|
|
4133
|
+
assertHttpUrl,
|
|
4134
|
+
__resetTaskRegistryCacheForTests,
|
|
4135
|
+
__resetDirConventionsCacheForTests,
|
|
4136
|
+
WEB_FETCH_PDF_DEFAULT_TIMEOUT_SECONDS,
|
|
4137
|
+
WEB_FETCH_MAX_TIMEOUT_SECONDS,
|
|
4138
|
+
WEB_FETCH_MAX_RESPONSE_BYTES,
|
|
4139
|
+
WEB_FETCH_DEFAULT_TIMEOUT_SECONDS,
|
|
4140
|
+
TOOL_OUTPUT_DIRNAME,
|
|
4141
|
+
TASK_DISABLED_BUILTINS,
|
|
4142
|
+
TASK_CHILD_TOOL_OVERRIDES,
|
|
4143
|
+
TAIL_CHARS,
|
|
4144
|
+
STEER_WRAPPED_OUTPUT_FIELD,
|
|
4145
|
+
STEER_NOTE,
|
|
4146
|
+
STEER_FIELD,
|
|
4147
|
+
STEER_DIRNAME,
|
|
4148
|
+
SHEET_ROW_CAP,
|
|
4149
|
+
SEARCH_OUTPUT_CAP_BYTES,
|
|
4150
|
+
READ_FILE_MAX_LINE_CHARS,
|
|
4151
|
+
READ_FILE_MAX_CONTENT_CHARS,
|
|
4152
|
+
READ_FILE_MAX_BYTES,
|
|
4153
|
+
READ_FILE_DEFAULT_LINE_LIMIT,
|
|
4154
|
+
PDF_PAGE_CAP,
|
|
4155
|
+
PDF_EMPTY_PAGE_NOTE,
|
|
4156
|
+
MOCK_SCENARIOS,
|
|
4157
|
+
MAX_SEARCH_FILE_BYTES,
|
|
4158
|
+
MAX_PREVIEW,
|
|
4159
|
+
HEAD_CHARS,
|
|
4160
|
+
GATEWAY_MODELS_URL,
|
|
4161
|
+
FALLBACK_USER_AGENT,
|
|
4162
|
+
DEFAULT_WATCH_DEBOUNCE_MS,
|
|
4163
|
+
DEFAULT_MAX_WATCH_NOTIFICATIONS,
|
|
4164
|
+
DEFAULT_MAX_INLINE_MEDIA_BYTES,
|
|
4165
|
+
DEFAULT_MAX_INLINE_IMAGE_BYTES,
|
|
4166
|
+
CHAT_ATTACHMENT_FIELD,
|
|
4167
|
+
BROWSER_USER_AGENT,
|
|
4168
|
+
ALWAYS_IGNORED
|
|
4169
|
+
};
|