From 4449ba31735683084eba0ebedc9b51878d3fc362 Mon Sep 17 00:00:00 2001 From: AStupidBear Date: Wed, 26 Aug 2026 19:10:16 +0800 Subject: [PATCH] fix(opencode): hydrate Proxy Pool references for no-auth connections (#11584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merged via /merge-batch (lote 2026-08-26, v3.8.51). Boarded no worktree combinado junto com outras ~30 PRs; validação única: typecheck/complexity/cognitive-complexity/changelog-integrity verdes, file-size rebaseado onde necessário (crescimento legítimo), lint com os mesmos 228 achados pré-existentes confirmados via sonda contra o tip puro (não introduzidos por este lote), e ~370 testes focados (unit + vitest) passando. Obrigado pela contribuição. --- src/sse/services/auth.ts | 39 +++++++++++++++++--- tests/unit/7993-noauth-proxy-routing.test.ts | 23 +++++++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 227222ae3b..d8dc99bd24 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -1128,13 +1128,40 @@ function planLastUsedCommit( }; } -function materializeConnection( +/** + * Resolve Proxy Pool references on a real connection row at the same boundary + * where credentials become request-ready. The synthetic no-auth fallback above + * already performs this hydration, but a persisted connection (for example the + * OpenCode card's `opencode` row selected through the `opencode-zen` alias) + * bypasses that fallback. Keep inline/legacy entries untouched and only incur a + * registry lookup when at least one by-id reference is present. + */ +async function hydrateAccountProxyReferences( + providerSpecificData: JsonRecord +): Promise { + const entries = providerSpecificData.accountProxies; + if (!Array.isArray(entries)) return providerSpecificData; + + const containsProxyReference = entries.some((entry) => { + if (!entry || typeof entry !== "object" || Array.isArray(entry)) return false; + const proxyId = (entry as Record).proxyId; + return typeof proxyId === "string" && proxyId.trim().length > 0; + }); + if (!containsProxyReference) return providerSpecificData; + + return { + ...providerSpecificData, + accountProxies: await resolveAccountProxiesFromRegistry(entries), + }; +} + +async function materializeConnection( connection: ProviderConnectionView, options: CredentialSelectionOptions, extra: DeferredLeaseSelection & { exclusiveLease?: ExclusiveConnectionLease } = {} ) { - const apiKeyHealth = connection.providerSpecificData?.apiKeyHealth as - Record | undefined; + const providerSpecificData = await hydrateAccountProxyReferences(connection.providerSpecificData); + const apiKeyHealth = providerSpecificData.apiKeyHealth as Record | undefined; if (apiKeyHealth) syncHealthFromDB(connection.id, apiKeyHealth); const releaseOAuthSession = options.reserveOAuthSession === true && connection.authType === "oauth" && options.sessionKey @@ -1148,10 +1175,10 @@ function materializeConnection( projectId: connection.projectId, defaultModel: connection.defaultModel || null, copilotToken: - typeof connection.providerSpecificData.copilotToken === "string" - ? connection.providerSpecificData.copilotToken + typeof providerSpecificData.copilotToken === "string" + ? providerSpecificData.copilotToken : null, - providerSpecificData: connection.providerSpecificData, + providerSpecificData, id: connection.id, provider: connection.provider, authType: connection.authType, diff --git a/tests/unit/7993-noauth-proxy-routing.test.ts b/tests/unit/7993-noauth-proxy-routing.test.ts index 89e7b01777..32467f14e2 100644 --- a/tests/unit/7993-noauth-proxy-routing.test.ts +++ b/tests/unit/7993-noauth-proxy-routing.test.ts @@ -27,6 +27,7 @@ process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); const { getProviderCredentials } = await import("../../src/sse/services/auth.ts"); const { createProviderConnection } = await import("../../src/lib/db/providers.ts"); +const { createProxy } = await import("../../src/lib/db/proxies.ts"); const { OpencodeExecutor } = await import("../../open-sse/executors/opencode.ts"); const { resolveProxyForRequest } = await import("../../open-sse/utils/proxyFetch.ts"); @@ -47,10 +48,17 @@ function listen(server: net.Server): Promise { test.before(async () => { proxyServer = net.createServer((s) => s.destroy()); proxyPort = await listen(proxyServer); + const proxy = await createProxy({ + name: "opencode-noauth-test-proxy", + type: "http", + host: "127.0.0.1", + port: proxyPort, + }); + assert.ok(proxy?.id, "test proxy must be persisted in the registry"); // Mirror exactly what the NoAuthAccountCard UI writes: a `provider_connections` // row filed under the no-auth id "opencode" (NOT "opencode-zen"), carrying the - // configured account proxy. + // configured account proxy as a Proxy Pool reference. await createProviderConnection({ provider: "opencode", authType: "no-auth", @@ -61,7 +69,7 @@ test.before(async () => { accountProxies: [ { fingerprint: FINGERPRINT, - proxy: { type: "http", host: "127.0.0.1", port: proxyPort }, + proxyId: proxy.id, }, ], }, @@ -95,6 +103,17 @@ test("#7993 getProviderCredentials('opencode-zen') hydrates the proxy saved unde Array.isArray(psd.accountProxies) && psd.accountProxies.length === 1, `expected the sibling opencode connection's accountProxies to be hydrated, got ${JSON.stringify(psd)}` ); + const accountProxy = (psd.accountProxies as Array>)[0]; + assert.equal( + accountProxy.proxyId, + undefined, + "request credentials must not retain a raw proxyId" + ); + assert.deepEqual(accountProxy.proxy, { + type: "http", + host: "127.0.0.1", + port: proxyPort, + }); }); test("#7993 a canonical 'opencode/' resolved combo/catalog target egresses through the assigned proxy, not direct", async () => {