vtcode-llm
September 11, 2026 · View on GitHub
Root AGENTS.md | Canonical LLM provider trait, types, and implementations.
Key Modules
provider/ trait + shared types | providers/ per-provider impls | providers/custom_provider.rs custom profile router | provider.rs re-exports | client.rs + optimized_client.rs | copilot/ (feature-gated) | open_responses/ | factory_types.rs + provider_config_types.rs config | system_prompt.rs injection | http_client.rs | types.rs shared types | utils.rs + single_response.rs + tool_bridge.rs + config_adapter.rs + rig_adapter.rs + provider_base.rs + error_display.rs + model_resolver.rs + usage_cost.rs (shared normalized usage and raw/effective cost) infra (merged from core) | process_env.rs provider child environment policy
Architecture Notes
- Canonical home for all provider code. Core's
llm/is a thin re-export layer + factory/CGP. Merge Gateway defaults to native/v1/responses; an explicit/v1/openaibase selects legacy Chat Completions. Native snapshots are normalized into incremental deltas after the first cumulative frame is confirmed, while the initial snapshot remains buffered sofallback_restartcan discard stale pre-output state. Reasoning is route-specific: forwardreasoning_effortonly for routes advertising that control andthinking.budget_tokens(clamped belowmax_tokens) for thinking-budget routes, never a generic provider-wide field; persisted effort settings are omitted on an unsupported active route during normal request assembly, while explicit capability resolution remains strict; normalized reasoning must classify provider-confirmed summaries separately from raw or continuation-only details, exposing only summaries to UI while preserving continuation metadata; preserve providerReasoningStageevents when converting to normalized streams so the core runtime lifecycle can render them. Collapsed-output notices use one typedMessageClearAtmarker after tool-result user messages; only provider/model routes advertising native support use the required beta andclear_at, while all other builders translate the marker to ordinary system/history instructions without sendingclear_at. ModelResolver::resolve_with_modeandavailability_with_modemust receive the loaded config's credential storage mode; compatibility wrappers are only for callers without workspace config.ResolvedModel.api_key_envcarries provider-override credential identity through availability and picker selection; do not infer availability from provider alone.system_prompt.rsprovides stub getters withOnceLocksetters; vtcode-core overrides at init.- The runloop's stream-timeout retry falls back to non-streaming keyed on
supports_non_streaming; a provider that services non-streaming generation must override it explicitly (seeMergeGatewayProvider) — relying on the trait default lets the fallback silently regress.
Dependencies
vtcode-commons (HTTP, CGP, types) | vtcode-config (provider config, timeouts) | vtcode-utility-tool-specs (schemas) | vtcode-exec-events | vtcode-macros | vtcode-safety (child-process environment policy)
Coding Conventions
Providers in providers/<name>/mod.rs. Use anyhow::Result, tracing, not println!. Provider-specific types stay local; shared go in types.rs or provider/. OpenResponses streaming wire parsing dispatches by type; keep hot SSE fields borrowed and update the wire fields and mapping together when adding events.
- Custom provider profiles match exact model IDs; explicit API formats select the wire backend without protocol fallback.
- Provider tool formatters return
Result<Option<Value>, LLMError>; never hide serialization failures. Flattened provider extension maps must reject collisions with reserved wire fields before serialization.
OpenAI-Compatible Providers
providers/openai_compat.rsowns the shared shell:OpenAiCompatSpec(per-provider consts/overrides) +OpenAiCompatCore<S>+impl_openai_compat_provider!. New compat providers implement a Spec (~50-200 lines), not a fullLLMProvider; NVIDIA also accepts arbitrary explicit IDs and maps thinking throughchat_template_kwargs.- Model normalization happens in
core.prepare(), notconvert_request()— payload tests must callpreparefirst.stream: trueis only inserted whenrequest.streamis set. - Providers with extra protocols (evolink Anthropic path, opencode) hand-write the provider over
OpenAiCompatCoreinstead of using the macro. - Registration contract: keep the type name and 7-arg
from_configconsumed byimpl_standard_provider_constructor!in vtcode-core. The Open Responses bridge maps authoritative plan-approvalThreadEventvariants tovtcode.*custom events for client parity. Provider error bodies are stream-read with a 16 KiB cap, then diagnostics use the bounded, secret-redacting sanitizer before metadata, display, or logs. Provider-owned child processes use the shared filtered environment, with only narrow GitHub-auth exceptions for Copilot. Copilot stdio EOF, timeout, and cancellation paths must clear pending calls; frames remain bounded. ContextWindowProvidercarries discovered model capacity per provider instance; delegate every transport/capability method and scope overrides to the selected model.