feat: GitHub Models provider, user-defined providers, MCP multi-provider with file context#81
Open
MayCXC wants to merge 27 commits intoMadAppGang:mainfrom
Open
feat: GitHub Models provider, user-defined providers, MCP multi-provider with file context#81MayCXC wants to merge 27 commits intoMadAppGang:mainfrom
MayCXC wants to merge 27 commits intoMadAppGang:mainfrom
Conversation
99f6b16 to
22d2f96
Compare
DialectManager had no state: constructor allocated all dialects, getAdapter() did a linear scan, needsTransformation() called getAdapter() again. Replace with a pure function that does the same scan without the class ceremony. All callers updated: composed-handler, cli, local-adapter, openrouter-api-format, and all test files.
Add parseResponse() to APIFormat interface with default OpenAI implementation. Override in GeminiAPIFormat (candidates/parts) and AnthropicAPIFormat (content blocks). Enables non-streaming callers to parse responses without hardcoding wire formats. Add tokenStrategy to ProviderTransport interface. Each transport declares its own strategy (delta-aware, accumulate-both, local). ComposedHandler reads from transport, options override. Add getNonStreamingEndpoint() to ProviderTransport for Gemini (:generateContent vs :streamGenerateContent). Add unwrapResponse to ProviderTransport for CodeAssist envelope. ComposedHandler reads from transport as fallback.
Granular factory functions for constructing Layer 1 (APIFormat) and Layer 3 (ProviderTransport) independently without the full ComposedHandler. Enables non-streaming callers (MCP server, batch mode) to compose transport + format adapter directly. resolveAPIFormat(providerName, modelName) returns the wire format adapter. resolveTransport(provider, modelName, apiKey) returns the HTTP transport. Both return null for unknown providers.
…nsport RequestQueue: generic request queue with configurable concurrency, timeout, rate limiting, and retry. Provider-specific hooks for Gemini (quotaResetDelay parsing), OpenRouter (X-RateLimit headers), and local models (OOM retry). Replaces scattered per-transport queue implementations. BaseTransport: abstract base owning a RequestQueue. ApiKeyTransport adds Bearer auth + baseUrl/apiPath. OAuthTransport adds token-based auth with refresh. New transports can extend these instead of implementing ProviderTransport from scratch.
All 10 transport implementations now extend BaseTransport, ApiKeyTransport, or OAuthTransport instead of implementing ProviderTransport from scratch. ApiKeyTransport: PoeProvider, OllamaProviderTransport, LiteLLMProviderTransport BaseTransport (custom auth): OpenAIProviderTransport, AnthropicProviderTransport, GeminiProviderTransport, OpenRouterProviderTransport, LocalTransport OAuthTransport: GeminiCodeAssistProviderTransport, VertexProviderTransport Gemini and OpenRouter now use RequestQueue with provider-specific hooks (geminiOnResponse, createOpenRouterHooks) instead of standalone queue singletons. tokenStrategy removed from ComposedHandlerOptions in all profiles (transport provides it). unwrapGeminiResponse removed from ComposedHandlerOptions (transport.unwrapResponse provides it). Net: -47 lines across 12 files.
Replace standalone hook functions (geminiOnResponse, createOpenRouterHooks) with protected method overrides on the transport subclasses: - GeminiProviderTransport.onResponse(): quotaResetDelay parsing - GeminiCodeAssistProviderTransport.onResponse(): same - OpenRouterProviderTransport.onResponse(): X-RateLimit headers - OpenRouterProviderTransport.calculateDelay(): rate limit spreading BaseTransport defines onResponse(), shouldRetry(), calculateDelay() with sensible defaults. Subclasses override for provider-specific behavior. No hook functions or closures. Removed geminiOnResponse, createOpenRouterHooks, oomShouldRetry from request-queue.ts (dead code, behavior now on transport classes).
Move matching logic from individual shouldHandle() implementations into a declarative DIALECTS table in the resolver. Each entry declares families and vendor prefixes. The resolver does the matching via matchesModelFamily, not the adapter classes. shouldHandle() still exists on the adapters (used by local-adapter and openrouter-api-format for inner adapter resolution) but the primary dispatch path is the centralized table.
ProviderProfile now declares createFormat() and createTransport() alongside createHandler(). The factory functions resolveAPIFormat() and resolveTransport() delegate to PROVIDER_PROFILES instead of maintaining duplicate lookup tables. Adding a provider: one entry in PROVIDER_PROFILES with three methods. No other tables to keep in sync. Net: -105 lines from eliminating the duplicate maps.
Replace ProviderProfile interface, profile objects, PROVIDER_PROFILES lookup table, and delegate factory functions with a single createHandlerForProvider switch statement. One place to add a provider. No indirection, no duplicate tables. Exported SUPPORTED_PROVIDERS set for test coverage verification. Net: -184 lines.
Remove ProfileContext indirection. createHandlerForProvider now takes (def, modelName, apiKey, targetModel, port, opts) where def is the ProviderDefinition from BUILTIN_PROVIDERS. Private resolveTransport() and resolveAPIFormat() switch on def.transport to select the right classes. One source of truth: BUILTIN_PROVIDERS declares transport type, the switch interprets it. proxy-server.ts looks up ProviderDefinition by resolved provider name and passes it directly. No RemoteProvider wrapping needed at the call site (toRemoteProvider is called internally by the transport resolver). Tests verify by iterating BUILTIN_PROVIDERS with getProviderByName.
…lect-manager All three resolvers (transport, API format, model dialect) now live in provider-profiles.ts. resolveModelDialect is a flat if/else chain using matchesModelFamily, same pattern as the other resolvers. createHandlerForProvider passes the resolved dialect to ComposedHandler via modelDialect option. ComposedHandler uses it as-is instead of calling a separate resolver. Deleted adapters/dialect-manager.ts. All callers updated to import from providers/provider-profiles.ts.
The resolver in provider-profiles.ts owns model matching. shouldHandle on adapter classes was dead code since the DIALECTS table (now a flat switch in resolveModelDialect) replaced it. Removed from: BaseAPIFormat abstract declaration, ModelDialect interface, DefaultAPIFormat, and all 14 adapter implementations. Cleaned up matchesModelFamily imports that were only used by shouldHandle. Net: -116 lines across 18 files.
…istry Replace 4 handler maps (openRouterHandlers, localProviderHandlers, remoteProviderHandlers, poeHandlers) and 4 helper functions with a single handlerCache + getHandler that delegates to createHandlerForProvider. proxy-server.ts no longer imports any transport or adapter classes. All construction goes through provider-profiles.ts. Added to provider-profiles.ts: - OpenRouter and Poe cases in resolveTransport/resolveAPIFormat - resolveEffective for publicKeyFallback (zen auth-less access) - createLocalHandler for ollama/lmstudio/vllm/mlx/URL models proxy-server.ts: 538 to 427 lines.
…ote-provider-registry, simplify claude-runner KimiCodingTransport: subclass of AnthropicProviderTransport with OAuth fallback in getHeaders(). Removes provider.name check from anthropic-compat.ts. Resolver creates the right subclass. Local adapter subclasses: LocalQwenFormatAdapter (no_think, sampling), LocalDeepSeekFormatAdapter, LocalLlamaFormatAdapter, LocalMistralFormatAdapter. Each overrides getSamplingParams(). Resolver selects subclass via matchesModelFamily. local-adapter.ts no longer branches on model name. remote-provider-registry.ts deleted. All callers replaced with getProviderByName/getProviderDefinitionByRemoteName from provider-definitions.ts. claude-runner.ts: removed hasNativeAnthropicMapping and onCleanup. Always set placeholder credentials (proxy handles all auth).
…ute maps, resolver def/apiKey BaseModelDialect: new abstract base for dialect-only classes (GrokModelDialect, DeepSeekModelDialect, QwenModelDialect, MiniMaxModelDialect, GLMModelDialect, XiaomiModelDialect). BaseAPIFormat now implements only APIFormat (wire format). GeminiModelDialect: extracted reasoning filter logic from GeminiAPIFormat. resolveModelDialect returns GeminiModelDialect for gemini models. GeminiAPIFormat is pure wire format. ZenTransport: extends OpenAIProviderTransport, defaults apiKey to "public". resolveTransport uses it for opencode-zen cases. Pre-computed maps in provider-definitions.ts: PROVIDERS_BY_NAME, PROVIDER_SHORTCUTS, LEGACY_PREFIX_PATTERNS, NATIVE_MODEL_PATTERNS, API_KEY_INFO, PROVIDER_DISPLAY_NAMES. Built eagerly at module load. model-parser uses them directly. ProviderResolution gains def and apiKey fields so proxy-server can pass them to createHandlerForProvider without re-resolving.
getShortcuts, getLegacyPrefixPatterns, getNativeModelPatterns, getShortestPrefix had zero callers (replaced by pre-computed maps). Updated tests to use maps directly.
Move ollama/lmstudio-specific logic from LocalTransport into subclasses. LocalTransport is now a generic base for any OpenAI-compatible local model server. OllamaTransport: num_ctx injection, /api/tags health check, /api/show context window detection. LMStudioTransport: /v1/models context window detection. Resolver selects the right subclass. No name checks in LocalTransport.
Grok XML tool format instruction: moved from openai-messages.ts and openrouter-api-format.ts into GrokModelDialect.prepareRequest(). Gemini output format instruction: moved from openrouter-api-format.ts into GeminiModelDialect.prepareRequest(). Gemini middleware registration: always register, let MiddlewareManager filter by shouldHandle(modelId) instead of branching in composed-handler. openrouter-api-format.ts no longer has any model-specific branches. openai-messages.ts no longer checks model identity. composed-handler.ts no longer checks for gemini.
getToolGuidanceHeader() is a protected method on LocalModelAdapter that subclasses override. Removes the last model-family branch from local-adapter.ts.
…rely resolveMiddlewares(modelId) in provider-profiles.ts selects which middleware to register. ComposedHandler receives them via options. shouldHandle removed from: ModelMiddleware interface, GeminiThoughtSignatureMiddleware, MiddlewareManager. MiddlewareManager no longer filters; all registered middleware is active (resolver already selected the right ones). Zero shouldHandle references remain in the codebase.
22d2f96 to
dbe0850
Compare
dbe0850 to
b11f8d3
Compare
5e32d32 to
f81d8f9
Compare
…URL parsing) Zen: added opencode-zen-minimax and opencode-zen-go-minimax definitions with transport: "anthropic". resolveEffective swaps definitions for minimax models. No model branching in resolvers. ZenTransport handles GPT endpoint internally. Renamed adapter to formatAdapter in ComposedHandlerOptions. Renamed explicitAdapter to formatAdapter in ComposedHandler. resolveModelDialect returns BaseModelDialect | null. Removed CodexAPIFormat, OpenAIAPIFormat, DefaultAPIFormat from dialect resolver. Models without dialect quirks get null. supportsVision default false in BaseModelDialect. Grok uses catalog lookup, Gemini always true. reasoningBlockDepth removed. inReasoningBlock reset fixed. getNonStreamingEndpoint uses URL parsing to strip alt param. preserveVendorPrefix on ProviderDefinition. processTextContent chaining via modelDialect param on stream parsers.
644c2f5 to
0753edf
Compare
0753edf to
176bb11
Compare
GitHub Models: new builtin provider (gh@ shortcut, GITHUB_MODELS_TOKEN). User-defined providers: registerProvider() + loadUserProviders() reads ~/.claudish/config.json at module init. MCP server: runPrompt uses resolveEffective + resolveTransport + resolveAPIFormat + adapter.parseResponse for any provider. files parameter on run_prompt and compare_models appends file contents after the prompt.
176bb11 to
6636132
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GitHub Models provider, user-defined providers, MCP multi-provider rewrite with file context. Rebased onto v6.2.2. Depends on #80.
GitHub Models (
gh@)models.github.ai/inference, OpenAI transportGITHUB_MODELS_TOKEN(fine-grained PAT)gh@gpt-4o,gh@o3-miniUser-defined providers
~/.claudish/config.jsonproviderssectionregisterProvider()adds to all derived maps (PROVIDERS_BY_NAME, shortcuts, patterns, etc.)MCP server rewrite
runPrompt()uses the full resolver stack:resolveEffective+resolveTransport+resolveAPIFormat+adapter.parseResponse()provider@modelsyntax for any provider:g@gemini-2.5-flash,gh@gpt-4o,oai@gpt-5.4transport.getNonStreamingEndpoint()for Gemini (URL-parsed, no ?alt=sse),stream: false+delete stream_optionsfor OpenAI-compatiblefilesparameter onrun_promptandcompare_models: file contents appended after the prompt as labeled code blocksTested
g@gemini-2.5-flashbasic + filesgh@gpt-4obasic + filescompare_modelswith both providers + files