Download server/src/__tests__/lib/client-classifier.test.ts from automindy/fokemal: direct link, hf CLI and curl.
- Browser
- Download file 5.65 kB
-
https://huggingface.co/spaces/automindy/fokemal/resolve/main/server/src/__tests__/lib/client-classifier.test.ts
- Command line
-
hf download hf://spaces/automindy/fokemal/server/src/__tests__/lib/client-classifier.test.ts
-
curl -L -o client-classifier.test.ts https://huggingface.co/spaces/automindy/fokemal/resolve/main/server/src/__tests__/lib/client-classifier.test.ts
5.65 kB
| import { describe, expect, it } from 'vitest'; | |
| import type { Request } from 'express'; | |
| import { classifyClientAgent } from '../../lib/client-classifier.js'; | |
| function request(path: string, headers: Record<string, string> = {}): Request { | |
| return { | |
| originalUrl: path, | |
| headers, | |
| } as unknown as Request; | |
| } | |
| describe('client agent classification', () => { | |
| it('prefers distinctive headers and protocol paths', () => { | |
| expect(classifyClientAgent(request('/v1/messages', { | |
| 'x-claude-code-session-id': 'session', | |
| }))).toBe('claude-code'); | |
| expect(classifyClientAgent(request('/v1/responses'))).toBe('codex'); | |
| expect(classifyClientAgent(request('/v1beta/models/x:generateContent'))).toBe('gemini-cli'); | |
| expect(classifyClientAgent(request('/api/chat'))).toBe('ollama-client'); | |
| expect(classifyClientAgent(request('/v1/t/revocable/responses'))).toBe('codex'); | |
| expect(classifyClientAgent(request('/v1/t/revocable/api/chat'))).toBe('ollama-client'); | |
| }); | |
| it('recognizes common coding-agent user agents', () => { | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'opencode/1.2.3', | |
| }))).toBe('opencode'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'aider/0.80', | |
| }))).toBe('aider'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'Kilo-Code/4.2', | |
| }))).toBe('kilo-code'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'crush/0.8', | |
| }))).toBe('crush'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'deepseek-harness/0.3.1 (+https://github.com/deepseek-ai/deepseek-harness)', | |
| }))).toBe('deepseek-harness'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'atomcode/5.0.9', | |
| }))).toBe('atomcode'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'RooCode/3.50.4', | |
| }))).toBe('roo'); | |
| }); | |
| it('recognizes OpenClaw by the static header its generated provider sends', () => { | |
| // A custom provider in OpenClaw gets the bare openai-node UA, so the | |
| // generator sets `headers: { User-Agent: 'openclaw' }`; the bare word and | |
| // the `openclaw/<version>` form OpenClaw uses natively both count. | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'openclaw', | |
| }))).toBe('openclaw'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'openclaw/2026.9.4', | |
| }))).toBe('openclaw'); | |
| // The unmodified SDK UA still files under openai-sdk, so a hand-written | |
| // provider without the header is not misattributed. | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'OpenAI/JS 7.8.0', | |
| }))).toBe('openai-sdk'); | |
| }); | |
| it('recognizes Hermes Agent by its header and its model-probe UA', () => { | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'hermes-agent', | |
| }))).toBe('hermes-agent'); | |
| expect(classifyClientAgent(request('/v1/models', { | |
| 'user-agent': 'hermes-cli/0.21.3', | |
| }))).toBe('hermes-agent'); | |
| }); | |
| it('recognizes Pi by its header and its native UA without catching other `pi` substrings', () => { | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'pi-coding-agent', | |
| }))).toBe('pi'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'pi/0.99.2 (darwin; node/v24.4.0; arm64)', | |
| }))).toBe('pi'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'OpenAI/JS 6.10.0', | |
| }))).toBe('openai-sdk'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'my-api-client/1.0', | |
| }))).not.toBe('pi'); | |
| }); | |
| it('recognizes Reasonix by the UA it sends to every endpoint', () => { | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'Reasonix/2.29.0', | |
| }))).toBe('reasonix'); | |
| // Its web_fetch tool is not a model request from the agent loop. | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'reasonix-web-fetch/1.0', | |
| }))).not.toBe('reasonix'); | |
| }); | |
| it('separates MiMo Code from the OpenCode it derives from', () => { | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'mimo/0.4.0', | |
| }))).toBe('mimo-code'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'MiMo-Code/0.4.0 (opencode)', | |
| }))).toBe('mimo-code'); | |
| expect(classifyClientAgent(request('/v1/chat/completions', { | |
| 'user-agent': 'opencode/1.2.3', | |
| }))).toBe('opencode'); | |
| }); | |
| it('recognizes Claude Code by its real claude-cli user agent', () => { | |
| expect(classifyClientAgent(request('/v1/messages', { | |
| 'user-agent': 'claude-cli/1.0.62 (external, cli)', | |
| }))).toBe('claude-code'); | |
| expect(classifyClientAgent(request('/v1/messages', { | |
| 'x-session-id': 'affinity', | |
| }))).toBe('claude-code'); | |
| expect(classifyClientAgent(request('/v1/messages'))).toBe('anthropic-sdk'); | |
| }); | |
| it('lets user agents beat wire-format fallbacks', () => { | |
| // Qwen Code speaks the Gemini wire; it must not count as gemini-cli. | |
| expect(classifyClientAgent(request('/v1beta/models/x:generateContent', { | |
| 'user-agent': 'QwenCode/0.5 qwen-code', | |
| }))).toBe('qwen-code'); | |
| // A plain openai SDK on /v1/responses is not Codex. | |
| expect(classifyClientAgent(request('/v1/responses', { | |
| 'user-agent': 'OpenAI/NodeJS/4.52.0', | |
| }))).toBe('openai-sdk'); | |
| }); | |
| }); | |