@wutiankai/npc-dialogue 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -0
- package/dist/index.cjs +883 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +323 -0
- package/dist/index.d.ts +323 -0
- package/dist/index.js +847 -0
- package/dist/index.js.map +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/providers/openai-compatible.ts","../src/providers/config.ts","../src/audit.ts","../src/dialogue/prompt/gated-secrets.ts","../src/dialogue/prompt/system-prompt.ts","../src/dialogue/prompt/user-prompt.ts","../src/dialogue/prompts.ts","../src/dialogue/parse.ts","../src/dialogue/schema.ts","../src/dialogue/gate-review.ts","../src/dialogue/engine.ts"],"sourcesContent":["import type {\r\n NarrativeRequest,\r\n ProviderRawResponse,\r\n ProviderState,\r\n ProviderStatus,\r\n} from \"./types\";\r\n\r\n// ─── Narrative Provider Interface ───────────────────────────────────\r\n// Replaces the old AIProvider. Key differences:\r\n// - Lifecycle: initialize() / dispose()\r\n// - Health: getStatus() / healthCheck()\r\n// - Single call method: unified interface for narration & dialogue\r\n// - Returns structured ProviderRawResponse, not bare string\r\n\r\nexport interface NarrativeProvider {\r\n readonly id: string;\r\n\r\n initialize(): Promise<void>;\r\n dispose(): Promise<void>;\r\n\r\n getStatus(): ProviderStatus;\r\n healthCheck(): Promise<boolean>;\r\n\r\n call(\r\n request: NarrativeRequest,\r\n systemPrompt: string,\r\n userPrompt: string\r\n ): Promise<ProviderRawResponse>;\r\n}\r\n\r\n// ─── Passthrough Provider ───────────────────────────────────────────\r\n// Replaces FallbackProvider. Critical distinction:\r\n// - id is \"passthrough\" (not \"fallback\" — no implication it's substituting for AI)\r\n// - Always returns source-appropriate data\r\n// - Never calls any AI service\r\n// - Used when no AI provider is configured — the engine and caller always\r\n// know this is NOT AI output\r\n\r\nexport class PassthroughProvider implements NarrativeProvider {\r\n readonly id = \"passthrough\";\r\n\r\n private status: ProviderStatus = {\r\n state: \"ready\",\r\n providerId: \"passthrough\",\r\n lastHealthCheck: 0,\r\n consecutiveFailures: 0,\r\n configRedacted: { baseUrl: \"(none)\", model: \"(none)\" },\r\n };\r\n\r\n async initialize(): Promise<void> {\r\n this.status.state = \"ready\";\r\n this.status.lastHealthCheck = Date.now();\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n this.status.state = \"disposed\";\r\n }\r\n\r\n getStatus(): ProviderStatus {\r\n return { ...this.status };\r\n }\r\n\r\n async healthCheck(): Promise<boolean> {\r\n return true; // Passthrough is always healthy — it does nothing\r\n }\r\n\r\n async call(request: NarrativeRequest, _systemPrompt: string, _userPrompt: string): Promise<ProviderRawResponse> {\r\n // For dialogue, return the topicResponse; for narration, return the commandMessage\r\n const text =\r\n request.type === \"dialogue\" && request.dialogueContext\r\n ? request.dialogueContext.topicResponse\r\n : request.commandMessage;\r\n\r\n return {\r\n text,\r\n model: \"(passthrough)\",\r\n latencyMs: 0,\r\n };\r\n }\r\n}\r\n","import type { NarrativeProvider } from \"../provider\";\r\nimport type {\r\n NarrativeRequest,\r\n ProviderRawResponse,\r\n ProviderState,\r\n ProviderStatus,\r\n} from \"../types\";\r\nimport type { ProviderConfig } from \"./config\";\r\n\r\nconst MAX_CONSECUTIVE_FAILURES = 3;\r\n\r\n/** Strip trailing slashes so concatenation with /chat/completions is always correct. */\r\nfunction normalizeBaseUrl(url: string): string {\r\n return url.replace(/\\/+$/, \"\");\r\n}\r\n\r\nexport class OpenAICompatibleProvider implements NarrativeProvider {\r\n readonly id = \"openai-compat\";\r\n\r\n private state: ProviderState = \"uninitialized\";\r\n private consecutiveFailures = 0;\r\n private lastHealthCheck = 0;\r\n private lastError?: string;\r\n\r\n constructor(private config: ProviderConfig) {}\r\n\r\n async initialize(): Promise<void> {\r\n this.state = \"initializing\";\r\n try {\r\n // Validate config has required fields\r\n if (!this.config.apiKey) {\r\n throw new Error(\"API key is required\");\r\n }\r\n if (!this.config.baseUrl) {\r\n throw new Error(\"Base URL is required\");\r\n }\r\n\r\n // Quick connectivity check — send a minimal request\r\n // We don't fail initialization on network errors; the provider starts\r\n // in \"ready\" and will degrade on first actual failure.\r\n this.state = \"ready\";\r\n this.lastHealthCheck = Date.now();\r\n } catch (err) {\r\n this.state = \"failed\";\r\n this.lastError = err instanceof Error ? err.message : String(err);\r\n throw err;\r\n }\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n this.state = \"disposed\";\r\n }\r\n\r\n getStatus(): ProviderStatus {\r\n return {\r\n state: this.state,\r\n providerId: this.id,\r\n lastHealthCheck: this.lastHealthCheck,\r\n consecutiveFailures: this.consecutiveFailures,\r\n lastError: this.lastError,\r\n configRedacted: {\r\n baseUrl: this.config.baseUrl,\r\n model: this.config.model,\r\n },\r\n };\r\n }\r\n\r\n async healthCheck(): Promise<boolean> {\r\n try {\r\n // Minimal request to verify connectivity\r\n const response = await fetch(`${normalizeBaseUrl(this.config.baseUrl)}/chat/completions`, {\r\n method: \"POST\",\r\n headers: {\r\n Authorization: `Bearer ${this.config.apiKey}`,\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({\r\n model: this.config.model,\r\n messages: [{ role: \"user\", content: \"ping\" }],\r\n max_tokens: 1,\r\n }),\r\n });\r\n\r\n this.lastHealthCheck = Date.now();\r\n\r\n if (response.ok) {\r\n this.consecutiveFailures = 0;\r\n if (this.state === \"degraded\" || this.state === \"failed\") {\r\n this.state = \"ready\";\r\n }\r\n return true;\r\n }\r\n\r\n this.recordFailure(`Health check failed: ${response.status}`);\r\n return false;\r\n } catch (err) {\r\n this.recordFailure(\r\n `Health check error: ${err instanceof Error ? err.message : String(err)}`\r\n );\r\n return false;\r\n }\r\n }\r\n\r\n async call(\r\n _request: NarrativeRequest,\r\n systemPrompt: string,\r\n userPrompt: string\r\n ): Promise<ProviderRawResponse> {\r\n if (this.state === \"disposed\") {\r\n throw new Error(\"Provider is disposed\");\r\n }\r\n\r\n // Prompt is already built by engine via prompts.ts — just forward to API\r\n const start = Date.now();\r\n\r\n // 12-second timeout to prevent indefinite hangs; the engine will\r\n // fall back to passthrough on timeout errors.\r\n const controller = new AbortController();\r\n const timeout = setTimeout(() => controller.abort(), 12_000);\r\n\r\n let response: Response;\r\n try {\r\n response = await fetch(`${normalizeBaseUrl(this.config.baseUrl)}/chat/completions`, {\r\n method: \"POST\",\r\n headers: {\r\n Authorization: `Bearer ${this.config.apiKey}`,\r\n \"Content-Type\": \"application/json\",\r\n },\r\n body: JSON.stringify({\r\n model: this.config.model,\r\n messages: [\r\n { role: \"system\", content: systemPrompt },\r\n { role: \"user\", content: userPrompt },\r\n ],\r\n temperature: this.config.temperature,\r\n max_tokens: this.config.maxTokens,\r\n }),\r\n signal: controller.signal,\r\n });\r\n } catch (err) {\r\n clearTimeout(timeout);\r\n this.recordFailure(err instanceof Error ? err.message : String(err));\r\n if ((err as Error).name === \"AbortError\") {\r\n throw new Error(\"AI request timed out after 12 seconds\");\r\n }\r\n throw err;\r\n }\r\n clearTimeout(timeout);\r\n\r\n const latencyMs = Date.now() - start;\r\n\r\n if (!response.ok) {\r\n const body = await response.text().catch(() => \"\");\r\n this.recordFailure(`API error ${response.status}: ${body.slice(0, 200)}`);\r\n throw new Error(`AI request failed: ${response.status} ${response.statusText}`);\r\n }\r\n\r\n const data = await response.json();\r\n const text = data.choices?.[0]?.message?.content?.trim() ?? \"\";\r\n\r\n if (!text) {\r\n this.recordFailure(\"Empty response from AI\");\r\n throw new Error(\"AI returned empty response\");\r\n }\r\n\r\n // Success — reset failure counter\r\n this.consecutiveFailures = 0;\r\n if (this.state === \"degraded\") {\r\n this.state = \"ready\";\r\n }\r\n this.lastHealthCheck = Date.now();\r\n\r\n return {\r\n text,\r\n model: this.config.model,\r\n latencyMs,\r\n promptTokenCount: data.usage?.prompt_tokens,\r\n completionTokenCount: data.usage?.completion_tokens,\r\n };\r\n }\r\n\r\n // ─── Internal ──────────────────────────────────────────────────────\r\n\r\n private recordFailure(error: string): void {\r\n this.consecutiveFailures++;\r\n this.lastError = error;\r\n if (this.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {\r\n this.state = \"failed\";\r\n } else {\r\n this.state = \"degraded\";\r\n }\r\n }\r\n}\r\n","export interface ProviderConfig {\r\n apiKey?: string;\r\n baseUrl: string;\r\n model: string;\r\n maxTokens: number;\r\n temperature: number;\r\n}\r\n\r\nexport function loadConfigFromEnv(): ProviderConfig {\r\n return {\r\n apiKey: process.env.OPENAI_API_KEY || process.env.AI_API_KEY || undefined,\r\n baseUrl: process.env.AI_BASE_URL || \"https://api.openai.com/v1\",\r\n model: process.env.AI_MODEL || \"gpt-4o-mini\",\r\n maxTokens: parseInt(process.env.AI_MAX_TOKENS || \"300\", 10),\r\n temperature: parseFloat(process.env.AI_TEMPERATURE || \"0.7\")\r\n };\r\n}\r\n","import type { AuditFilter, AuditRecord, AuditStats } from \"./types\";\r\n\r\n// ─── In-Memory Audit Log ────────────────────────────────────────────\r\n// Bounded ring buffer. Every NarrativeResponse produces exactly one AuditRecord.\r\n\r\nexport class AuditLog {\r\n private entries: AuditRecord[] = [];\r\n private readonly maxEntries: number;\r\n\r\n constructor(maxEntries = 1000) {\r\n this.maxEntries = maxEntries > 0 ? maxEntries : Infinity;\r\n }\r\n\r\n append(record: AuditRecord): void {\r\n this.entries.push(record);\r\n // Evict oldest entries when capacity exceeded\r\n while (this.entries.length > this.maxEntries) {\r\n this.entries.shift();\r\n }\r\n }\r\n\r\n query(filter?: AuditFilter): AuditRecord[] {\r\n if (!filter) return [...this.entries];\r\n\r\n return this.entries.filter((r) => {\r\n if (filter.source !== undefined && r.responseSource !== filter.source)\r\n return false;\r\n if (filter.requestType !== undefined && r.requestType !== filter.requestType)\r\n return false;\r\n if (filter.minTurnIndex !== undefined && r.turnIndex < filter.minTurnIndex)\r\n return false;\r\n if (filter.maxTurnIndex !== undefined && r.turnIndex > filter.maxTurnIndex)\r\n return false;\r\n if (\r\n filter.validationPassed !== undefined &&\r\n r.validationPassed !== filter.validationPassed\r\n )\r\n return false;\r\n return true;\r\n });\r\n }\r\n\r\n stats(): AuditStats {\r\n let totalRequests = 0;\r\n let aiSuccessCount = 0;\r\n let passthroughCount = 0;\r\n let aiFailureCount = 0;\r\n let validationFailureCount = 0;\r\n let constraintViolationCount = 0;\r\n let totalLatency = 0;\r\n const byProvider: Record<string, { calls: number; failures: number }> = {};\r\n\r\n for (const r of this.entries) {\r\n totalRequests++;\r\n totalLatency += r.latencyMs;\r\n\r\n if (r.responseSource === \"ai\") {\r\n aiSuccessCount++;\r\n } else {\r\n passthroughCount++;\r\n }\r\n\r\n if (!r.validationPassed) {\r\n validationFailureCount++;\r\n }\r\n\r\n if (r.constraintViolations.length > 0) {\r\n constraintViolationCount++;\r\n }\r\n\r\n // Track provider stats — count non-ai responses after an ai attempt as failures\r\n if (!byProvider[r.providerId]) {\r\n byProvider[r.providerId] = { calls: 0, failures: 0 };\r\n }\r\n byProvider[r.providerId].calls++;\r\n if (r.responseSource === \"passthrough\" && r.providerId !== \"passthrough\") {\r\n // Provider was supposed to provide AI but fell back\r\n byProvider[r.providerId].failures++;\r\n }\r\n }\r\n\r\n return {\r\n totalRequests,\r\n aiSuccessCount,\r\n passthroughCount,\r\n aiFailureCount,\r\n validationFailureCount,\r\n constraintViolationCount,\r\n averageLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,\r\n byProvider,\r\n };\r\n }\r\n\r\n clear(): void {\r\n this.entries = [];\r\n }\r\n\r\n export(): AuditRecord[] {\r\n return [...this.entries];\r\n }\r\n\r\n get length(): number {\r\n return this.entries.length;\r\n }\r\n}\r\n","import type { NpcScript } from \"../types\";\r\n\r\n// ─── Gated Secret Formatting (filtered by valid gate IDs) ────────────\r\n// Only secrets whose topicGateId is in the valid set are shown to the AI.\r\n// Inaccessible secrets are simply omitted from the prompt entirely.\r\n\r\nexport function formatGatedSecretsEn(script: NpcScript, validTopicGateIds: string[]): string {\r\n const available = script.gatedSecrets.filter(\r\n (s) => validTopicGateIds.includes(s.topicGateId)\r\n );\r\n if (available.length === 0) {\r\n return \" (none currently available)\";\r\n }\r\n return available\r\n .map(\r\n (s) => {\r\n const keywords = [\r\n ...(s.triggerKeywords ?? []),\r\n ...(s.triggerPhrases ?? []),\r\n ];\r\n const keywordLine = keywords.length > 0\r\n ? `\\n Trigger keywords: ${keywords.join(\", \")}`\r\n : \"\";\r\n return ` [${s.topicGateId}] ${s.description}\\n Reveal conditions: ${s.revealConditions}${keywordLine}\\n If pressed on this: ${s.reactionWhenPressed}`;\r\n }\r\n )\r\n .join(\"\\n\");\r\n}\r\n\r\nexport function formatGatedSecretsZh(script: NpcScript, validTopicGateIds: string[]): string {\r\n const available = script.gatedSecrets.filter(\r\n (s) => validTopicGateIds.includes(s.topicGateId)\r\n );\r\n if (available.length === 0) {\r\n return \" (当前无可触发的门控秘密)\";\r\n }\r\n return available\r\n .map(\r\n (s) => {\r\n const keywords = [\r\n ...(s.triggerKeywords ?? []),\r\n ...(s.triggerPhrases ?? []),\r\n ];\r\n const keywordLine = keywords.length > 0\r\n ? `\\n 触发关键词:${keywords.join(\", \")}`\r\n : \"\";\r\n return ` [${s.topicGateId}] ${s.description}\\n 触发条件:${s.revealConditions}${keywordLine}\\n 被追问时:${s.reactionWhenPressed}`;\r\n }\r\n )\r\n .join(\"\\n\");\r\n}\r\n","import type { NpcScript } from \"../types\";\r\nimport { formatGatedSecretsEn, formatGatedSecretsZh } from \"./gated-secrets\";\r\n\r\n// ─── System Prompt Builders ──────────────────────────────────────────\r\n// Constructs the character-defining system prompt with rules, persona,\r\n// knowledge, gated secrets, ignorance, and relationships.\r\n//\r\n// AI output is CANDIDATE signals — the DialoguePolicy layer has final\r\n// authority over gates, trust, and item effects.\r\n\r\nexport function buildSystemPromptEn(\r\n script: NpcScript,\r\n validTopicGateIds: string[],\r\n currentTrust: number\r\n): string {\r\n const p = script.persona;\r\n const privateKnowledge = currentTrust >= 1 ? script.privateKnowledge : [];\r\n\r\n const setting = script.worldSetting ?? \"a mystery investigation game\";\r\n let prompt = `You are ${script.name}, ${script.role}. You are a character in ${setting}.\r\n\r\nHARD RULES — VIOLATION OF ANY RULE WILL BREAK THE GAME:\r\n1. You are NOT a narrator, system, detective assistant, or omniscient AI.\r\n2. You can ONLY know what is explicitly written in your character script below.\r\n3. If the player asks about something outside your knowledge, you MUST respond as your character would: show ignorance, deflect, misunderstand, or guess incorrectly.\r\n4. You MUST NOT reveal the content of any GATED SECRETS listed below. Setting candidateGateId is NOT revealing — it is a review signal for the system to decide.\r\n5. Set candidateGateId as a REVIEW SIGNAL when the player's input is PLAUSIBLY RELATED to the revealConditions or triggerKeywords of a gated secret. The system reviews this signal — a false positive is harmless; a missed signal loses an opportunity.\r\n6. You MUST output ONLY valid JSON — absolutely no text before or after the JSON object.\r\n7. Your \"dialogue\" field must be in your character's own voice — first person, in-character, authentic to your personality.\r\n8. Private knowledge is only included below when your current trust allows it. If it is not listed, you do not know it for this exchange.\r\n\r\nYOUR PERSONA:\r\n- Personality: ${p.personality}\r\n- Background: ${p.background}\r\n- Speech patterns: ${p.speechPatterns}`;\r\n\r\n if (p.emotionalBaseline) {\r\n prompt += `\\n- Emotional baseline: ${p.emotionalBaseline}`;\r\n }\r\n if (p.forbiddenTone) {\r\n prompt += `\\n- Forbidden tone: ${p.forbiddenTone}`;\r\n }\r\n\r\n prompt += `\r\n\r\nWHAT YOU KNOW (public knowledge — you may share this freely):\r\n${script.publicKnowledge.map((k) => ` [${k.topic}] ${k.content}`).join(\"\\n\")}\r\n\r\nWHAT YOU KNOW (private knowledge — you know this but will NOT volunteer it unless asked directly):\r\n${formatKnowledgeList(privateKnowledge, \" (none available at current trust)\")}\r\n\r\nGATED SECRETS (keep their content hidden — but when the player's input is plausibly related, signal the system by setting candidateGateId):\r\n${formatGatedSecretsEn(script, validTopicGateIds)}\r\n\r\nTHINGS YOU DO NOT KNOW (you must not demonstrate knowledge of these):\r\n${script.ignorance.map((i) => ` - ${i}`).join(\"\\n\")}\r\n\r\nYOUR RELATIONSHIPS WITH OTHER CHARACTERS:\r\n${script.relationships.map((r) => ` - ${r.npcId}: ${r.attitude} (${r.notes})`).join(\"\\n\")}\r\n\r\nOUTPUT FORMAT — you MUST return ONLY this JSON object, nothing else:\r\n{\r\n \"dialogue\": \"Your in-character response as ${script.name}\",\r\n \"candidateGateId\": null,\r\n \"gateEvidence\": \"\",\r\n \"gateConfidence\": \"low\",\r\n \"candidateActionHint\": null\r\n}\r\n\r\nRules for the JSON fields:\r\n- dialogue: string, 1-1000 characters. Your response in character.\r\n- candidateGateId: string (a valid topicGateId) or null. A REVIEW SIGNAL — set when the player's input is plausibly related to a secret's revealConditions or triggerKeywords. Setting this does NOT reveal the secret; the system decides whether to accept it.\r\n- gateEvidence: string. What part of the player's input matched the secret's conditions, or \"\" if no gate triggered.\r\n- gateConfidence: \"low\" | \"medium\" | \"high\". How confident you are that a gate should trigger.\r\nEXAMPLES:\r\n\r\nPOSITIVE (player input plausibly relates → set candidateGateId):\r\n Player: “I found a strange letter hidden in the desk.”\r\n → candidateGateId: “topic_suspect_secret_letter” (letter mentioned; plausibly related)\r\n\r\n Player: “你看到那晚的踪迹了吗?”\r\n → candidateGateId: “topic_witness_footprints” (footprints mentioned; plausibly related)\r\n\r\nNEGATIVE (nothing relates → leave null):\r\n Player: \"How's the weather today?\"\r\n → candidateGateId: null (no secret's topic is mentioned)\r\n\r\n Player: “晚饭吃什么?”\r\n → candidateGateId: null (not related to any gate)\r\n\r\n- candidateActionHint: string or null. If your dialogue implies an action like giving an item or granting access, describe it here (e.g., \"item_given\", \"access_granted\"). The system decides whether the action actually happens.`;\r\n\r\n return prompt;\r\n}\r\n\r\nexport function buildSystemPromptZh(\r\n script: NpcScript,\r\n validTopicGateIds: string[],\r\n currentTrust: number\r\n): string {\r\n const p = script.persona;\r\n const privateKnowledge = currentTrust >= 1 ? script.privateKnowledge : [];\r\n\r\n const setting = script.worldSetting ??\r\n \"一场神秘的调查推理游戏\";\r\n let prompt = `你是${script.name},${script.role}。你是${setting}中的角色。\r\n\r\n硬性规则 — 违反任何规则将导致游戏出错:\r\n1. 你不是旁白、系统、侦探助手或全知 AI。\r\n2. 你只能知道角色剧本中明确写明的信息。\r\n3. 如果玩家问超出你知识范围的事,你必须以角色身份表示不知道、回避、误解或猜测。\r\n4. 你不得主动泄露「门控秘密」的内容。设置 candidateGateId 不是泄露——这是发给系统的审查信号,由系统决定是否触发。\r\n5. 将 candidateGateId 视为审查信号:当玩家输入与某个门控秘密的触发条件或触发关键词合理相关时,设置该信号。系统审查后才决定是否触发——误报无害,漏报错失机会。\r\n6. 你必须只输出合法的 JSON,不能在 JSON 前后输出任何文本。\r\n7. dialogue 必须是你本人的口吻,第一人称,符合你的性格。\r\n8. 私密知识只会在当前信任允许时列在下方。如果没有列出,本轮对话你不能使用这些信息。\r\n\r\n你的性格:\r\n- 性格特点:${p.personality}\r\n- 背景故事:${p.background}\r\n- 说话方式:${p.speechPatterns}`;\r\n\r\n if (p.emotionalBaseline) {\r\n prompt += `\\n- 情绪基调:${p.emotionalBaseline}`;\r\n }\r\n if (p.forbiddenTone) {\r\n prompt += `\\n- 禁止语调:${p.forbiddenTone}`;\r\n }\r\n\r\n prompt += `\r\n\r\n你知道的事情(公开知识 — 可以自由分享):\r\n${script.publicKnowledge.map((k) => ` [${k.topic}] ${k.content}`).join(\"\\n\")}\r\n\r\n你知道但不主动提起的事情(私密知识 — 除非被直接问起否则不会主动说):\r\n${formatKnowledgeList(privateKnowledge, \" (当前信任下无可用私密知识)\")}\r\n\r\n门控秘密(内容不得透露——但当玩家输入合理相关时,请设置 candidateGateId 通知系统审查):\r\n${formatGatedSecretsZh(script, validTopicGateIds)}\r\n\r\n你不知道的事情(不能表现出了解):\r\n${script.ignorance.map((i) => ` - ${i}`).join(\"\\n\")}\r\n\r\n你与其他角色的关系:\r\n${script.relationships.map((r) => ` - ${r.npcId}:${r.attitude}(${r.notes})`).join(\"\\n\")}\r\n\r\n输出格式 — 你必须只返回以下 JSON 对象,不得有其他文本:\r\n{\r\n \"dialogue\": \"你作为${script.name}的角色化回答\",\r\n \"candidateGateId\": null,\r\n \"gateEvidence\": \"\",\r\n \"gateConfidence\": \"low\",\r\n \"candidateActionHint\": null\r\n}\r\n\r\nJSON 字段规则:\r\n- dialogue:字符串,1-1000 字。你的角色化回答。\r\n- candidateGateId:字符串(合法的 topicGateId)或 null。审查信号——当玩家输入与秘密的触发条件或触发关键词合理相关时设置。设置此项不等于泄露秘密;系统决定是否采纳。\r\n- gateEvidence:字符串。玩家输入中匹配触发条件的部分,未触发则为 \"\"。\r\n- gateConfidence:\"low\" | \"medium\" | \"high\"。你对触发门控的信心。\r\n示例:\r\n\r\n正例(玩家输入合理相关 → 设置 candidateGateId):\r\n 玩家:\"我在书桌里发现了一封奇怪的信。\"\r\n → candidateGateId: \"topic_suspect_secret_letter\"(提到了信件,合理相关)\r\n\r\n 玩家:\"Did you see the footprints that night?\"\r\n → candidateGateId: \"topic_witness_footprints\"(提到了足迹,合理相关)\r\n\r\n负例(完全不相关 → 保持 null):\r\n 玩家:\"今天天气怎么样?\"\r\n → candidateGateId: null(与任何秘密无关)\r\n\r\n 玩家:\"What's for dinner tonight?\"\r\n → candidateGateId: null(与任何秘密无关)\r\n\r\n- candidateActionHint:字符串或 null。如果你的对话暗示了某个动作(如给予物品、授权进入),在此描述(如 \"item_given\"、\"access_granted\")。系统决定该动作是否实际发生。`;\r\n\r\n return prompt;\r\n}\r\n\r\nfunction formatKnowledgeList(\r\n entries: Array<{ topic: string; content: string }>,\r\n emptyText: string\r\n): string {\r\n if (entries.length === 0) return emptyText;\r\n return entries.map((k) => ` [${k.topic}] ${k.content}`).join(\"\\n\");\r\n}\r\n","import type { NpcScript, DialogueContext, ConversationExchange } from \"../types\";\r\n\r\n// ─── User Prompt Builder ─────────────────────────────────────────────\r\n// Constructs the situational user prompt with current game state,\r\n// conversation history, and the player's input.\r\n\r\nexport function buildUserPrompt(\r\n script: NpcScript,\r\n playerInput: string,\r\n context: DialogueContext,\r\n recentExchanges: ConversationExchange[],\r\n lang: \"en\" | \"zh\"\r\n): string {\r\n const isZh = lang === \"zh\";\r\n const npcName = script.name;\r\n\r\n const langInstruction = isZh\r\n ? \"你必须用中文回复。\"\r\n : \"You must respond in English.\";\r\n\r\n let prompt = `${langInstruction}\r\n\r\nCURRENT SITUATION:\r\n- Room: ${context.currentRoom}\r\n- Investigation turns remaining: ${context.turnsRemaining}\r\n- Player's inventory: ${context.playerInventory.length > 0 ? context.playerInventory.join(\", \") : \"(empty)\"}\r\n- Discovered clues: ${context.discoveredClues.length > 0 ? context.discoveredClues.join(\", \") : \"(none)\"}\r\n- Your trust toward the player: ${context.currentTrust}/2\r\n- Topics already discussed: ${context.exhaustedTopicGateIds.length > 0 ? context.exhaustedTopicGateIds.join(\", \") : \"(none)\"}\r\n- Available topic gates that could be triggered: ${context.validTopicGateIds.length > 0 ? context.validTopicGateIds.join(\", \") : \"(none)\"}`;\r\n\r\n if (recentExchanges.length > 0) {\r\n prompt += `\\n\\nRECENT CONVERSATION (last ${recentExchanges.length} exchanges):`;\r\n for (const ex of recentExchanges) {\r\n prompt += `\\nPlayer: \"${ex.playerInput}\"\\n${npcName}: \"${ex.npcResponse}\"`;\r\n if (ex.triggeredTopicGateId) {\r\n prompt += ` [triggered: ${ex.triggeredTopicGateId}]`;\r\n }\r\n }\r\n }\r\n\r\n prompt += `\\n\\nPLAYER SAYS:\\n\"${playerInput}\"\\n\\nRespond as ${npcName} in valid JSON.`;\r\n\r\n return prompt;\r\n}\r\n","import type { NpcScript, DialogueContext, ConversationExchange } from \"./types\";\r\nimport { buildSystemPromptEn, buildSystemPromptZh } from \"./prompt/system-prompt\";\r\nimport { buildUserPrompt } from \"./prompt/user-prompt\";\r\n\r\n// ─── NPC Dialogue Prompt Builder (Facade) ────────────────────────────\r\n// Delegates to prompt/ sub-modules for system prompt, user prompt,\r\n// and gated secret formatting. External callers only need this file.\r\n\r\nexport interface DialoguePromptPair {\r\n system: string;\r\n user: string;\r\n}\r\n\r\nexport function buildNpcDialoguePrompt(\r\n npcScript: NpcScript,\r\n playerInput: string,\r\n context: DialogueContext,\r\n recentExchanges: ConversationExchange[],\r\n lang: \"en\" | \"zh\"\r\n): DialoguePromptPair {\r\n const isZh = lang === \"zh\";\r\n const validGateIds = context.validTopicGateIds;\r\n\r\n const system = isZh\r\n ? buildSystemPromptZh(npcScript, validGateIds, context.currentTrust)\r\n : buildSystemPromptEn(npcScript, validGateIds, context.currentTrust);\r\n\r\n const user = buildUserPrompt(npcScript, playerInput, context, recentExchanges, lang);\r\n\r\n return { system, user };\r\n}\r\n","import type { DialogueAiResponse } from \"./types\";\r\n\r\n// ─── AI Response Parsing ─────────────────────────────────────────────\r\n// Handles extraction of structured data from raw AI text output.\r\n// Supports: complete JSON, markdown-wrapped JSON, truncated JSON.\r\n//\r\n// Accepts both OLD field names (triggeredTopicGateId,\r\n// confidence, matchedEvidence) and NEW field names (candidateGateId,\r\n// gateConfidence, gateEvidence, candidateActionHint).\r\n\r\n/**\r\n * Parse AI response text as JSON. Handles:\r\n * - markdown-wrapped JSON\r\n * - truncated JSON (model hit max_tokens mid-response)\r\n * - raw JSON without wrapper\r\n * Accepts both old and new field name formats.\r\n */\r\nexport function parseAiJson(rawText: string): DialogueAiResponse {\r\n let jsonStr = rawText.trim();\r\n\r\n // Remove markdown code block wrapper if present\r\n const codeBlockMatch = jsonStr.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\r\n if (codeBlockMatch) {\r\n jsonStr = codeBlockMatch[1].trim();\r\n }\r\n\r\n // Try to find a complete JSON object first\r\n const jsonMatch = jsonStr.match(/\\{[\\s\\S]*\\}/);\r\n if (jsonMatch) {\r\n try {\r\n const parsed = JSON.parse(jsonMatch[0]);\r\n return buildResponse(parsed);\r\n } catch {\r\n // Complete JSON not parseable — fall through to truncated handling\r\n }\r\n }\r\n\r\n // Handle truncated JSON: model hit max_tokens mid-response.\r\n // Try both old and new field names.\r\n const dialogueMatch = jsonStr.match(/\"dialogue\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n const dialogue = dialogueMatch ? unescapeJsonString(dialogueMatch[1]) : \"\";\r\n\r\n // Try new field name first, then old\r\n let candidateGateId: string | null = null;\r\n const newGateMatch = jsonStr.match(/\"candidateGateId\"\\s*:\\s*\"([^\"]+)\"/);\r\n const oldGateMatch = jsonStr.match(/\"triggeredTopicGateId\"\\s*:\\s*\"([^\"]+)\"/);\r\n if (newGateMatch) {\r\n candidateGateId = newGateMatch[1];\r\n } else if (oldGateMatch) {\r\n candidateGateId = oldGateMatch[1];\r\n }\r\n\r\n // Try confidence / gateConfidence\r\n let gateConfidence: \"low\" | \"medium\" | \"high\" = \"low\";\r\n if (jsonStr.includes('\"gateConfidence\"') || jsonStr.includes('\"confidence\"')) {\r\n if (jsonStr.includes('\"high\"')) gateConfidence = \"high\";\r\n else if (jsonStr.includes('\"medium\"')) gateConfidence = \"medium\";\r\n }\r\n\r\n // Try gateEvidence / matchedEvidence\r\n let gateEvidence = \"\";\r\n const newEvidenceMatch = jsonStr.match(/\"gateEvidence\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n const oldEvidenceMatch = jsonStr.match(/\"matchedEvidence\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/);\r\n if (newEvidenceMatch) {\r\n gateEvidence = unescapeJsonString(newEvidenceMatch[1]);\r\n } else if (oldEvidenceMatch) {\r\n gateEvidence = unescapeJsonString(oldEvidenceMatch[1]);\r\n }\r\n\r\n if (!dialogue && !candidateGateId) {\r\n throw new Error(\"No JSON object found in AI response\");\r\n }\r\n\r\n return {\r\n dialogue,\r\n candidateGateId,\r\n gateEvidence,\r\n gateConfidence,\r\n candidateActionHint: null,\r\n };\r\n}\r\n\r\n// ─── Internal Helpers ────────────────────────────────────────────────\r\n\r\n/**\r\n * Unescape JSON string values (handles \\n, \\\", \\\\, etc.)\r\n */\r\nfunction unescapeJsonString(s: string): string {\r\n return s\r\n .replace(/\\\\n/g, \"\\n\")\r\n .replace(/\\\\t/g, \"\\t\")\r\n .replace(/\\\\\"/g, '\"')\r\n .replace(/\\\\\\\\/g, \"\\\\\");\r\n}\r\n\r\n/**\r\n * Build a DialogueAiResponse from a parsed JSON object.\r\n * Accepts both old and new field names, mapping old→new.\r\n */\r\nfunction buildResponse(parsed: any): DialogueAiResponse {\r\n const dialogue =\r\n typeof parsed.dialogue === \"string\" ? parsed.dialogue : String(parsed.dialogue ?? \"\");\r\n\r\n // candidateGateId (new) or triggeredTopicGateId (old)\r\n const candidateGateId =\r\n typeof parsed.candidateGateId === \"string\"\r\n ? parsed.candidateGateId\r\n : typeof parsed.triggeredTopicGateId === \"string\"\r\n ? parsed.triggeredTopicGateId\r\n : null;\r\n\r\n // gateEvidence (new) or matchedEvidence (old)\r\n const gateEvidence =\r\n typeof parsed.gateEvidence === \"string\"\r\n ? parsed.gateEvidence\r\n : typeof parsed.matchedEvidence === \"string\"\r\n ? parsed.matchedEvidence\r\n : \"\";\r\n\r\n // gateConfidence (new) or confidence (old)\r\n const rawConfidence = parsed.gateConfidence ?? parsed.confidence;\r\n const gateConfidence: \"low\" | \"medium\" | \"high\" =\r\n rawConfidence === \"low\" || rawConfidence === \"medium\" || rawConfidence === \"high\"\r\n ? rawConfidence\r\n : \"low\";\r\n\r\n // candidateActionHint (new field)\r\n const candidateActionHint =\r\n typeof parsed.candidateActionHint === \"string\" ? parsed.candidateActionHint : null;\r\n\r\n return {\r\n dialogue,\r\n candidateGateId,\r\n gateEvidence,\r\n gateConfidence,\r\n candidateActionHint,\r\n };\r\n}\r\n","import type { DialogueAiResponse } from \"./types\";\r\n\r\n// ─── Structural Validation ───────────────────────────────────────────\r\n// Normalizes the shape of an AI dialogue response. Runtime/policy layers\r\n// decide whether candidate gates are legal in the current game state.\r\n\r\nexport function validateDialogueResponse(parsed: DialogueAiResponse): DialogueAiResponse {\r\n const result: DialogueAiResponse = { ...parsed };\r\n\r\n if (typeof result.dialogue !== \"string\" || result.dialogue.length < 1) {\r\n result.dialogue = \"...\";\r\n }\r\n if (result.dialogue.length > 1000) {\r\n result.dialogue = result.dialogue.slice(0, 1000);\r\n }\r\n\r\n if (typeof result.candidateGateId !== \"string\") {\r\n result.candidateGateId = null;\r\n }\r\n if (typeof result.gateEvidence !== \"string\") {\r\n result.gateEvidence = \"\";\r\n }\r\n if (\r\n result.gateConfidence !== \"low\" &&\r\n result.gateConfidence !== \"medium\" &&\r\n result.gateConfidence !== \"high\"\r\n ) {\r\n result.gateConfidence = \"low\";\r\n }\r\n if (typeof result.candidateActionHint !== \"string\") {\r\n result.candidateActionHint = null;\r\n }\r\n\r\n return result;\r\n}\r\n","import type { DialogueAiResponse, DialogueContext, NpcScript } from \"./types\";\r\n\r\n// ─── Gate Trigger Reviewer ───────────────────────────────────────────\r\n// Lightweight reviewer that prevents false gate triggers.\r\n// This is a CANDIDATE gate filter — it does not execute game state changes.\r\n// The runtime (game-runtime executeTalk) has final authority.\r\n//\r\n// NOTE: Short/greeting input filtering was moved to dialogue-intent.ts\r\n// in the web-services layer. This module only checks keyword relevance\r\n// and AI evidence quality.\r\n\r\n// ─── Bilingual Keyword Mapping ───────────────────────────────────────\r\n// Maps English keywords from secret definitions to common Chinese\r\n// equivalents so that keyword relevance checks work across languages.\r\n\r\n/**\r\n * Expand explicit English keywords with Chinese equivalents for cross-language matching.\r\n *\r\n * @param englishKeywords - Keywords from secret definitions (typically English)\r\n * @param bilingualMap - Optional map of English keyword → Chinese equivalents.\r\n * Provide a game-specific map for bilingual gate matching.\r\n */\r\nexport function expandKeywordsBilingually(\r\n englishKeywords: string[],\r\n bilingualMap?: Record<string, string[]>\r\n): string[] {\r\n if (!bilingualMap || Object.keys(bilingualMap).length === 0) {\r\n return [...englishKeywords];\r\n }\r\n\r\n const expanded: string[] = [...englishKeywords];\r\n for (const kw of englishKeywords) {\r\n const lower = kw.toLowerCase();\r\n if (bilingualMap[lower]) {\r\n expanded.push(...bilingualMap[lower]);\r\n }\r\n // Also check partial matches (e.g. \"tower\" in \"bell tower\")\r\n for (const [engKey, zhValues] of Object.entries(bilingualMap)) {\r\n if (lower.includes(engKey) || engKey.includes(lower)) {\r\n expanded.push(...zhValues);\r\n }\r\n }\r\n }\r\n return expanded;\r\n}\r\n\r\n// ─── Review Function ─────────────────────────────────────────────────\r\n\r\n/**\r\n * Review a validated AI response for false gate triggers.\r\n *\r\n * Rules (greeting/short-input filtering is handled by dialogue-intent.ts):\r\n * 1. If explicit trigger keywords/phrases exist, player input must match one\r\n * 2. AI must provide gateEvidence or gateConfidence ≥ medium\r\n */\r\nexport function reviewGateTrigger(\r\n validated: DialogueAiResponse,\r\n playerInput: string,\r\n npcScript: NpcScript,\r\n _context: DialogueContext,\r\n options?: { bilingualKeywordMap?: Record<string, string[]> }\r\n): DialogueAiResponse {\r\n if (validated.candidateGateId === null) {\r\n return validated;\r\n }\r\n\r\n const playerLower = playerInput.trim().toLowerCase();\r\n\r\n // Find the matching secret\r\n const secret = npcScript.gatedSecrets.find(\r\n (s) => s.topicGateId === validated.candidateGateId\r\n );\r\n if (!secret) {\r\n return { ...validated, candidateGateId: null };\r\n }\r\n\r\n // Rule 1: explicit trigger keywords/phrases only. Do not split prose into\r\n // generic words like \"player\", \"asks\", \"about\", or \"what\".\r\n const triggerTerms = [\r\n ...(secret.triggerKeywords ?? []),\r\n ...(secret.triggerPhrases ?? []),\r\n ].filter((term) => term.trim().length > 0);\r\n const expandedTerms = expandKeywordsBilingually(triggerTerms, options?.bilingualKeywordMap);\r\n\r\n const hasExplicitTerms = expandedTerms.length > 0;\r\n const hasRelevance = expandedTerms.some((kw) => playerLower.includes(kw.toLowerCase()));\r\n if (hasExplicitTerms && !hasRelevance) {\r\n return { ...validated, candidateGateId: null };\r\n }\r\n\r\n // Rule 2: AI must show evidence of matching — either gateEvidence is\r\n // non-empty, OR gateConfidence is at least \"medium\"\r\n const hasGateEvidence = validated.gateEvidence && validated.gateEvidence.trim().length > 0;\r\n const hasConfidence = validated.gateConfidence === \"medium\" || validated.gateConfidence === \"high\";\r\n\r\n if (!hasGateEvidence && !hasConfidence) {\r\n return { ...validated, candidateGateId: null };\r\n }\r\n\r\n return validated;\r\n}\r\n","import type { NarrativeProvider } from \"../provider\";\r\nimport type { AuditRecord } from \"../types\";\r\nimport { AuditLog } from \"../audit\";\r\nimport { buildNpcDialoguePrompt } from \"./prompts\";\r\nimport { parseAiJson } from \"./parse\";\r\nimport { validateDialogueResponse } from \"./schema\";\r\nimport { reviewGateTrigger } from \"./gate-review\";\r\nimport type {\r\n NpcScript,\r\n DialogueAiResponse,\r\n DialogueRequest,\r\n DialogueResult,\r\n} from \"./types\";\r\n\r\n// ─── DialogueEngine ──────────────────────────────────────────────────\r\n// Handles AI-driven free-form NPC dialogue.\r\n// Reuses the existing NarrativeProvider.\r\n// Does NOT modify game state — only returns candidate signals.\r\n// The DialoguePolicy in web-services has final authority.\r\n\r\nexport interface DialogueEngineConfig {\r\n lang: \"en\" | \"zh\";\r\n maxConversationHistory: number;\r\n failOpen: boolean;\r\n /** Optional bilingual keyword map for cross-language gate matching.\r\n * Keys are English terms, values are arrays of Chinese equivalents. */\r\n bilingualKeywordMap?: Record<string, string[]>;\r\n}\r\n\r\nconst DEFAULT_DIALOGUE_CONFIG: DialogueEngineConfig = {\r\n lang: \"en\",\r\n maxConversationHistory: 5,\r\n failOpen: true,\r\n};\r\n\r\nexport class DialogueEngine {\r\n private provider: NarrativeProvider;\r\n private auditLog: AuditLog;\r\n private config: DialogueEngineConfig;\r\n private lang: \"en\" | \"zh\";\r\n private initialized = false;\r\n\r\n constructor(provider: NarrativeProvider, config?: Partial<DialogueEngineConfig>) {\r\n this.provider = provider;\r\n this.config = { ...DEFAULT_DIALOGUE_CONFIG, ...config };\r\n this.lang = this.config.lang;\r\n this.auditLog = new AuditLog(1000);\r\n }\r\n\r\n // ─── Lifecycle ─────────────────────────────────────────────────────\r\n\r\n async initialize(): Promise<void> {\r\n if (this.initialized) return;\r\n // Provider should already be initialized by the caller\r\n this.initialized = true;\r\n }\r\n\r\n async dispose(): Promise<void> {\r\n this.initialized = false;\r\n }\r\n\r\n isAiAvailable(): boolean {\r\n if (this.provider.id === \"passthrough\") return false;\r\n const status = this.provider.getStatus();\r\n return status.state === \"ready\" || status.state === \"degraded\";\r\n }\r\n\r\n // ─── Core API ──────────────────────────────────────────────────────\r\n\r\n async handleFreeFormDialogue(request: DialogueRequest): Promise<DialogueResult> {\r\n const startTime = Date.now();\r\n\r\n const passthroughResult: DialogueResult = {\r\n dialogue: \"\",\r\n candidateGateId: null,\r\n gateEvidence: \"\",\r\n gateConfidence: \"low\",\r\n candidateActionHint: null,\r\n triggeredTopicGateId: null,\r\n source: \"passthrough\",\r\n latencyMs: Date.now() - startTime,\r\n rawAiText: \"\",\r\n model: \"\",\r\n systemPrompt: \"\",\r\n userPrompt: \"\",\r\n };\r\n\r\n // If AI is not available, return passthrough\r\n if (!this.isAiAvailable()) {\r\n return passthroughResult;\r\n }\r\n\r\n // Build prompt\r\n const { system, user } = buildNpcDialoguePrompt(\r\n request.npcScript,\r\n request.playerInput,\r\n request.context,\r\n request.context.recentExchanges.slice(-this.config.maxConversationHistory),\r\n this.lang\r\n );\r\n\r\n // Call provider\r\n let rawText: string;\r\n let rawModel: string = \"\";\r\n let rawPromptTokens: number | undefined;\r\n let rawCompletionTokens: number | undefined;\r\n try {\r\n const narrativeRequest = this.buildMinimalRequest(request);\r\n const raw = await this.provider.call(narrativeRequest, system, user);\r\n rawText = raw.text;\r\n rawModel = raw.model;\r\n rawPromptTokens = raw.promptTokenCount;\r\n rawCompletionTokens = raw.completionTokenCount;\r\n } catch (err) {\r\n console.error(\"[DialogueEngine] provider.call failed:\", err);\r\n return passthroughResult;\r\n }\r\n\r\n // Parse AI JSON\r\n let parsed: DialogueAiResponse;\r\n try {\r\n parsed = parseAiJson(rawText);\r\n } catch {\r\n // JSON parse failed — show raw AI text as dialogue (better than canned greeting)\r\n return {\r\n dialogue: rawText.slice(0, 1000) || \"...\",\r\n candidateGateId: null,\r\n gateEvidence: \"\",\r\n gateConfidence: \"low\",\r\n candidateActionHint: null,\r\n triggeredTopicGateId: null,\r\n source: \"ai\",\r\n latencyMs: Date.now() - startTime,\r\n rawAiText: rawText,\r\n model: rawModel,\r\n systemPrompt: system,\r\n userPrompt: user,\r\n };\r\n }\r\n\r\n // Validate structure\r\n const validated = validateDialogueResponse(parsed);\r\n\r\n // Gate trigger review (keyword relevance + evidence quality only)\r\n const reviewed = reviewGateTrigger(\r\n validated,\r\n request.playerInput,\r\n request.npcScript,\r\n request.context,\r\n { bilingualKeywordMap: this.config.bilingualKeywordMap }\r\n );\r\n\r\n // Record audit\r\n this.recordAudit(request, reviewed, startTime);\r\n\r\n return {\r\n dialogue: reviewed.dialogue,\r\n candidateGateId: reviewed.candidateGateId,\r\n gateEvidence: reviewed.gateEvidence,\r\n gateConfidence: reviewed.gateConfidence,\r\n candidateActionHint: reviewed.candidateActionHint,\r\n source: \"ai\",\r\n latencyMs: Date.now() - startTime,\r\n rawAiText: rawText,\r\n model: rawModel,\r\n promptTokens: rawPromptTokens,\r\n completionTokens: rawCompletionTokens,\r\n systemPrompt: system,\r\n userPrompt: user,\r\n // Backward compatibility\r\n triggeredTopicGateId: reviewed.candidateGateId,\r\n };\r\n }\r\n\r\n // ─── Helpers ───────────────────────────────────────────────────────\r\n\r\n private buildMinimalRequest(request: DialogueRequest) {\r\n return {\r\n id: `dlg_${Date.now().toString(36)}`,\r\n type: \"dialogue\" as const,\r\n turnIndex: request.context.currentTurn,\r\n events: [],\r\n commandMessage: request.playerInput,\r\n visibleState: {} as any,\r\n grounding: {\r\n currentRoomName: request.context.currentRoom,\r\n visibleExits: [],\r\n presentNpcNames: [],\r\n inventoryItemNames: request.context.playerInventory,\r\n discoveredClueNames: request.context.discoveredClues,\r\n knownConsequences: [],\r\n turnsRemaining: request.context.turnsRemaining,\r\n },\r\n timestamp: Date.now(),\r\n lang: this.lang,\r\n };\r\n }\r\n\r\n private recordAudit(\r\n request: DialogueRequest,\r\n result: DialogueAiResponse,\r\n startTime: number\r\n ): void {\r\n const record: AuditRecord = {\r\n requestId: `dlg_audit_${Date.now().toString(36)}`,\r\n turnIndex: request.context.currentTurn,\r\n requestType: \"dialogue\",\r\n eventTypes: [],\r\n commandVerb: \"talk\",\r\n responseSource: \"ai\",\r\n responseTextPreview: result.dialogue.slice(0, 100),\r\n validationPassed: true,\r\n constraintViolations: [],\r\n providerState: this.provider.getStatus().state as AuditRecord[\"providerState\"],\r\n providerId: this.provider.id,\r\n promptStructure: {\r\n systemPromptHash: \"\",\r\n groundingFactCount: 0,\r\n eventCount: 0,\r\n },\r\n latencyMs: Date.now() - startTime,\r\n timestamp: Date.now(),\r\n };\r\n this.auditLog.append(record);\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wutiankai/npc-dialogue",
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"description": "AI NPC dialogue engine for mystery/investigation games — dialogue generation, gate review, bilingual keyword matching",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
|
|
12
|
+
"require": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
19
|
+
"prepublishOnly": "npm run build",
|
|
20
|
+
"clean": "rm -rf dist"
|
|
21
|
+
},
|
|
22
|
+
"keywords": ["ai", "npc", "dialogue", "mystery", "game", "narrative", "gate-review"],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"tsup": "^8.0.0",
|
|
26
|
+
"typescript": "^5.5.0"
|
|
27
|
+
}
|
|
28
|
+
}
|