@steerable/agent-shell 0.6.44 → 0.6.46

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.
@@ -18,7 +18,7 @@
18
18
  * should know an image was attached even when it was too large to send.
19
19
  */
20
20
  import { getNativeImage } from './runtime.js';
21
- import { statSync } from 'fs';
21
+ import { readFileSync, statSync } from 'fs';
22
22
  import { basename, extname } from 'path';
23
23
  /** Refuse to read source files larger than this (10 MB). */
24
24
  export const IMAGE_MAX_SOURCE_BYTES = 10 * 1024 * 1024;
@@ -82,13 +82,6 @@ export function processViewImage(input, nativeImage = getNativeImage()) {
82
82
  needsFollowup: true,
83
83
  };
84
84
  }
85
- if (!nativeImage) {
86
- return {
87
- success: false,
88
- error: '当前运行环境不支持图片解码',
89
- needsFollowup: true,
90
- };
91
- }
92
85
  let sourceBytes;
93
86
  try {
94
87
  sourceBytes = statSync(input.path).size;
@@ -111,6 +104,9 @@ export function processViewImage(input, nativeImage = getNativeImage()) {
111
104
  needsFollowup: true,
112
105
  };
113
106
  }
107
+ if (!nativeImage) {
108
+ return viewImageWithoutDecoder(input, sourceBytes, maxEdge);
109
+ }
114
110
  let rendered = nativeImage.createFromPath(input.path);
115
111
  if (rendered.isEmpty()) {
116
112
  return { success: false, error: '不是可识别的图片', needsFollowup: true };
@@ -159,6 +155,88 @@ export function processViewImage(input, nativeImage = getNativeImage()) {
159
155
  },
160
156
  };
161
157
  }
158
+ function viewImageWithoutDecoder(input, sourceBytes, maxEdge) {
159
+ if (input.region) {
160
+ return {
161
+ success: false,
162
+ error: '当前环境不能裁剪图片',
163
+ needsFollowup: true,
164
+ };
165
+ }
166
+ if (sourceBytes > IMAGE_MAX_ENCODED_BYTES) {
167
+ return {
168
+ success: false,
169
+ error: `源文件 ${formatMb(sourceBytes)}MB 超过 ${formatMb(IMAGE_MAX_ENCODED_BYTES)}MB,当前环境不能缩放`,
170
+ needsFollowup: true,
171
+ };
172
+ }
173
+ const sourceExt = extname(input.path).toLowerCase();
174
+ if (sourceExt !== '.png' && sourceExt !== '.jpg' && sourceExt !== '.jpeg') {
175
+ return {
176
+ success: false,
177
+ error: '当前环境只能直接发送 PNG 和 JPEG',
178
+ needsFollowup: true,
179
+ };
180
+ }
181
+ const natural = sourceExt === '.png' ? 'png' : 'jpeg';
182
+ if (input.format != null && input.format !== natural) {
183
+ return {
184
+ success: false,
185
+ error: '当前环境不能转码',
186
+ needsFollowup: true,
187
+ };
188
+ }
189
+ const bytes = readFileSync(input.path);
190
+ const size = readRasterSize(bytes);
191
+ if (size && (size.width > maxEdge || size.height > maxEdge)) {
192
+ return {
193
+ success: false,
194
+ error: '当前环境不能缩放',
195
+ needsFollowup: true,
196
+ };
197
+ }
198
+ const mediaType = natural === 'jpeg' ? 'image/jpeg' : 'image/png';
199
+ return {
200
+ success: true,
201
+ data: {
202
+ width: size?.width ?? 0,
203
+ height: size?.height ?? 0,
204
+ sourcePath: input.path,
205
+ mediaType,
206
+ _image: { b64: bytes.toString('base64'), media_type: mediaType },
207
+ },
208
+ };
209
+ }
210
+ function readRasterSize(bytes) {
211
+ if (bytes.length >= 24 &&
212
+ bytes[0] === 137 &&
213
+ bytes.toString('ascii', 1, 4) === 'PNG') {
214
+ return { width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20) };
215
+ }
216
+ if (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8)
217
+ return null;
218
+ let offset = 2;
219
+ while (offset + 9 < bytes.length) {
220
+ if (bytes[offset] !== 0xff)
221
+ return null;
222
+ const marker = bytes[offset + 1];
223
+ if (marker === 0xd8 || marker === 0xd9) {
224
+ offset += 2;
225
+ continue;
226
+ }
227
+ const length = bytes.readUInt16BE(offset + 2);
228
+ if (length < 2 || offset + 2 + length > bytes.length)
229
+ return null;
230
+ if (marker >= 0xc0 && marker <= 0xc3) {
231
+ return {
232
+ height: bytes.readUInt16BE(offset + 5),
233
+ width: bytes.readUInt16BE(offset + 7),
234
+ };
235
+ }
236
+ offset += 2 + length;
237
+ }
238
+ return null;
239
+ }
162
240
  function resolveCrop(region, size) {
163
241
  const values = [region.x, region.y, region.w, region.h];
164
242
  if (!values.every(Number.isFinite))
@@ -208,8 +286,25 @@ export function processImageAttachments(files) {
208
286
  }
209
287
  const nativeImage = getNativeImage();
210
288
  if (!nativeImage) {
211
- // Non-Electron host (BS server, unit tests, headless) — can't decode images.
212
- notes.push(`- ${label}:当前运行环境不支持图片解码,未附加`);
289
+ // Tauri and the browser server run plain Node, which has no Electron
290
+ // nativeImage. The bytes are already a PNG/JPEG the provider can read;
291
+ // pass them through when they fit the encoded cap. Resize stays on the
292
+ // Electron path.
293
+ if (sourceBytes > IMAGE_MAX_ENCODED_BYTES) {
294
+ notes.push(`- ${label}:源文件 ${formatMb(sourceBytes)}MB 超过 ${formatMb(IMAGE_MAX_ENCODED_BYTES)}MB,当前环境不能缩放,未附加`);
295
+ continue;
296
+ }
297
+ const ext = extname(file.path).toLowerCase();
298
+ const mediaType = ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg'
299
+ : ext === '.gif' ? 'image/gif'
300
+ : ext === '.webp' ? 'image/webp'
301
+ : ext === '.bmp' ? 'image/bmp'
302
+ : 'image/png';
303
+ images.push({
304
+ data: readFileSync(file.path).toString('base64'),
305
+ mediaType,
306
+ });
307
+ notes.push(`- ${label}(原图)`);
213
308
  continue;
214
309
  }
215
310
  const image = nativeImage.createFromPath(file.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steerable/agent-shell",
3
- "version": "0.6.44",
3
+ "version": "0.6.46",
4
4
  "description": "Steerable framework — product-neutral Node HostRuntime and headless HTTP server shared by Tauri desktop and browser deployments; scenario packs extend it through @steerable/pack-sdk.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://steerableframework.com/",
@@ -55,9 +55,9 @@
55
55
  "he": "^1.2.0",
56
56
  "node-pty": "^1.1.0",
57
57
  "semver": "^7.8.0",
58
- "@steerable/agent-protocol": "0.6.44",
59
- "@steerable/agent-harness": "0.6.44",
60
- "@steerable/pack-sdk": "0.6.44"
58
+ "@steerable/agent-protocol": "0.6.46",
59
+ "@steerable/pack-sdk": "0.6.46",
60
+ "@steerable/agent-harness": "0.6.46"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/better-sqlite3": "^7.6.13",