oh-my-opencode 1.2.3 → 1.2.4

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.
@@ -1130,6 +1130,32 @@ function isRetryableError(status) {
1130
1130
  return true;
1131
1131
  return false;
1132
1132
  }
1133
+ var GCP_PERMISSION_ERROR_PATTERNS = [
1134
+ "PERMISSION_DENIED",
1135
+ "does not have permission",
1136
+ "Cloud AI Companion API has not been used",
1137
+ "has not been enabled"
1138
+ ];
1139
+ function isGcpPermissionError(text) {
1140
+ return GCP_PERMISSION_ERROR_PATTERNS.some((pattern) => text.includes(pattern));
1141
+ }
1142
+ function calculateRetryDelay(attempt) {
1143
+ return Math.min(200 * Math.pow(2, attempt), 2000);
1144
+ }
1145
+ async function isRetryableResponse(response) {
1146
+ if (isRetryableError(response.status))
1147
+ return true;
1148
+ if (response.status === 403) {
1149
+ try {
1150
+ const text = await response.clone().text();
1151
+ if (text.includes("SUBSCRIPTION_REQUIRED") || text.includes("Gemini Code Assist license")) {
1152
+ debugLog3(`[RETRY] 403 SUBSCRIPTION_REQUIRED detected, will retry with next endpoint`);
1153
+ return true;
1154
+ }
1155
+ } catch {}
1156
+ }
1157
+ return false;
1158
+ }
1133
1159
  async function attemptFetch(options) {
1134
1160
  const { endpoint, url, init, accessToken, projectId, sessionId, modelName, thoughtSignature } = options;
1135
1161
  debugLog3(`Trying endpoint: ${endpoint}`);
@@ -1178,18 +1204,40 @@ async function attemptFetch(options) {
1178
1204
  thoughtSignature
1179
1205
  });
1180
1206
  debugLog3(`[REQ] streaming=${transformed.streaming}, url=${transformed.url}`);
1181
- const response = await fetch(transformed.url, {
1182
- method: init.method || "POST",
1183
- headers: transformed.headers,
1184
- body: JSON.stringify(transformed.body),
1185
- signal: init.signal
1186
- });
1187
- debugLog3(`[RESP] status=${response.status} content-type=${response.headers.get("content-type") ?? ""} url=${response.url}`);
1188
- if (!response.ok && isRetryableError(response.status)) {
1189
- debugLog3(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
1190
- return null;
1207
+ const maxPermissionRetries = 10;
1208
+ for (let attempt = 0;attempt <= maxPermissionRetries; attempt++) {
1209
+ const response = await fetch(transformed.url, {
1210
+ method: init.method || "POST",
1211
+ headers: transformed.headers,
1212
+ body: JSON.stringify(transformed.body),
1213
+ signal: init.signal
1214
+ });
1215
+ debugLog3(`[RESP] status=${response.status} content-type=${response.headers.get("content-type") ?? ""} url=${response.url}`);
1216
+ if (response.status === 401) {
1217
+ debugLog3(`[401] Unauthorized response detected, signaling token refresh needed`);
1218
+ return "needs-refresh";
1219
+ }
1220
+ if (response.status === 403) {
1221
+ try {
1222
+ const text = await response.clone().text();
1223
+ if (isGcpPermissionError(text)) {
1224
+ if (attempt < maxPermissionRetries) {
1225
+ const delay = calculateRetryDelay(attempt);
1226
+ debugLog3(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
1227
+ await new Promise((resolve) => setTimeout(resolve, delay));
1228
+ continue;
1229
+ }
1230
+ debugLog3(`[RETRY] GCP permission error, max retries exceeded`);
1231
+ }
1232
+ } catch {}
1233
+ }
1234
+ if (!response.ok && await isRetryableResponse(response)) {
1235
+ debugLog3(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
1236
+ return null;
1237
+ }
1238
+ return response;
1191
1239
  }
1192
- return response;
1240
+ return null;
1193
1241
  } catch (error) {
1194
1242
  debugLog3(`Endpoint failed: ${endpoint} (${error instanceof Error ? error.message : "Unknown error"}), trying next`);
1195
1243
  return null;
@@ -1318,45 +1366,99 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
1318
1366
  const sessionId = getOrCreateSessionId(fetchInstanceId);
1319
1367
  const thoughtSignature = getThoughtSignature(fetchInstanceId);
1320
1368
  debugLog3(`[TSIG][GET] sessionId=${sessionId}, signature=${thoughtSignature ? thoughtSignature.substring(0, 20) + "..." : "none"}`);
1321
- for (let i = 0;i < maxEndpoints; i++) {
1322
- const endpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS[i];
1323
- const response = await attemptFetch({
1324
- endpoint,
1325
- url,
1326
- init,
1327
- accessToken: cachedTokens.access_token,
1328
- projectId,
1329
- sessionId,
1330
- modelName,
1331
- thoughtSignature
1332
- });
1333
- if (response === "pass-through") {
1334
- debugLog3("Non-string body detected, passing through with auth headers");
1335
- const headersWithAuth = {
1336
- ...init.headers,
1337
- Authorization: `Bearer ${cachedTokens.access_token}`
1338
- };
1339
- return fetch(url, { ...init, headers: headersWithAuth });
1340
- }
1341
- if (response) {
1342
- debugLog3(`Success with endpoint: ${endpoint}`);
1343
- const transformedResponse = await transformResponseWithThinking(response, modelName || "", fetchInstanceId);
1344
- return transformedResponse;
1345
- }
1346
- }
1347
- const errorMessage = `All Antigravity endpoints failed after ${maxEndpoints} attempts`;
1348
- debugLog3(errorMessage);
1349
- return new Response(JSON.stringify({
1350
- error: {
1351
- message: errorMessage,
1352
- type: "endpoint_failure",
1353
- code: "all_endpoints_failed"
1369
+ let hasRefreshedFor401 = false;
1370
+ const executeWithEndpoints = async () => {
1371
+ for (let i = 0;i < maxEndpoints; i++) {
1372
+ const endpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS[i];
1373
+ const response = await attemptFetch({
1374
+ endpoint,
1375
+ url,
1376
+ init,
1377
+ accessToken: cachedTokens.access_token,
1378
+ projectId,
1379
+ sessionId,
1380
+ modelName,
1381
+ thoughtSignature
1382
+ });
1383
+ if (response === "pass-through") {
1384
+ debugLog3("Non-string body detected, passing through with auth headers");
1385
+ const headersWithAuth = {
1386
+ ...init.headers,
1387
+ Authorization: `Bearer ${cachedTokens.access_token}`
1388
+ };
1389
+ return fetch(url, { ...init, headers: headersWithAuth });
1390
+ }
1391
+ if (response === "needs-refresh") {
1392
+ if (hasRefreshedFor401) {
1393
+ debugLog3("[401] Already refreshed once, returning unauthorized error");
1394
+ return new Response(JSON.stringify({
1395
+ error: {
1396
+ message: "Authentication failed after token refresh",
1397
+ type: "unauthorized",
1398
+ code: "token_refresh_failed"
1399
+ }
1400
+ }), {
1401
+ status: 401,
1402
+ statusText: "Unauthorized",
1403
+ headers: { "Content-Type": "application/json" }
1404
+ });
1405
+ }
1406
+ debugLog3("[401] Refreshing token and retrying...");
1407
+ hasRefreshedFor401 = true;
1408
+ try {
1409
+ const newTokens = await refreshAccessToken(refreshParts.refreshToken, clientId, clientSecret);
1410
+ cachedTokens = {
1411
+ type: "antigravity",
1412
+ access_token: newTokens.access_token,
1413
+ refresh_token: newTokens.refresh_token,
1414
+ expires_in: newTokens.expires_in,
1415
+ timestamp: Date.now()
1416
+ };
1417
+ clearProjectContextCache();
1418
+ const formattedRefresh = formatTokenForStorage(newTokens.refresh_token, refreshParts.projectId || "", refreshParts.managedProjectId);
1419
+ await client.set(providerId, {
1420
+ access: newTokens.access_token,
1421
+ refresh: formattedRefresh,
1422
+ expires: Date.now() + newTokens.expires_in * 1000
1423
+ });
1424
+ debugLog3("[401] Token refreshed, retrying request...");
1425
+ return executeWithEndpoints();
1426
+ } catch (refreshError) {
1427
+ debugLog3(`[401] Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`);
1428
+ return new Response(JSON.stringify({
1429
+ error: {
1430
+ message: `Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`,
1431
+ type: "unauthorized",
1432
+ code: "token_refresh_failed"
1433
+ }
1434
+ }), {
1435
+ status: 401,
1436
+ statusText: "Unauthorized",
1437
+ headers: { "Content-Type": "application/json" }
1438
+ });
1439
+ }
1440
+ }
1441
+ if (response) {
1442
+ debugLog3(`Success with endpoint: ${endpoint}`);
1443
+ const transformedResponse = await transformResponseWithThinking(response, modelName || "", fetchInstanceId);
1444
+ return transformedResponse;
1445
+ }
1354
1446
  }
1355
- }), {
1356
- status: 503,
1357
- statusText: "Service Unavailable",
1358
- headers: { "Content-Type": "application/json" }
1359
- });
1447
+ const errorMessage = `All Antigravity endpoints failed after ${maxEndpoints} attempts`;
1448
+ debugLog3(errorMessage);
1449
+ return new Response(JSON.stringify({
1450
+ error: {
1451
+ message: errorMessage,
1452
+ type: "endpoint_failure",
1453
+ code: "all_endpoints_failed"
1454
+ }
1455
+ }), {
1456
+ status: 503,
1457
+ statusText: "Service Unavailable",
1458
+ headers: { "Content-Type": "application/json" }
1459
+ });
1460
+ };
1461
+ return executeWithEndpoints();
1360
1462
  };
1361
1463
  }
1362
1464
  // src/auth/antigravity/plugin.ts