@nebulaos/client 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1819 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AuthenticationError: () => AuthenticationError,
34
+ Batcher: () => Batcher,
35
+ ClientError: () => ClientError,
36
+ ConnectionError: () => ConnectionError,
37
+ DBMemory: () => DBMemory,
38
+ HttpDomainEventsExporter: () => HttpDomainEventsExporter,
39
+ HttpTelemetryExporter: () => HttpTelemetryExporter,
40
+ InstrumentedHttpClient: () => InstrumentedHttpClient,
41
+ NebulaClient: () => NebulaClient,
42
+ NotFoundError: () => NotFoundError,
43
+ OTelTracingProvider: () => OTelTracingProvider,
44
+ PromptCapture: () => PromptCapture,
45
+ RAGClient: () => RAGClient,
46
+ RegistryError: () => RegistryError,
47
+ SocketTelemetryExporter: () => SocketTelemetryExporter,
48
+ TelemetryService: () => TelemetryService,
49
+ clientConfigSchema: () => clientConfigSchema,
50
+ otelTracingConfigSchema: () => otelTracingConfigSchema,
51
+ serverConfigSchema: () => serverConfigSchema,
52
+ setupOTelTracing: () => setupOTelTracing
53
+ });
54
+ module.exports = __toCommonJS(index_exports);
55
+
56
+ // src/config/client-config.ts
57
+ var import_zod = require("zod");
58
+ var serverConfigSchema = import_zod.z.object({
59
+ url: import_zod.z.string().min(1),
60
+ apiKey: import_zod.z.string().min(1),
61
+ batchSize: import_zod.z.number().int().positive().optional(),
62
+ flushInterval: import_zod.z.number().int().positive().optional()
63
+ });
64
+ var otelTracingConfigSchema = import_zod.z.object({
65
+ /** OTLP HTTP endpoint. Defaults to http://localhost:4318/v1/traces */
66
+ otlpEndpoint: import_zod.z.string().optional()
67
+ });
68
+ var clientConfigSchema = import_zod.z.object({
69
+ /**
70
+ * @deprecated clientId is now automatically extracted from the API key.
71
+ * This field is optional and only used for local identification/logging.
72
+ */
73
+ clientId: import_zod.z.string().min(1).optional(),
74
+ /**
75
+ * List of agents to register. Can be either:
76
+ * - Agent instances (backwards compatible, but may cause memory leakage between executions)
77
+ * - Factory functions that return agent instances (recommended for proper isolation)
78
+ */
79
+ agents: import_zod.z.array(import_zod.z.custom()).optional(),
80
+ tools: import_zod.z.array(import_zod.z.custom()).optional(),
81
+ workflows: import_zod.z.array(import_zod.z.custom()).optional(),
82
+ server: serverConfigSchema.optional(),
83
+ /**
84
+ * OpenTelemetry tracing configuration.
85
+ * When set, the client will set up an OTel TracerProvider and export
86
+ * spans directly to the configured OTLP endpoint.
87
+ */
88
+ tracing: otelTracingConfigSchema.optional(),
89
+ /**
90
+ * Client-side log level (same semantics as @nebulaos/core ConsoleLogger).
91
+ * When set to anything other than "none", the client will emit debug logs for
92
+ * WS/HTTP communication (without full payloads).
93
+ */
94
+ logLevel: import_zod.z.custom().optional(),
95
+ // Backwards compatibility (deprecated): prefer `logLevel`.
96
+ debug: import_zod.z.object({
97
+ enabled: import_zod.z.boolean().optional(),
98
+ level: import_zod.z.custom().optional()
99
+ }).optional()
100
+ });
101
+
102
+ // src/errors/client-errors.ts
103
+ var ClientError = class extends Error {
104
+ constructor(message, code, statusCode = 500, details) {
105
+ super(message);
106
+ this.code = code;
107
+ this.statusCode = statusCode;
108
+ this.details = details;
109
+ this.name = "ClientError";
110
+ }
111
+ };
112
+ var RegistryError = class extends ClientError {
113
+ constructor(message, details) {
114
+ super(message, "REGISTRY_ERROR", 400, details);
115
+ this.name = "RegistryError";
116
+ }
117
+ };
118
+ var NotFoundError = class extends ClientError {
119
+ constructor(entity, id) {
120
+ super(`${entity} '${id}' not found`, "NOT_FOUND", 404);
121
+ this.name = "NotFoundError";
122
+ }
123
+ };
124
+ var AuthenticationError = class extends ClientError {
125
+ constructor(message = "Authentication failed: Invalid API key or missing credentials", details) {
126
+ super(message, "AUTHENTICATION_ERROR", 401, details);
127
+ this.name = "AuthenticationError";
128
+ }
129
+ };
130
+ var ConnectionError = class extends ClientError {
131
+ constructor(message, details) {
132
+ super(message, "CONNECTION_ERROR", 0, details);
133
+ this.name = "ConnectionError";
134
+ }
135
+ };
136
+
137
+ // src/registry/registry.ts
138
+ var Registry = class {
139
+ agentsById = /* @__PURE__ */ new Map();
140
+ agentsByName = /* @__PURE__ */ new Map();
141
+ workflows = /* @__PURE__ */ new Map();
142
+ toolsById = /* @__PURE__ */ new Map();
143
+ httpClient;
144
+ /**
145
+ * Sets the HTTP client to be injected into agents for tool execution.
146
+ */
147
+ setHttpClient(client) {
148
+ this.httpClient = client;
149
+ }
150
+ /**
151
+ * Registers an agent factory or instance.
152
+ *
153
+ * If an instance is passed directly (backwards compatibility), it will be wrapped
154
+ * in a factory that always returns the same instance. For proper isolation between
155
+ * executions, prefer passing a factory function.
156
+ *
157
+ * @param agentOrFactory - Agent instance or factory function
158
+ */
159
+ registerAgent(agentOrFactory) {
160
+ let factory;
161
+ let sampleAgent;
162
+ if (typeof agentOrFactory === "function") {
163
+ factory = agentOrFactory;
164
+ sampleAgent = factory();
165
+ } else {
166
+ const instance = agentOrFactory;
167
+ factory = () => instance;
168
+ sampleAgent = instance;
169
+ }
170
+ const id = "id" in sampleAgent && typeof sampleAgent.id === "string" && sampleAgent.id ? sampleAgent.id : sampleAgent.name;
171
+ if (this.agentsById.has(id)) {
172
+ throw new RegistryError(`Agent '${id}' already registered`);
173
+ }
174
+ if (this.agentsByName.has(sampleAgent.name)) {
175
+ throw new RegistryError(`Agent name '${sampleAgent.name}' already registered`);
176
+ }
177
+ const entry = {
178
+ factory,
179
+ id,
180
+ name: sampleAgent.name
181
+ };
182
+ this.agentsById.set(id, entry);
183
+ this.agentsByName.set(sampleAgent.name, entry);
184
+ }
185
+ registerWorkflow(workflow) {
186
+ const workflowId = workflow.id;
187
+ if (!workflowId) {
188
+ throw new RegistryError("Workflow id is required to register workflow");
189
+ }
190
+ if (this.workflows.has(workflowId)) {
191
+ throw new RegistryError(`Workflow '${workflowId}' already registered`);
192
+ }
193
+ this.workflows.set(workflowId, workflow);
194
+ }
195
+ registerTool(tool) {
196
+ const id = tool?.id;
197
+ if (!id || typeof id !== "string") {
198
+ throw new RegistryError("Tool id is required to register tool");
199
+ }
200
+ if (this.toolsById.has(id)) {
201
+ throw new RegistryError(`Tool '${id}' already registered`);
202
+ }
203
+ this.toolsById.set(id, tool);
204
+ }
205
+ /**
206
+ * Gets a new agent instance by id or name.
207
+ *
208
+ * Each call creates a fresh instance via the registered factory,
209
+ * ensuring no memory/context leakage between executions.
210
+ *
211
+ * @param idOrName - Agent id or name
212
+ * @returns A new agent instance
213
+ */
214
+ getAgent(idOrName) {
215
+ const entry = this.agentsById.get(idOrName) ?? this.agentsByName.get(idOrName);
216
+ if (!entry) {
217
+ throw new NotFoundError("agent", idOrName);
218
+ }
219
+ const agent = entry.factory();
220
+ if (this.httpClient && typeof agent.setHttpClient === "function") {
221
+ agent.setHttpClient(this.httpClient);
222
+ }
223
+ return agent;
224
+ }
225
+ getWorkflow(id) {
226
+ const workflow = this.workflows.get(id);
227
+ if (!workflow) {
228
+ throw new NotFoundError("workflow", id);
229
+ }
230
+ return workflow;
231
+ }
232
+ getTool(id) {
233
+ const tool = this.toolsById.get(id);
234
+ if (!tool) {
235
+ throw new NotFoundError("tool", id);
236
+ }
237
+ return tool;
238
+ }
239
+ /**
240
+ * Lists all registered agents by creating a sample instance of each.
241
+ *
242
+ * Used for Cloud registration where we need agent metadata (name, id, tools, etc).
243
+ * Creates one instance per agent to extract metadata.
244
+ *
245
+ * @returns Array of agent instances (one per registered agent)
246
+ */
247
+ listAgents() {
248
+ return Array.from(this.agentsById.values()).map((entry) => entry.factory());
249
+ }
250
+ listTools() {
251
+ return Array.from(this.toolsById.values());
252
+ }
253
+ listWorkflows() {
254
+ return Array.from(this.workflows.values());
255
+ }
256
+ };
257
+
258
+ // src/connection/server-connection.ts
259
+ var import_socket = require("socket.io-client");
260
+ var import_uuid = require("uuid");
261
+ var import_core = require("@nebulaos/core");
262
+ var import_zod_to_json_schema = require("zod-to-json-schema");
263
+
264
+ // src/logger/time.ts
265
+ function nowMs() {
266
+ const anyGlobal = globalThis;
267
+ if (anyGlobal?.performance?.now) return anyGlobal.performance.now();
268
+ return Date.now();
269
+ }
270
+ function durationMs(startMs) {
271
+ return Math.max(0, nowMs() - startMs);
272
+ }
273
+
274
+ // src/logger/summaries.ts
275
+ function summarizeValue(value) {
276
+ if (value === null) return "null";
277
+ if (value === void 0) return "undefined";
278
+ if (typeof value === "string") return `string(len=${value.length})`;
279
+ if (typeof value === "number") return "number";
280
+ if (typeof value === "boolean") return "boolean";
281
+ if (Array.isArray(value)) return `array(len=${value.length})`;
282
+ if (typeof value === "object") return `object(keys=${Object.keys(value).length})`;
283
+ return typeof value;
284
+ }
285
+ function summarizeDomainEvent(event) {
286
+ return {
287
+ type: event.type,
288
+ executionId: event.executionId,
289
+ rootExecutionId: event.rootExecutionId,
290
+ parentExecutionId: event.parentExecutionId,
291
+ correlationId: event.correlationId,
292
+ traceId: event.trace?.traceId,
293
+ spanId: event.trace?.spanId,
294
+ parentSpanId: event.trace?.parentSpanId
295
+ };
296
+ }
297
+ function summarizeTelemetryEvent(event) {
298
+ return {
299
+ type: event.type,
300
+ executionId: event.executionId,
301
+ correlationId: event.correlationId,
302
+ traceId: event.trace?.traceId,
303
+ spanId: event.trace?.spanId,
304
+ parentSpanId: event.trace?.parentSpanId,
305
+ spanKind: event.span?.kind,
306
+ spanName: event.span?.name
307
+ };
308
+ }
309
+ function summarizeDomainBatch(events) {
310
+ const counts = {};
311
+ for (const e of events) {
312
+ counts[e.type] = (counts[e.type] ?? 0) + 1;
313
+ }
314
+ const topTypes = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, 6).map(([type, count]) => `${type}\xD7${count}`);
315
+ return {
316
+ count: events.length,
317
+ topTypes: topTypes.join(", ")
318
+ };
319
+ }
320
+
321
+ // src/connection/server-connection.ts
322
+ var ServerConnection = class {
323
+ constructor(config, logger) {
324
+ this.config = config;
325
+ this.clientId = config.clientId;
326
+ this.instanceId = config.instanceId ?? (0, import_uuid.v4)();
327
+ this.logger = logger;
328
+ this.socket = this.createSocket();
329
+ }
330
+ socket;
331
+ reconnectAttempts = 0;
332
+ registry;
333
+ agents = [];
334
+ tools = [];
335
+ workflows = [];
336
+ snapshotTimer;
337
+ connectionAttempted = false;
338
+ lastConnectError = null;
339
+ hasAuthenticated = false;
340
+ logger;
341
+ commandStarts = /* @__PURE__ */ new Map();
342
+ clientId;
343
+ instanceId;
344
+ /**
345
+ * Sets the registry for executing agents and workflows.
346
+ */
347
+ setRegistry(registry) {
348
+ this.registry = registry;
349
+ }
350
+ /**
351
+ * Stores agents and workflows for registration.
352
+ */
353
+ registerResources(agents, tools, workflows) {
354
+ this.agents = agents;
355
+ this.tools = tools;
356
+ this.workflows = workflows;
357
+ }
358
+ collectAgentsFromWorkflows() {
359
+ const out = [];
360
+ const seen = /* @__PURE__ */ new Set();
361
+ for (const workflow of this.workflows) {
362
+ const workflowWithNodes = workflow;
363
+ if (typeof workflowWithNodes.__internalNodes !== "function") continue;
364
+ const nodes = workflowWithNodes.__internalNodes();
365
+ if (!Array.isArray(nodes)) continue;
366
+ for (const node of nodes) {
367
+ if (node.type !== "agent") continue;
368
+ const agentNode = node;
369
+ const agent = agentNode.agent;
370
+ if (!agent) continue;
371
+ const id = agent.id ?? agent.name;
372
+ if (!id) continue;
373
+ if (seen.has(id)) continue;
374
+ seen.add(id);
375
+ out.push(agent);
376
+ }
377
+ }
378
+ return out;
379
+ }
380
+ collectToolsFromAgents(agents) {
381
+ const out = [];
382
+ const seen = /* @__PURE__ */ new Set();
383
+ for (const agent of agents) {
384
+ const agentWithConfig = agent;
385
+ const config = agentWithConfig.config;
386
+ if (!config || typeof config !== "object") continue;
387
+ const tools = Array.isArray(config.tools) ? config.tools : [];
388
+ for (const tool of tools) {
389
+ if (!tool || typeof tool.id !== "string") continue;
390
+ if (seen.has(tool.id)) continue;
391
+ seen.add(tool.id);
392
+ out.push(tool);
393
+ }
394
+ }
395
+ return out;
396
+ }
397
+ /**
398
+ * Creates and configures the Socket.IO client.
399
+ */
400
+ createSocket() {
401
+ const socket = (0, import_socket.io)(`${this.config.url}/runtime`, {
402
+ auth: { token: this.config.apiKey },
403
+ reconnection: true,
404
+ reconnectionDelay: 1e3,
405
+ reconnectionDelayMax: 5e3,
406
+ transports: ["websocket"]
407
+ });
408
+ this.setupEventHandlers(socket);
409
+ return socket;
410
+ }
411
+ /**
412
+ * Sets up Socket.IO event handlers
413
+ */
414
+ setupEventHandlers(socket) {
415
+ socket.on("connect", () => {
416
+ if (this.logger) {
417
+ this.logger.info("WS connected", { url: this.config.url, transport: "websocket" });
418
+ } else {
419
+ console.log("\u2705 Connected to NebulaOS Cloud");
420
+ }
421
+ this.reconnectAttempts = 0;
422
+ this.lastConnectError = null;
423
+ this.hasAuthenticated = false;
424
+ });
425
+ socket.on("disconnect", (reason) => {
426
+ if (!this.hasAuthenticated && reason === "io server disconnect") {
427
+ if (this.logger) {
428
+ this.logger.error("WS rejected by server (likely auth failure)", { reason });
429
+ } else {
430
+ console.error(
431
+ `\u{1F510} Connection rejected by server (likely authentication failure). Please check your NEBULAOS_API_KEY environment variable. (Reason: ${reason})`
432
+ );
433
+ }
434
+ } else {
435
+ if (this.logger) {
436
+ this.logger.warn("WS disconnected", { reason });
437
+ } else {
438
+ console.warn(`\u26A0\uFE0F Disconnected: ${reason}`);
439
+ }
440
+ }
441
+ });
442
+ socket.on("connect_error", (error) => {
443
+ this.reconnectAttempts++;
444
+ this.lastConnectError = error;
445
+ const rawMessage = error instanceof Error ? error.message : String(error);
446
+ const rawStack = error instanceof Error ? error.stack : void 0;
447
+ const errorWithData = error;
448
+ const serverData = errorWithData.data;
449
+ const serverMessage = typeof serverData?.message === "string" ? serverData.message : typeof serverData?.error === "string" ? serverData.error : void 0;
450
+ const lower = rawMessage.toLowerCase();
451
+ const looksLikeAuth = lower.includes("authorization") || lower.includes("token") || lower.includes("unauthorized") || lower.includes("authentication") || lower.includes("api key") || lower.includes("invalid");
452
+ const meta = {
453
+ url: this.config.url,
454
+ attempt: this.reconnectAttempts,
455
+ serverMessage,
456
+ serverData,
457
+ // Preserve raw stack for debugging (dev use)
458
+ stack: rawStack,
459
+ hint: looksLikeAuth ? "Looks like auth failure. Check NEBULAOS_API_KEY." : "See error details above (network/DNS/URL/CORS)."
460
+ };
461
+ if (this.logger) {
462
+ this.logger.error(`WS connect_error: ${rawMessage}`, meta);
463
+ } else {
464
+ console.error(`\u274C connect_error (attempt ${this.reconnectAttempts}): ${rawMessage}`, meta);
465
+ }
466
+ });
467
+ socket.on("client:online", () => {
468
+ this.hasAuthenticated = true;
469
+ if (this.logger) {
470
+ this.logger.info("WS authenticated (client:online)");
471
+ }
472
+ void this.registerFull();
473
+ });
474
+ socket.on(
475
+ "command:execute:agent",
476
+ (payload) => void this.handleAgentCommand(payload)
477
+ );
478
+ socket.on(
479
+ "command:execute:workflow",
480
+ (payload) => void this.handleWorkflowCommand(payload)
481
+ );
482
+ socket.on(
483
+ "command:execute:tool",
484
+ (payload) => void this.handleToolCommand(payload)
485
+ );
486
+ }
487
+ /**
488
+ * Handles agent execution commands from the server
489
+ */
490
+ async handleAgentCommand(payload) {
491
+ const startedAt = nowMs();
492
+ this.commandStarts.set(payload.commandId, startedAt);
493
+ if (this.logger) {
494
+ this.logger.info("Command received", {
495
+ type: payload.type,
496
+ target: payload.targetName,
497
+ commandId: payload.commandId,
498
+ executionId: payload.executionId,
499
+ input: summarizeValue(payload.input)
500
+ });
501
+ } else {
502
+ console.log(`\u{1F4E5} Received agent execution command: ${payload.targetName}`);
503
+ }
504
+ if (!this.registry) {
505
+ this.sendError(payload.commandId, {
506
+ code: "REGISTRY_NOT_SET",
507
+ message: "Registry not configured"
508
+ });
509
+ return;
510
+ }
511
+ try {
512
+ const agent = this.registry.getAgent(payload.targetName);
513
+ let agentInput;
514
+ if (payload.input) {
515
+ if (typeof payload.input === "string") {
516
+ agentInput = payload.input;
517
+ } else if (Array.isArray(payload.input)) {
518
+ agentInput = payload.input;
519
+ } else {
520
+ agentInput = JSON.stringify(payload.input);
521
+ }
522
+ }
523
+ const options = payload.options && typeof payload.options === "object" ? { ...payload.options, executionId: payload.executionId } : { executionId: payload.executionId };
524
+ const result = await import_core.ExecutionContext.run(
525
+ {
526
+ executionId: payload.executionId,
527
+ rootExecutionId: payload.executionId,
528
+ parentExecutionId: void 0
529
+ },
530
+ async () => agent.execute(agentInput, options)
531
+ );
532
+ this.sendResponse(payload.commandId, payload.executionId, result);
533
+ if (this.logger) {
534
+ this.logger.info("Command handled", {
535
+ commandId: payload.commandId,
536
+ executionId: payload.executionId,
537
+ durationMs: Number(durationMs(startedAt).toFixed(2))
538
+ });
539
+ }
540
+ } catch (error) {
541
+ this.sendError(payload.commandId, {
542
+ code: "AGENT_EXECUTION_ERROR",
543
+ message: error instanceof Error ? error.message : "Unknown error",
544
+ details: error
545
+ });
546
+ }
547
+ }
548
+ /**
549
+ * Handles workflow execution commands from the server
550
+ */
551
+ async handleWorkflowCommand(payload) {
552
+ const startedAt = nowMs();
553
+ this.commandStarts.set(payload.commandId, startedAt);
554
+ if (this.logger) {
555
+ this.logger.info("Command received", {
556
+ type: payload.type,
557
+ target: payload.targetName,
558
+ commandId: payload.commandId,
559
+ executionId: payload.executionId,
560
+ input: summarizeValue(payload.input)
561
+ });
562
+ } else {
563
+ console.log(`\u{1F4E5} Received workflow execution command: ${payload.targetName}`);
564
+ }
565
+ if (!this.registry) {
566
+ this.sendError(payload.commandId, {
567
+ code: "REGISTRY_NOT_SET",
568
+ message: "Registry not configured"
569
+ });
570
+ return;
571
+ }
572
+ try {
573
+ const workflow = this.registry.getWorkflow(payload.targetName);
574
+ const result = await import_core.ExecutionContext.run(
575
+ {
576
+ executionId: payload.executionId,
577
+ rootExecutionId: payload.executionId,
578
+ parentExecutionId: void 0
579
+ },
580
+ async () => workflow.run(
581
+ payload.input,
582
+ { executionId: payload.executionId }
583
+ )
584
+ );
585
+ this.sendResponse(payload.commandId, payload.executionId, result);
586
+ if (this.logger) {
587
+ this.logger.info("Command handled", {
588
+ commandId: payload.commandId,
589
+ executionId: payload.executionId,
590
+ durationMs: Number(durationMs(startedAt).toFixed(2))
591
+ });
592
+ }
593
+ } catch (error) {
594
+ this.sendError(payload.commandId, {
595
+ code: "WORKFLOW_EXECUTION_ERROR",
596
+ message: error instanceof Error ? error.message : "Unknown error",
597
+ details: error
598
+ });
599
+ }
600
+ }
601
+ /**
602
+ * Handles tool execution commands from the server.
603
+ */
604
+ async handleToolCommand(payload) {
605
+ const startedAt = nowMs();
606
+ this.commandStarts.set(payload.commandId, startedAt);
607
+ if (this.logger) {
608
+ this.logger.info("Command received", {
609
+ type: payload.type,
610
+ target: payload.targetName,
611
+ commandId: payload.commandId,
612
+ executionId: payload.executionId,
613
+ input: summarizeValue(payload.input)
614
+ });
615
+ } else {
616
+ console.log(`\u{1F4E5} Received tool execution command: ${payload.targetName}`);
617
+ }
618
+ if (!this.registry) {
619
+ this.sendError(payload.commandId, {
620
+ code: "REGISTRY_NOT_SET",
621
+ message: "Registry not configured"
622
+ });
623
+ return;
624
+ }
625
+ try {
626
+ const tool = this.registry.getTool(payload.targetName);
627
+ const result = await import_core.ExecutionContext.run(
628
+ {
629
+ executionId: payload.executionId,
630
+ rootExecutionId: payload.executionId,
631
+ parentExecutionId: void 0
632
+ },
633
+ async () => import_core.Tracing.withSpan(
634
+ {
635
+ kind: "tool",
636
+ name: `tool:${tool.id}`,
637
+ executionId: payload.executionId,
638
+ data: {
639
+ toolId: tool.id
640
+ }
641
+ },
642
+ async () => {
643
+ return tool.execute({}, payload.input);
644
+ }
645
+ )
646
+ );
647
+ this.sendResponse(payload.commandId, payload.executionId, result);
648
+ if (this.logger) {
649
+ this.logger.info("Command handled", {
650
+ commandId: payload.commandId,
651
+ executionId: payload.executionId,
652
+ durationMs: Number(durationMs(startedAt).toFixed(2))
653
+ });
654
+ }
655
+ } catch (error) {
656
+ this.sendError(payload.commandId, {
657
+ code: "TOOL_EXECUTION_ERROR",
658
+ message: error instanceof Error ? error.message : "Unknown error",
659
+ details: error
660
+ });
661
+ }
662
+ }
663
+ /**
664
+ * Executes an agent and waits for completion
665
+ */
666
+ // NOTE:
667
+ // We intentionally removed the previous "wait for start event to resolve executionId" logic.
668
+ // Cloud already provides a stable `payload.executionId` and expects it back in `command:response`.
669
+ /**
670
+ * Sends a successful command response to the server
671
+ */
672
+ sendResponse(commandId, executionId, result) {
673
+ const response = {
674
+ commandId,
675
+ executionId,
676
+ result
677
+ };
678
+ this.socket.emit("command:response", response);
679
+ const startedAt = this.commandStarts.get(commandId);
680
+ if (this.logger) {
681
+ this.logger.info("Command response sent", {
682
+ commandId,
683
+ executionId,
684
+ durationMs: startedAt ? Number(durationMs(startedAt).toFixed(2)) : void 0,
685
+ result: summarizeValue(result)
686
+ });
687
+ } else {
688
+ console.log(`\u2705 Sent response for command ${commandId}`);
689
+ }
690
+ }
691
+ /**
692
+ * Sends an error response to the server
693
+ */
694
+ sendError(commandId, error) {
695
+ const errorResponse = {
696
+ commandId,
697
+ error
698
+ };
699
+ this.socket.emit("command:error", errorResponse);
700
+ const startedAt = this.commandStarts.get(commandId);
701
+ if (this.logger) {
702
+ this.logger.error("Command error sent", {
703
+ commandId,
704
+ durationMs: startedAt ? Number(durationMs(startedAt).toFixed(2)) : void 0,
705
+ code: error.code,
706
+ message: error.message
707
+ });
708
+ } else {
709
+ console.error(`\u274C Sent error for command ${commandId}:`, error.message);
710
+ }
711
+ }
712
+ /**
713
+ * Gets the stable ID for an agent (id if available, otherwise name).
714
+ */
715
+ getAgentId(agent) {
716
+ return agent.id ?? agent.name;
717
+ }
718
+ /**
719
+ * Gets the description from an agent if available.
720
+ */
721
+ getAgentDescription(agent) {
722
+ const agentWithConfig = agent;
723
+ const config = agentWithConfig.config;
724
+ if (config && typeof config === "object" && "description" in config) {
725
+ return typeof config.description === "string" ? config.description : void 0;
726
+ }
727
+ return void 0;
728
+ }
729
+ /**
730
+ * Builds the Cloud registration payload (RegisterClientDto compatible).
731
+ */
732
+ async buildRegisterPayload(input) {
733
+ const workflowAgents = this.collectAgentsFromWorkflows();
734
+ const agents = [...this.agents];
735
+ for (const agent of workflowAgents) {
736
+ const id = this.getAgentId(agent);
737
+ const exists = agents.some((x) => this.getAgentId(x) === id);
738
+ if (!exists) agents.push(agent);
739
+ }
740
+ const agentTools = this.collectToolsFromAgents(agents);
741
+ const tools = [...this.tools];
742
+ for (const tool of agentTools) {
743
+ if (tools.some((x) => x.id === tool.id)) continue;
744
+ tools.push(tool);
745
+ }
746
+ const resources = [
747
+ ...agents.map((agent) => {
748
+ const runtimeResourceId = this.getAgentId(agent);
749
+ const description = this.getAgentDescription(agent);
750
+ return {
751
+ type: "agent",
752
+ runtimeResourceId,
753
+ displayName: agent.name,
754
+ kind: agent.kind,
755
+ description
756
+ };
757
+ }),
758
+ ...this.workflows.map((workflow) => {
759
+ const definition = input.includeDefinitions ? this.buildWorkflowDefinition(workflow) : void 0;
760
+ return {
761
+ type: "workflow",
762
+ runtimeResourceId: workflow.id,
763
+ displayName: workflow.name ?? workflow.id,
764
+ description: workflow.description,
765
+ ...definition ? { definition } : {}
766
+ };
767
+ }),
768
+ ...tools.map((tool) => {
769
+ const definition = input.includeDefinitions ? this.buildToolDefinition(tool) : void 0;
770
+ return {
771
+ type: "tool",
772
+ runtimeResourceId: tool.id,
773
+ displayName: tool.id,
774
+ description: tool.description,
775
+ ...definition ? { definition } : {}
776
+ };
777
+ })
778
+ ];
779
+ if (input.includeDefinitions) {
780
+ await Promise.all(
781
+ resources.filter((r) => r.type === "agent").map(async (r) => {
782
+ const agent = agents.find((a) => this.getAgentId(a) === r.runtimeResourceId);
783
+ if (!agent) return;
784
+ const def = await this.buildAgentDefinition(agent);
785
+ if (def) r.definition = def;
786
+ })
787
+ );
788
+ }
789
+ return {
790
+ instanceId: this.instanceId,
791
+ // Dedupe by (type + runtimeResourceId) to avoid duplicates when tools are discovered from multiple sources.
792
+ resources: resources.filter((r, idx, arr) => {
793
+ const key = `${r.type}:${r.runtimeResourceId}`;
794
+ return idx === arr.findIndex((x) => `${x.type}:${x.runtimeResourceId}` === key);
795
+ })
796
+ };
797
+ }
798
+ isZodSchema(value) {
799
+ return !!value && typeof value === "object" && "safeParse" in value && typeof value.safeParse === "function";
800
+ }
801
+ isInstructionResolvable(value) {
802
+ return !!value && typeof value === "object" && "resolve" in value && typeof value.resolve === "function";
803
+ }
804
+ /**
805
+ * Agent definition payload for Cloud persistence + UI inspection.
806
+ *
807
+ * IMPORTANT:
808
+ * - Do NOT embed tool schemas here; tools are registered as independent resources.
809
+ * - We only reference tool ids to avoid duplication.
810
+ */
811
+ async buildAgentDefinition(agent) {
812
+ const agentWithConfig = agent;
813
+ const config = agentWithConfig.config;
814
+ if (!config || typeof config !== "object") return void 0;
815
+ const model = config.model;
816
+ const instructions = config.instructions;
817
+ const tools = Array.isArray(config.tools) ? config.tools : [];
818
+ const interceptors = config.interceptors;
819
+ let instructionsText = "";
820
+ if (typeof instructions === "string") {
821
+ instructionsText = instructions;
822
+ } else if (this.isInstructionResolvable(instructions)) {
823
+ instructionsText = await instructions.resolve();
824
+ }
825
+ const requestInterceptorNames = interceptors?.request?.map((fn) => {
826
+ if (typeof fn === "function" && fn.name) {
827
+ return fn.name;
828
+ }
829
+ return "anonymous";
830
+ }) ?? [];
831
+ const responseInterceptorNames = interceptors?.response?.map((fn) => {
832
+ if (typeof fn === "function" && fn.name) {
833
+ return fn.name;
834
+ }
835
+ return "anonymous";
836
+ }) ?? [];
837
+ const modelInfo = model && typeof model === "object" && "providerName" in model && "modelName" in model ? {
838
+ provider: model.providerName,
839
+ model: model.modelName,
840
+ capabilities: model.capabilities
841
+ } : void 0;
842
+ return {
843
+ schemaVersion: 1,
844
+ resourceType: "agent",
845
+ agentId: agent.id ?? agent.name,
846
+ agentName: agent.name,
847
+ kind: agent.kind,
848
+ model: modelInfo,
849
+ instructionsText,
850
+ toolIds: tools.map((t) => t.id),
851
+ interceptors: {
852
+ request: requestInterceptorNames,
853
+ response: responseInterceptorNames
854
+ }
855
+ };
856
+ }
857
+ /**
858
+ * Builds a workflow definition payload suitable for Cloud persistence + UI graph rendering.
859
+ *
860
+ * The base definition comes from `workflow.describe()` (graph nodes/edges).
861
+ * If the workflow was created with an input Zod schema, we also attach `inputSchema`
862
+ * as a JSON Schema object so the UI can render an input form.
863
+ */
864
+ buildWorkflowDefinition(workflow) {
865
+ const workflowWithNodes = workflow;
866
+ if (typeof workflowWithNodes.describe !== "function") return void 0;
867
+ const base = workflowWithNodes.describe();
868
+ if (!base || typeof base !== "object") return void 0;
869
+ const workflowUnknown = workflow;
870
+ const workflowWithConfig = workflowUnknown && typeof workflowUnknown === "object" && "config" in workflowUnknown && typeof workflowUnknown.config === "object" ? workflowUnknown.config?.inputSchema : void 0;
871
+ const inputSchemaCandidate = workflowWithConfig;
872
+ const withSchemas = this.isZodSchema(inputSchemaCandidate) ? { ...base, inputSchema: (0, import_zod_to_json_schema.zodToJsonSchema)(inputSchemaCandidate) } : base;
873
+ const nodes = Array.isArray(withSchemas.nodes) ? withSchemas.nodes : void 0;
874
+ const agentIds = Array.isArray(nodes) ? nodes.map((n) => typeof n?.agentId === "string" && n.agentId ? n.agentId : void 0).filter((x) => !!x) : [];
875
+ return {
876
+ ...withSchemas,
877
+ schemaVersion: 1,
878
+ resourceType: "workflow",
879
+ refs: {
880
+ agents: Array.from(new Set(agentIds)),
881
+ tools: [],
882
+ workflows: []
883
+ }
884
+ };
885
+ }
886
+ buildToolDefinition(tool) {
887
+ const inputSchemaCandidate = tool.inputSchema;
888
+ const inputSchema = this.isZodSchema(inputSchemaCandidate) ? (0, import_zod_to_json_schema.zodToJsonSchema)(inputSchemaCandidate) : void 0;
889
+ return {
890
+ schemaVersion: 1,
891
+ resourceType: "tool",
892
+ toolId: tool.id,
893
+ description: tool.description,
894
+ ...inputSchema ? { inputSchema } : {},
895
+ outputType: "unknown"
896
+ };
897
+ }
898
+ /**
899
+ * Full registration (discovery) call. This is idempotent on the Cloud side.
900
+ */
901
+ async registerFull() {
902
+ const payload = await this.buildRegisterPayload({ includeDefinitions: true });
903
+ const start = nowMs();
904
+ return new Promise((resolve, reject) => {
905
+ const timeout = setTimeout(() => {
906
+ reject(
907
+ new ConnectionError(
908
+ "Registration timeout: Server did not respond to registration request. Please check your connection and try again."
909
+ )
910
+ );
911
+ }, 1e4);
912
+ this.socket.emit("client:register:full", payload, (response) => {
913
+ clearTimeout(timeout);
914
+ if (response.success) {
915
+ const defCount = payload.resources.filter((r) => !!r.definition).length;
916
+ const agentDefCount = payload.resources.filter(
917
+ (r) => r.type === "agent" && !!r.definition
918
+ ).length;
919
+ const workflowDefCount = payload.resources.filter(
920
+ (r) => r.type === "workflow" && !!r.definition
921
+ ).length;
922
+ const toolDefCount = payload.resources.filter(
923
+ (r) => r.type === "tool" && !!r.definition
924
+ ).length;
925
+ if (this.logger) {
926
+ this.logger.info("Client registered", {
927
+ clientId: this.clientId,
928
+ instanceId: payload.instanceId,
929
+ resources: payload.resources.length,
930
+ definitions: {
931
+ total: defCount,
932
+ agents: agentDefCount,
933
+ workflows: workflowDefCount,
934
+ tools: toolDefCount
935
+ },
936
+ durationMs: Number(durationMs(start).toFixed(2))
937
+ });
938
+ } else {
939
+ console.log(
940
+ `\u2705 Client registered successfully: ${this.clientId} (${payload.instanceId})`
941
+ );
942
+ console.log(` - ${payload.resources.length} resources registered`);
943
+ console.log(
944
+ ` - definitions: total=${defCount}, agents=${agentDefCount}, workflows=${workflowDefCount}, tools=${toolDefCount}`
945
+ );
946
+ }
947
+ resolve();
948
+ return;
949
+ }
950
+ const errorMessage = response.error ?? "Unknown registration error";
951
+ if (errorMessage.includes("authentication") || errorMessage.includes("Unauthorized") || errorMessage.includes("token") || errorMessage.includes("API key")) {
952
+ reject(
953
+ new AuthenticationError(
954
+ `Registration failed: ${errorMessage}. Please check your NEBULAOS_API_KEY environment variable.`
955
+ )
956
+ );
957
+ return;
958
+ }
959
+ reject(
960
+ new ConnectionError(
961
+ `Registration failed: ${errorMessage}. Please check your connection and configuration.`
962
+ )
963
+ );
964
+ });
965
+ });
966
+ }
967
+ startSnapshotLoop() {
968
+ const intervalMs = this.config.snapshotIntervalMs ?? 6e4;
969
+ if (this.snapshotTimer) clearInterval(this.snapshotTimer);
970
+ this.snapshotTimer = setInterval(() => {
971
+ if (!this.socket.connected) return;
972
+ void this.buildRegisterPayload({ includeDefinitions: false }).then((payload) => {
973
+ this.socket.emit("client:snapshot", payload);
974
+ });
975
+ }, intervalMs);
976
+ }
977
+ /**
978
+ * Connects to the Cloud runtime.
979
+ */
980
+ async connect() {
981
+ return new Promise((resolve, reject) => {
982
+ if (this.socket.connected) {
983
+ resolve();
984
+ return;
985
+ }
986
+ this.connectionAttempted = true;
987
+ this.hasAuthenticated = false;
988
+ let resolved = false;
989
+ let authTimeout;
990
+ const timeout = setTimeout(() => {
991
+ if (resolved) return;
992
+ resolved = true;
993
+ this.connectionAttempted = false;
994
+ reject(
995
+ new ConnectionError(
996
+ `Connection timeout after 10 seconds. Please check your NEBULAOS_URL (${this.config.url}) and ensure the server is running.`
997
+ )
998
+ );
999
+ }, 1e4);
1000
+ const cleanup = () => {
1001
+ clearTimeout(timeout);
1002
+ if (authTimeout) clearTimeout(authTimeout);
1003
+ this.socket.off("connect", connectHandler);
1004
+ this.socket.off("connect_error", errorHandler);
1005
+ this.socket.off("disconnect", disconnectHandler);
1006
+ this.socket.off("client:online", onlineHandler);
1007
+ };
1008
+ const onlineHandler = () => {
1009
+ if (resolved) return;
1010
+ resolved = true;
1011
+ cleanup();
1012
+ this.connectionAttempted = false;
1013
+ this.hasAuthenticated = true;
1014
+ this.startSnapshotLoop();
1015
+ resolve();
1016
+ };
1017
+ const connectHandler = () => {
1018
+ clearTimeout(timeout);
1019
+ authTimeout = setTimeout(() => {
1020
+ if (resolved) return;
1021
+ resolved = true;
1022
+ cleanup();
1023
+ this.connectionAttempted = false;
1024
+ reject(
1025
+ new AuthenticationError(
1026
+ `Connected but authentication was not confirmed by server. Please check your NEBULAOS_API_KEY environment variable.`
1027
+ )
1028
+ );
1029
+ }, 2500);
1030
+ };
1031
+ const disconnectHandler = (reason) => {
1032
+ if (resolved) return;
1033
+ if (this.connectionAttempted && reason === "io server disconnect") {
1034
+ resolved = true;
1035
+ cleanup();
1036
+ this.connectionAttempted = false;
1037
+ if (this.lastConnectError) {
1038
+ const errorMsg = this.lastConnectError.message.toLowerCase();
1039
+ if (errorMsg.includes("authorization") || errorMsg.includes("token") || errorMsg.includes("unauthorized") || errorMsg.includes("authentication") || errorMsg.includes("api key") || errorMsg.includes("invalid")) {
1040
+ reject(
1041
+ new AuthenticationError(
1042
+ `Authentication failed: Invalid API key or missing credentials. Please check your NEBULAOS_API_KEY environment variable. (Error: ${this.lastConnectError.message})`
1043
+ )
1044
+ );
1045
+ return;
1046
+ }
1047
+ }
1048
+ reject(
1049
+ new AuthenticationError(
1050
+ `Connection rejected by server: Invalid API key or missing credentials. Please check your NEBULAOS_API_KEY environment variable. (Disconnect reason: ${reason})`
1051
+ )
1052
+ );
1053
+ return;
1054
+ }
1055
+ };
1056
+ const errorHandler = (error) => {
1057
+ if (resolved) return;
1058
+ const errorMsg = error.message.toLowerCase();
1059
+ if (errorMsg.includes("authorization") || errorMsg.includes("token") || errorMsg.includes("unauthorized") || errorMsg.includes("authentication") || errorMsg.includes("api key") || errorMsg.includes("invalid")) {
1060
+ resolved = true;
1061
+ cleanup();
1062
+ this.connectionAttempted = false;
1063
+ reject(
1064
+ new AuthenticationError(
1065
+ `Authentication failed: Invalid API key or missing credentials. Please check your NEBULAOS_API_KEY environment variable. (Server error: ${error.message})`
1066
+ )
1067
+ );
1068
+ return;
1069
+ }
1070
+ if (errorMsg.includes("timeout") || errorMsg.includes("econnrefused")) {
1071
+ resolved = true;
1072
+ cleanup();
1073
+ this.connectionAttempted = false;
1074
+ reject(
1075
+ new ConnectionError(
1076
+ `Connection failed: Unable to reach NebulaOS Cloud at ${this.config.url}. Please check your NEBULAOS_URL environment variable and ensure the server is running. (Error: ${error.message})`
1077
+ )
1078
+ );
1079
+ return;
1080
+ }
1081
+ setTimeout(() => {
1082
+ if (!resolved) {
1083
+ resolved = true;
1084
+ cleanup();
1085
+ this.connectionAttempted = false;
1086
+ reject(
1087
+ new ConnectionError(
1088
+ `Connection error: ${error.message}. Please check your NEBULAOS_URL (${this.config.url}) and NEBULAOS_API_KEY.`
1089
+ )
1090
+ );
1091
+ }
1092
+ }, 1e3);
1093
+ };
1094
+ this.socket.once("connect", connectHandler);
1095
+ this.socket.once("connect_error", errorHandler);
1096
+ this.socket.once("disconnect", disconnectHandler);
1097
+ this.socket.once("client:online", onlineHandler);
1098
+ this.socket.connect();
1099
+ });
1100
+ }
1101
+ /**
1102
+ * Disconnects from the server gracefully
1103
+ */
1104
+ async disconnect() {
1105
+ console.log("\u{1F6D1} Disconnecting from NebulaOS Cloud...");
1106
+ if (this.snapshotTimer) clearInterval(this.snapshotTimer);
1107
+ this.snapshotTimer = void 0;
1108
+ if (this.socket.connected) {
1109
+ await this.sendShutdownNotice({ reason: "disconnect()" });
1110
+ this.socket.disconnect();
1111
+ }
1112
+ console.log("\u{1F44B} Disconnected");
1113
+ }
1114
+ /**
1115
+ * Best-effort shutdown notice to allow the Cloud to immediately clean up instance availability.
1116
+ * Never throws: disconnect must be resilient.
1117
+ */
1118
+ async sendShutdownNotice(input) {
1119
+ const payload = {
1120
+ instanceId: this.instanceId,
1121
+ ...input.reason ? { reason: input.reason } : {}
1122
+ };
1123
+ await new Promise((resolve) => {
1124
+ const timeout = setTimeout(() => resolve(), 1500);
1125
+ try {
1126
+ this.socket.emit("client:shutdown", payload, (_response) => {
1127
+ clearTimeout(timeout);
1128
+ resolve();
1129
+ });
1130
+ } catch {
1131
+ clearTimeout(timeout);
1132
+ resolve();
1133
+ }
1134
+ });
1135
+ }
1136
+ /**
1137
+ * Checks if the client is connected
1138
+ */
1139
+ isConnected() {
1140
+ return this.socket.connected;
1141
+ }
1142
+ /**
1143
+ * Returns the client ID
1144
+ */
1145
+ getClientId() {
1146
+ return this.clientId;
1147
+ }
1148
+ getInstanceId() {
1149
+ return this.instanceId;
1150
+ }
1151
+ /**
1152
+ * Returns the socket instance for telemetry
1153
+ */
1154
+ getSocket() {
1155
+ return this.socket;
1156
+ }
1157
+ };
1158
+
1159
+ // src/client.ts
1160
+ var import_core4 = require("@nebulaos/core");
1161
+
1162
+ // src/telemetry/http-telemetry-exporter.ts
1163
+ var import_axios = __toESM(require("axios"));
1164
+ var HttpTelemetryExporter = class {
1165
+ constructor(input, config = {}, logger) {
1166
+ this.input = input;
1167
+ this.logger = logger;
1168
+ this.baseUrl = input.cloudUrl.replace(/\/+$/, "");
1169
+ this.timeoutMs = config.timeoutMs ?? 2e3;
1170
+ }
1171
+ baseUrl;
1172
+ timeoutMs;
1173
+ async exportBatch(events) {
1174
+ for (const event of events) {
1175
+ await this.sendSingle(event);
1176
+ }
1177
+ }
1178
+ async sendSingle(event) {
1179
+ const url = `${this.baseUrl}/telemetry/events`;
1180
+ const start = nowMs();
1181
+ try {
1182
+ const res = await import_axios.default.post(
1183
+ url,
1184
+ { events: [event] },
1185
+ {
1186
+ timeout: this.timeoutMs,
1187
+ headers: {
1188
+ "Content-Type": "application/json",
1189
+ ...this.input.apiKey ? { Authorization: `Bearer ${this.input.apiKey}` } : {}
1190
+ },
1191
+ // Telemetry should never throw due to non-2xx.
1192
+ validateStatus: () => true
1193
+ }
1194
+ );
1195
+ if (this.logger) {
1196
+ this.logger.debug(
1197
+ "HTTP telemetry sent",
1198
+ {
1199
+ method: "POST",
1200
+ path: "/telemetry/events",
1201
+ status: res.status,
1202
+ durationMs: Number(durationMs(start).toFixed(2)),
1203
+ timeoutMs: this.timeoutMs,
1204
+ event: summarizeTelemetryEvent(event)
1205
+ }
1206
+ );
1207
+ }
1208
+ } catch (err) {
1209
+ if (this.logger) {
1210
+ const message = err instanceof Error ? err.message : String(err);
1211
+ this.logger.warn(
1212
+ "HTTP telemetry failed (best-effort)",
1213
+ {
1214
+ method: "POST",
1215
+ path: "/telemetry/events",
1216
+ durationMs: Number(durationMs(start).toFixed(2)),
1217
+ timeoutMs: this.timeoutMs,
1218
+ error: message,
1219
+ event: summarizeTelemetryEvent(event)
1220
+ }
1221
+ );
1222
+ }
1223
+ }
1224
+ }
1225
+ };
1226
+
1227
+ // src/domain-events/http-domain-events-exporter.ts
1228
+ var import_axios2 = __toESM(require("axios"));
1229
+ var HttpDomainEventsExporter = class {
1230
+ constructor(input, config = {}, logger) {
1231
+ this.input = input;
1232
+ this.logger = logger;
1233
+ this.baseUrl = input.cloudUrl.replace(/\/+$/, "");
1234
+ this.timeoutMs = config.timeoutMs ?? 2e3;
1235
+ }
1236
+ baseUrl;
1237
+ timeoutMs;
1238
+ async exportBatch(events) {
1239
+ if (events.length === 0) return;
1240
+ const url = `${this.baseUrl}/execution/events`;
1241
+ const start = nowMs();
1242
+ try {
1243
+ const res = await import_axios2.default.post(
1244
+ url,
1245
+ { events },
1246
+ {
1247
+ timeout: this.timeoutMs,
1248
+ headers: {
1249
+ "Content-Type": "application/json",
1250
+ ...this.input.apiKey ? { Authorization: `Bearer ${this.input.apiKey}` } : {}
1251
+ },
1252
+ // Domain events should never throw due to non-2xx.
1253
+ validateStatus: () => true
1254
+ }
1255
+ );
1256
+ if (this.logger) {
1257
+ this.logger.debug(
1258
+ "HTTP domain events sent",
1259
+ {
1260
+ method: "POST",
1261
+ path: "/execution/events",
1262
+ status: res.status,
1263
+ durationMs: Number(durationMs(start).toFixed(2)),
1264
+ timeoutMs: this.timeoutMs,
1265
+ batch: summarizeDomainBatch(events),
1266
+ sample: events.slice(0, 3).map((e) => summarizeDomainEvent(e))
1267
+ }
1268
+ );
1269
+ }
1270
+ } catch {
1271
+ if (this.logger) {
1272
+ this.logger.warn(
1273
+ "HTTP domain events failed (best-effort)",
1274
+ {
1275
+ method: "POST",
1276
+ path: "/execution/events",
1277
+ durationMs: Number(durationMs(start).toFixed(2)),
1278
+ timeoutMs: this.timeoutMs,
1279
+ batch: summarizeDomainBatch(events),
1280
+ sample: events.slice(0, 3).map((e) => summarizeDomainEvent(e))
1281
+ }
1282
+ );
1283
+ }
1284
+ }
1285
+ }
1286
+ };
1287
+
1288
+ // src/logger/client-debug.ts
1289
+ function parseBool(value) {
1290
+ if (!value) return false;
1291
+ const v = value.trim().toLowerCase();
1292
+ return v === "1" || v === "true" || v === "yes" || v === "on";
1293
+ }
1294
+ function resolveClientDebugConfig(input) {
1295
+ const env = process.env.NEBULAOS_CLIENT_DEBUG?.trim().toLowerCase();
1296
+ if (env) {
1297
+ if (env === "off" || env === "0" || env === "false") {
1298
+ return { enabled: false, level: "none" };
1299
+ }
1300
+ if (env === "debug") {
1301
+ return { enabled: true, level: "debug" };
1302
+ }
1303
+ if (env === "info") {
1304
+ return { enabled: true, level: "info" };
1305
+ }
1306
+ if (parseBool(env)) {
1307
+ return { enabled: true, level: "debug" };
1308
+ }
1309
+ }
1310
+ if (input?.logLevel) {
1311
+ return {
1312
+ enabled: input.logLevel !== "none",
1313
+ level: input.logLevel
1314
+ };
1315
+ }
1316
+ const enabled = input?.enabled ?? false;
1317
+ const level = input?.level ?? (enabled ? "debug" : "none");
1318
+ return { enabled, level };
1319
+ }
1320
+
1321
+ // src/client.ts
1322
+ var import_uuid2 = require("uuid");
1323
+
1324
+ // src/tracing/setup.ts
1325
+ var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
1326
+ var import_resources = require("@opentelemetry/resources");
1327
+ var import_context_async_hooks = require("@opentelemetry/context-async-hooks");
1328
+ var import_instrumentation_http = require("@opentelemetry/instrumentation-http");
1329
+ var import_instrumentation = require("@opentelemetry/instrumentation");
1330
+ function setupOTelTracing(config) {
1331
+ const resource = new import_resources.Resource({
1332
+ "service.name": config.serviceName
1333
+ });
1334
+ const provider = new import_sdk_trace_base.BasicTracerProvider({ resource });
1335
+ provider.register({
1336
+ contextManager: new import_context_async_hooks.AsyncLocalStorageContextManager()
1337
+ });
1338
+ (0, import_instrumentation.registerInstrumentations)({
1339
+ tracerProvider: provider,
1340
+ instrumentations: [
1341
+ new import_instrumentation_http.HttpInstrumentation()
1342
+ ]
1343
+ });
1344
+ }
1345
+
1346
+ // src/tracing/otel-provider.ts
1347
+ var import_api = require("@opentelemetry/api");
1348
+ var import_core2 = require("@nebulaos/core");
1349
+ var OTelTracingProvider = class {
1350
+ constructor(serviceName, exporter) {
1351
+ this.exporter = exporter;
1352
+ this.tracer = import_api.trace.getTracer(serviceName);
1353
+ }
1354
+ tracer;
1355
+ /**
1356
+ * Ensures an OTel ID string is in W3C lowercase hex format.
1357
+ *
1358
+ * If the value is already valid hex of the expected length it is returned
1359
+ * as-is. Otherwise we assume it is base64-encoded and convert it.
1360
+ *
1361
+ * @param id Raw ID from OTel spanContext()
1362
+ * @param hexLen Expected hex length (16 for spanId, 32 for traceId)
1363
+ */
1364
+ ensureW3cHex(id, hexLen) {
1365
+ if (!id) return void 0;
1366
+ const hexRegex = hexLen === 16 ? /^[0-9a-f]{16}$/ : /^[0-9a-f]{32}$/;
1367
+ if (hexRegex.test(id)) return id;
1368
+ try {
1369
+ const hex = Buffer.from(id, "base64").toString("hex");
1370
+ return hex.slice(0, hexLen).padStart(hexLen, "0");
1371
+ } catch {
1372
+ return void 0;
1373
+ }
1374
+ }
1375
+ startSpan(input) {
1376
+ const parentCtx = import_api.context.active();
1377
+ const parentSpan = import_api.trace.getSpan(parentCtx);
1378
+ const otelSpan = this.tracer.startSpan(
1379
+ `${input.kind}:${input.name}`,
1380
+ {
1381
+ attributes: {
1382
+ "nebula.span.kind": input.kind,
1383
+ "nebula.span.name": input.name,
1384
+ ...input.correlationId ? { "nebula.correlationId": input.correlationId } : {},
1385
+ ...input.executionId ? { "nebula.executionId": input.executionId } : {}
1386
+ }
1387
+ },
1388
+ parentCtx
1389
+ );
1390
+ const spanContext = otelSpan.spanContext();
1391
+ const traceId = this.ensureW3cHex(spanContext.traceId, 32) ?? spanContext.traceId;
1392
+ const spanId = this.ensureW3cHex(spanContext.spanId, 16) ?? spanContext.spanId;
1393
+ const parentSpanId = this.ensureW3cHex(parentSpan?.spanContext().spanId, 16);
1394
+ const activeSpan = new OTelActiveSpan(
1395
+ traceId,
1396
+ spanId,
1397
+ parentSpanId,
1398
+ input.kind,
1399
+ input.name,
1400
+ input.correlationId,
1401
+ input.executionId,
1402
+ this.exporter,
1403
+ otelSpan
1404
+ );
1405
+ const startEvent = {
1406
+ v: 1,
1407
+ type: "telemetry:span:start",
1408
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1409
+ trace: { traceId, spanId, parentSpanId },
1410
+ correlationId: input.correlationId,
1411
+ executionId: input.executionId,
1412
+ span: {
1413
+ kind: input.kind,
1414
+ name: input.name,
1415
+ data: input.data ?? {}
1416
+ }
1417
+ };
1418
+ this.exporter.exportBatch([startEvent]).catch(() => {
1419
+ });
1420
+ return activeSpan;
1421
+ }
1422
+ getContext() {
1423
+ const span = import_api.trace.getSpan(import_api.context.active());
1424
+ if (!span) return void 0;
1425
+ const sc = span.spanContext();
1426
+ return {
1427
+ traceId: this.ensureW3cHex(sc.traceId, 32) ?? sc.traceId,
1428
+ spanId: this.ensureW3cHex(sc.spanId, 16) ?? sc.spanId
1429
+ };
1430
+ }
1431
+ async withSpan(input, fn) {
1432
+ const parentCtx = import_api.context.active();
1433
+ const parentSpan = import_api.trace.getSpan(parentCtx);
1434
+ const otelSpan = this.tracer.startSpan(
1435
+ `${input.kind}:${input.name}`,
1436
+ {
1437
+ attributes: {
1438
+ "nebula.span.kind": input.kind,
1439
+ "nebula.span.name": input.name,
1440
+ ...input.correlationId ? { "nebula.correlationId": input.correlationId } : {},
1441
+ ...input.executionId ? { "nebula.executionId": input.executionId } : {}
1442
+ }
1443
+ },
1444
+ parentCtx
1445
+ );
1446
+ const spanContext = otelSpan.spanContext();
1447
+ const traceId = this.ensureW3cHex(spanContext.traceId, 32) ?? spanContext.traceId;
1448
+ const spanId = this.ensureW3cHex(spanContext.spanId, 16) ?? spanContext.spanId;
1449
+ const parentSpanId = this.ensureW3cHex(parentSpan?.spanContext().spanId, 16);
1450
+ const activeSpan = new OTelActiveSpan(
1451
+ traceId,
1452
+ spanId,
1453
+ parentSpanId,
1454
+ input.kind,
1455
+ input.name,
1456
+ input.correlationId,
1457
+ input.executionId,
1458
+ this.exporter,
1459
+ otelSpan
1460
+ );
1461
+ const startEvent = {
1462
+ v: 1,
1463
+ type: "telemetry:span:start",
1464
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1465
+ trace: {
1466
+ traceId,
1467
+ spanId,
1468
+ parentSpanId
1469
+ },
1470
+ correlationId: input.correlationId,
1471
+ executionId: input.executionId,
1472
+ span: {
1473
+ kind: input.kind,
1474
+ name: input.name,
1475
+ data: input.data ?? {}
1476
+ }
1477
+ };
1478
+ try {
1479
+ await this.exporter.exportBatch([startEvent]);
1480
+ } catch {
1481
+ }
1482
+ const ctx = import_api.trace.setSpan(parentCtx, otelSpan);
1483
+ return import_api.context.with(ctx, async () => {
1484
+ try {
1485
+ const result = await fn(activeSpan);
1486
+ if (!activeSpan.isEnded) {
1487
+ await activeSpan.end({ status: "success" });
1488
+ }
1489
+ return result;
1490
+ } catch (error) {
1491
+ await activeSpan.end({ status: "error" });
1492
+ throw error;
1493
+ }
1494
+ });
1495
+ }
1496
+ };
1497
+ var OTelActiveSpan = class extends import_core2.ActiveSpan {
1498
+ constructor(traceId, spanId, parentSpanId, kind, name, correlationId, executionId, exporter, otelSpan) {
1499
+ super(traceId, spanId, parentSpanId, kind, name, correlationId, executionId, exporter);
1500
+ this.otelSpan = otelSpan;
1501
+ }
1502
+ async end(input) {
1503
+ if (this.isEnded) return;
1504
+ if (input.status === "error") {
1505
+ this.otelSpan.setStatus({ code: import_api.SpanStatusCode.ERROR });
1506
+ } else {
1507
+ this.otelSpan.setStatus({ code: import_api.SpanStatusCode.OK });
1508
+ }
1509
+ this.otelSpan.end();
1510
+ await super.end(input);
1511
+ }
1512
+ };
1513
+
1514
+ // src/http/instrumented-http-client.ts
1515
+ var import_core3 = require("@nebulaos/core");
1516
+ var InstrumentedHttpClient = class {
1517
+ async fetch(url, options) {
1518
+ const method = options?.method || "GET";
1519
+ const spanName = `http:${method} ${new URL(url).origin}`;
1520
+ return import_core3.Tracing.withSpan(
1521
+ {
1522
+ kind: "http",
1523
+ name: spanName,
1524
+ data: {
1525
+ "http.method": method,
1526
+ "http.url": url
1527
+ }
1528
+ },
1529
+ async (span) => {
1530
+ const startTime = Date.now();
1531
+ try {
1532
+ const response = await fetch(url, options);
1533
+ const contentType = response.headers.get("content-type") || "";
1534
+ let data;
1535
+ if (contentType.includes("application/json")) {
1536
+ data = await response.json();
1537
+ } else {
1538
+ data = await response.text();
1539
+ }
1540
+ await span.end({
1541
+ status: response.ok ? "success" : "error",
1542
+ data: {
1543
+ "http.status_code": response.status,
1544
+ "http.response_content_length": response.headers.get("content-length"),
1545
+ "http.duration_ms": Date.now() - startTime
1546
+ }
1547
+ });
1548
+ return {
1549
+ status: response.status,
1550
+ statusText: response.statusText,
1551
+ headers: response.headers,
1552
+ data
1553
+ };
1554
+ } catch (error) {
1555
+ await span.end({
1556
+ status: "error",
1557
+ data: {
1558
+ "http.error": error instanceof Error ? error.message : String(error),
1559
+ "http.duration_ms": Date.now() - startTime
1560
+ }
1561
+ });
1562
+ throw error;
1563
+ }
1564
+ }
1565
+ );
1566
+ }
1567
+ async get(url, options) {
1568
+ return this.fetch(url, { ...options, method: "GET" });
1569
+ }
1570
+ async post(url, body, options) {
1571
+ return this.fetch(url, {
1572
+ ...options,
1573
+ method: "POST",
1574
+ body: body ? JSON.stringify(body) : void 0,
1575
+ headers: { "Content-Type": "application/json", ...options?.headers }
1576
+ });
1577
+ }
1578
+ };
1579
+
1580
+ // src/client.ts
1581
+ var NebulaClient = class {
1582
+ constructor(config) {
1583
+ this.config = config;
1584
+ clientConfigSchema.parse(config);
1585
+ if (!config.clientId) {
1586
+ config.clientId = `client-${(0, import_uuid2.v4)()}`;
1587
+ }
1588
+ if (config.server) {
1589
+ this.setGlobalClientContext(config.server.apiKey, config.server.url);
1590
+ }
1591
+ this.registry = new Registry();
1592
+ this.registry.setHttpClient(new InstrumentedHttpClient());
1593
+ config.agents?.forEach((agent) => this.registry.registerAgent(agent));
1594
+ config.tools?.forEach((tool) => this.registry.registerTool(tool));
1595
+ config.workflows?.forEach((workflow) => this.registry.registerWorkflow(workflow));
1596
+ if (config.server) {
1597
+ const debug = resolveClientDebugConfig({
1598
+ logLevel: config.logLevel,
1599
+ // deprecated:
1600
+ enabled: config.debug?.enabled,
1601
+ level: config.debug?.level
1602
+ });
1603
+ if (debug.enabled) {
1604
+ this.logger = new import_core4.ConsoleLogger(debug.level, "nebulaos/client");
1605
+ this.logger.info(
1606
+ "Client debug enabled",
1607
+ { level: debug.level, ws: true, http: true }
1608
+ );
1609
+ }
1610
+ this.serverConnection = new ServerConnection({
1611
+ url: config.server.url,
1612
+ apiKey: config.server.apiKey,
1613
+ clientId: config.clientId
1614
+ }, this.logger);
1615
+ this.serverConnection.setRegistry(this.registry);
1616
+ this.telemetryExporter = new HttpTelemetryExporter(
1617
+ {
1618
+ cloudUrl: config.server.url,
1619
+ apiKey: config.server.apiKey,
1620
+ clientId: config.clientId
1621
+ },
1622
+ {
1623
+ timeoutMs: 2e3
1624
+ },
1625
+ this.logger
1626
+ );
1627
+ import_core4.Tracing.setExporter(this.telemetryExporter);
1628
+ setupOTelTracing({
1629
+ serviceName: config.clientId ?? "nebulaos-client"
1630
+ });
1631
+ const otelProvider = new OTelTracingProvider(
1632
+ config.clientId ?? "nebulaos-client",
1633
+ this.telemetryExporter
1634
+ );
1635
+ import_core4.Tracing.setProvider(otelProvider);
1636
+ this.domainEventsExporter = new HttpDomainEventsExporter(
1637
+ {
1638
+ cloudUrl: config.server.url,
1639
+ apiKey: config.server.apiKey,
1640
+ clientId: config.clientId
1641
+ },
1642
+ {
1643
+ timeoutMs: 2e3
1644
+ },
1645
+ this.logger
1646
+ );
1647
+ import_core4.DomainEvents.setExporter(this.domainEventsExporter);
1648
+ }
1649
+ }
1650
+ registry;
1651
+ serverConnection;
1652
+ telemetryExporter;
1653
+ domainEventsExporter;
1654
+ logger;
1655
+ registerAgent(agent) {
1656
+ this.registry.registerAgent(agent);
1657
+ }
1658
+ registerTool(tool) {
1659
+ this.registry.registerTool(tool);
1660
+ }
1661
+ registerWorkflow(workflow) {
1662
+ this.registry.registerWorkflow(workflow);
1663
+ }
1664
+ async start() {
1665
+ if (this.serverConnection) {
1666
+ this.serverConnection.registerResources(
1667
+ this.registry.listAgents(),
1668
+ this.registry.listTools(),
1669
+ this.registry.listWorkflows()
1670
+ );
1671
+ try {
1672
+ await this.serverConnection.connect();
1673
+ } catch (error) {
1674
+ if (error instanceof Error) {
1675
+ throw error;
1676
+ }
1677
+ throw new Error(`Failed to connect to NebulaOS Cloud: ${String(error)}`);
1678
+ }
1679
+ }
1680
+ }
1681
+ async stop() {
1682
+ await this.serverConnection?.disconnect();
1683
+ }
1684
+ getClientId() {
1685
+ return this.serverConnection?.getClientId();
1686
+ }
1687
+ isConnected() {
1688
+ return this.serverConnection?.isConnected() ?? false;
1689
+ }
1690
+ /**
1691
+ * Sets global client context for skills to access API key and server URL
1692
+ * This allows skills like RagOpenAISkill to automatically use the client's credentials
1693
+ */
1694
+ setGlobalClientContext(apiKey, serverUrl) {
1695
+ try {
1696
+ import("@nebulaos/rag-openai-skill").then((module2) => {
1697
+ if (module2.ClientContext) {
1698
+ module2.ClientContext.setApiKey(apiKey);
1699
+ module2.ClientContext.setServerUrl(serverUrl);
1700
+ }
1701
+ }).catch(() => {
1702
+ });
1703
+ } catch {
1704
+ }
1705
+ }
1706
+ };
1707
+
1708
+ // src/telemetry/telemetry.service.ts
1709
+ var TelemetryService = class {
1710
+ };
1711
+
1712
+ // src/telemetry/batcher.ts
1713
+ var Batcher = class {
1714
+ };
1715
+
1716
+ // src/telemetry/socket-telemetry-exporter.ts
1717
+ var SocketTelemetryExporter = class {
1718
+ constructor(serverConnection, config = {}) {
1719
+ this.serverConnection = serverConnection;
1720
+ this.config = config;
1721
+ }
1722
+ queue = [];
1723
+ flushTimer;
1724
+ flushing = false;
1725
+ async exportBatch(events) {
1726
+ this.queue.push(...events);
1727
+ this.ensureTimer();
1728
+ await this.flushIfNeeded();
1729
+ }
1730
+ async flush() {
1731
+ if (this.flushing) return;
1732
+ if (this.queue.length === 0) return;
1733
+ this.flushing = true;
1734
+ try {
1735
+ const maxBatchSize = this.config.maxBatchSize ?? 50;
1736
+ while (this.queue.length > 0) {
1737
+ const batch = this.queue.splice(0, maxBatchSize);
1738
+ await this.sendTelemetryBatch(batch);
1739
+ }
1740
+ } finally {
1741
+ this.flushing = false;
1742
+ }
1743
+ }
1744
+ stop() {
1745
+ if (this.flushTimer) clearInterval(this.flushTimer);
1746
+ this.flushTimer = void 0;
1747
+ }
1748
+ async sendTelemetryBatch(events) {
1749
+ if (!this.serverConnection.isConnected()) {
1750
+ return;
1751
+ }
1752
+ const socket = this.serverConnection.getSocket();
1753
+ socket.emit("telemetry:batch", {
1754
+ clientId: this.serverConnection.getClientId(),
1755
+ events
1756
+ });
1757
+ }
1758
+ ensureTimer() {
1759
+ if (this.flushTimer) return;
1760
+ const flushIntervalMs = this.config.flushIntervalMs ?? 500;
1761
+ this.flushTimer = setInterval(() => {
1762
+ void this.flush();
1763
+ }, flushIntervalMs);
1764
+ }
1765
+ async flushIfNeeded() {
1766
+ const maxBatchSize = this.config.maxBatchSize ?? 50;
1767
+ if (this.queue.length >= maxBatchSize) {
1768
+ await this.flush();
1769
+ }
1770
+ }
1771
+ };
1772
+
1773
+ // src/memory/db-memory.ts
1774
+ var DBMemory = class {
1775
+ async get(_key, _sessionId) {
1776
+ return null;
1777
+ }
1778
+ async set(_key, _value, _sessionId, _ttl) {
1779
+ }
1780
+ async delete(_key, _sessionId) {
1781
+ }
1782
+ async clear(_sessionId) {
1783
+ }
1784
+ async getHistory(_sessionId, _limit) {
1785
+ return [];
1786
+ }
1787
+ };
1788
+
1789
+ // src/prompts/prompt-capture.ts
1790
+ var PromptCapture = class {
1791
+ };
1792
+
1793
+ // src/rag/rag-client.ts
1794
+ var RAGClient = class {
1795
+ };
1796
+ // Annotate the CommonJS export names for ESM import in node:
1797
+ 0 && (module.exports = {
1798
+ AuthenticationError,
1799
+ Batcher,
1800
+ ClientError,
1801
+ ConnectionError,
1802
+ DBMemory,
1803
+ HttpDomainEventsExporter,
1804
+ HttpTelemetryExporter,
1805
+ InstrumentedHttpClient,
1806
+ NebulaClient,
1807
+ NotFoundError,
1808
+ OTelTracingProvider,
1809
+ PromptCapture,
1810
+ RAGClient,
1811
+ RegistryError,
1812
+ SocketTelemetryExporter,
1813
+ TelemetryService,
1814
+ clientConfigSchema,
1815
+ otelTracingConfigSchema,
1816
+ serverConfigSchema,
1817
+ setupOTelTracing
1818
+ });
1819
+ //# sourceMappingURL=index.js.map