@kb-labs/agent-sdk 0.6.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.
@@ -0,0 +1,379 @@
1
+ import { LLMTier, IterationSnapshot, RunEvaluation } from '@kb-labs/agent-contracts';
2
+ import { LLMMessage, LLMTool } from '@kb-labs/sdk';
3
+
4
+ /**
5
+ * AgentEventBus — typed pub/sub for passive observers.
6
+ *
7
+ * EventBus is for observers that don't affect execution flow.
8
+ * For flow control, use AgentMiddleware instead.
9
+ *
10
+ * Policies (enforced by agent-core implementation):
11
+ * - bounded queue: max 1000 pending async handlers
12
+ * - max recursion depth: 1 (emit inside handler does not recurse)
13
+ * - error isolation: handler throw does not break other handlers
14
+ * - async handler timeout: 5000ms (configurable per handler)
15
+ */
16
+
17
+ interface AgentEvents {
18
+ 'run:start': {
19
+ task: string;
20
+ tier: LLMTier;
21
+ };
22
+ 'run:end': {
23
+ success: boolean;
24
+ totalTokens: number;
25
+ durationMs: number;
26
+ stopReason: string;
27
+ };
28
+ 'iteration:start': {
29
+ iteration: number;
30
+ maxIterations: number;
31
+ };
32
+ 'iteration:end': {
33
+ iteration: number;
34
+ };
35
+ 'llm:start': {
36
+ iteration: number;
37
+ messageCount: number;
38
+ toolCount: number;
39
+ systemPromptChars: number;
40
+ };
41
+ 'llm:end': {
42
+ iteration: number;
43
+ promptTokens: number;
44
+ completionTokens: number;
45
+ stopReason: string;
46
+ hasToolCalls: boolean;
47
+ toolCallCount: number;
48
+ durationMs: number;
49
+ content?: string;
50
+ };
51
+ 'tool:start': {
52
+ iteration: number;
53
+ toolName: string;
54
+ input: Record<string, unknown>;
55
+ };
56
+ 'tool:end': {
57
+ iteration: number;
58
+ toolName: string;
59
+ success: boolean;
60
+ durationMs: number;
61
+ outputLength: number;
62
+ output?: string;
63
+ metadata?: Record<string, unknown>;
64
+ };
65
+ /** Generic middleware decision event — emitted by any middleware into the bus */
66
+ 'middleware:event': {
67
+ name: string;
68
+ event: string;
69
+ data: Record<string, unknown>;
70
+ };
71
+ /** Debug-only: full LLM prompt + response (emitted when RunContext.debug = true) */
72
+ 'llm:debug': {
73
+ iteration: number;
74
+ systemPrompt: string;
75
+ messages: Array<{
76
+ role: string;
77
+ content: string;
78
+ }>;
79
+ responseContent: string;
80
+ };
81
+ 'escalate': {
82
+ fromTier: LLMTier;
83
+ toTier: LLMTier;
84
+ reason: string;
85
+ };
86
+ 'abort': {
87
+ reason: string;
88
+ };
89
+ 'spawn': {
90
+ profileId: string;
91
+ childRunId: string;
92
+ };
93
+ }
94
+ type Unsubscribe = () => void;
95
+ interface AgentEventBus {
96
+ emit<K extends keyof AgentEvents>(event: K, data: AgentEvents[K]): void;
97
+ /** Subscribe with a synchronous handler */
98
+ on<K extends keyof AgentEvents>(event: K, handler: (data: AgentEvents[K]) => void): Unsubscribe;
99
+ /** Subscribe with an async handler (subject to timeout policy) */
100
+ onAsync<K extends keyof AgentEvents>(event: K, handler: (data: AgentEvents[K]) => Promise<void>): Unsubscribe;
101
+ /** Wait for all pending async handlers to settle */
102
+ drain(): Promise<void>;
103
+ /** Remove all subscriptions */
104
+ clear(): void;
105
+ }
106
+
107
+ /**
108
+ * Execution context objects for the Agent SDK.
109
+ *
110
+ * Each context carries only what that layer needs — no `this` references,
111
+ * no god-object coupling. Low coupling = easy mocking in tests.
112
+ *
113
+ * Key invariant: RunContext.messages is readonly.
114
+ * The only way to append to history is via LoopContext.appendMessage().
115
+ */
116
+
117
+ interface ContextMeta {
118
+ get<T>(namespace: string, key: string): T | undefined;
119
+ set<T>(namespace: string, key: string, value: T): void;
120
+ /** Returns shallow copy of the namespace map */
121
+ getNamespace(namespace: string): Record<string, unknown>;
122
+ }
123
+ interface RunContext {
124
+ /** Current task string */
125
+ task: string;
126
+ /** Current LLM tier */
127
+ tier: LLMTier;
128
+ /**
129
+ * Conversation messages — readonly.
130
+ * Append only via LoopContext.appendMessage() to prevent accidental mutation.
131
+ */
132
+ readonly messages: ReadonlyArray<LLMMessage>;
133
+ /** Available tools for this run */
134
+ tools: LLMTool[];
135
+ /** Current iteration number (1-based) */
136
+ iteration: number;
137
+ /** Maximum iterations allowed */
138
+ maxIterations: number;
139
+ /** Whether abort was requested */
140
+ aborted: boolean;
141
+ /** AbortSignal — mandatory, for graceful cancel propagation */
142
+ abortSignal: AbortSignal;
143
+ /** Unique run ID — used for tracing and linking sub-agent spans */
144
+ requestId: string;
145
+ /** Optional hard deadline (unix ms). Enforcement is middleware's responsibility. */
146
+ deadlineMs?: number;
147
+ /** Session ID (if any) */
148
+ sessionId?: string;
149
+ /** Namespaced side-channel metadata for middleware */
150
+ meta: ContextMeta;
151
+ /** Event bus for middleware-to-middleware and middleware-to-observer communication */
152
+ eventBus: AgentEventBus;
153
+ /** Debug mode: middleware emit full prompts/responses via llm:debug events */
154
+ debug: boolean;
155
+ }
156
+ interface LLMCtx {
157
+ run: RunContext;
158
+ /** Messages to send — may be patched by beforeLLMCall middleware */
159
+ messages: LLMMessage[];
160
+ /** Tools available for this call */
161
+ tools: LLMTool[];
162
+ /** Tier-specific temperature override */
163
+ temperature?: number;
164
+ }
165
+ interface LLMCallPatch {
166
+ messages?: LLMMessage[];
167
+ tools?: LLMTool[];
168
+ temperature?: number;
169
+ /** Force a specific tool choice. Use to require the LLM to call a specific tool. */
170
+ toolChoice?: 'auto' | 'required' | 'none' | {
171
+ type: 'function';
172
+ function: {
173
+ name: string;
174
+ };
175
+ };
176
+ }
177
+ interface LLMCallResult {
178
+ content: string;
179
+ toolCalls: Array<{
180
+ id: string;
181
+ name: string;
182
+ input: Record<string, unknown>;
183
+ }>;
184
+ usage?: {
185
+ promptTokens: number;
186
+ completionTokens: number;
187
+ };
188
+ /** LLM stop reason: 'end_turn' | 'tool_use' | 'max_tokens' | undefined */
189
+ stopReason?: string;
190
+ }
191
+ interface ToolCallInput {
192
+ id: string;
193
+ name: string;
194
+ input: Record<string, unknown>;
195
+ }
196
+ interface ToolOutput {
197
+ toolCallId: string;
198
+ output: string;
199
+ success: boolean;
200
+ error?: string;
201
+ metadata?: Record<string, unknown>;
202
+ }
203
+ interface ToolExecCtx {
204
+ run: RunContext;
205
+ /** Tool name being executed */
206
+ toolName: string;
207
+ /** Raw tool input from LLM */
208
+ input: Record<string, unknown>;
209
+ /** Iteration in which the tool is being called */
210
+ iteration: number;
211
+ /** AbortSignal inherited from run — for cancelling long-running tool calls */
212
+ abortSignal: AbortSignal;
213
+ /** Same as run.requestId — for cross-referencing in traces */
214
+ requestId: string;
215
+ }
216
+
217
+ /**
218
+ * Middleware interface for the Agent SDK.
219
+ *
220
+ * Middleware hooks into the execution pipeline at well-defined points.
221
+ * Implement only the hooks you need — all are optional.
222
+ *
223
+ * Ordering:
224
+ * before-hooks (onStart, beforeIteration, beforeLLMCall, beforeToolExec) — ascending order
225
+ * after-hooks (afterToolExec, afterLLMCall, afterIteration, onStop, onComplete) — descending order
226
+ *
227
+ * Fail policies:
228
+ * 'fail-open' — if middleware throws, log and continue (default, for observers)
229
+ * 'fail-closed' — if middleware throws, stop the run (for critical guards)
230
+ */
231
+
232
+ type ControlAction = 'continue' | 'stop' | 'escalate';
233
+ interface AgentMiddleware {
234
+ /** Unique name for logging and deduplication */
235
+ name: string;
236
+ /**
237
+ * Execution order. Lower = runs first in before-hooks, last in after-hooks.
238
+ * Core middlewares: 0–99. Custom: 100+.
239
+ */
240
+ order: number;
241
+ config?: {
242
+ /** On error: continue silently ('fail-open') or stop run ('fail-closed'). Default: 'fail-open' */
243
+ failPolicy: 'fail-open' | 'fail-closed';
244
+ /** Hook timeout in ms (0 = no timeout). Default: 5000 */
245
+ timeoutMs?: number;
246
+ };
247
+ /** Called once at pipeline construction — if returns false, middleware is skipped entirely */
248
+ enabled?(): boolean;
249
+ /** Called once when the run starts, before the first iteration */
250
+ onStart?(ctx: RunContext): Promise<void> | void;
251
+ /** Called when the run ends for any reason (complete, abort, error) */
252
+ onStop?(ctx: RunContext, reason: string): Promise<void> | void;
253
+ /** Called only on successful completion, after onStop */
254
+ onComplete?(ctx: RunContext): Promise<void> | void;
255
+ /**
256
+ * Called before each iteration.
257
+ * Return 'stop' to end the run early.
258
+ * Return 'escalate' to trigger tier escalation.
259
+ * Return 'continue' (or undefined) to proceed normally.
260
+ */
261
+ beforeIteration?(ctx: RunContext): Promise<ControlAction> | ControlAction;
262
+ /** Called after each iteration — useful for metrics, logging, snapshots */
263
+ afterIteration?(ctx: RunContext): Promise<void> | void;
264
+ /**
265
+ * Called before the LLM API call.
266
+ * Returns a patch of what to change in the request (messages, tools, temperature).
267
+ * Returning undefined (or empty object) = no change.
268
+ * Patches from all middlewares are merged in order.
269
+ */
270
+ beforeLLMCall?(ctx: LLMCtx): Promise<LLMCallPatch | undefined> | LLMCallPatch | undefined;
271
+ /** Called after the LLM responded. Read-only — cannot modify the response. */
272
+ afterLLMCall?(ctx: LLMCtx, result: LLMCallResult): Promise<void> | void;
273
+ /**
274
+ * Called before each tool execution.
275
+ * Return 'skip' to prevent the tool from running (output will be empty).
276
+ * Return 'execute' (or undefined) to proceed.
277
+ */
278
+ beforeToolExec?(ctx: ToolExecCtx): Promise<'execute' | 'skip'> | 'execute' | 'skip';
279
+ /** Called after each tool execution (even if skipped or errored) */
280
+ afterToolExec?(ctx: ToolExecCtx, result: ToolOutput): Promise<void> | void;
281
+ }
282
+ /**
283
+ * Extend this to implement only the hooks you need.
284
+ *
285
+ * @example
286
+ * class RateLimitMiddleware extends BaseMiddleware {
287
+ * name = 'rate-limit';
288
+ * order = 50;
289
+ *
290
+ * async beforeIteration(ctx: RunContext): Promise<ControlAction> {
291
+ * if (this.isOverLimit()) return 'stop';
292
+ * return 'continue';
293
+ * }
294
+ * }
295
+ */
296
+ declare abstract class BaseMiddleware implements AgentMiddleware {
297
+ abstract name: string;
298
+ abstract order: number;
299
+ config: {
300
+ failPolicy: "fail-open";
301
+ };
302
+ }
303
+
304
+ /**
305
+ * ExecutionLoop — the iteration engine of an agent run.
306
+ *
307
+ * LoopContext provides only infrastructure primitives:
308
+ * - appendMessage() — the only way to mutate message history
309
+ * - callLLM() — makes the LLM call (with middleware pipeline applied)
310
+ * - executeTools() — executes tool calls (guards + output processors applied)
311
+ *
312
+ * Business logic (stop conditions, report detection, result building)
313
+ * lives in the ExecutionLoop implementation (LinearExecutionLoop in agent-core),
314
+ * not in LoopContext. This keeps the contract minimal and testable.
315
+ *
316
+ * Built-in: LinearExecutionLoop (agent-core)
317
+ * Custom example: GraphExecutionLoop (parallel branches)
318
+ */
319
+
320
+ interface LoopContext {
321
+ run: RunContext;
322
+ /**
323
+ * The only way to append messages to history.
324
+ * Enforces the readonly constraint on RunContext.messages.
325
+ */
326
+ appendMessage(message: LLMMessage): void;
327
+ /**
328
+ * Makes the LLM call with the current context.
329
+ * Applies beforeLLMCall / afterLLMCall middleware pipeline internally.
330
+ */
331
+ callLLM(): Promise<LLMCallResult>;
332
+ /**
333
+ * Executes a batch of tool calls.
334
+ * Applies guard pipeline + output processors internally.
335
+ */
336
+ executeTools(calls: ToolCallInput[]): Promise<ToolOutput[]>;
337
+ /**
338
+ * Evaluates run progress after a meaningful iteration using structured loop signals.
339
+ * ExecutionLoop uses this to decide whether to continue, narrow scope, or synthesize.
340
+ */
341
+ evaluateRun(snapshot: IterationSnapshot): Promise<RunEvaluation | null>;
342
+ /**
343
+ * Runs beforeIteration middleware hooks.
344
+ * Returns 'continue', 'stop', or 'escalate'.
345
+ */
346
+ beforeIteration(): Promise<ControlAction>;
347
+ /**
348
+ * Runs afterIteration middleware hooks.
349
+ * Should be called at the end of every iteration (before checking stop conditions
350
+ * that return early, call this just before returning).
351
+ */
352
+ afterIteration(): Promise<void>;
353
+ }
354
+ interface LoopOutput {
355
+ /** Final answer text (from report tool or last LLM response) */
356
+ answer: string;
357
+ /** Machine-readable stop reason */
358
+ reasonCode: string;
359
+ /** Whether the loop completed successfully */
360
+ success: boolean;
361
+ /** Extra metadata from the stop condition */
362
+ metadata?: Record<string, unknown>;
363
+ }
364
+ type LoopResult = {
365
+ outcome: 'complete';
366
+ result: LoopOutput;
367
+ } | {
368
+ outcome: 'escalate';
369
+ reason: string;
370
+ } | {
371
+ outcome: 'handoff';
372
+ toAgent: string;
373
+ context: unknown;
374
+ };
375
+ interface ExecutionLoop {
376
+ run(ctx: LoopContext): Promise<LoopResult>;
377
+ }
378
+
379
+ export { type AgentMiddleware as A, BaseMiddleware as B, type ContextMeta as C, type ExecutionLoop as E, type LLMCallResult as L, type RunContext as R, type ToolExecCtx as T, type Unsubscribe as U, type LLMCtx as a, type LLMCallPatch as b, type ToolCallInput as c, type ToolOutput as d, type ControlAction as e, type LoopContext as f, type LoopOutput as g, type LoopResult as h, type AgentEvents as i, type AgentEventBus as j };
@@ -0,0 +1,42 @@
1
+ import { C as ContextMeta, R as RunContext, a as LLMCtx, T as ToolExecCtx, L as LLMCallResult, d as ToolOutput, f as LoopContext, A as AgentMiddleware, e as ControlAction } from './loop-BeFjNV2F.js';
2
+ import '@kb-labs/agent-contracts';
3
+ import '@kb-labs/sdk';
4
+
5
+ /**
6
+ * @kb-labs/agent-sdk/testing
7
+ *
8
+ * Ready-made mock helpers for testing agent-core, plugins, and any code
9
+ * that works with SDK types. Import from this sub-path — never from main index.
10
+ *
11
+ * @example
12
+ * import { makeRunCtx, makeLoopCtx, makeLLMResponse } from '@kb-labs/agent-sdk/testing';
13
+ *
14
+ * All helpers use vitest's `vi.fn()` — vitest must be available in the test env.
15
+ */
16
+
17
+ declare function makeMeta(): ContextMeta & {
18
+ _store: Map<string, Map<string, unknown>>;
19
+ };
20
+ declare function makeRunCtx(overrides?: Partial<RunContext>): RunContext;
21
+ declare function makeLLMCtx(runCtx?: RunContext, overrides?: Partial<LLMCtx>): LLMCtx;
22
+ declare function makeToolExecCtx(runCtx?: RunContext, overrides?: Partial<ToolExecCtx>): ToolExecCtx;
23
+ declare function makeLLMResponse(overrides?: Partial<LLMCallResult>): LLMCallResult;
24
+ /** Shorthand: LLM response that calls the report tool */
25
+ declare function makeReportResponse(answer: string): LLMCallResult;
26
+ declare function makeToolOutput(overrides?: Partial<ToolOutput>): ToolOutput;
27
+ interface LoopCtxOptions {
28
+ run?: RunContext;
29
+ /** Sequential LLM responses — each call pops the next one */
30
+ llmResponses?: LLMCallResult[];
31
+ /** Tool outputs returned by executeTools */
32
+ toolOutputs?: ToolOutput[];
33
+ }
34
+ declare function makeLoopCtx(opts?: LoopCtxOptions): LoopContext;
35
+ declare function makeMiddleware(overrides: Partial<AgentMiddleware> & {
36
+ name: string;
37
+ order: number;
38
+ }): AgentMiddleware;
39
+ /** Middleware that always returns a fixed ControlAction from beforeIteration */
40
+ declare function makeControlMiddleware(name: string, order: number, action: ControlAction): AgentMiddleware;
41
+
42
+ export { type LoopCtxOptions, makeControlMiddleware, makeLLMCtx, makeLLMResponse, makeLoopCtx, makeMeta, makeMiddleware, makeReportResponse, makeRunCtx, makeToolExecCtx, makeToolOutput };
@@ -0,0 +1,114 @@
1
+ import { vi } from 'vitest';
2
+
3
+ // src/testing.ts
4
+ function makeMeta() {
5
+ const store = /* @__PURE__ */ new Map();
6
+ return {
7
+ _store: store,
8
+ get: vi.fn(
9
+ (ns, key) => store.get(ns)?.get(key)
10
+ ),
11
+ set: vi.fn((ns, key, value) => {
12
+ if (!store.has(ns)) {
13
+ store.set(ns, /* @__PURE__ */ new Map());
14
+ }
15
+ store.get(ns).set(key, value);
16
+ }),
17
+ getNamespace: vi.fn(
18
+ (ns) => Object.fromEntries(store.get(ns) ?? /* @__PURE__ */ new Map())
19
+ )
20
+ };
21
+ }
22
+ function makeRunCtx(overrides = {}) {
23
+ return {
24
+ task: "test task",
25
+ tier: "medium",
26
+ messages: [],
27
+ tools: [],
28
+ iteration: 0,
29
+ maxIterations: 20,
30
+ aborted: false,
31
+ abortSignal: new AbortController().signal,
32
+ requestId: "req-test",
33
+ meta: makeMeta(),
34
+ ...overrides
35
+ };
36
+ }
37
+ function makeLLMCtx(runCtx, overrides = {}) {
38
+ return {
39
+ run: runCtx ?? makeRunCtx(),
40
+ messages: [{ role: "user", content: "hello" }],
41
+ tools: [],
42
+ ...overrides
43
+ };
44
+ }
45
+ function makeToolExecCtx(runCtx, overrides = {}) {
46
+ const run = runCtx ?? makeRunCtx();
47
+ return {
48
+ run,
49
+ toolName: "fs_read",
50
+ input: {},
51
+ iteration: run.iteration,
52
+ abortSignal: run.abortSignal,
53
+ requestId: run.requestId,
54
+ ...overrides
55
+ };
56
+ }
57
+ function makeLLMResponse(overrides = {}) {
58
+ return {
59
+ content: "",
60
+ toolCalls: [],
61
+ ...overrides
62
+ };
63
+ }
64
+ function makeReportResponse(answer) {
65
+ return makeLLMResponse({
66
+ toolCalls: [{ id: "tc-report", name: "report", input: { answer } }]
67
+ });
68
+ }
69
+ function makeToolOutput(overrides = {}) {
70
+ return {
71
+ toolCallId: "tc-1",
72
+ output: "result",
73
+ success: true,
74
+ ...overrides
75
+ };
76
+ }
77
+ function makeLoopCtx(opts = {}) {
78
+ const run = opts.run ?? makeRunCtx();
79
+ const responses = [...opts.llmResponses ?? [makeLLMResponse()]];
80
+ let callIndex = 0;
81
+ return {
82
+ run,
83
+ appendMessage: vi.fn(),
84
+ callLLM: vi.fn(async () => {
85
+ const r = responses[callIndex] ?? makeLLMResponse();
86
+ callIndex++;
87
+ return r;
88
+ }),
89
+ executeTools: vi.fn(
90
+ async (_calls) => opts.toolOutputs ?? []
91
+ ),
92
+ evaluateRun: vi.fn(async () => null),
93
+ beforeIteration: vi.fn(async () => "continue"),
94
+ afterIteration: vi.fn(async () => {
95
+ })
96
+ };
97
+ }
98
+ function makeMiddleware(overrides) {
99
+ return {
100
+ config: { failPolicy: "fail-open" },
101
+ ...overrides
102
+ };
103
+ }
104
+ function makeControlMiddleware(name, order, action) {
105
+ return makeMiddleware({
106
+ name,
107
+ order,
108
+ beforeIteration: async () => action
109
+ });
110
+ }
111
+
112
+ export { makeControlMiddleware, makeLLMCtx, makeLLMResponse, makeLoopCtx, makeMeta, makeMiddleware, makeReportResponse, makeRunCtx, makeToolExecCtx, makeToolOutput };
113
+ //# sourceMappingURL=testing.js.map
114
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["/**\n * @kb-labs/agent-sdk/testing\n *\n * Ready-made mock helpers for testing agent-core, plugins, and any code\n * that works with SDK types. Import from this sub-path — never from main index.\n *\n * @example\n * import { makeRunCtx, makeLoopCtx, makeLLMResponse } from '@kb-labs/agent-sdk/testing';\n *\n * All helpers use vitest's `vi.fn()` — vitest must be available in the test env.\n */\n\nimport { vi } from 'vitest';\nimport type { RunContext, ContextMeta, LLMCtx, LLMCallResult, ToolExecCtx, ToolOutput, ToolCallInput } from './contexts.js';\nimport type { LoopContext } from './loop.js';\nimport type { AgentMiddleware, ControlAction } from './middleware.js';\n\n// ─── ContextMeta mock ─────────────────────────────────────────────────────────\n\nexport function makeMeta(): ContextMeta & { _store: Map<string, Map<string, unknown>> } {\n const store = new Map<string, Map<string, unknown>>();\n return {\n _store: store,\n get: vi.fn((ns: string, key: string) =>\n store.get(ns)?.get(key),\n ) as ContextMeta['get'],\n set: vi.fn((ns: string, key: string, value: unknown) => {\n if (!store.has(ns)) {\n store.set(ns, new Map());\n }\n store.get(ns)!.set(key, value);\n }),\n getNamespace: vi.fn((ns: string) =>\n Object.fromEntries(store.get(ns) ?? new Map()),\n ),\n };\n}\n\n// ─── RunContext mock ──────────────────────────────────────────────────────────\n\nexport function makeRunCtx(overrides: Partial<RunContext> = {}): RunContext {\n return {\n task: 'test task',\n tier: 'medium',\n messages: [],\n tools: [],\n iteration: 0,\n maxIterations: 20,\n aborted: false,\n abortSignal: new AbortController().signal,\n requestId: 'req-test',\n meta: makeMeta(),\n ...overrides,\n } as RunContext;\n}\n\n// ─── LLMCtx mock ─────────────────────────────────────────────────────────────\n\nexport function makeLLMCtx(runCtx?: RunContext, overrides: Partial<LLMCtx> = {}): LLMCtx {\n return {\n run: runCtx ?? makeRunCtx(),\n messages: [{ role: 'user', content: 'hello' }],\n tools: [],\n ...overrides,\n };\n}\n\n// ─── ToolExecCtx mock ─────────────────────────────────────────────────────────\n\nexport function makeToolExecCtx(\n runCtx?: RunContext,\n overrides: Partial<ToolExecCtx> = {},\n): ToolExecCtx {\n const run = runCtx ?? makeRunCtx();\n return {\n run,\n toolName: 'fs_read',\n input: {},\n iteration: run.iteration,\n abortSignal: run.abortSignal,\n requestId: run.requestId,\n ...overrides,\n };\n}\n\n// ─── LLMCallResult mock ───────────────────────────────────────────────────────\n\nexport function makeLLMResponse(overrides: Partial<LLMCallResult> = {}): LLMCallResult {\n return {\n content: '',\n toolCalls: [],\n ...overrides,\n };\n}\n\n/** Shorthand: LLM response that calls the report tool */\nexport function makeReportResponse(answer: string): LLMCallResult {\n return makeLLMResponse({\n toolCalls: [{ id: 'tc-report', name: 'report', input: { answer } }],\n });\n}\n\n// ─── ToolOutput mock ──────────────────────────────────────────────────────────\n\nexport function makeToolOutput(overrides: Partial<ToolOutput> = {}): ToolOutput {\n return {\n toolCallId: 'tc-1',\n output: 'result',\n success: true,\n ...overrides,\n };\n}\n\n// ─── LoopContext mock ─────────────────────────────────────────────────────────\n\nexport interface LoopCtxOptions {\n run?: RunContext;\n /** Sequential LLM responses — each call pops the next one */\n llmResponses?: LLMCallResult[];\n /** Tool outputs returned by executeTools */\n toolOutputs?: ToolOutput[];\n}\n\nexport function makeLoopCtx(opts: LoopCtxOptions = {}): LoopContext {\n const run = opts.run ?? makeRunCtx();\n const responses = [...(opts.llmResponses ?? [makeLLMResponse()])];\n let callIndex = 0;\n\n return {\n run,\n appendMessage: vi.fn(),\n callLLM: vi.fn(async (): Promise<LLMCallResult> => {\n const r = responses[callIndex] ?? makeLLMResponse();\n callIndex++;\n return r;\n }),\n executeTools: vi.fn(async (_calls: ToolCallInput[]): Promise<ToolOutput[]> =>\n opts.toolOutputs ?? [],\n ),\n evaluateRun: vi.fn(async () => null),\n beforeIteration: vi.fn(async () => 'continue' as const),\n afterIteration: vi.fn(async () => {}),\n };\n}\n\n// ─── AgentMiddleware mock ─────────────────────────────────────────────────────\n\nexport function makeMiddleware(\n overrides: Partial<AgentMiddleware> & { name: string; order: number },\n): AgentMiddleware {\n return {\n config: { failPolicy: 'fail-open' },\n ...overrides,\n };\n}\n\n/** Middleware that always returns a fixed ControlAction from beforeIteration */\nexport function makeControlMiddleware(\n name: string,\n order: number,\n action: ControlAction,\n): AgentMiddleware {\n return makeMiddleware({\n name,\n order,\n beforeIteration: async () => action,\n });\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@kb-labs/agent-sdk",
3
+ "version": "0.6.0",
4
+ "type": "module",
5
+ "description": "Agent SDK — contracts and interfaces for composing agents. No logic, only types.",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./testing": {
14
+ "import": "./dist/testing.js",
15
+ "types": "./dist/testing.d.ts"
16
+ },
17
+ "./dist/*": "./dist/*"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md"
22
+ ],
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "clean": "rimraf dist",
26
+ "build": "tsup --config tsup.config.ts",
27
+ "dev": "tsup --config tsup.config.ts --watch",
28
+ "lint": "eslint src --ext .ts",
29
+ "lint:fix": "eslint . --fix",
30
+ "type-check": "tsc --noEmit",
31
+ "test": "vitest run --passWithNoTests"
32
+ },
33
+ "dependencies": {
34
+ "@kb-labs/agent-contracts": "^0.6.0",
35
+ "@kb-labs/sdk": "^1.5.0",
36
+ "vitest": "^3.2.4"
37
+ },
38
+ "devDependencies": {
39
+ "@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit",
40
+ "@types/node": "^24.3.3",
41
+ "eslint": "^9",
42
+ "rimraf": "^6.0.1",
43
+ "tsup": "^8.5.0",
44
+ "typescript": "^5.6.3"
45
+ },
46
+ "engines": {
47
+ "node": ">=20.0.0",
48
+ "pnpm": ">=9.0.0"
49
+ }
50
+ }