@wrongstack/runtime 0.2.0 → 0.3.3

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/README.md CHANGED
@@ -20,3 +20,25 @@ import { Agent, Container, EventBus } from '@wrongstack/core';
20
20
  The `WrongStackPack` interface in `@wrongstack/runtime/pack` is the target shape
21
21
  for extension packages that contribute tools, providers, slash commands, or
22
22
  agent lifecycle extensions.
23
+
24
+ ## Image routing
25
+
26
+ `@wrongstack/runtime/vision` owns the host-level image input decision:
27
+
28
+ - if the active provider reports `capabilities.vision`, image blocks are sent
29
+ natively;
30
+ - otherwise, the host can provide `VisionAdapter`s that turn images into text
31
+ descriptions before `agent.run()`;
32
+ - if neither route exists, the router throws a clear unsupported-image error
33
+ instead of silently flattening the image to `[image]`.
34
+
35
+ `createToolVisionAdapters(toolRegistry)` can discover safe, read-only
36
+ image-understanding tools, including MCP-wrapped tools, and expose them through
37
+ the same adapter contract. Built-in MCP presets such as `zai-vision` and
38
+ `minimax-vision` are configured as read-only adapter candidates. Hosts may pass
39
+ a function that calls `createToolVisionAdapters()` at routing time so MCP
40
+ reconnects and `tools/list_changed` refreshes are picked up before each image
41
+ is analyzed.
42
+
43
+ `@wrongstack/runtime/clipboard` exposes the shared OS clipboard PNG reader used
44
+ by TUI `Alt+V` and CLI `/image`.
@@ -0,0 +1,8 @@
1
+ interface ClipboardImage {
2
+ base64: string;
3
+ mediaType: 'image/png';
4
+ bytes: number;
5
+ }
6
+ declare function readClipboardImage(): Promise<ClipboardImage | null>;
7
+
8
+ export { type ClipboardImage, readClipboardImage };
@@ -0,0 +1,114 @@
1
+ import { spawn } from 'child_process';
2
+ import * as fs from 'fs/promises';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+
6
+ // src/clipboard.ts
7
+ var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
8
+ async function readClipboardImage() {
9
+ const platform = process.platform;
10
+ if (platform === "win32") return readWindows();
11
+ if (platform === "darwin") return readDarwin();
12
+ if (platform === "linux") return readLinux();
13
+ return null;
14
+ }
15
+ async function readWindows() {
16
+ const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
17
+ const ps = [
18
+ "Add-Type -AssemblyName System.Windows.Forms",
19
+ "Add-Type -AssemblyName System.Drawing",
20
+ "$img = [System.Windows.Forms.Clipboard]::GetImage()",
21
+ 'if ($img -eq $null) { Write-Output "NO_IMAGE"; exit 0 }',
22
+ `$img.Save('${tmp.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)`,
23
+ 'Write-Output "OK"'
24
+ ].join("; ");
25
+ const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
26
+ if (!out || out.trim() === "NO_IMAGE") return null;
27
+ if (!out.includes("OK")) return null;
28
+ return readPngFile(tmp);
29
+ }
30
+ async function readDarwin() {
31
+ const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
32
+ const script = [
33
+ "try",
34
+ ` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
35
+ " write (the clipboard as \xABclass PNGf\xBB) to the_file",
36
+ " close access the_file",
37
+ "on error",
38
+ " try",
39
+ ' close access POSIX file "' + tmp + '"',
40
+ " end try",
41
+ ' return "NO_IMAGE"',
42
+ "end try",
43
+ 'return "OK"'
44
+ ].join("\n");
45
+ const out = await runCmd("osascript", ["-e", script]);
46
+ if (!out || out.trim() !== "OK") return null;
47
+ return readPngFile(tmp);
48
+ }
49
+ async function readLinux() {
50
+ const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);
51
+ const tries = [
52
+ ["wl-paste", ["--type", "image/png"]],
53
+ ["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
54
+ ];
55
+ for (const [cmd, args] of tries) {
56
+ const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
57
+ if (ok) return readPngFile(tmp);
58
+ }
59
+ return null;
60
+ }
61
+ async function readPngFile(p) {
62
+ try {
63
+ const buf = await fs.readFile(p);
64
+ if (buf.length === 0) {
65
+ await fs.unlink(p).catch(() => void 0);
66
+ return null;
67
+ }
68
+ if (buf.length > MAX_IMAGE_BYTES) {
69
+ await fs.unlink(p).catch(() => void 0);
70
+ throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
71
+ }
72
+ if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
73
+ await fs.unlink(p).catch(() => void 0);
74
+ return null;
75
+ }
76
+ await fs.unlink(p).catch(() => void 0);
77
+ return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
78
+ } catch (err) {
79
+ if (err.code === "ENOENT") return null;
80
+ throw err;
81
+ }
82
+ }
83
+ function runCmd(cmd, args) {
84
+ return new Promise((resolve) => {
85
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
86
+ let out = "";
87
+ child.stdout.on("data", (c) => {
88
+ out += String(c);
89
+ });
90
+ child.on("error", () => resolve(null));
91
+ child.on("exit", (code) => resolve(code === 0 ? out : null));
92
+ });
93
+ }
94
+ function runCmdToFile(cmd, args, outPath) {
95
+ return new Promise((resolve) => {
96
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
97
+ const chunks = [];
98
+ child.stdout.on("data", (c) => chunks.push(c));
99
+ child.on("error", () => resolve(false));
100
+ child.on("exit", async (code) => {
101
+ if (code !== 0 || chunks.length === 0) return resolve(false);
102
+ try {
103
+ await fs.writeFile(outPath, Buffer.concat(chunks));
104
+ resolve(true);
105
+ } catch {
106
+ resolve(false);
107
+ }
108
+ });
109
+ });
110
+ }
111
+
112
+ export { readClipboardImage };
113
+ //# sourceMappingURL=clipboard.js.map
114
+ //# sourceMappingURL=clipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/clipboard.ts"],"names":[],"mappings":"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","file":"clipboard.js","sourcesContent":["import { spawn } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nexport interface ClipboardImage {\n base64: string;\n mediaType: 'image/png';\n bytes: number;\n}\n\nconst MAX_IMAGE_BYTES = 10 * 1024 * 1024;\n\nexport async function readClipboardImage(): Promise<ClipboardImage | null> {\n const platform = process.platform;\n if (platform === 'win32') return readWindows();\n if (platform === 'darwin') return readDarwin();\n if (platform === 'linux') return readLinux();\n return null;\n}\n\nasync function readWindows(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);\n const ps = [\n 'Add-Type -AssemblyName System.Windows.Forms',\n 'Add-Type -AssemblyName System.Drawing',\n '$img = [System.Windows.Forms.Clipboard]::GetImage()',\n 'if ($img -eq $null) { Write-Output \"NO_IMAGE\"; exit 0 }',\n `$img.Save('${tmp.replace(/\\\\/g, '\\\\\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)`,\n 'Write-Output \"OK\"',\n ].join('; ');\n const out = await runCmd('powershell', ['-NoProfile', '-Command', ps]);\n if (!out || out.trim() === 'NO_IMAGE') return null;\n if (!out.includes('OK')) return null;\n return readPngFile(tmp);\n}\n\nasync function readDarwin(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);\n const script = [\n 'try',\n ` set the_file to (open for access POSIX file \"${tmp}\" with write permission)`,\n ' write (the clipboard as «class PNGf») to the_file',\n ' close access the_file',\n 'on error',\n ' try',\n ' close access POSIX file \"' + tmp + '\"',\n ' end try',\n ' return \"NO_IMAGE\"',\n 'end try',\n 'return \"OK\"',\n ].join('\\n');\n const out = await runCmd('osascript', ['-e', script]);\n if (!out || out.trim() !== 'OK') return null;\n return readPngFile(tmp);\n}\n\nasync function readLinux(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);\n const tries: Array<[string, string[]]> = [\n ['wl-paste', ['--type', 'image/png']],\n ['xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o']],\n ];\n for (const [cmd, args] of tries) {\n const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);\n if (ok) return readPngFile(tmp);\n }\n return null;\n}\n\nasync function readPngFile(p: string): Promise<ClipboardImage | null> {\n try {\n const buf = await fs.readFile(p);\n if (buf.length === 0) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n if (buf.length > MAX_IMAGE_BYTES) {\n await fs.unlink(p).catch(() => undefined);\n throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);\n }\n if (buf[0] !== 0x89 || buf[1] !== 0x50 || buf[2] !== 0x4e || buf[3] !== 0x47) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n await fs.unlink(p).catch(() => undefined);\n return { base64: buf.toString('base64'), mediaType: 'image/png', bytes: buf.length };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n\nfunction runCmd(cmd: string, args: string[]): Promise<string | null> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });\n let out = '';\n child.stdout.on('data', (c) => {\n out += String(c);\n });\n child.on('error', () => resolve(null));\n child.on('exit', (code) => resolve(code === 0 ? out : null));\n });\n}\n\nfunction runCmdToFile(cmd: string, args: string[], outPath: string): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });\n const chunks: Buffer[] = [];\n child.stdout.on('data', (c: Buffer) => chunks.push(c));\n child.on('error', () => resolve(false));\n child.on('exit', async (code) => {\n if (code !== 0 || chunks.length === 0) return resolve(false);\n try {\n await fs.writeFile(outPath, Buffer.concat(chunks));\n resolve(true);\n } catch {\n resolve(false);\n }\n });\n });\n}\n"]}
package/dist/host.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import '@wrongstack/core/defaults';
2
- import '@wrongstack/core/infrastructure';
3
1
  import { PluginAPI, Agent, Context, EventBus, ToolRegistry, ProviderRegistry, SlashCommandRegistry, SessionWriter, ExtensionRegistry } from '@wrongstack/core';
4
2
  import { WrongStackPack } from './pack.js';
5
3
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,33 @@
1
1
  export * from '@wrongstack/core/defaults';
2
2
  export { DefaultPathResolver, DefaultTokenCounter } from '@wrongstack/core/infrastructure';
3
+ import { Config, WstackPaths, Logger, ModelsRegistry, DefaultSystemPromptBuilderOptions, Container } from '@wrongstack/core';
3
4
  export { DefaultSystemPromptBuilder, DefaultSystemPromptBuilderOptions } from '@wrongstack/core';
4
5
  export { WrongStackPack } from './pack.js';
5
6
  export { AppliedPack, ApplyPackOptions, RuntimeHost, RuntimeHostParts, applyWrongStackPack, applyWrongStackPacks, createRuntimeHostFromParts } from './host.js';
7
+ export { ImageInputUnsupportedError, ToolVisionAdapterOptions, VisionAdapter, VisionAdapterInput, VisionAdapters, VisionRoutingOptions, VisionRoutingResult, createToolVisionAdapters, routeImagesForModel } from './vision.js';
8
+ export { ClipboardImage, readClipboardImage } from './clipboard.js';
9
+
10
+ interface CreateContainerOptions {
11
+ config: Config;
12
+ wpaths: WstackPaths;
13
+ logger: Logger;
14
+ modelsRegistry: ModelsRegistry;
15
+ permission?: {
16
+ yolo?: boolean;
17
+ promptDelegate?: (tool: unknown, input: unknown, suggestedPattern: string) => Promise<'yes' | 'no' | 'always' | 'deny'>;
18
+ };
19
+ compactor?: {
20
+ preserveK?: number;
21
+ eliseThreshold?: number;
22
+ };
23
+ systemPrompt?: Partial<DefaultSystemPromptBuilderOptions>;
24
+ /** Bundled skills directory path (resolved at boot time). */
25
+ bundledSkillsDir?: string;
26
+ }
27
+ /**
28
+ * Create a Container pre-bound with all default service implementations.
29
+ * Both CLI and WebUI use this factory so container wiring stays in one place.
30
+ */
31
+ declare function createDefaultContainer(opts: CreateContainerOptions): Container;
32
+
33
+ export { type CreateContainerOptions, createDefaultContainer };
package/dist/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  export * from '@wrongstack/core/defaults';
2
2
  export { DefaultPathResolver, DefaultTokenCounter } from '@wrongstack/core/infrastructure';
3
+ import { Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor } from '@wrongstack/core';
3
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
+ import { spawn } from 'child_process';
4
9
 
5
10
  // src/index.ts
6
11
 
@@ -73,7 +78,344 @@ async function applyWrongStackPacks(host, packs, opts = {}) {
73
78
  throw err;
74
79
  }
75
80
  }
81
+ function createDefaultContainer(opts) {
82
+ const { config, wpaths, logger, modelsRegistry } = opts;
83
+ const container = new Container();
84
+ const configStore = new DefaultConfigStore(config);
85
+ container.bind(TOKENS.ConfigStore, () => configStore);
86
+ container.bind(TOKENS.Logger, () => logger);
87
+ container.bind(TOKENS.SecretScrubber, () => new DefaultSecretScrubber());
88
+ container.bind(TOKENS.RetryPolicy, () => new DefaultRetryPolicy());
89
+ container.bind(TOKENS.ErrorHandler, () => new DefaultErrorHandler());
90
+ container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);
91
+ container.bind(TOKENS.TokenCounter, () => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider }));
92
+ const modeStore = new DefaultModeStore({ directory: wpaths.configDir });
93
+ container.bind(TOKENS.ModeStore, () => modeStore);
94
+ container.bind(TOKENS.SessionStore, () => new DefaultSessionStore({ dir: wpaths.projectSessions }));
95
+ const memoryStore = new DefaultMemoryStore({ paths: wpaths });
96
+ container.bind(TOKENS.MemoryStore, () => memoryStore);
97
+ const skillLoader = new DefaultSkillLoader({ paths: wpaths, bundledDir: opts.bundledSkillsDir });
98
+ container.bind(TOKENS.SkillLoader, () => skillLoader);
99
+ if (opts.systemPrompt) {
100
+ container.bind(TOKENS.SystemPromptBuilder, () => new DefaultSystemPromptBuilder(opts.systemPrompt));
101
+ }
102
+ container.bind(
103
+ TOKENS.PermissionPolicy,
104
+ () => new DefaultPermissionPolicy({
105
+ trustFile: wpaths.projectTrust,
106
+ yolo: opts.permission?.yolo ?? false,
107
+ promptDelegate: opts.permission?.promptDelegate
108
+ })
109
+ );
110
+ container.bind(
111
+ TOKENS.Compactor,
112
+ () => new HybridCompactor({
113
+ preserveK: opts.compactor?.preserveK ?? 20,
114
+ eliseThreshold: opts.compactor?.eliseThreshold ?? 0.7
115
+ })
116
+ );
117
+ return container;
118
+ }
119
+ var ImageInputUnsupportedError = class extends Error {
120
+ constructor(opts) {
121
+ const target = [opts.providerId, opts.model].filter(Boolean).join("/") || "current model";
122
+ super(
123
+ `${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.`
124
+ );
125
+ this.name = "ImageInputUnsupportedError";
126
+ }
127
+ };
128
+ async function routeImagesForModel(blocks, opts) {
129
+ const images = blocks.filter((b) => b.type === "image");
130
+ if (images.length === 0) {
131
+ return { blocks, route: "none", convertedImages: 0 };
132
+ }
133
+ if (opts.supportsVision) {
134
+ return { blocks, route: "native", convertedImages: 0 };
135
+ }
136
+ const adapters = await resolveAdapters(opts.adapters);
137
+ if (adapters.length === 0) {
138
+ throw new ImageInputUnsupportedError({
139
+ providerId: opts.providerId,
140
+ model: opts.model,
141
+ imageCount: images.length
142
+ });
143
+ }
144
+ const out = [];
145
+ let convertedImages = 0;
146
+ let lastErr;
147
+ let adapterName;
148
+ for (const block of blocks) {
149
+ if (block.type !== "image") {
150
+ out.push(block);
151
+ continue;
152
+ }
153
+ let description;
154
+ for (const adapter of adapters) {
155
+ try {
156
+ description = await adapter.describe({
157
+ image: block,
158
+ prompt: opts.prompt,
159
+ ctx: opts.ctx,
160
+ signal: opts.signal
161
+ });
162
+ adapterName = adapter.name;
163
+ break;
164
+ } catch (err) {
165
+ lastErr = err;
166
+ }
167
+ }
168
+ if (!description?.trim()) {
169
+ throw new Error(
170
+ `No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ""}`
171
+ );
172
+ }
173
+ convertedImages++;
174
+ out.push({
175
+ type: "text",
176
+ text: `[Image ${convertedImages} analyzed via ${adapterName ?? "vision adapter"}]
177
+ ${description.trim()}`
178
+ });
179
+ }
180
+ return { blocks: out, route: "adapter", convertedImages, adapterName };
181
+ }
182
+ async function resolveAdapters(adapters) {
183
+ if (!adapters) return [];
184
+ return typeof adapters === "function" ? await adapters() : adapters;
185
+ }
186
+ function createToolVisionAdapters(registry, opts = {}) {
187
+ return registry.list().filter(isLikelyVisionTool).map((tool) => ({
188
+ name: tool.name,
189
+ async describe(input) {
190
+ const currentTool = registry.get(tool.name);
191
+ if (!currentTool) {
192
+ throw new Error(`Tool "${tool.name}" is no longer registered`);
193
+ }
194
+ const built = await buildToolPayload(currentTool, input.image, input.prompt ?? opts.prompt);
195
+ if (!built) {
196
+ throw new Error(`Tool "${currentTool.name}" does not expose a supported image input schema`);
197
+ }
198
+ try {
199
+ const result = await currentTool.execute(built.payload, input.ctx, {
200
+ signal: input.signal
201
+ });
202
+ return stringifyToolResult(result);
203
+ } finally {
204
+ await built.cleanup?.();
205
+ }
206
+ }
207
+ }));
208
+ }
209
+ function isLikelyVisionTool(tool) {
210
+ if (tool.permission !== "auto" || tool.mutating) return false;
211
+ const haystack = `${tool.name} ${tool.description ?? ""} ${tool.usageHint ?? ""}`.toLowerCase();
212
+ if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;
213
+ if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;
214
+ const props = schemaProperties(tool);
215
+ return [
216
+ "image",
217
+ "base64",
218
+ "data",
219
+ "url",
220
+ "image_url",
221
+ "imageUrl",
222
+ "path",
223
+ "image_path",
224
+ "imagePath",
225
+ "image_url",
226
+ "imageUrl",
227
+ "file_path",
228
+ "filePath",
229
+ "filename",
230
+ "file",
231
+ "mediaType",
232
+ "mimeType"
233
+ ].some((key) => key in props);
234
+ }
235
+ 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.") {
236
+ const props = schemaProperties(tool);
237
+ const payload = {};
238
+ const mediaType = image.source.media_type ?? "image/png";
239
+ const data = image.source.data;
240
+ const url = image.source.url;
241
+ let cleanup;
242
+ const pathKey = firstPresent(props, [
243
+ "path",
244
+ "image_path",
245
+ "imagePath",
246
+ "image_url",
247
+ "imageUrl",
248
+ "file_path",
249
+ "filePath",
250
+ "filename",
251
+ "file"
252
+ ]);
253
+ if (pathKey && image.source.type === "base64" && data) {
254
+ const p = await writeTempImage(data, mediaType);
255
+ payload[pathKey] = p;
256
+ cleanup = async () => {
257
+ await fs2.unlink(p).catch(() => void 0);
258
+ await fs2.rmdir(path.dirname(p)).catch(() => void 0);
259
+ };
260
+ } else if ("image" in props) {
261
+ payload.image = image.source.type === "base64" ? { type: "base64", mediaType, media_type: mediaType, data } : { type: "url", url };
262
+ } else if (image.source.type === "base64" && "base64" in props) {
263
+ payload.base64 = data;
264
+ } else if (image.source.type === "base64" && "data" in props) {
265
+ payload.data = data;
266
+ } else if (image.source.type === "url" && "url" in props) {
267
+ payload.url = url;
268
+ } else if (image.source.type === "url" && "image_url" in props) {
269
+ payload.image_url = url;
270
+ } else if (image.source.type === "url" && "imageUrl" in props) {
271
+ payload.imageUrl = url;
272
+ } else {
273
+ return null;
274
+ }
275
+ if ("mediaType" in props) payload.mediaType = mediaType;
276
+ if ("mimeType" in props) payload.mimeType = mediaType;
277
+ if ("media_type" in props) payload.media_type = mediaType;
278
+ if ("prompt" in props) payload.prompt = prompt;
279
+ if ("query" in props) payload.query = prompt;
280
+ if ("instruction" in props) payload.instruction = prompt;
281
+ return { payload, cleanup };
282
+ }
283
+ function firstPresent(props, keys) {
284
+ return keys.find((key) => key in props);
285
+ }
286
+ async function writeTempImage(data, mediaType) {
287
+ const ext = mediaType.includes("jpeg") || mediaType.includes("jpg") ? "jpg" : "png";
288
+ const dir = await fs2.mkdtemp(path.join(os2.tmpdir(), "wstack-vision-"));
289
+ const file = path.join(dir, `image.${ext}`);
290
+ await fs2.writeFile(file, data, "base64");
291
+ return file;
292
+ }
293
+ function schemaProperties(tool) {
294
+ const schema = tool.inputSchema;
295
+ if (!schema || typeof schema !== "object") return {};
296
+ const props = schema.properties;
297
+ return props && typeof props === "object" ? props : {};
298
+ }
299
+ function stringifyToolResult(value) {
300
+ if (typeof value === "string") return value;
301
+ if (Array.isArray(value)) {
302
+ return value.map((item) => {
303
+ if (item && typeof item === "object" && "text" in item) {
304
+ return String(item.text ?? "");
305
+ }
306
+ return typeof item === "string" ? item : JSON.stringify(item);
307
+ }).join("\n");
308
+ }
309
+ if (value && typeof value === "object" && "text" in value) {
310
+ return String(value.text ?? "");
311
+ }
312
+ return JSON.stringify(value);
313
+ }
314
+ var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
315
+ async function readClipboardImage() {
316
+ const platform = process.platform;
317
+ if (platform === "win32") return readWindows();
318
+ if (platform === "darwin") return readDarwin();
319
+ if (platform === "linux") return readLinux();
320
+ return null;
321
+ }
322
+ async function readWindows() {
323
+ const tmp = path.join(os2.tmpdir(), `wstack-clip-${Date.now()}.png`);
324
+ const ps = [
325
+ "Add-Type -AssemblyName System.Windows.Forms",
326
+ "Add-Type -AssemblyName System.Drawing",
327
+ "$img = [System.Windows.Forms.Clipboard]::GetImage()",
328
+ 'if ($img -eq $null) { Write-Output "NO_IMAGE"; exit 0 }',
329
+ `$img.Save('${tmp.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)`,
330
+ 'Write-Output "OK"'
331
+ ].join("; ");
332
+ const out = await runCmd("powershell", ["-NoProfile", "-Command", ps]);
333
+ if (!out || out.trim() === "NO_IMAGE") return null;
334
+ if (!out.includes("OK")) return null;
335
+ return readPngFile(tmp);
336
+ }
337
+ async function readDarwin() {
338
+ const tmp = path.join(os2.tmpdir(), `wstack-clip-${Date.now()}.png`);
339
+ const script = [
340
+ "try",
341
+ ` set the_file to (open for access POSIX file "${tmp}" with write permission)`,
342
+ " write (the clipboard as \xABclass PNGf\xBB) to the_file",
343
+ " close access the_file",
344
+ "on error",
345
+ " try",
346
+ ' close access POSIX file "' + tmp + '"',
347
+ " end try",
348
+ ' return "NO_IMAGE"',
349
+ "end try",
350
+ 'return "OK"'
351
+ ].join("\n");
352
+ const out = await runCmd("osascript", ["-e", script]);
353
+ if (!out || out.trim() !== "OK") return null;
354
+ return readPngFile(tmp);
355
+ }
356
+ async function readLinux() {
357
+ const tmp = path.join(os2.tmpdir(), `wstack-clip-${Date.now()}.png`);
358
+ const tries = [
359
+ ["wl-paste", ["--type", "image/png"]],
360
+ ["xclip", ["-selection", "clipboard", "-t", "image/png", "-o"]]
361
+ ];
362
+ for (const [cmd, args] of tries) {
363
+ const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);
364
+ if (ok) return readPngFile(tmp);
365
+ }
366
+ return null;
367
+ }
368
+ async function readPngFile(p) {
369
+ try {
370
+ const buf = await fs2.readFile(p);
371
+ if (buf.length === 0) {
372
+ await fs2.unlink(p).catch(() => void 0);
373
+ return null;
374
+ }
375
+ if (buf.length > MAX_IMAGE_BYTES) {
376
+ await fs2.unlink(p).catch(() => void 0);
377
+ throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);
378
+ }
379
+ if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71) {
380
+ await fs2.unlink(p).catch(() => void 0);
381
+ return null;
382
+ }
383
+ await fs2.unlink(p).catch(() => void 0);
384
+ return { base64: buf.toString("base64"), mediaType: "image/png", bytes: buf.length };
385
+ } catch (err) {
386
+ if (err.code === "ENOENT") return null;
387
+ throw err;
388
+ }
389
+ }
390
+ function runCmd(cmd, args) {
391
+ return new Promise((resolve) => {
392
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
393
+ let out = "";
394
+ child.stdout.on("data", (c) => {
395
+ out += String(c);
396
+ });
397
+ child.on("error", () => resolve(null));
398
+ child.on("exit", (code) => resolve(code === 0 ? out : null));
399
+ });
400
+ }
401
+ function runCmdToFile(cmd, args, outPath) {
402
+ return new Promise((resolve) => {
403
+ const child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
404
+ const chunks = [];
405
+ child.stdout.on("data", (c) => chunks.push(c));
406
+ child.on("error", () => resolve(false));
407
+ child.on("exit", async (code) => {
408
+ if (code !== 0 || chunks.length === 0) return resolve(false);
409
+ try {
410
+ await fs2.writeFile(outPath, Buffer.concat(chunks));
411
+ resolve(true);
412
+ } catch {
413
+ resolve(false);
414
+ }
415
+ });
416
+ });
417
+ }
76
418
 
77
- export { applyWrongStackPack, applyWrongStackPacks, createRuntimeHostFromParts };
419
+ export { ImageInputUnsupportedError, applyWrongStackPack, applyWrongStackPacks, createDefaultContainer, createRuntimeHostFromParts, createToolVisionAdapters, readClipboardImage, routeImagesForModel };
78
420
  //# sourceMappingURL=index.js.map
79
421
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/host.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n Agent,\n Context,\n EventBus,\n ExtensionRegistry,\n PluginAPI,\n ProviderRegistry,\n SessionWriter,\n SlashCommandRegistry,\n ToolRegistry,\n} from '@wrongstack/core';\nimport type { WrongStackPack } from './pack.js';\n\nexport interface RuntimeHost {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry;\n shutdown(): Promise<void>;\n}\n\nexport interface RuntimeHostParts {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry;\n shutdown?: () => void | Promise<void>;\n}\n\nexport function createRuntimeHostFromParts(parts: RuntimeHostParts): RuntimeHost {\n return {\n agent: parts.agent,\n context: parts.context,\n events: parts.events,\n tools: parts.tools,\n providers: parts.providers,\n slashCommands: parts.slashCommands,\n session: parts.session,\n extensions: parts.extensions,\n async shutdown() {\n await parts.shutdown?.();\n },\n };\n}\n\nexport interface ApplyPackOptions {\n owner?: string;\n api?: PluginAPI;\n}\n\nexport interface AppliedPack {\n pack: WrongStackPack;\n owner: string;\n teardown(): Promise<void>;\n}\n\nexport async function applyWrongStackPack(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry;\n },\n pack: WrongStackPack,\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack> {\n const owner = opts.owner ?? pack.name;\n const unregisterExtensions: Array<() => void> = [];\n\n if (pack.tools) {\n host.tools.registerAllOrThrow([...pack.tools], owner);\n }\n if (pack.providers) {\n host.providers.registerAll([...pack.providers]);\n }\n if (pack.slashCommands) {\n host.slashCommands.registerAll([...pack.slashCommands], owner);\n }\n if (pack.extensions && host.extensions) {\n for (const ext of pack.extensions) {\n unregisterExtensions.push(host.extensions.register(ext));\n }\n }\n if (pack.setup) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines setup() but no PluginAPI was provided`);\n }\n await pack.setup(opts.api);\n }\n\n return {\n pack,\n owner,\n async teardown() {\n for (const unregister of unregisterExtensions.reverse()) {\n unregister();\n }\n if (pack.teardown) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines teardown() but no PluginAPI was provided`);\n }\n await pack.teardown(opts.api);\n }\n },\n };\n}\n\nexport async function applyWrongStackPacks(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry;\n },\n packs: readonly WrongStackPack[],\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack[]> {\n const applied: AppliedPack[] = [];\n try {\n for (const pack of packs) {\n applied.push(await applyWrongStackPack(host, pack, opts));\n }\n return applied;\n } catch (err) {\n for (const mounted of applied.reverse()) {\n await mounted.teardown().catch(() => undefined);\n }\n throw err;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/host.ts","../src/container.ts","../src/vision.ts","../src/clipboard.ts"],"names":["fs","os","path2"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n Agent,\n Context,\n EventBus,\n ExtensionRegistry,\n PluginAPI,\n ProviderRegistry,\n SessionWriter,\n SlashCommandRegistry,\n ToolRegistry,\n} from '@wrongstack/core';\nimport type { WrongStackPack } from './pack.js';\n\nexport interface RuntimeHost {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry;\n shutdown(): Promise<void>;\n}\n\nexport interface RuntimeHostParts {\n agent: Agent;\n context: Context;\n events: EventBus;\n tools: ToolRegistry;\n providers: ProviderRegistry;\n slashCommands: SlashCommandRegistry;\n session: SessionWriter;\n extensions?: ExtensionRegistry;\n shutdown?: () => void | Promise<void>;\n}\n\nexport function createRuntimeHostFromParts(parts: RuntimeHostParts): RuntimeHost {\n return {\n agent: parts.agent,\n context: parts.context,\n events: parts.events,\n tools: parts.tools,\n providers: parts.providers,\n slashCommands: parts.slashCommands,\n session: parts.session,\n extensions: parts.extensions,\n async shutdown() {\n await parts.shutdown?.();\n },\n };\n}\n\nexport interface ApplyPackOptions {\n owner?: string;\n api?: PluginAPI;\n}\n\nexport interface AppliedPack {\n pack: WrongStackPack;\n owner: string;\n teardown(): Promise<void>;\n}\n\nexport async function applyWrongStackPack(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry;\n },\n pack: WrongStackPack,\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack> {\n const owner = opts.owner ?? pack.name;\n const unregisterExtensions: Array<() => void> = [];\n\n if (pack.tools) {\n host.tools.registerAllOrThrow([...pack.tools], owner);\n }\n if (pack.providers) {\n host.providers.registerAll([...pack.providers]);\n }\n if (pack.slashCommands) {\n host.slashCommands.registerAll([...pack.slashCommands], owner);\n }\n if (pack.extensions && host.extensions) {\n for (const ext of pack.extensions) {\n unregisterExtensions.push(host.extensions.register(ext));\n }\n }\n if (pack.setup) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines setup() but no PluginAPI was provided`);\n }\n await pack.setup(opts.api);\n }\n\n return {\n pack,\n owner,\n async teardown() {\n for (const unregister of unregisterExtensions.reverse()) {\n unregister();\n }\n if (pack.teardown) {\n if (!opts.api) {\n throw new Error(`Pack \"${pack.name}\" defines teardown() but no PluginAPI was provided`);\n }\n await pack.teardown(opts.api);\n }\n },\n };\n}\n\nexport async function applyWrongStackPacks(\n host: Pick<RuntimeHost, 'tools' | 'providers' | 'slashCommands'> & {\n extensions?: ExtensionRegistry;\n },\n packs: readonly WrongStackPack[],\n opts: ApplyPackOptions = {},\n): Promise<AppliedPack[]> {\n const applied: AppliedPack[] = [];\n try {\n for (const pack of packs) {\n applied.push(await applyWrongStackPack(host, pack, opts));\n }\n return applied;\n } catch (err) {\n for (const mounted of applied.reverse()) {\n await mounted.teardown().catch(() => undefined);\n }\n throw err;\n }\n}\n","import {\n Container,\n DefaultConfigStore,\n DefaultErrorHandler,\n DefaultMemoryStore,\n DefaultModeStore,\n DefaultPermissionPolicy,\n DefaultRetryPolicy,\n DefaultSecretScrubber,\n DefaultSessionStore,\n DefaultSkillLoader,\n DefaultSystemPromptBuilder,\n DefaultTokenCounter,\n HybridCompactor,\n TOKENS,\n type Config,\n type Logger,\n type ModelsRegistry,\n type WstackPaths,\n} from '@wrongstack/core';\nimport type { DefaultSystemPromptBuilderOptions } from '@wrongstack/core';\n\nexport interface CreateContainerOptions {\n config: Config;\n wpaths: WstackPaths;\n logger: Logger;\n modelsRegistry: ModelsRegistry;\n permission?: {\n yolo?: boolean;\n promptDelegate?: (tool: unknown, input: unknown, suggestedPattern: string) => Promise<'yes' | 'no' | 'always' | 'deny'>;\n };\n compactor?: { preserveK?: number; eliseThreshold?: number };\n systemPrompt?: Partial<DefaultSystemPromptBuilderOptions>;\n /** Bundled skills directory path (resolved at boot time). */\n bundledSkillsDir?: string;\n}\n\n/**\n * Create a Container pre-bound with all default service implementations.\n * Both CLI and WebUI use this factory so container wiring stays in one place.\n */\nexport function createDefaultContainer(opts: CreateContainerOptions): Container {\n const { config, wpaths, logger, modelsRegistry } = opts;\n const container = new Container();\n\n const configStore = new DefaultConfigStore(config);\n container.bind(TOKENS.ConfigStore, () => configStore);\n container.bind(TOKENS.Logger, () => logger);\n container.bind(TOKENS.SecretScrubber, () => new DefaultSecretScrubber());\n container.bind(TOKENS.RetryPolicy, () => new DefaultRetryPolicy());\n container.bind(TOKENS.ErrorHandler, () => new DefaultErrorHandler());\n container.bind(TOKENS.ModelsRegistry, () => modelsRegistry);\n container.bind(TOKENS.TokenCounter, () => new DefaultTokenCounter({ registry: modelsRegistry, providerId: config.provider }));\n\n const modeStore = new DefaultModeStore({ directory: wpaths.configDir });\n container.bind(TOKENS.ModeStore, () => modeStore);\n container.bind(TOKENS.SessionStore, () => new DefaultSessionStore({ dir: wpaths.projectSessions }));\n\n const memoryStore = new DefaultMemoryStore({ paths: wpaths });\n container.bind(TOKENS.MemoryStore, () => memoryStore);\n\n const skillLoader = new DefaultSkillLoader({ paths: wpaths, bundledDir: opts.bundledSkillsDir });\n container.bind(TOKENS.SkillLoader, () => skillLoader);\n\n if (opts.systemPrompt) {\n container.bind(TOKENS.SystemPromptBuilder, () => new DefaultSystemPromptBuilder(opts.systemPrompt as DefaultSystemPromptBuilderOptions));\n }\n\n container.bind(TOKENS.PermissionPolicy, () =>\n new DefaultPermissionPolicy({\n trustFile: wpaths.projectTrust,\n yolo: opts.permission?.yolo ?? false,\n promptDelegate: opts.permission?.promptDelegate as any,\n }),\n );\n\n container.bind(TOKENS.Compactor, () =>\n new HybridCompactor({\n preserveK: opts.compactor?.preserveK ?? 20,\n eliseThreshold: opts.compactor?.eliseThreshold ?? 0.7,\n }),\n );\n\n return container;\n}\n","import * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { ContentBlock, Context, ImageBlock, Tool, ToolRegistry } from '@wrongstack/core';\n\nexport interface VisionAdapterInput {\n image: ImageBlock;\n prompt?: string;\n ctx: Context;\n signal: AbortSignal;\n}\n\nexport interface VisionAdapter {\n name: string;\n describe(input: VisionAdapterInput): Promise<string>;\n}\n\nexport type VisionAdapters =\n | readonly VisionAdapter[]\n | (() => readonly VisionAdapter[] | Promise<readonly VisionAdapter[]>);\n\nexport interface VisionRoutingOptions {\n supportsVision: boolean;\n adapters?: VisionAdapters;\n ctx: Context;\n signal: AbortSignal;\n prompt?: string;\n providerId?: string;\n model?: string;\n}\n\nexport interface VisionRoutingResult {\n blocks: ContentBlock[];\n route: 'native' | 'adapter' | 'none';\n convertedImages: number;\n adapterName?: string;\n}\n\nexport class ImageInputUnsupportedError extends Error {\n constructor(opts: { providerId?: string; model?: string; imageCount: number }) {\n const target = [opts.providerId, opts.model].filter(Boolean).join('/') || 'current model';\n super(\n `${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.`,\n );\n this.name = 'ImageInputUnsupportedError';\n }\n}\n\nexport async function routeImagesForModel(\n blocks: ContentBlock[],\n opts: VisionRoutingOptions,\n): Promise<VisionRoutingResult> {\n const images = blocks.filter((b): b is ImageBlock => b.type === 'image');\n if (images.length === 0) {\n return { blocks, route: 'none', convertedImages: 0 };\n }\n if (opts.supportsVision) {\n return { blocks, route: 'native', convertedImages: 0 };\n }\n\n const adapters = await resolveAdapters(opts.adapters);\n if (adapters.length === 0) {\n throw new ImageInputUnsupportedError({\n providerId: opts.providerId,\n model: opts.model,\n imageCount: images.length,\n });\n }\n\n const out: ContentBlock[] = [];\n let convertedImages = 0;\n let lastErr: unknown;\n let adapterName: string | undefined;\n for (const block of blocks) {\n if (block.type !== 'image') {\n out.push(block);\n continue;\n }\n let description: string | undefined;\n for (const adapter of adapters) {\n try {\n description = await adapter.describe({\n image: block,\n prompt: opts.prompt,\n ctx: opts.ctx,\n signal: opts.signal,\n });\n adapterName = adapter.name;\n break;\n } catch (err) {\n lastErr = err;\n }\n }\n if (!description?.trim()) {\n throw new Error(\n `No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ''}`,\n );\n }\n convertedImages++;\n out.push({\n type: 'text',\n text: `[Image ${convertedImages} analyzed via ${adapterName ?? 'vision adapter'}]\\n${description.trim()}`,\n });\n }\n\n return { blocks: out, route: 'adapter', convertedImages, adapterName };\n}\n\nasync function resolveAdapters(adapters: VisionAdapters | undefined): Promise<readonly VisionAdapter[]> {\n if (!adapters) return [];\n return typeof adapters === 'function' ? await adapters() : adapters;\n}\n\nexport interface ToolVisionAdapterOptions {\n prompt?: string;\n}\n\nexport function createToolVisionAdapters(\n registry: ToolRegistry,\n opts: ToolVisionAdapterOptions = {},\n): VisionAdapter[] {\n return registry\n .list()\n .filter(isLikelyVisionTool)\n .map((tool) => ({\n name: tool.name,\n async describe(input: VisionAdapterInput): Promise<string> {\n const currentTool = registry.get(tool.name);\n if (!currentTool) {\n throw new Error(`Tool \"${tool.name}\" is no longer registered`);\n }\n const built = await buildToolPayload(currentTool, input.image, input.prompt ?? opts.prompt);\n if (!built) {\n throw new Error(`Tool \"${currentTool.name}\" does not expose a supported image input schema`);\n }\n try {\n const result = await currentTool.execute(built.payload, input.ctx, {\n signal: input.signal,\n });\n return stringifyToolResult(result);\n } finally {\n await built.cleanup?.();\n }\n },\n }));\n}\n\nfunction isLikelyVisionTool(tool: Tool): boolean {\n if (tool.permission !== 'auto' || tool.mutating) return false;\n const haystack = `${tool.name} ${tool.description ?? ''} ${tool.usageHint ?? ''}`.toLowerCase();\n if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;\n if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;\n const props = schemaProperties(tool);\n return [\n 'image',\n 'base64',\n 'data',\n 'url',\n 'image_url',\n 'imageUrl',\n 'path',\n 'image_path',\n 'imagePath',\n 'image_url',\n 'imageUrl',\n 'file_path',\n 'filePath',\n 'filename',\n 'file',\n 'mediaType',\n 'mimeType',\n ].some((key) => key in props);\n}\n\nasync function buildToolPayload(\n tool: Tool,\n image: ImageBlock,\n prompt = 'Describe this image for a coding agent. Include visible text, UI state, errors, layout, and any details needed to answer the user.',\n): Promise<{ payload: Record<string, unknown>; cleanup?: () => Promise<void> } | null> {\n const props = schemaProperties(tool);\n const payload: Record<string, unknown> = {};\n const mediaType = image.source.media_type ?? 'image/png';\n const data = image.source.data;\n const url = image.source.url;\n let cleanup: (() => Promise<void>) | undefined;\n\n const pathKey = firstPresent(props, [\n 'path',\n 'image_path',\n 'imagePath',\n 'image_url',\n 'imageUrl',\n 'file_path',\n 'filePath',\n 'filename',\n 'file',\n ]);\n if (pathKey && image.source.type === 'base64' && data) {\n const p = await writeTempImage(data, mediaType);\n payload[pathKey] = p;\n cleanup = async () => {\n await fs.unlink(p).catch(() => undefined);\n await fs.rmdir(path.dirname(p)).catch(() => undefined);\n };\n } else if ('image' in props) {\n payload.image =\n image.source.type === 'base64'\n ? { type: 'base64', mediaType, media_type: mediaType, data }\n : { type: 'url', url };\n } else if (image.source.type === 'base64' && 'base64' in props) {\n payload.base64 = data;\n } else if (image.source.type === 'base64' && 'data' in props) {\n payload.data = data;\n } else if (image.source.type === 'url' && 'url' in props) {\n payload.url = url;\n } else if (image.source.type === 'url' && 'image_url' in props) {\n payload.image_url = url;\n } else if (image.source.type === 'url' && 'imageUrl' in props) {\n payload.imageUrl = url;\n } else {\n return null;\n }\n\n if ('mediaType' in props) payload.mediaType = mediaType;\n if ('mimeType' in props) payload.mimeType = mediaType;\n if ('media_type' in props) payload.media_type = mediaType;\n if ('prompt' in props) payload.prompt = prompt;\n if ('query' in props) payload.query = prompt;\n if ('instruction' in props) payload.instruction = prompt;\n return { payload, cleanup };\n}\n\nfunction firstPresent(props: Record<string, unknown>, keys: string[]): string | undefined {\n return keys.find((key) => key in props);\n}\n\nasync function writeTempImage(data: string, mediaType: string): Promise<string> {\n const ext = mediaType.includes('jpeg') || mediaType.includes('jpg') ? 'jpg' : 'png';\n const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'wstack-vision-'));\n const file = path.join(dir, `image.${ext}`);\n await fs.writeFile(file, data, 'base64');\n return file;\n}\n\nfunction schemaProperties(tool: Tool): Record<string, unknown> {\n const schema = tool.inputSchema;\n if (!schema || typeof schema !== 'object') return {};\n const props = (schema as { properties?: unknown }).properties;\n return props && typeof props === 'object' ? (props as Record<string, unknown>) : {};\n}\n\nfunction stringifyToolResult(value: unknown): string {\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) {\n return value\n .map((item) => {\n if (item && typeof item === 'object' && 'text' in item) {\n return String((item as { text?: unknown }).text ?? '');\n }\n return typeof item === 'string' ? item : JSON.stringify(item);\n })\n .join('\\n');\n }\n if (value && typeof value === 'object' && 'text' in value) {\n return String((value as { text?: unknown }).text ?? '');\n }\n return JSON.stringify(value);\n}\n","import { spawn } from 'node:child_process';\nimport * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nexport interface ClipboardImage {\n base64: string;\n mediaType: 'image/png';\n bytes: number;\n}\n\nconst MAX_IMAGE_BYTES = 10 * 1024 * 1024;\n\nexport async function readClipboardImage(): Promise<ClipboardImage | null> {\n const platform = process.platform;\n if (platform === 'win32') return readWindows();\n if (platform === 'darwin') return readDarwin();\n if (platform === 'linux') return readLinux();\n return null;\n}\n\nasync function readWindows(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);\n const ps = [\n 'Add-Type -AssemblyName System.Windows.Forms',\n 'Add-Type -AssemblyName System.Drawing',\n '$img = [System.Windows.Forms.Clipboard]::GetImage()',\n 'if ($img -eq $null) { Write-Output \"NO_IMAGE\"; exit 0 }',\n `$img.Save('${tmp.replace(/\\\\/g, '\\\\\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)`,\n 'Write-Output \"OK\"',\n ].join('; ');\n const out = await runCmd('powershell', ['-NoProfile', '-Command', ps]);\n if (!out || out.trim() === 'NO_IMAGE') return null;\n if (!out.includes('OK')) return null;\n return readPngFile(tmp);\n}\n\nasync function readDarwin(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);\n const script = [\n 'try',\n ` set the_file to (open for access POSIX file \"${tmp}\" with write permission)`,\n ' write (the clipboard as «class PNGf») to the_file',\n ' close access the_file',\n 'on error',\n ' try',\n ' close access POSIX file \"' + tmp + '\"',\n ' end try',\n ' return \"NO_IMAGE\"',\n 'end try',\n 'return \"OK\"',\n ].join('\\n');\n const out = await runCmd('osascript', ['-e', script]);\n if (!out || out.trim() !== 'OK') return null;\n return readPngFile(tmp);\n}\n\nasync function readLinux(): Promise<ClipboardImage | null> {\n const tmp = path.join(os.tmpdir(), `wstack-clip-${Date.now()}.png`);\n const tries: Array<[string, string[]]> = [\n ['wl-paste', ['--type', 'image/png']],\n ['xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o']],\n ];\n for (const [cmd, args] of tries) {\n const ok = await runCmdToFile(cmd, args, tmp).catch(() => false);\n if (ok) return readPngFile(tmp);\n }\n return null;\n}\n\nasync function readPngFile(p: string): Promise<ClipboardImage | null> {\n try {\n const buf = await fs.readFile(p);\n if (buf.length === 0) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n if (buf.length > MAX_IMAGE_BYTES) {\n await fs.unlink(p).catch(() => undefined);\n throw new Error(`Clipboard image exceeds ${MAX_IMAGE_BYTES / 1024 / 1024}MB limit`);\n }\n if (buf[0] !== 0x89 || buf[1] !== 0x50 || buf[2] !== 0x4e || buf[3] !== 0x47) {\n await fs.unlink(p).catch(() => undefined);\n return null;\n }\n await fs.unlink(p).catch(() => undefined);\n return { base64: buf.toString('base64'), mediaType: 'image/png', bytes: buf.length };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n\nfunction runCmd(cmd: string, args: string[]): Promise<string | null> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });\n let out = '';\n child.stdout.on('data', (c) => {\n out += String(c);\n });\n child.on('error', () => resolve(null));\n child.on('exit', (code) => resolve(code === 0 ? out : null));\n });\n}\n\nfunction runCmdToFile(cmd: string, args: string[], outPath: string): Promise<boolean> {\n return new Promise((resolve) => {\n const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });\n const chunks: Buffer[] = [];\n child.stdout.on('data', (c: Buffer) => chunks.push(c));\n child.on('error', () => resolve(false));\n child.on('exit', async (code) => {\n if (code !== 0 || chunks.length === 0) return resolve(false);\n try {\n await fs.writeFile(outPath, Buffer.concat(chunks));\n resolve(true);\n } catch {\n resolve(false);\n }\n });\n });\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import { ImageBlock, Context, ContentBlock, ToolRegistry } from '@wrongstack/core';
2
+
3
+ interface VisionAdapterInput {
4
+ image: ImageBlock;
5
+ prompt?: string;
6
+ ctx: Context;
7
+ signal: AbortSignal;
8
+ }
9
+ interface VisionAdapter {
10
+ name: string;
11
+ describe(input: VisionAdapterInput): Promise<string>;
12
+ }
13
+ type VisionAdapters = readonly VisionAdapter[] | (() => readonly VisionAdapter[] | Promise<readonly VisionAdapter[]>);
14
+ interface VisionRoutingOptions {
15
+ supportsVision: boolean;
16
+ adapters?: VisionAdapters;
17
+ ctx: Context;
18
+ signal: AbortSignal;
19
+ prompt?: string;
20
+ providerId?: string;
21
+ model?: string;
22
+ }
23
+ interface VisionRoutingResult {
24
+ blocks: ContentBlock[];
25
+ route: 'native' | 'adapter' | 'none';
26
+ convertedImages: number;
27
+ adapterName?: string;
28
+ }
29
+ declare class ImageInputUnsupportedError extends Error {
30
+ constructor(opts: {
31
+ providerId?: string;
32
+ model?: string;
33
+ imageCount: number;
34
+ });
35
+ }
36
+ declare function routeImagesForModel(blocks: ContentBlock[], opts: VisionRoutingOptions): Promise<VisionRoutingResult>;
37
+ interface ToolVisionAdapterOptions {
38
+ prompt?: string;
39
+ }
40
+ declare function createToolVisionAdapters(registry: ToolRegistry, opts?: ToolVisionAdapterOptions): VisionAdapter[];
41
+
42
+ export { ImageInputUnsupportedError, type ToolVisionAdapterOptions, type VisionAdapter, type VisionAdapterInput, type VisionAdapters, type VisionRoutingOptions, type VisionRoutingResult, createToolVisionAdapters, routeImagesForModel };
package/dist/vision.js ADDED
@@ -0,0 +1,204 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as os from 'os';
3
+ import * as path from 'path';
4
+
5
+ // src/vision.ts
6
+ var ImageInputUnsupportedError = class extends Error {
7
+ constructor(opts) {
8
+ const target = [opts.providerId, opts.model].filter(Boolean).join("/") || "current model";
9
+ super(
10
+ `${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.`
11
+ );
12
+ this.name = "ImageInputUnsupportedError";
13
+ }
14
+ };
15
+ async function routeImagesForModel(blocks, opts) {
16
+ const images = blocks.filter((b) => b.type === "image");
17
+ if (images.length === 0) {
18
+ return { blocks, route: "none", convertedImages: 0 };
19
+ }
20
+ if (opts.supportsVision) {
21
+ return { blocks, route: "native", convertedImages: 0 };
22
+ }
23
+ const adapters = await resolveAdapters(opts.adapters);
24
+ if (adapters.length === 0) {
25
+ throw new ImageInputUnsupportedError({
26
+ providerId: opts.providerId,
27
+ model: opts.model,
28
+ imageCount: images.length
29
+ });
30
+ }
31
+ const out = [];
32
+ let convertedImages = 0;
33
+ let lastErr;
34
+ let adapterName;
35
+ for (const block of blocks) {
36
+ if (block.type !== "image") {
37
+ out.push(block);
38
+ continue;
39
+ }
40
+ let description;
41
+ for (const adapter of adapters) {
42
+ try {
43
+ description = await adapter.describe({
44
+ image: block,
45
+ prompt: opts.prompt,
46
+ ctx: opts.ctx,
47
+ signal: opts.signal
48
+ });
49
+ adapterName = adapter.name;
50
+ break;
51
+ } catch (err) {
52
+ lastErr = err;
53
+ }
54
+ }
55
+ if (!description?.trim()) {
56
+ throw new Error(
57
+ `No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ""}`
58
+ );
59
+ }
60
+ convertedImages++;
61
+ out.push({
62
+ type: "text",
63
+ text: `[Image ${convertedImages} analyzed via ${adapterName ?? "vision adapter"}]
64
+ ${description.trim()}`
65
+ });
66
+ }
67
+ return { blocks: out, route: "adapter", convertedImages, adapterName };
68
+ }
69
+ async function resolveAdapters(adapters) {
70
+ if (!adapters) return [];
71
+ return typeof adapters === "function" ? await adapters() : adapters;
72
+ }
73
+ function createToolVisionAdapters(registry, opts = {}) {
74
+ return registry.list().filter(isLikelyVisionTool).map((tool) => ({
75
+ name: tool.name,
76
+ async describe(input) {
77
+ const currentTool = registry.get(tool.name);
78
+ if (!currentTool) {
79
+ throw new Error(`Tool "${tool.name}" is no longer registered`);
80
+ }
81
+ const built = await buildToolPayload(currentTool, input.image, input.prompt ?? opts.prompt);
82
+ if (!built) {
83
+ throw new Error(`Tool "${currentTool.name}" does not expose a supported image input schema`);
84
+ }
85
+ try {
86
+ const result = await currentTool.execute(built.payload, input.ctx, {
87
+ signal: input.signal
88
+ });
89
+ return stringifyToolResult(result);
90
+ } finally {
91
+ await built.cleanup?.();
92
+ }
93
+ }
94
+ }));
95
+ }
96
+ function isLikelyVisionTool(tool) {
97
+ if (tool.permission !== "auto" || tool.mutating) return false;
98
+ const haystack = `${tool.name} ${tool.description ?? ""} ${tool.usageHint ?? ""}`.toLowerCase();
99
+ if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;
100
+ if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;
101
+ const props = schemaProperties(tool);
102
+ return [
103
+ "image",
104
+ "base64",
105
+ "data",
106
+ "url",
107
+ "image_url",
108
+ "imageUrl",
109
+ "path",
110
+ "image_path",
111
+ "imagePath",
112
+ "image_url",
113
+ "imageUrl",
114
+ "file_path",
115
+ "filePath",
116
+ "filename",
117
+ "file",
118
+ "mediaType",
119
+ "mimeType"
120
+ ].some((key) => key in props);
121
+ }
122
+ 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.") {
123
+ const props = schemaProperties(tool);
124
+ const payload = {};
125
+ const mediaType = image.source.media_type ?? "image/png";
126
+ const data = image.source.data;
127
+ const url = image.source.url;
128
+ let cleanup;
129
+ const pathKey = firstPresent(props, [
130
+ "path",
131
+ "image_path",
132
+ "imagePath",
133
+ "image_url",
134
+ "imageUrl",
135
+ "file_path",
136
+ "filePath",
137
+ "filename",
138
+ "file"
139
+ ]);
140
+ if (pathKey && image.source.type === "base64" && data) {
141
+ const p = await writeTempImage(data, mediaType);
142
+ payload[pathKey] = p;
143
+ cleanup = async () => {
144
+ await fs.unlink(p).catch(() => void 0);
145
+ await fs.rmdir(path.dirname(p)).catch(() => void 0);
146
+ };
147
+ } else if ("image" in props) {
148
+ payload.image = image.source.type === "base64" ? { type: "base64", mediaType, media_type: mediaType, data } : { type: "url", url };
149
+ } else if (image.source.type === "base64" && "base64" in props) {
150
+ payload.base64 = data;
151
+ } else if (image.source.type === "base64" && "data" in props) {
152
+ payload.data = data;
153
+ } else if (image.source.type === "url" && "url" in props) {
154
+ payload.url = url;
155
+ } else if (image.source.type === "url" && "image_url" in props) {
156
+ payload.image_url = url;
157
+ } else if (image.source.type === "url" && "imageUrl" in props) {
158
+ payload.imageUrl = url;
159
+ } else {
160
+ return null;
161
+ }
162
+ if ("mediaType" in props) payload.mediaType = mediaType;
163
+ if ("mimeType" in props) payload.mimeType = mediaType;
164
+ if ("media_type" in props) payload.media_type = mediaType;
165
+ if ("prompt" in props) payload.prompt = prompt;
166
+ if ("query" in props) payload.query = prompt;
167
+ if ("instruction" in props) payload.instruction = prompt;
168
+ return { payload, cleanup };
169
+ }
170
+ function firstPresent(props, keys) {
171
+ return keys.find((key) => key in props);
172
+ }
173
+ async function writeTempImage(data, mediaType) {
174
+ const ext = mediaType.includes("jpeg") || mediaType.includes("jpg") ? "jpg" : "png";
175
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), "wstack-vision-"));
176
+ const file = path.join(dir, `image.${ext}`);
177
+ await fs.writeFile(file, data, "base64");
178
+ return file;
179
+ }
180
+ function schemaProperties(tool) {
181
+ const schema = tool.inputSchema;
182
+ if (!schema || typeof schema !== "object") return {};
183
+ const props = schema.properties;
184
+ return props && typeof props === "object" ? props : {};
185
+ }
186
+ function stringifyToolResult(value) {
187
+ if (typeof value === "string") return value;
188
+ if (Array.isArray(value)) {
189
+ return value.map((item) => {
190
+ if (item && typeof item === "object" && "text" in item) {
191
+ return String(item.text ?? "");
192
+ }
193
+ return typeof item === "string" ? item : JSON.stringify(item);
194
+ }).join("\n");
195
+ }
196
+ if (value && typeof value === "object" && "text" in value) {
197
+ return String(value.text ?? "");
198
+ }
199
+ return JSON.stringify(value);
200
+ }
201
+
202
+ export { ImageInputUnsupportedError, createToolVisionAdapters, routeImagesForModel };
203
+ //# sourceMappingURL=vision.js.map
204
+ //# sourceMappingURL=vision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vision.ts"],"names":[],"mappings":"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","file":"vision.js","sourcesContent":["import * as fs from 'node:fs/promises';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { ContentBlock, Context, ImageBlock, Tool, ToolRegistry } from '@wrongstack/core';\n\nexport interface VisionAdapterInput {\n image: ImageBlock;\n prompt?: string;\n ctx: Context;\n signal: AbortSignal;\n}\n\nexport interface VisionAdapter {\n name: string;\n describe(input: VisionAdapterInput): Promise<string>;\n}\n\nexport type VisionAdapters =\n | readonly VisionAdapter[]\n | (() => readonly VisionAdapter[] | Promise<readonly VisionAdapter[]>);\n\nexport interface VisionRoutingOptions {\n supportsVision: boolean;\n adapters?: VisionAdapters;\n ctx: Context;\n signal: AbortSignal;\n prompt?: string;\n providerId?: string;\n model?: string;\n}\n\nexport interface VisionRoutingResult {\n blocks: ContentBlock[];\n route: 'native' | 'adapter' | 'none';\n convertedImages: number;\n adapterName?: string;\n}\n\nexport class ImageInputUnsupportedError extends Error {\n constructor(opts: { providerId?: string; model?: string; imageCount: number }) {\n const target = [opts.providerId, opts.model].filter(Boolean).join('/') || 'current model';\n super(\n `${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.`,\n );\n this.name = 'ImageInputUnsupportedError';\n }\n}\n\nexport async function routeImagesForModel(\n blocks: ContentBlock[],\n opts: VisionRoutingOptions,\n): Promise<VisionRoutingResult> {\n const images = blocks.filter((b): b is ImageBlock => b.type === 'image');\n if (images.length === 0) {\n return { blocks, route: 'none', convertedImages: 0 };\n }\n if (opts.supportsVision) {\n return { blocks, route: 'native', convertedImages: 0 };\n }\n\n const adapters = await resolveAdapters(opts.adapters);\n if (adapters.length === 0) {\n throw new ImageInputUnsupportedError({\n providerId: opts.providerId,\n model: opts.model,\n imageCount: images.length,\n });\n }\n\n const out: ContentBlock[] = [];\n let convertedImages = 0;\n let lastErr: unknown;\n let adapterName: string | undefined;\n for (const block of blocks) {\n if (block.type !== 'image') {\n out.push(block);\n continue;\n }\n let description: string | undefined;\n for (const adapter of adapters) {\n try {\n description = await adapter.describe({\n image: block,\n prompt: opts.prompt,\n ctx: opts.ctx,\n signal: opts.signal,\n });\n adapterName = adapter.name;\n break;\n } catch (err) {\n lastErr = err;\n }\n }\n if (!description?.trim()) {\n throw new Error(\n `No image-understanding adapter could process an image.${lastErr instanceof Error ? ` Last error: ${lastErr.message}` : ''}`,\n );\n }\n convertedImages++;\n out.push({\n type: 'text',\n text: `[Image ${convertedImages} analyzed via ${adapterName ?? 'vision adapter'}]\\n${description.trim()}`,\n });\n }\n\n return { blocks: out, route: 'adapter', convertedImages, adapterName };\n}\n\nasync function resolveAdapters(adapters: VisionAdapters | undefined): Promise<readonly VisionAdapter[]> {\n if (!adapters) return [];\n return typeof adapters === 'function' ? await adapters() : adapters;\n}\n\nexport interface ToolVisionAdapterOptions {\n prompt?: string;\n}\n\nexport function createToolVisionAdapters(\n registry: ToolRegistry,\n opts: ToolVisionAdapterOptions = {},\n): VisionAdapter[] {\n return registry\n .list()\n .filter(isLikelyVisionTool)\n .map((tool) => ({\n name: tool.name,\n async describe(input: VisionAdapterInput): Promise<string> {\n const currentTool = registry.get(tool.name);\n if (!currentTool) {\n throw new Error(`Tool \"${tool.name}\" is no longer registered`);\n }\n const built = await buildToolPayload(currentTool, input.image, input.prompt ?? opts.prompt);\n if (!built) {\n throw new Error(`Tool \"${currentTool.name}\" does not expose a supported image input schema`);\n }\n try {\n const result = await currentTool.execute(built.payload, input.ctx, {\n signal: input.signal,\n });\n return stringifyToolResult(result);\n } finally {\n await built.cleanup?.();\n }\n },\n }));\n}\n\nfunction isLikelyVisionTool(tool: Tool): boolean {\n if (tool.permission !== 'auto' || tool.mutating) return false;\n const haystack = `${tool.name} ${tool.description ?? ''} ${tool.usageHint ?? ''}`.toLowerCase();\n if (/(generate|create|draw|paint|edit|upscale|remove|write|delete)/.test(haystack)) return false;\n if (!/(vision|image|screenshot|ocr|describe|analy[sz]e)/.test(haystack)) return false;\n const props = schemaProperties(tool);\n return [\n 'image',\n 'base64',\n 'data',\n 'url',\n 'image_url',\n 'imageUrl',\n 'path',\n 'image_path',\n 'imagePath',\n 'image_url',\n 'imageUrl',\n 'file_path',\n 'filePath',\n 'filename',\n 'file',\n 'mediaType',\n 'mimeType',\n ].some((key) => key in props);\n}\n\nasync function buildToolPayload(\n tool: Tool,\n image: ImageBlock,\n prompt = 'Describe this image for a coding agent. Include visible text, UI state, errors, layout, and any details needed to answer the user.',\n): Promise<{ payload: Record<string, unknown>; cleanup?: () => Promise<void> } | null> {\n const props = schemaProperties(tool);\n const payload: Record<string, unknown> = {};\n const mediaType = image.source.media_type ?? 'image/png';\n const data = image.source.data;\n const url = image.source.url;\n let cleanup: (() => Promise<void>) | undefined;\n\n const pathKey = firstPresent(props, [\n 'path',\n 'image_path',\n 'imagePath',\n 'image_url',\n 'imageUrl',\n 'file_path',\n 'filePath',\n 'filename',\n 'file',\n ]);\n if (pathKey && image.source.type === 'base64' && data) {\n const p = await writeTempImage(data, mediaType);\n payload[pathKey] = p;\n cleanup = async () => {\n await fs.unlink(p).catch(() => undefined);\n await fs.rmdir(path.dirname(p)).catch(() => undefined);\n };\n } else if ('image' in props) {\n payload.image =\n image.source.type === 'base64'\n ? { type: 'base64', mediaType, media_type: mediaType, data }\n : { type: 'url', url };\n } else if (image.source.type === 'base64' && 'base64' in props) {\n payload.base64 = data;\n } else if (image.source.type === 'base64' && 'data' in props) {\n payload.data = data;\n } else if (image.source.type === 'url' && 'url' in props) {\n payload.url = url;\n } else if (image.source.type === 'url' && 'image_url' in props) {\n payload.image_url = url;\n } else if (image.source.type === 'url' && 'imageUrl' in props) {\n payload.imageUrl = url;\n } else {\n return null;\n }\n\n if ('mediaType' in props) payload.mediaType = mediaType;\n if ('mimeType' in props) payload.mimeType = mediaType;\n if ('media_type' in props) payload.media_type = mediaType;\n if ('prompt' in props) payload.prompt = prompt;\n if ('query' in props) payload.query = prompt;\n if ('instruction' in props) payload.instruction = prompt;\n return { payload, cleanup };\n}\n\nfunction firstPresent(props: Record<string, unknown>, keys: string[]): string | undefined {\n return keys.find((key) => key in props);\n}\n\nasync function writeTempImage(data: string, mediaType: string): Promise<string> {\n const ext = mediaType.includes('jpeg') || mediaType.includes('jpg') ? 'jpg' : 'png';\n const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'wstack-vision-'));\n const file = path.join(dir, `image.${ext}`);\n await fs.writeFile(file, data, 'base64');\n return file;\n}\n\nfunction schemaProperties(tool: Tool): Record<string, unknown> {\n const schema = tool.inputSchema;\n if (!schema || typeof schema !== 'object') return {};\n const props = (schema as { properties?: unknown }).properties;\n return props && typeof props === 'object' ? (props as Record<string, unknown>) : {};\n}\n\nfunction stringifyToolResult(value: unknown): string {\n if (typeof value === 'string') return value;\n if (Array.isArray(value)) {\n return value\n .map((item) => {\n if (item && typeof item === 'object' && 'text' in item) {\n return String((item as { text?: unknown }).text ?? '');\n }\n return typeof item === 'string' ? item : JSON.stringify(item);\n })\n .join('\\n');\n }\n if (value && typeof value === 'object' && 'text' in value) {\n return String((value as { text?: unknown }).text ?? '');\n }\n return JSON.stringify(value);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/runtime",
3
- "version": "0.2.0",
3
+ "version": "0.3.3",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack default runtime implementations and host-level composition helpers built on @wrongstack/core.",
6
6
  "repository": {
@@ -28,6 +28,14 @@
28
28
  "types": "./dist/host.d.ts",
29
29
  "import": "./dist/host.js"
30
30
  },
31
+ "./vision": {
32
+ "types": "./dist/vision.d.ts",
33
+ "import": "./dist/vision.js"
34
+ },
35
+ "./clipboard": {
36
+ "types": "./dist/clipboard.d.ts",
37
+ "import": "./dist/clipboard.js"
38
+ },
31
39
  "./package.json": "./package.json"
32
40
  },
33
41
  "files": [
@@ -35,7 +43,7 @@
35
43
  "README.md"
36
44
  ],
37
45
  "dependencies": {
38
- "@wrongstack/core": "0.3.1"
46
+ "@wrongstack/core": "0.3.3"
39
47
  },
40
48
  "devDependencies": {
41
49
  "@types/node": "^22.19.19",