copilot-api-plus 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/auth-Cm_0h9bp.js.map +1 -1
- package/dist/get-models-Bbb8B5jI.js.map +1 -1
- package/dist/main.js +841 -523
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -108,6 +108,83 @@ const checkUsage = defineCommand({
|
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/lib/config.ts
|
|
113
|
+
const CONFIG_FILENAME = "config.json";
|
|
114
|
+
/**
|
|
115
|
+
* Get the path to the config file
|
|
116
|
+
*/
|
|
117
|
+
function getConfigPath() {
|
|
118
|
+
return path.join(PATHS.DATA_DIR, CONFIG_FILENAME);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Load configuration from file
|
|
122
|
+
*/
|
|
123
|
+
async function loadConfig() {
|
|
124
|
+
try {
|
|
125
|
+
const configPath = getConfigPath();
|
|
126
|
+
const content = await fs.readFile(configPath);
|
|
127
|
+
return JSON.parse(content);
|
|
128
|
+
} catch {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Save configuration to file
|
|
134
|
+
*/
|
|
135
|
+
async function saveConfig(config) {
|
|
136
|
+
const configPath = getConfigPath();
|
|
137
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
138
|
+
consola.debug(`Configuration saved to ${configPath}`);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get proxy configuration
|
|
142
|
+
*/
|
|
143
|
+
async function getProxyConfig() {
|
|
144
|
+
return (await loadConfig()).proxy;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Save proxy configuration
|
|
148
|
+
*/
|
|
149
|
+
async function saveProxyConfig(proxyConfig) {
|
|
150
|
+
const config = await loadConfig();
|
|
151
|
+
config.proxy = proxyConfig;
|
|
152
|
+
await saveConfig(config);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Clear proxy configuration
|
|
156
|
+
*/
|
|
157
|
+
async function clearProxyConfig() {
|
|
158
|
+
const config = await loadConfig();
|
|
159
|
+
delete config.proxy;
|
|
160
|
+
await saveConfig(config);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Apply saved proxy configuration to environment variables
|
|
164
|
+
* This should be called at startup to restore proxy settings
|
|
165
|
+
*/
|
|
166
|
+
async function applyProxyConfig() {
|
|
167
|
+
const proxyConfig = await getProxyConfig();
|
|
168
|
+
if (!proxyConfig || !proxyConfig.enabled) return false;
|
|
169
|
+
if (proxyConfig.httpProxy) {
|
|
170
|
+
process.env.HTTP_PROXY = proxyConfig.httpProxy;
|
|
171
|
+
process.env.http_proxy = proxyConfig.httpProxy;
|
|
172
|
+
}
|
|
173
|
+
if (proxyConfig.httpsProxy) {
|
|
174
|
+
process.env.HTTPS_PROXY = proxyConfig.httpsProxy;
|
|
175
|
+
process.env.https_proxy = proxyConfig.httpsProxy;
|
|
176
|
+
}
|
|
177
|
+
if (proxyConfig.noProxy) {
|
|
178
|
+
process.env.NO_PROXY = proxyConfig.noProxy;
|
|
179
|
+
process.env.no_proxy = proxyConfig.noProxy;
|
|
180
|
+
}
|
|
181
|
+
consola.info("Proxy configuration loaded from saved settings");
|
|
182
|
+
if (proxyConfig.httpProxy) consola.info(` HTTP_PROXY: ${proxyConfig.httpProxy}`);
|
|
183
|
+
if (proxyConfig.httpsProxy) consola.info(` HTTPS_PROXY: ${proxyConfig.httpsProxy}`);
|
|
184
|
+
if (proxyConfig.noProxy) consola.info(` NO_PROXY: ${proxyConfig.noProxy}`);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
111
188
|
//#endregion
|
|
112
189
|
//#region src/debug.ts
|
|
113
190
|
async function getPackageVersion() {
|
|
@@ -135,29 +212,61 @@ async function checkTokenExists() {
|
|
|
135
212
|
return false;
|
|
136
213
|
}
|
|
137
214
|
}
|
|
215
|
+
async function checkFileExists(path$1) {
|
|
216
|
+
try {
|
|
217
|
+
if (!(await fs.stat(path$1)).isFile()) return false;
|
|
218
|
+
return (await fs.readFile(path$1, "utf8")).trim().length > 0;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
138
223
|
async function getDebugInfo() {
|
|
139
|
-
const
|
|
224
|
+
const zenAuthPath = getZenAuthPath();
|
|
225
|
+
const antigravityAuthPath = getAntigravityAuthPath();
|
|
226
|
+
const [version, githubExists, zenExists, antigravityExists, proxyConfig] = await Promise.all([
|
|
227
|
+
getPackageVersion(),
|
|
228
|
+
checkTokenExists(),
|
|
229
|
+
checkFileExists(zenAuthPath),
|
|
230
|
+
checkFileExists(antigravityAuthPath),
|
|
231
|
+
getProxyConfig()
|
|
232
|
+
]);
|
|
140
233
|
return {
|
|
141
234
|
version,
|
|
142
235
|
runtime: getRuntimeInfo(),
|
|
143
236
|
paths: {
|
|
144
237
|
APP_DIR: PATHS.APP_DIR,
|
|
145
|
-
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
|
|
238
|
+
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH,
|
|
239
|
+
ZEN_AUTH_PATH: zenAuthPath,
|
|
240
|
+
ANTIGRAVITY_AUTH_PATH: antigravityAuthPath
|
|
146
241
|
},
|
|
147
|
-
|
|
242
|
+
credentials: {
|
|
243
|
+
github: githubExists,
|
|
244
|
+
zen: zenExists,
|
|
245
|
+
antigravity: antigravityExists
|
|
246
|
+
},
|
|
247
|
+
proxy: proxyConfig
|
|
148
248
|
};
|
|
149
249
|
}
|
|
150
250
|
function printDebugInfoPlain(info) {
|
|
151
|
-
|
|
251
|
+
let proxyStatus = "Not configured";
|
|
252
|
+
if (info.proxy) proxyStatus = info.proxy.enabled ? `Enabled (${info.proxy.httpProxy || info.proxy.httpsProxy})` : "Disabled";
|
|
253
|
+
consola.info(`copilot-api-plus debug
|
|
152
254
|
|
|
153
255
|
Version: ${info.version}
|
|
154
256
|
Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
|
|
155
257
|
|
|
156
258
|
Paths:
|
|
157
|
-
|
|
158
|
-
|
|
259
|
+
APP_DIR: ${info.paths.APP_DIR}
|
|
260
|
+
GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
|
|
261
|
+
ZEN_AUTH_PATH: ${info.paths.ZEN_AUTH_PATH}
|
|
262
|
+
ANTIGRAVITY_AUTH_PATH: ${info.paths.ANTIGRAVITY_AUTH_PATH}
|
|
263
|
+
|
|
264
|
+
Credentials:
|
|
265
|
+
GitHub Copilot: ${info.credentials.github ? "✅ Configured" : "❌ Not configured"}
|
|
266
|
+
OpenCode Zen: ${info.credentials.zen ? "✅ Configured" : "❌ Not configured"}
|
|
267
|
+
Google Antigravity: ${info.credentials.antigravity ? "✅ Configured" : "❌ Not configured"}
|
|
159
268
|
|
|
160
|
-
|
|
269
|
+
Proxy: ${proxyStatus}`);
|
|
161
270
|
}
|
|
162
271
|
function printDebugInfoJson(info) {
|
|
163
272
|
console.log(JSON.stringify(info, null, 2));
|
|
@@ -196,6 +305,12 @@ async function runLogout(options) {
|
|
|
196
305
|
consola.info(`Antigravity accounts: ${getAntigravityAuthPath()}`);
|
|
197
306
|
return;
|
|
198
307
|
}
|
|
308
|
+
if (options.github) {
|
|
309
|
+
await clearGithubToken();
|
|
310
|
+
consola.success("Logged out from GitHub Copilot");
|
|
311
|
+
consola.info(`Token file location: ${PATHS.GITHUB_TOKEN_PATH}`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
199
314
|
if (options.zen) {
|
|
200
315
|
await clearZenAuth();
|
|
201
316
|
consola.success("Logged out from OpenCode Zen");
|
|
@@ -246,6 +361,12 @@ const logout = defineCommand({
|
|
|
246
361
|
description: "Clear stored credentials and logout"
|
|
247
362
|
},
|
|
248
363
|
args: {
|
|
364
|
+
github: {
|
|
365
|
+
alias: "g",
|
|
366
|
+
type: "boolean",
|
|
367
|
+
default: false,
|
|
368
|
+
description: "Clear only GitHub Copilot token"
|
|
369
|
+
},
|
|
249
370
|
zen: {
|
|
250
371
|
alias: "z",
|
|
251
372
|
type: "boolean",
|
|
@@ -266,6 +387,7 @@ const logout = defineCommand({
|
|
|
266
387
|
},
|
|
267
388
|
run({ args }) {
|
|
268
389
|
return runLogout({
|
|
390
|
+
github: args.github,
|
|
269
391
|
zen: args.zen,
|
|
270
392
|
antigravity: args.antigravity,
|
|
271
393
|
all: args.all
|
|
@@ -273,83 +395,6 @@ const logout = defineCommand({
|
|
|
273
395
|
}
|
|
274
396
|
});
|
|
275
397
|
|
|
276
|
-
//#endregion
|
|
277
|
-
//#region src/lib/config.ts
|
|
278
|
-
const CONFIG_FILENAME = "config.json";
|
|
279
|
-
/**
|
|
280
|
-
* Get the path to the config file
|
|
281
|
-
*/
|
|
282
|
-
function getConfigPath() {
|
|
283
|
-
return path.join(PATHS.DATA_DIR, CONFIG_FILENAME);
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Load configuration from file
|
|
287
|
-
*/
|
|
288
|
-
async function loadConfig() {
|
|
289
|
-
try {
|
|
290
|
-
const configPath = getConfigPath();
|
|
291
|
-
const content = await fs.readFile(configPath, "utf-8");
|
|
292
|
-
return JSON.parse(content);
|
|
293
|
-
} catch {
|
|
294
|
-
return {};
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Save configuration to file
|
|
299
|
-
*/
|
|
300
|
-
async function saveConfig(config) {
|
|
301
|
-
const configPath = getConfigPath();
|
|
302
|
-
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
303
|
-
consola.debug(`Configuration saved to ${configPath}`);
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Get proxy configuration
|
|
307
|
-
*/
|
|
308
|
-
async function getProxyConfig() {
|
|
309
|
-
return (await loadConfig()).proxy;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Save proxy configuration
|
|
313
|
-
*/
|
|
314
|
-
async function saveProxyConfig(proxyConfig) {
|
|
315
|
-
const config = await loadConfig();
|
|
316
|
-
config.proxy = proxyConfig;
|
|
317
|
-
await saveConfig(config);
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Clear proxy configuration
|
|
321
|
-
*/
|
|
322
|
-
async function clearProxyConfig() {
|
|
323
|
-
const config = await loadConfig();
|
|
324
|
-
delete config.proxy;
|
|
325
|
-
await saveConfig(config);
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Apply saved proxy configuration to environment variables
|
|
329
|
-
* This should be called at startup to restore proxy settings
|
|
330
|
-
*/
|
|
331
|
-
async function applyProxyConfig() {
|
|
332
|
-
const proxyConfig = await getProxyConfig();
|
|
333
|
-
if (!proxyConfig || !proxyConfig.enabled) return false;
|
|
334
|
-
if (proxyConfig.httpProxy) {
|
|
335
|
-
process.env.HTTP_PROXY = proxyConfig.httpProxy;
|
|
336
|
-
process.env.http_proxy = proxyConfig.httpProxy;
|
|
337
|
-
}
|
|
338
|
-
if (proxyConfig.httpsProxy) {
|
|
339
|
-
process.env.HTTPS_PROXY = proxyConfig.httpsProxy;
|
|
340
|
-
process.env.https_proxy = proxyConfig.httpsProxy;
|
|
341
|
-
}
|
|
342
|
-
if (proxyConfig.noProxy) {
|
|
343
|
-
process.env.NO_PROXY = proxyConfig.noProxy;
|
|
344
|
-
process.env.no_proxy = proxyConfig.noProxy;
|
|
345
|
-
}
|
|
346
|
-
consola.info("Proxy configuration loaded from saved settings");
|
|
347
|
-
if (proxyConfig.httpProxy) consola.info(` HTTP_PROXY: ${proxyConfig.httpProxy}`);
|
|
348
|
-
if (proxyConfig.httpsProxy) consola.info(` HTTPS_PROXY: ${proxyConfig.httpsProxy}`);
|
|
349
|
-
if (proxyConfig.noProxy) consola.info(` NO_PROXY: ${proxyConfig.noProxy}`);
|
|
350
|
-
return true;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
398
|
//#endregion
|
|
354
399
|
//#region src/proxy-config.ts
|
|
355
400
|
const proxy = defineCommand({
|
|
@@ -636,6 +681,165 @@ const apiKeyAuthMiddleware = async (c, next) => {
|
|
|
636
681
|
await next();
|
|
637
682
|
};
|
|
638
683
|
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/services/antigravity/stream-parser.ts
|
|
686
|
+
/**
|
|
687
|
+
* Create initial stream state
|
|
688
|
+
*/
|
|
689
|
+
function createStreamState() {
|
|
690
|
+
return {
|
|
691
|
+
buffer: "",
|
|
692
|
+
inputTokens: 0,
|
|
693
|
+
outputTokens: 0,
|
|
694
|
+
contentBlockIndex: 0,
|
|
695
|
+
thinkingBlockStarted: false,
|
|
696
|
+
textBlockStarted: false
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Parse a single SSE line and return the JSON data if valid
|
|
701
|
+
*/
|
|
702
|
+
function parseSSELine(line) {
|
|
703
|
+
if (!line.startsWith("data: ")) return null;
|
|
704
|
+
const data = line.slice(6).trim();
|
|
705
|
+
if (data === "[DONE]" || data === "") return null;
|
|
706
|
+
try {
|
|
707
|
+
return JSON.parse(data);
|
|
708
|
+
} catch {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Extract candidates and usage from parsed data
|
|
714
|
+
*/
|
|
715
|
+
function extractFromData(data) {
|
|
716
|
+
const candidates = data.response?.candidates || data.candidates || [];
|
|
717
|
+
const usage = data.response?.usageMetadata || data.usageMetadata;
|
|
718
|
+
return {
|
|
719
|
+
candidates,
|
|
720
|
+
usage
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Process a single part and emit events
|
|
725
|
+
*/
|
|
726
|
+
function processPart(part, state$1, emit) {
|
|
727
|
+
if (part.thought && part.text) {
|
|
728
|
+
processThinkingPart(part.text, state$1, emit);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (part.text && !part.thought) {
|
|
732
|
+
processTextPart(part.text, state$1, emit);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
if (part.functionCall) processToolPart(part.functionCall, state$1, emit);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Process thinking content
|
|
739
|
+
*/
|
|
740
|
+
function processThinkingPart(text, state$1, emit) {
|
|
741
|
+
if (!state$1.thinkingBlockStarted) {
|
|
742
|
+
emit({
|
|
743
|
+
type: "thinking_start",
|
|
744
|
+
index: state$1.contentBlockIndex
|
|
745
|
+
});
|
|
746
|
+
state$1.thinkingBlockStarted = true;
|
|
747
|
+
}
|
|
748
|
+
emit({
|
|
749
|
+
type: "thinking_delta",
|
|
750
|
+
index: state$1.contentBlockIndex,
|
|
751
|
+
text
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Process text content
|
|
756
|
+
*/
|
|
757
|
+
function processTextPart(text, state$1, emit) {
|
|
758
|
+
if (state$1.thinkingBlockStarted && !state$1.textBlockStarted) {
|
|
759
|
+
emit({
|
|
760
|
+
type: "thinking_stop",
|
|
761
|
+
index: state$1.contentBlockIndex
|
|
762
|
+
});
|
|
763
|
+
state$1.contentBlockIndex++;
|
|
764
|
+
state$1.thinkingBlockStarted = false;
|
|
765
|
+
}
|
|
766
|
+
if (!state$1.textBlockStarted) {
|
|
767
|
+
emit({
|
|
768
|
+
type: "text_start",
|
|
769
|
+
index: state$1.contentBlockIndex
|
|
770
|
+
});
|
|
771
|
+
state$1.textBlockStarted = true;
|
|
772
|
+
}
|
|
773
|
+
emit({
|
|
774
|
+
type: "text_delta",
|
|
775
|
+
index: state$1.contentBlockIndex,
|
|
776
|
+
text
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Process tool/function call
|
|
781
|
+
*/
|
|
782
|
+
function processToolPart(functionCall, state$1, emit) {
|
|
783
|
+
if (state$1.textBlockStarted) {
|
|
784
|
+
emit({
|
|
785
|
+
type: "text_stop",
|
|
786
|
+
index: state$1.contentBlockIndex
|
|
787
|
+
});
|
|
788
|
+
state$1.contentBlockIndex++;
|
|
789
|
+
state$1.textBlockStarted = false;
|
|
790
|
+
} else if (state$1.thinkingBlockStarted) {
|
|
791
|
+
emit({
|
|
792
|
+
type: "thinking_stop",
|
|
793
|
+
index: state$1.contentBlockIndex
|
|
794
|
+
});
|
|
795
|
+
state$1.contentBlockIndex++;
|
|
796
|
+
state$1.thinkingBlockStarted = false;
|
|
797
|
+
}
|
|
798
|
+
emit({
|
|
799
|
+
type: "tool_use",
|
|
800
|
+
index: state$1.contentBlockIndex,
|
|
801
|
+
name: functionCall.name,
|
|
802
|
+
args: functionCall.args
|
|
803
|
+
});
|
|
804
|
+
state$1.contentBlockIndex++;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Handle finish reason and close open blocks
|
|
808
|
+
*/
|
|
809
|
+
function handleFinish(state$1, emit) {
|
|
810
|
+
if (state$1.textBlockStarted) {
|
|
811
|
+
emit({
|
|
812
|
+
type: "text_stop",
|
|
813
|
+
index: state$1.contentBlockIndex
|
|
814
|
+
});
|
|
815
|
+
state$1.textBlockStarted = false;
|
|
816
|
+
} else if (state$1.thinkingBlockStarted) {
|
|
817
|
+
emit({
|
|
818
|
+
type: "thinking_stop",
|
|
819
|
+
index: state$1.contentBlockIndex
|
|
820
|
+
});
|
|
821
|
+
state$1.thinkingBlockStarted = false;
|
|
822
|
+
}
|
|
823
|
+
emit({
|
|
824
|
+
type: "usage",
|
|
825
|
+
inputTokens: state$1.inputTokens,
|
|
826
|
+
outputTokens: state$1.outputTokens
|
|
827
|
+
});
|
|
828
|
+
emit({
|
|
829
|
+
type: "finish",
|
|
830
|
+
stopReason: "end_turn"
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Process chunk and update buffer, returning complete lines
|
|
835
|
+
*/
|
|
836
|
+
function processChunk(chunk, state$1) {
|
|
837
|
+
state$1.buffer += chunk;
|
|
838
|
+
const lines = state$1.buffer.split("\n");
|
|
839
|
+
state$1.buffer = lines.pop() || "";
|
|
840
|
+
return lines;
|
|
841
|
+
}
|
|
842
|
+
|
|
639
843
|
//#endregion
|
|
640
844
|
//#region src/services/antigravity/create-chat-completions.ts
|
|
641
845
|
const ANTIGRAVITY_API_HOST$1 = "daily-cloudcode-pa.sandbox.googleapis.com";
|
|
@@ -647,38 +851,18 @@ const ANTIGRAVITY_USER_AGENT$1 = "antigravity/1.11.3 windows/amd64";
|
|
|
647
851
|
*/
|
|
648
852
|
function convertMessages$1(messages) {
|
|
649
853
|
const contents = [];
|
|
650
|
-
let systemInstruction
|
|
854
|
+
let systemInstruction;
|
|
651
855
|
for (const message of messages) {
|
|
652
856
|
if (message.role === "system") {
|
|
653
|
-
systemInstruction =
|
|
654
|
-
role: "user",
|
|
655
|
-
parts: [{ text: typeof message.content === "string" ? message.content : message.content.map((c) => c.text || "").join("") }]
|
|
656
|
-
};
|
|
857
|
+
systemInstruction = buildSystemInstruction(message.content);
|
|
657
858
|
continue;
|
|
658
859
|
}
|
|
659
860
|
const role = message.role === "assistant" ? "model" : "user";
|
|
660
|
-
|
|
861
|
+
const parts = buildMessageParts(message.content);
|
|
862
|
+
contents.push({
|
|
661
863
|
role,
|
|
662
|
-
parts
|
|
864
|
+
parts
|
|
663
865
|
});
|
|
664
|
-
else {
|
|
665
|
-
const parts = [];
|
|
666
|
-
for (const part of message.content) if (part.type === "text") parts.push({ text: part.text });
|
|
667
|
-
else if (part.type === "image_url" && part.image_url?.url) {
|
|
668
|
-
const url = part.image_url.url;
|
|
669
|
-
if (url.startsWith("data:")) {
|
|
670
|
-
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
671
|
-
if (match) parts.push({ inlineData: {
|
|
672
|
-
mimeType: match[1],
|
|
673
|
-
data: match[2]
|
|
674
|
-
} });
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
contents.push({
|
|
678
|
-
role,
|
|
679
|
-
parts
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
866
|
}
|
|
683
867
|
return {
|
|
684
868
|
contents,
|
|
@@ -686,10 +870,44 @@ function convertMessages$1(messages) {
|
|
|
686
870
|
};
|
|
687
871
|
}
|
|
688
872
|
/**
|
|
873
|
+
* Build system instruction from content
|
|
874
|
+
*/
|
|
875
|
+
function buildSystemInstruction(content) {
|
|
876
|
+
return {
|
|
877
|
+
role: "user",
|
|
878
|
+
parts: [{ text: typeof content === "string" ? content : content.map((c) => c.text || "").join("") }]
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Build message parts from content
|
|
883
|
+
*/
|
|
884
|
+
function buildMessageParts(content) {
|
|
885
|
+
if (typeof content === "string") return [{ text: content }];
|
|
886
|
+
const parts = [];
|
|
887
|
+
for (const part of content) if (part.type === "text") parts.push({ text: part.text });
|
|
888
|
+
else if (part.type === "image_url" && part.image_url?.url) {
|
|
889
|
+
const imageData = parseBase64Image(part.image_url.url);
|
|
890
|
+
if (imageData) parts.push({ inlineData: imageData });
|
|
891
|
+
}
|
|
892
|
+
return parts;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Parse base64 image URL
|
|
896
|
+
*/
|
|
897
|
+
function parseBase64Image(url) {
|
|
898
|
+
if (!url.startsWith("data:")) return null;
|
|
899
|
+
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
900
|
+
if (!match) return null;
|
|
901
|
+
return {
|
|
902
|
+
mimeType: match[1],
|
|
903
|
+
data: match[2]
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
689
907
|
* Convert tools to Antigravity format
|
|
690
908
|
*/
|
|
691
909
|
function convertTools$1(tools) {
|
|
692
|
-
if (!tools || tools.length === 0) return;
|
|
910
|
+
if (!tools || tools.length === 0) return void 0;
|
|
693
911
|
return tools.map((tool) => {
|
|
694
912
|
const t = tool;
|
|
695
913
|
if (t.type === "function" && t.function) return { functionDeclarations: [{
|
|
@@ -701,20 +919,12 @@ function convertTools$1(tools) {
|
|
|
701
919
|
});
|
|
702
920
|
}
|
|
703
921
|
/**
|
|
704
|
-
*
|
|
922
|
+
* Build Antigravity request body
|
|
705
923
|
*/
|
|
706
|
-
|
|
707
|
-
const accessToken = await getValidAccessToken();
|
|
708
|
-
if (!accessToken) return new Response(JSON.stringify({ error: {
|
|
709
|
-
message: "No valid Antigravity access token available. Please run login first.",
|
|
710
|
-
type: "auth_error"
|
|
711
|
-
} }), {
|
|
712
|
-
status: 401,
|
|
713
|
-
headers: { "Content-Type": "application/json" }
|
|
714
|
-
});
|
|
924
|
+
function buildRequestBody(request) {
|
|
715
925
|
const { contents, systemInstruction } = convertMessages$1(request.messages);
|
|
716
926
|
const tools = convertTools$1(request.tools);
|
|
717
|
-
const
|
|
927
|
+
const body = {
|
|
718
928
|
model: request.model,
|
|
719
929
|
contents,
|
|
720
930
|
generationConfig: {
|
|
@@ -724,13 +934,36 @@ async function createAntigravityChatCompletion(request) {
|
|
|
724
934
|
maxOutputTokens: request.max_tokens ?? 8096
|
|
725
935
|
}
|
|
726
936
|
};
|
|
727
|
-
if (systemInstruction)
|
|
728
|
-
if (tools)
|
|
729
|
-
if (isThinkingModel(request.model))
|
|
730
|
-
...
|
|
937
|
+
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
938
|
+
if (tools) body.tools = tools;
|
|
939
|
+
if (isThinkingModel(request.model)) body.generationConfig = {
|
|
940
|
+
...body.generationConfig,
|
|
731
941
|
thinkingConfig: { includeThoughts: true }
|
|
732
942
|
};
|
|
943
|
+
return body;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
946
|
+
* Create error response
|
|
947
|
+
*/
|
|
948
|
+
function createErrorResponse$1(message, type, status, details) {
|
|
949
|
+
const error = {
|
|
950
|
+
message,
|
|
951
|
+
type
|
|
952
|
+
};
|
|
953
|
+
if (details) error.details = details;
|
|
954
|
+
return new Response(JSON.stringify({ error }), {
|
|
955
|
+
status,
|
|
956
|
+
headers: { "Content-Type": "application/json" }
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Create chat completion with Antigravity
|
|
961
|
+
*/
|
|
962
|
+
async function createAntigravityChatCompletion(request) {
|
|
963
|
+
const accessToken = await getValidAccessToken();
|
|
964
|
+
if (!accessToken) return createErrorResponse$1("No valid Antigravity access token available. Please run login first.", "auth_error", 401);
|
|
733
965
|
const endpoint = request.stream ? ANTIGRAVITY_STREAM_URL$1 : ANTIGRAVITY_NO_STREAM_URL$1;
|
|
966
|
+
const body = buildRequestBody(request);
|
|
734
967
|
consola.debug(`Antigravity request to ${endpoint} with model ${request.model}`);
|
|
735
968
|
try {
|
|
736
969
|
const response = await fetch(endpoint, {
|
|
@@ -742,120 +975,46 @@ async function createAntigravityChatCompletion(request) {
|
|
|
742
975
|
"Content-Type": "application/json",
|
|
743
976
|
"Accept-Encoding": "gzip"
|
|
744
977
|
},
|
|
745
|
-
body: JSON.stringify(
|
|
978
|
+
body: JSON.stringify(body)
|
|
746
979
|
});
|
|
747
|
-
if (!response.ok)
|
|
748
|
-
|
|
749
|
-
consola.error(`Antigravity error: ${response.status} ${errorText}`);
|
|
750
|
-
if (response.status === 403) await disableCurrentAccount();
|
|
751
|
-
if (response.status === 429 || response.status === 503) await rotateAccount();
|
|
752
|
-
return new Response(JSON.stringify({ error: {
|
|
753
|
-
message: `Antigravity API error: ${response.status}`,
|
|
754
|
-
type: "api_error",
|
|
755
|
-
details: errorText
|
|
756
|
-
} }), {
|
|
757
|
-
status: response.status,
|
|
758
|
-
headers: { "Content-Type": "application/json" }
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
if (request.stream) return transformStreamResponse(response, request.model);
|
|
762
|
-
else return transformNonStreamResponse(response, request.model);
|
|
980
|
+
if (!response.ok) return await handleApiError$1(response);
|
|
981
|
+
return request.stream ? transformStreamResponse$1(response, request.model) : await transformNonStreamResponse$1(response, request.model);
|
|
763
982
|
} catch (error) {
|
|
764
983
|
consola.error("Antigravity request error:", error);
|
|
765
|
-
return
|
|
766
|
-
message: `Request failed: ${error}`,
|
|
767
|
-
type: "request_error"
|
|
768
|
-
} }), {
|
|
769
|
-
status: 500,
|
|
770
|
-
headers: { "Content-Type": "application/json" }
|
|
771
|
-
});
|
|
984
|
+
return createErrorResponse$1(`Request failed: ${String(error)}`, "request_error", 500);
|
|
772
985
|
}
|
|
773
986
|
}
|
|
774
987
|
/**
|
|
988
|
+
* Handle API error response
|
|
989
|
+
*/
|
|
990
|
+
async function handleApiError$1(response) {
|
|
991
|
+
const errorText = await response.text();
|
|
992
|
+
consola.error(`Antigravity error: ${response.status} ${errorText}`);
|
|
993
|
+
if (response.status === 403) await disableCurrentAccount();
|
|
994
|
+
if (response.status === 429 || response.status === 503) await rotateAccount();
|
|
995
|
+
return createErrorResponse$1(`Antigravity API error: ${response.status}`, "api_error", response.status, errorText);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Generate request ID
|
|
999
|
+
*/
|
|
1000
|
+
function generateRequestId() {
|
|
1001
|
+
return `chatcmpl-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
775
1004
|
* Transform Antigravity stream response to OpenAI format
|
|
776
1005
|
*/
|
|
777
|
-
function transformStreamResponse(response, model) {
|
|
1006
|
+
function transformStreamResponse$1(response, model) {
|
|
778
1007
|
const reader = response.body?.getReader();
|
|
779
1008
|
if (!reader) return new Response("No response body", { status: 500 });
|
|
780
|
-
const encoder = new TextEncoder();
|
|
1009
|
+
const encoder$1 = new TextEncoder();
|
|
781
1010
|
const decoder = new TextDecoder();
|
|
1011
|
+
const requestId = generateRequestId();
|
|
782
1012
|
const stream = new ReadableStream({ async start(controller) {
|
|
783
|
-
|
|
784
|
-
const requestId = `chatcmpl-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
1013
|
+
const state$1 = createStreamState();
|
|
785
1014
|
try {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
790
|
-
controller.close();
|
|
791
|
-
break;
|
|
792
|
-
}
|
|
793
|
-
buffer += decoder.decode(value, { stream: true });
|
|
794
|
-
const lines = buffer.split("\n");
|
|
795
|
-
buffer = lines.pop() || "";
|
|
796
|
-
for (const line of lines) if (line.startsWith("data: ")) {
|
|
797
|
-
const data = line.slice(6).trim();
|
|
798
|
-
if (data === "[DONE]" || data === "") continue;
|
|
799
|
-
try {
|
|
800
|
-
const parsed = JSON.parse(data);
|
|
801
|
-
const candidate = (parsed.response?.candidates || parsed.candidates)?.[0];
|
|
802
|
-
const parts = candidate?.content?.parts || [];
|
|
803
|
-
for (const part of parts) {
|
|
804
|
-
if (part.thought && part.text) {
|
|
805
|
-
const chunk = {
|
|
806
|
-
id: requestId,
|
|
807
|
-
object: "chat.completion.chunk",
|
|
808
|
-
created: Math.floor(Date.now() / 1e3),
|
|
809
|
-
model,
|
|
810
|
-
choices: [{
|
|
811
|
-
index: 0,
|
|
812
|
-
delta: { reasoning_content: part.text },
|
|
813
|
-
finish_reason: null
|
|
814
|
-
}]
|
|
815
|
-
};
|
|
816
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
|
817
|
-
continue;
|
|
818
|
-
}
|
|
819
|
-
if (part.text) {
|
|
820
|
-
const chunk = {
|
|
821
|
-
id: requestId,
|
|
822
|
-
object: "chat.completion.chunk",
|
|
823
|
-
created: Math.floor(Date.now() / 1e3),
|
|
824
|
-
model,
|
|
825
|
-
choices: [{
|
|
826
|
-
index: 0,
|
|
827
|
-
delta: { content: part.text },
|
|
828
|
-
finish_reason: candidate?.finishReason === "STOP" ? "stop" : null
|
|
829
|
-
}]
|
|
830
|
-
};
|
|
831
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
|
832
|
-
}
|
|
833
|
-
if (part.functionCall) {
|
|
834
|
-
const chunk = {
|
|
835
|
-
id: requestId,
|
|
836
|
-
object: "chat.completion.chunk",
|
|
837
|
-
created: Math.floor(Date.now() / 1e3),
|
|
838
|
-
model,
|
|
839
|
-
choices: [{
|
|
840
|
-
index: 0,
|
|
841
|
-
delta: { tool_calls: [{
|
|
842
|
-
index: 0,
|
|
843
|
-
id: `call_${Date.now()}`,
|
|
844
|
-
type: "function",
|
|
845
|
-
function: {
|
|
846
|
-
name: part.functionCall.name,
|
|
847
|
-
arguments: JSON.stringify(part.functionCall.args)
|
|
848
|
-
}
|
|
849
|
-
}] },
|
|
850
|
-
finish_reason: null
|
|
851
|
-
}]
|
|
852
|
-
};
|
|
853
|
-
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
} catch {}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
1015
|
+
await processOpenAIStream(reader, decoder, state$1, controller, encoder$1, requestId, model);
|
|
1016
|
+
controller.enqueue(encoder$1.encode("data: [DONE]\n\n"));
|
|
1017
|
+
controller.close();
|
|
859
1018
|
} catch (error) {
|
|
860
1019
|
consola.error("Stream transform error:", error);
|
|
861
1020
|
controller.error(error);
|
|
@@ -868,27 +1027,78 @@ function transformStreamResponse(response, model) {
|
|
|
868
1027
|
} });
|
|
869
1028
|
}
|
|
870
1029
|
/**
|
|
871
|
-
*
|
|
1030
|
+
* Process stream and emit OpenAI format chunks
|
|
872
1031
|
*/
|
|
873
|
-
async function
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
arguments: JSON.stringify(part.functionCall.args)
|
|
1032
|
+
async function processOpenAIStream(reader, decoder, state$1, controller, encoder$1, requestId, model) {
|
|
1033
|
+
while (true) {
|
|
1034
|
+
const { done, value } = await reader.read();
|
|
1035
|
+
if (done) break;
|
|
1036
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1037
|
+
const lines = processChunk(chunk, state$1);
|
|
1038
|
+
for (const line of lines) {
|
|
1039
|
+
const data = parseSSELine(line);
|
|
1040
|
+
if (!data) continue;
|
|
1041
|
+
const { candidates } = extractFromData(data);
|
|
1042
|
+
const candidate = candidates[0];
|
|
1043
|
+
const parts = candidate?.content?.parts ?? [];
|
|
1044
|
+
for (const part of parts) {
|
|
1045
|
+
const chunkData = buildOpenAIChunk(part, requestId, model, candidate?.finishReason);
|
|
1046
|
+
if (chunkData) controller.enqueue(encoder$1.encode(`data: ${JSON.stringify(chunkData)}\n\n`));
|
|
889
1047
|
}
|
|
890
|
-
}
|
|
1048
|
+
}
|
|
891
1049
|
}
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Build OpenAI format chunk from part
|
|
1053
|
+
*/
|
|
1054
|
+
function buildOpenAIChunk(part, requestId, model, finishReason) {
|
|
1055
|
+
const baseChunk = {
|
|
1056
|
+
id: requestId,
|
|
1057
|
+
object: "chat.completion.chunk",
|
|
1058
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1059
|
+
model
|
|
1060
|
+
};
|
|
1061
|
+
if (part.thought && part.text) return {
|
|
1062
|
+
...baseChunk,
|
|
1063
|
+
choices: [{
|
|
1064
|
+
index: 0,
|
|
1065
|
+
delta: { reasoning_content: part.text },
|
|
1066
|
+
finish_reason: null
|
|
1067
|
+
}]
|
|
1068
|
+
};
|
|
1069
|
+
if (part.text && !part.thought) return {
|
|
1070
|
+
...baseChunk,
|
|
1071
|
+
choices: [{
|
|
1072
|
+
index: 0,
|
|
1073
|
+
delta: { content: part.text },
|
|
1074
|
+
finish_reason: finishReason === "STOP" ? "stop" : null
|
|
1075
|
+
}]
|
|
1076
|
+
};
|
|
1077
|
+
if (part.functionCall) return {
|
|
1078
|
+
...baseChunk,
|
|
1079
|
+
choices: [{
|
|
1080
|
+
index: 0,
|
|
1081
|
+
delta: { tool_calls: [{
|
|
1082
|
+
index: 0,
|
|
1083
|
+
id: `call_${Date.now()}`,
|
|
1084
|
+
type: "function",
|
|
1085
|
+
function: {
|
|
1086
|
+
name: part.functionCall.name,
|
|
1087
|
+
arguments: JSON.stringify(part.functionCall.args)
|
|
1088
|
+
}
|
|
1089
|
+
}] },
|
|
1090
|
+
finish_reason: null
|
|
1091
|
+
}]
|
|
1092
|
+
};
|
|
1093
|
+
return null;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Transform Antigravity non-stream response to OpenAI format
|
|
1097
|
+
*/
|
|
1098
|
+
async function transformNonStreamResponse$1(response, model) {
|
|
1099
|
+
const data = await response.json();
|
|
1100
|
+
const parts = (data.candidates?.[0])?.content?.parts ?? [];
|
|
1101
|
+
const { content, reasoningContent, toolCalls } = extractNonStreamContent(parts);
|
|
892
1102
|
const message = {
|
|
893
1103
|
role: "assistant",
|
|
894
1104
|
content: content || null
|
|
@@ -896,23 +1106,48 @@ async function transformNonStreamResponse(response, model) {
|
|
|
896
1106
|
if (reasoningContent) message.reasoning_content = reasoningContent;
|
|
897
1107
|
if (toolCalls.length > 0) message.tool_calls = toolCalls;
|
|
898
1108
|
const openaiResponse = {
|
|
899
|
-
id:
|
|
1109
|
+
id: generateRequestId(),
|
|
900
1110
|
object: "chat.completion",
|
|
901
1111
|
created: Math.floor(Date.now() / 1e3),
|
|
902
1112
|
model,
|
|
903
1113
|
choices: [{
|
|
904
1114
|
index: 0,
|
|
905
1115
|
message,
|
|
906
|
-
finish_reason:
|
|
1116
|
+
finish_reason: "stop"
|
|
907
1117
|
}],
|
|
908
1118
|
usage: {
|
|
909
|
-
prompt_tokens: data.usageMetadata?.promptTokenCount
|
|
910
|
-
completion_tokens: data.usageMetadata?.candidatesTokenCount
|
|
911
|
-
total_tokens: data.usageMetadata?.totalTokenCount
|
|
1119
|
+
prompt_tokens: data.usageMetadata?.promptTokenCount ?? 0,
|
|
1120
|
+
completion_tokens: data.usageMetadata?.candidatesTokenCount ?? 0,
|
|
1121
|
+
total_tokens: data.usageMetadata?.totalTokenCount ?? 0
|
|
912
1122
|
}
|
|
913
1123
|
};
|
|
914
1124
|
return new Response(JSON.stringify(openaiResponse), { headers: { "Content-Type": "application/json" } });
|
|
915
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Extract content from non-stream response parts
|
|
1128
|
+
*/
|
|
1129
|
+
function extractNonStreamContent(parts) {
|
|
1130
|
+
let content = "";
|
|
1131
|
+
let reasoningContent = "";
|
|
1132
|
+
const toolCalls = [];
|
|
1133
|
+
for (const part of parts) {
|
|
1134
|
+
if (part.thought && part.text) reasoningContent += part.text;
|
|
1135
|
+
else if (part.text) content += part.text;
|
|
1136
|
+
if (part.functionCall) toolCalls.push({
|
|
1137
|
+
id: `call_${Date.now()}`,
|
|
1138
|
+
type: "function",
|
|
1139
|
+
function: {
|
|
1140
|
+
name: part.functionCall.name,
|
|
1141
|
+
arguments: JSON.stringify(part.functionCall.args)
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
return {
|
|
1146
|
+
content,
|
|
1147
|
+
reasoningContent,
|
|
1148
|
+
toolCalls
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
916
1151
|
|
|
917
1152
|
//#endregion
|
|
918
1153
|
//#region src/routes/antigravity/chat-completions/route.ts
|
|
@@ -932,6 +1167,166 @@ app$1.post("/", async (c) => {
|
|
|
932
1167
|
});
|
|
933
1168
|
const antigravityChatCompletionsRoute = app$1;
|
|
934
1169
|
|
|
1170
|
+
//#endregion
|
|
1171
|
+
//#region src/services/antigravity/anthropic-events.ts
|
|
1172
|
+
/**
|
|
1173
|
+
* Anthropic SSE Event Builder
|
|
1174
|
+
*
|
|
1175
|
+
* Builds Anthropic-compatible SSE events for streaming responses.
|
|
1176
|
+
* Extracted for better code organization and reusability.
|
|
1177
|
+
*/
|
|
1178
|
+
const encoder = new TextEncoder();
|
|
1179
|
+
/**
|
|
1180
|
+
* Create message_start event
|
|
1181
|
+
*/
|
|
1182
|
+
function createMessageStart(messageId, model) {
|
|
1183
|
+
const event = {
|
|
1184
|
+
type: "message_start",
|
|
1185
|
+
message: {
|
|
1186
|
+
id: messageId,
|
|
1187
|
+
type: "message",
|
|
1188
|
+
role: "assistant",
|
|
1189
|
+
content: [],
|
|
1190
|
+
model,
|
|
1191
|
+
stop_reason: null,
|
|
1192
|
+
stop_sequence: null,
|
|
1193
|
+
usage: {
|
|
1194
|
+
input_tokens: 0,
|
|
1195
|
+
output_tokens: 0
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
return encoder.encode(`event: message_start\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Create message_stop event
|
|
1203
|
+
*/
|
|
1204
|
+
function createMessageStop() {
|
|
1205
|
+
return encoder.encode(`event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}\n\n`);
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Create content_block_start event for thinking
|
|
1209
|
+
*/
|
|
1210
|
+
function createThinkingBlockStart(index) {
|
|
1211
|
+
const event = {
|
|
1212
|
+
type: "content_block_start",
|
|
1213
|
+
index,
|
|
1214
|
+
content_block: {
|
|
1215
|
+
type: "thinking",
|
|
1216
|
+
thinking: ""
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
return encoder.encode(`event: content_block_start\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Create content_block_delta event for thinking
|
|
1223
|
+
*/
|
|
1224
|
+
function createThinkingDelta(index, text) {
|
|
1225
|
+
const event = {
|
|
1226
|
+
type: "content_block_delta",
|
|
1227
|
+
index,
|
|
1228
|
+
delta: {
|
|
1229
|
+
type: "thinking_delta",
|
|
1230
|
+
thinking: text
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
return encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Create content_block_start event for text
|
|
1237
|
+
*/
|
|
1238
|
+
function createTextBlockStart(index) {
|
|
1239
|
+
const event = {
|
|
1240
|
+
type: "content_block_start",
|
|
1241
|
+
index,
|
|
1242
|
+
content_block: {
|
|
1243
|
+
type: "text",
|
|
1244
|
+
text: ""
|
|
1245
|
+
}
|
|
1246
|
+
};
|
|
1247
|
+
return encoder.encode(`event: content_block_start\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Create content_block_delta event for text
|
|
1251
|
+
*/
|
|
1252
|
+
function createTextDelta(index, text) {
|
|
1253
|
+
const event = {
|
|
1254
|
+
type: "content_block_delta",
|
|
1255
|
+
index,
|
|
1256
|
+
delta: {
|
|
1257
|
+
type: "text_delta",
|
|
1258
|
+
text
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
return encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Create content_block_stop event
|
|
1265
|
+
*/
|
|
1266
|
+
function createBlockStop(index) {
|
|
1267
|
+
const event = {
|
|
1268
|
+
type: "content_block_stop",
|
|
1269
|
+
index
|
|
1270
|
+
};
|
|
1271
|
+
return encoder.encode(`event: content_block_stop\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Create content_block_start event for tool_use
|
|
1275
|
+
*/
|
|
1276
|
+
function createToolBlockStart(index, toolId, name) {
|
|
1277
|
+
const event = {
|
|
1278
|
+
type: "content_block_start",
|
|
1279
|
+
index,
|
|
1280
|
+
content_block: {
|
|
1281
|
+
type: "tool_use",
|
|
1282
|
+
id: toolId,
|
|
1283
|
+
name,
|
|
1284
|
+
input: {}
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
return encoder.encode(`event: content_block_start\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Create content_block_delta event for tool input
|
|
1291
|
+
*/
|
|
1292
|
+
function createToolDelta(index, args) {
|
|
1293
|
+
const event = {
|
|
1294
|
+
type: "content_block_delta",
|
|
1295
|
+
index,
|
|
1296
|
+
delta: {
|
|
1297
|
+
type: "input_json_delta",
|
|
1298
|
+
partial_json: JSON.stringify(args)
|
|
1299
|
+
}
|
|
1300
|
+
};
|
|
1301
|
+
return encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Create message_delta event with stop reason and usage
|
|
1305
|
+
*/
|
|
1306
|
+
function createMessageDelta(stopReason, outputTokens) {
|
|
1307
|
+
const event = {
|
|
1308
|
+
type: "message_delta",
|
|
1309
|
+
delta: {
|
|
1310
|
+
stop_reason: stopReason,
|
|
1311
|
+
stop_sequence: null
|
|
1312
|
+
},
|
|
1313
|
+
usage: { output_tokens: outputTokens }
|
|
1314
|
+
};
|
|
1315
|
+
return encoder.encode(`event: message_delta\ndata: ${JSON.stringify(event)}\n\n`);
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Generate a unique message ID
|
|
1319
|
+
*/
|
|
1320
|
+
function generateMessageId() {
|
|
1321
|
+
return `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Generate a unique tool ID
|
|
1325
|
+
*/
|
|
1326
|
+
function generateToolId() {
|
|
1327
|
+
return `toolu_${Date.now()}`;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
935
1330
|
//#endregion
|
|
936
1331
|
//#region src/services/antigravity/create-messages.ts
|
|
937
1332
|
const ANTIGRAVITY_API_HOST = "daily-cloudcode-pa.sandbox.googleapis.com";
|
|
@@ -943,29 +1338,18 @@ const ANTIGRAVITY_USER_AGENT = "antigravity/1.11.3 windows/amd64";
|
|
|
943
1338
|
*/
|
|
944
1339
|
function convertMessages(messages, system) {
|
|
945
1340
|
const contents = [];
|
|
946
|
-
let systemInstruction
|
|
1341
|
+
let systemInstruction;
|
|
947
1342
|
if (system) systemInstruction = {
|
|
948
1343
|
role: "user",
|
|
949
1344
|
parts: [{ text: system }]
|
|
950
1345
|
};
|
|
951
1346
|
for (const message of messages) {
|
|
952
1347
|
const role = message.role === "assistant" ? "model" : "user";
|
|
953
|
-
|
|
1348
|
+
const parts = buildParts(message.content);
|
|
1349
|
+
if (parts.length > 0) contents.push({
|
|
954
1350
|
role,
|
|
955
|
-
parts
|
|
1351
|
+
parts
|
|
956
1352
|
});
|
|
957
|
-
else {
|
|
958
|
-
const parts = [];
|
|
959
|
-
for (const block of message.content) if (block.type === "text" && block.text) parts.push({ text: block.text });
|
|
960
|
-
else if (block.type === "image" && block.source) parts.push({ inlineData: {
|
|
961
|
-
mimeType: block.source.media_type,
|
|
962
|
-
data: block.source.data
|
|
963
|
-
} });
|
|
964
|
-
if (parts.length > 0) contents.push({
|
|
965
|
-
role,
|
|
966
|
-
parts
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
1353
|
}
|
|
970
1354
|
return {
|
|
971
1355
|
contents,
|
|
@@ -973,10 +1357,23 @@ function convertMessages(messages, system) {
|
|
|
973
1357
|
};
|
|
974
1358
|
}
|
|
975
1359
|
/**
|
|
1360
|
+
* Build parts array from message content
|
|
1361
|
+
*/
|
|
1362
|
+
function buildParts(content) {
|
|
1363
|
+
if (typeof content === "string") return [{ text: content }];
|
|
1364
|
+
const parts = [];
|
|
1365
|
+
for (const block of content) if (block.type === "text" && block.text) parts.push({ text: block.text });
|
|
1366
|
+
else if (block.type === "image" && block.source) parts.push({ inlineData: {
|
|
1367
|
+
mimeType: block.source.media_type,
|
|
1368
|
+
data: block.source.data
|
|
1369
|
+
} });
|
|
1370
|
+
return parts;
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
976
1373
|
* Convert tools to Antigravity format
|
|
977
1374
|
*/
|
|
978
1375
|
function convertTools(tools) {
|
|
979
|
-
if (!tools || tools.length === 0) return;
|
|
1376
|
+
if (!tools || tools.length === 0) return void 0;
|
|
980
1377
|
return tools.map((tool) => {
|
|
981
1378
|
const t = tool;
|
|
982
1379
|
return { functionDeclarations: [{
|
|
@@ -987,23 +1384,12 @@ function convertTools(tools) {
|
|
|
987
1384
|
});
|
|
988
1385
|
}
|
|
989
1386
|
/**
|
|
990
|
-
*
|
|
1387
|
+
* Build Antigravity request body
|
|
991
1388
|
*/
|
|
992
|
-
|
|
993
|
-
const accessToken = await getValidAccessToken();
|
|
994
|
-
if (!accessToken) return new Response(JSON.stringify({
|
|
995
|
-
type: "error",
|
|
996
|
-
error: {
|
|
997
|
-
type: "authentication_error",
|
|
998
|
-
message: "No valid Antigravity access token available. Please run login first."
|
|
999
|
-
}
|
|
1000
|
-
}), {
|
|
1001
|
-
status: 401,
|
|
1002
|
-
headers: { "Content-Type": "application/json" }
|
|
1003
|
-
});
|
|
1389
|
+
function buildAntigravityRequest(request) {
|
|
1004
1390
|
const { contents, systemInstruction } = convertMessages(request.messages, request.system);
|
|
1005
1391
|
const tools = convertTools(request.tools);
|
|
1006
|
-
const
|
|
1392
|
+
const body = {
|
|
1007
1393
|
model: request.model,
|
|
1008
1394
|
contents,
|
|
1009
1395
|
generationConfig: {
|
|
@@ -1013,13 +1399,37 @@ async function createAntigravityMessages(request) {
|
|
|
1013
1399
|
maxOutputTokens: request.max_tokens ?? 8096
|
|
1014
1400
|
}
|
|
1015
1401
|
};
|
|
1016
|
-
if (systemInstruction)
|
|
1017
|
-
if (tools)
|
|
1018
|
-
if (isThinkingModel(request.model))
|
|
1019
|
-
...
|
|
1402
|
+
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
1403
|
+
if (tools) body.tools = tools;
|
|
1404
|
+
if (isThinkingModel(request.model)) body.generationConfig = {
|
|
1405
|
+
...body.generationConfig,
|
|
1020
1406
|
thinkingConfig: { includeThoughts: true }
|
|
1021
1407
|
};
|
|
1408
|
+
return body;
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Create error response
|
|
1412
|
+
*/
|
|
1413
|
+
function createErrorResponse(type, message, status) {
|
|
1414
|
+
return new Response(JSON.stringify({
|
|
1415
|
+
type: "error",
|
|
1416
|
+
error: {
|
|
1417
|
+
type,
|
|
1418
|
+
message
|
|
1419
|
+
}
|
|
1420
|
+
}), {
|
|
1421
|
+
status,
|
|
1422
|
+
headers: { "Content-Type": "application/json" }
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Create Anthropic-compatible message response using Antigravity
|
|
1427
|
+
*/
|
|
1428
|
+
async function createAntigravityMessages(request) {
|
|
1429
|
+
const accessToken = await getValidAccessToken();
|
|
1430
|
+
if (!accessToken) return createErrorResponse("authentication_error", "No valid Antigravity access token available. Please run login first.", 401);
|
|
1022
1431
|
const endpoint = request.stream ? ANTIGRAVITY_STREAM_URL : ANTIGRAVITY_NO_STREAM_URL;
|
|
1432
|
+
const body = buildAntigravityRequest(request);
|
|
1023
1433
|
consola.debug(`Antigravity messages request to ${endpoint} with model ${request.model}`);
|
|
1024
1434
|
try {
|
|
1025
1435
|
const response = await fetch(endpoint, {
|
|
@@ -1031,212 +1441,84 @@ async function createAntigravityMessages(request) {
|
|
|
1031
1441
|
"Content-Type": "application/json",
|
|
1032
1442
|
"Accept-Encoding": "gzip"
|
|
1033
1443
|
},
|
|
1034
|
-
body: JSON.stringify(
|
|
1444
|
+
body: JSON.stringify(body)
|
|
1035
1445
|
});
|
|
1036
|
-
if (!response.ok)
|
|
1037
|
-
|
|
1038
|
-
consola.error(`Antigravity error: ${response.status} ${errorText}`);
|
|
1039
|
-
if (response.status === 403) await disableCurrentAccount();
|
|
1040
|
-
if (response.status === 429 || response.status === 503) await rotateAccount();
|
|
1041
|
-
return new Response(JSON.stringify({
|
|
1042
|
-
type: "error",
|
|
1043
|
-
error: {
|
|
1044
|
-
type: "api_error",
|
|
1045
|
-
message: `Antigravity API error: ${response.status}`
|
|
1046
|
-
}
|
|
1047
|
-
}), {
|
|
1048
|
-
status: response.status,
|
|
1049
|
-
headers: { "Content-Type": "application/json" }
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
if (request.stream) return transformStreamToAnthropic(response, request.model);
|
|
1053
|
-
else return transformNonStreamToAnthropic(response, request.model);
|
|
1446
|
+
if (!response.ok) return await handleApiError(response);
|
|
1447
|
+
return request.stream ? transformStreamResponse(response, request.model) : await transformNonStreamResponse(response, request.model);
|
|
1054
1448
|
} catch (error) {
|
|
1055
1449
|
consola.error("Antigravity messages request error:", error);
|
|
1056
|
-
return
|
|
1057
|
-
type: "error",
|
|
1058
|
-
error: {
|
|
1059
|
-
type: "api_error",
|
|
1060
|
-
message: `Request failed: ${error}`
|
|
1061
|
-
}
|
|
1062
|
-
}), {
|
|
1063
|
-
status: 500,
|
|
1064
|
-
headers: { "Content-Type": "application/json" }
|
|
1065
|
-
});
|
|
1450
|
+
return createErrorResponse("api_error", `Request failed: ${String(error)}`, 500);
|
|
1066
1451
|
}
|
|
1067
1452
|
}
|
|
1068
1453
|
/**
|
|
1454
|
+
* Handle API error response
|
|
1455
|
+
*/
|
|
1456
|
+
async function handleApiError(response) {
|
|
1457
|
+
const errorText = await response.text();
|
|
1458
|
+
consola.error(`Antigravity error: ${response.status} ${errorText}`);
|
|
1459
|
+
if (response.status === 403) await disableCurrentAccount();
|
|
1460
|
+
if (response.status === 429 || response.status === 503) await rotateAccount();
|
|
1461
|
+
return createErrorResponse("api_error", `Antigravity API error: ${response.status}`, response.status);
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Emit SSE event to controller based on stream event type
|
|
1465
|
+
*/
|
|
1466
|
+
function emitSSEEvent(event, controller, state$1) {
|
|
1467
|
+
switch (event.type) {
|
|
1468
|
+
case "thinking_start":
|
|
1469
|
+
controller.enqueue(createThinkingBlockStart(event.index));
|
|
1470
|
+
break;
|
|
1471
|
+
case "thinking_delta":
|
|
1472
|
+
controller.enqueue(createThinkingDelta(event.index, event.text));
|
|
1473
|
+
break;
|
|
1474
|
+
case "thinking_stop":
|
|
1475
|
+
controller.enqueue(createBlockStop(event.index));
|
|
1476
|
+
break;
|
|
1477
|
+
case "text_start":
|
|
1478
|
+
controller.enqueue(createTextBlockStart(event.index));
|
|
1479
|
+
break;
|
|
1480
|
+
case "text_delta":
|
|
1481
|
+
controller.enqueue(createTextDelta(event.index, event.text));
|
|
1482
|
+
break;
|
|
1483
|
+
case "text_stop":
|
|
1484
|
+
controller.enqueue(createBlockStop(event.index));
|
|
1485
|
+
break;
|
|
1486
|
+
case "tool_use":
|
|
1487
|
+
emitToolUseEvents(event, controller);
|
|
1488
|
+
break;
|
|
1489
|
+
case "usage":
|
|
1490
|
+
state$1.inputTokens = event.inputTokens;
|
|
1491
|
+
state$1.outputTokens = event.outputTokens;
|
|
1492
|
+
break;
|
|
1493
|
+
case "finish":
|
|
1494
|
+
controller.enqueue(createMessageDelta(event.stopReason, state$1.outputTokens));
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Emit tool use events
|
|
1500
|
+
*/
|
|
1501
|
+
function emitToolUseEvents(event, controller) {
|
|
1502
|
+
const toolId = generateToolId();
|
|
1503
|
+
controller.enqueue(createToolBlockStart(event.index, toolId, event.name));
|
|
1504
|
+
controller.enqueue(createToolDelta(event.index, event.args));
|
|
1505
|
+
controller.enqueue(createBlockStop(event.index));
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1069
1508
|
* Transform Antigravity stream response to Anthropic format
|
|
1070
1509
|
*/
|
|
1071
|
-
function
|
|
1510
|
+
function transformStreamResponse(response, model) {
|
|
1072
1511
|
const reader = response.body?.getReader();
|
|
1073
1512
|
if (!reader) return new Response("No response body", { status: 500 });
|
|
1074
|
-
const encoder = new TextEncoder();
|
|
1075
1513
|
const decoder = new TextDecoder();
|
|
1514
|
+
const messageId = generateMessageId();
|
|
1076
1515
|
const stream = new ReadableStream({ async start(controller) {
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
let inputTokens = 0;
|
|
1080
|
-
let outputTokens = 0;
|
|
1081
|
-
let contentBlockIndex = 0;
|
|
1082
|
-
let thinkingBlockStarted = false;
|
|
1083
|
-
let textBlockStarted = false;
|
|
1084
|
-
const messageStart = {
|
|
1085
|
-
type: "message_start",
|
|
1086
|
-
message: {
|
|
1087
|
-
id: messageId,
|
|
1088
|
-
type: "message",
|
|
1089
|
-
role: "assistant",
|
|
1090
|
-
content: [],
|
|
1091
|
-
model,
|
|
1092
|
-
stop_reason: null,
|
|
1093
|
-
stop_sequence: null,
|
|
1094
|
-
usage: {
|
|
1095
|
-
input_tokens: 0,
|
|
1096
|
-
output_tokens: 0
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
};
|
|
1100
|
-
controller.enqueue(encoder.encode(`event: message_start\ndata: ${JSON.stringify(messageStart)}\n\n`));
|
|
1516
|
+
const state$1 = createStreamState();
|
|
1517
|
+
controller.enqueue(createMessageStart(messageId, model));
|
|
1101
1518
|
try {
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
controller.enqueue(encoder.encode(`event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}\n\n`));
|
|
1106
|
-
controller.close();
|
|
1107
|
-
break;
|
|
1108
|
-
}
|
|
1109
|
-
buffer += decoder.decode(value, { stream: true });
|
|
1110
|
-
const lines = buffer.split("\n");
|
|
1111
|
-
buffer = lines.pop() || "";
|
|
1112
|
-
for (const line of lines) if (line.startsWith("data: ")) {
|
|
1113
|
-
const data = line.slice(6).trim();
|
|
1114
|
-
if (data === "[DONE]" || data === "") continue;
|
|
1115
|
-
try {
|
|
1116
|
-
const parsed = JSON.parse(data);
|
|
1117
|
-
const candidate = (parsed.response?.candidates || parsed.candidates)?.[0];
|
|
1118
|
-
const parts = candidate?.content?.parts || [];
|
|
1119
|
-
const usage = parsed.response?.usageMetadata || parsed.usageMetadata;
|
|
1120
|
-
if (usage) {
|
|
1121
|
-
inputTokens = usage.promptTokenCount || inputTokens;
|
|
1122
|
-
outputTokens = usage.candidatesTokenCount || outputTokens;
|
|
1123
|
-
}
|
|
1124
|
-
for (const part of parts) {
|
|
1125
|
-
if (part.thought && part.text) {
|
|
1126
|
-
if (!thinkingBlockStarted) {
|
|
1127
|
-
const blockStart = {
|
|
1128
|
-
type: "content_block_start",
|
|
1129
|
-
index: contentBlockIndex,
|
|
1130
|
-
content_block: {
|
|
1131
|
-
type: "thinking",
|
|
1132
|
-
thinking: ""
|
|
1133
|
-
}
|
|
1134
|
-
};
|
|
1135
|
-
controller.enqueue(encoder.encode(`event: content_block_start\ndata: ${JSON.stringify(blockStart)}\n\n`));
|
|
1136
|
-
thinkingBlockStarted = true;
|
|
1137
|
-
}
|
|
1138
|
-
const thinkingDelta = {
|
|
1139
|
-
type: "content_block_delta",
|
|
1140
|
-
index: contentBlockIndex,
|
|
1141
|
-
delta: {
|
|
1142
|
-
type: "thinking_delta",
|
|
1143
|
-
thinking: part.text
|
|
1144
|
-
}
|
|
1145
|
-
};
|
|
1146
|
-
controller.enqueue(encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify(thinkingDelta)}\n\n`));
|
|
1147
|
-
continue;
|
|
1148
|
-
}
|
|
1149
|
-
if (part.text && !part.thought) {
|
|
1150
|
-
if (thinkingBlockStarted && !textBlockStarted) {
|
|
1151
|
-
const blockStop = {
|
|
1152
|
-
type: "content_block_stop",
|
|
1153
|
-
index: contentBlockIndex
|
|
1154
|
-
};
|
|
1155
|
-
controller.enqueue(encoder.encode(`event: content_block_stop\ndata: ${JSON.stringify(blockStop)}\n\n`));
|
|
1156
|
-
contentBlockIndex++;
|
|
1157
|
-
}
|
|
1158
|
-
if (!textBlockStarted) {
|
|
1159
|
-
const blockStart = {
|
|
1160
|
-
type: "content_block_start",
|
|
1161
|
-
index: contentBlockIndex,
|
|
1162
|
-
content_block: {
|
|
1163
|
-
type: "text",
|
|
1164
|
-
text: ""
|
|
1165
|
-
}
|
|
1166
|
-
};
|
|
1167
|
-
controller.enqueue(encoder.encode(`event: content_block_start\ndata: ${JSON.stringify(blockStart)}\n\n`));
|
|
1168
|
-
textBlockStarted = true;
|
|
1169
|
-
}
|
|
1170
|
-
const textDelta = {
|
|
1171
|
-
type: "content_block_delta",
|
|
1172
|
-
index: contentBlockIndex,
|
|
1173
|
-
delta: {
|
|
1174
|
-
type: "text_delta",
|
|
1175
|
-
text: part.text
|
|
1176
|
-
}
|
|
1177
|
-
};
|
|
1178
|
-
controller.enqueue(encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify(textDelta)}\n\n`));
|
|
1179
|
-
}
|
|
1180
|
-
if (part.functionCall) {
|
|
1181
|
-
if (textBlockStarted || thinkingBlockStarted) {
|
|
1182
|
-
const blockStop = {
|
|
1183
|
-
type: "content_block_stop",
|
|
1184
|
-
index: contentBlockIndex
|
|
1185
|
-
};
|
|
1186
|
-
controller.enqueue(encoder.encode(`event: content_block_stop\ndata: ${JSON.stringify(blockStop)}\n\n`));
|
|
1187
|
-
contentBlockIndex++;
|
|
1188
|
-
textBlockStarted = false;
|
|
1189
|
-
thinkingBlockStarted = false;
|
|
1190
|
-
}
|
|
1191
|
-
const toolBlockStart = {
|
|
1192
|
-
type: "content_block_start",
|
|
1193
|
-
index: contentBlockIndex,
|
|
1194
|
-
content_block: {
|
|
1195
|
-
type: "tool_use",
|
|
1196
|
-
id: `toolu_${Date.now()}`,
|
|
1197
|
-
name: part.functionCall.name,
|
|
1198
|
-
input: {}
|
|
1199
|
-
}
|
|
1200
|
-
};
|
|
1201
|
-
controller.enqueue(encoder.encode(`event: content_block_start\ndata: ${JSON.stringify(toolBlockStart)}\n\n`));
|
|
1202
|
-
const toolDelta = {
|
|
1203
|
-
type: "content_block_delta",
|
|
1204
|
-
index: contentBlockIndex,
|
|
1205
|
-
delta: {
|
|
1206
|
-
type: "input_json_delta",
|
|
1207
|
-
partial_json: JSON.stringify(part.functionCall.args)
|
|
1208
|
-
}
|
|
1209
|
-
};
|
|
1210
|
-
controller.enqueue(encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify(toolDelta)}\n\n`));
|
|
1211
|
-
const toolBlockStop = {
|
|
1212
|
-
type: "content_block_stop",
|
|
1213
|
-
index: contentBlockIndex
|
|
1214
|
-
};
|
|
1215
|
-
controller.enqueue(encoder.encode(`event: content_block_stop\ndata: ${JSON.stringify(toolBlockStop)}\n\n`));
|
|
1216
|
-
contentBlockIndex++;
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
if (candidate?.finishReason === "STOP") {
|
|
1220
|
-
if (textBlockStarted || thinkingBlockStarted) {
|
|
1221
|
-
const blockStop = {
|
|
1222
|
-
type: "content_block_stop",
|
|
1223
|
-
index: contentBlockIndex
|
|
1224
|
-
};
|
|
1225
|
-
controller.enqueue(encoder.encode(`event: content_block_stop\ndata: ${JSON.stringify(blockStop)}\n\n`));
|
|
1226
|
-
}
|
|
1227
|
-
const messageDelta = {
|
|
1228
|
-
type: "message_delta",
|
|
1229
|
-
delta: {
|
|
1230
|
-
stop_reason: "end_turn",
|
|
1231
|
-
stop_sequence: null
|
|
1232
|
-
},
|
|
1233
|
-
usage: { output_tokens: outputTokens }
|
|
1234
|
-
};
|
|
1235
|
-
controller.enqueue(encoder.encode(`event: message_delta\ndata: ${JSON.stringify(messageDelta)}\n\n`));
|
|
1236
|
-
}
|
|
1237
|
-
} catch {}
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1519
|
+
await processStream(reader, decoder, state$1, controller);
|
|
1520
|
+
controller.enqueue(createMessageStop());
|
|
1521
|
+
controller.close();
|
|
1240
1522
|
} catch (error) {
|
|
1241
1523
|
consola.error("Stream transform error:", error);
|
|
1242
1524
|
controller.error(error);
|
|
@@ -1249,12 +1531,56 @@ function transformStreamToAnthropic(response, model) {
|
|
|
1249
1531
|
} });
|
|
1250
1532
|
}
|
|
1251
1533
|
/**
|
|
1534
|
+
* Process the stream and emit events
|
|
1535
|
+
*/
|
|
1536
|
+
async function processStream(reader, decoder, state$1, controller) {
|
|
1537
|
+
const emit = (event) => emitSSEEvent(event, controller, state$1);
|
|
1538
|
+
while (true) {
|
|
1539
|
+
const { done, value } = await reader.read();
|
|
1540
|
+
if (done) break;
|
|
1541
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1542
|
+
const lines = processChunk(chunk, state$1);
|
|
1543
|
+
for (const line of lines) {
|
|
1544
|
+
const data = parseSSELine(line);
|
|
1545
|
+
if (!data) continue;
|
|
1546
|
+
const { candidates, usage } = extractFromData(data);
|
|
1547
|
+
if (usage) {
|
|
1548
|
+
state$1.inputTokens = usage.promptTokenCount ?? state$1.inputTokens;
|
|
1549
|
+
state$1.outputTokens = usage.candidatesTokenCount ?? state$1.outputTokens;
|
|
1550
|
+
}
|
|
1551
|
+
const candidate = candidates[0];
|
|
1552
|
+
const parts = candidate?.content?.parts ?? [];
|
|
1553
|
+
for (const part of parts) processPart(part, state$1, emit);
|
|
1554
|
+
if (candidate?.finishReason === "STOP") handleFinish(state$1, emit);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1252
1559
|
* Transform Antigravity non-stream response to Anthropic format
|
|
1253
1560
|
*/
|
|
1254
|
-
async function
|
|
1561
|
+
async function transformNonStreamResponse(response, model) {
|
|
1255
1562
|
const data = await response.json();
|
|
1256
|
-
const
|
|
1257
|
-
const
|
|
1563
|
+
const parts = (data.candidates?.[0])?.content?.parts ?? [];
|
|
1564
|
+
const content = buildNonStreamContent(parts);
|
|
1565
|
+
const anthropicResponse = {
|
|
1566
|
+
id: generateMessageId(),
|
|
1567
|
+
type: "message",
|
|
1568
|
+
role: "assistant",
|
|
1569
|
+
content,
|
|
1570
|
+
model,
|
|
1571
|
+
stop_reason: "end_turn",
|
|
1572
|
+
stop_sequence: null,
|
|
1573
|
+
usage: {
|
|
1574
|
+
input_tokens: data.usageMetadata?.promptTokenCount ?? 0,
|
|
1575
|
+
output_tokens: data.usageMetadata?.candidatesTokenCount ?? 0
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
return new Response(JSON.stringify(anthropicResponse), { headers: { "Content-Type": "application/json" } });
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Build content array for non-stream response
|
|
1582
|
+
*/
|
|
1583
|
+
function buildNonStreamContent(parts) {
|
|
1258
1584
|
const content = [];
|
|
1259
1585
|
for (const part of parts) {
|
|
1260
1586
|
if (part.thought && part.text) content.push({
|
|
@@ -1267,25 +1593,12 @@ async function transformNonStreamToAnthropic(response, model) {
|
|
|
1267
1593
|
});
|
|
1268
1594
|
if (part.functionCall) content.push({
|
|
1269
1595
|
type: "tool_use",
|
|
1270
|
-
id:
|
|
1596
|
+
id: generateToolId(),
|
|
1271
1597
|
name: part.functionCall.name,
|
|
1272
1598
|
input: part.functionCall.args
|
|
1273
1599
|
});
|
|
1274
1600
|
}
|
|
1275
|
-
|
|
1276
|
-
id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
|
|
1277
|
-
type: "message",
|
|
1278
|
-
role: "assistant",
|
|
1279
|
-
content,
|
|
1280
|
-
model,
|
|
1281
|
-
stop_reason: candidate?.finishReason === "STOP" ? "end_turn" : "end_turn",
|
|
1282
|
-
stop_sequence: null,
|
|
1283
|
-
usage: {
|
|
1284
|
-
input_tokens: data.usageMetadata?.promptTokenCount || 0,
|
|
1285
|
-
output_tokens: data.usageMetadata?.candidatesTokenCount || 0
|
|
1286
|
-
}
|
|
1287
|
-
};
|
|
1288
|
-
return new Response(JSON.stringify(anthropicResponse), { headers: { "Content-Type": "application/json" } });
|
|
1601
|
+
return content;
|
|
1289
1602
|
}
|
|
1290
1603
|
|
|
1291
1604
|
//#endregion
|
|
@@ -1375,11 +1688,11 @@ const encodingCache = /* @__PURE__ */ new Map();
|
|
|
1375
1688
|
/**
|
|
1376
1689
|
* Calculate tokens for tool calls
|
|
1377
1690
|
*/
|
|
1378
|
-
const calculateToolCallsTokens = (toolCalls, encoder, constants) => {
|
|
1691
|
+
const calculateToolCallsTokens = (toolCalls, encoder$1, constants) => {
|
|
1379
1692
|
let tokens = 0;
|
|
1380
1693
|
for (const toolCall of toolCalls) {
|
|
1381
1694
|
tokens += constants.funcInit;
|
|
1382
|
-
tokens += encoder.encode(JSON.stringify(toolCall)).length;
|
|
1695
|
+
tokens += encoder$1.encode(JSON.stringify(toolCall)).length;
|
|
1383
1696
|
}
|
|
1384
1697
|
tokens += constants.funcEnd;
|
|
1385
1698
|
return tokens;
|
|
@@ -1387,34 +1700,34 @@ const calculateToolCallsTokens = (toolCalls, encoder, constants) => {
|
|
|
1387
1700
|
/**
|
|
1388
1701
|
* Calculate tokens for content parts
|
|
1389
1702
|
*/
|
|
1390
|
-
const calculateContentPartsTokens = (contentParts, encoder) => {
|
|
1703
|
+
const calculateContentPartsTokens = (contentParts, encoder$1) => {
|
|
1391
1704
|
let tokens = 0;
|
|
1392
|
-
for (const part of contentParts) if (part.type === "image_url") tokens += encoder.encode(part.image_url.url).length + 85;
|
|
1393
|
-
else if (part.text) tokens += encoder.encode(part.text).length;
|
|
1705
|
+
for (const part of contentParts) if (part.type === "image_url") tokens += encoder$1.encode(part.image_url.url).length + 85;
|
|
1706
|
+
else if (part.text) tokens += encoder$1.encode(part.text).length;
|
|
1394
1707
|
return tokens;
|
|
1395
1708
|
};
|
|
1396
1709
|
/**
|
|
1397
1710
|
* Calculate tokens for a single message
|
|
1398
1711
|
*/
|
|
1399
|
-
const calculateMessageTokens = (message, encoder, constants) => {
|
|
1712
|
+
const calculateMessageTokens = (message, encoder$1, constants) => {
|
|
1400
1713
|
const tokensPerMessage = 3;
|
|
1401
1714
|
const tokensPerName = 1;
|
|
1402
1715
|
let tokens = tokensPerMessage;
|
|
1403
1716
|
for (const [key, value] of Object.entries(message)) {
|
|
1404
|
-
if (typeof value === "string") tokens += encoder.encode(value).length;
|
|
1717
|
+
if (typeof value === "string") tokens += encoder$1.encode(value).length;
|
|
1405
1718
|
if (key === "name") tokens += tokensPerName;
|
|
1406
|
-
if (key === "tool_calls") tokens += calculateToolCallsTokens(value, encoder, constants);
|
|
1407
|
-
if (key === "content" && Array.isArray(value)) tokens += calculateContentPartsTokens(value, encoder);
|
|
1719
|
+
if (key === "tool_calls") tokens += calculateToolCallsTokens(value, encoder$1, constants);
|
|
1720
|
+
if (key === "content" && Array.isArray(value)) tokens += calculateContentPartsTokens(value, encoder$1);
|
|
1408
1721
|
}
|
|
1409
1722
|
return tokens;
|
|
1410
1723
|
};
|
|
1411
1724
|
/**
|
|
1412
1725
|
* Calculate tokens using custom algorithm
|
|
1413
1726
|
*/
|
|
1414
|
-
const calculateTokens = (messages, encoder, constants) => {
|
|
1727
|
+
const calculateTokens = (messages, encoder$1, constants) => {
|
|
1415
1728
|
if (messages.length === 0) return 0;
|
|
1416
1729
|
let numTokens = 0;
|
|
1417
|
-
for (const message of messages) numTokens += calculateMessageTokens(message, encoder, constants);
|
|
1730
|
+
for (const message of messages) numTokens += calculateMessageTokens(message, encoder$1, constants);
|
|
1418
1731
|
numTokens += 3;
|
|
1419
1732
|
return numTokens;
|
|
1420
1733
|
};
|
|
@@ -1466,7 +1779,7 @@ const getModelConstants = (model) => {
|
|
|
1466
1779
|
* Calculate tokens for a single parameter
|
|
1467
1780
|
*/
|
|
1468
1781
|
const calculateParameterTokens = (key, prop, context) => {
|
|
1469
|
-
const { encoder, constants } = context;
|
|
1782
|
+
const { encoder: encoder$1, constants } = context;
|
|
1470
1783
|
let tokens = constants.propKey;
|
|
1471
1784
|
if (typeof prop !== "object" || prop === null) return tokens;
|
|
1472
1785
|
const param = prop;
|
|
@@ -1477,12 +1790,12 @@ const calculateParameterTokens = (key, prop, context) => {
|
|
|
1477
1790
|
tokens += constants.enumInit;
|
|
1478
1791
|
for (const item of param.enum) {
|
|
1479
1792
|
tokens += constants.enumItem;
|
|
1480
|
-
tokens += encoder.encode(String(item)).length;
|
|
1793
|
+
tokens += encoder$1.encode(String(item)).length;
|
|
1481
1794
|
}
|
|
1482
1795
|
}
|
|
1483
1796
|
if (paramDesc.endsWith(".")) paramDesc = paramDesc.slice(0, -1);
|
|
1484
1797
|
const line = `${paramName}:${paramType}:${paramDesc}`;
|
|
1485
|
-
tokens += encoder.encode(line).length;
|
|
1798
|
+
tokens += encoder$1.encode(line).length;
|
|
1486
1799
|
const excludedKeys = new Set([
|
|
1487
1800
|
"type",
|
|
1488
1801
|
"description",
|
|
@@ -1491,14 +1804,14 @@ const calculateParameterTokens = (key, prop, context) => {
|
|
|
1491
1804
|
for (const propertyName of Object.keys(param)) if (!excludedKeys.has(propertyName)) {
|
|
1492
1805
|
const propertyValue = param[propertyName];
|
|
1493
1806
|
const propertyText = typeof propertyValue === "string" ? propertyValue : JSON.stringify(propertyValue);
|
|
1494
|
-
tokens += encoder.encode(`${propertyName}:${propertyText}`).length;
|
|
1807
|
+
tokens += encoder$1.encode(`${propertyName}:${propertyText}`).length;
|
|
1495
1808
|
}
|
|
1496
1809
|
return tokens;
|
|
1497
1810
|
};
|
|
1498
1811
|
/**
|
|
1499
1812
|
* Calculate tokens for function parameters
|
|
1500
1813
|
*/
|
|
1501
|
-
const calculateParametersTokens = (parameters, encoder, constants) => {
|
|
1814
|
+
const calculateParametersTokens = (parameters, encoder$1, constants) => {
|
|
1502
1815
|
if (!parameters || typeof parameters !== "object") return 0;
|
|
1503
1816
|
const params = parameters;
|
|
1504
1817
|
let tokens = 0;
|
|
@@ -1507,36 +1820,36 @@ const calculateParametersTokens = (parameters, encoder, constants) => {
|
|
|
1507
1820
|
if (Object.keys(properties).length > 0) {
|
|
1508
1821
|
tokens += constants.propInit;
|
|
1509
1822
|
for (const propKey of Object.keys(properties)) tokens += calculateParameterTokens(propKey, properties[propKey], {
|
|
1510
|
-
encoder,
|
|
1823
|
+
encoder: encoder$1,
|
|
1511
1824
|
constants
|
|
1512
1825
|
});
|
|
1513
1826
|
}
|
|
1514
1827
|
} else {
|
|
1515
1828
|
const paramText = typeof value === "string" ? value : JSON.stringify(value);
|
|
1516
|
-
tokens += encoder.encode(`${key}:${paramText}`).length;
|
|
1829
|
+
tokens += encoder$1.encode(`${key}:${paramText}`).length;
|
|
1517
1830
|
}
|
|
1518
1831
|
return tokens;
|
|
1519
1832
|
};
|
|
1520
1833
|
/**
|
|
1521
1834
|
* Calculate tokens for a single tool
|
|
1522
1835
|
*/
|
|
1523
|
-
const calculateToolTokens = (tool, encoder, constants) => {
|
|
1836
|
+
const calculateToolTokens = (tool, encoder$1, constants) => {
|
|
1524
1837
|
let tokens = constants.funcInit;
|
|
1525
1838
|
const func = tool.function;
|
|
1526
1839
|
const fName = func.name;
|
|
1527
1840
|
let fDesc = func.description || "";
|
|
1528
1841
|
if (fDesc.endsWith(".")) fDesc = fDesc.slice(0, -1);
|
|
1529
1842
|
const line = fName + ":" + fDesc;
|
|
1530
|
-
tokens += encoder.encode(line).length;
|
|
1531
|
-
if (typeof func.parameters === "object" && func.parameters !== null) tokens += calculateParametersTokens(func.parameters, encoder, constants);
|
|
1843
|
+
tokens += encoder$1.encode(line).length;
|
|
1844
|
+
if (typeof func.parameters === "object" && func.parameters !== null) tokens += calculateParametersTokens(func.parameters, encoder$1, constants);
|
|
1532
1845
|
return tokens;
|
|
1533
1846
|
};
|
|
1534
1847
|
/**
|
|
1535
1848
|
* Calculate token count for tools based on model
|
|
1536
1849
|
*/
|
|
1537
|
-
const numTokensForTools = (tools, encoder, constants) => {
|
|
1850
|
+
const numTokensForTools = (tools, encoder$1, constants) => {
|
|
1538
1851
|
let funcTokenCount = 0;
|
|
1539
|
-
for (const tool of tools) funcTokenCount += calculateToolTokens(tool, encoder, constants);
|
|
1852
|
+
for (const tool of tools) funcTokenCount += calculateToolTokens(tool, encoder$1, constants);
|
|
1540
1853
|
funcTokenCount += constants.funcEnd;
|
|
1541
1854
|
return funcTokenCount;
|
|
1542
1855
|
};
|
|
@@ -1545,14 +1858,14 @@ const numTokensForTools = (tools, encoder, constants) => {
|
|
|
1545
1858
|
*/
|
|
1546
1859
|
const getTokenCount = async (payload, model) => {
|
|
1547
1860
|
const tokenizer = getTokenizerFromModel(model);
|
|
1548
|
-
const encoder = await getEncodeChatFunction(tokenizer);
|
|
1861
|
+
const encoder$1 = await getEncodeChatFunction(tokenizer);
|
|
1549
1862
|
const simplifiedMessages = payload.messages;
|
|
1550
1863
|
const inputMessages = simplifiedMessages.filter((msg) => msg.role !== "assistant");
|
|
1551
1864
|
const outputMessages = simplifiedMessages.filter((msg) => msg.role === "assistant");
|
|
1552
1865
|
const constants = getModelConstants(model);
|
|
1553
|
-
let inputTokens = calculateTokens(inputMessages, encoder, constants);
|
|
1554
|
-
if (payload.tools && payload.tools.length > 0) inputTokens += numTokensForTools(payload.tools, encoder, constants);
|
|
1555
|
-
const outputTokens = calculateTokens(outputMessages, encoder, constants);
|
|
1866
|
+
let inputTokens = calculateTokens(inputMessages, encoder$1, constants);
|
|
1867
|
+
if (payload.tools && payload.tools.length > 0) inputTokens += numTokensForTools(payload.tools, encoder$1, constants);
|
|
1868
|
+
const outputTokens = calculateTokens(outputMessages, encoder$1, constants);
|
|
1556
1869
|
return {
|
|
1557
1870
|
input: inputTokens,
|
|
1558
1871
|
output: outputTokens
|
|
@@ -1702,7 +2015,10 @@ function translateModelName(model) {
|
|
|
1702
2015
|
"claude-3-5-haiku": "claude-haiku-4.5",
|
|
1703
2016
|
"claude-3-haiku": "claude-haiku-4.5"
|
|
1704
2017
|
})) if (modelBase.startsWith(oldFormat) && supportedModels.includes(newFormat)) return newFormat;
|
|
1705
|
-
|
|
2018
|
+
let modelFamily = null;
|
|
2019
|
+
if (model.includes("opus")) modelFamily = "opus";
|
|
2020
|
+
else if (model.includes("sonnet")) modelFamily = "sonnet";
|
|
2021
|
+
else if (model.includes("haiku")) modelFamily = "haiku";
|
|
1706
2022
|
if (modelFamily) {
|
|
1707
2023
|
const familyModel = supportedModels.find((m) => m.includes(modelFamily));
|
|
1708
2024
|
if (familyModel) return familyModel;
|
|
@@ -2429,7 +2745,9 @@ async function runServer(options) {
|
|
|
2429
2745
|
}
|
|
2430
2746
|
const serverUrl = `http://localhost:${options.port}`;
|
|
2431
2747
|
if (options.claudeCode) {
|
|
2432
|
-
|
|
2748
|
+
let models = state.models;
|
|
2749
|
+
if (state.zenMode) models = state.zenModels;
|
|
2750
|
+
else if (state.antigravityMode) models = state.antigravityModels;
|
|
2433
2751
|
invariant(models, "Models should be loaded by now");
|
|
2434
2752
|
const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
|
|
2435
2753
|
type: "select",
|