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.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: trueandmodalities.input: ["text", "image"]reflect the model’s realvision: truecapability from NativePort’s catalog, set from what the gateway reports, not assumed.limit.output: 8192is 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 asmax_tokens. Raise or lower it for your workload; it isn’t asserting a fact about the model.costmirrors 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_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/moonshotai/Kimi-K3exactly, 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.429gateway_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
- Use Kimi K3 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 Kimi K3 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.