@kaged/llm
Pure-fetch LLM provider interface supporting Anthropic, OpenAI, Google, and Antigravity API shapes with SSE streaming, cost calculation, and model discovery
31
source files
8
test files
~5.2k
lines
✓ 110 pass
tests
pass
typecheck
clean
lint
Test results 110
estimateTokens — algorithm selection > uses tiktoken for anthropic models
[170.46ms]
estimateTokens — algorithm selection > uses tiktoken for openai models
[0.160ms]
estimateTokens — algorithm selection > uses fallback for google/gemini models
[0.130ms]
estimateTokens — algorithm selection > uses fallback for groq models
[0.030ms]
estimateTokens — algorithm selection > uses fallback when modelMeta is null
[0.020ms]
estimateTokens — reservedOutputTokens > defaults to 4096
[0.130ms]
estimateTokens — reservedOutputTokens > echoes custom value
[0.100ms]
estimateTokens — reservedOutputTokens > totalTokens = inputTokens + reservedOutputTokens
[0.160ms]
estimateTokens — context window and fraction > contextWindow comes from modelMeta.maxInputTokens
[0.120ms]
estimateTokens — context window and fraction > contextWindow is null when modelMeta is null
[0.030ms]
estimateTokens — context window and fraction > fraction is totalTokens / contextWindow
[0.090ms]
estimateTokens — context window and fraction > fraction uses FALLBACK_CONTEXT_WINDOW when modelMeta is null
[0.040ms]
estimateTokens — token counting > inputTokens increases with more messages
[0.200ms]
estimateTokens — token counting > inputTokens increases with longer messages
[0.140ms]
estimateTokens — token counting > inputTokens increases with longer system prompt
[0.260ms]
estimateTokens — token counting > system prompt as array is joined and counted
[0.150ms]
estimateTokens — token counting > empty messages and empty system prompt produces minimal tokens
[0.050ms]
estimateTokens — message types > counts system messages
[0.080ms]
estimateTokens — message types > counts tool result messages
[0.140ms]
estimateTokens — message types > counts assistant tool call messages
[0.110ms]
estimateTokens — message types > counts thinking content in assistant messages
[0.130ms]
estimateTokens — message types > counts multimodal user messages with images
[0.140ms]
estimateTokens — conservative estimation > estimate is non-zero for any non-empty input
[0.110ms]
estimateTokens — conservative estimation > tiktoken and fallback both produce positive counts for same input
[0.150ms]
ModelMeta — tokenizer field > anthropic models have tiktoken tokenizer
[0.050ms]
ModelMeta — tokenizer field > openai models have tiktoken tokenizer
[0.030ms]
ModelMeta — tokenizer field > ollama models have unknown tokenizer
[0.020ms]
estimateTokens — large message lists > handles 100 messages without error
[2.90ms]
estimateTokens — plugin memory in system prompt > counts plugin-wrapped content in system prompt
[0.580ms]
lookupModelMeta — catalog key format > resolves anthropic models via provider/modelId key
[0.040ms]
lookupModelMeta — catalog key format > resolves openai models via provider/modelId key
[0.030ms]
lookupModelMeta — catalog key format > resolves ollama models via provider/modelId key
[0.030ms]
lookupModelMeta — catalog key format > returns null for unknown model
[0.020ms]
lookupModelMeta — catalog key format > returns null for unknown provider
[0.010ms]
lookupModelMeta — capabilities > claude-sonnet-4 reports reasoning and vision
[0.030ms]
lookupModelMeta — capabilities > claude-haiku-3.5 does not report reasoning
[0.020ms]
lookupModelMeta — capabilities > gpt-5 reports reasoning
[0.020ms]
lookupModelMeta — pricing > extracts non-zero pricing for paid models
[0.030ms]
lookupModelMeta — pricing > reasoning pricing is non-null for models that have it
[0.030ms]
lookupModelMeta — pricing > reasoning pricing is null for models without it
[0.020ms]
lookupModelMeta — pricing > ollama models have zero pricing
[0.030ms]
lookupModelMeta — pricing > extracts token limits
[0.020ms]
calculateCost > zero usage produces zero cost
[0.110ms]
calculateCost > calculates input and output cost
[0.060ms]
calculateCost > uses output rate for reasoning when reasoning rate is null
[0.050ms]
calculateCost > uses dedicated reasoning rate when present
[0.040ms]
calculateCost > returns all zeros when meta is null
[0.030ms]
resolveModelMeta > no overrides returns default meta with all sources as default
[0.290ms]
resolveModelMeta > override takes precedence over catalog default
[0.150ms]
resolveModelMeta > override on context window
[0.060ms]
resolveModelMeta > builds meta from overrides only when model not in catalog
[0.110ms]
classifyRetry > overloaded provider message is transient provider_error, retryable
[0.280ms]
classifyRetry > 429 rate limit is retryable
[0.040ms]
classifyRetry > 503 is retryable provider_error
[0.020ms]
classifyRetry > network errors are retryable
[0.050ms]
classifyRetry > context overflow flag forces context_too_long, not retryable
[0.040ms]
classifyRetry > auth failures are not retryable
[0.030ms]
classifyRetry > spend limit is not retryable
[0.020ms]
classifyRetry > unknown errors default to run_failed, not retryable
[0.020ms]
classifyRetry > Retry-After ms yields absolute retryAfterUntil
[0.030ms]
classifyRetry > Retry-After seconds converted to ms
[0.020ms]
classifyRetry > rate limit with long minutes cooldown produces far-future retryAfterUntil
[0.030ms]
loadCatalog > loads a CatalogSnapshot from a JSON object
[0.070ms]
loadCatalog > parses providers keyed by canonical name
[0.050ms]
loadCatalog > parses models keyed by canonical provider/modelId
[0.030ms]
loadCatalog > preserves the provider npm package name
[0.030ms]
loadCatalog > preserves per-model npm override when present
[0.030ms]
loadCatalog > caches after first call (same reference returned for same input)
[0.020ms]
loadCatalog > rejects a snapshot with missing schemaVersion
[0.090ms]
loadCatalog > rejects a snapshot with no providers
[0.050ms]
listProviders > returns all providers in the snapshot
[0.070ms]
listProviders > each provider entry carries npm package and api baseURL
[0.050ms]
listModels > returns all models when no provider filter
[0.050ms]
listModels > filters by provider when providerName is given
[0.060ms]
listModels > returns empty array for unknown provider filter
[0.020ms]
lookupProvider > returns the provider entry by name
[0.030ms]
lookupProvider > returns undefined for unknown provider
[0.020ms]
lookupModel > returns the model entry by provider + modelId
[0.030ms]
lookupModel > returns undefined for unknown model
[0.020ms]
lookupModel > returns undefined when provider is unknown
[0.020ms]
resolvePackageName > returns the provider's npm package when no overrides and no per-model npm
[0.060ms]
resolvePackageName > returns the catalog's per-model npm override when present
[0.030ms]
resolvePackageName > operator packageOverride wins over catalog per-model npm
[0.050ms]
resolvePackageName > operator packageOverride wins over catalog provider npm
[0.030ms]
resolvePackageName > unrelated overrides do not affect the resolved package
[0.030ms]
resolveModel > throws driver_not_bundled when the resolved package is not a bundled driver
[0.440ms]
resolveModel > aborts when signal is already triggered
[0.140ms]
resolveModel > looks the resolved package up in the bundled-driver registry and instantiates it
[0.240ms]
resolveModel > honors operator packageOverride over catalog npm when selecting the driver
[0.150ms]
resolveModel > custom provider rides a bundled driver via route.npmPackage + baseUrl
[0.120ms]
resolveModel > wraps the resolved model with middleware before returning
[0.100ms]
wrapWithRetry > returns the result on first success without retry
[0.960ms]
wrapWithRetry > retries on 429 and eventually succeeds
[5.00ms]
wrapWithRetry > does not retry on 400 (client error)
[0.180ms]
wrapWithRetry > retries on 500 (server error)
[1.19ms]
wrapWithRetry > retries on network errors (ECONNREFUSED)
[1.19ms]
wrapWithRetry > stops retrying after maxAttempts and surfaces the final error
[2.25ms]
wrapWithRetry > abort signal prevents further retries
[0.150ms]
wrapWithRetry > honors Retry-After header when present (delays longer than base)
[2.29ms]
wrapWithRetry > respects maxDelayMs cap
[101.61ms]
wrapWithRetry: prototype-chain preservation > preserves provider and modelId when provider is a prototype getter
[0.350ms]
wrapWithRetry: prototype-chain preservation > retry still routes through the original prototype doStream
[3.60ms]
wrapWithSpendGate > proceeds when no limits are configured
[0.470ms]
wrapWithSpendGate > proceeds when spend is under configured limits
[0.180ms]
wrapWithSpendGate > blocks when 5h spend limit is exceeded
[0.360ms]
wrapWithSpendGate > blocks when 7d spend limit is exceeded
[0.180ms]
wrapWithSpendGate > blocks at the limit boundary (currentSpend === limit)
[0.250ms]
wrapWithSpendGate > checks spend before each call, not just once
[0.310ms]
wrapWithSpendGate > error message identifies which limit was exceeded and includes current spend
[0.290ms]
wrapWithSpendGate: prototype-chain preservation > preserves provider and modelId when provider is a prototype getter
[0.290ms]