diff --git a/package-lock.json b/package-lock.json index f229f53933..ca74f5f42e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39238,6 +39238,7 @@ "uuid": "^9.0.1", "which": "^5.0.0", "ws": "^8.14.2", + "yaml": "2.9.1", "zod": "^4.4.3" }, "devDependencies": { @@ -40096,6 +40097,21 @@ "node": "^18.17.0 || >=20.5.0" } }, + "packages/server/node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "packages/server/node_modules/yocto-queue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", diff --git a/packages/server/package.json b/packages/server/package.json index 726bf2601d..a4fd019585 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -144,6 +144,7 @@ "uuid": "^9.0.1", "which": "^5.0.0", "ws": "^8.14.2", + "yaml": "2.9.1", "zod": "^4.4.3" }, "devDependencies": { diff --git a/packages/server/src/server/agent/providers/omp/usage-reference.ts b/packages/server/src/server/agent/providers/omp/usage-reference.ts index 3f681228fd..ca5f4efc34 100644 --- a/packages/server/src/server/agent/providers/omp/usage-reference.ts +++ b/packages/server/src/server/agent/providers/omp/usage-reference.ts @@ -3,6 +3,7 @@ import { createRequire } from "node:module"; import { join } from "node:path"; import { z } from "zod"; +import { parse as parseYaml } from "yaml"; import type { UsageReference } from "../../agent-sdk-types.js"; import { resolveOmpDiagnosticPaths } from "./provider-config.js"; @@ -42,11 +43,10 @@ interface UsageDb { close(): void; } const localRequire = createRequire(import.meta.url); -const yaml = localRequire("js-yaml") as { load(value: string): unknown }; function readYaml(file: string): unknown { try { - return yaml.load(readFileSync(file, "utf8")); + return parseYaml(readFileSync(file, "utf8")); } catch { return undefined; } @@ -68,7 +68,11 @@ function hasKeyOverride(provider: string, env: NodeJS.ProcessEnv, agentDir: stri return models.success && models.data.providers?.[provider]?.apiKey !== undefined; } -function readCredential(db: UsageDb, provider: string, sessionId: string): unknown { +function readCredential( + db: UsageDb, + provider: string, + sessionId: string, +): z.infer | null { // Upstream: packages/ai/src/auth/affinity.ts:83-88,115-132; sqlite-credential-store.ts:447-448,711-722. const cache = db .prepare( @@ -78,11 +82,14 @@ function readCredential(db: UsageDb, provider: string, sessionId: string): unkno if (cache) { const sticky = stickySchema.safeParse(JSON.parse(String(cache.value))); if (!sticky.success || sticky.data.type !== "oauth") return null; - return db - .prepare( - "SELECT id, provider, credential_type, data, disabled_cause FROM auth_credentials WHERE id = ?", - ) - .get(sticky.data.credentialId); + return parseOAuthCredential( + db + .prepare( + "SELECT id, provider, credential_type, data, disabled_cause FROM auth_credentials WHERE id = ?", + ) + .get(sticky.data.credentialId), + provider, + ); } // Upstream: packages/ai/src/auth/cascade.ts:276-345 selects OAuth before env or stored API keys. const rows = db @@ -90,11 +97,16 @@ function readCredential(db: UsageDb, provider: string, sessionId: string): unkno "SELECT id, provider, credential_type, data, disabled_cause FROM auth_credentials WHERE provider = ? AND credential_type = 'oauth' AND disabled_cause IS NULL", ) .all(provider); - const validRows = rows.filter((row) => validOAuthRow(row, provider)); + const validRows = rows + .map((row) => parseOAuthCredential(row, provider)) + .filter((row): row is z.infer => row !== null); return validRows.length === 1 ? validRows[0] : null; } -function validOAuthRow(value: unknown, provider: string): boolean { +function parseOAuthCredential( + value: unknown, + provider: string, +): z.infer | null { const row = rowSchema.safeParse(value); if ( !row.success || @@ -102,12 +114,13 @@ function validOAuthRow(value: unknown, provider: string): boolean { row.data.credential_type !== "oauth" || row.data.disabled_cause !== null ) - return false; + return null; try { + // Upstream: packages/ai/src/auth/sqlite-credential-store.ts:117-155 stores OAuth data as JSON without type. const oauth = oauthSchema.safeParse(JSON.parse(row.data.data)); - return oauth.success && oauth.data.expires > Date.now(); + return oauth.success && oauth.data.expires > Date.now() ? oauth.data : null; } catch { - return false; + return null; } } @@ -135,25 +148,16 @@ export function resolveOmpUsageReference( return null; } try { - const row = rowSchema.safeParse(readCredential(db, provider, sessionId)); - if ( - !row.success || - row.data.provider !== provider || - row.data.credential_type !== "oauth" || - row.data.disabled_cause !== null - ) - return null; - // Upstream: packages/ai/src/auth/sqlite-credential-store.ts:117-155 stores OAuth data as JSON without type. - const oauth = oauthSchema.safeParse(JSON.parse(row.data.data)); - if (!oauth.success || oauth.data.expires <= Date.now()) return null; + const oauth = readCredential(db, provider, sessionId); + if (!oauth) return null; if (source === "claude") { - return { source, input: { accessToken: oauth.data.access } }; + return { source, input: { accessToken: oauth.access } }; } return { source, input: { - accessToken: oauth.data.access, - ...(oauth.data.accountId ? { accountId: oauth.data.accountId } : {}), + accessToken: oauth.access, + ...(oauth.accountId ? { accountId: oauth.accountId } : {}), }, }; } catch {