@aiqa/sdk 1.4.0 → 1.5.0-develop.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 CHANGED
@@ -13,7 +13,11 @@ A TypeScript/JavaScript SDK for interacting with the AIQA WebSocket service. Thi
13
13
  - [Recording Management](#recording-management)
14
14
  - [Fact Management](#fact-management)
15
15
  - [Event Handling](#event-handling)
16
+ - [Verification lifecycle](#verification-lifecycle)
16
17
  - [Types](#types)
18
+ - [AddFactResult](#addfactresult)
19
+ - [VerificationStatus](#verificationstatus)
20
+ - [FactVerifiedPayload](#factverifiedpayload)
17
21
  - [FlagFactResult](#flagfactresult)
18
22
  - [Examples](#examples)
19
23
  - [Error Handling](#error-handling)
@@ -130,7 +134,18 @@ console.log(result.success, result.language);
130
134
 
131
135
  #### `addFact(fact)`
132
136
 
133
- Adds a fact to the active application (Promise-based).
137
+ Adds a fact to the active application and waits for the **terminal**
138
+ verification verdict (Promise-based). The Promise resolves with an
139
+ [`AddFactResult`](#addfactresult); subscribe to the
140
+ [`FACT_VERIFIED`](#event-handling) broadcast event to render progress
141
+ (`awaiting_question`, `awaiting_answer`, `partial`,
142
+ `pending_confirmation_matched`, `pending_confirmation_unmatched`)
143
+ while you wait.
144
+
145
+ `addFact()` does not throw on verification failure — an unmatched
146
+ fact resolves with `is_valid_answer: false` and (where the backend
147
+ provides it) `verification_status: "unmatched"` or `"contradicted"`.
148
+ Reject paths are reserved for transport / protocol errors.
134
149
 
135
150
  **Parameters:**
136
151
 
@@ -166,7 +181,7 @@ Adds a fact to the active application (Promise-based).
166
181
  ```
167
182
 
168
183
  ```typescript
169
- const isMatched = await sdk.addFact({
184
+ const result = await sdk.addFact({
170
185
  id: "fact-123",
171
186
  questionId: "agreement-q",
172
187
  question: "Do you agree?",
@@ -174,6 +189,17 @@ const isMatched = await sdk.addFact({
174
189
  answer_format: { type: "STRING" },
175
190
  metadata: { uiFieldId: "policy-agreement", section: "consent" },
176
191
  });
192
+
193
+ if (result.is_valid_answer) {
194
+ // verification_status will be "matched" (or "contradicted" if the
195
+ // customer explicitly contradicted the expected answer).
196
+ console.log("matched:", result.ai_answer);
197
+ } else {
198
+ // verification_status will be "unmatched". matched_components /
199
+ // missing_components describe what the partial-match heuristics
200
+ // were able to extract.
201
+ console.log("unmatched:", result.missing_components);
202
+ }
177
203
  ```
178
204
 
179
205
  ##### Question identity and `questionId`
@@ -278,6 +304,12 @@ Registers an event listener. Call `connect()` before `on()`.
278
304
  - `reconnect_failed` - Socket.IO reconnection failed
279
305
  - `authenticated` - Local event fired after successful authentication (provides Application object)
280
306
  - `app_version` - Server event with version requirements `{ min, max }`
307
+ - `FACT_VERIFIED` - Broadcast event fired on **every** verification
308
+ state transition (both non-terminal progress and terminal verdicts)
309
+ for **every** fact in the active conversation. Payload shape:
310
+ [`FactVerifiedPayload`](#factverifiedpayload). Use this to render
311
+ live verification progress in your UI; `addFact()`'s Promise only
312
+ resolves on the terminal state.
281
313
 
282
314
  ```typescript
283
315
  sdk.connect();
@@ -288,7 +320,47 @@ sdk.on("connect", () => {
288
320
  sdk.on("authenticated", (application) => {
289
321
  console.log("Authenticated:", application);
290
322
  });
323
+
324
+ sdk.on("FACT_VERIFIED", (payload) => {
325
+ // Renders progress for every fact in the conversation.
326
+ console.log(payload.factId, payload.verification_status);
327
+ });
328
+ ```
329
+
330
+ ## Verification lifecycle
331
+
332
+ A single `addFact()` call walks through a sequence of states emitted
333
+ on the `FACT_VERIFIED` broadcast event before the Promise resolves:
334
+
291
335
  ```
336
+ processing
337
+ └─> awaiting_question (agent hasn't asked yet)
338
+ └─> awaiting_answer (asked, customer hasn't answered yet)
339
+ └─> partial (partial evidence — e.g. "305" of "30521")
340
+ └─> pending_confirmation_matched
341
+ (backend has a match but is holding a settling
342
+ window for mid-answer clarifications like
343
+ spellings or "I mean…" corrections)
344
+ └─> pending_confirmation_unmatched
345
+ (same, but for a held unmatched verdict)
346
+ │
347
+ ▼
348
+ matched | unmatched | contradicted ← terminal
349
+ ```
350
+
351
+ Rules for consumers:
352
+
353
+ - **`addFact()` resolves only on a terminal state** (`matched`,
354
+ `unmatched`, `contradicted`). Render progress from the
355
+ `FACT_VERIFIED` event, render the final verdict from the Promise
356
+ result.
357
+ - **Treat unknown `verification_status` strings as `processing`.**
358
+ The enum is additive — older SDK builds must fall through gracefully
359
+ when the backend ships a new state.
360
+ - **`pending_confirmation_*` is never terminal.** The wire-visible
361
+ latency penalty is bounded by `settling_window_ms` on the server
362
+ (default 2000 ms). The held verdict can flip — that's the whole
363
+ point — so don't treat it as a commit.
292
364
 
293
365
  #### `off(eventName, callback?)`
294
366
 
@@ -321,11 +393,106 @@ Returned by `getFacts()`. Extends `ApplicationFactInput` and adds server-side fi
321
393
  type ApplicationFact = ApplicationFactInput & {
322
394
  ai_answer?: any;
323
395
  question_status: "searching" | "asked" | "not_asked";
324
- verification_status: "processing" | "matched" | "unmatched";
396
+ verification_status: VerificationStatus;
397
+ flagged: boolean; // true once flagFact() has been called for this fact
325
398
  metadata: Record<string, string>; // always present; {} when not set on addFact
326
399
  };
327
400
  ```
328
401
 
402
+ ### AddFactResult
403
+
404
+ Resolved value of `addFact()`. Only emitted at the terminal verification
405
+ state — see [Verification lifecycle](#verification-lifecycle).
406
+
407
+ ```typescript
408
+ interface AddFactResult {
409
+ /** Conversation the fact was attached to. */
410
+ conversation_id?: string;
411
+ /** Echo of the original question text from the fact input. */
412
+ question?: string;
413
+ /** Echo of the resolved answer_format.type (e.g. "STRING", "NUMBER"). */
414
+ answer_type?: string;
415
+
416
+ agent_answer?: string;
417
+ ai_answer?: string;
418
+ is_valid_answer: boolean;
419
+ question_asked?: boolean;
420
+
421
+ /**
422
+ * 0–1 (or 0–100 — scale matches the configured similarity method).
423
+ * Similarity between the agent's utterance and the expected question.
424
+ */
425
+ question_match_score?: number;
426
+
427
+ /**
428
+ * 0–1 score derived from how well the customer's answer matched expected.
429
+ * Populated by the verification queue from partial-match heuristics
430
+ * (numeric-prefix ratio, matched-components ratio).
431
+ */
432
+ answer_match_score?: number;
433
+
434
+ /**
435
+ * Top-level state at terminal resolution. Always one of `matched`,
436
+ * `unmatched`, or `contradicted` because the `addFact()` Promise only
437
+ * resolves on terminals. Optional during the rollout window for
438
+ * backwards compatibility with older backends.
439
+ */
440
+ verification_status?: VerificationStatus;
441
+
442
+ /**
443
+ * Per-fact partial-match breakdown carried through from the
444
+ * verification queue's component-wise comparators. For numeric-prefix
445
+ * facts these are `["prefix"]` / `["suffix"]`; future work will
446
+ * generalise to addresses, names, dates, etc.
447
+ */
448
+ matched_components?: string[];
449
+ missing_components?: string[];
450
+
451
+ metadata?: Record<string, string>;
452
+ }
453
+ ```
454
+
455
+ ### VerificationStatus
456
+
457
+ ```typescript
458
+ type VerificationStatus =
459
+ | "processing"
460
+ | "awaiting_question"
461
+ | "awaiting_answer"
462
+ | "partial"
463
+ | "pending_confirmation_matched"
464
+ | "pending_confirmation_unmatched"
465
+ | "matched"
466
+ | "unmatched"
467
+ | "contradicted";
468
+ ```
469
+
470
+ The enum is additive: consumers that `switch` on this value MUST
471
+ treat unknown strings as `"processing"` so older SDK builds keep
472
+ working after the backend ships a new state. See the
473
+ [Verification lifecycle](#verification-lifecycle) for the transitions
474
+ and which states are terminal.
475
+
476
+ ### FactVerifiedPayload
477
+
478
+ Payload of the `FACT_VERIFIED` broadcast event. Same shape as
479
+ `AddFactResult` plus the `factId` / `success` / `error` envelope.
480
+
481
+ ```typescript
482
+ interface FactVerifiedPayload {
483
+ factId: string;
484
+ success: boolean;
485
+ verification_status?: VerificationStatus;
486
+ question_match_score?: number;
487
+ answer_match_score?: number;
488
+ matched_components?: string[];
489
+ missing_components?: string[];
490
+ /** Same shape as AddFactResult so the same renderer handles both. */
491
+ data?: AddFactResult;
492
+ error?: string;
493
+ }
494
+ ```
495
+
329
496
  ### ApplicationFactInput
330
497
 
331
498
  ```typescript
@@ -475,8 +642,16 @@ sdk.on("connect", async () => {
475
642
  await sdk.startRecording();
476
643
  console.log("Recording started");
477
644
 
478
- // Add a fact
479
- const factMatched = await sdk.addFact({
645
+ // Optional: render live verification progress for every fact
646
+ // in the conversation while the addFact() Promise is in flight.
647
+ sdk.on("FACT_VERIFIED", (payload) => {
648
+ console.log(
649
+ `[${payload.factId}] ${payload.verification_status ?? "processing"}`,
650
+ );
651
+ });
652
+
653
+ // Add a fact (Promise resolves at the terminal verdict)
654
+ const result = await sdk.addFact({
480
655
  id: "q1",
481
656
  question: "What is your name?",
482
657
  expected_answer: "",
@@ -485,7 +660,14 @@ sdk.on("connect", async () => {
485
660
  },
486
661
  });
487
662
 
488
- console.log("Fact matched:", factMatched);
663
+ console.log(
664
+ "Terminal verdict:",
665
+ result.verification_status,
666
+ "is_valid_answer:",
667
+ result.is_valid_answer,
668
+ "ai_answer:",
669
+ result.ai_answer,
670
+ );
489
671
 
490
672
  // Stop recording
491
673
  await sdk.stopRecording();
@@ -32,7 +32,7 @@ declare enum ConversationLanguage {
32
32
  ES = "es",
33
33
  FR = "fr"
34
34
  }
35
- type SdkEvent = Methods | "connect" | "disconnect" | "close" | "error" | "reconnect_attempt" | "reconnect_error" | "reconnect_failed" | "authenticated" | "app_version";
35
+ type SdkEvent = Methods | "connect" | "disconnect" | "close" | "error" | "reconnect_attempt" | "reconnect_error" | "reconnect_failed" | "authenticated" | "app_version" | "FACT_VERIFIED";
36
36
  declare enum FactAnswerFormatType {
37
37
  string = "STRING",
38
38
  number = "NUMBER",
@@ -73,18 +73,111 @@ type ApplicationFactInput = {
73
73
  metadata?: Record<string, string>;
74
74
  };
75
75
  type QuestionStatus = "searching" | "asked" | "not_asked";
76
- type VerificationStatus = "processing" | "matched" | "unmatched";
76
+ /**
77
+ * Per-fact verification state.
78
+ *
79
+ * Extended from the original three-state shape to carry progress signals
80
+ * the backend already detects but previously could not express. Consumers
81
+ * that switch on this value MUST treat unknown strings as "processing"
82
+ * (default fall-through) so older SDK builds keep working after the
83
+ * backend starts emitting the additive states.
84
+ *
85
+ * Semantics:
86
+ * - `processing` generic, just queued or transcription not ready
87
+ * - `awaiting_question` agent hasn't asked the question yet
88
+ * - `awaiting_answer` question asked, customer hasn't answered yet
89
+ * - `partial` partial evidence; backend still working on it
90
+ * - `pending_confirmation_matched` backend has a matching answer but is
91
+ * holding a settling window for mid-answer
92
+ * clarifications (e.g. spellings, "I mean…");
93
+ * resolves to `matched`/`unmatched`/`contradicted`
94
+ * - `pending_confirmation_unmatched` same as above but the held verdict is
95
+ * `unmatched`; UI can show "we heard X but it
96
+ * doesn't match, waiting to confirm" instead
97
+ * of a hard red verdict
98
+ * - `matched` terminal: answer matched expected
99
+ * - `unmatched` terminal: budget exhausted with no match
100
+ * - `contradicted` terminal (future): customer contradicted expected
101
+ */
102
+ type VerificationStatus = "processing" | "awaiting_question" | "awaiting_answer" | "partial" | "pending_confirmation_matched" | "pending_confirmation_unmatched" | "matched" | "unmatched" | "contradicted";
77
103
  type ApplicationFact = ApplicationFactInput & {
78
104
  ai_answer?: any;
79
105
  question_status: QuestionStatus;
80
106
  verification_status: VerificationStatus;
107
+ /** True once `flagFact(factId)` has been called for this fact. */
108
+ flagged: boolean;
81
109
  };
82
110
  interface AddFactResult {
111
+ /** Conversation the fact was attached to. Useful for cross-fact correlation in agent UIs. */
112
+ conversation_id?: string;
113
+ /** Echo of the original question text from the fact input. */
114
+ question?: string;
115
+ /** Echo of the resolved `answer_format.type` (e.g. "STRING", "NUMBER", "SINGLE_CHOICE"). */
116
+ answer_type?: string;
83
117
  agent_answer?: string;
84
118
  ai_answer?: string;
85
119
  is_valid_answer: boolean;
86
120
  question_asked?: boolean;
121
+ /**
122
+ * 0–1 (or 0–100 — scale matches the configured similarity method).
123
+ * Similarity between the agent's utterance and the expected question.
124
+ * Renamed from `match_score` to disambiguate from the answer-side score.
125
+ */
126
+ question_match_score?: number;
127
+ /**
128
+ * 0–1 score derived from how well the customer's answer matched expected.
129
+ * Populated by the verification queue from partial-match heuristics
130
+ * (numeric-prefix ratio, matched-components ratio). Undefined when no
131
+ * partial signal exists or for terminal results before this rolls out.
132
+ */
133
+ answer_match_score?: number;
87
134
  metadata?: Record<string, string>;
135
+ /**
136
+ * Top-level state at terminal resolution. Always one of `matched`,
137
+ * `unmatched`, or `contradicted` because the `addFact()` Promise only
138
+ * resolves on terminals. Optional during the rollout window for
139
+ * backwards compatibility with older backends.
140
+ */
141
+ verification_status?: VerificationStatus;
142
+ /**
143
+ * Per-fact partial-match breakdown carried through from the
144
+ * verification queue's component-wise comparators. For numeric-prefix
145
+ * facts these are `["prefix"]` / `["suffix"]`; future work will
146
+ * generalise to addresses, names, dates, etc.
147
+ */
148
+ matched_components?: string[];
149
+ missing_components?: string[];
150
+ }
151
+ /**
152
+ * Payload of the `FACT_VERIFIED` broadcast event.
153
+ *
154
+ * Fires on EVERY state transition the verification queue produces —
155
+ * non-terminal (`partial`, `awaiting_question`, `awaiting_answer`,
156
+ * `pending_confirmation_matched`, `pending_confirmation_unmatched`,
157
+ * `processing`) AND terminal (`matched`, `unmatched`, `contradicted`).
158
+ *
159
+ * Subscribe with:
160
+ * sdk.on("FACT_VERIFIED", (payload: FactVerifiedPayload) => {...});
161
+ *
162
+ * `addFact()`'s Promise resolves only on terminals; this event stream
163
+ * is the channel for rendering live progress in the agent UI.
164
+ */
165
+ interface FactVerifiedPayload {
166
+ factId: string;
167
+ success: boolean;
168
+ verification_status?: VerificationStatus;
169
+ question_match_score?: number;
170
+ answer_match_score?: number;
171
+ matched_components?: string[];
172
+ missing_components?: string[];
173
+ /**
174
+ * Inner data payload. Shape matches `AddFactResult` (minus the
175
+ * top-level envelope fields) so consumers can reuse the same
176
+ * rendering code for the terminal addFact() resolution and the
177
+ * broadcast event.
178
+ */
179
+ data?: AddFactResult;
180
+ error?: string;
88
181
  }
89
182
  type FlagFactResult = {
90
183
  success: boolean;
@@ -136,4 +229,4 @@ declare class AiQaSdk implements IAiqaSdk {
136
229
  private isManagerEvent;
137
230
  }
138
231
 
139
- export { type AddFactResult, AiQaSdk, type Application, type ApplicationFact, type ApplicationFactAnswerChoice, type ApplicationFactAnswerFormat, type ApplicationFactInput, ConversationLanguage, FactAnswerFormatType, type FlagFactResult, IAiqaSdk, Methods, type QuestionStatus, type SdkError, type SdkEvent, type SetConversationLanguageResult, type SetConversationProductResult, type VerificationStatus };
232
+ export { type AddFactResult, AiQaSdk, type Application, type ApplicationFact, type ApplicationFactAnswerChoice, type ApplicationFactAnswerFormat, type ApplicationFactInput, ConversationLanguage, FactAnswerFormatType, type FactVerifiedPayload, type FlagFactResult, IAiqaSdk, Methods, type QuestionStatus, type SdkError, type SdkEvent, type SetConversationLanguageResult, type SetConversationProductResult, type VerificationStatus };
@@ -3396,7 +3396,7 @@ var compareVersions = (left, right) => {
3396
3396
  // package.json
3397
3397
  var package_default = {
3398
3398
  name: "@aiqa/sdk",
3399
- version: "1.4.0-develop.1",
3399
+ version: "1.4.0-develop.3",
3400
3400
  main: "dist/node/node.js",
3401
3401
  module: "dist/node/node.js",
3402
3402
  types: "dist/node/node.d.ts",
@@ -3599,7 +3599,7 @@ var AiQaSdk = class {
3599
3599
  reject(parsed.error);
3600
3600
  return;
3601
3601
  }
3602
- resolve(parsed && parsed.data !== void 0 ? parsed.data : parsed);
3602
+ resolve(parsed);
3603
3603
  };
3604
3604
  this.socket.on(requestId, handler);
3605
3605
  const payload = data && typeof data === "object" ? { ...data, requestId } : { requestId, data };
@@ -3629,7 +3629,23 @@ var AiQaSdk = class {
3629
3629
  is_skipped: fact.is_skipped ?? false
3630
3630
  };
3631
3631
  const result = await this.sendMessage("ADD_FACT" /* ADD_FACT */, payload);
3632
- return result && result.data !== void 0 ? result.data : result;
3632
+ if (!result) return result;
3633
+ const inner = result.data !== void 0 ? result.data : result;
3634
+ const hasEnvelope = result.verification_status !== void 0 || result.matched_components !== void 0 || result.missing_components !== void 0;
3635
+ if (!hasEnvelope || typeof inner !== "object" || inner === null) {
3636
+ return inner;
3637
+ }
3638
+ const merged = { ...inner };
3639
+ if (result.verification_status !== void 0) {
3640
+ merged.verification_status = result.verification_status;
3641
+ }
3642
+ if (result.matched_components !== void 0) {
3643
+ merged.matched_components = result.matched_components;
3644
+ }
3645
+ if (result.missing_components !== void 0) {
3646
+ merged.missing_components = result.missing_components;
3647
+ }
3648
+ return merged;
3633
3649
  }
3634
3650
  async flagFact(factId) {
3635
3651
  const result = await this.sendMessage("FLAG_FACT" /* FLAG_FACT */, {