@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 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 compactUserMessage = useTranscriptLayout().userMessage === "compact";
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: compactUserMessage ? (
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;
@@ -0,0 +1,5 @@
1
+ import { forwardServer } from "../server.js";
2
+ const reportBrowserProviderUsage = forwardServer("reportBrowserProviderUsage");
3
+ export {
4
+ reportBrowserProviderUsage
5
+ };
@@ -83,6 +83,7 @@ export interface MarkdownStreamProps {
83
83
  */
84
84
  imagePolicy?: "self" | "other" | "ai" | "inherit";
85
85
  }
86
- declare function MarkdownStream(props: MarkdownStreamProps): import("react").FunctionComponentElement<import("react").SuspenseProps> | null;
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, useEffect, useState } from "react";
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 [mounted, setMounted] = useState(false);
6
- useEffect(() => setMounted(true), []);
7
- if (!mounted) return null;
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.10",
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/records": "latest",
4701
- "@ai-matrx/data": "latest",
4702
- "@ai-matrx/rich-editor": "latest"
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": "c1236d2d5f8a42087300bafce53317efb25700af"
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
- };