[codex] fix cross-database JSON boundary handling (#283)

* fix: normalize cross-database json boundaries

* fix: address json boundary review comments

---------

Co-authored-by: apple <apple@appledeMacBook-Pro.local>
This commit is contained in:
Cita
2026-03-26 20:21:19 +08:00
committed by GitHub
parent 7e1fb8eb1b
commit b3387dfba7
30 changed files with 1134 additions and 97 deletions
@@ -0,0 +1,51 @@
import { describe, expect, it } from 'vitest';
import {
buildCodexOauthInfo,
getCodexOauthInfoFromExtraConfig,
isCodexPlatform,
} from './codexAccount.js';
describe('codexAccount', () => {
it('reads codex oauth info from parsed extra config objects', () => {
const extraConfig = {
oauth: {
provider: 'codex',
accountKey: 'chatgpt-account-123',
refreshToken: 'refresh-token',
},
};
expect(getCodexOauthInfoFromExtraConfig(extraConfig)).toEqual(expect.objectContaining({
provider: 'codex',
accountId: 'chatgpt-account-123',
accountKey: 'chatgpt-account-123',
refreshToken: 'refresh-token',
}));
});
it('recognizes parsed codex extra config objects as codex platform accounts', () => {
expect(isCodexPlatform({
oauth: {
provider: 'codex',
accountKey: 'chatgpt-account-123',
},
})).toBe(true);
});
it('builds codex oauth info from parsed extra config objects', () => {
const oauth = buildCodexOauthInfo({
oauth: {
provider: 'codex',
accountKey: 'chatgpt-account-123',
refreshToken: 'refresh-token',
},
});
expect(oauth).toEqual(expect.objectContaining({
provider: 'codex',
accountId: 'chatgpt-account-123',
accountKey: 'chatgpt-account-123',
refreshToken: 'refresh-token',
}));
});
});
+30 -4
View File
@@ -10,21 +10,47 @@ export type CodexOauthInfo = OauthInfo & {
provider: 'codex';
};
export function getCodexOauthInfoFromExtraConfig(extraConfig?: string | null): CodexOauthInfo | null {
type OauthExtraConfigInput = Parameters<typeof getOauthInfoFromExtraConfig>[0];
type BuildOauthInfoInput = Parameters<typeof buildOauthInfo>[0];
type OauthIdentityCarrierLike = Pick<
typeof schema.accounts.$inferSelect,
'extraConfig' | 'oauthProvider' | 'oauthAccountKey' | 'oauthProjectId'
>;
function isOauthIdentityCarrierLike(value: unknown): value is OauthIdentityCarrierLike {
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
return 'extraConfig' in value
|| 'oauthProvider' in value
|| 'oauthAccountKey' in value
|| 'oauthProjectId' in value;
}
export function getCodexOauthInfoFromExtraConfig(extraConfig?: OauthExtraConfigInput): CodexOauthInfo | null {
const oauth = getOauthInfoFromExtraConfig(extraConfig);
if (!oauth || oauth.provider !== 'codex') return null;
return oauth as CodexOauthInfo;
}
export function isCodexPlatform(
account: Pick<typeof schema.accounts.$inferSelect, 'extraConfig'> | string | null | undefined,
account: OauthIdentityCarrierLike | OauthExtraConfigInput,
): boolean {
return isOauthProvider(account, 'codex');
if (!account || typeof account === 'string') {
return isOauthProvider(account, 'codex');
}
if (isOauthIdentityCarrierLike(account)) {
return isOauthProvider(account, 'codex');
}
return getCodexOauthInfoFromExtraConfig(account) !== null;
}
export function buildCodexOauthInfo(
extraConfig?: string | null,
extraConfig?: BuildOauthInfoInput,
patch: Partial<CodexOauthInfo> = {},
): CodexOauthInfo {
return buildOauthInfo(extraConfig, { provider: 'codex', ...patch }) as CodexOauthInfo;
}
export type {
OauthExtraConfigInput,
OauthIdentityCarrierLike,
};
@@ -58,6 +58,30 @@ describe('oauth account identity helpers', () => {
}));
});
it('reads oauth metadata when extraConfig is already a parsed object', () => {
const oauth = getOauthInfoFromAccount({
oauthProvider: null,
oauthAccountKey: null,
oauthProjectId: null,
extraConfig: {
oauth: {
provider: 'codex',
accountKey: 'json-user',
projectId: 'json-project',
refreshToken: 'refresh-token',
},
},
});
expect(oauth).toEqual(expect.objectContaining({
provider: 'codex',
accountId: 'json-user',
accountKey: 'json-user',
projectId: 'json-project',
refreshToken: 'refresh-token',
}));
});
it('reconstructs oauth runtime state from extraConfig even when identity fields are stripped', () => {
const oauth = getOauthInfoFromAccount({
oauthProvider: 'gemini-cli',
+15 -7
View File
@@ -23,6 +23,8 @@ type ParsedExtraConfig = {
oauth?: ParsedOauthInfo;
};
type ExtraConfigInput = string | Record<string, unknown> | null | undefined;
export type OauthModelDiscoveryStatus = 'healthy' | 'abnormal';
export type OauthInfo = {
@@ -46,7 +48,7 @@ export type OauthInfo = {
export type StoredOauthState = Omit<OauthInfo, 'provider' | 'accountId' | 'accountKey' | 'projectId'>;
type OauthIdentityCarrier = {
extraConfig?: string | null;
extraConfig?: ExtraConfigInput;
oauthProvider?: string | null;
oauthAccountKey?: string | null;
oauthProjectId?: string | null;
@@ -54,11 +56,17 @@ type OauthIdentityCarrier = {
type StoredOauthIdentity = Pick<OauthInfo, 'provider' | 'accountId' | 'accountKey' | 'projectId'>;
function parseExtraConfig(extraConfig?: string | null): ParsedExtraConfig {
function isRecord(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
function parseExtraConfig(extraConfig?: ExtraConfigInput): ParsedExtraConfig {
if (!extraConfig) return {};
if (isRecord(extraConfig)) return extraConfig as ParsedExtraConfig;
if (typeof extraConfig !== 'string') return {};
try {
const parsed = JSON.parse(extraConfig) as unknown;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
if (!isRecord(parsed)) return {};
return parsed as ParsedExtraConfig;
} catch {
return {};
@@ -115,7 +123,7 @@ function asModelDiscoveryStatus(value: unknown): OauthModelDiscoveryStatus | und
return undefined;
}
function parseStoredOauthIdentity(extraConfig?: string | null): StoredOauthIdentity | null {
function parseStoredOauthIdentity(extraConfig?: ExtraConfigInput): StoredOauthIdentity | null {
const parsed = parseExtraConfig(extraConfig).oauth;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
const accountKey = asTrimmedString(parsed.accountKey) || asTrimmedString(parsed.accountId);
@@ -129,7 +137,7 @@ function parseStoredOauthIdentity(extraConfig?: string | null): StoredOauthIdent
};
}
function parseStoredOauthRuntimeState(extraConfig?: string | null): Partial<OauthInfo> | null {
function parseStoredOauthRuntimeState(extraConfig?: ExtraConfigInput): Partial<OauthInfo> | null {
const parsed = parseExtraConfig(extraConfig).oauth;
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null;
return {
@@ -147,7 +155,7 @@ function parseStoredOauthRuntimeState(extraConfig?: string | null): Partial<Oaut
};
}
export function getOauthInfoFromExtraConfig(extraConfig?: string | null): OauthInfo | null {
export function getOauthInfoFromExtraConfig(extraConfig?: ExtraConfigInput): OauthInfo | null {
const identity = parseStoredOauthIdentity(extraConfig);
const runtime = parseStoredOauthRuntimeState(extraConfig);
const provider = identity?.provider;
@@ -212,7 +220,7 @@ export function buildOauthIdentityBackfillPatch(
}
export function buildOauthInfo(
extraConfig?: string | null,
extraConfig?: ExtraConfigInput,
patch: Partial<OauthInfo> = {},
): OauthInfo {
const provider = patch.provider || getOauthInfoFromExtraConfig(extraConfig)?.provider;
+12 -9
View File
@@ -21,13 +21,21 @@ import {
buildStoredOauthState,
buildStoredOauthStateFromAccount,
getOauthInfoFromAccount,
type OauthInfo,
} from './oauthAccount.js';
import { buildCodexOauthInfo } from './codexAccount.js';
import {
buildCodexOauthInfo,
type OauthExtraConfigInput,
type OauthIdentityCarrierLike,
} from './codexAccount.js';
import { ensureOauthIdentityBackfill } from './oauthIdentityBackfill.js';
import { buildQuotaSnapshotFromOauthInfo, refreshOauthQuotaSnapshot } from './quota.js';
type OAuthProviderMetadata = ReturnType<typeof listOauthProviders>[number];
const MANUAL_CALLBACK_DELAY_MS = 15_000;
type OauthProviderHeaderAccountInput = OauthIdentityCarrierLike & {
extraConfig?: OauthExtraConfigInput;
};
type OAuthStartInstructions = {
redirectUri: string;
@@ -597,13 +605,8 @@ export async function startOauthRebindFlow(accountId: number, requestOrigin?: st
}
export function buildOauthProviderHeaders(input: {
account?: {
extraConfig?: string | null;
oauthProvider?: string | null;
oauthAccountKey?: string | null;
oauthProjectId?: string | null;
} | null;
extraConfig?: string | null;
account?: OauthProviderHeaderAccountInput | null;
extraConfig?: OauthExtraConfigInput;
downstreamHeaders?: Record<string, unknown>;
}) {
const oauth = getOauthInfoFromAccount(input.account || {
@@ -619,7 +622,7 @@ export function buildOauthProviderHeaders(input: {
}
export function buildCodexOauthProviderHeaders(input: {
extraConfig?: string | null;
extraConfig?: OauthExtraConfigInput;
downstreamHeaders?: Record<string, unknown>;
}) {
const oauth = buildCodexOauthInfo(input.extraConfig);