@talqing/client 0.5.0 → 0.7.0
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 +1 -1
- package/README.md +42 -18
- package/dist/cjs/chat.d.ts +104 -0
- package/dist/cjs/chat.d.ts.map +1 -0
- package/dist/cjs/chat.js +158 -0
- package/dist/cjs/chat.js.map +1 -0
- package/dist/cjs/images.js +2 -2
- package/dist/cjs/images.js.map +1 -1
- package/dist/cjs/index.d.ts +3 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +10 -6
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/participant.d.ts +8 -0
- package/dist/cjs/participant.d.ts.map +1 -0
- package/dist/cjs/participant.js +13 -0
- package/dist/cjs/participant.js.map +1 -0
- package/dist/esm/chat.d.ts +104 -0
- package/dist/esm/chat.d.ts.map +1 -0
- package/dist/esm/chat.js +153 -0
- package/dist/esm/chat.js.map +1 -0
- package/dist/esm/images.js +1 -1
- package/dist/esm/images.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/participant.d.ts +8 -0
- package/dist/esm/participant.d.ts.map +1 -0
- package/dist/esm/participant.js +10 -0
- package/dist/esm/participant.js.map +1 -0
- package/package.json +2 -2
- package/src/chat.ts +238 -0
- package/src/images.ts +1 -1
- package/src/index.ts +15 -6
- package/src/participant.ts +12 -0
- package/src/userdata.ts +0 -57
package/dist/esm/chat.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/* ── chats ────────────────────────────────────────────────────────────────
|
|
2
|
+
*
|
|
3
|
+
* A text chat with an agent, from a browser. Your SERVER starts the chat
|
|
4
|
+
* (`POST /v1/chats`) and mints a token for it (`POST /v1/chats/{id}/token`);
|
|
5
|
+
* the browser holds only that token, which opens that one chat and nothing
|
|
6
|
+
* else.
|
|
7
|
+
*/
|
|
8
|
+
/** An error response from the API. */
|
|
9
|
+
export class TalqingChatError extends Error {
|
|
10
|
+
constructor(status, message, errors) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "TalqingChatError";
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.errors = errors;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** How long before `expires_at` a new token is fetched. */
|
|
18
|
+
const REFRESH_BEFORE_EXPIRY_MS = 60000;
|
|
19
|
+
/** One chat, driven from a browser.
|
|
20
|
+
*
|
|
21
|
+
* `fetchToken` asks YOUR server for the chat's token, and is called again
|
|
22
|
+
* shortly before each one expires. */
|
|
23
|
+
export class TalqingChat {
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.minted = null;
|
|
26
|
+
this.fetchToken = options.fetchToken;
|
|
27
|
+
}
|
|
28
|
+
/** Send a message. `events` yields the reply as it is generated; `result`
|
|
29
|
+
* resolves with the whole turn. Either can be used without the other. */
|
|
30
|
+
send(text, options = {}) {
|
|
31
|
+
const queue = [];
|
|
32
|
+
let finished = false;
|
|
33
|
+
let failure;
|
|
34
|
+
let wake = null;
|
|
35
|
+
const notify = () => {
|
|
36
|
+
wake?.();
|
|
37
|
+
wake = null;
|
|
38
|
+
};
|
|
39
|
+
const result = (async () => {
|
|
40
|
+
const response = await this.request("/messages", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
message: text,
|
|
44
|
+
images: options.images ?? [],
|
|
45
|
+
client_message_id: crypto.randomUUID(),
|
|
46
|
+
stream: true,
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
for await (const event of readEvents(response)) {
|
|
50
|
+
queue.push(event);
|
|
51
|
+
notify();
|
|
52
|
+
if (event.event === "turn.result") {
|
|
53
|
+
const { event: _name, ...turn } = event;
|
|
54
|
+
return turn;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw new TalqingChatError(response.status, "the reply ended before it finished", []);
|
|
58
|
+
})();
|
|
59
|
+
result
|
|
60
|
+
.catch((error) => {
|
|
61
|
+
failure = error;
|
|
62
|
+
})
|
|
63
|
+
.finally(() => {
|
|
64
|
+
finished = true;
|
|
65
|
+
notify();
|
|
66
|
+
});
|
|
67
|
+
const events = {
|
|
68
|
+
[Symbol.asyncIterator]: async function* () {
|
|
69
|
+
for (;;) {
|
|
70
|
+
while (queue.length > 0)
|
|
71
|
+
yield queue.shift();
|
|
72
|
+
if (finished) {
|
|
73
|
+
if (failure !== undefined)
|
|
74
|
+
throw failure;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
await new Promise((resolve) => {
|
|
78
|
+
wake = resolve;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
return { events, result };
|
|
84
|
+
}
|
|
85
|
+
/** The chat so far, oldest first. */
|
|
86
|
+
async items() {
|
|
87
|
+
const items = [];
|
|
88
|
+
for (;;) {
|
|
89
|
+
const response = await this.request(`/items?limit=500&offset=${items.length}`, {
|
|
90
|
+
method: "GET",
|
|
91
|
+
});
|
|
92
|
+
const page = (await response.json());
|
|
93
|
+
items.push(...page.items);
|
|
94
|
+
if (!page.has_more)
|
|
95
|
+
return items;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** End the chat. Further messages are refused. */
|
|
99
|
+
async end() {
|
|
100
|
+
await this.request("/end", { method: "POST" });
|
|
101
|
+
}
|
|
102
|
+
credentials() {
|
|
103
|
+
const current = this.minted;
|
|
104
|
+
this.minted = (async () => {
|
|
105
|
+
const held = await current?.catch(() => null);
|
|
106
|
+
if (held && Date.parse(held.expires_at) - Date.now() > REFRESH_BEFORE_EXPIRY_MS) {
|
|
107
|
+
return held;
|
|
108
|
+
}
|
|
109
|
+
return this.fetchToken();
|
|
110
|
+
})();
|
|
111
|
+
return this.minted;
|
|
112
|
+
}
|
|
113
|
+
async request(path, init) {
|
|
114
|
+
const { api_url, chat_id, token } = await this.credentials();
|
|
115
|
+
const response = await fetch(`${api_url}/v1/chats/${chat_id}${path}`, {
|
|
116
|
+
...init,
|
|
117
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
const body = (await response.json().catch(() => null));
|
|
121
|
+
throw new TalqingChatError(response.status, body?.detail?.message ?? response.statusText, body?.detail?.errors ?? []);
|
|
122
|
+
}
|
|
123
|
+
return response;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Parse a `text/event-stream` body into its frames. Keep-alive comments are dropped. */
|
|
127
|
+
async function* readEvents(response) {
|
|
128
|
+
if (!response.body)
|
|
129
|
+
return;
|
|
130
|
+
const reader = response.body.getReader();
|
|
131
|
+
const decoder = new TextDecoder();
|
|
132
|
+
let buffer = "";
|
|
133
|
+
for (;;) {
|
|
134
|
+
const { done, value } = await reader.read();
|
|
135
|
+
if (done)
|
|
136
|
+
return;
|
|
137
|
+
buffer += decoder.decode(value, { stream: true });
|
|
138
|
+
let boundary = buffer.indexOf("\n\n");
|
|
139
|
+
while (boundary !== -1) {
|
|
140
|
+
const frame = buffer.slice(0, boundary);
|
|
141
|
+
buffer = buffer.slice(boundary + 2);
|
|
142
|
+
const data = frame
|
|
143
|
+
.split("\n")
|
|
144
|
+
.filter((line) => line.startsWith("data:"))
|
|
145
|
+
.map((line) => line.slice(5).trimStart())
|
|
146
|
+
.join("\n");
|
|
147
|
+
if (data)
|
|
148
|
+
yield JSON.parse(data);
|
|
149
|
+
boundary = buffer.indexOf("\n\n");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=chat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/images.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { talqingAgentParticipant } from "./
|
|
1
|
+
import { talqingAgentParticipant } from "./participant.js";
|
|
2
2
|
/* ── images ───────────────────────────────────────────────────────────────
|
|
3
3
|
*
|
|
4
4
|
* A caller attaches a photo on a web voice or video call and the agent sees
|
package/dist/esm/images.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../src/images.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../src/images.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { createTalqingTokenSource } from "./token.js";
|
|
2
2
|
export type { TalqingCallCredentials } from "./token.js";
|
|
3
|
-
export {
|
|
3
|
+
export { talqingAgentParticipant } from "./participant.js";
|
|
4
4
|
export { onTalqingImageResult, sendTalqingImage, talqingDownscaleImage, talqingImageDataUrl, talqingImageRejection, TALQING_IMAGE_ACK_TIMEOUT_MS, TALQING_IMAGE_MAX_BYTES, TALQING_IMAGE_MAX_EDGE_PX, TALQING_IMAGE_TOPIC, } from "./images.js";
|
|
5
5
|
export type { TalqingImageStatus, TalqingSentImage } from "./images.js";
|
|
6
6
|
export { registerTalqingRpcHandlers, TalqingRpcError } from "./rpc.js";
|
|
7
7
|
export type { TalqingFrontendRpcEnvelope, TalqingRpcHandler, TalqingRpcHandlers, TalqingRpcInvocation, } from "./rpc.js";
|
|
8
8
|
export { TALQING_AUDIO_CAPTURE_DEFAULTS, TALQING_SCREEN_SHARE_CAPTURE, TALQING_SCREEN_SHARE_PUBLISH, } from "./media.js";
|
|
9
|
+
export { TalqingChat, TalqingChatError } from "./chat.js";
|
|
10
|
+
export type { TalqingChatAttachment, TalqingChatCredentials, TalqingChatEvent, TalqingChatImage, TalqingChatItem, TalqingChatTurn, } from "./chat.js";
|
|
9
11
|
export type { JsonObject, JsonPrimitive, JsonValue } from "./json.js";
|
|
10
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AACtD,YAAY,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,4BAA4B,EAC5B,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACvE,YAAY,EACV,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC1D,YAAY,EACV,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AAEnB,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/esm/index.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/* The Talqing call protocol, over a LiveKit `Room`. Framework-free.
|
|
2
2
|
*
|
|
3
3
|
* Everything here is something only Talqing can write: the mint-once token
|
|
4
|
-
* source, the
|
|
4
|
+
* source, the `talqing.images` byte-stream topic
|
|
5
5
|
* and its ack, the `talqing.frontend_rpc` envelope, and the measured capture
|
|
6
6
|
* settings. Nothing here renames LiveKit — for a room, a track or a
|
|
7
7
|
* participant, use `livekit-client` directly.
|
|
8
8
|
*
|
|
9
9
|
* React bindings for all of it are `@talqing/react`.
|
|
10
|
+
*
|
|
11
|
+
* `TalqingChat` is the one thing here with no room behind it: a text chat, over
|
|
12
|
+
* HTTP, on a chat token.
|
|
10
13
|
*/
|
|
11
14
|
export { createTalqingTokenSource } from "./token.js";
|
|
12
|
-
export {
|
|
15
|
+
export { talqingAgentParticipant } from "./participant.js";
|
|
13
16
|
export { onTalqingImageResult, sendTalqingImage, talqingDownscaleImage, talqingImageDataUrl, talqingImageRejection, TALQING_IMAGE_ACK_TIMEOUT_MS, TALQING_IMAGE_MAX_BYTES, TALQING_IMAGE_MAX_EDGE_PX, TALQING_IMAGE_TOPIC, } from "./images.js";
|
|
14
17
|
export { registerTalqingRpcHandlers, TalqingRpcError } from "./rpc.js";
|
|
15
18
|
export { TALQING_AUDIO_CAPTURE_DEFAULTS, TALQING_SCREEN_SHARE_CAPTURE, TALQING_SCREEN_SHARE_PUBLISH, } from "./media.js";
|
|
19
|
+
export { TalqingChat, TalqingChatError } from "./chat.js";
|
|
16
20
|
//# sourceMappingURL=index.js.map
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAGtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,4BAA4B,EAC5B,uBAAuB,EACvB,yBAAyB,EACzB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAQvE,OAAO,EACL,8BAA8B,EAC9B,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RemoteParticipant, type Room } from "livekit-client";
|
|
2
|
+
/** The agent on this call, or undefined before it has joined.
|
|
3
|
+
*
|
|
4
|
+
* On a video call the avatar worker is an agent participant too, but it joins
|
|
5
|
+
* on the agent's behalf and never answers RPCs, so the first one found is the
|
|
6
|
+
* one to talk to. */
|
|
7
|
+
export declare function talqingAgentParticipant(room: Room): RemoteParticipant | undefined;
|
|
8
|
+
//# sourceMappingURL=participant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"participant.d.ts","sourceRoot":"","sources":["../../src/participant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,iBAAiB,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEpF;;;;sBAIsB;AACtB,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,IAAI,GAAG,iBAAiB,GAAG,SAAS,CAIjF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ParticipantKind } from "livekit-client";
|
|
2
|
+
/** The agent on this call, or undefined before it has joined.
|
|
3
|
+
*
|
|
4
|
+
* On a video call the avatar worker is an agent participant too, but it joins
|
|
5
|
+
* on the agent's behalf and never answers RPCs, so the first one found is the
|
|
6
|
+
* one to talk to. */
|
|
7
|
+
export function talqingAgentParticipant(room) {
|
|
8
|
+
return [...room.remoteParticipants.values()].find((participant) => participant.kind === ParticipantKind.AGENT);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=participant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"participant.js","sourceRoot":"","sources":["../../src/participant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqC,MAAM,gBAAgB,CAAC;AAEpF;;;;sBAIsB;AACtB,MAAM,UAAU,uBAAuB,CAAC,IAAU;IAChD,OAAO,CAAC,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC/C,CAAC,WAA8B,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,KAAK,eAAe,CAAC,KAAK,CAC/E,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@talqing/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The Talqing call protocol over a LiveKit room — token, userdata, images, screen share and frontend RPC. Framework-free.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://talqing.com",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"livekit-client": "^2.20.2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"livekit-client": "^2.22.
|
|
59
|
+
"livekit-client": "^2.22.4",
|
|
60
60
|
"typescript": "~5.9.3"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/src/chat.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/* ── chats ────────────────────────────────────────────────────────────────
|
|
2
|
+
*
|
|
3
|
+
* A text chat with an agent, from a browser. Your SERVER starts the chat
|
|
4
|
+
* (`POST /v1/chats`) and mints a token for it (`POST /v1/chats/{id}/token`);
|
|
5
|
+
* the browser holds only that token, which opens that one chat and nothing
|
|
6
|
+
* else.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** What `POST /v1/chats/{id}/token` returns, named as the API names it — so a
|
|
10
|
+
* server that mints one can forward the response through unchanged. */
|
|
11
|
+
export interface TalqingChatCredentials {
|
|
12
|
+
chat_id: string;
|
|
13
|
+
token: string;
|
|
14
|
+
/** ISO 8601. A token lasts an hour. */
|
|
15
|
+
expires_at: string;
|
|
16
|
+
/** The API this chat lives on. */
|
|
17
|
+
api_url: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TalqingChatAttachment {
|
|
21
|
+
id: string;
|
|
22
|
+
kind: "image";
|
|
23
|
+
mime_type: string;
|
|
24
|
+
bytes: number;
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
filename: string | null;
|
|
28
|
+
/** Signed, and short-lived: valid until `url_expires_at`. */
|
|
29
|
+
url: string;
|
|
30
|
+
url_expires_at: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** One message in the chat. A browser only ever receives what the person
|
|
34
|
+
* chatting can see — never the agent's tool calls. */
|
|
35
|
+
export interface TalqingChatItem {
|
|
36
|
+
id: string;
|
|
37
|
+
/** The message this one answers; null on the person's own messages. */
|
|
38
|
+
trigger_item_id: string | null;
|
|
39
|
+
direction: "inbound" | "outbound" | "internal";
|
|
40
|
+
type: string;
|
|
41
|
+
role: "user" | "assistant" | "system" | "developer" | "tool" | null;
|
|
42
|
+
/** Null on a message that is only images. */
|
|
43
|
+
text: string | null;
|
|
44
|
+
attachments: TalqingChatAttachment[];
|
|
45
|
+
created_at: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** What one message led to. */
|
|
49
|
+
export interface TalqingChatTurn {
|
|
50
|
+
input: TalqingChatItem;
|
|
51
|
+
/** `canceled`: a newer message arrived first, named by `superseded_by_item_id`. */
|
|
52
|
+
status: "done" | "error" | "canceled" | "running";
|
|
53
|
+
error: string | null;
|
|
54
|
+
superseded_by_item_id: string | null;
|
|
55
|
+
/** Everything the agent said in reply, oldest first. */
|
|
56
|
+
items: TalqingChatItem[];
|
|
57
|
+
/** `ended` when the agent ended the chat on this turn. */
|
|
58
|
+
chat_status: "open" | "ended";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A frame of a reply as it is generated. `assistant.delta` chunks append. */
|
|
62
|
+
export type TalqingChatEvent =
|
|
63
|
+
| { event: "assistant.started"; trigger_item_id: string }
|
|
64
|
+
| { event: "assistant.delta"; trigger_item_id: string; text: string }
|
|
65
|
+
| { event: "assistant.completed"; trigger_item_id: string; text: string | null }
|
|
66
|
+
| ({ event: "item.created" } & TalqingChatItem)
|
|
67
|
+
| ({ event: "turn.result" } & TalqingChatTurn);
|
|
68
|
+
|
|
69
|
+
/** An image to send with a message, as a `data:` URL. */
|
|
70
|
+
export interface TalqingChatImage {
|
|
71
|
+
data_url: string;
|
|
72
|
+
filename?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** An error response from the API. */
|
|
76
|
+
export class TalqingChatError extends Error {
|
|
77
|
+
readonly name = "TalqingChatError";
|
|
78
|
+
readonly status: number;
|
|
79
|
+
/** Per-field problems, when the failure had more than one. */
|
|
80
|
+
readonly errors: string[];
|
|
81
|
+
|
|
82
|
+
constructor(status: number, message: string, errors: string[]) {
|
|
83
|
+
super(message);
|
|
84
|
+
this.status = status;
|
|
85
|
+
this.errors = errors;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** How long before `expires_at` a new token is fetched. */
|
|
90
|
+
const REFRESH_BEFORE_EXPIRY_MS = 60_000;
|
|
91
|
+
|
|
92
|
+
/** One chat, driven from a browser.
|
|
93
|
+
*
|
|
94
|
+
* `fetchToken` asks YOUR server for the chat's token, and is called again
|
|
95
|
+
* shortly before each one expires. */
|
|
96
|
+
export class TalqingChat {
|
|
97
|
+
private readonly fetchToken: () => Promise<TalqingChatCredentials>;
|
|
98
|
+
private minted: Promise<TalqingChatCredentials> | null = null;
|
|
99
|
+
|
|
100
|
+
constructor(options: { fetchToken: () => Promise<TalqingChatCredentials> }) {
|
|
101
|
+
this.fetchToken = options.fetchToken;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Send a message. `events` yields the reply as it is generated; `result`
|
|
105
|
+
* resolves with the whole turn. Either can be used without the other. */
|
|
106
|
+
send(
|
|
107
|
+
text: string,
|
|
108
|
+
options: { images?: TalqingChatImage[] } = {},
|
|
109
|
+
): { events: AsyncIterable<TalqingChatEvent>; result: Promise<TalqingChatTurn> } {
|
|
110
|
+
const queue: TalqingChatEvent[] = [];
|
|
111
|
+
let finished = false;
|
|
112
|
+
let failure: unknown;
|
|
113
|
+
let wake: (() => void) | null = null;
|
|
114
|
+
const notify = () => {
|
|
115
|
+
wake?.();
|
|
116
|
+
wake = null;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const result = (async () => {
|
|
120
|
+
const response = await this.request("/messages", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
body: JSON.stringify({
|
|
123
|
+
message: text,
|
|
124
|
+
images: options.images ?? [],
|
|
125
|
+
client_message_id: crypto.randomUUID(),
|
|
126
|
+
stream: true,
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
for await (const event of readEvents(response)) {
|
|
130
|
+
queue.push(event);
|
|
131
|
+
notify();
|
|
132
|
+
if (event.event === "turn.result") {
|
|
133
|
+
const { event: _name, ...turn } = event;
|
|
134
|
+
return turn;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
throw new TalqingChatError(response.status, "the reply ended before it finished", []);
|
|
138
|
+
})();
|
|
139
|
+
result
|
|
140
|
+
.catch((error: unknown) => {
|
|
141
|
+
failure = error;
|
|
142
|
+
})
|
|
143
|
+
.finally(() => {
|
|
144
|
+
finished = true;
|
|
145
|
+
notify();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const events: AsyncIterable<TalqingChatEvent> = {
|
|
149
|
+
[Symbol.asyncIterator]: async function* () {
|
|
150
|
+
for (;;) {
|
|
151
|
+
while (queue.length > 0) yield queue.shift() as TalqingChatEvent;
|
|
152
|
+
if (finished) {
|
|
153
|
+
if (failure !== undefined) throw failure;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
await new Promise<void>((resolve) => {
|
|
157
|
+
wake = resolve;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
return { events, result };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The chat so far, oldest first. */
|
|
166
|
+
async items(): Promise<TalqingChatItem[]> {
|
|
167
|
+
const items: TalqingChatItem[] = [];
|
|
168
|
+
for (;;) {
|
|
169
|
+
const response = await this.request(`/items?limit=500&offset=${items.length}`, {
|
|
170
|
+
method: "GET",
|
|
171
|
+
});
|
|
172
|
+
const page = (await response.json()) as { items: TalqingChatItem[]; has_more: boolean };
|
|
173
|
+
items.push(...page.items);
|
|
174
|
+
if (!page.has_more) return items;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** End the chat. Further messages are refused. */
|
|
179
|
+
async end(): Promise<void> {
|
|
180
|
+
await this.request("/end", { method: "POST" });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private credentials(): Promise<TalqingChatCredentials> {
|
|
184
|
+
const current = this.minted;
|
|
185
|
+
this.minted = (async () => {
|
|
186
|
+
const held = await current?.catch(() => null);
|
|
187
|
+
if (held && Date.parse(held.expires_at) - Date.now() > REFRESH_BEFORE_EXPIRY_MS) {
|
|
188
|
+
return held;
|
|
189
|
+
}
|
|
190
|
+
return this.fetchToken();
|
|
191
|
+
})();
|
|
192
|
+
return this.minted;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private async request(path: string, init: RequestInit): Promise<Response> {
|
|
196
|
+
const { api_url, chat_id, token } = await this.credentials();
|
|
197
|
+
const response = await fetch(`${api_url}/v1/chats/${chat_id}${path}`, {
|
|
198
|
+
...init,
|
|
199
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
200
|
+
});
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
const body = (await response.json().catch(() => null)) as {
|
|
203
|
+
detail?: { message?: string; errors?: string[] };
|
|
204
|
+
} | null;
|
|
205
|
+
throw new TalqingChatError(
|
|
206
|
+
response.status,
|
|
207
|
+
body?.detail?.message ?? response.statusText,
|
|
208
|
+
body?.detail?.errors ?? [],
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return response;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Parse a `text/event-stream` body into its frames. Keep-alive comments are dropped. */
|
|
216
|
+
async function* readEvents(response: Response): AsyncGenerator<TalqingChatEvent> {
|
|
217
|
+
if (!response.body) return;
|
|
218
|
+
const reader = response.body.getReader();
|
|
219
|
+
const decoder = new TextDecoder();
|
|
220
|
+
let buffer = "";
|
|
221
|
+
for (;;) {
|
|
222
|
+
const { done, value } = await reader.read();
|
|
223
|
+
if (done) return;
|
|
224
|
+
buffer += decoder.decode(value, { stream: true });
|
|
225
|
+
let boundary = buffer.indexOf("\n\n");
|
|
226
|
+
while (boundary !== -1) {
|
|
227
|
+
const frame = buffer.slice(0, boundary);
|
|
228
|
+
buffer = buffer.slice(boundary + 2);
|
|
229
|
+
const data = frame
|
|
230
|
+
.split("\n")
|
|
231
|
+
.filter((line) => line.startsWith("data:"))
|
|
232
|
+
.map((line) => line.slice(5).trimStart())
|
|
233
|
+
.join("\n");
|
|
234
|
+
if (data) yield JSON.parse(data) as TalqingChatEvent;
|
|
235
|
+
boundary = buffer.indexOf("\n\n");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
package/src/images.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
/* The Talqing call protocol, over a LiveKit `Room`. Framework-free.
|
|
2
2
|
*
|
|
3
3
|
* Everything here is something only Talqing can write: the mint-once token
|
|
4
|
-
* source, the
|
|
4
|
+
* source, the `talqing.images` byte-stream topic
|
|
5
5
|
* and its ack, the `talqing.frontend_rpc` envelope, and the measured capture
|
|
6
6
|
* settings. Nothing here renames LiveKit — for a room, a track or a
|
|
7
7
|
* participant, use `livekit-client` directly.
|
|
8
8
|
*
|
|
9
9
|
* React bindings for all of it are `@talqing/react`.
|
|
10
|
+
*
|
|
11
|
+
* `TalqingChat` is the one thing here with no room behind it: a text chat, over
|
|
12
|
+
* HTTP, on a chat token.
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
export { createTalqingTokenSource } from "./token.js";
|
|
13
16
|
export type { TalqingCallCredentials } from "./token.js";
|
|
14
17
|
|
|
15
|
-
export {
|
|
16
|
-
getTalqingUserdata,
|
|
17
|
-
setTalqingUserdata,
|
|
18
|
-
talqingAgentParticipant,
|
|
19
|
-
} from "./userdata.js";
|
|
18
|
+
export { talqingAgentParticipant } from "./participant.js";
|
|
20
19
|
|
|
21
20
|
export {
|
|
22
21
|
onTalqingImageResult,
|
|
@@ -45,4 +44,14 @@ export {
|
|
|
45
44
|
TALQING_SCREEN_SHARE_PUBLISH,
|
|
46
45
|
} from "./media.js";
|
|
47
46
|
|
|
47
|
+
export { TalqingChat, TalqingChatError } from "./chat.js";
|
|
48
|
+
export type {
|
|
49
|
+
TalqingChatAttachment,
|
|
50
|
+
TalqingChatCredentials,
|
|
51
|
+
TalqingChatEvent,
|
|
52
|
+
TalqingChatImage,
|
|
53
|
+
TalqingChatItem,
|
|
54
|
+
TalqingChatTurn,
|
|
55
|
+
} from "./chat.js";
|
|
56
|
+
|
|
48
57
|
export type { JsonObject, JsonPrimitive, JsonValue } from "./json.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ParticipantKind, type RemoteParticipant, type Room } from "livekit-client";
|
|
2
|
+
|
|
3
|
+
/** The agent on this call, or undefined before it has joined.
|
|
4
|
+
*
|
|
5
|
+
* On a video call the avatar worker is an agent participant too, but it joins
|
|
6
|
+
* on the agent's behalf and never answers RPCs, so the first one found is the
|
|
7
|
+
* one to talk to. */
|
|
8
|
+
export function talqingAgentParticipant(room: Room): RemoteParticipant | undefined {
|
|
9
|
+
return [...room.remoteParticipants.values()].find(
|
|
10
|
+
(participant: RemoteParticipant) => participant.kind === ParticipantKind.AGENT,
|
|
11
|
+
);
|
|
12
|
+
}
|
package/src/userdata.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { ParticipantKind, type RemoteParticipant, type Room } from "livekit-client";
|
|
2
|
-
import type { JsonObject } from "./json.js";
|
|
3
|
-
|
|
4
|
-
/** The agent on this call, or undefined before it has joined.
|
|
5
|
-
*
|
|
6
|
-
* On a video call the avatar worker is an agent participant too, but it joins
|
|
7
|
-
* on the agent's behalf and never answers RPCs, so the first one found is the
|
|
8
|
-
* one to talk to. */
|
|
9
|
-
export function talqingAgentParticipant(room: Room): RemoteParticipant | undefined {
|
|
10
|
-
return [...room.remoteParticipants.values()].find(
|
|
11
|
-
(participant: RemoteParticipant) => participant.kind === ParticipantKind.AGENT,
|
|
12
|
-
);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Merge a patch into the live session's userdata, over RPC to the agent.
|
|
16
|
-
*
|
|
17
|
-
* Resolves rather than throws when the agent is not there: a write that did
|
|
18
|
-
* not land is a state a page can render. */
|
|
19
|
-
export async function setTalqingUserdata(
|
|
20
|
-
room: Room,
|
|
21
|
-
patch: JsonObject,
|
|
22
|
-
responseTimeout = 3,
|
|
23
|
-
): Promise<{ ok: boolean; error?: string }> {
|
|
24
|
-
const agent = talqingAgentParticipant(room);
|
|
25
|
-
if (!agent) return { ok: false, error: "agent participant is not connected" };
|
|
26
|
-
const response = await room.localParticipant.performRpc({
|
|
27
|
-
destinationIdentity: agent.identity,
|
|
28
|
-
method: "talqing.client.userdata_set",
|
|
29
|
-
payload: JSON.stringify(patch),
|
|
30
|
-
responseTimeout,
|
|
31
|
-
});
|
|
32
|
-
return JSON.parse(response || "{}") as { ok: boolean; error?: string };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Read the live session's userdata, or only the keys named.
|
|
36
|
-
*
|
|
37
|
-
* Throws when the agent is not there, unlike `setTalqingUserdata`: a read that
|
|
38
|
-
* returned nothing is not a state anything can render. */
|
|
39
|
-
export async function getTalqingUserdata(
|
|
40
|
-
room: Room,
|
|
41
|
-
keys?: string[],
|
|
42
|
-
responseTimeout = 3,
|
|
43
|
-
): Promise<JsonObject> {
|
|
44
|
-
const agent = talqingAgentParticipant(room);
|
|
45
|
-
if (!agent) throw new Error("agent participant is not connected");
|
|
46
|
-
const response = await room.localParticipant.performRpc({
|
|
47
|
-
destinationIdentity: agent.identity,
|
|
48
|
-
method: "talqing.client.userdata_get",
|
|
49
|
-
payload: JSON.stringify(keys ? { keys } : {}),
|
|
50
|
-
responseTimeout,
|
|
51
|
-
});
|
|
52
|
-
const parsed = JSON.parse(response || "{}") as JsonObject & { ok?: boolean; error?: string };
|
|
53
|
-
if (parsed.ok === false) {
|
|
54
|
-
throw new Error(String(parsed.error || "userdata RPC failed"));
|
|
55
|
-
}
|
|
56
|
-
return parsed;
|
|
57
|
-
}
|