@wrongstack/runtime 0.277.1 → 0.280.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/dist/clipboard.js +23 -5
- package/dist/clipboard.js.map +1 -1
- package/dist/host.js +20 -18
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +761 -582
- package/dist/index.js.map +1 -1
- package/dist/probe.js.map +1 -1
- package/dist/vision.d.ts +5 -1
- package/dist/vision.js +52 -38
- package/dist/vision.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,124 +1,185 @@
|
|
|
1
|
+
import { buildChildEnv, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, buildRecoveryStrategies, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultPromptLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, createStrategyCompactor, EventBus, resolveModelMatrix, resolveModelTargetFromEntry, ToolRegistry, Context, createDefaultPipelines, applyModelRuntime, mergeModelRuntime, WIDE_SUBAGENT_CAPABILITIES, AutoApprovePermissionPolicy, ToolExecutor, Agent, createFallbackModelExtension, assertNotPrivateHost } from '@wrongstack/core';
|
|
2
|
+
export { DefaultSystemPromptBuilder } from '@wrongstack/core';
|
|
1
3
|
export * from '@wrongstack/core/defaults';
|
|
2
4
|
export { DefaultPathResolver, DefaultTokenCounter } from '@wrongstack/core/infrastructure';
|
|
3
|
-
import { Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, buildRecoveryStrategies, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultPromptLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, createStrategyCompactor, assertNotPrivateHost, buildChildEnv, EventBus, resolveModelMatrix, resolveModelTargetFromEntry, ToolRegistry, Context, WIDE_SUBAGENT_CAPABILITIES, AutoApprovePermissionPolicy, ToolExecutor, Agent, createDefaultPipelines, createFallbackModelExtension } from '@wrongstack/core';
|
|
4
|
-
export { DefaultSystemPromptBuilder } from '@wrongstack/core';
|
|
5
|
-
import * as fs2 from 'fs/promises';
|
|
6
|
-
import * as os2 from 'os';
|
|
7
|
-
import * as path from 'path';
|
|
8
5
|
import { spawn } from 'child_process';
|
|
9
6
|
import { randomUUID } from 'crypto';
|
|
7
|
+
import * as fs from 'fs/promises';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import * as path from 'path';
|
|
10
10
|
|
|
11
11
|
// src/index.ts
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
tools: parts.tools,
|
|
20
|
-
providers: parts.providers,
|
|
21
|
-
slashCommands: parts.slashCommands,
|
|
22
|
-
session: parts.session,
|
|
23
|
-
extensions: parts.extensions,
|
|
24
|
-
async shutdown() {
|
|
25
|
-
await parts.shutdown?.();
|
|
26
|
-
}
|
|
27
|
-
};
|
|
12
|
+
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
13
|
+
async function readClipboardImage() {
|
|
14
|
+
const platform = process.platform;
|
|
15
|
+
if (platform === "win32") return readWindows();
|
|
16
|
+
if (platform === "darwin") return readDarwin();
|
|
17
|
+
if (platform === "linux") return readLinux();
|
|
18
|
+
return null;
|
|
28
19
|
}
|
|
29
|
-
async function
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
host.tools.registerAllOrThrow(tools, owner);
|
|
38
|
-
for (const t of tools) registeredToolNames.push(t.name);
|
|
39
|
-
}
|
|
40
|
-
if (pack.providers) {
|
|
41
|
-
const providers = [...pack.providers];
|
|
42
|
-
host.providers.registerAll(providers);
|
|
43
|
-
for (const p of providers) registeredProviderTypes.push(p.type);
|
|
20
|
+
async function readClipboardText() {
|
|
21
|
+
const platform = process.platform;
|
|
22
|
+
if (platform === "win32") {
|
|
23
|
+
const ps = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw";
|
|
24
|
+
const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
|
|
25
|
+
if (out == null) return null;
|
|
26
|
+
const text = out.replace(/\r?\n$/, "");
|
|
27
|
+
return text.length > 0 ? text : null;
|
|
44
28
|
}
|
|
45
|
-
if (
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
for (const c of cmds) registeredCommandNames.push(c.name);
|
|
29
|
+
if (platform === "darwin") {
|
|
30
|
+
const out = await runCmd("pbpaste", []);
|
|
31
|
+
return out && out.length > 0 ? out : null;
|
|
49
32
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
if (platform === "linux") {
|
|
34
|
+
const tries = [
|
|
35
|
+
["wl-paste", ["--no-newline"]],
|
|
36
|
+
["xclip", ["-selection", "clipboard", "-o"]]
|
|
37
|
+
];
|
|
38
|
+
for (const [cmd, args] of tries) {
|
|
39
|
+
const out = await runCmd(cmd, args);
|
|
40
|
+
if (out && out.length > 0) return out;
|
|
53
41
|
}
|
|
42
|
+
return null;
|
|
54
43
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
async function readWindows() {
|
|
47
|
+
const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);
|
|
48
|
+
const ps = [
|
|
49
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
50
|
+
"Add-Type -AssemblyName System.Drawing",
|
|
51
|
+
"$img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
52
|
+
'if ($img -eq $null) { Write-Output "NO_IMAGE"; exit 0 }',
|
|
53
|
+
`$img.Save('${tmp.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)`,
|
|
54
|
+
'Write-Output "OK"'
|
|
55
|
+
].join("; ");
|
|
56
|
+
const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
|
|
57
|
+
if (!out || out.trim() === "NO_IMAGE") return null;
|
|
58
|
+
if (!out.includes("OK")) return null;
|
|
59
|
+
return readPngFile(tmp);
|
|
60
|
+
}
|
|
61
|
+
async function readDarwin() {
|
|
62
|
+
const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);
|
|
63
|
+
const script = [
|
|
64
|
+
"try",
|
|
65
|
+
` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
|
|
66
|
+
" write (the clipboard as \xABclass PNGf\xBB) to the_file",
|
|
67
|
+
" close access the_file",
|
|
68
|
+
"on error",
|
|
69
|
+
" try",
|
|
70
|
+
' close access POSIX file "' + tmp + '"',
|
|
71
|
+
" end try",
|
|
72
|
+
' return "NO_IMAGE"',
|
|
73
|
+
"end try",
|
|
74
|
+
'return "OK"'
|
|
75
|
+
].join("\n");
|
|
76
|
+
const out = await runCmd("osascript", ["-e", script]);
|
|
77
|
+
if (out?.trim() !== "OK") return null;
|
|
78
|
+
return readPngFile(tmp);
|
|
79
|
+
}
|
|
80
|
+
async function readLinux() {
|
|
81
|
+
const tmp = path.join(os.tmpdir(), `wstack-clip-${randomUUID()}.png`);
|
|
82
|
+
const tries = [
|
|
83
|
+
["wl-paste", ["--type", "image/png"]],
|
|
84
|
+
["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
|
|
85
|
+
];
|
|
86
|
+
for (const [cmd, args] of tries) {
|
|
87
|
+
const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
|
|
88
|
+
if (ok) return readPngFile(tmp);
|
|
76
89
|
}
|
|
77
|
-
return
|
|
78
|
-
pack,
|
|
79
|
-
owner,
|
|
80
|
-
async teardown() {
|
|
81
|
-
for (const unregister of unregisterExtensions.reverse()) {
|
|
82
|
-
unregister();
|
|
83
|
-
}
|
|
84
|
-
for (const name of registeredCommandNames.reverse()) {
|
|
85
|
-
host.slashCommands.unregister(name);
|
|
86
|
-
}
|
|
87
|
-
for (const name of registeredToolNames.reverse()) {
|
|
88
|
-
host.tools.unregister(name);
|
|
89
|
-
}
|
|
90
|
-
for (const type of registeredProviderTypes.reverse()) {
|
|
91
|
-
host.providers.unregister(type);
|
|
92
|
-
}
|
|
93
|
-
if (pack.teardown) {
|
|
94
|
-
if (!opts.api) {
|
|
95
|
-
throw new Error(`Pack "${pack.name}" defines teardown() but no PluginAPI was provided`);
|
|
96
|
-
}
|
|
97
|
-
await pack.teardown(opts.api);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
};
|
|
90
|
+
return null;
|
|
101
91
|
}
|
|
102
|
-
async function
|
|
103
|
-
const applied = [];
|
|
92
|
+
async function readPngFile(p) {
|
|
104
93
|
try {
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
const buf = await fs.readFile(p);
|
|
95
|
+
if (buf.length === 0) {
|
|
96
|
+
await fs.unlink(p).catch(() => void 0);
|
|
97
|
+
return null;
|
|
107
98
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"PackRollbackWarning"
|
|
116
|
-
);
|
|
117
|
-
});
|
|
99
|
+
if (buf.length > MAX_IMAGE_BYTES) {
|
|
100
|
+
await fs.unlink(p).catch(() => void 0);
|
|
101
|
+
throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
|
|
102
|
+
}
|
|
103
|
+
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
|
|
104
|
+
await fs.unlink(p).catch(() => void 0);
|
|
105
|
+
return null;
|
|
118
106
|
}
|
|
107
|
+
await fs.unlink(p).catch(() => void 0);
|
|
108
|
+
return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err.code === "ENOENT") return null;
|
|
119
111
|
throw err;
|
|
120
112
|
}
|
|
121
113
|
}
|
|
114
|
+
var CLIPBOARD_CMD_TIMEOUT_MS = 5e3;
|
|
115
|
+
function runCmd(cmd, args) {
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
const child = spawn(cmd, args, {
|
|
118
|
+
env: buildChildEnv(),
|
|
119
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
120
|
+
windowsHide: true
|
|
121
|
+
});
|
|
122
|
+
let out = "";
|
|
123
|
+
let settled = false;
|
|
124
|
+
let killedByTimeout = false;
|
|
125
|
+
const finish = (value) => {
|
|
126
|
+
if (settled) return;
|
|
127
|
+
settled = true;
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
clearTimeout(killCap);
|
|
130
|
+
resolve(value);
|
|
131
|
+
};
|
|
132
|
+
const timer = setTimeout(() => {
|
|
133
|
+
killedByTimeout = true;
|
|
134
|
+
child.kill("SIGTERM");
|
|
135
|
+
}, CLIPBOARD_CMD_TIMEOUT_MS);
|
|
136
|
+
const killCap = setTimeout(() => finish(null), CLIPBOARD_CMD_TIMEOUT_MS + 2e3);
|
|
137
|
+
child.stdout.on("data", (c) => {
|
|
138
|
+
out += String(c);
|
|
139
|
+
});
|
|
140
|
+
child.on("error", () => finish(null));
|
|
141
|
+
child.on("exit", (code) => {
|
|
142
|
+
if (killedByTimeout) return finish(null);
|
|
143
|
+
finish(code === 0 ? out : null);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function runCmdToFile(cmd, args, outPath) {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
const child = spawn(cmd, args, {
|
|
150
|
+
env: buildChildEnv(),
|
|
151
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
152
|
+
windowsHide: true
|
|
153
|
+
});
|
|
154
|
+
const chunks = [];
|
|
155
|
+
let settled = false;
|
|
156
|
+
let killedByTimeout = false;
|
|
157
|
+
const finish = (value) => {
|
|
158
|
+
if (settled) return;
|
|
159
|
+
settled = true;
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
clearTimeout(killCap);
|
|
162
|
+
resolve(value);
|
|
163
|
+
};
|
|
164
|
+
const timer = setTimeout(() => {
|
|
165
|
+
killedByTimeout = true;
|
|
166
|
+
child.kill("SIGTERM");
|
|
167
|
+
}, CLIPBOARD_CMD_TIMEOUT_MS);
|
|
168
|
+
const killCap = setTimeout(() => finish(false), CLIPBOARD_CMD_TIMEOUT_MS + 2e3);
|
|
169
|
+
child.stdout.on("data", (c) => chunks.push(c));
|
|
170
|
+
child.on("error", () => finish(false));
|
|
171
|
+
child.on("exit", async (code) => {
|
|
172
|
+
if (killedByTimeout) return finish(false);
|
|
173
|
+
if (code !== 0 || chunks.length === 0) return finish(false);
|
|
174
|
+
try {
|
|
175
|
+
await fs.writeFile(outPath, Buffer.concat(chunks));
|
|
176
|
+
finish(true);
|
|
177
|
+
} catch {
|
|
178
|
+
finish(false);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
122
183
|
function createDefaultContainer(opts) {
|
|
123
184
|
const { config, wpaths, logger, modelsRegistry } = opts;
|
|
124
185
|
const container = new Container();
|
|
@@ -140,7 +201,11 @@ function createDefaultContainer(opts) {
|
|
|
140
201
|
container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
|
|
141
202
|
container.bind(
|
|
142
203
|
TOKENS.TokenCounter,
|
|
143
|
-
() => new DefaultTokenCounter({
|
|
204
|
+
() => new DefaultTokenCounter({
|
|
205
|
+
registry: modelsRegistry,
|
|
206
|
+
providerId: config.provider,
|
|
207
|
+
events: opts.events
|
|
208
|
+
})
|
|
144
209
|
);
|
|
145
210
|
const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
|
|
146
211
|
container.bind(TOKENS.ModeStore, () => modeStore);
|
|
@@ -155,9 +220,18 @@ function createDefaultContainer(opts) {
|
|
|
155
220
|
);
|
|
156
221
|
const memoryStore = new DefaultMemoryStore({ paths: wpaths, events: opts.events });
|
|
157
222
|
container.bind(TOKENS.MemoryStore, () => memoryStore);
|
|
158
|
-
const skillLoader = new DefaultSkillLoader({
|
|
223
|
+
const skillLoader = new DefaultSkillLoader({
|
|
224
|
+
paths: wpaths,
|
|
225
|
+
bundledDir: opts.bundledSkillsDir,
|
|
226
|
+
readClaudeSkills: config.skills?.readClaudeSkills,
|
|
227
|
+
foreignSources: config.skills?.foreignSources,
|
|
228
|
+
extraDirs: config.skills?.extraDirs
|
|
229
|
+
});
|
|
159
230
|
container.bind(TOKENS.SkillLoader, () => skillLoader);
|
|
160
|
-
const promptLoader = new DefaultPromptLoader({
|
|
231
|
+
const promptLoader = new DefaultPromptLoader({
|
|
232
|
+
paths: wpaths,
|
|
233
|
+
bundledDir: opts.bundledPromptsDir
|
|
234
|
+
});
|
|
161
235
|
container.bind(TOKENS.PromptLoader, () => promptLoader);
|
|
162
236
|
if (opts.systemPrompt) {
|
|
163
237
|
container.bind(
|
|
@@ -167,25 +241,24 @@ function createDefaultContainer(opts) {
|
|
|
167
241
|
globalDir: wpaths.globalInstructions,
|
|
168
242
|
projectDir: wpaths.inProjectInstructions
|
|
169
243
|
},
|
|
244
|
+
skillMode: config.skills?.mode,
|
|
245
|
+
skillEagerMaxChars: config.skills?.eagerMaxChars,
|
|
170
246
|
...opts.systemPrompt
|
|
171
247
|
})
|
|
172
248
|
);
|
|
173
249
|
}
|
|
174
|
-
container.bind(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (opts.permission?.promptDelegate !== void 0) {
|
|
184
|
-
policyOptions.promptDelegate = opts.permission.promptDelegate;
|
|
185
|
-
}
|
|
186
|
-
return new DefaultPermissionPolicy(policyOptions);
|
|
250
|
+
container.bind(TOKENS.PermissionPolicy, () => {
|
|
251
|
+
const policyOptions = {
|
|
252
|
+
trustFile: wpaths.projectTrust,
|
|
253
|
+
yolo: opts.permission?.yolo ?? false,
|
|
254
|
+
yoloDestructive: opts.permission?.yoloDestructive ?? opts.permission?.forceAllYolo ?? false,
|
|
255
|
+
confirmDestructive: opts.permission?.confirmDestructive ?? false
|
|
256
|
+
};
|
|
257
|
+
if (opts.permission?.promptDelegate !== void 0) {
|
|
258
|
+
policyOptions.promptDelegate = opts.permission.promptDelegate;
|
|
187
259
|
}
|
|
188
|
-
|
|
260
|
+
return new DefaultPermissionPolicy(policyOptions);
|
|
261
|
+
});
|
|
189
262
|
container.bind(
|
|
190
263
|
TOKENS.Compactor,
|
|
191
264
|
() => (
|
|
@@ -209,196 +282,594 @@ function createDefaultContainer(opts) {
|
|
|
209
282
|
);
|
|
210
283
|
return container;
|
|
211
284
|
}
|
|
212
|
-
var
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const out = [];
|
|
246
|
-
let convertedImages = 0;
|
|
247
|
-
let lastErr;
|
|
248
|
-
let adapterName;
|
|
249
|
-
for (const block of blocks) {
|
|
250
|
-
if (block.type !== "image") {
|
|
251
|
-
out.push(block);
|
|
252
|
-
continue;
|
|
285
|
+
var SUBAGENT_BASELINE = "You are a subagent executing one delegated task end-to-end. Work autonomously with your tools; do not ask for confirmation on routine in-project actions. Keep output concise.";
|
|
286
|
+
var _SUBAGENT_ABORT = "wrongstack:subagent-abort-controller";
|
|
287
|
+
function makeLightSubagentFactory(deps) {
|
|
288
|
+
const configStore = deps.container.resolve(TOKENS.ConfigStore);
|
|
289
|
+
const tokenCounter = deps.container.resolve(TOKENS.TokenCounter);
|
|
290
|
+
const secretScrubber = deps.container.resolve(TOKENS.SecretScrubber);
|
|
291
|
+
const systemPromptBuilder = deps.container.resolve(TOKENS.SystemPromptBuilder);
|
|
292
|
+
return async (subCfg) => {
|
|
293
|
+
const events = new EventBus();
|
|
294
|
+
const config = configStore.get();
|
|
295
|
+
const modelsRegistry = deps.modelsRegistry ?? deps.container.safeResolve(TOKENS.ModelsRegistry);
|
|
296
|
+
const matrixEntry = subCfg.model ? void 0 : resolveModelMatrix(config.modelMatrix, subCfg.role);
|
|
297
|
+
const matrixTarget = resolveModelTargetFromEntry(config, matrixEntry);
|
|
298
|
+
const effProvider = subCfg.provider ?? matrixTarget?.provider ?? config.provider;
|
|
299
|
+
const effModel = subCfg.model ?? matrixTarget?.model ?? config.model;
|
|
300
|
+
const provider = buildProvider(deps.providerRegistry, config, effProvider, effModel);
|
|
301
|
+
let subReasoningConfig = await resolveReasoningConfig(modelsRegistry, effProvider, effModel);
|
|
302
|
+
const runtimeOverride = subCfg.modelRuntime ?? matrixTarget?.modelRuntime;
|
|
303
|
+
const subCwd = subCfg.cwd ?? deps.cwd ?? deps.projectRoot;
|
|
304
|
+
const allowed = filterToolList(deps.toolRegistry, subCfg.tools);
|
|
305
|
+
const subRegistry = new ToolRegistry();
|
|
306
|
+
for (const t of allowed) subRegistry.register(t);
|
|
307
|
+
const baseSystem = await systemPromptBuilder.build({
|
|
308
|
+
cwd: subCwd,
|
|
309
|
+
projectRoot: deps.projectRoot,
|
|
310
|
+
tools: allowed,
|
|
311
|
+
model: effModel,
|
|
312
|
+
provider: effProvider,
|
|
313
|
+
subagent: true
|
|
314
|
+
});
|
|
315
|
+
baseSystem.unshift({ type: "text", text: SUBAGENT_BASELINE });
|
|
316
|
+
if (subCfg.systemPromptOverride) {
|
|
317
|
+
baseSystem.push({ type: "text", text: subCfg.systemPromptOverride });
|
|
253
318
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
319
|
+
const agentName = subCfg.name ?? subCfg.id ?? `sub_${cryptoId()}`;
|
|
320
|
+
const session = makeSubagentSessionShim(deps.session);
|
|
321
|
+
const ac = new AbortController();
|
|
322
|
+
const ctx = new Context({
|
|
323
|
+
systemPrompt: baseSystem,
|
|
324
|
+
provider,
|
|
325
|
+
session,
|
|
326
|
+
signal: ac.signal,
|
|
327
|
+
tokenCounter,
|
|
328
|
+
cwd: subCwd,
|
|
329
|
+
projectRoot: deps.projectRoot,
|
|
330
|
+
allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
|
|
331
|
+
model: effModel,
|
|
332
|
+
tools: allowed,
|
|
333
|
+
agentId: agentName,
|
|
334
|
+
agentName,
|
|
335
|
+
traceId: deps.session.traceId
|
|
336
|
+
});
|
|
337
|
+
if (subCfg.role) ctx.meta["agentRole"] = subCfg.role;
|
|
338
|
+
ctx.meta[_SUBAGENT_ABORT] = ac;
|
|
339
|
+
const pipelines = createDefaultPipelines();
|
|
340
|
+
pipelines.request.use({
|
|
341
|
+
name: "ModelRuntimeSettings",
|
|
342
|
+
async handler(req) {
|
|
343
|
+
return applyModelRuntime(req, {
|
|
344
|
+
getSettings: () => mergeModelRuntime(configStore.get().modelRuntime, runtimeOverride),
|
|
345
|
+
getReasoningConfig: () => subReasoningConfig,
|
|
346
|
+
getCapabilities: () => ctx.provider.capabilities
|
|
262
347
|
});
|
|
263
|
-
adapterName = adapter.name;
|
|
264
|
-
break;
|
|
265
|
-
} catch (err) {
|
|
266
|
-
lastErr = err;
|
|
267
348
|
}
|
|
268
|
-
}
|
|
269
|
-
if (!description?.trim()) {
|
|
270
|
-
throw new Error(
|
|
271
|
-
`No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ""}`
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
convertedImages++;
|
|
275
|
-
out.push({
|
|
276
|
-
type: "text",
|
|
277
|
-
text: `[Image ${convertedImages} analyzed via ${adapterName ?? "vision adapter"}]
|
|
278
|
-
${description.trim()}`
|
|
279
349
|
});
|
|
350
|
+
const caps = subCfg.allowedCapabilities ?? WIDE_SUBAGENT_CAPABILITIES;
|
|
351
|
+
const permissionPolicy = new AutoApprovePermissionPolicy(caps);
|
|
352
|
+
const toolExecutor = new ToolExecutor(subRegistry, {
|
|
353
|
+
permissionPolicy,
|
|
354
|
+
secretScrubber,
|
|
355
|
+
events,
|
|
356
|
+
confirmAwaiter: void 0,
|
|
357
|
+
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
|
|
358
|
+
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
|
|
359
|
+
tracer: void 0
|
|
360
|
+
});
|
|
361
|
+
const agent = new Agent({
|
|
362
|
+
container: deps.container,
|
|
363
|
+
tools: subRegistry,
|
|
364
|
+
providers: deps.providerRegistry,
|
|
365
|
+
events,
|
|
366
|
+
pipelines,
|
|
367
|
+
context: ctx,
|
|
368
|
+
permissionPolicy,
|
|
369
|
+
toolExecutor
|
|
370
|
+
});
|
|
371
|
+
const subFallbacks = subCfg.fallbackModels;
|
|
372
|
+
const matrixFallbacks = matrixTarget?.fallbackModels;
|
|
373
|
+
agent.extensions.register(
|
|
374
|
+
createFallbackModelExtension({
|
|
375
|
+
getConfig: () => {
|
|
376
|
+
const live = configStore.get();
|
|
377
|
+
if (subFallbacks?.length) return { ...live, fallbackModels: subFallbacks };
|
|
378
|
+
if (matrixFallbacks?.length) return { ...live, fallbackModels: matrixFallbacks };
|
|
379
|
+
return live;
|
|
380
|
+
},
|
|
381
|
+
buildProvider: (id, model) => buildProvider(deps.providerRegistry, configStore.get(), id, model ?? effModel),
|
|
382
|
+
onModelSwitch: async (id, model) => {
|
|
383
|
+
subReasoningConfig = await resolveReasoningConfig(modelsRegistry, id, model);
|
|
384
|
+
},
|
|
385
|
+
events
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
return { agent, events };
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function filterToolList(registry, allow) {
|
|
392
|
+
const all = registry.list();
|
|
393
|
+
if (!allow || allow.length === 0) return all;
|
|
394
|
+
const allowSet = new Set(allow);
|
|
395
|
+
return all.filter((t) => allowSet.has(t.name));
|
|
396
|
+
}
|
|
397
|
+
function buildProvider(registry, config, providerId, model) {
|
|
398
|
+
const providerConfig = config.providers?.[providerId] ?? {
|
|
399
|
+
type: providerId,
|
|
400
|
+
apiKey: config.apiKey,
|
|
401
|
+
baseUrl: config.baseUrl
|
|
402
|
+
};
|
|
403
|
+
if (!registry.has(providerId)) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`No provider factory registered for "${providerId}" \u2014 cannot build a subagent provider for the SDD run.`
|
|
406
|
+
);
|
|
280
407
|
}
|
|
281
|
-
return {
|
|
408
|
+
return registry.create({ ...providerConfig, type: providerId, model });
|
|
282
409
|
}
|
|
283
|
-
async function
|
|
284
|
-
if (!
|
|
285
|
-
|
|
410
|
+
async function resolveReasoningConfig(registry, providerId, modelId) {
|
|
411
|
+
if (!registry) return void 0;
|
|
412
|
+
try {
|
|
413
|
+
return (await registry.getModel(providerId, modelId))?.capabilities.reasoningConfig;
|
|
414
|
+
} catch {
|
|
415
|
+
return void 0;
|
|
416
|
+
}
|
|
286
417
|
}
|
|
287
|
-
function
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
418
|
+
function makeSubagentSessionShim(parent) {
|
|
419
|
+
return {
|
|
420
|
+
id: parent.id,
|
|
421
|
+
transcriptPath: parent.transcriptPath,
|
|
422
|
+
get traceId() {
|
|
423
|
+
return parent.traceId;
|
|
424
|
+
},
|
|
425
|
+
set traceId(value) {
|
|
426
|
+
parent.traceId = value;
|
|
427
|
+
},
|
|
428
|
+
get pendingToolUses() {
|
|
429
|
+
return [];
|
|
430
|
+
},
|
|
431
|
+
append: (ev) => parent.append({ ...ev }),
|
|
432
|
+
appendBatch: (evs) => parent.appendBatch(evs.map((e) => ({ ...e }))),
|
|
433
|
+
flush: () => parent.flush(),
|
|
434
|
+
close: async () => {
|
|
435
|
+
},
|
|
436
|
+
recordFileChange: () => {
|
|
437
|
+
},
|
|
438
|
+
recordSideEffect: () => {
|
|
439
|
+
},
|
|
440
|
+
writeCheckpoint: async () => {
|
|
441
|
+
},
|
|
442
|
+
writeFileSnapshot: async () => {
|
|
443
|
+
},
|
|
444
|
+
truncateToCheckpoint: async () => 0,
|
|
445
|
+
clearSession: async () => {
|
|
446
|
+
},
|
|
447
|
+
writeInFlightMarker: async () => {
|
|
448
|
+
},
|
|
449
|
+
clearInFlightMarker: async () => {
|
|
307
450
|
}
|
|
308
|
-
}
|
|
451
|
+
};
|
|
309
452
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
const haystack = `${tool.name} ${tool.description ?? ""} ${tool.usageHint ?? ""}`.toLowerCase();
|
|
313
|
-
if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;
|
|
314
|
-
if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;
|
|
315
|
-
const props = schemaProperties(tool);
|
|
316
|
-
return [
|
|
317
|
-
"image",
|
|
318
|
-
"base64",
|
|
319
|
-
"data",
|
|
320
|
-
"url",
|
|
321
|
-
"image_url",
|
|
322
|
-
"imageUrl",
|
|
323
|
-
"path",
|
|
324
|
-
"image_path",
|
|
325
|
-
"imagePath",
|
|
326
|
-
"image_url",
|
|
327
|
-
"imageUrl",
|
|
328
|
-
"file_path",
|
|
329
|
-
"filePath",
|
|
330
|
-
"filename",
|
|
331
|
-
"file",
|
|
332
|
-
"mediaType",
|
|
333
|
-
"mimeType"
|
|
334
|
-
].some((key) => key in props);
|
|
453
|
+
function cryptoId() {
|
|
454
|
+
return randomUUID().slice(0, 8);
|
|
335
455
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
456
|
+
|
|
457
|
+
// src/host.ts
|
|
458
|
+
function createRuntimeHostFromParts(parts) {
|
|
459
|
+
return {
|
|
460
|
+
agent: parts.agent,
|
|
461
|
+
context: parts.context,
|
|
462
|
+
events: parts.events,
|
|
463
|
+
tools: parts.tools,
|
|
464
|
+
providers: parts.providers,
|
|
465
|
+
slashCommands: parts.slashCommands,
|
|
466
|
+
session: parts.session,
|
|
467
|
+
extensions: parts.extensions,
|
|
468
|
+
async shutdown() {
|
|
469
|
+
await parts.shutdown?.();
|
|
349
470
|
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
async function applyWrongStackPack(host, pack, opts = {}) {
|
|
474
|
+
const owner = opts.owner ?? pack.name;
|
|
475
|
+
const unregisterExtensions = [];
|
|
476
|
+
const registeredToolNames = [];
|
|
477
|
+
const registeredCommandNames = [];
|
|
478
|
+
const registeredProviderTypes = [];
|
|
479
|
+
if (pack.tools) {
|
|
480
|
+
const tools = [...pack.tools];
|
|
481
|
+
host.tools.registerAllOrThrow(tools, owner);
|
|
482
|
+
for (const t of tools) registeredToolNames.push(t.name);
|
|
350
483
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
"image_url",
|
|
356
|
-
"imageUrl",
|
|
357
|
-
"file_path",
|
|
358
|
-
"filePath",
|
|
359
|
-
"filename",
|
|
360
|
-
"file"
|
|
361
|
-
]);
|
|
362
|
-
if (pathKey && image.source.type === "base64" && data) {
|
|
363
|
-
const p = await writeTempImage(data, mediaType);
|
|
364
|
-
payload[pathKey] = p;
|
|
365
|
-
cleanup = async () => {
|
|
366
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
367
|
-
await fs2.rmdir(path.dirname(p)).catch(() => void 0);
|
|
368
|
-
};
|
|
369
|
-
} else if ("image" in props) {
|
|
370
|
-
payload.image = image.source.type === "base64" ? { type: "base64", mediaType, media_type: mediaType, data } : { type: "url", url };
|
|
371
|
-
} else if (image.source.type === "base64" && "base64" in props) {
|
|
372
|
-
payload.base64 = data;
|
|
373
|
-
} else if (image.source.type === "base64" && "data" in props) {
|
|
374
|
-
payload.data = data;
|
|
375
|
-
} else if (image.source.type === "url" && "url" in props) {
|
|
376
|
-
payload.url = url;
|
|
377
|
-
} else if (image.source.type === "url" && "image_url" in props) {
|
|
378
|
-
payload.image_url = url;
|
|
379
|
-
} else if (image.source.type === "url" && "imageUrl" in props) {
|
|
380
|
-
payload.imageUrl = url;
|
|
381
|
-
} else {
|
|
382
|
-
return null;
|
|
484
|
+
if (pack.providers) {
|
|
485
|
+
const providers = [...pack.providers];
|
|
486
|
+
host.providers.registerAll(providers);
|
|
487
|
+
for (const p of providers) registeredProviderTypes.push(p.type);
|
|
383
488
|
}
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
489
|
+
if (pack.slashCommands) {
|
|
490
|
+
const cmds = [...pack.slashCommands];
|
|
491
|
+
host.slashCommands.registerAll(cmds, owner);
|
|
492
|
+
for (const c of cmds)
|
|
493
|
+
registeredCommandNames.push(owner === "core" ? c.name : `${owner}:${c.name}`);
|
|
494
|
+
}
|
|
495
|
+
if (pack.extensions && host.extensions) {
|
|
496
|
+
for (const ext of pack.extensions) {
|
|
497
|
+
unregisterExtensions.push(host.extensions.register(ext));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (pack.setup) {
|
|
501
|
+
if (!opts.api) {
|
|
502
|
+
throw new Error(`Pack "${pack.name}" defines setup() but no PluginAPI was provided`);
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
await pack.setup(opts.api);
|
|
506
|
+
} catch (setupErr) {
|
|
507
|
+
for (let i = unregisterExtensions.length - 1; i >= 0; i--) {
|
|
508
|
+
unregisterExtensions[i]();
|
|
509
|
+
}
|
|
510
|
+
for (let i = registeredCommandNames.length - 1; i >= 0; i--) {
|
|
511
|
+
host.slashCommands.unregister(registeredCommandNames[i]);
|
|
512
|
+
}
|
|
513
|
+
for (let i = registeredToolNames.length - 1; i >= 0; i--) {
|
|
514
|
+
host.tools.unregister(registeredToolNames[i]);
|
|
515
|
+
}
|
|
516
|
+
for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {
|
|
517
|
+
host.providers.unregister(registeredProviderTypes[i]);
|
|
518
|
+
}
|
|
519
|
+
throw setupErr;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
pack,
|
|
524
|
+
owner,
|
|
525
|
+
async teardown() {
|
|
526
|
+
for (let i = unregisterExtensions.length - 1; i >= 0; i--) {
|
|
527
|
+
unregisterExtensions[i]();
|
|
528
|
+
}
|
|
529
|
+
for (let i = registeredCommandNames.length - 1; i >= 0; i--) {
|
|
530
|
+
host.slashCommands.unregister(registeredCommandNames[i]);
|
|
531
|
+
}
|
|
532
|
+
for (let i = registeredToolNames.length - 1; i >= 0; i--) {
|
|
533
|
+
host.tools.unregister(registeredToolNames[i]);
|
|
534
|
+
}
|
|
535
|
+
for (let i = registeredProviderTypes.length - 1; i >= 0; i--) {
|
|
536
|
+
host.providers.unregister(registeredProviderTypes[i]);
|
|
537
|
+
}
|
|
538
|
+
if (pack.teardown) {
|
|
539
|
+
if (!opts.api) {
|
|
540
|
+
throw new Error(`Pack "${pack.name}" defines teardown() but no PluginAPI was provided`);
|
|
541
|
+
}
|
|
542
|
+
await pack.teardown(opts.api);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
async function applyWrongStackPacks(host, packs, opts = {}) {
|
|
548
|
+
const applied = [];
|
|
549
|
+
try {
|
|
550
|
+
for (const pack of packs) {
|
|
551
|
+
applied.push(await applyWrongStackPack(host, pack, opts));
|
|
552
|
+
}
|
|
553
|
+
return applied;
|
|
554
|
+
} catch (err) {
|
|
555
|
+
for (let i = applied.length - 1; i >= 0; i--) {
|
|
556
|
+
const mounted = applied[i];
|
|
557
|
+
await mounted.teardown().catch((teardownErr) => {
|
|
558
|
+
const detail = teardownErr instanceof Error ? teardownErr.message : String(teardownErr);
|
|
559
|
+
process.emitWarning(
|
|
560
|
+
`Pack teardown during error rollback failed: ${detail}`,
|
|
561
|
+
"PackRollbackWarning"
|
|
562
|
+
);
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
throw err;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/local-llm-probe.ts
|
|
570
|
+
var PROBE_TIMEOUT_MS = 3e3;
|
|
571
|
+
async function probeLocalLlm(opts) {
|
|
572
|
+
const { baseUrl, apiKey, noAuth, scrubber, fetchImpl, timeoutMs } = opts;
|
|
573
|
+
const fetchFn = fetchImpl ?? fetch;
|
|
574
|
+
const timeout = timeoutMs ?? PROBE_TIMEOUT_MS;
|
|
575
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
576
|
+
const url = /\/models$/.test(base) ? base : `${base}/models`;
|
|
577
|
+
const headers = { accept: "application/json" };
|
|
578
|
+
if (!noAuth && apiKey) {
|
|
579
|
+
headers["authorization"] = `Bearer ${apiKey}`;
|
|
580
|
+
}
|
|
581
|
+
const started = Date.now();
|
|
582
|
+
let res;
|
|
583
|
+
try {
|
|
584
|
+
res = await fetchFn(url, {
|
|
585
|
+
method: "GET",
|
|
586
|
+
headers,
|
|
587
|
+
signal: AbortSignal.timeout(timeout)
|
|
588
|
+
});
|
|
589
|
+
} catch (err) {
|
|
590
|
+
const elapsed2 = Date.now() - started;
|
|
591
|
+
const name = err?.name;
|
|
592
|
+
if (name === "TimeoutError" || name === "AbortError") {
|
|
593
|
+
return { ok: false, status: "timeout", elapsedMs: elapsed2, detail: `> ${timeout}ms` };
|
|
594
|
+
}
|
|
595
|
+
const detail = scrubber.scrub(err?.message ?? String(err));
|
|
596
|
+
return { ok: false, status: "unreachable", elapsedMs: elapsed2, detail };
|
|
597
|
+
}
|
|
598
|
+
const elapsed = Date.now() - started;
|
|
599
|
+
if (!res.ok) {
|
|
600
|
+
let bodySlice = "";
|
|
601
|
+
try {
|
|
602
|
+
const txt = await res.text();
|
|
603
|
+
bodySlice = txt.slice(0, 200);
|
|
604
|
+
} catch {
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
ok: false,
|
|
608
|
+
status: "http_error",
|
|
609
|
+
httpStatus: res.status,
|
|
610
|
+
elapsedMs: elapsed,
|
|
611
|
+
detail: scrubber.scrub(bodySlice) || void 0
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const modelIds = [];
|
|
615
|
+
try {
|
|
616
|
+
const parsed = await res.json();
|
|
617
|
+
if (!parsed || typeof parsed !== "object") {
|
|
618
|
+
return {
|
|
619
|
+
ok: false,
|
|
620
|
+
status: "invalid_response",
|
|
621
|
+
httpStatus: res.status,
|
|
622
|
+
elapsedMs: elapsed,
|
|
623
|
+
detail: "response is not a JSON object"
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
const obj = parsed;
|
|
627
|
+
const dataList = obj["data"];
|
|
628
|
+
const modelsList = obj["models"];
|
|
629
|
+
const rawList = Array.isArray(dataList) ? dataList : Array.isArray(modelsList) ? modelsList : null;
|
|
630
|
+
if (rawList === null) {
|
|
631
|
+
return {
|
|
632
|
+
ok: false,
|
|
633
|
+
status: "invalid_response",
|
|
634
|
+
httpStatus: res.status,
|
|
635
|
+
elapsedMs: elapsed,
|
|
636
|
+
detail: "no `data` or `models` array in response"
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
const seen = /* @__PURE__ */ new Set();
|
|
640
|
+
for (const entry of rawList) {
|
|
641
|
+
if (!entry || typeof entry !== "object") continue;
|
|
642
|
+
const e = entry;
|
|
643
|
+
const raw = typeof e["id"] === "string" ? e["id"] : typeof e["name"] === "string" ? e["name"] : null;
|
|
644
|
+
if (raw === null) continue;
|
|
645
|
+
const id = scrubber.scrub(raw).trim();
|
|
646
|
+
if (id.length === 0) continue;
|
|
647
|
+
if (seen.has(id)) continue;
|
|
648
|
+
seen.add(id);
|
|
649
|
+
modelIds.push(id);
|
|
650
|
+
}
|
|
651
|
+
} catch (err) {
|
|
652
|
+
return {
|
|
653
|
+
ok: false,
|
|
654
|
+
status: "invalid_response",
|
|
655
|
+
httpStatus: res.status,
|
|
656
|
+
elapsedMs: elapsed,
|
|
657
|
+
detail: scrubber.scrub(err?.message ?? "parse failed")
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
ok: true,
|
|
662
|
+
status: "ok",
|
|
663
|
+
httpStatus: res.status,
|
|
664
|
+
elapsedMs: elapsed,
|
|
665
|
+
modelCount: modelIds.length,
|
|
666
|
+
modelIds
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
var ImageInputUnsupportedError = class extends Error {
|
|
670
|
+
constructor(opts) {
|
|
671
|
+
const target = [opts.providerId, opts.model].filter(Boolean).join("/") || "current model";
|
|
672
|
+
super(
|
|
673
|
+
`${target} does not support image input, and no image-understanding adapter is available for ${opts.imageCount} image${opts.imageCount === 1 ? "" : "s"}. Switch to a vision model or enable an MCP/tool adapter that can describe images.`
|
|
674
|
+
);
|
|
675
|
+
this.name = "ImageInputUnsupportedError";
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
var VisionUrlBlockedError = class extends Error {
|
|
679
|
+
url;
|
|
680
|
+
constructor(opts) {
|
|
681
|
+
super(`vision: blocked unsafe image URL "${opts.url}" (${opts.reason})`);
|
|
682
|
+
this.name = "VisionUrlBlockedError";
|
|
683
|
+
this.url = opts.url;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
async function routeImagesForModel(blocks, opts) {
|
|
687
|
+
const images = blocks.filter((b) => b.type === "image");
|
|
688
|
+
if (images.length === 0) {
|
|
689
|
+
return { blocks, route: "none", convertedImages: 0 };
|
|
690
|
+
}
|
|
691
|
+
if (opts.supportsVision) {
|
|
692
|
+
for (const img of images) {
|
|
693
|
+
if (img.source.type === "url" && img.source.url) {
|
|
694
|
+
try {
|
|
695
|
+
await assertNotPrivateHost(new URL(img.source.url).hostname);
|
|
696
|
+
} catch (err) {
|
|
697
|
+
const reason = err instanceof Error && err.message.startsWith("fetch:") ? err.message.slice("fetch:".length).trim() : "unresolvable host";
|
|
698
|
+
throw new VisionUrlBlockedError({ url: img.source.url, reason });
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return { blocks, route: "native", convertedImages: 0 };
|
|
703
|
+
}
|
|
704
|
+
const adapters = await resolveAdapters(opts.adapters);
|
|
705
|
+
if (adapters.length === 0) {
|
|
706
|
+
throw new ImageInputUnsupportedError({
|
|
707
|
+
providerId: opts.providerId,
|
|
708
|
+
model: opts.model,
|
|
709
|
+
imageCount: images.length
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
const out = [];
|
|
713
|
+
let convertedImages = 0;
|
|
714
|
+
let lastErr;
|
|
715
|
+
let adapterName;
|
|
716
|
+
for (const block of blocks) {
|
|
717
|
+
if (block.type !== "image") {
|
|
718
|
+
out.push(block);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
let description;
|
|
722
|
+
for (const adapter of adapters) {
|
|
723
|
+
try {
|
|
724
|
+
description = await adapter.describe({
|
|
725
|
+
image: block,
|
|
726
|
+
prompt: opts.prompt,
|
|
727
|
+
ctx: opts.ctx,
|
|
728
|
+
signal: opts.signal
|
|
729
|
+
});
|
|
730
|
+
adapterName = adapter.name;
|
|
731
|
+
break;
|
|
732
|
+
} catch (err) {
|
|
733
|
+
lastErr = err;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (!description?.trim()) {
|
|
737
|
+
throw new Error(
|
|
738
|
+
`No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ""}`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
convertedImages++;
|
|
742
|
+
out.push({
|
|
743
|
+
type: "text",
|
|
744
|
+
text: `[Image ${convertedImages} analyzed via ${adapterName ?? "vision adapter"}]
|
|
745
|
+
${description.trim()}`
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return { blocks: out, route: "adapter", convertedImages, adapterName };
|
|
749
|
+
}
|
|
750
|
+
async function resolveAdapters(adapters) {
|
|
751
|
+
if (!adapters) return [];
|
|
752
|
+
return typeof adapters === "function" ? await adapters() : adapters;
|
|
753
|
+
}
|
|
754
|
+
function createToolVisionAdapters(registry, opts = {}) {
|
|
755
|
+
return registry.list().filter(isLikelyVisionTool).map((tool) => ({
|
|
756
|
+
name: tool.name,
|
|
757
|
+
async describe(input) {
|
|
758
|
+
const currentTool = registry.get(tool.name);
|
|
759
|
+
if (!currentTool) {
|
|
760
|
+
throw new Error(`Tool "${tool.name}" is no longer registered`);
|
|
761
|
+
}
|
|
762
|
+
const built = await buildToolPayload(currentTool, input.image, input.prompt ?? opts.prompt);
|
|
763
|
+
if (!built) {
|
|
764
|
+
throw new Error(
|
|
765
|
+
`Tool "${currentTool.name}" does not expose a supported image input schema`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
const result = await currentTool.execute(built.payload, input.ctx, {
|
|
770
|
+
signal: input.signal
|
|
771
|
+
});
|
|
772
|
+
return stringifyToolResult(result);
|
|
773
|
+
} finally {
|
|
774
|
+
await built.cleanup?.();
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}));
|
|
778
|
+
}
|
|
779
|
+
var VISION_IMAGE_KEYS = [
|
|
780
|
+
"image",
|
|
781
|
+
"base64",
|
|
782
|
+
"data",
|
|
783
|
+
"url",
|
|
784
|
+
"image_url",
|
|
785
|
+
"imageUrl",
|
|
786
|
+
"path",
|
|
787
|
+
"image_path",
|
|
788
|
+
"imagePath",
|
|
789
|
+
"file_path",
|
|
790
|
+
"filePath",
|
|
791
|
+
"filename",
|
|
792
|
+
"file",
|
|
793
|
+
"mediaType",
|
|
794
|
+
"mimeType"
|
|
795
|
+
];
|
|
796
|
+
var VISION_PATH_KEYS = [
|
|
797
|
+
"path",
|
|
798
|
+
"image_path",
|
|
799
|
+
"imagePath",
|
|
800
|
+
"image_url",
|
|
801
|
+
"imageUrl",
|
|
802
|
+
"file_path",
|
|
803
|
+
"filePath",
|
|
804
|
+
"filename",
|
|
805
|
+
"file"
|
|
806
|
+
];
|
|
807
|
+
var VISION_MEDIA_TYPE_KEYS = ["mediaType", "mimeType", "media_type"];
|
|
808
|
+
var VISION_PROMPT_KEYS = ["prompt", "query", "instruction"];
|
|
809
|
+
function isLikelyVisionTool(tool) {
|
|
810
|
+
if (tool.permission !== "auto" || tool.mutating) return false;
|
|
811
|
+
const haystack = `${tool.name} ${tool.description ?? ""} ${tool.usageHint ?? ""}`.toLowerCase();
|
|
812
|
+
if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;
|
|
813
|
+
if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;
|
|
814
|
+
const props = schemaProperties(tool);
|
|
815
|
+
return VISION_IMAGE_KEYS.some((key) => key in props);
|
|
816
|
+
}
|
|
817
|
+
async function buildToolPayload(tool, image, prompt = "Describe this image for a coding agent. Include visible text, UI state, errors, layout, and any details needed to answer the user.") {
|
|
818
|
+
const props = schemaProperties(tool);
|
|
819
|
+
const payload = {};
|
|
820
|
+
const mediaType = image.source.media_type ?? "image/png";
|
|
821
|
+
const data = image.source.data;
|
|
822
|
+
const url = image.source.url;
|
|
823
|
+
let cleanup;
|
|
824
|
+
if (image.source.type === "url" && url) {
|
|
825
|
+
try {
|
|
826
|
+
await assertNotPrivateHost(new URL(url).hostname);
|
|
827
|
+
} catch (err) {
|
|
828
|
+
const reason = err instanceof Error && err.message.startsWith("fetch:") ? err.message.slice("fetch:".length).trim() : "unresolvable host";
|
|
829
|
+
throw new VisionUrlBlockedError({ url, reason });
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const pathKey = firstPresent(props, [...VISION_PATH_KEYS]);
|
|
833
|
+
if (pathKey && image.source.type === "base64" && data) {
|
|
834
|
+
const p = await writeTempImage(data, mediaType);
|
|
835
|
+
payload[pathKey] = p;
|
|
836
|
+
cleanup = async () => {
|
|
837
|
+
await fs.unlink(p).catch(() => void 0);
|
|
838
|
+
await fs.rmdir(path.dirname(p)).catch(() => void 0);
|
|
839
|
+
};
|
|
840
|
+
} else if ("image" in props) {
|
|
841
|
+
payload.image = image.source.type === "base64" ? { type: "base64", mediaType, media_type: mediaType, data } : { type: "url", url };
|
|
842
|
+
} else if (image.source.type === "base64" && "base64" in props) {
|
|
843
|
+
payload.base64 = data;
|
|
844
|
+
} else if (image.source.type === "base64" && "data" in props) {
|
|
845
|
+
payload.data = data;
|
|
846
|
+
} else if (image.source.type === "url" && "url" in props) {
|
|
847
|
+
payload.url = url;
|
|
848
|
+
} else if (image.source.type === "url" && "image_url" in props) {
|
|
849
|
+
payload.image_url = url;
|
|
850
|
+
} else if (image.source.type === "url" && "imageUrl" in props) {
|
|
851
|
+
payload.imageUrl = url;
|
|
852
|
+
} else {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
for (const key of VISION_MEDIA_TYPE_KEYS) {
|
|
856
|
+
if (key in props) payload[key] = mediaType;
|
|
857
|
+
}
|
|
858
|
+
for (const key of VISION_PROMPT_KEYS) {
|
|
859
|
+
if (key in props) payload[key] = prompt;
|
|
860
|
+
}
|
|
861
|
+
const built = { payload };
|
|
862
|
+
if (cleanup !== void 0) built.cleanup = cleanup;
|
|
863
|
+
return built;
|
|
393
864
|
}
|
|
394
865
|
function firstPresent(props, keys) {
|
|
395
866
|
return keys.find((key) => key in props);
|
|
396
867
|
}
|
|
397
868
|
async function writeTempImage(data, mediaType) {
|
|
398
869
|
const ext = mediaType.includes("jpeg") || mediaType.includes("jpg") ? "jpg" : "png";
|
|
399
|
-
const dir = await
|
|
870
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "wstack-vision-"));
|
|
400
871
|
const file = path.join(dir, `image.${ext}`);
|
|
401
|
-
await
|
|
872
|
+
await fs.writeFile(file, data, "base64");
|
|
402
873
|
return file;
|
|
403
874
|
}
|
|
404
875
|
function schemaProperties(tool) {
|
|
@@ -422,299 +893,7 @@ function stringifyToolResult(value) {
|
|
|
422
893
|
}
|
|
423
894
|
return JSON.stringify(value);
|
|
424
895
|
}
|
|
425
|
-
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
426
|
-
async function readClipboardImage() {
|
|
427
|
-
const platform = process.platform;
|
|
428
|
-
if (platform === "win32") return readWindows();
|
|
429
|
-
if (platform === "darwin") return readDarwin();
|
|
430
|
-
if (platform === "linux") return readLinux();
|
|
431
|
-
return null;
|
|
432
|
-
}
|
|
433
|
-
async function readClipboardText() {
|
|
434
|
-
const platform = process.platform;
|
|
435
|
-
if (platform === "win32") {
|
|
436
|
-
const ps = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard -Raw";
|
|
437
|
-
const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
|
|
438
|
-
if (out == null) return null;
|
|
439
|
-
const text = out.replace(/\r?\n$/, "");
|
|
440
|
-
return text.length > 0 ? text : null;
|
|
441
|
-
}
|
|
442
|
-
if (platform === "darwin") {
|
|
443
|
-
const out = await runCmd("pbpaste", []);
|
|
444
|
-
return out && out.length > 0 ? out : null;
|
|
445
|
-
}
|
|
446
|
-
if (platform === "linux") {
|
|
447
|
-
const tries = [
|
|
448
|
-
["wl-paste", ["--no-newline"]],
|
|
449
|
-
["xclip", ["-selection", "clipboard", "-o"]]
|
|
450
|
-
];
|
|
451
|
-
for (const [cmd, args] of tries) {
|
|
452
|
-
const out = await runCmd(cmd, args);
|
|
453
|
-
if (out && out.length > 0) return out;
|
|
454
|
-
}
|
|
455
|
-
return null;
|
|
456
|
-
}
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
async function readWindows() {
|
|
460
|
-
const tmp = path.join(os2.tmpdir(), `wstack-clip-${randomUUID()}.png`);
|
|
461
|
-
const ps = [
|
|
462
|
-
"Add-Type -AssemblyName System.Windows.Forms",
|
|
463
|
-
"Add-Type -AssemblyName System.Drawing",
|
|
464
|
-
"$img = [System.Windows.Forms.Clipboard]::GetImage()",
|
|
465
|
-
'if ($img -eq $null) { Write-Output "NO_IMAGE"; exit 0 }',
|
|
466
|
-
`$img.Save('${tmp.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)`,
|
|
467
|
-
'Write-Output "OK"'
|
|
468
|
-
].join("; ");
|
|
469
|
-
const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
|
|
470
|
-
if (!out || out.trim() === "NO_IMAGE") return null;
|
|
471
|
-
if (!out.includes("OK")) return null;
|
|
472
|
-
return readPngFile(tmp);
|
|
473
|
-
}
|
|
474
|
-
async function readDarwin() {
|
|
475
|
-
const tmp = path.join(os2.tmpdir(), `wstack-clip-${randomUUID()}.png`);
|
|
476
|
-
const script = [
|
|
477
|
-
"try",
|
|
478
|
-
` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
|
|
479
|
-
" write (the clipboard as \xABclass PNGf\xBB) to the_file",
|
|
480
|
-
" close access the_file",
|
|
481
|
-
"on error",
|
|
482
|
-
" try",
|
|
483
|
-
' close access POSIX file "' + tmp + '"',
|
|
484
|
-
" end try",
|
|
485
|
-
' return "NO_IMAGE"',
|
|
486
|
-
"end try",
|
|
487
|
-
'return "OK"'
|
|
488
|
-
].join("\n");
|
|
489
|
-
const out = await runCmd("osascript", ["-e", script]);
|
|
490
|
-
if (out?.trim() !== "OK") return null;
|
|
491
|
-
return readPngFile(tmp);
|
|
492
|
-
}
|
|
493
|
-
async function readLinux() {
|
|
494
|
-
const tmp = path.join(os2.tmpdir(), `wstack-clip-${randomUUID()}.png`);
|
|
495
|
-
const tries = [
|
|
496
|
-
["wl-paste", ["--type", "image/png"]],
|
|
497
|
-
["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
|
|
498
|
-
];
|
|
499
|
-
for (const [cmd, args] of tries) {
|
|
500
|
-
const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
|
|
501
|
-
if (ok) return readPngFile(tmp);
|
|
502
|
-
}
|
|
503
|
-
return null;
|
|
504
|
-
}
|
|
505
|
-
async function readPngFile(p) {
|
|
506
|
-
try {
|
|
507
|
-
const buf = await fs2.readFile(p);
|
|
508
|
-
if (buf.length === 0) {
|
|
509
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
510
|
-
return null;
|
|
511
|
-
}
|
|
512
|
-
if (buf.length > MAX_IMAGE_BYTES) {
|
|
513
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
514
|
-
throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
|
|
515
|
-
}
|
|
516
|
-
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
|
|
517
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
await fs2.unlink(p).catch(() => void 0);
|
|
521
|
-
return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
|
|
522
|
-
} catch (err) {
|
|
523
|
-
if (err.code === "ENOENT") return null;
|
|
524
|
-
throw err;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
var CLIPBOARD_CMD_TIMEOUT_MS = 5e3;
|
|
528
|
-
function runCmd(cmd, args) {
|
|
529
|
-
return new Promise((resolve) => {
|
|
530
|
-
const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
531
|
-
let out = "";
|
|
532
|
-
let settled = false;
|
|
533
|
-
const finish = (value) => {
|
|
534
|
-
if (settled) return;
|
|
535
|
-
settled = true;
|
|
536
|
-
clearTimeout(timer);
|
|
537
|
-
resolve(value);
|
|
538
|
-
};
|
|
539
|
-
const timer = setTimeout(() => {
|
|
540
|
-
child.kill("SIGTERM");
|
|
541
|
-
finish(null);
|
|
542
|
-
}, CLIPBOARD_CMD_TIMEOUT_MS);
|
|
543
|
-
child.stdout.on("data", (c) => {
|
|
544
|
-
out += String(c);
|
|
545
|
-
});
|
|
546
|
-
child.on("error", () => finish(null));
|
|
547
|
-
child.on("exit", (code) => finish(code === 0 ? out : null));
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
function runCmdToFile(cmd, args, outPath) {
|
|
551
|
-
return new Promise((resolve) => {
|
|
552
|
-
const child = spawn(cmd, args, { env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
553
|
-
const chunks = [];
|
|
554
|
-
let settled = false;
|
|
555
|
-
const finish = (value) => {
|
|
556
|
-
if (settled) return;
|
|
557
|
-
settled = true;
|
|
558
|
-
clearTimeout(timer);
|
|
559
|
-
resolve(value);
|
|
560
|
-
};
|
|
561
|
-
const timer = setTimeout(() => {
|
|
562
|
-
child.kill("SIGTERM");
|
|
563
|
-
finish(false);
|
|
564
|
-
}, CLIPBOARD_CMD_TIMEOUT_MS);
|
|
565
|
-
child.stdout.on("data", (c) => chunks.push(c));
|
|
566
|
-
child.on("error", () => finish(false));
|
|
567
|
-
child.on("exit", async (code) => {
|
|
568
|
-
if (code !== 0 || chunks.length === 0) return finish(false);
|
|
569
|
-
try {
|
|
570
|
-
await fs2.writeFile(outPath, Buffer.concat(chunks));
|
|
571
|
-
finish(true);
|
|
572
|
-
} catch {
|
|
573
|
-
finish(false);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
var SUBAGENT_BASELINE = "You are a subagent executing one delegated task end-to-end. Work autonomously with your tools; do not ask for confirmation on routine in-project actions. Keep output concise.";
|
|
579
|
-
var _SUBAGENT_ABORT = "wrongstack:subagent-abort-controller";
|
|
580
|
-
function makeLightSubagentFactory(deps) {
|
|
581
|
-
const configStore = deps.container.resolve(TOKENS.ConfigStore);
|
|
582
|
-
const tokenCounter = deps.container.resolve(TOKENS.TokenCounter);
|
|
583
|
-
const secretScrubber = deps.container.resolve(TOKENS.SecretScrubber);
|
|
584
|
-
const systemPromptBuilder = deps.container.resolve(TOKENS.SystemPromptBuilder);
|
|
585
|
-
return async (subCfg) => {
|
|
586
|
-
const events = new EventBus();
|
|
587
|
-
const config = configStore.get();
|
|
588
|
-
const matrixEntry = subCfg.model ? void 0 : resolveModelMatrix(config.modelMatrix, subCfg.role);
|
|
589
|
-
const matrixTarget = resolveModelTargetFromEntry(config, matrixEntry);
|
|
590
|
-
const effProvider = subCfg.provider ?? matrixTarget?.provider ?? config.provider;
|
|
591
|
-
const effModel = subCfg.model ?? matrixTarget?.model ?? config.model;
|
|
592
|
-
const provider = buildProvider(deps.providerRegistry, config, effProvider, effModel);
|
|
593
|
-
const subCwd = subCfg.cwd ?? deps.cwd ?? deps.projectRoot;
|
|
594
|
-
const allowed = filterToolList(deps.toolRegistry, subCfg.tools);
|
|
595
|
-
const subRegistry = new ToolRegistry();
|
|
596
|
-
for (const t of allowed) subRegistry.register(t);
|
|
597
|
-
const baseSystem = await systemPromptBuilder.build({
|
|
598
|
-
cwd: subCwd,
|
|
599
|
-
projectRoot: deps.projectRoot,
|
|
600
|
-
tools: allowed,
|
|
601
|
-
model: effModel,
|
|
602
|
-
provider: effProvider,
|
|
603
|
-
subagent: true
|
|
604
|
-
});
|
|
605
|
-
baseSystem.unshift({ type: "text", text: SUBAGENT_BASELINE });
|
|
606
|
-
if (subCfg.systemPromptOverride) {
|
|
607
|
-
baseSystem.push({ type: "text", text: subCfg.systemPromptOverride });
|
|
608
|
-
}
|
|
609
|
-
const agentName = subCfg.name ?? subCfg.id ?? `sub_${cryptoId()}`;
|
|
610
|
-
const session = makeSubagentSessionShim(deps.session);
|
|
611
|
-
const ac = new AbortController();
|
|
612
|
-
const ctx = new Context({
|
|
613
|
-
systemPrompt: baseSystem,
|
|
614
|
-
provider,
|
|
615
|
-
session,
|
|
616
|
-
signal: ac.signal,
|
|
617
|
-
tokenCounter,
|
|
618
|
-
cwd: subCwd,
|
|
619
|
-
projectRoot: deps.projectRoot,
|
|
620
|
-
allowOutsideProjectRoot: config.features?.allowOutsideProjectRoot ?? !(config.tools?.restrictToProjectRoot ?? false),
|
|
621
|
-
model: effModel,
|
|
622
|
-
tools: allowed,
|
|
623
|
-
agentId: agentName,
|
|
624
|
-
agentName
|
|
625
|
-
});
|
|
626
|
-
if (subCfg.role) ctx.meta["agentRole"] = subCfg.role;
|
|
627
|
-
ctx.meta[_SUBAGENT_ABORT] = ac;
|
|
628
|
-
const caps = subCfg.allowedCapabilities ?? WIDE_SUBAGENT_CAPABILITIES;
|
|
629
|
-
const permissionPolicy = new AutoApprovePermissionPolicy(caps);
|
|
630
|
-
const toolExecutor = new ToolExecutor(subRegistry, {
|
|
631
|
-
permissionPolicy,
|
|
632
|
-
secretScrubber,
|
|
633
|
-
events,
|
|
634
|
-
confirmAwaiter: void 0,
|
|
635
|
-
iterationTimeoutMs: config.tools?.iterationTimeoutMs ?? 12e4,
|
|
636
|
-
perIterationOutputCapBytes: config.tools?.perIterationOutputCapBytes ?? 1e5,
|
|
637
|
-
tracer: void 0
|
|
638
|
-
});
|
|
639
|
-
const agent = new Agent({
|
|
640
|
-
container: deps.container,
|
|
641
|
-
tools: subRegistry,
|
|
642
|
-
providers: deps.providerRegistry,
|
|
643
|
-
events,
|
|
644
|
-
pipelines: createDefaultPipelines(),
|
|
645
|
-
context: ctx,
|
|
646
|
-
permissionPolicy,
|
|
647
|
-
toolExecutor
|
|
648
|
-
});
|
|
649
|
-
const subFallbacks = subCfg.fallbackModels;
|
|
650
|
-
const matrixFallbacks = matrixTarget?.fallbackModels;
|
|
651
|
-
agent.extensions.register(
|
|
652
|
-
createFallbackModelExtension({
|
|
653
|
-
getConfig: () => {
|
|
654
|
-
const live = configStore.get();
|
|
655
|
-
if (subFallbacks && subFallbacks.length) return { ...live, fallbackModels: subFallbacks };
|
|
656
|
-
if (matrixFallbacks && matrixFallbacks.length) return { ...live, fallbackModels: matrixFallbacks };
|
|
657
|
-
return live;
|
|
658
|
-
},
|
|
659
|
-
buildProvider: (id) => buildProvider(deps.providerRegistry, configStore.get(), id, effModel),
|
|
660
|
-
events
|
|
661
|
-
})
|
|
662
|
-
);
|
|
663
|
-
return { agent, events };
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
function filterToolList(registry, allow) {
|
|
667
|
-
const all = registry.list();
|
|
668
|
-
if (!allow || allow.length === 0) return all;
|
|
669
|
-
const allowSet = new Set(allow);
|
|
670
|
-
return all.filter((t) => allowSet.has(t.name));
|
|
671
|
-
}
|
|
672
|
-
function buildProvider(registry, config, providerId, model) {
|
|
673
|
-
const providerConfig = config.providers?.[providerId] ?? {
|
|
674
|
-
type: providerId,
|
|
675
|
-
apiKey: config.apiKey,
|
|
676
|
-
baseUrl: config.baseUrl
|
|
677
|
-
};
|
|
678
|
-
if (!registry.has(providerId)) {
|
|
679
|
-
throw new Error(
|
|
680
|
-
`No provider factory registered for "${providerId}" \u2014 cannot build a subagent provider for the SDD run.`
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
return registry.create({ ...providerConfig, type: providerId, model });
|
|
684
|
-
}
|
|
685
|
-
function makeSubagentSessionShim(parent) {
|
|
686
|
-
return {
|
|
687
|
-
id: parent.id,
|
|
688
|
-
transcriptPath: parent.transcriptPath,
|
|
689
|
-
get pendingToolUses() {
|
|
690
|
-
return [];
|
|
691
|
-
},
|
|
692
|
-
append: (ev) => parent.append({ ...ev }),
|
|
693
|
-
appendBatch: (evs) => parent.appendBatch(evs.map((e) => ({ ...e }))),
|
|
694
|
-
flush: () => parent.flush(),
|
|
695
|
-
close: async () => {
|
|
696
|
-
},
|
|
697
|
-
recordFileChange: () => {
|
|
698
|
-
},
|
|
699
|
-
recordSideEffect: () => {
|
|
700
|
-
},
|
|
701
|
-
writeCheckpoint: async () => {
|
|
702
|
-
},
|
|
703
|
-
writeFileSnapshot: async () => {
|
|
704
|
-
},
|
|
705
|
-
truncateToCheckpoint: async () => 0,
|
|
706
|
-
clearSession: async () => {
|
|
707
|
-
},
|
|
708
|
-
writeInFlightMarker: async () => {
|
|
709
|
-
},
|
|
710
|
-
clearInFlightMarker: async () => {
|
|
711
|
-
}
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
function cryptoId() {
|
|
715
|
-
return crypto.randomUUID().slice(0, 8);
|
|
716
|
-
}
|
|
717
896
|
|
|
718
|
-
export { ImageInputUnsupportedError, VisionUrlBlockedError, applyWrongStackPack, applyWrongStackPacks, createDefaultContainer, createRuntimeHostFromParts, createToolVisionAdapters, makeLightSubagentFactory, readClipboardImage, readClipboardText, routeImagesForModel };
|
|
897
|
+
export { ImageInputUnsupportedError, VISION_IMAGE_KEYS, VISION_MEDIA_TYPE_KEYS, VISION_PATH_KEYS, VISION_PROMPT_KEYS, VisionUrlBlockedError, applyWrongStackPack, applyWrongStackPacks, createDefaultContainer, createRuntimeHostFromParts, createToolVisionAdapters, makeLightSubagentFactory, probeLocalLlm, readClipboardImage, readClipboardText, routeImagesForModel };
|
|
719
898
|
//# sourceMappingURL=index.js.map
|
|
720
899
|
//# sourceMappingURL=index.js.map
|