@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.
@@ -0,0 +1,323 @@
1
+ type ProviderState = "uninitialized" | "initializing" | "ready" | "degraded" | "failed" | "disposed";
2
+ interface ProviderStatus {
3
+ state: ProviderState;
4
+ providerId: string;
5
+ lastHealthCheck: number;
6
+ consecutiveFailures: number;
7
+ lastError?: string;
8
+ configRedacted: {
9
+ baseUrl: string;
10
+ model: string;
11
+ };
12
+ }
13
+ type TrustLevel = 0 | 1 | 2;
14
+ interface GroundingData {
15
+ currentRoomName: string;
16
+ visibleExits: string[];
17
+ presentNpcNames: string[];
18
+ inventoryItemNames: string[];
19
+ discoveredClueNames: string[];
20
+ knownConsequences: string[];
21
+ turnsRemaining: number;
22
+ }
23
+ type RuntimeEvent = Record<string, unknown>;
24
+ type VisibleState = Record<string, unknown>;
25
+ interface NarrativeRequest {
26
+ id: string;
27
+ type: "narration" | "dialogue";
28
+ turnIndex: number;
29
+ events: RuntimeEvent[];
30
+ commandMessage: string;
31
+ visibleState: VisibleState;
32
+ dialogueContext?: {
33
+ npcId: string;
34
+ npcName: string;
35
+ npcRole: string;
36
+ topic: string;
37
+ topicResponse: string;
38
+ trustLevel: TrustLevel;
39
+ recentEvents: RuntimeEvent[];
40
+ };
41
+ grounding: GroundingData;
42
+ timestamp: number;
43
+ lang: "en" | "zh";
44
+ }
45
+ interface AuditRecord {
46
+ requestId: string;
47
+ turnIndex: number;
48
+ requestType: "narration" | "dialogue";
49
+ eventTypes: string[];
50
+ commandVerb: string;
51
+ responseSource: "ai" | "passthrough";
52
+ responseTextPreview: string;
53
+ validationPassed: boolean;
54
+ constraintViolations: string[];
55
+ providerState: ProviderState;
56
+ providerId: string;
57
+ promptStructure: {
58
+ systemPromptHash: string;
59
+ groundingFactCount: number;
60
+ eventCount: number;
61
+ };
62
+ latencyMs: number;
63
+ timestamp: number;
64
+ }
65
+ interface AuditFilter {
66
+ source?: "ai" | "passthrough";
67
+ minTurnIndex?: number;
68
+ maxTurnIndex?: number;
69
+ requestType?: "narration" | "dialogue";
70
+ validationPassed?: boolean;
71
+ }
72
+ interface AuditStats {
73
+ totalRequests: number;
74
+ aiSuccessCount: number;
75
+ passthroughCount: number;
76
+ aiFailureCount: number;
77
+ validationFailureCount: number;
78
+ constraintViolationCount: number;
79
+ averageLatencyMs: number;
80
+ byProvider: Record<string, {
81
+ calls: number;
82
+ failures: number;
83
+ }>;
84
+ }
85
+ interface ProviderRawResponse {
86
+ text: string;
87
+ model: string;
88
+ latencyMs: number;
89
+ promptTokenCount?: number;
90
+ completionTokenCount?: number;
91
+ }
92
+
93
+ interface NarrativeProvider {
94
+ readonly id: string;
95
+ initialize(): Promise<void>;
96
+ dispose(): Promise<void>;
97
+ getStatus(): ProviderStatus;
98
+ healthCheck(): Promise<boolean>;
99
+ call(request: NarrativeRequest, systemPrompt: string, userPrompt: string): Promise<ProviderRawResponse>;
100
+ }
101
+ declare class PassthroughProvider implements NarrativeProvider {
102
+ readonly id = "passthrough";
103
+ private status;
104
+ initialize(): Promise<void>;
105
+ dispose(): Promise<void>;
106
+ getStatus(): ProviderStatus;
107
+ healthCheck(): Promise<boolean>;
108
+ call(request: NarrativeRequest, _systemPrompt: string, _userPrompt: string): Promise<ProviderRawResponse>;
109
+ }
110
+
111
+ interface ProviderConfig {
112
+ apiKey?: string;
113
+ baseUrl: string;
114
+ model: string;
115
+ maxTokens: number;
116
+ temperature: number;
117
+ }
118
+ declare function loadConfigFromEnv(): ProviderConfig;
119
+
120
+ declare class OpenAICompatibleProvider implements NarrativeProvider {
121
+ private config;
122
+ readonly id = "openai-compat";
123
+ private state;
124
+ private consecutiveFailures;
125
+ private lastHealthCheck;
126
+ private lastError?;
127
+ constructor(config: ProviderConfig);
128
+ initialize(): Promise<void>;
129
+ dispose(): Promise<void>;
130
+ getStatus(): ProviderStatus;
131
+ healthCheck(): Promise<boolean>;
132
+ call(_request: NarrativeRequest, systemPrompt: string, userPrompt: string): Promise<ProviderRawResponse>;
133
+ private recordFailure;
134
+ }
135
+
136
+ declare class AuditLog {
137
+ private entries;
138
+ private readonly maxEntries;
139
+ constructor(maxEntries?: number);
140
+ append(record: AuditRecord): void;
141
+ query(filter?: AuditFilter): AuditRecord[];
142
+ stats(): AuditStats;
143
+ clear(): void;
144
+ export(): AuditRecord[];
145
+ get length(): number;
146
+ }
147
+
148
+ /**
149
+ * NPC Script — the complete character bible for AI-driven dialogue.
150
+ * Defined per-NPC per-adventure.
151
+ */
152
+ interface NpcScript {
153
+ npcId: string;
154
+ name: string;
155
+ role: string;
156
+ /** Optional world/genre framing for the system prompt.
157
+ * E.g. "a murder mystery game set in a snowbound manor" */
158
+ worldSetting?: string;
159
+ persona: {
160
+ personality: string;
161
+ background: string;
162
+ speechPatterns: string;
163
+ emotionalBaseline?: string;
164
+ forbiddenTone?: string;
165
+ };
166
+ publicKnowledge: NpcKnowledgeEntry[];
167
+ privateKnowledge: NpcKnowledgeEntry[];
168
+ gatedSecrets: NpcSecret[];
169
+ ignorance: string[];
170
+ relationships: {
171
+ npcId: string;
172
+ attitude: string;
173
+ notes: string;
174
+ }[];
175
+ }
176
+ interface NpcKnowledgeEntry {
177
+ id: string;
178
+ topic: string;
179
+ content: string;
180
+ }
181
+ interface NpcSecret {
182
+ id: string;
183
+ topicGateId: string;
184
+ description: string;
185
+ revealConditions: string;
186
+ reactionWhenPressed: string;
187
+ triggerKeywords?: string[];
188
+ triggerPhrases?: string[];
189
+ }
190
+ interface ConversationExchange {
191
+ playerInput: string;
192
+ npcResponse: string;
193
+ /** Gate that was actually triggered (after policy review), or null. */
194
+ triggeredTopicGateId: string | null;
195
+ timestamp: number;
196
+ policyNotes?: string[];
197
+ possibleStateClaim?: "item_given" | "access_granted" | null;
198
+ runtimeConfirmed?: boolean;
199
+ }
200
+ /**
201
+ * What the AI returns — candidate signals, NOT executable decisions.
202
+ *
203
+ * AI proposes gate triggers/action hints, but the DialoguePolicy/runtime
204
+ * layers decide what to accept.
205
+ */
206
+ interface DialogueAiResponse {
207
+ dialogue: string;
208
+ /** AI-proposed gate trigger — policy layer has final authority. */
209
+ candidateGateId: string | null;
210
+ /** What part of the player's input suggested this gate. */
211
+ gateEvidence: string;
212
+ /** AI confidence in the gate trigger. */
213
+ gateConfidence: "low" | "medium" | "high";
214
+ /** Optional action the AI thinks is happening (e.g., "item_given"). */
215
+ candidateActionHint: string | null;
216
+ }
217
+ interface DialogueContext {
218
+ currentRoom: string;
219
+ currentTurn: number;
220
+ turnsRemaining: number;
221
+ playerInventory: string[];
222
+ discoveredClues: string[];
223
+ currentTrust: number;
224
+ exhaustedTopicGateIds: string[];
225
+ recentExchanges: ConversationExchange[];
226
+ validTopicGateIds: string[];
227
+ }
228
+ interface DialogueRequest {
229
+ npcScript: NpcScript;
230
+ playerInput: string;
231
+ context: DialogueContext;
232
+ }
233
+ /**
234
+ * What the DialogueEngine returns to callers.
235
+ *
236
+ * Contains candidate signals from AI plus temporary backward-compatible
237
+ * fields so that callers can migrate incrementally.
238
+ */
239
+ interface DialogueResult {
240
+ dialogue: string;
241
+ source: "ai" | "passthrough";
242
+ latencyMs: number;
243
+ candidateGateId: string | null;
244
+ gateEvidence: string;
245
+ gateConfidence: "low" | "medium" | "high";
246
+ /** AI-suggested action (e.g., "item_given"). Policy reviews this. */
247
+ candidateActionHint: string | null;
248
+ /** Raw AI output text before parsing. */
249
+ rawAiText: string;
250
+ /** Model identifier used for this call. */
251
+ model: string;
252
+ /** Prompt token count (if available from provider). */
253
+ promptTokens?: number;
254
+ /** Completion token count (if available from provider). */
255
+ completionTokens?: number;
256
+ /** Full system prompt sent to AI. */
257
+ systemPrompt: string;
258
+ /** Full user prompt sent to AI. */
259
+ userPrompt: string;
260
+ /** @deprecated Use candidateGateId. Maps from candidateGateId. */
261
+ triggeredTopicGateId: string | null;
262
+ }
263
+
264
+ interface DialogueEngineConfig {
265
+ lang: "en" | "zh";
266
+ maxConversationHistory: number;
267
+ failOpen: boolean;
268
+ /** Optional bilingual keyword map for cross-language gate matching.
269
+ * Keys are English terms, values are arrays of Chinese equivalents. */
270
+ bilingualKeywordMap?: Record<string, string[]>;
271
+ }
272
+ declare class DialogueEngine {
273
+ private provider;
274
+ private auditLog;
275
+ private config;
276
+ private lang;
277
+ private initialized;
278
+ constructor(provider: NarrativeProvider, config?: Partial<DialogueEngineConfig>);
279
+ initialize(): Promise<void>;
280
+ dispose(): Promise<void>;
281
+ isAiAvailable(): boolean;
282
+ handleFreeFormDialogue(request: DialogueRequest): Promise<DialogueResult>;
283
+ private buildMinimalRequest;
284
+ private recordAudit;
285
+ }
286
+
287
+ interface DialoguePromptPair {
288
+ system: string;
289
+ user: string;
290
+ }
291
+ declare function buildNpcDialoguePrompt(npcScript: NpcScript, playerInput: string, context: DialogueContext, recentExchanges: ConversationExchange[], lang: "en" | "zh"): DialoguePromptPair;
292
+
293
+ /**
294
+ * Parse AI response text as JSON. Handles:
295
+ * - markdown-wrapped JSON
296
+ * - truncated JSON (model hit max_tokens mid-response)
297
+ * - raw JSON without wrapper
298
+ * Accepts both old and new field name formats.
299
+ */
300
+ declare function parseAiJson(rawText: string): DialogueAiResponse;
301
+
302
+ declare function validateDialogueResponse(parsed: DialogueAiResponse): DialogueAiResponse;
303
+
304
+ /**
305
+ * Expand explicit English keywords with Chinese equivalents for cross-language matching.
306
+ *
307
+ * @param englishKeywords - Keywords from secret definitions (typically English)
308
+ * @param bilingualMap - Optional map of English keyword → Chinese equivalents.
309
+ * Provide a game-specific map for bilingual gate matching.
310
+ */
311
+ declare function expandKeywordsBilingually(englishKeywords: string[], bilingualMap?: Record<string, string[]>): string[];
312
+ /**
313
+ * Review a validated AI response for false gate triggers.
314
+ *
315
+ * Rules (greeting/short-input filtering is handled by dialogue-intent.ts):
316
+ * 1. If explicit trigger keywords/phrases exist, player input must match one
317
+ * 2. AI must provide gateEvidence or gateConfidence ≥ medium
318
+ */
319
+ declare function reviewGateTrigger(validated: DialogueAiResponse, playerInput: string, npcScript: NpcScript, _context: DialogueContext, options?: {
320
+ bilingualKeywordMap?: Record<string, string[]>;
321
+ }): DialogueAiResponse;
322
+
323
+ export { AuditLog, type AuditRecord, type AuditStats, type ConversationExchange, type DialogueAiResponse, type DialogueContext, DialogueEngine, type DialogueEngineConfig, type DialoguePromptPair, type DialogueRequest, type DialogueResult, type GroundingData, type NarrativeProvider, type NarrativeRequest, type NpcKnowledgeEntry, type NpcScript, type NpcSecret, OpenAICompatibleProvider, PassthroughProvider, type ProviderConfig, type ProviderRawResponse, type ProviderState, type ProviderStatus, type TrustLevel, buildNpcDialoguePrompt, expandKeywordsBilingually, loadConfigFromEnv, parseAiJson, reviewGateTrigger, validateDialogueResponse };