NativePort
← How-to

How to Connect GLM-5.2 to Coding Agents with OpenCode and Pi

Register NativePort's OpenAI-compatible route for GLM-5.2 as a custom provider in OpenCode (opencode.json) and Pi (models.json), matching each project's current config schema — no bespoke SDK code required.

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.apiKey uses 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: false and modalities.input: ["text"] reflect GLM-5.2’s real vision: false in NativePort’s catalog. Sending an image to this model through the gateway fails with 400 unsupported_parameter, so the config doesn’t advertise a capability the route will refuse.
  • reasoning: true tells OpenCode that GLM-5.2 reasons by default, so it parses and displays the model’s reasoning_content correctly 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 a reasoning_effort parameter 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 sending reasoning_effort; unlike Pi below, it just doesn’t send one by default for this adapter.
  • limit.output: 8192 is 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 as max_tokens; raise or lower it for your workload.
  • cost mirrors 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. deepinfra is 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" with cost: { "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_KEY isn’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.
  • 404 model_not_found: almost always a copy-paste error in the model id. It must be huggingface/zai-org/GLM-5.2 exactly, matching the object key you registered. Include the :deepinfra suffix only if that’s the entry you’re calling.
  • 429 gateway_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.
  • 400 unsupported_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 of store, reasoning_effort, a developer-role message, or a strict-mode tool schema. On Pi, this is the failure you’ll see if any of the four compat flags above is missing. On OpenCode, the same code would mean an image slipped through despite attachment: false.

Where to go next