nexus-agentd 0.1.0 → 0.1.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/LICENSE +22 -0
- package/README.md +351 -179
- package/dist/a2a/runtime.d.ts +30 -0
- package/dist/a2a/runtime.js +374 -0
- package/dist/acp/runtime.d.ts +12 -3
- package/dist/acp/runtime.js +248 -17
- 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 +281 -53
- package/dist/control-plane.d.ts +91 -0
- package/dist/control-plane.js +629 -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 +911 -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 +193 -6
- package/dist/types.js +2 -1
- package/dist/webui/app.d.ts +1 -0
- package/dist/webui/app.js +1072 -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 +30 -27
- package/package.json +6 -6
package/dist/config.js
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { agentdDriverKinds } from './types.js';
|
|
4
|
+
export async function ensureAgentdConfig(filePath, options = {}) {
|
|
5
|
+
const absolute = path.resolve(filePath);
|
|
6
|
+
try {
|
|
7
|
+
await readFile(absolute, 'utf8');
|
|
8
|
+
return { created: false };
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error.code !== 'ENOENT')
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
const initial = {
|
|
15
|
+
listen: {
|
|
16
|
+
host: options.host?.trim() || '127.0.0.1',
|
|
17
|
+
port: bootstrapNumber(options.port, 8787, 1, 65535)
|
|
18
|
+
},
|
|
19
|
+
initialized: false,
|
|
20
|
+
workspaceRoots: [path.resolve(options.workspace || process.cwd())],
|
|
21
|
+
sessionTtlMs: 24 * 60 * 60 * 1000,
|
|
22
|
+
promptTimeoutMs: 30 * 60 * 1000,
|
|
23
|
+
cleanupIntervalMs: 60_000,
|
|
24
|
+
apiKeys: [],
|
|
25
|
+
agents: {}
|
|
26
|
+
};
|
|
27
|
+
await mkdir(path.dirname(absolute), { recursive: true });
|
|
28
|
+
try {
|
|
29
|
+
const handle = await open(absolute, 'wx', 0o600);
|
|
30
|
+
try {
|
|
31
|
+
await handle.writeFile(`${JSON.stringify(initial, null, 2)}\n`, 'utf8');
|
|
32
|
+
await handle.sync();
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
await handle.close();
|
|
36
|
+
}
|
|
37
|
+
return { created: true };
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error.code === 'EEXIST') {
|
|
41
|
+
return { created: false };
|
|
42
|
+
}
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
4
46
|
export async function loadAgentdConfig(filePath) {
|
|
5
47
|
const absolute = path.resolve(filePath);
|
|
6
48
|
const raw = await readFile(absolute, 'utf8');
|
|
@@ -13,54 +55,65 @@ export async function loadAgentdConfig(filePath) {
|
|
|
13
55
|
}
|
|
14
56
|
if (!isRecord(value))
|
|
15
57
|
throw new Error('nexus-agentd config root must be an object');
|
|
16
|
-
const listen =
|
|
17
|
-
const host =
|
|
18
|
-
const port =
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
58
|
+
const listen = optionalRecord(value.listen, 'listen') || {};
|
|
59
|
+
const host = optionalString(listen.host, 'listen.host') || '127.0.0.1';
|
|
60
|
+
const port = integerValue(listen.port, 'listen.port', 8787, 1, 65535);
|
|
61
|
+
const initialized = optionalBoolean(value.initialized, 'initialized') ?? true;
|
|
62
|
+
const rawAuthToken = optionalString(value.authToken, 'authToken');
|
|
63
|
+
const authToken = rawAuthToken ? resolveSecret(rawAuthToken) : undefined;
|
|
64
|
+
const adminPasswordHash = optionalString(value.adminPasswordHash, 'adminPasswordHash');
|
|
65
|
+
const apiKeys = parseApiKeys(value.apiKeys);
|
|
66
|
+
if (!initialized && (rawAuthToken || adminPasswordHash || apiKeys.length)) {
|
|
67
|
+
throw new Error('nexus-agentd pending setup must not contain authToken, credentials, or API keys');
|
|
68
|
+
}
|
|
69
|
+
if (initialized && !authToken && !adminPasswordHash && !apiKeys.length) {
|
|
70
|
+
throw new Error('nexus-agentd requires an adminPasswordHash, authToken, or API key after initialization');
|
|
71
|
+
}
|
|
72
|
+
if (authToken && !apiKeys.some((key) => secretsEqual(key.secret, authToken))) {
|
|
73
|
+
apiKeys.unshift({
|
|
74
|
+
id: 'legacy',
|
|
75
|
+
name: 'Legacy Access Key',
|
|
76
|
+
secret: authToken,
|
|
77
|
+
enabled: true,
|
|
78
|
+
scope: { allAgents: true, agentIds: [] },
|
|
79
|
+
createdAt: 0
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const workspaceRoots = stringArray(value.workspaceRoots, 'workspaceRoots');
|
|
23
83
|
if (!workspaceRoots.length) {
|
|
24
84
|
throw new Error('nexus-agentd workspaceRoots must contain at least one path');
|
|
25
85
|
}
|
|
26
|
-
|
|
27
|
-
throw new Error('nexus-agentd agents must be an object');
|
|
28
|
-
}
|
|
86
|
+
const rawAgents = requiredRecord(value.agents, 'agents');
|
|
29
87
|
const agents = {};
|
|
30
|
-
for (const [id, input] of Object.entries(
|
|
31
|
-
|
|
32
|
-
throw new Error(`Agent config must be an object: ${id}`);
|
|
33
|
-
const driver = stringValue(input.driver) || inferDriver(id);
|
|
34
|
-
if (!isAgentdDriverKind(driver)) {
|
|
35
|
-
throw new Error(`Unsupported nexus-agentd driver: ${driver || id}`);
|
|
36
|
-
}
|
|
37
|
-
const permissionPolicy = stringValue(input.permissionPolicy) || 'ask';
|
|
38
|
-
if (permissionPolicy !== 'ask' && permissionPolicy !== 'deny') {
|
|
39
|
-
throw new Error(`Invalid permissionPolicy for ${id}`);
|
|
40
|
-
}
|
|
41
|
-
agents[id] = {
|
|
42
|
-
driver,
|
|
43
|
-
name: optionalString(input.name),
|
|
44
|
-
description: optionalString(input.description),
|
|
45
|
-
enabled: input.enabled !== false,
|
|
46
|
-
command: optionalString(input.command),
|
|
47
|
-
args: input.args === undefined ? undefined : arrayOfStrings(input.args),
|
|
48
|
-
inheritEnv: input.inheritEnv === undefined
|
|
49
|
-
? undefined
|
|
50
|
-
: arrayOfStrings(input.inheritEnv),
|
|
51
|
-
env: recordOfStrings(input.env),
|
|
52
|
-
permissionPolicy,
|
|
53
|
-
permissionTimeoutMs: numberValue(input.permissionTimeoutMs, 15 * 60 * 1000, 1000, 24 * 60 * 60 * 1000)
|
|
54
|
-
};
|
|
88
|
+
for (const [id, input] of Object.entries(rawAgents)) {
|
|
89
|
+
agents[id] = parseAgent(id, input);
|
|
55
90
|
}
|
|
56
91
|
return {
|
|
57
92
|
listen: { host, port },
|
|
93
|
+
initialized,
|
|
58
94
|
authToken,
|
|
95
|
+
adminPasswordHash,
|
|
96
|
+
apiKeys,
|
|
59
97
|
workspaceRoots,
|
|
60
|
-
maxRequestBytes:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
maxRequestBytes: integerValue(value.maxRequestBytes, 'maxRequestBytes', 1024 * 1024, 1024, 16 * 1024 * 1024),
|
|
99
|
+
maxAttachmentBytes: integerValue(value.maxAttachmentBytes, 'maxAttachmentBytes', 32 * 1024 * 1024, 1024, 64 * 1024 * 1024),
|
|
100
|
+
artifactStoragePath: resolveConfigPath(absolute, optionalString(value.artifactStoragePath, 'artifactStoragePath') || './artifacts'),
|
|
101
|
+
maxArtifactBytes: integerValue(value.maxArtifactBytes, 'maxArtifactBytes', 512 * 1024 * 1024, 1024, 16 * 1024 * 1024 * 1024),
|
|
102
|
+
maxArtifactStorageBytes: integerValue(value.maxArtifactStorageBytes, 'maxArtifactStorageBytes', 4 * 1024 * 1024 * 1024, 1024 * 1024, Number.MAX_SAFE_INTEGER),
|
|
103
|
+
maxPublishedArtifacts: integerValue(value.maxPublishedArtifacts, 'maxPublishedArtifacts', 4096, 1, 1_000_000),
|
|
104
|
+
maxConcurrentArtifactPublishes: integerValue(value.maxConcurrentArtifactPublishes, 'maxConcurrentArtifactPublishes', 4, 1, 128),
|
|
105
|
+
artifactTtlMs: integerValue(value.artifactTtlMs, 'artifactTtlMs', 24 * 60 * 60 * 1000, 60_000, 30 * 24 * 60 * 60 * 1000),
|
|
106
|
+
maxEventsPerSession: integerValue(value.maxEventsPerSession, 'maxEventsPerSession', 2048, 64, 100_000),
|
|
107
|
+
maxOutputChars: integerValue(value.maxOutputChars, 'maxOutputChars', 512 * 1024, 16 * 1024, 16 * 1024 * 1024),
|
|
108
|
+
sessionTtlMs: integerValue(value.sessionTtlMs, 'sessionTtlMs', 24 * 60 * 60 * 1000, 60_000, 30 * 24 * 60 * 60 * 1000),
|
|
109
|
+
cleanupIntervalMs: integerValue(value.cleanupIntervalMs, 'cleanupIntervalMs', 60_000, 5_000, 60 * 60 * 1000),
|
|
110
|
+
requestTimeoutMs: integerValue(value.requestTimeoutMs, 'requestTimeoutMs', 30_000, 1_000, 5 * 60_000),
|
|
111
|
+
promptTimeoutMs: integerValue(value.promptTimeoutMs, 'promptTimeoutMs', 30 * 60_000, 10_000, 24 * 60 * 60 * 1000),
|
|
112
|
+
maxSessions: integerValue(value.maxSessions, 'maxSessions', 64, 1, 10_000),
|
|
113
|
+
maxSseConnections: integerValue(value.maxSseConnections, 'maxSseConnections', 128, 1, 10_000),
|
|
114
|
+
maxConnections: integerValue(value.maxConnections, 'maxConnections', 256, 8, 100_000),
|
|
115
|
+
adminSessionTtlMs: integerValue(value.adminSessionTtlMs, 'adminSessionTtlMs', 12 * 60 * 60 * 1000, 5 * 60_000, 7 * 24 * 60 * 60 * 1000),
|
|
116
|
+
secureAdminCookies: optionalBoolean(value.secureAdminCookies, 'secureAdminCookies') ?? false,
|
|
64
117
|
agents
|
|
65
118
|
};
|
|
66
119
|
}
|
|
@@ -75,6 +128,126 @@ export function resolveSecret(value) {
|
|
|
75
128
|
throw new Error(`Secret environment variable is missing: ${name}`);
|
|
76
129
|
return secret;
|
|
77
130
|
}
|
|
131
|
+
function parseAgent(id, value) {
|
|
132
|
+
const input = requiredRecord(value, `agents.${id}`);
|
|
133
|
+
const protocol = optionalString(input.protocol, `agents.${id}.protocol`) || 'acp';
|
|
134
|
+
if (protocol === 'a2a')
|
|
135
|
+
return parseA2AAgent(id, input);
|
|
136
|
+
if (protocol !== 'acp')
|
|
137
|
+
throw new Error(`Unsupported protocol for ${id}: ${protocol}`);
|
|
138
|
+
const driver = optionalString(input.driver, `agents.${id}.driver`) || inferDriver(id);
|
|
139
|
+
if (!isAgentdDriverKind(driver)) {
|
|
140
|
+
throw new Error(`Unsupported nexus-agentd driver: ${driver || id}`);
|
|
141
|
+
}
|
|
142
|
+
const permissionPolicy = optionalString(input.permissionPolicy, `agents.${id}.permissionPolicy`) || 'ask';
|
|
143
|
+
if (permissionPolicy !== 'ask' && permissionPolicy !== 'deny') {
|
|
144
|
+
throw new Error(`Invalid permissionPolicy for ${id}`);
|
|
145
|
+
}
|
|
146
|
+
const result = {
|
|
147
|
+
protocol: 'acp',
|
|
148
|
+
driver,
|
|
149
|
+
name: optionalString(input.name, `agents.${id}.name`),
|
|
150
|
+
description: optionalString(input.description, `agents.${id}.description`),
|
|
151
|
+
enabled: optionalBoolean(input.enabled, `agents.${id}.enabled`) ?? true,
|
|
152
|
+
workspace: optionalString(input.workspace, `agents.${id}.workspace`),
|
|
153
|
+
command: optionalString(input.command, `agents.${id}.command`),
|
|
154
|
+
args: input.args === undefined
|
|
155
|
+
? undefined
|
|
156
|
+
: stringArray(input.args, `agents.${id}.args`),
|
|
157
|
+
inheritEnv: input.inheritEnv === undefined
|
|
158
|
+
? undefined
|
|
159
|
+
: stringArray(input.inheritEnv, `agents.${id}.inheritEnv`),
|
|
160
|
+
env: optionalStringRecord(input.env, `agents.${id}.env`),
|
|
161
|
+
permissionPolicy,
|
|
162
|
+
permissionTimeoutMs: integerValue(input.permissionTimeoutMs, `agents.${id}.permissionTimeoutMs`, 15 * 60 * 1000, 1000, 24 * 60 * 60 * 1000)
|
|
163
|
+
};
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
function parseA2AAgent(id, input) {
|
|
167
|
+
const agentCardUrl = optionalString(input.agentCardUrl, `agents.${id}.agentCardUrl`);
|
|
168
|
+
const agentUrl = optionalString(input.agentUrl, `agents.${id}.agentUrl`);
|
|
169
|
+
if (!agentCardUrl && !agentUrl) {
|
|
170
|
+
throw new Error(`nexus-agentd agents.${id}.agentCardUrl is required`);
|
|
171
|
+
}
|
|
172
|
+
if (agentCardUrl)
|
|
173
|
+
validateHttpUrl(agentCardUrl, `agents.${id}.agentCardUrl`);
|
|
174
|
+
if (agentUrl)
|
|
175
|
+
validateHttpUrl(agentUrl, `agents.${id}.agentUrl`);
|
|
176
|
+
const preferredTransport = (optionalString(input.preferredTransport, `agents.${id}.preferredTransport`) || 'auto');
|
|
177
|
+
if (!['auto', 'jsonrpc', 'http-json'].includes(preferredTransport)) {
|
|
178
|
+
throw new Error(`Invalid A2A preferredTransport for ${id}`);
|
|
179
|
+
}
|
|
180
|
+
const rawAuth = optionalRecord(input.auth, `agents.${id}.auth`);
|
|
181
|
+
let auth;
|
|
182
|
+
if (rawAuth) {
|
|
183
|
+
const type = (optionalString(rawAuth.type, `agents.${id}.auth.type`) ||
|
|
184
|
+
'none');
|
|
185
|
+
if (!['none', 'bearer', 'header'].includes(type)) {
|
|
186
|
+
throw new Error(`Invalid A2A auth type for ${id}`);
|
|
187
|
+
}
|
|
188
|
+
const rawValue = optionalString(rawAuth.value, `agents.${id}.auth.value`);
|
|
189
|
+
const headerName = optionalString(rawAuth.headerName, `agents.${id}.auth.headerName`);
|
|
190
|
+
if (type !== 'none' && !rawValue) {
|
|
191
|
+
throw new Error(`A2A auth value is required for ${id}`);
|
|
192
|
+
}
|
|
193
|
+
if (type === 'header' && !validHeaderName(headerName || '')) {
|
|
194
|
+
throw new Error(`A valid A2A auth headerName is required for ${id}`);
|
|
195
|
+
}
|
|
196
|
+
auth = {
|
|
197
|
+
type,
|
|
198
|
+
value: rawValue ? resolveSecret(rawValue) : undefined,
|
|
199
|
+
headerName: type === 'header' ? headerName : undefined
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
protocol: 'a2a',
|
|
204
|
+
name: optionalString(input.name, `agents.${id}.name`),
|
|
205
|
+
description: optionalString(input.description, `agents.${id}.description`),
|
|
206
|
+
enabled: optionalBoolean(input.enabled, `agents.${id}.enabled`) ?? true,
|
|
207
|
+
agentCardUrl,
|
|
208
|
+
agentUrl,
|
|
209
|
+
preferredTransport,
|
|
210
|
+
auth,
|
|
211
|
+
timeoutMs: integerValue(input.timeoutMs, `agents.${id}.timeoutMs`, 60_000, 1_000, 30 * 60_000)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function parseApiKeys(value) {
|
|
215
|
+
if (value === undefined)
|
|
216
|
+
return [];
|
|
217
|
+
if (!Array.isArray(value))
|
|
218
|
+
throw new Error('nexus-agentd apiKeys must be an array');
|
|
219
|
+
const ids = new Set();
|
|
220
|
+
return value.map((entry, index) => {
|
|
221
|
+
const item = requiredRecord(entry, `apiKeys[${index}]`);
|
|
222
|
+
const id = requiredString(item.id, `apiKeys[${index}].id`);
|
|
223
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(id)) {
|
|
224
|
+
throw new Error(`Invalid API key id: ${id}`);
|
|
225
|
+
}
|
|
226
|
+
if (ids.has(id))
|
|
227
|
+
throw new Error(`Duplicate API key id: ${id}`);
|
|
228
|
+
ids.add(id);
|
|
229
|
+
const rawSecret = requiredString(item.secret, `apiKeys[${index}].secret`);
|
|
230
|
+
const scope = requiredRecord(item.scope, `apiKeys[${index}].scope`);
|
|
231
|
+
const allAgents = optionalBoolean(scope.allAgents, `apiKeys[${index}].scope.allAgents`);
|
|
232
|
+
if (allAgents === undefined) {
|
|
233
|
+
throw new Error(`apiKeys[${index}].scope.allAgents is required`);
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
id,
|
|
237
|
+
name: requiredString(item.name, `apiKeys[${index}].name`),
|
|
238
|
+
secret: resolveSecret(rawSecret),
|
|
239
|
+
enabled: optionalBoolean(item.enabled, `apiKeys[${index}].enabled`) ?? true,
|
|
240
|
+
scope: {
|
|
241
|
+
allAgents,
|
|
242
|
+
agentIds: stringArray(scope.agentIds ?? [], `apiKeys[${index}].scope.agentIds`)
|
|
243
|
+
},
|
|
244
|
+
createdAt: integerValue(item.createdAt, `apiKeys[${index}].createdAt`, 0, 0, Number.MAX_SAFE_INTEGER),
|
|
245
|
+
lastUsedAt: item.lastUsedAt === undefined
|
|
246
|
+
? undefined
|
|
247
|
+
: integerValue(item.lastUsedAt, `apiKeys[${index}].lastUsedAt`, 0, 0, Number.MAX_SAFE_INTEGER)
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
}
|
|
78
251
|
function inferDriver(id) {
|
|
79
252
|
const value = id.trim().toLowerCase();
|
|
80
253
|
return isAgentdDriverKind(value) ? value : '';
|
|
@@ -82,34 +255,89 @@ function inferDriver(id) {
|
|
|
82
255
|
function isAgentdDriverKind(value) {
|
|
83
256
|
return agentdDriverKinds.includes(value);
|
|
84
257
|
}
|
|
258
|
+
function requiredRecord(value, name) {
|
|
259
|
+
if (!isRecord(value))
|
|
260
|
+
throw new Error(`nexus-agentd ${name} must be an object`);
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
function optionalRecord(value, name) {
|
|
264
|
+
if (value === undefined)
|
|
265
|
+
return undefined;
|
|
266
|
+
return requiredRecord(value, name);
|
|
267
|
+
}
|
|
85
268
|
function isRecord(value) {
|
|
86
269
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
87
270
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
271
|
+
function requiredString(value, name) {
|
|
272
|
+
const result = optionalString(value, name);
|
|
273
|
+
if (!result)
|
|
274
|
+
throw new Error(`nexus-agentd ${name} is required`);
|
|
275
|
+
return result;
|
|
90
276
|
}
|
|
91
|
-
function optionalString(value) {
|
|
92
|
-
|
|
277
|
+
function optionalString(value, name) {
|
|
278
|
+
if (value === undefined || value === null)
|
|
279
|
+
return undefined;
|
|
280
|
+
if (typeof value !== 'string')
|
|
281
|
+
throw new Error(`nexus-agentd ${name} must be a string`);
|
|
282
|
+
return value.trim() || undefined;
|
|
93
283
|
}
|
|
94
|
-
function
|
|
284
|
+
function optionalBoolean(value, name) {
|
|
285
|
+
if (value === undefined)
|
|
286
|
+
return undefined;
|
|
287
|
+
if (typeof value !== 'boolean')
|
|
288
|
+
throw new Error(`nexus-agentd ${name} must be a boolean`);
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
291
|
+
function stringArray(value, name) {
|
|
95
292
|
if (!Array.isArray(value))
|
|
96
|
-
|
|
97
|
-
return value.map(
|
|
293
|
+
throw new Error(`nexus-agentd ${name} must be an array`);
|
|
294
|
+
return value.map((entry, index) => requiredString(entry, `${name}[${index}]`));
|
|
98
295
|
}
|
|
99
|
-
function
|
|
100
|
-
if (
|
|
296
|
+
function optionalStringRecord(value, name) {
|
|
297
|
+
if (value === undefined)
|
|
101
298
|
return undefined;
|
|
299
|
+
const input = requiredRecord(value, name);
|
|
102
300
|
const result = {};
|
|
103
|
-
for (const [key, item] of Object.entries(
|
|
104
|
-
if (typeof item !== 'string')
|
|
105
|
-
|
|
301
|
+
for (const [key, item] of Object.entries(input)) {
|
|
302
|
+
if (typeof item !== 'string') {
|
|
303
|
+
throw new Error(`nexus-agentd ${name}.${key} must be a string`);
|
|
304
|
+
}
|
|
106
305
|
result[key] = item;
|
|
107
306
|
}
|
|
108
307
|
return result;
|
|
109
308
|
}
|
|
110
|
-
function
|
|
309
|
+
function integerValue(value, name, fallback, min, max) {
|
|
310
|
+
if (value === undefined)
|
|
311
|
+
return fallback;
|
|
312
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < min || value > max) {
|
|
313
|
+
throw new Error(`nexus-agentd ${name} must be an integer between ${min} and ${max}`);
|
|
314
|
+
}
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
function bootstrapNumber(value, fallback, min, max) {
|
|
111
318
|
const number = Number(value);
|
|
112
319
|
if (!Number.isFinite(number))
|
|
113
320
|
return fallback;
|
|
114
321
|
return Math.min(max, Math.max(min, Math.trunc(number)));
|
|
115
322
|
}
|
|
323
|
+
function validHeaderName(value) {
|
|
324
|
+
return /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(value);
|
|
325
|
+
}
|
|
326
|
+
function validateHttpUrl(value, name) {
|
|
327
|
+
let url;
|
|
328
|
+
try {
|
|
329
|
+
url = new URL(value);
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
throw new Error(`nexus-agentd ${name} must be a valid URL`);
|
|
333
|
+
}
|
|
334
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash) {
|
|
335
|
+
throw new Error(`nexus-agentd ${name} must be an http(s) URL without credentials or a fragment`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function resolveConfigPath(configPath, value) {
|
|
339
|
+
return path.resolve(path.dirname(configPath), value);
|
|
340
|
+
}
|
|
341
|
+
function secretsEqual(left, right) {
|
|
342
|
+
return left.length === right.length && left === right;
|
|
343
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { SessionManager } from './session.js';
|
|
2
|
+
import { type A2AAuthType, type A2ATransportPreference, type AgentdApiKeyPrincipal, type AgentdApiKeyScope, type AgentdApiKeyView, type AgentdConfig, type AgentdControlPlaneView, type AgentdDriverKind, type AgentdProtocol, type PermissionPolicy } from './types.js';
|
|
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
|
+
}
|
|
30
|
+
export declare class AgentdControlPlane {
|
|
31
|
+
private readonly configPath;
|
|
32
|
+
private config;
|
|
33
|
+
private readonly sessions;
|
|
34
|
+
private queue;
|
|
35
|
+
private usageTimer?;
|
|
36
|
+
constructor(configPath: string, config: AgentdConfig, sessions: SessionManager);
|
|
37
|
+
snapshot(): AgentdControlPlaneView;
|
|
38
|
+
isInitialized(): boolean;
|
|
39
|
+
needsAdminSetup(): boolean;
|
|
40
|
+
/** @deprecated Legacy data-plane key accessor. */
|
|
41
|
+
accessKey(): string | undefined;
|
|
42
|
+
initializeAdminPassword(passwordInput: string, confirmationInput: string): Promise<{
|
|
43
|
+
initialized: true;
|
|
44
|
+
adminSetupRequired: false;
|
|
45
|
+
}>;
|
|
46
|
+
/** @deprecated Kept as a clear error for integrations using the old bootstrap API. */
|
|
47
|
+
initializeAccessKey(): void;
|
|
48
|
+
verifyAdminPassword(password: string): Promise<boolean>;
|
|
49
|
+
changeAdminPassword(current: string, next: string, confirmation: string): Promise<{
|
|
50
|
+
changed: true;
|
|
51
|
+
}>;
|
|
52
|
+
authenticateApiKey(secret: string): AgentdApiKeyPrincipal | undefined;
|
|
53
|
+
listApiKeys(): AgentdApiKeyView[];
|
|
54
|
+
createApiKey(nameInput: string, scopeInput: AgentdApiKeyScope, customSecret?: string): Promise<{
|
|
55
|
+
key: AgentdApiKeyView;
|
|
56
|
+
secret: string;
|
|
57
|
+
}>;
|
|
58
|
+
updateApiKey(idInput: string, update: AgentdApiKeyUpdate): Promise<AgentdApiKeyView>;
|
|
59
|
+
deleteApiKey(idInput: string): Promise<{
|
|
60
|
+
deleted: true;
|
|
61
|
+
}>;
|
|
62
|
+
revealApiKey(idInput: string): {
|
|
63
|
+
secret: string;
|
|
64
|
+
};
|
|
65
|
+
regenerateApiKey(idInput: string): Promise<{
|
|
66
|
+
key: AgentdApiKeyView;
|
|
67
|
+
secret: string;
|
|
68
|
+
}>;
|
|
69
|
+
/** @deprecated Rotates the migrated legacy key when present. */
|
|
70
|
+
rotateAccessKey(): Promise<{
|
|
71
|
+
accessKey: string;
|
|
72
|
+
}>;
|
|
73
|
+
putWorkspaceRoots(values: string[]): Promise<AgentdControlPlaneView>;
|
|
74
|
+
putRuntimeSettings(update: AgentdRuntimeSettingsUpdate): Promise<AgentdControlPlaneView>;
|
|
75
|
+
putAgent(idInput: string, update: AgentdAgentUpdate): Promise<AgentdControlPlaneView>;
|
|
76
|
+
deleteAgent(idInput: string): Promise<AgentdControlPlaneView>;
|
|
77
|
+
private updateAgents;
|
|
78
|
+
private validateScope;
|
|
79
|
+
private requireApiKey;
|
|
80
|
+
private apiKeyView;
|
|
81
|
+
private markApiKeyUsed;
|
|
82
|
+
private rawApiKeys;
|
|
83
|
+
private readRawConfig;
|
|
84
|
+
private persist;
|
|
85
|
+
private agentView;
|
|
86
|
+
private exclusive;
|
|
87
|
+
}
|
|
88
|
+
export declare class ControlPlaneError extends Error {
|
|
89
|
+
readonly status: number;
|
|
90
|
+
constructor(status: number, message: string);
|
|
91
|
+
}
|