@myagentroam/agent 0.9.65 → 0.9.66
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/model/openai-responses.js +26 -5
- package/dist/prompts/output.d.ts +1 -0
- package/dist/prompts/output.js +1 -0
- package/dist/runtime/context-gc-runtime.js +47 -10
- package/dist/runtime/context-gc.d.ts +8 -1
- package/dist/runtime/context-gc.js +170 -16
- package/dist/runtime/context-projection.js +3 -1
- package/dist/sdk/agent.js +111 -39
- package/dist/sdk/types.d.ts +1 -0
- package/dist/session/context-gc-checkpoint.d.ts +9 -1
- package/dist/session/context-gc-checkpoint.js +130 -12
- package/dist/session/context-gc-replacements.d.ts +6 -3
- package/dist/session/context-gc-replacements.js +75 -15
- package/dist/session/context-gc-subagent.d.ts +12 -0
- package/dist/session/context-gc-subagent.js +79 -7
- package/dist/session/jsonl-store.d.ts +15 -0
- package/dist/session/jsonl-store.js +15 -0
- package/dist/subagent/scheduler.d.ts +14 -1
- package/dist/subagent/scheduler.js +46 -3
- package/dist/subagent/session-controller.d.ts +4 -1
- package/dist/subagent/session-controller.js +118 -22
- package/dist/tools/agent-wait.js +12 -8
- package/dist/tools/execution-limits.d.ts +1 -0
- package/dist/tools/execution-limits.js +1 -0
- package/dist/tools/view-image.js +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ export class SubagentSessionController {
|
|
|
8
8
|
limits;
|
|
9
9
|
#agents = new Map();
|
|
10
10
|
#queue = [];
|
|
11
|
+
#completionWaiters = new Set();
|
|
11
12
|
#running = 0;
|
|
12
13
|
#disposed = false;
|
|
13
14
|
#createdInExecution = 0;
|
|
@@ -20,7 +21,8 @@ export class SubagentSessionController {
|
|
|
20
21
|
}
|
|
21
22
|
static async open(input) {
|
|
22
23
|
const controller = new SubagentSessionController(input.parentSessionId, input.rootSessionId, input.backend, input.limits);
|
|
23
|
-
|
|
24
|
+
const sessions = await input.backend.listChildren(input.parentSessionId);
|
|
25
|
+
for (const session of sessions)
|
|
24
26
|
controller.#agents.set(session.id, restoreAgentState(session));
|
|
25
27
|
return controller;
|
|
26
28
|
}
|
|
@@ -130,9 +132,27 @@ export class SubagentSessionController {
|
|
|
130
132
|
if (waitMs !== undefined &&
|
|
131
133
|
state.turn &&
|
|
132
134
|
(state.status === 'queued' || state.status === 'running'))
|
|
133
|
-
await waitForCompletion(state
|
|
135
|
+
await this.#waitForCompletion(state, waitMs, options.signal);
|
|
134
136
|
return this.#snapshot(state);
|
|
135
137
|
}
|
|
138
|
+
async outputAny(options = {}) {
|
|
139
|
+
options.signal?.throwIfAborted();
|
|
140
|
+
validateOutputOptions(options);
|
|
141
|
+
const states = [...this.#agents.values()];
|
|
142
|
+
if (states.length === 0)
|
|
143
|
+
throw new MarAgentError('MAR_AGENT_SUBAGENT_NOT_FOUND', 'Subagent was not found.');
|
|
144
|
+
const live = states.filter((state) => state.status === 'queued' || state.status === 'running');
|
|
145
|
+
if (live.length === 0 || options.waitMs === 0)
|
|
146
|
+
return this.#snapshot(live.at(-1) ?? states.at(-1));
|
|
147
|
+
const waitMs = options.waitMs === undefined
|
|
148
|
+
? undefined
|
|
149
|
+
: Math.max(options.waitMs, TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs);
|
|
150
|
+
if (waitMs !== undefined) {
|
|
151
|
+
await this.#waitForAnyCompletion(live, waitMs, options.signal);
|
|
152
|
+
}
|
|
153
|
+
const completed = live.find((state) => state.status !== 'queued' && state.status !== 'running');
|
|
154
|
+
return this.#snapshot(completed ?? live.at(-1));
|
|
155
|
+
}
|
|
136
156
|
async message(agentId, message, options = {}) {
|
|
137
157
|
const execution = this.#requireExecution();
|
|
138
158
|
if (typeof message !== 'string' ||
|
|
@@ -211,6 +231,7 @@ export class SubagentSessionController {
|
|
|
211
231
|
state.terminalPending = true;
|
|
212
232
|
await this.#publishTerminal(state);
|
|
213
233
|
state.turn.resolve();
|
|
234
|
+
this.#notifyCompletion(state);
|
|
214
235
|
delete state.turn;
|
|
215
236
|
this.#drain();
|
|
216
237
|
}
|
|
@@ -241,6 +262,7 @@ export class SubagentSessionController {
|
|
|
241
262
|
state.status = 'cancelled';
|
|
242
263
|
this.#closeReceipt(state);
|
|
243
264
|
state.turn?.resolve();
|
|
265
|
+
this.#notifyCompletion(state);
|
|
244
266
|
delete state.turn;
|
|
245
267
|
}
|
|
246
268
|
for (const state of this.#agents.values()) {
|
|
@@ -347,6 +369,7 @@ export class SubagentSessionController {
|
|
|
347
369
|
this.#closeReceipt(state);
|
|
348
370
|
this.#running--;
|
|
349
371
|
turn.resolve();
|
|
372
|
+
this.#notifyCompletion(state);
|
|
350
373
|
if (state.turn === turn)
|
|
351
374
|
delete state.turn;
|
|
352
375
|
this.#drain();
|
|
@@ -359,6 +382,7 @@ export class SubagentSessionController {
|
|
|
359
382
|
delete state.turn;
|
|
360
383
|
this.#running--;
|
|
361
384
|
turn?.resolve();
|
|
385
|
+
this.#notifyCompletion(state);
|
|
362
386
|
await this.#publishTerminal(state);
|
|
363
387
|
this.#drain();
|
|
364
388
|
}
|
|
@@ -369,6 +393,16 @@ export class SubagentSessionController {
|
|
|
369
393
|
state.errorMessage = diagnostic.message;
|
|
370
394
|
state.terminalPending = true;
|
|
371
395
|
}
|
|
396
|
+
#waitForAnyCompletion(candidates, waitMs, signal) {
|
|
397
|
+
return waitForCompletionNotification(this.#completionWaiters, (state) => candidates.includes(state), waitMs, signal);
|
|
398
|
+
}
|
|
399
|
+
#waitForCompletion(state, waitMs, signal) {
|
|
400
|
+
return waitForCompletionNotification(this.#completionWaiters, (completed) => completed === state, waitMs, signal);
|
|
401
|
+
}
|
|
402
|
+
#notifyCompletion(state) {
|
|
403
|
+
for (const waiter of this.#completionWaiters)
|
|
404
|
+
waiter(state);
|
|
405
|
+
}
|
|
372
406
|
#closeReceipt(state) {
|
|
373
407
|
if (state.receipt?.deliveryStatus === 'accepted' && state.receipt.turnId === state.turn?.turnId)
|
|
374
408
|
state.receipt.deliveryStatus = state.status === 'cancelled' ? 'cancelled' : 'failed';
|
|
@@ -404,6 +438,15 @@ export class SubagentSessionController {
|
|
|
404
438
|
return this.#execution;
|
|
405
439
|
}
|
|
406
440
|
#snapshot(state) {
|
|
441
|
+
const terminal = state.status === 'completed' ||
|
|
442
|
+
state.status === 'failed' ||
|
|
443
|
+
state.status === 'cancelled' ||
|
|
444
|
+
state.status === 'interrupted';
|
|
445
|
+
const latestMessage = state.latestMessage === undefined
|
|
446
|
+
? undefined
|
|
447
|
+
: terminal
|
|
448
|
+
? { ...state.latestMessage }
|
|
449
|
+
: compactRunningLatestMessage(state.latestMessage);
|
|
407
450
|
return {
|
|
408
451
|
agentId: state.agentId,
|
|
409
452
|
description: state.description,
|
|
@@ -412,11 +455,18 @@ export class SubagentSessionController {
|
|
|
412
455
|
...(state.modelId === undefined ? {} : { modelId: state.modelId }),
|
|
413
456
|
...(state.modelName === undefined ? {} : { modelName: state.modelName }),
|
|
414
457
|
...(state.reasoningEffort === undefined ? {} : { reasoningEffort: state.reasoningEffort }),
|
|
415
|
-
...(
|
|
458
|
+
...(latestMessage === undefined ? {} : { latestMessage }),
|
|
459
|
+
...(state.latestActivity === undefined
|
|
460
|
+
? {}
|
|
461
|
+
: { latestActivity: { ...state.latestActivity } }),
|
|
416
462
|
...(state.summary === undefined ? {} : { summary: state.summary }),
|
|
417
|
-
...(state.changedFiles.length === 0
|
|
418
|
-
|
|
419
|
-
|
|
463
|
+
...(!terminal || state.changedFiles.length === 0
|
|
464
|
+
? {}
|
|
465
|
+
: { changedFiles: [...state.changedFiles] }),
|
|
466
|
+
...(!terminal || state.verification.length === 0
|
|
467
|
+
? {}
|
|
468
|
+
: { verification: [...state.verification] }),
|
|
469
|
+
...(!terminal || state.evidence.length === 0
|
|
420
470
|
? {}
|
|
421
471
|
: { evidence: state.evidence.map((item) => ({ ...item })) }),
|
|
422
472
|
...(state.errorCode === undefined ? {} : { errorCode: state.errorCode }),
|
|
@@ -435,6 +485,16 @@ export class SubagentSessionController {
|
|
|
435
485
|
}
|
|
436
486
|
}
|
|
437
487
|
}
|
|
488
|
+
function compactRunningLatestMessage(message) {
|
|
489
|
+
if (message.phase !== 'commentary')
|
|
490
|
+
return { ...message };
|
|
491
|
+
const text = truncateCharacters(message.text, TOOL_EXECUTION_LIMITS.subagentRunningLatestMessageMaxCharacters);
|
|
492
|
+
return {
|
|
493
|
+
...message,
|
|
494
|
+
text,
|
|
495
|
+
truncated: message.truncated === true || text !== message.text
|
|
496
|
+
};
|
|
497
|
+
}
|
|
438
498
|
function validateTaskInput(input) {
|
|
439
499
|
if (input.parentSubagentId ||
|
|
440
500
|
!input.description.trim() ||
|
|
@@ -486,10 +546,6 @@ function restoreAgentState(session) {
|
|
|
486
546
|
const header = session.records.findLast((record) => record.type === 'execution.header' && isRecord(record.payload))?.payload;
|
|
487
547
|
const terminal = currentRecords.findLast((record) => ['execution.completed', 'execution.failed', 'execution.cancelled'].includes(eventType(record) ?? ''));
|
|
488
548
|
const terminalType = eventType(terminal);
|
|
489
|
-
const latestMessageRecord = session.records.findLast((record) => eventType(record) === 'message.completed');
|
|
490
|
-
const latestMessage = latestMessageRecord && isRecord(latestMessageRecord.payload)
|
|
491
|
-
? latestMessageFromPayload(latestMessageRecord.payload)
|
|
492
|
-
: undefined;
|
|
493
549
|
const state = {
|
|
494
550
|
agentId: session.id,
|
|
495
551
|
description: session.source.description,
|
|
@@ -515,36 +571,64 @@ function restoreAgentState(session) {
|
|
|
515
571
|
...(isReasoningEffort(header?.reasoningEffort)
|
|
516
572
|
? { reasoningEffort: header.reasoningEffort }
|
|
517
573
|
: {}),
|
|
518
|
-
...(latestMessage === undefined ? {} : { latestMessage }),
|
|
519
574
|
changedFiles: [],
|
|
520
575
|
verification: [],
|
|
521
576
|
evidence: [],
|
|
522
577
|
terminalPending: false
|
|
523
578
|
};
|
|
524
|
-
|
|
525
|
-
|
|
579
|
+
for (const record of currentRecords) {
|
|
580
|
+
if (!isRecord(record.payload) ||
|
|
581
|
+
!['message.completed', 'tool.started', 'tool.completed', 'tool.failed'].includes(eventType(record) ?? ''))
|
|
582
|
+
continue;
|
|
583
|
+
const event = record.payload;
|
|
584
|
+
observeAgentEvent(state, event);
|
|
585
|
+
}
|
|
586
|
+
if (state.status === 'completed' && state.latestMessage?.phase === 'final_answer')
|
|
587
|
+
state.summary = truncateUtf8(state.latestMessage.text, TOOL_EXECUTION_LIMITS.subagentSummaryMaxBytes);
|
|
526
588
|
if (state.status === 'failed' && terminal && isRecord(terminal.payload)) {
|
|
527
589
|
if (typeof terminal.payload.code === 'string')
|
|
528
590
|
state.errorCode = terminal.payload.code;
|
|
529
591
|
if (typeof terminal.payload.message === 'string')
|
|
530
592
|
state.errorMessage = terminal.payload.message;
|
|
531
593
|
}
|
|
532
|
-
for (const record of currentRecords) {
|
|
533
|
-
if (!isRecord(record.payload) || eventType(record) !== 'tool.completed')
|
|
534
|
-
continue;
|
|
535
|
-
const event = record.payload;
|
|
536
|
-
if (event.type === 'tool.completed')
|
|
537
|
-
observeAgentEvent(state, event);
|
|
538
|
-
}
|
|
539
594
|
return state;
|
|
540
595
|
}
|
|
541
596
|
function observeAgentEvent(state, event) {
|
|
542
597
|
if (event.type === 'message.completed') {
|
|
543
598
|
state.latestMessage = latestMessageFromEvent(event);
|
|
599
|
+
state.latestActivity = {
|
|
600
|
+
kind: 'message',
|
|
601
|
+
timestamp: event.timestamp,
|
|
602
|
+
phase: event.phase
|
|
603
|
+
};
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (event.type === 'tool.started') {
|
|
607
|
+
state.latestActivity = {
|
|
608
|
+
kind: 'tool',
|
|
609
|
+
timestamp: event.timestamp,
|
|
610
|
+
toolName: event.toolName,
|
|
611
|
+
status: 'started'
|
|
612
|
+
};
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
if (event.type === 'tool.failed') {
|
|
616
|
+
state.latestActivity = {
|
|
617
|
+
kind: 'tool',
|
|
618
|
+
timestamp: event.timestamp,
|
|
619
|
+
toolName: event.toolName,
|
|
620
|
+
status: 'failed'
|
|
621
|
+
};
|
|
544
622
|
return;
|
|
545
623
|
}
|
|
546
624
|
if (event.type !== 'tool.completed')
|
|
547
625
|
return;
|
|
626
|
+
state.latestActivity = {
|
|
627
|
+
kind: 'tool',
|
|
628
|
+
timestamp: event.timestamp,
|
|
629
|
+
toolName: event.toolName,
|
|
630
|
+
status: 'completed'
|
|
631
|
+
};
|
|
548
632
|
if (event.toolName === 'apply_patch' && isRecord(event.data)) {
|
|
549
633
|
const data = event.data;
|
|
550
634
|
const files = Array.isArray(data.files) ? data.files : [];
|
|
@@ -643,7 +727,7 @@ function truncateCharacters(value, maximumCharacters) {
|
|
|
643
727
|
? value
|
|
644
728
|
: characters.slice(0, maximumCharacters).join('');
|
|
645
729
|
}
|
|
646
|
-
function
|
|
730
|
+
function waitForCompletionNotification(waiters, accepts, waitMs, signal) {
|
|
647
731
|
return new Promise((resolve, reject) => {
|
|
648
732
|
let settled = false;
|
|
649
733
|
const finish = (error) => {
|
|
@@ -652,20 +736,32 @@ function waitForCompletion(completion, waitMs, signal) {
|
|
|
652
736
|
settled = true;
|
|
653
737
|
clearTimeout(timer);
|
|
654
738
|
signal?.removeEventListener('abort', abort);
|
|
739
|
+
waiters.delete(notify);
|
|
655
740
|
if (error === undefined)
|
|
656
741
|
resolve();
|
|
657
742
|
else
|
|
658
743
|
reject(error);
|
|
659
744
|
};
|
|
745
|
+
const notify = (value) => {
|
|
746
|
+
if (accepts(value))
|
|
747
|
+
finish();
|
|
748
|
+
};
|
|
660
749
|
const abort = () => finish(signal?.reason ?? new MarAgentError('MAR_AGENT_EXECUTION_CANCELLED', 'Execution cancelled.'));
|
|
661
750
|
const timer = setTimeout(finish, waitMs);
|
|
662
751
|
timer.unref();
|
|
752
|
+
waiters.add(notify);
|
|
663
753
|
signal?.addEventListener('abort', abort, { once: true });
|
|
664
|
-
void completion.then(() => finish());
|
|
665
754
|
if (signal?.aborted)
|
|
666
755
|
abort();
|
|
667
756
|
});
|
|
668
757
|
}
|
|
758
|
+
function validateOutputOptions(options) {
|
|
759
|
+
if (options.waitMs !== undefined &&
|
|
760
|
+
(!Number.isInteger(options.waitMs) ||
|
|
761
|
+
options.waitMs < TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs ||
|
|
762
|
+
options.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs))
|
|
763
|
+
throw new MarAgentError('MAR_AGENT_TOOL_INPUT_INVALID', 'Task wait is invalid.');
|
|
764
|
+
}
|
|
669
765
|
function linkAbort(signal, abort) {
|
|
670
766
|
const listener = () => abort();
|
|
671
767
|
signal.addEventListener('abort', listener, { once: true });
|
package/dist/tools/agent-wait.js
CHANGED
|
@@ -3,7 +3,7 @@ import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
|
|
|
3
3
|
export const agentWaitToolDefinition = {
|
|
4
4
|
name: 'agent_wait',
|
|
5
5
|
parallelSafety: 'serial',
|
|
6
|
-
description: 'Read or wait a bounded time for a child agent result
|
|
6
|
+
description: 'Read or wait a bounded time for a child agent result. Omit agentId to wait for whichever live child returns first, matching Codex-style untargeted waiting and avoiding UUID transcription; provide the exact agentId returned by agent_start only when observing a specific child. If no child is live, untargeted waiting reads the most recently created child. For an exec processId, use exec with action:"poll" instead. Omitting waitMs waits up to 30 seconds by default; waitMs=0 returns the current snapshot immediately. Positive waits range up to 5 minutes, and positive values below 10 seconds are raised to 10 seconds. Every result includes the real agentId and description. latestMessage is the most recent public assistant commentary/final message, never hidden reasoning or a tool result; latestActivity identifies whether the latest observed activity was a public message or a bounded tool started/completed/failed status. To request a fresh textual progress report, send an append agent_message and then wait. A queued or running result is not completion; wait again only when useful, or cancel if no longer needed. Terminal results include bounded summary, changedFiles, verification, and read evidence with path/line ranges; use that evidence to avoid repeating the full exploration, while independently checking consequential edits and claims. Avoid busy polling.',
|
|
7
7
|
inputSchema: {
|
|
8
8
|
type: 'object',
|
|
9
9
|
properties: {
|
|
@@ -16,18 +16,21 @@ export const agentWaitToolDefinition = {
|
|
|
16
16
|
description: `Timeout in milliseconds. Defaults to ${TOOL_EXECUTION_LIMITS.agentWaitDefaultWaitMs}, min ${TOOL_EXECUTION_LIMITS.agentWaitMinWaitMs}, max ${TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs}. Zero returns immediately. Positive values below ${TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs} are raised to ${TOOL_EXECUTION_LIMITS.agentWaitWaitFloorMs}.`
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
required: ['agentId'],
|
|
20
19
|
additionalProperties: false
|
|
21
20
|
}
|
|
22
21
|
};
|
|
23
22
|
export async function executeAgentWait(arguments_, context) {
|
|
24
23
|
const value = parseAgentWaitInput(arguments_);
|
|
25
|
-
|
|
24
|
+
const subagents = context.subagents;
|
|
25
|
+
if (!subagents)
|
|
26
26
|
throw new MarAgentError('MAR_AGENT_SUBAGENT_RECURSION_DISABLED', 'Subagents cannot recurse.');
|
|
27
|
-
const
|
|
27
|
+
const options = {
|
|
28
28
|
waitMs: value.waitMs ?? TOOL_EXECUTION_LIMITS.agentWaitDefaultWaitMs,
|
|
29
29
|
signal: context.signal
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
|
+
const task = value.agentId === undefined
|
|
32
|
+
? await subagents.outputAny(options)
|
|
33
|
+
: await subagents.output(value.agentId, options);
|
|
31
34
|
return { content: JSON.stringify(task), data: task };
|
|
32
35
|
}
|
|
33
36
|
function parseAgentWaitInput(value) {
|
|
@@ -35,8 +38,9 @@ function parseAgentWaitInput(value) {
|
|
|
35
38
|
return invalidAgentWait();
|
|
36
39
|
const input = value;
|
|
37
40
|
if (Object.keys(input).some((key) => !['agentId', 'waitMs'].includes(key)) ||
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
(input.agentId !== undefined &&
|
|
42
|
+
input.agentId !== null &&
|
|
43
|
+
(typeof input.agentId !== 'string' || input.agentId.trim().length === 0)) ||
|
|
40
44
|
(input.waitMs !== undefined &&
|
|
41
45
|
input.waitMs !== null &&
|
|
42
46
|
(!Number.isInteger(input.waitMs) ||
|
|
@@ -44,7 +48,7 @@ function parseAgentWaitInput(value) {
|
|
|
44
48
|
input.waitMs > TOOL_EXECUTION_LIMITS.agentWaitMaxWaitMs)))
|
|
45
49
|
return invalidAgentWait();
|
|
46
50
|
return {
|
|
47
|
-
agentId: input.agentId,
|
|
51
|
+
...(typeof input.agentId === 'string' ? { agentId: input.agentId.trim() } : {}),
|
|
48
52
|
...(typeof input.waitMs === 'number' ? { waitMs: input.waitMs } : {})
|
|
49
53
|
};
|
|
50
54
|
}
|
|
@@ -34,6 +34,7 @@ export declare const TOOL_EXECUTION_LIMITS: Readonly<{
|
|
|
34
34
|
subagentMessageMaxBytes: 131072;
|
|
35
35
|
subagentReasoningEffortMaxCharacters: 64;
|
|
36
36
|
subagentSummaryMaxBytes: 16384;
|
|
37
|
+
subagentRunningLatestMessageMaxCharacters: 2000;
|
|
37
38
|
subagentEvidenceMaxItems: 64;
|
|
38
39
|
subagentVerificationMaxItems: 16;
|
|
39
40
|
subagentChangedFilesMaxItems: 200;
|
|
@@ -34,6 +34,7 @@ export const TOOL_EXECUTION_LIMITS = Object.freeze({
|
|
|
34
34
|
subagentMessageMaxBytes: 131_072,
|
|
35
35
|
subagentReasoningEffortMaxCharacters: 64,
|
|
36
36
|
subagentSummaryMaxBytes: 16_384,
|
|
37
|
+
subagentRunningLatestMessageMaxCharacters: 2_000,
|
|
37
38
|
subagentEvidenceMaxItems: 64,
|
|
38
39
|
subagentVerificationMaxItems: 16,
|
|
39
40
|
subagentChangedFilesMaxItems: 200,
|
package/dist/tools/view-image.js
CHANGED
|
@@ -44,7 +44,7 @@ export class ViewImageTool {
|
|
|
44
44
|
sha256: createHash('sha256').update(data).digest('hex')
|
|
45
45
|
};
|
|
46
46
|
return {
|
|
47
|
-
content: `Viewed image ${input.path} (${mimeType}, ${data.byteLength} bytes
|
|
47
|
+
content: `Viewed image ${input.path} (${mimeType}, ${data.byteLength} bytes).`,
|
|
48
48
|
data: metadata,
|
|
49
49
|
images: [{ mimeType, dataBase64: data.toString('base64') }]
|
|
50
50
|
};
|