@yeaft/webchat-agent 1.0.592 → 1.0.593

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.
@@ -1,14 +1,14 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { CONFIG } from '../config.js';
3
- import { agents } from '../context.js';
3
+ import { agents, pendingFiles } from '../context.js';
4
4
  import { forwardToAgent, resolveAgentAccessError, sendToWebClient } from '../ws-utils.js';
5
5
 
6
6
  const FIELDS = Object.freeze({
7
- status: [], open: [], snapshot: [],
8
- send: ['text', 'clientMessageId'], think: ['text', 'clientMessageId'],
7
+ status: [], open: [], snapshot: [], receipt: ['clientMessageId', 'requestHash'],
8
+ send: ['text', 'clientMessageId', 'attachments'], think: ['text', 'clientMessageId', 'attachments'],
9
9
  dream: ['clientMessageId'], cancel: ['episodeId'],
10
10
  messages: ['cursor', 'limit'], traces: ['cursor', 'limit'],
11
- settings: ['autonomyEnabled'],
11
+ settings: ['autonomyEnabled', 'modelCandidates'],
12
12
  });
13
13
 
14
14
  /** Request-only relay: identity is supplied by the authenticated Server, never the browser.
@@ -17,7 +17,7 @@ const FIELDS = Object.freeze({
17
17
  export function createPersonRelay({
18
18
  agentMap = agents, send = sendToWebClient, forward = forwardToAgent,
19
19
  accessError = resolveAgentAccessError, skipAuth = () => CONFIG.skipAuth,
20
- timeoutMs = 30_000,
20
+ timeoutMs = 30_000, uploads = pendingFiles, uploadTtlMs = CONFIG.fileCleanupInterval ?? 600_000, now = Date.now,
21
21
  } = {}) {
22
22
  const pending = new Map();
23
23
  function forget(id) {
@@ -66,6 +66,34 @@ export function createPersonRelay({
66
66
  await reply(client, envelope, { ok: false, error: 'Digital person input is too large' });
67
67
  return true;
68
68
  }
69
+ if (op === 'send' || op === 'think') {
70
+ try {
71
+ if (payload.text != null && (typeof payload.text !== 'string' || Buffer.byteLength(payload.text) > 8192)) throw new Error('Digital person text exceeds 8192 UTF-8 bytes');
72
+ if (Object.hasOwn(source || {}, 'files')) throw new Error('Upload files first; only attachment fileId references are accepted');
73
+ const refs = payload.attachments ?? [];
74
+ if (!Array.isArray(refs) || refs.length > 4) throw new Error('Digital person supports at most 4 attachments');
75
+ const seen = new Set(); let total = 0;
76
+ // Resolve every reference before forwarding anything. Keep uploads until their original TTL:
77
+ // delivery/ack loss, disconnect and explicit retry all reuse exactly the same bytes.
78
+ const files = refs.map(ref => {
79
+ if (!ref || typeof ref !== 'object' || Array.isArray(ref) || Object.keys(ref).length !== 1 ||
80
+ typeof ref.fileId !== 'string' || !ref.fileId || ref.fileId.length > 128 || seen.has(ref.fileId)) throw new Error('Invalid digital person attachment reference');
81
+ seen.add(ref.fileId);
82
+ const file = uploads.get(ref.fileId);
83
+ if (!file || !Number.isFinite(file.uploadedAt) || now() - file.uploadedAt >= uploadTtlMs || file.uploadedAt > now()) throw Object.assign(new Error('Digital person attachment expired; check receipt before re-uploading with the same clientMessageId'), { code: 'attachment_expired' });
84
+ if (!skipAuth() && (!file.userId || file.userId !== client.userId)) throw new Error('Digital person attachment access denied');
85
+ if (!Buffer.isBuffer(file.buffer)) throw new Error('Invalid digital person upload');
86
+ total += file.buffer.length;
87
+ if (file.buffer.length > Math.min(CONFIG.maxFileSize ?? Infinity, 5 * 1024 * 1024) || total > 10 * 1024 * 1024) throw new Error('Digital person attachments exceed 5 MiB per file or 10 MiB total');
88
+ return { name: file.name, mimeType: file.mimeType, data: file.buffer.toString('base64') };
89
+ });
90
+ delete payload.attachments;
91
+ if (files.length) payload.files = files;
92
+ } catch (error) {
93
+ await reply(client, envelope, { ok: false, errorCode: error.code === 'attachment_expired' ? 'attachment_expired' : 'invalid_request', error: error.message });
94
+ return true;
95
+ }
96
+ }
69
97
  const relayId = randomUUID();
70
98
  const row = { client, agent, ownerId, agentId, envelope };
71
99
  row.timer = setTimeout(() => {
@@ -98,7 +126,7 @@ export function createPersonRelay({
98
126
  await reply(row.client, row.envelope, msg.ok === true
99
127
  ? { ok: true, data: msg.data }
100
128
  : { ok: false,
101
- errorCode: ['outcome_unknown', 'invalid_request', 'busy', 'unsupported', 'not_configured', 'not_open', 'stale', 'idempotency_conflict'].includes(msg.errorCode) ? msg.errorCode : 'requestFailed',
129
+ errorCode: ['outcome_unknown', 'invalid_request', 'busy', 'unsupported', 'not_configured', 'not_open', 'stale', 'idempotency_conflict', 'attachment_expired', 'invalid_attachment', 'unsupported_attachment', 'attachment_limit', 'image_model', 'model_selection'].includes(msg.errorCode) ? msg.errorCode : 'requestFailed',
102
130
  error: typeof msg.error === 'string' ? msg.error.slice(0, 500) : 'Digital person request failed' });
103
131
  return true;
104
132
  },
@@ -1 +1 @@
1
- {"version":"1.0.592"}
1
+ {"version":"1.0.593"}