@jackchen_me/open-multi-agent 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/package.json +8 -2
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
  4. package/.github/pull_request_template.md +0 -14
  5. package/.github/workflows/ci.yml +0 -23
  6. package/CLAUDE.md +0 -80
  7. package/CODE_OF_CONDUCT.md +0 -48
  8. package/CONTRIBUTING.md +0 -72
  9. package/DECISIONS.md +0 -43
  10. package/README_zh.md +0 -277
  11. package/SECURITY.md +0 -17
  12. package/examples/01-single-agent.ts +0 -131
  13. package/examples/02-team-collaboration.ts +0 -167
  14. package/examples/03-task-pipeline.ts +0 -201
  15. package/examples/04-multi-model-team.ts +0 -261
  16. package/examples/05-copilot-test.ts +0 -49
  17. package/examples/06-local-model.ts +0 -200
  18. package/examples/07-fan-out-aggregate.ts +0 -209
  19. package/examples/08-gemma4-local.ts +0 -192
  20. package/examples/09-structured-output.ts +0 -73
  21. package/examples/10-task-retry.ts +0 -132
  22. package/examples/11-trace-observability.ts +0 -133
  23. package/examples/12-grok.ts +0 -154
  24. package/examples/13-gemini.ts +0 -48
  25. package/src/agent/agent.ts +0 -622
  26. package/src/agent/loop-detector.ts +0 -137
  27. package/src/agent/pool.ts +0 -285
  28. package/src/agent/runner.ts +0 -542
  29. package/src/agent/structured-output.ts +0 -126
  30. package/src/index.ts +0 -182
  31. package/src/llm/adapter.ts +0 -98
  32. package/src/llm/anthropic.ts +0 -389
  33. package/src/llm/copilot.ts +0 -552
  34. package/src/llm/gemini.ts +0 -378
  35. package/src/llm/grok.ts +0 -29
  36. package/src/llm/openai-common.ts +0 -294
  37. package/src/llm/openai.ts +0 -292
  38. package/src/memory/shared.ts +0 -181
  39. package/src/memory/store.ts +0 -124
  40. package/src/orchestrator/orchestrator.ts +0 -1071
  41. package/src/orchestrator/scheduler.ts +0 -352
  42. package/src/task/queue.ts +0 -464
  43. package/src/task/task.ts +0 -239
  44. package/src/team/messaging.ts +0 -232
  45. package/src/team/team.ts +0 -334
  46. package/src/tool/built-in/bash.ts +0 -187
  47. package/src/tool/built-in/file-edit.ts +0 -154
  48. package/src/tool/built-in/file-read.ts +0 -105
  49. package/src/tool/built-in/file-write.ts +0 -81
  50. package/src/tool/built-in/grep.ts +0 -362
  51. package/src/tool/built-in/index.ts +0 -50
  52. package/src/tool/executor.ts +0 -178
  53. package/src/tool/framework.ts +0 -557
  54. package/src/tool/text-tool-extractor.ts +0 -219
  55. package/src/types.ts +0 -542
  56. package/src/utils/semaphore.ts +0 -89
  57. package/src/utils/trace.ts +0 -34
  58. package/tests/agent-hooks.test.ts +0 -473
  59. package/tests/agent-pool.test.ts +0 -212
  60. package/tests/approval.test.ts +0 -464
  61. package/tests/built-in-tools.test.ts +0 -393
  62. package/tests/gemini-adapter.test.ts +0 -97
  63. package/tests/grok-adapter.test.ts +0 -74
  64. package/tests/llm-adapters.test.ts +0 -357
  65. package/tests/loop-detection.test.ts +0 -456
  66. package/tests/openai-fallback.test.ts +0 -159
  67. package/tests/orchestrator.test.ts +0 -281
  68. package/tests/scheduler.test.ts +0 -221
  69. package/tests/semaphore.test.ts +0 -57
  70. package/tests/shared-memory.test.ts +0 -122
  71. package/tests/structured-output.test.ts +0 -331
  72. package/tests/task-queue.test.ts +0 -244
  73. package/tests/task-retry.test.ts +0 -368
  74. package/tests/task-utils.test.ts +0 -155
  75. package/tests/team-messaging.test.ts +0 -329
  76. package/tests/text-tool-extractor.test.ts +0 -170
  77. package/tests/tool-executor.test.ts +0 -193
  78. package/tests/trace.test.ts +0 -453
  79. package/tsconfig.json +0 -25
  80. package/vitest.config.ts +0 -9
package/src/types.ts DELETED
@@ -1,542 +0,0 @@
1
- /**
2
- * @fileoverview Core type definitions for the open-multi-agent orchestration framework.
3
- *
4
- * All public types are exported from this single module. Downstream modules
5
- * import only what they need, keeping the dependency graph acyclic.
6
- */
7
-
8
- import type { ZodSchema } from 'zod'
9
-
10
- // ---------------------------------------------------------------------------
11
- // Content blocks
12
- // ---------------------------------------------------------------------------
13
-
14
- /** Plain-text content produced by a model or supplied by the user. */
15
- export interface TextBlock {
16
- readonly type: 'text'
17
- readonly text: string
18
- }
19
-
20
- /**
21
- * A request by the model to invoke a named tool with a structured input.
22
- * The `id` is unique per turn and is referenced by {@link ToolResultBlock}.
23
- */
24
- export interface ToolUseBlock {
25
- readonly type: 'tool_use'
26
- readonly id: string
27
- readonly name: string
28
- readonly input: Record<string, unknown>
29
- }
30
-
31
- /**
32
- * The result of executing a tool, keyed back to the originating
33
- * {@link ToolUseBlock} via `tool_use_id`.
34
- */
35
- export interface ToolResultBlock {
36
- readonly type: 'tool_result'
37
- readonly tool_use_id: string
38
- readonly content: string
39
- readonly is_error?: boolean
40
- }
41
-
42
- /** A base64-encoded image passed to or returned from a model. */
43
- export interface ImageBlock {
44
- readonly type: 'image'
45
- readonly source: {
46
- readonly type: 'base64'
47
- readonly media_type: string
48
- readonly data: string
49
- }
50
- }
51
-
52
- /** Union of all content block variants that may appear in a message. */
53
- export type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | ImageBlock
54
-
55
- // ---------------------------------------------------------------------------
56
- // LLM messages & responses
57
- // ---------------------------------------------------------------------------
58
-
59
- /**
60
- * A single message in a conversation thread.
61
- * System messages are passed separately via {@link LLMChatOptions.systemPrompt}.
62
- */
63
- export interface LLMMessage {
64
- readonly role: 'user' | 'assistant'
65
- readonly content: ContentBlock[]
66
- }
67
-
68
- /** Token accounting for a single API call. */
69
- export interface TokenUsage {
70
- readonly input_tokens: number
71
- readonly output_tokens: number
72
- }
73
-
74
- /** Normalised response returned by every {@link LLMAdapter} implementation. */
75
- export interface LLMResponse {
76
- readonly id: string
77
- readonly content: ContentBlock[]
78
- readonly model: string
79
- readonly stop_reason: string
80
- readonly usage: TokenUsage
81
- }
82
-
83
- // ---------------------------------------------------------------------------
84
- // Streaming
85
- // ---------------------------------------------------------------------------
86
-
87
- /**
88
- * A discrete event emitted during streaming generation.
89
- *
90
- * - `text` — incremental text delta
91
- * - `tool_use` — the model has begun or completed a tool-use block
92
- * - `tool_result` — a tool result has been appended to the stream
93
- * - `done` — the stream has ended; `data` is the final {@link LLMResponse}
94
- * - `error` — an unrecoverable error occurred; `data` is an `Error`
95
- */
96
- export interface StreamEvent {
97
- readonly type: 'text' | 'tool_use' | 'tool_result' | 'loop_detected' | 'done' | 'error'
98
- readonly data: unknown
99
- }
100
-
101
- // ---------------------------------------------------------------------------
102
- // Tool definitions
103
- // ---------------------------------------------------------------------------
104
-
105
- /** The serialisable tool schema sent to the LLM provider. */
106
- export interface LLMToolDef {
107
- readonly name: string
108
- readonly description: string
109
- /** JSON Schema object describing the tool's `input` parameter. */
110
- readonly inputSchema: Record<string, unknown>
111
- }
112
-
113
- /**
114
- * Context injected into every tool execution.
115
- *
116
- * Both `abortSignal` and `abortController` are provided so that tools and the
117
- * executor can choose the most ergonomic API for their use-case:
118
- *
119
- * - Long-running shell commands that need to kill a child process can use
120
- * `abortController.signal` directly.
121
- * - Simple cancellation checks can read `abortSignal?.aborted`.
122
- *
123
- * When constructing a context, set `abortController` and derive `abortSignal`
124
- * from it, or provide both independently.
125
- */
126
- export interface ToolUseContext {
127
- /** High-level description of the agent invoking this tool. */
128
- readonly agent: AgentInfo
129
- /** Team context, present when the tool runs inside a multi-agent team. */
130
- readonly team?: TeamInfo
131
- /**
132
- * Convenience reference to the abort signal.
133
- * Equivalent to `abortController?.signal` when an `abortController` is set.
134
- */
135
- readonly abortSignal?: AbortSignal
136
- /**
137
- * Full abort controller, available when the caller needs to inspect or
138
- * programmatically abort the signal.
139
- * Tools should prefer `abortSignal` for simple cancellation checks.
140
- */
141
- readonly abortController?: AbortController
142
- /** Working directory hint for file-system tools. */
143
- readonly cwd?: string
144
- /** Arbitrary caller-supplied metadata (session ID, request ID, etc.). */
145
- readonly metadata?: Readonly<Record<string, unknown>>
146
- }
147
-
148
- /** Minimal descriptor for the agent that is invoking a tool. */
149
- export interface AgentInfo {
150
- readonly name: string
151
- readonly role: string
152
- readonly model: string
153
- }
154
-
155
- /** Descriptor for a team of agents with shared memory. */
156
- export interface TeamInfo {
157
- readonly name: string
158
- readonly agents: readonly string[]
159
- readonly sharedMemory: MemoryStore
160
- }
161
-
162
- /** Value returned by a tool's `execute` function. */
163
- export interface ToolResult {
164
- readonly data: string
165
- readonly isError?: boolean
166
- }
167
-
168
- /**
169
- * A tool registered with the framework.
170
- *
171
- * `inputSchema` is a Zod schema used for validation before `execute` is called.
172
- * At API call time it is converted to JSON Schema via {@link LLMToolDef}.
173
- */
174
- export interface ToolDefinition<TInput = Record<string, unknown>> {
175
- readonly name: string
176
- readonly description: string
177
- readonly inputSchema: ZodSchema<TInput>
178
- execute(input: TInput, context: ToolUseContext): Promise<ToolResult>
179
- }
180
-
181
- // ---------------------------------------------------------------------------
182
- // Agent
183
- // ---------------------------------------------------------------------------
184
-
185
- /** Context passed to the {@link AgentConfig.beforeRun} hook. */
186
- export interface BeforeRunHookContext {
187
- /** The user prompt text. */
188
- readonly prompt: string
189
- /** The agent's static configuration. */
190
- readonly agent: AgentConfig
191
- }
192
-
193
- /** Static configuration for a single agent. */
194
- export interface AgentConfig {
195
- readonly name: string
196
- readonly model: string
197
- readonly provider?: 'anthropic' | 'copilot' | 'grok' | 'openai' | 'gemini'
198
- /**
199
- * Custom base URL for OpenAI-compatible APIs (Ollama, vLLM, LM Studio, etc.).
200
- * Note: local servers that don't require auth still need `apiKey` set to a
201
- * non-empty placeholder (e.g. `'ollama'`) because the OpenAI SDK validates it.
202
- */
203
- readonly baseURL?: string
204
- /** API key override; falls back to the provider's standard env var. */
205
- readonly apiKey?: string
206
- readonly systemPrompt?: string
207
- /** Names of tools (from the tool registry) available to this agent. */
208
- readonly tools?: readonly string[]
209
- readonly maxTurns?: number
210
- readonly maxTokens?: number
211
- readonly temperature?: number
212
- /**
213
- * Maximum wall-clock time (in milliseconds) for the entire agent run.
214
- * When exceeded, the run is aborted via `AbortSignal.timeout()`.
215
- * Useful for local models where inference can be unpredictably slow.
216
- */
217
- readonly timeoutMs?: number
218
- /**
219
- * Loop detection configuration. When set, the agent tracks repeated tool
220
- * calls and text outputs to detect stuck loops before `maxTurns` is reached.
221
- */
222
- readonly loopDetection?: LoopDetectionConfig
223
- /**
224
- * Optional Zod schema for structured output. When set, the agent's final
225
- * output is parsed as JSON and validated against this schema. A single
226
- * retry with error feedback is attempted on validation failure.
227
- */
228
- readonly outputSchema?: ZodSchema
229
- /**
230
- * Called before each agent run. Receives the prompt and agent config.
231
- * Return a (possibly modified) context to continue, or throw to abort the run.
232
- * Only `prompt` from the returned context is applied; `agent` is read-only informational.
233
- */
234
- readonly beforeRun?: (context: BeforeRunHookContext) => Promise<BeforeRunHookContext> | BeforeRunHookContext
235
- /**
236
- * Called after each agent run completes successfully. Receives the run result.
237
- * Return a (possibly modified) result, or throw to mark the run as failed.
238
- * Not called when the run throws. For error observation, handle errors at the call site.
239
- */
240
- readonly afterRun?: (result: AgentRunResult) => Promise<AgentRunResult> | AgentRunResult
241
- }
242
-
243
- // ---------------------------------------------------------------------------
244
- // Loop detection
245
- // ---------------------------------------------------------------------------
246
-
247
- /** Configuration for agent loop detection. */
248
- export interface LoopDetectionConfig {
249
- /**
250
- * Maximum consecutive times the same tool call (name + args) or text
251
- * output can repeat before detection triggers. Default: `3`.
252
- */
253
- readonly maxRepetitions?: number
254
- /**
255
- * Number of recent turns to track for repetition analysis. Default: `4`.
256
- */
257
- readonly loopDetectionWindow?: number
258
- /**
259
- * Action to take when a loop is detected.
260
- * - `'warn'` — inject a "you appear stuck" message, give the LLM one
261
- * more chance; terminate if the loop persists (default)
262
- * - `'terminate'` — stop the run immediately
263
- * - `function` — custom callback (sync or async); return `'continue'`,
264
- * `'inject'`, or `'terminate'` to control the outcome
265
- */
266
- readonly onLoopDetected?: 'warn' | 'terminate' | ((info: LoopDetectionInfo) => 'continue' | 'inject' | 'terminate' | Promise<'continue' | 'inject' | 'terminate'>)
267
- }
268
-
269
- /** Diagnostic payload emitted when a loop is detected. */
270
- export interface LoopDetectionInfo {
271
- readonly kind: 'tool_repetition' | 'text_repetition'
272
- /** Number of consecutive identical occurrences observed. */
273
- readonly repetitions: number
274
- /** Human-readable description of the detected loop. */
275
- readonly detail: string
276
- }
277
-
278
- /** Lifecycle state tracked during an agent run. */
279
- export interface AgentState {
280
- status: 'idle' | 'running' | 'completed' | 'error'
281
- messages: LLMMessage[]
282
- tokenUsage: TokenUsage
283
- error?: Error
284
- }
285
-
286
- /** A single recorded tool invocation within a run. */
287
- export interface ToolCallRecord {
288
- readonly toolName: string
289
- readonly input: Record<string, unknown>
290
- readonly output: string
291
- /** Wall-clock duration in milliseconds. */
292
- readonly duration: number
293
- }
294
-
295
- /** The final result produced when an agent run completes (or fails). */
296
- export interface AgentRunResult {
297
- readonly success: boolean
298
- readonly output: string
299
- readonly messages: LLMMessage[]
300
- readonly tokenUsage: TokenUsage
301
- readonly toolCalls: ToolCallRecord[]
302
- /**
303
- * Parsed and validated structured output when `outputSchema` is set on the
304
- * agent config. `undefined` when no schema is configured or validation
305
- * failed after retry.
306
- */
307
- readonly structured?: unknown
308
- /** True when the run was terminated or warned due to loop detection. */
309
- readonly loopDetected?: boolean
310
- }
311
-
312
- // ---------------------------------------------------------------------------
313
- // Team
314
- // ---------------------------------------------------------------------------
315
-
316
- /** Static configuration for a team of cooperating agents. */
317
- export interface TeamConfig {
318
- readonly name: string
319
- readonly agents: readonly AgentConfig[]
320
- readonly sharedMemory?: boolean
321
- readonly maxConcurrency?: number
322
- }
323
-
324
- /** Aggregated result for a full team run. */
325
- export interface TeamRunResult {
326
- readonly success: boolean
327
- /** Keyed by agent name. */
328
- readonly agentResults: Map<string, AgentRunResult>
329
- readonly totalTokenUsage: TokenUsage
330
- }
331
-
332
- // ---------------------------------------------------------------------------
333
- // Task
334
- // ---------------------------------------------------------------------------
335
-
336
- /** Valid states for a {@link Task}. */
337
- export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'blocked' | 'skipped'
338
-
339
- /** A discrete unit of work tracked by the orchestrator. */
340
- export interface Task {
341
- readonly id: string
342
- readonly title: string
343
- readonly description: string
344
- status: TaskStatus
345
- /** Agent name responsible for executing this task. */
346
- assignee?: string
347
- /** IDs of tasks that must complete before this one can start. */
348
- dependsOn?: readonly string[]
349
- result?: string
350
- readonly createdAt: Date
351
- updatedAt: Date
352
- /** Maximum number of retry attempts on failure (default: 0 — no retry). */
353
- readonly maxRetries?: number
354
- /** Base delay in ms before the first retry (default: 1000). */
355
- readonly retryDelayMs?: number
356
- /** Exponential backoff multiplier (default: 2). */
357
- readonly retryBackoff?: number
358
- }
359
-
360
- // ---------------------------------------------------------------------------
361
- // Orchestrator
362
- // ---------------------------------------------------------------------------
363
-
364
- /**
365
- * Progress event emitted by the orchestrator during a run.
366
- *
367
- * **v0.3 addition:** `'task_skipped'` — consumers with exhaustive switches
368
- * on `type` will need to add a case for this variant.
369
- */
370
- export interface OrchestratorEvent {
371
- readonly type:
372
- | 'agent_start'
373
- | 'agent_complete'
374
- | 'task_start'
375
- | 'task_complete'
376
- | 'task_skipped'
377
- | 'task_retry'
378
- | 'message'
379
- | 'error'
380
- readonly agent?: string
381
- readonly task?: string
382
- readonly data?: unknown
383
- }
384
-
385
- /** Top-level configuration for the orchestrator. */
386
- export interface OrchestratorConfig {
387
- readonly maxConcurrency?: number
388
- readonly defaultModel?: string
389
- readonly defaultProvider?: 'anthropic' | 'copilot' | 'grok' | 'openai' | 'gemini'
390
- readonly defaultBaseURL?: string
391
- readonly defaultApiKey?: string
392
- readonly onProgress?: (event: OrchestratorEvent) => void
393
- readonly onTrace?: (event: TraceEvent) => void | Promise<void>
394
- /**
395
- * Optional approval gate called between task execution rounds.
396
- *
397
- * After a batch of tasks completes, this callback receives all
398
- * completed {@link Task}s from that round and the list of tasks about
399
- * to start next. Return `true` to continue or `false` to abort —
400
- * remaining tasks will be marked `'skipped'`.
401
- *
402
- * Not called when:
403
- * - No tasks succeeded in the round (all failed).
404
- * - No pending tasks remain after the round (final batch).
405
- *
406
- * **Note:** Do not mutate the {@link Task} objects passed to this
407
- * callback — they are live references to queue state. Mutation is
408
- * undefined behavior.
409
- */
410
- readonly onApproval?: (completedTasks: readonly Task[], nextTasks: readonly Task[]) => Promise<boolean>
411
- }
412
-
413
- // ---------------------------------------------------------------------------
414
- // Trace events — lightweight observability spans
415
- // ---------------------------------------------------------------------------
416
-
417
- /** Trace event type discriminants. */
418
- export type TraceEventType = 'llm_call' | 'tool_call' | 'task' | 'agent'
419
-
420
- /** Shared fields present on every trace event. */
421
- export interface TraceEventBase {
422
- /** Unique identifier for the entire run (runTeam / runTasks / runAgent call). */
423
- readonly runId: string
424
- readonly type: TraceEventType
425
- /** Unix epoch ms when the span started. */
426
- readonly startMs: number
427
- /** Unix epoch ms when the span ended. */
428
- readonly endMs: number
429
- /** Wall-clock duration in milliseconds (`endMs - startMs`). */
430
- readonly durationMs: number
431
- /** Agent name associated with this span. */
432
- readonly agent: string
433
- /** Task ID associated with this span. */
434
- readonly taskId?: string
435
- }
436
-
437
- /** Emitted for each LLM API call (one per agent turn). */
438
- export interface LLMCallTrace extends TraceEventBase {
439
- readonly type: 'llm_call'
440
- readonly model: string
441
- readonly turn: number
442
- readonly tokens: TokenUsage
443
- }
444
-
445
- /** Emitted for each tool execution. */
446
- export interface ToolCallTrace extends TraceEventBase {
447
- readonly type: 'tool_call'
448
- readonly tool: string
449
- readonly isError: boolean
450
- }
451
-
452
- /** Emitted when a task completes (wraps the full retry sequence). */
453
- export interface TaskTrace extends TraceEventBase {
454
- readonly type: 'task'
455
- readonly taskId: string
456
- readonly taskTitle: string
457
- readonly success: boolean
458
- readonly retries: number
459
- }
460
-
461
- /** Emitted when an agent run completes (wraps the full conversation loop). */
462
- export interface AgentTrace extends TraceEventBase {
463
- readonly type: 'agent'
464
- readonly turns: number
465
- readonly tokens: TokenUsage
466
- readonly toolCalls: number
467
- }
468
-
469
- /** Discriminated union of all trace event types. */
470
- export type TraceEvent = LLMCallTrace | ToolCallTrace | TaskTrace | AgentTrace
471
-
472
- // ---------------------------------------------------------------------------
473
- // Memory
474
- // ---------------------------------------------------------------------------
475
-
476
- /** A single key-value record stored in a {@link MemoryStore}. */
477
- export interface MemoryEntry {
478
- readonly key: string
479
- readonly value: string
480
- readonly metadata?: Readonly<Record<string, unknown>>
481
- readonly createdAt: Date
482
- }
483
-
484
- /**
485
- * Persistent (or in-memory) key-value store shared across agents.
486
- * Implementations may be backed by Redis, SQLite, or plain objects.
487
- */
488
- export interface MemoryStore {
489
- get(key: string): Promise<MemoryEntry | null>
490
- set(key: string, value: string, metadata?: Record<string, unknown>): Promise<void>
491
- list(): Promise<MemoryEntry[]>
492
- delete(key: string): Promise<void>
493
- clear(): Promise<void>
494
- }
495
-
496
- // ---------------------------------------------------------------------------
497
- // LLM adapter
498
- // ---------------------------------------------------------------------------
499
-
500
- /** Options shared by both chat and streaming calls. */
501
- export interface LLMChatOptions {
502
- readonly model: string
503
- readonly tools?: readonly LLMToolDef[]
504
- readonly maxTokens?: number
505
- readonly temperature?: number
506
- readonly systemPrompt?: string
507
- readonly abortSignal?: AbortSignal
508
- }
509
-
510
- /**
511
- * Options for streaming calls.
512
- * Extends {@link LLMChatOptions} without additional fields — the separation
513
- * exists so callers can type-narrow and implementations can diverge later.
514
- */
515
- export interface LLMStreamOptions extends LLMChatOptions {}
516
-
517
- /**
518
- * Provider-agnostic interface that every LLM backend must implement.
519
- *
520
- * @example
521
- * ```ts
522
- * const adapter: LLMAdapter = createAdapter('anthropic')
523
- * const response = await adapter.chat(messages, { model: 'claude-opus-4-6' })
524
- * ```
525
- */
526
- export interface LLMAdapter {
527
- /** Human-readable provider name, e.g. `'anthropic'` or `'openai'`. */
528
- readonly name: string
529
-
530
- /**
531
- * Send a chat request and return the complete response.
532
- * Throws on non-retryable API errors.
533
- */
534
- chat(messages: LLMMessage[], options: LLMChatOptions): Promise<LLMResponse>
535
-
536
- /**
537
- * Send a chat request and yield {@link StreamEvent}s incrementally.
538
- * The final event in the sequence always has `type === 'done'` on success,
539
- * or `type === 'error'` on failure.
540
- */
541
- stream(messages: LLMMessage[], options: LLMStreamOptions): AsyncIterable<StreamEvent>
542
- }
@@ -1,89 +0,0 @@
1
- /**
2
- * @fileoverview Shared counting semaphore for concurrency control.
3
- *
4
- * Used by both {@link ToolExecutor} and {@link AgentPool} to cap the number of
5
- * concurrent async operations without requiring any third-party dependencies.
6
- *
7
- * This is intentionally self-contained and tuned for Promise/async use —
8
- * not a general OS-semaphore replacement.
9
- */
10
-
11
- // ---------------------------------------------------------------------------
12
- // Semaphore
13
- // ---------------------------------------------------------------------------
14
-
15
- /**
16
- * Classic counting semaphore for concurrency control.
17
- *
18
- * `acquire()` resolves immediately if a slot is free, otherwise queues the
19
- * caller. `release()` unblocks the next waiter in FIFO order.
20
- *
21
- * Node.js is single-threaded, so this is safe without atomics or mutex
22
- * primitives — the semaphore gates concurrent async operations, not CPU threads.
23
- */
24
- export class Semaphore {
25
- private current = 0
26
- private readonly queue: Array<() => void> = []
27
-
28
- /**
29
- * @param max - Maximum number of concurrent holders. Must be >= 1.
30
- */
31
- constructor(private readonly max: number) {
32
- if (max < 1) {
33
- throw new RangeError(`Semaphore max must be at least 1, got ${max}`)
34
- }
35
- }
36
-
37
- /**
38
- * Acquire a slot. Resolves immediately when one is free, or waits until a
39
- * holder calls `release()`.
40
- */
41
- acquire(): Promise<void> {
42
- if (this.current < this.max) {
43
- this.current++
44
- return Promise.resolve()
45
- }
46
-
47
- return new Promise<void>(resolve => {
48
- this.queue.push(resolve)
49
- })
50
- }
51
-
52
- /**
53
- * Release a previously acquired slot.
54
- * If callers are queued, the next one is unblocked synchronously.
55
- */
56
- release(): void {
57
- const next = this.queue.shift()
58
- if (next !== undefined) {
59
- // A queued caller is waiting — hand the slot directly to it.
60
- // `current` stays the same: we consumed the slot immediately.
61
- next()
62
- } else {
63
- this.current--
64
- }
65
- }
66
-
67
- /**
68
- * Run `fn` while holding one slot, automatically releasing it afterward
69
- * even if `fn` throws.
70
- */
71
- async run<T>(fn: () => Promise<T>): Promise<T> {
72
- await this.acquire()
73
- try {
74
- return await fn()
75
- } finally {
76
- this.release()
77
- }
78
- }
79
-
80
- /** Number of slots currently in use. */
81
- get active(): number {
82
- return this.current
83
- }
84
-
85
- /** Number of callers waiting for a slot. */
86
- get pending(): number {
87
- return this.queue.length
88
- }
89
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * @fileoverview Trace emission utilities for the observability layer.
3
- */
4
-
5
- import { randomUUID } from 'node:crypto'
6
- import type { TraceEvent } from '../types.js'
7
-
8
- /**
9
- * Safely emit a trace event. Swallows callback errors so a broken
10
- * subscriber never crashes agent execution.
11
- */
12
- export function emitTrace(
13
- fn: ((event: TraceEvent) => void | Promise<void>) | undefined,
14
- event: TraceEvent,
15
- ): void {
16
- if (!fn) return
17
- try {
18
- // Guard async callbacks: if fn returns a Promise, swallow its rejection
19
- // so an async onTrace never produces an unhandled promise rejection.
20
- const result = fn(event) as unknown
21
- if (result && typeof (result as Promise<unknown>).catch === 'function') {
22
- ;(result as Promise<unknown>).catch(noop)
23
- }
24
- } catch {
25
- // Intentionally swallowed — observability must never break execution.
26
- }
27
- }
28
-
29
- function noop() {}
30
-
31
- /** Generate a unique run ID for trace correlation. */
32
- export function generateRunId(): string {
33
- return randomUUID()
34
- }