@intelligo-dev/chat 1.0.0-beta.13
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/LICENSE +201 -0
- package/NOTICE +6 -0
- package/README.md +116 -0
- package/dist/artifact-writer.d.ts +45 -0
- package/dist/artifact-writer.d.ts.map +1 -0
- package/dist/artifact-writer.js +77 -0
- package/dist/artifact-writer.js.map +1 -0
- package/dist/attachments.d.ts +56 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +204 -0
- package/dist/attachments.js.map +1 -0
- package/dist/body.d.ts +72 -0
- package/dist/body.d.ts.map +1 -0
- package/dist/body.js +174 -0
- package/dist/body.js.map +1 -0
- package/dist/client.d.ts +65 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +61 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +322 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +11 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +23 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/feedback.d.ts +22 -0
- package/dist/feedback.d.ts.map +1 -0
- package/dist/feedback.js +46 -0
- package/dist/feedback.js.map +1 -0
- package/dist/generation.d.ts +104 -0
- package/dist/generation.d.ts.map +1 -0
- package/dist/generation.js +85 -0
- package/dist/generation.js.map +1 -0
- package/dist/handler.d.ts +30 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +913 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +19 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +34 -0
- package/dist/messages.js.map +1 -0
- package/dist/parts.d.ts +117 -0
- package/dist/parts.d.ts.map +1 -0
- package/dist/parts.js +13 -0
- package/dist/parts.js.map +1 -0
- package/dist/quota.d.ts +32 -0
- package/dist/quota.d.ts.map +1 -0
- package/dist/quota.js +81 -0
- package/dist/quota.js.map +1 -0
- package/dist/share.d.ts +24 -0
- package/dist/share.d.ts.map +1 -0
- package/dist/share.js +78 -0
- package/dist/share.js.map +1 -0
- package/dist/testing.d.ts +36 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +82 -0
- package/dist/testing.js.map +1 -0
- package/dist/title.d.ts +7 -0
- package/dist/title.d.ts.map +1 -0
- package/dist/title.js +15 -0
- package/dist/title.js.map +1 -0
- package/dist/usage.d.ts +21 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +35 -0
- package/dist/usage.js.map +1 -0
- package/dist/windowing.d.ts +38 -0
- package/dist/windowing.d.ts.map +1 -0
- package/dist/windowing.js +82 -0
- package/dist/windowing.js.map +1 -0
- package/package.json +78 -0
- package/src/artifact-writer.ts +114 -0
- package/src/attachments.ts +262 -0
- package/src/body.ts +236 -0
- package/src/client.ts +133 -0
- package/src/config.ts +376 -0
- package/src/errors.ts +80 -0
- package/src/feedback.ts +62 -0
- package/src/generation.ts +150 -0
- package/src/handler.ts +1164 -0
- package/src/index.ts +93 -0
- package/src/messages.ts +39 -0
- package/src/parts.ts +143 -0
- package/src/quota.ts +105 -0
- package/src/share.ts +103 -0
- package/src/testing.ts +150 -0
- package/src/title.ts +15 -0
- package/src/usage.ts +46 -0
- package/src/windowing.ts +110 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stored attachments: the upload route and the route that serves them.
|
|
3
|
+
*
|
|
4
|
+
* Inline attachments (the default policy) travel inside the message as
|
|
5
|
+
* data URLs and need nothing here. A deployment that wants files
|
|
6
|
+
* outside the transcript — larger than a message should carry, kept
|
|
7
|
+
* after the conversation is shared, extracted to text for the model —
|
|
8
|
+
* sets `attachments.mode: "stored"`, binds a storage adapter from its
|
|
9
|
+
* composition root, and mounts these two handlers:
|
|
10
|
+
*
|
|
11
|
+
* // app/api/chat/upload/route.ts
|
|
12
|
+
* export const { POST } = createChatUploadHandler(chatServerConfig);
|
|
13
|
+
* // app/api/chat/attachments/[id]/route.ts
|
|
14
|
+
* export const { GET } = createChatAttachmentHandler(chatServerConfig);
|
|
15
|
+
*
|
|
16
|
+
* The upload answers `{ id, url, filename, mediaType, size }`; the
|
|
17
|
+
* composer puts `url` in the file part; the transport signs it for the
|
|
18
|
+
* model and persists the app URL, which this route redirects to a
|
|
19
|
+
* fresh signed URL for anyone in the workspace who can open the
|
|
20
|
+
* conversation. No signed URL is ever persisted or shared.
|
|
21
|
+
*/
|
|
22
|
+
import { requireWorkspace } from "@intelligo-dev/auth";
|
|
23
|
+
import { createAttachment, getAttachment, isAttachmentServiceError, setExtractedText, } from "@intelligo-dev/core/attachments";
|
|
24
|
+
import { createLogger } from "@intelligo-dev/core/logger";
|
|
25
|
+
import { attachmentStorageKey, getStorageAdapter, StorageUnavailableError, } from "@intelligo-dev/core/storage";
|
|
26
|
+
import { ATTACHMENT_MAX_BYTES, attachmentUrl } from "./body.js";
|
|
27
|
+
/** Room for the multipart boundaries and headers around the file. */
|
|
28
|
+
const FORM_OVERHEAD_BYTES = 64 * 1024;
|
|
29
|
+
import { DEFAULT_CHAT_MESSAGES, refuse } from "./errors.js";
|
|
30
|
+
const log = createLogger("Chat");
|
|
31
|
+
const SIGNED_URL_SECONDS = 900;
|
|
32
|
+
async function defaultAuthenticate() {
|
|
33
|
+
const { workspace, user } = await requireWorkspace();
|
|
34
|
+
return { workspaceId: workspace.id, userId: user.id };
|
|
35
|
+
}
|
|
36
|
+
function safeFilename(name) {
|
|
37
|
+
const base = name.split(/[\\/]/).pop() ?? "";
|
|
38
|
+
return base.replace(/[\x00-\x1f]/g, "").slice(0, 255) || "file";
|
|
39
|
+
}
|
|
40
|
+
function errorMessage(error) {
|
|
41
|
+
return error instanceof Error ? error.message : String(error);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `POST multipart/form-data` with one `file` field → an attachment row
|
|
45
|
+
* and the URL the composer puts in the file part. Refuses a type the
|
|
46
|
+
* policy does not accept, a file over `maxBytes`, and anything without
|
|
47
|
+
* a session.
|
|
48
|
+
*/
|
|
49
|
+
export function createChatUploadHandler(config) {
|
|
50
|
+
const authenticate = config.authenticate ?? defaultAuthenticate;
|
|
51
|
+
const policy = config.attachments;
|
|
52
|
+
async function messagesFor(request) {
|
|
53
|
+
return config.messages ? config.messages(request) : DEFAULT_CHAT_MESSAGES;
|
|
54
|
+
}
|
|
55
|
+
async function POST(request) {
|
|
56
|
+
await config.onRequest?.();
|
|
57
|
+
const t = await messagesFor(request);
|
|
58
|
+
if (!policy || policy.mode !== "stored") {
|
|
59
|
+
return refuse("BAD_REQUEST", t("attachmentRejected"));
|
|
60
|
+
}
|
|
61
|
+
let actor;
|
|
62
|
+
try {
|
|
63
|
+
actor = await authenticate(request);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return refuse("UNAUTHORIZED", t("unauthorized"));
|
|
67
|
+
}
|
|
68
|
+
// Refused before the body is read: `formData()` buffers all of it.
|
|
69
|
+
const maxBytes = policy.maxBytes ?? ATTACHMENT_MAX_BYTES;
|
|
70
|
+
const declared = Number(request.headers.get("content-length"));
|
|
71
|
+
if (Number.isFinite(declared) &&
|
|
72
|
+
declared > maxBytes + FORM_OVERHEAD_BYTES) {
|
|
73
|
+
return refuse("BAD_REQUEST", t("attachmentRejected"));
|
|
74
|
+
}
|
|
75
|
+
let form;
|
|
76
|
+
try {
|
|
77
|
+
form = await request.formData();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return refuse("BAD_REQUEST", t("invalidBody"));
|
|
81
|
+
}
|
|
82
|
+
const file = form.get("file");
|
|
83
|
+
if (!(file instanceof Blob)) {
|
|
84
|
+
return refuse("BAD_REQUEST", t("invalidBody"));
|
|
85
|
+
}
|
|
86
|
+
const mediaType = file.type || "application/octet-stream";
|
|
87
|
+
if (!policy.accept.includes(mediaType)) {
|
|
88
|
+
return refuse("BAD_REQUEST", t("attachmentRejected"));
|
|
89
|
+
}
|
|
90
|
+
if (file.size > maxBytes) {
|
|
91
|
+
return refuse("BAD_REQUEST", t("attachmentRejected"));
|
|
92
|
+
}
|
|
93
|
+
let storage;
|
|
94
|
+
try {
|
|
95
|
+
storage = getStorageAdapter();
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error instanceof StorageUnavailableError) {
|
|
99
|
+
log.error("Attachment upload with no storage adapter bound");
|
|
100
|
+
return refuse("INTERNAL", t("internalError"));
|
|
101
|
+
}
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
const id = crypto.randomUUID();
|
|
105
|
+
const filename = safeFilename(typeof file.name === "string" ? file.name : "file");
|
|
106
|
+
const key = attachmentStorageKey(actor.workspaceId, id);
|
|
107
|
+
try {
|
|
108
|
+
await storage.put({
|
|
109
|
+
key,
|
|
110
|
+
body: file,
|
|
111
|
+
contentType: mediaType,
|
|
112
|
+
contentLength: file.size,
|
|
113
|
+
});
|
|
114
|
+
await createAttachment(actor, {
|
|
115
|
+
id,
|
|
116
|
+
storageKey: key,
|
|
117
|
+
filename,
|
|
118
|
+
mediaType,
|
|
119
|
+
sizeBytes: file.size,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
log.error("Attachment upload failed", { error: errorMessage(error) });
|
|
124
|
+
return refuse("INTERNAL", t("internalError"));
|
|
125
|
+
}
|
|
126
|
+
// Text extraction is best effort: a PDF that will not parse still
|
|
127
|
+
// uploads, and the model gets the file part alone.
|
|
128
|
+
if (policy.extractText && !mediaType.startsWith("image/")) {
|
|
129
|
+
try {
|
|
130
|
+
const text = await policy.extractText({
|
|
131
|
+
id,
|
|
132
|
+
filename,
|
|
133
|
+
mediaType,
|
|
134
|
+
bytes: async () => new Uint8Array(await file.arrayBuffer()),
|
|
135
|
+
});
|
|
136
|
+
if (text)
|
|
137
|
+
await setExtractedText(actor, { id, text });
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
log.warn("Attachment text extraction failed", {
|
|
141
|
+
attachmentId: id,
|
|
142
|
+
error: errorMessage(error),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const result = {
|
|
147
|
+
id,
|
|
148
|
+
url: attachmentUrl(policy, id),
|
|
149
|
+
filename,
|
|
150
|
+
mediaType,
|
|
151
|
+
size: file.size,
|
|
152
|
+
};
|
|
153
|
+
return Response.json(result, { status: 201 });
|
|
154
|
+
}
|
|
155
|
+
return { POST };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* `GET /api/chat/attachments/[id]` → a redirect to a short-lived signed
|
|
159
|
+
* URL, for anyone signed in to the attachment's workspace. The app URL
|
|
160
|
+
* is what the transcript persists, so it must keep working after the
|
|
161
|
+
* signed one expires.
|
|
162
|
+
*/
|
|
163
|
+
export function createChatAttachmentHandler(config) {
|
|
164
|
+
const authenticate = config.authenticate ?? defaultAuthenticate;
|
|
165
|
+
async function messagesFor(request) {
|
|
166
|
+
return config.messages ? config.messages(request) : DEFAULT_CHAT_MESSAGES;
|
|
167
|
+
}
|
|
168
|
+
async function GET(request, context) {
|
|
169
|
+
await config.onRequest?.();
|
|
170
|
+
const t = await messagesFor(request);
|
|
171
|
+
const { id } = await context.params;
|
|
172
|
+
let actor;
|
|
173
|
+
try {
|
|
174
|
+
actor = await authenticate(request);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return refuse("UNAUTHORIZED", t("unauthorized"));
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const row = await getAttachment(actor, id);
|
|
181
|
+
const url = await getStorageAdapter().getSignedUrl(row.storageKey, {
|
|
182
|
+
expiresInSeconds: SIGNED_URL_SECONDS,
|
|
183
|
+
disposition: "inline",
|
|
184
|
+
filename: row.filename,
|
|
185
|
+
});
|
|
186
|
+
return new Response(null, {
|
|
187
|
+
status: 302,
|
|
188
|
+
headers: { location: url, "cache-control": "private, no-store" },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (isAttachmentServiceError(error) && error.code === "not_found") {
|
|
193
|
+
return refuse("NOT_FOUND", t("notFound"));
|
|
194
|
+
}
|
|
195
|
+
log.error("Attachment read failed", {
|
|
196
|
+
attachmentId: id,
|
|
197
|
+
error: errorMessage(error),
|
|
198
|
+
});
|
|
199
|
+
return refuse("INTERNAL", t("internalError"));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { GET };
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
|
package/dist/body.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request body, validated by hand.
|
|
3
|
+
*
|
|
4
|
+
* Not zod: a schema library in a package's public surface would have
|
|
5
|
+
* to be a peer (its `instanceof` fails across copies), and the body
|
|
6
|
+
* has four fields. The shape is the AI SDK's `DefaultChatTransport`
|
|
7
|
+
* one — `id`, `messages`, `trigger`, `messageId` — plus whatever the
|
|
8
|
+
* application's transport added, which is handed back untouched as
|
|
9
|
+
* `extra` for `resolveAgent` to read (an `agentId`, a model choice).
|
|
10
|
+
*/
|
|
11
|
+
import type { UIMessage } from "ai";
|
|
12
|
+
export type ChatAttachmentPolicy = {
|
|
13
|
+
/** Media types a file part may carry, e.g. `["image/jpeg", "image/png"]`. */
|
|
14
|
+
accept: readonly string[];
|
|
15
|
+
/** Largest payload accepted, in bytes. `ATTACHMENT_MAX_BYTES` when omitted. */
|
|
16
|
+
maxBytes?: number;
|
|
17
|
+
/**
|
|
18
|
+
* `inline` (default): the file travels in the message as a data URL
|
|
19
|
+
* and is persisted with it. `stored`: the file was uploaded first
|
|
20
|
+
* through the upload route and the part carries the app URL the
|
|
21
|
+
* attachment route serves; the transport signs it for the model.
|
|
22
|
+
*/
|
|
23
|
+
mode?: "inline" | "stored";
|
|
24
|
+
/** The app URL of a stored attachment. Default `/api/chat/attachments/<id>`. */
|
|
25
|
+
urlFor?: (id: string) => string;
|
|
26
|
+
/**
|
|
27
|
+
* Turns a stored, non-image file into text for the model — a PDF, a
|
|
28
|
+
* spreadsheet. Runs once, at upload; the text is kept on the row and
|
|
29
|
+
* appended to the message the model sees. Unset: the file part is
|
|
30
|
+
* passed through as-is and the provider decides what to do with it.
|
|
31
|
+
*/
|
|
32
|
+
extractText?: (file: {
|
|
33
|
+
id: string;
|
|
34
|
+
filename: string;
|
|
35
|
+
mediaType: string;
|
|
36
|
+
bytes: () => Promise<Uint8Array>;
|
|
37
|
+
}) => Promise<string | null>;
|
|
38
|
+
};
|
|
39
|
+
export type ChatBody = {
|
|
40
|
+
id: string;
|
|
41
|
+
messages: UIMessage[];
|
|
42
|
+
trigger: "submit-message" | "regenerate-message" | undefined;
|
|
43
|
+
messageId: string | undefined;
|
|
44
|
+
/** Fields the application's transport added beyond the SDK's own. */
|
|
45
|
+
extra: Record<string, unknown>;
|
|
46
|
+
};
|
|
47
|
+
export type ChatBodyRejection = {
|
|
48
|
+
key: "invalidBody" | "messageTooLong" | "attachmentRejected";
|
|
49
|
+
params?: Record<string, string | number>;
|
|
50
|
+
};
|
|
51
|
+
export type ParsedChatBody = {
|
|
52
|
+
ok: true;
|
|
53
|
+
body: ChatBody;
|
|
54
|
+
} | {
|
|
55
|
+
ok: false;
|
|
56
|
+
rejection: ChatBodyRejection;
|
|
57
|
+
};
|
|
58
|
+
/** 10 MB: the limit a policy that names none gets. */
|
|
59
|
+
export declare const ATTACHMENT_MAX_BYTES: number;
|
|
60
|
+
/** The app URL of a stored attachment under this policy. */
|
|
61
|
+
export declare function attachmentUrl(policy: Pick<ChatAttachmentPolicy, "urlFor">, id: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* The attachment id a stored file part's URL names, or null when the
|
|
64
|
+
* URL is not one this policy hands out. Absolute and relative forms
|
|
65
|
+
* of the same path both match.
|
|
66
|
+
*/
|
|
67
|
+
export declare function attachmentIdFromUrl(policy: Pick<ChatAttachmentPolicy, "urlFor">, url: string): string | null;
|
|
68
|
+
export declare function parseChatBody(json: unknown, options: {
|
|
69
|
+
maxMessageLength: number;
|
|
70
|
+
attachments: ChatAttachmentPolicy | false;
|
|
71
|
+
}): ParsedChatBody;
|
|
72
|
+
//# sourceMappingURL=body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAIpC,MAAM,MAAM,oBAAoB,GAAG;IACjC,6EAA6E;IAC7E,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC3B,gFAAgF;IAChF,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE;QACnB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;KAClC,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,OAAO,EAAE,gBAAgB,GAAG,oBAAoB,GAAG,SAAS,CAAC;IAC7D,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,aAAa,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,cAAc,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAE7E,sDAAsD;AACtD,eAAO,MAAM,oBAAoB,QAAmB,CAAC;AAmCrD,4DAA4D;AAC5D,wBAAgB,aAAa,CAC3B,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,EAC5C,EAAE,EAAE,MAAM,GACT,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAC,EAC5C,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,IAAI,CAmBf;AAgCD,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,EACb,OAAO,EAAE;IACP,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,oBAAoB,GAAG,KAAK,CAAC;CAC3C,GACA,cAAc,CAkEhB"}
|
package/dist/body.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The request body, validated by hand.
|
|
3
|
+
*
|
|
4
|
+
* Not zod: a schema library in a package's public surface would have
|
|
5
|
+
* to be a peer (its `instanceof` fails across copies), and the body
|
|
6
|
+
* has four fields. The shape is the AI SDK's `DefaultChatTransport`
|
|
7
|
+
* one — `id`, `messages`, `trigger`, `messageId` — plus whatever the
|
|
8
|
+
* application's transport added, which is handed back untouched as
|
|
9
|
+
* `extra` for `resolveAgent` to read (an `agentId`, a model choice).
|
|
10
|
+
*/
|
|
11
|
+
import { extractText } from "./windowing.js";
|
|
12
|
+
/** 10 MB: the limit a policy that names none gets. */
|
|
13
|
+
export const ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
|
|
14
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15
|
+
/**
|
|
16
|
+
* No `system`: the system prompt is the server's. A client that could
|
|
17
|
+
* send one could rewrite the agent's instructions.
|
|
18
|
+
*/
|
|
19
|
+
const ROLES = new Set(["user", "assistant"]);
|
|
20
|
+
const SDK_FIELDS = new Set(["id", "messages", "trigger", "messageId"]);
|
|
21
|
+
const ID_PLACEHOLDER = "__ATTACHMENT_ID__";
|
|
22
|
+
function isRecord(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function isUIMessage(value) {
|
|
26
|
+
if (!isRecord(value))
|
|
27
|
+
return false;
|
|
28
|
+
if (typeof value.id !== "string" || !value.id)
|
|
29
|
+
return false;
|
|
30
|
+
if (typeof value.role !== "string" || !ROLES.has(value.role))
|
|
31
|
+
return false;
|
|
32
|
+
if (!Array.isArray(value.parts))
|
|
33
|
+
return false;
|
|
34
|
+
return value.parts.every((part) => isRecord(part) && typeof part.type === "string");
|
|
35
|
+
}
|
|
36
|
+
function dataUrlBytes(url) {
|
|
37
|
+
if (!url.startsWith("data:"))
|
|
38
|
+
return null;
|
|
39
|
+
const comma = url.indexOf(",");
|
|
40
|
+
if (comma === -1)
|
|
41
|
+
return null;
|
|
42
|
+
const payload = url.slice(comma + 1);
|
|
43
|
+
return url.slice(0, comma).endsWith(";base64")
|
|
44
|
+
? Math.floor((payload.length * 3) / 4)
|
|
45
|
+
: payload.length;
|
|
46
|
+
}
|
|
47
|
+
/** The app URL of a stored attachment under this policy. */
|
|
48
|
+
export function attachmentUrl(policy, id) {
|
|
49
|
+
return policy.urlFor ? policy.urlFor(id) : `/api/chat/attachments/${id}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The attachment id a stored file part's URL names, or null when the
|
|
53
|
+
* URL is not one this policy hands out. Absolute and relative forms
|
|
54
|
+
* of the same path both match.
|
|
55
|
+
*/
|
|
56
|
+
export function attachmentIdFromUrl(policy, url) {
|
|
57
|
+
const template = attachmentUrl(policy, ID_PLACEHOLDER);
|
|
58
|
+
const at = template.indexOf(ID_PLACEHOLDER);
|
|
59
|
+
if (at === -1)
|
|
60
|
+
return null;
|
|
61
|
+
const prefix = template.slice(0, at);
|
|
62
|
+
const suffix = template.slice(at + ID_PLACEHOLDER.length);
|
|
63
|
+
let path = url;
|
|
64
|
+
if (/^https?:\/\//i.test(url)) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = new URL(url);
|
|
67
|
+
path = parsed.pathname + parsed.search;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!path.startsWith(prefix) || !path.endsWith(suffix))
|
|
74
|
+
return null;
|
|
75
|
+
const id = path.slice(prefix.length, path.length - suffix.length);
|
|
76
|
+
return id && !id.includes("/") ? id : null;
|
|
77
|
+
}
|
|
78
|
+
function rejectedAttachment(message, policy) {
|
|
79
|
+
for (const part of message.parts) {
|
|
80
|
+
if (part.type !== "file")
|
|
81
|
+
continue;
|
|
82
|
+
if (policy === false)
|
|
83
|
+
return true;
|
|
84
|
+
const file = part;
|
|
85
|
+
if (typeof file.mediaType !== "string" ||
|
|
86
|
+
!policy.accept.includes(file.mediaType)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (typeof file.url !== "string")
|
|
90
|
+
return true;
|
|
91
|
+
if (policy.mode === "stored") {
|
|
92
|
+
// A stored part names an upload; the row is checked by the
|
|
93
|
+
// handler, which knows the tenant. A data URL here bypassed the
|
|
94
|
+
// upload route and its limits, so it is refused.
|
|
95
|
+
if (attachmentIdFromUrl(policy, file.url) === null)
|
|
96
|
+
return true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const bytes = dataUrlBytes(file.url);
|
|
100
|
+
if (bytes !== null && bytes > (policy.maxBytes ?? ATTACHMENT_MAX_BYTES)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
export function parseChatBody(json, options) {
|
|
107
|
+
const invalid = {
|
|
108
|
+
ok: false,
|
|
109
|
+
rejection: { key: "invalidBody" },
|
|
110
|
+
};
|
|
111
|
+
if (!isRecord(json))
|
|
112
|
+
return invalid;
|
|
113
|
+
if (typeof json.id !== "string" || !UUID.test(json.id))
|
|
114
|
+
return invalid;
|
|
115
|
+
if (!Array.isArray(json.messages) || json.messages.length === 0)
|
|
116
|
+
return invalid;
|
|
117
|
+
if (!json.messages.every(isUIMessage))
|
|
118
|
+
return invalid;
|
|
119
|
+
if (json.trigger !== undefined &&
|
|
120
|
+
json.trigger !== "submit-message" &&
|
|
121
|
+
json.trigger !== "regenerate-message") {
|
|
122
|
+
return invalid;
|
|
123
|
+
}
|
|
124
|
+
if (json.messageId !== undefined && typeof json.messageId !== "string") {
|
|
125
|
+
return invalid;
|
|
126
|
+
}
|
|
127
|
+
const messages = json.messages;
|
|
128
|
+
// Every user message, not only the new one: the history is the
|
|
129
|
+
// client's too, and a turn is billed for all of it.
|
|
130
|
+
for (const message of messages) {
|
|
131
|
+
if (message.role !== "user")
|
|
132
|
+
continue;
|
|
133
|
+
const length = extractText(message.parts).length;
|
|
134
|
+
if (length > options.maxMessageLength) {
|
|
135
|
+
return {
|
|
136
|
+
ok: false,
|
|
137
|
+
rejection: {
|
|
138
|
+
key: "messageTooLong",
|
|
139
|
+
params: { max: options.maxMessageLength },
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (rejectedAttachment(message, options.attachments)) {
|
|
144
|
+
return { ok: false, rejection: { key: "attachmentRejected" } };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const last = messages[messages.length - 1];
|
|
148
|
+
if (last.role === "assistant") {
|
|
149
|
+
// A turn that continues the assistant's own message — tool results
|
|
150
|
+
// or approval answers added client-side — names that message. The
|
|
151
|
+
// SDK sends exactly this; anything else is a hand-made body that
|
|
152
|
+
// would make the reply a fresh message with the tool loop lost.
|
|
153
|
+
if (json.trigger === "regenerate-message")
|
|
154
|
+
return invalid;
|
|
155
|
+
if (json.messageId !== last.id)
|
|
156
|
+
return invalid;
|
|
157
|
+
}
|
|
158
|
+
const extra = {};
|
|
159
|
+
for (const [key, value] of Object.entries(json)) {
|
|
160
|
+
if (!SDK_FIELDS.has(key))
|
|
161
|
+
extra[key] = value;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
ok: true,
|
|
165
|
+
body: {
|
|
166
|
+
id: json.id,
|
|
167
|
+
messages,
|
|
168
|
+
trigger: json.trigger,
|
|
169
|
+
messageId: json.messageId,
|
|
170
|
+
extra,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=body.js.map
|
package/dist/body.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body.js","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a chat client needs to know about the transport. Imports nothing at
|
|
3
|
+
* runtime (`./parts` is types and one type guard), so the UI never pulls
|
|
4
|
+
* the server handler, or Drizzle, Stripe and the auth server behind it,
|
|
5
|
+
* into the browser.
|
|
6
|
+
*/
|
|
7
|
+
/** Stable codes the transport answers with. The status is fixed per code. */
|
|
8
|
+
export declare const CHAT_ERROR_CODES: readonly ["BAD_REQUEST", "UNAUTHORIZED", "QUOTA_EXCEEDED", "FEATURE_GATED", "NOT_FOUND", "RATE_LIMITED", "INTERNAL", "BILLING_NOT_CONFIGURED", "MODEL_UNAVAILABLE"];
|
|
9
|
+
export type ChatErrorCode = (typeof CHAT_ERROR_CODES)[number];
|
|
10
|
+
/** The JSON body of every non-2xx answer from the chat transport. */
|
|
11
|
+
export type ChatErrorBody = {
|
|
12
|
+
/** Human-readable, in the caller's locale when the app bound a translator. */
|
|
13
|
+
error: string;
|
|
14
|
+
code: ChatErrorCode;
|
|
15
|
+
/**
|
|
16
|
+
* The entitlement port's own refusal code (`insufficient_credits`,
|
|
17
|
+
* `allowance_depleted`, …) when `code` is `QUOTA_EXCEEDED`,
|
|
18
|
+
* `BILLING_NOT_CONFIGURED` or `MODEL_UNAVAILABLE` (`unknown_model`).
|
|
19
|
+
*/
|
|
20
|
+
reasonCode?: string;
|
|
21
|
+
/** Set on `RATE_LIMITED`. */
|
|
22
|
+
retryAfterSeconds?: number;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* What the chat page opened with: a server-rendered estimate of the
|
|
26
|
+
* caller's credit, so the page can say "you are out" before a message
|
|
27
|
+
* is spent on finding out. Amounts are micros of the billing currency.
|
|
28
|
+
*/
|
|
29
|
+
export type ChatQuotaState = {
|
|
30
|
+
/** False when the next turn would be refused. */
|
|
31
|
+
allowed: boolean;
|
|
32
|
+
/** Why, when the engine refused. */
|
|
33
|
+
reason: string | null;
|
|
34
|
+
/** Typed refusal, for a UI that wants to distinguish them. */
|
|
35
|
+
code: string | null;
|
|
36
|
+
/** Balance left across every pool. */
|
|
37
|
+
remaining: number;
|
|
38
|
+
/** Worst-case cost of one turn. */
|
|
39
|
+
estimated: number;
|
|
40
|
+
/** Where "upgrade" and "top up" should go. */
|
|
41
|
+
upgradeHref: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A model the composer may offer. `id` is a registered model id; the
|
|
45
|
+
* transport refuses any other. `featureKey` gates it by plan.
|
|
46
|
+
*/
|
|
47
|
+
export type ChatModelOption = {
|
|
48
|
+
id: string;
|
|
49
|
+
label: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
/** Plan feature the workspace needs for this model; unset means every plan. */
|
|
52
|
+
featureKey?: string;
|
|
53
|
+
};
|
|
54
|
+
export type { ChatAgentData, ChatArtifactData, ChatAuthorizationData, ChatCompactionData, ChatDataChunk, ChatDataPart, ChatDataPartName, ChatDataParts, ChatMessageMetadata, ChatQuestionData, ChatQuestionOption, ChatStatusData, ChatTaskData, ChatTaskItem, ChatTaskStatus, ChatUIMessage, ChatUIMessageChunk, } from "./parts.js";
|
|
55
|
+
export { isChatDataPart } from "./parts.js";
|
|
56
|
+
/**
|
|
57
|
+
* The transport's error body, from the error `useChat` surfaces.
|
|
58
|
+
*
|
|
59
|
+
* The AI SDK's transport throws an `Error` whose message is the
|
|
60
|
+
* response text, so a JSON refusal arrives as a string. Anything that
|
|
61
|
+
* is not one of ours — a network failure, a proxy's HTML page — is
|
|
62
|
+
* null, and belongs in a generic error strip rather than a banner.
|
|
63
|
+
*/
|
|
64
|
+
export declare function parseChatError(error: unknown): ChatErrorBody | null;
|
|
65
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,qKAUnB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,qEAAqE;AACrE,MAAM,MAAM,aAAa,GAAG;IAC1B,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,aAAa,EACb,kBAAkB,GACnB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAIzC;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAgCnE"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a chat client needs to know about the transport. Imports nothing at
|
|
3
|
+
* runtime (`./parts` is types and one type guard), so the UI never pulls
|
|
4
|
+
* the server handler, or Drizzle, Stripe and the auth server behind it,
|
|
5
|
+
* into the browser.
|
|
6
|
+
*/
|
|
7
|
+
/** Stable codes the transport answers with. The status is fixed per code. */
|
|
8
|
+
export const CHAT_ERROR_CODES = [
|
|
9
|
+
"BAD_REQUEST",
|
|
10
|
+
"UNAUTHORIZED",
|
|
11
|
+
"QUOTA_EXCEEDED",
|
|
12
|
+
"FEATURE_GATED",
|
|
13
|
+
"NOT_FOUND",
|
|
14
|
+
"RATE_LIMITED",
|
|
15
|
+
"INTERNAL",
|
|
16
|
+
"BILLING_NOT_CONFIGURED",
|
|
17
|
+
"MODEL_UNAVAILABLE",
|
|
18
|
+
];
|
|
19
|
+
export { isChatDataPart } from "./parts.js";
|
|
20
|
+
const CODES = new Set(CHAT_ERROR_CODES);
|
|
21
|
+
/**
|
|
22
|
+
* The transport's error body, from the error `useChat` surfaces.
|
|
23
|
+
*
|
|
24
|
+
* The AI SDK's transport throws an `Error` whose message is the
|
|
25
|
+
* response text, so a JSON refusal arrives as a string. Anything that
|
|
26
|
+
* is not one of ours — a network failure, a proxy's HTML page — is
|
|
27
|
+
* null, and belongs in a generic error strip rather than a banner.
|
|
28
|
+
*/
|
|
29
|
+
export function parseChatError(error) {
|
|
30
|
+
const text = error instanceof Error
|
|
31
|
+
? error.message
|
|
32
|
+
: typeof error === "string"
|
|
33
|
+
? error
|
|
34
|
+
: null;
|
|
35
|
+
if (!text)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(text);
|
|
39
|
+
if (typeof parsed !== "object" ||
|
|
40
|
+
parsed === null ||
|
|
41
|
+
typeof parsed.error !== "string" ||
|
|
42
|
+
typeof parsed.code !== "string" ||
|
|
43
|
+
!CODES.has(parsed.code)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
error: parsed.error,
|
|
48
|
+
code: parsed.code,
|
|
49
|
+
...(typeof parsed.reasonCode === "string"
|
|
50
|
+
? { reasonCode: parsed.reasonCode }
|
|
51
|
+
: {}),
|
|
52
|
+
...(typeof parsed.retryAfterSeconds === "number"
|
|
53
|
+
? { retryAfterSeconds: parsed.retryAfterSeconds }
|
|
54
|
+
: {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,aAAa;IACb,cAAc;IACd,gBAAgB;IAChB,eAAe;IACf,WAAW;IACX,cAAc;IACd,UAAU;IACV,wBAAwB;IACxB,mBAAmB;CACX,CAAC;AAsEX,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,KAAK,GAAwB,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,MAAM,IAAI,GACR,KAAK,YAAY,KAAK;QACpB,CAAC,CAAC,KAAK,CAAC,OAAO;QACf,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ;YACzB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,IAAI,CAAC;IACb,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA2B,CAAC;QAC1D,IACE,OAAO,MAAM,KAAK,QAAQ;YAC1B,MAAM,KAAK,IAAI;YACf,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAC/B,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EACvB,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,IAAI,EAAE,MAAM,CAAC,IAAqB;YAClC,GAAG,CAAC,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;gBACvC,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE;gBACnC,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,OAAO,MAAM,CAAC,iBAAiB,KAAK,QAAQ;gBAC9C,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,EAAE;gBACjD,CAAC,CAAC,EAAE,CAAC;SACR,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|