superoc 0.1.0

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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/bin/nimsuper.js +21 -0
  4. package/bin/nimsuper.ts +10 -0
  5. package/bin/superoc.js +21 -0
  6. package/bin/superoc.ts +10 -0
  7. package/dist/antigravity.d.ts +59 -0
  8. package/dist/antigravity.d.ts.map +1 -0
  9. package/dist/antigravity.js +423 -0
  10. package/dist/antigravity.js.map +1 -0
  11. package/dist/errors.d.ts +22 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +122 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/errors.test.d.ts +2 -0
  16. package/dist/errors.test.d.ts.map +1 -0
  17. package/dist/errors.test.js +137 -0
  18. package/dist/errors.test.js.map +1 -0
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +809 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/opencode-sync.d.ts +9 -0
  24. package/dist/opencode-sync.d.ts.map +1 -0
  25. package/dist/opencode-sync.js +157 -0
  26. package/dist/opencode-sync.js.map +1 -0
  27. package/dist/provider.d.ts +5 -0
  28. package/dist/provider.d.ts.map +1 -0
  29. package/dist/provider.js +100 -0
  30. package/dist/provider.js.map +1 -0
  31. package/dist/provider.test.d.ts +2 -0
  32. package/dist/provider.test.d.ts.map +1 -0
  33. package/dist/provider.test.js +94 -0
  34. package/dist/provider.test.js.map +1 -0
  35. package/dist/quota.d.ts +12 -0
  36. package/dist/quota.d.ts.map +1 -0
  37. package/dist/quota.js +105 -0
  38. package/dist/quota.js.map +1 -0
  39. package/dist/quota.test.d.ts +2 -0
  40. package/dist/quota.test.d.ts.map +1 -0
  41. package/dist/quota.test.js +20 -0
  42. package/dist/quota.test.js.map +1 -0
  43. package/dist/storage.d.ts +43 -0
  44. package/dist/storage.d.ts.map +1 -0
  45. package/dist/storage.js +447 -0
  46. package/dist/storage.js.map +1 -0
  47. package/dist/themes.d.ts +64 -0
  48. package/dist/themes.d.ts.map +1 -0
  49. package/dist/themes.js +424 -0
  50. package/dist/themes.js.map +1 -0
  51. package/dist/tui/actions.d.ts +14 -0
  52. package/dist/tui/actions.d.ts.map +1 -0
  53. package/dist/tui/actions.js +464 -0
  54. package/dist/tui/actions.js.map +1 -0
  55. package/dist/tui/app.d.ts +2 -0
  56. package/dist/tui/app.d.ts.map +1 -0
  57. package/dist/tui/app.js +323 -0
  58. package/dist/tui/app.js.map +1 -0
  59. package/dist/tui/benchmark.d.ts +42 -0
  60. package/dist/tui/benchmark.d.ts.map +1 -0
  61. package/dist/tui/benchmark.js +379 -0
  62. package/dist/tui/benchmark.js.map +1 -0
  63. package/dist/tui/screens.d.ts +23 -0
  64. package/dist/tui/screens.d.ts.map +1 -0
  65. package/dist/tui/screens.js +791 -0
  66. package/dist/tui/screens.js.map +1 -0
  67. package/dist/tui/state.d.ts +54 -0
  68. package/dist/tui/state.d.ts.map +1 -0
  69. package/dist/tui/state.js +85 -0
  70. package/dist/tui/state.js.map +1 -0
  71. package/dist/tui/types.d.ts +16 -0
  72. package/dist/tui/types.d.ts.map +1 -0
  73. package/dist/tui/types.js +2 -0
  74. package/dist/tui/types.js.map +1 -0
  75. package/dist/tui/ui.d.ts +17 -0
  76. package/dist/tui/ui.d.ts.map +1 -0
  77. package/dist/tui/ui.js +77 -0
  78. package/dist/tui/ui.js.map +1 -0
  79. package/dist/types.d.ts +52 -0
  80. package/dist/types.d.ts.map +1 -0
  81. package/dist/types.js +2 -0
  82. package/dist/types.js.map +1 -0
  83. package/package.json +68 -0
  84. package/scripts/postinstall.js +77 -0
  85. package/scripts/uninstall.js +119 -0
package/dist/index.js ADDED
@@ -0,0 +1,809 @@
1
+ import { loadStore, saveStore, addKey, getNextKey, getActiveKeys, getDefaultStore, recordRateLimit, resetRateLimit, recordModelRateLimit, } from "./storage.js";
2
+ import { describeError, is429Error, isStatusMessageRateLimited, shouldRetryForError, } from "./errors.js";
3
+ import { detectProviderForRequest, getProviderHeaders } from "./provider.js";
4
+ import { authorizeAntigravity, exchangeAntigravity, getOrRefreshAntigravityAccessToken, getAntigravityHeaders, } from "./antigravity.js";
5
+ import { getNormalizedQuota } from "./quota.js";
6
+ const PROVIDERS = ["nvidia", "google", "antigravity"];
7
+ const NIM_BASE_URL = "https://integrate.api.nvidia.com";
8
+ const GEMINI_BASE_URL = "https://generativelanguage.googleapis.com";
9
+ const VALID_STRATEGIES = ["round-robin", "least-failures"];
10
+ function isValidStrategy(val) {
11
+ return val === "round-robin" || val === "least-failures";
12
+ }
13
+ function modelKey(model) {
14
+ return `${model.providerID}/${model.modelID}`;
15
+ }
16
+ function getEnvKeyName(provider) {
17
+ if (provider === "nvidia")
18
+ return "NVIDIA_API_KEY";
19
+ if (provider === "google")
20
+ return "GOOGLE_API_KEY";
21
+ return "ANTIGRAVITY_API_KEY";
22
+ }
23
+ function isProviderRequest(provider, modelApiStr, providerId, modelProviderId) {
24
+ const detected = detectProviderForRequest({
25
+ provider: { info: { id: providerId } },
26
+ model: { providerID: modelProviderId, api: modelApiStr },
27
+ });
28
+ return detected === provider;
29
+ }
30
+ async function isSubagentSession(client, sessionID) {
31
+ try {
32
+ const res = await client.session.get({ path: { id: sessionID } });
33
+ const data = res && typeof res === "object" && "data" in res
34
+ ? res.data
35
+ : res;
36
+ if (!data || typeof data !== "object")
37
+ return false;
38
+ return data?.parentID !== undefined;
39
+ }
40
+ catch (err) {
41
+ console.debug(`[nimsuper] isSubagentSession failed for ${sessionID}:`, err);
42
+ return false;
43
+ }
44
+ }
45
+ const SUBAGENT_CACHE_MAX_SIZE = 1000;
46
+ const SUBAGENT_CACHE_TTL_MS = 60_000;
47
+ const ERROR_DEDUP_WINDOW_MS = 500;
48
+ const SESSIONS_MAX_SIZE = 500;
49
+ const SESSIONS_MAX_AGE_MS = 10 * 60 * 1000;
50
+ const subAgentCache = new Map();
51
+ async function isSubagentSessionCached(client, sessionID) {
52
+ const cached = subAgentCache.get(sessionID);
53
+ if (cached !== undefined) {
54
+ if (cached > Date.now())
55
+ return true;
56
+ subAgentCache.delete(sessionID);
57
+ }
58
+ const result = await isSubagentSession(client, sessionID);
59
+ if (result) {
60
+ if (subAgentCache.size >= SUBAGENT_CACHE_MAX_SIZE) {
61
+ const firstKey = subAgentCache.keys().next().value;
62
+ if (firstKey !== undefined)
63
+ subAgentCache.delete(firstKey);
64
+ }
65
+ subAgentCache.set(sessionID, Date.now() + SUBAGENT_CACHE_TTL_MS);
66
+ }
67
+ return result;
68
+ }
69
+ function findChainIndex(chain, model) {
70
+ if (!model)
71
+ return -1;
72
+ return chain.findIndex((entry) => entry.id === model.modelID);
73
+ }
74
+ export const SuperocPlugin = async (input, options) => {
75
+ const client = input.client;
76
+ const config = {
77
+ storePath: options?.storePath,
78
+ rotationStrategy: isValidStrategy(options?.rotationStrategy)
79
+ ? options.rotationStrategy
80
+ : "round-robin",
81
+ };
82
+ const store = loadStore(config) ?? getDefaultStore();
83
+ if (!store.fallbackChains)
84
+ store.fallbackChains = { nvidia: [], google: [], antigravity: [] };
85
+ const sessions = new Map();
86
+ const reloadFromDisk = () => {
87
+ let fresh = null;
88
+ try {
89
+ fresh = loadStore(config);
90
+ }
91
+ catch (err) {
92
+ console.debug("[nimsuper] Failed to reload store from disk:", err);
93
+ return;
94
+ }
95
+ if (fresh === null)
96
+ return;
97
+ try {
98
+ store.keys = fresh.keys;
99
+ store.currentIndex = fresh.currentIndex;
100
+ store.rotationStrategy = fresh.rotationStrategy;
101
+ store.updatedAt = fresh.updatedAt;
102
+ store.lastUsedKeyId = fresh.lastUsedKeyId;
103
+ store.fallbackChains = {
104
+ nvidia: Array.isArray(fresh.fallbackChains?.nvidia) ? fresh.fallbackChains.nvidia : [],
105
+ google: Array.isArray(fresh.fallbackChains?.google) ? fresh.fallbackChains.google : [],
106
+ antigravity: Array.isArray(fresh.fallbackChains?.antigravity) ? fresh.fallbackChains.antigravity : [],
107
+ };
108
+ store.maxRateLimitFailures =
109
+ typeof fresh.maxRateLimitFailures === "number" &&
110
+ Number.isFinite(fresh.maxRateLimitFailures) &&
111
+ fresh.maxRateLimitFailures >= 1
112
+ ? fresh.maxRateLimitFailures
113
+ : getDefaultStore().maxRateLimitFailures;
114
+ }
115
+ catch (err) {
116
+ console.debug("[nimsuper] Failed to apply reloaded store:", err);
117
+ }
118
+ };
119
+ const safeSaveStore = () => {
120
+ try {
121
+ saveStore(store, config);
122
+ }
123
+ catch (err) {
124
+ console.error("[nimsuper] Failed to save store:", err);
125
+ }
126
+ };
127
+ for (const provider of PROVIDERS) {
128
+ const activeKeys = getActiveKeys(store, provider);
129
+ if (activeKeys.length === 0) {
130
+ const envKey = process.env[getEnvKeyName(provider)];
131
+ if (envKey) {
132
+ const existing = store.keys.find((k) => k.name === "env-default" && k.provider === provider);
133
+ if (!existing) {
134
+ addKey(store, "env-default", envKey, provider);
135
+ safeSaveStore();
136
+ }
137
+ }
138
+ }
139
+ }
140
+ const showToast = async (variant, message) => {
141
+ try {
142
+ await client.tui?.showToast?.({ body: { title: "Model Fallback", message, variant } });
143
+ }
144
+ catch (err) {
145
+ console.debug("[nimsuper] showToast failed:", err);
146
+ }
147
+ };
148
+ const getState = (sessionID) => {
149
+ const existing = sessions.get(sessionID);
150
+ if (existing)
151
+ return existing;
152
+ if (sessions.size >= SESSIONS_MAX_SIZE) {
153
+ const now = Date.now();
154
+ let oldestId;
155
+ let oldestTime = Infinity;
156
+ for (const [id, s] of sessions) {
157
+ if (s.createdAt < oldestTime) {
158
+ oldestTime = s.createdAt;
159
+ oldestId = id;
160
+ }
161
+ }
162
+ if (oldestId)
163
+ sessions.delete(oldestId);
164
+ for (const [id, s] of sessions) {
165
+ if (now - s.createdAt > SESSIONS_MAX_AGE_MS) {
166
+ sessions.delete(id);
167
+ }
168
+ }
169
+ }
170
+ const next = {
171
+ attemptIndex: 0,
172
+ inRetry: false,
173
+ aborting: false,
174
+ pendingRetryIndex: undefined,
175
+ lastUserMessageID: undefined,
176
+ activeChainKey: undefined,
177
+ activeChainModelId: undefined,
178
+ rateLimitCount: 0,
179
+ currentModelId: undefined,
180
+ lastFailedModelId: undefined,
181
+ lastErrorHandledAt: 0,
182
+ createdAt: Date.now(),
183
+ sessionProviderId: undefined,
184
+ lastUsedKeyId: undefined,
185
+ };
186
+ sessions.set(sessionID, next);
187
+ return next;
188
+ };
189
+ const cleanupSession = (sessionID) => {
190
+ sessions.delete(sessionID);
191
+ };
192
+ const waitForSessionIdle = async (sessionID, timeoutMs = 2000) => {
193
+ const start = Date.now();
194
+ while (Date.now() - start < timeoutMs) {
195
+ try {
196
+ const res = await client.session.status({});
197
+ const data = res && typeof res === "object" && "data" in res
198
+ ? res.data
199
+ : res;
200
+ if (data && typeof data === "object") {
201
+ const statusMap = data;
202
+ const status = statusMap[sessionID];
203
+ if (status?.type === "idle")
204
+ return true;
205
+ if (!status)
206
+ return true;
207
+ }
208
+ }
209
+ catch {
210
+ // status endpoint might not be available, keep polling
211
+ }
212
+ await new Promise((resolve) => setTimeout(resolve, 50));
213
+ }
214
+ console.debug(`[nimsuper] waitForSessionIdle timed out for ${sessionID}`);
215
+ return false;
216
+ };
217
+ const getChainForProvider = (provider) => store.fallbackChains[provider];
218
+ const triggerRetry = async (sessionID, state, reason) => {
219
+ const provider = state.sessionProviderId ?? "nvidia";
220
+ const chain = getChainForProvider(provider);
221
+ if (chain.length < 2)
222
+ return false;
223
+ let nextIndex = (state.attemptIndex + 1) % chain.length;
224
+ if (state.lastFailedModelId &&
225
+ chain[nextIndex]?.id === state.lastFailedModelId &&
226
+ chain.length > 2) {
227
+ nextIndex = (nextIndex + 1) % chain.length;
228
+ }
229
+ state.inRetry = true;
230
+ state.pendingRetryIndex = nextIndex;
231
+ try {
232
+ const source = chain[state.attemptIndex];
233
+ const target = chain[nextIndex];
234
+ if (!source || !target)
235
+ return false;
236
+ await showToast("warning", `${source.name} → ${target.name}${reason ? `: ${reason}` : ""}`);
237
+ const messagesResult = await client.session.messages({ path: { id: sessionID } });
238
+ const entries = messagesResult && "data" in messagesResult ? messagesResult.data : messagesResult;
239
+ if (!Array.isArray(entries))
240
+ return false;
241
+ const userMessages = entries.filter((entry) => entry?.info?.role === "user");
242
+ if (userMessages.length === 0)
243
+ return false;
244
+ const lastUser = userMessages[userMessages.length - 1];
245
+ const lastUserInfo = lastUser.info;
246
+ const lastUserParts = lastUser.parts;
247
+ if (state.lastUserMessageID &&
248
+ lastUserInfo?.id !== state.lastUserMessageID) {
249
+ return false;
250
+ }
251
+ const promptParts = [];
252
+ if (Array.isArray(lastUserParts)) {
253
+ for (const part of lastUserParts) {
254
+ if (part?.type === "text") {
255
+ promptParts.push({
256
+ type: "text",
257
+ id: part.id,
258
+ text: part.text,
259
+ synthetic: part.synthetic,
260
+ ignored: part.ignored,
261
+ });
262
+ }
263
+ }
264
+ }
265
+ state.aborting = true;
266
+ try {
267
+ await client.session.abort({ path: { id: sessionID } });
268
+ }
269
+ catch (abortErr) {
270
+ console.debug(`[nimsuper] abort failed for ${sessionID}:`, abortErr);
271
+ }
272
+ const idle = await waitForSessionIdle(sessionID);
273
+ if (!idle) {
274
+ console.debug(`[nimsuper] session ${sessionID} did not go idle after abort`);
275
+ state.pendingRetryIndex = undefined;
276
+ return false;
277
+ }
278
+ await client.session.prompt({
279
+ path: { id: sessionID },
280
+ body: {
281
+ messageID: lastUserInfo?.id,
282
+ agent: lastUserInfo?.agent,
283
+ model: {
284
+ providerID: state.sessionProviderId ?? provider,
285
+ modelID: target.id,
286
+ },
287
+ parts: promptParts,
288
+ },
289
+ });
290
+ return true;
291
+ }
292
+ catch (err) {
293
+ console.debug(`[nimsuper] triggerRetry failed for ${sessionID}:`, err);
294
+ state.pendingRetryIndex = undefined;
295
+ return false;
296
+ }
297
+ finally {
298
+ state.inRetry = false;
299
+ }
300
+ };
301
+ const handleSessionError = async (event) => {
302
+ const props = event.properties;
303
+ const error = props?.error;
304
+ const sessionID = props?.sessionID;
305
+ if (is429Error(error)) {
306
+ const stateForBlacklist = sessionID ? sessions.get(sessionID) : undefined;
307
+ const errorKeyId = stateForBlacklist?.lastUsedKeyId ?? store.lastUsedKeyId;
308
+ reloadFromDisk();
309
+ if (errorKeyId) {
310
+ recordRateLimit(store, errorKeyId);
311
+ const modelForBlacklist = stateForBlacklist?.currentModelId ?? stateForBlacklist?.activeChainModelId;
312
+ if (modelForBlacklist) {
313
+ recordModelRateLimit(store, errorKeyId, modelForBlacklist);
314
+ }
315
+ if (stateForBlacklist) {
316
+ stateForBlacklist.lastFailedModelId = modelForBlacklist;
317
+ }
318
+ }
319
+ safeSaveStore();
320
+ }
321
+ if (!sessionID)
322
+ return;
323
+ const state = sessions.get(sessionID);
324
+ if (!state)
325
+ return;
326
+ if (state.aborting) {
327
+ state.aborting = false;
328
+ return;
329
+ }
330
+ if (state.inRetry)
331
+ return;
332
+ const now = Date.now();
333
+ if (now - state.lastErrorHandledAt < ERROR_DEDUP_WINDOW_MS)
334
+ return;
335
+ state.lastErrorHandledAt = now;
336
+ if (!shouldRetryForError(error, state)) {
337
+ if (!is429Error(error))
338
+ state.rateLimitCount = 0;
339
+ return;
340
+ }
341
+ if (await isSubagentSessionCached(client, sessionID)) {
342
+ if (is429Error(error)) {
343
+ await showToast("warning", "Subagent rate limited — model switch skipped to preserve parent task");
344
+ }
345
+ return;
346
+ }
347
+ if (is429Error(error)) {
348
+ state.rateLimitCount++;
349
+ if (state.rateLimitCount < store.maxRateLimitFailures)
350
+ return;
351
+ }
352
+ else {
353
+ state.rateLimitCount = 0;
354
+ return;
355
+ }
356
+ const reason = describeError(error, state, store.maxRateLimitFailures);
357
+ await triggerRetry(sessionID, state, reason);
358
+ };
359
+ const handleSessionStatusRetry = async (sessionID, status) => {
360
+ const message = status.message;
361
+ const is429 = isStatusMessageRateLimited(message);
362
+ if (!is429)
363
+ return;
364
+ const state = sessions.get(sessionID);
365
+ if (!state)
366
+ return;
367
+ if (state.inRetry)
368
+ return;
369
+ if (await isSubagentSessionCached(client, sessionID))
370
+ return;
371
+ reloadFromDisk();
372
+ const errorKeyId = state?.lastUsedKeyId ?? store.lastUsedKeyId;
373
+ if (errorKeyId) {
374
+ recordRateLimit(store, errorKeyId);
375
+ const modelForBlacklist = state.currentModelId ?? state.activeChainModelId;
376
+ if (modelForBlacklist) {
377
+ recordModelRateLimit(store, errorKeyId, modelForBlacklist);
378
+ }
379
+ state.lastFailedModelId = modelForBlacklist;
380
+ }
381
+ safeSaveStore();
382
+ state.rateLimitCount++;
383
+ if (state.rateLimitCount < store.maxRateLimitFailures)
384
+ return;
385
+ const reason = `Rate limited (429) — ${state.rateLimitCount}/${store.maxRateLimitFailures} consecutive`;
386
+ await triggerRetry(sessionID, state, reason);
387
+ };
388
+ const handleSessionStepFailed = async (event) => {
389
+ const props = event.properties;
390
+ const sessionID = props?.sessionID;
391
+ if (!sessionID)
392
+ return;
393
+ const error = props?.error;
394
+ const errorMessage = typeof error?.message === "string" ? error.message : undefined;
395
+ if (!isStatusMessageRateLimited(errorMessage) && !is429Error(error))
396
+ return;
397
+ const state = sessions.get(sessionID);
398
+ if (!state)
399
+ return;
400
+ if (state.inRetry)
401
+ return;
402
+ const now = Date.now();
403
+ if (now - state.lastErrorHandledAt < ERROR_DEDUP_WINDOW_MS)
404
+ return;
405
+ state.lastErrorHandledAt = now;
406
+ const errorKeyId = state?.lastUsedKeyId ?? store.lastUsedKeyId;
407
+ reloadFromDisk();
408
+ if (errorKeyId) {
409
+ recordRateLimit(store, errorKeyId);
410
+ const modelForBlacklist = state.currentModelId ?? state.activeChainModelId;
411
+ if (modelForBlacklist) {
412
+ recordModelRateLimit(store, errorKeyId, modelForBlacklist);
413
+ }
414
+ state.lastFailedModelId = modelForBlacklist;
415
+ }
416
+ safeSaveStore();
417
+ if (await isSubagentSessionCached(client, sessionID)) {
418
+ await showToast("warning", "Subagent rate limited — model switch skipped to preserve parent task");
419
+ return;
420
+ }
421
+ state.rateLimitCount++;
422
+ if (state.rateLimitCount < store.maxRateLimitFailures)
423
+ return;
424
+ const reason = `Rate limited (429) — ${state.rateLimitCount}/${store.maxRateLimitFailures} consecutive`;
425
+ await triggerRetry(sessionID, state, reason);
426
+ };
427
+ function createSseUnwrapTransform() {
428
+ const decoder = new TextDecoder();
429
+ const encoder = new TextEncoder();
430
+ let buffer = "";
431
+ return new TransformStream({
432
+ transform(chunk, controller) {
433
+ buffer += decoder.decode(chunk, { stream: true });
434
+ const lines = buffer.split("\n");
435
+ buffer = lines.pop() || "";
436
+ for (const line of lines) {
437
+ if (line.startsWith("data:")) {
438
+ const jsonStr = line.slice(5).trim();
439
+ if (!jsonStr) {
440
+ controller.enqueue(encoder.encode(line + "\n"));
441
+ continue;
442
+ }
443
+ try {
444
+ const parsed = JSON.parse(jsonStr);
445
+ if (parsed.response !== undefined) {
446
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(parsed.response)}\n`));
447
+ continue;
448
+ }
449
+ }
450
+ catch { }
451
+ }
452
+ controller.enqueue(encoder.encode(line + "\n"));
453
+ }
454
+ },
455
+ flush(controller) {
456
+ if (buffer.length > 0) {
457
+ if (buffer.startsWith("data:")) {
458
+ const jsonStr = buffer.slice(5).trim();
459
+ try {
460
+ const parsed = JSON.parse(jsonStr);
461
+ if (parsed.response !== undefined) {
462
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(parsed.response)}\n`));
463
+ return;
464
+ }
465
+ }
466
+ catch { }
467
+ }
468
+ controller.enqueue(encoder.encode(buffer));
469
+ }
470
+ },
471
+ });
472
+ }
473
+ const hooks = {
474
+ auth: {
475
+ provider: "google",
476
+ loader: async (_getAuth, providerContext) => {
477
+ if (providerContext?.models) {
478
+ try {
479
+ reloadFromDisk();
480
+ for (const [id, model] of Object.entries(providerContext.models)) {
481
+ if (id.startsWith("antigravity-") && model) {
482
+ const quota = await getNormalizedQuota(store.keys, id);
483
+ const cleanBase = (model.name ?? id).replace(/\s+5h:.*$/, "");
484
+ model.name = `${cleanBase} 5h: ${quota.fiveHourPercent}% W: ${quota.weeklyPercent}%`;
485
+ }
486
+ }
487
+ }
488
+ catch { }
489
+ }
490
+ return {
491
+ apiKey: "",
492
+ async fetch(input, init) {
493
+ const urlString = typeof input === "string"
494
+ ? input
495
+ : input instanceof URL
496
+ ? input.toString()
497
+ : input.url;
498
+ if (urlString.includes("generativelanguage.googleapis.com")) {
499
+ const match = urlString.match(/\/models\/([^:]+):(\w+)/);
500
+ const rawModel = match ? match[1] : "";
501
+ const action = match ? match[2] : "streamGenerateContent";
502
+ const isStreaming = action === "streamGenerateContent" || urlString.includes("alt=sse");
503
+ const isAntigravityModel = rawModel.startsWith("antigravity-") ||
504
+ /claude|gpt-oss|gemini-3|gemini-pro-agent/i.test(rawModel);
505
+ reloadFromDisk();
506
+ const activeAntigravityKeys = getActiveKeys(store, "antigravity");
507
+ if (isAntigravityModel || activeAntigravityKeys.length > 0) {
508
+ let attempts = 0;
509
+ const maxAttempts = Math.max(1, activeAntigravityKeys.length);
510
+ while (attempts < maxAttempts) {
511
+ attempts++;
512
+ const next = getNextKey(store, config, rawModel, "antigravity");
513
+ if (!next)
514
+ break;
515
+ const authRes = await getOrRefreshAntigravityAccessToken(next.key.key);
516
+ if (!authRes) {
517
+ recordRateLimit(store, next.key.id);
518
+ safeSaveStore();
519
+ continue;
520
+ }
521
+ const effectiveModel = rawModel.replace(/^antigravity-/, "");
522
+ let bodyStr = init?.body;
523
+ let parsedBody = typeof bodyStr === "string" ? JSON.parse(bodyStr) : bodyStr;
524
+ const wrappedBody = JSON.stringify({
525
+ project: authRes.projectId || "rising-fact-p41fc",
526
+ model: effectiveModel,
527
+ request: parsedBody,
528
+ requestType: "agent",
529
+ userAgent: "antigravity",
530
+ });
531
+ const headers = new Headers(init?.headers ?? {});
532
+ headers.set("Authorization", `Bearer ${authRes.accessToken}`);
533
+ headers.set("User-Agent", `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/1.18.3 Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`);
534
+ headers.set("X-Goog-Api-Client", "google-cloud-sdk vscode_cloudshelleditor/0.1");
535
+ headers.set("Client-Metadata", `{"ideType":"ANTIGRAVITY","platform":"WINDOWS","pluginType":"GEMINI"}`);
536
+ headers.delete("x-goog-api-key");
537
+ headers.delete("x-api-key");
538
+ headers.delete("x-goog-user-project");
539
+ if (isStreaming)
540
+ headers.set("Accept", "text/event-stream");
541
+ const endpoints = [
542
+ "https://daily-cloudcode-pa.sandbox.googleapis.com",
543
+ "https://cloudcode-pa.googleapis.com",
544
+ ];
545
+ let gotRes = null;
546
+ for (const ep of endpoints) {
547
+ const transformedUrl = `${ep}/v1internal:${action}${isStreaming ? "?alt=sse" : ""}`;
548
+ const r = await fetch(transformedUrl, {
549
+ ...init,
550
+ headers,
551
+ body: wrappedBody,
552
+ });
553
+ if (r.ok) {
554
+ gotRes = r;
555
+ break;
556
+ }
557
+ if (r.status === 429) {
558
+ gotRes = r;
559
+ }
560
+ }
561
+ if (gotRes && gotRes.ok) {
562
+ // Update in-memory quota labels in background after successful response
563
+ if (providerContext?.models) {
564
+ setTimeout(async () => {
565
+ try {
566
+ reloadFromDisk();
567
+ const freshQuota = await getNormalizedQuota(store.keys, rawModel, true);
568
+ const targetModel = providerContext.models[rawModel] ??
569
+ providerContext.models[`antigravity-${rawModel}`];
570
+ if (targetModel) {
571
+ const cleanBase = (targetModel.name ?? rawModel).replace(/\s+5h:.*$/, "");
572
+ targetModel.name = `${cleanBase} 5h: ${freshQuota.fiveHourPercent}% W: ${freshQuota.weeklyPercent}%`;
573
+ }
574
+ }
575
+ catch { }
576
+ }, 2000);
577
+ }
578
+ if (isStreaming && gotRes.body) {
579
+ const transformedStream = gotRes.body.pipeThrough(createSseUnwrapTransform());
580
+ return new Response(transformedStream, {
581
+ status: gotRes.status,
582
+ statusText: gotRes.statusText,
583
+ headers: gotRes.headers,
584
+ });
585
+ }
586
+ return gotRes;
587
+ }
588
+ if (gotRes && gotRes.status === 429) {
589
+ recordRateLimit(store, next.key.id);
590
+ safeSaveStore();
591
+ continue;
592
+ }
593
+ if (gotRes)
594
+ return gotRes;
595
+ }
596
+ }
597
+ }
598
+ return fetch(input, init);
599
+ },
600
+ };
601
+ },
602
+ methods: [
603
+ {
604
+ type: "api",
605
+ label: "Enter NVIDIA NIM API Key",
606
+ async authorize(inputs) {
607
+ const key = inputs?.["apiKey"];
608
+ if (!key)
609
+ return { type: "failed" };
610
+ try {
611
+ const res = await fetch(`${NIM_BASE_URL}/v1/models`, { headers: { Authorization: `Bearer ${key}` } });
612
+ if (!res.ok)
613
+ return { type: "failed" };
614
+ }
615
+ catch (err) {
616
+ console.debug("[nimsuper] authorize fetch failed:", err);
617
+ return { type: "failed" };
618
+ }
619
+ return { type: "success", key, provider: "nvidia" };
620
+ },
621
+ },
622
+ {
623
+ type: "oauth",
624
+ label: "OAuth with Google (Antigravity)",
625
+ async authorize() {
626
+ const auth = authorizeAntigravity();
627
+ return {
628
+ url: auth.url,
629
+ instructions: "Log in with your Google account in your browser",
630
+ method: "code",
631
+ async callback(code) {
632
+ const res = await exchangeAntigravity(code, auth.state);
633
+ if (res.type === "success") {
634
+ return {
635
+ type: "success",
636
+ refresh: res.refresh,
637
+ access: res.access,
638
+ expires: res.expires,
639
+ provider: "antigravity",
640
+ };
641
+ }
642
+ return { type: "failed" };
643
+ },
644
+ };
645
+ },
646
+ },
647
+ ],
648
+ },
649
+ "chat.headers": async (_input, _output) => {
650
+ const provider = detectProviderForRequest(_input);
651
+ if (!provider)
652
+ return;
653
+ reloadFromDisk();
654
+ const prevKeyId = store.lastUsedKeyId;
655
+ const modelIdForRotation = _input.model?.id;
656
+ const next = getNextKey(store, config, modelIdForRotation, provider);
657
+ if (next) {
658
+ if (provider === "antigravity") {
659
+ const authRes = await getOrRefreshAntigravityAccessToken(next.key.key);
660
+ if (authRes) {
661
+ const headers = getAntigravityHeaders(authRes.accessToken, authRes.projectId);
662
+ Object.assign(_output.headers, headers);
663
+ }
664
+ }
665
+ else {
666
+ const headers = getProviderHeaders(provider, next.key.key);
667
+ Object.assign(_output.headers, headers);
668
+ }
669
+ if (prevKeyId && prevKeyId !== next.key.id) {
670
+ resetRateLimit(store, prevKeyId);
671
+ }
672
+ safeSaveStore();
673
+ }
674
+ if (modelIdForRotation && _input.sessionID) {
675
+ const state = getState(_input.sessionID);
676
+ state.currentModelId = modelIdForRotation;
677
+ if (next) {
678
+ state.lastUsedKeyId = next.key.id;
679
+ }
680
+ }
681
+ },
682
+ "chat.message": async (input, output) => {
683
+ const reqModel = output.message.model ?? input.model;
684
+ const reqProviderId = reqModel?.providerID;
685
+ let matchedProvider = null;
686
+ for (const provider of PROVIDERS) {
687
+ const chain = getChainForProvider(provider);
688
+ if (reqModel && findChainIndex(chain, reqModel) >= 0) {
689
+ matchedProvider = provider;
690
+ break;
691
+ }
692
+ const isProvider = typeof reqProviderId === "string" && reqProviderId.toLowerCase().includes(provider);
693
+ if (isProvider) {
694
+ matchedProvider = provider;
695
+ }
696
+ }
697
+ if (!matchedProvider)
698
+ return;
699
+ const chain = getChainForProvider(matchedProvider);
700
+ if (chain.length === 0)
701
+ return;
702
+ const sessionID = input.sessionID;
703
+ const state = getState(sessionID);
704
+ const requestedModel = output.message.model ?? input.model;
705
+ let activeChainKey = state.activeChainKey;
706
+ let activeChainKeyStr = activeChainKey;
707
+ if (!activeChainKeyStr || state.pendingRetryIndex === undefined) {
708
+ if (!requestedModel) {
709
+ cleanupSession(sessionID);
710
+ return;
711
+ }
712
+ activeChainKeyStr = modelKey(requestedModel);
713
+ }
714
+ const chainIndex = findChainIndex(chain, requestedModel);
715
+ if (chainIndex < 0 && state.pendingRetryIndex === undefined) {
716
+ cleanupSession(sessionID);
717
+ return;
718
+ }
719
+ let desiredIndex;
720
+ if (state.pendingRetryIndex !== undefined) {
721
+ desiredIndex = state.pendingRetryIndex;
722
+ }
723
+ else {
724
+ desiredIndex = chainIndex >= 0 ? chainIndex : 0;
725
+ }
726
+ const target = chain[desiredIndex];
727
+ if (!target) {
728
+ cleanupSession(sessionID);
729
+ return;
730
+ }
731
+ output.message.model = {
732
+ providerID: requestedModel?.providerID ?? matchedProvider,
733
+ modelID: target.id,
734
+ };
735
+ state.activeChainKey = activeChainKeyStr;
736
+ state.activeChainModelId = target.id;
737
+ state.sessionProviderId = matchedProvider;
738
+ state.attemptIndex = desiredIndex;
739
+ state.lastUserMessageID = output.message.id;
740
+ },
741
+ "shell.env": async (_input, output) => {
742
+ reloadFromDisk();
743
+ for (const provider of PROVIDERS) {
744
+ const envKeyName = getEnvKeyName(provider);
745
+ if (output.env[envKeyName] !== undefined || getActiveKeys(store, provider).length > 0) {
746
+ const next = getNextKey(store, config, undefined, provider);
747
+ if (next) {
748
+ output.env[envKeyName] = next.key.key;
749
+ safeSaveStore();
750
+ }
751
+ }
752
+ }
753
+ },
754
+ event: async ({ event }) => {
755
+ if (event.type === "session.error") {
756
+ await handleSessionError(event);
757
+ return;
758
+ }
759
+ if (event.type === "session.next.step.failed") {
760
+ await handleSessionStepFailed(event);
761
+ return;
762
+ }
763
+ if (event.type === "session.status") {
764
+ const props = event.properties;
765
+ const sessionID = props?.sessionID;
766
+ const status = props?.status;
767
+ const statusType = status?.type;
768
+ if (statusType === "retry" && sessionID && status) {
769
+ await handleSessionStatusRetry(sessionID, status);
770
+ return;
771
+ }
772
+ if (statusType === "idle" && sessionID) {
773
+ const state = sessions.get(sessionID);
774
+ if (!state)
775
+ return;
776
+ state.rateLimitCount = 0;
777
+ state.pendingRetryIndex = undefined;
778
+ state.lastFailedModelId = undefined;
779
+ if (state.inRetry)
780
+ return;
781
+ cleanupSession(sessionID);
782
+ return;
783
+ }
784
+ }
785
+ if (event.type === "session.idle") {
786
+ const sessionID = event.properties?.sessionID;
787
+ if (!sessionID)
788
+ return;
789
+ const state = sessions.get(sessionID);
790
+ if (!state)
791
+ return;
792
+ if (state.inRetry)
793
+ return;
794
+ state.pendingRetryIndex = undefined;
795
+ state.lastFailedModelId = undefined;
796
+ cleanupSession(sessionID);
797
+ }
798
+ if (event.type === "session.deleted") {
799
+ const sessionID = event.properties?.info?.id;
800
+ if (sessionID)
801
+ cleanupSession(sessionID);
802
+ }
803
+ },
804
+ };
805
+ return hooks;
806
+ };
807
+ export const NimSuperPlugin = SuperocPlugin;
808
+ export default SuperocPlugin;
809
+ //# sourceMappingURL=index.js.map