zcode-nvidia-nim-fix is a local OpenAI-compatible Chat Completions proxy for NVIDIA NIM. It started as a workaround for ZCode sending a literal extra_body field, which NVIDIA rejects. It now supports model-specific request mapping, a private API-key pool, bounded queues, streaming, a cached model catalog, and optional OpenCode model synchronization. The package and repository names retain the original ZCode name; the proxy also works with other compatible clients.
Current example model: z-ai/glm-5.3. The proxy is not tied to this model. Check NVIDIA's model catalog and the chosen model's Chat Completions documentation before adding an ID. A catalog entry alone does not guarantee chat support or access for your key. GLM 5.2 remains supported where NVIDIA offers it, but it is now a historical example of the original extra_body error.
| Item | Value |
|---|---|
| Local base URL | http://127.0.0.1:18787/v1 |
| Chat endpoint | POST /v1/chat/completions |
| Cached model list | GET /v1/models |
| Health | GET /health |
| NVIDIA upstream | https://integrate.api.nvidia.com/v1 |
| Windows launcher | run_proxy.ps1 |
The proxy removes unsupported request fields, preserves messages and structured tool calls, and forwards JSON or SSE streaming responses. It does not implement the Responses, Embeddings, image, audio, or file APIs. It does not grant a larger context window or guarantee that a model will produce valid tool calls.
- Windows 10/11, PowerShell 5.1 or 7+, and Python 3.10+.
- At least one NVIDIA API key for inference.
- ZCode, OpenCode, or another client that supports a custom OpenAI-compatible Chat Completions base URL.
Open PowerShell in the repository directory and set up Python once:
python --version
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install --upgrade pipThe proxy has no third-party runtime dependencies. run_proxy.ps1 activates .venv automatically. If PowerShell blocks local scripts, run Set-ExecutionPolicy -Scope Process Bypass in that terminal before starting the launcher.
Run one proxy process on port 18787 at a time. The modes use different credentials:
| Mode | What the client sends to the proxy | What NVIDIA receives | Key rotation |
|---|---|---|---|
Pool |
One private local NIM_PROXY_CLIENT_KEY |
A key selected from private NVIDIA_API_KEY_n entries |
Yes, among healthy keys |
Client |
That client's own real NVIDIA key | The same real key | No |
Env |
Any nonempty placeholder | One NVIDIA_API_KEY from the proxy process |
No |
Recommended when several local projects should share a private key pool. Copy the safe template, then edit only your local .env:
Copy-Item .env.example .envReplace its seven EXAMPLE_INVALID_... values with a unique local NIM_PROXY_CLIENT_KEY and up to six real NVIDIA_API_KEY_1 through NVIDIA_API_KEY_6 values. The template values are randomly generated invalid examples, not usable credentials. To generate a local proxy credential, use:
([guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N'))Keep the generated value private. The local key must differ from every NVIDIA key. The .env parser accepts only the local key and numbered NVIDIA keys. .env is Git-ignored and must never be committed.
Start the pool:
.\run_proxy.ps1 -ApiKeyMode Pool -UpstreamTimeoutSeconds 600In the IDE, use the local NIM_PROXY_CLIENT_KEY as the provider API key. The proxy cycles through eligible keys in numeric order, returning to key 1 after key 6. Busy or cooling keys are skipped; 401/403 keys are quarantined until restart. A pool is useful for scheduling requests, but cannot bypass NVIDIA's per-account or per-model limits.
.\run_proxy.ps1 -ApiKeyMode Client -UpstreamTimeoutSeconds 600Give each IDE provider the local base URL and its own real NVIDIA key. Requests using one key share its FIFO queue. Different keys may run concurrently. The proxy cannot rotate a Client-mode request to another key.
$env:NVIDIA_API_KEY = 'REPLACE_LOCALLY_WITH_YOUR_KEY'
.\run_proxy.ps1 -ApiKeyMode EnvThe client can use any nonempty placeholder as its API key. The proxy uses NVIDIA_API_KEY for every upstream request. Do not copy the placeholder into a published configuration.
Create a custom OpenAI-compatible / Chat Completions provider:
| Client setting | Value |
|---|---|
| Base URL | http://127.0.0.1:18787/v1 |
| Endpoint | /chat/completions |
| API key | Credential for the mode selected above |
| Model ID | For example, z-ai/glm-5.3 or another supported NVIDIA chat model |
Some clients append /v1 themselves. In that case configure http://127.0.0.1:18787; the final path arriving at the proxy must be /v1/chat/completions. The same local URL can serve several IDE projects. If you deliberately run a second proxy, use a different NIM_PROXY_PORT and matching client URL.
ZCode may offer a context-window and maximum-output setting. Use limits that the hosted NVIDIA endpoint actually accepts. A model card's architectural limit is not proof of the hosted limit. Input plus requested output can exceed NVIDIA's allowance even when the client shows a larger context value. The proxy does not compact conversations or alter token budgets.
OpenCode supports custom providers through JSON configuration. This repository includes a credential-free example using @ai-sdk/openai-compatible. The global Windows file is %USERPROFILE%\.config\opencode\opencode.json; OpenCode may also load opencode.jsonc, project settings, or OPENCODE_CONFIG overrides. See the OpenCode configuration guide for precedence.
The example defines nim-local at http://127.0.0.1:18787/v1. Merge its provider.nim-local object into your existing configuration rather than replacing other providers. In OpenCode, connect provider ID nim-local and use your local NIM_PROXY_CLIENT_KEY from .env. Select a model from the OpenCode model picker. Start the Pool-mode proxy before chatting.
The proxy can add current, documented Chat Completions models to nim-local when they appear in NVIDIA's live public catalog. Close OpenCode for the initial sync, then start:
.\run_proxy.ps1 -ApiKeyMode Pool -DebugMode -UpstreamTimeoutSeconds 600 `
-OpenCodeConfig "$HOME\.config\opencode\opencode.json"Wait for Model catalog refreshed and OpenCode model sync complete, then reopen OpenCode. Sync preserves selected model, credentials, and non-DeepSeek manual entries; it removes obsolete DeepSeek entries and adds the current V4.1 Flash ID under nim-local and any existing nvidia_nim_1 through nvidia_nim_6 providers that point to the official NVIDIA endpoint. Other providers are untouched. It only uses a successful public catalog refresh, not a stale cache. The live /v1/models feed includes embedding, safety, and other non-chat models, so the OpenCode list is intentionally limited to documented chat models; it is not every catalog ID. It accepts strict JSON at the selected path; JSONC, malformed files, duplicate properties, symlinks, or a provider with a conflicting endpoint are rejected. Before changing an existing file, it stores a private backup under %LOCALAPPDATA%\zcode-nvidia-nim-fix\opencode-backups. Keep those backups private because your existing config may contain credentials.
You may additionally configure nvidia_nim_1 through nvidia_nim_6 as separate OpenCode providers, each with https://integrate.api.nvidia.com/v1 and its own NVIDIA key stored in OpenCode's local credential store. These bypass the proxy, including its sanitizer, pool, queue, and default reasoning mapping. Model sync also updates these six existing providers when their endpoint matches exactly, but it does not create them or change their credentials. Do not put real keys in JSON examples or the repository.
Reviewed 2026-10-04. Previously, model sync assigned every model a generic
131072 context and 16384 output budget and skipped existing entries. This
made OpenCode compact much earlier than documented for larger-context models.
The limit registry now uses exact model IDs:
| Model ID | Configured context (tokens) | Configured output budget | NVIDIA source |
|---|---|---|---|
z-ai/glm-5.3 |
1,048,576 | 16,384 | Model card |
z-ai/glm-5.3-flash |
1,048,576 | 16,384 | Model card |
moonshotai/kimi-k3 |
1,048,576 | 16,384 | Model card |
deepseek-ai/deepseek-v4.1-flash |
1,048,576 | 16,384 | Model card |
moonshotai/kimi-k2.6 |
262,144 | 16,384 | Model card |
google/gemma-4-31b-it |
262,144 | 16,384 | Model card |
poolside/laguna-xs-2.1 |
262,144 | 16,384 | Model card |
nvidia/nemotron-3-super-120b-a12b |
1,048,576 | 16,384 | Model card |
nvidia/nemotron-3-ultra-550b-a55b |
1,048,576 | 16,384 | Model card |
nvidia/nemotron-3.5-lightning-30b-a3b |
1,048,576 | 16,384 | Model card, NVIDIA's exact 1M recipe |
meta/muse-glimmer-30b |
131,072 | 16,384 | Model card |
openai/gpt-oss-20b |
131,072 | 4,096 | Hosted validation reported 131,072; output schema |
Evidence boundary: most context values above are documented model capacities,
not independently verified maximum-length hosted requests. Small synthetic
validation requests did not confirm the full window for GLM, Kimi or DeepSeek:
several timed out, Kimi K2.6 returned 404, and some endpoints accepted streaming
headers without revealing their context ceiling. GPT-OSS returned an explicit
131,072-token ceiling. A 200 header or a max_tokens validation cap is not
proof that a long input fits. No full-window inference/load test was run.
If NVIDIA returns a smaller deployment-specific context limit, its error takes
precedence: set that verified value manually for that provider/model. This
registry does not unlock a larger server-side deployment or apply 1M to GLM 5.2.
output is an operating budget, not an assertion of the model's maximum
output capacity or its reasoning effort. We retain 16,384 rather than reserving
the entire context for output; GPT-OSS uses its documented 4,096 hosted cap.
Reasoning tokens can share the generation budget. Input, tool schemas, output
reservation and client safety margins all matter. In OpenCode
1.15.13,
the normal compaction threshold subtracts the effective output reservation from
context; an explicit limit.input introduces a separate input ceiling. Exact
behavior is version-dependent. Keep automatic compaction enabled for safety.
On a successful live-catalog refresh, sync fills missing limits and migrates only
the exact old { "context": 131072, "output": 16384 } template for known models.
Other explicit limits, including an explicit input cap, are preserved. Existing
model names, effort options, credentials and other providers are unchanged.
If you intentionally used that exact old pair, replace it with your preferred
non-template limit before sync. Unknown/new model IDs never inherit a guessed 1M.
To apply: close OpenCode, run the Pool launch command above, wait for successful
model sync, then reopen OpenCode. Disconnect is unnecessary. If the displayed
limit remains stale, check the selected provider ID and overrides in
opencode.jsonc, project config or OPENCODE_CONFIG; edit the effective model's
limit.context and limit.output, not the credential file. Start a short test
conversation before resuming a large coding session. Changes to displayed limits
cannot restore detail already removed by an earlier compaction.
-
Confirm the exact model ID in the public catalog, then verify that it supports Chat Completions and the capabilities your client needs.
-
Close OpenCode. Back up the global configuration outside the repository:
$configPath = Join-Path $HOME '.config\opencode\opencode.json' $backupPath = "$configPath.backup-$(Get-Date -Format yyyyMMdd-HHmmss)" Copy-Item -LiteralPath $configPath -Destination $backupPath
-
Edit
provider.<provider-id>.modelsin that file. Keep existing entries, separate siblings with commas, and add the exact ID. For example, inside amodelsobject:{ "existing/model-id": { "name": "Existing model" }, "vendor/new-chat-model-id": { "name": "New chat model", "limit": { "context": 131072, "output": 16384 } } }Replace the example limits with values supported by the endpoint. Add
reasoning,tool_call, orinterleavedsettings only when the model supports them. Add the model separately undernim-localand/or whichever direct providers should display it; no disconnect is needed. -
Validate the file, reopen OpenCode, and choose the new model:
Get-Content -Raw -LiteralPath $configPath | ConvertFrom-Json | Out-Null opencode models nim-local --pure
Substitute
nvidia_nim_1or another provider ID in the last command as needed. Seeing a model in the list confirms configuration loading, not successful inference. Test with a short prompt.
If a provider disappears, inspect disabled_providers and any duplicate provider definition in opencode.jsonc, project config, or other OpenCode configuration sources. OpenCode can disable a provider after Disconnect even though its model entries remain in opencode.json. The local credential file %USERPROFILE%\.local\share\opencode\auth.json is for authentication; model entries do not belong there.
The proxy recognizes exact IDs in the reasoning profile registry. A newly published catalog ID does not automatically gain a new reasoning mapping. These rules apply to requests through the proxy, including models manually added to the IDE:
| Model ID | No effort selected in client | Explicit supported values |
|---|---|---|
moonshotai/kimi-k3 |
Sends top-level reasoning_effort: "max" |
low, high, max |
z-ai/glm-5.3, z-ai/glm-5.3-flash |
Uses NVIDIA's documented native max default; sends no unverified wire override |
None enabled for hosted API |
deepseek-ai/deepseek-v4.1-flash |
Uses hosted default; NVIDIA's model page does not document a hosted max request field, so the proxy does not invent one |
No verified hosted override |
| Retired DeepSeek V4 Pro/Flash IDs, if manually requested | Legacy mapping remains for compatibility, but these IDs are not synced into OpenCode and may be unavailable upstream | none, high, max |
openai/gpt-oss-20b, openai/gpt-oss-120b |
Uses provider default; max is not a documented value |
low, medium, high |
| Other models, including GLM 5.2 | Uses provider default; strips unknown effort fields | No guessed override |
For Kimi K3, the proxy also removes sampling fields NVIDIA documents as fixed. An explicit valid effort takes precedence over a profile default. Missing or unsupported values fall back to that profile's default. It never inserts a textual /max command into your prompt. max effort can increase latency; it does not change max_tokens or context length.
NVIDIA describes the GLM 5.3 model's default as max, but its hosted request documentation has not been verified here to accept a per-request effort override. The proxy leaves that native default in place. DeepSeek V4.1 Flash's model page discusses high-effort evaluation, but its hosted example does not specify an effort parameter. Until NVIDIA publishes or a live test confirms the hosted wire format, selecting Max in OpenCode cannot be guaranteed for that model. Other models without a verified max capability use their provider default. References: GLM 5.3 model card, Kimi K3 API, and NVIDIA model catalog. A configured profile does not make a retired or inaccessible endpoint work.
When the upstream is NVIDIA's public endpoint, a background worker fetches /v1/models on startup and refreshes every six hours. It sends no API key, prompt, or project file. The last good public list is cached under %LOCALAPPDATA%\zcode-nvidia-nim-fix\models.json. A failed refresh retains the last good list and retries with bounded backoff. Discovery never blocks chat and does not restrict which model ID a chat request can use.
(Invoke-RestMethod http://127.0.0.1:18787/health).model_catalogStatus can be loading, ready, stale, unavailable, or disabled. Before the first successful refresh, GET /v1/models returns 503 model_catalog_unavailable; chat remains available. Pool mode requires the local bearer key for /v1/models. Client mode requires a nonempty bearer value, but the public catalog lookup does not verify it with NVIDIA. Env mode permits a local unauthenticated lookup. A custom upstream or -DisableModelDiscovery disables public catalog discovery. The public list can include non-chat models and contains no capability or per-key entitlement details.
Start the proxy in your chosen mode in one terminal. In another terminal, check health and send a small chat request. The example uses a deliberately invalid placeholder and therefore requires replacement with your local pool key in Pool mode or your NVIDIA key in Client mode. In Env mode, any nonempty placeholder works.
Invoke-RestMethod http://127.0.0.1:18787/health
$body = @{
model = 'z-ai/glm-5.3'
messages = @(@{ role = 'user'; content = 'Say hello in one sentence.' })
stream = $false
max_tokens = 128
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Method Post `
-Uri 'http://127.0.0.1:18787/v1/chat/completions' `
-Headers @{ Authorization = 'Bearer REPLACE_LOCALLY_WITH_MODE_CREDENTIAL' } `
-ContentType 'application/json' -Body $bodyThe original ZCode failure was a request containing literal extra_body. The proxy drops that wrapper; only documented model-specific options may be mapped out of it. The GLM 5.2 case is retained in the sanitizer tests. Do not send extra_body in a direct NVIDIA request.
The screenshot below shows an older GLM 5.2 session with extra_body stripped. The current example above uses GLM 5.3.
The local ZCode base URL is shown here; the screenshot's masked API-key field does not reveal a credential:
run_proxy.ps1 activates .venv and checks the required credentials. The two dedicated debug launchers also enable OpenCode sync for %USERPROFILE%\.config\opencode\opencode.json:
start_proxy_pool_debug.bat
start_proxy_client_debug.batThe Pool launcher reads .env; the Client launcher forwards each client's NVIDIA key. Both use a 600-second upstream timeout and port 18787. Stop the running proxy with Ctrl+C before switching modes. A saved OpenCode credential must match the newly selected mode. For no OpenCode file writes or another config path, call run_proxy.ps1 directly:
On Windows, WinError 10013 during startup can mean the listening port is reserved even when no process owns it. Check with netsh interface ipv4 show excludedportrange protocol=tcp. The old default, 8787, may fall inside a Windows-reserved range; the new default is 18787. To override it for the current PowerShell session, set $env:NIM_PROXY_PORT='18788' before starting the proxy and update every IDE's local base URL to match (for example http://127.0.0.1:18788/v1). Do not delete Windows port reservations to fix this. If OpenCode sync is enabled, update provider.nim-local.options.baseURL in your private OpenCode config before restarting; direct NVIDIA providers keep their public URL.
.\run_proxy.ps1 -ApiKeyMode Pool -DebugMode -UpstreamTimeoutSeconds 600
.\run_proxy.ps1 -ApiKeyMode Client -ModelRefreshSeconds 3600
.\run_proxy.ps1 -ApiKeyMode Client -DisableModelDiscovery
.\run_proxy.ps1 -ApiKeyMode Pool -OpenCodeConfig "$HOME\.config\opencode\opencode.json"start_proxy.bat forwards arguments to the PowerShell launcher. -DebugMode logs stripped field names and policy decisions without printing keys or full messages. -ToolCallTextMode pass relays raw model-generated <tool_call> text; the default diagnostic mode replaces that markup with a readable diagnostic instead of treating it as an executable tool call.
| Parameter | Default | Purpose |
|---|---|---|
-UpstreamTimeoutSeconds |
300 |
Wait for upstream to start responding; examples use 600 |
-MaxConcurrentPerKey |
1 |
Active requests per key |
-MaxQueuePerKey |
4 |
Waiting requests per key in Env/Client mode |
-MaxTotalQueued |
32 |
Total queued requests |
-QueueWaitSeconds |
180 |
Maximum queue wait |
-RateLimitCooldownSeconds |
60 |
Fallback cooldown if NVIDIA omits Retry-After |
-Max5xxFailovers |
1 |
Alternate pool keys after upstream 5xx/transport error |
-ModelRefreshSeconds |
21600 |
Public catalog refresh interval; minimum 30 |
-DisableModelDiscovery |
off | Disable /v1/models discovery |
-OpenCodeConfig |
unset | Opt in to add-only nim-local sync in a strict JSON file |
Queue defaults are conservative. Higher concurrency can increase NVIDIA 429 responses. The underlying Python CLI uses lower-case flags such as --api-key-mode pool, --upstream-timeout-seconds 600, --opencode-config PATH, and --no-model-discovery. Additional environment overrides include NIM_PROXY_HOST, NIM_PROXY_PORT, and NIM_PROXY_UPSTREAM_BASE_URL.
| Symptom or condition | Meaning and action |
|---|---|
400 Unsupported parameter(s): extra_body |
Original unsanitized request; verify the IDE points to the local /v1 URL and the proxy is running. |
400 context/input+output limit |
Lower requested output or compact/start a new conversation. Changing only the IDE's displayed context limit does not enlarge NVIDIA's hosted limit. |
401 from local Pool provider |
Check that the IDE uses NIM_PROXY_CLIENT_KEY, not a real NVIDIA key. |
429 proxy_queue_full |
Local queue is full; reduce simultaneous requests. The proxy supplies Retry-After: 5. |
NVIDIA 429 |
Upstream rate or quota limit. In Pool mode the key cools down and another untried key may be selected. |
504 proxy_queue_timeout |
Request waited past the configured queue limit; check active work and retry later. |
504 upstream_timeout |
NVIDIA did not start responding before the upstream timeout; try a shorter request or another model. Client-side timeouts can be shorter than the proxy setting. |
NVIDIA 500/5xx |
Upstream error; Pool mode permits bounded failover before streaming starts. Repeated failures across keys may indicate model or platform availability trouble. |
Plain-text <tool_call> shown as a diagnostic |
The model returned text rather than structured OpenAI tool_calls; choose a model/profile supporting structured tools. |
Client disconnected log |
IDE closed the connection before the proxy could send its final response; a proxy crash is not implied. |
Same-key requests use FIFO queues; different healthy keys can work in parallel. In Pool mode, a key is tried at most once per request. NVIDIA 408/429 cools a key, 401/403 quarantines it until restart, and 400/404/422 does not trigger key switching. After an SSE stream starts, the proxy cannot safely retry it on another key. When failover is exhausted, the last upstream response is returned so the client sees its real status.
GET /health exposes aggregate queue and pool counts, not credential values. Restarting the proxy clears its in-memory queue but does not erase NVIDIA rate limits or any in-flight work already sent upstream.
- Real API keys belong only in your local
.env, process environment, or IDE credential store. Never copy them into README examples, tracked JSON, screenshots, Git commits, issues, or releases. .env,.env.*,.venv/, build artifacts, and local caches are excluded by.gitignore..env.examplecontains random invalid values solely to show the expected variable names.- The default bind address is
127.0.0.1. Remote binding requires an explicit override and is forbidden in Env mode. If you deliberately expose Client/Pool mode on a network, add TLS termination and firewall controls. - Pool mode validates the local token without forwarding it to NVIDIA. Client mode forwards the supplied NVIDIA token upstream. Logs omit keys and full message content.
- Keep OpenCode configs,
auth.json, proxy cache backups, and any exported logs private. A local config backup may contain credentials that predate this proxy.
.\.venv\Scripts\python.exe -m pytest
.\.venv\Scripts\python.exe -m ruff check .
.\.venv\Scripts\python.exe -m mypy nvidia_nim_proxy tests
.\.venv\Scripts\python.exe -m pip wheel --no-deps --wheel-dir dist .CI runs Python tests, lint, type checks, wheel packaging, and Windows launcher checks. The project follows Semantic Versioning; version history is listed in CHANGELOG.md. GitHub releases are currently unavailable pending maintainer approval.
Roadmap: verify hosted GLM effort overrides before sending them, add exact profiles for newly documented NVIDIA models, and consider a Windows service wrapper. See LICENSE for licensing.

