Files
gbrain/test/ai/capabilities.test.ts
T
7e4094b2cd fix(ai): OpenRouter family-scoped prompt caching + expansion on chat-capable openai-compat recipes (#3152)
Takeover of #1988 (OpenRouter prompt caching), reimplemented on current
master: supports_prompt_cache may now be a per-model-id predicate; the
OpenRouter recipe marks openai/* chat and anthropic/claude-* routes
cacheable. Claude routes get an explicit cache_control on the system
content block via the recipe compat fetch shim (OpenRouter's documented
per-block format, not a top-level body field), signaled through a private
in-process marker header instead of the promptCacheKey sentinel that now
collides with the real OpenAI prompt_cache_key derivation. Cache reads on
OpenAI-compatible routes surface via the SDK's cachedInputTokens.

Root fix for #1135: deepseek, groq, and together now declare expansion
touchpoints (their expansion path is the same plain OpenAI-compatible
languageModel call as chat), so an explicit expansion_model pointed at
them no longer silently yields zero expansion.

Co-authored-by: Garry Tan <garrytan@gmail.com>
Co-authored-by: tmchow <tmchow@users.noreply.github.com>
Co-authored-by: warkcod <warkcod@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 13:07:00 -07:00

93 lines
4.0 KiB
TypeScript

import { describe, it, expect } from 'bun:test';
import { getProviderCapabilities, classifyCapabilities } from '../../src/core/ai/capabilities.ts';
describe('getProviderCapabilities (v0.38 Slice 1 — D6/D7 recipe-driven capabilities)', () => {
it('returns full capabilities for Anthropic (canonical reference)', () => {
const caps = getProviderCapabilities('anthropic:claude-sonnet-4-6');
expect(caps.supportsToolCalling).toBe(true);
expect(caps.supportsPromptCaching).toBe(true);
expect(caps.supportsParallelTools).toBe(true);
expect(caps.maxContext).toBe(200000);
});
it('returns capabilities for OpenAI (no prompt caching field set as true)', () => {
const caps = getProviderCapabilities('openai:gpt-5.2');
expect(caps.supportsToolCalling).toBe(true);
expect(caps.supportsPromptCaching).toBe(false); // OpenAI implicit caching doesn't get marked
expect(caps.maxContext).toBe(200000);
});
it('returns capabilities for Google Gemini', () => {
const caps = getProviderCapabilities('google:gemini-1.5-pro');
expect(caps.supportsToolCalling).toBe(true);
expect(caps.supportsPromptCaching).toBe(false);
expect(caps.maxContext).toBe(1000000); // Gemini 1.5 Pro
});
it('marks OpenRouter OpenAI/Anthropic routes as cache-capable (per-model predicate)', () => {
const openaiCaps = getProviderCapabilities('openrouter:openai/gpt-5.2');
expect(openaiCaps.supportsToolCalling).toBe(true);
expect(openaiCaps.supportsPromptCaching).toBe(true);
const anthropicCaps = getProviderCapabilities('openrouter:anthropic/claude-sonnet-4.6');
expect(anthropicCaps.supportsToolCalling).toBe(true);
expect(anthropicCaps.supportsPromptCaching).toBe(true);
});
it('does not mark every OpenRouter route as cache-capable', () => {
const caps = getProviderCapabilities('openrouter:deepseek/deepseek-chat');
expect(caps.supportsToolCalling).toBe(true);
expect(caps.supportsPromptCaching).toBe(false);
});
it('honors Anthropic alias (undated → dated)', () => {
const caps = getProviderCapabilities('anthropic:claude-haiku-4-5');
expect(caps.supportsToolCalling).toBe(true);
});
it('throws for unknown provider', () => {
expect(() => getProviderCapabilities('madeup-provider:foo')).toThrow();
});
it('throws for embedding-only provider (no chat touchpoint)', () => {
expect(() => getProviderCapabilities('voyage:voyage-3-large')).toThrow(
/does not offer a chat touchpoint/,
);
});
it('throws for missing colon', () => {
expect(() => getProviderCapabilities('claude-sonnet-4-6')).toThrow(/missing a provider prefix/);
});
});
describe('classifyCapabilities (D6 — three-tier capability verdict)', () => {
it('returns ok for fully-capable Anthropic models', () => {
expect(classifyCapabilities('anthropic:claude-sonnet-4-6')).toBe('ok');
expect(classifyCapabilities('anthropic:claude-opus-4-7')).toBe('ok');
});
it('returns degraded:no_caching for OpenAI (tools yes, caching no)', () => {
expect(classifyCapabilities('openai:gpt-5.2')).toBe('degraded:no_caching');
});
it('returns degraded:no_caching for Google Gemini', () => {
expect(classifyCapabilities('google:gemini-1.5-pro')).toBe('degraded:no_caching');
});
it('returns ok for cacheable OpenRouter routes, degraded:no_caching otherwise', () => {
expect(classifyCapabilities('openrouter:openai/gpt-5.2')).toBe('ok');
expect(classifyCapabilities('openrouter:anthropic/claude-sonnet-4.6')).toBe('ok');
expect(classifyCapabilities('openrouter:deepseek/deepseek-chat')).toBe('degraded:no_caching');
});
it('returns unknown for unrecognized providers', () => {
expect(classifyCapabilities('madeup:something')).toBe('unknown');
});
it('returns unknown for embedding-only providers (chat touchpoint missing)', () => {
// Voyage has no chat touchpoint → throws inside getProviderCapabilities
// → classifyCapabilities catches → returns 'unknown'.
expect(classifyCapabilities('voyage:voyage-3-large')).toBe('unknown');
});
});