@xtrape/capsule-agent-node 0.4.0 → 0.5.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/NOTICE +3 -3
- package/README.md +58 -46
- package/dist/index.cjs +158 -16
- package/dist/index.d.cts +105 -17
- package/dist/index.d.ts +105 -17
- package/dist/index.js +159 -15
- package/package.json +6 -5
package/NOTICE
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Copyright 2026 Xtrape
|
|
1
|
+
Xtrape Embedded Agent SDK for Node.js
|
|
2
|
+
Copyright 2026 Xtrape contributors
|
|
3
3
|
|
|
4
|
-
This product includes software developed by the Xtrape
|
|
4
|
+
This product includes software developed by the Xtrape contributors.
|
package/README.md
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Xtrape Embedded Agent SDK for Node.js
|
|
2
2
|
|
|
3
|
-
> Node.js
|
|
3
|
+
> Node.js Embedded Agent SDK for connecting Workers to the Xtrape control plane.
|
|
4
|
+
|
|
5
|
+
The npm name `@xtrape/capsule-agent-node`, the `CapsuleAgent` export, and some
|
|
6
|
+
existing wire fields are legacy compatibility identifiers. `Capsule` is not a
|
|
7
|
+
current Xtrape concept. New integrations should use `XtrapeAgent` and the
|
|
8
|
+
`worker` option.
|
|
4
9
|
|
|
5
10
|
[](./LICENSE)
|
|
6
11
|
[](https://xtrape-com.github.io/xtrape-capsule-site/)
|
|
7
12
|
[](https://xtrape-com.github.io/xtrape-capsule-site/agents/node-embedded-agent)
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
The package supports the Node.js Embedded Agent mode for one Worker. Multi-Worker
|
|
15
|
+
ownership belongs to a sidecar or external Agent. Gateway, when present, routes
|
|
16
|
+
Agent/control-plane traffic and does not replace the Agent role.
|
|
10
17
|
|
|
11
|
-
> **Package status:** Xtrape
|
|
18
|
+
> **Package status:** Xtrape is currently in **Public Review** before
|
|
12
19
|
> the `v0.1.0 Public Preview` release. This package is published under the
|
|
13
20
|
> `public-review` dist-tag. APIs, contracts, deployment instructions, and SDK
|
|
14
21
|
> interfaces may still change.
|
|
@@ -36,32 +43,32 @@ pnpm build
|
|
|
36
43
|
|
|
37
44
|
## Minimal Example
|
|
38
45
|
|
|
39
|
-
This example
|
|
46
|
+
This example uses the canonical alias `XtrapeAgent`, configures
|
|
40
47
|
providers with `.health()` / `.configs()`, register Actions with `.action()`,
|
|
41
48
|
then call `start()`.
|
|
42
49
|
|
|
43
50
|
```ts
|
|
44
|
-
import {
|
|
51
|
+
import { XtrapeAgent } from "@xtrape/capsule-agent-node";
|
|
45
52
|
|
|
46
|
-
const agent = new
|
|
53
|
+
const agent = new XtrapeAgent({
|
|
47
54
|
backendUrl: process.env.OPSTAGE_BACKEND_URL ?? "http://localhost:8080",
|
|
48
55
|
registrationToken: process.env.OPSTAGE_REGISTRATION_TOKEN,
|
|
49
56
|
tokenStore: { file: "./data/agent-token.txt" },
|
|
50
|
-
// Optional. If omitted, the SDK derives
|
|
51
|
-
// Provide it explicitly when one Agent owns multiple
|
|
57
|
+
// Optional. If omitted, the SDK derives Agent identity from `worker`.
|
|
58
|
+
// Provide it explicitly when one Agent owns multiple Workers on
|
|
52
59
|
// the same host, or when you want a stable agent code that survives
|
|
53
60
|
// service renames.
|
|
54
61
|
agent: {
|
|
55
|
-
code: "hello-
|
|
56
|
-
name: "Hello
|
|
62
|
+
code: "hello-worker-agent",
|
|
63
|
+
name: "Hello Worker Agent",
|
|
57
64
|
runtime: "nodejs",
|
|
58
65
|
},
|
|
59
|
-
|
|
60
|
-
code: "hello-
|
|
61
|
-
name: "Hello
|
|
66
|
+
worker: {
|
|
67
|
+
code: "hello-worker",
|
|
68
|
+
name: "Hello Worker",
|
|
62
69
|
version: "0.1.0",
|
|
63
70
|
runtime: "nodejs",
|
|
64
|
-
description: "Minimal
|
|
71
|
+
description: "Minimal Worker example",
|
|
65
72
|
},
|
|
66
73
|
});
|
|
67
74
|
|
|
@@ -112,10 +119,12 @@ await agent.start();
|
|
|
112
119
|
|
|
113
120
|
## How Registration Works
|
|
114
121
|
|
|
115
|
-
1. An operator creates a single-use
|
|
116
|
-
|
|
122
|
+
1. An operator creates a single-use registration credential in Panel CE (the
|
|
123
|
+
current API calls it a Registration Token).
|
|
124
|
+
2. The Worker starts with `registrationToken` and calls the Agent registration
|
|
117
125
|
API.
|
|
118
|
-
3.
|
|
126
|
+
3. The control plane returns an Agent ID and Agent credential (currently named
|
|
127
|
+
an Agent token).
|
|
119
128
|
4. The SDK stores the issued credentials in `tokenStore.file` as `<agentId>:<agentToken>`.
|
|
120
129
|
5. Future restarts reuse the stored Agent token; the Registration Token is not
|
|
121
130
|
needed again.
|
|
@@ -123,14 +132,15 @@ await agent.start();
|
|
|
123
132
|
If the token file is lost or the Agent is revoked, create a new Registration
|
|
124
133
|
Token and restart the service with it.
|
|
125
134
|
|
|
126
|
-
##
|
|
135
|
+
## Worker Manifest
|
|
127
136
|
|
|
128
|
-
The `
|
|
137
|
+
The `worker` option describes the Worker reported to the control plane. The old
|
|
138
|
+
`service` option remains accepted for compatibility:
|
|
129
139
|
|
|
130
140
|
```ts
|
|
131
|
-
|
|
132
|
-
code: "hello-
|
|
133
|
-
name: "Hello
|
|
141
|
+
worker: {
|
|
142
|
+
code: "hello-worker", // stable unique Worker code
|
|
143
|
+
name: "Hello Worker", // operator-facing display name
|
|
134
144
|
description: "...", // optional
|
|
135
145
|
version: "0.1.0", // service version
|
|
136
146
|
runtime: "nodejs", // nodejs | java | python | go | other
|
|
@@ -138,8 +148,9 @@ service: {
|
|
|
138
148
|
}
|
|
139
149
|
```
|
|
140
150
|
|
|
141
|
-
The SDK wraps this as a
|
|
142
|
-
`schemaVersion: "1.0"`, and `agentMode: "embedded"`.
|
|
151
|
+
The SDK wraps this as a Worker manifest with `kind: "Worker"`,
|
|
152
|
+
`schemaVersion: "1.0"`, and `agentMode: "embedded"`. Existing Panel versions
|
|
153
|
+
continue accepting it through the legacy reported-service wire envelope.
|
|
143
154
|
|
|
144
155
|
## Health Reporting
|
|
145
156
|
|
|
@@ -153,13 +164,13 @@ agent.health(async () => ({
|
|
|
153
164
|
}));
|
|
154
165
|
```
|
|
155
166
|
|
|
156
|
-
The provider runs for heartbeats and
|
|
167
|
+
The provider runs for heartbeats and Worker Runtime Reports. Do not include secrets in
|
|
157
168
|
`details`.
|
|
158
169
|
|
|
159
170
|
Agent health providers return protocol-level `HealthStatus` values: `UP`,
|
|
160
171
|
`DEGRADED`, `DOWN`, `UNKNOWN`.
|
|
161
172
|
|
|
162
|
-
|
|
173
|
+
Xtrape Panel may derive an operator-facing `effectiveStatus`: `HEALTHY`, `UNHEALTHY`,
|
|
163
174
|
`STALE`, `OFFLINE`.
|
|
164
175
|
|
|
165
176
|
## Config Reporting
|
|
@@ -186,8 +197,9 @@ agent.configs(() => [
|
|
|
186
197
|
]);
|
|
187
198
|
```
|
|
188
199
|
|
|
189
|
-
|
|
190
|
-
|
|
200
|
+
Config Definitions and safe observations are reported to Panel for visibility.
|
|
201
|
+
The Worker or deployment platform remains the configuration owner; current CE
|
|
202
|
+
does not push arbitrary values into the Worker.
|
|
191
203
|
|
|
192
204
|
## Actions
|
|
193
205
|
|
|
@@ -207,12 +219,12 @@ agent.action({
|
|
|
207
219
|
});
|
|
208
220
|
```
|
|
209
221
|
|
|
210
|
-
Action metadata is published in the
|
|
222
|
+
Action metadata is published in the Worker Runtime Report. The SDK intentionally strips
|
|
211
223
|
runtime-only handler functions before reporting the Action Catalog.
|
|
212
224
|
|
|
213
225
|
## Action Prepare / Execute
|
|
214
226
|
|
|
215
|
-
|
|
227
|
+
Xtrape Panel uses two Command types for actions:
|
|
216
228
|
|
|
217
229
|
1. `ACTION_PREPARE` — created when the UI opens an Action panel. The SDK calls
|
|
218
230
|
`prepare()` if present and returns dynamic form metadata such as
|
|
@@ -229,8 +241,8 @@ The embedded Agent starts three loops by default:
|
|
|
229
241
|
|
|
230
242
|
| Loop | Default | Purpose |
|
|
231
243
|
| -------------- | ---------: | ---------------------------------- |
|
|
232
|
-
| Heartbeat | 30 seconds | Agent liveness
|
|
233
|
-
|
|
|
244
|
+
| Heartbeat | 30 seconds | Agent liveness |
|
|
245
|
+
| Worker report | 60 seconds | Manifest, config observations, Actions, health |
|
|
234
246
|
| Command poll | 5 seconds | Fetch and execute pending Commands |
|
|
235
247
|
|
|
236
248
|
You can override intervals:
|
|
@@ -240,7 +252,7 @@ new CapsuleAgent({
|
|
|
240
252
|
// ...
|
|
241
253
|
intervals: {
|
|
242
254
|
heartbeatMs: 30_000,
|
|
243
|
-
|
|
255
|
+
workerReportMs: 60_000,
|
|
244
256
|
commandPollMs: 5_000,
|
|
245
257
|
},
|
|
246
258
|
});
|
|
@@ -265,7 +277,7 @@ Security recommendations:
|
|
|
265
277
|
|
|
266
278
|
- chmod the containing directory so only the service user can read it;
|
|
267
279
|
- never commit token files;
|
|
268
|
-
- rotate by revoking the Agent in
|
|
280
|
+
- rotate by revoking the Agent in Xtrape Panel and registering a new one;
|
|
269
281
|
- prefer secret managers for production wrappers when available.
|
|
270
282
|
|
|
271
283
|
## Security Notes
|
|
@@ -342,7 +354,7 @@ try {
|
|
|
342
354
|
process.exit(65);
|
|
343
355
|
}
|
|
344
356
|
if (err instanceof NetworkError) {
|
|
345
|
-
console.error("could not reach
|
|
357
|
+
console.error("could not reach Xtrape Panel:", err.message);
|
|
346
358
|
process.exit(75);
|
|
347
359
|
}
|
|
348
360
|
throw err;
|
|
@@ -390,7 +402,7 @@ and `logger` are configured, `structuredLogger` wins.
|
|
|
390
402
|
|
|
391
403
|
| Package | Compatible with |
|
|
392
404
|
| ---------------------------------- | ------------------------------------------------------------- |
|
|
393
|
-
| `@xtrape/capsule-agent-node@0.1.x` |
|
|
405
|
+
| `@xtrape/capsule-agent-node@0.1.x` | Xtrape Panel CE `0.1.x` and `@xtrape/capsule-contracts-node@0.1.x` |
|
|
394
406
|
|
|
395
407
|
Use matching minor versions across CE, Agent SDK, and Contracts during Public
|
|
396
408
|
Review and Public Preview. The wire protocol may still change before `v1.0`.
|
|
@@ -405,16 +417,16 @@ The token is single-use and short-lived.
|
|
|
405
417
|
(registration tokens flip to `USED` on first use).
|
|
406
418
|
- Check the token has not expired. Operators set `expiresInSeconds` when
|
|
407
419
|
creating the token; the default in CE is short.
|
|
408
|
-
- Check the token has not been revoked from the
|
|
420
|
+
- Check the token has not been revoked from the Xtrape Panel console.
|
|
409
421
|
- Re-create a fresh token in the console and start the agent with it.
|
|
410
422
|
|
|
411
423
|
### Agent reports `ECONNREFUSED` / cannot reach backend
|
|
412
424
|
|
|
413
425
|
- Confirm `OPSTAGE_BACKEND_URL` resolves from inside the container or host
|
|
414
426
|
where the agent runs. The default of `http://localhost:8080` only works if
|
|
415
|
-
the agent runs on the same host as
|
|
427
|
+
the agent runs on the same host as Xtrape Panel.
|
|
416
428
|
- If you sit behind a reverse proxy, point `backendUrl` at the proxy URL —
|
|
417
|
-
not the internal
|
|
429
|
+
not the internal Xtrape Panel container address.
|
|
418
430
|
- Outbound HTTPS through corporate proxies: respect `HTTPS_PROXY` /
|
|
419
431
|
`NO_PROXY` env vars (`undici` honors them via `setGlobalDispatcher`).
|
|
420
432
|
|
|
@@ -427,7 +439,7 @@ The token is single-use and short-lived.
|
|
|
427
439
|
file exists but contains anything else, delete it and re-register with a
|
|
428
440
|
fresh registration token.
|
|
429
441
|
- Token revocation: an operator may have revoked the agent or the agent
|
|
430
|
-
token from the console. Inspect the agent's status in
|
|
442
|
+
token from the console. Inspect the agent's status in Xtrape Panel; if it shows
|
|
431
443
|
`REVOKED` or `DISABLED`, that's the cause.
|
|
432
444
|
|
|
433
445
|
### Heartbeats succeed but action results never arrive
|
|
@@ -446,7 +458,7 @@ The token is single-use and short-lived.
|
|
|
446
458
|
- Site: https://xtrape-com.github.io/xtrape-capsule-site/
|
|
447
459
|
- Node embedded Agent guide: https://xtrape-com.github.io/xtrape-capsule-site/agents/node-embedded-agent
|
|
448
460
|
- Action model: https://xtrape-com.github.io/xtrape-capsule-site/agents/action-model
|
|
449
|
-
-
|
|
461
|
+
- Xtrape Panel CE: https://xtrape-com.github.io/xtrape-capsule-site/opstage-ce/overview
|
|
450
462
|
|
|
451
463
|
## Contributing
|
|
452
464
|
|
|
@@ -456,10 +468,10 @@ safety guidance.
|
|
|
456
468
|
|
|
457
469
|
## License
|
|
458
470
|
|
|
459
|
-
Apache-2.0. "Xtrape"
|
|
471
|
+
Apache-2.0. "Xtrape" and "Opstage" are trademarks of their
|
|
460
472
|
respective owners; the open-source license does not grant trademark rights.
|
|
461
473
|
|
|
462
|
-
## v0.4 Experimental
|
|
474
|
+
## v0.4 Experimental legacy event routing Hook
|
|
463
475
|
|
|
464
476
|
The SDK exposes a minimal experimental publish hook into CE's built-in SQLite-backed in-process event-to-command router:
|
|
465
477
|
|
|
@@ -470,7 +482,7 @@ await agent.publishBusEvent({
|
|
|
470
482
|
});
|
|
471
483
|
```
|
|
472
484
|
|
|
473
|
-
The hook posts to
|
|
485
|
+
The hook posts to Xtrape Panel CE as the registered embedded agent and defaults `sourceServiceCode` to the configured service code.
|
|
474
486
|
|
|
475
487
|
Typed errors for Bus failure modes:
|
|
476
488
|
|
|
@@ -478,4 +490,4 @@ Typed errors for Bus failure modes:
|
|
|
478
490
|
- `BusRateLimitedError` — `BUS_RATE_LIMITED` (429)
|
|
479
491
|
- `BusDepthExceededError` — `BUS_DEPTH_EXCEEDED` (422)
|
|
480
492
|
|
|
481
|
-
|
|
493
|
+
legacy event routing is experimental in v0.4: not a standalone Bus Server, external broker, workflow engine, or service mesh API.
|
package/dist/index.cjs
CHANGED
|
@@ -27,9 +27,11 @@ __export(index_exports, {
|
|
|
27
27
|
BusDisabledError: () => BusDisabledError,
|
|
28
28
|
BusRateLimitedError: () => BusRateLimitedError,
|
|
29
29
|
CapsuleAgent: () => CapsuleAgent,
|
|
30
|
+
CeServiceAgent: () => CeServiceAgent,
|
|
30
31
|
FileTokenStore: () => FileTokenStore,
|
|
31
32
|
NetworkError: () => NetworkError,
|
|
32
|
-
RegistrationError: () => RegistrationError
|
|
33
|
+
RegistrationError: () => RegistrationError,
|
|
34
|
+
XtrapeAgent: () => CapsuleAgent
|
|
33
35
|
});
|
|
34
36
|
module.exports = __toCommonJS(index_exports);
|
|
35
37
|
|
|
@@ -136,13 +138,22 @@ var AgentApiClient = class {
|
|
|
136
138
|
reportServices(agentId, token, req) {
|
|
137
139
|
return this.request(`/api/agents/${agentId}/services/report`, { method: "POST", token, body: JSON.stringify(req) });
|
|
138
140
|
}
|
|
141
|
+
/** Report Workers through the canonical endpoint, with a legacy fallback. */
|
|
142
|
+
async reportWorkers(agentId, token, req) {
|
|
143
|
+
try {
|
|
144
|
+
return await this.request(`/api/agents/${agentId}/workers/report`, { method: "POST", token, body: JSON.stringify(req) });
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (!(error instanceof AgentApiError) || error.status !== 404) throw error;
|
|
147
|
+
return this.reportServices(agentId, token, { services: req.workers });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
139
150
|
pollCommands(agentId, token) {
|
|
140
151
|
return this.request(`/api/agents/${agentId}/commands`, { method: "GET", token });
|
|
141
152
|
}
|
|
142
153
|
reportResult(agentId, token, commandId, req) {
|
|
143
154
|
return this.request(`/api/agents/${agentId}/commands/${commandId}/result`, { method: "POST", token, body: JSON.stringify(req) });
|
|
144
155
|
}
|
|
145
|
-
/**
|
|
156
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
146
157
|
publishBusEvent(agentId, token, req) {
|
|
147
158
|
return this.request(`/api/agents/${agentId}/bus/events`, { method: "POST", token, body: JSON.stringify({ ...req, experimental: req.experimental ?? "v0.4-experimental" }) });
|
|
148
159
|
}
|
|
@@ -215,6 +226,12 @@ function emitLog(consoleSink, structured, level, message, fields) {
|
|
|
215
226
|
var CapsuleAgent = class {
|
|
216
227
|
constructor(options) {
|
|
217
228
|
this.options = options;
|
|
229
|
+
if (options.worker && options.service) {
|
|
230
|
+
throw new Error("Agent options accept `worker` or legacy `service`, not both.");
|
|
231
|
+
}
|
|
232
|
+
if (!options.worker && !options.service) {
|
|
233
|
+
throw new Error("Agent options require `worker` (or legacy `service`).");
|
|
234
|
+
}
|
|
218
235
|
this.client = new AgentApiClient(options.backendUrl);
|
|
219
236
|
this.store = new FileTokenStore(options.tokenStore?.file);
|
|
220
237
|
this.logger = options.logger ?? console;
|
|
@@ -248,7 +265,7 @@ var CapsuleAgent = class {
|
|
|
248
265
|
this.stopped = false;
|
|
249
266
|
try {
|
|
250
267
|
await this.ensureRegistered();
|
|
251
|
-
await this.
|
|
268
|
+
await this.reportWorker();
|
|
252
269
|
await this.heartbeat();
|
|
253
270
|
await this.pollOnce();
|
|
254
271
|
} catch (e) {
|
|
@@ -257,7 +274,7 @@ var CapsuleAgent = class {
|
|
|
257
274
|
}
|
|
258
275
|
if (this.options.autoStartLoops !== false) {
|
|
259
276
|
this.loop("heartbeat", this.options.intervals?.heartbeatMs ?? (this.options.heartbeatIntervalSeconds ?? 30) * 1e3, () => this.heartbeat());
|
|
260
|
-
this.loop("
|
|
277
|
+
this.loop("worker-report", this.options.intervals?.workerReportMs ?? this.options.intervals?.serviceReportMs ?? 6e4, () => this.reportWorker());
|
|
261
278
|
this.loop("command-poll", this.options.intervals?.commandPollMs ?? (this.options.commandPollIntervalSeconds ?? 5) * 1e3, () => this.pollOnce());
|
|
262
279
|
}
|
|
263
280
|
}
|
|
@@ -286,38 +303,45 @@ var CapsuleAgent = class {
|
|
|
286
303
|
if (this.agentId) return;
|
|
287
304
|
}
|
|
288
305
|
if (!this.options.registrationToken) throw new Error("OPSTAGE registration token is required for first registration");
|
|
289
|
-
const
|
|
306
|
+
const worker = this.workerDefinition();
|
|
307
|
+
const agent = this.options.agent ?? { code: worker.code, name: worker.name, runtime: worker.runtime };
|
|
290
308
|
const res = await this.retry(() => this.client.register({ registrationToken: this.options.registrationToken, agent: { code: agent.code, name: agent.name, mode: "embedded", runtime: agent.runtime ?? "nodejs" }, service: this.serviceSnapshot() }));
|
|
291
309
|
this.agentId = res.agentId;
|
|
292
310
|
this.token = res.agentToken;
|
|
293
311
|
await this.store.save(`${this.agentId}:${this.token}`);
|
|
294
312
|
this.log("info", `registered agent ${this.agentId}`);
|
|
295
313
|
}
|
|
314
|
+
workerDefinition() {
|
|
315
|
+
if (this.options.worker) {
|
|
316
|
+
return this.options.worker;
|
|
317
|
+
}
|
|
318
|
+
return this.options.service;
|
|
319
|
+
}
|
|
296
320
|
serviceSnapshot() {
|
|
297
|
-
const
|
|
298
|
-
|
|
321
|
+
const worker = this.workerDefinition();
|
|
322
|
+
const manifest = { kind: "Worker", schemaVersion: "1.0", code: worker.code, name: worker.name, description: worker.description, version: worker.version, runtime: worker.runtime, agentMode: "embedded", ...worker.manifest ?? {} };
|
|
323
|
+
return { code: worker.code, name: worker.name, description: worker.description, version: worker.version, runtime: worker.runtime, manifest, actions: [...this.actions.values()].map(({ handler, prepare, inputSchema, outputSchema, ...a }) => a) };
|
|
299
324
|
}
|
|
300
325
|
async runHealth() {
|
|
301
326
|
return this.healthProvider();
|
|
302
327
|
}
|
|
303
|
-
/**
|
|
328
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
304
329
|
async publishBusEvent(input) {
|
|
305
|
-
if (!this.agentId || !this.token) throw new Error("Agent must be started before publishing
|
|
306
|
-
const sourceServiceCode = input.sourceServiceCode ?? this.
|
|
330
|
+
if (!this.agentId || !this.token) throw new Error("Agent must be started before publishing routed events.");
|
|
331
|
+
const sourceServiceCode = input.sourceServiceCode ?? this.workerDefinition().code;
|
|
307
332
|
const result = await this.retry(() => this.client.publishBusEvent(this.agentId, this.token, { ...input, sourceServiceCode }));
|
|
308
333
|
this.log("info", "experimental bus event published", { eventId: result.eventId, eventType: input.eventType, sourceServiceCode, routedCommands: result.routedCommands.length });
|
|
309
334
|
return result;
|
|
310
335
|
}
|
|
311
|
-
async
|
|
336
|
+
async reportWorker() {
|
|
312
337
|
if (!this.agentId || !this.token) return;
|
|
313
338
|
const [health, configs] = await Promise.all([this.runHealth(), this.configProvider()]);
|
|
314
|
-
const
|
|
315
|
-
await this.retry(() => this.client.
|
|
339
|
+
const worker = { ...this.serviceSnapshot(), health, configs };
|
|
340
|
+
await this.retry(() => this.client.reportWorkers(this.agentId, this.token, { workers: [worker] }));
|
|
316
341
|
}
|
|
317
342
|
async heartbeat() {
|
|
318
343
|
if (!this.agentId || !this.token) return;
|
|
319
|
-
|
|
320
|
-
await this.retry(() => this.client.heartbeat(this.agentId, this.token, { health }));
|
|
344
|
+
await this.retry(() => this.client.heartbeat(this.agentId, this.token, {}));
|
|
321
345
|
}
|
|
322
346
|
async pollOnce() {
|
|
323
347
|
if (!this.agentId || !this.token) return;
|
|
@@ -403,6 +427,122 @@ var CapsuleAgent = class {
|
|
|
403
427
|
return { value: obj };
|
|
404
428
|
}
|
|
405
429
|
};
|
|
430
|
+
|
|
431
|
+
// src/ce/index.ts
|
|
432
|
+
var import_node_crypto = require("crypto");
|
|
433
|
+
var import_capsule_contracts_node = require("@xtrape/capsule-contracts-node");
|
|
434
|
+
var CeServiceAgent = class {
|
|
435
|
+
serviceId;
|
|
436
|
+
instanceId;
|
|
437
|
+
serverUrl;
|
|
438
|
+
manifest;
|
|
439
|
+
address;
|
|
440
|
+
tenant;
|
|
441
|
+
fetchImpl;
|
|
442
|
+
health = "HEALTHY";
|
|
443
|
+
hbTimer;
|
|
444
|
+
constructor(opts) {
|
|
445
|
+
this.serverUrl = opts.serverUrl.replace(/\/+$/, "");
|
|
446
|
+
this.manifest = opts.manifest;
|
|
447
|
+
this.address = opts.address;
|
|
448
|
+
this.tenant = opts.manifest.tenant ?? "default";
|
|
449
|
+
this.serviceId = opts.manifest.service.id;
|
|
450
|
+
this.instanceId = opts.instanceId ?? `${this.serviceId}-${(0, import_node_crypto.randomUUID)().slice(0, 8)}`;
|
|
451
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
452
|
+
}
|
|
453
|
+
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
454
|
+
instance() {
|
|
455
|
+
return import_capsule_contracts_node.ServiceInstanceSchema.parse({
|
|
456
|
+
contractVersion: import_capsule_contracts_node.CE_CONTRACT_VERSION,
|
|
457
|
+
serviceId: this.serviceId,
|
|
458
|
+
instanceId: this.instanceId,
|
|
459
|
+
tenant: this.tenant,
|
|
460
|
+
address: this.address,
|
|
461
|
+
version: this.manifest.service.version,
|
|
462
|
+
health: this.health
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
/** POST /v1/instances { manifest, instance } */
|
|
466
|
+
async register() {
|
|
467
|
+
await this.post("/v1/instances", { manifest: this.manifest, instance: this.instance() });
|
|
468
|
+
return { instanceId: this.instanceId };
|
|
469
|
+
}
|
|
470
|
+
setHealth(h) {
|
|
471
|
+
this.health = h;
|
|
472
|
+
}
|
|
473
|
+
/** POST /v1/instances/{id}/heartbeat */
|
|
474
|
+
async sendHeartbeat(metricsSummary) {
|
|
475
|
+
const hb = {
|
|
476
|
+
contractVersion: import_capsule_contracts_node.CE_CONTRACT_VERSION,
|
|
477
|
+
serviceId: this.serviceId,
|
|
478
|
+
instanceId: this.instanceId,
|
|
479
|
+
tenant: this.tenant,
|
|
480
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
481
|
+
health: this.health,
|
|
482
|
+
...metricsSummary ? { metricsSummary } : {}
|
|
483
|
+
};
|
|
484
|
+
await this.post(`/v1/instances/${encodeURIComponent(this.instanceId)}/heartbeat`, hb);
|
|
485
|
+
}
|
|
486
|
+
/** Start a periodic heartbeat; returns a stop function. Timer is unref'd. */
|
|
487
|
+
startHeartbeat(intervalMs = 1e4, metrics) {
|
|
488
|
+
this.stopHeartbeat();
|
|
489
|
+
this.hbTimer = setInterval(() => {
|
|
490
|
+
void this.sendHeartbeat(metrics?.()).catch(() => {
|
|
491
|
+
});
|
|
492
|
+
}, intervalMs);
|
|
493
|
+
this.hbTimer.unref?.();
|
|
494
|
+
return () => this.stopHeartbeat();
|
|
495
|
+
}
|
|
496
|
+
stopHeartbeat() {
|
|
497
|
+
if (this.hbTimer) {
|
|
498
|
+
clearInterval(this.hbTimer);
|
|
499
|
+
this.hbTimer = void 0;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/** DELETE /v1/instances/{id} — graceful deregister. */
|
|
503
|
+
async deregister() {
|
|
504
|
+
await this.fetchJson(`/v1/instances/${encodeURIComponent(this.instanceId)}`, { method: "DELETE" });
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Validate an inbound message, run the handler, and build the reply message
|
|
508
|
+
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
509
|
+
*/
|
|
510
|
+
async handleMessage(raw, handler) {
|
|
511
|
+
const msg = import_capsule_contracts_node.ConversationMessageSchema.parse(raw);
|
|
512
|
+
const out = await handler(msg);
|
|
513
|
+
const reply = typeof out === "string" ? { content: out } : out;
|
|
514
|
+
return import_capsule_contracts_node.ConversationMessageSchema.parse({
|
|
515
|
+
contractVersion: import_capsule_contracts_node.CE_CONTRACT_VERSION,
|
|
516
|
+
messageId: `msg_${(0, import_node_crypto.randomUUID)().slice(0, 12)}`,
|
|
517
|
+
conversationId: msg.conversationId,
|
|
518
|
+
tenant: msg.tenant ?? "default",
|
|
519
|
+
// reply back to whoever sent it (USER/SERVICE/SYSTEM are all valid targets)
|
|
520
|
+
targetType: msg.senderType,
|
|
521
|
+
targetId: msg.senderId,
|
|
522
|
+
senderType: "SERVICE",
|
|
523
|
+
senderId: this.serviceId,
|
|
524
|
+
messageType: reply.messageType ?? "SUMMARY",
|
|
525
|
+
content: reply.content,
|
|
526
|
+
replyToMessageId: msg.messageId,
|
|
527
|
+
...reply.payload ? { payload: reply.payload } : {}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
post(path, body) {
|
|
531
|
+
return this.fetchJson(path, { method: "POST", body: JSON.stringify(body) });
|
|
532
|
+
}
|
|
533
|
+
async fetchJson(path, init) {
|
|
534
|
+
const res = await this.fetchImpl(this.serverUrl + path, {
|
|
535
|
+
...init,
|
|
536
|
+
headers: { "content-type": "application/json", ...init.headers ?? {} }
|
|
537
|
+
});
|
|
538
|
+
const text = await res.text();
|
|
539
|
+
const data = text ? JSON.parse(text) : void 0;
|
|
540
|
+
if (!res.ok) {
|
|
541
|
+
throw new Error(`server-ce ${path} -> ${res.status}: ${text}`);
|
|
542
|
+
}
|
|
543
|
+
return data;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
406
546
|
// Annotate the CommonJS export names for ESM import in node:
|
|
407
547
|
0 && (module.exports = {
|
|
408
548
|
AgentApiClient,
|
|
@@ -412,7 +552,9 @@ var CapsuleAgent = class {
|
|
|
412
552
|
BusDisabledError,
|
|
413
553
|
BusRateLimitedError,
|
|
414
554
|
CapsuleAgent,
|
|
555
|
+
CeServiceAgent,
|
|
415
556
|
FileTokenStore,
|
|
416
557
|
NetworkError,
|
|
417
|
-
RegistrationError
|
|
558
|
+
RegistrationError,
|
|
559
|
+
XtrapeAgent
|
|
418
560
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionPrepareResult, PublishBusEventRequest, RuntimeKind, HealthReportInput, ConfigItemInput, ActionDefinitionInput, PublishBusEventResponse, ReportedService, RegisterAgentRequest, AgentHeartbeatRequest, ServiceReportRequest, ReportCommandResultRequest } from '@xtrape/capsule-contracts-node';
|
|
1
|
+
import { ActionPrepareResult, PublishBusEventRequest, RuntimeKind, HealthReportInput, ConfigItemInput, ActionDefinitionInput, PublishBusEventResponse, ReportedService, RegisterAgentRequest, AgentHeartbeatRequest, ServiceReportRequest, ReportCommandResultRequest, ConversationMessage, ServiceManifest, ServiceInstance, ServiceInstanceHealth } from '@xtrape/capsule-contracts-node';
|
|
2
2
|
|
|
3
3
|
type AgentMode = "embedded";
|
|
4
4
|
type ActionHandler = (payload: Record<string, unknown>) => Promise<{
|
|
@@ -38,7 +38,15 @@ interface StructuredLogger {
|
|
|
38
38
|
* `structuredLogger` wins.
|
|
39
39
|
*/
|
|
40
40
|
type ConsoleLogger = Pick<Console, "debug" | "info" | "warn" | "error">;
|
|
41
|
-
interface
|
|
41
|
+
interface WorkerDefinition {
|
|
42
|
+
code: string;
|
|
43
|
+
name: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
version: string;
|
|
46
|
+
runtime: RuntimeKind;
|
|
47
|
+
manifest?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
interface CapsuleAgentOptionsBase {
|
|
42
50
|
backendUrl: string;
|
|
43
51
|
registrationToken?: string;
|
|
44
52
|
tokenStore?: {
|
|
@@ -49,17 +57,12 @@ interface CapsuleAgentOptions {
|
|
|
49
57
|
name?: string;
|
|
50
58
|
runtime?: RuntimeKind;
|
|
51
59
|
};
|
|
52
|
-
service: {
|
|
53
|
-
code: string;
|
|
54
|
-
name: string;
|
|
55
|
-
description?: string;
|
|
56
|
-
version: string;
|
|
57
|
-
runtime: RuntimeKind;
|
|
58
|
-
manifest?: Record<string, unknown>;
|
|
59
|
-
};
|
|
60
60
|
intervals?: {
|
|
61
61
|
heartbeatMs?: number;
|
|
62
62
|
commandPollMs?: number;
|
|
63
|
+
/** Canonical Worker Runtime Report interval. */
|
|
64
|
+
workerReportMs?: number;
|
|
65
|
+
/** @deprecated Compatibility alias for `workerReportMs`. */
|
|
63
66
|
serviceReportMs?: number;
|
|
64
67
|
};
|
|
65
68
|
heartbeatIntervalSeconds?: number;
|
|
@@ -75,6 +78,19 @@ interface CapsuleAgentOptions {
|
|
|
75
78
|
structuredLogger?: StructuredLogger;
|
|
76
79
|
failOnStartError?: boolean;
|
|
77
80
|
}
|
|
81
|
+
/** Canonical Embedded Agent options: exactly one runtime unit via `worker`. */
|
|
82
|
+
type WorkerAgentOptions = CapsuleAgentOptionsBase & {
|
|
83
|
+
worker: WorkerDefinition;
|
|
84
|
+
/** @deprecated Legacy alias; do not combine with `worker`. */
|
|
85
|
+
service?: never;
|
|
86
|
+
};
|
|
87
|
+
/** Legacy Embedded Agent options: exactly one runtime unit via `service`. */
|
|
88
|
+
type LegacyServiceAgentOptions = CapsuleAgentOptionsBase & {
|
|
89
|
+
/** @deprecated Legacy compatibility alias for `worker`. */
|
|
90
|
+
service: WorkerDefinition;
|
|
91
|
+
worker?: never;
|
|
92
|
+
};
|
|
93
|
+
type CapsuleAgentOptions = WorkerAgentOptions | LegacyServiceAgentOptions;
|
|
78
94
|
type HealthProvider = () => Promise<HealthReportInput> | HealthReportInput;
|
|
79
95
|
type ConfigProvider = () => Promise<ConfigItemInput[]> | ConfigItemInput[];
|
|
80
96
|
interface RegisteredAction extends ActionDefinitionInput {
|
|
@@ -87,9 +103,12 @@ interface TokenStore {
|
|
|
87
103
|
clear(): Promise<void>;
|
|
88
104
|
}
|
|
89
105
|
type ServiceSnapshot = ReportedService;
|
|
90
|
-
|
|
91
|
-
type
|
|
106
|
+
type WorkerSnapshot = ReportedService;
|
|
107
|
+
type XtrapeAgentOptions = CapsuleAgentOptions;
|
|
108
|
+
/** Legacy v0.4 event-routing publish input. Subject to change before v1.0. */
|
|
109
|
+
type PublishBusEventInput = Omit<PublishBusEventRequest, "sourceServiceCode" | "experimental"> & {
|
|
92
110
|
sourceServiceCode?: string;
|
|
111
|
+
experimental?: PublishBusEventRequest["experimental"];
|
|
93
112
|
};
|
|
94
113
|
type PublishBusEventResult = PublishBusEventResponse;
|
|
95
114
|
|
|
@@ -114,11 +133,12 @@ declare class CapsuleAgent {
|
|
|
114
133
|
stop(): Promise<void>;
|
|
115
134
|
private loop;
|
|
116
135
|
private ensureRegistered;
|
|
136
|
+
private workerDefinition;
|
|
117
137
|
private serviceSnapshot;
|
|
118
138
|
runHealth(): Promise<HealthReportInput>;
|
|
119
|
-
/**
|
|
139
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
120
140
|
publishBusEvent(input: PublishBusEventInput): Promise<PublishBusEventResult>;
|
|
121
|
-
private
|
|
141
|
+
private reportWorker;
|
|
122
142
|
private heartbeat;
|
|
123
143
|
private pollOnce;
|
|
124
144
|
private execute;
|
|
@@ -156,7 +176,8 @@ declare class AgentApiError extends Error {
|
|
|
156
176
|
/**
|
|
157
177
|
* The backend rejected the registration token (expired, revoked, already
|
|
158
178
|
* used, malformed). The agent cannot proceed without operator action: a
|
|
159
|
-
* fresh registration
|
|
179
|
+
* a fresh registration credential must be created in Panel. The current API
|
|
180
|
+
* still calls this value a registration token.
|
|
160
181
|
*/
|
|
161
182
|
declare class RegistrationError extends AgentApiError {
|
|
162
183
|
constructor(message: string, status: number, body?: unknown, code?: string);
|
|
@@ -201,6 +222,10 @@ declare class AgentApiClient {
|
|
|
201
222
|
}>;
|
|
202
223
|
heartbeat(agentId: string, token: string, req: AgentHeartbeatRequest): Promise<unknown>;
|
|
203
224
|
reportServices(agentId: string, token: string, req: ServiceReportRequest): Promise<unknown>;
|
|
225
|
+
/** Report Workers through the canonical endpoint, with a legacy fallback. */
|
|
226
|
+
reportWorkers(agentId: string, token: string, req: {
|
|
227
|
+
workers: ReportedService[];
|
|
228
|
+
}): Promise<unknown>;
|
|
204
229
|
pollCommands(agentId: string, token: string): Promise<{
|
|
205
230
|
type: "ACTION_EXECUTE" | "ACTION_PREPARE";
|
|
206
231
|
id: string;
|
|
@@ -216,7 +241,7 @@ declare class AgentApiClient {
|
|
|
216
241
|
completedAt?: string | null | undefined;
|
|
217
242
|
}[]>;
|
|
218
243
|
reportResult(agentId: string, token: string, commandId: string, req: ReportCommandResultRequest): Promise<unknown>;
|
|
219
|
-
/**
|
|
244
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
220
245
|
publishBusEvent(agentId: string, token: string, req: PublishBusEventInput): Promise<{
|
|
221
246
|
experimental: "v0.4-experimental";
|
|
222
247
|
eventId: string;
|
|
@@ -233,4 +258,67 @@ declare class AgentApiClient {
|
|
|
233
258
|
}>;
|
|
234
259
|
}
|
|
235
260
|
|
|
236
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Xtrape CE Phase 0 service agent.
|
|
263
|
+
*
|
|
264
|
+
* Framework-agnostic SDK for an `xtrape-service` to:
|
|
265
|
+
* - register an instance to `xtrape-server-ce`;
|
|
266
|
+
* - send heartbeats;
|
|
267
|
+
* - validate inbound ConversationMessages and build replies.
|
|
268
|
+
*
|
|
269
|
+
* See xtrape-docs runtime-platform/04 (registration & heartbeat) and /03 (message).
|
|
270
|
+
*/
|
|
271
|
+
interface CeServiceAgentOptions {
|
|
272
|
+
/** xtrape-server-ce base URL, e.g. http://localhost:3000 */
|
|
273
|
+
serverUrl: string;
|
|
274
|
+
/** this service's manifest */
|
|
275
|
+
manifest: ServiceManifest;
|
|
276
|
+
/** this instance's reachable address (URL) */
|
|
277
|
+
address: string;
|
|
278
|
+
/** optional fixed instance id; defaults to `<serviceId>-<rand>` */
|
|
279
|
+
instanceId?: string;
|
|
280
|
+
/** injectable fetch (tests); defaults to global fetch */
|
|
281
|
+
fetchImpl?: typeof fetch;
|
|
282
|
+
}
|
|
283
|
+
type CeMessageReply = string | {
|
|
284
|
+
content: string;
|
|
285
|
+
messageType?: ConversationMessage["messageType"];
|
|
286
|
+
payload?: Record<string, unknown>;
|
|
287
|
+
};
|
|
288
|
+
/** Handler invoked with a validated inbound message; returns the reply content. */
|
|
289
|
+
type CeMessageHandler = (msg: ConversationMessage) => CeMessageReply | Promise<CeMessageReply>;
|
|
290
|
+
declare class CeServiceAgent {
|
|
291
|
+
readonly serviceId: string;
|
|
292
|
+
readonly instanceId: string;
|
|
293
|
+
private readonly serverUrl;
|
|
294
|
+
private readonly manifest;
|
|
295
|
+
private readonly address;
|
|
296
|
+
private readonly tenant;
|
|
297
|
+
private readonly fetchImpl;
|
|
298
|
+
private health;
|
|
299
|
+
private hbTimer?;
|
|
300
|
+
constructor(opts: CeServiceAgentOptions);
|
|
301
|
+
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
302
|
+
instance(): ServiceInstance;
|
|
303
|
+
/** POST /v1/instances { manifest, instance } */
|
|
304
|
+
register(): Promise<{
|
|
305
|
+
instanceId: string;
|
|
306
|
+
}>;
|
|
307
|
+
setHealth(h: ServiceInstanceHealth): void;
|
|
308
|
+
/** POST /v1/instances/{id}/heartbeat */
|
|
309
|
+
sendHeartbeat(metricsSummary?: Record<string, unknown>): Promise<void>;
|
|
310
|
+
/** Start a periodic heartbeat; returns a stop function. Timer is unref'd. */
|
|
311
|
+
startHeartbeat(intervalMs?: number, metrics?: () => Record<string, unknown>): () => void;
|
|
312
|
+
stopHeartbeat(): void;
|
|
313
|
+
/** DELETE /v1/instances/{id} — graceful deregister. */
|
|
314
|
+
deregister(): Promise<void>;
|
|
315
|
+
/**
|
|
316
|
+
* Validate an inbound message, run the handler, and build the reply message
|
|
317
|
+
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
318
|
+
*/
|
|
319
|
+
handleMessage(raw: unknown, handler: CeMessageHandler): Promise<ConversationMessage>;
|
|
320
|
+
private post;
|
|
321
|
+
private fetchJson;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { type ActionHandler, type ActionPrepareHandler, AgentApiClient, AgentApiError, AgentAuthError, type AgentLogLevel, type AgentLogRecord, type AgentMode, BusDepthExceededError, BusDisabledError, BusRateLimitedError, CapsuleAgent, type CapsuleAgentOptions, type CeMessageHandler, type CeMessageReply, CeServiceAgent, type CeServiceAgentOptions, type ConfigProvider, type ConsoleLogger, FileTokenStore, type HealthProvider, type LegacyServiceAgentOptions, NetworkError, type PublishBusEventInput, type PublishBusEventResult, type RegisteredAction, RegistrationError, type ServiceSnapshot, type StructuredLogger, type TokenStore, type WorkerAgentOptions, type WorkerDefinition, type WorkerSnapshot, CapsuleAgent as XtrapeAgent, type XtrapeAgentOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ActionPrepareResult, PublishBusEventRequest, RuntimeKind, HealthReportInput, ConfigItemInput, ActionDefinitionInput, PublishBusEventResponse, ReportedService, RegisterAgentRequest, AgentHeartbeatRequest, ServiceReportRequest, ReportCommandResultRequest } from '@xtrape/capsule-contracts-node';
|
|
1
|
+
import { ActionPrepareResult, PublishBusEventRequest, RuntimeKind, HealthReportInput, ConfigItemInput, ActionDefinitionInput, PublishBusEventResponse, ReportedService, RegisterAgentRequest, AgentHeartbeatRequest, ServiceReportRequest, ReportCommandResultRequest, ConversationMessage, ServiceManifest, ServiceInstance, ServiceInstanceHealth } from '@xtrape/capsule-contracts-node';
|
|
2
2
|
|
|
3
3
|
type AgentMode = "embedded";
|
|
4
4
|
type ActionHandler = (payload: Record<string, unknown>) => Promise<{
|
|
@@ -38,7 +38,15 @@ interface StructuredLogger {
|
|
|
38
38
|
* `structuredLogger` wins.
|
|
39
39
|
*/
|
|
40
40
|
type ConsoleLogger = Pick<Console, "debug" | "info" | "warn" | "error">;
|
|
41
|
-
interface
|
|
41
|
+
interface WorkerDefinition {
|
|
42
|
+
code: string;
|
|
43
|
+
name: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
version: string;
|
|
46
|
+
runtime: RuntimeKind;
|
|
47
|
+
manifest?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
interface CapsuleAgentOptionsBase {
|
|
42
50
|
backendUrl: string;
|
|
43
51
|
registrationToken?: string;
|
|
44
52
|
tokenStore?: {
|
|
@@ -49,17 +57,12 @@ interface CapsuleAgentOptions {
|
|
|
49
57
|
name?: string;
|
|
50
58
|
runtime?: RuntimeKind;
|
|
51
59
|
};
|
|
52
|
-
service: {
|
|
53
|
-
code: string;
|
|
54
|
-
name: string;
|
|
55
|
-
description?: string;
|
|
56
|
-
version: string;
|
|
57
|
-
runtime: RuntimeKind;
|
|
58
|
-
manifest?: Record<string, unknown>;
|
|
59
|
-
};
|
|
60
60
|
intervals?: {
|
|
61
61
|
heartbeatMs?: number;
|
|
62
62
|
commandPollMs?: number;
|
|
63
|
+
/** Canonical Worker Runtime Report interval. */
|
|
64
|
+
workerReportMs?: number;
|
|
65
|
+
/** @deprecated Compatibility alias for `workerReportMs`. */
|
|
63
66
|
serviceReportMs?: number;
|
|
64
67
|
};
|
|
65
68
|
heartbeatIntervalSeconds?: number;
|
|
@@ -75,6 +78,19 @@ interface CapsuleAgentOptions {
|
|
|
75
78
|
structuredLogger?: StructuredLogger;
|
|
76
79
|
failOnStartError?: boolean;
|
|
77
80
|
}
|
|
81
|
+
/** Canonical Embedded Agent options: exactly one runtime unit via `worker`. */
|
|
82
|
+
type WorkerAgentOptions = CapsuleAgentOptionsBase & {
|
|
83
|
+
worker: WorkerDefinition;
|
|
84
|
+
/** @deprecated Legacy alias; do not combine with `worker`. */
|
|
85
|
+
service?: never;
|
|
86
|
+
};
|
|
87
|
+
/** Legacy Embedded Agent options: exactly one runtime unit via `service`. */
|
|
88
|
+
type LegacyServiceAgentOptions = CapsuleAgentOptionsBase & {
|
|
89
|
+
/** @deprecated Legacy compatibility alias for `worker`. */
|
|
90
|
+
service: WorkerDefinition;
|
|
91
|
+
worker?: never;
|
|
92
|
+
};
|
|
93
|
+
type CapsuleAgentOptions = WorkerAgentOptions | LegacyServiceAgentOptions;
|
|
78
94
|
type HealthProvider = () => Promise<HealthReportInput> | HealthReportInput;
|
|
79
95
|
type ConfigProvider = () => Promise<ConfigItemInput[]> | ConfigItemInput[];
|
|
80
96
|
interface RegisteredAction extends ActionDefinitionInput {
|
|
@@ -87,9 +103,12 @@ interface TokenStore {
|
|
|
87
103
|
clear(): Promise<void>;
|
|
88
104
|
}
|
|
89
105
|
type ServiceSnapshot = ReportedService;
|
|
90
|
-
|
|
91
|
-
type
|
|
106
|
+
type WorkerSnapshot = ReportedService;
|
|
107
|
+
type XtrapeAgentOptions = CapsuleAgentOptions;
|
|
108
|
+
/** Legacy v0.4 event-routing publish input. Subject to change before v1.0. */
|
|
109
|
+
type PublishBusEventInput = Omit<PublishBusEventRequest, "sourceServiceCode" | "experimental"> & {
|
|
92
110
|
sourceServiceCode?: string;
|
|
111
|
+
experimental?: PublishBusEventRequest["experimental"];
|
|
93
112
|
};
|
|
94
113
|
type PublishBusEventResult = PublishBusEventResponse;
|
|
95
114
|
|
|
@@ -114,11 +133,12 @@ declare class CapsuleAgent {
|
|
|
114
133
|
stop(): Promise<void>;
|
|
115
134
|
private loop;
|
|
116
135
|
private ensureRegistered;
|
|
136
|
+
private workerDefinition;
|
|
117
137
|
private serviceSnapshot;
|
|
118
138
|
runHealth(): Promise<HealthReportInput>;
|
|
119
|
-
/**
|
|
139
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
120
140
|
publishBusEvent(input: PublishBusEventInput): Promise<PublishBusEventResult>;
|
|
121
|
-
private
|
|
141
|
+
private reportWorker;
|
|
122
142
|
private heartbeat;
|
|
123
143
|
private pollOnce;
|
|
124
144
|
private execute;
|
|
@@ -156,7 +176,8 @@ declare class AgentApiError extends Error {
|
|
|
156
176
|
/**
|
|
157
177
|
* The backend rejected the registration token (expired, revoked, already
|
|
158
178
|
* used, malformed). The agent cannot proceed without operator action: a
|
|
159
|
-
* fresh registration
|
|
179
|
+
* a fresh registration credential must be created in Panel. The current API
|
|
180
|
+
* still calls this value a registration token.
|
|
160
181
|
*/
|
|
161
182
|
declare class RegistrationError extends AgentApiError {
|
|
162
183
|
constructor(message: string, status: number, body?: unknown, code?: string);
|
|
@@ -201,6 +222,10 @@ declare class AgentApiClient {
|
|
|
201
222
|
}>;
|
|
202
223
|
heartbeat(agentId: string, token: string, req: AgentHeartbeatRequest): Promise<unknown>;
|
|
203
224
|
reportServices(agentId: string, token: string, req: ServiceReportRequest): Promise<unknown>;
|
|
225
|
+
/** Report Workers through the canonical endpoint, with a legacy fallback. */
|
|
226
|
+
reportWorkers(agentId: string, token: string, req: {
|
|
227
|
+
workers: ReportedService[];
|
|
228
|
+
}): Promise<unknown>;
|
|
204
229
|
pollCommands(agentId: string, token: string): Promise<{
|
|
205
230
|
type: "ACTION_EXECUTE" | "ACTION_PREPARE";
|
|
206
231
|
id: string;
|
|
@@ -216,7 +241,7 @@ declare class AgentApiClient {
|
|
|
216
241
|
completedAt?: string | null | undefined;
|
|
217
242
|
}[]>;
|
|
218
243
|
reportResult(agentId: string, token: string, commandId: string, req: ReportCommandResultRequest): Promise<unknown>;
|
|
219
|
-
/**
|
|
244
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
220
245
|
publishBusEvent(agentId: string, token: string, req: PublishBusEventInput): Promise<{
|
|
221
246
|
experimental: "v0.4-experimental";
|
|
222
247
|
eventId: string;
|
|
@@ -233,4 +258,67 @@ declare class AgentApiClient {
|
|
|
233
258
|
}>;
|
|
234
259
|
}
|
|
235
260
|
|
|
236
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Xtrape CE Phase 0 service agent.
|
|
263
|
+
*
|
|
264
|
+
* Framework-agnostic SDK for an `xtrape-service` to:
|
|
265
|
+
* - register an instance to `xtrape-server-ce`;
|
|
266
|
+
* - send heartbeats;
|
|
267
|
+
* - validate inbound ConversationMessages and build replies.
|
|
268
|
+
*
|
|
269
|
+
* See xtrape-docs runtime-platform/04 (registration & heartbeat) and /03 (message).
|
|
270
|
+
*/
|
|
271
|
+
interface CeServiceAgentOptions {
|
|
272
|
+
/** xtrape-server-ce base URL, e.g. http://localhost:3000 */
|
|
273
|
+
serverUrl: string;
|
|
274
|
+
/** this service's manifest */
|
|
275
|
+
manifest: ServiceManifest;
|
|
276
|
+
/** this instance's reachable address (URL) */
|
|
277
|
+
address: string;
|
|
278
|
+
/** optional fixed instance id; defaults to `<serviceId>-<rand>` */
|
|
279
|
+
instanceId?: string;
|
|
280
|
+
/** injectable fetch (tests); defaults to global fetch */
|
|
281
|
+
fetchImpl?: typeof fetch;
|
|
282
|
+
}
|
|
283
|
+
type CeMessageReply = string | {
|
|
284
|
+
content: string;
|
|
285
|
+
messageType?: ConversationMessage["messageType"];
|
|
286
|
+
payload?: Record<string, unknown>;
|
|
287
|
+
};
|
|
288
|
+
/** Handler invoked with a validated inbound message; returns the reply content. */
|
|
289
|
+
type CeMessageHandler = (msg: ConversationMessage) => CeMessageReply | Promise<CeMessageReply>;
|
|
290
|
+
declare class CeServiceAgent {
|
|
291
|
+
readonly serviceId: string;
|
|
292
|
+
readonly instanceId: string;
|
|
293
|
+
private readonly serverUrl;
|
|
294
|
+
private readonly manifest;
|
|
295
|
+
private readonly address;
|
|
296
|
+
private readonly tenant;
|
|
297
|
+
private readonly fetchImpl;
|
|
298
|
+
private health;
|
|
299
|
+
private hbTimer?;
|
|
300
|
+
constructor(opts: CeServiceAgentOptions);
|
|
301
|
+
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
302
|
+
instance(): ServiceInstance;
|
|
303
|
+
/** POST /v1/instances { manifest, instance } */
|
|
304
|
+
register(): Promise<{
|
|
305
|
+
instanceId: string;
|
|
306
|
+
}>;
|
|
307
|
+
setHealth(h: ServiceInstanceHealth): void;
|
|
308
|
+
/** POST /v1/instances/{id}/heartbeat */
|
|
309
|
+
sendHeartbeat(metricsSummary?: Record<string, unknown>): Promise<void>;
|
|
310
|
+
/** Start a periodic heartbeat; returns a stop function. Timer is unref'd. */
|
|
311
|
+
startHeartbeat(intervalMs?: number, metrics?: () => Record<string, unknown>): () => void;
|
|
312
|
+
stopHeartbeat(): void;
|
|
313
|
+
/** DELETE /v1/instances/{id} — graceful deregister. */
|
|
314
|
+
deregister(): Promise<void>;
|
|
315
|
+
/**
|
|
316
|
+
* Validate an inbound message, run the handler, and build the reply message
|
|
317
|
+
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
318
|
+
*/
|
|
319
|
+
handleMessage(raw: unknown, handler: CeMessageHandler): Promise<ConversationMessage>;
|
|
320
|
+
private post;
|
|
321
|
+
private fetchJson;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export { type ActionHandler, type ActionPrepareHandler, AgentApiClient, AgentApiError, AgentAuthError, type AgentLogLevel, type AgentLogRecord, type AgentMode, BusDepthExceededError, BusDisabledError, BusRateLimitedError, CapsuleAgent, type CapsuleAgentOptions, type CeMessageHandler, type CeMessageReply, CeServiceAgent, type CeServiceAgentOptions, type ConfigProvider, type ConsoleLogger, FileTokenStore, type HealthProvider, type LegacyServiceAgentOptions, NetworkError, type PublishBusEventInput, type PublishBusEventResult, type RegisteredAction, RegistrationError, type ServiceSnapshot, type StructuredLogger, type TokenStore, type WorkerAgentOptions, type WorkerDefinition, type WorkerSnapshot, CapsuleAgent as XtrapeAgent, type XtrapeAgentOptions };
|
package/dist/index.js
CHANGED
|
@@ -101,13 +101,22 @@ var AgentApiClient = class {
|
|
|
101
101
|
reportServices(agentId, token, req) {
|
|
102
102
|
return this.request(`/api/agents/${agentId}/services/report`, { method: "POST", token, body: JSON.stringify(req) });
|
|
103
103
|
}
|
|
104
|
+
/** Report Workers through the canonical endpoint, with a legacy fallback. */
|
|
105
|
+
async reportWorkers(agentId, token, req) {
|
|
106
|
+
try {
|
|
107
|
+
return await this.request(`/api/agents/${agentId}/workers/report`, { method: "POST", token, body: JSON.stringify(req) });
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (!(error instanceof AgentApiError) || error.status !== 404) throw error;
|
|
110
|
+
return this.reportServices(agentId, token, { services: req.workers });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
104
113
|
pollCommands(agentId, token) {
|
|
105
114
|
return this.request(`/api/agents/${agentId}/commands`, { method: "GET", token });
|
|
106
115
|
}
|
|
107
116
|
reportResult(agentId, token, commandId, req) {
|
|
108
117
|
return this.request(`/api/agents/${agentId}/commands/${commandId}/result`, { method: "POST", token, body: JSON.stringify(req) });
|
|
109
118
|
}
|
|
110
|
-
/**
|
|
119
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
111
120
|
publishBusEvent(agentId, token, req) {
|
|
112
121
|
return this.request(`/api/agents/${agentId}/bus/events`, { method: "POST", token, body: JSON.stringify({ ...req, experimental: req.experimental ?? "v0.4-experimental" }) });
|
|
113
122
|
}
|
|
@@ -180,6 +189,12 @@ function emitLog(consoleSink, structured, level, message, fields) {
|
|
|
180
189
|
var CapsuleAgent = class {
|
|
181
190
|
constructor(options) {
|
|
182
191
|
this.options = options;
|
|
192
|
+
if (options.worker && options.service) {
|
|
193
|
+
throw new Error("Agent options accept `worker` or legacy `service`, not both.");
|
|
194
|
+
}
|
|
195
|
+
if (!options.worker && !options.service) {
|
|
196
|
+
throw new Error("Agent options require `worker` (or legacy `service`).");
|
|
197
|
+
}
|
|
183
198
|
this.client = new AgentApiClient(options.backendUrl);
|
|
184
199
|
this.store = new FileTokenStore(options.tokenStore?.file);
|
|
185
200
|
this.logger = options.logger ?? console;
|
|
@@ -213,7 +228,7 @@ var CapsuleAgent = class {
|
|
|
213
228
|
this.stopped = false;
|
|
214
229
|
try {
|
|
215
230
|
await this.ensureRegistered();
|
|
216
|
-
await this.
|
|
231
|
+
await this.reportWorker();
|
|
217
232
|
await this.heartbeat();
|
|
218
233
|
await this.pollOnce();
|
|
219
234
|
} catch (e) {
|
|
@@ -222,7 +237,7 @@ var CapsuleAgent = class {
|
|
|
222
237
|
}
|
|
223
238
|
if (this.options.autoStartLoops !== false) {
|
|
224
239
|
this.loop("heartbeat", this.options.intervals?.heartbeatMs ?? (this.options.heartbeatIntervalSeconds ?? 30) * 1e3, () => this.heartbeat());
|
|
225
|
-
this.loop("
|
|
240
|
+
this.loop("worker-report", this.options.intervals?.workerReportMs ?? this.options.intervals?.serviceReportMs ?? 6e4, () => this.reportWorker());
|
|
226
241
|
this.loop("command-poll", this.options.intervals?.commandPollMs ?? (this.options.commandPollIntervalSeconds ?? 5) * 1e3, () => this.pollOnce());
|
|
227
242
|
}
|
|
228
243
|
}
|
|
@@ -251,38 +266,45 @@ var CapsuleAgent = class {
|
|
|
251
266
|
if (this.agentId) return;
|
|
252
267
|
}
|
|
253
268
|
if (!this.options.registrationToken) throw new Error("OPSTAGE registration token is required for first registration");
|
|
254
|
-
const
|
|
269
|
+
const worker = this.workerDefinition();
|
|
270
|
+
const agent = this.options.agent ?? { code: worker.code, name: worker.name, runtime: worker.runtime };
|
|
255
271
|
const res = await this.retry(() => this.client.register({ registrationToken: this.options.registrationToken, agent: { code: agent.code, name: agent.name, mode: "embedded", runtime: agent.runtime ?? "nodejs" }, service: this.serviceSnapshot() }));
|
|
256
272
|
this.agentId = res.agentId;
|
|
257
273
|
this.token = res.agentToken;
|
|
258
274
|
await this.store.save(`${this.agentId}:${this.token}`);
|
|
259
275
|
this.log("info", `registered agent ${this.agentId}`);
|
|
260
276
|
}
|
|
277
|
+
workerDefinition() {
|
|
278
|
+
if (this.options.worker) {
|
|
279
|
+
return this.options.worker;
|
|
280
|
+
}
|
|
281
|
+
return this.options.service;
|
|
282
|
+
}
|
|
261
283
|
serviceSnapshot() {
|
|
262
|
-
const
|
|
263
|
-
|
|
284
|
+
const worker = this.workerDefinition();
|
|
285
|
+
const manifest = { kind: "Worker", schemaVersion: "1.0", code: worker.code, name: worker.name, description: worker.description, version: worker.version, runtime: worker.runtime, agentMode: "embedded", ...worker.manifest ?? {} };
|
|
286
|
+
return { code: worker.code, name: worker.name, description: worker.description, version: worker.version, runtime: worker.runtime, manifest, actions: [...this.actions.values()].map(({ handler, prepare, inputSchema, outputSchema, ...a }) => a) };
|
|
264
287
|
}
|
|
265
288
|
async runHealth() {
|
|
266
289
|
return this.healthProvider();
|
|
267
290
|
}
|
|
268
|
-
/**
|
|
291
|
+
/** Legacy v0.4 event-routing compatibility hook. Subject to change before v1.0. */
|
|
269
292
|
async publishBusEvent(input) {
|
|
270
|
-
if (!this.agentId || !this.token) throw new Error("Agent must be started before publishing
|
|
271
|
-
const sourceServiceCode = input.sourceServiceCode ?? this.
|
|
293
|
+
if (!this.agentId || !this.token) throw new Error("Agent must be started before publishing routed events.");
|
|
294
|
+
const sourceServiceCode = input.sourceServiceCode ?? this.workerDefinition().code;
|
|
272
295
|
const result = await this.retry(() => this.client.publishBusEvent(this.agentId, this.token, { ...input, sourceServiceCode }));
|
|
273
296
|
this.log("info", "experimental bus event published", { eventId: result.eventId, eventType: input.eventType, sourceServiceCode, routedCommands: result.routedCommands.length });
|
|
274
297
|
return result;
|
|
275
298
|
}
|
|
276
|
-
async
|
|
299
|
+
async reportWorker() {
|
|
277
300
|
if (!this.agentId || !this.token) return;
|
|
278
301
|
const [health, configs] = await Promise.all([this.runHealth(), this.configProvider()]);
|
|
279
|
-
const
|
|
280
|
-
await this.retry(() => this.client.
|
|
302
|
+
const worker = { ...this.serviceSnapshot(), health, configs };
|
|
303
|
+
await this.retry(() => this.client.reportWorkers(this.agentId, this.token, { workers: [worker] }));
|
|
281
304
|
}
|
|
282
305
|
async heartbeat() {
|
|
283
306
|
if (!this.agentId || !this.token) return;
|
|
284
|
-
|
|
285
|
-
await this.retry(() => this.client.heartbeat(this.agentId, this.token, { health }));
|
|
307
|
+
await this.retry(() => this.client.heartbeat(this.agentId, this.token, {}));
|
|
286
308
|
}
|
|
287
309
|
async pollOnce() {
|
|
288
310
|
if (!this.agentId || !this.token) return;
|
|
@@ -368,6 +390,126 @@ var CapsuleAgent = class {
|
|
|
368
390
|
return { value: obj };
|
|
369
391
|
}
|
|
370
392
|
};
|
|
393
|
+
|
|
394
|
+
// src/ce/index.ts
|
|
395
|
+
import { randomUUID } from "crypto";
|
|
396
|
+
import {
|
|
397
|
+
CE_CONTRACT_VERSION,
|
|
398
|
+
ServiceInstanceSchema,
|
|
399
|
+
ConversationMessageSchema
|
|
400
|
+
} from "@xtrape/capsule-contracts-node";
|
|
401
|
+
var CeServiceAgent = class {
|
|
402
|
+
serviceId;
|
|
403
|
+
instanceId;
|
|
404
|
+
serverUrl;
|
|
405
|
+
manifest;
|
|
406
|
+
address;
|
|
407
|
+
tenant;
|
|
408
|
+
fetchImpl;
|
|
409
|
+
health = "HEALTHY";
|
|
410
|
+
hbTimer;
|
|
411
|
+
constructor(opts) {
|
|
412
|
+
this.serverUrl = opts.serverUrl.replace(/\/+$/, "");
|
|
413
|
+
this.manifest = opts.manifest;
|
|
414
|
+
this.address = opts.address;
|
|
415
|
+
this.tenant = opts.manifest.tenant ?? "default";
|
|
416
|
+
this.serviceId = opts.manifest.service.id;
|
|
417
|
+
this.instanceId = opts.instanceId ?? `${this.serviceId}-${randomUUID().slice(0, 8)}`;
|
|
418
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
419
|
+
}
|
|
420
|
+
/** The ServiceInstance this agent reports on register/heartbeat. */
|
|
421
|
+
instance() {
|
|
422
|
+
return ServiceInstanceSchema.parse({
|
|
423
|
+
contractVersion: CE_CONTRACT_VERSION,
|
|
424
|
+
serviceId: this.serviceId,
|
|
425
|
+
instanceId: this.instanceId,
|
|
426
|
+
tenant: this.tenant,
|
|
427
|
+
address: this.address,
|
|
428
|
+
version: this.manifest.service.version,
|
|
429
|
+
health: this.health
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/** POST /v1/instances { manifest, instance } */
|
|
433
|
+
async register() {
|
|
434
|
+
await this.post("/v1/instances", { manifest: this.manifest, instance: this.instance() });
|
|
435
|
+
return { instanceId: this.instanceId };
|
|
436
|
+
}
|
|
437
|
+
setHealth(h) {
|
|
438
|
+
this.health = h;
|
|
439
|
+
}
|
|
440
|
+
/** POST /v1/instances/{id}/heartbeat */
|
|
441
|
+
async sendHeartbeat(metricsSummary) {
|
|
442
|
+
const hb = {
|
|
443
|
+
contractVersion: CE_CONTRACT_VERSION,
|
|
444
|
+
serviceId: this.serviceId,
|
|
445
|
+
instanceId: this.instanceId,
|
|
446
|
+
tenant: this.tenant,
|
|
447
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
448
|
+
health: this.health,
|
|
449
|
+
...metricsSummary ? { metricsSummary } : {}
|
|
450
|
+
};
|
|
451
|
+
await this.post(`/v1/instances/${encodeURIComponent(this.instanceId)}/heartbeat`, hb);
|
|
452
|
+
}
|
|
453
|
+
/** Start a periodic heartbeat; returns a stop function. Timer is unref'd. */
|
|
454
|
+
startHeartbeat(intervalMs = 1e4, metrics) {
|
|
455
|
+
this.stopHeartbeat();
|
|
456
|
+
this.hbTimer = setInterval(() => {
|
|
457
|
+
void this.sendHeartbeat(metrics?.()).catch(() => {
|
|
458
|
+
});
|
|
459
|
+
}, intervalMs);
|
|
460
|
+
this.hbTimer.unref?.();
|
|
461
|
+
return () => this.stopHeartbeat();
|
|
462
|
+
}
|
|
463
|
+
stopHeartbeat() {
|
|
464
|
+
if (this.hbTimer) {
|
|
465
|
+
clearInterval(this.hbTimer);
|
|
466
|
+
this.hbTimer = void 0;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/** DELETE /v1/instances/{id} — graceful deregister. */
|
|
470
|
+
async deregister() {
|
|
471
|
+
await this.fetchJson(`/v1/instances/${encodeURIComponent(this.instanceId)}`, { method: "DELETE" });
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Validate an inbound message, run the handler, and build the reply message
|
|
475
|
+
* (senderType=SERVICE, targeted back at the sender, with replyToMessageId).
|
|
476
|
+
*/
|
|
477
|
+
async handleMessage(raw, handler) {
|
|
478
|
+
const msg = ConversationMessageSchema.parse(raw);
|
|
479
|
+
const out = await handler(msg);
|
|
480
|
+
const reply = typeof out === "string" ? { content: out } : out;
|
|
481
|
+
return ConversationMessageSchema.parse({
|
|
482
|
+
contractVersion: CE_CONTRACT_VERSION,
|
|
483
|
+
messageId: `msg_${randomUUID().slice(0, 12)}`,
|
|
484
|
+
conversationId: msg.conversationId,
|
|
485
|
+
tenant: msg.tenant ?? "default",
|
|
486
|
+
// reply back to whoever sent it (USER/SERVICE/SYSTEM are all valid targets)
|
|
487
|
+
targetType: msg.senderType,
|
|
488
|
+
targetId: msg.senderId,
|
|
489
|
+
senderType: "SERVICE",
|
|
490
|
+
senderId: this.serviceId,
|
|
491
|
+
messageType: reply.messageType ?? "SUMMARY",
|
|
492
|
+
content: reply.content,
|
|
493
|
+
replyToMessageId: msg.messageId,
|
|
494
|
+
...reply.payload ? { payload: reply.payload } : {}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
post(path, body) {
|
|
498
|
+
return this.fetchJson(path, { method: "POST", body: JSON.stringify(body) });
|
|
499
|
+
}
|
|
500
|
+
async fetchJson(path, init) {
|
|
501
|
+
const res = await this.fetchImpl(this.serverUrl + path, {
|
|
502
|
+
...init,
|
|
503
|
+
headers: { "content-type": "application/json", ...init.headers ?? {} }
|
|
504
|
+
});
|
|
505
|
+
const text = await res.text();
|
|
506
|
+
const data = text ? JSON.parse(text) : void 0;
|
|
507
|
+
if (!res.ok) {
|
|
508
|
+
throw new Error(`server-ce ${path} -> ${res.status}: ${text}`);
|
|
509
|
+
}
|
|
510
|
+
return data;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
371
513
|
export {
|
|
372
514
|
AgentApiClient,
|
|
373
515
|
AgentApiError,
|
|
@@ -376,7 +518,9 @@ export {
|
|
|
376
518
|
BusDisabledError,
|
|
377
519
|
BusRateLimitedError,
|
|
378
520
|
CapsuleAgent,
|
|
521
|
+
CeServiceAgent,
|
|
379
522
|
FileTokenStore,
|
|
380
523
|
NetworkError,
|
|
381
|
-
RegistrationError
|
|
524
|
+
RegistrationError,
|
|
525
|
+
CapsuleAgent as XtrapeAgent
|
|
382
526
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xtrape/capsule-agent-node",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Node.js Agent SDK for
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Node.js Embedded Agent SDK for reporting Workers to the Xtrape control plane.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"xtrape",
|
|
7
|
-
"
|
|
7
|
+
"worker",
|
|
8
|
+
"panel",
|
|
8
9
|
"opstage",
|
|
9
10
|
"agent-sdk",
|
|
10
11
|
"automation",
|
|
@@ -39,11 +40,11 @@
|
|
|
39
40
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
40
41
|
"prepack": "pnpm build",
|
|
41
42
|
"test": "vitest run",
|
|
42
|
-
"typecheck": "tsc --noEmit",
|
|
43
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.type-tests.json",
|
|
43
44
|
"lint": "tsc --noEmit"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@xtrape/capsule-contracts-node": "^0.
|
|
47
|
+
"@xtrape/capsule-contracts-node": "^0.5.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"tsup": "^8.3.5",
|