@vinaverse/client 0.1.0-alpha.3 → 0.1.0-alpha.4
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/dist/{chunk-AWHPWRGT.js → chunk-MKUNSAMT.js} +73 -11
- package/dist/chunk-MKUNSAMT.js.map +1 -0
- package/dist/conversation/index.cjs +198 -27
- package/dist/conversation/index.cjs.map +1 -1
- package/dist/conversation/index.d.cts +56 -5
- package/dist/conversation/index.d.ts +56 -5
- package/dist/conversation/index.js +130 -22
- package/dist/conversation/index.js.map +1 -1
- package/dist/index.cjs +70 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{session--W6JXfwo.d.cts → session-C3qZwGPG.d.cts} +130 -7
- package/dist/{session--W6JXfwo.d.ts → session-C3qZwGPG.d.ts} +130 -7
- package/package.json +4 -4
- package/dist/chunk-AWHPWRGT.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Q as Question, A as AnswerInput, H as HeldCard, T as ToolLine, S as SessionInfo, C as ClientSecret, a as SessionOptions, P as ProposalOutcome } from '../session
|
|
2
|
-
export { b as CONTRACT, c as Card, d as
|
|
1
|
+
import { Q as Question, A as AnswerInput, H as HeldCard, W as Welcome, T as ToolLine, S as SessionInfo, C as ClientSecret, a as SessionOptions, P as ProposalOutcome } from '../session-C3qZwGPG.cjs';
|
|
2
|
+
export { b as CONTRACT, c as Card, d as CardGroup, e as CardImage, f as CardKind, g as CardLink, h as ClientErrorCode, i as HeldQuestion, j as Proposal, V as VinaverseClientError, k as isKnownCard } from '../session-C3qZwGPG.cjs';
|
|
3
3
|
|
|
4
4
|
type AnswerProblem =
|
|
5
5
|
/** Nothing chosen or typed, and the question is required (or skipped while required). */
|
|
@@ -47,7 +47,10 @@ type ConversationStatus =
|
|
|
47
47
|
| 'sse'
|
|
48
48
|
/** The session ended; `next` says how the conversation goes on. */
|
|
49
49
|
| 'ended'
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* The client secret was refused, and the new session the store then asked for was refused as well: `start()` (the
|
|
52
|
+
* person's "Try again") asks for another.
|
|
53
|
+
*/
|
|
51
54
|
| 'unauthorized'
|
|
52
55
|
/** `dispose()` or `clear()` closed it. */
|
|
53
56
|
| 'closed';
|
|
@@ -123,6 +126,11 @@ interface ConversationView {
|
|
|
123
126
|
readonly name: string | null;
|
|
124
127
|
readonly avatarUrl: string | null;
|
|
125
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* The tenant's greeting and chips for the session's mode (v1-draft.20, `Session.welcome`), or null. A UI shows it on
|
|
131
|
+
* an empty conversation; a chip sends its `message` as the person's own text (`send`).
|
|
132
|
+
*/
|
|
133
|
+
readonly welcome: Welcome | null;
|
|
126
134
|
/** The server's AI disclosure; null until the first one, when the UI shows its own line. Never hidden. */
|
|
127
135
|
readonly disclosure: string | null;
|
|
128
136
|
/** While a person from the business replies: their role (`by`, never a name). */
|
|
@@ -183,6 +191,13 @@ type ConversationSession = SessionInfo & {
|
|
|
183
191
|
readonly avatar_url?: string;
|
|
184
192
|
};
|
|
185
193
|
readonly disclosure?: string;
|
|
194
|
+
/**
|
|
195
|
+
* When the session ends (`Session.expires_at`: the earliest of its own limit and the tenant's tool token). Past it,
|
|
196
|
+
* the conversation goes on in a new session from `getSession`, never with a fresh secret for this one.
|
|
197
|
+
*/
|
|
198
|
+
readonly expires_at?: string;
|
|
199
|
+
/** v1-draft.20: the tenant's greeting and chips for the session's mode (`Session.welcome`); no model call. */
|
|
200
|
+
readonly welcome?: Welcome;
|
|
186
201
|
};
|
|
187
202
|
/**
|
|
188
203
|
* Runs a callback before the next frame; `requestAnimationFrame` where there is one. Every listener, of the view and
|
|
@@ -265,6 +280,8 @@ declare class ConversationStore {
|
|
|
265
280
|
private readySession;
|
|
266
281
|
private cardOrder;
|
|
267
282
|
private kept;
|
|
283
|
+
/** Cards an earlier session drew, kept by a merged transcript: drawn as restored, taking no tap. */
|
|
284
|
+
private readonly inert;
|
|
268
285
|
private locale;
|
|
269
286
|
/** The request for a session in flight, to abort when it is no longer wanted. */
|
|
270
287
|
private fetching;
|
|
@@ -303,7 +320,12 @@ declare class ConversationStore {
|
|
|
303
320
|
/** In the background (`closeInBackground`): the connection is closed; the engine keeps what was sent, to resend. */
|
|
304
321
|
private suspended;
|
|
305
322
|
private suspend;
|
|
306
|
-
/**
|
|
323
|
+
/**
|
|
324
|
+
* Back in the foreground, or the person acted while away: a new connection to the same session, with a fresh secret
|
|
325
|
+
* when the backend gives one. A session that passed its end meanwhile is never reopened (a fresh secret cannot bring
|
|
326
|
+
* back an expired tool token): the person's action continues the conversation in a new session from `getSession`,
|
|
327
|
+
* and until then nothing connects.
|
|
328
|
+
*/
|
|
307
329
|
private wake;
|
|
308
330
|
/** The network as the app sees it; null when unknown. Back online reconnects a connection that gave up. */
|
|
309
331
|
setOnline(online: boolean | null): void;
|
|
@@ -348,6 +370,8 @@ declare class ConversationStore {
|
|
|
348
370
|
/** The person acted after the session ended idle, expired or at its limit: the next session takes what they sent. */
|
|
349
371
|
private continueIfEnded;
|
|
350
372
|
private reconnectIfDown;
|
|
373
|
+
/** A refusal already led to a new session, and none has been ready since: another refusal is no cue. */
|
|
374
|
+
private rolled;
|
|
351
375
|
private statusChanged;
|
|
352
376
|
private event;
|
|
353
377
|
/** The person's text that started a turn, while its message is on screen. */
|
|
@@ -376,10 +400,37 @@ declare class ConversationStore {
|
|
|
376
400
|
private key;
|
|
377
401
|
private restore;
|
|
378
402
|
private schedulePersist;
|
|
403
|
+
/** Past the session's end (`expires_at`), when the backend passed it on. */
|
|
404
|
+
private sessionOver;
|
|
379
405
|
private live;
|
|
380
406
|
private required;
|
|
381
407
|
}
|
|
382
408
|
/** One conversation with an agent, for any UI: see `ConversationStore`. */
|
|
383
409
|
declare function createConversation(options: ConversationOptions): ConversationStore;
|
|
384
410
|
|
|
385
|
-
|
|
411
|
+
/** A session request, with the mode it is for. */
|
|
412
|
+
interface ModeSessionRequest extends SessionRequest {
|
|
413
|
+
readonly mode: string;
|
|
414
|
+
}
|
|
415
|
+
interface ConversationsByModeOptions extends Omit<ConversationOptions, 'getSession'> {
|
|
416
|
+
/** Your backend creates the session for the mode's agent (its `subject.type`, its agent), with its service key. */
|
|
417
|
+
readonly getSession: (request: ModeSessionRequest) => Promise<ConversationSession>;
|
|
418
|
+
/** Every mode the app has, so `clear()` forgets each one's kept history, even one not opened since launch. */
|
|
419
|
+
readonly modes?: readonly string[];
|
|
420
|
+
}
|
|
421
|
+
interface ConversationsByMode {
|
|
422
|
+
/** The mode's store, made the first time it is asked for; the same store after that. */
|
|
423
|
+
get(mode: string): ConversationStore;
|
|
424
|
+
/**
|
|
425
|
+
* The mode the person switched to: its store, with every other mode's sent to the background (no pings; closed
|
|
426
|
+
* with `closeInBackground`). The kit then drives the selected one with the app's foreground and background.
|
|
427
|
+
*/
|
|
428
|
+
select(mode: string): ConversationStore;
|
|
429
|
+
/** Close every mode's store. */
|
|
430
|
+
dispose(): void;
|
|
431
|
+
/** Close every mode's store and forget what each kept on the device (sign-out). */
|
|
432
|
+
clear(): void;
|
|
433
|
+
}
|
|
434
|
+
declare function createConversationsByMode(options: ConversationsByModeOptions): ConversationsByMode;
|
|
435
|
+
|
|
436
|
+
export { AnswerInput, type AnswerProblem, type CardItem, type ConversationBanner, type ConversationOptions, type ConversationPersistence, type ConversationSession, type ConversationStatus, ConversationStore, type ConversationView, type ConversationsByMode, type ConversationsByModeOptions, type FrameScheduler, HeldCard, MAX_TEXT, type MessageItem, type MessageState, type ModeSessionRequest, ProposalOutcome, Question, type SessionReason, type SessionRequest, type StreamingText, type SystemEvent, type SystemItem, type TimelineItem, ToolLine, Welcome, answerProblem, codePoints, createConversation, createConversationsByMode, isAllowedLink, isValidLinkPrefix };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Q as Question, A as AnswerInput, H as HeldCard, T as ToolLine, S as SessionInfo, C as ClientSecret, a as SessionOptions, P as ProposalOutcome } from '../session
|
|
2
|
-
export { b as CONTRACT, c as Card, d as
|
|
1
|
+
import { Q as Question, A as AnswerInput, H as HeldCard, W as Welcome, T as ToolLine, S as SessionInfo, C as ClientSecret, a as SessionOptions, P as ProposalOutcome } from '../session-C3qZwGPG.js';
|
|
2
|
+
export { b as CONTRACT, c as Card, d as CardGroup, e as CardImage, f as CardKind, g as CardLink, h as ClientErrorCode, i as HeldQuestion, j as Proposal, V as VinaverseClientError, k as isKnownCard } from '../session-C3qZwGPG.js';
|
|
3
3
|
|
|
4
4
|
type AnswerProblem =
|
|
5
5
|
/** Nothing chosen or typed, and the question is required (or skipped while required). */
|
|
@@ -47,7 +47,10 @@ type ConversationStatus =
|
|
|
47
47
|
| 'sse'
|
|
48
48
|
/** The session ended; `next` says how the conversation goes on. */
|
|
49
49
|
| 'ended'
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* The client secret was refused, and the new session the store then asked for was refused as well: `start()` (the
|
|
52
|
+
* person's "Try again") asks for another.
|
|
53
|
+
*/
|
|
51
54
|
| 'unauthorized'
|
|
52
55
|
/** `dispose()` or `clear()` closed it. */
|
|
53
56
|
| 'closed';
|
|
@@ -123,6 +126,11 @@ interface ConversationView {
|
|
|
123
126
|
readonly name: string | null;
|
|
124
127
|
readonly avatarUrl: string | null;
|
|
125
128
|
};
|
|
129
|
+
/**
|
|
130
|
+
* The tenant's greeting and chips for the session's mode (v1-draft.20, `Session.welcome`), or null. A UI shows it on
|
|
131
|
+
* an empty conversation; a chip sends its `message` as the person's own text (`send`).
|
|
132
|
+
*/
|
|
133
|
+
readonly welcome: Welcome | null;
|
|
126
134
|
/** The server's AI disclosure; null until the first one, when the UI shows its own line. Never hidden. */
|
|
127
135
|
readonly disclosure: string | null;
|
|
128
136
|
/** While a person from the business replies: their role (`by`, never a name). */
|
|
@@ -183,6 +191,13 @@ type ConversationSession = SessionInfo & {
|
|
|
183
191
|
readonly avatar_url?: string;
|
|
184
192
|
};
|
|
185
193
|
readonly disclosure?: string;
|
|
194
|
+
/**
|
|
195
|
+
* When the session ends (`Session.expires_at`: the earliest of its own limit and the tenant's tool token). Past it,
|
|
196
|
+
* the conversation goes on in a new session from `getSession`, never with a fresh secret for this one.
|
|
197
|
+
*/
|
|
198
|
+
readonly expires_at?: string;
|
|
199
|
+
/** v1-draft.20: the tenant's greeting and chips for the session's mode (`Session.welcome`); no model call. */
|
|
200
|
+
readonly welcome?: Welcome;
|
|
186
201
|
};
|
|
187
202
|
/**
|
|
188
203
|
* Runs a callback before the next frame; `requestAnimationFrame` where there is one. Every listener, of the view and
|
|
@@ -265,6 +280,8 @@ declare class ConversationStore {
|
|
|
265
280
|
private readySession;
|
|
266
281
|
private cardOrder;
|
|
267
282
|
private kept;
|
|
283
|
+
/** Cards an earlier session drew, kept by a merged transcript: drawn as restored, taking no tap. */
|
|
284
|
+
private readonly inert;
|
|
268
285
|
private locale;
|
|
269
286
|
/** The request for a session in flight, to abort when it is no longer wanted. */
|
|
270
287
|
private fetching;
|
|
@@ -303,7 +320,12 @@ declare class ConversationStore {
|
|
|
303
320
|
/** In the background (`closeInBackground`): the connection is closed; the engine keeps what was sent, to resend. */
|
|
304
321
|
private suspended;
|
|
305
322
|
private suspend;
|
|
306
|
-
/**
|
|
323
|
+
/**
|
|
324
|
+
* Back in the foreground, or the person acted while away: a new connection to the same session, with a fresh secret
|
|
325
|
+
* when the backend gives one. A session that passed its end meanwhile is never reopened (a fresh secret cannot bring
|
|
326
|
+
* back an expired tool token): the person's action continues the conversation in a new session from `getSession`,
|
|
327
|
+
* and until then nothing connects.
|
|
328
|
+
*/
|
|
307
329
|
private wake;
|
|
308
330
|
/** The network as the app sees it; null when unknown. Back online reconnects a connection that gave up. */
|
|
309
331
|
setOnline(online: boolean | null): void;
|
|
@@ -348,6 +370,8 @@ declare class ConversationStore {
|
|
|
348
370
|
/** The person acted after the session ended idle, expired or at its limit: the next session takes what they sent. */
|
|
349
371
|
private continueIfEnded;
|
|
350
372
|
private reconnectIfDown;
|
|
373
|
+
/** A refusal already led to a new session, and none has been ready since: another refusal is no cue. */
|
|
374
|
+
private rolled;
|
|
351
375
|
private statusChanged;
|
|
352
376
|
private event;
|
|
353
377
|
/** The person's text that started a turn, while its message is on screen. */
|
|
@@ -376,10 +400,37 @@ declare class ConversationStore {
|
|
|
376
400
|
private key;
|
|
377
401
|
private restore;
|
|
378
402
|
private schedulePersist;
|
|
403
|
+
/** Past the session's end (`expires_at`), when the backend passed it on. */
|
|
404
|
+
private sessionOver;
|
|
379
405
|
private live;
|
|
380
406
|
private required;
|
|
381
407
|
}
|
|
382
408
|
/** One conversation with an agent, for any UI: see `ConversationStore`. */
|
|
383
409
|
declare function createConversation(options: ConversationOptions): ConversationStore;
|
|
384
410
|
|
|
385
|
-
|
|
411
|
+
/** A session request, with the mode it is for. */
|
|
412
|
+
interface ModeSessionRequest extends SessionRequest {
|
|
413
|
+
readonly mode: string;
|
|
414
|
+
}
|
|
415
|
+
interface ConversationsByModeOptions extends Omit<ConversationOptions, 'getSession'> {
|
|
416
|
+
/** Your backend creates the session for the mode's agent (its `subject.type`, its agent), with its service key. */
|
|
417
|
+
readonly getSession: (request: ModeSessionRequest) => Promise<ConversationSession>;
|
|
418
|
+
/** Every mode the app has, so `clear()` forgets each one's kept history, even one not opened since launch. */
|
|
419
|
+
readonly modes?: readonly string[];
|
|
420
|
+
}
|
|
421
|
+
interface ConversationsByMode {
|
|
422
|
+
/** The mode's store, made the first time it is asked for; the same store after that. */
|
|
423
|
+
get(mode: string): ConversationStore;
|
|
424
|
+
/**
|
|
425
|
+
* The mode the person switched to: its store, with every other mode's sent to the background (no pings; closed
|
|
426
|
+
* with `closeInBackground`). The kit then drives the selected one with the app's foreground and background.
|
|
427
|
+
*/
|
|
428
|
+
select(mode: string): ConversationStore;
|
|
429
|
+
/** Close every mode's store. */
|
|
430
|
+
dispose(): void;
|
|
431
|
+
/** Close every mode's store and forget what each kept on the device (sign-out). */
|
|
432
|
+
clear(): void;
|
|
433
|
+
}
|
|
434
|
+
declare function createConversationsByMode(options: ConversationsByModeOptions): ConversationsByMode;
|
|
435
|
+
|
|
436
|
+
export { AnswerInput, type AnswerProblem, type CardItem, type ConversationBanner, type ConversationOptions, type ConversationPersistence, type ConversationSession, type ConversationStatus, ConversationStore, type ConversationView, type ConversationsByMode, type ConversationsByModeOptions, type FrameScheduler, HeldCard, MAX_TEXT, type MessageItem, type MessageState, type ModeSessionRequest, ProposalOutcome, Question, type SessionReason, type SessionRequest, type StreamingText, type SystemEvent, type SystemItem, type TimelineItem, ToolLine, Welcome, answerProblem, codePoints, createConversation, createConversationsByMode, isAllowedLink, isValidLinkPrefix };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ConversationEngine, randomId, VinaverseSession, VinaverseClientError, isKnownCard } from '../chunk-
|
|
2
|
-
export { CONTRACT, VinaverseClientError, isKnownCard } from '../chunk-
|
|
1
|
+
import { ConversationEngine, randomId, VinaverseSession, VinaverseClientError, transcriptHasIds, isKnownCard } from '../chunk-MKUNSAMT.js';
|
|
2
|
+
export { CONTRACT, VinaverseClientError, isKnownCard } from '../chunk-MKUNSAMT.js';
|
|
3
3
|
|
|
4
4
|
// src/conversation/answers.ts
|
|
5
5
|
var CHOICE = /* @__PURE__ */ new Set([
|
|
@@ -153,7 +153,8 @@ function buildTimeline(input, previous) {
|
|
|
153
153
|
type: "card",
|
|
154
154
|
key: card.id,
|
|
155
155
|
card,
|
|
156
|
-
busy: input.busy.has(card.id),
|
|
156
|
+
busy: !input.inert?.has(card.id) && input.busy.has(card.id),
|
|
157
|
+
...input.inert?.has(card.id) ? { restored: true } : {},
|
|
157
158
|
...input.decisionErrors?.has(card.id) ? { decisionError: true } : {}
|
|
158
159
|
},
|
|
159
160
|
order: input.cardOrder?.get(card.id) ?? card.seq
|
|
@@ -185,17 +186,10 @@ function buildTimeline(input, previous) {
|
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
188
|
live.sort((a, b) => a.order - b.order);
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (!transcript)
|
|
193
|
-
for (const item of input.history) {
|
|
194
|
-
const current = liveByKey.get(item.key);
|
|
195
|
-
items.push(current ?? item);
|
|
196
|
-
if (current) placed.add(item.key);
|
|
197
|
-
}
|
|
198
|
-
for (const { item } of live) if (!placed.has(item.key)) items.push(item);
|
|
189
|
+
const items = mergeByKey(
|
|
190
|
+
input.transcriptSeen === true ? [] : input.history,
|
|
191
|
+
live.map((l) => l.item)
|
|
192
|
+
);
|
|
199
193
|
let same = items.length === previous.length;
|
|
200
194
|
const shared = items.map((item, i) => {
|
|
201
195
|
const prev = before.get(item.key);
|
|
@@ -205,6 +199,24 @@ function buildTimeline(input, previous) {
|
|
|
205
199
|
});
|
|
206
200
|
return same ? previous : shared;
|
|
207
201
|
}
|
|
202
|
+
function mergeByKey(history, live) {
|
|
203
|
+
if (!history.length) return [...live];
|
|
204
|
+
const items = [...history];
|
|
205
|
+
const at = (key) => items.findIndex((item) => item.key === key);
|
|
206
|
+
let last = -1;
|
|
207
|
+
live.forEach((item, i) => {
|
|
208
|
+
const found = at(item.key);
|
|
209
|
+
if (found >= 0) {
|
|
210
|
+
items[found] = item;
|
|
211
|
+
last = found;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const next = live.slice(i + 1).map((l) => at(l.key)).find((p) => p >= 0);
|
|
215
|
+
last = next === void 0 ? items.length : last >= 0 ? last + 1 : next;
|
|
216
|
+
items.splice(last, 0, item);
|
|
217
|
+
});
|
|
218
|
+
return items;
|
|
219
|
+
}
|
|
208
220
|
function messageItem(m) {
|
|
209
221
|
const role = m.role === "assistant" ? "agent" : m.role;
|
|
210
222
|
return {
|
|
@@ -279,6 +291,8 @@ var ConversationStore = class {
|
|
|
279
291
|
readySession = null;
|
|
280
292
|
cardOrder = /* @__PURE__ */ new Map();
|
|
281
293
|
kept = [];
|
|
294
|
+
/** Cards an earlier session drew, kept by a merged transcript: drawn as restored, taking no tap. */
|
|
295
|
+
inert = /* @__PURE__ */ new Set();
|
|
282
296
|
locale;
|
|
283
297
|
/** The request for a session in flight, to abort when it is no longer wanted. */
|
|
284
298
|
fetching = null;
|
|
@@ -320,6 +334,8 @@ var ConversationStore = class {
|
|
|
320
334
|
this.live();
|
|
321
335
|
if (this.session) {
|
|
322
336
|
if (this.session.status === "idle") this.session.connect();
|
|
337
|
+
else if (this.session.status === "unauthorized")
|
|
338
|
+
void this.resumeWith("resume");
|
|
323
339
|
return;
|
|
324
340
|
}
|
|
325
341
|
void this.ensureSession("open");
|
|
@@ -384,9 +400,19 @@ var ConversationStore = class {
|
|
|
384
400
|
session.close();
|
|
385
401
|
this.update();
|
|
386
402
|
}
|
|
387
|
-
/**
|
|
388
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Back in the foreground, or the person acted while away: a new connection to the same session, with a fresh secret
|
|
405
|
+
* when the backend gives one. A session that passed its end meanwhile is never reopened (a fresh secret cannot bring
|
|
406
|
+
* back an expired tool token): the person's action continues the conversation in a new session from `getSession`,
|
|
407
|
+
* and until then nothing connects.
|
|
408
|
+
*/
|
|
409
|
+
async wake(byPerson = false) {
|
|
389
410
|
const info = this.info;
|
|
411
|
+
if (info && this.sessionOver()) {
|
|
412
|
+
if (byPerson) await this.resumeWith("resume");
|
|
413
|
+
else this.update();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
390
416
|
this.suspended = false;
|
|
391
417
|
if (!info) return;
|
|
392
418
|
let next = info;
|
|
@@ -426,7 +452,7 @@ var ConversationStore = class {
|
|
|
426
452
|
}
|
|
427
453
|
if (this.suspended) {
|
|
428
454
|
this.early.set(id, text);
|
|
429
|
-
void this.wake();
|
|
455
|
+
void this.wake(true);
|
|
430
456
|
this.update();
|
|
431
457
|
return id;
|
|
432
458
|
}
|
|
@@ -436,6 +462,12 @@ var ConversationStore = class {
|
|
|
436
462
|
this.update();
|
|
437
463
|
return id;
|
|
438
464
|
}
|
|
465
|
+
if (this.sessionOver()) {
|
|
466
|
+
this.early.set(id, text);
|
|
467
|
+
void this.resumeWith("resume");
|
|
468
|
+
this.update();
|
|
469
|
+
return id;
|
|
470
|
+
}
|
|
439
471
|
this.nextId = id;
|
|
440
472
|
this.session.sendText(text);
|
|
441
473
|
this.nextId = null;
|
|
@@ -572,11 +604,21 @@ var ConversationStore = class {
|
|
|
572
604
|
}
|
|
573
605
|
open(info) {
|
|
574
606
|
this.info = info;
|
|
607
|
+
this.suspended = false;
|
|
608
|
+
const refreshSecret = this.options.refreshSecret;
|
|
575
609
|
const session = new VinaverseSession({
|
|
576
610
|
...this.options.transport,
|
|
577
611
|
session: info,
|
|
578
612
|
engine: this.engine,
|
|
579
|
-
...
|
|
613
|
+
...refreshSecret ? {
|
|
614
|
+
// Past the session's end a fresh secret cannot help: refused here, and the store rolls over instead.
|
|
615
|
+
refreshSecret: (id) => this.sessionOver() ? Promise.reject(
|
|
616
|
+
new VinaverseClientError(
|
|
617
|
+
"needs_new_session",
|
|
618
|
+
"This session has ended."
|
|
619
|
+
)
|
|
620
|
+
) : refreshSecret(id)
|
|
621
|
+
} : {},
|
|
580
622
|
createId: () => this.nextId ?? (this.options.createId ?? randomId)()
|
|
581
623
|
});
|
|
582
624
|
this.session = session;
|
|
@@ -584,7 +626,9 @@ var ConversationStore = class {
|
|
|
584
626
|
session.on("state", () => this.update()),
|
|
585
627
|
session.on("status", (status) => this.statusChanged(status)),
|
|
586
628
|
session.on("event", (event) => this.event(event)),
|
|
587
|
-
session.on("error", (error) =>
|
|
629
|
+
session.on("error", (error) => {
|
|
630
|
+
if (!this.sessionOver()) this.options.onError?.(error);
|
|
631
|
+
})
|
|
588
632
|
);
|
|
589
633
|
session.setForeground(this.foreground);
|
|
590
634
|
session.connect();
|
|
@@ -611,6 +655,10 @@ var ConversationStore = class {
|
|
|
611
655
|
this.resuming = this.fetchSession(reason).then((info) => {
|
|
612
656
|
this.resuming = null;
|
|
613
657
|
if (!info || this.disposed || !this.session) return;
|
|
658
|
+
if (this.session.status === "closed") {
|
|
659
|
+
this.open(info);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
614
662
|
this.info = info;
|
|
615
663
|
this.session.resume(info);
|
|
616
664
|
this.flushEarly();
|
|
@@ -625,10 +673,18 @@ var ConversationStore = class {
|
|
|
625
673
|
}
|
|
626
674
|
reconnectIfDown() {
|
|
627
675
|
const session = this.session;
|
|
628
|
-
if (session && session.status === "idle" && this.
|
|
676
|
+
if (session && session.status === "idle" && !this.sessionOver() && // past its end: the person's next action gets a new session
|
|
677
|
+
this.nextFrom(this.engine.state()) === null)
|
|
629
678
|
session.connect();
|
|
630
679
|
}
|
|
680
|
+
/** A refusal already led to a new session, and none has been ready since: another refusal is no cue. */
|
|
681
|
+
rolled = false;
|
|
631
682
|
statusChanged(status) {
|
|
683
|
+
if (status === "ready") this.rolled = false;
|
|
684
|
+
if (status === "unauthorized" && !this.rolled) {
|
|
685
|
+
this.rolled = true;
|
|
686
|
+
void this.resumeWith("resume");
|
|
687
|
+
}
|
|
632
688
|
if (status === "idle")
|
|
633
689
|
this.offlineAfterRetries = this.lastStatus === "reconnecting";
|
|
634
690
|
else if (status !== "reconnecting") this.offlineAfterRetries = false;
|
|
@@ -653,9 +709,18 @@ var ConversationStore = class {
|
|
|
653
709
|
const session = frame.session?.id;
|
|
654
710
|
const moved = typeof session === "string" && session !== this.readySession;
|
|
655
711
|
if (typeof session === "string") this.readySession = session;
|
|
656
|
-
if (frame.resumed === true && Array.isArray(frame.transcript)) {
|
|
712
|
+
if (frame.resumed === true && Array.isArray(frame.transcript) && !transcriptHasIds(frame.transcript)) {
|
|
657
713
|
this.transcriptSeen = true;
|
|
658
714
|
this.anchorCards(moved);
|
|
715
|
+
} else if (moved && frame.resumed === true) {
|
|
716
|
+
const pending = new Set(
|
|
717
|
+
(Array.isArray(frame.pending_cards) ? frame.pending_cards : []).map(
|
|
718
|
+
(c) => str(c.id)
|
|
719
|
+
)
|
|
720
|
+
);
|
|
721
|
+
for (const card of this.engine.state().cards)
|
|
722
|
+
if (pending.has(card.id)) this.inert.delete(card.id);
|
|
723
|
+
else this.inert.add(card.id);
|
|
659
724
|
}
|
|
660
725
|
return;
|
|
661
726
|
}
|
|
@@ -814,6 +879,7 @@ var ConversationStore = class {
|
|
|
814
879
|
transcriptSeen: this.transcriptSeen,
|
|
815
880
|
cardOrder: this.cardOrder,
|
|
816
881
|
kept: this.kept,
|
|
882
|
+
inert: this.inert,
|
|
817
883
|
system: this.system
|
|
818
884
|
},
|
|
819
885
|
prev?.items ?? NO_ITEMS
|
|
@@ -830,6 +896,7 @@ var ConversationStore = class {
|
|
|
830
896
|
next,
|
|
831
897
|
agent: sameAgent(prev?.agent, name, avatarUrl) ? prev.agent : { name, avatarUrl },
|
|
832
898
|
disclosure: s.disclosure ?? this.info?.disclosure ?? null,
|
|
899
|
+
welcome: this.info?.welcome ?? null,
|
|
833
900
|
takeover: s.takeover,
|
|
834
901
|
items,
|
|
835
902
|
activity: prev && sameTools(prev.activity, activity) ? prev.activity : activity,
|
|
@@ -936,6 +1003,11 @@ var ConversationStore = class {
|
|
|
936
1003
|
}, PERSIST_DELAY);
|
|
937
1004
|
}
|
|
938
1005
|
// ------------------------------------------------------------ guards
|
|
1006
|
+
/** Past the session's end (`expires_at`), when the backend passed it on. */
|
|
1007
|
+
sessionOver() {
|
|
1008
|
+
const end = this.info?.expires_at;
|
|
1009
|
+
return end !== void 0 && Date.now() >= Date.parse(end);
|
|
1010
|
+
}
|
|
939
1011
|
live() {
|
|
940
1012
|
if (this.disposed)
|
|
941
1013
|
throw new VinaverseClientError(
|
|
@@ -989,6 +1061,42 @@ function shallowEqual(a, b) {
|
|
|
989
1061
|
return a.status === b.status && a.banner === b.banner && a.next === b.next && a.agent.name === b.agent.name && a.agent.avatarUrl === b.agent.avatarUrl && a.disclosure === b.disclosure && a.takeover?.by === b.takeover?.by && a.takeover?.at === b.takeover?.at && a.takeover === null === (b.takeover === null) && a.items === b.items && a.activity === b.activity && a.responding === b.responding && a.openQuestion === b.openQuestion && a.answersNeedWebSocket === b.answersNeedWebSocket && a.turnsLeft === b.turnsLeft && a.history === b.history;
|
|
990
1062
|
}
|
|
991
1063
|
|
|
992
|
-
|
|
1064
|
+
// src/conversation/modes.ts
|
|
1065
|
+
function createConversationsByMode(options) {
|
|
1066
|
+
const { getSession, modes = [], ...shared } = options;
|
|
1067
|
+
const base = shared.persistenceKey ?? "vinaverse.conversation";
|
|
1068
|
+
const stores = /* @__PURE__ */ new Map();
|
|
1069
|
+
const get = (mode) => {
|
|
1070
|
+
let store = stores.get(mode);
|
|
1071
|
+
if (!store) {
|
|
1072
|
+
store = createConversation({
|
|
1073
|
+
...shared,
|
|
1074
|
+
persistenceKey: `${base}.${mode}`,
|
|
1075
|
+
getSession: (request) => getSession({ ...request, mode })
|
|
1076
|
+
});
|
|
1077
|
+
stores.set(mode, store);
|
|
1078
|
+
}
|
|
1079
|
+
return store;
|
|
1080
|
+
};
|
|
1081
|
+
return {
|
|
1082
|
+
get,
|
|
1083
|
+
select(mode) {
|
|
1084
|
+
for (const [other, store] of stores)
|
|
1085
|
+
if (other !== mode) store.setForeground(false);
|
|
1086
|
+
return get(mode);
|
|
1087
|
+
},
|
|
1088
|
+
dispose() {
|
|
1089
|
+
for (const store of stores.values()) store.dispose();
|
|
1090
|
+
stores.clear();
|
|
1091
|
+
},
|
|
1092
|
+
clear() {
|
|
1093
|
+
for (const mode of /* @__PURE__ */ new Set([...stores.keys(), ...modes]))
|
|
1094
|
+
get(mode).clear();
|
|
1095
|
+
stores.clear();
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
export { ConversationStore, MAX_TEXT, answerProblem, codePoints, createConversation, createConversationsByMode, isAllowedLink, isValidLinkPrefix };
|
|
993
1101
|
//# sourceMappingURL=index.js.map
|
|
994
1102
|
//# sourceMappingURL=index.js.map
|