opencode-gemini-auth 1.3.4 → 1.3.6
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/package.json +1 -1
- package/src/plugin/request-helpers.ts +0 -28
- package/src/plugin/request.ts +107 -33
package/package.json
CHANGED
|
@@ -120,34 +120,6 @@ export function extractUsageMetadata(body: GeminiApiBody): GeminiUsageMetadata |
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/**
|
|
124
|
-
* Walks SSE lines to find a usage-bearing response chunk.
|
|
125
|
-
*/
|
|
126
|
-
export function extractUsageFromSsePayload(payload: string): GeminiUsageMetadata | null {
|
|
127
|
-
const lines = payload.split("\n");
|
|
128
|
-
for (const line of lines) {
|
|
129
|
-
if (!line.startsWith("data:")) {
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
const jsonText = line.slice(5).trim();
|
|
133
|
-
if (!jsonText) {
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
const parsed = JSON.parse(jsonText);
|
|
138
|
-
if (parsed && typeof parsed === "object") {
|
|
139
|
-
const usage = extractUsageMetadata({ response: (parsed as Record<string, unknown>).response });
|
|
140
|
-
if (usage) {
|
|
141
|
-
return usage;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
123
|
/**
|
|
152
124
|
* Enhances 404 errors for Gemini 3 models with a direct preview-access message.
|
|
153
125
|
*/
|
package/src/plugin/request.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { CODE_ASSIST_HEADERS, GEMINI_CODE_ASSIST_ENDPOINT } from "../constants";
|
|
2
2
|
import { logGeminiDebugResponse, type GeminiDebugContext } from "./debug";
|
|
3
3
|
import {
|
|
4
|
-
extractUsageFromSsePayload,
|
|
5
4
|
extractUsageMetadata,
|
|
6
5
|
normalizeThinkingConfig,
|
|
7
6
|
parseGeminiApiBody,
|
|
@@ -20,32 +19,85 @@ const MODEL_FALLBACKS: Record<string, string> = {
|
|
|
20
19
|
* @returns True when the URL targets generativelanguage.googleapis.com.
|
|
21
20
|
*/
|
|
22
21
|
export function isGenerativeLanguageRequest(input: RequestInfo): input is string {
|
|
23
|
-
return
|
|
22
|
+
return toRequestUrlString(input).includes("generativelanguage.googleapis.com");
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
|
-
* Rewrites SSE
|
|
26
|
+
* Rewrites SSE payload lines so downstream consumers see only the inner `response` objects.
|
|
28
27
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
function transformStreamingLine(line: string): string {
|
|
29
|
+
if (!line.startsWith("data:")) {
|
|
30
|
+
return line;
|
|
31
|
+
}
|
|
32
|
+
const json = line.slice(5).trim();
|
|
33
|
+
if (!json) {
|
|
34
|
+
return line;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(json) as { response?: unknown };
|
|
38
|
+
if (parsed.response !== undefined) {
|
|
39
|
+
return `data: ${JSON.stringify(parsed.response)}`;
|
|
40
|
+
}
|
|
41
|
+
} catch (_) {}
|
|
42
|
+
return line;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Streams SSE payloads, rewriting data lines on the fly.
|
|
47
|
+
*/
|
|
48
|
+
function transformStreamingPayloadStream(
|
|
49
|
+
stream: ReadableStream<Uint8Array>,
|
|
50
|
+
): ReadableStream<Uint8Array> {
|
|
51
|
+
const decoder = new TextDecoder();
|
|
52
|
+
const encoder = new TextEncoder();
|
|
53
|
+
let buffer = "";
|
|
54
|
+
let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
|
55
|
+
|
|
56
|
+
return new ReadableStream<Uint8Array>({
|
|
57
|
+
start(controller) {
|
|
58
|
+
reader = stream.getReader();
|
|
59
|
+
const pump = (): void => {
|
|
60
|
+
reader!
|
|
61
|
+
.read()
|
|
62
|
+
.then(({ done, value }) => {
|
|
63
|
+
if (done) {
|
|
64
|
+
buffer += decoder.decode();
|
|
65
|
+
if (buffer.length > 0) {
|
|
66
|
+
controller.enqueue(encoder.encode(transformStreamingLine(buffer)));
|
|
67
|
+
}
|
|
68
|
+
controller.close();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
buffer += decoder.decode(value, { stream: true });
|
|
73
|
+
|
|
74
|
+
let newlineIndex = buffer.indexOf("\n");
|
|
75
|
+
while (newlineIndex !== -1) {
|
|
76
|
+
const line = buffer.slice(0, newlineIndex);
|
|
77
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
78
|
+
const hasCarriageReturn = line.endsWith("\r");
|
|
79
|
+
const rawLine = hasCarriageReturn ? line.slice(0, -1) : line;
|
|
80
|
+
const transformed = transformStreamingLine(rawLine);
|
|
81
|
+
const suffix = hasCarriageReturn ? "\r\n" : "\n";
|
|
82
|
+
controller.enqueue(encoder.encode(`${transformed}${suffix}`));
|
|
83
|
+
newlineIndex = buffer.indexOf("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
pump();
|
|
87
|
+
})
|
|
88
|
+
.catch((error) => {
|
|
89
|
+
controller.error(error);
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
pump();
|
|
94
|
+
},
|
|
95
|
+
cancel(reason) {
|
|
96
|
+
if (reader) {
|
|
97
|
+
reader.cancel(reason).catch(() => {});
|
|
39
98
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (parsed.response !== undefined) {
|
|
43
|
-
return `data: ${JSON.stringify(parsed.response)}`;
|
|
44
|
-
}
|
|
45
|
-
} catch (_) {}
|
|
46
|
-
return line;
|
|
47
|
-
})
|
|
48
|
-
.join("\n");
|
|
99
|
+
},
|
|
100
|
+
});
|
|
49
101
|
}
|
|
50
102
|
|
|
51
103
|
/**
|
|
@@ -72,7 +124,7 @@ export function prepareGeminiRequest(
|
|
|
72
124
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
73
125
|
headers.delete("x-api-key");
|
|
74
126
|
|
|
75
|
-
const match = input.match(/\/models\/([^:]+):(\w+)/);
|
|
127
|
+
const match = toRequestUrlString(input).match(/\/models\/([^:]+):(\w+)/);
|
|
76
128
|
if (!match) {
|
|
77
129
|
return {
|
|
78
130
|
request: input,
|
|
@@ -136,7 +188,6 @@ export function prepareGeminiRequest(
|
|
|
136
188
|
}
|
|
137
189
|
|
|
138
190
|
delete requestPayload.cached_content;
|
|
139
|
-
delete requestPayload.cachedContent;
|
|
140
191
|
if (requestPayload.extra_body && typeof requestPayload.extra_body === "object") {
|
|
141
192
|
delete (requestPayload.extra_body as Record<string, unknown>).cached_content;
|
|
142
193
|
delete (requestPayload.extra_body as Record<string, unknown>).cachedContent;
|
|
@@ -182,9 +233,23 @@ export function prepareGeminiRequest(
|
|
|
182
233
|
};
|
|
183
234
|
}
|
|
184
235
|
|
|
236
|
+
function toRequestUrlString(value: RequestInfo): string {
|
|
237
|
+
if (typeof value === "string") {
|
|
238
|
+
return value;
|
|
239
|
+
}
|
|
240
|
+
if (value instanceof URL) {
|
|
241
|
+
return value.toString();
|
|
242
|
+
}
|
|
243
|
+
const candidate = (value as Request).url;
|
|
244
|
+
if (candidate) {
|
|
245
|
+
return candidate;
|
|
246
|
+
}
|
|
247
|
+
return value.toString();
|
|
248
|
+
}
|
|
249
|
+
|
|
185
250
|
/**
|
|
186
251
|
* Normalizes Gemini responses: applies retry headers, extracts cache usage into headers,
|
|
187
|
-
* rewrites preview errors,
|
|
252
|
+
* rewrites preview errors, rewrites streaming payloads, and logs debug metadata.
|
|
188
253
|
*/
|
|
189
254
|
export async function transformGeminiResponse(
|
|
190
255
|
response: Response,
|
|
@@ -204,8 +269,22 @@ export async function transformGeminiResponse(
|
|
|
204
269
|
}
|
|
205
270
|
|
|
206
271
|
try {
|
|
207
|
-
const text = await response.text();
|
|
208
272
|
const headers = new Headers(response.headers);
|
|
273
|
+
|
|
274
|
+
if (streaming && response.ok && isEventStreamResponse && response.body) {
|
|
275
|
+
logGeminiDebugResponse(debugContext, response, {
|
|
276
|
+
note: "Streaming SSE payload (body omitted)",
|
|
277
|
+
headersOverride: headers,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return new Response(transformStreamingPayloadStream(response.body), {
|
|
281
|
+
status: response.status,
|
|
282
|
+
statusText: response.statusText,
|
|
283
|
+
headers,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const text = await response.text();
|
|
209
288
|
|
|
210
289
|
if (!response.ok && text) {
|
|
211
290
|
try {
|
|
@@ -238,12 +317,11 @@ export async function transformGeminiResponse(
|
|
|
238
317
|
headers,
|
|
239
318
|
};
|
|
240
319
|
|
|
241
|
-
const usageFromSse = streaming && isEventStreamResponse ? extractUsageFromSsePayload(text) : null;
|
|
242
320
|
const parsed: GeminiApiBody | null = !streaming || !isEventStreamResponse ? parseGeminiApiBody(text) : null;
|
|
243
321
|
const patched = parsed ? rewriteGeminiPreviewAccessError(parsed, response.status, requestedModel) : null;
|
|
244
322
|
const effectiveBody = patched ?? parsed ?? undefined;
|
|
245
323
|
|
|
246
|
-
const usage =
|
|
324
|
+
const usage = effectiveBody ? extractUsageMetadata(effectiveBody) : null;
|
|
247
325
|
if (usage?.cachedContentTokenCount !== undefined) {
|
|
248
326
|
headers.set("x-gemini-cached-content-token-count", String(usage.cachedContentTokenCount));
|
|
249
327
|
if (usage.totalTokenCount !== undefined) {
|
|
@@ -259,14 +337,10 @@ export async function transformGeminiResponse(
|
|
|
259
337
|
|
|
260
338
|
logGeminiDebugResponse(debugContext, response, {
|
|
261
339
|
body: text,
|
|
262
|
-
note: streaming ? "Streaming SSE payload" : undefined,
|
|
340
|
+
note: streaming ? "Streaming SSE payload (buffered)" : undefined,
|
|
263
341
|
headersOverride: headers,
|
|
264
342
|
});
|
|
265
343
|
|
|
266
|
-
if (streaming && response.ok && isEventStreamResponse) {
|
|
267
|
-
return new Response(transformStreamingPayload(text), init);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
344
|
if (!parsed) {
|
|
271
345
|
return new Response(text, init);
|
|
272
346
|
}
|