[codex] Fix Anyrouter model sync shield handling (#348)
* fix anyrouter model sync shield handling * fix shield challenge message precedence
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user