OpenCode and Pi are both terminal coding agents that register a custom OpenAI-compatible model provider by editing a config file, no forked build required and no plugin compiled against a specific SDK version. Both can reach GLM-5.2 through NativePort’s unified /inference/v1/chat/completions endpoint using the canonical model id huggingface/zai-org/GLM-5.2. This tutorial covers the exact config shape each project currently documents, adapted for GLM-5.2’s real capabilities, and what to do when a request fails.
What you’ll need
- A NativePort API key (sign up, $5 of credit is seeded automatically), exported as an environment variable:
export NATIVEPORT_API_KEY="np_..."
- Either agent installed:
npm install -g opencode-ai
# and/or
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
GLM-5.2 reports tools: true in its NativePort catalog entry, which is what both agents actually depend on. They drive edits and shell commands through native tool calls, not through response_format, so the model’s lack of structured-output support (see the Python API tutorial) doesn’t affect either setup below. Its catalog entry also reports vision: false, unlike some other models on this route, and the configs below reflect that by declaring text-only input.
Both configs below are verified working configurations against the live gateway, not just schema-checked against each project’s config format: OpenCode 1.18.8 runs this configuration with reasoning: true and attachment: false/text-only modality. Pi 0.82.1 needs more than that. It only completes a request with reasoning: true and four compat flags set (supportsStore, supportsReasoningEffort, supportsDeveloperRole, supportsStrictMode, all false); leaving any of those four out returns a 400, reliably rather than intermittently. Both configs are pinned to the versions above because config schemas across these projects can change between releases; check your installed version against these if something doesn’t match.
OpenCode
OpenCode reads provider config from opencode.json, either project-local (./opencode.json) or global (~/.config/opencode/opencode.json). Custom OpenAI-compatible providers use the @ai-sdk/openai-compatible adapter, validated here against OpenCode’s own published schema at https://opencode.ai/config.json:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"nativeport": {
"npm": "@ai-sdk/openai-compatible",
"name": "NativePort",
"options": {
"baseURL": "https://api.nativeport.ai/inference/v1",
"apiKey": "{env:NATIVEPORT_API_KEY}"
},
"models": {
"huggingface/zai-org/GLM-5.2": {
"name": "GLM-5.2",
"attachment": false,
"tool_call": true,
"reasoning": true,
"cost": { "input": 1.4, "output": 4.4 },
"limit": { "context": 1048576, "output": 8192 },
"modalities": { "input": ["text"] }
}
}
}
}
}
Notes on the fields that aren’t just cosmetic:
options.apiKeyuses OpenCode’s{env:VAR_NAME}interpolation syntax: the literal string{env:NATIVEPORT_API_KEY}is written into the file, and OpenCode resolves it from the environment at request time, so the key itself never lands in the config.attachment: falseandmodalities.input: ["text"]reflect GLM-5.2’s realvision: falsein NativePort’s catalog. Sending an image to this model through the gateway fails with400 unsupported_parameter, so the config doesn’t advertise a capability the route will refuse.reasoning: truetells OpenCode that GLM-5.2 reasons by default, so it parses and displays the model’sreasoning_contentcorrectly instead of treating it as a non-reasoning model. That’s a separate thing from controlling how much it reasons: NativePort’s unified endpoint doesn’t accept areasoning_effortparameter for this model. GLM-5.2 upstream has multiple thinking-effort levels, but that control isn’t exposed through this route (see the Python API tutorial for the full supported-parameters list). OpenCode 1.18.8 didn’t need an explicit flag to avoid sendingreasoning_effort; unlike Pi below, it just doesn’t send one by default for this adapter.limit.output: 8192is not a published GLM-5.2 ceiling. NativePort’s model catalog doesn’t expose a separate max-output figure for this route, only the 1,048,576-token context window. This value is the request-time cap OpenCode sends asmax_tokens; raise or lower it for your workload.costmirrors NativePort’s real per-million-token rate on the default pinned provider,novita($1.40 input / $4.40 output), so OpenCode’s usage tracking reports accurately. This is bookkeeping, not billing; NativePort meters usage server-side regardless of what’s in this file.deepinfrais admitted too, at $0.93/$3.00 per 1M on the same context; to use it instead, register a second model entry keyed"huggingface/zai-org/GLM-5.2:deepinfra"withcost: { "input": 0.93, "output": 3 }and everything else identical.
Select the model from the CLI:
opencode run --model nativeport/huggingface/zai-org/GLM-5.2 "Summarize this repository."
or list what’s registered:
opencode models nativeport
Pi
Pi reads custom providers from ~/.pi/agent/models.json, reloaded automatically each time you open /model; no restart needed after an edit:
{
"providers": {
"nativeport": {
"baseUrl": "https://api.nativeport.ai/inference/v1",
"api": "openai-completions",
"apiKey": "$NATIVEPORT_API_KEY",
"models": [
{
"id": "huggingface/zai-org/GLM-5.2",
"name": "GLM-5.2 (NativePort)",
"reasoning": true,
"input": ["text"],
"contextWindow": 1048576,
"cost": { "input": 1.4, "output": 4.4, "cacheRead": 0, "cacheWrite": 0 },
"compat": {
"supportsStore": false,
"supportsReasoningEffort": false,
"supportsDeveloperRole": false,
"supportsStrictMode": false
}
}
]
}
}
}
All four compat flags are required, not just supportsStore. Pi 0.82.1 defaults to sending a store field, a reasoning_effort field, developer-role messages, and strict-mode tool schemas. NativePort’s endpoint rejects all four for this model, and leaving any single flag off returns a 400. Set all four to false and the request goes through clean.
reasoning: true is the opposite of what you’d guess from those four falses, and that’s the point: it’s a different axis. The compat flags tell Pi what the route accepts (nothing about reasoning control); reasoning: true tells Pi what the model actually does, which is reason by default and return a reasoning_content field. Pi needs to know that to display the response correctly, independent of whether it’s allowed to steer how much reasoning happens.
input: ["text"] mirrors the same fact as the OpenCode config above: GLM-5.2 is text-only on this route. apiKey: "$NATIVEPORT_API_KEY" uses Pi’s environment-interpolation syntax ($VAR or ${VAR}), the same idea as OpenCode’s but a different literal form. maxTokens is deliberately omitted rather than guessed: Pi defaults it to 16,384 when unset, comfortably above the floor that matters here. A request capped much lower can end while GLM-5.2 is still inside its own reasoning phase, leaving content empty even on a 200 (see the Python API tutorial for the numbers behind that). Pi’s default is nowhere near that floor, so this is a non-issue at the default; it matters if you lower maxTokens yourself.
As with OpenCode, deepinfra can be registered as a second entry, "id": "huggingface/zai-org/GLM-5.2:deepinfra" with cost: { "input": 0.93, "output": 3, "cacheRead": 0, "cacheWrite": 0 }, if the lower per-token rate matters more to you than staying on the cataloged default.
Launch pi against it directly:
pi --provider nativeport --model huggingface/zai-org/GLM-5.2 -p "Summarize this repository."
or start an interactive session and switch with /model (Ctrl+L).
Errors you’ll actually hit
Both agents surface the gateway’s HTTP response rather than inventing their own; the codes match the Python API tutorial:
401:NATIVEPORT_API_KEYisn’t set in the shell that launched the agent, or the interpolation syntax was typo’d ({env:...}for OpenCode,$...for Pi; they are not interchangeable between the two tools).402: the NativePort balance is $0. Neither agent retries around this; top up and re-run.404model_not_found: almost always a copy-paste error in the model id. It must behuggingface/zai-org/GLM-5.2exactly, matching the object key you registered. Include the:deepinfrasuffix only if that’s the entry you’re calling.429gateway_rate_limited: back off. This is a per-account gateway limit, unrelated to Novita’s or DeepInfra’s own rate limits on the backing model.400unsupported_parameter: the most likely cause with this specific model is an agent sending an image (GLM-5.2 is text-only here), or, on Pi specifically, any one ofstore,reasoning_effort, adeveloper-role message, or a strict-mode tool schema. On Pi, this is the failure you’ll see if any of the fourcompatflags above is missing. On OpenCode, the same code would mean an image slipped through despiteattachment: false.
Where to go next
- Use GLM-5.2 through an OpenAI-compatible API in Python: the same endpoint, called directly, useful for confirming a request shape before you debug it through an agent.
- Can you run GLM-5.2 locally?: why a hosted route is the practical choice for a model this size.
- Hugging Face provider page: the router this model sits behind.
- Pricing: the full billing model.