@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/index.ts DELETED
@@ -1,182 +0,0 @@
1
- /**
2
- * @fileoverview open-multi-agent — public API surface.
3
- *
4
- * Import from `'open-multi-agent'` to access everything you need:
5
- *
6
- * ```ts
7
- * import { OpenMultiAgent, Agent, Team, defineTool } from 'open-multi-agent'
8
- * ```
9
- *
10
- * ## Quickstart
11
- *
12
- * ### Single agent
13
- * ```ts
14
- * const orchestrator = new OpenMultiAgent({ defaultModel: 'claude-opus-4-6' })
15
- * const result = await orchestrator.runAgent(
16
- * { name: 'assistant', model: 'claude-opus-4-6' },
17
- * 'Explain monads in one paragraph.',
18
- * )
19
- * console.log(result.output)
20
- * ```
21
- *
22
- * ### Multi-agent team (auto-orchestrated)
23
- * ```ts
24
- * const orchestrator = new OpenMultiAgent()
25
- * const team = orchestrator.createTeam('writers', {
26
- * name: 'writers',
27
- * agents: [
28
- * { name: 'researcher', model: 'claude-opus-4-6', systemPrompt: 'You research topics thoroughly.' },
29
- * { name: 'writer', model: 'claude-opus-4-6', systemPrompt: 'You write clear documentation.' },
30
- * ],
31
- * sharedMemory: true,
32
- * })
33
- * const result = await orchestrator.runTeam(team, 'Write a guide on TypeScript generics.')
34
- * console.log(result.agentResults.get('coordinator')?.output)
35
- * ```
36
- *
37
- * ### Custom tools
38
- * ```ts
39
- * import { z } from 'zod'
40
- *
41
- * const myTool = defineTool({
42
- * name: 'fetch_data',
43
- * description: 'Fetch JSON data from a URL.',
44
- * inputSchema: z.object({ url: z.string().url() }),
45
- * execute: async ({ url }) => {
46
- * const res = await fetch(url)
47
- * return { data: await res.text() }
48
- * },
49
- * })
50
- * ```
51
- */
52
-
53
- // ---------------------------------------------------------------------------
54
- // Orchestrator (primary entry point)
55
- // ---------------------------------------------------------------------------
56
-
57
- export { OpenMultiAgent, executeWithRetry, computeRetryDelay } from './orchestrator/orchestrator.js'
58
- export { Scheduler } from './orchestrator/scheduler.js'
59
- export type { SchedulingStrategy } from './orchestrator/scheduler.js'
60
-
61
- // ---------------------------------------------------------------------------
62
- // Agent layer
63
- // ---------------------------------------------------------------------------
64
-
65
- export { Agent } from './agent/agent.js'
66
- export { LoopDetector } from './agent/loop-detector.js'
67
- export { buildStructuredOutputInstruction, extractJSON, validateOutput } from './agent/structured-output.js'
68
- export { AgentPool, Semaphore } from './agent/pool.js'
69
- export type { PoolStatus } from './agent/pool.js'
70
-
71
- // ---------------------------------------------------------------------------
72
- // Team layer
73
- // ---------------------------------------------------------------------------
74
-
75
- export { Team } from './team/team.js'
76
- export { MessageBus } from './team/messaging.js'
77
- export type { Message } from './team/messaging.js'
78
-
79
- // ---------------------------------------------------------------------------
80
- // Task layer
81
- // ---------------------------------------------------------------------------
82
-
83
- export { TaskQueue } from './task/queue.js'
84
- export { createTask, isTaskReady, getTaskDependencyOrder, validateTaskDependencies } from './task/task.js'
85
- export type { TaskQueueEvent } from './task/queue.js'
86
-
87
- // ---------------------------------------------------------------------------
88
- // Tool system
89
- // ---------------------------------------------------------------------------
90
-
91
- export { defineTool, ToolRegistry, zodToJsonSchema } from './tool/framework.js'
92
- export { ToolExecutor } from './tool/executor.js'
93
- export type { ToolExecutorOptions, BatchToolCall } from './tool/executor.js'
94
- export {
95
- registerBuiltInTools,
96
- BUILT_IN_TOOLS,
97
- bashTool,
98
- fileReadTool,
99
- fileWriteTool,
100
- fileEditTool,
101
- grepTool,
102
- } from './tool/built-in/index.js'
103
-
104
- // ---------------------------------------------------------------------------
105
- // LLM adapters
106
- // ---------------------------------------------------------------------------
107
-
108
- export { createAdapter } from './llm/adapter.js'
109
- export type { SupportedProvider } from './llm/adapter.js'
110
-
111
- // ---------------------------------------------------------------------------
112
- // Memory
113
- // ---------------------------------------------------------------------------
114
-
115
- export { InMemoryStore } from './memory/store.js'
116
- export { SharedMemory } from './memory/shared.js'
117
-
118
- // ---------------------------------------------------------------------------
119
- // Types — all public interfaces re-exported for consumer type-checking
120
- // ---------------------------------------------------------------------------
121
-
122
- export type {
123
- // Content blocks
124
- TextBlock,
125
- ToolUseBlock,
126
- ToolResultBlock,
127
- ImageBlock,
128
- ContentBlock,
129
-
130
- // LLM
131
- LLMMessage,
132
- LLMResponse,
133
- LLMAdapter,
134
- LLMChatOptions,
135
- LLMStreamOptions,
136
- LLMToolDef,
137
- TokenUsage,
138
- StreamEvent,
139
-
140
- // Tools
141
- ToolDefinition,
142
- ToolResult,
143
- ToolUseContext,
144
- AgentInfo,
145
- TeamInfo,
146
-
147
- // Agent
148
- AgentConfig,
149
- AgentState,
150
- AgentRunResult,
151
- BeforeRunHookContext,
152
- ToolCallRecord,
153
- LoopDetectionConfig,
154
- LoopDetectionInfo,
155
-
156
- // Team
157
- TeamConfig,
158
- TeamRunResult,
159
-
160
- // Task
161
- Task,
162
- TaskStatus,
163
-
164
- // Orchestrator
165
- OrchestratorConfig,
166
- OrchestratorEvent,
167
-
168
- // Trace
169
- TraceEventType,
170
- TraceEventBase,
171
- TraceEvent,
172
- LLMCallTrace,
173
- ToolCallTrace,
174
- TaskTrace,
175
- AgentTrace,
176
-
177
- // Memory
178
- MemoryEntry,
179
- MemoryStore,
180
- } from './types.js'
181
-
182
- export { generateRunId } from './utils/trace.js'
@@ -1,98 +0,0 @@
1
- /**
2
- * @fileoverview LLM adapter factory.
3
- *
4
- * Re-exports the {@link LLMAdapter} interface and provides a
5
- * {@link createAdapter} factory that returns the correct concrete
6
- * implementation based on the requested provider.
7
- *
8
- * @example
9
- * ```ts
10
- * import { createAdapter } from './adapter.js'
11
- *
12
- * const anthropic = createAdapter('anthropic')
13
- * const openai = createAdapter('openai', process.env.OPENAI_API_KEY)
14
- * const gemini = createAdapter('gemini', process.env.GEMINI_API_KEY)
15
- * ```
16
- */
17
-
18
- export type {
19
- LLMAdapter,
20
- LLMChatOptions,
21
- LLMStreamOptions,
22
- LLMToolDef,
23
- LLMMessage,
24
- LLMResponse,
25
- StreamEvent,
26
- TokenUsage,
27
- ContentBlock,
28
- TextBlock,
29
- ToolUseBlock,
30
- ToolResultBlock,
31
- ImageBlock,
32
- } from '../types.js'
33
-
34
- import type { LLMAdapter } from '../types.js'
35
-
36
- /**
37
- * The set of LLM providers supported out of the box.
38
- * Additional providers can be integrated by implementing {@link LLMAdapter}
39
- * directly and bypassing this factory.
40
- */
41
- export type SupportedProvider = 'anthropic' | 'copilot' | 'grok' | 'openai' | 'gemini'
42
-
43
- /**
44
- * Instantiate the appropriate {@link LLMAdapter} for the given provider.
45
- *
46
- * API keys fall back to the standard environment variables when not supplied
47
- * explicitly:
48
- * - `anthropic` → `ANTHROPIC_API_KEY`
49
- * - `openai` → `OPENAI_API_KEY`
50
- * - `gemini` → `GEMINI_API_KEY` / `GOOGLE_API_KEY`
51
- * - `grok` → `XAI_API_KEY`
52
- * - `copilot` → `GITHUB_COPILOT_TOKEN` / `GITHUB_TOKEN`, or interactive
53
- * OAuth2 device flow if neither is set
54
- *
55
- * Adapters are imported lazily so that projects using only one provider
56
- * are not forced to install the SDK for the other.
57
- *
58
- * @param provider - Which LLM provider to target.
59
- * @param apiKey - Optional API key override; falls back to env var.
60
- * @param baseURL - Optional base URL for OpenAI-compatible APIs (Ollama, vLLM, etc.).
61
- * @throws {Error} When the provider string is not recognised.
62
- */
63
- export async function createAdapter(
64
- provider: SupportedProvider,
65
- apiKey?: string,
66
- baseURL?: string,
67
- ): Promise<LLMAdapter> {
68
- switch (provider) {
69
- case 'anthropic': {
70
- const { AnthropicAdapter } = await import('./anthropic.js')
71
- return new AnthropicAdapter(apiKey, baseURL)
72
- }
73
- case 'copilot': {
74
- if (baseURL) {
75
- console.warn('[open-multi-agent] baseURL is not supported for the copilot provider and will be ignored.')
76
- }
77
- const { CopilotAdapter } = await import('./copilot.js')
78
- return new CopilotAdapter(apiKey)
79
- }
80
- case 'gemini': {
81
- const { GeminiAdapter } = await import('./gemini.js')
82
- return new GeminiAdapter(apiKey)
83
- }
84
- case 'openai': {
85
- const { OpenAIAdapter } = await import('./openai.js')
86
- return new OpenAIAdapter(apiKey, baseURL)
87
- }
88
- case 'grok': {
89
- const { GrokAdapter } = await import('./grok.js')
90
- return new GrokAdapter(apiKey, baseURL)
91
- }
92
- default: {
93
- // The `never` cast here makes TypeScript enforce exhaustiveness.
94
- const _exhaustive: never = provider
95
- throw new Error(`Unsupported LLM provider: ${String(_exhaustive)}`)
96
- }
97
- }
98
- }
@@ -1,389 +0,0 @@
1
- /**
2
- * @fileoverview Anthropic Claude adapter implementing {@link LLMAdapter}.
3
- *
4
- * Converts between the framework's internal {@link ContentBlock} types and the
5
- * Anthropic SDK's wire format, handling tool definitions, system prompts, and
6
- * both batch and streaming response paths.
7
- *
8
- * API key resolution order:
9
- * 1. `apiKey` constructor argument
10
- * 2. `ANTHROPIC_API_KEY` environment variable
11
- *
12
- * @example
13
- * ```ts
14
- * import { AnthropicAdapter } from './anthropic.js'
15
- *
16
- * const adapter = new AnthropicAdapter()
17
- * const response = await adapter.chat(messages, {
18
- * model: 'claude-opus-4-6',
19
- * maxTokens: 1024,
20
- * })
21
- * ```
22
- */
23
-
24
- import Anthropic from '@anthropic-ai/sdk'
25
- import type {
26
- ContentBlockParam,
27
- ImageBlockParam,
28
- MessageParam,
29
- TextBlockParam,
30
- ToolResultBlockParam,
31
- ToolUseBlockParam,
32
- Tool as AnthropicTool,
33
- } from '@anthropic-ai/sdk/resources/messages/messages.js'
34
-
35
- import type {
36
- ContentBlock,
37
- ImageBlock,
38
- LLMAdapter,
39
- LLMChatOptions,
40
- LLMMessage,
41
- LLMResponse,
42
- LLMStreamOptions,
43
- LLMToolDef,
44
- StreamEvent,
45
- TextBlock,
46
- ToolResultBlock,
47
- ToolUseBlock,
48
- } from '../types.js'
49
-
50
- // ---------------------------------------------------------------------------
51
- // Internal helpers
52
- // ---------------------------------------------------------------------------
53
-
54
- /**
55
- * Convert a single framework {@link ContentBlock} into an Anthropic
56
- * {@link ContentBlockParam} suitable for the `messages` array.
57
- *
58
- * `tool_result` blocks are only valid inside `user`-role messages, which is
59
- * handled by {@link toAnthropicMessages} based on role context.
60
- */
61
- function toAnthropicContentBlockParam(block: ContentBlock): ContentBlockParam {
62
- switch (block.type) {
63
- case 'text': {
64
- const param: TextBlockParam = { type: 'text', text: block.text }
65
- return param
66
- }
67
- case 'tool_use': {
68
- const param: ToolUseBlockParam = {
69
- type: 'tool_use',
70
- id: block.id,
71
- name: block.name,
72
- input: block.input,
73
- }
74
- return param
75
- }
76
- case 'tool_result': {
77
- const param: ToolResultBlockParam = {
78
- type: 'tool_result',
79
- tool_use_id: block.tool_use_id,
80
- content: block.content,
81
- is_error: block.is_error,
82
- }
83
- return param
84
- }
85
- case 'image': {
86
- // Anthropic only accepts a subset of MIME types; we pass them through
87
- // trusting the caller to supply a valid media_type value.
88
- const param: ImageBlockParam = {
89
- type: 'image',
90
- source: {
91
- type: 'base64',
92
- media_type: block.source.media_type as
93
- | 'image/jpeg'
94
- | 'image/png'
95
- | 'image/gif'
96
- | 'image/webp',
97
- data: block.source.data,
98
- },
99
- }
100
- return param
101
- }
102
- default: {
103
- // Exhaustiveness guard — TypeScript will flag this at compile time if a
104
- // new variant is added to ContentBlock without updating this switch.
105
- const _exhaustive: never = block
106
- throw new Error(`Unhandled content block type: ${JSON.stringify(_exhaustive)}`)
107
- }
108
- }
109
- }
110
-
111
- /**
112
- * Convert framework messages into Anthropic's `MessageParam[]` format.
113
- *
114
- * The Anthropic API requires strict user/assistant alternation. We do not
115
- * enforce that here — the caller is responsible for producing a valid
116
- * conversation history.
117
- */
118
- function toAnthropicMessages(messages: LLMMessage[]): MessageParam[] {
119
- return messages.map((msg): MessageParam => ({
120
- role: msg.role,
121
- content: msg.content.map(toAnthropicContentBlockParam),
122
- }))
123
- }
124
-
125
- /**
126
- * Convert framework {@link LLMToolDef}s into Anthropic's `Tool` objects.
127
- *
128
- * The `inputSchema` on {@link LLMToolDef} is already a plain JSON Schema
129
- * object, so we just need to reshape the wrapper.
130
- */
131
- function toAnthropicTools(tools: readonly LLMToolDef[]): AnthropicTool[] {
132
- return tools.map((t): AnthropicTool => ({
133
- name: t.name,
134
- description: t.description,
135
- input_schema: {
136
- type: 'object',
137
- ...(t.inputSchema as Record<string, unknown>),
138
- },
139
- }))
140
- }
141
-
142
- /**
143
- * Convert an Anthropic SDK `ContentBlock` into a framework {@link ContentBlock}.
144
- *
145
- * We only map the subset of SDK types that the framework exposes. Unknown
146
- * variants (thinking, server_tool_use, etc.) are converted to a text block
147
- * carrying a stringified representation so data is never silently dropped.
148
- */
149
- function fromAnthropicContentBlock(
150
- block: Anthropic.Messages.ContentBlock,
151
- ): ContentBlock {
152
- switch (block.type) {
153
- case 'text': {
154
- const text: TextBlock = { type: 'text', text: block.text }
155
- return text
156
- }
157
- case 'tool_use': {
158
- const toolUse: ToolUseBlock = {
159
- type: 'tool_use',
160
- id: block.id,
161
- name: block.name,
162
- input: block.input as Record<string, unknown>,
163
- }
164
- return toolUse
165
- }
166
- default: {
167
- // Graceful degradation for SDK types we don't model (thinking, etc.).
168
- const fallback: TextBlock = {
169
- type: 'text',
170
- text: `[unsupported block type: ${(block as { type: string }).type}]`,
171
- }
172
- return fallback
173
- }
174
- }
175
- }
176
-
177
- // ---------------------------------------------------------------------------
178
- // Adapter implementation
179
- // ---------------------------------------------------------------------------
180
-
181
- /**
182
- * LLM adapter backed by the Anthropic Claude API.
183
- *
184
- * Thread-safe — a single instance may be shared across concurrent agent runs.
185
- * The underlying SDK client is stateless across requests.
186
- */
187
- export class AnthropicAdapter implements LLMAdapter {
188
- readonly name = 'anthropic'
189
-
190
- readonly #client: Anthropic
191
-
192
- constructor(apiKey?: string, baseURL?: string) {
193
- this.#client = new Anthropic({
194
- apiKey: apiKey ?? process.env['ANTHROPIC_API_KEY'],
195
- baseURL,
196
- })
197
- }
198
-
199
- // -------------------------------------------------------------------------
200
- // chat()
201
- // -------------------------------------------------------------------------
202
-
203
- /**
204
- * Send a synchronous (non-streaming) chat request and return the complete
205
- * {@link LLMResponse}.
206
- *
207
- * Throws an `Anthropic.APIError` on non-2xx responses. Callers should catch
208
- * and handle these (e.g. rate limits, context window exceeded).
209
- */
210
- async chat(messages: LLMMessage[], options: LLMChatOptions): Promise<LLMResponse> {
211
- const anthropicMessages = toAnthropicMessages(messages)
212
-
213
- const response = await this.#client.messages.create(
214
- {
215
- model: options.model,
216
- max_tokens: options.maxTokens ?? 4096,
217
- messages: anthropicMessages,
218
- system: options.systemPrompt,
219
- tools: options.tools ? toAnthropicTools(options.tools) : undefined,
220
- temperature: options.temperature,
221
- },
222
- {
223
- signal: options.abortSignal,
224
- },
225
- )
226
-
227
- const content = response.content.map(fromAnthropicContentBlock)
228
-
229
- return {
230
- id: response.id,
231
- content,
232
- model: response.model,
233
- stop_reason: response.stop_reason ?? 'end_turn',
234
- usage: {
235
- input_tokens: response.usage.input_tokens,
236
- output_tokens: response.usage.output_tokens,
237
- },
238
- }
239
- }
240
-
241
- // -------------------------------------------------------------------------
242
- // stream()
243
- // -------------------------------------------------------------------------
244
-
245
- /**
246
- * Send a streaming chat request and yield {@link StreamEvent}s as they
247
- * arrive from the API.
248
- *
249
- * Sequence guarantees:
250
- * - Zero or more `text` events containing incremental deltas
251
- * - Zero or more `tool_use` events when the model calls a tool (emitted once
252
- * per tool use, after input JSON has been fully assembled)
253
- * - Exactly one terminal event: `done` (with the complete {@link LLMResponse}
254
- * as `data`) or `error` (with an `Error` as `data`)
255
- */
256
- async *stream(
257
- messages: LLMMessage[],
258
- options: LLMStreamOptions,
259
- ): AsyncIterable<StreamEvent> {
260
- const anthropicMessages = toAnthropicMessages(messages)
261
-
262
- // MessageStream gives us typed events and handles SSE reconnect internally.
263
- const stream = this.#client.messages.stream(
264
- {
265
- model: options.model,
266
- max_tokens: options.maxTokens ?? 4096,
267
- messages: anthropicMessages,
268
- system: options.systemPrompt,
269
- tools: options.tools ? toAnthropicTools(options.tools) : undefined,
270
- temperature: options.temperature,
271
- },
272
- {
273
- signal: options.abortSignal,
274
- },
275
- )
276
-
277
- // Accumulate tool-use input JSON as it streams in.
278
- // key = content block index, value = partially assembled input JSON string
279
- const toolInputBuffers = new Map<number, { id: string; name: string; json: string }>()
280
-
281
- try {
282
- for await (const event of stream) {
283
- switch (event.type) {
284
- case 'content_block_start': {
285
- const block = event.content_block
286
- if (block.type === 'tool_use') {
287
- toolInputBuffers.set(event.index, {
288
- id: block.id,
289
- name: block.name,
290
- json: '',
291
- })
292
- }
293
- break
294
- }
295
-
296
- case 'content_block_delta': {
297
- const delta = event.delta
298
-
299
- if (delta.type === 'text_delta') {
300
- const textEvent: StreamEvent = { type: 'text', data: delta.text }
301
- yield textEvent
302
- } else if (delta.type === 'input_json_delta') {
303
- const buf = toolInputBuffers.get(event.index)
304
- if (buf !== undefined) {
305
- buf.json += delta.partial_json
306
- }
307
- }
308
- break
309
- }
310
-
311
- case 'content_block_stop': {
312
- const buf = toolInputBuffers.get(event.index)
313
- if (buf !== undefined) {
314
- // Parse the accumulated JSON and emit a tool_use event.
315
- let parsedInput: Record<string, unknown> = {}
316
- try {
317
- const parsed: unknown = JSON.parse(buf.json)
318
- if (
319
- parsed !== null &&
320
- typeof parsed === 'object' &&
321
- !Array.isArray(parsed)
322
- ) {
323
- parsedInput = parsed as Record<string, unknown>
324
- }
325
- } catch {
326
- // Malformed JSON from the model — surface as an empty object
327
- // rather than crashing the stream.
328
- }
329
-
330
- const toolUseBlock: ToolUseBlock = {
331
- type: 'tool_use',
332
- id: buf.id,
333
- name: buf.name,
334
- input: parsedInput,
335
- }
336
- const toolUseEvent: StreamEvent = { type: 'tool_use', data: toolUseBlock }
337
- yield toolUseEvent
338
- toolInputBuffers.delete(event.index)
339
- }
340
- break
341
- }
342
-
343
- // message_start, message_delta, message_stop — we handle the final
344
- // response via stream.finalMessage() below rather than piecemeal.
345
- default:
346
- break
347
- }
348
- }
349
-
350
- // Await the fully assembled final message (token counts, stop_reason, etc.)
351
- const finalMessage = await stream.finalMessage()
352
- const content = finalMessage.content.map(fromAnthropicContentBlock)
353
-
354
- const finalResponse: LLMResponse = {
355
- id: finalMessage.id,
356
- content,
357
- model: finalMessage.model,
358
- stop_reason: finalMessage.stop_reason ?? 'end_turn',
359
- usage: {
360
- input_tokens: finalMessage.usage.input_tokens,
361
- output_tokens: finalMessage.usage.output_tokens,
362
- },
363
- }
364
-
365
- const doneEvent: StreamEvent = { type: 'done', data: finalResponse }
366
- yield doneEvent
367
- } catch (err) {
368
- const error = err instanceof Error ? err : new Error(String(err))
369
- const errorEvent: StreamEvent = { type: 'error', data: error }
370
- yield errorEvent
371
- }
372
- }
373
- }
374
-
375
- // Re-export types that consumers of this module commonly need alongside the adapter.
376
- export type {
377
- ContentBlock,
378
- ImageBlock,
379
- LLMAdapter,
380
- LLMChatOptions,
381
- LLMMessage,
382
- LLMResponse,
383
- LLMStreamOptions,
384
- LLMToolDef,
385
- StreamEvent,
386
- TextBlock,
387
- ToolResultBlock,
388
- ToolUseBlock,
389
- }