1 line
17 KiB
JSON
1 line
17 KiB
JSON
{"repo": "chencc7002/tftclarity", "files": {"test/structured-parser-provider.test.js": {"8117e4bbcc8bf7214ed4aeb735be33e8021cfb83": {"t": 1785768980.1675582, "n": 7986, "h": "4d8d4d6d3dd84313", "c": "import test from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n MemoryCacheStore,\n createCatalog,\n createChatStructuredParser,\n recommendForInput,\n resolveStructuredParserConfig\n} from \"../src/index.js\";\nimport {\n createSmallWindowRuntimeAsync,\n handleRecommendRequest,\n resolveSmallWindowStructuredParserConfig\n} from \"../src/app/small-window-server.js\";\nimport { loadLocalEnvironment } from \"../src/config/load-env.js\";\n\nconst fixtureRows = [\n {\n unit_builds: \"TFT17_Xayah&TFT_Item_GuinsoosRageblade|TFT_Item_InfinityEdge|TFT_Item_GiantSlayer\",\n placement_count: [120, 100, 90, 80, 60, 40, 30, 20]\n },\n {\n unit_builds: \"TFT17_Xayah&TFT_Item_GuinsoosRageblade|TFT_Item_LastWhisper|TFT_Item_Deathblade\",\n placement_count: [60, 55, 50, 50, 40, 30, 20, 10]\n }\n];\n\nfunction fakeChatResponse(content) {\n return {\n ok: true,\n status: 200,\n async json() {\n return {\n choices: [\n {\n message: {\n content\n }\n }\n ]\n };\n }\n };\n}\n\ntest(\"chat structured parser posts the prompt contract and parses JSON responses\", async () => {\n const calls = [];\n const parser = createChatStructuredParser({\n endpoint: \"https://llm.local/v1/chat/completions\",\n model: \"test-model\",\n apiKey: \"secret\",\n promptText: \"Return strict JSON.\",\n fetchImpl: async (url, init) => {\n calls.push({ url, init });\n return fakeChatResponse(\"```json\\n{\\\"entities\\\":{\\\"unit_mentions\\\":[\\\"霞\\\"]},\\\"needs_clarification\\\":false}\\n```\");\n }\n });\n\n const output = await parser({\n input: \"羽毛女怎么带\",\n parsed: {\n intent: \"unit_best_3_items\",\n parser: {\n entityMatches: []\n }\n },\n catalogSummary: {\n units: 1,\n items: 13,\n traits: 1\n }\n });\n\n assert.deepEqual(output.entities.unit_mentions, [\"霞\"]);\n assert.equal(calls.length, 1);\n assert.equal(calls[0].url, \"https://llm.local/v1/chat/completions\");\n assert.equal(calls[0].init.headers.authorization, \"Bearer secret\");\n\n const body = JSON.parse(calls[0].init.body);\n assert.equal(body.model, \"test-model\");\n assert.equal(body.response_format.type, \"json_object\");\n assert.equal(body.messages[0].role, \"system\");\n assert.match(body.messages[1].content, /羽毛女怎么带/);\n assert.match(body.messages[1].content, /already_parsed/);\n});\n\ntest(\"structured parser config is disabled by default and validates enabled provider settings\", () => {\n assert.deepEqual(resolveStructuredParserConfig({}, {}), {\n enabled: false,\n provider: \"off\",\n mode: \"auto\"\n });\n\n assert.throws(() => resolveStructuredParserConfig({\n provider: \"chat\"\n }, {}), /missing: TFT_AGENT_LLM_ENDPOINT, TFT_AGENT_LLM_MODEL/);\n\n assert.deepEqual(resolveSmallWindowStructuredParserConfig({}, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/v1/chat/completions\",\n TFT_AGENT_LLM_MODEL: \"test-model\",\n TFT_AGENT_LLM_MODE: \"always\",\n TFT_AGENT_LLM_TIMEOUT_MS: \"2500\"\n }), {\n enabled: true,\n provider: \"chat\",\n mode: \"always\",\n endpoint: \"https://llm.local/v1/chat/completions\",\n model: \"test-model\",\n apiKey: undefined,\n timeoutMs: 2500,\n temperature: 0,\n maxTokens: 500,\n includeResponseFormat: true\n });\n});\n\ntest(\"OpenAI-compatible environment aliases infer chat configuration and normalize the endpoint\", () => {\n assert.deepEqual(resolveStructuredParserConfig({}, {\n OPENAI_API_KEY: \"secret\",\n OPENAI_BASE_URL: \"https://aihubmix.com/v1/\",\n MODEL_NAME: \"test-model\"\n }), {\n enabled: true,\n provider: \"chat\",\n mode: \"auto\",\n endpoint: \"https://aihubmix.com/v1/chat/completions\",\n model: \"test-model\",\n apiKey: \"secret\",\n timeoutMs: 1500,\n temperature: 0,\n maxTokens: 500,\n includeResponseFormat: true\n });\n\n const explicit = resolveStructuredParserConfig({}, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/custom-endpoint\",\n TFT_AGENT_LLM_MODEL: \"preferred-model\",\n OPENAI_BASE_URL: \"https://aihubmix.com/v1\",\n MODEL_NAME: \"fallback-model\"\n });\n assert.equal(explicit.endpoint, \"https://llm.local/custom-endpoint\");\n assert.equal(explicit.model, \"preferred-model\");\n});\n\ntest(\"small-window TurnDelta provider disables model thinking so structured content is not empty\", async () => {\n let observedBody;\n const rawDelta = {\n schemaVersion: \"turn-delta.v1\",\n dialogueAct: \"modify\",\n taskRelation: \"modify\",\n explicitTaskFrame: null,\n entityOperations: [],\n constraintOperations: [{\n operation: \"set\",\n field: \"rank\",\n value: \"MASTER_PLUS\"\n }],\n presentation: {\n requestedCount: null,\n pageDirection: null,\n avoidSeen: false\n },\n confidence: 0.92,\n ambiguities: []\n };\n const runtime = await createSmallWindowRuntimeAsync({\n cacheStore: new MemoryCacheStore(),\n catalog: createCatalog(),\n fetchItems: false,\n metaTFTClient: {},\n compsClient: {},\n llmFetch: async (_url, init) => {\n observedBody = JSON.parse(init.body);\n return fakeChatResponse(JSON.stringify(rawDelta));\n }\n }, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/v1/chat/completions\",\n TFT_AGENT_LLM_MODEL: \"test-model\"\n });\n\n await runtime.turnDeltaProvider({\n schemaVersion: \"turn-delta.v1\",\n messages: [{ role: \"user\", content: \"follow-up\" }],\n budget: { maxOutputTokens: 500 }\n });\n\n assert.deepEqual(observedBody.thinking, { type: \"disabled\" });\n});\n\ntest(\"local env loading reads a selected file without overriding existing process values\", () => {\n const tempDir = mkdtempSync(join(tmpdir(), \"tft-agent-env-\"));\n const envPath = join(tempDir, \".env\");\n const target = { OPENAI_API_KEY: \"existing-secret\" };\n try {\n writeFileSync(envPath, [\n \"OPENAI_API_KEY=file-secret\",\n \"OPENAI_BASE_URL=https://aihubmix.com/v1\",\n \"MODEL_NAME=test-model\"\n ].join(\"\\n\"), \"utf8\");\n\n const result = loadLocalEnvironment({ path: envPath, processEnv: target });\n assert.equal(result.loaded, true);\n assert.equal(result.path, envPath);\n assert.equal(target.OPENAI_API_KEY, \"existing-secret\");\n assert.equal(target.OPENAI_BASE_URL, \"https://aihubmix.com/v1\");\n assert.equal(target.MODEL_NAME, \"test-model\");\n assert.equal(result.keys.includes(\"OPENAI_API_KEY\"), true);\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n});\n\ntest(\"small-window runtime wires configured structured parser into recommendations\", async () => {\n let calls = 0;\n const runtime = await createSmallWindowRuntimeAsync({\n cacheStore: new MemoryCacheStore(),\n catalog: createCatalog(),\n fetchItems: false,\n metaTFTClient: {},\n compsClient: {},\n llmFetch: async () => {\n calls += 1;\n return fakeChatResponse(JSON.stringify({\n intent: \"unit_best_3_items\",\n entities: {\n unit_mentions: [\"霞\"],\n item_mentions: [\"羊刀\"]\n },\n constraints: {\n item_count: 3,\n item_policy: \"ordinary_only\"\n },\n needs_clarification: false,\n clarification_question: null\n }));\n },\n recommendForInputImpl: (input, options) => recommendForInput(input, {\n ...options,\n response: fixtureRows\n })\n }, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/v1/chat/completions\",\n TFT_AGENT_LLM_MODEL: \"test-model\"\n });\n\n const { statusCode, payload } = await handleRecommendRequest({\n input: \"羽毛女有鬼索刀怎么带\"\n }, runtime);\n\n assert.equal(statusCode, 200);\n assert.equal(payload.ok, true);\n assert.equal(calls, 1);\n assert.equal(payload.query.unit, \"TFT17_Xayah\");\n assert.deepEqual(payload.lockedItems.map((item) => item.apiName), [\"TFT_Item_GuinsoosRageblade\"]);\n assert.equal(payload.cards.length, 2);\n});\n"}, "c1c80c06b5cef19d7156d62b70302e29832ee863": {"t": 1785846989.8965325, "n": 7986, "h": "4d8d4d6d3dd84313", "c": "import test from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { mkdtempSync, rmSync, writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n MemoryCacheStore,\n createCatalog,\n createChatStructuredParser,\n recommendForInput,\n resolveStructuredParserConfig\n} from \"../src/index.js\";\nimport {\n createSmallWindowRuntimeAsync,\n handleRecommendRequest,\n resolveSmallWindowStructuredParserConfig\n} from \"../src/app/small-window-server.js\";\nimport { loadLocalEnvironment } from \"../src/config/load-env.js\";\n\nconst fixtureRows = [\n {\n unit_builds: \"TFT17_Xayah&TFT_Item_GuinsoosRageblade|TFT_Item_InfinityEdge|TFT_Item_GiantSlayer\",\n placement_count: [120, 100, 90, 80, 60, 40, 30, 20]\n },\n {\n unit_builds: \"TFT17_Xayah&TFT_Item_GuinsoosRageblade|TFT_Item_LastWhisper|TFT_Item_Deathblade\",\n placement_count: [60, 55, 50, 50, 40, 30, 20, 10]\n }\n];\n\nfunction fakeChatResponse(content) {\n return {\n ok: true,\n status: 200,\n async json() {\n return {\n choices: [\n {\n message: {\n content\n }\n }\n ]\n };\n }\n };\n}\n\ntest(\"chat structured parser posts the prompt contract and parses JSON responses\", async () => {\n const calls = [];\n const parser = createChatStructuredParser({\n endpoint: \"https://llm.local/v1/chat/completions\",\n model: \"test-model\",\n apiKey: \"secret\",\n promptText: \"Return strict JSON.\",\n fetchImpl: async (url, init) => {\n calls.push({ url, init });\n return fakeChatResponse(\"```json\\n{\\\"entities\\\":{\\\"unit_mentions\\\":[\\\"霞\\\"]},\\\"needs_clarification\\\":false}\\n```\");\n }\n });\n\n const output = await parser({\n input: \"羽毛女怎么带\",\n parsed: {\n intent: \"unit_best_3_items\",\n parser: {\n entityMatches: []\n }\n },\n catalogSummary: {\n units: 1,\n items: 13,\n traits: 1\n }\n });\n\n assert.deepEqual(output.entities.unit_mentions, [\"霞\"]);\n assert.equal(calls.length, 1);\n assert.equal(calls[0].url, \"https://llm.local/v1/chat/completions\");\n assert.equal(calls[0].init.headers.authorization, \"Bearer secret\");\n\n const body = JSON.parse(calls[0].init.body);\n assert.equal(body.model, \"test-model\");\n assert.equal(body.response_format.type, \"json_object\");\n assert.equal(body.messages[0].role, \"system\");\n assert.match(body.messages[1].content, /羽毛女怎么带/);\n assert.match(body.messages[1].content, /already_parsed/);\n});\n\ntest(\"structured parser config is disabled by default and validates enabled provider settings\", () => {\n assert.deepEqual(resolveStructuredParserConfig({}, {}), {\n enabled: false,\n provider: \"off\",\n mode: \"auto\"\n });\n\n assert.throws(() => resolveStructuredParserConfig({\n provider: \"chat\"\n }, {}), /missing: TFT_AGENT_LLM_ENDPOINT, TFT_AGENT_LLM_MODEL/);\n\n assert.deepEqual(resolveSmallWindowStructuredParserConfig({}, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/v1/chat/completions\",\n TFT_AGENT_LLM_MODEL: \"test-model\",\n TFT_AGENT_LLM_MODE: \"always\",\n TFT_AGENT_LLM_TIMEOUT_MS: \"2500\"\n }), {\n enabled: true,\n provider: \"chat\",\n mode: \"always\",\n endpoint: \"https://llm.local/v1/chat/completions\",\n model: \"test-model\",\n apiKey: undefined,\n timeoutMs: 2500,\n temperature: 0,\n maxTokens: 500,\n includeResponseFormat: true\n });\n});\n\ntest(\"OpenAI-compatible environment aliases infer chat configuration and normalize the endpoint\", () => {\n assert.deepEqual(resolveStructuredParserConfig({}, {\n OPENAI_API_KEY: \"secret\",\n OPENAI_BASE_URL: \"https://aihubmix.com/v1/\",\n MODEL_NAME: \"test-model\"\n }), {\n enabled: true,\n provider: \"chat\",\n mode: \"auto\",\n endpoint: \"https://aihubmix.com/v1/chat/completions\",\n model: \"test-model\",\n apiKey: \"secret\",\n timeoutMs: 1500,\n temperature: 0,\n maxTokens: 500,\n includeResponseFormat: true\n });\n\n const explicit = resolveStructuredParserConfig({}, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/custom-endpoint\",\n TFT_AGENT_LLM_MODEL: \"preferred-model\",\n OPENAI_BASE_URL: \"https://aihubmix.com/v1\",\n MODEL_NAME: \"fallback-model\"\n });\n assert.equal(explicit.endpoint, \"https://llm.local/custom-endpoint\");\n assert.equal(explicit.model, \"preferred-model\");\n});\n\ntest(\"small-window TurnDelta provider disables model thinking so structured content is not empty\", async () => {\n let observedBody;\n const rawDelta = {\n schemaVersion: \"turn-delta.v1\",\n dialogueAct: \"modify\",\n taskRelation: \"modify\",\n explicitTaskFrame: null,\n entityOperations: [],\n constraintOperations: [{\n operation: \"set\",\n field: \"rank\",\n value: \"MASTER_PLUS\"\n }],\n presentation: {\n requestedCount: null,\n pageDirection: null,\n avoidSeen: false\n },\n confidence: 0.92,\n ambiguities: []\n };\n const runtime = await createSmallWindowRuntimeAsync({\n cacheStore: new MemoryCacheStore(),\n catalog: createCatalog(),\n fetchItems: false,\n metaTFTClient: {},\n compsClient: {},\n llmFetch: async (_url, init) => {\n observedBody = JSON.parse(init.body);\n return fakeChatResponse(JSON.stringify(rawDelta));\n }\n }, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/v1/chat/completions\",\n TFT_AGENT_LLM_MODEL: \"test-model\"\n });\n\n await runtime.turnDeltaProvider({\n schemaVersion: \"turn-delta.v1\",\n messages: [{ role: \"user\", content: \"follow-up\" }],\n budget: { maxOutputTokens: 500 }\n });\n\n assert.deepEqual(observedBody.thinking, { type: \"disabled\" });\n});\n\ntest(\"local env loading reads a selected file without overriding existing process values\", () => {\n const tempDir = mkdtempSync(join(tmpdir(), \"tft-agent-env-\"));\n const envPath = join(tempDir, \".env\");\n const target = { OPENAI_API_KEY: \"existing-secret\" };\n try {\n writeFileSync(envPath, [\n \"OPENAI_API_KEY=file-secret\",\n \"OPENAI_BASE_URL=https://aihubmix.com/v1\",\n \"MODEL_NAME=test-model\"\n ].join(\"\\n\"), \"utf8\");\n\n const result = loadLocalEnvironment({ path: envPath, processEnv: target });\n assert.equal(result.loaded, true);\n assert.equal(result.path, envPath);\n assert.equal(target.OPENAI_API_KEY, \"existing-secret\");\n assert.equal(target.OPENAI_BASE_URL, \"https://aihubmix.com/v1\");\n assert.equal(target.MODEL_NAME, \"test-model\");\n assert.equal(result.keys.includes(\"OPENAI_API_KEY\"), true);\n } finally {\n rmSync(tempDir, { recursive: true, force: true });\n }\n});\n\ntest(\"small-window runtime wires configured structured parser into recommendations\", async () => {\n let calls = 0;\n const runtime = await createSmallWindowRuntimeAsync({\n cacheStore: new MemoryCacheStore(),\n catalog: createCatalog(),\n fetchItems: false,\n metaTFTClient: {},\n compsClient: {},\n llmFetch: async () => {\n calls += 1;\n return fakeChatResponse(JSON.stringify({\n intent: \"unit_best_3_items\",\n entities: {\n unit_mentions: [\"霞\"],\n item_mentions: [\"羊刀\"]\n },\n constraints: {\n item_count: 3,\n item_policy: \"ordinary_only\"\n },\n needs_clarification: false,\n clarification_question: null\n }));\n },\n recommendForInputImpl: (input, options) => recommendForInput(input, {\n ...options,\n response: fixtureRows\n })\n }, {\n TFT_AGENT_LLM_PROVIDER: \"chat\",\n TFT_AGENT_LLM_ENDPOINT: \"https://llm.local/v1/chat/completions\",\n TFT_AGENT_LLM_MODEL: \"test-model\"\n });\n\n const { statusCode, payload } = await handleRecommendRequest({\n input: \"羽毛女有鬼索刀怎么带\"\n }, runtime);\n\n assert.equal(statusCode, 200);\n assert.equal(payload.ok, true);\n assert.equal(calls, 1);\n assert.equal(payload.query.unit, \"TFT17_Xayah\");\n assert.deepEqual(payload.lockedItems.map((item) => item.apiName), [\"TFT_Item_GuinsoosRageblade\"]);\n assert.equal(payload.cards.length, 2);\n});\n"}}}} |