gpteam 0.1.20 → 0.1.21
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 +1 -1
- package/lib/help.js +1 -1
- package/lib/image-mcp/image.js +95 -15
- package/package.json +1 -1
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. 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. 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
package/lib/image-mcp/image.js
CHANGED
|
@@ -29,6 +29,7 @@ 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)
|
|
@@ -330,21 +331,8 @@ async function fetchImageOnce(fetchImpl, credentials, payload, options) {
|
|
|
330
331
|
signal: requestSignal.signal
|
|
331
332
|
});
|
|
332
333
|
if (!response.ok) throw await imageErrorFromHTTPResponse(response, credentials.apiKey);
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
};
|
|
334
|
+
if (isEventStreamResponse(response)) return await readImageSSE(response);
|
|
335
|
+
return await readImageJSON(response);
|
|
348
336
|
} catch (error) {
|
|
349
337
|
if (error instanceof ImageMCPError) throw error;
|
|
350
338
|
throw imageErrorFromFetch(error, {
|
|
@@ -357,6 +345,98 @@ async function fetchImageOnce(fetchImpl, credentials, payload, options) {
|
|
|
357
345
|
}
|
|
358
346
|
}
|
|
359
347
|
|
|
348
|
+
async function readImageJSON(response) {
|
|
349
|
+
const data = await response.json();
|
|
350
|
+
const first = Array.isArray(data && data.data) ? data.data[0] : null;
|
|
351
|
+
return imageResultFromItem(first);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function readImageSSE(response) {
|
|
355
|
+
const text = await response.text();
|
|
356
|
+
let lastError = null;
|
|
357
|
+
for (const event of parseSSEDataEvents(text)) {
|
|
358
|
+
if (!event || event === '[DONE]') continue;
|
|
359
|
+
let payload;
|
|
360
|
+
try {
|
|
361
|
+
payload = JSON.parse(event);
|
|
362
|
+
} catch {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (payload && payload.error) {
|
|
366
|
+
lastError = imageErrorFromSSEPayload(payload);
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const item = imageResultFromStreamPayload(payload);
|
|
370
|
+
if (item) return item;
|
|
371
|
+
}
|
|
372
|
+
if (lastError) throw lastError;
|
|
373
|
+
throw missingImageDataError();
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function parseSSEDataEvents(text) {
|
|
377
|
+
const events = [];
|
|
378
|
+
let current = [];
|
|
379
|
+
for (const rawLine of String(text || '').split(/\r?\n/)) {
|
|
380
|
+
if (rawLine === '') {
|
|
381
|
+
if (current.length > 0) events.push(current.join('\n'));
|
|
382
|
+
current = [];
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (rawLine.startsWith('data:')) current.push(rawLine.slice(5).trimStart());
|
|
386
|
+
}
|
|
387
|
+
if (current.length > 0) events.push(current.join('\n'));
|
|
388
|
+
return events;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function imageResultFromStreamPayload(payload) {
|
|
392
|
+
const type = String(payload && payload.type || '');
|
|
393
|
+
if (!type.endsWith('.completed')) return null;
|
|
394
|
+
return imageResultFromItem(payload);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function imageResultFromItem(first) {
|
|
398
|
+
const b64 = first && typeof first.b64_json === 'string' ? first.b64_json : imageDataURLToB64(first && first.url);
|
|
399
|
+
if (!b64) throw missingImageDataError();
|
|
400
|
+
return {
|
|
401
|
+
b64,
|
|
402
|
+
revisedPrompt: first && typeof first.revised_prompt === 'string' ? first.revised_prompt : ''
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function imageDataURLToB64(value) {
|
|
407
|
+
const text = String(value || '').trim();
|
|
408
|
+
const match = text.match(/^data:image\/[a-z0-9.+-]+;base64,(.+)$/i);
|
|
409
|
+
return match ? match[1] : '';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function missingImageDataError() {
|
|
413
|
+
return new ImageMCPError('GPTeam 图片接口没有返回 b64_json 图片数据。', {
|
|
414
|
+
code: 'image_data_missing',
|
|
415
|
+
category: 'response_invalid',
|
|
416
|
+
stage: 'local',
|
|
417
|
+
retryable: false
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function imageErrorFromSSEPayload(payload) {
|
|
422
|
+
const error = payload && payload.error;
|
|
423
|
+
const message = typeof error === 'string' ? error : String(error && error.message || 'GPTeam 图片流返回错误。');
|
|
424
|
+
const code = typeof error === 'object' && error ? String(error.code || payload.code || 'upstream_stream_error') : String(payload.code || 'upstream_stream_error');
|
|
425
|
+
return new ImageMCPError(message, {
|
|
426
|
+
code,
|
|
427
|
+
category: 'upstream',
|
|
428
|
+
stage: 'upstream',
|
|
429
|
+
retryable: code === 'rate_limit_exceeded' || code === 'server_error'
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function isEventStreamResponse(response) {
|
|
434
|
+
const contentType = response && response.headers && typeof response.headers.get === 'function'
|
|
435
|
+
? String(response.headers.get('content-type') || '')
|
|
436
|
+
: '';
|
|
437
|
+
return /text\/event-stream/i.test(contentType);
|
|
438
|
+
}
|
|
439
|
+
|
|
360
440
|
function buildSuccessResult(input) {
|
|
361
441
|
const result = {
|
|
362
442
|
ok: true,
|