@happyvertical/smrt-chat 0.53.3 → 0.53.5
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/AGENTS.md +17 -16
- package/README.md +24 -0
- package/dist/assistant-turn.d.ts +65 -0
- package/dist/assistant-turn.d.ts.map +1 -1
- package/dist/chunks/{ChatService-jJi2JJJi.js → ChatService-Dl5tsK_F.js} +172 -13
- package/dist/chunks/ChatService-Dl5tsK_F.js.map +1 -0
- package/dist/chunks/chat-stream-DJQO6uHe.js +1577 -0
- package/dist/chunks/chat-stream-DJQO6uHe.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1482
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-runtime.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/services/ChatService.d.ts +96 -0
- package/dist/services/ChatService.d.ts.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +8 -5
- package/dist/svelte/components/assistant/assistant-http-client.d.ts +64 -0
- package/dist/svelte/components/assistant/assistant-http-client.d.ts.map +1 -0
- package/dist/svelte/components/assistant/assistant-http-client.js +212 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +3 -0
- package/dist/sveltekit.d.ts +343 -0
- package/dist/sveltekit.d.ts.map +1 -0
- package/dist/sveltekit.js +973 -0
- package/dist/sveltekit.js.map +1 -0
- package/package.json +20 -15
- package/dist/chunks/ChatService-jJi2JJJi.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -142,22 +142,23 @@ always waits. Details: [`docs/assistant-dock.md`](../../docs/assistant-dock.md).
|
|
|
142
142
|
|
|
143
143
|
## AssistantDock (#2904)
|
|
144
144
|
|
|
145
|
-
`AssistantDock` (`svelte/components/assistant/`, exported from `./svelte`)
|
|
146
|
-
|
|
147
|
-
`
|
|
148
|
-
(
|
|
149
|
-
|
|
150
|
-
`
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
(`
|
|
155
|
-
`
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
145
|
+
`AssistantDock` (`svelte/components/assistant/`, exported from `./svelte`)
|
|
146
|
+
uses a host `DataSurfaceRegistry`'s mounted descriptors (none: plain chat).
|
|
147
|
+
Actions: `normalizeDataSurfaceActionRequest` → `AssistantActionClient`
|
|
148
|
+
(preview/apply over a server `DataSurfaceActionAdapter`, not the
|
|
149
|
+
`data-surface-bridge.ts` channel). Generated `ChatThread`/`ChatMessage`
|
|
150
|
+
`list`/`get` are tenant- not member-scoped; the dock never calls them.
|
|
151
|
+
|
|
152
|
+
**Server routes (#3368).** `./sveltekit` `mountAssistantRoutes()` serves the
|
|
153
|
+
dock from one `[...path]` route; `createAssistantHttp{Transport,ActionClient}`
|
|
154
|
+
(`./svelte`) are its browser half. Principal from `event.locals` or
|
|
155
|
+
`resolvePrincipal`; threads only from the actor's keyed session room in that
|
|
156
|
+
tenant (else 404). `allowedTools` is fail-closed; without `tools` it offers
|
|
157
|
+
those manifest ops (an unprovided name is an error, #3414). `ai` defaults to
|
|
158
|
+
the config `ai` block. Pass `runtime`: a turn never keeps the RLS request tx
|
|
159
|
+
(own `runAsPrincipal` tx). `clientRequestId` makes the row id a UUIDv5
|
|
160
|
+
(`clientRequestMessageId`): the PK is the retry reservation. See
|
|
161
|
+
[docs](../../docs/assistant-dock.md).
|
|
161
162
|
|
|
162
163
|
## Gotchas
|
|
163
164
|
|
package/README.md
CHANGED
|
@@ -260,6 +260,30 @@ waiting | done | failed | cancelled`), with `pauseRun` / `continueRun`,
|
|
|
260
260
|
`matchesToolAllowList` is exported browser-safe. Full guide:
|
|
261
261
|
[`docs/assistant-dock.md`](../../docs/assistant-dock.md).
|
|
262
262
|
|
|
263
|
+
The server side mounts in one route file. The principal comes from
|
|
264
|
+
`event.locals` (`@happyvertical/smrt-users/sveltekit`); threads are scoped to
|
|
265
|
+
the caller's own assistant session in the active tenant; tools are
|
|
266
|
+
fail-closed (`allowedTools`, absent means none) and, without `tools`, are the
|
|
267
|
+
manifest operations `allowedTools` names. `ai` defaults to the `smrt.config`
|
|
268
|
+
`ai` block. Pass the application `runtime`: each request then uses its own
|
|
269
|
+
database (`runtime.databaseConfig()`), and under `database-rls` a streamed
|
|
270
|
+
turn, which outlives the request's transaction, runs in a transaction of its
|
|
271
|
+
own for the same principal (`runtime.runAsPrincipal`):
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
// src/routes/api/assistant/[...path]/+server.ts
|
|
275
|
+
import { mountAssistantRoutes } from '@happyvertical/smrt-chat/sveltekit';
|
|
276
|
+
import { runtime } from '$lib/server/smrt';
|
|
277
|
+
export const { GET, POST } = mountAssistantRoutes({
|
|
278
|
+
allowedTools: ['notes.read', 'notes.create'],
|
|
279
|
+
runtime,
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
In the page, `createAssistantHttpTransport({ endpoint: '/api/assistant' })`
|
|
284
|
+
is the dock's `transport`, and `createAssistantHttpActionClient` its
|
|
285
|
+
`actionClient` when the route is given `actions: { adapter }`.
|
|
286
|
+
|
|
263
287
|
## API
|
|
264
288
|
|
|
265
289
|
### Models
|
package/dist/assistant-turn.d.ts
CHANGED
|
@@ -59,6 +59,12 @@ export interface AssistantTurnContinuation {
|
|
|
59
59
|
tokens?: number;
|
|
60
60
|
/** `Date.now()` when the turn started (for `maxTurnMs`). Absent: `createdAt`. */
|
|
61
61
|
startedAt?: number;
|
|
62
|
+
/**
|
|
63
|
+
* The message that started the turn (`AssistantTurnOptions.originMessageId`),
|
|
64
|
+
* kept server-side so a resume restores it from here, never from the
|
|
65
|
+
* request.
|
|
66
|
+
*/
|
|
67
|
+
originMessageId?: string;
|
|
62
68
|
}
|
|
63
69
|
/**
|
|
64
70
|
* Where suspended turns wait. `take` is single-use: it returns the
|
|
@@ -69,7 +75,24 @@ export interface AssistantTurnContinuation {
|
|
|
69
75
|
export interface AssistantContinuationStore {
|
|
70
76
|
save(key: string, continuation: AssistantTurnContinuation): Promise<void>;
|
|
71
77
|
take(key: string, id: string): Promise<AssistantTurnContinuation | null>;
|
|
78
|
+
/**
|
|
79
|
+
* Whether `id` is still waiting under `key` (present and unexpired, or
|
|
80
|
+
* claimed by a resume that is still activating), without consuming it.
|
|
81
|
+
* Optional; both built-in stores implement it.
|
|
82
|
+
*/
|
|
83
|
+
has?(key: string, id: string): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Forget a continuation `take` returned. The built-in stores' `take`
|
|
86
|
+
* CLAIMS the entry (no second `take` can return it, and `has` stays true)
|
|
87
|
+
* instead of deleting it, so a resume is never invisible between taking
|
|
88
|
+
* its continuation and recording that it runs; the runner calls `release`
|
|
89
|
+
* once that is recorded. An unreleased claim expires after
|
|
90
|
+
* {@link CONTINUATION_CLAIM_TTL_MS}. Optional.
|
|
91
|
+
*/
|
|
92
|
+
release?(key: string, id: string): Promise<void>;
|
|
72
93
|
}
|
|
94
|
+
/** How long a claimed (taken, unreleased) continuation still counts. */
|
|
95
|
+
export declare const CONTINUATION_CLAIM_TTL_MS: number;
|
|
73
96
|
/** In-memory store, for tests and single-process demos. */
|
|
74
97
|
export declare function createMemoryContinuationStore(options?: {
|
|
75
98
|
ttlMs?: number;
|
|
@@ -135,6 +158,29 @@ export interface AssistantTurnOptions<M = Record<string, unknown>> {
|
|
|
135
158
|
continuationId: string;
|
|
136
159
|
results: ClientToolResultInput[];
|
|
137
160
|
};
|
|
161
|
+
/**
|
|
162
|
+
* The message that started this turn (e.g. the user's send). Every reply
|
|
163
|
+
* the turn authors links to it (`replyToMessageId`), and a suspension keeps
|
|
164
|
+
* it with the continuation. Ignored on resume: the consumed continuation's
|
|
165
|
+
* value is used instead.
|
|
166
|
+
*/
|
|
167
|
+
originMessageId?: string;
|
|
168
|
+
/**
|
|
169
|
+
* The turn's own lifecycle, reported (and awaited) by the runner at the
|
|
170
|
+
* moment each fact becomes true, whether or not anyone still reads the
|
|
171
|
+
* events: `running` when a fresh turn starts or a resume has consumed its
|
|
172
|
+
* continuation, `suspended` once the continuation is stored (before its
|
|
173
|
+
* `client_tool_calls` event), and `completed` / `cancelled` / `failed`
|
|
174
|
+
* when the leg ends (`completed` after the reply is stored). A throw is
|
|
175
|
+
* logged and never breaks the turn, except for a resume's `running`: when
|
|
176
|
+
* that report throws or returns `false` (not recorded), the leg stops
|
|
177
|
+
* before any model or tool call with `resume_not_recorded`, and its
|
|
178
|
+
* continuation stays claimed (claimable again after
|
|
179
|
+
* {@link CONTINUATION_CLAIM_TTL_MS}). Not called for a resume whose
|
|
180
|
+
* continuation is missing, foreign or expired. Resolve `false` (or throw)
|
|
181
|
+
* for "not recorded"; any other value counts as recorded.
|
|
182
|
+
*/
|
|
183
|
+
onState?: (state: AssistantTurnState) => unknown;
|
|
138
184
|
tools?: ManifestTool[];
|
|
139
185
|
/** Server tools, narrowed to `principal.allowedTools`. */
|
|
140
186
|
extraTools?: PrincipalTool[];
|
|
@@ -188,11 +234,25 @@ export interface AssistantTurnOptions<M = Record<string, unknown>> {
|
|
|
188
234
|
onError?: AssistantTurnErrorLogger;
|
|
189
235
|
now?: () => number;
|
|
190
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* One lifecycle fact of a turn leg, for {@link AssistantTurnOptions.onState}.
|
|
239
|
+
* `resumedFrom` is the continuation this leg consumed (`null` for the first
|
|
240
|
+
* leg), so a recorder can refuse a write from a leg that is no longer
|
|
241
|
+
* current. `continuationId` is set for `suspended`.
|
|
242
|
+
*/
|
|
243
|
+
export interface AssistantTurnState {
|
|
244
|
+
state: 'running' | 'suspended' | 'completed' | 'cancelled' | 'failed';
|
|
245
|
+
originMessageId?: string;
|
|
246
|
+
resumedFrom: string | null;
|
|
247
|
+
continuationId?: string;
|
|
248
|
+
}
|
|
191
249
|
/** What {@link runAssistantTurn} returns when its generator completes. */
|
|
192
250
|
export interface AssistantTurnResult {
|
|
193
251
|
stoppedReason: AssistantTurnStopReason | 'client_tools' | 'error';
|
|
194
252
|
content: string;
|
|
195
253
|
loop?: ToolLoopResult;
|
|
254
|
+
/** The turn's origin message (from the continuation on a resume). */
|
|
255
|
+
originMessageId?: string;
|
|
196
256
|
}
|
|
197
257
|
/** What the browser is told when a turn fails unexpectedly. */
|
|
198
258
|
export declare const ASSISTANT_TURN_GENERIC_ERROR = "The assistant ran into a problem. Please try again.";
|
|
@@ -232,5 +292,10 @@ export declare function createAssistantTurnResponse(events: AsyncGenerator<Assis
|
|
|
232
292
|
headers?: Record<string, string>;
|
|
233
293
|
/** Server-side log for a failure while pulling (default `console.error`). */
|
|
234
294
|
onError?: AssistantTurnErrorLogger;
|
|
295
|
+
/**
|
|
296
|
+
* Called synchronously when the reader cancels the body (a client that
|
|
297
|
+
* went away), before the generator is closed — e.g. to abort the turn.
|
|
298
|
+
*/
|
|
299
|
+
onCancel?: () => void;
|
|
235
300
|
}): Response;
|
|
236
301
|
//# sourceMappingURL=assistant-turn.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-turn.d.ts","sourceRoot":"","sources":["../src/assistant-turn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAC7E,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACd,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAEV,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAE1B,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAE1B,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAExB,iEAAiE;AACjE,eAAO,MAAM,2BAA2B,QAAiB,CAAC;AAE1D;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,CAAC,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,gDAAgD;IAChD,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,mDAAmD;IACnD,WAAW,EAAE,oBAAoB,EAAE,CAAC;IACpC,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"assistant-turn.d.ts","sourceRoot":"","sources":["../src/assistant-turn.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
1
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, crossPackageRef, field, foreignKey, isPostgresDatabase, smrt } from "@happyvertical/smrt-core";
|
|
2
2
|
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
3
|
-
import { ProfileCollection, resolveAgentProfileId } from "@happyvertical/smrt-profiles";
|
|
4
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { ProfileCollection, resolveAgentProfileId } from "@happyvertical/smrt-profiles";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
6
|
var __defProp$7 = Object.defineProperty;
|
|
7
7
|
var __exportAll = (all, no_symbols) => {
|
|
@@ -1231,9 +1231,37 @@ var VoiceSessionCollection = class extends SmrtCollection {
|
|
|
1231
1231
|
//#endregion
|
|
1232
1232
|
//#region src/services/ChatService.ts
|
|
1233
1233
|
var ChatService_exports = /* @__PURE__ */ __exportAll({
|
|
1234
|
+
ChatClientRequestConflictError: () => ChatClientRequestConflictError,
|
|
1234
1235
|
ChatService: () => ChatService,
|
|
1236
|
+
clientRequestMessageId: () => clientRequestMessageId,
|
|
1235
1237
|
sendAgentReply: () => sendAgentReply
|
|
1236
1238
|
});
|
|
1239
|
+
var CLIENT_REQUEST_NAMESPACE = "b7e2f1c4-5a3d-4e8f-9b6a-2c1d0e9f8a7b";
|
|
1240
|
+
function clientRequestMessageId(params) {
|
|
1241
|
+
const namespace = Buffer.from(CLIENT_REQUEST_NAMESPACE.replace(/-/g, ""), "hex");
|
|
1242
|
+
const name = Buffer.from(JSON.stringify([
|
|
1243
|
+
"smrt-chat:client-request",
|
|
1244
|
+
params.tenantId,
|
|
1245
|
+
params.roomId,
|
|
1246
|
+
params.threadId ?? null,
|
|
1247
|
+
params.actorProfileId,
|
|
1248
|
+
params.clientRequestId
|
|
1249
|
+
]), "utf8");
|
|
1250
|
+
const bytes = createHash("sha1").update(namespace).update(name).digest().subarray(0, 16);
|
|
1251
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
1252
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1253
|
+
const hex = bytes.toString("hex");
|
|
1254
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1255
|
+
}
|
|
1256
|
+
var ChatClientRequestConflictError = class extends Error {
|
|
1257
|
+
constructor(messageId) {
|
|
1258
|
+
super("A message for this client request is already stored.");
|
|
1259
|
+
this.messageId = messageId;
|
|
1260
|
+
this.name = "ChatClientRequestConflictError";
|
|
1261
|
+
}
|
|
1262
|
+
messageId;
|
|
1263
|
+
code = "CHAT_CLIENT_REQUEST_CONFLICT";
|
|
1264
|
+
};
|
|
1237
1265
|
var RUN_AGENT_REPLY = /* @__PURE__ */ Symbol("smrt-chat.runAgentReply");
|
|
1238
1266
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
1239
1267
|
var ChatService = class ChatService {
|
|
@@ -1330,17 +1358,58 @@ var ChatService = class ChatService {
|
|
|
1330
1358
|
* {@link ChatService.writeMessage} path.
|
|
1331
1359
|
*/
|
|
1332
1360
|
async sendMessage(params) {
|
|
1333
|
-
|
|
1361
|
+
const id = params.clientRequestId ? clientRequestMessageId({
|
|
1334
1362
|
tenantId: params.tenantId,
|
|
1335
1363
|
roomId: params.roomId,
|
|
1336
|
-
senderProfileId: params.actorProfileId,
|
|
1337
|
-
content: params.content,
|
|
1338
|
-
role: "user",
|
|
1339
|
-
messageType: params.messageType ?? "text",
|
|
1340
1364
|
threadId: params.threadId ?? null,
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
});
|
|
1365
|
+
actorProfileId: params.actorProfileId,
|
|
1366
|
+
clientRequestId: params.clientRequestId
|
|
1367
|
+
}) : void 0;
|
|
1368
|
+
const savepoint = id ? this.#reservationSavepoint() : null;
|
|
1369
|
+
if (savepoint) await savepoint.open();
|
|
1370
|
+
try {
|
|
1371
|
+
const message = await this.#writeMessage({
|
|
1372
|
+
tenantId: params.tenantId,
|
|
1373
|
+
roomId: params.roomId,
|
|
1374
|
+
senderProfileId: params.actorProfileId,
|
|
1375
|
+
content: params.content,
|
|
1376
|
+
role: "user",
|
|
1377
|
+
messageType: params.messageType ?? "text",
|
|
1378
|
+
threadId: params.threadId ?? null,
|
|
1379
|
+
agentSessionId: params.agentSessionId ?? null,
|
|
1380
|
+
replyToMessageId: params.replyToMessageId ?? null,
|
|
1381
|
+
...id ? { id } : {},
|
|
1382
|
+
metadata: params.clientRequestId ? { clientRequestId: params.clientRequestId } : null,
|
|
1383
|
+
attachments: params.attachments?.length ? params.attachments : null
|
|
1384
|
+
});
|
|
1385
|
+
if (savepoint) await savepoint.release();
|
|
1386
|
+
return message;
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
if (savepoint) await savepoint.rollback();
|
|
1389
|
+
if (id && error?.code === "VALIDATION_UNIQUE_CONSTRAINT") throw new ChatClientRequestConflictError(id);
|
|
1390
|
+
throw error;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* A savepoint around a reserved insert when the messages handle is a
|
|
1395
|
+
* PostgreSQL transaction handle (`@happyvertical/sql` transaction handles
|
|
1396
|
+
* have no `beginTransaction`), else `null`.
|
|
1397
|
+
*/
|
|
1398
|
+
#reservationSavepoint() {
|
|
1399
|
+
const db = this.#messages.db;
|
|
1400
|
+
if (!isPostgresDatabase(db) || typeof db.beginTransaction === "function" || typeof db.query !== "function") return null;
|
|
1401
|
+
const name = `smrt_chat_reserve_${createHash("sha1").update(`${Date.now()}:${Math.random()}`).digest("hex").slice(0, 12)}`;
|
|
1402
|
+
return {
|
|
1403
|
+
open: async () => {
|
|
1404
|
+
await db.query(`SAVEPOINT ${name}`);
|
|
1405
|
+
},
|
|
1406
|
+
release: async () => {
|
|
1407
|
+
await db.query(`RELEASE SAVEPOINT ${name}`);
|
|
1408
|
+
},
|
|
1409
|
+
rollback: async () => {
|
|
1410
|
+
await db.query(`ROLLBACK TO SAVEPOINT ${name}`).catch(() => void 0);
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1344
1413
|
}
|
|
1345
1414
|
/**
|
|
1346
1415
|
* INTERNAL persistence path for all message writes. Not exposed publicly: it
|
|
@@ -1375,6 +1444,10 @@ var ChatService = class ChatService {
|
|
|
1375
1444
|
})) throw new Error("replyToMessageId does not belong to this room/tenant (authorization denied)");
|
|
1376
1445
|
}
|
|
1377
1446
|
const message = await this.#messages.create({
|
|
1447
|
+
...write.id ? {
|
|
1448
|
+
id: write.id,
|
|
1449
|
+
_insertOnly: true
|
|
1450
|
+
} : {},
|
|
1378
1451
|
tenantId: write.tenantId,
|
|
1379
1452
|
roomId: write.roomId,
|
|
1380
1453
|
senderProfileId: write.senderProfileId,
|
|
@@ -1384,7 +1457,9 @@ var ChatService = class ChatService {
|
|
|
1384
1457
|
threadId: write.threadId ?? null,
|
|
1385
1458
|
agentSessionId: write.agentSessionId ?? null,
|
|
1386
1459
|
replyToMessageId: write.replyToMessageId ?? null,
|
|
1387
|
-
toolCallData: write.toolCallData ? JSON.stringify(write.toolCallData) : null
|
|
1460
|
+
toolCallData: write.toolCallData ? JSON.stringify(write.toolCallData) : null,
|
|
1461
|
+
...write.metadata ? { metadata: JSON.stringify(write.metadata) } : {},
|
|
1462
|
+
...write.attachments ? { attachments: JSON.stringify(write.attachments) } : {}
|
|
1388
1463
|
});
|
|
1389
1464
|
const room = await this.#rooms.get({
|
|
1390
1465
|
id: write.roomId,
|
|
@@ -1754,6 +1829,7 @@ var ChatService = class ChatService {
|
|
|
1754
1829
|
messageType: params.messageType ?? "text",
|
|
1755
1830
|
threadId: params.threadId ?? null,
|
|
1756
1831
|
agentSessionId: params.agentSessionId,
|
|
1832
|
+
replyToMessageId: params.replyToMessageId ?? null,
|
|
1757
1833
|
toolCallData: params.toolCallData ?? null
|
|
1758
1834
|
});
|
|
1759
1835
|
}
|
|
@@ -1963,6 +2039,89 @@ var ChatService = class ChatService {
|
|
|
1963
2039
|
})).reverse();
|
|
1964
2040
|
}
|
|
1965
2041
|
/**
|
|
2042
|
+
* A stored message and the replies linked to it (`replyToMessageId`),
|
|
2043
|
+
* oldest first (#3368), or `null` when the message is not in the thread.
|
|
2044
|
+
* Tenant- and membership-bound like {@link ChatService.getThreadMessages}.
|
|
2045
|
+
* Replies are found by their link, not by position, so overlapping turns
|
|
2046
|
+
* in one thread never claim each other's replies.
|
|
2047
|
+
*/
|
|
2048
|
+
async getThreadMessageReplies(params) {
|
|
2049
|
+
const thread = await this.#threads.get({
|
|
2050
|
+
id: params.threadId,
|
|
2051
|
+
tenantId: params.tenantId
|
|
2052
|
+
});
|
|
2053
|
+
if (!thread) throw new Error("Thread not found");
|
|
2054
|
+
await this.#requireActiveMembership(thread.roomId, params.actorProfileId, params.tenantId);
|
|
2055
|
+
const message = await this.#messages.get({
|
|
2056
|
+
id: params.messageId,
|
|
2057
|
+
threadId: params.threadId,
|
|
2058
|
+
tenantId: params.tenantId
|
|
2059
|
+
});
|
|
2060
|
+
if (!message || message.isDeleted) return null;
|
|
2061
|
+
return {
|
|
2062
|
+
message,
|
|
2063
|
+
replies: await this.#messages.list({
|
|
2064
|
+
where: {
|
|
2065
|
+
tenantId: params.tenantId,
|
|
2066
|
+
threadId: params.threadId,
|
|
2067
|
+
replyToMessageId: params.messageId,
|
|
2068
|
+
isDeleted: false
|
|
2069
|
+
},
|
|
2070
|
+
orderBy: "created_at ASC",
|
|
2071
|
+
limit: params.limit ?? 100
|
|
2072
|
+
})
|
|
2073
|
+
};
|
|
2074
|
+
}
|
|
2075
|
+
/**
|
|
2076
|
+
* Record where the turn answering a client request stands, on the actor's
|
|
2077
|
+
* own user message (`metadata.turnOutcome`, #3368). Only the message's
|
|
2078
|
+
* author, still a member of its room, can record it.
|
|
2079
|
+
*
|
|
2080
|
+
* Transitions only move forward, per leg, and are compare-and-set on the
|
|
2081
|
+
* row's revision (a concurrent writer re-reads and is re-checked):
|
|
2082
|
+
* `unset → running → suspended(c) → running(resumedFrom c) → … →
|
|
2083
|
+
* completed | cancelled | failed`, plus `unset → suspended | terminal` for
|
|
2084
|
+
* a first leg whose earlier write was lost. A write from a leg that is not
|
|
2085
|
+
* the current one (its `resumedFrom` differs), a `running` that does not
|
|
2086
|
+
* consume the waiting continuation, or anything after a terminal outcome
|
|
2087
|
+
* is refused. Returns whether the write was applied.
|
|
2088
|
+
*/
|
|
2089
|
+
async recordClientRequestOutcome(params) {
|
|
2090
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
2091
|
+
const message = await this.#messages.get({
|
|
2092
|
+
id: params.messageId,
|
|
2093
|
+
threadId: params.threadId,
|
|
2094
|
+
tenantId: params.tenantId
|
|
2095
|
+
});
|
|
2096
|
+
if (message?.role !== "user" || message.senderProfileId !== params.actorProfileId) throw new Error("Message not found");
|
|
2097
|
+
await this.#requireActiveMembership(message.roomId, params.actorProfileId, params.tenantId);
|
|
2098
|
+
const metadata = message.getMetadata();
|
|
2099
|
+
if (!ChatService.#outcomeTransitionAllowed(metadata, params.outcome, params.resumedFrom ?? null, params.continuationId ?? null)) return false;
|
|
2100
|
+
message.setMetadata({
|
|
2101
|
+
...metadata,
|
|
2102
|
+
turnOutcome: params.outcome,
|
|
2103
|
+
turnSettledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2104
|
+
turnContinuationId: params.outcome === "suspended" ? params.continuationId ?? null : params.resumedFrom ?? null
|
|
2105
|
+
});
|
|
2106
|
+
try {
|
|
2107
|
+
await message.save();
|
|
2108
|
+
return true;
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
if (error?.code !== "RUNTIME_REVISION_CONFLICT") throw error;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return false;
|
|
2114
|
+
}
|
|
2115
|
+
static #outcomeTransitionAllowed(metadata, next, resumedFrom, continuationId) {
|
|
2116
|
+
const current = typeof metadata.turnOutcome === "string" ? metadata.turnOutcome : null;
|
|
2117
|
+
const recordedId = typeof metadata.turnContinuationId === "string" ? metadata.turnContinuationId : null;
|
|
2118
|
+
if (current === "completed" || current === "cancelled" || current === "failed") return false;
|
|
2119
|
+
if (next === "running") return resumedFrom === null ? current === null : current === "suspended" && recordedId === resumedFrom;
|
|
2120
|
+
if (next === "suspended" && !continuationId) return false;
|
|
2121
|
+
if (current === "running") return recordedId === resumedFrom;
|
|
2122
|
+
return current === null && resumedFrom === null;
|
|
2123
|
+
}
|
|
2124
|
+
/**
|
|
1966
2125
|
* Update the per-session agent configuration (allowedTools / systemPrompt),
|
|
1967
2126
|
* restricted to the session owner — the room owner participant (S5 #1392).
|
|
1968
2127
|
*
|
|
@@ -2015,6 +2174,6 @@ function sendAgentReply(service, params) {
|
|
|
2015
2174
|
return ChatService[RUN_AGENT_REPLY](service, params);
|
|
2016
2175
|
}
|
|
2017
2176
|
//#endregion
|
|
2018
|
-
export {
|
|
2177
|
+
export { sendAgentReply as a, ChatThread as c, ChatParticipant as d, ChatMessage as f, clientRequestMessageId as i, ChatRoom as l, ChatService as n, VoiceSessionCollection as o, AgentSession as p, ChatService_exports as r, VoiceSession as s, ChatClientRequestConflictError as t, ChatReaction as u };
|
|
2019
2178
|
|
|
2020
|
-
//# sourceMappingURL=ChatService-
|
|
2179
|
+
//# sourceMappingURL=ChatService-Dl5tsK_F.js.map
|