@vinaverse/client 0.1.0-alpha.1 → 0.1.0-alpha.2
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/LICENSE +202 -0
- package/README.md +3 -1
- package/dist/conversation/index.cjs +97 -3
- package/dist/conversation/index.cjs.map +1 -1
- package/dist/conversation/index.d.cts +22 -0
- package/dist/conversation/index.d.ts +22 -0
- package/dist/conversation/index.js +97 -3
- package/dist/conversation/index.js.map +1 -1
- package/package.json +9 -9
|
@@ -219,6 +219,12 @@ interface ConversationOptions {
|
|
|
219
219
|
readonly createId?: () => string;
|
|
220
220
|
/** Something failed that no call returned: getting a session, an input that could not be delivered. */
|
|
221
221
|
readonly onError?: (error: unknown) => void;
|
|
222
|
+
/**
|
|
223
|
+
* Close the connection while the app is in the background, and open it again in the foreground (a backend that
|
|
224
|
+
* mints a client secret per connection: with `refreshSecret`, a fresh one is asked for first). Default false: the
|
|
225
|
+
* connection stays, with no pings, until the session idles out. Nothing typed or answered is lost either way.
|
|
226
|
+
*/
|
|
227
|
+
readonly closeInBackground?: boolean;
|
|
222
228
|
}
|
|
223
229
|
/** A text is at most 2000 code points (`TurnInput`). */
|
|
224
230
|
declare const MAX_TEXT = 2000;
|
|
@@ -249,6 +255,10 @@ declare class ConversationStore {
|
|
|
249
255
|
private historyState;
|
|
250
256
|
/** A resumed session.ready carried a transcript: the server's history replaced what the device kept. */
|
|
251
257
|
private transcriptSeen;
|
|
258
|
+
/** The session of the last session.ready: a card of another one takes no tap (§6). */
|
|
259
|
+
private readySession;
|
|
260
|
+
private cardOrder;
|
|
261
|
+
private kept;
|
|
252
262
|
private locale;
|
|
253
263
|
/** The request for a session in flight, to abort when it is no longer wanted. */
|
|
254
264
|
private fetching;
|
|
@@ -284,6 +294,11 @@ declare class ConversationStore {
|
|
|
284
294
|
setLocale(locale: string | undefined): void;
|
|
285
295
|
/** The app came to the foreground or went to the background (AppState, `visibilitychange`). */
|
|
286
296
|
setForeground(foreground: boolean): void;
|
|
297
|
+
/** In the background (`closeInBackground`): the connection is closed; the engine keeps what was sent, to resend. */
|
|
298
|
+
private suspended;
|
|
299
|
+
private suspend;
|
|
300
|
+
/** Back in the foreground: a new connection to the same session, with a fresh secret when the backend gives one. */
|
|
301
|
+
private wake;
|
|
287
302
|
/** The network as the app sees it; null when unknown. Back online reconnects a connection that gave up. */
|
|
288
303
|
setOnline(online: boolean | null): void;
|
|
289
304
|
/** Send a typed message; returns its `client_event_id`. Typed before a session exists, it is sent once one does. */
|
|
@@ -323,6 +338,13 @@ declare class ConversationStore {
|
|
|
323
338
|
private reconnectIfDown;
|
|
324
339
|
private statusChanged;
|
|
325
340
|
private event;
|
|
341
|
+
/**
|
|
342
|
+
* A resumed session's transcript replaced the messages, and a chat transcript carries text only: the engine drops
|
|
343
|
+
* the cards it drew, and draws the pending ones again at the end. Each card on screen is anchored just after the
|
|
344
|
+
* message it followed (by key; the transcript keeps the live keys), so a pending proposal stays where it was, and a
|
|
345
|
+
* card the transcript dropped stays drawn there; taking no tap when it belongs to an earlier session.
|
|
346
|
+
*/
|
|
347
|
+
private anchorCards;
|
|
326
348
|
/**
|
|
327
349
|
* Recomputes now (getSnapshot and getStreaming are always current) and notifies from the scheduler, at most once
|
|
328
350
|
* per frame, both channels in the same flush: a streamed reply that completes becomes its message in one frame.
|
|
@@ -219,6 +219,12 @@ interface ConversationOptions {
|
|
|
219
219
|
readonly createId?: () => string;
|
|
220
220
|
/** Something failed that no call returned: getting a session, an input that could not be delivered. */
|
|
221
221
|
readonly onError?: (error: unknown) => void;
|
|
222
|
+
/**
|
|
223
|
+
* Close the connection while the app is in the background, and open it again in the foreground (a backend that
|
|
224
|
+
* mints a client secret per connection: with `refreshSecret`, a fresh one is asked for first). Default false: the
|
|
225
|
+
* connection stays, with no pings, until the session idles out. Nothing typed or answered is lost either way.
|
|
226
|
+
*/
|
|
227
|
+
readonly closeInBackground?: boolean;
|
|
222
228
|
}
|
|
223
229
|
/** A text is at most 2000 code points (`TurnInput`). */
|
|
224
230
|
declare const MAX_TEXT = 2000;
|
|
@@ -249,6 +255,10 @@ declare class ConversationStore {
|
|
|
249
255
|
private historyState;
|
|
250
256
|
/** A resumed session.ready carried a transcript: the server's history replaced what the device kept. */
|
|
251
257
|
private transcriptSeen;
|
|
258
|
+
/** The session of the last session.ready: a card of another one takes no tap (§6). */
|
|
259
|
+
private readySession;
|
|
260
|
+
private cardOrder;
|
|
261
|
+
private kept;
|
|
252
262
|
private locale;
|
|
253
263
|
/** The request for a session in flight, to abort when it is no longer wanted. */
|
|
254
264
|
private fetching;
|
|
@@ -284,6 +294,11 @@ declare class ConversationStore {
|
|
|
284
294
|
setLocale(locale: string | undefined): void;
|
|
285
295
|
/** The app came to the foreground or went to the background (AppState, `visibilitychange`). */
|
|
286
296
|
setForeground(foreground: boolean): void;
|
|
297
|
+
/** In the background (`closeInBackground`): the connection is closed; the engine keeps what was sent, to resend. */
|
|
298
|
+
private suspended;
|
|
299
|
+
private suspend;
|
|
300
|
+
/** Back in the foreground: a new connection to the same session, with a fresh secret when the backend gives one. */
|
|
301
|
+
private wake;
|
|
287
302
|
/** The network as the app sees it; null when unknown. Back online reconnects a connection that gave up. */
|
|
288
303
|
setOnline(online: boolean | null): void;
|
|
289
304
|
/** Send a typed message; returns its `client_event_id`. Typed before a session exists, it is sent once one does. */
|
|
@@ -323,6 +338,13 @@ declare class ConversationStore {
|
|
|
323
338
|
private reconnectIfDown;
|
|
324
339
|
private statusChanged;
|
|
325
340
|
private event;
|
|
341
|
+
/**
|
|
342
|
+
* A resumed session's transcript replaced the messages, and a chat transcript carries text only: the engine drops
|
|
343
|
+
* the cards it drew, and draws the pending ones again at the end. Each card on screen is anchored just after the
|
|
344
|
+
* message it followed (by key; the transcript keeps the live keys), so a pending proposal stays where it was, and a
|
|
345
|
+
* card the transcript dropped stays drawn there; taking no tap when it belongs to an earlier session.
|
|
346
|
+
*/
|
|
347
|
+
private anchorCards;
|
|
326
348
|
/**
|
|
327
349
|
* Recomputes now (getSnapshot and getStreaming are always current) and notifies from the scheduler, at most once
|
|
328
350
|
* per frame, both channels in the same flush: a streamed reply that completes becomes its message in one frame.
|
|
@@ -156,10 +156,13 @@ function buildTimeline(input, previous) {
|
|
|
156
156
|
busy: input.busy.has(card.id),
|
|
157
157
|
...input.decisionErrors?.has(card.id) ? { decisionError: true } : {}
|
|
158
158
|
},
|
|
159
|
-
order: card.seq
|
|
159
|
+
order: input.cardOrder?.get(card.id) ?? card.seq
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
for (const { item, order } of input.system) live.push({ item, order });
|
|
163
|
+
const held = new Set(state.cards.map((c) => c.id));
|
|
164
|
+
for (const { item, order } of input.kept ?? [])
|
|
165
|
+
if (!held.has(item.key)) live.push({ item, order });
|
|
163
166
|
for (const [id, sent] of input.sent) {
|
|
164
167
|
const pending = pendingState(id, input);
|
|
165
168
|
if (pending === null) continue;
|
|
@@ -269,6 +272,10 @@ var ConversationStore = class {
|
|
|
269
272
|
historyState = "none";
|
|
270
273
|
/** A resumed session.ready carried a transcript: the server's history replaced what the device kept. */
|
|
271
274
|
transcriptSeen = false;
|
|
275
|
+
/** The session of the last session.ready: a card of another one takes no tap (§6). */
|
|
276
|
+
readySession = null;
|
|
277
|
+
cardOrder = /* @__PURE__ */ new Map();
|
|
278
|
+
kept = [];
|
|
272
279
|
locale;
|
|
273
280
|
/** The request for a session in flight, to abort when it is no longer wanted. */
|
|
274
281
|
fetching = null;
|
|
@@ -350,9 +357,49 @@ var ConversationStore = class {
|
|
|
350
357
|
/** The app came to the foreground or went to the background (AppState, `visibilitychange`). */
|
|
351
358
|
setForeground(foreground) {
|
|
352
359
|
this.foreground = foreground;
|
|
360
|
+
if (this.options.closeInBackground && !this.disposed) {
|
|
361
|
+
if (!foreground) {
|
|
362
|
+
this.suspend();
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (this.suspended) {
|
|
366
|
+
void this.wake();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
353
370
|
this.session?.setForeground(foreground);
|
|
354
371
|
if (foreground) this.reconnectIfDown();
|
|
355
372
|
}
|
|
373
|
+
/** In the background (`closeInBackground`): the connection is closed; the engine keeps what was sent, to resend. */
|
|
374
|
+
suspended = false;
|
|
375
|
+
suspend() {
|
|
376
|
+
const session = this.session;
|
|
377
|
+
if (!session || this.suspended || session.status === "ended") return;
|
|
378
|
+
this.suspended = true;
|
|
379
|
+
for (const off of this.unsubscribe) off();
|
|
380
|
+
this.unsubscribe = [];
|
|
381
|
+
session.close();
|
|
382
|
+
this.update();
|
|
383
|
+
}
|
|
384
|
+
/** Back in the foreground: a new connection to the same session, with a fresh secret when the backend gives one. */
|
|
385
|
+
async wake() {
|
|
386
|
+
const info = this.info;
|
|
387
|
+
this.suspended = false;
|
|
388
|
+
if (!info) return;
|
|
389
|
+
let next = info;
|
|
390
|
+
if (this.options.refreshSecret) {
|
|
391
|
+
try {
|
|
392
|
+
next = {
|
|
393
|
+
...info,
|
|
394
|
+
client_secret: await this.options.refreshSecret(info.id)
|
|
395
|
+
};
|
|
396
|
+
} catch (error) {
|
|
397
|
+
this.options.onError?.(error);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (this.disposed || this.suspended) return;
|
|
401
|
+
this.open(next);
|
|
402
|
+
}
|
|
356
403
|
/** The network as the app sees it; null when unknown. Back online reconnects a connection that gave up. */
|
|
357
404
|
setOnline(online) {
|
|
358
405
|
this.online = online;
|
|
@@ -374,6 +421,12 @@ var ConversationStore = class {
|
|
|
374
421
|
this.update();
|
|
375
422
|
return id;
|
|
376
423
|
}
|
|
424
|
+
if (this.suspended) {
|
|
425
|
+
this.early.set(id, text);
|
|
426
|
+
void this.wake();
|
|
427
|
+
this.update();
|
|
428
|
+
return id;
|
|
429
|
+
}
|
|
377
430
|
if (this.nextFrom(this.engine.state()) === "continue_here") {
|
|
378
431
|
this.early.set(id, text);
|
|
379
432
|
void this.resumeWith("continue_here");
|
|
@@ -575,10 +628,16 @@ var ConversationStore = class {
|
|
|
575
628
|
];
|
|
576
629
|
};
|
|
577
630
|
switch (frame.type) {
|
|
578
|
-
case "session.ready":
|
|
579
|
-
|
|
631
|
+
case "session.ready": {
|
|
632
|
+
const session = frame.session?.id;
|
|
633
|
+
const moved = typeof session === "string" && session !== this.readySession;
|
|
634
|
+
if (typeof session === "string") this.readySession = session;
|
|
635
|
+
if (frame.resumed === true && Array.isArray(frame.transcript)) {
|
|
580
636
|
this.transcriptSeen = true;
|
|
637
|
+
this.anchorCards(moved);
|
|
638
|
+
}
|
|
581
639
|
return;
|
|
640
|
+
}
|
|
582
641
|
// `at` is required (v1-draft.15); the same takeover announced again after a reconnect has the same `at`, so it
|
|
583
642
|
// draws one row. A frame without one still gets a row of its own.
|
|
584
643
|
case "takeover.started":
|
|
@@ -624,6 +683,38 @@ var ConversationStore = class {
|
|
|
624
683
|
return;
|
|
625
684
|
}
|
|
626
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* A resumed session's transcript replaced the messages, and a chat transcript carries text only: the engine drops
|
|
688
|
+
* the cards it drew, and draws the pending ones again at the end. Each card on screen is anchored just after the
|
|
689
|
+
* message it followed (by key; the transcript keeps the live keys), so a pending proposal stays where it was, and a
|
|
690
|
+
* card the transcript dropped stays drawn there; taking no tap when it belongs to an earlier session.
|
|
691
|
+
*/
|
|
692
|
+
anchorCards(sessionChanged) {
|
|
693
|
+
const state = this.engine.state();
|
|
694
|
+
const seqOf = new Map(state.messages.map((m) => [m.key, m.seq]));
|
|
695
|
+
const first = Math.min(...state.messages.map((m) => m.seq), 0);
|
|
696
|
+
const held = new Set(state.cards.map((c) => c.id));
|
|
697
|
+
const order = /* @__PURE__ */ new Map();
|
|
698
|
+
const kept = [];
|
|
699
|
+
let anchor = first - 1;
|
|
700
|
+
let step = 0;
|
|
701
|
+
for (const item of this.view.items) {
|
|
702
|
+
if (item.type === "message" && seqOf.has(item.key)) {
|
|
703
|
+
anchor = seqOf.get(item.key);
|
|
704
|
+
step = 0;
|
|
705
|
+
} else if (item.type === "card") {
|
|
706
|
+
const at = anchor + 0.5 + ++step / 1e3;
|
|
707
|
+
order.set(item.key, at);
|
|
708
|
+
if (!held.has(item.key))
|
|
709
|
+
kept.push({
|
|
710
|
+
item: sessionChanged ? { ...item, busy: false, restored: true } : item,
|
|
711
|
+
order: at
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
this.cardOrder = order;
|
|
716
|
+
this.kept = kept;
|
|
717
|
+
}
|
|
627
718
|
// ------------------------------------------------------------ the view
|
|
628
719
|
/**
|
|
629
720
|
* Recomputes now (getSnapshot and getStreaming are always current) and notifies from the scheduler, at most once
|
|
@@ -680,6 +771,8 @@ var ConversationStore = class {
|
|
|
680
771
|
busy: this.busy,
|
|
681
772
|
decisionErrors: this.decisionErrors,
|
|
682
773
|
transcriptSeen: this.transcriptSeen,
|
|
774
|
+
cardOrder: this.cardOrder,
|
|
775
|
+
kept: this.kept,
|
|
683
776
|
system: this.system
|
|
684
777
|
},
|
|
685
778
|
prev?.items ?? NO_ITEMS
|
|
@@ -709,6 +802,7 @@ var ConversationStore = class {
|
|
|
709
802
|
}
|
|
710
803
|
status() {
|
|
711
804
|
if (this.disposed) return "closed";
|
|
805
|
+
if (this.suspended) return "idle";
|
|
712
806
|
const session = this.session;
|
|
713
807
|
if (!session) {
|
|
714
808
|
if (this.failed) return "unavailable";
|