nexus-agentd 0.2.6 → 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.
Files changed (56) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +103 -14
  3. package/dist/a2a/runtime.d.ts +4 -1
  4. package/dist/a2a/runtime.js +213 -10
  5. package/dist/acp/runtime.js +13 -6
  6. package/dist/artifact-store.d.ts +162 -0
  7. package/dist/artifact-store.js +1081 -0
  8. package/dist/cli.js +13 -14
  9. package/dist/config.js +45 -5
  10. package/dist/control-plane-validation.d.ts +54 -0
  11. package/dist/control-plane-validation.js +152 -0
  12. package/dist/control-plane.d.ts +9 -32
  13. package/dist/control-plane.js +41 -157
  14. package/dist/drivers/index.js +12 -0
  15. package/dist/drivers/pi.js +1 -1
  16. package/dist/drivers/stdio.d.ts +4 -1
  17. package/dist/drivers/stdio.js +135 -19
  18. package/dist/events.d.ts +9 -0
  19. package/dist/events.js +17 -4
  20. package/dist/index.js +10 -1
  21. package/dist/process-tree.js +15 -6
  22. package/dist/run-store.d.ts +52 -7
  23. package/dist/run-store.js +178 -22
  24. package/dist/server/auth-policy.d.ts +27 -0
  25. package/dist/server/auth-policy.js +143 -0
  26. package/dist/server/connection.d.ts +8 -0
  27. package/dist/server/connection.js +67 -0
  28. package/dist/server/diagnostics.d.ts +40 -0
  29. package/dist/server/diagnostics.js +130 -0
  30. package/dist/server/metrics.d.ts +51 -0
  31. package/dist/server/metrics.js +56 -0
  32. package/dist/server/quota.d.ts +59 -0
  33. package/dist/server/quota.js +176 -0
  34. package/dist/server/validation.d.ts +29 -0
  35. package/dist/server/validation.js +221 -0
  36. package/dist/server.d.ts +3 -0
  37. package/dist/server.js +682 -418
  38. package/dist/session/admin-runs.d.ts +73 -0
  39. package/dist/session/admin-runs.js +297 -0
  40. package/dist/session/admission.d.ts +32 -0
  41. package/dist/session/admission.js +62 -0
  42. package/dist/session/readiness.d.ts +33 -0
  43. package/dist/session/readiness.js +174 -0
  44. package/dist/session.d.ts +58 -6
  45. package/dist/session.js +244 -109
  46. package/dist/sse-writer.d.ts +7 -0
  47. package/dist/sse-writer.js +60 -0
  48. package/dist/types.d.ts +44 -2
  49. package/dist/types.js +1 -0
  50. package/dist/webui/app/sources.js +16 -8
  51. package/dist/webui/index.js +11 -11
  52. package/dist/webui/markup.js +2 -0
  53. package/dist/webui/styles.js +46 -1
  54. package/examples/client.mjs +102 -0
  55. package/nexus-agentd.example.json +53 -34
  56. package/package.json +3 -2
@@ -0,0 +1,221 @@
1
+ /** HTTP input errors are kept separate from route and domain operations. */
2
+ export class RequestError extends Error {
3
+ status;
4
+ constructor(status, message) {
5
+ super(message);
6
+ this.status = status;
7
+ this.name = 'RequestError';
8
+ }
9
+ }
10
+ export function assertJsonContentType(request) {
11
+ const contentType = stringHeader(request.headers['content-type'])
12
+ .split(';', 1)[0]
13
+ .trim()
14
+ .toLowerCase();
15
+ if (contentType !== 'application/json') {
16
+ throw new RequestError(415, 'Request requires application/json');
17
+ }
18
+ }
19
+ export async function assertEmptyJsonBody(request, maxBytes) {
20
+ if (Number(request.headers['content-length'] || 0) <= 0)
21
+ return;
22
+ assertJsonContentType(request);
23
+ const body = await readJsonBody(request, maxBytes);
24
+ assertOnlyKeys(body, []);
25
+ }
26
+ export async function readJsonBody(request, maxBytes) {
27
+ const declared = Number(request.headers['content-length']);
28
+ if (Number.isFinite(declared) && declared > maxBytes) {
29
+ throw new RequestError(413, 'Request body is too large');
30
+ }
31
+ const chunks = [];
32
+ let total = 0;
33
+ for await (const value of request) {
34
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
35
+ total += chunk.length;
36
+ if (total > maxBytes)
37
+ throw new RequestError(413, 'Request body is too large');
38
+ chunks.push(chunk);
39
+ }
40
+ const raw = Buffer.concat(chunks).toString('utf8');
41
+ if (!raw.trim())
42
+ return {};
43
+ try {
44
+ const parsed = JSON.parse(raw);
45
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
46
+ throw new Error('body must be an object');
47
+ }
48
+ return parsed;
49
+ }
50
+ catch (error) {
51
+ throw new RequestError(400, `Invalid JSON body: ${errorMessage(error)}`);
52
+ }
53
+ }
54
+ export async function readBytesBody(request, maxBytes) {
55
+ const declared = Number(request.headers['content-length']);
56
+ if (Number.isFinite(declared) && declared > maxBytes) {
57
+ throw new RequestError(413, 'Request body is too large');
58
+ }
59
+ const chunks = [];
60
+ let total = 0;
61
+ for await (const value of request) {
62
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
63
+ total += chunk.length;
64
+ if (total > maxBytes)
65
+ throw new RequestError(413, 'Request body is too large');
66
+ chunks.push(chunk);
67
+ }
68
+ return Buffer.concat(chunks);
69
+ }
70
+ export function assertOnlyKeys(body, allowed) {
71
+ const accepted = new Set(allowed);
72
+ const unknown = Object.keys(body).filter((key) => !accepted.has(key));
73
+ if (unknown.length) {
74
+ throw new RequestError(400, `Unsupported request fields: ${unknown.join(', ')}`);
75
+ }
76
+ }
77
+ export function requiredString(value, name) {
78
+ const text = typeof value === 'string' ? value.trim() : '';
79
+ if (!text)
80
+ throw new RequestError(400, `${name} is required`);
81
+ return text;
82
+ }
83
+ export function requiredRawString(value, name) {
84
+ if (typeof value !== 'string' || !value)
85
+ throw new RequestError(400, `${name} is required`);
86
+ return value;
87
+ }
88
+ export function requiredMessage(value) {
89
+ if (typeof value !== 'string' || !value.trim()) {
90
+ throw new RequestError(400, 'message is required');
91
+ }
92
+ return value;
93
+ }
94
+ export function requiredInteger(value, name) {
95
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
96
+ throw new RequestError(400, `${name} must be an integer`);
97
+ }
98
+ return value;
99
+ }
100
+ export function optionalAttachmentIds(value) {
101
+ if (value === undefined)
102
+ return [];
103
+ if (!Array.isArray(value) || value.length > 16) {
104
+ throw new RequestError(400, 'attachments must be an array with at most 16 ids');
105
+ }
106
+ return value.map((item) => requiredString(item, 'attachments'));
107
+ }
108
+ export function normalizePublishPaths(body) {
109
+ const bodyPaths = body.paths;
110
+ if (bodyPaths !== undefined) {
111
+ if (!Array.isArray(bodyPaths) || bodyPaths.length === 0) {
112
+ throw new RequestError(400, 'paths must be a non-empty array of strings');
113
+ }
114
+ if (bodyPaths.length > 32) {
115
+ throw new RequestError(400, 'paths accepts at most 32 entries');
116
+ }
117
+ return bodyPaths.map((item) => requiredString(item, 'paths'));
118
+ }
119
+ return [requiredString(body.path, 'path')];
120
+ }
121
+ export function normalizedMediaType(value) {
122
+ const mediaType = stringHeader(value).split(';', 1)[0].trim().toLowerCase();
123
+ return mediaType || undefined;
124
+ }
125
+ export function decodeHeader(value) {
126
+ const raw = stringHeader(value);
127
+ if (!raw)
128
+ return '';
129
+ try {
130
+ return decodeURIComponent(raw);
131
+ }
132
+ catch {
133
+ return raw;
134
+ }
135
+ }
136
+ export function cleanQuery(value) {
137
+ const text = String(value || '').trim();
138
+ return text || undefined;
139
+ }
140
+ export function boundedLimit(value) {
141
+ if (!value)
142
+ return undefined;
143
+ const number = Number(value);
144
+ if (!Number.isInteger(number) || number < 1 || number > 200) {
145
+ throw new RequestError(400, 'limit must be an integer between 1 and 200');
146
+ }
147
+ return number;
148
+ }
149
+ export function optionalRunState(value) {
150
+ if (!value)
151
+ return undefined;
152
+ const states = new Set([
153
+ 'created',
154
+ 'running',
155
+ 'input_required',
156
+ 'permission_required',
157
+ 'completed',
158
+ 'failed',
159
+ 'canceled'
160
+ ]);
161
+ if (!states.has(value))
162
+ throw new RequestError(400, 'Invalid run state');
163
+ return value;
164
+ }
165
+ export function optionalRawString(value, name) {
166
+ if (value === undefined || value === null || value === '')
167
+ return undefined;
168
+ if (typeof value !== 'string')
169
+ throw new RequestError(400, `${name} must be a string`);
170
+ return value;
171
+ }
172
+ export function optionalString(value) {
173
+ if (value === undefined || value === null)
174
+ return undefined;
175
+ if (typeof value !== 'string')
176
+ throw new RequestError(400, 'Expected a string');
177
+ return value.trim() || undefined;
178
+ }
179
+ export function optionalPendingAction(value) {
180
+ if (value === undefined || value === null || value === '')
181
+ return undefined;
182
+ if (value === 'accept' || value === 'decline' || value === 'cancel') {
183
+ return value;
184
+ }
185
+ throw new RequestError(400, 'action must be accept, decline, or cancel');
186
+ }
187
+ export function optionalBoolean(value, name) {
188
+ if (value === undefined)
189
+ return undefined;
190
+ if (typeof value !== 'boolean')
191
+ throw new RequestError(400, `${name} must be boolean`);
192
+ return value;
193
+ }
194
+ export function optionalNumber(value, name) {
195
+ if (value === undefined)
196
+ return undefined;
197
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
198
+ throw new RequestError(400, `${name} must be an integer`);
199
+ }
200
+ return value;
201
+ }
202
+ export function requiredStringArray(value, name, allowEmpty = false) {
203
+ if (!Array.isArray(value))
204
+ throw new RequestError(400, `${name} must be an array of strings`);
205
+ const result = value.map((item) => {
206
+ if (typeof item !== 'string' || !item.trim()) {
207
+ throw new RequestError(400, `${name} must contain non-empty strings`);
208
+ }
209
+ return item.trim();
210
+ });
211
+ if (!allowEmpty && !result.length) {
212
+ throw new RequestError(400, `${name} must contain at least one value`);
213
+ }
214
+ return result;
215
+ }
216
+ function stringHeader(value) {
217
+ return Array.isArray(value) ? value[0] || '' : value || '';
218
+ }
219
+ function errorMessage(error) {
220
+ return error instanceof Error ? error.message : String(error);
221
+ }
package/dist/server.d.ts CHANGED
@@ -2,4 +2,7 @@ import http from 'node:http';
2
2
  import { type AgentdControlPlane } from './control-plane.js';
3
3
  import { SessionManager } from './session.js';
4
4
  import type { AgentdConfig } from './types.js';
5
+ export { QuotaExceededError, QuotaManager, quotaLimitsFromConfig } from './server/quota.js';
6
+ export { DiagnosticRunner, DiagnosticConcurrencyError } from './server/diagnostics.js';
7
+ export { GatewayMetrics } from './server/metrics.js';
5
8
  export declare function createAgentdServer(config: AgentdConfig, sessions: SessionManager, controlPlane?: AgentdControlPlane): http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;