179 lines
5.2 KiB
TypeScript
179 lines
5.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
detectDownstreamClientContext,
|
|
extractClaudeCodeSessionId,
|
|
isCodexResponsesSurface,
|
|
} from './downstreamClientContext.js';
|
|
|
|
describe('extractClaudeCodeSessionId', () => {
|
|
it('extracts session uuid from axonhub-compatible Claude Code user ids', () => {
|
|
expect(extractClaudeCodeSessionId(
|
|
'user_20836b5653ed68aa981604f502c0a491397f6053826a93c953423632578d38ad_account__session_f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
|
)).toBe('f25958b8-e75c-455d-8b40-f006d87cc2a4');
|
|
});
|
|
|
|
it('returns null for non-Claude-Code user ids', () => {
|
|
expect(extractClaudeCodeSessionId('user_123')).toBe(null);
|
|
expect(extractClaudeCodeSessionId('session_f25958b8-e75c-455d-8b40-f006d87cc2a4')).toBe(null);
|
|
});
|
|
});
|
|
|
|
describe('isCodexResponsesSurface', () => {
|
|
it('detects Codex responses surface from originator, stainless, and turn-state headers', () => {
|
|
expect(isCodexResponsesSurface({
|
|
originator: 'codex_cli_rs',
|
|
})).toBe(true);
|
|
|
|
expect(isCodexResponsesSurface({
|
|
'x-stainless-lang': 'typescript',
|
|
})).toBe(true);
|
|
|
|
expect(isCodexResponsesSurface({
|
|
'x-codex-turn-state': 'turn-state-123',
|
|
})).toBe(true);
|
|
});
|
|
|
|
it('returns false for generic responses clients', () => {
|
|
expect(isCodexResponsesSurface({
|
|
'content-type': 'application/json',
|
|
})).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('detectDownstreamClientContext', () => {
|
|
it('recognizes Codex requests and attaches Session_id as session and trace hint', () => {
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/responses',
|
|
headers: {
|
|
originator: 'codex_cli_rs',
|
|
Session_id: 'codex-session-123',
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'codex',
|
|
sessionId: 'codex-session-123',
|
|
traceHint: 'codex-session-123',
|
|
});
|
|
});
|
|
|
|
it('keeps Codex requests without Session_id as client-only context', () => {
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/responses/compact',
|
|
headers: {
|
|
'x-stainless-lang': 'typescript',
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'codex',
|
|
});
|
|
});
|
|
|
|
it('recognizes Claude Code requests from metadata.user_id without mutating the body', () => {
|
|
const body = {
|
|
model: 'claude-opus-4-6',
|
|
metadata: {
|
|
user_id: 'user_20836b5653ed68aa981604f502c0a491397f6053826a93c953423632578d38ad_account__session_f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
|
},
|
|
};
|
|
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/messages',
|
|
body,
|
|
})).toEqual({
|
|
clientKind: 'claude_code',
|
|
sessionId: 'f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
|
traceHint: 'f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
|
});
|
|
expect(body).toEqual({
|
|
model: 'claude-opus-4-6',
|
|
metadata: {
|
|
user_id: 'user_20836b5653ed68aa981604f502c0a491397f6053826a93c953423632578d38ad_account__session_f25958b8-e75c-455d-8b40-f006d87cc2a4',
|
|
},
|
|
});
|
|
});
|
|
|
|
it('falls back to generic when Claude metadata.user_id is missing or invalid', () => {
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/messages',
|
|
body: {
|
|
metadata: {
|
|
user_id: 'user_123',
|
|
},
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'generic',
|
|
});
|
|
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/messages',
|
|
body: {
|
|
metadata: {
|
|
session_id: 'abc123',
|
|
},
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'generic',
|
|
});
|
|
});
|
|
|
|
it('recognizes Gemini CLI internal routes as a first-class downstream client kind', () => {
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1internal:generateContent',
|
|
body: {
|
|
model: 'gpt-4.1',
|
|
contents: [
|
|
{
|
|
role: 'user',
|
|
parts: [{ text: 'hello' }],
|
|
},
|
|
],
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'gemini_cli',
|
|
});
|
|
});
|
|
|
|
it('recognizes app fingerprints alongside a generic protocol family', () => {
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/chat/completions',
|
|
headers: {
|
|
'x-title': 'Cherry Studio',
|
|
'http-referer': 'https://cherry-ai.com',
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'generic',
|
|
clientAppId: 'cherry_studio',
|
|
clientAppName: 'Cherry Studio',
|
|
clientConfidence: 'exact',
|
|
});
|
|
});
|
|
|
|
it('keeps protocol family detection when an app fingerprint also matches', () => {
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/responses',
|
|
headers: {
|
|
originator: 'codex_cli_rs',
|
|
'x-title': 'Cherry Studio',
|
|
'http-referer': 'https://cherry-ai.com',
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'codex',
|
|
clientAppId: 'cherry_studio',
|
|
clientAppName: 'Cherry Studio',
|
|
clientConfidence: 'exact',
|
|
});
|
|
});
|
|
|
|
it('marks weak app-only matches as heuristic instead of upgrading protocol behavior', () => {
|
|
expect(detectDownstreamClientContext({
|
|
downstreamPath: '/v1/chat/completions',
|
|
headers: {
|
|
'user-agent': 'CherryStudio/1.2.3',
|
|
},
|
|
})).toEqual({
|
|
clientKind: 'generic',
|
|
clientAppId: 'cherry_studio',
|
|
clientAppName: 'Cherry Studio',
|
|
clientConfidence: 'heuristic',
|
|
});
|
|
});
|
|
});
|