chat: point OpenCode at a fixed pm2-managed server (fixes empty model picker)
The per-cwd `opencode serve` spawning is replaced by a single fixed server (http://127.0.0.1:4096, OPENCODE_SERVER_URL) managed by pm2 — added as `officer-opencode` in ecosystem.config.cjs (cwd = home). Root-cause fix for the empty model selector: list-models shelled out to `opencode models`, which failed at runtime on the deployed server (the picker got only Claude tiers). It now reads the fixed server's GET /config/providers over HTTP — 11ms and reliable — so the curated OpenCode models (Big Pickle, Claude Haiku) show up. - server-manager.ts — drops spawning; exposes OPENCODE_SERVER_URL + a health check. - client.ts — createSession no longer binds a directory (sessions live in the one server's project). - send-opencode.ts / opencode-sessions.ts / chat.ts — use the fixed server; drop the cwd/home plumbing. Session list/load/delete/rename now hit :4096. Verified end-to-end against the live server: model list, streaming turn, session list, and transcript load all work with no spawning. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import type { ModelInfo } from './types';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { OPENCODE_SERVER_URL } from './opencode/server-manager';
|
||||
|
||||
// The Claude harness runs the `claude` CLI, so its tiers are a fixed set.
|
||||
const CLAUDE_CODE_MODELS: ModelInfo[] = [
|
||||
@@ -9,46 +8,47 @@ const CLAUDE_CODE_MODELS: ModelInfo[] = [
|
||||
{ id: 'claude-code/haiku', name: 'haiku', provider: 'claude-code', contextWindow: 200000, maxTokens: 8192, reasoning: false, images: true },
|
||||
];
|
||||
|
||||
const OPENCODE_BIN = process.env.OPENCODE_BIN || join(homedir(), '.opencode', 'bin', 'opencode');
|
||||
|
||||
// Curated OpenCode models to surface in the picker (the full `opencode models` catalog is ~58 entries).
|
||||
// Curated OpenCode models to surface in the picker (the full catalog is ~58 entries).
|
||||
const OPENCODE_ALLOWLIST = new Set(['opencode/big-pickle', 'opencode/claude-haiku-4-5']);
|
||||
|
||||
// Cache the OpenCode catalog; it's stable for a session and `opencode models` costs a subprocess.
|
||||
// Cache the OpenCode catalog; it's stable for a session.
|
||||
let openCodeCache: ModelInfo[] | null = null;
|
||||
|
||||
export function invalidateModelCache(): void {
|
||||
openCodeCache = null;
|
||||
}
|
||||
|
||||
// Enumerate OpenCode models via `opencode models` (ids are `providerID/modelID`, e.g.
|
||||
// `opencode/claude-opus-4-8`). Metadata (context/tokens/modalities) is left at neutral defaults for
|
||||
// now — it can be enriched later from a serve's GET /config/providers.
|
||||
type ProvidersResponse = {
|
||||
providers?: Array<{ id?: string; models?: Record<string, unknown> }>;
|
||||
};
|
||||
|
||||
// Enumerate OpenCode models from the fixed server's GET /config/providers (reliable — no subprocess).
|
||||
// Model ids are `providerID/modelID` (e.g. `opencode/claude-haiku-4-5`); metadata is left at neutral
|
||||
// defaults for now.
|
||||
async function listOpenCodeModels(): Promise<ModelInfo[]> {
|
||||
if (openCodeCache) return openCodeCache;
|
||||
try {
|
||||
const proc = Bun.spawn([OPENCODE_BIN, 'models'], { stdout: 'pipe', stderr: 'ignore' });
|
||||
const out = await new Response(proc.stdout).text();
|
||||
await proc.exited;
|
||||
const res = await fetch(`${OPENCODE_SERVER_URL}/config/providers`, { signal: AbortSignal.timeout(5000) });
|
||||
if (!res.ok) return [];
|
||||
const data = (await res.json()) as ProvidersResponse;
|
||||
|
||||
const models: ModelInfo[] = out
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[[0-9;]*m/g, '')
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => OPENCODE_ALLOWLIST.has(line))
|
||||
.map((full) => {
|
||||
const slash = full.indexOf('/');
|
||||
return {
|
||||
const models: ModelInfo[] = [];
|
||||
for (const provider of data.providers ?? []) {
|
||||
const providerId = provider.id ?? '';
|
||||
for (const modelId of Object.keys(provider.models ?? {})) {
|
||||
const full = `${providerId}/${modelId}`;
|
||||
if (!OPENCODE_ALLOWLIST.has(full)) continue;
|
||||
models.push({
|
||||
id: full,
|
||||
name: full.slice(slash + 1),
|
||||
provider: full.slice(0, slash),
|
||||
name: modelId,
|
||||
provider: providerId,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 8192,
|
||||
reasoning: false,
|
||||
images: true,
|
||||
} satisfies ModelInfo;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
openCodeCache = models;
|
||||
return models;
|
||||
|
||||
Reference in New Issue
Block a user