Skip to content

feat: GitHub Models provider, user-defined providers, MCP multi-provider with file context#81

Open
MayCXC wants to merge 27 commits intoMadAppGang:mainfrom
MayCXC:feat/github-models-cleanup
Open

feat: GitHub Models provider, user-defined providers, MCP multi-provider with file context#81
MayCXC wants to merge 27 commits intoMadAppGang:mainfrom
MayCXC:feat/github-models-cleanup

Conversation

@MayCXC
Copy link
Copy Markdown

@MayCXC MayCXC commented Mar 19, 2026

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@)

  • New builtin provider: models.github.ai/inference, OpenAI transport
  • Auth: GITHUB_MODELS_TOKEN (fine-grained PAT)
  • Usage: gh@gpt-4o, gh@o3-mini

User-defined providers

  • ~/.claudish/config.json providers section
  • registerProvider() adds to all derived maps (PROVIDERS_BY_NAME, shortcuts, patterns, etc.)
  • Called at module init after builtins
  • Supports: baseUrl, apiPath, transport, shortcuts, apiKeyEnvVar
  • Skips entries that collide with builtin names

MCP server rewrite

  • runPrompt() uses the full resolver stack: resolveEffective + resolveTransport + resolveAPIFormat + adapter.parseResponse()
  • Accepts provider@model syntax for any provider: g@gemini-2.5-flash, gh@gpt-4o, oai@gpt-5.4
  • Falls back to OpenRouter for bare vendor/model IDs
  • Non-streaming: transport.getNonStreamingEndpoint() for Gemini (URL-parsed, no ?alt=sse), stream: false + delete stream_options for OpenAI-compatible
  • files parameter on run_prompt and compare_models: file contents appended after the prompt as labeled code blocks
  • Removed team-orchestrator tools

Tested

  • g@gemini-2.5-flash basic + files
  • gh@gpt-4o basic + files
  • compare_models with both providers + files
  • 138/138 tests pass

@MayCXC MayCXC force-pushed the feat/github-models-cleanup branch 15 times, most recently from 99f6b16 to 22d2f96 Compare March 20, 2026 04:25
MayCXC added 15 commits March 24, 2026 05:18
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.
MayCXC added 9 commits March 24, 2026 07:12
…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.
@MayCXC MayCXC force-pushed the feat/github-models-cleanup branch from 22d2f96 to dbe0850 Compare March 24, 2026 08:58
@MayCXC MayCXC changed the title feat: GitHub Models provider, user-defined providers, registerProvider feat: GitHub Models provider, user-defined providers, MCP multi-provider Mar 24, 2026
@MayCXC MayCXC force-pushed the feat/github-models-cleanup branch from dbe0850 to b11f8d3 Compare March 24, 2026 09:06
@MayCXC MayCXC changed the title feat: GitHub Models provider, user-defined providers, MCP multi-provider feat: GitHub Models provider, user-defined providers, MCP multi-provider with file context Mar 24, 2026
@MayCXC MayCXC force-pushed the feat/github-models-cleanup branch 6 times, most recently from 5e32d32 to f81d8f9 Compare March 24, 2026 16:11
…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.
@MayCXC MayCXC force-pushed the feat/github-models-cleanup branch 2 times, most recently from 644c2f5 to 0753edf Compare March 24, 2026 20:23
@MayCXC MayCXC force-pushed the feat/github-models-cleanup branch from 0753edf to 176bb11 Compare March 24, 2026 21:48
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.
@MayCXC MayCXC force-pushed the feat/github-models-cleanup branch from 176bb11 to 6636132 Compare March 25, 2026 02:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant