@dudousxd/nestjs-agent-react 0.18.0 → 0.20.0
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/README.md +53 -23
- package/dist/client-0Wd-WPsu.d.cts +285 -0
- package/dist/client-0Wd-WPsu.d.ts +285 -0
- package/dist/generative-ui-BhXFni8j.d.ts +167 -0
- package/dist/generative-ui-C53b3FoA.d.cts +167 -0
- package/dist/genui-json-render.cjs +521 -4
- package/dist/genui-json-render.cjs.map +1 -1
- package/dist/genui-json-render.d.cts +15 -2
- package/dist/genui-json-render.d.ts +15 -2
- package/dist/genui-json-render.js +518 -2
- package/dist/genui-json-render.js.map +1 -1
- package/dist/genui.cjs +120 -23
- package/dist/genui.cjs.map +1 -1
- package/dist/genui.d.cts +5 -52
- package/dist/genui.d.ts +5 -52
- package/dist/genui.js +106 -11
- package/dist/genui.js.map +1 -1
- package/dist/index.cjs +216 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -261
- package/dist/index.d.ts +28 -261
- package/dist/index.js +41 -1
- package/dist/index.js.map +1 -1
- package/dist/media.cjs +155 -0
- package/dist/media.cjs.map +1 -0
- package/dist/media.d.cts +78 -0
- package/dist/media.d.ts +78 -0
- package/dist/media.js +129 -0
- package/dist/media.js.map +1 -0
- package/package.json +18 -4
- package/dist/types-DFD-soWU.d.cts +0 -86
- package/dist/types-DFD-soWU.d.ts +0 -86
package/dist/media.cjs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/media/index.ts
|
|
22
|
+
var media_exports = {};
|
|
23
|
+
__export(media_exports, {
|
|
24
|
+
MediaUploadError: () => MediaUploadError,
|
|
25
|
+
createMediaUpload: () => createMediaUpload,
|
|
26
|
+
mediaAttachments: () => mediaAttachments,
|
|
27
|
+
withMediaUploads: () => withMediaUploads
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(media_exports);
|
|
30
|
+
var import_nestjs_media_client = require("@dudousxd/nestjs-media-client");
|
|
31
|
+
function mediaAttachments(options = {}) {
|
|
32
|
+
return (file, uploadOptions, connection) => createMediaUpload({
|
|
33
|
+
...options,
|
|
34
|
+
baseUrl: connection.baseUrl,
|
|
35
|
+
getHeaders: connection.headers,
|
|
36
|
+
fetch: connection.fetch,
|
|
37
|
+
...connection.credentials !== void 0 ? {
|
|
38
|
+
credentials: connection.credentials
|
|
39
|
+
} : {}
|
|
40
|
+
})(file, uploadOptions);
|
|
41
|
+
}
|
|
42
|
+
__name(mediaAttachments, "mediaAttachments");
|
|
43
|
+
var MediaUploadError = class extends Error {
|
|
44
|
+
static {
|
|
45
|
+
__name(this, "MediaUploadError");
|
|
46
|
+
}
|
|
47
|
+
status;
|
|
48
|
+
method;
|
|
49
|
+
path;
|
|
50
|
+
constructor(status, method, path, statusText) {
|
|
51
|
+
super(`Attachment upload failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
|
|
52
|
+
this.name = "MediaUploadError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function createMediaUpload(options = {}) {
|
|
56
|
+
const origin = (options.baseUrl ?? "").replace(/\/$/, "");
|
|
57
|
+
const base = `${origin}${(options.path ?? "/agent").replace(/\/$/, "")}/attachments/uploads`;
|
|
58
|
+
const baseFetch = /* @__PURE__ */ __name(() => options.fetch ?? fetch, "baseFetch");
|
|
59
|
+
const fetchWithCredentials = /* @__PURE__ */ __name((input, init) => baseFetch()(input, {
|
|
60
|
+
...init,
|
|
61
|
+
...options.credentials !== void 0 ? {
|
|
62
|
+
credentials: options.credentials
|
|
63
|
+
} : {}
|
|
64
|
+
}), "fetchWithCredentials");
|
|
65
|
+
const headers = /* @__PURE__ */ __name(async () => ({
|
|
66
|
+
...options.headers,
|
|
67
|
+
...await options.getHeaders?.() ?? {}
|
|
68
|
+
}), "headers");
|
|
69
|
+
const discard = /* @__PURE__ */ __name((mediaId) => {
|
|
70
|
+
void (async () => {
|
|
71
|
+
await fetchWithCredentials(`${base}/${encodeURIComponent(mediaId)}`, {
|
|
72
|
+
method: "DELETE",
|
|
73
|
+
headers: await headers()
|
|
74
|
+
});
|
|
75
|
+
})().catch(() => void 0);
|
|
76
|
+
}, "discard");
|
|
77
|
+
const call = /* @__PURE__ */ __name(async (method, url, body) => {
|
|
78
|
+
const response = await fetchWithCredentials(url, {
|
|
79
|
+
method,
|
|
80
|
+
headers: {
|
|
81
|
+
accept: "application/json",
|
|
82
|
+
...body !== void 0 ? {
|
|
83
|
+
"content-type": "application/json"
|
|
84
|
+
} : {},
|
|
85
|
+
...await headers()
|
|
86
|
+
},
|
|
87
|
+
...body !== void 0 ? {
|
|
88
|
+
body: JSON.stringify(body)
|
|
89
|
+
} : {}
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new MediaUploadError(response.status, method, url, response.statusText);
|
|
93
|
+
}
|
|
94
|
+
return await response.json();
|
|
95
|
+
}, "call");
|
|
96
|
+
return async (file, { signal, onProgress } = {}) => {
|
|
97
|
+
signal?.throwIfAborted();
|
|
98
|
+
const begun = await call("POST", base, {
|
|
99
|
+
filename: file.name,
|
|
100
|
+
contentType: file.type,
|
|
101
|
+
size: file.size
|
|
102
|
+
});
|
|
103
|
+
const onAbort = /* @__PURE__ */ __name(() => discard(begun.mediaId), "onAbort");
|
|
104
|
+
signal?.addEventListener("abort", onAbort, {
|
|
105
|
+
once: true
|
|
106
|
+
});
|
|
107
|
+
try {
|
|
108
|
+
const location = /^https?:\/\//.test(begun.location) ? begun.location : `${origin}${begun.location}`;
|
|
109
|
+
await (0, import_nestjs_media_client.streamChunks)(location, file, {
|
|
110
|
+
resume: false,
|
|
111
|
+
fetchImpl: fetchWithCredentials,
|
|
112
|
+
getHeaders: headers,
|
|
113
|
+
...options.chunkSize !== void 0 ? {
|
|
114
|
+
chunkSize: options.chunkSize
|
|
115
|
+
} : {},
|
|
116
|
+
...options.retries !== void 0 ? {
|
|
117
|
+
retries: options.retries
|
|
118
|
+
} : {},
|
|
119
|
+
...signal !== void 0 ? {
|
|
120
|
+
signal
|
|
121
|
+
} : {},
|
|
122
|
+
onProgress: /* @__PURE__ */ __name((sent, total) => onProgress?.(total > 0 ? sent / total : 1), "onProgress")
|
|
123
|
+
});
|
|
124
|
+
signal?.throwIfAborted();
|
|
125
|
+
const attachment = await call("POST", `${base}/${encodeURIComponent(begun.mediaId)}/complete`);
|
|
126
|
+
onProgress?.(1);
|
|
127
|
+
return attachment;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (!signal?.aborted) discard(begun.mediaId);
|
|
130
|
+
throw error;
|
|
131
|
+
} finally {
|
|
132
|
+
signal?.removeEventListener("abort", onAbort);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
__name(createMediaUpload, "createMediaUpload");
|
|
137
|
+
function withMediaUploads(backend, options = {}) {
|
|
138
|
+
const upload = createMediaUpload(options);
|
|
139
|
+
return new Proxy(backend, {
|
|
140
|
+
get(target, property) {
|
|
141
|
+
if (property === "uploadAttachment") return upload;
|
|
142
|
+
const value = Reflect.get(target, property, target);
|
|
143
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
__name(withMediaUploads, "withMediaUploads");
|
|
148
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
149
|
+
0 && (module.exports = {
|
|
150
|
+
MediaUploadError,
|
|
151
|
+
createMediaUpload,
|
|
152
|
+
mediaAttachments,
|
|
153
|
+
withMediaUploads
|
|
154
|
+
});
|
|
155
|
+
//# sourceMappingURL=media.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/media/index.ts"],"sourcesContent":["import type { MessageAttachment } from '@dudousxd/nestjs-agent-core';\nimport { streamChunks } from '@dudousxd/nestjs-media-client';\nimport type {\n AgentBackend,\n AttachmentUploadStrategy,\n UploadAttachmentOptions,\n} from '../backend.js';\nimport type { AgentClientOptions } from '../client.js';\n\n/** Tuning for {@link mediaAttachments}; every field optional. */\nexport interface MediaAttachmentsOptions {\n /** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */\n path?: string;\n /** Bytes per tus `PATCH`. Default 5 MiB. */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/**\n * Resumable attachment uploads through `@dudousxd/nestjs-media`, in one line:\n *\n * ```ts\n * const chat = useAgentChat({ attachments: mediaAttachments() });\n * const files = useAttachments({ backend: chat.backend });\n * ```\n *\n * Reuses the chat client's own connection (base url, headers, credentials), so it needs no\n * configuration. Also takes `new AgentClient({ attachments: mediaAttachments() })`.\n */\nexport function mediaAttachments(options: MediaAttachmentsOptions = {}): AttachmentUploadStrategy {\n return (file, uploadOptions, connection) =>\n createMediaUpload({\n ...options,\n baseUrl: connection.baseUrl,\n getHeaders: connection.headers,\n fetch: connection.fetch,\n ...(connection.credentials !== undefined ? { credentials: connection.credentials } : {}),\n })(file, uploadOptions);\n}\n\n/**\n * Where and how {@link createMediaUpload} talks to the server. The connection fields are\n * {@link AgentClientOptions}' — pass the same ones your `AgentClient` gets, so the tus requests\n * carry the same cookies / CSRF header / bearer token as the rest of the chat.\n */\nexport interface MediaUploadOptions extends AgentClientOptions {\n /** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */\n path?: string;\n /** Bytes per tus `PATCH`. Default 5 MiB (nestjs-media-client's default). */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/** What `useAttachments({ upload })` and `AgentBackend.uploadAttachment` both take. */\nexport type AttachmentUpload = (\n file: File,\n options?: UploadAttachmentOptions,\n) => Promise<MessageAttachment>;\n\n/**\n * A non-2xx answer from the agent's upload routes. Carries `status` like `AgentHttpError` does\n * (413 too large, 415 type refused, 404 not yours) — its own class because this subpath is bundled\n * apart from the root entry, where an `instanceof AgentHttpError` would not match a copy.\n */\nexport class MediaUploadError extends Error {\n constructor(\n readonly status: number,\n readonly method: string,\n readonly path: string,\n statusText: string,\n ) {\n super(`Attachment upload failed: ${method} ${path} → ${status} ${statusText}`);\n this.name = 'MediaUploadError';\n }\n}\n\ninterface BeginResponse {\n mediaId: string;\n location: string;\n}\n\n/**\n * A resumable `upload` for `useAttachments`, backed by `@dudousxd/nestjs-media` on the server\n * (`AgentMediaAttachmentsModule` from `@dudousxd/nestjs-agent/media`):\n *\n * 1. `POST <path>/attachments/uploads` — the agent validates the file and opens a tus session the\n * actor owns;\n * 2. the bytes stream to nestjs-media's own tus endpoint in chunks (`streamChunks`), reporting\n * progress and honouring the abort signal;\n * 3. `POST <path>/attachments/uploads/:mediaId/complete` — the agent confirms the bytes landed and\n * answers the `MessageAttachment` the turn will reference by `mediaId`.\n *\n * An aborted or failed upload is discarded server-side (`DELETE`), best effort.\n */\nexport function createMediaUpload(options: MediaUploadOptions = {}): AttachmentUpload {\n const origin = (options.baseUrl ?? '').replace(/\\/$/, '');\n const base = `${origin}${(options.path ?? '/agent').replace(/\\/$/, '')}/attachments/uploads`;\n const baseFetch = (): typeof fetch => options.fetch ?? fetch;\n // Every request — the agent's and media's tus PATCHes — rides the same credentials mode.\n const fetchWithCredentials: typeof fetch = (input, init) =>\n baseFetch()(input, {\n ...init,\n ...(options.credentials !== undefined ? { credentials: options.credentials } : {}),\n });\n const headers = async (): Promise<Record<string, string>> => ({\n ...options.headers,\n ...((await options.getHeaders?.()) ?? {}),\n });\n const discard = (mediaId: string): void => {\n void (async () => {\n await fetchWithCredentials(`${base}/${encodeURIComponent(mediaId)}`, {\n method: 'DELETE',\n headers: await headers(),\n });\n })().catch(() => undefined);\n };\n const call = async <T>(method: string, url: string, body?: unknown): Promise<T> => {\n const response = await fetchWithCredentials(url, {\n method,\n headers: {\n accept: 'application/json',\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(await headers()),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) {\n throw new MediaUploadError(response.status, method, url, response.statusText);\n }\n return (await response.json()) as T;\n };\n\n return async (file, { signal, onProgress } = {}) => {\n signal?.throwIfAborted();\n const begun = await call<BeginResponse>('POST', base, {\n filename: file.name,\n contentType: file.type,\n size: file.size,\n });\n const onAbort = () => discard(begun.mediaId);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n const location = /^https?:\\/\\//.test(begun.location)\n ? begun.location\n : `${origin}${begun.location}`;\n await streamChunks(location, file, {\n resume: false,\n fetchImpl: fetchWithCredentials,\n getHeaders: headers,\n ...(options.chunkSize !== undefined ? { chunkSize: options.chunkSize } : {}),\n ...(options.retries !== undefined ? { retries: options.retries } : {}),\n ...(signal !== undefined ? { signal } : {}),\n onProgress: (sent, total) => onProgress?.(total > 0 ? sent / total : 1),\n });\n signal?.throwIfAborted();\n const attachment = await call<MessageAttachment>(\n 'POST',\n `${base}/${encodeURIComponent(begun.mediaId)}/complete`,\n );\n onProgress?.(1);\n return attachment;\n } catch (error) {\n if (!signal?.aborted) discard(begun.mediaId);\n throw error;\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n };\n}\n\n/**\n * Plug media uploads in once: the same backend, with `uploadAttachment` replaced by\n * {@link createMediaUpload}. Hand the result to `useAgentChat({ backend })` and\n * `useAttachments({ backend })` and every composer uploads resumably — no per-composer wiring.\n *\n * ```ts\n * const backend = withMediaUploads(new AgentClient(connection), connection);\n * ```\n *\n * Every other member is the original backend's, still bound to it.\n */\nexport function withMediaUploads<B extends AgentBackend>(\n backend: B,\n options: MediaUploadOptions = {},\n): B {\n const upload = createMediaUpload(options);\n return new Proxy(backend, {\n get(target, property) {\n if (property === 'uploadAttachment') return upload;\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":"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","names":["mediaAttachments","options","file","uploadOptions","connection","createMediaUpload","baseUrl","getHeaders","headers","fetch","credentials","undefined","MediaUploadError","Error","status","method","path","statusText","name","origin","replace","base","baseFetch","fetchWithCredentials","input","init","discard","mediaId","encodeURIComponent","catch","call","url","body","response","accept","JSON","stringify","ok","json","signal","onProgress","throwIfAborted","begun","filename","contentType","type","size","onAbort","addEventListener","once","location","test","streamChunks","resume","fetchImpl","chunkSize","retries","sent","total","attachment","error","aborted","removeEventListener","withMediaUploads","backend","upload","Proxy","get","target","property","value","Reflect","bind"]}
|
package/dist/media.d.cts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { MessageAttachment } from '@dudousxd/nestjs-agent-core';
|
|
2
|
+
import { U as UploadAttachmentOptions, d as AgentClientOptions, b as AttachmentUploadStrategy, A as AgentBackend } from './client-0Wd-WPsu.cjs';
|
|
3
|
+
|
|
4
|
+
/** Tuning for {@link mediaAttachments}; every field optional. */
|
|
5
|
+
interface MediaAttachmentsOptions {
|
|
6
|
+
/** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */
|
|
7
|
+
path?: string;
|
|
8
|
+
/** Bytes per tus `PATCH`. Default 5 MiB. */
|
|
9
|
+
chunkSize?: number;
|
|
10
|
+
/** Attempts per chunk before the upload fails. Default 3. */
|
|
11
|
+
retries?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resumable attachment uploads through `@dudousxd/nestjs-media`, in one line:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const chat = useAgentChat({ attachments: mediaAttachments() });
|
|
18
|
+
* const files = useAttachments({ backend: chat.backend });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Reuses the chat client's own connection (base url, headers, credentials), so it needs no
|
|
22
|
+
* configuration. Also takes `new AgentClient({ attachments: mediaAttachments() })`.
|
|
23
|
+
*/
|
|
24
|
+
declare function mediaAttachments(options?: MediaAttachmentsOptions): AttachmentUploadStrategy;
|
|
25
|
+
/**
|
|
26
|
+
* Where and how {@link createMediaUpload} talks to the server. The connection fields are
|
|
27
|
+
* {@link AgentClientOptions}' — pass the same ones your `AgentClient` gets, so the tus requests
|
|
28
|
+
* carry the same cookies / CSRF header / bearer token as the rest of the chat.
|
|
29
|
+
*/
|
|
30
|
+
interface MediaUploadOptions extends AgentClientOptions {
|
|
31
|
+
/** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */
|
|
32
|
+
path?: string;
|
|
33
|
+
/** Bytes per tus `PATCH`. Default 5 MiB (nestjs-media-client's default). */
|
|
34
|
+
chunkSize?: number;
|
|
35
|
+
/** Attempts per chunk before the upload fails. Default 3. */
|
|
36
|
+
retries?: number;
|
|
37
|
+
}
|
|
38
|
+
/** What `useAttachments({ upload })` and `AgentBackend.uploadAttachment` both take. */
|
|
39
|
+
type AttachmentUpload = (file: File, options?: UploadAttachmentOptions) => Promise<MessageAttachment>;
|
|
40
|
+
/**
|
|
41
|
+
* A non-2xx answer from the agent's upload routes. Carries `status` like `AgentHttpError` does
|
|
42
|
+
* (413 too large, 415 type refused, 404 not yours) — its own class because this subpath is bundled
|
|
43
|
+
* apart from the root entry, where an `instanceof AgentHttpError` would not match a copy.
|
|
44
|
+
*/
|
|
45
|
+
declare class MediaUploadError extends Error {
|
|
46
|
+
readonly status: number;
|
|
47
|
+
readonly method: string;
|
|
48
|
+
readonly path: string;
|
|
49
|
+
constructor(status: number, method: string, path: string, statusText: string);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A resumable `upload` for `useAttachments`, backed by `@dudousxd/nestjs-media` on the server
|
|
53
|
+
* (`AgentMediaAttachmentsModule` from `@dudousxd/nestjs-agent/media`):
|
|
54
|
+
*
|
|
55
|
+
* 1. `POST <path>/attachments/uploads` — the agent validates the file and opens a tus session the
|
|
56
|
+
* actor owns;
|
|
57
|
+
* 2. the bytes stream to nestjs-media's own tus endpoint in chunks (`streamChunks`), reporting
|
|
58
|
+
* progress and honouring the abort signal;
|
|
59
|
+
* 3. `POST <path>/attachments/uploads/:mediaId/complete` — the agent confirms the bytes landed and
|
|
60
|
+
* answers the `MessageAttachment` the turn will reference by `mediaId`.
|
|
61
|
+
*
|
|
62
|
+
* An aborted or failed upload is discarded server-side (`DELETE`), best effort.
|
|
63
|
+
*/
|
|
64
|
+
declare function createMediaUpload(options?: MediaUploadOptions): AttachmentUpload;
|
|
65
|
+
/**
|
|
66
|
+
* Plug media uploads in once: the same backend, with `uploadAttachment` replaced by
|
|
67
|
+
* {@link createMediaUpload}. Hand the result to `useAgentChat({ backend })` and
|
|
68
|
+
* `useAttachments({ backend })` and every composer uploads resumably — no per-composer wiring.
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* const backend = withMediaUploads(new AgentClient(connection), connection);
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* Every other member is the original backend's, still bound to it.
|
|
75
|
+
*/
|
|
76
|
+
declare function withMediaUploads<B extends AgentBackend>(backend: B, options?: MediaUploadOptions): B;
|
|
77
|
+
|
|
78
|
+
export { type AttachmentUpload, type MediaAttachmentsOptions, MediaUploadError, type MediaUploadOptions, createMediaUpload, mediaAttachments, withMediaUploads };
|
package/dist/media.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { MessageAttachment } from '@dudousxd/nestjs-agent-core';
|
|
2
|
+
import { U as UploadAttachmentOptions, d as AgentClientOptions, b as AttachmentUploadStrategy, A as AgentBackend } from './client-0Wd-WPsu.js';
|
|
3
|
+
|
|
4
|
+
/** Tuning for {@link mediaAttachments}; every field optional. */
|
|
5
|
+
interface MediaAttachmentsOptions {
|
|
6
|
+
/** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */
|
|
7
|
+
path?: string;
|
|
8
|
+
/** Bytes per tus `PATCH`. Default 5 MiB. */
|
|
9
|
+
chunkSize?: number;
|
|
10
|
+
/** Attempts per chunk before the upload fails. Default 3. */
|
|
11
|
+
retries?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resumable attachment uploads through `@dudousxd/nestjs-media`, in one line:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const chat = useAgentChat({ attachments: mediaAttachments() });
|
|
18
|
+
* const files = useAttachments({ backend: chat.backend });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Reuses the chat client's own connection (base url, headers, credentials), so it needs no
|
|
22
|
+
* configuration. Also takes `new AgentClient({ attachments: mediaAttachments() })`.
|
|
23
|
+
*/
|
|
24
|
+
declare function mediaAttachments(options?: MediaAttachmentsOptions): AttachmentUploadStrategy;
|
|
25
|
+
/**
|
|
26
|
+
* Where and how {@link createMediaUpload} talks to the server. The connection fields are
|
|
27
|
+
* {@link AgentClientOptions}' — pass the same ones your `AgentClient` gets, so the tus requests
|
|
28
|
+
* carry the same cookies / CSRF header / bearer token as the rest of the chat.
|
|
29
|
+
*/
|
|
30
|
+
interface MediaUploadOptions extends AgentClientOptions {
|
|
31
|
+
/** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */
|
|
32
|
+
path?: string;
|
|
33
|
+
/** Bytes per tus `PATCH`. Default 5 MiB (nestjs-media-client's default). */
|
|
34
|
+
chunkSize?: number;
|
|
35
|
+
/** Attempts per chunk before the upload fails. Default 3. */
|
|
36
|
+
retries?: number;
|
|
37
|
+
}
|
|
38
|
+
/** What `useAttachments({ upload })` and `AgentBackend.uploadAttachment` both take. */
|
|
39
|
+
type AttachmentUpload = (file: File, options?: UploadAttachmentOptions) => Promise<MessageAttachment>;
|
|
40
|
+
/**
|
|
41
|
+
* A non-2xx answer from the agent's upload routes. Carries `status` like `AgentHttpError` does
|
|
42
|
+
* (413 too large, 415 type refused, 404 not yours) — its own class because this subpath is bundled
|
|
43
|
+
* apart from the root entry, where an `instanceof AgentHttpError` would not match a copy.
|
|
44
|
+
*/
|
|
45
|
+
declare class MediaUploadError extends Error {
|
|
46
|
+
readonly status: number;
|
|
47
|
+
readonly method: string;
|
|
48
|
+
readonly path: string;
|
|
49
|
+
constructor(status: number, method: string, path: string, statusText: string);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A resumable `upload` for `useAttachments`, backed by `@dudousxd/nestjs-media` on the server
|
|
53
|
+
* (`AgentMediaAttachmentsModule` from `@dudousxd/nestjs-agent/media`):
|
|
54
|
+
*
|
|
55
|
+
* 1. `POST <path>/attachments/uploads` — the agent validates the file and opens a tus session the
|
|
56
|
+
* actor owns;
|
|
57
|
+
* 2. the bytes stream to nestjs-media's own tus endpoint in chunks (`streamChunks`), reporting
|
|
58
|
+
* progress and honouring the abort signal;
|
|
59
|
+
* 3. `POST <path>/attachments/uploads/:mediaId/complete` — the agent confirms the bytes landed and
|
|
60
|
+
* answers the `MessageAttachment` the turn will reference by `mediaId`.
|
|
61
|
+
*
|
|
62
|
+
* An aborted or failed upload is discarded server-side (`DELETE`), best effort.
|
|
63
|
+
*/
|
|
64
|
+
declare function createMediaUpload(options?: MediaUploadOptions): AttachmentUpload;
|
|
65
|
+
/**
|
|
66
|
+
* Plug media uploads in once: the same backend, with `uploadAttachment` replaced by
|
|
67
|
+
* {@link createMediaUpload}. Hand the result to `useAgentChat({ backend })` and
|
|
68
|
+
* `useAttachments({ backend })` and every composer uploads resumably — no per-composer wiring.
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* const backend = withMediaUploads(new AgentClient(connection), connection);
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* Every other member is the original backend's, still bound to it.
|
|
75
|
+
*/
|
|
76
|
+
declare function withMediaUploads<B extends AgentBackend>(backend: B, options?: MediaUploadOptions): B;
|
|
77
|
+
|
|
78
|
+
export { type AttachmentUpload, type MediaAttachmentsOptions, MediaUploadError, type MediaUploadOptions, createMediaUpload, mediaAttachments, withMediaUploads };
|
package/dist/media.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/media/index.ts
|
|
5
|
+
import { streamChunks } from "@dudousxd/nestjs-media-client";
|
|
6
|
+
function mediaAttachments(options = {}) {
|
|
7
|
+
return (file, uploadOptions, connection) => createMediaUpload({
|
|
8
|
+
...options,
|
|
9
|
+
baseUrl: connection.baseUrl,
|
|
10
|
+
getHeaders: connection.headers,
|
|
11
|
+
fetch: connection.fetch,
|
|
12
|
+
...connection.credentials !== void 0 ? {
|
|
13
|
+
credentials: connection.credentials
|
|
14
|
+
} : {}
|
|
15
|
+
})(file, uploadOptions);
|
|
16
|
+
}
|
|
17
|
+
__name(mediaAttachments, "mediaAttachments");
|
|
18
|
+
var MediaUploadError = class extends Error {
|
|
19
|
+
static {
|
|
20
|
+
__name(this, "MediaUploadError");
|
|
21
|
+
}
|
|
22
|
+
status;
|
|
23
|
+
method;
|
|
24
|
+
path;
|
|
25
|
+
constructor(status, method, path, statusText) {
|
|
26
|
+
super(`Attachment upload failed: ${method} ${path} \u2192 ${status} ${statusText}`), this.status = status, this.method = method, this.path = path;
|
|
27
|
+
this.name = "MediaUploadError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
function createMediaUpload(options = {}) {
|
|
31
|
+
const origin = (options.baseUrl ?? "").replace(/\/$/, "");
|
|
32
|
+
const base = `${origin}${(options.path ?? "/agent").replace(/\/$/, "")}/attachments/uploads`;
|
|
33
|
+
const baseFetch = /* @__PURE__ */ __name(() => options.fetch ?? fetch, "baseFetch");
|
|
34
|
+
const fetchWithCredentials = /* @__PURE__ */ __name((input, init) => baseFetch()(input, {
|
|
35
|
+
...init,
|
|
36
|
+
...options.credentials !== void 0 ? {
|
|
37
|
+
credentials: options.credentials
|
|
38
|
+
} : {}
|
|
39
|
+
}), "fetchWithCredentials");
|
|
40
|
+
const headers = /* @__PURE__ */ __name(async () => ({
|
|
41
|
+
...options.headers,
|
|
42
|
+
...await options.getHeaders?.() ?? {}
|
|
43
|
+
}), "headers");
|
|
44
|
+
const discard = /* @__PURE__ */ __name((mediaId) => {
|
|
45
|
+
void (async () => {
|
|
46
|
+
await fetchWithCredentials(`${base}/${encodeURIComponent(mediaId)}`, {
|
|
47
|
+
method: "DELETE",
|
|
48
|
+
headers: await headers()
|
|
49
|
+
});
|
|
50
|
+
})().catch(() => void 0);
|
|
51
|
+
}, "discard");
|
|
52
|
+
const call = /* @__PURE__ */ __name(async (method, url, body) => {
|
|
53
|
+
const response = await fetchWithCredentials(url, {
|
|
54
|
+
method,
|
|
55
|
+
headers: {
|
|
56
|
+
accept: "application/json",
|
|
57
|
+
...body !== void 0 ? {
|
|
58
|
+
"content-type": "application/json"
|
|
59
|
+
} : {},
|
|
60
|
+
...await headers()
|
|
61
|
+
},
|
|
62
|
+
...body !== void 0 ? {
|
|
63
|
+
body: JSON.stringify(body)
|
|
64
|
+
} : {}
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
throw new MediaUploadError(response.status, method, url, response.statusText);
|
|
68
|
+
}
|
|
69
|
+
return await response.json();
|
|
70
|
+
}, "call");
|
|
71
|
+
return async (file, { signal, onProgress } = {}) => {
|
|
72
|
+
signal?.throwIfAborted();
|
|
73
|
+
const begun = await call("POST", base, {
|
|
74
|
+
filename: file.name,
|
|
75
|
+
contentType: file.type,
|
|
76
|
+
size: file.size
|
|
77
|
+
});
|
|
78
|
+
const onAbort = /* @__PURE__ */ __name(() => discard(begun.mediaId), "onAbort");
|
|
79
|
+
signal?.addEventListener("abort", onAbort, {
|
|
80
|
+
once: true
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
const location = /^https?:\/\//.test(begun.location) ? begun.location : `${origin}${begun.location}`;
|
|
84
|
+
await streamChunks(location, file, {
|
|
85
|
+
resume: false,
|
|
86
|
+
fetchImpl: fetchWithCredentials,
|
|
87
|
+
getHeaders: headers,
|
|
88
|
+
...options.chunkSize !== void 0 ? {
|
|
89
|
+
chunkSize: options.chunkSize
|
|
90
|
+
} : {},
|
|
91
|
+
...options.retries !== void 0 ? {
|
|
92
|
+
retries: options.retries
|
|
93
|
+
} : {},
|
|
94
|
+
...signal !== void 0 ? {
|
|
95
|
+
signal
|
|
96
|
+
} : {},
|
|
97
|
+
onProgress: /* @__PURE__ */ __name((sent, total) => onProgress?.(total > 0 ? sent / total : 1), "onProgress")
|
|
98
|
+
});
|
|
99
|
+
signal?.throwIfAborted();
|
|
100
|
+
const attachment = await call("POST", `${base}/${encodeURIComponent(begun.mediaId)}/complete`);
|
|
101
|
+
onProgress?.(1);
|
|
102
|
+
return attachment;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (!signal?.aborted) discard(begun.mediaId);
|
|
105
|
+
throw error;
|
|
106
|
+
} finally {
|
|
107
|
+
signal?.removeEventListener("abort", onAbort);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
__name(createMediaUpload, "createMediaUpload");
|
|
112
|
+
function withMediaUploads(backend, options = {}) {
|
|
113
|
+
const upload = createMediaUpload(options);
|
|
114
|
+
return new Proxy(backend, {
|
|
115
|
+
get(target, property) {
|
|
116
|
+
if (property === "uploadAttachment") return upload;
|
|
117
|
+
const value = Reflect.get(target, property, target);
|
|
118
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
__name(withMediaUploads, "withMediaUploads");
|
|
123
|
+
export {
|
|
124
|
+
MediaUploadError,
|
|
125
|
+
createMediaUpload,
|
|
126
|
+
mediaAttachments,
|
|
127
|
+
withMediaUploads
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/media/index.ts"],"sourcesContent":["import type { MessageAttachment } from '@dudousxd/nestjs-agent-core';\nimport { streamChunks } from '@dudousxd/nestjs-media-client';\nimport type {\n AgentBackend,\n AttachmentUploadStrategy,\n UploadAttachmentOptions,\n} from '../backend.js';\nimport type { AgentClientOptions } from '../client.js';\n\n/** Tuning for {@link mediaAttachments}; every field optional. */\nexport interface MediaAttachmentsOptions {\n /** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */\n path?: string;\n /** Bytes per tus `PATCH`. Default 5 MiB. */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/**\n * Resumable attachment uploads through `@dudousxd/nestjs-media`, in one line:\n *\n * ```ts\n * const chat = useAgentChat({ attachments: mediaAttachments() });\n * const files = useAttachments({ backend: chat.backend });\n * ```\n *\n * Reuses the chat client's own connection (base url, headers, credentials), so it needs no\n * configuration. Also takes `new AgentClient({ attachments: mediaAttachments() })`.\n */\nexport function mediaAttachments(options: MediaAttachmentsOptions = {}): AttachmentUploadStrategy {\n return (file, uploadOptions, connection) =>\n createMediaUpload({\n ...options,\n baseUrl: connection.baseUrl,\n getHeaders: connection.headers,\n fetch: connection.fetch,\n ...(connection.credentials !== undefined ? { credentials: connection.credentials } : {}),\n })(file, uploadOptions);\n}\n\n/**\n * Where and how {@link createMediaUpload} talks to the server. The connection fields are\n * {@link AgentClientOptions}' — pass the same ones your `AgentClient` gets, so the tus requests\n * carry the same cookies / CSRF header / bearer token as the rest of the chat.\n */\nexport interface MediaUploadOptions extends AgentClientOptions {\n /** The agent's route prefix (`AgentModule`'s `path`, global prefix included). Default `/agent`. */\n path?: string;\n /** Bytes per tus `PATCH`. Default 5 MiB (nestjs-media-client's default). */\n chunkSize?: number;\n /** Attempts per chunk before the upload fails. Default 3. */\n retries?: number;\n}\n\n/** What `useAttachments({ upload })` and `AgentBackend.uploadAttachment` both take. */\nexport type AttachmentUpload = (\n file: File,\n options?: UploadAttachmentOptions,\n) => Promise<MessageAttachment>;\n\n/**\n * A non-2xx answer from the agent's upload routes. Carries `status` like `AgentHttpError` does\n * (413 too large, 415 type refused, 404 not yours) — its own class because this subpath is bundled\n * apart from the root entry, where an `instanceof AgentHttpError` would not match a copy.\n */\nexport class MediaUploadError extends Error {\n constructor(\n readonly status: number,\n readonly method: string,\n readonly path: string,\n statusText: string,\n ) {\n super(`Attachment upload failed: ${method} ${path} → ${status} ${statusText}`);\n this.name = 'MediaUploadError';\n }\n}\n\ninterface BeginResponse {\n mediaId: string;\n location: string;\n}\n\n/**\n * A resumable `upload` for `useAttachments`, backed by `@dudousxd/nestjs-media` on the server\n * (`AgentMediaAttachmentsModule` from `@dudousxd/nestjs-agent/media`):\n *\n * 1. `POST <path>/attachments/uploads` — the agent validates the file and opens a tus session the\n * actor owns;\n * 2. the bytes stream to nestjs-media's own tus endpoint in chunks (`streamChunks`), reporting\n * progress and honouring the abort signal;\n * 3. `POST <path>/attachments/uploads/:mediaId/complete` — the agent confirms the bytes landed and\n * answers the `MessageAttachment` the turn will reference by `mediaId`.\n *\n * An aborted or failed upload is discarded server-side (`DELETE`), best effort.\n */\nexport function createMediaUpload(options: MediaUploadOptions = {}): AttachmentUpload {\n const origin = (options.baseUrl ?? '').replace(/\\/$/, '');\n const base = `${origin}${(options.path ?? '/agent').replace(/\\/$/, '')}/attachments/uploads`;\n const baseFetch = (): typeof fetch => options.fetch ?? fetch;\n // Every request — the agent's and media's tus PATCHes — rides the same credentials mode.\n const fetchWithCredentials: typeof fetch = (input, init) =>\n baseFetch()(input, {\n ...init,\n ...(options.credentials !== undefined ? { credentials: options.credentials } : {}),\n });\n const headers = async (): Promise<Record<string, string>> => ({\n ...options.headers,\n ...((await options.getHeaders?.()) ?? {}),\n });\n const discard = (mediaId: string): void => {\n void (async () => {\n await fetchWithCredentials(`${base}/${encodeURIComponent(mediaId)}`, {\n method: 'DELETE',\n headers: await headers(),\n });\n })().catch(() => undefined);\n };\n const call = async <T>(method: string, url: string, body?: unknown): Promise<T> => {\n const response = await fetchWithCredentials(url, {\n method,\n headers: {\n accept: 'application/json',\n ...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n ...(await headers()),\n },\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n if (!response.ok) {\n throw new MediaUploadError(response.status, method, url, response.statusText);\n }\n return (await response.json()) as T;\n };\n\n return async (file, { signal, onProgress } = {}) => {\n signal?.throwIfAborted();\n const begun = await call<BeginResponse>('POST', base, {\n filename: file.name,\n contentType: file.type,\n size: file.size,\n });\n const onAbort = () => discard(begun.mediaId);\n signal?.addEventListener('abort', onAbort, { once: true });\n try {\n const location = /^https?:\\/\\//.test(begun.location)\n ? begun.location\n : `${origin}${begun.location}`;\n await streamChunks(location, file, {\n resume: false,\n fetchImpl: fetchWithCredentials,\n getHeaders: headers,\n ...(options.chunkSize !== undefined ? { chunkSize: options.chunkSize } : {}),\n ...(options.retries !== undefined ? { retries: options.retries } : {}),\n ...(signal !== undefined ? { signal } : {}),\n onProgress: (sent, total) => onProgress?.(total > 0 ? sent / total : 1),\n });\n signal?.throwIfAborted();\n const attachment = await call<MessageAttachment>(\n 'POST',\n `${base}/${encodeURIComponent(begun.mediaId)}/complete`,\n );\n onProgress?.(1);\n return attachment;\n } catch (error) {\n if (!signal?.aborted) discard(begun.mediaId);\n throw error;\n } finally {\n signal?.removeEventListener('abort', onAbort);\n }\n };\n}\n\n/**\n * Plug media uploads in once: the same backend, with `uploadAttachment` replaced by\n * {@link createMediaUpload}. Hand the result to `useAgentChat({ backend })` and\n * `useAttachments({ backend })` and every composer uploads resumably — no per-composer wiring.\n *\n * ```ts\n * const backend = withMediaUploads(new AgentClient(connection), connection);\n * ```\n *\n * Every other member is the original backend's, still bound to it.\n */\nexport function withMediaUploads<B extends AgentBackend>(\n backend: B,\n options: MediaUploadOptions = {},\n): B {\n const upload = createMediaUpload(options);\n return new Proxy(backend, {\n get(target, property) {\n if (property === 'uploadAttachment') return upload;\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":"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","names":["streamChunks","mediaAttachments","options","file","uploadOptions","connection","createMediaUpload","baseUrl","getHeaders","headers","fetch","credentials","undefined","MediaUploadError","Error","status","method","path","statusText","name","origin","replace","base","baseFetch","fetchWithCredentials","input","init","discard","mediaId","encodeURIComponent","catch","call","url","body","response","accept","JSON","stringify","ok","json","signal","onProgress","throwIfAborted","begun","filename","contentType","type","size","onAbort","addEventListener","once","location","test","streamChunks","resume","fetchImpl","chunkSize","retries","sent","total","attachment","error","aborted","removeEventListener","withMediaUploads","backend","upload","Proxy","get","target","property","value","Reflect","bind"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dudousxd/nestjs-agent-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/DavideCarvalho/nestjs-agent.git",
|
|
@@ -67,16 +67,27 @@
|
|
|
67
67
|
"types": "./dist/genui-json-render.d.cts",
|
|
68
68
|
"default": "./dist/genui-json-render.cjs"
|
|
69
69
|
}
|
|
70
|
+
},
|
|
71
|
+
"./media": {
|
|
72
|
+
"import": {
|
|
73
|
+
"types": "./dist/media.d.ts",
|
|
74
|
+
"default": "./dist/media.js"
|
|
75
|
+
},
|
|
76
|
+
"require": {
|
|
77
|
+
"types": "./dist/media.d.cts",
|
|
78
|
+
"default": "./dist/media.cjs"
|
|
79
|
+
}
|
|
70
80
|
}
|
|
71
81
|
},
|
|
72
82
|
"files": [
|
|
73
83
|
"dist"
|
|
74
84
|
],
|
|
75
85
|
"dependencies": {
|
|
76
|
-
"@dudousxd/nestjs-agent-core": "0.
|
|
86
|
+
"@dudousxd/nestjs-agent-core": "0.26.0"
|
|
77
87
|
},
|
|
78
88
|
"peerDependencies": {
|
|
79
89
|
"@ai-sdk/react": "^4.0.0",
|
|
90
|
+
"@dudousxd/nestjs-media-client": ">=0.3.0",
|
|
80
91
|
"@json-render/react": ">=0.21.0 <1.0.0",
|
|
81
92
|
"@streamdown/code": "^1.1.1",
|
|
82
93
|
"@streamdown/math": "^1.0.2",
|
|
@@ -94,6 +105,9 @@
|
|
|
94
105
|
"unist-util-visit": "^5.1.0"
|
|
95
106
|
},
|
|
96
107
|
"peerDependenciesMeta": {
|
|
108
|
+
"@dudousxd/nestjs-media-client": {
|
|
109
|
+
"optional": true
|
|
110
|
+
},
|
|
97
111
|
"@json-render/react": {
|
|
98
112
|
"optional": true
|
|
99
113
|
},
|
|
@@ -136,6 +150,7 @@
|
|
|
136
150
|
},
|
|
137
151
|
"devDependencies": {
|
|
138
152
|
"@ai-sdk/react": "4.0.17",
|
|
153
|
+
"@dudousxd/nestjs-media-client": "0.3.0",
|
|
139
154
|
"@json-render/core": "0.21.0",
|
|
140
155
|
"@json-render/react": "0.21.0",
|
|
141
156
|
"@streamdown/code": "1.1.1",
|
|
@@ -159,8 +174,7 @@
|
|
|
159
174
|
"tsup": "8.5.1",
|
|
160
175
|
"typescript": "5.9.3",
|
|
161
176
|
"unist-util-visit": "5.1.0",
|
|
162
|
-
"vitest": "2.1.9"
|
|
163
|
-
"@dudousxd/nestjs-agent-genui": "0.2.0"
|
|
177
|
+
"vitest": "2.1.9"
|
|
164
178
|
},
|
|
165
179
|
"scripts": {
|
|
166
180
|
"build": "tsup",
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { ComponentType, ReactNode } from 'react';
|
|
2
|
-
|
|
3
|
-
/** The `ui` frame component a composed tree is pushed under (`@dudousxd/nestjs-agent-genui`'s `GENUI_TREE_COMPONENT`). */
|
|
4
|
-
declare const GENUI_TREE_COMPONENT = "genui:tree";
|
|
5
|
-
/** One pushed component, normalized from whatever carried it (a transcript block, a `data-ui` part, a stored entry). */
|
|
6
|
-
interface GenerativeUIItem {
|
|
7
|
-
id: string;
|
|
8
|
-
component: string;
|
|
9
|
-
props: Record<string, unknown>;
|
|
10
|
-
version: number | null;
|
|
11
|
-
toolCallId: string | null;
|
|
12
|
-
}
|
|
13
|
-
/** A node of a composed tree (`genui:tree` frames): `{ type, props, children? }`. */
|
|
14
|
-
interface GenerativeUIElement {
|
|
15
|
-
type: string;
|
|
16
|
-
props: Record<string, unknown>;
|
|
17
|
-
children?: GenerativeUIElement[];
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* An app's renderer for one component: it receives the component's props spread, plus `children`
|
|
21
|
-
* when it is a layout node in a tree. Any React component — the library never styles anything.
|
|
22
|
-
*/
|
|
23
|
-
type GenuiRenderer<P = any> = ComponentType<P & {
|
|
24
|
-
children?: ReactNode;
|
|
25
|
-
}>;
|
|
26
|
-
/** Component name → the app's renderer. */
|
|
27
|
-
type GenuiRegistry = Record<string, GenuiRenderer>;
|
|
28
|
-
/**
|
|
29
|
-
* Resolve a component the registry does not have — typically a tenant's own component, fetched for
|
|
30
|
-
* the exact `version` a message was rendered with. Return `null`/`undefined` for "no such
|
|
31
|
-
* component". May be async; results are cached per resolver, name and version.
|
|
32
|
-
*/
|
|
33
|
-
type ResolveComponent = (name: string, version: number | null) => GenuiRenderer | null | undefined | Promise<GenuiRenderer | null | undefined>;
|
|
34
|
-
interface GenuiIssueLike {
|
|
35
|
-
path: (string | number)[];
|
|
36
|
-
message: string;
|
|
37
|
-
}
|
|
38
|
-
type ValidationLike = {
|
|
39
|
-
ok: true;
|
|
40
|
-
value: Record<string, unknown>;
|
|
41
|
-
} | {
|
|
42
|
-
ok: false;
|
|
43
|
-
issues: GenuiIssueLike[];
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* What the renderer needs from a catalog to validate props before drawing them. A
|
|
47
|
-
* `@dudousxd/nestjs-agent-genui` `Catalog` satisfies it; declared structurally so this subpath does
|
|
48
|
-
* not depend on that package.
|
|
49
|
-
*/
|
|
50
|
-
interface GenuiCatalogLike {
|
|
51
|
-
has(name: string): boolean;
|
|
52
|
-
validate(name: string, props: unknown): Promise<ValidationLike>;
|
|
53
|
-
validateSync?(name: string, props: unknown): ValidationLike | undefined;
|
|
54
|
-
}
|
|
55
|
-
interface GenerativeUIOptions {
|
|
56
|
-
registry: GenuiRegistry;
|
|
57
|
-
/** Validate props against it before rendering. Components the catalog does not know render unvalidated. */
|
|
58
|
-
catalog?: GenuiCatalogLike;
|
|
59
|
-
resolveComponent?: ResolveComponent;
|
|
60
|
-
}
|
|
61
|
-
/** Why an item did not render. */
|
|
62
|
-
type GenerativeUIProblem = {
|
|
63
|
-
reason: 'unknown';
|
|
64
|
-
item: GenerativeUIItem;
|
|
65
|
-
} | {
|
|
66
|
-
reason: 'invalid';
|
|
67
|
-
item: GenerativeUIItem;
|
|
68
|
-
issues: GenuiIssueLike[];
|
|
69
|
-
} | {
|
|
70
|
-
reason: 'error';
|
|
71
|
-
item: GenerativeUIItem;
|
|
72
|
-
error: unknown;
|
|
73
|
-
};
|
|
74
|
-
type GenerativeUIState = {
|
|
75
|
-
status: 'ready';
|
|
76
|
-
item: GenerativeUIItem;
|
|
77
|
-
Component: GenuiRenderer;
|
|
78
|
-
props: Record<string, unknown>;
|
|
79
|
-
} | {
|
|
80
|
-
status: 'loading';
|
|
81
|
-
item: GenerativeUIItem;
|
|
82
|
-
} | ({
|
|
83
|
-
status: 'problem';
|
|
84
|
-
} & GenerativeUIProblem);
|
|
85
|
-
|
|
86
|
-
export { type GenerativeUIOptions as G, type ResolveComponent as R, type GenerativeUIItem as a, type GenerativeUIProblem as b, type GenerativeUIElement as c, type GenerativeUIState as d, GENUI_TREE_COMPONENT as e, type GenuiCatalogLike as f, type GenuiIssueLike as g, type GenuiRegistry as h, type GenuiRenderer as i };
|