Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
12 KiB
JSON
1 line
12 KiB
JSON
{"repo": "chrisdemonxxx/paperclip", "files": {"server/src/__tests__/adapter-models.test.ts": {"245a9bb20bd8923dcc6c908c7dfdb950a97edf52": {"t": 1786068443.165772, "n": 11161, "h": "d689547bc5d24e9d", "c": "import { afterEach, beforeEach, describe, expect, it, vi } from \"vitest\";\nimport { models as claudeFallbackModels } from \"@paperclipai/adapter-claude-local\";\nimport { resetClaudeModelsCacheForTests } from \"@paperclipai/adapter-claude-local/server\";\nimport { models as codexFallbackModels } from \"@paperclipai/adapter-codex-local\";\nimport { models as cursorFallbackModels } from \"@paperclipai/adapter-cursor-local\";\nimport { models as opencodeFallbackModels } from \"@paperclipai/adapter-opencode-local\";\nimport { resetOpenCodeModelsCacheForTests } from \"@paperclipai/adapter-opencode-local/server\";\nimport { listAdapterModels, listServerAdapters, refreshAdapterModels } from \"../adapters/index.js\";\nimport { resetCodexModelsCacheForTests } from \"../adapters/codex-models.js\";\nimport { resetCursorModelsCacheForTests, setCursorModelsRunnerForTests } from \"../adapters/cursor-models.js\";\n\nvi.mock(\"acpx/runtime\", () => ({\n createAcpRuntime: vi.fn(),\n createAgentRegistry: vi.fn(),\n createRuntimeStore: vi.fn(),\n isAcpRuntimeError: vi.fn(() => false),\n}));\n\ndescribe(\"adapter model listing\", () => {\n beforeEach(() => {\n delete process.env.OPENAI_API_KEY;\n delete process.env.ANTHROPIC_API_KEY;\n delete process.env.ANTHROPIC_BASE_URL;\n delete process.env.ANTHROPIC_BEDROCK_BASE_URL;\n delete process.env.CLAUDE_CODE_USE_BEDROCK;\n delete process.env.PAPERCLIP_OPENCODE_COMMAND;\n resetClaudeModelsCacheForTests();\n resetCodexModelsCacheForTests();\n resetCursorModelsCacheForTests();\n setCursorModelsRunnerForTests(null);\n resetOpenCodeModelsCacheForTests();\n vi.restoreAllMocks();\n });\n\n it(\"returns an empty list for unknown adapters\", async () => {\n const models = await listAdapterModels(\"unknown_adapter\");\n expect(models).toEqual([]);\n });\n\n it(\"does not expose models for the retired acpx_local tombstone\", () => {\n const adapter = listServerAdapters().find((candidate) => candidate.type === \"acpx_local\");\n\n expect(adapter?.models).toEqual([]);\n });\n\n it(\"returns codex fallback models when no OpenAI key is available\", async () => {\n const fetchSpy = vi.spyOn(globalThis, \"fetch\");\n const models = await listAdapterModels(\"codex_local\");\n\n expect(models).toEqual(codexFallbackModels);\n expect(models.some((model) => model.id === \"gpt-5.6\")).toBe(true);\n expect(models.some((model) => model.id === \"gpt-5.6-sol\")).toBe(true);\n expect(models.some((model) => model.id === \"gpt-5.6-terra\")).toBe(true);\n expect(models.some((model) => model.id === \"gpt-5.6-luna\")).toBe(true);\n expect(fetchSpy).not.toHaveBeenCalled();\n });\n\n it(\"returns claude fallback models including the latest Opus alias when no Anthropic key is available\", async () => {\n const fetchSpy = vi.spyOn(globalThis, \"fetch\");\n const models = await listAdapterModels(\"claude_local\");\n\n expect(models).toEqual(claudeFallbackModels);\n expect(models.some((model) => model.id === \"claude-opus-4-8\")).toBe(true);\n // Newer flagship models are offered, but Opus 4.8 stays the default (first) option.\n expect(models[0]?.id).toBe(\"claude-opus-4-8\");\n expect(models.some((model) => model.id === \"claude-fable-5\")).toBe(true);\n expect(models.some((model) => model.id === \"claude-mythos-5\")).toBe(true);\n expect(fetchSpy).not.toHaveBeenCalled();\n });\n\n it(\"loads claude models dynamically and merges fallback options\", async () => {\n process.env.ANTHROPIC_API_KEY = \"sk-ant-test\";\n const fetchSpy = vi.spyOn(globalThis, \"fetch\").mockResolvedValue({\n ok: true,\n json: async () => ({\n data: [\n { id: \"claude-sonnet-4-20250514\", display_name: \"Claude Sonnet 4\" },\n { id: \"claude-opus-4-8-20260529\", display_name: \"Claude Opus 4.8\" },\n ],\n }),\n } as Response);\n\n const first = await listAdapterModels(\"claude_local\");\n const second = await listAdapterModels(\"claude_local\");\n\n expect(fetchSpy).toHaveBeenCalledTimes(1);\n expect(first).toEqual(second);\n expect(first.some((model) => model.id === \"claude-opus-4-8-20260529\")).toBe(true);\n expect(first.some((model) => model.id === \"claude-opus-4-8\")).toBe(true);\n });\n\n it(\"refreshes cached claude models on demand\", async () => {\n process.env.ANTHROPIC_API_KEY = \"sk-ant-test\";\n const fetchSpy = vi.spyOn(globalThis, \"fetch\")\n .mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [{ id: \"claude-sonnet-4-20250514\", display_name: \"Claude Sonnet 4\" }],\n }),\n } as Response)\n .mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [{ id: \"claude-opus-4-8-20260529\", display_name: \"Claude Opus 4.8\" }],\n }),\n } as Response);\n\n const initial = await listAdapterModels(\"claude_local\");\n const refreshed = await refreshAdapterModels(\"claude_local\");\n\n expect(fetchSpy).toHaveBeenCalledTimes(2);\n expect(initial.some((model) => model.id === \"claude-sonnet-4-20250514\")).toBe(true);\n expect(refreshed.some((model) => model.id === \"claude-opus-4-8-20260529\")).toBe(true);\n });\n\n it(\"falls back to static claude models when Anthropic model discovery fails\", async () => {\n process.env.ANTHROPIC_API_KEY = \"sk-ant-test\";\n vi.spyOn(globalThis, \"fetch\").mockResolvedValue({\n ok: false,\n status: 401,\n json: async () => ({}),\n } as Response);\n\n const models = await listAdapterModels(\"claude_local\");\n expect(models).toEqual(claudeFallbackModels);\n });\n\n it(\"loads codex models dynamically and merges fallback options\", async () => {\n process.env.OPENAI_API_KEY = \"sk-test\";\n const fetchSpy = vi.spyOn(globalThis, \"fetch\").mockResolvedValue({\n ok: true,\n json: async () => ({\n data: [\n { id: \"gpt-5-pro\" },\n { id: \"gpt-5\" },\n ],\n }),\n } as Response);\n\n const first = await listAdapterModels(\"codex_local\");\n const second = await listAdapterModels(\"codex_local\");\n\n expect(fetchSpy).toHaveBeenCalledTimes(1);\n expect(first).toEqual(second);\n expect(first.some((model) => model.id === \"gpt-5-pro\")).toBe(true);\n expect(first.some((model) => model.id === \"codex-mini-latest\")).toBe(true);\n });\n\n it(\"refreshes cached codex models on demand\", async () => {\n process.env.OPENAI_API_KEY = \"sk-test\";\n const fetchSpy = vi.spyOn(globalThis, \"fetch\")\n .mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [{ id: \"gpt-5\" }],\n }),\n } as Response)\n .mockResolvedValueOnce({\n ok: true,\n json: async () => ({\n data: [{ id: \"gpt-5.6-terra\" }],\n }),\n } as Response);\n\n const initial = await listAdapterModels(\"codex_local\");\n const refreshed = await refreshAdapterModels(\"codex_local\");\n\n expect(fetchSpy).toHaveBeenCalledTimes(2);\n expect(initial.some((model) => model.id === \"gpt-5\")).toBe(true);\n expect(refreshed.some((model) => model.id === \"gpt-5.6-terra\")).toBe(true);\n expect(refreshed.some((model) => model.id === \"gpt-5.6-luna\")).toBe(true);\n });\n\n it(\"falls back to static codex models when OpenAI model discovery fails\", async () => {\n process.env.OPENAI_API_KEY = \"sk-test\";\n vi.spyOn(globalThis, \"fetch\").mockResolvedValue({\n ok: false,\n status: 401,\n json: async () => ({}),\n } as Response);\n\n const models = await listAdapterModels(\"codex_local\");\n expect(models).toEqual(codexFallbackModels);\n });\n\n\n it(\"returns cursor fallback models when CLI discovery is unavailable\", async () => {\n setCursorModelsRunnerForTests(() => ({\n status: null,\n stdout: \"\",\n stderr: \"\",\n hasError: true,\n }));\n\n const models = await listAdapterModels(\"cursor\");\n expect(models).toEqual(cursorFallbackModels);\n });\n\n it(\"returns opencode fallback models including gpt-5.4\", async () => {\n process.env.PAPERCLIP_OPENCODE_COMMAND = \"__paperclip_missing_opencode_command__\";\n\n const models = await listAdapterModels(\"opencode_local\");\n\n expect(models).toEqual(opencodeFallbackModels);\n });\n\n it(\"loads cursor models dynamically and caches them\", async () => {\n const runner = vi.fn(() => ({\n status: 0,\n stdout: \"Available models: auto, composer-1.5, gpt-5.3-codex-high, sonnet-4.6\",\n stderr: \"\",\n hasError: false,\n }));\n setCursorModelsRunnerForTests(runner);\n\n const first = await listAdapterModels(\"cursor\");\n const second = await listAdapterModels(\"cursor\");\n\n expect(runner).toHaveBeenCalledTimes(1);\n expect(first).toEqual(second);\n expect(first.some((model) => model.id === \"auto\")).toBe(true);\n expect(first.some((model) => model.id === \"gpt-5.3-codex-high\")).toBe(true);\n expect(first.some((model) => model.id === \"composer-1\")).toBe(true);\n });\n\n describe(\"PAPERCLIP_ADAPTER_MODELS declared models\", () => {\n afterEach(() => {\n delete process.env.PAPERCLIP_ADAPTER_MODELS;\n });\n\n it(\"prefers declared env models over adapter discovery\", async () => {\n process.env.PAPERCLIP_ADAPTER_MODELS = JSON.stringify({\n opencode_local: [\n { id: \"tensorix/deepseek/deepseek-chat-v3.1\", label: \"DeepSeek v3.1\" },\n { id: \"tensorix/z-ai/glm-4.7\" },\n ],\n });\n\n const models = await listAdapterModels(\"opencode_local\");\n\n expect(models).toEqual([\n { id: \"tensorix/deepseek/deepseek-chat-v3.1\", label: \"DeepSeek v3.1\" },\n { id: \"tensorix/z-ai/glm-4.7\", label: \"tensorix/z-ai/glm-4.7\" },\n ]);\n });\n\n it(\"observes env changes between calls (memo keyed by raw env value)\", async () => {\n process.env.PAPERCLIP_ADAPTER_MODELS = JSON.stringify({\n opencode_local: [{ id: \"model-a\" }],\n });\n expect(await listAdapterModels(\"opencode_local\")).toEqual([\n { id: \"model-a\", label: \"model-a\" },\n ]);\n\n process.env.PAPERCLIP_ADAPTER_MODELS = JSON.stringify({\n opencode_local: [{ id: \"model-b\" }],\n });\n expect(await listAdapterModels(\"opencode_local\")).toEqual([\n { id: \"model-b\", label: \"model-b\" },\n ]);\n });\n\n it(\"fails soft on malformed values: falls back to adapter models instead of throwing\", async () => {\n process.env.PAPERCLIP_ADAPTER_MODELS = \"{not json\";\n process.env.PAPERCLIP_OPENCODE_COMMAND = \"__paperclip_missing_opencode_command__\";\n const errorSpy = vi.spyOn(console, \"error\").mockImplementation(() => {});\n\n const models = await listAdapterModels(\"opencode_local\");\n expect(models).toEqual(opencodeFallbackModels);\n\n // Parsing is memoized per raw value: a second call must not re-log.\n const callsAfterFirst = errorSpy.mock.calls.length;\n expect(callsAfterFirst).toBeGreaterThan(0);\n await listAdapterModels(\"opencode_local\");\n expect(errorSpy.mock.calls.length).toBe(callsAfterFirst);\n });\n\n it(\"ignores declared models for adapters not in the map\", async () => {\n process.env.PAPERCLIP_ADAPTER_MODELS = JSON.stringify({\n opencode_local: [{ id: \"model-a\" }],\n });\n const models = await listAdapterModels(\"codex_local\");\n expect(models).toEqual(codexFallbackModels);\n });\n });\n});\n"}}}} |