gpteam 0.1.20 → 0.1.22

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
@@ -27,7 +27,7 @@ The Image MCP exposes both a synchronous compatibility tool and a local async jo
27
27
 
28
28
  Image MCP results are returned as stable JSON text and MCP `structuredContent`. Successful results include final file path, model, action, size, format, quality, byte size, SHA-256, MIME type, image dimensions, duration, retry count, `job_id`, `trace_id`, and optional `idempotency_key`. Error results use stable `error.code`, `error.message`, `error.retryable`, `error.stage`, `error.upstream_status`, and `error.trace_id` fields while keeping compatibility fields such as `category` and `http_status`.
29
29
 
30
- The MCP supports normal text-to-image generation and image-to-image/edit inputs. Pass `images` as data URLs, HTTPS URLs, or local file paths. Pass `mask` the same way for masked edits. `input_fidelity` is accepted for compatibility but is not forwarded to the current GPTeam Image 2 bridge because upstream Codex image edits reject it. File writes create missing directories, avoid overwriting existing files by adding `-v2`, `-v3`, etc., and validate PNG/JPEG/WebP before returning success. `overwrite: true` is available for explicit replacement. `return_revised_prompt` controls whether the upstream revised prompt is included in the result.
30
+ The MCP supports normal text-to-image generation and image-to-image/edit inputs. Pass `images` as data URLs, HTTPS URLs, or local file paths. For easier tool calling, `image`, `image_path`, `image_paths`, `input_image`, and `input_images` are accepted as aliases. Pass `mask` or `mask_path` the same way for masked edits. `input_fidelity` is accepted for compatibility but is not forwarded to the current GPTeam Image 2 bridge because upstream Codex image edits reject it. The MCP requests GPTeam image endpoints with `stream=true` and reads the final image event, so long image jobs get early stream bytes instead of sitting idle until the final JSON body. File writes create missing directories, avoid overwriting existing files by adding `-v2`, `-v3`, etc., and validate PNG/JPEG/WebP before returning success. `overwrite: true` is available for explicit replacement. `return_revised_prompt` controls whether the upstream revised prompt is included in the result.
31
31
 
32
32
  Claude Code is written to `~/.claude/settings.json` under the `env` section, using the GPTeam `/anthropic` base URL. OpenClaw writes `models.providers.gpteam` and also selects `gpteam/<model>` under `agents.defaults.model`, so the chosen model is active without an extra manual step.
33
33
 
package/lib/help.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PACKAGE_NAME = 'gpteam';
2
- export const PACKAGE_VERSION = '0.1.20';
2
+ export const PACKAGE_VERSION = '0.1.22';
3
3
 
4
4
  export function getHelpText() {
5
5
  return [
@@ -29,14 +29,15 @@ export function buildImageGenerationPayload(input = {}, options = {}) {
29
29
  model: String(input.model || DEFAULT_IMAGE_MODEL),
30
30
  prompt: String(input.prompt || '').trim(),
31
31
  response_format: 'b64_json',
32
+ stream: true,
32
33
  size: String(input.size || '1024x1024'),
33
34
  quality: String(input.quality || 'high'),
34
35
  output_format: normalizeImageFormat(input.format || input.output_format || DEFAULT_IMAGE_FORMAT)
35
36
  };
36
37
  const imageOptions = { ...options, home: options.home };
37
- const images = normalizeInputImages(input.images || input.image, imageOptions);
38
+ const images = normalizeInputImages(collectInputImageValues(input), imageOptions);
38
39
  if (images.length > 0) payload.images = images.map((imageURL) => ({ image_url: imageURL }));
39
- const mask = normalizeImageReference(input.mask, imageOptions);
40
+ const mask = normalizeImageReference(firstPresentImageReference(input.mask, input.mask_path), imageOptions);
40
41
  if (mask) payload.mask = { image_url: mask };
41
42
  copyOptionalImageToolOption(payload, input, 'background');
42
43
  copyOptionalImageToolOption(payload, input, 'moderation');
@@ -285,6 +286,8 @@ export function getCapabilities(options = {}) {
285
286
  supports_idempotency_key: true,
286
287
  supports_image_to_image: true,
287
288
  supports_mask: true,
289
+ image_input_fields: ['images', 'image', 'image_path', 'image_paths', 'input_image', 'input_images'],
290
+ mask_input_fields: ['mask', 'mask_path'],
288
291
  sizes: ['1024x1024', '1536x1024', '1024x1536', 'auto'],
289
292
  formats: ['png', 'jpeg', 'webp'],
290
293
  quality: ['low', 'medium', 'high', 'auto'],
@@ -330,21 +333,8 @@ async function fetchImageOnce(fetchImpl, credentials, payload, options) {
330
333
  signal: requestSignal.signal
331
334
  });
332
335
  if (!response.ok) throw await imageErrorFromHTTPResponse(response, credentials.apiKey);
333
- const data = await response.json();
334
- const first = Array.isArray(data && data.data) ? data.data[0] : null;
335
- const b64 = first && typeof first.b64_json === 'string' ? first.b64_json : '';
336
- if (!b64) {
337
- throw new ImageMCPError('GPTeam 图片接口没有返回 b64_json 图片数据。', {
338
- code: 'image_data_missing',
339
- category: 'response_invalid',
340
- stage: 'local',
341
- retryable: false
342
- });
343
- }
344
- return {
345
- b64,
346
- revisedPrompt: first && typeof first.revised_prompt === 'string' ? first.revised_prompt : ''
347
- };
336
+ if (isEventStreamResponse(response)) return await readImageSSE(response);
337
+ return await readImageJSON(response);
348
338
  } catch (error) {
349
339
  if (error instanceof ImageMCPError) throw error;
350
340
  throw imageErrorFromFetch(error, {
@@ -357,6 +347,98 @@ async function fetchImageOnce(fetchImpl, credentials, payload, options) {
357
347
  }
358
348
  }
359
349
 
350
+ async function readImageJSON(response) {
351
+ const data = await response.json();
352
+ const first = Array.isArray(data && data.data) ? data.data[0] : null;
353
+ return imageResultFromItem(first);
354
+ }
355
+
356
+ async function readImageSSE(response) {
357
+ const text = await response.text();
358
+ let lastError = null;
359
+ for (const event of parseSSEDataEvents(text)) {
360
+ if (!event || event === '[DONE]') continue;
361
+ let payload;
362
+ try {
363
+ payload = JSON.parse(event);
364
+ } catch {
365
+ continue;
366
+ }
367
+ if (payload && payload.error) {
368
+ lastError = imageErrorFromSSEPayload(payload);
369
+ continue;
370
+ }
371
+ const item = imageResultFromStreamPayload(payload);
372
+ if (item) return item;
373
+ }
374
+ if (lastError) throw lastError;
375
+ throw missingImageDataError();
376
+ }
377
+
378
+ function parseSSEDataEvents(text) {
379
+ const events = [];
380
+ let current = [];
381
+ for (const rawLine of String(text || '').split(/\r?\n/)) {
382
+ if (rawLine === '') {
383
+ if (current.length > 0) events.push(current.join('\n'));
384
+ current = [];
385
+ continue;
386
+ }
387
+ if (rawLine.startsWith('data:')) current.push(rawLine.slice(5).trimStart());
388
+ }
389
+ if (current.length > 0) events.push(current.join('\n'));
390
+ return events;
391
+ }
392
+
393
+ function imageResultFromStreamPayload(payload) {
394
+ const type = String(payload && payload.type || '');
395
+ if (!type.endsWith('.completed')) return null;
396
+ return imageResultFromItem(payload);
397
+ }
398
+
399
+ function imageResultFromItem(first) {
400
+ const b64 = first && typeof first.b64_json === 'string' ? first.b64_json : imageDataURLToB64(first && first.url);
401
+ if (!b64) throw missingImageDataError();
402
+ return {
403
+ b64,
404
+ revisedPrompt: first && typeof first.revised_prompt === 'string' ? first.revised_prompt : ''
405
+ };
406
+ }
407
+
408
+ function imageDataURLToB64(value) {
409
+ const text = String(value || '').trim();
410
+ const match = text.match(/^data:image\/[a-z0-9.+-]+;base64,(.+)$/i);
411
+ return match ? match[1] : '';
412
+ }
413
+
414
+ function missingImageDataError() {
415
+ return new ImageMCPError('GPTeam 图片接口没有返回 b64_json 图片数据。', {
416
+ code: 'image_data_missing',
417
+ category: 'response_invalid',
418
+ stage: 'local',
419
+ retryable: false
420
+ });
421
+ }
422
+
423
+ function imageErrorFromSSEPayload(payload) {
424
+ const error = payload && payload.error;
425
+ const message = typeof error === 'string' ? error : String(error && error.message || 'GPTeam 图片流返回错误。');
426
+ const code = typeof error === 'object' && error ? String(error.code || payload.code || 'upstream_stream_error') : String(payload.code || 'upstream_stream_error');
427
+ return new ImageMCPError(message, {
428
+ code,
429
+ category: 'upstream',
430
+ stage: 'upstream',
431
+ retryable: code === 'rate_limit_exceeded' || code === 'server_error'
432
+ });
433
+ }
434
+
435
+ function isEventStreamResponse(response) {
436
+ const contentType = response && response.headers && typeof response.headers.get === 'function'
437
+ ? String(response.headers.get('content-type') || '')
438
+ : '';
439
+ return /text\/event-stream/i.test(contentType);
440
+ }
441
+
360
442
  function buildSuccessResult(input) {
361
443
  const result = {
362
444
  ok: true,
@@ -552,6 +634,36 @@ function normalizeInputImages(value, options = {}) {
552
634
  return rawImages.map((item) => normalizeImageReference(item, options)).filter(Boolean);
553
635
  }
554
636
 
637
+ function collectInputImageValues(input = {}) {
638
+ const values = [];
639
+ appendImageAlias(values, input.images);
640
+ appendImageAlias(values, input.image);
641
+ appendImageAlias(values, input.image_path);
642
+ appendImageAlias(values, input.image_paths);
643
+ appendImageAlias(values, input.input_image);
644
+ appendImageAlias(values, input.input_images);
645
+ return values;
646
+ }
647
+
648
+ function appendImageAlias(target, value) {
649
+ if (Array.isArray(value)) {
650
+ for (const item of value) appendImageAlias(target, item);
651
+ return;
652
+ }
653
+ if (value) target.push(value);
654
+ }
655
+
656
+ function firstPresentImageReference(...values) {
657
+ for (const value of values) {
658
+ if (Array.isArray(value)) {
659
+ if (value.length > 0) return value[0];
660
+ continue;
661
+ }
662
+ if (value) return value;
663
+ }
664
+ return '';
665
+ }
666
+
555
667
  function normalizeImageReference(value, options = {}) {
556
668
  if (!value) return '';
557
669
  if (typeof value === 'object') {
@@ -53,10 +53,32 @@ const imageInputProperties = {
53
53
  items: { type: 'string' },
54
54
  description: 'Optional input images for image-to-image or edit. Values may be data URLs, HTTPS URLs, or local file paths.'
55
55
  },
56
+ image_path: {
57
+ type: 'string',
58
+ description: 'Optional single local image path alias for image-to-image or edit.'
59
+ },
60
+ image_paths: {
61
+ type: 'array',
62
+ items: { type: 'string' },
63
+ description: 'Optional local image path aliases for image-to-image or edit.'
64
+ },
65
+ input_image: {
66
+ type: 'string',
67
+ description: 'Optional single input image alias. Accepts a data URL, HTTPS URL, or local file path.'
68
+ },
69
+ input_images: {
70
+ type: 'array',
71
+ items: { type: 'string' },
72
+ description: 'Optional input image aliases. Accepts data URLs, HTTPS URLs, or local file paths.'
73
+ },
56
74
  mask: {
57
75
  type: 'string',
58
76
  description: 'Optional mask image for image edit. Value may be a data URL, HTTPS URL, or local file path.'
59
77
  },
78
+ mask_path: {
79
+ type: 'string',
80
+ description: 'Optional local mask image path alias for image edit.'
81
+ },
60
82
  input_fidelity: {
61
83
  type: 'string',
62
84
  description: 'Accepted for compatibility. The GPTeam Image 2 bridge currently ignores this option because the upstream Codex image tool rejects it on edits.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gpteam",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "GPTeam API interactive client configurator and ingress benchmark CLI.",
5
5
  "type": "module",
6
6
  "bin": {