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/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 [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
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
- tokenExists
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
- consola.info(`copilot-api debug
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
- - APP_DIR: ${info.paths.APP_DIR}
158
- - GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
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
- Token exists: ${info.tokenExists ? "Yes" : "No"}`);
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 = void 0;
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
- if (typeof message.content === "string") contents.push({
861
+ const parts = buildMessageParts(message.content);
862
+ contents.push({
661
863
  role,
662
- parts: [{ text: message.content }]
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
- * Create chat completion with Antigravity
922
+ * Build Antigravity request body
705
923
  */
706
- async function createAntigravityChatCompletion(request) {
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 antigravityRequest = {
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) antigravityRequest.systemInstruction = systemInstruction;
728
- if (tools) antigravityRequest.tools = tools;
729
- if (isThinkingModel(request.model)) antigravityRequest.generationConfig = {
730
- ...antigravityRequest.generationConfig,
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(antigravityRequest)
978
+ body: JSON.stringify(body)
746
979
  });
747
- if (!response.ok) {
748
- const errorText = await response.text();
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 new Response(JSON.stringify({ error: {
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
- let buffer = "";
784
- const requestId = `chatcmpl-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
1013
+ const state$1 = createStreamState();
785
1014
  try {
786
- while (true) {
787
- const { done, value } = await reader.read();
788
- if (done) {
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
- * Transform Antigravity non-stream response to OpenAI format
1030
+ * Process stream and emit OpenAI format chunks
872
1031
  */
873
- async function transformNonStreamResponse(response, model) {
874
- const data = await response.json();
875
- const candidate = data.candidates?.[0];
876
- const parts = candidate?.content?.parts || [];
877
- let content = "";
878
- let reasoningContent = "";
879
- const toolCalls = [];
880
- for (const part of parts) {
881
- if (part.thought && part.text) reasoningContent += part.text;
882
- else if (part.text) content += part.text;
883
- if (part.functionCall) toolCalls.push({
884
- id: `call_${Date.now()}`,
885
- type: "function",
886
- function: {
887
- name: part.functionCall.name,
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: `chatcmpl-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
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: candidate?.finishReason === "STOP" ? "stop" : "stop"
1116
+ finish_reason: "stop"
907
1117
  }],
908
1118
  usage: {
909
- prompt_tokens: data.usageMetadata?.promptTokenCount || 0,
910
- completion_tokens: data.usageMetadata?.candidatesTokenCount || 0,
911
- total_tokens: data.usageMetadata?.totalTokenCount || 0
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 = void 0;
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
- if (typeof message.content === "string") contents.push({
1348
+ const parts = buildParts(message.content);
1349
+ if (parts.length > 0) contents.push({
954
1350
  role,
955
- parts: [{ text: message.content }]
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
- * Create Anthropic-compatible message response using Antigravity
1387
+ * Build Antigravity request body
991
1388
  */
992
- async function createAntigravityMessages(request) {
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 antigravityRequest = {
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) antigravityRequest.systemInstruction = systemInstruction;
1017
- if (tools) antigravityRequest.tools = tools;
1018
- if (isThinkingModel(request.model)) antigravityRequest.generationConfig = {
1019
- ...antigravityRequest.generationConfig,
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(antigravityRequest)
1444
+ body: JSON.stringify(body)
1035
1445
  });
1036
- if (!response.ok) {
1037
- const errorText = await response.text();
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 new Response(JSON.stringify({
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 transformStreamToAnthropic(response, model) {
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
- let buffer = "";
1078
- const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
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
- while (true) {
1103
- const { done, value } = await reader.read();
1104
- if (done) {
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 transformNonStreamToAnthropic(response, model) {
1561
+ async function transformNonStreamResponse(response, model) {
1255
1562
  const data = await response.json();
1256
- const candidate = data.candidates?.[0];
1257
- const parts = candidate?.content?.parts || [];
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: `toolu_${Date.now()}`,
1596
+ id: generateToolId(),
1271
1597
  name: part.functionCall.name,
1272
1598
  input: part.functionCall.args
1273
1599
  });
1274
1600
  }
1275
- const anthropicResponse = {
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
- const modelFamily = model.includes("opus") ? "opus" : model.includes("sonnet") ? "sonnet" : model.includes("haiku") ? "haiku" : null;
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
- const models = state.zenMode ? state.zenModels : state.antigravityMode ? state.antigravityModels : state.models;
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",