Files
flynn/src/daemon/clientFactory.test.ts
T
William Valentin 1d126cddfb feat: add Zhipu AI (GLM) model provider support
Adds zhipuai as a new provider using the OpenAI-compatible API at
api.z.ai. Supports api_key config or ZHIPUAI_API_KEY env var, with
optional endpoint override.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:55:13 -08:00

188 lines
6.5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { createClientFromConfig, anthropicToGitHubModel, createAutoFallbackClient } from './index.js';
import { AnthropicClient } from '../models/anthropic.js';
import { OpenAIClient } from '../models/openai.js';
import { OllamaClient } from '../models/local/ollama.js';
import { LlamaCppClient } from '../models/local/llamacpp.js';
import { GeminiClient } from '../models/gemini.js';
import { BedrockClient } from '../models/bedrock.js';
import { GitHubModelsClient } from '../models/github.js';
describe('createClientFromConfig', () => {
it('creates AnthropicClient for anthropic provider', () => {
const client = createClientFromConfig({
provider: 'anthropic',
model: 'claude-sonnet-4-5-20250514',
api_key: 'sk-ant-test',
});
expect(client).toBeInstanceOf(AnthropicClient);
});
it('creates OpenAIClient for openai provider', () => {
const client = createClientFromConfig({
provider: 'openai',
model: 'gpt-4o',
api_key: 'sk-test',
});
expect(client).toBeInstanceOf(OpenAIClient);
});
it('creates OllamaClient for ollama provider', () => {
const client = createClientFromConfig({
provider: 'ollama',
model: 'llama3.2:1b',
endpoint: 'http://localhost:11434',
});
expect(client).toBeInstanceOf(OllamaClient);
});
it('creates OllamaClient with num_gpu option', () => {
const client = createClientFromConfig({
provider: 'ollama',
model: 'llama3.2:1b',
num_gpu: 0,
});
expect(client).toBeInstanceOf(OllamaClient);
});
it('creates LlamaCppClient for llamacpp provider', () => {
const client = createClientFromConfig({
provider: 'llamacpp',
model: 'ministral-reasoning',
endpoint: 'http://localhost:8080',
});
expect(client).toBeInstanceOf(LlamaCppClient);
});
it('defaults llamacpp endpoint to localhost:8080', () => {
const client = createClientFromConfig({
provider: 'llamacpp',
model: 'test-model',
});
expect(client).toBeInstanceOf(LlamaCppClient);
});
it('creates GeminiClient for gemini provider', () => {
const client = createClientFromConfig({
provider: 'gemini',
model: 'gemini-2.5-pro',
api_key: 'test-key',
});
expect(client).toBeInstanceOf(GeminiClient);
});
it('throws for unknown provider', () => {
expect(() => createClientFromConfig({
provider: 'unknown' as 'anthropic',
model: 'test',
})).toThrow('Unknown model provider: unknown');
});
it('creates OpenAIClient with OpenRouter baseURL for openrouter provider', () => {
const client = createClientFromConfig({
provider: 'openrouter',
model: 'meta-llama/llama-3.1-70b',
api_key: 'test-key',
});
expect(client).toBeInstanceOf(OpenAIClient);
});
it('creates OpenAIClient with Zhipu AI baseURL for zhipuai provider', () => {
const client = createClientFromConfig({
provider: 'zhipuai',
model: 'glm-4.5',
api_key: 'test-key',
});
expect(client).toBeInstanceOf(OpenAIClient);
});
it('creates BedrockClient for bedrock provider', () => {
const client = createClientFromConfig({
provider: 'bedrock',
model: 'anthropic.claude-3-sonnet',
});
expect(client).toBeInstanceOf(BedrockClient);
});
it('creates GitHubModelsClient for github provider', () => {
const client = createClientFromConfig({
provider: 'github',
model: 'claude-sonnet-4.5',
});
expect(client).toBeInstanceOf(GitHubModelsClient);
});
it('auto-maps Anthropic model names to GitHub equivalents for github provider', () => {
// User might copy-paste the Anthropic model name into a github fallback block
const client = createClientFromConfig({
provider: 'github',
model: 'claude-sonnet-4-5-20250929',
});
expect(client).toBeInstanceOf(GitHubModelsClient);
});
});
describe('anthropicToGitHubModel', () => {
it('maps claude-sonnet-4-20250514 to claude-sonnet-4', () => {
expect(anthropicToGitHubModel('claude-sonnet-4-20250514')).toBe('claude-sonnet-4');
});
it('maps claude-sonnet-4-5-20250929 to claude-sonnet-4.5', () => {
expect(anthropicToGitHubModel('claude-sonnet-4-5-20250929')).toBe('claude-sonnet-4.5');
});
it('maps claude-opus-4-20250514 to claude-opus-4', () => {
expect(anthropicToGitHubModel('claude-opus-4-20250514')).toBe('claude-opus-4');
});
it('maps claude-opus-4-5-20250918 to claude-opus-4.5', () => {
expect(anthropicToGitHubModel('claude-opus-4-5-20250918')).toBe('claude-opus-4.5');
});
it('maps claude-opus-4-6-20250715 to claude-opus-4.6', () => {
expect(anthropicToGitHubModel('claude-opus-4-6-20250715')).toBe('claude-opus-4.6');
});
it('maps claude-3-5-haiku-20241022 to claude-haiku-4.5', () => {
expect(anthropicToGitHubModel('claude-3-5-haiku-20241022')).toBe('claude-haiku-4.5');
});
it('maps claude-haiku-4-5-20251001 to claude-haiku-4.5', () => {
expect(anthropicToGitHubModel('claude-haiku-4-5-20251001')).toBe('claude-haiku-4.5');
});
it('strips date suffix and converts trailing version number with dot for unknown models', () => {
// "claude-sonnet-5-7-20260101" → strip date → "claude-sonnet-5-7" → dot → "claude-sonnet-5.7"
expect(anthropicToGitHubModel('claude-sonnet-5-7-20260101')).toBe('claude-sonnet-5.7');
});
it('strips date suffix for models without sub-version', () => {
// "claude-sonnet-5-20260101" → strip date → "claude-sonnet-5" (no trailing -N to dot-convert)
expect(anthropicToGitHubModel('claude-sonnet-5-20260101')).toBe('claude-sonnet-5');
});
it('returns undefined for models without date suffix', () => {
expect(anthropicToGitHubModel('llama3.2:1b')).toBeUndefined();
});
});
describe('createAutoFallbackClient', () => {
it('creates a GitHubModelsClient for anthropic provider', () => {
const client = createAutoFallbackClient({
provider: 'anthropic',
model: 'claude-sonnet-4-20250514',
});
expect(client).toBeInstanceOf(GitHubModelsClient);
});
it('returns undefined for non-anthropic providers', () => {
expect(createAutoFallbackClient({ provider: 'openai', model: 'gpt-4o' })).toBeUndefined();
expect(createAutoFallbackClient({ provider: 'ollama', model: 'llama3.2:1b' })).toBeUndefined();
expect(createAutoFallbackClient({ provider: 'gemini', model: 'gemini-2.5-pro' })).toBeUndefined();
});
it('returns undefined for unmappable anthropic models', () => {
expect(createAutoFallbackClient({ provider: 'anthropic', model: 'custom-model' })).toBeUndefined();
});
});