NativePort
← How-to

How to Connect Kimi K3 to Coding Agents with OpenCode and Pi

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

OpenCode and Pi are both terminal coding agents that let you 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 Kimi K3 through NativePort’s unified /inference/v1/chat/completions endpoint using the canonical model id huggingface/moonshotai/Kimi-K3. This tutorial covers the exact config shape each project currently documents, 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

Kimi K3 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. Both configs below are verified working configurations against the real gateway.

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/moonshotai/Kimi-K3": {
          "name": "Kimi K3",
          "attachment": true,
          "tool_call": true,
          "reasoning": false,
          "cost": { "input": 3, "output": 15 },
          "limit": { "context": 1048576, "output": 8192 },
          "modalities": { "input": ["text", "image"] }
        }
      }
    }
  }
}

This exact config runs against the live gateway on opencode-ai 1.18.8. Treat that as the version this config is known to work with, not a strict compatibility floor; a later release could change what it sends by default.

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: true and modalities.input: ["text", "image"] reflect the model’s real vision: true capability from NativePort’s catalog, set from what the gateway reports, not assumed.
  • limit.output: 8192 is not a published Kimi K3 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; it isn’t asserting a fact about the model.
  • cost mirrors NativePort’s real per-million-token rate ($3 input / $15 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.

Select the model from the CLI:

opencode run --model nativeport/huggingface/moonshotai/Kimi-K3 "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/moonshotai/Kimi-K3",
          "name": "Kimi K3 (NativePort)",
          "reasoning": false,
          "input": ["text", "image"],
          "contextWindow": 1048576,
          "cost": { "input": 3, "output": 15, "cacheRead": 0, "cacheWrite": 0 },
          "compat": { "supportsStore": false }
        }
      ]
    }
  }
}

compat.supportsStore: false isn’t optional. Without it, Pi’s request includes a store field the gateway rejects, and the very first call fails before it reaches the model; adding this flag is the fix. With it in place, @earendil-works/pi-coding-agent 0.82.1 runs this exact config against the live gateway. As with OpenCode above, treat the version as the one this config is known to work with, not a compatibility guarantee for other releases.

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, and NativePort doesn’t publish a separate output ceiling for this model to override that default with. Raise it explicitly if your workload needs longer completions and you’ve confirmed the gateway will serve them.

Launch pi against it directly:

pi --provider nativeport --model huggingface/moonshotai/Kimi-K3 -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/moonshotai/Kimi-K3 exactly, matching the object key you registered. If Kimi K3 was only just cataloged when you’re reading this, also allow for a moment of propagation delay before assuming the id is wrong.
  • 429 gateway_rate_limited: back off. This is a per-account gateway limit, unrelated to Fireworks’ own rate limits on the backing model.

Where to go next