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
19 KiB
JSON
1 line
19 KiB
JSON
{"repo": "alphadoiy/l2", "files": {".l2-agent/config.json": {"03eb982e8a26a47711741d56a93c44d4c9738cac": {"t": 1785768255.7661865, "n": 224, "h": "21fe09800c7610e0", "c": "{\n \"provider\": \"openai-compatible\",\n \"protocol\": \"openai-chat-completions\",\n \"model\": \"sensenova-6.7-flash-lite\",\n \"baseUrl\": \"https://token.sensenova.cn/v1\",\n \"apiKeyEnv\": \"OPENAI_API_KEY\",\n \"contextWindow\": 262144\n}\n"}}, "tests/unit/model-discovery.test.ts": {"03eb982e8a26a47711741d56a93c44d4c9738cac": {"t": 1786067816.74036, "n": 17782, "h": "6aa0711012b59b68", "c": "import { afterEach, describe, expect, it, vi } from \"vitest\";\nimport {\n fetchModelsForProtocol,\n isKnownModel,\n lookupContextWindow,\n} from \"../../src/providers/model-discovery.js\";\n\n// ---------------------------------------------------------------------------\n// lookupContextWindow\n// ---------------------------------------------------------------------------\n\ndescribe(\"lookupContextWindow\", () => {\n it(\"returns exact match for a known OpenAI model\", () => {\n expect(lookupContextWindow(\"gpt-4o\")).toBe(128_000);\n });\n\n it(\"returns exact match for gpt-4 (distinct from gpt-4o/gpt-4-turbo)\", () => {\n expect(lookupContextWindow(\"gpt-4\")).toBe(8_192);\n });\n\n it(\"returns exact match for gpt-4.1\", () => {\n expect(lookupContextWindow(\"gpt-4.1\")).toBe(1_047_576);\n });\n\n it(\"returns prefix match for gpt-4o-2024-11-20 (starts with gpt-4o-)\", () => {\n expect(lookupContextWindow(\"gpt-4o-2024-11-20\")).toBe(128_000);\n });\n\n it(\"returns prefix match for gpt-4.1-mini-2025-01-01 (starts with gpt-4.1-mini-)\", () => {\n expect(lookupContextWindow(\"gpt-4.1-mini-2025-01-01\")).toBe(1_047_576);\n });\n\n it(\"returns prefix match for gpt-4-turbo-preview (starts with gpt-4-turbo-)\", () => {\n expect(lookupContextWindow(\"gpt-4-turbo-preview\")).toBe(128_000);\n });\n\n it(\"returns prefix match for claude-3-5-sonnet-20241022 (starts with claude-3-5-sonnet-)\", () => {\n expect(lookupContextWindow(\"claude-3-5-sonnet-20241022\")).toBe(200_000);\n });\n\n it(\"returns prefix match for claude-3-haiku-20240307 (starts with claude-3-haiku-)\", () => {\n expect(lookupContextWindow(\"claude-3-haiku-20240307\")).toBe(200_000);\n });\n\n it(\"returns DEFAULT_CONTEXT_WINDOW (128 000) for a completely unknown model\", () => {\n // Use a model id that cannot possibly match any prefix to verify the fallback.\n expect(lookupContextWindow(\"totally-unknown-model-xyz-9999\")).toBe(128_000);\n });\n\n it(\"returns correct context window for deepseek-r1\", () => {\n expect(lookupContextWindow(\"deepseek-r1\")).toBe(64_000);\n });\n\n it(\"returns prefix match for deepseek-r1-distill-qwen-7b (starts with deepseek-r1-)\", () => {\n expect(lookupContextWindow(\"deepseek-r1-distill-qwen-7b\")).toBe(64_000);\n });\n\n it(\"returns correct context window for a Qwen model\", () => {\n expect(lookupContextWindow(\"qwen-plus\")).toBe(131_072);\n });\n});\n\n// ---------------------------------------------------------------------------\n// isKnownModel\n// ---------------------------------------------------------------------------\n\ndescribe(\"isKnownModel\", () => {\n it(\"returns true for an exact match\", () => {\n expect(isKnownModel(\"gpt-4o\")).toBe(true);\n });\n\n it(\"returns true for a prefix match\", () => {\n expect(isKnownModel(\"gpt-4o-2024-11-20\")).toBe(true);\n });\n\n it(\"returns true for a deepseek model\", () => {\n expect(isKnownModel(\"deepseek-chat\")).toBe(true);\n });\n\n it(\"returns false for a completely unknown model id\", () => {\n expect(isKnownModel(\"custom-proxy-model-abc\")).toBe(false);\n });\n\n it(\"returns false for a third-party alias not in the table\", () => {\n expect(isKnownModel(\"deepseek-v4-flash\")).toBe(false);\n });\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ntype FetchMock = ReturnType<typeof vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>>;\n\nfunction jsonOk(body: unknown): Response {\n return new Response(JSON.stringify(body), {\n status: 200,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction jsonError(status: number, message: string): Response {\n return new Response(JSON.stringify({ error: { message } }), {\n status,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\n// ---------------------------------------------------------------------------\n// fetchModelsForProtocol – OpenAI-compatible\n// ---------------------------------------------------------------------------\n\ndescribe(\"fetchModelsForProtocol – openai-chat-completions\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it(\"returns chat models sorted by id, excluding non-chat models\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(\n jsonOk({\n data: [\n { id: \"gpt-4o\", object: \"model\", created: 1, owned_by: \"openai\" },\n { id: \"gpt-4o-mini\", object: \"model\", created: 2, owned_by: \"openai\" },\n { id: \"whisper-1\", object: \"model\", created: 3, owned_by: \"openai\" },\n { id: \"dall-e-3\", object: \"model\", created: 4, owned_by: \"openai\" },\n { id: \"tts-1\", object: \"model\", created: 5, owned_by: \"openai\" },\n { id: \"text-embedding-ada-002\", object: \"model\", created: 6, owned_by: \"openai\" },\n { id: \"text-moderation-latest\", object: \"model\", created: 7, owned_by: \"openai\" },\n { id: \"gpt-3.5-turbo\", object: \"model\", created: 8, owned_by: \"openai\" },\n ],\n }),\n );\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"sk-test\",\n );\n\n expect(result.error).toBeUndefined();\n expect(result.models.map((m) => m.id)).toEqual([\"gpt-3.5-turbo\", \"gpt-4o\", \"gpt-4o-mini\"]);\n });\n\n it(\"sets contextWindow and contextWindowIsEstimate via lookupContextWindow\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(\n jsonOk({\n data: [\n { id: \"gpt-4o-mini\", object: \"model\", created: 1, owned_by: \"openai\" },\n { id: \"custom-proxy-v99\", object: \"model\", created: 2, owned_by: \"openai\" },\n ],\n }),\n );\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"sk-test\",\n );\n\n const known = result.models.find((m) => m.id === \"gpt-4o-mini\");\n const unknown = result.models.find((m) => m.id === \"custom-proxy-v99\");\n expect(known).toMatchObject({ contextWindow: 128_000, contextWindowIsEstimate: false });\n expect(unknown).toMatchObject({ contextWindow: 128_000, contextWindowIsEstimate: true });\n });\n\n it(\"calls the correct URL and sets Authorization header\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonOk({ data: [] }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"sk-secret\",\n );\n\n const [calledUrl, calledInit] = fetchMock.mock.calls[0] ?? [];\n expect(calledUrl).toBe(\"https://api.openai.com/v1/models\");\n expect(calledInit?.headers).toMatchObject({ Authorization: \"Bearer sk-secret\" });\n });\n\n it(\"does not append /models if baseUrl already ends with /models\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonOk({ data: [] }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1/models\",\n \"sk-test\",\n );\n\n const [calledUrl] = fetchMock.mock.calls[0] ?? [];\n expect(calledUrl).toBe(\"https://api.openai.com/v1/models\");\n });\n\n it(\"normalizes a chat completions endpoint before listing models\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonOk({ data: [] }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://proxy.example.com/v1/chat/completions\",\n \"sk-test\",\n );\n\n const [calledUrl] = fetchMock.mock.calls[0] ?? [];\n expect(calledUrl).toBe(\"https://proxy.example.com/v1/models\");\n });\n\n it(\"returns 404-specific error message when endpoint is not found\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(new Response(\"\", { status: 404 }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://proxy.example.com/v1\",\n \"sk-test\",\n );\n\n expect(result.models).toEqual([]);\n expect(result.error).toMatch(/does not support model listing/i);\n expect(result.error).toMatch(/models\\.json/i);\n });\n\n it(\"returns auth-specific error message on 401\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonError(401, \"Unauthorized\"));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"bad-key\",\n );\n\n expect(result.models).toEqual([]);\n expect(result.error).toMatch(/authentication failed/i);\n });\n\n it(\"returns error result when fetch throws a network error\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockRejectedValueOnce(new TypeError(\"Failed to fetch\"));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"sk-test\",\n );\n\n expect(result.models).toEqual([]);\n expect(result.error).toBe(\"Failed to fetch\");\n });\n\n it(\"returns error result when response is not valid JSON\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(new Response(\"not-json\", { status: 200 }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"sk-test\",\n );\n\n expect(result.models).toEqual([]);\n expect(result.error).toBeDefined();\n });\n\n it(\"returns error result when response shape is unexpected\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonOk({ models: [] })); // wrong key\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"sk-test\",\n );\n\n expect(result.models).toEqual([]);\n expect(result.error).toMatch(/unexpected response format/i);\n });\n\n it(\"filters models whose id contains 'embedding'\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(\n jsonOk({\n data: [\n { id: \"some-embedding-v1\", object: \"model\", created: 1, owned_by: \"openai\" },\n { id: \"gpt-4o\", object: \"model\", created: 2, owned_by: \"openai\" },\n ],\n }),\n );\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"openai-chat-completions\",\n \"https://api.openai.com/v1\",\n \"sk-test\",\n );\n\n expect(result.models.map((m) => m.id)).toEqual([\"gpt-4o\"]);\n });\n});\n\n// ---------------------------------------------------------------------------\n// fetchModelsForProtocol – Anthropic\n// ---------------------------------------------------------------------------\n\ndescribe(\"fetchModelsForProtocol – anthropic-messages\", () => {\n afterEach(() => {\n vi.unstubAllGlobals();\n });\n\n it(\"returns models with displayName and contextWindowIsEstimate on a single page (has_more: false)\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(\n jsonOk({\n data: [\n {\n id: \"claude-3-5-sonnet-20241022\",\n type: \"model\",\n display_name: \"Claude 3.5 Sonnet\",\n created_at: \"2024-10-22T00:00:00Z\",\n },\n {\n id: \"claude-3-haiku-20240307\",\n type: \"model\",\n display_name: \"Claude 3 Haiku\",\n created_at: \"2024-03-07T00:00:00Z\",\n },\n {\n id: \"proxy-custom-claude\",\n type: \"model\",\n display_name: \"Custom\",\n created_at: \"2024-01-01T00:00:00Z\",\n },\n ],\n has_more: false,\n last_id: null,\n }),\n );\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"anthropic-messages\",\n \"https://api.anthropic.com\",\n \"ant-key\",\n );\n\n expect(result.error).toBeUndefined();\n expect(result.models[0]).toMatchObject({\n id: \"claude-3-5-sonnet-20241022\",\n displayName: \"Claude 3.5 Sonnet\",\n contextWindow: 200_000,\n contextWindowIsEstimate: false,\n });\n expect(result.models[1]).toMatchObject({\n id: \"claude-3-haiku-20240307\",\n contextWindowIsEstimate: false,\n });\n expect(result.models[2]).toMatchObject({\n id: \"proxy-custom-claude\",\n contextWindowIsEstimate: true,\n });\n expect(fetchMock).toHaveBeenCalledTimes(1);\n });\n\n it(\"calls correct URL with Anthropic auth headers\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonOk({ data: [], has_more: false, last_id: null }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n await fetchModelsForProtocol(\"anthropic-messages\", \"https://api.anthropic.com\", \"ant-secret\");\n\n const [calledUrl, calledInit] = fetchMock.mock.calls[0] ?? [];\n expect(calledUrl).toBe(\"https://api.anthropic.com/v1/models?limit=100\");\n expect(calledInit?.headers).toMatchObject({\n \"x-api-key\": \"ant-secret\",\n \"anthropic-version\": \"2023-06-01\",\n });\n });\n\n it(\"does not duplicate /v1 when baseUrl already ends with /v1\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonOk({ data: [], has_more: false, last_id: null }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n await fetchModelsForProtocol(\"anthropic-messages\", \"https://api.anthropic.com/v1\", \"ant-test\");\n\n const [calledUrl] = fetchMock.mock.calls[0] ?? [];\n expect(calledUrl).toBe(\"https://api.anthropic.com/v1/models?limit=100\");\n });\n\n it(\"normalizes an Anthropic messages endpoint before listing models\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonOk({ data: [], has_more: false, last_id: null }));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n await fetchModelsForProtocol(\n \"anthropic-messages\",\n \"https://api.anthropic.com/v1/messages\",\n \"ant-test\",\n );\n\n const [calledUrl] = fetchMock.mock.calls[0] ?? [];\n expect(calledUrl).toBe(\"https://api.anthropic.com/v1/models?limit=100\");\n });\n\n it(\"merges models from multiple pages using after_id cursor\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n\n // First page\n fetchMock.mockResolvedValueOnce(\n jsonOk({\n data: [\n {\n id: \"claude-opus-4-20250514\",\n type: \"model\",\n display_name: \"Claude Opus 4\",\n created_at: \"2025-05-14T00:00:00Z\",\n },\n ],\n has_more: true,\n last_id: \"claude-opus-4-20250514\",\n }),\n );\n\n // Second page\n fetchMock.mockResolvedValueOnce(\n jsonOk({\n data: [\n {\n id: \"claude-sonnet-4-20250514\",\n type: \"model\",\n display_name: \"Claude Sonnet 4\",\n created_at: \"2025-05-14T00:00:00Z\",\n },\n ],\n has_more: false,\n last_id: null,\n }),\n );\n\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"anthropic-messages\",\n \"https://api.anthropic.com\",\n \"ant-key\",\n );\n\n expect(result.error).toBeUndefined();\n expect(result.models.map((m) => m.id)).toEqual([\n \"claude-opus-4-20250514\",\n \"claude-sonnet-4-20250514\",\n ]);\n expect(fetchMock).toHaveBeenCalledTimes(2);\n\n // Second call must include after_id\n const [secondUrl] = fetchMock.mock.calls[1] ?? [];\n expect(secondUrl).toContain(\"after_id=claude-opus-4-20250514\");\n });\n\n it(\"returns error result on 4xx without throwing\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockResolvedValueOnce(jsonError(403, \"Forbidden\"));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"anthropic-messages\",\n \"https://api.anthropic.com\",\n \"bad-key\",\n );\n\n expect(result.models).toEqual([]);\n expect(result.error).toMatch(/403/);\n });\n\n it(\"returns error result when fetch throws during pagination\", async () => {\n const fetchMock: FetchMock = vi.fn<(...args: Parameters<typeof fetch>) => Promise<Response>>();\n fetchMock.mockRejectedValueOnce(new TypeError(\"network failure\"));\n vi.stubGlobal(\"fetch\", fetchMock);\n\n const result = await fetchModelsForProtocol(\n \"anthropic-messages\",\n \"https://api.anthropic.com\",\n \"ant-key\",\n );\n\n expect(result.models).toEqual([]);\n expect(result.error).toBe(\"network failure\");\n });\n});\n"}}}} |