nexus-agentd 0.2.5 → 0.3.0
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/README.md +359 -250
- package/dist/a2a/runtime.d.ts +7 -1
- package/dist/a2a/runtime.js +290 -17
- package/dist/acp/runtime.d.ts +5 -1
- package/dist/acp/runtime.js +88 -39
- package/dist/artifact-store.d.ts +162 -0
- package/dist/artifact-store.js +1081 -0
- package/dist/cli.js +13 -14
- package/dist/completion-contract.js +1 -1
- package/dist/config.js +45 -5
- package/dist/control-plane-validation.d.ts +54 -0
- package/dist/control-plane-validation.js +152 -0
- package/dist/control-plane.d.ts +9 -32
- package/dist/control-plane.js +52 -158
- package/dist/drivers/index.js +12 -0
- package/dist/drivers/pi.js +1 -1
- package/dist/drivers/stdio.d.ts +4 -1
- package/dist/drivers/stdio.js +135 -19
- package/dist/events.d.ts +9 -0
- package/dist/events.js +17 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +33 -6
- package/dist/process-tree.js +15 -6
- package/dist/run-store.d.ts +57 -7
- package/dist/run-store.js +188 -23
- package/dist/server/auth-policy.d.ts +27 -0
- package/dist/server/auth-policy.js +143 -0
- package/dist/server/connection.d.ts +8 -0
- package/dist/server/connection.js +67 -0
- package/dist/server/diagnostics.d.ts +40 -0
- package/dist/server/diagnostics.js +130 -0
- package/dist/server/metrics.d.ts +51 -0
- package/dist/server/metrics.js +56 -0
- package/dist/server/quota.d.ts +59 -0
- package/dist/server/quota.js +176 -0
- package/dist/server/validation.d.ts +29 -0
- package/dist/server/validation.js +221 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +682 -418
- package/dist/session/admin-runs.d.ts +73 -0
- package/dist/session/admin-runs.js +297 -0
- package/dist/session/admission.d.ts +32 -0
- package/dist/session/admission.js +62 -0
- package/dist/session/readiness.d.ts +33 -0
- package/dist/session/readiness.js +174 -0
- package/dist/session-contract.d.ts +1 -0
- package/dist/session.d.ts +64 -7
- package/dist/session.js +321 -159
- package/dist/sse-writer.d.ts +7 -0
- package/dist/sse-writer.js +60 -0
- package/dist/types.d.ts +44 -2
- package/dist/types.js +1 -0
- package/dist/webui/app/sources.js +18 -10
- package/dist/webui/index.js +11 -11
- package/dist/webui/markup.js +69 -66
- package/dist/webui/styles.js +791 -616
- package/dist/workspace.d.ts +30 -0
- package/dist/workspace.js +67 -2
- package/examples/client.mjs +102 -0
- package/nexus-agentd.example.json +21 -2
- package/package.json +6 -3
package/dist/cli.js
CHANGED
|
@@ -18,22 +18,21 @@ const label = typeof address === 'object' && address
|
|
|
18
18
|
console.log(`nexus-agentd listening on ${label}`);
|
|
19
19
|
for (const url of displayUrls(address))
|
|
20
20
|
console.log(`WebUI: ${url}`);
|
|
21
|
-
if (bootstrap.created)
|
|
21
|
+
if (bootstrap.created)
|
|
22
22
|
console.log(`Created config: ${configPath}`);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if (runtime.controlPlane.setupToken) {
|
|
24
|
+
console.log('Setup required: enter the following one-time setup token in the WebUI, then create the Console Password.');
|
|
25
|
+
console.log(`Setup token: ${runtime.controlPlane.setupToken}`);
|
|
26
|
+
console.warn('Keep this token private. It changes on restart. Use a trusted network or HTTPS for remote setup.');
|
|
27
27
|
}
|
|
28
|
-
let closing
|
|
29
|
-
const close =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
process.once('
|
|
36
|
-
process.once('SIGTERM', () => void close().finally(() => process.exit(0)));
|
|
28
|
+
let closing;
|
|
29
|
+
const close = () => closing ??= runtime.close();
|
|
30
|
+
const shutdown = () => void close().then(() => process.exit(0), (error) => {
|
|
31
|
+
console.error(JSON.stringify({ level: 'error', event: 'shutdown_failed', message: error instanceof Error ? error.message : String(error) }));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
|
34
|
+
process.once('SIGINT', shutdown);
|
|
35
|
+
process.once('SIGTERM', shutdown);
|
|
37
36
|
function resolveConfigPath(args) {
|
|
38
37
|
const index = args.indexOf('--config');
|
|
39
38
|
const value = index >= 0 ? args[index + 1] : process.env.NEXUS_AGENTD_CONFIG;
|
|
@@ -4,6 +4,6 @@ export const TURN_COMPLETION_CONTRACT = [
|
|
|
4
4
|
'If you need user input or permission, use the protocol input/permission request and wait for the response.',
|
|
5
5
|
'Before ending the turn, verify the requested work and provide a non-empty final response.',
|
|
6
6
|
'The final response must summarize the outcome, verification performed, and workspace-relative paths for generated files.',
|
|
7
|
-
'Attach generated resources or artifacts through the protocol when supported. If the runtime only reports a local deliverable path, put each requested file on its own final line as MEDIA:<path
|
|
7
|
+
'Attach generated resources or artifacts through the protocol when supported. If the runtime only reports a local deliverable path, put each requested file on its own final line as MEDIA:<path>, where <path> stays inside the configured workspace roots; Agent Nexus snapshots that declared file as an artifact before the turn completes, refuses paths outside those roots, and attaches at most 8 files per turn.',
|
|
8
8
|
'</agent_nexus_completion_contract>'
|
|
9
9
|
].join('\n');
|
package/dist/config.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function ensureAgentdConfig(filePath, options = {}) {
|
|
|
13
13
|
}
|
|
14
14
|
const initial = {
|
|
15
15
|
listen: {
|
|
16
|
-
host: options.host?.trim() || '
|
|
16
|
+
host: options.host?.trim() || '0.0.0.0',
|
|
17
17
|
port: bootstrapNumber(options.port, 8787, 1, 65535)
|
|
18
18
|
},
|
|
19
19
|
initialized: false,
|
|
@@ -56,7 +56,7 @@ export async function loadAgentdConfig(filePath) {
|
|
|
56
56
|
if (!isRecord(value))
|
|
57
57
|
throw new Error('nexus-agentd config root must be an object');
|
|
58
58
|
const listen = optionalRecord(value.listen, 'listen') || {};
|
|
59
|
-
const host = optionalString(listen.host, 'listen.host') || '
|
|
59
|
+
const host = optionalString(listen.host, 'listen.host') || '0.0.0.0';
|
|
60
60
|
const port = integerValue(listen.port, 'listen.port', 8787, 1, 65535);
|
|
61
61
|
const initialized = optionalBoolean(value.initialized, 'initialized') ?? true;
|
|
62
62
|
const rawAuthToken = optionalString(value.authToken, 'authToken');
|
|
@@ -84,17 +84,45 @@ export async function loadAgentdConfig(filePath) {
|
|
|
84
84
|
throw new Error('nexus-agentd workspaceRoots must contain at least one path');
|
|
85
85
|
}
|
|
86
86
|
const rawAgents = requiredRecord(value.agents, 'agents');
|
|
87
|
-
const agents =
|
|
87
|
+
const agents = Object.create(null);
|
|
88
88
|
for (const [id, input] of Object.entries(rawAgents)) {
|
|
89
89
|
agents[id] = parseAgent(id, input);
|
|
90
90
|
}
|
|
91
|
+
const quotas = optionalRecord(value.quotas, 'quotas') || {};
|
|
92
|
+
const history = optionalRecord(value.history, 'history') || {};
|
|
93
|
+
const artifacts = optionalRecord(value.artifacts, 'artifacts') || {};
|
|
94
|
+
const maxSessions = integerValue(value.maxSessions, 'maxSessions', 64, 1, 10_000);
|
|
95
|
+
const maxSseConnections = integerValue(value.maxSseConnections, 'maxSseConnections', 128, 1, 10_000);
|
|
91
96
|
return {
|
|
92
97
|
listen: { host, port },
|
|
98
|
+
publicOrigins: value.publicOrigins === undefined ? [] : stringArray(value.publicOrigins, 'publicOrigins').map((origin) => {
|
|
99
|
+
validateHttpUrl(origin, 'publicOrigins');
|
|
100
|
+
if (new URL(origin).origin !== origin)
|
|
101
|
+
throw new Error('publicOrigins must contain exact origins without paths');
|
|
102
|
+
return origin;
|
|
103
|
+
}),
|
|
93
104
|
initialized,
|
|
94
105
|
authToken,
|
|
95
106
|
adminPasswordHash,
|
|
96
107
|
apiKeys,
|
|
97
108
|
workspaceRoots,
|
|
109
|
+
quotas: {
|
|
110
|
+
maxSessionsPerKey: integerValue(quotas.maxSessionsPerKey, 'quotas.maxSessionsPerKey', Math.min(16, maxSessions), 1, 10_000),
|
|
111
|
+
maxRunningRunsPerKey: integerValue(quotas.maxRunningRunsPerKey, 'quotas.maxRunningRunsPerKey', Math.min(4, maxSessions), 1, 10_000),
|
|
112
|
+
maxSsePerKey: integerValue(quotas.maxSsePerKey, 'quotas.maxSsePerKey', Math.min(8, maxSseConnections), 1, 10_000),
|
|
113
|
+
maxUploadBytesPerKey: integerValue(quotas.maxUploadBytesPerKey, 'quotas.maxUploadBytesPerKey', 32 * 1024 * 1024, 1024, 1024 * 1024 * 1024)
|
|
114
|
+
},
|
|
115
|
+
history: {
|
|
116
|
+
maxRuns: integerValue(history.maxRuns, 'history.maxRuns', 1000, 1, 100_000),
|
|
117
|
+
retentionDays: integerValue(history.retentionDays, 'history.retentionDays', 30, 1, 3650),
|
|
118
|
+
maxBytes: integerValue(history.maxBytes, 'history.maxBytes', 64 * 1024 * 1024, 1024, 1024 * 1024 * 1024)
|
|
119
|
+
},
|
|
120
|
+
artifacts: {
|
|
121
|
+
retentionDays: integerValue(artifacts.retentionDays, 'artifacts.retentionDays', 30, 1, 3650),
|
|
122
|
+
maxBytes: integerValue(artifacts.maxBytes, 'artifacts.maxBytes', 512 * 1024 * 1024, 1024, 1024 ** 4),
|
|
123
|
+
maxArtifactBytes: integerValue(artifacts.maxArtifactBytes, 'artifacts.maxArtifactBytes', 12 * 1024 * 1024, 1024, 12 * 1024 * 1024),
|
|
124
|
+
maxQueuedBytes: integerValue(artifacts.maxQueuedBytes, 'artifacts.maxQueuedBytes', 64 * 1024 * 1024, 1024, 512 * 1024 * 1024)
|
|
125
|
+
},
|
|
98
126
|
maxRequestBytes: integerValue(value.maxRequestBytes, 'maxRequestBytes', 1024 * 1024, 1024, 16 * 1024 * 1024),
|
|
99
127
|
maxAttachmentBytes: integerValue(value.maxAttachmentBytes, 'maxAttachmentBytes', 32 * 1024 * 1024, 1024, 64 * 1024 * 1024),
|
|
100
128
|
maxEventsPerSession: integerValue(value.maxEventsPerSession, 'maxEventsPerSession', 2048, 64, 100_000),
|
|
@@ -103,7 +131,7 @@ export async function loadAgentdConfig(filePath) {
|
|
|
103
131
|
cleanupIntervalMs: integerValue(value.cleanupIntervalMs, 'cleanupIntervalMs', 60_000, 5_000, 60 * 60 * 1000),
|
|
104
132
|
requestTimeoutMs: integerValue(value.requestTimeoutMs, 'requestTimeoutMs', 30_000, 1_000, 5 * 60_000),
|
|
105
133
|
promptTimeoutMs: integerValue(value.promptTimeoutMs, 'promptTimeoutMs', 30 * 60_000, 10_000, 24 * 60 * 60 * 1000),
|
|
106
|
-
maxSessions
|
|
134
|
+
maxSessions,
|
|
107
135
|
maxSseConnections: integerValue(value.maxSseConnections, 'maxSseConnections', 128, 1, 10_000),
|
|
108
136
|
maxConnections: integerValue(value.maxConnections, 'maxConnections', 256, 8, 100_000),
|
|
109
137
|
adminSessionTtlMs: integerValue(value.adminSessionTtlMs, 'adminSessionTtlMs', 12 * 60 * 60 * 1000, 5 * 60_000, 7 * 24 * 60 * 60 * 1000),
|
|
@@ -150,6 +178,9 @@ function parseAgent(id, value) {
|
|
|
150
178
|
args: input.args === undefined
|
|
151
179
|
? undefined
|
|
152
180
|
: stringArray(input.args, `agents.${id}.args`),
|
|
181
|
+
probeArgs: input.probeArgs === undefined
|
|
182
|
+
? undefined
|
|
183
|
+
: stringArray(input.probeArgs, `agents.${id}.probeArgs`),
|
|
153
184
|
inheritEnv: input.inheritEnv === undefined
|
|
154
185
|
? undefined
|
|
155
186
|
: stringArray(input.inheritEnv, `agents.${id}.inheritEnv`),
|
|
@@ -157,6 +188,9 @@ function parseAgent(id, value) {
|
|
|
157
188
|
permissionPolicy,
|
|
158
189
|
permissionTimeoutMs: integerValue(input.permissionTimeoutMs, `agents.${id}.permissionTimeoutMs`, 15 * 60 * 1000, 1000, 24 * 60 * 60 * 1000)
|
|
159
190
|
};
|
|
191
|
+
if (driver === 'stdio' && !result.command) {
|
|
192
|
+
throw new Error(`nexus-agentd agents.${id}.command is required for the stdio driver`);
|
|
193
|
+
}
|
|
160
194
|
return result;
|
|
161
195
|
}
|
|
162
196
|
function parseA2AAgent(id, input) {
|
|
@@ -204,7 +238,13 @@ function parseA2AAgent(id, input) {
|
|
|
204
238
|
agentUrl,
|
|
205
239
|
preferredTransport,
|
|
206
240
|
auth,
|
|
207
|
-
timeoutMs: integerValue(input.timeoutMs, `agents.${id}.timeoutMs`, 60_000, 1_000, 30 * 60_000)
|
|
241
|
+
timeoutMs: integerValue(input.timeoutMs, `agents.${id}.timeoutMs`, 60_000, 1_000, 30 * 60_000),
|
|
242
|
+
taskTimeoutMs: input.taskTimeoutMs === undefined
|
|
243
|
+
? undefined
|
|
244
|
+
: integerValue(input.taskTimeoutMs, `agents.${id}.taskTimeoutMs`, 30 * 60_000, 10_000, 24 * 60 * 60 * 1000),
|
|
245
|
+
streamIdleTimeoutMs: input.streamIdleTimeoutMs === undefined
|
|
246
|
+
? undefined
|
|
247
|
+
: integerValue(input.streamIdleTimeoutMs, `agents.${id}.streamIdleTimeoutMs`, 60_000, 1_000, 30 * 60_000)
|
|
208
248
|
};
|
|
209
249
|
}
|
|
210
250
|
function parseApiKeys(value) {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AgentdProtocol, AgentdDriverKind, PermissionPolicy, A2ATransportPreference, A2AAuthType, AgentdApiKeyScope } from './types.js';
|
|
2
|
+
export interface AgentdAgentUpdate {
|
|
3
|
+
protocol?: AgentdProtocol;
|
|
4
|
+
driver?: AgentdDriverKind;
|
|
5
|
+
name?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
workspace?: string;
|
|
9
|
+
permissionPolicy?: PermissionPolicy;
|
|
10
|
+
permissionTimeoutMs?: number;
|
|
11
|
+
agentCardUrl?: string;
|
|
12
|
+
agentUrl?: string;
|
|
13
|
+
preferredTransport?: A2ATransportPreference;
|
|
14
|
+
authType?: A2AAuthType;
|
|
15
|
+
authValue?: string;
|
|
16
|
+
authHeaderName?: string;
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
taskTimeoutMs?: number | null;
|
|
19
|
+
streamIdleTimeoutMs?: number | null;
|
|
20
|
+
}
|
|
21
|
+
export interface AgentdApiKeyUpdate {
|
|
22
|
+
name?: string;
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
scope?: AgentdApiKeyScope;
|
|
25
|
+
}
|
|
26
|
+
export interface AgentdRuntimeSettingsUpdate {
|
|
27
|
+
sessionTtlMs: number;
|
|
28
|
+
promptTimeoutMs: number;
|
|
29
|
+
cleanupIntervalMs: number;
|
|
30
|
+
}
|
|
31
|
+
export declare class ControlPlaneError extends Error {
|
|
32
|
+
readonly status: number;
|
|
33
|
+
constructor(status: number, message: string);
|
|
34
|
+
}
|
|
35
|
+
export declare function validateRuntimeSettings(update: AgentdRuntimeSettingsUpdate): void;
|
|
36
|
+
export declare function validateIntegerRange(value: number, min: number, max: number, name: string): void;
|
|
37
|
+
export declare function validateUpdate(update: AgentdAgentUpdate): void;
|
|
38
|
+
export declare function validateAgentId(value: string): string;
|
|
39
|
+
export declare function validateOpaqueId(value: string, label: string): string;
|
|
40
|
+
export declare function validatePassword(value: string, confirmation: string): string;
|
|
41
|
+
export declare function validateKeyName(value: string): string;
|
|
42
|
+
export declare function validateApiKeySecret(value: string): string;
|
|
43
|
+
export declare function validateA2ASecret(value: string): string;
|
|
44
|
+
export declare function validateAgentUrl(value: string): string;
|
|
45
|
+
export declare function defaultAgentCardUrl(agentUrl: string | undefined): string | undefined;
|
|
46
|
+
export declare function clampTimeout(value: number | undefined, fallback: number, min: number, max: number): number;
|
|
47
|
+
export declare function preservedAcpAdvanced(previous: Record<string, unknown>): Record<string, unknown>;
|
|
48
|
+
export declare function setOptionalString(target: Record<string, unknown>, key: string, value: string | undefined): void;
|
|
49
|
+
export declare function validHeaderName(value: string): boolean;
|
|
50
|
+
export declare function safeSecretEqual(left: string, right: string): boolean;
|
|
51
|
+
export declare function cleanString(value: unknown): string;
|
|
52
|
+
export declare function recordValue(value: unknown): Record<string, any>;
|
|
53
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
54
|
+
export declare function errorMessage(error: unknown): string;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { validateAdminPassword } from './auth.js';
|
|
3
|
+
export class ControlPlaneError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
constructor(status, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function validateRuntimeSettings(update) {
|
|
11
|
+
validateIntegerRange(update.sessionTtlMs, 60_000, 30 * 24 * 60 * 60 * 1000, 'sessionTtlMs');
|
|
12
|
+
validateIntegerRange(update.promptTimeoutMs, 10_000, 24 * 60 * 60 * 1000, 'promptTimeoutMs');
|
|
13
|
+
validateIntegerRange(update.cleanupIntervalMs, 5_000, 60 * 60 * 1000, 'cleanupIntervalMs');
|
|
14
|
+
}
|
|
15
|
+
export function validateIntegerRange(value, min, max, name) {
|
|
16
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
17
|
+
throw new ControlPlaneError(400, `${name} must be between ${min} and ${max}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function validateUpdate(update) {
|
|
21
|
+
if (update.protocol !== undefined && update.protocol !== 'acp' && update.protocol !== 'a2a') {
|
|
22
|
+
throw new ControlPlaneError(400, 'protocol must be acp or a2a');
|
|
23
|
+
}
|
|
24
|
+
if (update.permissionPolicy !== undefined &&
|
|
25
|
+
update.permissionPolicy !== 'ask' &&
|
|
26
|
+
update.permissionPolicy !== 'allow' &&
|
|
27
|
+
update.permissionPolicy !== 'deny') {
|
|
28
|
+
throw new ControlPlaneError(400, 'permissionPolicy must be ask, allow, or deny');
|
|
29
|
+
}
|
|
30
|
+
if (update.preferredTransport !== undefined &&
|
|
31
|
+
!['auto', 'jsonrpc', 'http-json'].includes(update.preferredTransport)) {
|
|
32
|
+
throw new ControlPlaneError(400, 'preferredTransport must be auto, jsonrpc, or http-json');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function validateAgentId(value) {
|
|
36
|
+
const id = decodeURIComponent(value).trim().toLowerCase();
|
|
37
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(id)) {
|
|
38
|
+
throw new ControlPlaneError(400, 'Invalid Agent ID');
|
|
39
|
+
}
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
export function validateOpaqueId(value, label) {
|
|
43
|
+
const id = decodeURIComponent(value).trim();
|
|
44
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(id)) {
|
|
45
|
+
throw new ControlPlaneError(400, `Invalid ${label} ID`);
|
|
46
|
+
}
|
|
47
|
+
return id;
|
|
48
|
+
}
|
|
49
|
+
export function validatePassword(value, confirmation) {
|
|
50
|
+
if (value !== confirmation) {
|
|
51
|
+
throw new ControlPlaneError(400, 'Console Password confirmation does not match');
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return validateAdminPassword(value);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new ControlPlaneError(400, errorMessage(error));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function validateKeyName(value) {
|
|
61
|
+
const name = cleanString(value);
|
|
62
|
+
if (!name)
|
|
63
|
+
throw new ControlPlaneError(400, 'API Key name is required');
|
|
64
|
+
if (name.length > 80)
|
|
65
|
+
throw new ControlPlaneError(400, 'API Key name is too long');
|
|
66
|
+
return name;
|
|
67
|
+
}
|
|
68
|
+
export function validateApiKeySecret(value) {
|
|
69
|
+
const secret = cleanString(value);
|
|
70
|
+
if (secret.length < 16) {
|
|
71
|
+
throw new ControlPlaneError(400, 'Custom API Key must contain at least 16 characters');
|
|
72
|
+
}
|
|
73
|
+
if (Buffer.byteLength(secret, 'utf8') > 512) {
|
|
74
|
+
throw new ControlPlaneError(400, 'Custom API Key must not exceed 512 bytes');
|
|
75
|
+
}
|
|
76
|
+
if (/^env:/i.test(secret) || /[\u0000-\u001f\u007f]/.test(secret)) {
|
|
77
|
+
throw new ControlPlaneError(400, 'Custom API Key contains an unsupported value');
|
|
78
|
+
}
|
|
79
|
+
return secret;
|
|
80
|
+
}
|
|
81
|
+
export function validateA2ASecret(value) {
|
|
82
|
+
const secret = cleanString(value);
|
|
83
|
+
if (!secret)
|
|
84
|
+
return '';
|
|
85
|
+
if (/^env:/i.test(secret) ||
|
|
86
|
+
Buffer.byteLength(secret, 'utf8') > 4096 ||
|
|
87
|
+
/[\u0000\r\n]/.test(secret)) {
|
|
88
|
+
throw new ControlPlaneError(400, 'A2A authentication value is invalid');
|
|
89
|
+
}
|
|
90
|
+
return secret;
|
|
91
|
+
}
|
|
92
|
+
export function validateAgentUrl(value) {
|
|
93
|
+
let url;
|
|
94
|
+
try {
|
|
95
|
+
url = new URL(value);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new ControlPlaneError(400, 'A2A Agent Card URL is invalid');
|
|
99
|
+
}
|
|
100
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash) {
|
|
101
|
+
throw new ControlPlaneError(400, 'A2A Agent Card URL must use http(s) without credentials or a fragment');
|
|
102
|
+
}
|
|
103
|
+
return url.toString().replace(/\/$/, '');
|
|
104
|
+
}
|
|
105
|
+
export function defaultAgentCardUrl(agentUrl) {
|
|
106
|
+
if (!agentUrl)
|
|
107
|
+
return undefined;
|
|
108
|
+
return new URL('/.well-known/agent-card.json', agentUrl).toString();
|
|
109
|
+
}
|
|
110
|
+
export function clampTimeout(value, fallback, min, max) {
|
|
111
|
+
if (value === undefined)
|
|
112
|
+
return fallback;
|
|
113
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
114
|
+
throw new ControlPlaneError(400, `timeout must be between ${min} and ${max}`);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
export function preservedAcpAdvanced(previous) {
|
|
119
|
+
const result = {};
|
|
120
|
+
for (const key of ['command', 'args', 'probeArgs', 'inheritEnv', 'env']) {
|
|
121
|
+
if (previous[key] !== undefined)
|
|
122
|
+
result[key] = structuredClone(previous[key]);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
export function setOptionalString(target, key, value) {
|
|
127
|
+
const text = cleanString(value);
|
|
128
|
+
if (text)
|
|
129
|
+
target[key] = text;
|
|
130
|
+
else
|
|
131
|
+
delete target[key];
|
|
132
|
+
}
|
|
133
|
+
export function validHeaderName(value) {
|
|
134
|
+
return /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(value);
|
|
135
|
+
}
|
|
136
|
+
export function safeSecretEqual(left, right) {
|
|
137
|
+
const leftBuffer = Buffer.from(left);
|
|
138
|
+
const rightBuffer = Buffer.from(right);
|
|
139
|
+
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
|
|
140
|
+
}
|
|
141
|
+
export function cleanString(value) {
|
|
142
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
143
|
+
}
|
|
144
|
+
export function recordValue(value) {
|
|
145
|
+
return isRecord(value) ? { ...value } : {};
|
|
146
|
+
}
|
|
147
|
+
export function isRecord(value) {
|
|
148
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
149
|
+
}
|
|
150
|
+
export function errorMessage(error) {
|
|
151
|
+
return error instanceof Error ? error.message : String(error);
|
|
152
|
+
}
|
package/dist/control-plane.d.ts
CHANGED
|
@@ -1,39 +1,20 @@
|
|
|
1
|
+
import { type AgentdAgentUpdate, type AgentdApiKeyUpdate, type AgentdRuntimeSettingsUpdate } from './control-plane-validation.js';
|
|
2
|
+
export { ControlPlaneError, type AgentdAgentUpdate, type AgentdApiKeyUpdate, type AgentdRuntimeSettingsUpdate } from './control-plane-validation.js';
|
|
1
3
|
import type { SessionManager } from './session.js';
|
|
2
|
-
import { type
|
|
3
|
-
export interface AgentdAgentUpdate {
|
|
4
|
-
protocol?: AgentdProtocol;
|
|
5
|
-
driver?: AgentdDriverKind;
|
|
6
|
-
name?: string;
|
|
7
|
-
description?: string;
|
|
8
|
-
enabled?: boolean;
|
|
9
|
-
workspace?: string;
|
|
10
|
-
permissionPolicy?: PermissionPolicy;
|
|
11
|
-
permissionTimeoutMs?: number;
|
|
12
|
-
agentCardUrl?: string;
|
|
13
|
-
agentUrl?: string;
|
|
14
|
-
preferredTransport?: A2ATransportPreference;
|
|
15
|
-
authType?: A2AAuthType;
|
|
16
|
-
authValue?: string;
|
|
17
|
-
authHeaderName?: string;
|
|
18
|
-
timeoutMs?: number;
|
|
19
|
-
}
|
|
20
|
-
export interface AgentdApiKeyUpdate {
|
|
21
|
-
name?: string;
|
|
22
|
-
enabled?: boolean;
|
|
23
|
-
scope?: AgentdApiKeyScope;
|
|
24
|
-
}
|
|
25
|
-
export interface AgentdRuntimeSettingsUpdate {
|
|
26
|
-
sessionTtlMs: number;
|
|
27
|
-
promptTimeoutMs: number;
|
|
28
|
-
cleanupIntervalMs: number;
|
|
29
|
-
}
|
|
4
|
+
import { type AgentdApiKeyPrincipal, type AgentdApiKeyScope, type AgentdApiKeyView, type AgentdConfig, type AgentdControlPlaneView } from './types.js';
|
|
30
5
|
export declare class AgentdControlPlane {
|
|
31
6
|
private readonly configPath;
|
|
32
7
|
private config;
|
|
33
8
|
private readonly sessions;
|
|
9
|
+
/** Process-local proof, delivered only through the local CLI / embedding API. */
|
|
10
|
+
setupToken?: string;
|
|
34
11
|
private queue;
|
|
35
12
|
private usageTimer?;
|
|
13
|
+
private readonly keyWatchers;
|
|
14
|
+
/** Observe authorization without updating last-used time or retaining a stale scope. */
|
|
15
|
+
watchApiKey(keyId: string, secret: string, agentId: string, revoked: () => void): () => void;
|
|
36
16
|
constructor(configPath: string, config: AgentdConfig, sessions: SessionManager);
|
|
17
|
+
verifySetupToken(token: string): boolean;
|
|
37
18
|
snapshot(): AgentdControlPlaneView;
|
|
38
19
|
isInitialized(): boolean;
|
|
39
20
|
needsAdminSetup(): boolean;
|
|
@@ -85,7 +66,3 @@ export declare class AgentdControlPlane {
|
|
|
85
66
|
private agentView;
|
|
86
67
|
private exclusive;
|
|
87
68
|
}
|
|
88
|
-
export declare class ControlPlaneError extends Error {
|
|
89
|
-
readonly status: number;
|
|
90
|
-
constructor(status: number, message: string);
|
|
91
|
-
}
|