@oh-my-pi/pi-coding-agent 17.0.0 → 17.0.2
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/CHANGELOG.md +128 -0
- package/dist/cli.js +4006 -3966
- package/dist/types/advisor/advise-tool.d.ts +5 -2
- package/dist/types/advisor/config.d.ts +14 -6
- package/dist/types/advisor/runtime.d.ts +20 -11
- package/dist/types/autolearn/controller.d.ts +1 -0
- package/dist/types/cli-commands.d.ts +14 -1
- package/dist/types/config/model-discovery.d.ts +17 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/model-resolver.d.ts +6 -2
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/models-config.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +32 -7
- package/dist/types/config/settings.d.ts +50 -0
- package/dist/types/cursor.d.ts +11 -0
- package/dist/types/discovery/helpers.d.ts +5 -2
- package/dist/types/discovery/substitute-plugin-root.d.ts +20 -7
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/managed-timers.d.ts +15 -0
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +18 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +40 -0
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/extensibility/utils.d.ts +2 -2
- package/dist/types/internal-urls/agent-protocol.d.ts +1 -0
- package/dist/types/lib/xai-http.d.ts +11 -0
- package/dist/types/mcp/transports/stdio.d.ts +13 -1
- package/dist/types/modes/acp/acp-agent.d.ts +3 -0
- package/dist/types/modes/acp/acp-mode.d.ts +6 -2
- package/dist/types/modes/components/advisor-config.d.ts +8 -1
- package/dist/types/modes/components/assistant-message.d.ts +2 -0
- package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +23 -1
- package/dist/types/modes/components/hook-editor.d.ts +16 -2
- package/dist/types/modes/components/login-dialog.test.d.ts +1 -0
- package/dist/types/modes/components/model-hub.d.ts +5 -2
- package/dist/types/modes/components/session-selector.d.ts +4 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -1
- package/dist/types/modes/components/tool-execution.test.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +8 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/noninteractive-dispose.test.d.ts +1 -0
- package/dist/types/modes/print-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +10 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +2 -2
- package/dist/types/modes/warp-events.d.ts +24 -0
- package/dist/types/modes/warp-events.test.d.ts +1 -0
- package/dist/types/plan-mode/model-transition.d.ts +47 -0
- package/dist/types/plan-mode/model-transition.test.d.ts +1 -0
- package/dist/types/registry/agent-lifecycle.d.ts +26 -1
- package/dist/types/sdk.d.ts +13 -2
- package/dist/types/session/agent-session.d.ts +43 -11
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-manager.d.ts +14 -0
- package/dist/types/session/streaming-output.d.ts +2 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +9 -0
- package/dist/types/slash-commands/types.d.ts +4 -4
- package/dist/types/system-prompt.d.ts +1 -1
- package/dist/types/task/label.d.ts +1 -1
- package/dist/types/telemetry-export.d.ts +34 -9
- package/dist/types/tools/approval.d.ts +8 -0
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/browser/registry.d.ts +7 -3
- package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
- package/dist/types/tools/essential-tools.d.ts +29 -0
- package/dist/types/tools/image-gen.d.ts +2 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/manage-skill.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +11 -2
- package/dist/types/utils/title-generator.d.ts +2 -1
- package/dist/types/web/search/index.d.ts +2 -0
- package/dist/types/web/search/providers/base.d.ts +3 -0
- package/dist/types/web/search/providers/kimi.d.ts +4 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +21 -16
- package/src/advisor/__tests__/advisor.test.ts +1358 -86
- package/src/advisor/__tests__/config.test.ts +58 -2
- package/src/advisor/advise-tool.ts +7 -3
- package/src/advisor/config.ts +76 -24
- package/src/advisor/runtime.ts +445 -92
- package/src/autolearn/controller.ts +23 -28
- package/src/cli/bench-cli.ts +4 -1
- package/src/cli/grep-cli.ts +2 -1
- package/src/cli-commands.ts +57 -23
- package/src/cli.ts +5 -1
- package/src/config/model-discovery.ts +81 -21
- package/src/config/model-registry.ts +34 -6
- package/src/config/model-resolver.ts +57 -12
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +43 -6
- package/src/config/settings.ts +405 -25
- package/src/cursor.ts +20 -3
- package/src/debug/report-bundle.ts +40 -4
- package/src/discovery/codex.ts +14 -4
- package/src/discovery/helpers.ts +28 -5
- package/src/discovery/substitute-plugin-root.ts +23 -7
- package/src/exec/bash-executor.ts +14 -5
- package/src/export/html/template.js +2 -0
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/managed-timers.ts +83 -0
- package/src/extensibility/extensions/runner.ts +26 -0
- package/src/extensibility/extensions/types.ts +18 -0
- package/src/extensibility/extensions/wrapper.ts +2 -1
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +96 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +225 -22
- package/src/extensibility/plugins/manager.ts +2 -2
- package/src/extensibility/shared-events.ts +6 -0
- package/src/extensibility/utils.ts +91 -25
- package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +73 -0
- package/src/internal-urls/agent-protocol.ts +73 -39
- package/src/irc/bus.ts +22 -3
- package/src/launch/broker.ts +3 -2
- package/src/launch/client.ts +2 -2
- package/src/launch/presence.ts +2 -2
- package/src/lib/xai-http.ts +28 -2
- package/src/lsp/client.ts +1 -1
- package/src/main.ts +11 -8
- package/src/mcp/manager.ts +9 -3
- package/src/mcp/transports/stdio.ts +103 -23
- package/src/modes/acp/acp-agent.ts +28 -9
- package/src/modes/acp/acp-event-mapper.ts +8 -0
- package/src/modes/acp/acp-mode.ts +18 -4
- package/src/modes/components/advisor-config.ts +65 -3
- package/src/modes/components/ask-dialog.ts +1 -1
- package/src/modes/components/assistant-message.ts +35 -14
- package/src/modes/components/chat-transcript-builder.ts +2 -0
- package/src/modes/components/custom-editor-plugin-ctor.test.ts +36 -0
- package/src/modes/components/custom-editor.ts +62 -1
- package/src/modes/components/hook-editor.ts +35 -5
- package/src/modes/components/login-dialog.test.ts +56 -0
- package/src/modes/components/login-dialog.ts +7 -3
- package/src/modes/components/model-hub.ts +138 -42
- package/src/modes/components/plan-review-overlay.ts +51 -10
- package/src/modes/components/session-selector.ts +10 -0
- package/src/modes/components/status-line/component.test.ts +1 -0
- package/src/modes/components/status-line/component.ts +2 -4
- package/src/modes/components/status-line/segments.ts +21 -6
- package/src/modes/components/tool-execution.test.ts +101 -0
- package/src/modes/components/tool-execution.ts +117 -9
- package/src/modes/components/transcript-container.ts +30 -2
- package/src/modes/controllers/command-controller-shared.ts +1 -1
- package/src/modes/controllers/command-controller.ts +168 -48
- package/src/modes/controllers/event-controller.ts +6 -0
- package/src/modes/controllers/extension-ui-controller.ts +4 -22
- package/src/modes/controllers/input-controller.ts +12 -12
- package/src/modes/controllers/selector-controller.ts +208 -46
- package/src/modes/interactive-mode.ts +205 -65
- package/src/modes/noninteractive-dispose.test.ts +60 -0
- package/src/modes/print-mode.ts +10 -5
- package/src/modes/rpc/host-tools.ts +2 -1
- package/src/modes/rpc/rpc-mode.ts +31 -7
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +10 -0
- package/src/modes/utils/interactive-context-helpers.ts +3 -1
- package/src/modes/utils/transcript-render-helpers.ts +3 -2
- package/src/modes/warp-events.test.ts +794 -0
- package/src/modes/warp-events.ts +232 -0
- package/src/plan-mode/model-transition.test.ts +60 -0
- package/src/plan-mode/model-transition.ts +51 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/registry/agent-lifecycle.ts +133 -18
- package/src/sdk.ts +227 -44
- package/src/session/agent-session.ts +1364 -382
- package/src/session/session-history-format.ts +20 -5
- package/src/session/session-listing.ts +6 -5
- package/src/session/session-loader.ts +9 -0
- package/src/session/session-manager.ts +48 -0
- package/src/session/session-persistence.ts +11 -0
- package/src/session/streaming-output.ts +3 -1
- package/src/slash-commands/builtin-registry.ts +9 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +16 -0
- package/src/slash-commands/types.ts +4 -4
- package/src/system-prompt.ts +2 -2
- package/src/task/executor.ts +1 -1
- package/src/task/label.ts +2 -0
- package/src/telemetry-export.ts +453 -97
- package/src/tools/approval.ts +11 -0
- package/src/tools/bash.ts +71 -38
- package/src/tools/browser/registry.ts +9 -2
- package/src/tools/browser/tab-supervisor.ts +65 -10
- package/src/tools/browser.ts +4 -3
- package/src/tools/essential-tools.ts +45 -0
- package/src/tools/eval.ts +3 -0
- package/src/tools/gh.ts +169 -2
- package/src/tools/image-gen.ts +624 -513
- package/src/tools/index.ts +10 -6
- package/src/tools/manage-skill.ts +5 -3
- package/src/tools/output-meta.ts +7 -0
- package/src/tools/path-utils.ts +8 -5
- package/src/tools/read.ts +48 -3
- package/src/tools/write.ts +22 -4
- package/src/tools/xdev.ts +14 -3
- package/src/utils/title-generator.ts +15 -4
- package/src/web/search/index.ts +21 -6
- package/src/web/search/providers/base.ts +3 -0
- package/src/web/search/providers/kimi.ts +18 -12
- package/src/web/search/providers/xai.ts +40 -9
- package/src/web/search/types.ts +6 -1
package/src/tools/image-gen.ts
CHANGED
|
@@ -45,7 +45,7 @@ const IMAGE_SYSTEM_INSTRUCTION =
|
|
|
45
45
|
"You are an AI image generator. Generate images based on user descriptions. Focus on creating high-quality, visually appealing images that match the user's request.";
|
|
46
46
|
|
|
47
47
|
export type ImageProvider = "antigravity" | "gemini" | "openai" | "openai-codex" | "openrouter" | "xai";
|
|
48
|
-
export type ImageProviderPreference =
|
|
48
|
+
export type ImageProviderPreference = ImageProvider | "auto";
|
|
49
49
|
|
|
50
50
|
interface ImageApiKey {
|
|
51
51
|
provider: ImageProvider;
|
|
@@ -57,7 +57,17 @@ interface ImageApiKey {
|
|
|
57
57
|
const COMMON_IMAGE_ASPECT_RATIOS = ["1:1", "3:4", "4:3", "9:16", "16:9"] as const;
|
|
58
58
|
const XAI_IMAGE_ASPECT_RATIOS = [...COMMON_IMAGE_ASPECT_RATIOS, "3:2", "2:3"] as const;
|
|
59
59
|
const COMMON_IMAGE_ASPECT_RATIO_SET = new Set<string>(COMMON_IMAGE_ASPECT_RATIOS);
|
|
60
|
-
const
|
|
60
|
+
const IMAGE_PROVIDER_CHOICES = [
|
|
61
|
+
"auto",
|
|
62
|
+
"antigravity",
|
|
63
|
+
"gemini",
|
|
64
|
+
"openai",
|
|
65
|
+
"openai-codex",
|
|
66
|
+
"openrouter",
|
|
67
|
+
"xai",
|
|
68
|
+
] as const;
|
|
69
|
+
const IMAGE_PROVIDER_PREFERENCES = new Set<string>(IMAGE_PROVIDER_CHOICES);
|
|
70
|
+
const AUTO_IMAGE_PROVIDER_ORDER = ["openai", "openai-codex", "antigravity", "xai", "openrouter", "gemini"] as const;
|
|
61
71
|
|
|
62
72
|
const responseModalitySchema = type('"IMAGE" | "TEXT"');
|
|
63
73
|
|
|
@@ -70,6 +80,10 @@ const inputImageSchema = type({
|
|
|
70
80
|
"mime_type?": type("string").describe("mime type"),
|
|
71
81
|
});
|
|
72
82
|
|
|
83
|
+
const imageProviderSchema = type
|
|
84
|
+
.enumerated(...IMAGE_PROVIDER_CHOICES)
|
|
85
|
+
.describe("image provider for this request; overrides the providers.image setting (default: use the setting)");
|
|
86
|
+
|
|
73
87
|
export const imageGenSchema = type({
|
|
74
88
|
subject: type("string").describe("main subject"),
|
|
75
89
|
"action?": type("string").describe("what subject is doing"),
|
|
@@ -82,6 +96,7 @@ export const imageGenSchema = type({
|
|
|
82
96
|
"aspect_ratio?": aspectRatioSchema,
|
|
83
97
|
"image_size?": imageSizeSchema,
|
|
84
98
|
"input?": inputImageSchema.array().describe("input images"),
|
|
99
|
+
"provider?": imageProviderSchema,
|
|
85
100
|
});
|
|
86
101
|
export type ImageGenParams = typeof imageGenSchema.infer;
|
|
87
102
|
export type GeminiResponseModality = typeof responseModalitySchema.infer;
|
|
@@ -547,53 +562,106 @@ async function findOpenAIHostedImageCredentials(
|
|
|
547
562
|
};
|
|
548
563
|
}
|
|
549
564
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
565
|
+
// Codex (ChatGPT subscription) chat models that carry OpenAI's hosted
|
|
566
|
+
// `image_generation` tool. Priority: newest general model first, then Codex
|
|
567
|
+
// variants; any available openai-codex hosted-image model is the last resort.
|
|
568
|
+
const CODEX_IMAGE_MODEL_PRIORITY = ["gpt-5.5", "gpt-5.4", "gpt-5.1", "gpt-5", "gpt-5-codex"] as const;
|
|
569
|
+
|
|
570
|
+
function resolveDefaultCodexImageModel(modelRegistry: ModelRegistry): Model | undefined {
|
|
571
|
+
for (const id of CODEX_IMAGE_MODEL_PRIORITY) {
|
|
572
|
+
const model = modelRegistry.find("openai-codex", id);
|
|
573
|
+
if (model && isOpenAIHostedImageModel(model)) return model;
|
|
574
|
+
}
|
|
575
|
+
return modelRegistry.getAll().find(model => model.provider === "openai-codex" && isOpenAIHostedImageModel(model));
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Codex subscription (ChatGPT OAuth) image credentials — engages OpenAI's hosted
|
|
580
|
+
* `image_generation` tool through a CONNECTED Codex account, independent of the
|
|
581
|
+
* active chat model. This is what lets image generation run on a ChatGPT
|
|
582
|
+
* subscription (no metered OPENAI_API_KEY) even when the active model is, e.g.,
|
|
583
|
+
* Claude. The active-model-is-codex case is already served by
|
|
584
|
+
* {@link findOpenAIHostedImageCredentials}, so it is skipped here to avoid a
|
|
585
|
+
* duplicate resolution.
|
|
586
|
+
*/
|
|
587
|
+
async function findCodexSubscriptionImageCredentials(
|
|
588
|
+
modelRegistry: ModelRegistry | undefined,
|
|
589
|
+
activeModel: Model | undefined,
|
|
553
590
|
sessionId?: string,
|
|
554
591
|
): Promise<ImageApiKey | null> {
|
|
555
|
-
|
|
556
|
-
if (
|
|
557
|
-
|
|
558
|
-
if (openAI) return openAI;
|
|
559
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
560
|
-
} else if (preferredImageProvider === "antigravity" && modelRegistry) {
|
|
561
|
-
const antigravity = await findAntigravityCredentials(modelRegistry, sessionId);
|
|
562
|
-
if (antigravity) return antigravity;
|
|
563
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
564
|
-
} else if (preferredImageProvider === "gemini") {
|
|
565
|
-
const gemini = await findGeminiImageCredentials(modelRegistry, sessionId);
|
|
566
|
-
if (gemini) return gemini;
|
|
567
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
568
|
-
} else if (preferredImageProvider === "openrouter") {
|
|
569
|
-
const openRouter = await findOpenRouterImageCredentials(modelRegistry, sessionId);
|
|
570
|
-
if (openRouter) return openRouter;
|
|
571
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
572
|
-
} else if (preferredImageProvider === "xai") {
|
|
573
|
-
const xai = await findXAIImageCredentials(modelRegistry);
|
|
574
|
-
if (xai) return xai;
|
|
575
|
-
// Fall through to auto-detect if preferred provider key not found.
|
|
592
|
+
if (!modelRegistry) return null;
|
|
593
|
+
if (isOpenAIHostedImageModel(activeModel) && getOpenAIHostedImageProvider(activeModel) === "openai-codex") {
|
|
594
|
+
return null;
|
|
576
595
|
}
|
|
577
|
-
|
|
578
|
-
//
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
596
|
+
// A Codex subscription credential is an OAuth JWT with an account claim. API
|
|
597
|
+
// keys stored under this provider cannot use the ChatGPT backend and must not
|
|
598
|
+
// prevent fallback providers from being selected.
|
|
599
|
+
const token = await modelRegistry.getApiKeyForProvider("openai-codex", sessionId);
|
|
600
|
+
if (!token || !getCodexAccountId(token)) return null;
|
|
601
|
+
const model = resolveDefaultCodexImageModel(modelRegistry);
|
|
602
|
+
if (!model) return null;
|
|
603
|
+
const apiKey = await modelRegistry.getApiKey(model, sessionId);
|
|
604
|
+
if (!isAuthenticated(apiKey) || !getCodexAccountId(apiKey)) return null;
|
|
605
|
+
return { provider: "openai-codex", apiKey, model };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function activeImageProvider(model: Model | undefined): Exclude<ImageProviderPreference, "auto"> | null {
|
|
609
|
+
switch (model?.provider) {
|
|
610
|
+
case "openai":
|
|
611
|
+
case "openai-codex":
|
|
612
|
+
return "openai";
|
|
613
|
+
case "google-antigravity":
|
|
614
|
+
return "antigravity";
|
|
615
|
+
case "xai":
|
|
616
|
+
case "xai-oauth":
|
|
617
|
+
return "xai";
|
|
618
|
+
case "openrouter":
|
|
619
|
+
return "openrouter";
|
|
620
|
+
case "google":
|
|
621
|
+
return "gemini";
|
|
622
|
+
default:
|
|
623
|
+
return null;
|
|
585
624
|
}
|
|
625
|
+
}
|
|
586
626
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
627
|
+
function imageProviderOrder(
|
|
628
|
+
activeModel: Model | undefined,
|
|
629
|
+
preference: ImageProviderPreference = preferredImageProvider,
|
|
630
|
+
): Array<Exclude<ImageProviderPreference, "auto">> {
|
|
631
|
+
const providers: Array<Exclude<ImageProviderPreference, "auto">> = [];
|
|
632
|
+
const added = new Set<Exclude<ImageProviderPreference, "auto">>();
|
|
633
|
+
const add = (provider: Exclude<ImageProviderPreference, "auto"> | null): void => {
|
|
634
|
+
if (!provider || added.has(provider)) return;
|
|
635
|
+
added.add(provider);
|
|
636
|
+
providers.push(provider);
|
|
637
|
+
};
|
|
592
638
|
|
|
593
|
-
|
|
594
|
-
|
|
639
|
+
if (preference !== "auto") add(preference);
|
|
640
|
+
add(activeImageProvider(activeModel));
|
|
641
|
+
for (const provider of AUTO_IMAGE_PROVIDER_ORDER) add(provider);
|
|
642
|
+
return providers;
|
|
643
|
+
}
|
|
595
644
|
|
|
596
|
-
|
|
645
|
+
async function findImageApiKey(
|
|
646
|
+
provider: Exclude<ImageProviderPreference, "auto">,
|
|
647
|
+
modelRegistry?: ModelRegistry,
|
|
648
|
+
activeModel?: Model,
|
|
649
|
+
sessionId?: string,
|
|
650
|
+
): Promise<ImageApiKey | null> {
|
|
651
|
+
switch (provider) {
|
|
652
|
+
case "openai":
|
|
653
|
+
return findOpenAIHostedImageCredentials(modelRegistry, activeModel, sessionId);
|
|
654
|
+
case "openai-codex":
|
|
655
|
+
return findCodexSubscriptionImageCredentials(modelRegistry, activeModel, sessionId);
|
|
656
|
+
case "antigravity":
|
|
657
|
+
return modelRegistry ? findAntigravityCredentials(modelRegistry, sessionId) : null;
|
|
658
|
+
case "xai":
|
|
659
|
+
return findXAIImageCredentials(modelRegistry);
|
|
660
|
+
case "openrouter":
|
|
661
|
+
return findOpenRouterImageCredentials(modelRegistry, sessionId);
|
|
662
|
+
case "gemini":
|
|
663
|
+
return findGeminiImageCredentials(modelRegistry, sessionId);
|
|
664
|
+
}
|
|
597
665
|
}
|
|
598
666
|
|
|
599
667
|
async function loadImageFromPath(imagePath: string, cwd: string): Promise<InlineImageData> {
|
|
@@ -900,9 +968,10 @@ async function generateOpenAIHostedImage(
|
|
|
900
968
|
|
|
901
969
|
if (!response.ok) {
|
|
902
970
|
const errorText = await response.text();
|
|
903
|
-
throw
|
|
904
|
-
|
|
905
|
-
|
|
971
|
+
throw new ProviderHttpError(
|
|
972
|
+
`OpenAI image request failed (${response.status}): ${getOpenAIResponseErrorMessage(errorText)}`,
|
|
973
|
+
response.status,
|
|
974
|
+
{ headers: response.headers },
|
|
906
975
|
);
|
|
907
976
|
}
|
|
908
977
|
|
|
@@ -1040,533 +1109,575 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1040
1109
|
async execute(_toolCallId, params, _onUpdate, ctx, signal) {
|
|
1041
1110
|
return untilAborted(signal, async () => {
|
|
1042
1111
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
1043
|
-
const
|
|
1044
|
-
if (!apiKey) {
|
|
1045
|
-
throw new Error(
|
|
1046
|
-
"No image API credentials found. Use a GPT Responses/Codex model with OpenAI credentials, login with google-antigravity or xAI Grok OAuth, or set XAI_API_KEY, OPENROUTER_API_KEY, GEMINI_API_KEY, or GOOGLE_API_KEY.",
|
|
1047
|
-
);
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
const provider = apiKey.provider;
|
|
1051
|
-
const model =
|
|
1052
|
-
provider === "openai" || provider === "openai-codex"
|
|
1053
|
-
? (apiKey.model?.id ?? "gpt")
|
|
1054
|
-
: provider === "antigravity"
|
|
1055
|
-
? DEFAULT_ANTIGRAVITY_MODEL
|
|
1056
|
-
: provider === "openrouter"
|
|
1057
|
-
? DEFAULT_OPENROUTER_MODEL
|
|
1058
|
-
: provider === "xai"
|
|
1059
|
-
? DEFAULT_XAI_IMAGE_MODEL
|
|
1060
|
-
: DEFAULT_MODEL;
|
|
1061
|
-
const resolvedModel = provider === "openrouter" ? resolveOpenRouterModel(model) : model;
|
|
1062
|
-
assertImageAspectRatioSupported(provider, params.aspect_ratio);
|
|
1112
|
+
const providerOrder = imageProviderOrder(ctx.model, params.provider ?? preferredImageProvider);
|
|
1063
1113
|
const cwd = ctx.sessionManager.getCwd();
|
|
1064
|
-
|
|
1065
|
-
const resolvedImages: InlineImageData[] = [];
|
|
1066
|
-
if (params.input?.length) {
|
|
1067
|
-
for (const input of params.input) {
|
|
1068
|
-
resolvedImages.push(await resolveInputImage(input, cwd));
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
1114
|
const requestSignal = ptree.combineSignals(signal, IMAGE_TIMEOUT);
|
|
1073
1115
|
const fetchImpl = ctx.fetch ?? fetch;
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1116
|
+
const failures: Array<{ provider: ImageProvider; error: ProviderHttpError }> = [];
|
|
1117
|
+
let unsupportedAspectRatioProvider: ImageProvider | undefined;
|
|
1118
|
+
let foundCredentials = false;
|
|
1119
|
+
let resolvedImageCache: InlineImageData[] | undefined;
|
|
1120
|
+
|
|
1121
|
+
for (const preferredProvider of providerOrder) {
|
|
1122
|
+
const apiKey = await findImageApiKey(preferredProvider, ctx.modelRegistry, ctx.model, sessionId);
|
|
1123
|
+
if (!apiKey) continue;
|
|
1124
|
+
foundCredentials = true;
|
|
1125
|
+
if (!resolvedImageCache) {
|
|
1126
|
+
resolvedImageCache = [];
|
|
1127
|
+
if (params.input?.length) {
|
|
1128
|
+
for (const input of params.input) {
|
|
1129
|
+
resolvedImageCache.push(await resolveInputImage(input, cwd));
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1078
1132
|
}
|
|
1133
|
+
const resolvedImages = resolvedImageCache;
|
|
1134
|
+
|
|
1135
|
+
const provider = apiKey.provider;
|
|
1136
|
+
try {
|
|
1137
|
+
const model =
|
|
1138
|
+
provider === "openai" || provider === "openai-codex"
|
|
1139
|
+
? (apiKey.model?.id ?? "gpt")
|
|
1140
|
+
: provider === "antigravity"
|
|
1141
|
+
? DEFAULT_ANTIGRAVITY_MODEL
|
|
1142
|
+
: provider === "openrouter"
|
|
1143
|
+
? DEFAULT_OPENROUTER_MODEL
|
|
1144
|
+
: provider === "xai"
|
|
1145
|
+
? DEFAULT_XAI_IMAGE_MODEL
|
|
1146
|
+
: DEFAULT_MODEL;
|
|
1147
|
+
const resolvedModel = provider === "openrouter" ? resolveOpenRouterModel(model) : model;
|
|
1148
|
+
if (
|
|
1149
|
+
params.aspect_ratio &&
|
|
1150
|
+
provider !== "xai" &&
|
|
1151
|
+
!COMMON_IMAGE_ASPECT_RATIO_SET.has(params.aspect_ratio)
|
|
1152
|
+
) {
|
|
1153
|
+
unsupportedAspectRatioProvider ??= provider;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (provider === "openai" || provider === "openai-codex") {
|
|
1157
|
+
if (!apiKey.model) {
|
|
1158
|
+
throw new Error("Missing active GPT model for OpenAI image generation");
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const hostedModel = apiKey.model;
|
|
1162
|
+
const hostedKey: ApiKey = ctx.modelRegistry.resolver(hostedModel, sessionId);
|
|
1163
|
+
|
|
1164
|
+
const parsed = await withAuth(
|
|
1165
|
+
hostedKey,
|
|
1166
|
+
key =>
|
|
1167
|
+
generateOpenAIHostedImage(
|
|
1168
|
+
key,
|
|
1169
|
+
hostedModel,
|
|
1170
|
+
params,
|
|
1171
|
+
resolvedImages,
|
|
1172
|
+
fetchImpl,
|
|
1173
|
+
requestSignal,
|
|
1174
|
+
sessionId,
|
|
1175
|
+
),
|
|
1176
|
+
{ signal: requestSignal },
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
if (parsed.images.length === 0) {
|
|
1180
|
+
const messageText = parsed.responseText ? `\n\n${parsed.responseText}` : "";
|
|
1181
|
+
return {
|
|
1182
|
+
content: [{ type: "text", text: `No image data returned.${messageText}` }],
|
|
1183
|
+
details: {
|
|
1184
|
+
provider,
|
|
1185
|
+
model,
|
|
1186
|
+
imageCount: 0,
|
|
1187
|
+
imagePaths: [],
|
|
1188
|
+
images: [],
|
|
1189
|
+
responseText: parsed.responseText,
|
|
1190
|
+
revisedPrompt: parsed.revisedPrompt,
|
|
1191
|
+
usage: parsed.usage,
|
|
1192
|
+
},
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const imagePaths = await saveImagesToTemp(parsed.images);
|
|
1197
|
+
|
|
1198
|
+
return {
|
|
1199
|
+
content: [
|
|
1200
|
+
{ type: "text", text: buildResponseSummary(provider, model, imagePaths, parsed.responseText) },
|
|
1201
|
+
],
|
|
1202
|
+
details: {
|
|
1203
|
+
provider,
|
|
1204
|
+
model,
|
|
1205
|
+
imageCount: parsed.images.length,
|
|
1206
|
+
imagePaths,
|
|
1207
|
+
images: parsed.images,
|
|
1208
|
+
responseText: parsed.responseText,
|
|
1209
|
+
revisedPrompt: parsed.revisedPrompt,
|
|
1210
|
+
usage: parsed.usage,
|
|
1211
|
+
},
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (provider === "antigravity") {
|
|
1216
|
+
if (!apiKey.projectId) {
|
|
1217
|
+
throw new Error("Missing projectId in antigravity credentials");
|
|
1218
|
+
}
|
|
1079
1219
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
const parsed = await withAuth(
|
|
1084
|
-
hostedKey,
|
|
1085
|
-
key =>
|
|
1086
|
-
generateOpenAIHostedImage(
|
|
1087
|
-
key,
|
|
1088
|
-
hostedModel,
|
|
1089
|
-
params,
|
|
1090
|
-
resolvedImages,
|
|
1091
|
-
fetchImpl,
|
|
1092
|
-
requestSignal,
|
|
1220
|
+
const prompt = assemblePrompt(params);
|
|
1221
|
+
const antigravityKey: ApiKey = ctx.modelRegistry.resolver("google-antigravity", {
|
|
1093
1222
|
sessionId,
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
);
|
|
1223
|
+
modelId: DEFAULT_ANTIGRAVITY_MODEL,
|
|
1224
|
+
});
|
|
1097
1225
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1226
|
+
const response = await withAuth(
|
|
1227
|
+
antigravityKey,
|
|
1228
|
+
async key => {
|
|
1229
|
+
// On a retry the resolver yields the raw stored credential JSON
|
|
1230
|
+
// ({ token, projectId }); the initial seed is the already-parsed
|
|
1231
|
+
// access token. Tolerate both, falling back to the seed projectId.
|
|
1232
|
+
const rotated = parseAntigravityCredentials(key);
|
|
1233
|
+
const bearer = rotated?.accessToken ?? key;
|
|
1234
|
+
const projectId = rotated?.projectId ?? apiKey.projectId!;
|
|
1235
|
+
const requestBody = buildAntigravityRequest(
|
|
1236
|
+
prompt,
|
|
1237
|
+
model,
|
|
1238
|
+
projectId,
|
|
1239
|
+
params.aspect_ratio,
|
|
1240
|
+
params.image_size,
|
|
1241
|
+
resolvedImages,
|
|
1242
|
+
);
|
|
1114
1243
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
images: parsed.images,
|
|
1127
|
-
responseText: parsed.responseText,
|
|
1128
|
-
revisedPrompt: parsed.revisedPrompt,
|
|
1129
|
-
usage: parsed.usage,
|
|
1130
|
-
},
|
|
1131
|
-
};
|
|
1132
|
-
}
|
|
1244
|
+
let endpoints = [DEFAULT_ANTIGRAVITY_ENDPOINT_PROD, DEFAULT_ANTIGRAVITY_ENDPOINT_SANDBOX];
|
|
1245
|
+
try {
|
|
1246
|
+
const mode = settings.get("providers.antigravityEndpoint");
|
|
1247
|
+
if (mode === "production") {
|
|
1248
|
+
endpoints = [DEFAULT_ANTIGRAVITY_ENDPOINT_PROD];
|
|
1249
|
+
} else if (mode === "sandbox") {
|
|
1250
|
+
endpoints = [DEFAULT_ANTIGRAVITY_ENDPOINT_SANDBOX];
|
|
1251
|
+
}
|
|
1252
|
+
} catch {
|
|
1253
|
+
// Ignored
|
|
1254
|
+
}
|
|
1133
1255
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1256
|
+
let resp: Response | undefined;
|
|
1257
|
+
let lastError: Error | undefined;
|
|
1258
|
+
|
|
1259
|
+
for (let i = 0; i < endpoints.length; i++) {
|
|
1260
|
+
const endpoint = endpoints[i];
|
|
1261
|
+
const isLastEndpoint = i === endpoints.length - 1;
|
|
1262
|
+
try {
|
|
1263
|
+
resp = await fetchImpl(`${endpoint}/v1internal:streamGenerateContent?alt=sse`, {
|
|
1264
|
+
method: "POST",
|
|
1265
|
+
headers: {
|
|
1266
|
+
Authorization: `Bearer ${bearer}`,
|
|
1267
|
+
"Content-Type": "application/json",
|
|
1268
|
+
Accept: "text/event-stream",
|
|
1269
|
+
"User-Agent": getAntigravityUserAgent(),
|
|
1270
|
+
},
|
|
1271
|
+
body: JSON.stringify(requestBody),
|
|
1272
|
+
signal: requestSignal,
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
if (resp.ok) {
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const errorText = await resp.text();
|
|
1280
|
+
let message = errorText;
|
|
1281
|
+
try {
|
|
1282
|
+
const parsedErr = JSON.parse(errorText) as { error?: { message?: string } };
|
|
1283
|
+
message = parsedErr.error?.message ?? message;
|
|
1284
|
+
} catch {
|
|
1285
|
+
// Keep raw text.
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
lastError = new ProviderHttpError(
|
|
1289
|
+
`Antigravity image request failed (${resp.status}): ${message}`,
|
|
1290
|
+
resp.status,
|
|
1291
|
+
{ headers: resp.headers },
|
|
1292
|
+
);
|
|
1293
|
+
|
|
1294
|
+
if (resp.status === 429 || (resp.status >= 500 && resp.status < 600)) {
|
|
1295
|
+
if (!isLastEndpoint) {
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
break;
|
|
1300
|
+
} catch (error) {
|
|
1301
|
+
lastError = error as Error;
|
|
1302
|
+
if (isLastEndpoint) {
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1138
1307
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
antigravityKey,
|
|
1147
|
-
async key => {
|
|
1148
|
-
// On a retry the resolver yields the raw stored credential JSON
|
|
1149
|
-
// ({ token, projectId }); the initial seed is the already-parsed
|
|
1150
|
-
// access token. Tolerate both, falling back to the seed projectId.
|
|
1151
|
-
const rotated = parseAntigravityCredentials(key);
|
|
1152
|
-
const bearer = rotated?.accessToken ?? key;
|
|
1153
|
-
const projectId = rotated?.projectId ?? apiKey.projectId!;
|
|
1154
|
-
const requestBody = buildAntigravityRequest(
|
|
1155
|
-
prompt,
|
|
1156
|
-
model,
|
|
1157
|
-
projectId,
|
|
1158
|
-
params.aspect_ratio,
|
|
1159
|
-
params.image_size,
|
|
1160
|
-
resolvedImages,
|
|
1308
|
+
if (!resp?.ok) {
|
|
1309
|
+
throw lastError ?? new Error("Antigravity image generation failed");
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
return resp;
|
|
1313
|
+
},
|
|
1314
|
+
{ signal: requestSignal },
|
|
1161
1315
|
);
|
|
1162
1316
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1317
|
+
const parsed = await parseAntigravitySseForImage(response, requestSignal);
|
|
1318
|
+
const responseText = parsed.text.length > 0 ? parsed.text.join(" ") : undefined;
|
|
1319
|
+
|
|
1320
|
+
if (parsed.images.length === 0) {
|
|
1321
|
+
const messageText = responseText ? `\n\n${responseText}` : "";
|
|
1322
|
+
return {
|
|
1323
|
+
content: [{ type: "text", text: `No image data returned.${messageText}` }],
|
|
1324
|
+
details: {
|
|
1325
|
+
provider,
|
|
1326
|
+
model,
|
|
1327
|
+
imageCount: 0,
|
|
1328
|
+
imagePaths: [],
|
|
1329
|
+
images: [],
|
|
1330
|
+
responseText,
|
|
1331
|
+
usage: parsed.usage,
|
|
1332
|
+
},
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
const imagePaths = await saveImagesToTemp(parsed.images);
|
|
1337
|
+
|
|
1338
|
+
return {
|
|
1339
|
+
content: [{ type: "text", text: buildResponseSummary(provider, model, imagePaths, responseText) }],
|
|
1340
|
+
details: {
|
|
1341
|
+
provider,
|
|
1342
|
+
model,
|
|
1343
|
+
imageCount: parsed.images.length,
|
|
1344
|
+
imagePaths,
|
|
1345
|
+
images: parsed.images,
|
|
1346
|
+
responseText,
|
|
1347
|
+
usage: parsed.usage,
|
|
1348
|
+
},
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
if (provider === "xai") {
|
|
1353
|
+
if (!ctx.modelRegistry) {
|
|
1354
|
+
throw new Error("Missing modelRegistry for xAI image generation");
|
|
1355
|
+
}
|
|
1356
|
+
const xaiCreds = await resolveXAIHttpCredentials(ctx.modelRegistry, resolvedModel);
|
|
1357
|
+
if (!xaiCreds) {
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
"No xAI credentials. Run /login → xAI Grok OAuth (SuperGrok or X Premium+) or set XAI_API_KEY.",
|
|
1360
|
+
);
|
|
1173
1361
|
}
|
|
1174
1362
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1363
|
+
const prompt = assemblePrompt(params);
|
|
1364
|
+
const aspectRatio = params.aspect_ratio ?? "1:1";
|
|
1365
|
+
const xaiResolution = resolveXAIResolution(params.image_size);
|
|
1177
1366
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1367
|
+
const isEdit = resolvedImages.length > 0;
|
|
1368
|
+
if (isEdit && resolvedImages.length > XAI_MAX_EDIT_IMAGES) {
|
|
1369
|
+
throw new Error(
|
|
1370
|
+
`xAI image edits accept up to ${XAI_MAX_EDIT_IMAGES} reference images; got ${resolvedImages.length}.`,
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
const xaiBaseBody: XAIImageRequestBase = {
|
|
1375
|
+
model: resolvedModel,
|
|
1376
|
+
prompt,
|
|
1377
|
+
aspect_ratio: aspectRatio,
|
|
1378
|
+
resolution: xaiResolution,
|
|
1379
|
+
n: 1,
|
|
1380
|
+
response_format: "b64_json",
|
|
1381
|
+
};
|
|
1382
|
+
const xaiBody: XAIImageRequestBody = isEdit
|
|
1383
|
+
? buildXAIEditPayload(xaiBaseBody, resolvedImages)
|
|
1384
|
+
: xaiBaseBody;
|
|
1385
|
+
const xaiEndpoint = isEdit ? "/images/edits" : "/images/generations";
|
|
1386
|
+
|
|
1387
|
+
const xaiKey: ApiKey = ctx.modelRegistry.resolver(xaiCreds.provider, {
|
|
1388
|
+
sessionId,
|
|
1389
|
+
baseUrl: xaiCreds.baseURL,
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
const xaiRawText = await withAuth(
|
|
1393
|
+
xaiKey,
|
|
1394
|
+
async key => {
|
|
1395
|
+
const resp = await fetchImpl(`${xaiCreds.baseURL}${xaiEndpoint}`, {
|
|
1183
1396
|
method: "POST",
|
|
1184
1397
|
headers: {
|
|
1185
|
-
Authorization: `Bearer ${
|
|
1398
|
+
Authorization: `Bearer ${key}`,
|
|
1186
1399
|
"Content-Type": "application/json",
|
|
1187
|
-
|
|
1188
|
-
"User-Agent": getAntigravityUserAgent(),
|
|
1400
|
+
"User-Agent": ohMyPiXAIUserAgent(),
|
|
1189
1401
|
},
|
|
1190
|
-
body: JSON.stringify(
|
|
1402
|
+
body: JSON.stringify(xaiBody),
|
|
1191
1403
|
signal: requestSignal,
|
|
1192
1404
|
});
|
|
1193
|
-
|
|
1194
|
-
if (resp.ok) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
const parsedErr = JSON.parse(errorText) as { error?: { message?: string } };
|
|
1202
|
-
message = parsedErr.error?.message ?? message;
|
|
1203
|
-
} catch {
|
|
1204
|
-
// Keep raw text.
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
lastError = new ProviderHttpError(
|
|
1208
|
-
`Antigravity image request failed (${resp.status}): ${message}`,
|
|
1209
|
-
resp.status,
|
|
1210
|
-
{ headers: resp.headers },
|
|
1211
|
-
);
|
|
1212
|
-
|
|
1213
|
-
if (resp.status === 429 || (resp.status >= 500 && resp.status < 600)) {
|
|
1214
|
-
if (!isLastEndpoint) {
|
|
1215
|
-
continue;
|
|
1405
|
+
const rawText = await resp.text();
|
|
1406
|
+
if (!resp.ok) {
|
|
1407
|
+
let message = rawText;
|
|
1408
|
+
try {
|
|
1409
|
+
const parsedErr = JSON.parse(rawText) as { error?: { message?: string } };
|
|
1410
|
+
message = parsedErr.error?.message ?? message;
|
|
1411
|
+
} catch {
|
|
1412
|
+
// Keep raw text.
|
|
1216
1413
|
}
|
|
1414
|
+
throw new ProviderHttpError(
|
|
1415
|
+
`xAI image request failed (${resp.status}): ${message}`,
|
|
1416
|
+
resp.status,
|
|
1417
|
+
{
|
|
1418
|
+
headers: resp.headers,
|
|
1419
|
+
},
|
|
1420
|
+
);
|
|
1217
1421
|
}
|
|
1218
|
-
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1422
|
+
return rawText;
|
|
1423
|
+
},
|
|
1424
|
+
{ signal: requestSignal },
|
|
1425
|
+
);
|
|
1426
|
+
|
|
1427
|
+
const xaiData = JSON.parse(xaiRawText) as {
|
|
1428
|
+
data?: Array<{ b64_json?: string; url?: string }>;
|
|
1429
|
+
};
|
|
1430
|
+
const xaiInlineImages: InlineImageData[] = [];
|
|
1431
|
+
for (const entry of xaiData.data ?? []) {
|
|
1432
|
+
if (entry.b64_json) {
|
|
1433
|
+
const bytes = Buffer.from(entry.b64_json, "base64");
|
|
1434
|
+
const mimeType = parseImageMetadata(bytes)?.mimeType ?? "image/png";
|
|
1435
|
+
xaiInlineImages.push({ data: entry.b64_json, mimeType });
|
|
1436
|
+
} else if (entry.url) {
|
|
1437
|
+
xaiInlineImages.push(await loadImageFromUrl(entry.url, fetchImpl, requestSignal));
|
|
1224
1438
|
}
|
|
1225
1439
|
}
|
|
1226
1440
|
|
|
1227
|
-
if (
|
|
1228
|
-
|
|
1441
|
+
if (xaiInlineImages.length === 0) {
|
|
1442
|
+
return {
|
|
1443
|
+
content: [{ type: "text", text: "No image data returned." }],
|
|
1444
|
+
details: {
|
|
1445
|
+
provider,
|
|
1446
|
+
model: resolvedModel,
|
|
1447
|
+
imageCount: 0,
|
|
1448
|
+
imagePaths: [],
|
|
1449
|
+
images: [],
|
|
1450
|
+
},
|
|
1451
|
+
};
|
|
1229
1452
|
}
|
|
1230
1453
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1454
|
+
const xaiImagePaths = await saveImagesToTemp(xaiInlineImages);
|
|
1455
|
+
|
|
1456
|
+
return {
|
|
1457
|
+
content: [
|
|
1458
|
+
{ type: "text", text: buildResponseSummary(provider, resolvedModel, xaiImagePaths, undefined) },
|
|
1459
|
+
],
|
|
1460
|
+
details: {
|
|
1461
|
+
provider,
|
|
1462
|
+
model: resolvedModel,
|
|
1463
|
+
imageCount: xaiInlineImages.length,
|
|
1464
|
+
imagePaths: xaiImagePaths,
|
|
1465
|
+
images: xaiInlineImages,
|
|
1466
|
+
},
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1238
1469
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
model,
|
|
1246
|
-
imageCount: 0,
|
|
1247
|
-
imagePaths: [],
|
|
1248
|
-
images: [],
|
|
1249
|
-
responseText,
|
|
1250
|
-
usage: parsed.usage,
|
|
1251
|
-
},
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1470
|
+
if (provider === "openrouter") {
|
|
1471
|
+
const prompt = assemblePrompt(params);
|
|
1472
|
+
const contentParts: OpenRouterContentPart[] = [{ type: "text", text: prompt }];
|
|
1473
|
+
for (const image of resolvedImages) {
|
|
1474
|
+
contentParts.push({ type: "image_url", image_url: { url: toDataUrl(image) } });
|
|
1475
|
+
}
|
|
1254
1476
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
details: {
|
|
1260
|
-
provider,
|
|
1261
|
-
model,
|
|
1262
|
-
imageCount: parsed.images.length,
|
|
1263
|
-
imagePaths,
|
|
1264
|
-
images: parsed.images,
|
|
1265
|
-
responseText,
|
|
1266
|
-
usage: parsed.usage,
|
|
1267
|
-
},
|
|
1268
|
-
};
|
|
1269
|
-
}
|
|
1477
|
+
const requestBody = {
|
|
1478
|
+
model: resolvedModel,
|
|
1479
|
+
messages: [{ role: "user" as const, content: contentParts }],
|
|
1480
|
+
};
|
|
1270
1481
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1482
|
+
const rawText = await withAuth(
|
|
1483
|
+
apiKey.apiKey,
|
|
1484
|
+
async key => {
|
|
1485
|
+
const resp = await fetchImpl("https://openrouter.ai/api/v1/chat/completions", {
|
|
1486
|
+
method: "POST",
|
|
1487
|
+
headers: {
|
|
1488
|
+
"Content-Type": "application/json",
|
|
1489
|
+
Authorization: `Bearer ${key}`,
|
|
1490
|
+
"HTTP-Referer": "https://omp.sh/",
|
|
1491
|
+
"X-OpenRouter-Title": "Oh-My-Pi",
|
|
1492
|
+
"X-OpenRouter-Categories": "cli-agent",
|
|
1493
|
+
},
|
|
1494
|
+
body: JSON.stringify(requestBody),
|
|
1495
|
+
signal: requestSignal,
|
|
1496
|
+
});
|
|
1497
|
+
const text = await resp.text();
|
|
1498
|
+
if (!resp.ok) {
|
|
1499
|
+
let message = text;
|
|
1500
|
+
try {
|
|
1501
|
+
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1502
|
+
message = parsed.error?.message ?? message;
|
|
1503
|
+
} catch {
|
|
1504
|
+
// Keep raw text.
|
|
1505
|
+
}
|
|
1506
|
+
throw new ProviderHttpError(
|
|
1507
|
+
`OpenRouter image request failed (${resp.status}): ${message}`,
|
|
1508
|
+
resp.status,
|
|
1509
|
+
{ headers: resp.headers },
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
return text;
|
|
1513
|
+
},
|
|
1514
|
+
{ signal: requestSignal },
|
|
1515
|
+
);
|
|
1281
1516
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1517
|
+
const data = JSON.parse(rawText) as OpenRouterResponse;
|
|
1518
|
+
const message = data.choices?.[0]?.message;
|
|
1519
|
+
const responseText = collectOpenRouterResponseText(message);
|
|
1520
|
+
const imageUrls = extractOpenRouterImageUrls(message);
|
|
1521
|
+
const inlineImages: InlineImageData[] = [];
|
|
1522
|
+
for (const imageUrl of imageUrls) {
|
|
1523
|
+
inlineImages.push(await loadImageFromUrl(imageUrl, fetchImpl, requestSignal));
|
|
1524
|
+
}
|
|
1285
1525
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1526
|
+
if (inlineImages.length === 0) {
|
|
1527
|
+
const messageText = responseText ? `\n\n${responseText}` : "";
|
|
1528
|
+
return {
|
|
1529
|
+
content: [{ type: "text", text: `No image data returned.${messageText}` }],
|
|
1530
|
+
details: {
|
|
1531
|
+
provider,
|
|
1532
|
+
model: resolvedModel,
|
|
1533
|
+
imageCount: 0,
|
|
1534
|
+
imagePaths: [],
|
|
1535
|
+
images: [],
|
|
1536
|
+
responseText,
|
|
1537
|
+
},
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1292
1540
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
const xaiKey: ApiKey = ctx.modelRegistry.resolver(xaiCreds.provider, {
|
|
1307
|
-
sessionId,
|
|
1308
|
-
baseUrl: xaiCreds.baseURL,
|
|
1309
|
-
});
|
|
1310
|
-
|
|
1311
|
-
const xaiRawText = await withAuth(
|
|
1312
|
-
xaiKey,
|
|
1313
|
-
async key => {
|
|
1314
|
-
const resp = await fetchImpl(`${xaiCreds.baseURL}${xaiEndpoint}`, {
|
|
1315
|
-
method: "POST",
|
|
1316
|
-
headers: {
|
|
1317
|
-
Authorization: `Bearer ${key}`,
|
|
1318
|
-
"Content-Type": "application/json",
|
|
1319
|
-
"User-Agent": ohMyPiXAIUserAgent(),
|
|
1541
|
+
const imagePaths = await saveImagesToTemp(inlineImages);
|
|
1542
|
+
|
|
1543
|
+
return {
|
|
1544
|
+
content: [
|
|
1545
|
+
{ type: "text", text: buildResponseSummary(provider, resolvedModel, imagePaths, responseText) },
|
|
1546
|
+
],
|
|
1547
|
+
details: {
|
|
1548
|
+
provider,
|
|
1549
|
+
model: resolvedModel,
|
|
1550
|
+
imageCount: inlineImages.length,
|
|
1551
|
+
imagePaths,
|
|
1552
|
+
images: inlineImages,
|
|
1553
|
+
responseText,
|
|
1320
1554
|
},
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
});
|
|
1324
|
-
const rawText = await resp.text();
|
|
1325
|
-
if (!resp.ok) {
|
|
1326
|
-
let message = rawText;
|
|
1327
|
-
try {
|
|
1328
|
-
const parsedErr = JSON.parse(rawText) as { error?: { message?: string } };
|
|
1329
|
-
message = parsedErr.error?.message ?? message;
|
|
1330
|
-
} catch {
|
|
1331
|
-
// Keep raw text.
|
|
1332
|
-
}
|
|
1333
|
-
throw new ProviderHttpError(`xAI image request failed (${resp.status}): ${message}`, resp.status, {
|
|
1334
|
-
headers: resp.headers,
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
return rawText;
|
|
1338
|
-
},
|
|
1339
|
-
{ signal: requestSignal },
|
|
1340
|
-
);
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1341
1557
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
const xaiInlineImages: InlineImageData[] = [];
|
|
1346
|
-
for (const entry of xaiData.data ?? []) {
|
|
1347
|
-
if (entry.b64_json) {
|
|
1348
|
-
const bytes = Buffer.from(entry.b64_json, "base64");
|
|
1349
|
-
const mimeType = parseImageMetadata(bytes)?.mimeType ?? "image/png";
|
|
1350
|
-
xaiInlineImages.push({ data: entry.b64_json, mimeType });
|
|
1351
|
-
} else if (entry.url) {
|
|
1352
|
-
xaiInlineImages.push(await loadImageFromUrl(entry.url, fetchImpl, requestSignal));
|
|
1558
|
+
const parts = [] as Array<{ text?: string; inlineData?: InlineImageData }>;
|
|
1559
|
+
for (const image of resolvedImages) {
|
|
1560
|
+
parts.push({ inlineData: image });
|
|
1353
1561
|
}
|
|
1354
|
-
|
|
1562
|
+
parts.push({ text: assemblePrompt(params) });
|
|
1355
1563
|
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
model: resolvedModel,
|
|
1362
|
-
imageCount: 0,
|
|
1363
|
-
imagePaths: [],
|
|
1364
|
-
images: [],
|
|
1365
|
-
},
|
|
1564
|
+
const generationConfig: {
|
|
1565
|
+
responseModalities: GeminiResponseModality[];
|
|
1566
|
+
imageConfig?: { aspectRatio?: string; imageSize?: string };
|
|
1567
|
+
} = {
|
|
1568
|
+
responseModalities: ["IMAGE"],
|
|
1366
1569
|
};
|
|
1367
|
-
}
|
|
1368
1570
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
details: {
|
|
1376
|
-
provider,
|
|
1377
|
-
model: resolvedModel,
|
|
1378
|
-
imageCount: xaiInlineImages.length,
|
|
1379
|
-
imagePaths: xaiImagePaths,
|
|
1380
|
-
images: xaiInlineImages,
|
|
1381
|
-
},
|
|
1382
|
-
};
|
|
1383
|
-
}
|
|
1571
|
+
if (params.aspect_ratio || params.image_size) {
|
|
1572
|
+
generationConfig.imageConfig = {
|
|
1573
|
+
aspectRatio: params.aspect_ratio,
|
|
1574
|
+
imageSize: params.image_size,
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1384
1577
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
contentParts.push({ type: "image_url", image_url: { url: toDataUrl(image) } });
|
|
1390
|
-
}
|
|
1578
|
+
const requestBody = {
|
|
1579
|
+
contents: [{ role: "user" as const, parts }],
|
|
1580
|
+
generationConfig,
|
|
1581
|
+
};
|
|
1391
1582
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
"X-OpenRouter-Title": "Oh-My-Pi",
|
|
1407
|
-
"X-OpenRouter-Categories": "cli-agent",
|
|
1408
|
-
},
|
|
1409
|
-
body: JSON.stringify(requestBody),
|
|
1410
|
-
signal: requestSignal,
|
|
1411
|
-
});
|
|
1412
|
-
const text = await resp.text();
|
|
1413
|
-
if (!resp.ok) {
|
|
1414
|
-
let message = text;
|
|
1415
|
-
try {
|
|
1416
|
-
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1417
|
-
message = parsed.error?.message ?? message;
|
|
1418
|
-
} catch {
|
|
1419
|
-
// Keep raw text.
|
|
1420
|
-
}
|
|
1421
|
-
throw new ProviderHttpError(
|
|
1422
|
-
`OpenRouter image request failed (${resp.status}): ${message}`,
|
|
1423
|
-
resp.status,
|
|
1424
|
-
{ headers: resp.headers },
|
|
1583
|
+
const rawText = await withAuth(
|
|
1584
|
+
apiKey.apiKey,
|
|
1585
|
+
async key => {
|
|
1586
|
+
const resp = await fetchImpl(
|
|
1587
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`,
|
|
1588
|
+
{
|
|
1589
|
+
method: "POST",
|
|
1590
|
+
headers: {
|
|
1591
|
+
"Content-Type": "application/json",
|
|
1592
|
+
"x-goog-api-key": key,
|
|
1593
|
+
},
|
|
1594
|
+
body: JSON.stringify(requestBody),
|
|
1595
|
+
signal: requestSignal,
|
|
1596
|
+
},
|
|
1425
1597
|
);
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1598
|
+
const text = await resp.text();
|
|
1599
|
+
if (!resp.ok) {
|
|
1600
|
+
let message = text;
|
|
1601
|
+
try {
|
|
1602
|
+
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1603
|
+
message = parsed.error?.message ?? message;
|
|
1604
|
+
} catch {
|
|
1605
|
+
// Keep raw text.
|
|
1606
|
+
}
|
|
1607
|
+
throw new ProviderHttpError(
|
|
1608
|
+
`Gemini image request failed (${resp.status}): ${message}`,
|
|
1609
|
+
resp.status,
|
|
1610
|
+
{
|
|
1611
|
+
headers: resp.headers,
|
|
1612
|
+
},
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
return text;
|
|
1616
|
+
},
|
|
1617
|
+
{ signal: requestSignal },
|
|
1618
|
+
);
|
|
1431
1619
|
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1620
|
+
const data = JSON.parse(rawText) as GeminiGenerateContentResponse;
|
|
1621
|
+
const responseParts = combineParts(data);
|
|
1622
|
+
const responseText = collectResponseText(responseParts);
|
|
1623
|
+
const inlineImages = collectInlineImages(responseParts);
|
|
1624
|
+
|
|
1625
|
+
if (inlineImages.length === 0) {
|
|
1626
|
+
const blocked = data.promptFeedback?.blockReason
|
|
1627
|
+
? `Blocked: ${data.promptFeedback.blockReason}`
|
|
1628
|
+
: "No image data returned.";
|
|
1629
|
+
return {
|
|
1630
|
+
content: [{ type: "text", text: `${blocked}${responseText ? `\n\n${responseText}` : ""}` }],
|
|
1631
|
+
details: {
|
|
1632
|
+
provider,
|
|
1633
|
+
model,
|
|
1634
|
+
imageCount: 0,
|
|
1635
|
+
imagePaths: [],
|
|
1636
|
+
images: [],
|
|
1637
|
+
responseText,
|
|
1638
|
+
promptFeedback: data.promptFeedback,
|
|
1639
|
+
usage: data.usageMetadata,
|
|
1640
|
+
},
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
const imagePaths = await saveImagesToTemp(inlineImages);
|
|
1440
1645
|
|
|
1441
|
-
if (inlineImages.length === 0) {
|
|
1442
|
-
const messageText = responseText ? `\n\n${responseText}` : "";
|
|
1443
1646
|
return {
|
|
1444
|
-
content: [{ type: "text", text:
|
|
1647
|
+
content: [{ type: "text", text: buildResponseSummary(provider, model, imagePaths, responseText) }],
|
|
1445
1648
|
details: {
|
|
1446
1649
|
provider,
|
|
1447
|
-
model
|
|
1448
|
-
imageCount:
|
|
1449
|
-
imagePaths
|
|
1450
|
-
images:
|
|
1650
|
+
model,
|
|
1651
|
+
imageCount: inlineImages.length,
|
|
1652
|
+
imagePaths,
|
|
1653
|
+
images: inlineImages,
|
|
1451
1654
|
responseText,
|
|
1655
|
+
promptFeedback: data.promptFeedback,
|
|
1656
|
+
usage: data.usageMetadata,
|
|
1452
1657
|
},
|
|
1453
1658
|
};
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
if (!(error instanceof ProviderHttpError) || requestSignal?.aborted) {
|
|
1661
|
+
throw error;
|
|
1662
|
+
}
|
|
1663
|
+
failures.push({ provider, error });
|
|
1454
1664
|
}
|
|
1455
|
-
|
|
1456
|
-
const imagePaths = await saveImagesToTemp(inlineImages);
|
|
1457
|
-
|
|
1458
|
-
return {
|
|
1459
|
-
content: [
|
|
1460
|
-
{ type: "text", text: buildResponseSummary(provider, resolvedModel, imagePaths, responseText) },
|
|
1461
|
-
],
|
|
1462
|
-
details: {
|
|
1463
|
-
provider,
|
|
1464
|
-
model: resolvedModel,
|
|
1465
|
-
imageCount: inlineImages.length,
|
|
1466
|
-
imagePaths,
|
|
1467
|
-
images: inlineImages,
|
|
1468
|
-
responseText,
|
|
1469
|
-
},
|
|
1470
|
-
};
|
|
1471
1665
|
}
|
|
1472
1666
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1667
|
+
if (!foundCredentials) {
|
|
1668
|
+
throw new Error(
|
|
1669
|
+
"No image API credentials found. Connect a Codex (ChatGPT) subscription, use a GPT Responses/Codex model with OpenAI credentials, log in with google-antigravity or xAI Grok OAuth, or set OPENAI_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY, GEMINI_API_KEY, or GOOGLE_API_KEY.",
|
|
1670
|
+
);
|
|
1476
1671
|
}
|
|
1477
|
-
parts.push({ text: assemblePrompt(params) });
|
|
1478
|
-
|
|
1479
|
-
const generationConfig: {
|
|
1480
|
-
responseModalities: GeminiResponseModality[];
|
|
1481
|
-
imageConfig?: { aspectRatio?: string; imageSize?: string };
|
|
1482
|
-
} = {
|
|
1483
|
-
responseModalities: ["IMAGE"],
|
|
1484
|
-
};
|
|
1485
1672
|
|
|
1486
|
-
if (
|
|
1487
|
-
|
|
1488
|
-
aspectRatio: params.aspect_ratio,
|
|
1489
|
-
imageSize: params.image_size,
|
|
1490
|
-
};
|
|
1673
|
+
if (failures.length === 0 && unsupportedAspectRatioProvider) {
|
|
1674
|
+
assertImageAspectRatioSupported(unsupportedAspectRatioProvider, params.aspect_ratio);
|
|
1491
1675
|
}
|
|
1492
1676
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
};
|
|
1497
|
-
|
|
1498
|
-
const rawText = await withAuth(
|
|
1499
|
-
apiKey.apiKey,
|
|
1500
|
-
async key => {
|
|
1501
|
-
const resp = await fetchImpl(
|
|
1502
|
-
`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`,
|
|
1503
|
-
{
|
|
1504
|
-
method: "POST",
|
|
1505
|
-
headers: {
|
|
1506
|
-
"Content-Type": "application/json",
|
|
1507
|
-
"x-goog-api-key": key,
|
|
1508
|
-
},
|
|
1509
|
-
body: JSON.stringify(requestBody),
|
|
1510
|
-
signal: requestSignal,
|
|
1511
|
-
},
|
|
1512
|
-
);
|
|
1513
|
-
const text = await resp.text();
|
|
1514
|
-
if (!resp.ok) {
|
|
1515
|
-
let message = text;
|
|
1516
|
-
try {
|
|
1517
|
-
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1518
|
-
message = parsed.error?.message ?? message;
|
|
1519
|
-
} catch {
|
|
1520
|
-
// Keep raw text.
|
|
1521
|
-
}
|
|
1522
|
-
throw new ProviderHttpError(`Gemini image request failed (${resp.status}): ${message}`, resp.status, {
|
|
1523
|
-
headers: resp.headers,
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1526
|
-
return text;
|
|
1527
|
-
},
|
|
1528
|
-
{ signal: requestSignal },
|
|
1677
|
+
throw new AggregateError(
|
|
1678
|
+
failures.map(failure => failure.error),
|
|
1679
|
+
`Image generation failed for all credentialed providers: ${failures.map(failure => failure.provider).join(", ")}`,
|
|
1529
1680
|
);
|
|
1530
|
-
|
|
1531
|
-
const data = JSON.parse(rawText) as GeminiGenerateContentResponse;
|
|
1532
|
-
const responseParts = combineParts(data);
|
|
1533
|
-
const responseText = collectResponseText(responseParts);
|
|
1534
|
-
const inlineImages = collectInlineImages(responseParts);
|
|
1535
|
-
|
|
1536
|
-
if (inlineImages.length === 0) {
|
|
1537
|
-
const blocked = data.promptFeedback?.blockReason
|
|
1538
|
-
? `Blocked: ${data.promptFeedback.blockReason}`
|
|
1539
|
-
: "No image data returned.";
|
|
1540
|
-
return {
|
|
1541
|
-
content: [{ type: "text", text: `${blocked}${responseText ? `\n\n${responseText}` : ""}` }],
|
|
1542
|
-
details: {
|
|
1543
|
-
provider,
|
|
1544
|
-
model,
|
|
1545
|
-
imageCount: 0,
|
|
1546
|
-
imagePaths: [],
|
|
1547
|
-
images: [],
|
|
1548
|
-
responseText,
|
|
1549
|
-
promptFeedback: data.promptFeedback,
|
|
1550
|
-
usage: data.usageMetadata,
|
|
1551
|
-
},
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
const imagePaths = await saveImagesToTemp(inlineImages);
|
|
1556
|
-
|
|
1557
|
-
return {
|
|
1558
|
-
content: [{ type: "text", text: buildResponseSummary(provider, model, imagePaths, responseText) }],
|
|
1559
|
-
details: {
|
|
1560
|
-
provider,
|
|
1561
|
-
model,
|
|
1562
|
-
imageCount: inlineImages.length,
|
|
1563
|
-
imagePaths,
|
|
1564
|
-
images: inlineImages,
|
|
1565
|
-
responseText,
|
|
1566
|
-
promptFeedback: data.promptFeedback,
|
|
1567
|
-
usage: data.usageMetadata,
|
|
1568
|
-
},
|
|
1569
|
-
};
|
|
1570
1681
|
});
|
|
1571
1682
|
},
|
|
1572
1683
|
};
|