@ai-matrx/chat 0.7.10 → 0.7.12
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 +24 -0
- package/dist/agents/components/messages-display/assistant/AgentAssistantMessage.js +2 -1
- package/dist/agents/components/messages-display/user/AgentUserMessage.js +10 -4
- package/dist/agents/components/shared/transcript-layout.d.ts +4 -2
- package/dist/agents/redux/execution-system/instance-variable-values/instance-variable-values.selectors.d.ts +70 -0
- package/dist/agents/redux/execution-system/instance-variable-values/instance-variable-values.selectors.js +15 -1
- 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/ui/markdown-stream/MarkdownStream.d.ts +2 -1
- package/dist/ui/markdown-stream/MarkdownStream.js +24 -5
- 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 +16 -16
- package/dist/agents/run/useRunAgent.d.ts +0 -69
- package/dist/agents/run/useRunAgent.js +0 -135
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @ai-matrx/chat
|
|
2
2
|
|
|
3
|
+
## 0.7.12
|
|
4
|
+
### Unreleased
|
|
5
|
+
### The variables a run used (Agent Factory R59)
|
|
6
|
+
|
|
7
|
+
- **`selectVariablesTheRunUsed(conversationId)`** (instance-variable-values selectors): the values a run's first turn was sent
|
|
8
|
+
with, frozen at send; before a send, the request merge. The builder's Side Chat helper reads it so a later edit of the field
|
|
9
|
+
cannot rewrite what the run was given.
|
|
10
|
+
- **Consumer action:** none.
|
|
11
|
+
|
|
12
|
+
### A persisted answer paints with its markdown (spend register item 32, cold-load CLS at 1440)
|
|
13
|
+
|
|
14
|
+
- **`MarkdownStream`**: the engine chunk is requested when a conversation surface's module loads (`preloadMarkdownStream`), kept once
|
|
15
|
+
resolved, and rendered synchronously on the first render of a new instance. The per-instance `mounted` effect gate (an extra commit
|
|
16
|
+
after paint) is now `useSyncExternalStore` (still client-only: server and hydration render nothing). Before, the answer block painted
|
|
17
|
+
as a 36 px header and grew to its full height ~200 ms later, shifting everything below it (CLS 0.06-0.28 at 1440).
|
|
18
|
+
- **Consumer action:** none.
|
|
19
|
+
|
|
20
|
+
## 0.7.11
|
|
21
|
+
### continue-agent-json is exported again
|
|
22
|
+
|
|
23
|
+
- **`./agents/redux/execution-system/thunks/continue-agent-json`** is back in the public surface; 0.7.9 dropped it
|
|
24
|
+
from `public-modules.json` while `matrx-frontend`'s Applet builder imports it (`continueAgentJson`).
|
|
25
|
+
- **Consumer action:** none.
|
|
26
|
+
|
|
3
27
|
## 0.7.10
|
|
4
28
|
### a refused answer is a failed run (lane M3)
|
|
5
29
|
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useState,
|
|
7
7
|
useEffect
|
|
8
8
|
} from "react";
|
|
9
|
-
import MarkdownStream from "../../../../ui/markdown-stream/MarkdownStream.js";
|
|
9
|
+
import MarkdownStream, { preloadMarkdownStream } from "../../../../ui/markdown-stream/MarkdownStream.js";
|
|
10
10
|
import "../rich-document/chat-message-source.js";
|
|
11
11
|
import { RecordAnnotations, annotationRecordOf } from "@ai-matrx/rich-content/host/app-bindings";
|
|
12
12
|
import { useAppDispatch, useAppSelector } from "../../../../store/hooks.js";
|
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
} from "../../../../host/ui-slots.js";
|
|
92
92
|
import { ErrorAlchemyMenu } from "../../../../host/ui-slots.js";
|
|
93
93
|
import { useInPlaceTrigger } from "@ai-matrx/rich-editor/in-place/EditInPlace";
|
|
94
|
+
void preloadMarkdownStream();
|
|
94
95
|
const _NO_LIVE_SOURCES = [];
|
|
95
96
|
const _selectNoLiveSources = () => _NO_LIVE_SOURCES;
|
|
96
97
|
function retryErrorMessage(err) {
|
|
@@ -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",
|
|
@@ -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>;
|
|
@@ -105,6 +105,76 @@ export declare const selectOwnVariableValues: (conversationId: string) => ((stat
|
|
|
105
105
|
memoize: typeof import("reselect").weakMapMemoize;
|
|
106
106
|
argsMemoize: typeof import("reselect").weakMapMemoize;
|
|
107
107
|
};
|
|
108
|
+
/**
|
|
109
|
+
* The variables a run USED: the values its first turn was sent with — frozen at
|
|
110
|
+
* send, host-wired names included — so a later edit of the variable field never
|
|
111
|
+
* rewrites what the run was given. Before any send it is what the next send would
|
|
112
|
+
* carry (the request merge, defaults included). Read by the agent builder's Side
|
|
113
|
+
* Chat helper to judge a test run.
|
|
114
|
+
*/
|
|
115
|
+
export declare const selectVariablesTheRunUsed: (conversationId: string) => ((state: {
|
|
116
|
+
voiceAgent: import("../../../../voice-agent/types.js").VoiceAgentState;
|
|
117
|
+
agentDefinition: import("../../../types/agent-definition.types.js").AgentDefinitionSliceState;
|
|
118
|
+
conversationList: import("../../conversation-list/conversation-list.types.js").ConversationListState;
|
|
119
|
+
conversationHistory: import("../../conversation-history/types.js").ConversationHistoryState;
|
|
120
|
+
agentShortcut: import("../../agent-shortcuts/types.js").AgentShortcutSliceState;
|
|
121
|
+
agentShortcutCategory: import("../../agent-shortcut-categories/types.js").AgentShortcutCategorySliceState;
|
|
122
|
+
surfaceUserState: import("../../../../surfaces/redux/userStateSlice.js").SurfaceUserStateSlice;
|
|
123
|
+
conversations: import("../conversations/conversations.slice.js").ConversationsState;
|
|
124
|
+
chatIncognito: import("../../chat/chat-incognito.slice.js").ChatIncognitoState;
|
|
125
|
+
chatRoute: import("../../chat/chat-route.slice.js").ChatRouteState;
|
|
126
|
+
instanceModelOverrides: import("../instance-model-overrides/instance-model-overrides.slice.js").InstanceModelOverridesState;
|
|
127
|
+
instanceInputCapabilities: import("../instance-input-capabilities/instance-input-capabilities.slice.js").InstanceInputCapabilitiesState;
|
|
128
|
+
instanceVariableValues: import("./instance-variable-values.slice.js").InstanceVariableValuesState;
|
|
129
|
+
instanceResources: import("../instance-resources/instance-resources.slice.js").InstanceResourcesState;
|
|
130
|
+
instanceContext: import("../instance-context/instance-context.slice.js").InstanceContextState;
|
|
131
|
+
instanceWorkingDocument: import("../instance-working-document/instance-working-document.slice.js").InstanceWorkingDocumentSliceState;
|
|
132
|
+
instanceUserInput: import("../instance-user-input/instance-user-input.slice.js").InstanceUserInputSliceState;
|
|
133
|
+
instanceClientTools: import("../instance-client-tools/instance-client-tools.slice.js").InstanceClientToolsState;
|
|
134
|
+
pendingAsks: import("../../../ui-first-tools/redux/pending-asks.slice.js").PendingAsksState;
|
|
135
|
+
conversationInbox: import("../inbox/inbox.slice.js").ConversationInboxState;
|
|
136
|
+
agentLists: import("../../../ui-first-tools/redux/agent-lists.slice.js").AgentListsState;
|
|
137
|
+
instanceUIState: import("../instance-ui-state/instance-ui-state.slice.js").InstanceUIStateSlice;
|
|
138
|
+
activeTools: import("../active-tools/active-tools.slice.js").ActiveToolsState;
|
|
139
|
+
activeRequests: import("../active-requests/active-requests.slice.js").ActiveRequestsState;
|
|
140
|
+
runSets: import("../run-sets/run-sets.slice.js").RunSetsState;
|
|
141
|
+
messages: import("../messages/messages.slice.js").MessagesState;
|
|
142
|
+
observability: import("../observability/observability.slice.js").ObservabilityState;
|
|
143
|
+
contextState: import("../context-state/context-state.slice.js").ContextStateSliceState;
|
|
144
|
+
observationalMemory: import("../observational-memory/observational-memory.slice.js").ObservationalMemoryState;
|
|
145
|
+
cacheBypass: import("../message-crud/cache-bypass.slice.js").CacheBypassState;
|
|
146
|
+
conversationFocus: import("../conversation-focus/conversation-focus.slice.js").ConversationFocusState;
|
|
147
|
+
surfaces: import("../../surfaces/surfaces.slice.js").SurfacesState;
|
|
148
|
+
surfacesCatalog: import("../../../../surfaces/redux/surfacesCatalogSlice.js").SurfacesCatalogSliceState;
|
|
149
|
+
agentSurfaceBindings: import("../../../../surfaces/redux/agentSurfaceBindingsSlice.js").AgentSurfaceBindingsSliceState;
|
|
150
|
+
surfaceConfig: import("../../../../surfaces/redux/surfaceConfigSlice.js").SurfaceConfigSliceState;
|
|
151
|
+
agentAssistantMarkdownDraft: import("../../agent-assistant-markdown-draft.slice.js").AgentAssistantMarkdownDraftState;
|
|
152
|
+
mcp: import("../../mcp/mcp.slice.js").McpSliceState;
|
|
153
|
+
proposedDirectives: import("../../proposed-directives/proposedDirectivesSlice.js").ProposedDirectivesState;
|
|
154
|
+
conversationAttachments: import("../../../connectors/attachments.slice.js").AttachmentsSliceState;
|
|
155
|
+
chatHost: import("../../../../store/chat-host.slice.js").ChatHostState;
|
|
156
|
+
hostConversations: import("../host-conversations/host-conversations.slice.js").HostConversationsState;
|
|
157
|
+
} & import("../../../../store/root-state.js").ChatHostReadSlices) => Record<string, unknown>) & {
|
|
158
|
+
clearCache: () => void;
|
|
159
|
+
resultsCount: () => number;
|
|
160
|
+
resetResultsCount: () => void;
|
|
161
|
+
} & {
|
|
162
|
+
resultFunc: (resultFuncArgs_0: import("./instance-variable-values.slice.js").InstanceVariableValuesEntry) => Record<string, unknown>;
|
|
163
|
+
memoizedResultFunc: ((resultFuncArgs_0: import("./instance-variable-values.slice.js").InstanceVariableValuesEntry) => Record<string, unknown>) & {
|
|
164
|
+
clearCache: () => void;
|
|
165
|
+
resultsCount: () => number;
|
|
166
|
+
resetResultsCount: () => void;
|
|
167
|
+
};
|
|
168
|
+
lastResult: () => Record<string, unknown>;
|
|
169
|
+
dependencies: [(state: ChatRootState) => import("./instance-variable-values.slice.js").InstanceVariableValuesEntry];
|
|
170
|
+
recomputations: () => number;
|
|
171
|
+
resetRecomputations: () => void;
|
|
172
|
+
dependencyRecomputations: () => number;
|
|
173
|
+
resetDependencyRecomputations: () => void;
|
|
174
|
+
} & {
|
|
175
|
+
memoize: typeof import("reselect").weakMapMemoize;
|
|
176
|
+
argsMemoize: typeof import("reselect").weakMapMemoize;
|
|
177
|
+
};
|
|
108
178
|
/** Immutable first-turn values, excluding names the host supplied. */
|
|
109
179
|
export declare const selectOwnSubmittedFirstTurnValues: (conversationId: string) => ((state: {
|
|
110
180
|
voiceAgent: import("../../../../voice-agent/types.js").VoiceAgentState;
|
|
@@ -25,6 +25,19 @@ const selectOwnVariableValues = (conversationId) => createSelector(
|
|
|
25
25
|
return Object.keys(own).length === 0 ? EMPTY_RECORD : own;
|
|
26
26
|
}
|
|
27
27
|
);
|
|
28
|
+
const selectVariablesTheRunUsed = (conversationId) => createSelector(
|
|
29
|
+
(state) => state.instanceVariableValues.byConversationId[conversationId],
|
|
30
|
+
(entry) => {
|
|
31
|
+
if (!entry) return EMPTY_RECORD;
|
|
32
|
+
if (entry.submittedFirstTurnValues) return entry.submittedFirstTurnValues;
|
|
33
|
+
const merged = resolveVariablesForRequest({
|
|
34
|
+
definitions: entry.definitions,
|
|
35
|
+
userValues: entry.userValues,
|
|
36
|
+
scopeValues: entry.scopeValues
|
|
37
|
+
});
|
|
38
|
+
return Object.keys(merged).length > 0 ? merged : EMPTY_RECORD;
|
|
39
|
+
}
|
|
40
|
+
);
|
|
28
41
|
const selectOwnSubmittedFirstTurnValues = (conversationId) => createSelector(
|
|
29
42
|
(state) => state.instanceVariableValues.byConversationId[conversationId],
|
|
30
43
|
(entry) => {
|
|
@@ -134,5 +147,6 @@ export {
|
|
|
134
147
|
selectScopeVariableValues,
|
|
135
148
|
selectUserVariableValues,
|
|
136
149
|
selectVariableProvenance,
|
|
137
|
-
selectVariablesForRequest
|
|
150
|
+
selectVariablesForRequest,
|
|
151
|
+
selectVariablesTheRunUsed
|
|
138
152
|
};
|
|
@@ -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;
|
|
@@ -83,6 +83,7 @@ export interface MarkdownStreamProps {
|
|
|
83
83
|
*/
|
|
84
84
|
imagePolicy?: "self" | "other" | "ai" | "inherit";
|
|
85
85
|
}
|
|
86
|
-
declare function
|
|
86
|
+
export declare function preloadMarkdownStream(): Promise<void>;
|
|
87
|
+
declare function MarkdownStream(props: MarkdownStreamProps): import("react").FunctionComponentElement<import("react").SuspenseProps> | import("react").ReactElement<MarkdownStreamProps, string | import("react").JSXElementConstructor<any>> | null;
|
|
87
88
|
export { MarkdownStream };
|
|
88
89
|
export default MarkdownStream;
|
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createElement, lazy, Suspense,
|
|
2
|
+
import { createElement, lazy, Suspense, useSyncExternalStore } from "react";
|
|
3
3
|
const MarkdownStreamImpl = lazy(() => import("./MarkdownStreamImpl.js"));
|
|
4
|
+
let loadedImpl = null;
|
|
5
|
+
let loading = null;
|
|
6
|
+
const readyListeners = /* @__PURE__ */ new Set();
|
|
7
|
+
function preloadMarkdownStream() {
|
|
8
|
+
if (typeof window === "undefined") return Promise.resolve();
|
|
9
|
+
loading ??= import("./MarkdownStreamImpl.js").then((m) => {
|
|
10
|
+
loadedImpl = m.default;
|
|
11
|
+
for (const notify of readyListeners) notify();
|
|
12
|
+
});
|
|
13
|
+
return loading;
|
|
14
|
+
}
|
|
15
|
+
const subscribeReady = (notify) => {
|
|
16
|
+
readyListeners.add(notify);
|
|
17
|
+
return () => readyListeners.delete(notify);
|
|
18
|
+
};
|
|
19
|
+
const clientReady = () => true;
|
|
20
|
+
const serverNotReady = () => false;
|
|
4
21
|
function MarkdownStream(props) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
if (
|
|
22
|
+
const client = useSyncExternalStore(subscribeReady, clientReady, serverNotReady);
|
|
23
|
+
if (!client) return null;
|
|
24
|
+
if (loadedImpl) return createElement(loadedImpl, props);
|
|
25
|
+
void preloadMarkdownStream();
|
|
8
26
|
return createElement(Suspense, { fallback: null }, createElement(MarkdownStreamImpl, props));
|
|
9
27
|
}
|
|
10
28
|
var MarkdownStream_default = MarkdownStream;
|
|
11
29
|
export {
|
|
12
30
|
MarkdownStream,
|
|
13
|
-
MarkdownStream_default as default
|
|
31
|
+
MarkdownStream_default as default,
|
|
32
|
+
preloadMarkdownStream
|
|
14
33
|
};
|
|
@@ -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.12",
|
|
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/alchemy": "latest",
|
|
4685
4684
|
"@ai-matrx/associations": "latest",
|
|
4686
4685
|
"@ai-matrx/browser-audio": "latest",
|
|
4687
4686
|
"@ai-matrx/canvas": "latest",
|
|
4688
|
-
"@ai-matrx/content-ir": "latest",
|
|
4689
|
-
"@ai-matrx/content-ir-react": "latest",
|
|
4690
4687
|
"@ai-matrx/agents": "latest",
|
|
4688
|
+
"@ai-matrx/content-ir-react": "latest",
|
|
4689
|
+
"@ai-matrx/alchemy": "latest",
|
|
4690
|
+
"@ai-matrx/data": "latest",
|
|
4691
|
+
"@ai-matrx/design-system": "latest",
|
|
4691
4692
|
"@ai-matrx/icons": "latest",
|
|
4692
|
-
"@ai-matrx/diff": "latest",
|
|
4693
|
-
"@ai-matrx/media": "latest",
|
|
4694
4693
|
"@ai-matrx/kit": "latest",
|
|
4694
|
+
"@ai-matrx/media": "latest",
|
|
4695
4695
|
"@ai-matrx/print": "latest",
|
|
4696
4696
|
"@ai-matrx/realtime": "latest",
|
|
4697
|
+
"@ai-matrx/records": "latest",
|
|
4697
4698
|
"@ai-matrx/records-ui": "latest",
|
|
4698
|
-
"@ai-matrx/design-system": "latest",
|
|
4699
4699
|
"@ai-matrx/rich-content": "latest",
|
|
4700
|
-
"@ai-matrx/
|
|
4701
|
-
"@ai-matrx/
|
|
4702
|
-
"@ai-matrx/
|
|
4700
|
+
"@ai-matrx/rich-editor": "latest",
|
|
4701
|
+
"@ai-matrx/content-ir": "latest",
|
|
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": "24ad46786a0c433cb3773b270bf74c9eb32898bc"
|
|
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
|
-
};
|