mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
fix(token-refresh): discover projectId during token refresh (#8860)
* fix(token-refresh): discover projectId during token refresh The token refresh path (tokenRefresh.ts) did not discover projectId for antigravity/agy accounts. Dashboard and health check refresh use this path, not the executor path. Add ensureAntigravityProjectAssigned call after refreshGoogleToken for antigravity/agy providers when projectId is empty. Signed-off-by: Minxi Hou <houminxi@gmail.com> * chore(quality): rebaseline the token-refresh test file + changelog fragment tests/unit/token-refresh-service.test.ts 1311->1378 (+67) — the four cases covering projectId discovery on the tokenRefresh.ts path. Growth is the tests this PR adds, nothing else. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Signed-off-by: Minxi Hou <houminxi@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
1
changelog.d/fixes/8860-tokenrefresh-projectid.md
Normal file
1
changelog.d/fixes/8860-tokenrefresh-projectid.md
Normal file
@@ -0,0 +1 @@
|
||||
- **Antigravity**: the shared token-refresh service now recovers a missing `projectId` too — #8842 fixed the executor path, but the Dashboard and the health check refresh through `open-sse/services/tokenRefresh.ts`, which kept returning credentials with an empty project and left those accounts on 422
|
||||
@@ -197,7 +197,7 @@
|
||||
"tests/unit/search-handler-extended.test.ts": 1071,
|
||||
"tests/unit/sse-auth.test.ts": 1600,
|
||||
"tests/unit/stream-utils.test.ts": 2445,
|
||||
"tests/unit/token-refresh-service.test.ts": 1311,
|
||||
"tests/unit/token-refresh-service.test.ts": 1378,
|
||||
"tests/unit/translator-openai-responses-req.test.ts": 1194,
|
||||
"tests/unit/translator-openai-to-gemini.test.ts": 1616,
|
||||
"tests/unit/translator-openai-to-kiro.test.ts": 1250,
|
||||
@@ -409,5 +409,6 @@
|
||||
"_rebaseline_2026_07_27_v3849_train2": "Merge-train 2 (7 PRs) — owner-approved 2026-07-27. Single entry: chatCore.ts 4955->5006 (#8595, Responses multi-turn image compaction before the context hard-reject). Genuine irreducible growth at the existing compaction chokepoint in handleChatCore — the PR adds a last-resort retry against the concrete budget plus the estimateFinalInputTokens helper, both wired at the pre-existing call site rather than a new branch. Covered by tests/unit/8560-responses-image-compaction.test.ts (4 tests).",
|
||||
"_rebaseline_2026_07_27_v3849_train3": "Merge-train 3 (13 PRs) — owner-approved 2026-07-27. Both entries are genuine irreducible growth at existing chokepoints, not new branches: src/lib/db/apiKeys.ts 1518->1529 (#8805 cx/* ≡ codex/* API-key model permissions); open-sse/handlers/chatCore.ts 5006->5020 (#8806 real response payload into plugin onResponse hooks). Covered by tests/unit/db-apiKeys-crud.test.ts (4 new cases) and the two plugin-hook test files updated in #8806 respectively.",
|
||||
"_rebaseline_2026_07_28_8842_antigravity_projectid_refresh": "PR #8842 (fix/antigravity-projectid-refresh) own growth: open-sse/executors/antigravity.ts 1493->1528 (+35 = projectId discovery in refreshCredentials: import ensureAntigravityProjectAssigned + trim projectId + call ensureAntigravityProjectAssigned with 8s timeout + persistDiscoveredAntigravityProjectId + log success/failure). Irreducible wiring at the existing credential-refresh chokepoint. Covered by tests/unit/executor-antigravity.test.ts (4 new test cases).",
|
||||
"_rebaseline_2026_07_28_8842_antigravity_test": "PR #8842 test growth: tests/unit/executor-antigravity.test.ts new testFrozen 1098 (4 new test cases for projectId discovery during refresh: discovers when empty, skips when set, handles failure, skips when access_token not a string). All above cap 1000 on day one."
|
||||
"_rebaseline_2026_07_28_8842_antigravity_test": "PR #8842 test growth: tests/unit/executor-antigravity.test.ts new testFrozen 1098 (4 new test cases for projectId discovery during refresh: discovers when empty, skips when set, handles failure, skips when access_token not a string). All above cap 1000 on day one.",
|
||||
"_rebaseline_2026_07_28_8860_tokenrefresh_projectid": "PR #8860 (fix/antigravity-projectid-centralized) own test growth: tests/unit/token-refresh-service.test.ts 1311->1378 (+67 = 4 cases covering projectId discovery on the tokenRefresh.ts path — the Dashboard/health-check refresh route, which #8842 did not reach since that fixed the executor path). Covered by the same file."
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ import { refreshKimiCodingToken } from "./tokenRefresh/providers/kimiCoding.ts";
|
||||
import { refreshGitLabDuoToken } from "./tokenRefresh/providers/gitlabDuo.ts";
|
||||
import { refreshClaudeOAuthToken } from "./tokenRefresh/providers/claudeOAuth.ts";
|
||||
import { refreshGoogleToken } from "./tokenRefresh/providers/google.ts";
|
||||
import { ensureAntigravityProjectAssigned } from "./antigravityProjectBootstrap.ts";
|
||||
import { persistDiscoveredAntigravityProjectId } from "./antigravityProjectPersist.ts";
|
||||
import { refreshCodexToken } from "./tokenRefresh/providers/codex.ts";
|
||||
import { refreshKiroToken } from "./tokenRefresh/providers/kiro.ts";
|
||||
import { refreshQoderToken } from "./tokenRefresh/providers/qoder.ts";
|
||||
@@ -275,8 +277,8 @@ async function _getAccessTokenInternal(provider, credentials, log, proxyConfig:
|
||||
|
||||
case "gemini":
|
||||
case "antigravity":
|
||||
case "agy":
|
||||
return await refreshGoogleToken(
|
||||
case "agy": {
|
||||
const result = await refreshGoogleToken(
|
||||
credentials.refreshToken,
|
||||
PROVIDERS[provider].clientId,
|
||||
PROVIDERS[provider].clientSecret,
|
||||
@@ -284,6 +286,45 @@ async function _getAccessTokenInternal(provider, credentials, log, proxyConfig:
|
||||
proxyConfig
|
||||
);
|
||||
|
||||
// Google One AI accounts get no projectId at OAuth exchange time.
|
||||
// Recover it via loadCodeAssist so downstream routing works.
|
||||
if (
|
||||
result?.accessToken &&
|
||||
(provider === "antigravity" || provider === "agy") &&
|
||||
!(credentials.projectId || credentials.providerSpecificData?.projectId)
|
||||
) {
|
||||
try {
|
||||
const discovered = await ensureAntigravityProjectAssigned(
|
||||
result.accessToken,
|
||||
fetch
|
||||
);
|
||||
if (discovered) {
|
||||
result.projectId = discovered;
|
||||
result.providerSpecificData = {
|
||||
...(credentials.providerSpecificData || {}),
|
||||
...(result.providerSpecificData || {}),
|
||||
projectId: discovered,
|
||||
};
|
||||
if (credentials.connectionId) {
|
||||
await persistDiscoveredAntigravityProjectId(
|
||||
credentials.connectionId,
|
||||
discovered,
|
||||
credentials.providerSpecificData
|
||||
);
|
||||
}
|
||||
log?.info?.("TOKEN", "Antigravity projectId discovered during token refresh", {
|
||||
projectId: discovered,
|
||||
});
|
||||
}
|
||||
} catch (discoveryError) {
|
||||
const msg = discoveryError instanceof Error ? discoveryError.message : String(discoveryError);
|
||||
log?.warn?.("TOKEN", `Antigravity projectId discovery failed: ${msg}`);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
case "claude":
|
||||
return await refreshClaudeOAuthToken(credentials.refreshToken, log, proxyConfig);
|
||||
|
||||
|
||||
@@ -795,6 +795,73 @@ test("supportsTokenRefresh, isUnrecoverableRefreshError and formatProviderCreden
|
||||
assert.equal(formatProviderCredentials("missing-provider", {}, log), null);
|
||||
});
|
||||
|
||||
test("getAccessToken discovers projectId for antigravity when stored value is empty", async () => {
|
||||
const log = createLog();
|
||||
const { clearAntigravityProjectCache } = await import("../../open-sse/services/antigravityProjectBootstrap.ts");
|
||||
clearAntigravityProjectCache();
|
||||
|
||||
let fetchCalls: string[] = [];
|
||||
await withMockedFetch(async (url, init) => {
|
||||
const urlStr = String(url);
|
||||
fetchCalls.push(urlStr);
|
||||
if (urlStr.includes("oauth2.googleapis.com/token")) {
|
||||
return jsonResponse({
|
||||
access_token: "new-token-1",
|
||||
refresh_token: "new-refresh",
|
||||
expires_in: 3600,
|
||||
});
|
||||
}
|
||||
if (urlStr.includes("loadCodeAssist")) {
|
||||
return jsonResponse({ cloudaicompanionProject: "discovered-project" });
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
}, async () => {
|
||||
const result = await getAccessToken("antigravity", {
|
||||
refreshToken: "refresh",
|
||||
projectId: "",
|
||||
connectionId: "conn-1",
|
||||
providerSpecificData: {},
|
||||
});
|
||||
assert.equal(result.projectId, "discovered-project");
|
||||
assert.ok(fetchCalls.some((u) => u.includes("loadCodeAssist")), "should call loadCodeAssist");
|
||||
});
|
||||
clearAntigravityProjectCache();
|
||||
});
|
||||
|
||||
|
||||
test("getAccessToken handles projectId discovery failure gracefully for antigravity", async () => {
|
||||
const log = createLog();
|
||||
const { clearAntigravityProjectCache } = await import("../../open-sse/services/antigravityProjectBootstrap.ts");
|
||||
clearAntigravityProjectCache();
|
||||
tokenRefresh._clearTokenRotationMap();
|
||||
|
||||
await withMockedFetch(async (url) => {
|
||||
const urlStr = String(url);
|
||||
if (urlStr.includes("oauth2.googleapis.com/token")) {
|
||||
return jsonResponse({
|
||||
access_token: "new-token-3",
|
||||
refresh_token: "new-refresh",
|
||||
expires_in: 3600,
|
||||
});
|
||||
}
|
||||
if (urlStr.includes("loadCodeAssist")) {
|
||||
return new Response("server error", { status: 500 });
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
}, async () => {
|
||||
const result = await getAccessToken("antigravity", {
|
||||
refreshToken: "refresh",
|
||||
projectId: "",
|
||||
connectionId: "conn-1",
|
||||
providerSpecificData: {},
|
||||
});
|
||||
assert.equal(result.accessToken, "new-token-3");
|
||||
assert.ok(result, "should return a result without throwing");
|
||||
});
|
||||
clearAntigravityProjectCache();
|
||||
});
|
||||
|
||||
|
||||
test("getAccessToken deduplicates concurrent refreshes for the same provider and token", async () => {
|
||||
const log = createLog();
|
||||
let fetchCount = 0;
|
||||
|
||||
Reference in New Issue
Block a user