mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 19:49:14 +00:00
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>
93 lines
4.0 KiB
TypeScript
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');
|
|
});
|
|
});
|