Local proxy that connects OpenAI-compatible clients (Vercel AI SDK, any POST /v1/chat/completions client) to CodeBuddy.
Requires Node.js ≥ 18.
npm install
cp .env.example .env # edit .env to add your CODEBUDDY_API_KEY
npm run dev # http://127.0.0.1:3456bin/wb manages a launchd agent that starts at login, restarts on exit, and
runs in the background. The plist is a template in deploy/; install renders
it into ~/Library/LaunchAgents so the service definition is versioned with the
code it starts.
./bin/wb install # render plist, load agent, wait for /health
./bin/wb status # launchd state, pid, port owner, /health
./bin/wb logs -f # follow stdout ('./bin/wb err -f' for stderr)
./bin/wb restart # also how .env changes take effect
./bin/wb stop # unload until the next login
./bin/wb uninstall # unload and remove the plistService logs go to ~/.local/state/workbuddy-api/; request logs stay in the
repo's logs/. Node is resolved through the fnm default alias, falling back
to node on PATH — launchd does not inherit your shell's environment, so
the plist also pins WorkingDirectory, which .env loading and the relative
TLS paths depend on.
A crashed process comes back within ThrottleInterval (10s). When the service
stays down, ./bin/wb doctor checks Node, .env, the TLS certs, the rendered
plist, and whether the port is held by a stray manual npm start.
| Method | Path | Description |
|---|---|---|
POST |
/v1/chat/completions |
OpenAI Chat Completions API (thinking effort passed through) |
GET |
/v1/models |
Model list with reasoning effort levels (from WorkBuddy product config) |
GET |
/v1/models/pi-catalog |
Ready-to-use Pi provider config (?provider=name) |
GET |
/health |
Health check |
import { createOpenAI } from "@ai-sdk/openai";
const local = createOpenAI({
baseURL: "http://127.0.0.1:3456/v1",
apiKey: "dummy", // proxy injects the real token
});
const result = await local.chat("default").generate("Hello");CodeBuddy is the sole upstream:
| Provider | Protocol | Notes |
|---|---|---|
| CodeBuddy | OpenAI /v2/chat/completions |
CLI request-header fingerprinting, prompt replacement |
CODEBUDDY_API_KEY is required.
| Variable | Required | Default | Description |
|---|---|---|---|
CODEBUDDY_API_KEY |
Yes | — | Enable CodeBuddy provider |
CODEBUDDY_BASE_URL |
No | https://www.codebuddy.ai |
Also: https://copilot.tencent.com (internal) |
CODEBUDDY_MODELS |
No | default |
Comma-separated model aliases |
CODEBUDDY_TARGET_MODEL |
No | (same as alias) | Real upstream model name |
CODEBUDDY_CLI_VERSION |
No | 2.110.0 |
Override auto-detected version |
DEFAULT_MODEL |
No | (none) | Default model when request omits it |
PORT |
No | 3456 |
|
HOST |
No | 127.0.0.1 |
POST /v1/chat/completions
└─ handleChatCompletions() → handleCodeBuddyRequest() (full adaptation stack)
├─ fetchUpstream(body)
│ ├─ provider.resolveURL() → /v2/chat/completions
│ ├─ provider.buildHeaders() → CLI fingerprint headers
│ └─ provider.preRequest() → model alias → real name
├─ stream:true → SSE field cleaning + pipe to client
└─ stream:false → aggregate SSE → single JSON response
- CodeBuddy path: prompt replacement, content filtering, forced streaming, SSE field cleaning, CLI fingerprint headers
See Request Paths and Design Decisions for details.
Model list is loaded from the WorkBuddy desktop client's cloud product config cache
(~/.workbuddy/cache/acc-product-config-v3-<pid>-<uuid>.json, filtered to the craft
agent's model whitelist), refreshed every 60 seconds. The client writes one snapshot per
process instance and shares the directory with its CLI config, so the loader picks the
newest snapshot that carries an agents section. No separate API call needed. If no such
cache exists, it falls back to ~/.codebuddy/local_storage/, the CLI package config, and
finally the bundled src/builtin-models.json.
The bundled fallback is a machine-generated snapshot — regenerate it with
node scripts/sync-builtin-models.js (or verify with --check) instead of editing by hand.
MIT