@jackchen_me/open-multi-agent 0.1.0 → 0.2.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.
Files changed (84) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
  3. package/.github/pull_request_template.md +14 -0
  4. package/.github/workflows/ci.yml +23 -0
  5. package/CLAUDE.md +72 -0
  6. package/CODE_OF_CONDUCT.md +48 -0
  7. package/CONTRIBUTING.md +72 -0
  8. package/DECISIONS.md +43 -0
  9. package/README.md +73 -140
  10. package/README_zh.md +217 -0
  11. package/SECURITY.md +17 -0
  12. package/dist/agent/agent.d.ts +5 -0
  13. package/dist/agent/agent.d.ts.map +1 -1
  14. package/dist/agent/agent.js +90 -3
  15. package/dist/agent/agent.js.map +1 -1
  16. package/dist/agent/structured-output.d.ts +33 -0
  17. package/dist/agent/structured-output.d.ts.map +1 -0
  18. package/dist/agent/structured-output.js +116 -0
  19. package/dist/agent/structured-output.js.map +1 -0
  20. package/dist/index.d.ts +2 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/llm/adapter.d.ts +9 -4
  25. package/dist/llm/adapter.d.ts.map +1 -1
  26. package/dist/llm/adapter.js +17 -5
  27. package/dist/llm/adapter.js.map +1 -1
  28. package/dist/llm/anthropic.d.ts +1 -1
  29. package/dist/llm/anthropic.d.ts.map +1 -1
  30. package/dist/llm/anthropic.js +2 -1
  31. package/dist/llm/anthropic.js.map +1 -1
  32. package/dist/llm/copilot.d.ts +92 -0
  33. package/dist/llm/copilot.d.ts.map +1 -0
  34. package/dist/llm/copilot.js +426 -0
  35. package/dist/llm/copilot.js.map +1 -0
  36. package/dist/llm/openai-common.d.ts +47 -0
  37. package/dist/llm/openai-common.d.ts.map +1 -0
  38. package/dist/llm/openai-common.js +209 -0
  39. package/dist/llm/openai-common.js.map +1 -0
  40. package/dist/llm/openai.d.ts +1 -1
  41. package/dist/llm/openai.d.ts.map +1 -1
  42. package/dist/llm/openai.js +3 -224
  43. package/dist/llm/openai.js.map +1 -1
  44. package/dist/orchestrator/orchestrator.d.ts +25 -1
  45. package/dist/orchestrator/orchestrator.d.ts.map +1 -1
  46. package/dist/orchestrator/orchestrator.js +130 -37
  47. package/dist/orchestrator/orchestrator.js.map +1 -1
  48. package/dist/task/queue.js +1 -1
  49. package/dist/task/queue.js.map +1 -1
  50. package/dist/task/task.d.ts +3 -0
  51. package/dist/task/task.d.ts.map +1 -1
  52. package/dist/task/task.js +5 -1
  53. package/dist/task/task.js.map +1 -1
  54. package/dist/team/messaging.d.ts.map +1 -1
  55. package/dist/team/messaging.js +2 -1
  56. package/dist/team/messaging.js.map +1 -1
  57. package/dist/types.d.ts +31 -3
  58. package/dist/types.d.ts.map +1 -1
  59. package/examples/05-copilot-test.ts +49 -0
  60. package/examples/06-local-model.ts +199 -0
  61. package/examples/07-fan-out-aggregate.ts +209 -0
  62. package/examples/08-gemma4-local.ts +203 -0
  63. package/examples/09-gemma4-auto-orchestration.ts +162 -0
  64. package/package.json +4 -3
  65. package/src/agent/agent.ts +115 -6
  66. package/src/agent/structured-output.ts +126 -0
  67. package/src/index.ts +2 -1
  68. package/src/llm/adapter.ts +18 -5
  69. package/src/llm/anthropic.ts +2 -1
  70. package/src/llm/copilot.ts +551 -0
  71. package/src/llm/openai-common.ts +255 -0
  72. package/src/llm/openai.ts +8 -258
  73. package/src/orchestrator/orchestrator.ts +164 -38
  74. package/src/task/queue.ts +1 -1
  75. package/src/task/task.ts +8 -1
  76. package/src/team/messaging.ts +3 -1
  77. package/src/types.ts +31 -2
  78. package/tests/semaphore.test.ts +57 -0
  79. package/tests/shared-memory.test.ts +122 -0
  80. package/tests/structured-output.test.ts +331 -0
  81. package/tests/task-queue.test.ts +244 -0
  82. package/tests/task-retry.test.ts +368 -0
  83. package/tests/task-utils.test.ts +155 -0
  84. package/tests/tool-executor.test.ts +193 -0
@@ -0,0 +1,255 @@
1
+ /**
2
+ * @fileoverview Shared OpenAI wire-format conversion helpers.
3
+ *
4
+ * Both the OpenAI and Copilot adapters use the OpenAI Chat Completions API
5
+ * format. This module contains the common conversion logic so it isn't
6
+ * duplicated across adapters.
7
+ */
8
+
9
+ import OpenAI from 'openai'
10
+ import type {
11
+ ChatCompletion,
12
+ ChatCompletionAssistantMessageParam,
13
+ ChatCompletionMessageParam,
14
+ ChatCompletionMessageToolCall,
15
+ ChatCompletionTool,
16
+ ChatCompletionToolMessageParam,
17
+ ChatCompletionUserMessageParam,
18
+ } from 'openai/resources/chat/completions/index.js'
19
+
20
+ import type {
21
+ ContentBlock,
22
+ LLMMessage,
23
+ LLMResponse,
24
+ LLMToolDef,
25
+ TextBlock,
26
+ ToolUseBlock,
27
+ } from '../types.js'
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Framework → OpenAI
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /**
34
+ * Convert a framework {@link LLMToolDef} to an OpenAI {@link ChatCompletionTool}.
35
+ */
36
+ export function toOpenAITool(tool: LLMToolDef): ChatCompletionTool {
37
+ return {
38
+ type: 'function',
39
+ function: {
40
+ name: tool.name,
41
+ description: tool.description,
42
+ parameters: tool.inputSchema as Record<string, unknown>,
43
+ },
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Determine whether a framework message contains any `tool_result` content
49
+ * blocks, which must be serialised as separate OpenAI `tool`-role messages.
50
+ */
51
+ function hasToolResults(msg: LLMMessage): boolean {
52
+ return msg.content.some((b) => b.type === 'tool_result')
53
+ }
54
+
55
+ /**
56
+ * Convert framework {@link LLMMessage}s into OpenAI
57
+ * {@link ChatCompletionMessageParam} entries.
58
+ *
59
+ * `tool_result` blocks are expanded into top-level `tool`-role messages
60
+ * because OpenAI uses a dedicated role for tool results rather than embedding
61
+ * them inside user-content arrays.
62
+ */
63
+ export function toOpenAIMessages(messages: LLMMessage[]): ChatCompletionMessageParam[] {
64
+ const result: ChatCompletionMessageParam[] = []
65
+
66
+ for (const msg of messages) {
67
+ if (msg.role === 'assistant') {
68
+ result.push(toOpenAIAssistantMessage(msg))
69
+ } else {
70
+ // user role
71
+ if (!hasToolResults(msg)) {
72
+ result.push(toOpenAIUserMessage(msg))
73
+ } else {
74
+ const nonToolBlocks = msg.content.filter((b) => b.type !== 'tool_result')
75
+ if (nonToolBlocks.length > 0) {
76
+ result.push(toOpenAIUserMessage({ role: 'user', content: nonToolBlocks }))
77
+ }
78
+
79
+ for (const block of msg.content) {
80
+ if (block.type === 'tool_result') {
81
+ const toolMsg: ChatCompletionToolMessageParam = {
82
+ role: 'tool',
83
+ tool_call_id: block.tool_use_id,
84
+ content: block.content,
85
+ }
86
+ result.push(toolMsg)
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ return result
94
+ }
95
+
96
+ /**
97
+ * Convert a `user`-role framework message into an OpenAI user message.
98
+ * Image blocks are converted to the OpenAI image_url content part format.
99
+ */
100
+ function toOpenAIUserMessage(msg: LLMMessage): ChatCompletionUserMessageParam {
101
+ if (msg.content.length === 1 && msg.content[0]?.type === 'text') {
102
+ return { role: 'user', content: msg.content[0].text }
103
+ }
104
+
105
+ type ContentPart = OpenAI.Chat.ChatCompletionContentPartText | OpenAI.Chat.ChatCompletionContentPartImage
106
+ const parts: ContentPart[] = []
107
+
108
+ for (const block of msg.content) {
109
+ if (block.type === 'text') {
110
+ parts.push({ type: 'text', text: block.text })
111
+ } else if (block.type === 'image') {
112
+ parts.push({
113
+ type: 'image_url',
114
+ image_url: {
115
+ url: `data:${block.source.media_type};base64,${block.source.data}`,
116
+ },
117
+ })
118
+ }
119
+ // tool_result blocks are handled by the caller (toOpenAIMessages); skip here.
120
+ }
121
+
122
+ return { role: 'user', content: parts }
123
+ }
124
+
125
+ /**
126
+ * Convert an `assistant`-role framework message into an OpenAI assistant message.
127
+ * `tool_use` blocks become `tool_calls`; `text` blocks become message content.
128
+ */
129
+ function toOpenAIAssistantMessage(msg: LLMMessage): ChatCompletionAssistantMessageParam {
130
+ const toolCalls: ChatCompletionMessageToolCall[] = []
131
+ const textParts: string[] = []
132
+
133
+ for (const block of msg.content) {
134
+ if (block.type === 'tool_use') {
135
+ toolCalls.push({
136
+ id: block.id,
137
+ type: 'function',
138
+ function: {
139
+ name: block.name,
140
+ arguments: JSON.stringify(block.input),
141
+ },
142
+ })
143
+ } else if (block.type === 'text') {
144
+ textParts.push(block.text)
145
+ }
146
+ }
147
+
148
+ const assistantMsg: ChatCompletionAssistantMessageParam = {
149
+ role: 'assistant',
150
+ content: textParts.length > 0 ? textParts.join('') : null,
151
+ }
152
+
153
+ if (toolCalls.length > 0) {
154
+ assistantMsg.tool_calls = toolCalls
155
+ }
156
+
157
+ return assistantMsg
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // OpenAI → Framework
162
+ // ---------------------------------------------------------------------------
163
+
164
+ /**
165
+ * Convert an OpenAI {@link ChatCompletion} into a framework {@link LLMResponse}.
166
+ *
167
+ * Takes only the first choice (index 0), consistent with how the framework
168
+ * is designed for single-output agents.
169
+ */
170
+ export function fromOpenAICompletion(completion: ChatCompletion): LLMResponse {
171
+ const choice = completion.choices[0]
172
+ if (choice === undefined) {
173
+ throw new Error('OpenAI returned a completion with no choices')
174
+ }
175
+
176
+ const content: ContentBlock[] = []
177
+ const message = choice.message
178
+
179
+ if (message.content !== null && message.content !== undefined) {
180
+ const textBlock: TextBlock = { type: 'text', text: message.content }
181
+ content.push(textBlock)
182
+ }
183
+
184
+ for (const toolCall of message.tool_calls ?? []) {
185
+ let parsedInput: Record<string, unknown> = {}
186
+ try {
187
+ const parsed: unknown = JSON.parse(toolCall.function.arguments)
188
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
189
+ parsedInput = parsed as Record<string, unknown>
190
+ }
191
+ } catch {
192
+ // Malformed arguments from the model — surface as empty object.
193
+ }
194
+
195
+ const toolUseBlock: ToolUseBlock = {
196
+ type: 'tool_use',
197
+ id: toolCall.id,
198
+ name: toolCall.function.name,
199
+ input: parsedInput,
200
+ }
201
+ content.push(toolUseBlock)
202
+ }
203
+
204
+ const stopReason = normalizeFinishReason(choice.finish_reason ?? 'stop')
205
+
206
+ return {
207
+ id: completion.id,
208
+ content,
209
+ model: completion.model,
210
+ stop_reason: stopReason,
211
+ usage: {
212
+ input_tokens: completion.usage?.prompt_tokens ?? 0,
213
+ output_tokens: completion.usage?.completion_tokens ?? 0,
214
+ },
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Normalize an OpenAI `finish_reason` string to the framework's canonical
220
+ * stop-reason vocabulary.
221
+ *
222
+ * Mapping:
223
+ * - `'stop'` → `'end_turn'`
224
+ * - `'tool_calls'` → `'tool_use'`
225
+ * - `'length'` → `'max_tokens'`
226
+ * - `'content_filter'` → `'content_filter'`
227
+ * - anything else → passed through unchanged
228
+ */
229
+ export function normalizeFinishReason(reason: string): string {
230
+ switch (reason) {
231
+ case 'stop': return 'end_turn'
232
+ case 'tool_calls': return 'tool_use'
233
+ case 'length': return 'max_tokens'
234
+ case 'content_filter': return 'content_filter'
235
+ default: return reason
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Prepend a system message when `systemPrompt` is provided, then append the
241
+ * converted conversation messages.
242
+ */
243
+ export function buildOpenAIMessageList(
244
+ messages: LLMMessage[],
245
+ systemPrompt: string | undefined,
246
+ ): ChatCompletionMessageParam[] {
247
+ const result: ChatCompletionMessageParam[] = []
248
+
249
+ if (systemPrompt !== undefined && systemPrompt.length > 0) {
250
+ result.push({ role: 'system', content: systemPrompt })
251
+ }
252
+
253
+ result.push(...toOpenAIMessages(messages))
254
+ return result
255
+ }
package/src/llm/openai.ts CHANGED
@@ -32,14 +32,7 @@
32
32
 
33
33
  import OpenAI from 'openai'
34
34
  import type {
35
- ChatCompletion,
36
- ChatCompletionAssistantMessageParam,
37
35
  ChatCompletionChunk,
38
- ChatCompletionMessageParam,
39
- ChatCompletionMessageToolCall,
40
- ChatCompletionTool,
41
- ChatCompletionToolMessageParam,
42
- ChatCompletionUserMessageParam,
43
36
  } from 'openai/resources/chat/completions/index.js'
44
37
 
45
38
  import type {
@@ -55,231 +48,12 @@ import type {
55
48
  ToolUseBlock,
56
49
  } from '../types.js'
57
50
 
58
- // ---------------------------------------------------------------------------
59
- // Internal helpers — framework → OpenAI
60
- // ---------------------------------------------------------------------------
61
-
62
- /**
63
- * Convert a framework {@link LLMToolDef} to an OpenAI {@link ChatCompletionTool}.
64
- *
65
- * OpenAI wraps the function definition inside a `function` key and a `type`
66
- * discriminant. The `inputSchema` is already a JSON Schema object.
67
- */
68
- function toOpenAITool(tool: LLMToolDef): ChatCompletionTool {
69
- return {
70
- type: 'function',
71
- function: {
72
- name: tool.name,
73
- description: tool.description,
74
- parameters: tool.inputSchema as Record<string, unknown>,
75
- },
76
- }
77
- }
78
-
79
- /**
80
- * Determine whether a framework message contains any `tool_result` content
81
- * blocks, which must be serialised as separate OpenAI `tool`-role messages.
82
- */
83
- function hasToolResults(msg: LLMMessage): boolean {
84
- return msg.content.some((b) => b.type === 'tool_result')
85
- }
86
-
87
- /**
88
- * Convert a single framework {@link LLMMessage} into one or more OpenAI
89
- * {@link ChatCompletionMessageParam} entries.
90
- *
91
- * The expansion is necessary because OpenAI represents tool results as
92
- * top-level messages with role `tool`, whereas in our model they are content
93
- * blocks inside a `user` message.
94
- *
95
- * Expansion rules:
96
- * - A `user` message containing only text/image blocks → single user message
97
- * - A `user` message containing `tool_result` blocks → one `tool` message per
98
- * tool_result block; any remaining text/image blocks are folded into an
99
- * additional user message prepended to the group
100
- * - An `assistant` message → single assistant message with optional tool_calls
101
- */
102
- function toOpenAIMessages(messages: LLMMessage[]): ChatCompletionMessageParam[] {
103
- const result: ChatCompletionMessageParam[] = []
104
-
105
- for (const msg of messages) {
106
- if (msg.role === 'assistant') {
107
- result.push(toOpenAIAssistantMessage(msg))
108
- } else {
109
- // user role
110
- if (!hasToolResults(msg)) {
111
- result.push(toOpenAIUserMessage(msg))
112
- } else {
113
- // Split: text/image blocks become a user message (if any exist), then
114
- // each tool_result block becomes an independent tool message.
115
- const nonToolBlocks = msg.content.filter((b) => b.type !== 'tool_result')
116
- if (nonToolBlocks.length > 0) {
117
- result.push(toOpenAIUserMessage({ role: 'user', content: nonToolBlocks }))
118
- }
119
-
120
- for (const block of msg.content) {
121
- if (block.type === 'tool_result') {
122
- const toolMsg: ChatCompletionToolMessageParam = {
123
- role: 'tool',
124
- tool_call_id: block.tool_use_id,
125
- content: block.content,
126
- }
127
- result.push(toolMsg)
128
- }
129
- }
130
- }
131
- }
132
- }
133
-
134
- return result
135
- }
136
-
137
- /**
138
- * Convert a `user`-role framework message into an OpenAI user message.
139
- * Image blocks are converted to the OpenAI image_url content part format.
140
- */
141
- function toOpenAIUserMessage(msg: LLMMessage): ChatCompletionUserMessageParam {
142
- // If the entire content is a single text block, use the compact string form
143
- // to keep the request payload smaller.
144
- if (msg.content.length === 1 && msg.content[0]?.type === 'text') {
145
- return { role: 'user', content: msg.content[0].text }
146
- }
147
-
148
- type ContentPart = OpenAI.Chat.ChatCompletionContentPartText | OpenAI.Chat.ChatCompletionContentPartImage
149
- const parts: ContentPart[] = []
150
-
151
- for (const block of msg.content) {
152
- if (block.type === 'text') {
153
- parts.push({ type: 'text', text: block.text })
154
- } else if (block.type === 'image') {
155
- parts.push({
156
- type: 'image_url',
157
- image_url: {
158
- url: `data:${block.source.media_type};base64,${block.source.data}`,
159
- },
160
- })
161
- }
162
- // tool_result blocks are handled by the caller (toOpenAIMessages); skip here.
163
- }
164
-
165
- return { role: 'user', content: parts }
166
- }
167
-
168
- /**
169
- * Convert an `assistant`-role framework message into an OpenAI assistant message.
170
- *
171
- * Any `tool_use` blocks become `tool_calls`; `text` blocks become the message content.
172
- */
173
- function toOpenAIAssistantMessage(msg: LLMMessage): ChatCompletionAssistantMessageParam {
174
- const toolCalls: ChatCompletionMessageToolCall[] = []
175
- const textParts: string[] = []
176
-
177
- for (const block of msg.content) {
178
- if (block.type === 'tool_use') {
179
- toolCalls.push({
180
- id: block.id,
181
- type: 'function',
182
- function: {
183
- name: block.name,
184
- arguments: JSON.stringify(block.input),
185
- },
186
- })
187
- } else if (block.type === 'text') {
188
- textParts.push(block.text)
189
- }
190
- }
191
-
192
- const assistantMsg: ChatCompletionAssistantMessageParam = {
193
- role: 'assistant',
194
- content: textParts.length > 0 ? textParts.join('') : null,
195
- }
196
-
197
- if (toolCalls.length > 0) {
198
- assistantMsg.tool_calls = toolCalls
199
- }
200
-
201
- return assistantMsg
202
- }
203
-
204
- // ---------------------------------------------------------------------------
205
- // Internal helpers — OpenAI → framework
206
- // ---------------------------------------------------------------------------
207
-
208
- /**
209
- * Convert an OpenAI {@link ChatCompletion} into a framework {@link LLMResponse}.
210
- *
211
- * We take only the first choice (index 0), consistent with how the framework
212
- * is designed for single-output agents.
213
- */
214
- function fromOpenAICompletion(completion: ChatCompletion): LLMResponse {
215
- const choice = completion.choices[0]
216
- if (choice === undefined) {
217
- throw new Error('OpenAI returned a completion with no choices')
218
- }
219
-
220
- const content: ContentBlock[] = []
221
- const message = choice.message
222
-
223
- if (message.content !== null && message.content !== undefined) {
224
- const textBlock: TextBlock = { type: 'text', text: message.content }
225
- content.push(textBlock)
226
- }
227
-
228
- for (const toolCall of message.tool_calls ?? []) {
229
- let parsedInput: Record<string, unknown> = {}
230
- try {
231
- const parsed: unknown = JSON.parse(toolCall.function.arguments)
232
- if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
233
- parsedInput = parsed as Record<string, unknown>
234
- }
235
- } catch {
236
- // Malformed arguments from the model — surface as empty object.
237
- }
238
-
239
- const toolUseBlock: ToolUseBlock = {
240
- type: 'tool_use',
241
- id: toolCall.id,
242
- name: toolCall.function.name,
243
- input: parsedInput,
244
- }
245
- content.push(toolUseBlock)
246
- }
247
-
248
- const stopReason = normalizeFinishReason(choice.finish_reason ?? 'stop')
249
-
250
- return {
251
- id: completion.id,
252
- content,
253
- model: completion.model,
254
- stop_reason: stopReason,
255
- usage: {
256
- input_tokens: completion.usage?.prompt_tokens ?? 0,
257
- output_tokens: completion.usage?.completion_tokens ?? 0,
258
- },
259
- }
260
- }
261
-
262
- /**
263
- * Normalize an OpenAI `finish_reason` string to the framework's canonical
264
- * stop-reason vocabulary so consumers never need to branch on provider-specific
265
- * strings.
266
- *
267
- * Mapping:
268
- * - `'stop'` → `'end_turn'`
269
- * - `'tool_calls'` → `'tool_use'`
270
- * - `'length'` → `'max_tokens'`
271
- * - `'content_filter'` → `'content_filter'`
272
- * - anything else → passed through unchanged
273
- */
274
- function normalizeFinishReason(reason: string): string {
275
- switch (reason) {
276
- case 'stop': return 'end_turn'
277
- case 'tool_calls': return 'tool_use'
278
- case 'length': return 'max_tokens'
279
- case 'content_filter': return 'content_filter'
280
- default: return reason
281
- }
282
- }
51
+ import {
52
+ toOpenAITool,
53
+ fromOpenAICompletion,
54
+ normalizeFinishReason,
55
+ buildOpenAIMessageList,
56
+ } from './openai-common.js'
283
57
 
284
58
  // ---------------------------------------------------------------------------
285
59
  // Adapter implementation
@@ -295,9 +69,10 @@ export class OpenAIAdapter implements LLMAdapter {
295
69
 
296
70
  readonly #client: OpenAI
297
71
 
298
- constructor(apiKey?: string) {
72
+ constructor(apiKey?: string, baseURL?: string) {
299
73
  this.#client = new OpenAI({
300
74
  apiKey: apiKey ?? process.env['OPENAI_API_KEY'],
75
+ baseURL,
301
76
  })
302
77
  }
303
78
 
@@ -484,31 +259,6 @@ export class OpenAIAdapter implements LLMAdapter {
484
259
  }
485
260
  }
486
261
 
487
- // ---------------------------------------------------------------------------
488
- // Private utility
489
- // ---------------------------------------------------------------------------
490
-
491
- /**
492
- * Prepend a system message when `systemPrompt` is provided, then append the
493
- * converted conversation messages.
494
- *
495
- * OpenAI represents system instructions as a message with `role: 'system'`
496
- * at the top of the array, not as a separate API parameter.
497
- */
498
- function buildOpenAIMessageList(
499
- messages: LLMMessage[],
500
- systemPrompt: string | undefined,
501
- ): ChatCompletionMessageParam[] {
502
- const result: ChatCompletionMessageParam[] = []
503
-
504
- if (systemPrompt !== undefined && systemPrompt.length > 0) {
505
- result.push({ role: 'system', content: systemPrompt })
506
- }
507
-
508
- result.push(...toOpenAIMessages(messages))
509
- return result
510
- }
511
-
512
262
  // Re-export types that consumers of this module commonly need alongside the adapter.
513
263
  export type {
514
264
  ContentBlock,