@ai-matrx/chat 0.7.9 → 0.7.11
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/CHANGELOG.md +21 -0
- package/dist/agents/components/messages-display/AgentConversationDisplay.d.ts +1 -2
- package/dist/agents/components/messages-display/AgentConversationDisplay.js +3 -7
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.d.ts +1 -3
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +1 -23
- package/dist/agents/components/messages-display/assistant/AssistantTurnGroup.d.ts +1 -2
- package/dist/agents/components/messages-display/assistant/AssistantTurnGroup.js +2 -4
- package/dist/agents/components/messages-display/assistant/answerless-turn.d.ts +0 -2
- package/dist/agents/components/messages-display/assistant/answerless-turn.js +0 -1
- package/dist/agents/components/messages-display/user/AgentUserMessage.js +10 -4
- package/dist/agents/components/shared/AgentConversationColumn.js +0 -1
- package/dist/agents/components/shared/transcript-layout.d.ts +4 -2
- package/dist/agents/hooks/useHeadlessAgentJson.js +9 -1
- package/dist/agents/redux/execution-system/thunks/record-run-output-refused.d.ts +14 -0
- package/dist/agents/redux/execution-system/thunks/record-run-output-refused.js +24 -0
- package/dist/host/defaults/server-api.d.ts +2 -1
- package/dist/host/defaults/server-api.js +7 -0
- package/dist/host/server/provider-session-usage.d.ts +5 -0
- package/dist/host/server/provider-session-usage.js +5 -0
- package/dist/voice-agent/hooks/useXaiVoiceSession.js +13 -0
- package/dist/voice-agent/usage.d.ts +20 -0
- package/dist/voice-agent/usage.js +50 -0
- package/package.json +11 -11
- package/dist/agents/run/useRunAgent.d.ts +0 -69
- package/dist/agents/run/useRunAgent.js +0 -135
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.7.11
|
|
4
|
+
### continue-agent-json is exported again
|
|
5
|
+
|
|
6
|
+
- **`./agents/redux/execution-system/thunks/continue-agent-json`** is back in the public surface; 0.7.9 dropped it
|
|
7
|
+
from `public-modules.json` while `matrx-frontend`'s Applet builder imports it (`continueAgentJson`).
|
|
8
|
+
- **Consumer action:** none.
|
|
9
|
+
|
|
10
|
+
## 0.7.10
|
|
11
|
+
### a refused answer is a failed run (lane M3)
|
|
12
|
+
|
|
13
|
+
- **`useHeadlessAgentJson.run`**: when `coerce` refuses the answer the model gave, the run is recorded as failed
|
|
14
|
+
(`chat.user_request` status `failed`, `error` = the reason, `metadata.output_refused`) through the guarded
|
|
15
|
+
`mergeJsonColumn` write (`recordRunOutputRefused`); a write that does not land is a `console.error`. Before, the run
|
|
16
|
+
stayed `completed` with `error` null forever. The error is re-thrown unchanged for the surface to say plainly.
|
|
17
|
+
- **Consumer action:** none.
|
|
18
|
+
|
|
3
19
|
## 0.7.9
|
|
4
20
|
|
|
5
21
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
@@ -15,6 +31,11 @@ No source changes intended and no consumer action required.
|
|
|
15
31
|
## 0.7.7
|
|
16
32
|
### Unreleased
|
|
17
33
|
|
|
34
|
+
- **No first-load layout shift from assistant messages** (spend register item 32): `AgentAssistantMessage` no longer swaps a fixed-height
|
|
35
|
+
skeleton for the markdown after 140 ms (measured CLS 0.48 on the run page). Persisted answers render in place on first paint.
|
|
36
|
+
`deferColdMarkdown` is removed from `AgentAssistantMessage`, `AssistantTurnGroup` and `AgentConversationDisplay` (the column keeps its
|
|
37
|
+
own history staging). **Consumer action:** none.
|
|
38
|
+
|
|
18
39
|
- **`continueAgentJson`** (`agents/redux/execution-system/thunks/continue-agent-json`): the NEXT turn of a
|
|
19
40
|
conversation whose first turn ran headlessly, waited on like the first (`adoptHeadlessAgentJson`). A turn is
|
|
20
41
|
the person's words (`userInput`) or a turn the HOST writes (`hostTurn` — sent as `host_turn`, never
|
|
@@ -8,9 +8,8 @@ interface AgentConversationDisplayProps {
|
|
|
8
8
|
*/
|
|
9
9
|
surfaceKey?: string;
|
|
10
10
|
compact?: boolean;
|
|
11
|
-
deferColdMarkdown?: boolean;
|
|
12
11
|
fallbackVisibleGroupLimit?: number | null;
|
|
13
12
|
bottomPinned?: boolean;
|
|
14
13
|
}
|
|
15
|
-
export declare function AgentConversationDisplay({ conversationId, surfaceKey, compact,
|
|
14
|
+
export declare function AgentConversationDisplay({ conversationId, surfaceKey, compact, fallbackVisibleGroupLimit, bottomPinned, }: AgentConversationDisplayProps): import("react").JSX.Element;
|
|
16
15
|
export {};
|
|
@@ -53,7 +53,6 @@ function AgentConversationDisplay({
|
|
|
53
53
|
conversationId,
|
|
54
54
|
surfaceKey,
|
|
55
55
|
compact = false,
|
|
56
|
-
deferColdMarkdown = false,
|
|
57
56
|
fallbackVisibleGroupLimit = null,
|
|
58
57
|
bottomPinned = false
|
|
59
58
|
}) {
|
|
@@ -262,8 +261,7 @@ function AgentConversationDisplay({
|
|
|
262
261
|
isStreamActive: false,
|
|
263
262
|
surfaceKey,
|
|
264
263
|
compact: true,
|
|
265
|
-
hideActionBar: true
|
|
266
|
-
deferColdMarkdown
|
|
264
|
+
hideActionBar: true
|
|
267
265
|
},
|
|
268
266
|
member.messageId
|
|
269
267
|
)
|
|
@@ -307,8 +305,7 @@ function AgentConversationDisplay({
|
|
|
307
305
|
isStreamActive: group.isStreamActive,
|
|
308
306
|
surfaceKey,
|
|
309
307
|
compact,
|
|
310
|
-
canRetry: group.canRetry
|
|
311
|
-
deferColdMarkdown
|
|
308
|
+
canRetry: group.canRetry
|
|
312
309
|
}
|
|
313
310
|
)
|
|
314
311
|
},
|
|
@@ -327,8 +324,7 @@ function AgentConversationDisplay({
|
|
|
327
324
|
conversationId,
|
|
328
325
|
surfaceKey,
|
|
329
326
|
compact,
|
|
330
|
-
members: group.members
|
|
331
|
-
deferColdMarkdown
|
|
327
|
+
members: group.members
|
|
332
328
|
}
|
|
333
329
|
)
|
|
334
330
|
},
|
|
@@ -33,8 +33,6 @@ export interface AgentAssistantMessageProps {
|
|
|
33
33
|
* button. Retry is non-destructive (the failed turn stays in history).
|
|
34
34
|
*/
|
|
35
35
|
canRetry?: boolean;
|
|
36
|
-
/** Chat cold-load only: show a short text skeleton before DB markdown mounts. */
|
|
37
|
-
deferColdMarkdown?: boolean;
|
|
38
36
|
/**
|
|
39
37
|
* False for the intermediate iterations of a multi-step agentic turn. Only
|
|
40
38
|
* the LAST member of a turn carries the answer, so only it may say "this run
|
|
@@ -43,4 +41,4 @@ export interface AgentAssistantMessageProps {
|
|
|
43
41
|
*/
|
|
44
42
|
isTurnAnswer?: boolean;
|
|
45
43
|
}
|
|
46
|
-
export declare function AgentAssistantMessage({ conversationId, requestId, messageId, isStreamActive, recordMessageIds, streamSlotStart, streamSlotEnd, surfaceKey, hideActionBar, canRetry,
|
|
44
|
+
export declare function AgentAssistantMessage({ conversationId, requestId, messageId, isStreamActive, recordMessageIds, streamSlotStart, streamSlotEnd, surfaceKey, hideActionBar, canRetry, isTurnAnswer, }: AgentAssistantMessageProps): import("react").JSX.Element;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import {
|
|
4
|
-
startTransition,
|
|
5
4
|
useCallback,
|
|
6
5
|
useMemo,
|
|
7
6
|
useState,
|
|
@@ -117,7 +116,6 @@ function AgentAssistantMessage({
|
|
|
117
116
|
surfaceKey,
|
|
118
117
|
hideActionBar = false,
|
|
119
118
|
canRetry = false,
|
|
120
|
-
deferColdMarkdown = false,
|
|
121
119
|
isTurnAnswer = true
|
|
122
120
|
}) {
|
|
123
121
|
useDebugContext("AgentAssistantMessage");
|
|
@@ -137,17 +135,7 @@ function AgentAssistantMessage({
|
|
|
137
135
|
}, [surfaceKey, conversationId, messageId, requestId, isStreamActive]);
|
|
138
136
|
const dispatch = useAppDispatch();
|
|
139
137
|
const [retrying, setRetrying] = useState(false);
|
|
140
|
-
const shouldDeferColdMarkdown = deferColdMarkdown && !requestId && !!messageId && !isStreamActive;
|
|
141
|
-
const [coldMarkdownSettledMessageId, setColdMarkdownSettledMessageId] = useState(null);
|
|
142
|
-
const coldMarkdownReady = !shouldDeferColdMarkdown || coldMarkdownSettledMessageId === messageId;
|
|
143
138
|
const [providerRetryBusyAction, setProviderRetryBusyAction] = useState(null);
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
if (!shouldDeferColdMarkdown || !messageId) return void 0;
|
|
146
|
-
const timer = window.setTimeout(() => {
|
|
147
|
-
startTransition(() => setColdMarkdownSettledMessageId(messageId));
|
|
148
|
-
}, 140);
|
|
149
|
-
return () => window.clearTimeout(timer);
|
|
150
|
-
}, [shouldDeferColdMarkdown, messageId]);
|
|
151
139
|
const { captureRef, isCapturing, captureAsPDF } = useDomCapturePrint();
|
|
152
140
|
const handleFullPrint = useCallback(() => {
|
|
153
141
|
captureAsPDF({
|
|
@@ -308,7 +296,6 @@ function AgentAssistantMessage({
|
|
|
308
296
|
rowParts: extractContentBlocks(record)
|
|
309
297
|
}),
|
|
310
298
|
failed,
|
|
311
|
-
coldMarkdownReady,
|
|
312
299
|
messageId,
|
|
313
300
|
renderedText,
|
|
314
301
|
attachmentCount: attachmentParts.length,
|
|
@@ -394,7 +381,7 @@ function AgentAssistantMessage({
|
|
|
394
381
|
"data-message-id": messageId ?? void 0,
|
|
395
382
|
className: "group/assistant-msg rounded transition-shadow",
|
|
396
383
|
children: [
|
|
397
|
-
|
|
384
|
+
bufferStream && isStreamActive && !failed ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
398
385
|
showProviderRetry && providerRetry && /* @__PURE__ */ jsx(
|
|
399
386
|
ProviderRetryCard,
|
|
400
387
|
{
|
|
@@ -538,15 +525,6 @@ function readErrorDoor(details) {
|
|
|
538
525
|
const { label, href } = door;
|
|
539
526
|
return typeof label === "string" && typeof href === "string" && href.startsWith("/") ? { label, href } : null;
|
|
540
527
|
}
|
|
541
|
-
function AssistantMarkdownSkeleton() {
|
|
542
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 py-1", "aria-hidden": true, children: [
|
|
543
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[96%] animate-pulse rounded bg-muted/55" }),
|
|
544
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[88%] animate-pulse rounded bg-muted/50" }),
|
|
545
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[72%] animate-pulse rounded bg-muted/45" }),
|
|
546
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[94%] animate-pulse rounded bg-muted/40" }),
|
|
547
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-[42%] animate-pulse rounded bg-muted/35" })
|
|
548
|
-
] });
|
|
549
|
-
}
|
|
550
528
|
export {
|
|
551
529
|
AgentAssistantMessage
|
|
552
530
|
};
|
|
@@ -16,7 +16,6 @@ interface AssistantTurnGroupProps {
|
|
|
16
16
|
compact?: boolean;
|
|
17
17
|
/** Every assistant member of this logical turn, in transcript order. */
|
|
18
18
|
members: AssistantTurnGroupMember[];
|
|
19
|
-
deferColdMarkdown?: boolean;
|
|
20
19
|
}
|
|
21
|
-
export declare function AssistantTurnGroup({ conversationId, surfaceKey, compact, members: rawMembers,
|
|
20
|
+
export declare function AssistantTurnGroup({ conversationId, surfaceKey, compact, members: rawMembers, }: AssistantTurnGroupProps): import("react").JSX.Element;
|
|
22
21
|
export {};
|
|
@@ -26,8 +26,7 @@ function AssistantTurnGroup({
|
|
|
26
26
|
conversationId,
|
|
27
27
|
surfaceKey,
|
|
28
28
|
compact = false,
|
|
29
|
-
members: rawMembers
|
|
30
|
-
deferColdMarkdown = false
|
|
29
|
+
members: rawMembers
|
|
31
30
|
}) {
|
|
32
31
|
const { captureRef, isCapturing, captureAsPDF } = useDomCapturePrint();
|
|
33
32
|
const persistedView = useAppSelector(
|
|
@@ -96,8 +95,7 @@ function AssistantTurnGroup({
|
|
|
96
95
|
surfaceKey,
|
|
97
96
|
compact,
|
|
98
97
|
hideActionBar: true,
|
|
99
|
-
isTurnAnswer: idx === members.length - 1 && m.streamSlotEnd === void 0
|
|
100
|
-
deferColdMarkdown: deferColdMarkdown && idx < members.length - 1
|
|
98
|
+
isTurnAnswer: idx === members.length - 1 && m.streamSlotEnd === void 0
|
|
101
99
|
}
|
|
102
100
|
) }, m.key))
|
|
103
101
|
}
|
|
@@ -113,8 +113,6 @@ export interface AnswerlessTurnInput {
|
|
|
113
113
|
isStreamActive: boolean;
|
|
114
114
|
/** True when the turn failed — the error path owns that case. */
|
|
115
115
|
failed: boolean;
|
|
116
|
-
/** False while a cold-load skeleton stands in for the persisted markdown. */
|
|
117
|
-
coldMarkdownReady: boolean;
|
|
118
116
|
/** `cx_message.id`; null/undefined until the row settles. */
|
|
119
117
|
messageId: string | null | undefined;
|
|
120
118
|
/** The text this turn renders (citation markers included). */
|
|
@@ -31,7 +31,6 @@ function isAnswerlessTurn(input) {
|
|
|
31
31
|
if (input.isStreamActive) return false;
|
|
32
32
|
if (input.awaitingPerson) return false;
|
|
33
33
|
if (input.failed) return false;
|
|
34
|
-
if (!input.coldMarkdownReady) return false;
|
|
35
34
|
if (!input.messageId) return false;
|
|
36
35
|
if (input.attachmentCount > 0) return false;
|
|
37
36
|
if (input.mediaBlockCount > 0) return false;
|
|
@@ -204,7 +204,9 @@ function AgentUserMessage({
|
|
|
204
204
|
bodyBlocks,
|
|
205
205
|
trimmedText
|
|
206
206
|
]);
|
|
207
|
-
const
|
|
207
|
+
const userMessageLook = useTranscriptLayout().userMessage;
|
|
208
|
+
const bubbleUserMessage = userMessageLook === "bubble";
|
|
209
|
+
const compactUserMessage = userMessageLook === "compact" || bubbleUserMessage;
|
|
208
210
|
const { isCollapsed, setIsCollapsed, shouldBeCollapsible, measureRef } = useCollapsibleMessageText(collapseSignature, compactUserMessage ? COMPACT_COLLAPSE_THRESHOLD_PX : void 0);
|
|
209
211
|
const authoredByHost = metadata?.authored_by === "host";
|
|
210
212
|
const isSuperAdmin = useAppSelector(selectIsSuperAdmin);
|
|
@@ -271,7 +273,8 @@ function AgentUserMessage({
|
|
|
271
273
|
return /* @__PURE__ */ jsxs(
|
|
272
274
|
"div",
|
|
273
275
|
{
|
|
274
|
-
className: cn("group relative", containerMargin),
|
|
276
|
+
className: cn("group relative", containerMargin, bubbleUserMessage && "flex flex-col items-end"),
|
|
277
|
+
"data-user-message-look": userMessageLook,
|
|
275
278
|
"data-message-id": messageId,
|
|
276
279
|
onMouseEnter: () => setIsHovered(true),
|
|
277
280
|
onMouseLeave: () => setIsHovered(false),
|
|
@@ -292,11 +295,14 @@ function AgentUserMessage({
|
|
|
292
295
|
/* @__PURE__ */ jsx(
|
|
293
296
|
"div",
|
|
294
297
|
{
|
|
295
|
-
className:
|
|
298
|
+
className: bubbleUserMessage ? (
|
|
299
|
+
// The person's turn on the trailing side, in a rounded bubble as wide as its words (Messages).
|
|
300
|
+
"w-fit max-w-[80%] min-w-0 bg-muted rounded-2xl px-3.5 py-2 [&_.math-content-wrapper]:my-0 [&_p:last-child]:mb-0"
|
|
301
|
+
) : compactUserMessage ? (
|
|
296
302
|
// Tight: no inset from the left, and the markdown paragraph's own margins dropped.
|
|
297
303
|
"bg-muted rounded-md px-3 py-2 [&_.math-content-wrapper]:my-0 [&_p:last-child]:mb-0"
|
|
298
304
|
) : "bg-muted border border-border rounded-lg px-2 py-2",
|
|
299
|
-
"data-user-message-style": compactUserMessage ? "compact" : "card",
|
|
305
|
+
"data-user-message-style": bubbleUserMessage ? "bubble" : compactUserMessage ? "compact" : "card",
|
|
300
306
|
children: /* @__PURE__ */ jsxs("div", { className: "relative min-w-0", children: [
|
|
301
307
|
/* @__PURE__ */ jsxs(
|
|
302
308
|
"div",
|
|
@@ -299,7 +299,6 @@ function AgentConversationColumn({
|
|
|
299
299
|
{
|
|
300
300
|
conversationId: displayId,
|
|
301
301
|
surfaceKey,
|
|
302
|
-
deferColdMarkdown,
|
|
303
302
|
bottomPinned: shouldPinColdScroll,
|
|
304
303
|
fallbackVisibleGroupLimit: deferColdMarkdown ? chatVisibleGroupLimit ?? CHAT_INITIAL_VISIBLE_GROUPS : null
|
|
305
304
|
}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* - `width`: "reading" (the canonical `max-w-3xl` reading column) | "fill" (the host's pane).
|
|
9
9
|
* - `align`: "center" | "start" — where a reading-width column sits in a wider pane.
|
|
10
10
|
* - `userMessage`: "compact" (DEFAULT: a flat tight block; only a genuinely long message
|
|
11
|
-
* collapses) | "card" (the bordered card that collapses a long message behind a chevron)
|
|
11
|
+
* collapses) | "card" (the bordered card that collapses a long message behind a chevron) |
|
|
12
|
+
* "bubble" (Messages / Xcode's coding assistant: the person's turn on the trailing side in a
|
|
13
|
+
* rounded bubble as wide as its words, answers full width; collapses like "compact").
|
|
12
14
|
* - `answerActions`: "hover" (DEFAULT: the action row is revealed on hover / keyboard focus, and
|
|
13
15
|
* always on touch — its height is reserved, so revealing it moves nothing; a finished answer ends
|
|
14
16
|
* on its last word) | "latest" (the latest answer's row is always visible, older ones on hover) |
|
|
@@ -18,7 +20,7 @@ import { type ReactNode } from "react";
|
|
|
18
20
|
export interface TranscriptLayout {
|
|
19
21
|
width?: "reading" | "fill";
|
|
20
22
|
align?: "center" | "start";
|
|
21
|
-
userMessage?: "card" | "compact";
|
|
23
|
+
userMessage?: "card" | "compact" | "bubble";
|
|
22
24
|
answerActions?: "shown" | "latest" | "hover";
|
|
23
25
|
}
|
|
24
26
|
export declare const DEFAULT_TRANSCRIPT_LAYOUT: Required<TranscriptLayout>;
|
|
@@ -3,6 +3,7 @@ import { useCallback, useState } from "react";
|
|
|
3
3
|
import { useAppDispatch, useAppSelector, useAppStore } from "../../store/hooks.js";
|
|
4
4
|
import { selectConversationRequestIds } from "../redux/execution-system/active-requests/active-requests.selectors.js";
|
|
5
5
|
import { useRetainRequestForViewer } from "../redux/execution-system/active-requests/useRetainRequestForViewer.js";
|
|
6
|
+
import { recordRunOutputRefused } from "../redux/execution-system/thunks/record-run-output-refused.js";
|
|
6
7
|
import {
|
|
7
8
|
runHeadlessAgentJson
|
|
8
9
|
} from "../redux/execution-system/thunks/run-headless-agent-json.js";
|
|
@@ -49,7 +50,14 @@ function useHeadlessAgentJson() {
|
|
|
49
50
|
...result.conversationId ? { conversationId: result.conversationId } : {}
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
+
if (!opts.coerce) return result.data;
|
|
54
|
+
try {
|
|
55
|
+
return opts.coerce(result.data, result);
|
|
56
|
+
} catch (refused) {
|
|
57
|
+
const why = refused instanceof Error ? refused.message : String(refused);
|
|
58
|
+
if (result.requestId) void recordRunOutputRefused(result.requestId, why);
|
|
59
|
+
throw refused;
|
|
60
|
+
}
|
|
53
61
|
} catch (e) {
|
|
54
62
|
const message = e instanceof Error ? e.message : "Failed to run the agent";
|
|
55
63
|
setError(message);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recordRunOutputRefused — a run whose answer the BROWSER refused is a failed run.
|
|
3
|
+
*
|
|
4
|
+
* The server marks a run `completed` the moment the model stops; whether the answer was usable is only
|
|
5
|
+
* known where it is read (the feature's `coerce`, its own check). Before this, such a run stayed
|
|
6
|
+
* `completed` with `error` null forever (Make's describe box, 2026-10-09: "personal" is not one of
|
|
7
|
+
* organization, individual). The refusal is written onto the run's own `chat.user_request` row —
|
|
8
|
+
* status `failed`, `error` = the reason, and `metadata.output_refused` — through the guarded
|
|
9
|
+
* read-modify-write (`mergeJsonColumn`, a version guard, never a blind update).
|
|
10
|
+
*
|
|
11
|
+
* Never silent: a write that does not land is a `console.error` naming the run.
|
|
12
|
+
*/
|
|
13
|
+
export declare const OUTPUT_REFUSED_METADATA_KEY = "output_refused";
|
|
14
|
+
export declare function recordRunOutputRefused(requestId: string, reason: string): Promise<boolean>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mergeJsonColumn } from "@ai-matrx/data/db";
|
|
2
|
+
import { supabase } from "../../../../host/db.js";
|
|
3
|
+
import { hasBrowserSession } from "../../../../host/identity.js";
|
|
4
|
+
const OUTPUT_REFUSED_METADATA_KEY = "output_refused";
|
|
5
|
+
async function recordRunOutputRefused(requestId, reason) {
|
|
6
|
+
if (!requestId || !await hasBrowserSession()) return false;
|
|
7
|
+
const result = await mergeJsonColumn({
|
|
8
|
+
fetchCurrent: () => supabase.schema("chat").from("user_request").select("id, version, metadata").eq("id", requestId).maybeSingle(),
|
|
9
|
+
readColumn: (row) => row.metadata,
|
|
10
|
+
merge: (current) => ({ ...current, [OUTPUT_REFUSED_METADATA_KEY]: { reason, at: (/* @__PURE__ */ new Date()).toISOString() } }),
|
|
11
|
+
applyUpdate: ({ value, expectedVersion, nextVersion }) => supabase.schema("chat").from("user_request").update({ metadata: value, version: nextVersion, status: "failed", error: reason.slice(0, 500) }).eq("id", requestId).eq("version", expectedVersion).select("id, version, metadata").maybeSingle()
|
|
12
|
+
});
|
|
13
|
+
if (result.status !== "saved") {
|
|
14
|
+
console.error(
|
|
15
|
+
`[run-output-refused] Could not record the refusal on run ${requestId}: ` + (result.status === "error" ? String(result.error) : `write ${result.status}`) + `. The run still reads as completed. Reason: ${reason}`
|
|
16
|
+
);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
export {
|
|
22
|
+
OUTPUT_REFUSED_METADATA_KEY,
|
|
23
|
+
recordRunOutputRefused
|
|
24
|
+
};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* values here — no admin lane, no admin door, no endpoint overrides, no local
|
|
11
11
|
* engine, no browser extension (each refusal says so once).
|
|
12
12
|
*/
|
|
13
|
-
import { applyOrganizationContextHeader, type MatrxCallError, type MatrxCallResult, type MatrxTransport, type ProviderSessionFailure, type ProviderSessionFailureVerdict } from "@ai-matrx/agents/matrx";
|
|
13
|
+
import { applyOrganizationContextHeader, type MatrxCallError, type MatrxCallResult, type MatrxTransport, type ProviderSessionFailure, type ProviderSessionFailureVerdict, type ProviderSessionUsage } from "@ai-matrx/agents/matrx";
|
|
14
14
|
import type { TypedStreamEvent } from "@ai-matrx/agents/generated/stream-events";
|
|
15
15
|
import type { ApiPaths, ApiSchemas as Schemas, DefaultBrokeredCredential, DefaultRequestOptions, GetResult, PatchBody, PatchResult, PathWith, PostBody, PostResult } from "./typed-paths.js";
|
|
16
16
|
import type { ChatIdentityPort, ChatOrgPort, ChatServerPort } from "../contract.js";
|
|
@@ -349,6 +349,7 @@ export declare function createDefaultServerApi(host: () => ServerHostView): {
|
|
|
349
349
|
captureErrors?: boolean;
|
|
350
350
|
}) => Promise<DefaultBrokeredCredential>;
|
|
351
351
|
reportBrowserProviderFailure: (failure: ProviderSessionFailure) => DefaultServerThunk<Promise<ProviderSessionFailureVerdict | null>>;
|
|
352
|
+
reportBrowserProviderUsage: (usage: ProviderSessionUsage) => void;
|
|
352
353
|
parseNdjsonStream: (response: Response, signal?: AbortSignal) => {
|
|
353
354
|
requestId: string | null;
|
|
354
355
|
conversationId: string | null;
|
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
executeMatrxCall,
|
|
8
8
|
normalizeMatrxError,
|
|
9
9
|
parseMatrxNdjsonResponse,
|
|
10
|
+
createProviderUsageReporter,
|
|
10
11
|
reportProviderSessionFailure,
|
|
12
|
+
reportProviderSessionUsage,
|
|
11
13
|
sendMatrxRequest
|
|
12
14
|
} from "@ai-matrx/agents/matrx";
|
|
13
15
|
import { announceOnce } from "../errors.js";
|
|
@@ -149,6 +151,9 @@ function createDefaultServerApi(host) {
|
|
|
149
151
|
});
|
|
150
152
|
}
|
|
151
153
|
const productionUrl = () => baseUrl();
|
|
154
|
+
const usageReporter = createProviderUsageReporter({
|
|
155
|
+
send: (usage) => reportProviderSessionUsage(transport({ source: "providerSessionUsage" }), usage)
|
|
156
|
+
});
|
|
152
157
|
return {
|
|
153
158
|
// call-api
|
|
154
159
|
callApi,
|
|
@@ -234,6 +239,8 @@ function createDefaultServerApi(host) {
|
|
|
234
239
|
return null;
|
|
235
240
|
}
|
|
236
241
|
},
|
|
242
|
+
// what a browser-held provider session spent: queued, retried, billed once per usage_id
|
|
243
|
+
reportBrowserProviderUsage: (usage) => usageReporter.report(usage),
|
|
237
244
|
// the NDJSON stream every run reads
|
|
238
245
|
// THE shared parser: a body that breaks mid-run is a TRANSPORT loss (the
|
|
239
246
|
// run may still finish and is reattachable), never a failed run.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server seam — what a browser-held provider session spent (`POST /broker/usage`), reported through
|
|
3
|
+
* the configured host's server client. Fire-and-forget: the host queues, retries and de-duplicates.
|
|
4
|
+
*/
|
|
5
|
+
export declare const reportBrowserProviderUsage: (usage: import("@ai-matrx/agents").ProviderSessionUsage) => void;
|
|
@@ -53,6 +53,8 @@ import {
|
|
|
53
53
|
createXaiClient
|
|
54
54
|
} from "../transport/xaiClient.js";
|
|
55
55
|
import { reportBrowserProviderFailure } from "../../host/server/provider-session-failure.js";
|
|
56
|
+
import { reportBrowserProviderUsage } from "../../host/server/provider-session-usage.js";
|
|
57
|
+
import { createXaiUsageMeter } from "../usage.js";
|
|
56
58
|
import { transcriptTextFromEvent } from "../transport/serverEvents.js";
|
|
57
59
|
import {
|
|
58
60
|
voiceDebugLog,
|
|
@@ -103,6 +105,13 @@ function useXaiVoiceSession(opts) {
|
|
|
103
105
|
const voiceIdRef = useRef(voiceId);
|
|
104
106
|
const realtimeModelRef = useRef(realtimeModel);
|
|
105
107
|
realtimeModelRef.current = realtimeModel;
|
|
108
|
+
const usageMeterRef = useRef(null);
|
|
109
|
+
if (!usageMeterRef.current) {
|
|
110
|
+
usageMeterRef.current = createXaiUsageMeter({
|
|
111
|
+
model: () => realtimeModelRef.current,
|
|
112
|
+
report: (usage) => reportBrowserProviderUsage(usage)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
106
115
|
const instructionsRef = useRef(instructions);
|
|
107
116
|
const toolsRef = useRef(tools);
|
|
108
117
|
voiceIdRef.current = voiceId;
|
|
@@ -334,6 +343,7 @@ function useXaiVoiceSession(opts) {
|
|
|
334
343
|
switch (event.type) {
|
|
335
344
|
case "session.created":
|
|
336
345
|
voiceDebugLog(instanceId, "info", "ws.session.created");
|
|
346
|
+
usageMeterRef.current?.started(event.session.id);
|
|
337
347
|
break;
|
|
338
348
|
case "session.updated": {
|
|
339
349
|
voiceDebugLog(
|
|
@@ -523,6 +533,7 @@ function useXaiVoiceSession(opts) {
|
|
|
523
533
|
break;
|
|
524
534
|
}
|
|
525
535
|
case "response.done": {
|
|
536
|
+
usageMeterRef.current?.responseDone(event.response);
|
|
526
537
|
runFlush();
|
|
527
538
|
if (!pendingAssistantTurnIdRef.current) break;
|
|
528
539
|
const playedMs = playbackRef.current?.markTurnEnded() ?? 0;
|
|
@@ -658,6 +669,7 @@ function useXaiVoiceSession(opts) {
|
|
|
658
669
|
assistantTurnStartedAtMsRef.current = null;
|
|
659
670
|
assistantAudioStartedAtMsRef.current = null;
|
|
660
671
|
firstAudioReceivedRef.current = false;
|
|
672
|
+
usageMeterRef.current?.ended();
|
|
661
673
|
xaiClientRef.current?.disconnect();
|
|
662
674
|
clearSessionSubscriptions();
|
|
663
675
|
await captureRef.current?.stop();
|
|
@@ -771,6 +783,7 @@ function useXaiVoiceSession(opts) {
|
|
|
771
783
|
const unsubEvent = client.onEvent(handleServerEvent);
|
|
772
784
|
const unsubError = client.onError(handleClientError);
|
|
773
785
|
const unsubClose = client.onClose((info) => {
|
|
786
|
+
usageMeterRef.current?.ended();
|
|
774
787
|
voiceDebugIncr(instanceId, "closeCount");
|
|
775
788
|
voiceDebugSetFlags(instanceId, {
|
|
776
789
|
lastCloseIntentional: info.intentional,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ProviderSessionUsage } from "@ai-matrx/agents/matrx";
|
|
2
|
+
import type { XaiResponseUsage } from "./transport/serverEvents.js";
|
|
3
|
+
export interface XaiUsageMeterOptions {
|
|
4
|
+
/** The realtime model the session opened with; unresolved = nothing priceable to report. */
|
|
5
|
+
model: () => string | null | undefined;
|
|
6
|
+
report: (usage: ProviderSessionUsage) => void;
|
|
7
|
+
now?: () => number;
|
|
8
|
+
}
|
|
9
|
+
export interface XaiUsageMeter {
|
|
10
|
+
/** Session opened: start the clock and remember xAI's session id. */
|
|
11
|
+
started(sessionId: string | null | undefined): void;
|
|
12
|
+
/** One `response.done`: report its tokens plus the session time since the last report. */
|
|
13
|
+
responseDone(response: {
|
|
14
|
+
id: string;
|
|
15
|
+
usage?: XaiResponseUsage;
|
|
16
|
+
}): void;
|
|
17
|
+
/** Session closed (clean or dropped): report the trailing slice of session time. Idempotent. */
|
|
18
|
+
ended(): void;
|
|
19
|
+
}
|
|
20
|
+
export declare function createXaiUsageMeter(options: XaiUsageMeterOptions): XaiUsageMeter;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function createXaiUsageMeter(options) {
|
|
2
|
+
const now = options.now ?? (() => Date.now());
|
|
3
|
+
let sessionId = null;
|
|
4
|
+
let sliceStartedAt = null;
|
|
5
|
+
let ordinal = 0;
|
|
6
|
+
function takeSeconds() {
|
|
7
|
+
if (sliceStartedAt === null) return 0;
|
|
8
|
+
const at = now();
|
|
9
|
+
const seconds = Math.max(0, (at - sliceStartedAt) / 1e3);
|
|
10
|
+
sliceStartedAt = at;
|
|
11
|
+
return seconds;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
started(id) {
|
|
15
|
+
sessionId = id?.trim() || null;
|
|
16
|
+
sliceStartedAt = now();
|
|
17
|
+
ordinal = 0;
|
|
18
|
+
},
|
|
19
|
+
responseDone(response) {
|
|
20
|
+
const model = options.model()?.trim();
|
|
21
|
+
if (!model || !response.id) return;
|
|
22
|
+
options.report({
|
|
23
|
+
provider: "xai",
|
|
24
|
+
model,
|
|
25
|
+
usage_id: response.id,
|
|
26
|
+
input_tokens: response.usage?.input_tokens,
|
|
27
|
+
output_tokens: response.usage?.output_tokens,
|
|
28
|
+
seconds: takeSeconds()
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
ended() {
|
|
32
|
+
const model = options.model()?.trim();
|
|
33
|
+
const startedAt = sliceStartedAt;
|
|
34
|
+
sliceStartedAt = null;
|
|
35
|
+
if (!model || startedAt === null) return;
|
|
36
|
+
const seconds = Math.max(0, (now() - startedAt) / 1e3);
|
|
37
|
+
if (seconds < 1) return;
|
|
38
|
+
ordinal += 1;
|
|
39
|
+
options.report({
|
|
40
|
+
provider: "xai",
|
|
41
|
+
model,
|
|
42
|
+
usage_id: `${sessionId ?? `session-${startedAt}`}:end:${ordinal}`,
|
|
43
|
+
seconds
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
createXaiUsageMeter
|
|
50
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/chat",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.11",
|
|
4
4
|
"description": "THE one AI Matrx person-to-agent chat system: the full chat (running agents, conversations, tool calls, streaming, context, surfaces, voice) as explicit per-module subpaths @ai-matrx/chat/<domain>/<module> (no wildcards: scripts/public-modules.json) over a host contract (/host, /host/react) whose only required value is a db. One chat, one renderer: the old kernel entries (., /external-store, /matrx, /react, /tools, styles.css, tokens.css) are retired.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -2140,6 +2140,11 @@
|
|
|
2140
2140
|
"import": "./dist/agents/redux/execution-system/thunks/call-conversation-api.js",
|
|
2141
2141
|
"default": "./dist/agents/redux/execution-system/thunks/call-conversation-api.js"
|
|
2142
2142
|
},
|
|
2143
|
+
"./agents/redux/execution-system/thunks/continue-agent-json": {
|
|
2144
|
+
"types": "./dist/agents/redux/execution-system/thunks/continue-agent-json.d.ts",
|
|
2145
|
+
"import": "./dist/agents/redux/execution-system/thunks/continue-agent-json.js",
|
|
2146
|
+
"default": "./dist/agents/redux/execution-system/thunks/continue-agent-json.js"
|
|
2147
|
+
},
|
|
2143
2148
|
"./agents/redux/execution-system/thunks/conversation-bundle": {
|
|
2144
2149
|
"types": "./dist/agents/redux/execution-system/thunks/conversation-bundle.d.ts",
|
|
2145
2150
|
"import": "./dist/agents/redux/execution-system/thunks/conversation-bundle.js",
|
|
@@ -2435,11 +2440,6 @@
|
|
|
2435
2440
|
"import": "./dist/agents/route/buildSystemAgentAiPayload.js",
|
|
2436
2441
|
"default": "./dist/agents/route/buildSystemAgentAiPayload.js"
|
|
2437
2442
|
},
|
|
2438
|
-
"./agents/run/useRunAgent": {
|
|
2439
|
-
"types": "./dist/agents/run/useRunAgent.d.ts",
|
|
2440
|
-
"import": "./dist/agents/run/useRunAgent.js",
|
|
2441
|
-
"default": "./dist/agents/run/useRunAgent.js"
|
|
2442
|
-
},
|
|
2443
2443
|
"./agents/runtime-reconnect/ServerOperationBanner": {
|
|
2444
2444
|
"types": "./dist/agents/runtime-reconnect/ServerOperationBanner.d.ts",
|
|
2445
2445
|
"import": "./dist/agents/runtime-reconnect/ServerOperationBanner.js",
|
|
@@ -4681,25 +4681,25 @@
|
|
|
4681
4681
|
"server-only": "^0.0.1",
|
|
4682
4682
|
"uuid": "^14.0.2",
|
|
4683
4683
|
"zod": "^4.6.5",
|
|
4684
|
-
"@ai-matrx/agents": "latest",
|
|
4685
4684
|
"@ai-matrx/alchemy": "latest",
|
|
4686
4685
|
"@ai-matrx/associations": "latest",
|
|
4687
4686
|
"@ai-matrx/browser-audio": "latest",
|
|
4688
4687
|
"@ai-matrx/canvas": "latest",
|
|
4689
4688
|
"@ai-matrx/content-ir": "latest",
|
|
4689
|
+
"@ai-matrx/agents": "latest",
|
|
4690
|
+
"@ai-matrx/content-ir-react": "latest",
|
|
4690
4691
|
"@ai-matrx/data": "latest",
|
|
4691
4692
|
"@ai-matrx/design-system": "latest",
|
|
4693
|
+
"@ai-matrx/icons": "latest",
|
|
4692
4694
|
"@ai-matrx/kit": "latest",
|
|
4693
4695
|
"@ai-matrx/media": "latest",
|
|
4694
|
-
"@ai-matrx/icons": "latest",
|
|
4695
4696
|
"@ai-matrx/print": "latest",
|
|
4696
|
-
"@ai-matrx/diff": "latest",
|
|
4697
4697
|
"@ai-matrx/realtime": "latest",
|
|
4698
4698
|
"@ai-matrx/records": "latest",
|
|
4699
4699
|
"@ai-matrx/records-ui": "latest",
|
|
4700
4700
|
"@ai-matrx/rich-content": "latest",
|
|
4701
4701
|
"@ai-matrx/rich-editor": "latest",
|
|
4702
|
-
"@ai-matrx/
|
|
4702
|
+
"@ai-matrx/diff": "latest"
|
|
4703
4703
|
},
|
|
4704
4704
|
"devDependencies": {
|
|
4705
4705
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
@@ -4818,5 +4818,5 @@
|
|
|
4818
4818
|
"check:side-effects": "node ../scripts/check-side-effects-listed.mjs . && node ../scripts/check-side-effects-listed.mjs --self-test",
|
|
4819
4819
|
"check:public-surface": "node ./scripts/sync-public-surface.mjs --check && node ./scripts/sync-public-surface.mjs --self-test"
|
|
4820
4820
|
},
|
|
4821
|
-
"gitHead": "
|
|
4821
|
+
"gitHead": "db3bd1fad7851f7648e0aeee094cf4df6152b3ae"
|
|
4822
4822
|
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { Action } from "redux";
|
|
2
|
-
import type { ThunkAction } from "redux-thunk";
|
|
3
|
-
import type { ChatRootState } from "../../store/root-state.js";
|
|
4
|
-
import { type CallScope, type LLMParamsBody } from "../../host/server/call-api.js";
|
|
5
|
-
import type { components } from "@ai-matrx/agents/generated/api-types";
|
|
6
|
-
export interface RunAgentArgs {
|
|
7
|
-
/** Live agent id (UUID) or slug. */
|
|
8
|
-
agentId: string;
|
|
9
|
-
/** The user message sent to the agent (optional for variable-only agents). */
|
|
10
|
-
userInput?: string;
|
|
11
|
-
/** Variable name → value map, filling the agent's declared variables. */
|
|
12
|
-
variables?: Record<string, string>;
|
|
13
|
-
/** Per-run model/config overrides (temperature, ai_model_id, …). */
|
|
14
|
-
configOverrides?: LLMParamsBody;
|
|
15
|
-
/** Entity-local scope. Explicit values beat the user's global active context. */
|
|
16
|
-
organizationId?: string;
|
|
17
|
-
projectId?: string;
|
|
18
|
-
taskId?: string;
|
|
19
|
-
/**
|
|
20
|
-
* Stable durable-entity identity. The server reloads this row and uses its
|
|
21
|
-
* saved scope; the browser does not get to redefine that scope.
|
|
22
|
-
*/
|
|
23
|
-
contextAnchor?: components["schemas"]["ContextAnchor"];
|
|
24
|
-
/** Durable producer attribution for the conversation and usage ledger. */
|
|
25
|
-
sourceApp: string;
|
|
26
|
-
sourceFeature: string;
|
|
27
|
-
/**
|
|
28
|
-
* Interactivity attestation — the ONLY input a client has into
|
|
29
|
-
* `origin_class`. "user" (the default) is a person's gesture on the screen
|
|
30
|
-
* that calls this hook; client code firing a run with no gesture behind it
|
|
31
|
-
* passes "auto". Omitting it entirely is what makes the server class our own
|
|
32
|
-
* screens as unattested API callers.
|
|
33
|
-
*/
|
|
34
|
-
initiation?: "user" | "auto";
|
|
35
|
-
/** Abort the in-flight run. */
|
|
36
|
-
signal?: AbortSignal;
|
|
37
|
-
/** Stream chunk-by-chunk text as it arrives (e.g. to show live progress). */
|
|
38
|
-
onChunk?: (fullText: string) => void;
|
|
39
|
-
}
|
|
40
|
-
export interface UseRunAgent {
|
|
41
|
-
/** Run the agent and resolve with the full accumulated text output. */
|
|
42
|
-
run: (args: RunAgentArgs) => Promise<string>;
|
|
43
|
-
running: boolean;
|
|
44
|
-
error: string | null;
|
|
45
|
-
reset: () => void;
|
|
46
|
-
}
|
|
47
|
-
/** Body type for POST /ai/agents/{agent_id} with transport-injected scope. */
|
|
48
|
-
type RunAgentBody = Omit<components["schemas"]["AgentStartRequest"], "organization_id" | "project_id" | "task_id"> & Partial<Pick<components["schemas"]["AgentStartRequest"], "organization_id" | "project_id" | "task_id">>;
|
|
49
|
-
export declare function buildRunAgentRequest(args: RunAgentArgs): {
|
|
50
|
-
body: RunAgentBody;
|
|
51
|
-
scopeOverrides: Partial<CallScope>;
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* Run a one-shot agent through the published package client and resolve with
|
|
55
|
-
* the full text output. Thunk-shaped so the hook (and thunk-style callers)
|
|
56
|
-
* share ONE implementation with real `getState` access.
|
|
57
|
-
*
|
|
58
|
-
* Result semantics (parity with the pre-package flow, proven by
|
|
59
|
-
* `useRunAgent.test.ts` + `run-agent-via-matrx.test.ts`):
|
|
60
|
-
* - a fatal `error` event → throw its `user_message` → `message` → fallback;
|
|
61
|
-
* - a `failed`/`cancelled` `user_request` completion → throw
|
|
62
|
-
* `result.error` → `result.user_message` → "The agent run <status>"
|
|
63
|
-
* (when both fire, the error event wins — the package/projector ruling);
|
|
64
|
-
* - otherwise resolve accumulated chunk text, falling back to the
|
|
65
|
-
* completion's `result.output`.
|
|
66
|
-
*/
|
|
67
|
-
export declare function runAgentViaMatrxClient(args: RunAgentArgs): ThunkAction<Promise<string>, ChatRootState, unknown, Action>;
|
|
68
|
-
export declare function useRunAgent(): UseRunAgent;
|
|
69
|
-
export {};
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useCallback, useRef, useState } from "react";
|
|
3
|
-
import { useAppDispatch } from "../../store/hooks.js";
|
|
4
|
-
import {
|
|
5
|
-
startAgentRun
|
|
6
|
-
} from "@ai-matrx/agents/matrx";
|
|
7
|
-
import {
|
|
8
|
-
createAgentRequestProjection,
|
|
9
|
-
projectAgentEvent
|
|
10
|
-
} from "@ai-matrx/agents/projection/request";
|
|
11
|
-
import {
|
|
12
|
-
buildRequestBody,
|
|
13
|
-
resolveScope,
|
|
14
|
-
waitForAuthReady
|
|
15
|
-
} from "../../host/server/call-api.js";
|
|
16
|
-
import { createMatrxTransport } from "../../host/server/matrx-transport.js";
|
|
17
|
-
import { applyDesktopTargetToRequestBody } from "@ai-matrx/agents/matrx";
|
|
18
|
-
import { extractErrorMessage } from "@ai-matrx/data/net";
|
|
19
|
-
import { selectDesktopTargetInstanceId } from "../../host/prefs.js";
|
|
20
|
-
function buildRunAgentRequest(args) {
|
|
21
|
-
const scopeOverrides = {};
|
|
22
|
-
if (args.organizationId !== void 0) {
|
|
23
|
-
scopeOverrides.organization_id = args.organizationId;
|
|
24
|
-
}
|
|
25
|
-
if (args.projectId !== void 0) scopeOverrides.project_id = args.projectId;
|
|
26
|
-
if (args.taskId !== void 0) scopeOverrides.task_id = args.taskId;
|
|
27
|
-
return {
|
|
28
|
-
body: {
|
|
29
|
-
// One-shot run: a freshly minted id for correlation, is_new so the
|
|
30
|
-
// server never looks anything up, store:false so nothing is written.
|
|
31
|
-
conversation_id: crypto.randomUUID(),
|
|
32
|
-
is_new: true,
|
|
33
|
-
store: false,
|
|
34
|
-
user_input: args.userInput ?? null,
|
|
35
|
-
variables: args.variables && Object.keys(args.variables).length > 0 ? args.variables : void 0,
|
|
36
|
-
config_overrides: args.configOverrides,
|
|
37
|
-
source_app: args.sourceApp,
|
|
38
|
-
source_feature: args.sourceFeature,
|
|
39
|
-
initiation: args.initiation ?? "user",
|
|
40
|
-
context_anchor: args.contextAnchor,
|
|
41
|
-
stream: true,
|
|
42
|
-
debug: false
|
|
43
|
-
},
|
|
44
|
-
scopeOverrides
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const stringField = (bag, key) => {
|
|
48
|
-
if (typeof bag !== "object" || bag === null) return null;
|
|
49
|
-
const value = bag[key];
|
|
50
|
-
return typeof value === "string" && value ? value : null;
|
|
51
|
-
};
|
|
52
|
-
function runAgentViaMatrxClient(args) {
|
|
53
|
-
return async (_dispatch, getState) => {
|
|
54
|
-
await waitForAuthReady(getState);
|
|
55
|
-
const state = getState();
|
|
56
|
-
const { body, scopeOverrides } = buildRunAgentRequest(args);
|
|
57
|
-
const scope = resolveScope(state, scopeOverrides);
|
|
58
|
-
const request = buildRequestBody(body, scope);
|
|
59
|
-
const desktopTargetInstanceId = selectDesktopTargetInstanceId(state);
|
|
60
|
-
if (desktopTargetInstanceId) {
|
|
61
|
-
applyDesktopTargetToRequestBody(request, desktopTargetInstanceId);
|
|
62
|
-
}
|
|
63
|
-
const transport = createMatrxTransport(getState, {
|
|
64
|
-
organizationId: scope.organization_id,
|
|
65
|
-
source: "runAgentViaMatrxClient"
|
|
66
|
-
});
|
|
67
|
-
const handle = await startAgentRun(
|
|
68
|
-
transport,
|
|
69
|
-
args.agentId,
|
|
70
|
-
request,
|
|
71
|
-
args.signal ? { signal: args.signal } : {}
|
|
72
|
-
);
|
|
73
|
-
let projection = createAgentRequestProjection({
|
|
74
|
-
requestId: handle.requestId ?? String(request.conversation_id),
|
|
75
|
-
conversationId: handle.conversationId
|
|
76
|
-
});
|
|
77
|
-
for await (const envelope of handle.events) {
|
|
78
|
-
const previousAnswer = projection.answer;
|
|
79
|
-
projection = projectAgentEvent(projection, envelope);
|
|
80
|
-
if (projection.answer !== previousAnswer) {
|
|
81
|
-
args.onChunk?.(projection.answer);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (projection.error) {
|
|
85
|
-
throw new Error(
|
|
86
|
-
stringField(projection.error, "user_message") ?? stringField(projection.error, "message") ?? "The agent run failed"
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
const completion = projection.completion;
|
|
90
|
-
const completionStatus = typeof completion?.status === "string" ? completion.status : null;
|
|
91
|
-
if (completionStatus === "failed" || completionStatus === "cancelled") {
|
|
92
|
-
const result = completion?.result;
|
|
93
|
-
throw new Error(
|
|
94
|
-
stringField(result, "error") ?? stringField(result, "user_message") ?? `The agent run ${completionStatus}`
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
if (projection.answer) return projection.answer;
|
|
98
|
-
return stringField(completion?.result, "output") ?? "";
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
function useRunAgent() {
|
|
102
|
-
const dispatch = useAppDispatch();
|
|
103
|
-
const [running, setRunning] = useState(false);
|
|
104
|
-
const [error, setError] = useState(null);
|
|
105
|
-
const runningRef = useRef(false);
|
|
106
|
-
const reset = useCallback(() => {
|
|
107
|
-
setRunning(false);
|
|
108
|
-
setError(null);
|
|
109
|
-
runningRef.current = false;
|
|
110
|
-
}, []);
|
|
111
|
-
const run = useCallback(
|
|
112
|
-
async (args) => {
|
|
113
|
-
setRunning(true);
|
|
114
|
-
setError(null);
|
|
115
|
-
runningRef.current = true;
|
|
116
|
-
try {
|
|
117
|
-
return await dispatch(runAgentViaMatrxClient(args));
|
|
118
|
-
} catch (err) {
|
|
119
|
-
const message = extractErrorMessage(err);
|
|
120
|
-
setError(message);
|
|
121
|
-
throw err instanceof Error ? err : new Error(message);
|
|
122
|
-
} finally {
|
|
123
|
-
runningRef.current = false;
|
|
124
|
-
setRunning(false);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
[dispatch]
|
|
128
|
-
);
|
|
129
|
-
return { run, running, error, reset };
|
|
130
|
-
}
|
|
131
|
-
export {
|
|
132
|
-
buildRunAgentRequest,
|
|
133
|
-
runAgentViaMatrxClient,
|
|
134
|
-
useRunAgent
|
|
135
|
-
};
|