Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d057f085d | |||
| 570fdae9c4 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "paperclip-adapter-opencode-k8s",
|
||||
"version": "0.1.35",
|
||||
"version": "0.1.37",
|
||||
"description": "Paperclip adapter plugin that runs OpenCode agents as Kubernetes Jobs",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -884,7 +884,6 @@ describe("execute — external cancel polling", () => {
|
||||
vi.useRealTimers();
|
||||
vi.unstubAllGlobals();
|
||||
delete process.env.PAPERCLIP_API_URL;
|
||||
delete process.env.PAPERCLIP_DEV_API_KEY;
|
||||
});
|
||||
|
||||
it("returns errorCode=cancelled and deletes job when issue status is cancelled", async () => {
|
||||
@@ -970,42 +969,6 @@ describe("execute — external cancel polling", () => {
|
||||
expect(result.errorCode).toBeUndefined();
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
||||
|
||||
it("uses PAPERCLIP_DEV_API_KEY over ctx.authToken when set", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
process.env.PAPERCLIP_API_URL = "http://test-api";
|
||||
process.env.PAPERCLIP_DEV_API_KEY = "dev-override-key";
|
||||
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ status: "cancelled" }),
|
||||
});
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
let jobDeleted = false;
|
||||
const batchApi = makeBatchApi();
|
||||
batchApi.deleteNamespacedJob.mockImplementation(() => { jobDeleted = true; return Promise.resolve({}); });
|
||||
batchApi.readNamespacedJob.mockImplementation(() => {
|
||||
if (jobDeleted) return Promise.reject(Object.assign(new Error("not found"), { statusCode: 404 }));
|
||||
return Promise.resolve({ status: { conditions: [] } });
|
||||
});
|
||||
vi.mocked(getBatchApi).mockReturnValue(batchApi as unknown as ReturnType<typeof getBatchApi>);
|
||||
|
||||
const ctx = makeCtx({}, { issueId: "issue-test-456" }, "ctx-auth-token");
|
||||
const executePromise = execute(ctx);
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
await vi.advanceTimersByTimeAsync(1_000);
|
||||
}
|
||||
|
||||
await executePromise;
|
||||
|
||||
expect(fetchMock).toHaveBeenCalledWith(
|
||||
"http://test-api/api/issues/issue-test-456",
|
||||
expect.objectContaining({ headers: expect.objectContaining({ Authorization: "Bearer dev-override-key" }) }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("execute — large-prompt Secret path", () => {
|
||||
|
||||
@@ -569,9 +569,7 @@ async function streamAndAwaitJob(
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, KEEPALIVE_INTERVAL_MS));
|
||||
if (logStopSignal.stopped || cancelSignal.cancelled) break;
|
||||
try {
|
||||
// Prefer PAPERCLIP_DEV_API_KEY if set (dev override), otherwise use
|
||||
// the per-run authToken issued by Paperclip for this execution.
|
||||
const apiKey = process.env.PAPERCLIP_DEV_API_KEY ?? ctx.authToken ?? "";
|
||||
const apiKey = ctx.authToken ?? "";
|
||||
const resp = await fetch(`${apiUrl}/api/issues/${issueId}`, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
import type { ServerAdapterModule } from "@paperclipai/adapter-utils";
|
||||
import { getAdapterSessionManagement } from "@paperclipai/adapter-utils";
|
||||
import { type, agentConfigurationDoc } from "../index.js";
|
||||
import { listK8sModels } from "./models.js";
|
||||
import { listK8sModels, STATIC_MODELS } from "./models.js";
|
||||
import { execute } from "./execute.js";
|
||||
import { testEnvironment } from "./test.js";
|
||||
import { sessionCodec } from "./session.js";
|
||||
@@ -14,6 +14,7 @@ export function createServerAdapter(): ServerAdapterModule {
|
||||
execute,
|
||||
testEnvironment,
|
||||
sessionCodec,
|
||||
models: STATIC_MODELS,
|
||||
listModels: listK8sModels,
|
||||
listSkills: listOpenCodeSkills,
|
||||
syncSkills: syncOpenCodeSkills,
|
||||
|
||||
@@ -484,15 +484,14 @@ describe("buildJobManifest — env wiring branches", () => {
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_API_KEY")?.value).toBe("tok_abc");
|
||||
});
|
||||
|
||||
it("inherits PAPERCLIP_API_URL and PAPERCLIP_DEV_API_KEY from selfPod inheritedEnv", () => {
|
||||
it("inherits PAPERCLIP_API_URL from selfPod inheritedEnv", () => {
|
||||
const selfPod = {
|
||||
...mockSelfPod,
|
||||
inheritedEnv: { PAPERCLIP_API_URL: "http://api", PAPERCLIP_DEV_API_KEY: "dev_key" },
|
||||
inheritedEnv: { PAPERCLIP_API_URL: "http://api" },
|
||||
};
|
||||
const result = buildJobManifest({ ctx: mockCtx, selfPod });
|
||||
const env = result.job.spec?.template.spec?.containers[0]?.env ?? [];
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_API_URL")?.value).toBe("http://api");
|
||||
expect(env.find((e) => e.name === "PAPERCLIP_DEV_API_KEY")?.value).toBe("dev_key");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -173,13 +173,6 @@ function buildEnvVars(
|
||||
if (selfPod.inheritedEnv.PAPERCLIP_API_URL) {
|
||||
paperclipEnv.PAPERCLIP_API_URL = selfPod.inheritedEnv.PAPERCLIP_API_URL;
|
||||
}
|
||||
// Inherit PAPERCLIP_DEV_API_KEY if set (dev-instance key, distinct from the
|
||||
// main-instance run JWT in PAPERCLIP_API_KEY). Used by the external cancel
|
||||
// polling in execute.ts to authenticate against the dev Paperclip instance.
|
||||
if (selfPod.inheritedEnv.PAPERCLIP_DEV_API_KEY) {
|
||||
paperclipEnv.PAPERCLIP_DEV_API_KEY = selfPod.inheritedEnv.PAPERCLIP_DEV_API_KEY;
|
||||
}
|
||||
|
||||
// Layer 3: Inherited from Deployment (Bedrock, API keys, etc.)
|
||||
const merged: Record<string, string> = {
|
||||
...selfPod.inheritedEnv,
|
||||
|
||||
@@ -3,6 +3,15 @@ import os from "node:os";
|
||||
import type { AdapterModel } from "@paperclipai/adapter-utils";
|
||||
import { asString, ensurePathInEnv, runChildProcess } from "@paperclipai/adapter-utils/server-utils";
|
||||
|
||||
export const STATIC_MODELS: AdapterModel[] = [
|
||||
{ id: "anthropic/claude-opus-4-7", label: "anthropic/claude-opus-4-7" },
|
||||
{ id: "anthropic/claude-sonnet-4-6", label: "anthropic/claude-sonnet-4-6" },
|
||||
{ id: "anthropic/claude-haiku-4-5", label: "anthropic/claude-haiku-4-5" },
|
||||
{ id: "openai/gpt-4o", label: "openai/gpt-4o" },
|
||||
{ id: "google/gemini-2.5-pro", label: "google/gemini-2.5-pro" },
|
||||
{ id: "google/gemini-2.5-flash", label: "google/gemini-2.5-flash" },
|
||||
];
|
||||
|
||||
const MODELS_CACHE_TTL_MS = 60_000;
|
||||
const MODELS_DISCOVERY_TIMEOUT_MS = 20_000;
|
||||
|
||||
|
||||
@@ -7,6 +7,16 @@ describe("createServerAdapter", () => {
|
||||
expect(adapter.type).toBe("opencode_k8s");
|
||||
});
|
||||
|
||||
it("exposes a non-empty static models list so the UI renders before listModels resolves", () => {
|
||||
const adapter = createServerAdapter();
|
||||
expect(Array.isArray(adapter.models)).toBe(true);
|
||||
expect(adapter.models!.length).toBeGreaterThan(0);
|
||||
for (const m of adapter.models!) {
|
||||
expect(m.id).toMatch(/^[^/]+\/.+/);
|
||||
expect(m.label).toBe(m.id);
|
||||
}
|
||||
});
|
||||
|
||||
it("exposes listModels for dynamic model discovery", () => {
|
||||
const adapter = createServerAdapter();
|
||||
expect(typeof adapter.listModels).toBe("function");
|
||||
|
||||
Reference in New Issue
Block a user