@kaltura-sdk/avatars 1.15.5 → 1.16.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/index.esm.js +152 -231
- package/package.json +2 -2
- package/src/lib/avatar-client-manager/machines/avatar-connection/actions/context/remove-stale-chat-entries-for-retry.d.ts +2 -0
- package/src/lib/avatar-client-manager/machines/avatar-connection/actions/context/test-utils/as-harness-action.d.ts +4 -0
- package/src/lib/avatar-client-manager/machines/avatar-connection/actions/context/track-internal-ask-echo.d.ts +2 -0
- package/src/lib/avatar-client-manager/machines/avatar-connection/avatar-connection-machine.d.ts +4 -1
- package/src/lib/avatar-client-manager/machines/avatar-connection/states/connected-state.d.ts +3 -3
- package/src/lib/avatar-client-manager/machines/avatar-connection/states/connected-states/agent-recovery-monitor-state.d.ts +2 -2
- package/src/lib/avatar-client-manager/machines/avatar-connection/states/connected-states/text-communication-state.d.ts +1 -1
- package/src/lib/avatar-client-manager/machines/avatar-connection/types/context.d.ts +1 -1
- package/src/lib/avatar-client-manager/machines/avatar-connection/types/schemas.d.ts +2 -0
- package/src/lib/avatar-client-manager/machines/avatar-connection/actions/context/append-user-text-entry.d.ts +0 -2
package/index.esm.js
CHANGED
|
@@ -6226,6 +6226,9 @@ const agentRawTextBaseDeltaSchema = z$1.object({
|
|
|
6226
6226
|
role: z$1.optional(z$1.string()),
|
|
6227
6227
|
type: z$1.optional(z$1.string()),
|
|
6228
6228
|
content: z$1.optional(z$1.union([z$1.string(), z$1.record(z$1.string(), z$1.unknown())])),
|
|
6229
|
+
// present on the bootstrap 'think' segment - the user's turn text, echoed
|
|
6230
|
+
// there instead of a dedicated 'human' segment type.
|
|
6231
|
+
human: z$1.optional(z$1.string()),
|
|
6229
6232
|
segmentNumber: z$1.optional(z$1.number()),
|
|
6230
6233
|
et: z$1.optional(z$1.number()),
|
|
6231
6234
|
segmentStart: z$1.optional(z$1.boolean()),
|
|
@@ -6383,7 +6386,7 @@ const textCommunicationState = {
|
|
|
6383
6386
|
}) => ({
|
|
6384
6387
|
message: event.text
|
|
6385
6388
|
})
|
|
6386
|
-
}, '
|
|
6389
|
+
}, 'trackInternalAskEcho']
|
|
6387
6390
|
}
|
|
6388
6391
|
},
|
|
6389
6392
|
states: {
|
|
@@ -7025,13 +7028,13 @@ const agentRecoveryMonitorState = {
|
|
|
7025
7028
|
actions: 'resetAgentRecoveryRetryCount'
|
|
7026
7029
|
}, {
|
|
7027
7030
|
guard: 'hasMoreRecoveryRetries',
|
|
7028
|
-
actions: ['incrementAgentRecoveryRetryCount', 'sendReprocessTurn']
|
|
7031
|
+
actions: ['removeStaleChatEntriesForRetry', 'incrementAgentRecoveryRetryCount', 'sendReprocessTurn']
|
|
7029
7032
|
}, {
|
|
7030
7033
|
guard: 'justExceededRecoveryRetries',
|
|
7031
7034
|
actions: ['incrementAgentRecoveryRetryCount', 'showAgentRecoveryExhaustedNotification']
|
|
7032
7035
|
}],
|
|
7033
7036
|
[AvatarConnectionMachineEventTypes.RequestAgentTurnRecovery]: {
|
|
7034
|
-
actions: 'sendReprocessTurn'
|
|
7037
|
+
actions: ['removeStaleChatEntriesForRetry', 'sendReprocessTurn']
|
|
7035
7038
|
}
|
|
7036
7039
|
},
|
|
7037
7040
|
invoke: {
|
|
@@ -20626,50 +20629,63 @@ const appendAvatarChatEntry = assign$1(({
|
|
|
20626
20629
|
};
|
|
20627
20630
|
});
|
|
20628
20631
|
|
|
20632
|
+
// A retry re-runs the same turn (same userInput -> same turnId) under a new
|
|
20633
|
+
// speechId/messageId, re-emitting both the user's `human` segment and the
|
|
20634
|
+
// avatar answer — both go stale, not just the avatar one.
|
|
20635
|
+
//
|
|
20636
|
+
// Matched on messageId, not turnId: the server can reuse the same turnId
|
|
20637
|
+
// across distinct, already-answered exchanges (e.g. repeated "="), so
|
|
20638
|
+
// turnId would wipe an unrelated earlier exchange too. messageId is
|
|
20639
|
+
// constant per exchange and never reused (confirmed against
|
|
20640
|
+
// kaltura_agent_executer.py), so it removes exactly the retried exchange.
|
|
20641
|
+
const removeStaleChatEntriesForRetry = assign$1(({
|
|
20642
|
+
context,
|
|
20643
|
+
event
|
|
20644
|
+
}) => {
|
|
20645
|
+
assertEvent(event, [AvatarConnectionMachineEventTypes.RequestAgentTurnRecovery, AvatarConnectionMachineEventTypes.AgentRecoveryTurnCompleted]);
|
|
20646
|
+
return {
|
|
20647
|
+
chatLog: context.chatLog.filter(entry => entry.messageId !== event.messageId)
|
|
20648
|
+
};
|
|
20649
|
+
});
|
|
20650
|
+
|
|
20651
|
+
// Upserts by id - a resend superseding a still-open turn keeps that turn's
|
|
20652
|
+
// original entry id (see on-chat-log.ts openTurnEntryIds), so this replaces
|
|
20653
|
+
// the entry in place instead of duplicating it.
|
|
20629
20654
|
const appendUserChatEntry = assign$1(({
|
|
20630
20655
|
context,
|
|
20631
20656
|
event
|
|
20632
20657
|
}) => {
|
|
20633
20658
|
assertEvent(event, AvatarConnectionMachineEventTypes.UserVoiceTurnReceived);
|
|
20634
|
-
|
|
20635
|
-
|
|
20636
|
-
|
|
20637
|
-
|
|
20638
|
-
|
|
20659
|
+
if (context.awaitingInternalAskEcho) {
|
|
20660
|
+
// Invisible kickoff prompt's own echo - consume without surfacing it.
|
|
20661
|
+
return {
|
|
20662
|
+
awaitingInternalAskEcho: false
|
|
20663
|
+
};
|
|
20664
|
+
}
|
|
20665
|
+
const existingIndex = context.chatLog.findIndex(entry => entry.id === event.entry.id);
|
|
20666
|
+
if (existingIndex === -1) {
|
|
20639
20667
|
return {
|
|
20640
|
-
|
|
20668
|
+
chatLog: [...context.chatLog, event.entry]
|
|
20641
20669
|
};
|
|
20642
20670
|
}
|
|
20671
|
+
const chatLog = [...context.chatLog];
|
|
20672
|
+
chatLog[existingIndex] = event.entry;
|
|
20643
20673
|
return {
|
|
20644
|
-
chatLog
|
|
20674
|
+
chatLog
|
|
20645
20675
|
};
|
|
20646
20676
|
});
|
|
20647
20677
|
|
|
20648
|
-
|
|
20678
|
+
// No optimistic entry for askViaText - chat log only reflects the server's
|
|
20679
|
+
// echoed `human` text (see on-chat-log.ts). The internal kickoff prompt
|
|
20680
|
+
// goes through that same echo, so its next one must be suppressed.
|
|
20681
|
+
const trackInternalAskEcho = assign$1(({
|
|
20649
20682
|
context,
|
|
20650
20683
|
event
|
|
20651
20684
|
}) => {
|
|
20652
20685
|
assertEvent(event, AvatarConnectionMachineEventTypes.AskViaText);
|
|
20653
|
-
|
|
20654
|
-
text,
|
|
20655
|
-
internal
|
|
20656
|
-
} = event;
|
|
20657
|
-
// raiseAskViaTextKickoff marks its auto-sent ask `internal` — it's a bootstrap prompt,
|
|
20658
|
-
// not something the user said, so keep it out of the visible chat log. Still record it
|
|
20659
|
-
// in pendingTextInputs so the ASR echo of it (agentTurnToTalk) is deduped the same way.
|
|
20660
|
-
if (internal) {
|
|
20661
|
-
return {
|
|
20662
|
-
pendingTextInputs: [...context.pendingTextInputs, text]
|
|
20663
|
-
};
|
|
20664
|
-
}
|
|
20665
|
-
const entry = {
|
|
20666
|
-
id: crypto.randomUUID(),
|
|
20667
|
-
role: 'user',
|
|
20668
|
-
text
|
|
20669
|
-
};
|
|
20686
|
+
if (!event.internal) return {};
|
|
20670
20687
|
return {
|
|
20671
|
-
|
|
20672
|
-
pendingTextInputs: [...context.pendingTextInputs, text]
|
|
20688
|
+
awaitingInternalAskEcho: true
|
|
20673
20689
|
};
|
|
20674
20690
|
});
|
|
20675
20691
|
|
|
@@ -20720,255 +20736,159 @@ const source$a = createTinyPubsubEventsSource(websocketMessage, {
|
|
|
20720
20736
|
agent_raw_text: {
|
|
20721
20737
|
schema: agentRawTextSchema
|
|
20722
20738
|
},
|
|
20723
|
-
agentTurnToTalk: {
|
|
20724
|
-
schema: agentTurnToTalkSchema
|
|
20725
|
-
},
|
|
20726
20739
|
generatingSpeech: {
|
|
20727
20740
|
schema: generatingSpeechSchema
|
|
20741
|
+
},
|
|
20742
|
+
agentTurnToTalk: {
|
|
20743
|
+
schema: agentTurnToTalkSchema
|
|
20728
20744
|
}
|
|
20729
20745
|
}, {
|
|
20730
20746
|
queueWhilePending: true
|
|
20731
20747
|
});
|
|
20732
|
-
//
|
|
20733
|
-
// =============
|
|
20734
|
-
// Builds the alternating user/avatar chat log from two independent, unordered
|
|
20735
|
-
// websocket event streams that share no common id:
|
|
20736
|
-
// - agentTurnToTalk: fires once per USER turn, carries the ASR transcript
|
|
20737
|
-
// (userTranscription). Empty userTranscription means a proactive/opening
|
|
20738
|
-
// turn (avatar speaks first, no preceding user input).
|
|
20739
|
-
// - agent_raw_text: streams the AVATAR's reply for a turn as chunked deltas
|
|
20740
|
-
// keyed by speechId, terminated by a delta with segmentEnd=true.
|
|
20741
|
-
// delta.type === 'avatar' or 'avatar-filler' chunks are chat text (filler
|
|
20742
|
-
// is spoken content too, e.g. "Let me check that for you..." streamed
|
|
20743
|
-
// ahead of the main answer while a tool call runs); 'think'/'tool'/'share'
|
|
20744
|
-
// chunks are filtered out here.
|
|
20748
|
+
// Builds alternating user/avatar chat log from agent_raw_text, keyed by turnId.
|
|
20745
20749
|
//
|
|
20746
|
-
//
|
|
20747
|
-
//
|
|
20748
|
-
//
|
|
20749
|
-
//
|
|
20750
|
-
//
|
|
20751
|
-
//
|
|
20752
|
-
//
|
|
20753
|
-
//
|
|
20754
|
-
//
|
|
20755
|
-
//
|
|
20756
|
-
//
|
|
20757
|
-
// emits instead of queueing.
|
|
20758
|
-
// - resolvedTurnIds: guards against a single turn producing more than one
|
|
20759
|
-
// avatar segmentEnd — either multiple segments under one speechId (e.g. a
|
|
20760
|
-
// tool call splits the reply into a chunk before and a chunk after the
|
|
20761
|
-
// tool result), or multiple speechIds sharing one turnId (a reprocessTurn
|
|
20762
|
-
// retry regenerates the whole answer under a new speechId but keeps the
|
|
20763
|
-
// same turnId). Only the FIRST resolved segment/attempt for a turnId
|
|
20764
|
-
// goes through the queue/counter above; later ones are known to already
|
|
20765
|
-
// belong to a claimed turn, so they're emitted immediately without
|
|
20766
|
-
// consuming another slot.
|
|
20750
|
+
// delta.type:
|
|
20751
|
+
// - 'think' with `human` — user's turn text. A resend while the turnId's
|
|
20752
|
+
// exchange is still unanswered cancels it server-side with no signal to
|
|
20753
|
+
// the client (conversation-manager's AgentAdapter.invalidateTask), gets
|
|
20754
|
+
// its own real messageId, and supersedes it in place client-side via
|
|
20755
|
+
// openTurnEntryIds below. A LATER, already-answered exchange reusing
|
|
20756
|
+
// the same turnId is unrelated and gets a fresh entry instead.
|
|
20757
|
+
// - 'avatar' / 'avatar-filler' — chat text, chunked by speechId, closed by
|
|
20758
|
+
// segmentEnd=true. Filler is spoken content too (tool-call ack lines).
|
|
20759
|
+
// - 'user-interruption' — turn aborted server-side. Flushes accumulated
|
|
20760
|
+
// avatar text and evicts that turnId:speechId buffer only.
|
|
20767
20761
|
//
|
|
20768
|
-
//
|
|
20769
|
-
// AvatarChatEntryReceived (see appendUserChatEntry/appendAvatarChatEntry,
|
|
20770
|
-
// which just push onto context.chatLog in the order events arrive here).
|
|
20762
|
+
// A retry reuses the turnId under a new speechId, tracked independently.
|
|
20771
20763
|
//
|
|
20772
|
-
//
|
|
20773
|
-
//
|
|
20774
|
-
//
|
|
20775
|
-
// accumulated by speechId and flushed as its own avatar-only entry.
|
|
20764
|
+
// `messageId` is constant per exchange, unique across exchanges even when
|
|
20765
|
+
// turnId repeats (confirmed against kaltura_agent_executer.py) —
|
|
20766
|
+
// remove-stale-chat-entries-for-retry.ts matches on it for retry cleanup.
|
|
20776
20767
|
//
|
|
20777
|
-
//
|
|
20778
|
-
//
|
|
20779
|
-
//
|
|
20768
|
+
// Opening phrase (resumed thread, avatar speaks first): never sent as a
|
|
20769
|
+
// normal turnId-keyed segment — `init_response` fires too early (before
|
|
20770
|
+
// this actor's setHandler(), dropped by the pubsub queue reset), so
|
|
20771
|
+
// `generatingSpeech` is the text source instead, flushed by
|
|
20772
|
+
// `agentTurnToTalk` (userTranscription === ''). BLANK_TEXT_MARKER is the
|
|
20773
|
+
// server's "no opening phrase configured" sentinel, skipped.
|
|
20780
20774
|
const onChatLog = fromCallback(({
|
|
20781
20775
|
input,
|
|
20782
20776
|
sendBack
|
|
20783
20777
|
}) => {
|
|
20784
|
-
// Accumulate
|
|
20778
|
+
// Accumulate avatar text by `${turnId}:${speechId}` — each speechId
|
|
20779
|
+
// closes independently (e.g. a filler segment vs the main answer).
|
|
20785
20780
|
const pending = new Map();
|
|
20786
|
-
// speechId of the turn currently accumulating chunks.
|
|
20787
|
-
// When a new speechId is seen, the previous turn's incomplete text is abandoned (interrupted).
|
|
20788
|
-
let currentSpeechId = null;
|
|
20789
|
-
// Avatar answers whose segmentEnd fired before their matching agentTurnToTalk arrived.
|
|
20790
|
-
// Completed avatar answers waiting for their paired agentTurnToTalk.
|
|
20791
|
-
const awaitingUserTurnQueue = [];
|
|
20792
|
-
// agentTurnToTalk events (with transcription) whose matching avatar answer hasn't
|
|
20793
|
-
// completed yet. Talk-vs-avatar-stream ordering isn't fixed — the ASR transcript can
|
|
20794
|
-
// beat or lag the avatar's own text stream on a per-turn basis (races harder when the
|
|
20795
|
-
// answer carries tool content) — so pairing is done via this queue+counter rather than
|
|
20796
|
-
// a single slot, which a later turn's speechId change would otherwise clobber.
|
|
20797
|
-
//
|
|
20798
|
-
// Known risk (backend, not fixable here — agentTurnToTalk carries no turnId/speechId
|
|
20799
|
-
// to correlate by id, only arrival order): both sides can drift out of 1:1 and desync
|
|
20800
|
-
// pairing for a later, unrelated turn instead of the intended one.
|
|
20801
|
-
// - Queue can strand an entry forever if its own agentTurnToTalk never fires — e.g.
|
|
20802
|
-
// conversation-manager's ttv.sayTurn throwing on the first chunk, or the
|
|
20803
|
-
// first-sentence punctuation gate in SpeechBuffer.append swallowing a short
|
|
20804
|
-
// punctuation-less reply. Raw text still gets forwarded; the talk pipeline doesn't run.
|
|
20805
|
-
// - Counter can stay inflated forever if agentTurnToTalk fires but its avatar segment
|
|
20806
|
-
// never reaches segmentEnd — e.g. onUserInterrupted not aborting AgentAdapter's loop,
|
|
20807
|
-
// dropping buffered avatar text without ever calling finish()/completeStream().
|
|
20808
|
-
// Real fix is backend: always run finish()/completeStream() on error/interrupt, and
|
|
20809
|
-
// populate turnId on agentTurnToTalk (schema already has the field, unused) so pairing
|
|
20810
|
-
// can be done by id instead of arrival order.
|
|
20811
|
-
let pendingUserTurnCount = 0;
|
|
20812
|
-
// turnIds whose first segment already claimed a pairing slot (queued or emitted).
|
|
20813
|
-
// Keyed by turnId, NOT speechId, because a single logical turn can produce more than
|
|
20814
|
-
// one speechId:
|
|
20815
|
-
// - Genie can stream a reply as multiple avatar segments under one speechId (e.g. a
|
|
20816
|
-
// tool call splits the answer into a segment before and a segment after the tool
|
|
20817
|
-
// result) — those segments share both speechId and turnId.
|
|
20818
|
-
// - A reprocessTurn retry (sendReprocessTurn, triggered by agentRecoveryMonitor or a
|
|
20819
|
-
// manual "retry" action) makes the backend regenerate the FULL answer under a NEW
|
|
20820
|
-
// speechId while keeping the SAME turnId — confirmed in logs: two distinct speechIds
|
|
20821
|
-
// sharing turnId "c83e031d-..." after a reprocessTurn. Guarding on speechId alone
|
|
20822
|
-
// would treat the retry's answer as an unrelated new turn.
|
|
20823
|
-
// Either way, the extra segment/retry belongs to a turn that's already paired, so it
|
|
20824
|
-
// must bypass the queue/counter entirely; otherwise it would consume the next,
|
|
20825
|
-
// unrelated user turn's pairing slot and desync every pairing for the rest of the
|
|
20826
|
-
// conversation. Intentionally never cleared: a later segment/retry for the same turnId
|
|
20827
|
-
// can arrive arbitrarily long after the first (slow tool call, slow retry, other turns
|
|
20828
|
-
// in between), so "already claimed" must be remembered for the rest of the session,
|
|
20829
|
-
// same lifetime as awaitingUserTurnQueue/pendingUserTurnCount above. Actor-scoped, dies
|
|
20830
|
-
// with it.
|
|
20831
|
-
const resolvedTurnIds = new Set();
|
|
20832
|
-
// Opening phrase accumulation: for resumed-thread sessions where init_response is not sent,
|
|
20833
|
-
// generatingSpeech chunks arrive before the first user turn — accumulate them keyed by speechId
|
|
20834
|
-
// and flush on the first proactive agentTurnToTalk (empty userTranscription).
|
|
20835
|
-
let openingPhraseSpeechId = null;
|
|
20836
|
-
const openingPhraseChunks = new Map();
|
|
20837
|
-
let openingPhraseEmitted = false;
|
|
20838
20781
|
let avatarEntrySequence = 0;
|
|
20782
|
+
// turnId -> entry id of its current unanswered exchange - reused across
|
|
20783
|
+
// superseding resends, cleared on a real answer (see top-of-file comment).
|
|
20784
|
+
const openTurnEntryIds = new Map();
|
|
20839
20785
|
const createAvatarEntryId = (messageId, speechId) => {
|
|
20840
20786
|
avatarEntrySequence += 1;
|
|
20841
20787
|
return `${messageId}:${speechId}:${avatarEntrySequence}`;
|
|
20842
20788
|
};
|
|
20843
|
-
|
|
20844
|
-
|
|
20845
|
-
|
|
20846
|
-
|
|
20847
|
-
type: AvatarConnectionMachineEventTypes.AvatarChatEntryReceived,
|
|
20848
|
-
entry
|
|
20849
|
-
});
|
|
20850
|
-
}
|
|
20851
|
-
};
|
|
20789
|
+
// Opening phrase accumulation — see top-of-file comment.
|
|
20790
|
+
let openingPhraseSpeechId = null;
|
|
20791
|
+
let openingPhraseText = '';
|
|
20792
|
+
let openingPhraseSettled = false;
|
|
20852
20793
|
return createTinyPubsubEventsHandler({
|
|
20853
20794
|
source: source$a,
|
|
20854
20795
|
logger: input.logger,
|
|
20855
20796
|
handlers: {
|
|
20856
20797
|
agent_raw_text: payload => {
|
|
20857
|
-
var _a, _b;
|
|
20798
|
+
var _a, _b, _c;
|
|
20858
20799
|
try {
|
|
20859
20800
|
const delta = JSON.parse(payload.delta);
|
|
20860
20801
|
const result = agentRawTextDeltaSchema.safeParse(delta);
|
|
20861
20802
|
if (!result.success) return;
|
|
20862
20803
|
const parsed = result.data;
|
|
20863
|
-
|
|
20864
|
-
|
|
20865
|
-
|
|
20866
|
-
|
|
20867
|
-
|
|
20868
|
-
const speechId = payload.speechId;
|
|
20869
|
-
// New speech started — abandon prior speech's incomplete accumulation.
|
|
20870
|
-
// Does NOT touch awaitingUserTurnQueue/pendingUserTurnCount: those are completed,
|
|
20871
|
-
// already-paired-or-waiting turns and must survive across speechId changes.
|
|
20872
|
-
if (speechId !== currentSpeechId) {
|
|
20873
|
-
pending.delete(currentSpeechId !== null && currentSpeechId !== void 0 ? currentSpeechId : '');
|
|
20874
|
-
currentSpeechId = speechId;
|
|
20875
|
-
}
|
|
20876
|
-
if (typeof parsed.content === 'string' && parsed.content) {
|
|
20877
|
-
pending.set(speechId, ((_a = pending.get(speechId)) !== null && _a !== void 0 ? _a : '') + parsed.content);
|
|
20878
|
-
}
|
|
20879
|
-
if (parsed.segmentEnd) {
|
|
20880
|
-
const text = (_b = pending.get(speechId)) !== null && _b !== void 0 ? _b : '';
|
|
20881
|
-
pending.delete(speechId);
|
|
20882
|
-
if (!text) return;
|
|
20883
|
-
const entry = {
|
|
20884
|
-
id: createAvatarEntryId(parsed.messageId, speechId),
|
|
20885
|
-
role: 'avatar',
|
|
20886
|
-
text,
|
|
20887
|
-
turnId: speechId
|
|
20888
|
-
};
|
|
20889
|
-
if (resolvedTurnIds.has(payload.turnId)) {
|
|
20890
|
-
// Not this turn's first segment/attempt — its pairing slot is already claimed
|
|
20891
|
-
// (multi-segment reply, or a reprocessTurn retry answering the same turnId).
|
|
20804
|
+
const key = `${payload.turnId}:${payload.speechId}`;
|
|
20805
|
+
if (parsed.type === 'think') {
|
|
20806
|
+
if (parsed.human) {
|
|
20807
|
+
const id = (_a = openTurnEntryIds.get(payload.turnId)) !== null && _a !== void 0 ? _a : parsed.messageId;
|
|
20808
|
+
openTurnEntryIds.set(payload.turnId, id);
|
|
20892
20809
|
sendBack({
|
|
20893
|
-
type: AvatarConnectionMachineEventTypes.
|
|
20894
|
-
entry
|
|
20895
|
-
|
|
20896
|
-
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
pendingUserTurnCount -= 1;
|
|
20902
|
-
sendBack({
|
|
20903
|
-
type: AvatarConnectionMachineEventTypes.AvatarChatEntryReceived,
|
|
20904
|
-
entry
|
|
20810
|
+
type: AvatarConnectionMachineEventTypes.UserVoiceTurnReceived,
|
|
20811
|
+
entry: {
|
|
20812
|
+
id,
|
|
20813
|
+
role: 'user',
|
|
20814
|
+
text: parsed.human,
|
|
20815
|
+
turnId: payload.turnId,
|
|
20816
|
+
messageId: parsed.messageId
|
|
20817
|
+
}
|
|
20905
20818
|
});
|
|
20906
|
-
} else {
|
|
20907
|
-
// agentTurnToTalk hasn't arrived yet — queue until it does
|
|
20908
|
-
awaitingUserTurnQueue.push(entry);
|
|
20909
20819
|
}
|
|
20820
|
+
return;
|
|
20910
20821
|
}
|
|
20911
|
-
|
|
20912
|
-
|
|
20913
|
-
|
|
20914
|
-
|
|
20915
|
-
generatingSpeech: payload => {
|
|
20916
|
-
var _a;
|
|
20917
|
-
if (openingPhraseEmitted) return;
|
|
20918
|
-
// Server sends this literal sentinel instead of real speech when no opening phrase
|
|
20919
|
-
// is configured — must not leak into the chat log as visible avatar text.
|
|
20920
|
-
if (payload.text === BLANK_TEXT_MARKER) return;
|
|
20921
|
-
if (openingPhraseSpeechId !== null && openingPhraseSpeechId !== payload.speechId) return;
|
|
20922
|
-
openingPhraseSpeechId = payload.speechId;
|
|
20923
|
-
openingPhraseChunks.set(payload.speechId, ((_a = openingPhraseChunks.get(payload.speechId)) !== null && _a !== void 0 ? _a : '') + payload.text);
|
|
20924
|
-
},
|
|
20925
|
-
agentTurnToTalk: payload => {
|
|
20926
|
-
var _a;
|
|
20927
|
-
const {
|
|
20928
|
-
userTranscription,
|
|
20929
|
-
turnId
|
|
20930
|
-
} = payload;
|
|
20931
|
-
if (!userTranscription) {
|
|
20932
|
-
// Proactive turn — flush opening phrase built from generatingSpeech chunks if present
|
|
20933
|
-
if (!openingPhraseEmitted && openingPhraseSpeechId) {
|
|
20934
|
-
const text = (_a = openingPhraseChunks.get(openingPhraseSpeechId)) !== null && _a !== void 0 ? _a : '';
|
|
20935
|
-
openingPhraseChunks.clear();
|
|
20936
|
-
openingPhraseEmitted = true;
|
|
20822
|
+
if (parsed.type === 'user-interruption') {
|
|
20823
|
+
openTurnEntryIds.delete(payload.turnId);
|
|
20824
|
+
const text = pending.get(key);
|
|
20825
|
+
pending.delete(key);
|
|
20937
20826
|
if (text) {
|
|
20938
20827
|
sendBack({
|
|
20939
20828
|
type: AvatarConnectionMachineEventTypes.AvatarChatEntryReceived,
|
|
20940
20829
|
entry: {
|
|
20941
|
-
id:
|
|
20830
|
+
id: createAvatarEntryId(parsed.messageId, payload.speechId),
|
|
20942
20831
|
role: 'avatar',
|
|
20943
20832
|
text,
|
|
20944
|
-
turnId:
|
|
20833
|
+
turnId: payload.turnId,
|
|
20834
|
+
messageId: parsed.messageId
|
|
20945
20835
|
}
|
|
20946
20836
|
});
|
|
20947
20837
|
}
|
|
20838
|
+
return;
|
|
20948
20839
|
}
|
|
20949
|
-
|
|
20950
|
-
|
|
20840
|
+
if (parsed.type !== 'avatar' && parsed.type !== 'avatar-filler') return;
|
|
20841
|
+
if (typeof parsed.content === 'string' && parsed.content) {
|
|
20842
|
+
pending.set(key, ((_b = pending.get(key)) !== null && _b !== void 0 ? _b : '') + parsed.content);
|
|
20843
|
+
}
|
|
20844
|
+
if (parsed.segmentEnd) {
|
|
20845
|
+
const text = (_c = pending.get(key)) !== null && _c !== void 0 ? _c : '';
|
|
20846
|
+
pending.delete(key);
|
|
20847
|
+
if (!text) return;
|
|
20848
|
+
// Real answer closes the exchange - a later reuse of this
|
|
20849
|
+
// turnId (e.g. repeated "=") must get a fresh entry, not this one.
|
|
20850
|
+
if (parsed.type === 'avatar') {
|
|
20851
|
+
openTurnEntryIds.delete(payload.turnId);
|
|
20852
|
+
}
|
|
20853
|
+
sendBack({
|
|
20854
|
+
type: AvatarConnectionMachineEventTypes.AvatarChatEntryReceived,
|
|
20855
|
+
entry: {
|
|
20856
|
+
id: createAvatarEntryId(parsed.messageId, payload.speechId),
|
|
20857
|
+
role: 'avatar',
|
|
20858
|
+
text,
|
|
20859
|
+
turnId: payload.turnId,
|
|
20860
|
+
messageId: parsed.messageId
|
|
20861
|
+
}
|
|
20862
|
+
});
|
|
20863
|
+
}
|
|
20864
|
+
} catch (_d) {
|
|
20865
|
+
// invalid JSON delta — ignore
|
|
20866
|
+
}
|
|
20867
|
+
},
|
|
20868
|
+
generatingSpeech: payload => {
|
|
20869
|
+
if (openingPhraseSettled) return;
|
|
20870
|
+
// No opening phrase configured — must not leak into the chat log.
|
|
20871
|
+
if (payload.text === BLANK_TEXT_MARKER) return;
|
|
20872
|
+
if (openingPhraseSpeechId !== null && openingPhraseSpeechId !== payload.speechId) {
|
|
20951
20873
|
return;
|
|
20952
20874
|
}
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
20956
|
-
|
|
20957
|
-
//
|
|
20875
|
+
openingPhraseSpeechId = payload.speechId;
|
|
20876
|
+
openingPhraseText += payload.text;
|
|
20877
|
+
},
|
|
20878
|
+
agentTurnToTalk: payload => {
|
|
20879
|
+
// Only the first occurrence decides the opening — later turns also fire this.
|
|
20880
|
+
if (openingPhraseSettled) return;
|
|
20881
|
+
openingPhraseSettled = true;
|
|
20882
|
+
if (payload.userTranscription || !openingPhraseText) return;
|
|
20958
20883
|
sendBack({
|
|
20959
|
-
type: AvatarConnectionMachineEventTypes.
|
|
20884
|
+
type: AvatarConnectionMachineEventTypes.AvatarChatEntryReceived,
|
|
20960
20885
|
entry: {
|
|
20961
|
-
id:
|
|
20962
|
-
role: '
|
|
20963
|
-
text:
|
|
20964
|
-
turnId
|
|
20886
|
+
id: openingPhraseSpeechId !== null && openingPhraseSpeechId !== void 0 ? openingPhraseSpeechId : 'init_response',
|
|
20887
|
+
role: 'avatar',
|
|
20888
|
+
text: openingPhraseText,
|
|
20889
|
+
turnId: 'init_response'
|
|
20965
20890
|
}
|
|
20966
20891
|
});
|
|
20967
|
-
if (awaitingUserTurnQueue.length > 0) {
|
|
20968
|
-
emitQueuedAvatarEntry();
|
|
20969
|
-
} else {
|
|
20970
|
-
pendingUserTurnCount += 1;
|
|
20971
|
-
}
|
|
20972
20892
|
}
|
|
20973
20893
|
}
|
|
20974
20894
|
});
|
|
@@ -23290,8 +23210,9 @@ const avatarConnectionMachine = setup({
|
|
|
23290
23210
|
logAgentClientToolCallResult,
|
|
23291
23211
|
recordClientToolCallLogEntry,
|
|
23292
23212
|
appendAvatarChatEntry,
|
|
23213
|
+
removeStaleChatEntriesForRetry,
|
|
23293
23214
|
appendUserChatEntry,
|
|
23294
|
-
|
|
23215
|
+
trackInternalAskEcho,
|
|
23295
23216
|
setScreenShareActive,
|
|
23296
23217
|
setScreenShareStream,
|
|
23297
23218
|
showScreenShareErrorNotification,
|
|
@@ -23463,7 +23384,7 @@ const avatarConnectionMachine = setup({
|
|
|
23463
23384
|
content: null,
|
|
23464
23385
|
chatLog: [],
|
|
23465
23386
|
clientToolCallLog: [],
|
|
23466
|
-
|
|
23387
|
+
awaitingInternalAskEcho: false,
|
|
23467
23388
|
userAgent: '',
|
|
23468
23389
|
userAgentHints: null,
|
|
23469
23390
|
kalturaAnalyticsService: input.kalturaAnalyticsService,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kaltura-sdk/avatars",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"author": "kaltura",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"repository": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"@kaltura-sdk/rtc-core": "^1.25.21",
|
|
14
14
|
"@local/genie-shared-utils": "^1.0.0",
|
|
15
15
|
"@unisphere/core": "^1.99.0",
|
|
16
|
-
"@unisphere/genie-types": "^1.39.
|
|
16
|
+
"@unisphere/genie-types": "^1.39.3",
|
|
17
17
|
"@unisphere/notifications-core": "^1.25.0",
|
|
18
18
|
"@unisphere/runtime": "^1.98.0",
|
|
19
19
|
"@unisphere/runtime-js": "^1.96.0",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ActionFunction, MachineContext } from 'xstate';
|
|
2
|
+
export declare function asHarnessAction<TContext extends MachineContext, TEvent extends {
|
|
3
|
+
type: string;
|
|
4
|
+
}>(action: unknown): ActionFunction<TContext, TEvent, TEvent, undefined, never, never, never, never, never>;
|
package/src/lib/avatar-client-manager/machines/avatar-connection/avatar-connection-machine.d.ts
CHANGED
|
@@ -391,11 +391,14 @@ export declare const avatarConnectionMachine: import("xstate").StateMachine<Avat
|
|
|
391
391
|
} | {
|
|
392
392
|
type: "appendAvatarChatEntry";
|
|
393
393
|
params: unknown;
|
|
394
|
+
} | {
|
|
395
|
+
type: "removeStaleChatEntriesForRetry";
|
|
396
|
+
params: unknown;
|
|
394
397
|
} | {
|
|
395
398
|
type: "appendUserChatEntry";
|
|
396
399
|
params: unknown;
|
|
397
400
|
} | {
|
|
398
|
-
type: "
|
|
401
|
+
type: "trackInternalAskEcho";
|
|
399
402
|
params: unknown;
|
|
400
403
|
} | {
|
|
401
404
|
type: "setScreenShareActive";
|
package/src/lib/avatar-client-manager/machines/avatar-connection/states/connected-state.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export declare const connectedState: {
|
|
|
96
96
|
initial: string;
|
|
97
97
|
on: {
|
|
98
98
|
AskViaText: {
|
|
99
|
-
actions: ("
|
|
99
|
+
actions: ("trackInternalAskEcho" | {
|
|
100
100
|
type: "sendAskViaText";
|
|
101
101
|
params: ({ event }: {
|
|
102
102
|
event: import("../types").AskViaTextEvent;
|
|
@@ -728,13 +728,13 @@ export declare const connectedState: {
|
|
|
728
728
|
actions: "resetAgentRecoveryRetryCount";
|
|
729
729
|
} | {
|
|
730
730
|
guard: "hasMoreRecoveryRetries";
|
|
731
|
-
actions: readonly ["incrementAgentRecoveryRetryCount", "sendReprocessTurn"];
|
|
731
|
+
actions: readonly ["removeStaleChatEntriesForRetry", "incrementAgentRecoveryRetryCount", "sendReprocessTurn"];
|
|
732
732
|
} | {
|
|
733
733
|
guard: "justExceededRecoveryRetries";
|
|
734
734
|
actions: readonly ["incrementAgentRecoveryRetryCount", "showAgentRecoveryExhaustedNotification"];
|
|
735
735
|
})[];
|
|
736
736
|
RequestAgentTurnRecovery: {
|
|
737
|
-
actions: "sendReprocessTurn";
|
|
737
|
+
actions: readonly ["removeStaleChatEntriesForRetry", "sendReprocessTurn"];
|
|
738
738
|
};
|
|
739
739
|
};
|
|
740
740
|
invoke: {
|
|
@@ -6,13 +6,13 @@ export declare const agentRecoveryMonitorState: {
|
|
|
6
6
|
actions: "resetAgentRecoveryRetryCount";
|
|
7
7
|
} | {
|
|
8
8
|
guard: "hasMoreRecoveryRetries";
|
|
9
|
-
actions: readonly ["incrementAgentRecoveryRetryCount", "sendReprocessTurn"];
|
|
9
|
+
actions: readonly ["removeStaleChatEntriesForRetry", "incrementAgentRecoveryRetryCount", "sendReprocessTurn"];
|
|
10
10
|
} | {
|
|
11
11
|
guard: "justExceededRecoveryRetries";
|
|
12
12
|
actions: readonly ["incrementAgentRecoveryRetryCount", "showAgentRecoveryExhaustedNotification"];
|
|
13
13
|
})[];
|
|
14
14
|
RequestAgentTurnRecovery: {
|
|
15
|
-
actions: "sendReprocessTurn";
|
|
15
|
+
actions: readonly ["removeStaleChatEntriesForRetry", "sendReprocessTurn"];
|
|
16
16
|
};
|
|
17
17
|
};
|
|
18
18
|
invoke: {
|
|
@@ -66,7 +66,7 @@ export interface AvatarConnectionMachineContext {
|
|
|
66
66
|
content: UnisphereChatBlock | null;
|
|
67
67
|
chatLog: ChatEntry[];
|
|
68
68
|
clientToolCallLog: ClientToolCallLogEntry[];
|
|
69
|
-
|
|
69
|
+
awaitingInternalAskEcho: boolean;
|
|
70
70
|
isAgentTalking: boolean;
|
|
71
71
|
userAgent: string;
|
|
72
72
|
userAgentHints: Record<string, string> | null;
|
|
@@ -69,6 +69,7 @@ export declare const agentRawTextDeltaSchema: z.ZodMiniObject<{
|
|
|
69
69
|
role: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
70
70
|
type: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
71
71
|
content: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniString<string>, z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniUnknown>]>>;
|
|
72
|
+
human: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
72
73
|
segmentNumber: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
73
74
|
et: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
74
75
|
segmentStart: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
@@ -86,6 +87,7 @@ export declare const agentRawTextClientToolDeltaSchema: z.ZodMiniObject<{
|
|
|
86
87
|
threadId: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
87
88
|
role: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
88
89
|
content: z.ZodMiniOptional<z.ZodMiniUnion<readonly [z.ZodMiniString<string>, z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniUnknown>]>>;
|
|
90
|
+
human: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
89
91
|
segmentNumber: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
90
92
|
et: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
91
93
|
segmentStart: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|