Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7043e71ff6 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "paperclip-adapter-opencode-k8s",
|
"name": "paperclip-adapter-opencode-k8s",
|
||||||
"version": "0.1.32",
|
"version": "0.1.33",
|
||||||
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
|
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,7 @@
|
|||||||
import type { ServerAdapterModule } from "@paperclipai/adapter-utils";
|
import type { ServerAdapterModule } from "@paperclipai/adapter-utils";
|
||||||
import { getAdapterSessionManagement } from "@paperclipai/adapter-utils";
|
import { getAdapterSessionManagement } from "@paperclipai/adapter-utils";
|
||||||
import { type, agentConfigurationDoc } from "../index.js";
|
import { type, agentConfigurationDoc } from "../index.js";
|
||||||
import { listK8sModels } from "./models.js";
|
import { listK8sModels, STATIC_MODELS } from "./models.js";
|
||||||
import { execute } from "./execute.js";
|
import { execute } from "./execute.js";
|
||||||
import { testEnvironment } from "./test.js";
|
import { testEnvironment } from "./test.js";
|
||||||
import { sessionCodec } from "./session.js";
|
import { sessionCodec } from "./session.js";
|
||||||
@@ -14,6 +14,7 @@ export function createServerAdapter(): ServerAdapterModule {
|
|||||||
execute,
|
execute,
|
||||||
testEnvironment,
|
testEnvironment,
|
||||||
sessionCodec,
|
sessionCodec,
|
||||||
|
models: STATIC_MODELS,
|
||||||
listModels: listK8sModels,
|
listModels: listK8sModels,
|
||||||
listSkills: listOpenCodeSkills,
|
listSkills: listOpenCodeSkills,
|
||||||
syncSkills: syncOpenCodeSkills,
|
syncSkills: syncOpenCodeSkills,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ vi.mock("child_process", () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { listK8sModels } = await import("./models.js");
|
const { listK8sModels, STATIC_MODELS } = await import("./models.js");
|
||||||
|
|
||||||
function mockExecResult(stdout: string) {
|
function mockExecResult(stdout: string) {
|
||||||
execMock.mockImplementation((_cmd, _opts, cb) => {
|
execMock.mockImplementation((_cmd, _opts, cb) => {
|
||||||
@@ -71,8 +71,15 @@ describe("listK8sModels", () => {
|
|||||||
|
|
||||||
const models = await listK8sModels();
|
const models = await listK8sModels();
|
||||||
|
|
||||||
|
expect(models).toBe(STATIC_MODELS);
|
||||||
expect(models.length).toBeGreaterThan(0);
|
expect(models.length).toBeGreaterThan(0);
|
||||||
expect(models.map((m) => m.id)).toContain("anthropic/claude-opus-4-7");
|
});
|
||||||
expect(models.map((m) => m.id)).toContain("openai/gpt-4o");
|
|
||||||
|
it("falls back to the static list when the CLI returns empty stdout", async () => {
|
||||||
|
mockExecResult("");
|
||||||
|
|
||||||
|
const models = await listK8sModels();
|
||||||
|
|
||||||
|
expect(models).toBe(STATIC_MODELS);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+21
-16
@@ -4,30 +4,35 @@ import { promisify } from "util";
|
|||||||
|
|
||||||
const execAsync = promisify(exec);
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
export const STATIC_MODELS: AdapterModel[] = [
|
||||||
|
{ id: "anthropic/claude-opus-4-7", label: "Claude Opus 4.7" },
|
||||||
|
{ id: "anthropic/claude-opus-4-6", label: "Claude Opus 4.6" },
|
||||||
|
{ id: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
|
||||||
|
{ id: "anthropic/claude-sonnet-4-5", label: "Claude Sonnet 4.5" },
|
||||||
|
{ id: "anthropic/claude-haiku-4-5", label: "Claude Haiku 4.5" },
|
||||||
|
{ id: "openai/gpt-4o", label: "GPT-4o" },
|
||||||
|
{ id: "openai/gpt-4o-mini", label: "GPT-4o mini" },
|
||||||
|
{ id: "openai/gpt-5", label: "GPT-5" },
|
||||||
|
{ id: "openai/gpt-5-mini", label: "GPT-5 mini" },
|
||||||
|
{ id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
||||||
|
{ id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash" },
|
||||||
|
{ id: "xai/grok-4", label: "Grok 4" },
|
||||||
|
{ id: "deepseek/deepseek-chat", label: "DeepSeek Chat" },
|
||||||
|
];
|
||||||
|
|
||||||
export async function listK8sModels(): Promise<AdapterModel[]> {
|
export async function listK8sModels(): Promise<AdapterModel[]> {
|
||||||
try {
|
try {
|
||||||
const result = await execAsync("opencode models", { timeout: 30_000 });
|
const result = await execAsync("opencode models", { timeout: 30_000 });
|
||||||
const output = result.stdout;
|
const lines = result.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
||||||
const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
if (lines.length === 0) return STATIC_MODELS;
|
||||||
const models: AdapterModel[] = [];
|
const models: AdapterModel[] = [];
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (!line) continue;
|
|
||||||
const parts = line.split("/");
|
const parts = line.split("/");
|
||||||
const id = line;
|
|
||||||
const label = parts[parts.length - 1].replace(/-/g, " ").replace(/_/g, " ");
|
const label = parts[parts.length - 1].replace(/-/g, " ").replace(/_/g, " ");
|
||||||
models.push({ id, label });
|
models.push({ id: line, label });
|
||||||
}
|
}
|
||||||
return models;
|
return models;
|
||||||
} catch {
|
} catch {
|
||||||
const fallback: AdapterModel[] = [
|
return STATIC_MODELS;
|
||||||
{ id: "anthropic/claude-opus-4-7", label: "Claude Opus 4.7" },
|
|
||||||
{ id: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet 4.6" },
|
|
||||||
{ id: "anthropic/claude-haiku-4-5", label: "Claude Haiku 4.5" },
|
|
||||||
{ id: "openai/gpt-4o", label: "GPT-4o" },
|
|
||||||
{ id: "openai/gpt-4o-mini", label: "GPT-4o mini" },
|
|
||||||
{ id: "google/gemini-2.5-pro", label: "Gemini 2.5 Pro" },
|
|
||||||
{ id: "google/gemini-2.5-flash", label: "Gemini 2.5 Flash" },
|
|
||||||
];
|
|
||||||
return fallback;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { createServerAdapter } from "./index.js";
|
||||||
|
import { STATIC_MODELS } from "./models.js";
|
||||||
|
|
||||||
|
describe("createServerAdapter", () => {
|
||||||
|
it("declares the opencode_k8s type", () => {
|
||||||
|
const adapter = createServerAdapter();
|
||||||
|
expect(adapter.type).toBe("opencode_k8s");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("exposes a non-empty static models list so the UI never renders zero models", () => {
|
||||||
|
const adapter = createServerAdapter();
|
||||||
|
expect(Array.isArray(adapter.models)).toBe(true);
|
||||||
|
expect(adapter.models!.length).toBeGreaterThan(0);
|
||||||
|
expect(adapter.models).toBe(STATIC_MODELS);
|
||||||
|
for (const m of adapter.models!) {
|
||||||
|
expect(typeof m.id).toBe("string");
|
||||||
|
expect(m.id.length).toBeGreaterThan(0);
|
||||||
|
expect(typeof m.label).toBe("string");
|
||||||
|
expect(m.label.length).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("also exposes listModels for dynamic refresh", () => {
|
||||||
|
const adapter = createServerAdapter();
|
||||||
|
expect(typeof adapter.listModels).toBe("function");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user