@nextclaw/ncp-react 0.3.4 → 0.3.6

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NcpAgentConversationSnapshot, NcpMessage, NcpRequestEnvelope, NcpAgentClientEndpoint } from '@nextclaw/ncp';
1
+ import { NcpAgentConversationSnapshot, NcpMessage, NcpRequestEnvelope, NcpAgentClientEndpoint, NcpMessagePart } from '@nextclaw/ncp';
2
2
 
3
3
  type NcpAgentSendInput = string | NcpRequestEnvelope;
4
4
  type UseNcpAgentResult = {
@@ -34,13 +34,17 @@ declare function useNcpAgent(sessionId: string, client: NcpAgentClientEndpoint):
34
34
 
35
35
  declare const DEFAULT_NCP_IMAGE_ATTACHMENT_ACCEPT = "image/png,image/jpeg,image/webp,image/gif";
36
36
  declare const DEFAULT_NCP_IMAGE_ATTACHMENT_MIME_TYPES: readonly ["image/png", "image/jpeg", "image/webp", "image/gif"];
37
+ declare const DEFAULT_NCP_ATTACHMENT_MAX_BYTES: number;
37
38
  declare const DEFAULT_NCP_IMAGE_ATTACHMENT_MAX_BYTES: number;
39
+ declare const DEFAULT_NCP_FALLBACK_ATTACHMENT_MIME_TYPE = "application/octet-stream";
38
40
  type NcpDraftAttachment = {
39
41
  id: string;
40
42
  name: string;
41
43
  mimeType: string;
42
- contentBase64: string;
43
44
  sizeBytes: number;
45
+ attachmentUri?: string;
46
+ url?: string;
47
+ contentBase64?: string;
44
48
  };
45
49
  type NcpRejectedAttachment = {
46
50
  fileName: string;
@@ -52,6 +56,9 @@ type ReadNcpDraftAttachmentsOptions = {
52
56
  acceptedMimeTypes?: readonly string[];
53
57
  maxBytes?: number;
54
58
  };
59
+ type UploadNcpDraftAttachmentsOptions = ReadNcpDraftAttachmentsOptions & {
60
+ uploadBatch: (files: File[]) => Promise<NcpDraftAttachment[]>;
61
+ };
55
62
  type ReadNcpDraftAttachmentsResult = {
56
63
  attachments: NcpDraftAttachment[];
57
64
  rejected: NcpRejectedAttachment[];
@@ -61,10 +68,12 @@ declare function buildNcpRequestEnvelope(params: {
61
68
  sessionId: string;
62
69
  text?: string;
63
70
  attachments?: readonly NcpDraftAttachment[];
71
+ parts?: readonly NcpMessagePart[];
64
72
  metadata?: Record<string, unknown>;
65
73
  messageId?: string;
66
74
  timestamp?: string;
67
75
  }): NcpRequestEnvelope | null;
68
76
  declare function readFilesAsNcpDraftAttachments(files: Iterable<File>, options?: ReadNcpDraftAttachmentsOptions): Promise<ReadNcpDraftAttachmentsResult>;
77
+ declare function uploadFilesAsNcpDraftAttachments(files: Iterable<File>, options: UploadNcpDraftAttachmentsOptions): Promise<ReadNcpDraftAttachmentsResult>;
69
78
 
70
- export { DEFAULT_NCP_IMAGE_ATTACHMENT_ACCEPT, DEFAULT_NCP_IMAGE_ATTACHMENT_MAX_BYTES, DEFAULT_NCP_IMAGE_ATTACHMENT_MIME_TYPES, type NcpConversationSeed, type NcpConversationSeedLoader, type NcpDraftAttachment, type NcpRejectedAttachment, type ReadNcpDraftAttachmentsOptions, type ReadNcpDraftAttachmentsResult, type UseHydratedNcpAgentOptions, type UseHydratedNcpAgentResult, type UseNcpAgentResult, buildNcpImageAttachmentDataUrl, buildNcpRequestEnvelope, readFilesAsNcpDraftAttachments, useHydratedNcpAgent, useNcpAgent };
79
+ export { DEFAULT_NCP_ATTACHMENT_MAX_BYTES, DEFAULT_NCP_FALLBACK_ATTACHMENT_MIME_TYPE, DEFAULT_NCP_IMAGE_ATTACHMENT_ACCEPT, DEFAULT_NCP_IMAGE_ATTACHMENT_MAX_BYTES, DEFAULT_NCP_IMAGE_ATTACHMENT_MIME_TYPES, type NcpConversationSeed, type NcpConversationSeedLoader, type NcpDraftAttachment, type NcpRejectedAttachment, type ReadNcpDraftAttachmentsOptions, type ReadNcpDraftAttachmentsResult, type UploadNcpDraftAttachmentsOptions, type UseHydratedNcpAgentOptions, type UseHydratedNcpAgentResult, type UseNcpAgentResult, buildNcpImageAttachmentDataUrl, buildNcpRequestEnvelope, readFilesAsNcpDraftAttachments, uploadFilesAsNcpDraftAttachments, useHydratedNcpAgent, useNcpAgent };
package/dist/index.js CHANGED
@@ -233,7 +233,9 @@ var DEFAULT_NCP_IMAGE_ATTACHMENT_MIME_TYPES = [
233
233
  "image/webp",
234
234
  "image/gif"
235
235
  ];
236
- var DEFAULT_NCP_IMAGE_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
236
+ var DEFAULT_NCP_ATTACHMENT_MAX_BYTES = 10 * 1024 * 1024;
237
+ var DEFAULT_NCP_IMAGE_ATTACHMENT_MAX_BYTES = DEFAULT_NCP_ATTACHMENT_MAX_BYTES;
238
+ var DEFAULT_NCP_FALLBACK_ATTACHMENT_MIME_TYPE = "application/octet-stream";
237
239
  function createAttachmentId() {
238
240
  return `ncp-file-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
239
241
  }
@@ -255,19 +257,57 @@ function toBase64Content(dataUrl) {
255
257
  const commaIndex = dataUrl.indexOf(",");
256
258
  return commaIndex >= 0 ? dataUrl.slice(commaIndex + 1) : dataUrl;
257
259
  }
260
+ function normalizeAttachmentMimeType(file) {
261
+ const mimeType = file.type.trim().toLowerCase();
262
+ return mimeType.length > 0 ? mimeType : DEFAULT_NCP_FALLBACK_ATTACHMENT_MIME_TYPE;
263
+ }
264
+ function validateAttachmentFile(file, options) {
265
+ const acceptedMimeTypes = options.acceptedMimeTypes && options.acceptedMimeTypes.length > 0 ? new Set(options.acceptedMimeTypes.map((mimeType2) => mimeType2.trim().toLowerCase())) : null;
266
+ const maxBytes = options.maxBytes ?? DEFAULT_NCP_ATTACHMENT_MAX_BYTES;
267
+ const mimeType = normalizeAttachmentMimeType(file);
268
+ if (acceptedMimeTypes && !acceptedMimeTypes.has(mimeType)) {
269
+ return {
270
+ ok: false,
271
+ rejected: {
272
+ fileName: file.name,
273
+ mimeType,
274
+ sizeBytes: file.size,
275
+ reason: "unsupported-type"
276
+ }
277
+ };
278
+ }
279
+ if (file.size > maxBytes) {
280
+ return {
281
+ ok: false,
282
+ rejected: {
283
+ fileName: file.name,
284
+ mimeType,
285
+ sizeBytes: file.size,
286
+ reason: "too-large"
287
+ }
288
+ };
289
+ }
290
+ return { ok: true, mimeType };
291
+ }
258
292
  function buildNcpImageAttachmentDataUrl(attachment) {
293
+ if (attachment.url?.trim()) {
294
+ return attachment.url.trim();
295
+ }
296
+ if (!attachment.contentBase64?.trim()) {
297
+ throw new Error(`Attachment ${attachment.name} does not have image content.`);
298
+ }
259
299
  return `data:${attachment.mimeType};base64,${attachment.contentBase64}`;
260
300
  }
261
301
  function buildNcpRequestEnvelope(params) {
262
- const trimmedText = params.text?.trim() ?? "";
263
- const attachments = params.attachments ?? [];
264
- const parts = [
265
- ...trimmedText ? [{ type: "text", text: trimmedText }] : [],
266
- ...attachments.map((attachment) => ({
302
+ const parts = params.parts && params.parts.length > 0 ? params.parts.map((part) => structuredClone(part)) : [
303
+ ...params.text?.trim() ?? "" ? [{ type: "text", text: params.text.trim() }] : [],
304
+ ...(params.attachments ?? []).map((attachment) => ({
267
305
  type: "file",
268
306
  name: attachment.name,
269
307
  mimeType: attachment.mimeType,
270
- contentBase64: attachment.contentBase64,
308
+ ...attachment.attachmentUri?.trim() ? { attachmentUri: attachment.attachmentUri.trim() } : {},
309
+ ...attachment.url?.trim() ? { url: attachment.url.trim() } : {},
310
+ ...attachment.contentBase64?.trim() ? { contentBase64: attachment.contentBase64.trim() } : {},
271
311
  sizeBytes: attachment.sizeBytes
272
312
  }))
273
313
  ];
@@ -291,30 +331,12 @@ function buildNcpRequestEnvelope(params) {
291
331
  };
292
332
  }
293
333
  async function readFilesAsNcpDraftAttachments(files, options = {}) {
294
- const acceptedMimeTypes = new Set(
295
- options.acceptedMimeTypes ?? DEFAULT_NCP_IMAGE_ATTACHMENT_MIME_TYPES
296
- );
297
- const maxBytes = options.maxBytes ?? DEFAULT_NCP_IMAGE_ATTACHMENT_MAX_BYTES;
298
334
  const attachments = [];
299
335
  const rejected = [];
300
336
  for (const file of files) {
301
- const mimeType = file.type.trim().toLowerCase();
302
- if (!acceptedMimeTypes.has(mimeType)) {
303
- rejected.push({
304
- fileName: file.name,
305
- mimeType,
306
- sizeBytes: file.size,
307
- reason: "unsupported-type"
308
- });
309
- continue;
310
- }
311
- if (file.size > maxBytes) {
312
- rejected.push({
313
- fileName: file.name,
314
- mimeType,
315
- sizeBytes: file.size,
316
- reason: "too-large"
317
- });
337
+ const validation = validateAttachmentFile(file, options);
338
+ if (!validation.ok) {
339
+ rejected.push(validation.rejected);
318
340
  continue;
319
341
  }
320
342
  try {
@@ -322,14 +344,14 @@ async function readFilesAsNcpDraftAttachments(files, options = {}) {
322
344
  attachments.push({
323
345
  id: createAttachmentId(),
324
346
  name: file.name,
325
- mimeType,
347
+ mimeType: validation.mimeType,
326
348
  contentBase64: toBase64Content(dataUrl),
327
349
  sizeBytes: file.size
328
350
  });
329
351
  } catch {
330
352
  rejected.push({
331
353
  fileName: file.name,
332
- mimeType,
354
+ mimeType: validation.mimeType,
333
355
  sizeBytes: file.size,
334
356
  reason: "read-failed"
335
357
  });
@@ -337,13 +359,51 @@ async function readFilesAsNcpDraftAttachments(files, options = {}) {
337
359
  }
338
360
  return { attachments, rejected };
339
361
  }
362
+ async function uploadFilesAsNcpDraftAttachments(files, options) {
363
+ const validFiles = [];
364
+ const rejected = [];
365
+ for (const file of files) {
366
+ const validation = validateAttachmentFile(file, options);
367
+ if (!validation.ok) {
368
+ rejected.push(validation.rejected);
369
+ continue;
370
+ }
371
+ validFiles.push(file);
372
+ }
373
+ if (validFiles.length === 0) {
374
+ return { attachments: [], rejected };
375
+ }
376
+ try {
377
+ const attachments = await options.uploadBatch(validFiles);
378
+ return {
379
+ attachments,
380
+ rejected
381
+ };
382
+ } catch {
383
+ rejected.push(
384
+ ...validFiles.map((file) => ({
385
+ fileName: file.name,
386
+ mimeType: normalizeAttachmentMimeType(file),
387
+ sizeBytes: file.size,
388
+ reason: "read-failed"
389
+ }))
390
+ );
391
+ return {
392
+ attachments: [],
393
+ rejected
394
+ };
395
+ }
396
+ }
340
397
  export {
398
+ DEFAULT_NCP_ATTACHMENT_MAX_BYTES,
399
+ DEFAULT_NCP_FALLBACK_ATTACHMENT_MIME_TYPE,
341
400
  DEFAULT_NCP_IMAGE_ATTACHMENT_ACCEPT,
342
401
  DEFAULT_NCP_IMAGE_ATTACHMENT_MAX_BYTES,
343
402
  DEFAULT_NCP_IMAGE_ATTACHMENT_MIME_TYPES,
344
403
  buildNcpImageAttachmentDataUrl,
345
404
  buildNcpRequestEnvelope,
346
405
  readFilesAsNcpDraftAttachments,
406
+ uploadFilesAsNcpDraftAttachments,
347
407
  useHydratedNcpAgent,
348
408
  useNcpAgent
349
409
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-react",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "private": false,
5
5
  "description": "React bindings for building NCP-based agent applications.",
6
6
  "type": "module",
@@ -15,8 +15,8 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@nextclaw/ncp": "0.3.2",
19
- "@nextclaw/ncp-toolkit": "0.4.2"
18
+ "@nextclaw/ncp-toolkit": "0.4.3",
19
+ "@nextclaw/ncp": "0.3.3"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "react": "^18.0.0 || ^19.0.0"