nexus-agentd 0.1.1 → 0.1.7
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 +22 -0
- package/README.md +376 -185
- package/dist/a2a/runtime.d.ts +32 -0
- package/dist/a2a/runtime.js +382 -0
- package/dist/acp/runtime.d.ts +14 -3
- package/dist/acp/runtime.js +256 -17
- package/dist/agent-instructions.d.ts +11 -0
- package/dist/agent-instructions.js +44 -0
- package/dist/artifact-store.d.ts +41 -0
- package/dist/artifact-store.js +340 -0
- package/dist/auth.d.ts +13 -0
- package/dist/auth.js +77 -0
- package/dist/cli.js +58 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js +284 -53
- package/dist/control-plane.d.ts +92 -0
- package/dist/control-plane.js +646 -0
- package/dist/drivers/hermes.d.ts +2 -0
- package/dist/drivers/hermes.js +10 -0
- package/dist/drivers/index.js +6 -0
- package/dist/events.js +13 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +15 -3
- package/dist/run-store.d.ts +49 -0
- package/dist/run-store.js +291 -0
- package/dist/server.d.ts +3 -2
- package/dist/server.js +913 -41
- package/dist/session-contract.d.ts +16 -4
- package/dist/session.d.ts +74 -8
- package/dist/session.js +433 -38
- package/dist/types.d.ts +204 -6
- package/dist/types.js +2 -1
- package/dist/webui/app.d.ts +1 -0
- package/dist/webui/app.js +1074 -0
- package/dist/webui/icons.d.ts +9 -0
- package/dist/webui/icons.js +10 -0
- package/dist/webui/index.d.ts +3 -0
- package/dist/webui/index.js +32 -0
- package/dist/webui/markup.d.ts +1 -0
- package/dist/webui/markup.js +68 -0
- package/dist/webui/styles.d.ts +1 -0
- package/dist/webui/styles.js +269 -0
- package/dist/workspace-files.d.ts +48 -0
- package/dist/workspace-files.js +269 -0
- package/nexus-agentd.example.json +31 -27
- package/package.json +6 -6
package/dist/acp/runtime.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
2
4
|
import { Readable, Writable } from 'node:stream';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
3
6
|
import * as acp from '@agentclientprotocol/sdk';
|
|
7
|
+
import { buildAgentInstructions, composeInitialAgentPrompt } from '../agent-instructions.js';
|
|
4
8
|
export class AcpProcessRuntime {
|
|
5
9
|
driver;
|
|
6
10
|
sink;
|
|
@@ -10,13 +14,26 @@ export class AcpProcessRuntime {
|
|
|
10
14
|
pendingInput;
|
|
11
15
|
disposed = false;
|
|
12
16
|
prompting = false;
|
|
13
|
-
|
|
17
|
+
maxStderrChunkChars;
|
|
18
|
+
promptTimeoutMs;
|
|
19
|
+
instructions;
|
|
20
|
+
firstPrompt = true;
|
|
21
|
+
promptCapabilities = {};
|
|
22
|
+
inputDirectory;
|
|
23
|
+
constructor(driver, sink, maxStderrChunkChars = 16 * 1024, promptTimeoutMs = 30 * 60 * 1000, customInstructions) {
|
|
14
24
|
this.driver = driver;
|
|
15
25
|
this.sink = sink;
|
|
26
|
+
const limit = Number.isFinite(maxStderrChunkChars)
|
|
27
|
+
? Math.floor(maxStderrChunkChars)
|
|
28
|
+
: 16 * 1024;
|
|
29
|
+
this.maxStderrChunkChars = Math.max(1, Math.min(16 * 1024, limit));
|
|
30
|
+
this.promptTimeoutMs = Math.max(10_000, Math.trunc(promptTimeoutMs));
|
|
31
|
+
this.instructions = buildAgentInstructions(customInstructions);
|
|
16
32
|
}
|
|
17
33
|
async start(workspace) {
|
|
18
34
|
if (this.process)
|
|
19
35
|
throw new Error('ACP runtime is already started');
|
|
36
|
+
this.inputDirectory = path.join(workspace, '.nexus-inputs', this.sink.id);
|
|
20
37
|
const child = this.driver.spawn(workspace);
|
|
21
38
|
this.process = child;
|
|
22
39
|
this.captureStderr(child);
|
|
@@ -47,7 +64,7 @@ export class AcpProcessRuntime {
|
|
|
47
64
|
},
|
|
48
65
|
clientInfo: {
|
|
49
66
|
name: 'nexus-agentd',
|
|
50
|
-
version: '0.1.
|
|
67
|
+
version: '0.1.4'
|
|
51
68
|
}
|
|
52
69
|
});
|
|
53
70
|
if (initialize.protocolVersion !== acp.PROTOCOL_VERSION) {
|
|
@@ -56,6 +73,7 @@ export class AcpProcessRuntime {
|
|
|
56
73
|
text: `ACP negotiated protocol ${initialize.protocolVersion}`
|
|
57
74
|
});
|
|
58
75
|
}
|
|
76
|
+
this.promptCapabilities = initialize.agentCapabilities?.promptCapabilities || {};
|
|
59
77
|
const session = await this.connection.agent.request(acp.methods.agent.session.new, {
|
|
60
78
|
cwd: workspace,
|
|
61
79
|
mcpServers: []
|
|
@@ -63,7 +81,7 @@ export class AcpProcessRuntime {
|
|
|
63
81
|
this.sink.setAcpSessionId(String(session.sessionId));
|
|
64
82
|
this.sink.setState('created');
|
|
65
83
|
}
|
|
66
|
-
async prompt(message) {
|
|
84
|
+
async prompt(message, attachments = []) {
|
|
67
85
|
if (!this.connection)
|
|
68
86
|
throw new Error('ACP runtime is not connected');
|
|
69
87
|
if (this.prompting)
|
|
@@ -72,13 +90,18 @@ export class AcpProcessRuntime {
|
|
|
72
90
|
this.prompting = true;
|
|
73
91
|
this.sink.clearPending();
|
|
74
92
|
this.sink.setState('running');
|
|
93
|
+
const promptMessage = this.firstPrompt
|
|
94
|
+
? composeInitialAgentPrompt(this.instructions, message)
|
|
95
|
+
: message;
|
|
96
|
+
this.firstPrompt = false;
|
|
75
97
|
try {
|
|
76
|
-
const response = await this.connection.agent.request(acp.methods.agent.session.prompt, {
|
|
98
|
+
const response = await withTimeout(this.connection.agent.request(acp.methods.agent.session.prompt, {
|
|
77
99
|
sessionId,
|
|
78
|
-
prompt:
|
|
79
|
-
});
|
|
80
|
-
if (this.sink.state === 'canceled')
|
|
100
|
+
prompt: await this.promptBlocks(promptMessage, attachments)
|
|
101
|
+
}), this.promptTimeoutMs, 'ACP prompt timed out');
|
|
102
|
+
if (this.sink.state === 'canceled' || this.sink.state === 'failed') {
|
|
81
103
|
return;
|
|
104
|
+
}
|
|
82
105
|
if (response.stopReason === 'cancelled') {
|
|
83
106
|
this.sink.setState('canceled');
|
|
84
107
|
}
|
|
@@ -93,12 +116,14 @@ export class AcpProcessRuntime {
|
|
|
93
116
|
if (this.sink.state !== 'canceled') {
|
|
94
117
|
this.sink.setState('failed', error instanceof Error ? error.message : String(error));
|
|
95
118
|
}
|
|
119
|
+
if (error instanceof PromptTimeoutError)
|
|
120
|
+
await this.dispose();
|
|
96
121
|
}
|
|
97
122
|
finally {
|
|
98
123
|
this.prompting = false;
|
|
99
124
|
}
|
|
100
125
|
}
|
|
101
|
-
async respondPending(message) {
|
|
126
|
+
async respondPending(message, _attachments = []) {
|
|
102
127
|
if (this.pendingInput) {
|
|
103
128
|
this.finishInput(message);
|
|
104
129
|
return;
|
|
@@ -168,6 +193,10 @@ export class AcpProcessRuntime {
|
|
|
168
193
|
}
|
|
169
194
|
this.connection?.close();
|
|
170
195
|
this.connection = undefined;
|
|
196
|
+
if (this.inputDirectory) {
|
|
197
|
+
await rm(this.inputDirectory, { recursive: true, force: true }).catch(() => undefined);
|
|
198
|
+
this.inputDirectory = undefined;
|
|
199
|
+
}
|
|
171
200
|
const child = this.process;
|
|
172
201
|
this.process = undefined;
|
|
173
202
|
if (child && child.exitCode === null && child.signalCode === null) {
|
|
@@ -286,11 +315,15 @@ export class AcpProcessRuntime {
|
|
|
286
315
|
content: update.content
|
|
287
316
|
});
|
|
288
317
|
}
|
|
318
|
+
else {
|
|
319
|
+
this.captureArtifact(update.content, `message:${String(update.messageId || 'assistant')}:0`);
|
|
320
|
+
}
|
|
289
321
|
break;
|
|
290
322
|
case 'agent_thought_chunk':
|
|
291
323
|
this.sink.emit('thought_chunk', update);
|
|
292
324
|
break;
|
|
293
325
|
case 'tool_call':
|
|
326
|
+
this.captureToolArtifacts(String(update.toolCallId), update.content, update.title);
|
|
294
327
|
this.sink.emit('tool_call', update);
|
|
295
328
|
if (update.locations?.length) {
|
|
296
329
|
this.sink.emit('file_activity', {
|
|
@@ -300,6 +333,7 @@ export class AcpProcessRuntime {
|
|
|
300
333
|
}
|
|
301
334
|
break;
|
|
302
335
|
case 'tool_call_update':
|
|
336
|
+
this.captureToolArtifacts(String(update.toolCallId), update.content, update.title || update.name);
|
|
303
337
|
this.sink.emit('tool_update', update);
|
|
304
338
|
if (update.locations?.length) {
|
|
305
339
|
this.sink.emit('file_activity', {
|
|
@@ -317,21 +351,57 @@ export class AcpProcessRuntime {
|
|
|
317
351
|
break;
|
|
318
352
|
}
|
|
319
353
|
}
|
|
354
|
+
captureToolArtifacts(toolCallId, content, hint) {
|
|
355
|
+
content?.forEach((item, index) => {
|
|
356
|
+
if (item.type !== 'content')
|
|
357
|
+
return;
|
|
358
|
+
this.captureArtifact(item.content, `tool:${toolCallId}:${index}`, hint || undefined);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
captureArtifact(content, id, hint) {
|
|
362
|
+
const artifact = artifactFromContent(content, id, hint);
|
|
363
|
+
if (artifact)
|
|
364
|
+
this.sink.addArtifact(artifact);
|
|
365
|
+
}
|
|
320
366
|
captureStderr(child) {
|
|
321
367
|
let buffer = '';
|
|
322
|
-
|
|
323
|
-
|
|
368
|
+
let flushed = false;
|
|
369
|
+
const emit = (text) => {
|
|
370
|
+
if (!text)
|
|
371
|
+
return;
|
|
372
|
+
for (let offset = 0; offset < text.length; offset += this.maxStderrChunkChars) {
|
|
373
|
+
this.sink.emit('terminal_output', {
|
|
374
|
+
stream: 'stderr',
|
|
375
|
+
text: text.slice(offset, offset + this.maxStderrChunkChars)
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
const drain = (flush = false) => {
|
|
324
380
|
const lines = buffer.split(/\r?\n/);
|
|
325
381
|
buffer = lines.pop() || '';
|
|
326
|
-
for (const line of lines)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
});
|
|
332
|
-
}
|
|
382
|
+
for (const line of lines)
|
|
383
|
+
emit(line);
|
|
384
|
+
while (buffer.length >= this.maxStderrChunkChars) {
|
|
385
|
+
emit(buffer.slice(0, this.maxStderrChunkChars));
|
|
386
|
+
buffer = buffer.slice(this.maxStderrChunkChars);
|
|
333
387
|
}
|
|
388
|
+
if (flush && buffer) {
|
|
389
|
+
emit(buffer);
|
|
390
|
+
buffer = '';
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const flush = () => {
|
|
394
|
+
if (flushed)
|
|
395
|
+
return;
|
|
396
|
+
flushed = true;
|
|
397
|
+
drain(true);
|
|
398
|
+
};
|
|
399
|
+
child.stderr.on('data', (chunk) => {
|
|
400
|
+
buffer += String(chunk);
|
|
401
|
+
drain();
|
|
334
402
|
});
|
|
403
|
+
child.stderr.once('end', flush);
|
|
404
|
+
child.stderr.once('close', flush);
|
|
335
405
|
}
|
|
336
406
|
onProcessFailure(error) {
|
|
337
407
|
if (this.disposed)
|
|
@@ -345,6 +415,175 @@ export class AcpProcessRuntime {
|
|
|
345
415
|
}
|
|
346
416
|
return value;
|
|
347
417
|
}
|
|
418
|
+
async promptBlocks(message, attachments) {
|
|
419
|
+
const blocks = [{ type: 'text', text: message }];
|
|
420
|
+
for (const attachment of attachments) {
|
|
421
|
+
const mediaType = attachment.mediaType || 'application/octet-stream';
|
|
422
|
+
const data = attachment.bytes.toString('base64');
|
|
423
|
+
if (mediaType.startsWith('image/') && this.promptCapabilities.image) {
|
|
424
|
+
blocks.push({ type: 'image', data, mimeType: mediaType });
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (mediaType.startsWith('audio/') && this.promptCapabilities.audio) {
|
|
428
|
+
blocks.push({ type: 'audio', data, mimeType: mediaType });
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (this.promptCapabilities.embeddedContext) {
|
|
432
|
+
blocks.push({
|
|
433
|
+
type: 'resource',
|
|
434
|
+
resource: {
|
|
435
|
+
uri: `nexus-input://${this.sink.id}/${encodeURIComponent(attachment.name)}`,
|
|
436
|
+
blob: data,
|
|
437
|
+
mimeType: mediaType
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
blocks.push(await this.writeResourceLink(attachment, mediaType));
|
|
443
|
+
}
|
|
444
|
+
return blocks;
|
|
445
|
+
}
|
|
446
|
+
async writeResourceLink(attachment, mediaType) {
|
|
447
|
+
const directory = this.inputDirectory || path.join(process.cwd(), '.nexus-inputs', this.sink.id);
|
|
448
|
+
this.inputDirectory = directory;
|
|
449
|
+
await mkdir(directory, { recursive: true });
|
|
450
|
+
const filename = `${attachment.id}-${safeFilename(attachment.name) || 'attachment'}`;
|
|
451
|
+
const filePath = path.join(directory, filename);
|
|
452
|
+
await writeFile(filePath, attachment.bytes, { mode: 0o600 });
|
|
453
|
+
return {
|
|
454
|
+
type: 'resource_link',
|
|
455
|
+
name: attachment.name,
|
|
456
|
+
uri: pathToFileURL(filePath).toString(),
|
|
457
|
+
mimeType: mediaType,
|
|
458
|
+
size: attachment.bytes.length
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
class PromptTimeoutError extends Error {
|
|
463
|
+
}
|
|
464
|
+
function safeFilename(value) {
|
|
465
|
+
return String(value || '')
|
|
466
|
+
.replace(/[<>:"/\\|?*\u0000-\u001f]/g, '_')
|
|
467
|
+
.trim()
|
|
468
|
+
.slice(0, 180);
|
|
469
|
+
}
|
|
470
|
+
function withTimeout(promise, timeoutMs, message) {
|
|
471
|
+
let timer;
|
|
472
|
+
const timeout = new Promise((_, reject) => {
|
|
473
|
+
timer = setTimeout(() => reject(new PromptTimeoutError(message)), timeoutMs);
|
|
474
|
+
timer.unref?.();
|
|
475
|
+
});
|
|
476
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
477
|
+
if (timer)
|
|
478
|
+
clearTimeout(timer);
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
function artifactFromContent(content, id, hint) {
|
|
482
|
+
switch (content.type) {
|
|
483
|
+
case 'image': {
|
|
484
|
+
const filename = artifactFilename(content.uri, hint, `image-${artifactIndex(id)}`, content.mimeType);
|
|
485
|
+
return {
|
|
486
|
+
id,
|
|
487
|
+
name: filename,
|
|
488
|
+
filename,
|
|
489
|
+
mediaType: content.mimeType,
|
|
490
|
+
bytesBase64: content.data,
|
|
491
|
+
url: usableUrl(content.uri),
|
|
492
|
+
metadata: artifactMetadata(id, content.uri, content.annotations)
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
case 'audio': {
|
|
496
|
+
const filename = artifactFilename(undefined, hint, `audio-${artifactIndex(id)}`, content.mimeType);
|
|
497
|
+
return {
|
|
498
|
+
id,
|
|
499
|
+
name: filename,
|
|
500
|
+
filename,
|
|
501
|
+
mediaType: content.mimeType,
|
|
502
|
+
bytesBase64: content.data,
|
|
503
|
+
metadata: artifactMetadata(id, undefined, content.annotations)
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
case 'resource_link': {
|
|
507
|
+
const filename = artifactFilename(content.uri, content.name || hint, `resource-${artifactIndex(id)}`, content.mimeType || undefined);
|
|
508
|
+
return {
|
|
509
|
+
id,
|
|
510
|
+
name: content.title || content.name || filename,
|
|
511
|
+
description: content.description || undefined,
|
|
512
|
+
filename,
|
|
513
|
+
mediaType: content.mimeType || undefined,
|
|
514
|
+
url: content.uri,
|
|
515
|
+
metadata: artifactMetadata(id, content.uri, content.annotations, content.size ?? undefined)
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
case 'resource': {
|
|
519
|
+
const resource = content.resource;
|
|
520
|
+
const filename = artifactFilename(resource.uri, hint, `resource-${artifactIndex(id)}`, resource.mimeType || undefined);
|
|
521
|
+
return {
|
|
522
|
+
id,
|
|
523
|
+
name: filename,
|
|
524
|
+
filename,
|
|
525
|
+
mediaType: resource.mimeType || undefined,
|
|
526
|
+
text: 'text' in resource ? resource.text : undefined,
|
|
527
|
+
bytesBase64: 'blob' in resource ? resource.blob : undefined,
|
|
528
|
+
url: usableUrl(resource.uri),
|
|
529
|
+
metadata: artifactMetadata(id, resource.uri, content.annotations)
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
default:
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function artifactMetadata(sourceId, uri, annotations, size) {
|
|
537
|
+
return {
|
|
538
|
+
sourceId,
|
|
539
|
+
...(uri ? { uri } : {}),
|
|
540
|
+
...(size !== undefined ? { size } : {}),
|
|
541
|
+
...(annotations ? { annotations } : {})
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
function artifactFilename(uri, hint, fallback, mediaType) {
|
|
545
|
+
return (filenameFromValue(uri) ||
|
|
546
|
+
filenameFromValue(hint) ||
|
|
547
|
+
`${fallback}${extensionForMediaType(mediaType)}`);
|
|
548
|
+
}
|
|
549
|
+
function filenameFromValue(value) {
|
|
550
|
+
if (!value)
|
|
551
|
+
return undefined;
|
|
552
|
+
let candidate = value;
|
|
553
|
+
try {
|
|
554
|
+
candidate = new URL(value).pathname;
|
|
555
|
+
}
|
|
556
|
+
catch { }
|
|
557
|
+
candidate = candidate.split(/[\\/]/).pop() || candidate;
|
|
558
|
+
candidate = candidate.split(/[?#]/, 1)[0].trim();
|
|
559
|
+
const filename = candidate.match(/([^\s]+\.[A-Za-z0-9]{1,16})$/)?.[1];
|
|
560
|
+
if (!filename)
|
|
561
|
+
return undefined;
|
|
562
|
+
try {
|
|
563
|
+
return decodeURIComponent(filename);
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
return filename;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function usableUrl(value) {
|
|
570
|
+
return value && /^(?:https?|file):/i.test(value) ? value : undefined;
|
|
571
|
+
}
|
|
572
|
+
function artifactIndex(id) {
|
|
573
|
+
return id.split(':').pop() || 'output';
|
|
574
|
+
}
|
|
575
|
+
function extensionForMediaType(mediaType) {
|
|
576
|
+
const extensions = {
|
|
577
|
+
'image/png': '.png',
|
|
578
|
+
'image/jpeg': '.jpg',
|
|
579
|
+
'image/gif': '.gif',
|
|
580
|
+
'image/webp': '.webp',
|
|
581
|
+
'audio/mpeg': '.mp3',
|
|
582
|
+
'audio/wav': '.wav',
|
|
583
|
+
'audio/ogg': '.ogg',
|
|
584
|
+
'application/pdf': '.pdf'
|
|
585
|
+
};
|
|
586
|
+
return mediaType ? extensions[mediaType.toLowerCase()] || '' : '';
|
|
348
587
|
}
|
|
349
588
|
function elicitationOptions(params) {
|
|
350
589
|
if (!isFormElicitation(params))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const MAX_AGENT_INSTRUCTIONS_CHARS: number;
|
|
2
|
+
/**
|
|
3
|
+
* Baseline guidance for Agents running behind Agent Nexus.
|
|
4
|
+
*
|
|
5
|
+
* This is delivered as the first prompt because ACP and A2A do not expose a
|
|
6
|
+
* portable system-message field across all supported Agent implementations.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_AGENT_INSTRUCTIONS: string;
|
|
9
|
+
export declare function normalizeAgentInstructions(value: unknown, field?: string): string | undefined;
|
|
10
|
+
export declare function buildAgentInstructions(custom?: string): string;
|
|
11
|
+
export declare function composeInitialAgentPrompt(instructions: string, userMessage: string): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const MAX_AGENT_INSTRUCTIONS_CHARS = 32 * 1024;
|
|
2
|
+
/**
|
|
3
|
+
* Baseline guidance for Agents running behind Agent Nexus.
|
|
4
|
+
*
|
|
5
|
+
* This is delivered as the first prompt because ACP and A2A do not expose a
|
|
6
|
+
* portable system-message field across all supported Agent implementations.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_AGENT_INSTRUCTIONS = [
|
|
9
|
+
'You are running behind the Agent Nexus conversational host.',
|
|
10
|
+
'When an external tool or operation needs the user to choose, confirm, authorize, pay, or provide more information before it can continue:',
|
|
11
|
+
'1. Do not present the task as completed.',
|
|
12
|
+
'2. Use the host\'s native user-input, elicitation, or permission request mechanism when available; do not only print a normal-text question and stop.',
|
|
13
|
+
'3. Include a concise prompt and structured options when the mechanism supports them. For payment, include the payment URL and stable order metadata.',
|
|
14
|
+
'4. After the user replies, validate the result with the upstream tool. Never treat a message such as "payment completed" as proof that payment succeeded.',
|
|
15
|
+
'Only finish the task after the external operation has actually completed.'
|
|
16
|
+
].join('\n');
|
|
17
|
+
export function normalizeAgentInstructions(value, field = 'instructions') {
|
|
18
|
+
if (value === undefined || value === null)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (typeof value !== 'string')
|
|
21
|
+
throw new Error(`${field} must be a string`);
|
|
22
|
+
const text = value.trim();
|
|
23
|
+
if (text.length > MAX_AGENT_INSTRUCTIONS_CHARS) {
|
|
24
|
+
throw new Error(`${field} must be at most ${MAX_AGENT_INSTRUCTIONS_CHARS} characters`);
|
|
25
|
+
}
|
|
26
|
+
return text || undefined;
|
|
27
|
+
}
|
|
28
|
+
export function buildAgentInstructions(custom) {
|
|
29
|
+
const extra = normalizeAgentInstructions(custom);
|
|
30
|
+
return extra
|
|
31
|
+
? `${DEFAULT_AGENT_INSTRUCTIONS}\n\nAdditional agent-specific instructions:\n${extra}`
|
|
32
|
+
: DEFAULT_AGENT_INSTRUCTIONS;
|
|
33
|
+
}
|
|
34
|
+
export function composeInitialAgentPrompt(instructions, userMessage) {
|
|
35
|
+
return [
|
|
36
|
+
'<agent-nexus-host-instructions>',
|
|
37
|
+
instructions,
|
|
38
|
+
'</agent-nexus-host-instructions>',
|
|
39
|
+
'',
|
|
40
|
+
'<user-request>',
|
|
41
|
+
userMessage,
|
|
42
|
+
'</user-request>'
|
|
43
|
+
].join('\n');
|
|
44
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type FileHandle } from 'node:fs/promises';
|
|
2
|
+
import { type Readable } from 'node:stream';
|
|
3
|
+
import type { AgentdPublishedFile } from './types.js';
|
|
4
|
+
interface StoredArtifact extends AgentdPublishedFile {
|
|
5
|
+
filePath: string;
|
|
6
|
+
handle?: FileHandle;
|
|
7
|
+
}
|
|
8
|
+
export declare class ArtifactStoreError extends Error {
|
|
9
|
+
readonly status: number;
|
|
10
|
+
constructor(status: number, message: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class GatewayArtifactStore {
|
|
13
|
+
private readonly directory;
|
|
14
|
+
private readonly ttlMs;
|
|
15
|
+
private readonly maxBytes;
|
|
16
|
+
private readonly maxStorageBytes;
|
|
17
|
+
private readonly maxFiles;
|
|
18
|
+
private readonly maxConcurrentPublishes;
|
|
19
|
+
private readonly byCacheKey;
|
|
20
|
+
private cleanupTimer?;
|
|
21
|
+
private capacityQueue;
|
|
22
|
+
private activePublishes;
|
|
23
|
+
private reservedBytes;
|
|
24
|
+
private reservedFiles;
|
|
25
|
+
constructor(directory: string, ttlMs: number, maxBytes: number, maxStorageBytes?: number, maxFiles?: number, maxConcurrentPublishes?: number);
|
|
26
|
+
startCleanup(intervalMs?: number): void;
|
|
27
|
+
stopCleanup(): void;
|
|
28
|
+
publishFile(filePath: string, preferredName?: string, mediaType?: string): Promise<AgentdPublishedFile>;
|
|
29
|
+
publishHandle(handle: FileHandle, preferredName: string, mediaType?: string): Promise<AgentdPublishedFile>;
|
|
30
|
+
publishBytes(bytes: Uint8Array, preferredName: string, mediaType?: string, cacheKey?: string): Promise<AgentdPublishedFile>;
|
|
31
|
+
publishStream(input: Readable, preferredName: string, mediaType?: string, declaredSize?: number, cacheKey?: string): Promise<AgentdPublishedFile>;
|
|
32
|
+
open(id: string, expiresInput: string, requestedName: string): Promise<StoredArtifact>;
|
|
33
|
+
cleanup(): Promise<void>;
|
|
34
|
+
private ensureDirectory;
|
|
35
|
+
private isAvailable;
|
|
36
|
+
private reserveCapacity;
|
|
37
|
+
private storageUsage;
|
|
38
|
+
}
|
|
39
|
+
export declare function contentDisposition(name: string): string;
|
|
40
|
+
export declare function mediaTypeForName(name: string): string;
|
|
41
|
+
export {};
|