@gajae-code/coding-agent 0.13.1 → 0.13.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/CHANGELOG.md +41 -0
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +3 -3
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +5 -5
- package/dist/types/modes/components/pet-capability.d.ts +5 -5
- package/dist/types/modes/components/settings-selector.d.ts +5 -0
- package/dist/types/modes/theme/defaults/index.d.ts +118 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +13 -0
- package/dist/types/session/session-manager.d.ts +7 -0
- package/dist/types/tools/image-gen.d.ts +1 -0
- package/package.json +8 -8
- package/src/config/model-profile-activation.ts +3 -1
- package/src/config/model-profiles.ts +59 -2
- package/src/config/model-registry.ts +16 -1
- package/src/config/settings-schema.ts +1 -1
- package/src/coordinator-mcp/server.ts +3 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/main.ts +4 -0
- package/src/modes/components/chrome.ts +4 -3
- package/src/modes/components/dynamic-theme-text.ts +22 -0
- package/src/modes/components/gajae-pet-widget.ts +130 -42
- package/src/modes/components/model-selector.ts +1 -6
- package/src/modes/components/pet-capability.ts +10 -5
- package/src/modes/components/settings-selector.ts +20 -5
- package/src/modes/components/status-line/segments.ts +3 -3
- package/src/modes/controllers/selector-controller.ts +61 -5
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/defaults/ouroboros.json +118 -0
- package/src/sdk/broker/process-incarnation.ts +6 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
- package/src/sdk/bus/telegram-daemon.ts +51 -43
- package/src/session/agent-session.ts +39 -0
- package/src/session/internal/managed-session-storage.ts +162 -40
- package/src/session/session-manager.ts +28 -0
- package/src/slash-commands/builtin-registry.ts +62 -8
- package/src/task/index.ts +5 -2
- package/src/tools/bash.ts +11 -9
- package/src/tools/image-gen.ts +322 -126
- package/src/tools/todo-contract.ts +55 -3
- package/src/tools/todo-write.ts +4 -3
package/src/tools/image-gen.ts
CHANGED
|
@@ -41,6 +41,124 @@ const IMAGE_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
|
41
41
|
const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
42
42
|
const OPENAI_IMAGE_OUTPUT_FORMAT = "webp";
|
|
43
43
|
const OPENAI_IMAGE_MIME_TYPE = "image/webp";
|
|
44
|
+
const MAX_PROVIDER_TEXT_LENGTH = 4096;
|
|
45
|
+
const REDACTED_PROVIDER_SECRET = "[redacted]";
|
|
46
|
+
const MAX_PROVIDER_BODY_BYTES = 64 * 1024 * 1024;
|
|
47
|
+
const MAX_PROVIDER_SSE_EVENT_BYTES = MAX_PROVIDER_BODY_BYTES;
|
|
48
|
+
const PROVIDER_MALFORMED_RESPONSE = "Provider returned a malformed image response.";
|
|
49
|
+
const PROVIDER_OVERSIZED_RESPONSE = "Provider image response exceeded the supported size.";
|
|
50
|
+
|
|
51
|
+
function escapeRegExp(value: string): string {
|
|
52
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function redactImageProviderText(value: unknown, activeApiKey?: string): string {
|
|
56
|
+
let text = typeof value === "string" ? value : value === undefined || value === null ? "" : String(value);
|
|
57
|
+
const key = activeApiKey?.trim();
|
|
58
|
+
if (key) {
|
|
59
|
+
text = text.replace(new RegExp(escapeRegExp(key), "g"), REDACTED_PROVIDER_SECRET);
|
|
60
|
+
const keyChars = [...key].filter(character => !/[\s\p{Cc}\p{Cf}]/u.test(character));
|
|
61
|
+
if (keyChars.length > 0) {
|
|
62
|
+
const separatorTolerantKey = keyChars.map(escapeRegExp).join("[\\s\\p{Cc}\\p{Cf}]*");
|
|
63
|
+
text = text.replace(new RegExp(separatorTolerantKey, "gu"), REDACTED_PROVIDER_SECRET);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
text = text.replace(/[\p{Cc}\p{Cf}]/gu, " ");
|
|
67
|
+
text = text
|
|
68
|
+
.replace(/\b(?:bearer|basic)\s+[^\s,;]+/gi, match => `${match.split(/\s+/, 1)[0]} ${REDACTED_PROVIDER_SECRET}`)
|
|
69
|
+
.replace(
|
|
70
|
+
/(?:\b|["'])(?:authorization|api[-_ ]?key|access[-_ ]?token|refresh[-_ ]?token|token)["']?\s*[:=]\s*(?:"[^"]*"|'[^']*'|[^\s,;}]+)/gi,
|
|
71
|
+
match => match.replace(/([:=]\s*).*/, `$1${REDACTED_PROVIDER_SECRET}`),
|
|
72
|
+
)
|
|
73
|
+
.replace(/\b(?:sk|rk|pk|sess|ghp|gho|github_pat|xox[baprs])-[-A-Za-z0-9._]{8,}\b/gi, REDACTED_PROVIDER_SECRET)
|
|
74
|
+
.replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, REDACTED_PROVIDER_SECRET)
|
|
75
|
+
.replace(/\b[A-Za-z0-9+/_-]{40,}={0,2}\b/g, REDACTED_PROVIDER_SECRET);
|
|
76
|
+
return text.length > MAX_PROVIDER_TEXT_LENGTH ? `${text.slice(0, MAX_PROVIDER_TEXT_LENGTH - 1)}…` : text;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function tryParseProviderJson<T>(rawText: string): T | undefined {
|
|
80
|
+
if (Buffer.byteLength(rawText, "utf8") > MAX_PROVIDER_BODY_BYTES) return undefined;
|
|
81
|
+
try {
|
|
82
|
+
const parsed: unknown = JSON.parse(rawText);
|
|
83
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as T) : undefined;
|
|
84
|
+
} catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseProviderJson<T>(rawText: string, activeApiKey?: string): T {
|
|
90
|
+
const parsed = tryParseProviderJson<T>(rawText);
|
|
91
|
+
if (parsed !== undefined) return parsed;
|
|
92
|
+
const message =
|
|
93
|
+
Buffer.byteLength(rawText, "utf8") > MAX_PROVIDER_BODY_BYTES
|
|
94
|
+
? PROVIDER_OVERSIZED_RESPONSE
|
|
95
|
+
: PROVIDER_MALFORMED_RESPONSE;
|
|
96
|
+
throw new Error(redactImageProviderText(message, activeApiKey));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function readProviderResponseText(
|
|
100
|
+
response: Response,
|
|
101
|
+
activeApiKey?: string,
|
|
102
|
+
signal?: AbortSignal,
|
|
103
|
+
): Promise<string> {
|
|
104
|
+
const contentLength = response.headers.get("content-length");
|
|
105
|
+
if (contentLength !== null) {
|
|
106
|
+
const declaredLength = Number(contentLength);
|
|
107
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_PROVIDER_BODY_BYTES) {
|
|
108
|
+
await response.body?.cancel().catch(() => undefined);
|
|
109
|
+
throw new Error(PROVIDER_OVERSIZED_RESPONSE);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!response.body) return "";
|
|
114
|
+
|
|
115
|
+
const reader = response.body.getReader();
|
|
116
|
+
const decoder = new TextDecoder("utf-8");
|
|
117
|
+
const chunks: string[] = [];
|
|
118
|
+
let totalBytes = 0;
|
|
119
|
+
try {
|
|
120
|
+
while (true) {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
if (done) break;
|
|
123
|
+
if (!value || value.byteLength === 0) continue;
|
|
124
|
+
|
|
125
|
+
if (totalBytes > MAX_PROVIDER_BODY_BYTES - value.byteLength) {
|
|
126
|
+
await reader.cancel().catch(() => undefined);
|
|
127
|
+
throw new Error(PROVIDER_OVERSIZED_RESPONSE);
|
|
128
|
+
}
|
|
129
|
+
totalBytes += value.byteLength;
|
|
130
|
+
const decoded = decoder.decode(value, { stream: true });
|
|
131
|
+
if (decoded.length > 0) chunks.push(decoded);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const trailingText = decoder.decode();
|
|
135
|
+
if (trailingText.length > 0) chunks.push(trailingText);
|
|
136
|
+
return chunks.join("");
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
139
|
+
throw new Error(redactImageProviderText(error instanceof Error ? error.message : error, activeApiKey));
|
|
140
|
+
} finally {
|
|
141
|
+
reader.releaseLock();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const PROVIDER_SSE_READ_OPTIONS = {
|
|
146
|
+
maxEventBytes: MAX_PROVIDER_SSE_EVENT_BYTES,
|
|
147
|
+
maxTotalBytes: MAX_PROVIDER_BODY_BYTES,
|
|
148
|
+
} as const;
|
|
149
|
+
|
|
150
|
+
async function fetchImageProvider(
|
|
151
|
+
input: string | URL | Request,
|
|
152
|
+
init: RequestInit,
|
|
153
|
+
activeApiKey: string,
|
|
154
|
+
): Promise<Response> {
|
|
155
|
+
try {
|
|
156
|
+
return await fetch(input, init);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (init.signal?.aborted) throw init.signal.reason ?? error;
|
|
159
|
+
throw new Error(redactImageProviderText(error instanceof Error ? error.message : error, activeApiKey));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
44
162
|
|
|
45
163
|
const ANTIGRAVITY_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
|
|
46
164
|
const ALIBABA_TOKEN_PLAN_HOST = "https://token-plan.ap-southeast-1.maas.aliyuncs.com";
|
|
@@ -244,6 +362,30 @@ interface OpenAIHostedImageResult {
|
|
|
244
362
|
usage?: OpenAIResponsesUsage;
|
|
245
363
|
}
|
|
246
364
|
|
|
365
|
+
function redactOpenAIHostedImageResult(result: OpenAIHostedImageResult, activeApiKey: string): OpenAIHostedImageResult {
|
|
366
|
+
return {
|
|
367
|
+
...result,
|
|
368
|
+
responseText: result.responseText ? redactImageProviderText(result.responseText, activeApiKey) : undefined,
|
|
369
|
+
revisedPrompt: result.revisedPrompt ? redactImageProviderText(result.revisedPrompt, activeApiKey) : undefined,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function redactGeminiPromptFeedback(
|
|
374
|
+
promptFeedback: GeminiPromptFeedback | undefined,
|
|
375
|
+
activeApiKey: string,
|
|
376
|
+
): GeminiPromptFeedback | undefined {
|
|
377
|
+
if (!promptFeedback) return undefined;
|
|
378
|
+
return {
|
|
379
|
+
blockReason: promptFeedback.blockReason
|
|
380
|
+
? redactImageProviderText(promptFeedback.blockReason, activeApiKey)
|
|
381
|
+
: undefined,
|
|
382
|
+
safetyRatings: promptFeedback.safetyRatings?.map(rating => ({
|
|
383
|
+
category: rating.category ? redactImageProviderText(rating.category, activeApiKey) : undefined,
|
|
384
|
+
probability: rating.probability ? redactImageProviderText(rating.probability, activeApiKey) : undefined,
|
|
385
|
+
})),
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
247
389
|
interface OpenRouterImageUrl {
|
|
248
390
|
url: string;
|
|
249
391
|
}
|
|
@@ -566,7 +708,11 @@ async function readImageResponse(
|
|
|
566
708
|
}
|
|
567
709
|
}
|
|
568
710
|
|
|
569
|
-
async function loadImageFromUrl(
|
|
711
|
+
async function loadImageFromUrl(
|
|
712
|
+
imageUrl: string,
|
|
713
|
+
signal?: AbortSignal,
|
|
714
|
+
activeApiKey?: string,
|
|
715
|
+
): Promise<InlineImageData> {
|
|
570
716
|
if (imageUrl.startsWith("data:")) {
|
|
571
717
|
const normalized = normalizeDataUrl(imageUrl.trim());
|
|
572
718
|
if (!normalized.mimeType) {
|
|
@@ -599,8 +745,11 @@ async function loadImageFromUrl(imageUrl: string, signal?: AbortSignal): Promise
|
|
|
599
745
|
}
|
|
600
746
|
validateImageResponseFraming(response);
|
|
601
747
|
if (status < 200 || status >= 300) {
|
|
602
|
-
const preview = (
|
|
603
|
-
|
|
748
|
+
const preview = redactImageProviderText(
|
|
749
|
+
(await readImageResponse(response, MAX_IMAGE_ERROR_PREVIEW_SIZE, signal)).toString("utf8"),
|
|
750
|
+
activeApiKey,
|
|
751
|
+
);
|
|
752
|
+
throw new Error(`Image download failed (${status}): ${preview || "provider returned an error"}`);
|
|
604
753
|
}
|
|
605
754
|
const rawContentType = response.headers["content-type"];
|
|
606
755
|
const contentType =
|
|
@@ -1049,13 +1198,9 @@ function collectOpenAIHostedImageResult(response: OpenAIHostedImageResponse): Op
|
|
|
1049
1198
|
};
|
|
1050
1199
|
}
|
|
1051
1200
|
|
|
1052
|
-
function getOpenAIResponseErrorMessage(rawText: string): string {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
return parsed.error?.message ?? rawText;
|
|
1056
|
-
} catch {
|
|
1057
|
-
return rawText;
|
|
1058
|
-
}
|
|
1201
|
+
function getOpenAIResponseErrorMessage(rawText: string, activeApiKey?: string): string {
|
|
1202
|
+
const parsed = tryParseProviderJson<{ error?: { message?: string } }>(rawText);
|
|
1203
|
+
return redactImageProviderText(parsed?.error?.message ?? "provider returned an error", activeApiKey);
|
|
1059
1204
|
}
|
|
1060
1205
|
|
|
1061
1206
|
function getOpenAIBaseUrl(model: Model, authCredentialType?: "api_key" | "oauth"): string {
|
|
@@ -1113,7 +1258,11 @@ function buildOpenAIImageHeaders(model: Model, apiKey: string, sessionId: string
|
|
|
1113
1258
|
return headers;
|
|
1114
1259
|
}
|
|
1115
1260
|
|
|
1116
|
-
async function parseOpenAIHostedImageSse(
|
|
1261
|
+
async function parseOpenAIHostedImageSse(
|
|
1262
|
+
response: Response,
|
|
1263
|
+
signal: AbortSignal | undefined,
|
|
1264
|
+
activeApiKey: string,
|
|
1265
|
+
): Promise<OpenAIHostedImageResult> {
|
|
1117
1266
|
if (!response.body) {
|
|
1118
1267
|
throw new Error("No response body");
|
|
1119
1268
|
}
|
|
@@ -1121,21 +1270,33 @@ async function parseOpenAIHostedImageSse(response: Response, signal?: AbortSigna
|
|
|
1121
1270
|
const fallbackOutput: OpenAIResponseOutput[] = [];
|
|
1122
1271
|
let completedResponse: OpenAIHostedImageResponse | undefined;
|
|
1123
1272
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
throw new Error(
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1273
|
+
try {
|
|
1274
|
+
for await (const event of readSseJson<OpenAISseEvent>(
|
|
1275
|
+
response.body,
|
|
1276
|
+
signal,
|
|
1277
|
+
undefined,
|
|
1278
|
+
PROVIDER_SSE_READ_OPTIONS,
|
|
1279
|
+
)) {
|
|
1280
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) throw new Error(PROVIDER_MALFORMED_RESPONSE);
|
|
1281
|
+
if (event.type === "error") {
|
|
1282
|
+
const message = event.error?.message ?? event.message ?? "OpenAI image request failed";
|
|
1283
|
+
throw new Error(redactImageProviderText(message, activeApiKey));
|
|
1284
|
+
}
|
|
1285
|
+
if (event.type === "response.failed") {
|
|
1286
|
+
const message = event.response?.error?.message ?? "OpenAI image request failed";
|
|
1287
|
+
throw new Error(redactImageProviderText(message, activeApiKey));
|
|
1288
|
+
}
|
|
1289
|
+
if (event.type === "response.output_item.done" && event.item) {
|
|
1290
|
+
fallbackOutput.push(event.item);
|
|
1291
|
+
}
|
|
1292
|
+
if ((event.type === "response.completed" || event.type === "response.done") && event.response) {
|
|
1293
|
+
completedResponse = event.response;
|
|
1294
|
+
}
|
|
1138
1295
|
}
|
|
1296
|
+
} catch (error) {
|
|
1297
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
1298
|
+
if (error instanceof Error && error.message !== PROVIDER_MALFORMED_RESPONSE) throw error;
|
|
1299
|
+
throw new Error(redactImageProviderText(PROVIDER_MALFORMED_RESPONSE, activeApiKey));
|
|
1139
1300
|
}
|
|
1140
1301
|
|
|
1141
1302
|
return collectOpenAIHostedImageResult(
|
|
@@ -1157,25 +1318,32 @@ async function generateOpenAIHostedImage(
|
|
|
1157
1318
|
const promptText = assemblePrompt(params);
|
|
1158
1319
|
const stream = model.api === "openai-codex-responses" || model.provider === "openai-codex";
|
|
1159
1320
|
const requestBody = buildOpenAIHostedImageRequest(model, promptText, params, inputImages, stream);
|
|
1160
|
-
const response = await
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1321
|
+
const response = await fetchImageProvider(
|
|
1322
|
+
getOpenAIResponsesUrl(model, options?.authCredentialType),
|
|
1323
|
+
{
|
|
1324
|
+
method: "POST",
|
|
1325
|
+
headers: buildOpenAIImageHeaders(model, apiKey, sessionId),
|
|
1326
|
+
body: JSON.stringify(requestBody),
|
|
1327
|
+
signal,
|
|
1328
|
+
},
|
|
1329
|
+
apiKey,
|
|
1330
|
+
);
|
|
1166
1331
|
|
|
1167
1332
|
if (!response.ok) {
|
|
1168
|
-
const errorText = await response
|
|
1169
|
-
throw new Error(
|
|
1333
|
+
const errorText = await readProviderResponseText(response, apiKey, signal);
|
|
1334
|
+
throw new Error(
|
|
1335
|
+
`OpenAI image request failed (${response.status}): ${getOpenAIResponseErrorMessage(errorText, apiKey)}`,
|
|
1336
|
+
);
|
|
1170
1337
|
}
|
|
1171
1338
|
|
|
1172
1339
|
const contentType = response.headers.get("content-type") ?? "";
|
|
1173
1340
|
if (stream || contentType.includes("text/event-stream")) {
|
|
1174
|
-
return parseOpenAIHostedImageSse(response, signal);
|
|
1341
|
+
return redactOpenAIHostedImageResult(await parseOpenAIHostedImageSse(response, signal, apiKey), apiKey);
|
|
1175
1342
|
}
|
|
1176
1343
|
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1344
|
+
const rawText = await readProviderResponseText(response, apiKey, signal);
|
|
1345
|
+
const data = parseProviderJson<OpenAIHostedImageResponse>(rawText, apiKey);
|
|
1346
|
+
return redactOpenAIHostedImageResult(collectOpenAIHostedImageResult(data), apiKey);
|
|
1179
1347
|
}
|
|
1180
1348
|
|
|
1181
1349
|
function combineParts(response: GeminiGenerateContentResponse): GeminiPart[] {
|
|
@@ -1234,7 +1402,11 @@ interface AntigravitySseResult {
|
|
|
1234
1402
|
usage?: GeminiUsageMetadata;
|
|
1235
1403
|
}
|
|
1236
1404
|
|
|
1237
|
-
async function parseAntigravitySseForImage(
|
|
1405
|
+
async function parseAntigravitySseForImage(
|
|
1406
|
+
response: Response,
|
|
1407
|
+
signal: AbortSignal | undefined,
|
|
1408
|
+
activeApiKey?: string,
|
|
1409
|
+
): Promise<AntigravitySseResult> {
|
|
1238
1410
|
if (!response.body) {
|
|
1239
1411
|
throw new Error("No response body");
|
|
1240
1412
|
}
|
|
@@ -1242,25 +1414,42 @@ async function parseAntigravitySseForImage(response: Response, signal?: AbortSig
|
|
|
1242
1414
|
const textParts: string[] = [];
|
|
1243
1415
|
const images: InlineImageData[] = [];
|
|
1244
1416
|
let usage: GeminiUsageMetadata | undefined;
|
|
1417
|
+
let chunks: AntigravityResponseChunk[];
|
|
1418
|
+
try {
|
|
1419
|
+
chunks = [];
|
|
1420
|
+
for await (const chunk of readSseJson<AntigravityResponseChunk>(
|
|
1421
|
+
response.body,
|
|
1422
|
+
signal,
|
|
1423
|
+
undefined,
|
|
1424
|
+
PROVIDER_SSE_READ_OPTIONS,
|
|
1425
|
+
)) {
|
|
1426
|
+
if (!chunk || typeof chunk !== "object" || Array.isArray(chunk)) throw new Error(PROVIDER_MALFORMED_RESPONSE);
|
|
1427
|
+
chunks.push(chunk);
|
|
1428
|
+
}
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
1431
|
+
throw new Error(redactImageProviderText(PROVIDER_MALFORMED_RESPONSE, activeApiKey));
|
|
1432
|
+
}
|
|
1245
1433
|
|
|
1246
|
-
for
|
|
1434
|
+
for (const chunk of chunks) {
|
|
1247
1435
|
const responseData = chunk.response;
|
|
1248
|
-
if (!responseData) continue;
|
|
1249
|
-
|
|
1250
|
-
for (const candidate of
|
|
1251
|
-
|
|
1252
|
-
|
|
1436
|
+
if (!responseData || typeof responseData !== "object") continue;
|
|
1437
|
+
const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
|
|
1438
|
+
for (const candidate of candidates) {
|
|
1439
|
+
if (!candidate || typeof candidate !== "object") continue;
|
|
1440
|
+
const parts = Array.isArray(candidate.content?.parts) ? candidate.content.parts : [];
|
|
1253
1441
|
for (const part of parts) {
|
|
1254
|
-
if (part
|
|
1442
|
+
if (!part || typeof part !== "object") continue;
|
|
1443
|
+
if (typeof part.text === "string" && part.text.length > 0) {
|
|
1255
1444
|
textParts.push(part.text);
|
|
1256
1445
|
}
|
|
1257
1446
|
const inlineData = part.inlineData;
|
|
1258
|
-
if (inlineData
|
|
1447
|
+
if (inlineData && typeof inlineData === "object" && inlineData.data && inlineData.mimeType) {
|
|
1259
1448
|
images.push({ data: inlineData.data, mimeType: inlineData.mimeType });
|
|
1260
1449
|
}
|
|
1261
1450
|
}
|
|
1262
1451
|
}
|
|
1263
|
-
if (responseData.usageMetadata) {
|
|
1452
|
+
if (responseData.usageMetadata && typeof responseData.usageMetadata === "object") {
|
|
1264
1453
|
usage = responseData.usageMetadata;
|
|
1265
1454
|
}
|
|
1266
1455
|
}
|
|
@@ -1381,32 +1570,34 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1381
1570
|
|
|
1382
1571
|
const { getAntigravityUserAgent } =
|
|
1383
1572
|
require("@gajae-code/ai/providers/google-gemini-headers") as typeof import("@gajae-code/ai/providers/google-gemini-headers");
|
|
1384
|
-
const response = await
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1573
|
+
const response = await fetchImageProvider(
|
|
1574
|
+
`${ANTIGRAVITY_ENDPOINT}/v1internal:streamGenerateContent?alt=sse`,
|
|
1575
|
+
{
|
|
1576
|
+
method: "POST",
|
|
1577
|
+
headers: {
|
|
1578
|
+
Authorization: `Bearer ${apiKey.apiKey}`,
|
|
1579
|
+
"Content-Type": "application/json",
|
|
1580
|
+
Accept: "text/event-stream",
|
|
1581
|
+
"User-Agent": getAntigravityUserAgent(),
|
|
1582
|
+
},
|
|
1583
|
+
body: JSON.stringify(requestBody),
|
|
1584
|
+
signal: requestSignal,
|
|
1391
1585
|
},
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
});
|
|
1586
|
+
apiKey.apiKey,
|
|
1587
|
+
);
|
|
1395
1588
|
|
|
1396
1589
|
if (!response.ok) {
|
|
1397
|
-
const errorText = await response.
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
// Keep raw text.
|
|
1404
|
-
}
|
|
1405
|
-
throw new Error(`Antigravity image request failed (${response.status}): ${message}`);
|
|
1590
|
+
const errorText = await readProviderResponseText(response, apiKey.apiKey, requestSignal);
|
|
1591
|
+
const parsed = tryParseProviderJson<{ error?: { message?: string } }>(errorText);
|
|
1592
|
+
const message = parsed?.error?.message;
|
|
1593
|
+
throw new Error(
|
|
1594
|
+
`Antigravity image request failed (${response.status}): ${redactImageProviderText(message ?? "provider returned an error", apiKey.apiKey)}`,
|
|
1595
|
+
);
|
|
1406
1596
|
}
|
|
1407
1597
|
|
|
1408
|
-
const parsed = await parseAntigravitySseForImage(response, requestSignal);
|
|
1409
|
-
const responseText =
|
|
1598
|
+
const parsed = await parseAntigravitySseForImage(response, requestSignal, apiKey.apiKey);
|
|
1599
|
+
const responseText =
|
|
1600
|
+
parsed.text.length > 0 ? redactImageProviderText(parsed.text.join(" "), apiKey.apiKey) : undefined;
|
|
1410
1601
|
|
|
1411
1602
|
if (parsed.images.length === 0) {
|
|
1412
1603
|
const messageText = responseText ? `\n\n${responseText}` : "";
|
|
@@ -1452,38 +1643,40 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1452
1643
|
messages: [{ role: "user" as const, content: contentParts }],
|
|
1453
1644
|
};
|
|
1454
1645
|
|
|
1455
|
-
const response = await
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1646
|
+
const response = await fetchImageProvider(
|
|
1647
|
+
"https://openrouter.ai/api/v1/chat/completions",
|
|
1648
|
+
{
|
|
1649
|
+
method: "POST",
|
|
1650
|
+
headers: {
|
|
1651
|
+
"Content-Type": "application/json",
|
|
1652
|
+
Authorization: `Bearer ${apiKey.apiKey}`,
|
|
1653
|
+
"HTTP-Referer": "https://gaebal-gajae.dev/",
|
|
1654
|
+
"X-OpenRouter-Title": "Gajae Code",
|
|
1655
|
+
"X-OpenRouter-Categories": "cli-agent",
|
|
1656
|
+
},
|
|
1657
|
+
body: JSON.stringify(requestBody),
|
|
1658
|
+
signal: requestSignal,
|
|
1463
1659
|
},
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
});
|
|
1660
|
+
apiKey.apiKey,
|
|
1661
|
+
);
|
|
1467
1662
|
|
|
1468
|
-
const rawText = await response.
|
|
1663
|
+
const rawText = await readProviderResponseText(response, apiKey.apiKey, requestSignal);
|
|
1469
1664
|
if (!response.ok) {
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
// Keep raw text.
|
|
1476
|
-
}
|
|
1477
|
-
throw new Error(`OpenRouter image request failed (${response.status}): ${message}`);
|
|
1665
|
+
const parsed = tryParseProviderJson<{ error?: { message?: string } }>(rawText);
|
|
1666
|
+
const message = parsed?.error?.message;
|
|
1667
|
+
throw new Error(
|
|
1668
|
+
`OpenRouter image request failed (${response.status}): ${redactImageProviderText(message ?? "provider returned an error", apiKey.apiKey)}`,
|
|
1669
|
+
);
|
|
1478
1670
|
}
|
|
1479
1671
|
|
|
1480
|
-
const data =
|
|
1672
|
+
const data = parseProviderJson<OpenRouterResponse>(rawText, apiKey.apiKey);
|
|
1481
1673
|
const message = data.choices?.[0]?.message;
|
|
1482
|
-
const
|
|
1674
|
+
const rawResponseText = collectOpenRouterResponseText(message);
|
|
1675
|
+
const responseText = rawResponseText ? redactImageProviderText(rawResponseText, apiKey.apiKey) : undefined;
|
|
1483
1676
|
const imageUrls = extractOpenRouterImageUrls(message);
|
|
1484
1677
|
const inlineImages: InlineImageData[] = [];
|
|
1485
1678
|
for (const imageUrl of imageUrls) {
|
|
1486
|
-
inlineImages.push(await loadImageFromUrl(imageUrl, requestSignal));
|
|
1679
|
+
inlineImages.push(await loadImageFromUrl(imageUrl, requestSignal, apiKey.apiKey));
|
|
1487
1680
|
}
|
|
1488
1681
|
|
|
1489
1682
|
if (inlineImages.length === 0) {
|
|
@@ -1526,38 +1719,42 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1526
1719
|
params.image_size,
|
|
1527
1720
|
);
|
|
1528
1721
|
|
|
1529
|
-
const response = await
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1722
|
+
const response = await fetchImageProvider(
|
|
1723
|
+
ALIBABA_IMAGE_GENERATION_URL,
|
|
1724
|
+
{
|
|
1725
|
+
method: "POST",
|
|
1726
|
+
headers: {
|
|
1727
|
+
"Content-Type": "application/json",
|
|
1728
|
+
Authorization: `Bearer ${apiKey.apiKey}`,
|
|
1729
|
+
},
|
|
1730
|
+
body: JSON.stringify(requestBody),
|
|
1731
|
+
signal: requestSignal,
|
|
1534
1732
|
},
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
});
|
|
1733
|
+
apiKey.apiKey,
|
|
1734
|
+
);
|
|
1538
1735
|
|
|
1539
|
-
const rawText = await response.
|
|
1736
|
+
const rawText = await readProviderResponseText(response, apiKey.apiKey, requestSignal);
|
|
1540
1737
|
if (!response.ok) {
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
// Keep raw text.
|
|
1547
|
-
}
|
|
1548
|
-
throw new Error(`Alibaba image request failed (${response.status}): ${message}`);
|
|
1738
|
+
const parsed = tryParseProviderJson<{ message?: string; error?: { message?: string } }>(rawText);
|
|
1739
|
+
const message = parsed?.error?.message ?? parsed?.message;
|
|
1740
|
+
throw new Error(
|
|
1741
|
+
`Alibaba image request failed (${response.status}): ${redactImageProviderText(message ?? "provider returned an error", apiKey.apiKey)}`,
|
|
1742
|
+
);
|
|
1549
1743
|
}
|
|
1550
1744
|
|
|
1551
|
-
const data =
|
|
1745
|
+
const data = parseProviderJson<AlibabaImageResponse>(rawText, apiKey.apiKey);
|
|
1552
1746
|
if (data.code) {
|
|
1553
|
-
throw new Error(
|
|
1747
|
+
throw new Error(
|
|
1748
|
+
`Alibaba image request failed: ${redactImageProviderText(`${data.code}: ${data.message ?? ""}`, apiKey.apiKey)}`,
|
|
1749
|
+
);
|
|
1554
1750
|
}
|
|
1555
1751
|
|
|
1556
|
-
const { imageUrls, responseText } = collectAlibabaImageResult(data);
|
|
1752
|
+
const { imageUrls, responseText: rawResponseText } = collectAlibabaImageResult(data);
|
|
1753
|
+
const responseText = rawResponseText ? redactImageProviderText(rawResponseText, apiKey.apiKey) : undefined;
|
|
1557
1754
|
// Result URLs are short-lived OSS-signed URLs (24h); download immediately.
|
|
1558
1755
|
const inlineImages: InlineImageData[] = [];
|
|
1559
1756
|
for (const imageUrl of imageUrls) {
|
|
1560
|
-
inlineImages.push(await loadImageFromUrl(imageUrl, requestSignal));
|
|
1757
|
+
inlineImages.push(await loadImageFromUrl(imageUrl, requestSignal, apiKey.apiKey));
|
|
1561
1758
|
}
|
|
1562
1759
|
|
|
1563
1760
|
if (inlineImages.length === 0) {
|
|
@@ -1615,7 +1812,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1615
1812
|
generationConfig,
|
|
1616
1813
|
};
|
|
1617
1814
|
|
|
1618
|
-
const response = await
|
|
1815
|
+
const response = await fetchImageProvider(
|
|
1619
1816
|
`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`,
|
|
1620
1817
|
{
|
|
1621
1818
|
method: "POST",
|
|
@@ -1626,28 +1823,27 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1626
1823
|
body: JSON.stringify(requestBody),
|
|
1627
1824
|
signal: requestSignal,
|
|
1628
1825
|
},
|
|
1826
|
+
apiKey.apiKey,
|
|
1629
1827
|
);
|
|
1630
1828
|
|
|
1631
|
-
const rawText = await response.
|
|
1829
|
+
const rawText = await readProviderResponseText(response, apiKey.apiKey, requestSignal);
|
|
1632
1830
|
if (!response.ok) {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
// Keep raw text.
|
|
1639
|
-
}
|
|
1640
|
-
throw new Error(`Gemini image request failed (${response.status}): ${message}`);
|
|
1831
|
+
const parsed = tryParseProviderJson<{ error?: { message?: string } }>(rawText);
|
|
1832
|
+
const message = parsed?.error?.message;
|
|
1833
|
+
throw new Error(
|
|
1834
|
+
`Gemini image request failed (${response.status}): ${redactImageProviderText(message ?? "provider returned an error", apiKey.apiKey)}`,
|
|
1835
|
+
);
|
|
1641
1836
|
}
|
|
1642
1837
|
|
|
1643
|
-
const data =
|
|
1838
|
+
const data = parseProviderJson<GeminiGenerateContentResponse>(rawText, apiKey.apiKey);
|
|
1644
1839
|
const responseParts = combineParts(data);
|
|
1645
|
-
const
|
|
1840
|
+
const rawResponseText = collectResponseText(responseParts);
|
|
1841
|
+
const responseText = rawResponseText ? redactImageProviderText(rawResponseText, apiKey.apiKey) : undefined;
|
|
1646
1842
|
const inlineImages = collectInlineImages(responseParts);
|
|
1647
1843
|
|
|
1648
1844
|
if (inlineImages.length === 0) {
|
|
1649
1845
|
const blocked = data.promptFeedback?.blockReason
|
|
1650
|
-
? `Blocked: ${data.promptFeedback.blockReason}`
|
|
1846
|
+
? `Blocked: ${redactImageProviderText(data.promptFeedback.blockReason, apiKey.apiKey)}`
|
|
1651
1847
|
: "No image data returned.";
|
|
1652
1848
|
return {
|
|
1653
1849
|
content: [{ type: "text", text: `${blocked}${responseText ? `\n\n${responseText}` : ""}` }],
|
|
@@ -1658,7 +1854,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1658
1854
|
imagePaths: [],
|
|
1659
1855
|
images: [],
|
|
1660
1856
|
responseText,
|
|
1661
|
-
promptFeedback: data.promptFeedback,
|
|
1857
|
+
promptFeedback: redactGeminiPromptFeedback(data.promptFeedback, apiKey.apiKey),
|
|
1662
1858
|
usage: data.usageMetadata,
|
|
1663
1859
|
},
|
|
1664
1860
|
};
|
|
@@ -1675,7 +1871,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1675
1871
|
imagePaths,
|
|
1676
1872
|
images: inlineImages,
|
|
1677
1873
|
responseText,
|
|
1678
|
-
promptFeedback: data.promptFeedback,
|
|
1874
|
+
promptFeedback: redactGeminiPromptFeedback(data.promptFeedback, apiKey.apiKey),
|
|
1679
1875
|
usage: data.usageMetadata,
|
|
1680
1876
|
},
|
|
1681
1877
|
};
|