@ai-matrx/chat 0.4.22 → 0.4.24

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/agents/components/context-items/bodies/UnregisteredBody.d.ts +6 -3
  3. package/dist/agents/components/context-items/bodies/UnregisteredBody.js +3 -9
  4. package/dist/agents/components/inputs/resources/attach-resource.js +1 -0
  5. package/dist/agents/components/inputs/smart-input/SmartAgentInput.js +16 -1
  6. package/dist/agents/components/inputs/smart-input/composer/ComposerMenu.js +3 -2
  7. package/dist/agents/components/inputs/smart-input/composer/ComposerPlusMenu.js +41 -35
  8. package/dist/agents/components/messages-display/user/AgentUserMessage.js +3 -1
  9. package/dist/agents/components/shared/AgentConversationColumn.d.ts +10 -1
  10. package/dist/agents/components/shared/AgentConversationColumn.js +26 -2
  11. package/dist/agents/redux/execution-system/messages/messages.slice.js +6 -1
  12. package/dist/agents/redux/execution-system/thunks/await-launching-conversation.d.ts +16 -0
  13. package/dist/agents/redux/execution-system/thunks/await-launching-conversation.js +16 -0
  14. package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.js +34 -1
  15. package/dist/agents/redux/execution-system/thunks/process-stream.js +11 -0
  16. package/dist/agents/redux/execution-system/thunks/smart-execute.thunk.js +10 -0
  17. package/dist/host/app-data-slots.d.ts +0 -1
  18. package/dist/host/app-data-slots.js +25 -5
  19. package/dist/host/bare-host-chrome.js +4 -0
  20. package/dist/host/contract.d.ts +1 -1
  21. package/dist/host/contract.js +2 -1
  22. package/dist/host/conversation-hooks-react.d.ts +2 -0
  23. package/dist/host/conversation-hooks-react.js +12 -0
  24. package/dist/host/conversation-hooks.d.ts +79 -0
  25. package/dist/host/conversation-hooks.js +91 -0
  26. package/dist/host/defaults/bare-block-dispatch.d.ts +6 -0
  27. package/dist/host/defaults/bare-block-dispatch.js +60 -0
  28. package/dist/host/defaults/files-host.d.ts +26 -0
  29. package/dist/host/defaults/files-host.js +212 -0
  30. package/dist/host/defaults/resource-picker-menu.d.ts +14 -0
  31. package/dist/host/defaults/resource-picker-menu.js +117 -0
  32. package/dist/host/defaults/setting-control-input.d.ts +11 -0
  33. package/dist/host/defaults/setting-control-input.js +91 -0
  34. package/dist/host/index.d.ts +1 -0
  35. package/dist/host/index.js +1 -0
  36. package/dist/host/react.d.ts +1 -0
  37. package/dist/host/react.js +2 -0
  38. package/dist/host/ui-slots.d.ts +25 -1
  39. package/dist/host/ui-slots.js +59 -15
  40. package/dist/host/window-openers.d.ts +2 -0
  41. package/dist/host/window-openers.js +4 -0
  42. package/dist/react.cjs +1 -1
  43. package/dist/react.cjs.map +1 -1
  44. package/dist/react.js +1 -1
  45. package/dist/react.js.map +1 -1
  46. package/dist/tool-call-visualization/registry/registry.js +4 -4
  47. package/dist/ui/chat-panel/ChatPanel.d.ts +50 -0
  48. package/dist/ui/chat-panel/ChatPanel.js +175 -0
  49. package/dist/ui/chat-panel/drag.d.ts +87 -0
  50. package/dist/ui/chat-panel/drag.js +194 -0
  51. package/dist/window-panels/windows/agents/AgentRunWindow.js +17 -0
  52. package/dist/window-panels/windows/agents/agentRunWindowAddress.d.ts +19 -0
  53. package/dist/window-panels/windows/agents/agentRunWindowAddress.js +11 -1
  54. package/package.json +6 -6
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Per-conversation host hooks (HOST-OWNED-SOURCE-PLAN §2.6, slice P28f).
3
+ *
4
+ * A database-backed host (the workflow studio) runs its conversations through
5
+ * the package's own doors — the mandate route, the real list, the real
6
+ * composer — and only adds what is truly its own, per conversation:
7
+ *
8
+ * beforeSend the pre-send gate. Runs before admission; returns the
9
+ * turn's `variables` + `context`, or refuses with the
10
+ * host's sentence (the words go back in the box).
11
+ * held a sentence that holds the composer ("a change is
12
+ * waiting for you on the canvas"); null = not held.
13
+ * onEvent the stream-event tap: every event of every turn on this
14
+ * conversation, once, in order, after the package applied
15
+ * it. Must not throw (a throw is reported and swallowed).
16
+ * startConversation the host names the next conversation's id; the client
17
+ * never mints one.
18
+ * everyTurnThroughMandate
19
+ * every turn (not only turn 1) POSTs the mandate route,
20
+ * with the gate's variables + context — for a mandate
21
+ * whose provision checks guaranteed values on every turn.
22
+ *
23
+ * Kept OUT of Redux on purpose: these are functions and subscriptions owned by
24
+ * a mounted host surface, registered on mount and released on unmount.
25
+ */
26
+ import type { TypedStreamEvent } from "@ai-matrx/agents/generated/stream-events";
27
+ import { HostSendRefused } from "./source.js";
28
+ export interface HostTurnExtras {
29
+ variables?: Record<string, unknown>;
30
+ context?: Record<string, unknown>;
31
+ }
32
+ export interface HostBeforeSendInput {
33
+ conversationId: string;
34
+ text: string;
35
+ }
36
+ export interface HostHeld {
37
+ get(): string | null;
38
+ subscribe(listener: () => void): () => void;
39
+ }
40
+ export interface HostStreamTapContext {
41
+ conversationId: string;
42
+ requestId: string;
43
+ }
44
+ export interface ConversationHostHooks {
45
+ beforeSend?: (input: HostBeforeSendInput) => Promise<HostTurnExtras | HostSendRefused>;
46
+ held?: HostHeld;
47
+ onEvent?: (event: TypedStreamEvent, ctx: HostStreamTapContext) => void;
48
+ startConversation?: () => Promise<string>;
49
+ everyTurnThroughMandate?: boolean;
50
+ /** How a stored user turn reads on screen (display only; the record is unchanged). */
51
+ displayUserText?: (text: string) => string;
52
+ }
53
+ /** Register a conversation's hooks. Returns the release; a newer registration for the same id wins. */
54
+ export declare function registerConversationHooks(conversationId: string, hooks: ConversationHostHooks): () => void;
55
+ export declare function getConversationHooks(conversationId: string): ConversationHostHooks | undefined;
56
+ export type BeforeSendOutcome = {
57
+ ok: true;
58
+ } | {
59
+ ok: false;
60
+ sentence: string;
61
+ };
62
+ /**
63
+ * Run the conversation's pre-send gate. A conversation with no gate passes.
64
+ * A held conversation is refused with the held sentence. The gate's extras are
65
+ * kept for the request this turn assembles (`takeTurnExtras`).
66
+ */
67
+ export declare function runBeforeSend(conversationId: string, text: string): Promise<BeforeSendOutcome>;
68
+ /** The extras the gate returned for this turn; consumed once. */
69
+ export declare function takeTurnExtras(conversationId: string): HostTurnExtras | null;
70
+ /** Hand one stream event to the conversation's tap. Never throws. */
71
+ export declare function tapStreamEvent(conversationId: string, requestId: string, event: unknown): void;
72
+ /** Should this conversation's later turns also POST the mandate route? */
73
+ export declare function continuesThroughMandate(conversationId: string): boolean;
74
+ /** Ask the conversation's host for the next conversation id; null when it has no say. */
75
+ export declare function startHostConversation(conversationId: string): Promise<string | null>;
76
+ /** Subscribe to changes of this conversation's held sentence (registry + the host's own source). */
77
+ export declare function subscribeConversationHeld(conversationId: string | null | undefined, listener: () => void): () => void;
78
+ /** The held sentence right now, or null. */
79
+ export declare function readConversationHeld(conversationId: string | null | undefined): string | null;
@@ -0,0 +1,91 @@
1
+ import { isHostSendRefused } from "./source.js";
2
+ const hooksByConversation = /* @__PURE__ */ new Map();
3
+ const extrasByConversation = /* @__PURE__ */ new Map();
4
+ const registryListeners = /* @__PURE__ */ new Set();
5
+ function notifyRegistry() {
6
+ for (const listener of registryListeners) listener();
7
+ }
8
+ function registerConversationHooks(conversationId, hooks) {
9
+ hooksByConversation.set(conversationId, hooks);
10
+ notifyRegistry();
11
+ return () => {
12
+ if (hooksByConversation.get(conversationId) === hooks) {
13
+ hooksByConversation.delete(conversationId);
14
+ extrasByConversation.delete(conversationId);
15
+ notifyRegistry();
16
+ }
17
+ };
18
+ }
19
+ function getConversationHooks(conversationId) {
20
+ return hooksByConversation.get(conversationId);
21
+ }
22
+ async function runBeforeSend(conversationId, text) {
23
+ const hooks = hooksByConversation.get(conversationId);
24
+ if (!hooks) return { ok: true };
25
+ const heldSentence = hooks.held?.get() ?? null;
26
+ if (heldSentence) return { ok: false, sentence: heldSentence };
27
+ if (!hooks.beforeSend) return { ok: true };
28
+ try {
29
+ const result = await hooks.beforeSend({ conversationId, text });
30
+ if (isHostSendRefused(result)) return { ok: false, sentence: result.message };
31
+ extrasByConversation.set(conversationId, result ?? {});
32
+ return { ok: true };
33
+ } catch (error) {
34
+ if (isHostSendRefused(error)) return { ok: false, sentence: error.message };
35
+ const message = error instanceof Error ? error.message : String(error);
36
+ return { ok: false, sentence: message || "This message could not be prepared. Nothing was sent." };
37
+ }
38
+ }
39
+ function takeTurnExtras(conversationId) {
40
+ const extras = extrasByConversation.get(conversationId) ?? null;
41
+ extrasByConversation.delete(conversationId);
42
+ return extras;
43
+ }
44
+ function tapStreamEvent(conversationId, requestId, event) {
45
+ const tap = hooksByConversation.get(conversationId)?.onEvent;
46
+ if (!tap) return;
47
+ try {
48
+ tap(event, { conversationId, requestId });
49
+ } catch (error) {
50
+ console.error(`[chat] the host's stream tap threw on conversation "${conversationId}"`, error);
51
+ }
52
+ }
53
+ function continuesThroughMandate(conversationId) {
54
+ return hooksByConversation.get(conversationId)?.everyTurnThroughMandate === true;
55
+ }
56
+ async function startHostConversation(conversationId) {
57
+ const start = hooksByConversation.get(conversationId)?.startConversation;
58
+ return start ? start() : null;
59
+ }
60
+ function subscribeConversationHeld(conversationId, listener) {
61
+ let release = subscribeOnce(conversationId, onChange);
62
+ function onChange() {
63
+ release();
64
+ release = subscribeOnce(conversationId, onChange);
65
+ listener();
66
+ }
67
+ return () => release();
68
+ }
69
+ function subscribeOnce(conversationId, listener) {
70
+ registryListeners.add(listener);
71
+ const held = conversationId ? hooksByConversation.get(conversationId)?.held : void 0;
72
+ const releaseHeld = held?.subscribe(listener);
73
+ return () => {
74
+ registryListeners.delete(listener);
75
+ releaseHeld?.();
76
+ };
77
+ }
78
+ function readConversationHeld(conversationId) {
79
+ return conversationId ? hooksByConversation.get(conversationId)?.held?.get() ?? null : null;
80
+ }
81
+ export {
82
+ continuesThroughMandate,
83
+ getConversationHooks,
84
+ readConversationHeld,
85
+ registerConversationHooks,
86
+ runBeforeSend,
87
+ startHostConversation,
88
+ subscribeConversationHeld,
89
+ takeTurnExtras,
90
+ tapStreamEvent
91
+ };
@@ -0,0 +1,6 @@
1
+ /** The block types the bare host gives a readable fallback (the app's cards replace them). */
2
+ export declare const BARE_READABLE_BLOCK_TYPES: readonly ["decision", "artifact", "matrx", "input_remarks", "decision_questions", "decision_answers", "speech_script", "directive_receipt"];
3
+ /** Markup → readable markdown: options become a list, every other tag is dropped, its text kept. */
4
+ export declare function readableMarkup(content: string): string;
5
+ /** Register the readable fallback for each type no host registered (idempotent). */
6
+ export declare function registerBareReadableBlocks(): void;
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ import {
3
+ registerBlockDispatch,
4
+ resolveBlockDispatch
5
+ } from "@ai-matrx/rich-content/display/chat-markdown/block-registry/block-dispatch";
6
+ const BARE_READABLE_BLOCK_TYPES = [
7
+ "decision",
8
+ "artifact",
9
+ "matrx",
10
+ "input_remarks",
11
+ "decision_questions",
12
+ "decision_answers",
13
+ "speech_script",
14
+ "directive_receipt"
15
+ ];
16
+ function attr(tag, name) {
17
+ return tag.match(new RegExp(`${name}\\s*=\\s*"([^"]*)"`, "i"))?.[1] ?? null;
18
+ }
19
+ function readableMarkup(content) {
20
+ return content.replace(/<option\b([^>]*)>([\s\S]*?)<\/option>/gi, (_m, attrs, text) => {
21
+ const label = attr(attrs, "label");
22
+ const body = text.trim();
23
+ return `
24
+ - ${label && body && label !== body ? `**${label}** ${body}` : label || body}`;
25
+ }).replace(/<option\b([^>]*)\/>/gi, (_m, attrs) => `
26
+ - ${attr(attrs, "label") ?? ""}`).replace(/<\/?[a-z_][\w:.-]*(?:\s[^>]*)?\/?>/gi, "").replace(/\n{3,}/g, "\n\n").trim();
27
+ }
28
+ function asJsonFence(value) {
29
+ return "```json\n" + JSON.stringify(value, null, 2) + "\n```";
30
+ }
31
+ function readable(ctx) {
32
+ const { block } = ctx;
33
+ const content = typeof block.content === "string" ? block.content : "";
34
+ const meta = block.metadata ?? {};
35
+ if (block.type === "matrx") {
36
+ try {
37
+ return ctx.renderBasicMarkdown(asJsonFence(JSON.parse(content)));
38
+ } catch {
39
+ return ctx.renderBasicMarkdown("```\n" + content + "\n```");
40
+ }
41
+ }
42
+ const title = typeof meta.artifactTitle === "string" ? `**${meta.artifactTitle}**
43
+
44
+ ` : "";
45
+ const text = readableMarkup(content);
46
+ if (text) return ctx.renderBasicMarkdown(title + text);
47
+ if (block.serverData !== void 0 && block.serverData !== null) return ctx.renderBasicMarkdown(title + asJsonFence(block.serverData));
48
+ return ctx.renderBasicMarkdown(title);
49
+ }
50
+ const READABLE = (ctx) => readable(ctx);
51
+ function registerBareReadableBlocks() {
52
+ const missing = {};
53
+ for (const type of BARE_READABLE_BLOCK_TYPES) if (!resolveBlockDispatch(type)) missing[type] = READABLE;
54
+ if (Object.keys(missing).length) registerBlockDispatch(missing);
55
+ }
56
+ export {
57
+ BARE_READABLE_BLOCK_TYPES,
58
+ readableMarkup,
59
+ registerBareReadableBlocks
60
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The files engine's host for a bare chat host (P29a finding 3): a host that gave chat only a db
3
+ * still uploads attachments. `BareHostChrome` configures `@ai-matrx/media`'s files engine ONCE with
4
+ * the chat host's db, its server port (bearer + organization headers), the chat store (which mounts
5
+ * the `cloudFiles` slice) and its organization/identity/notify ports. A host that configured the
6
+ * engine itself (matrx-frontend: `features/files/files-host.ts`) is never overridden.
7
+ */
8
+ import { type FilesServerClient, type FilesShareLinks } from "@ai-matrx/media/files/engine/host/configure";
9
+ import type { ResolvedChatHost } from "../contract.js";
10
+ /** The standalone files service (`matrx-files`): the routes it owns go there, everything else to the chat server. */
11
+ export declare const DEFAULT_FILES_SERVICE_URL = "https://files.matrxserver.com";
12
+ export declare function isFilesServiceRoute(path: string, method?: string): boolean;
13
+ /** A failed call, carrying the status and the server's own words. */
14
+ export declare class FilesRequestError extends Error {
15
+ readonly status: number | null;
16
+ readonly code: string;
17
+ readonly userMessage: string;
18
+ constructor(status: number | null, message: string, code?: string);
19
+ }
20
+ export declare function createDefaultFilesServer(host: () => ResolvedChatHost): FilesServerClient;
21
+ /** Where a share link opens (the platform's own `/s/<token>` page). */
22
+ export declare const DEFAULT_SHARE_LINK_ORIGIN = "https://aimatrx.com";
23
+ /** Share links through the platform's own RPCs over the host's db (an attachment upload mints one). */
24
+ export declare function createDefaultShareLinks(host: () => ResolvedChatHost): FilesShareLinks;
25
+ /** Wire the files engine to a bare chat host, unless the host (or a test) already did. */
26
+ export declare function configureBareFilesHost(host: ResolvedChatHost): void;
@@ -0,0 +1,212 @@
1
+ import {
2
+ configureFilesHost,
3
+ isFilesHostConfigured
4
+ } from "@ai-matrx/media/files/engine/host/configure";
5
+ import { getStoreSingleton } from "../../store/store-singleton.js";
6
+ const DEFAULT_FILES_SERVICE_URL = "https://files.matrxserver.com";
7
+ const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
8
+ const FILES_SERVICE_ROUTES = [
9
+ [/^\/files\/upload\/?$/i, /* @__PURE__ */ new Set(["POST"])],
10
+ [/^\/files\/bulk\/?$/i, /* @__PURE__ */ new Set(["POST"])],
11
+ [/^\/files\/sync\/(?:changes|folders)\/?$/i, /* @__PURE__ */ new Set(["GET"])],
12
+ [new RegExp(`^/files/${UUID}/?$`, "i"), /* @__PURE__ */ new Set(["GET", "PATCH", "DELETE"])],
13
+ [new RegExp(`^/files/${UUID}/download/?$`, "i"), /* @__PURE__ */ new Set(["GET"])],
14
+ [new RegExp(`^/files/${UUID}/pdf-pages/?$`, "i"), /* @__PURE__ */ new Set(["POST"])],
15
+ [new RegExp(`^/files/${UUID}/restore/?$`, "i"), /* @__PURE__ */ new Set(["POST"])],
16
+ [new RegExp(`^/files/${UUID}/asset/?$`, "i"), /* @__PURE__ */ new Set(["GET"])],
17
+ [new RegExp(`^/files/${UUID}/share-links/?$`, "i"), /* @__PURE__ */ new Set(["GET", "POST"])],
18
+ [/^\/assets\/?$/i, /* @__PURE__ */ new Set(["POST"])],
19
+ [/^\/assets\/presets\/?$/i, /* @__PURE__ */ new Set(["GET"])],
20
+ [/^\/assets\/(?:preview|preview\/multipart|pdf-compress|pdf-compress\/multipart)\/?$/i, /* @__PURE__ */ new Set(["POST"])],
21
+ [new RegExp(`^/assets/${UUID}/?$`, "i"), /* @__PURE__ */ new Set(["GET"])],
22
+ [/^\/share\/[^/]+(?:\/download)?\/?$/i, /* @__PURE__ */ new Set(["GET"])]
23
+ ];
24
+ function isFilesServiceRoute(path, method = "GET") {
25
+ const pathname = path.split(/[?#]/, 1)[0];
26
+ return FILES_SERVICE_ROUTES.some(([pattern, methods]) => pattern.test(pathname) && methods.has(method.toUpperCase()));
27
+ }
28
+ class FilesRequestError extends Error {
29
+ status;
30
+ code;
31
+ userMessage;
32
+ constructor(status, message, code = "internal") {
33
+ super(message);
34
+ this.name = "FilesRequestError";
35
+ this.status = status;
36
+ this.code = code;
37
+ this.userMessage = message;
38
+ }
39
+ }
40
+ const trim = (url) => url.replace(/\/+$/, "");
41
+ function createDefaultFilesServer(host) {
42
+ const filesBase = (override) => trim(override ?? DEFAULT_FILES_SERVICE_URL);
43
+ const serverBase = (override) => trim(override ?? host().server.baseUrl());
44
+ const baseFor = (path, override, method) => override ? trim(override) : isFilesServiceRoute(path, method) ? filesBase() : serverBase();
45
+ async function buildHeaders(opts, includeContentType) {
46
+ const requestId = opts.requestId ?? (globalThis.crypto?.randomUUID?.() ?? `${Date.now()}`);
47
+ const headers = { ...await host().server.headers?.() ?? {}, "X-Request-Id": requestId };
48
+ if (opts.organizationId) headers["X-Organization-Id"] = opts.organizationId;
49
+ if (opts.idempotencyKey) headers["Idempotency-Key"] = opts.idempotencyKey;
50
+ if (includeContentType) headers["Content-Type"] = "application/json";
51
+ return { headers, requestId };
52
+ }
53
+ const meta = (status, requestId, serverRequestId) => ({ requestId, status, serverRequestId });
54
+ async function failure(response) {
55
+ let message = `HTTP ${response.status}`;
56
+ let code = response.status >= 500 ? "internal" : "invalid_request";
57
+ try {
58
+ const body = await response.json();
59
+ const said = body.user_message ?? body.message ?? body.detail;
60
+ if (typeof said === "string" && said) message = said;
61
+ if (typeof body.error === "string") code = body.error;
62
+ } catch {
63
+ }
64
+ return new FilesRequestError(response.status, message, code);
65
+ }
66
+ async function send(method, path, opts = {}, body, form) {
67
+ const { headers, requestId } = await buildHeaders(opts, body !== void 0 && !form);
68
+ const response = await fetch(`${baseFor(path, opts.baseUrlOverride, method)}${path}`, {
69
+ method,
70
+ headers,
71
+ body: form ?? (body === void 0 ? void 0 : JSON.stringify(body)),
72
+ signal: opts.signal
73
+ });
74
+ if (!response.ok) throw await failure(response);
75
+ const text = await response.text();
76
+ const data = text ? JSON.parse(text) : null;
77
+ return { data, meta: meta(response.status, requestId, response.headers.get("x-request-id")) };
78
+ }
79
+ async function download(path, opts = {}) {
80
+ const { headers, requestId } = await buildHeaders(opts, false);
81
+ const response = await fetch(`${baseFor(path, opts.baseUrlOverride, "GET")}${path}`, { headers, signal: opts.signal });
82
+ if (!response.ok) throw await failure(response);
83
+ const disposition = response.headers.get("content-disposition");
84
+ const filename = disposition?.match(/filename\*?=(?:UTF-8'')?"?([^";]+)"?/i)?.[1] ?? null;
85
+ return { blob: await response.blob(), meta: meta(response.status, requestId, response.headers.get("x-request-id")), filename: filename ? decodeURIComponent(filename) : null };
86
+ }
87
+ return {
88
+ resolveFilesBaseUrl: filesBase,
89
+ resolveBaseUrlForPath: baseFor,
90
+ buildHeaders: (opts, includeContentType) => buildHeaders(opts, includeContentType),
91
+ getJson: (path, opts) => send("GET", path, opts),
92
+ postJson: (path, body, opts) => send("POST", path, opts, body),
93
+ patchJson: (path, body, opts) => send("PATCH", path, opts, body),
94
+ putJson: (path, body, opts) => send("PUT", path, opts, body),
95
+ del: (path, opts) => send("DELETE", path, opts),
96
+ delJson: (path, body, opts) => send("DELETE", path, opts, body),
97
+ postMultipart: (path, form, opts) => send("POST", path, opts, void 0, form),
98
+ uploadWithProgress: (path, form, onProgress, opts = {}) => new Promise((resolve, reject) => {
99
+ void buildHeaders(opts, false).then(({ headers, requestId }) => {
100
+ const xhr = new XMLHttpRequest();
101
+ xhr.open("POST", `${baseFor(path, opts.baseUrlOverride, "POST")}${path}`);
102
+ for (const [key, value] of Object.entries(headers)) xhr.setRequestHeader(key, value);
103
+ xhr.upload.addEventListener("progress", (event) => onProgress({ loaded: event.loaded, total: event.total }));
104
+ xhr.addEventListener("load", () => {
105
+ if (xhr.status >= 200 && xhr.status < 300) {
106
+ try {
107
+ resolve({ data: xhr.responseText ? JSON.parse(xhr.responseText) : null, meta: meta(xhr.status, requestId, xhr.getResponseHeader("x-request-id")) });
108
+ } catch (error) {
109
+ reject(error);
110
+ }
111
+ } else {
112
+ let message = `Upload failed (${xhr.status}).`;
113
+ try {
114
+ const body = JSON.parse(xhr.responseText);
115
+ message = body.user_message ?? body.message ?? body.detail ?? message;
116
+ } catch {
117
+ }
118
+ reject(new FilesRequestError(xhr.status, message));
119
+ }
120
+ });
121
+ xhr.addEventListener("error", () => reject(new FilesRequestError(null, "Upload failed \u2014 check your connection.")));
122
+ xhr.addEventListener("abort", () => reject(new FilesRequestError(null, "Upload cancelled.", "cancelled")));
123
+ if (opts.signal) {
124
+ if (opts.signal.aborted) xhr.abort();
125
+ else opts.signal.addEventListener("abort", () => xhr.abort(), { once: true });
126
+ }
127
+ xhr.send(form);
128
+ }, reject);
129
+ }),
130
+ downloadBlob: download,
131
+ downloadBlobWithProgress: async (path, onProgress, opts) => {
132
+ const result = await download(path, opts);
133
+ onProgress({ loaded: result.blob.size, total: result.blob.size });
134
+ return result;
135
+ }
136
+ };
137
+ }
138
+ const DEFAULT_SHARE_LINK_ORIGIN = "https://aimatrx.com";
139
+ function createDefaultShareLinks(host) {
140
+ const rpc = (fn, args) => host().db.rpc(fn, args);
141
+ const shareLinkUrl = (token) => `${DEFAULT_SHARE_LINK_ORIGIN}/s/${token}`;
142
+ return {
143
+ shareLinkUrl,
144
+ async createShareLink(options) {
145
+ const { data, error } = await rpc("create_share_link", {
146
+ p_resource_type: options.resourceType,
147
+ p_resource_id: options.resourceId,
148
+ p_permission_level: options.permissionLevel ?? "viewer",
149
+ p_expires_at: options.expiresAt ?? void 0,
150
+ p_max_uses: options.maxUses ?? void 0,
151
+ p_label: options.label ?? void 0
152
+ });
153
+ if (error) return { success: false, error: error.message ?? "Failed to create link" };
154
+ const res = data;
155
+ if (!res?.success || !res.token) return { success: false, error: res?.error ?? "Failed to create link" };
156
+ return { success: true, token: res.token, shortToken: res.short_token ?? null, url: shareLinkUrl(res.token) };
157
+ },
158
+ async listShareLinks(resourceType, resourceId) {
159
+ const { data, error } = await rpc("list_share_links", { p_resource_type: resourceType, p_resource_id: resourceId });
160
+ if (error || !Array.isArray(data)) throw new Error(`Could not load this item's share links${error?.message ? `: ${error.message}` : "."}`);
161
+ return data.map((r) => ({
162
+ id: r.id,
163
+ token: r.token,
164
+ shortToken: r.short_token ?? null,
165
+ permissionLevel: r.permission_level,
166
+ label: r.label ?? null,
167
+ expiresAt: r.expires_at ?? null,
168
+ maxUses: r.max_uses ?? null,
169
+ useCount: r.use_count ?? 0,
170
+ isActive: r.is_active ?? false,
171
+ createdAt: r.created_at ?? null,
172
+ lastUsedAt: r.last_used_at ?? null
173
+ }));
174
+ },
175
+ async revokeShareLink(linkId) {
176
+ const { data, error } = await rpc("revoke_share_link", { p_link_id: linkId });
177
+ if (error) return { success: false, error: error.message };
178
+ const res = data;
179
+ return { success: !!res?.success, error: res?.error };
180
+ }
181
+ };
182
+ }
183
+ const UPLOAD_REASON = "upload this file";
184
+ function configureBareFilesHost(host) {
185
+ if (isFilesHostConfigured()) return;
186
+ configureFilesHost({
187
+ db: host.db,
188
+ server: createDefaultFilesServer(() => host),
189
+ store: () => getStoreSingleton(),
190
+ scope: () => ({
191
+ userId: host.identity.current().userId ?? null,
192
+ organizationId: host.org.active()?.id ?? null,
193
+ projectId: null,
194
+ taskId: null
195
+ }),
196
+ shareLinks: createDefaultShareLinks(() => host),
197
+ ensureOrganizationContext: async ({ organizationId }) => organizationId ?? await host.org.require(UPLOAD_REASON),
198
+ notify: {
199
+ success: (message) => host.notify.success(message),
200
+ error: (message) => host.notify.error(message)
201
+ }
202
+ });
203
+ }
204
+ export {
205
+ DEFAULT_FILES_SERVICE_URL,
206
+ DEFAULT_SHARE_LINK_ORIGIN,
207
+ FilesRequestError,
208
+ configureBareFilesHost,
209
+ createDefaultFilesServer,
210
+ createDefaultShareLinks,
211
+ isFilesServiceRoute
212
+ };
@@ -0,0 +1,14 @@
1
+ import type { Resource } from "../../agents/resources/types.js";
2
+ import type { ResourcePickerViewId } from "../../agents/resources/picker-view-id.js";
3
+ export interface DefaultResourcePickerMenuProps {
4
+ conversationId: string;
5
+ initialView?: ResourcePickerViewId;
6
+ initialUploadFiles?: readonly File[];
7
+ onResourceSelected?: (resource: Resource) => unknown;
8
+ onExitInitialView?: () => void;
9
+ onClose?: () => void;
10
+ [extra: string]: unknown;
11
+ }
12
+ /** The views this default draws; every other view is the host picker's. */
13
+ export declare const DEFAULT_PICKER_VIEWS: ReadonlySet<ResourcePickerViewId>;
14
+ export declare function DefaultResourcePickerMenu({ conversationId, initialView, initialUploadFiles, onResourceSelected, onExitInitialView, onClose, }: DefaultResourcePickerMenuProps): import("react").JSX.Element | null;
@@ -0,0 +1,117 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { Button, Input } from "@ai-matrx/design-system";
5
+ import { useUploadAgentResources } from "../../agents/components/inputs/resources/usePasteImageResource.js";
6
+ const DEFAULT_PICKER_VIEWS = /* @__PURE__ */ new Set(["files", "webpage"]);
7
+ function FilesView({ conversationId, initialUploadFiles, done }) {
8
+ const upload = useUploadAgentResources(conversationId);
9
+ const input = useRef(null);
10
+ const [dragging, setDragging] = useState(false);
11
+ const started = useRef(false);
12
+ const send = (files) => {
13
+ if (files.length === 0) return;
14
+ void upload(files);
15
+ done();
16
+ };
17
+ useEffect(() => {
18
+ if (started.current || !initialUploadFiles?.length) return;
19
+ started.current = true;
20
+ send([...initialUploadFiles]);
21
+ });
22
+ return /* @__PURE__ */ jsxs(
23
+ "div",
24
+ {
25
+ className: `m-2 flex flex-col items-center gap-2 rounded-md border border-dashed p-4 text-sm ${dragging ? "border-primary bg-primary/5" : "border-border"}`,
26
+ "data-chat-default-picker": "files",
27
+ onDragOver: (event) => {
28
+ event.preventDefault();
29
+ setDragging(true);
30
+ },
31
+ onDragLeave: () => setDragging(false),
32
+ onDrop: (event) => {
33
+ event.preventDefault();
34
+ setDragging(false);
35
+ send(Array.from(event.dataTransfer.files));
36
+ },
37
+ children: [
38
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Drop files here" }),
39
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: () => input.current?.click(), children: "Choose files" }),
40
+ /* @__PURE__ */ jsx(
41
+ "input",
42
+ {
43
+ ref: input,
44
+ type: "file",
45
+ multiple: true,
46
+ hidden: true,
47
+ onChange: (event) => {
48
+ send(Array.from(event.target.files ?? []));
49
+ event.target.value = "";
50
+ }
51
+ }
52
+ )
53
+ ]
54
+ }
55
+ );
56
+ }
57
+ function normalizeUrl(raw) {
58
+ const text = raw.trim();
59
+ if (!text) return null;
60
+ try {
61
+ const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(text) ? text : `https://${text}`);
62
+ return url.protocol === "http:" || url.protocol === "https:" ? url.toString() : null;
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+ function LinkView({ select }) {
68
+ const [text, setText] = useState("");
69
+ const url = normalizeUrl(text);
70
+ return /* @__PURE__ */ jsxs(
71
+ "form",
72
+ {
73
+ className: "flex items-center gap-2 p-2",
74
+ "data-chat-default-picker": "webpage",
75
+ onSubmit: (event) => {
76
+ event.preventDefault();
77
+ if (url) select(url);
78
+ },
79
+ children: [
80
+ /* @__PURE__ */ jsx(Input, { autoFocus: true, className: "h-8 text-sm", placeholder: "https://", value: text, onChange: (event) => setText(event.target.value) }),
81
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "sm", disabled: !url, children: "Attach" })
82
+ ]
83
+ }
84
+ );
85
+ }
86
+ function DefaultResourcePickerMenu({
87
+ conversationId,
88
+ initialView,
89
+ initialUploadFiles,
90
+ onResourceSelected,
91
+ onExitInitialView,
92
+ onClose
93
+ }) {
94
+ const done = () => {
95
+ onExitInitialView?.();
96
+ onClose?.();
97
+ };
98
+ if (initialView === "files") {
99
+ return /* @__PURE__ */ jsx(FilesView, { conversationId, initialUploadFiles, done });
100
+ }
101
+ if (initialView === "webpage") {
102
+ return /* @__PURE__ */ jsx(
103
+ LinkView,
104
+ {
105
+ select: (url) => {
106
+ void onResourceSelected?.({ type: "webpage", data: url });
107
+ done();
108
+ }
109
+ }
110
+ );
111
+ }
112
+ return null;
113
+ }
114
+ export {
115
+ DEFAULT_PICKER_VIEWS,
116
+ DefaultResourcePickerMenu
117
+ };
@@ -0,0 +1,11 @@
1
+ import type { ControlDefinition } from "../../agents/redux/agent-settings/types.js";
2
+ export interface DefaultSettingControlInputProps {
3
+ settingKey: string;
4
+ control: ControlDefinition;
5
+ value: unknown;
6
+ onChange: (value: unknown) => void;
7
+ disabled?: boolean;
8
+ id?: string;
9
+ [extra: string]: unknown;
10
+ }
11
+ export declare function DefaultSettingControlInput({ settingKey, control, value, onChange, disabled, id }: DefaultSettingControlInputProps): import("react").JSX.Element;