@slashfi/agents-sdk 0.72.0 → 0.73.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/adk.js +5 -9
- package/dist/adk.js.map +1 -1
- package/dist/agent-definitions/remote-registry.d.ts +15 -0
- package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
- package/dist/agent-definitions/remote-registry.js +42 -17
- package/dist/agent-definitions/remote-registry.js.map +1 -1
- package/dist/cjs/agent-definitions/remote-registry.js +42 -17
- package/dist/cjs/agent-definitions/remote-registry.js.map +1 -1
- package/dist/cjs/config-store.js +125 -8
- package/dist/cjs/config-store.js.map +1 -1
- package/dist/cjs/events.js +11 -3
- package/dist/cjs/events.js.map +1 -1
- package/dist/cjs/fetch-types.js +3 -0
- package/dist/cjs/fetch-types.js.map +1 -0
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/key-manager.js +7 -1
- package/dist/cjs/key-manager.js.map +1 -1
- package/dist/cjs/logger.js +115 -0
- package/dist/cjs/logger.js.map +1 -0
- package/dist/cjs/registry-consumer.js.map +1 -1
- package/dist/cjs/registry.js +1 -1
- package/dist/cjs/registry.js.map +1 -1
- package/dist/cjs/server.js +70 -13
- package/dist/cjs/server.js.map +1 -1
- package/dist/config-store.d.ts +19 -0
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +125 -8
- package/dist/config-store.js.map +1 -1
- package/dist/events.d.ts +6 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +11 -3
- package/dist/events.js.map +1 -1
- package/dist/fetch-types.d.ts +11 -0
- package/dist/fetch-types.d.ts.map +1 -0
- package/dist/fetch-types.js +2 -0
- package/dist/fetch-types.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/key-manager.d.ts +6 -0
- package/dist/key-manager.d.ts.map +1 -1
- package/dist/key-manager.js +7 -1
- package/dist/key-manager.js.map +1 -1
- package/dist/logger.d.ts +42 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +109 -0
- package/dist/logger.js.map +1 -0
- package/dist/registry-consumer.d.ts +8 -2
- package/dist/registry-consumer.d.ts.map +1 -1
- package/dist/registry-consumer.js.map +1 -1
- package/dist/registry.d.ts +6 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +1 -1
- package/dist/registry.js.map +1 -1
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +70 -13
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/adk.ts +5 -10
- package/src/agent-definitions/remote-registry.ts +56 -28
- package/src/config-store.ts +168 -9
- package/src/events.ts +16 -6
- package/src/fetch-types.ts +13 -0
- package/src/index.ts +13 -0
- package/src/key-manager.ts +12 -1
- package/src/logger.test.ts +206 -0
- package/src/logger.ts +123 -0
- package/src/registry-consumer.ts +13 -7
- package/src/registry.ts +7 -2
- package/src/server.ts +76 -42
package/src/events.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Filtering happens in the callback, not the API.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { getDefaultLogger, type Logger } from "./logger.js";
|
|
13
14
|
import type { AgentDefinition, CallAgentRequest, CallAgentResponse } from "./types.js";
|
|
14
15
|
// =============================================================================
|
|
15
16
|
// Event Types
|
|
@@ -293,11 +294,17 @@ export interface EventBus {
|
|
|
293
294
|
): void;
|
|
294
295
|
}
|
|
295
296
|
|
|
297
|
+
export interface EventBusOptions {
|
|
298
|
+
/** Logger for listener errors (default: module-level default logger) */
|
|
299
|
+
logger?: Logger;
|
|
300
|
+
}
|
|
301
|
+
|
|
296
302
|
/**
|
|
297
303
|
* Create an event bus.
|
|
298
304
|
*/
|
|
299
|
-
export function createEventBus(): EventBus {
|
|
305
|
+
export function createEventBus(options: EventBusOptions = {}): EventBus {
|
|
300
306
|
const listeners: ListenerEntry[] = [];
|
|
307
|
+
const logger = options.logger ?? getDefaultLogger();
|
|
301
308
|
|
|
302
309
|
function on<T extends EventType>(
|
|
303
310
|
eventType: T,
|
|
@@ -346,11 +353,14 @@ export function createEventBus(): EventBus {
|
|
|
346
353
|
try {
|
|
347
354
|
await listener.callback(event as never);
|
|
348
355
|
} catch (err) {
|
|
349
|
-
// Never propagate listener errors — log and continue
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
356
|
+
// Never propagate listener errors — log and continue.
|
|
357
|
+
// Structured fields so Datadog keeps the whole record as one event.
|
|
358
|
+
logger.error("event_listener_error", {
|
|
359
|
+
component: "agents-sdk.events",
|
|
360
|
+
event_type: event.type,
|
|
361
|
+
agent_path: event.agentPath,
|
|
362
|
+
error: err,
|
|
363
|
+
});
|
|
354
364
|
}
|
|
355
365
|
}
|
|
356
366
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural `fetch` type that's compatible across Node (undici-backed) and
|
|
3
|
+
* Bun (which extends the global fetch with extras like `preconnect`).
|
|
4
|
+
*
|
|
5
|
+
* Using `typeof globalThis.fetch` in options types causes type-errors when a
|
|
6
|
+
* Node-typed fetch implementation (e.g. an undici.Agent-backed wrapper) is
|
|
7
|
+
* passed in a codebase whose @types/bun is loaded. This structural subset is
|
|
8
|
+
* the minimum surface the SDK actually uses and both runtimes satisfy it.
|
|
9
|
+
*/
|
|
10
|
+
export type FetchFn = (
|
|
11
|
+
input: string | URL | Request,
|
|
12
|
+
init?: RequestInit,
|
|
13
|
+
) => Promise<Response>;
|
package/src/index.ts
CHANGED
|
@@ -126,6 +126,7 @@ export type {
|
|
|
126
126
|
export { createEventBus } from "./events.js";
|
|
127
127
|
export type {
|
|
128
128
|
EventBus,
|
|
129
|
+
EventBusOptions,
|
|
129
130
|
EventType,
|
|
130
131
|
SystemEventType,
|
|
131
132
|
CustomEventMap,
|
|
@@ -145,6 +146,18 @@ export type {
|
|
|
145
146
|
ListenerEntry,
|
|
146
147
|
} from "./events.js";
|
|
147
148
|
|
|
149
|
+
// Logger
|
|
150
|
+
export {
|
|
151
|
+
createConsoleJsonLogger,
|
|
152
|
+
createNoopLogger,
|
|
153
|
+
getDefaultLogger,
|
|
154
|
+
setDefaultLogger,
|
|
155
|
+
} from "./logger.js";
|
|
156
|
+
export type { LogFields, LogLevel, Logger } from "./logger.js";
|
|
157
|
+
|
|
158
|
+
// Structural fetch type
|
|
159
|
+
export type { FetchFn } from "./fetch-types.js";
|
|
160
|
+
|
|
148
161
|
// Server
|
|
149
162
|
export {
|
|
150
163
|
createAgentServer,
|
package/src/key-manager.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
generateKeyPair,
|
|
22
22
|
importJWK,
|
|
23
23
|
} from "jose";
|
|
24
|
+
import { getDefaultLogger, type Logger } from "./logger.js";
|
|
24
25
|
|
|
25
26
|
// ── Types ──
|
|
26
27
|
|
|
@@ -88,6 +89,11 @@ export interface KeyManagerOptions {
|
|
|
88
89
|
tokenTtlSeconds?: number;
|
|
89
90
|
/** Enable background key rotation (default: true). Set to false on read-only replicas. */
|
|
90
91
|
enableRotation?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Structured logger for rotation errors. Defaults to the module-level
|
|
94
|
+
* default logger (single-line JSON).
|
|
95
|
+
*/
|
|
96
|
+
logger?: Logger;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
// ── Constants ──
|
|
@@ -148,6 +154,7 @@ export async function createKeyManager(
|
|
|
148
154
|
tokenTtlSeconds = 300,
|
|
149
155
|
enableRotation = true,
|
|
150
156
|
} = opts;
|
|
157
|
+
const logger = opts.logger ?? getDefaultLogger();
|
|
151
158
|
|
|
152
159
|
let keys: CachedKey[] = [];
|
|
153
160
|
|
|
@@ -221,7 +228,11 @@ export async function createKeyManager(
|
|
|
221
228
|
try {
|
|
222
229
|
await checkAndRotate();
|
|
223
230
|
} catch (err) {
|
|
224
|
-
|
|
231
|
+
logger.error("key_rotation_failed", {
|
|
232
|
+
component: "agents-sdk.key-manager",
|
|
233
|
+
issuer,
|
|
234
|
+
error: err,
|
|
235
|
+
});
|
|
225
236
|
}
|
|
226
237
|
}, checkIntervalMs)
|
|
227
238
|
: null;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createEventBus } from "./events.js";
|
|
3
|
+
import {
|
|
4
|
+
type Logger,
|
|
5
|
+
createConsoleJsonLogger,
|
|
6
|
+
createNoopLogger,
|
|
7
|
+
getDefaultLogger,
|
|
8
|
+
setDefaultLogger,
|
|
9
|
+
} from "./logger.js";
|
|
10
|
+
|
|
11
|
+
function captureLogger(): { logger: Logger; records: unknown[] } {
|
|
12
|
+
const records: unknown[] = [];
|
|
13
|
+
const logger: Logger = {
|
|
14
|
+
debug: (msg, fields) => records.push({ level: "debug", msg, fields }),
|
|
15
|
+
info: (msg, fields) => records.push({ level: "info", msg, fields }),
|
|
16
|
+
warn: (msg, fields) => records.push({ level: "warn", msg, fields }),
|
|
17
|
+
error: (msg, fields) => records.push({ level: "error", msg, fields }),
|
|
18
|
+
with: () => logger,
|
|
19
|
+
};
|
|
20
|
+
return { logger, records };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("createConsoleJsonLogger", () => {
|
|
24
|
+
test("emits single-line JSON for each record", () => {
|
|
25
|
+
const lines: string[] = [];
|
|
26
|
+
const originalError = console.error;
|
|
27
|
+
const originalLog = console.log;
|
|
28
|
+
console.error = (line: string) => lines.push(line);
|
|
29
|
+
console.log = (line: string) => lines.push(line);
|
|
30
|
+
try {
|
|
31
|
+
const logger = createConsoleJsonLogger();
|
|
32
|
+
logger.info("hello", { foo: "bar" });
|
|
33
|
+
logger.error("oops", { err: new Error("boom") });
|
|
34
|
+
} finally {
|
|
35
|
+
console.error = originalError;
|
|
36
|
+
console.log = originalLog;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(lines.length).toBe(2);
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
// Single line
|
|
42
|
+
expect(line.includes("\n")).toBe(false);
|
|
43
|
+
// Valid JSON
|
|
44
|
+
expect(() => JSON.parse(line)).not.toThrow();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const info = JSON.parse(lines[0]!);
|
|
48
|
+
expect(info.level).toBe("info");
|
|
49
|
+
expect(info.message).toBe("hello");
|
|
50
|
+
expect(info.foo).toBe("bar");
|
|
51
|
+
expect(typeof info.timestamp).toBe("string");
|
|
52
|
+
|
|
53
|
+
const err = JSON.parse(lines[1]!);
|
|
54
|
+
expect(err.level).toBe("error");
|
|
55
|
+
expect(err.message).toBe("oops");
|
|
56
|
+
expect(err.err.name).toBe("Error");
|
|
57
|
+
expect(err.err.message).toBe("boom");
|
|
58
|
+
expect(typeof err.err.stack).toBe("string");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("with() merges base fields into child records", () => {
|
|
62
|
+
const lines: string[] = [];
|
|
63
|
+
const originalLog = console.log;
|
|
64
|
+
console.log = (line: string) => lines.push(line);
|
|
65
|
+
try {
|
|
66
|
+
const root = createConsoleJsonLogger({ service: "atlas-api" });
|
|
67
|
+
const child = root.with({ request_id: "abc123" });
|
|
68
|
+
child.info("req", { method: "GET" });
|
|
69
|
+
} finally {
|
|
70
|
+
console.log = originalLog;
|
|
71
|
+
}
|
|
72
|
+
expect(lines.length).toBe(1);
|
|
73
|
+
const record = JSON.parse(lines[0]!);
|
|
74
|
+
expect(record.service).toBe("atlas-api");
|
|
75
|
+
expect(record.request_id).toBe("abc123");
|
|
76
|
+
expect(record.method).toBe("GET");
|
|
77
|
+
expect(record.message).toBe("req");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("handles non-serializable values without throwing", () => {
|
|
81
|
+
const lines: string[] = [];
|
|
82
|
+
const originalLog = console.log;
|
|
83
|
+
console.log = (line: string) => lines.push(line);
|
|
84
|
+
try {
|
|
85
|
+
const logger = createConsoleJsonLogger();
|
|
86
|
+
const circular: Record<string, unknown> = {};
|
|
87
|
+
circular.self = circular;
|
|
88
|
+
logger.info("cycle", { circular });
|
|
89
|
+
} finally {
|
|
90
|
+
console.log = originalLog;
|
|
91
|
+
}
|
|
92
|
+
expect(lines.length).toBe(1);
|
|
93
|
+
const record = JSON.parse(lines[0]!);
|
|
94
|
+
expect(record.serializer_error).toBe(true);
|
|
95
|
+
expect(record.message).toBe("cycle");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("serializes bigint and drops functions", () => {
|
|
99
|
+
const lines: string[] = [];
|
|
100
|
+
const originalLog = console.log;
|
|
101
|
+
console.log = (line: string) => lines.push(line);
|
|
102
|
+
try {
|
|
103
|
+
const logger = createConsoleJsonLogger();
|
|
104
|
+
logger.info("values", { big: 42n, fn: () => "ignored" });
|
|
105
|
+
} finally {
|
|
106
|
+
console.log = originalLog;
|
|
107
|
+
}
|
|
108
|
+
const record = JSON.parse(lines[0]!);
|
|
109
|
+
expect(record.big).toBe("42");
|
|
110
|
+
expect(record.fn).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("createNoopLogger", () => {
|
|
115
|
+
test("drops every record and with() returns self", () => {
|
|
116
|
+
const noop = createNoopLogger();
|
|
117
|
+
expect(() => {
|
|
118
|
+
noop.debug("d");
|
|
119
|
+
noop.info("i");
|
|
120
|
+
noop.warn("w");
|
|
121
|
+
noop.error("e", { err: new Error("ignored") });
|
|
122
|
+
}).not.toThrow();
|
|
123
|
+
const child = noop.with({ ctx: "x" });
|
|
124
|
+
expect(typeof child.info).toBe("function");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("default logger", () => {
|
|
129
|
+
test("setDefaultLogger / getDefaultLogger round-trip", () => {
|
|
130
|
+
const original = getDefaultLogger();
|
|
131
|
+
const { logger, records } = captureLogger();
|
|
132
|
+
try {
|
|
133
|
+
setDefaultLogger(logger);
|
|
134
|
+
getDefaultLogger().info("routed");
|
|
135
|
+
expect(records.length).toBe(1);
|
|
136
|
+
} finally {
|
|
137
|
+
setDefaultLogger(original);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("createEventBus uses injected logger", () => {
|
|
143
|
+
test("listener error is logged as a single structured record", async () => {
|
|
144
|
+
const { logger, records } = captureLogger();
|
|
145
|
+
const bus = createEventBus({ logger });
|
|
146
|
+
bus.on("tool/call", () => {
|
|
147
|
+
throw new Error("listener boom");
|
|
148
|
+
});
|
|
149
|
+
await bus.emit({
|
|
150
|
+
type: "tool/call",
|
|
151
|
+
agentPath: "@test",
|
|
152
|
+
timestamp: Date.now(),
|
|
153
|
+
tool: "demo",
|
|
154
|
+
params: {},
|
|
155
|
+
});
|
|
156
|
+
expect(records.length).toBe(1);
|
|
157
|
+
const rec = records[0] as {
|
|
158
|
+
level: string;
|
|
159
|
+
msg: string;
|
|
160
|
+
fields: Record<string, unknown>;
|
|
161
|
+
};
|
|
162
|
+
expect(rec.level).toBe("error");
|
|
163
|
+
expect(rec.msg).toBe("event_listener_error");
|
|
164
|
+
expect(rec.fields.component).toBe("agents-sdk.events");
|
|
165
|
+
expect(rec.fields.event_type).toBe("tool/call");
|
|
166
|
+
expect(rec.fields.agent_path).toBe("@test");
|
|
167
|
+
expect(rec.fields.error).toBeInstanceOf(Error);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("listener errors never propagate", async () => {
|
|
171
|
+
const bus = createEventBus({ logger: createNoopLogger() });
|
|
172
|
+
bus.on("invoke", () => {
|
|
173
|
+
throw new Error("nope");
|
|
174
|
+
});
|
|
175
|
+
await expect(
|
|
176
|
+
bus.emit({
|
|
177
|
+
type: "invoke",
|
|
178
|
+
agentPath: "@test",
|
|
179
|
+
timestamp: Date.now(),
|
|
180
|
+
prompt: "hi",
|
|
181
|
+
}),
|
|
182
|
+
).resolves.toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("falls back to module default logger when no option passed", async () => {
|
|
186
|
+
const original = getDefaultLogger();
|
|
187
|
+
const { logger, records } = captureLogger();
|
|
188
|
+
try {
|
|
189
|
+
setDefaultLogger(logger);
|
|
190
|
+
const bus = createEventBus();
|
|
191
|
+
bus.on("tool/call", () => {
|
|
192
|
+
throw new Error("captured");
|
|
193
|
+
});
|
|
194
|
+
await bus.emit({
|
|
195
|
+
type: "tool/call",
|
|
196
|
+
agentPath: "@test",
|
|
197
|
+
timestamp: Date.now(),
|
|
198
|
+
tool: "demo",
|
|
199
|
+
params: {},
|
|
200
|
+
});
|
|
201
|
+
expect(records.length).toBe(1);
|
|
202
|
+
} finally {
|
|
203
|
+
setDefaultLogger(original);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
});
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger — pluggable structured logger for the agents-sdk.
|
|
3
|
+
*
|
|
4
|
+
* The SDK emits errors and traces via this interface so consumers (e.g. atlas)
|
|
5
|
+
* can route logs into their own observability stack. The default logger writes
|
|
6
|
+
* single-line JSON to stdout/stderr, which keeps Datadog from splitting
|
|
7
|
+
* multi-line stack traces into separate events.
|
|
8
|
+
*
|
|
9
|
+
* @example Inject your own logger
|
|
10
|
+
* ```ts
|
|
11
|
+
* const registry = createAgentRegistry({ logger: myStructuredLogger });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @example Disable all SDK logging
|
|
15
|
+
* ```ts
|
|
16
|
+
* const registry = createAgentRegistry({ logger: createNoopLogger() });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export type LogFields = Record<string, unknown>;
|
|
21
|
+
|
|
22
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
23
|
+
|
|
24
|
+
export interface Logger {
|
|
25
|
+
debug(message: string, fields?: LogFields): void;
|
|
26
|
+
info(message: string, fields?: LogFields): void;
|
|
27
|
+
warn(message: string, fields?: LogFields): void;
|
|
28
|
+
error(message: string, fields?: LogFields): void;
|
|
29
|
+
/** Return a child logger that merges the given fields into every record. */
|
|
30
|
+
with(fields: LogFields): Logger;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Replacer that expands Error objects into plain JSON-serializable fields
|
|
35
|
+
* (name/message/stack/cause), and falls back to String() for other
|
|
36
|
+
* non-serializable values so the logger never throws or drops the message.
|
|
37
|
+
*/
|
|
38
|
+
function errorReplacer(_key: string, value: unknown): unknown {
|
|
39
|
+
if (value instanceof Error) {
|
|
40
|
+
return {
|
|
41
|
+
name: value.name,
|
|
42
|
+
message: value.message,
|
|
43
|
+
stack: value.stack,
|
|
44
|
+
...(value.cause !== undefined ? { cause: value.cause } : {}),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === "bigint") return value.toString();
|
|
48
|
+
if (typeof value === "function") return undefined;
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Default logger: emits a single JSON line per record.
|
|
54
|
+
* - debug/info → stdout
|
|
55
|
+
* - warn/error → stderr
|
|
56
|
+
*
|
|
57
|
+
* Kept intentionally minimal. Hosts should replace this with their own
|
|
58
|
+
* transport in production.
|
|
59
|
+
*/
|
|
60
|
+
export function createConsoleJsonLogger(base: LogFields = {}): Logger {
|
|
61
|
+
function emit(level: LogLevel, message: string, fields?: LogFields): void {
|
|
62
|
+
const record = {
|
|
63
|
+
level,
|
|
64
|
+
timestamp: new Date().toISOString(),
|
|
65
|
+
message,
|
|
66
|
+
...base,
|
|
67
|
+
...fields,
|
|
68
|
+
};
|
|
69
|
+
let line: string;
|
|
70
|
+
try {
|
|
71
|
+
line = JSON.stringify(record, errorReplacer);
|
|
72
|
+
} catch {
|
|
73
|
+
// Last-ditch fallback: if the payload can't be serialized, at least
|
|
74
|
+
// emit the message so callers aren't flying blind.
|
|
75
|
+
line = JSON.stringify({
|
|
76
|
+
level,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
message,
|
|
79
|
+
serializer_error: true,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (level === "error" || level === "warn") {
|
|
83
|
+
console.error(line);
|
|
84
|
+
} else {
|
|
85
|
+
console.log(line);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
debug: (message, fields) => emit("debug", message, fields),
|
|
90
|
+
info: (message, fields) => emit("info", message, fields),
|
|
91
|
+
warn: (message, fields) => emit("warn", message, fields),
|
|
92
|
+
error: (message, fields) => emit("error", message, fields),
|
|
93
|
+
with: (fields) => createConsoleJsonLogger({ ...base, ...fields }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Logger that drops every record. Useful for silencing SDK output in tests. */
|
|
98
|
+
export function createNoopLogger(): Logger {
|
|
99
|
+
const noop = (): void => {};
|
|
100
|
+
const self: Logger = {
|
|
101
|
+
debug: noop,
|
|
102
|
+
info: noop,
|
|
103
|
+
warn: noop,
|
|
104
|
+
error: noop,
|
|
105
|
+
with: () => self,
|
|
106
|
+
};
|
|
107
|
+
return self;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Module-level default logger. Hosts that want JSON output everywhere can
|
|
112
|
+
* set this once at startup instead of threading a logger through every
|
|
113
|
+
* factory call.
|
|
114
|
+
*/
|
|
115
|
+
let defaultLogger: Logger = createConsoleJsonLogger();
|
|
116
|
+
|
|
117
|
+
export function setDefaultLogger(logger: Logger): void {
|
|
118
|
+
defaultLogger = logger;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getDefaultLogger(): Logger {
|
|
122
|
+
return defaultLogger;
|
|
123
|
+
}
|
package/src/registry-consumer.ts
CHANGED
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
ResolvedRegistry,
|
|
40
40
|
} from "./define-config.js";
|
|
41
41
|
import type { CallAgentRequest } from "./call-agent-schema.js";
|
|
42
|
+
import type { FetchFn } from "./fetch-types.js";
|
|
42
43
|
import type { SecuritySchemeSummary, CallAgentResponse } from "./types.js";
|
|
43
44
|
import {
|
|
44
45
|
isSecretUri,
|
|
@@ -331,7 +332,7 @@ async function defaultSecretResolver(
|
|
|
331
332
|
async function listFromMcpServer(
|
|
332
333
|
url: string,
|
|
333
334
|
auth: { token?: string; headers?: Record<string, string> },
|
|
334
|
-
fetchFn:
|
|
335
|
+
fetchFn: FetchFn,
|
|
335
336
|
): Promise<AgentListing[]> {
|
|
336
337
|
const serverUrl = url.replace(/\/$/, "");
|
|
337
338
|
|
|
@@ -409,7 +410,7 @@ function issuerFromMcpUrlAndServerInfo(
|
|
|
409
410
|
async function discoverRegistryViaMcp(
|
|
410
411
|
registryUrl: string,
|
|
411
412
|
authHeaders: Record<string, string>,
|
|
412
|
-
fetchFn:
|
|
413
|
+
fetchFn: FetchFn,
|
|
413
414
|
): Promise<RegistryConfiguration> {
|
|
414
415
|
const serverUrl = registryUrl.replace(/\/$/, "");
|
|
415
416
|
const headers: Record<string, string> = {
|
|
@@ -467,7 +468,7 @@ async function callMcpTool(
|
|
|
467
468
|
toolName: string,
|
|
468
469
|
params: Record<string, unknown>,
|
|
469
470
|
auth: { token?: string; headers?: Record<string, string> },
|
|
470
|
-
fetchFn:
|
|
471
|
+
fetchFn: FetchFn,
|
|
471
472
|
): Promise<unknown> {
|
|
472
473
|
const serverUrl = url.replace(/\/$/, "");
|
|
473
474
|
const headers: Record<string, string> = {
|
|
@@ -539,7 +540,7 @@ async function callHttpsTool(
|
|
|
539
540
|
_toolName: string,
|
|
540
541
|
params: Record<string, unknown>,
|
|
541
542
|
auth: { token?: string; headers?: Record<string, string> },
|
|
542
|
-
fetchFn:
|
|
543
|
+
fetchFn: FetchFn,
|
|
543
544
|
): Promise<unknown> {
|
|
544
545
|
const method = (params.method as string) ?? "GET";
|
|
545
546
|
const path = (params.path as string) ?? "";
|
|
@@ -582,8 +583,13 @@ export interface RegistryConsumerOptions {
|
|
|
582
583
|
/** Bearer token for authenticated registries */
|
|
583
584
|
token?: string;
|
|
584
585
|
|
|
585
|
-
/**
|
|
586
|
-
|
|
586
|
+
/**
|
|
587
|
+
* Custom fetch implementation. Forwarded to every outbound HTTP call the
|
|
588
|
+
* consumer makes (discovery, jwks, callRegistry, secret resolve). Hosts
|
|
589
|
+
* running in long-lived servers should pass a hardened fetch to avoid
|
|
590
|
+
* dead-socket hangs on rolling deploys.
|
|
591
|
+
*/
|
|
592
|
+
fetch?: FetchFn;
|
|
587
593
|
}
|
|
588
594
|
|
|
589
595
|
// ============================================
|
|
@@ -651,7 +657,7 @@ export async function createRegistryConsumer(
|
|
|
651
657
|
config: ConsumerConfig,
|
|
652
658
|
options: RegistryConsumerOptions = {},
|
|
653
659
|
): Promise<RegistryConsumer> {
|
|
654
|
-
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
660
|
+
const fetchFn: FetchFn = options.fetch ?? globalThis.fetch;
|
|
655
661
|
const resolveSecretFn = options.resolveSecret ?? defaultSecretResolver;
|
|
656
662
|
|
|
657
663
|
// Normalize registries
|
package/src/registry.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { dirname, resolve } from "node:path";
|
|
8
8
|
import type { AgentEvent, BaseEvent, CallAgentToolCallEvent, CustomEventMap, EventCallback, EventType, ListAgentsResult, ListAgentsToolCallEvent } from "./events.js";
|
|
9
9
|
import { createEventBus } from "./events.js";
|
|
10
|
+
import type { Logger } from "./logger.js";
|
|
10
11
|
import type { SerializedAgentDefinition } from "./serialized.js";
|
|
11
12
|
import type {
|
|
12
13
|
AgentAction,
|
|
@@ -87,7 +88,11 @@ export interface AgentRegistryOptions {
|
|
|
87
88
|
contextFactory?: ContextFactory;
|
|
88
89
|
/** Lifecycle middleware hooks */
|
|
89
90
|
middleware?: RegistryMiddleware;
|
|
90
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Structured logger for SDK-internal events (listener errors, etc.).
|
|
93
|
+
* Defaults to the module-level default logger (JSON to stdout/stderr).
|
|
94
|
+
*/
|
|
95
|
+
logger?: Logger;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
/**
|
|
@@ -231,7 +236,7 @@ export function createAgentRegistry(
|
|
|
231
236
|
): AgentRegistry {
|
|
232
237
|
const { defaultVisibility = "internal" } = options;
|
|
233
238
|
const agents = new Map<string, AgentDefinition>();
|
|
234
|
-
const eventBus = createEventBus();
|
|
239
|
+
const eventBus = createEventBus({ logger: options.logger });
|
|
235
240
|
|
|
236
241
|
/**
|
|
237
242
|
* Check if agent supports the requested action.
|