Refactor proxy-core surfaces and transformer boundaries (#163)
* refactor: add canonical request contracts * test: harden transformer boundary contracts * refactor: adapt protocol transformers to canonical contract * refactor: add cli profile registry * refactor: extract provider profiles * refactor: add conductor and provider executors * refactor: add models surface * refactor: migrate responses surface * refactor: migrate chat surface * refactor: migrate gemini surface * refactor: migrate file surfaces * test: align runtime executor undici mock * fix: preserve compact SSE closeout payloads * feat: support all-api-hub v2 backup imports * fix: aggregate codex oauth SSE final payloads * fix: harden responses SSE failure handling
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import type { CliProfileDefinition } from './types.js';
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
const claudeCodeUserIdPattern = /^user_[0-9a-f]{64}_account__session_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
function isClaudeSurface(path: string): boolean {
|
||||
const normalizedPath = path.trim().toLowerCase();
|
||||
return normalizedPath === '/v1/messages'
|
||||
|| normalizedPath === '/anthropic/v1/messages'
|
||||
|| normalizedPath === '/v1/messages/count_tokens';
|
||||
}
|
||||
|
||||
export function extractClaudeCodeSessionId(userId: string): string | null {
|
||||
const trimmed = userId.trim();
|
||||
if (!claudeCodeUserIdPattern.test(trimmed)) return null;
|
||||
|
||||
const sessionPrefix = '__session_';
|
||||
const sessionIndex = trimmed.lastIndexOf(sessionPrefix);
|
||||
if (sessionIndex === -1) return null;
|
||||
|
||||
const sessionId = trimmed.slice(sessionIndex + sessionPrefix.length).trim();
|
||||
return sessionId || null;
|
||||
}
|
||||
|
||||
export const claudeCodeCliProfile: CliProfileDefinition = {
|
||||
id: 'claude_code',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: false,
|
||||
supportsResponsesWebsocketIncremental: false,
|
||||
preservesContinuation: true,
|
||||
supportsCountTokens: true,
|
||||
echoesTurnState: false,
|
||||
},
|
||||
detect(input) {
|
||||
if (!isClaudeSurface(input.downstreamPath)) return null;
|
||||
if (!isRecord(input.body) || !isRecord(input.body.metadata)) return null;
|
||||
|
||||
const userId = typeof input.body.metadata.user_id === 'string'
|
||||
? input.body.metadata.user_id.trim()
|
||||
: '';
|
||||
const sessionId = userId ? extractClaudeCodeSessionId(userId) : null;
|
||||
if (!sessionId) return null;
|
||||
|
||||
return {
|
||||
id: 'claude_code',
|
||||
sessionId,
|
||||
traceHint: sessionId,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { CliProfileDefinition, DetectCliProfileInput } from './types.js';
|
||||
|
||||
function headerValueToString(value: unknown): string | null {
|
||||
if (typeof value === 'string') {
|
||||
const trimmed = value.trim();
|
||||
return trimmed || null;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
for (const item of value) {
|
||||
if (typeof item !== 'string') continue;
|
||||
const trimmed = item.trim();
|
||||
if (trimmed) return trimmed;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getHeaderValue(headers: Record<string, unknown> | undefined, targetKey: string): string | null {
|
||||
if (!headers) return null;
|
||||
const normalizedTarget = targetKey.trim().toLowerCase();
|
||||
|
||||
for (const [rawKey, rawValue] of Object.entries(headers)) {
|
||||
if (rawKey.trim().toLowerCase() !== normalizedTarget) continue;
|
||||
return headerValueToString(rawValue);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasHeaderPrefix(headers: Record<string, unknown> | undefined, prefix: string): boolean {
|
||||
if (!headers) return false;
|
||||
const normalizedPrefix = prefix.trim().toLowerCase();
|
||||
return Object.entries(headers).some(([rawKey, rawValue]) => {
|
||||
const key = rawKey.trim().toLowerCase();
|
||||
return key.startsWith(normalizedPrefix) && !!headerValueToString(rawValue);
|
||||
});
|
||||
}
|
||||
|
||||
function isCodexPath(path: string): boolean {
|
||||
const normalizedPath = path.trim().toLowerCase();
|
||||
return normalizedPath.startsWith('/v1/responses')
|
||||
|| normalizedPath === '/v1/chat/completions'
|
||||
|| normalizedPath.startsWith('/v1/messages');
|
||||
}
|
||||
|
||||
export function isCodexResponsesSurface(headers?: Record<string, unknown>): boolean {
|
||||
return isCodexRequest({
|
||||
downstreamPath: '/v1/responses',
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
export function getCodexSessionId(headers?: Record<string, unknown>): string | null {
|
||||
return getHeaderValue(headers, 'session_id') || getHeaderValue(headers, 'session-id');
|
||||
}
|
||||
|
||||
export function isCodexRequest(input: DetectCliProfileInput): boolean {
|
||||
if (!isCodexPath(input.downstreamPath)) return false;
|
||||
const headers = input.headers;
|
||||
if (!headers) return false;
|
||||
|
||||
const originator = getHeaderValue(headers, 'originator');
|
||||
if (originator?.toLowerCase() === 'codex_cli_rs') return true;
|
||||
if (getHeaderValue(headers, 'openai-beta')) return true;
|
||||
if (hasHeaderPrefix(headers, 'x-stainless-')) return true;
|
||||
if (getCodexSessionId(headers)) return true;
|
||||
if (getHeaderValue(headers, 'x-codex-turn-state')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export const codexCliProfile: CliProfileDefinition = {
|
||||
id: 'codex',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: true,
|
||||
supportsResponsesWebsocketIncremental: true,
|
||||
preservesContinuation: true,
|
||||
supportsCountTokens: false,
|
||||
echoesTurnState: true,
|
||||
},
|
||||
detect(input) {
|
||||
if (!isCodexRequest(input)) return null;
|
||||
|
||||
const sessionId = getCodexSessionId(input.headers) || undefined;
|
||||
return {
|
||||
id: 'codex',
|
||||
...(sessionId ? { sessionId, traceHint: sessionId } : {}),
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { CliProfileDefinition } from './types.js';
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isGeminiCliPath(path: string): boolean {
|
||||
const normalizedPath = path.trim().toLowerCase();
|
||||
return normalizedPath === '/v1internal:generatecontent'
|
||||
|| normalizedPath === '/v1internal:streamgeneratecontent'
|
||||
|| normalizedPath === '/v1internal:counttokens';
|
||||
}
|
||||
|
||||
function hasGeminiCliBodyShape(body: unknown): boolean {
|
||||
if (!isRecord(body)) return false;
|
||||
return typeof body.model === 'string'
|
||||
|| Array.isArray(body.contents)
|
||||
|| (isRecord(body.request) && (Array.isArray(body.request.contents) || typeof body.request.model === 'string'));
|
||||
}
|
||||
|
||||
export const geminiCliProfile: CliProfileDefinition = {
|
||||
id: 'gemini_cli',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: false,
|
||||
supportsResponsesWebsocketIncremental: false,
|
||||
preservesContinuation: false,
|
||||
supportsCountTokens: true,
|
||||
echoesTurnState: false,
|
||||
},
|
||||
detect(input) {
|
||||
if (!isGeminiCliPath(input.downstreamPath)) return null;
|
||||
if (input.body !== undefined && !hasGeminiCliBodyShape(input.body)) return null;
|
||||
return {
|
||||
id: 'gemini_cli',
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { CliProfileDefinition } from './types.js';
|
||||
|
||||
export const genericCliProfile: CliProfileDefinition = {
|
||||
id: 'generic',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: false,
|
||||
supportsResponsesWebsocketIncremental: false,
|
||||
preservesContinuation: false,
|
||||
supportsCountTokens: false,
|
||||
echoesTurnState: false,
|
||||
},
|
||||
detect() {
|
||||
return {
|
||||
id: 'generic',
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { detectCliProfile } from './registry.js';
|
||||
|
||||
describe('detectCliProfile', () => {
|
||||
it('detects Codex responses requests and exposes Codex capability flags', () => {
|
||||
expect(detectCliProfile({
|
||||
downstreamPath: '/v1/responses',
|
||||
headers: {
|
||||
originator: 'codex_cli_rs',
|
||||
Session_id: 'codex-session-123',
|
||||
},
|
||||
})).toEqual({
|
||||
id: 'codex',
|
||||
sessionId: 'codex-session-123',
|
||||
traceHint: 'codex-session-123',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: true,
|
||||
supportsResponsesWebsocketIncremental: true,
|
||||
preservesContinuation: true,
|
||||
supportsCountTokens: false,
|
||||
echoesTurnState: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('treats x-codex-turn-state as a Codex marker even when session_id is absent', () => {
|
||||
expect(detectCliProfile({
|
||||
downstreamPath: '/v1/responses',
|
||||
headers: {
|
||||
'x-codex-turn-state': 'turn-state-123',
|
||||
},
|
||||
})).toMatchObject({
|
||||
id: 'codex',
|
||||
capabilities: {
|
||||
supportsResponsesWebsocketIncremental: true,
|
||||
echoesTurnState: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('detects Claude Code requests on the count_tokens surface and exposes token counting support', () => {
|
||||
expect(detectCliProfile({
|
||||
downstreamPath: '/v1/messages/count_tokens',
|
||||
body: {
|
||||
metadata: {
|
||||
user_id: 'user_20836b5653ed68aa981604f502c0a491397f6053826a93c953423632578d38ad_account__session_f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
||||
},
|
||||
},
|
||||
})).toEqual({
|
||||
id: 'claude_code',
|
||||
sessionId: 'f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
||||
traceHint: 'f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: false,
|
||||
supportsResponsesWebsocketIncremental: false,
|
||||
preservesContinuation: true,
|
||||
supportsCountTokens: true,
|
||||
echoesTurnState: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('detects Gemini CLI internal routes and exposes Gemini CLI capability flags', () => {
|
||||
expect(detectCliProfile({
|
||||
downstreamPath: '/v1internal:countTokens',
|
||||
body: {
|
||||
model: 'gpt-4.1',
|
||||
contents: [{ role: 'user', parts: [{ text: 'hello' }] }],
|
||||
},
|
||||
})).toEqual({
|
||||
id: 'gemini_cli',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: false,
|
||||
supportsResponsesWebsocketIncremental: false,
|
||||
preservesContinuation: false,
|
||||
supportsCountTokens: true,
|
||||
echoesTurnState: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to generic for native Gemini routes', () => {
|
||||
expect(detectCliProfile({
|
||||
downstreamPath: '/v1beta/models/gemini-2.5-flash:generateContent',
|
||||
body: {
|
||||
contents: [{ role: 'user', parts: [{ text: 'hello' }] }],
|
||||
},
|
||||
})).toEqual({
|
||||
id: 'generic',
|
||||
capabilities: {
|
||||
supportsResponsesCompact: false,
|
||||
supportsResponsesWebsocketIncremental: false,
|
||||
preservesContinuation: false,
|
||||
supportsCountTokens: false,
|
||||
echoesTurnState: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { claudeCodeCliProfile } from './claudeCodeProfile.js';
|
||||
import { codexCliProfile } from './codexProfile.js';
|
||||
import { geminiCliProfile } from './geminiCliProfile.js';
|
||||
import { genericCliProfile } from './genericProfile.js';
|
||||
import type { CliProfileDefinition, CliProfileId, DetectCliProfileInput, DetectedCliProfile } from './types.js';
|
||||
|
||||
const orderedProfiles: CliProfileDefinition[] = [
|
||||
claudeCodeCliProfile,
|
||||
codexCliProfile,
|
||||
geminiCliProfile,
|
||||
];
|
||||
|
||||
export const cliProfileRegistry: Record<CliProfileId, CliProfileDefinition> = {
|
||||
generic: genericCliProfile,
|
||||
codex: codexCliProfile,
|
||||
claude_code: claudeCodeCliProfile,
|
||||
gemini_cli: geminiCliProfile,
|
||||
};
|
||||
|
||||
export function getCliProfileDefinition(id: CliProfileId): CliProfileDefinition {
|
||||
return cliProfileRegistry[id];
|
||||
}
|
||||
|
||||
export function detectCliProfile(input: DetectCliProfileInput): DetectedCliProfile {
|
||||
for (const profile of orderedProfiles) {
|
||||
const detected = profile.detect(input);
|
||||
if (!detected) continue;
|
||||
return {
|
||||
...detected,
|
||||
capabilities: profile.capabilities,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: genericCliProfile.id,
|
||||
capabilities: genericCliProfile.capabilities,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
export type CliProfileId =
|
||||
| 'generic'
|
||||
| 'codex'
|
||||
| 'claude_code'
|
||||
| 'gemini_cli';
|
||||
|
||||
export type CliProfileCapabilities = {
|
||||
supportsResponsesCompact: boolean;
|
||||
supportsResponsesWebsocketIncremental: boolean;
|
||||
preservesContinuation: boolean;
|
||||
supportsCountTokens: boolean;
|
||||
echoesTurnState: boolean;
|
||||
};
|
||||
|
||||
export type DetectCliProfileInput = {
|
||||
downstreamPath: string;
|
||||
headers?: Record<string, unknown>;
|
||||
body?: unknown;
|
||||
};
|
||||
|
||||
export type DetectedCliProfile = {
|
||||
id: CliProfileId;
|
||||
sessionId?: string;
|
||||
traceHint?: string;
|
||||
capabilities: CliProfileCapabilities;
|
||||
};
|
||||
|
||||
export type CliProfileDefinition = {
|
||||
id: CliProfileId;
|
||||
capabilities: CliProfileCapabilities;
|
||||
detect(input: DetectCliProfileInput): Omit<DetectedCliProfile, 'capabilities'> | null;
|
||||
};
|
||||
Reference in New Issue
Block a user