[codex] Fix Anyrouter model sync shield handling (#348)

* fix anyrouter model sync shield handling

* fix shield challenge message precedence
This commit is contained in:
Cita
2026-03-31 19:32:25 +08:00
committed by GitHub
parent 87390651f4
commit d930b87762
4 changed files with 181 additions and 3 deletions
@@ -4,6 +4,7 @@ import { AddressInfo } from 'node:net';
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { NewApiAdapter } from './newApi.js';
import { AnyRouterAdapter } from './anyrouter.js';
interface RequestSnapshot {
method: string;
@@ -25,6 +26,7 @@ const SHIELD_LOGIN_COOKIE = 'challenge-seed';
const COOKIE_ONLY_LOGIN_USERNAME = 'cookie-only-user';
const COOKIE_ONLY_LOGIN_PASSWORD = 'cookie-only-pass';
const COOKIE_ONLY_LOGIN_SESSION = 'cookie-only-session';
const OPENAI_MODELS_SHIELDED_TOKEN = 'openai-models-shielded-token';
const COOKIE_SHIELDED_TOKEN = Buffer.from(
`1771864970|${Buffer.from('username=linuxdo_131936').toString('base64')}|sig`,
).toString('base64');
@@ -67,6 +69,34 @@ describe('NewApiAdapter', () => {
});
if (req.url === '/v1/models') {
if (typeof req.headers.authorization === 'string' && req.headers.authorization === `Bearer ${OPENAI_MODELS_SHIELDED_TOKEN}`) {
const cookieHeader = typeof req.headers.cookie === 'string' ? req.headers.cookie : '';
if (!cookieHeader.includes(`acw_sc__v2=${ANYROUTER_CHALLENGE_ACW}`)) {
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Set-Cookie': `cdn_sec_tc=${SHIELD_LOGIN_COOKIE}; Path=/; HttpOnly`,
});
res.end(ANYROUTER_CHALLENGE_HTML);
return;
}
if (
!cookieHeader.includes(`cdn_sec_tc=${SHIELD_LOGIN_COOKIE}`)
|| !cookieHeader.includes(`session=${OPENAI_MODELS_SHIELDED_TOKEN}`)
) {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: { message: 'missing shield cookie context' } }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: [
{ id: 'claude-sonnet-4-5-20250929' },
{ id: 'claude-opus-4-6' },
],
}));
return;
}
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: { message: 'invalid token' } }));
return;
@@ -426,6 +456,29 @@ describe('NewApiAdapter', () => {
).toBe(true);
});
it('reuses shield cookie retry when anyrouter /v1/models returns challenge html', async () => {
const adapter = new AnyRouterAdapter();
const models = await adapter.getModels(baseUrl, OPENAI_MODELS_SHIELDED_TOKEN);
expect(models).toEqual(['claude-sonnet-4-5-20250929', 'claude-opus-4-6']);
expect(
requests.some(
(r) =>
r.url === '/v1/models'
&& typeof r.headers.cookie === 'string'
&& r.headers.cookie.includes(`session=${OPENAI_MODELS_SHIELDED_TOKEN}`),
),
).toBe(true);
expect(
requests.some(
(r) =>
r.url === '/v1/models'
&& typeof r.headers.cookie === 'string'
&& r.headers.cookie.includes(`acw_sc__v2=${ANYROUTER_CHALLENGE_ACW}`),
),
).toBe(true);
});
it('parses token list response with data.items[] shape', async () => {
const adapter = new NewApiAdapter();
const token = await adapter.getApiToken(baseUrl, 'session-token', 11494);
+29 -1
View File
@@ -2,6 +2,7 @@ import { ApiTokenInfo, BasePlatformAdapter, CheckinResult, BalanceInfo, UserInfo
import type { RequestInit as UndiciRequestInit } from 'undici';
import { createContext, runInContext } from 'node:vm';
import { withSiteProxyRequestInit } from '../siteProxy.js';
import { fetchJsonWithShieldCookieRetry } from './newApiShield.js';
export class NewApiAdapter extends BasePlatformAdapter {
readonly platformName: string = 'new-api';
@@ -761,12 +762,39 @@ export class NewApiAdapter extends BasePlatformAdapter {
return [];
}
private extractOpenAiModels(payload: any): string[] {
if (!Array.isArray(payload?.data)) return [];
return payload.data.map((m: any) => m?.id).filter(Boolean);
}
private async getOpenAiModelsViaShieldCookie(baseUrl: string, token: string): Promise<string[]> {
for (const cookie of this.buildCookieCandidates(token)) {
try {
const { data } = await fetchJsonWithShieldCookieRetry<any>(`${baseUrl}/v1/models`, {
headers: {
Authorization: `Bearer ${token}`,
Cookie: cookie,
},
});
const models = this.extractOpenAiModels(data);
if (models.length > 0) return models;
} catch {}
}
return [];
}
private async getOpenAiModels(baseUrl: string, token: string): Promise<string[]> {
const shouldTryShieldCookie = this.platformName === 'anyrouter' || token.includes('=');
if (shouldTryShieldCookie) {
const shieldModels = await this.getOpenAiModelsViaShieldCookie(baseUrl, token);
if (shieldModels.length > 0) return shieldModels;
}
try {
const res = await this.fetchJson<any>(`${baseUrl}/v1/models`, {
headers: { Authorization: `Bearer ${token}` },
});
return (res?.data || []).map((m: any) => m.id).filter(Boolean);
return this.extractOpenAiModels(res);
} catch {
return [];
}