@jackchen_me/open-multi-agent 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +1 -1
- package/CLAUDE.md +11 -3
- package/README.md +87 -20
- package/README_zh.md +85 -25
- package/dist/agent/agent.d.ts +15 -1
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +144 -10
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/loop-detector.d.ts +39 -0
- package/dist/agent/loop-detector.d.ts.map +1 -0
- package/dist/agent/loop-detector.js +122 -0
- package/dist/agent/loop-detector.js.map +1 -0
- package/dist/agent/pool.d.ts +2 -1
- package/dist/agent/pool.d.ts.map +1 -1
- package/dist/agent/pool.js +4 -2
- package/dist/agent/pool.js.map +1 -1
- package/dist/agent/runner.d.ts +23 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +113 -12
- package/dist/agent/runner.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/adapter.d.ts +4 -1
- package/dist/llm/adapter.d.ts.map +1 -1
- package/dist/llm/adapter.js +11 -0
- package/dist/llm/adapter.js.map +1 -1
- package/dist/llm/copilot.d.ts.map +1 -1
- package/dist/llm/copilot.js +2 -1
- package/dist/llm/copilot.js.map +1 -1
- package/dist/llm/gemini.d.ts +65 -0
- package/dist/llm/gemini.d.ts.map +1 -0
- package/dist/llm/gemini.js +317 -0
- package/dist/llm/gemini.js.map +1 -0
- package/dist/llm/grok.d.ts +21 -0
- package/dist/llm/grok.d.ts.map +1 -0
- package/dist/llm/grok.js +24 -0
- package/dist/llm/grok.js.map +1 -0
- package/dist/llm/openai-common.d.ts +8 -1
- package/dist/llm/openai-common.d.ts.map +1 -1
- package/dist/llm/openai-common.js +35 -2
- package/dist/llm/openai-common.js.map +1 -1
- package/dist/llm/openai.d.ts +1 -1
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +20 -2
- package/dist/llm/openai.js.map +1 -1
- package/dist/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.js +89 -9
- package/dist/orchestrator/orchestrator.js.map +1 -1
- package/dist/task/queue.d.ts +31 -2
- package/dist/task/queue.d.ts.map +1 -1
- package/dist/task/queue.js +69 -2
- package/dist/task/queue.js.map +1 -1
- package/dist/tool/text-tool-extractor.d.ts +32 -0
- package/dist/tool/text-tool-extractor.d.ts.map +1 -0
- package/dist/tool/text-tool-extractor.js +187 -0
- package/dist/tool/text-tool-extractor.js.map +1 -0
- package/dist/types.d.ts +139 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/trace.d.ts +12 -0
- package/dist/utils/trace.d.ts.map +1 -0
- package/dist/utils/trace.js +30 -0
- package/dist/utils/trace.js.map +1 -0
- package/examples/06-local-model.ts +1 -0
- package/examples/08-gemma4-local.ts +76 -87
- package/examples/09-structured-output.ts +73 -0
- package/examples/10-task-retry.ts +132 -0
- package/examples/11-trace-observability.ts +133 -0
- package/examples/12-grok.ts +154 -0
- package/examples/13-gemini.ts +48 -0
- package/package.json +11 -1
- package/src/agent/agent.ts +159 -10
- package/src/agent/loop-detector.ts +137 -0
- package/src/agent/pool.ts +9 -2
- package/src/agent/runner.ts +148 -19
- package/src/index.ts +15 -0
- package/src/llm/adapter.ts +12 -1
- package/src/llm/copilot.ts +2 -1
- package/src/llm/gemini.ts +378 -0
- package/src/llm/grok.ts +29 -0
- package/src/llm/openai-common.ts +41 -2
- package/src/llm/openai.ts +23 -3
- package/src/orchestrator/orchestrator.ts +105 -11
- package/src/task/queue.ts +73 -3
- package/src/tool/text-tool-extractor.ts +219 -0
- package/src/types.ts +157 -6
- package/src/utils/trace.ts +34 -0
- package/tests/agent-hooks.test.ts +473 -0
- package/tests/agent-pool.test.ts +212 -0
- package/tests/approval.test.ts +464 -0
- package/tests/built-in-tools.test.ts +393 -0
- package/tests/gemini-adapter.test.ts +97 -0
- package/tests/grok-adapter.test.ts +74 -0
- package/tests/llm-adapters.test.ts +357 -0
- package/tests/loop-detection.test.ts +456 -0
- package/tests/openai-fallback.test.ts +159 -0
- package/tests/orchestrator.test.ts +281 -0
- package/tests/scheduler.test.ts +221 -0
- package/tests/team-messaging.test.ts +329 -0
- package/tests/text-tool-extractor.test.ts +170 -0
- package/tests/trace.test.ts +453 -0
- package/vitest.config.ts +9 -0
- package/examples/09-gemma4-auto-orchestration.ts +0 -162
package/src/agent/runner.ts
CHANGED
|
@@ -25,7 +25,12 @@ import type {
|
|
|
25
25
|
ToolUseContext,
|
|
26
26
|
LLMAdapter,
|
|
27
27
|
LLMChatOptions,
|
|
28
|
+
TraceEvent,
|
|
29
|
+
LoopDetectionConfig,
|
|
30
|
+
LoopDetectionInfo,
|
|
28
31
|
} from '../types.js'
|
|
32
|
+
import { LoopDetector } from './loop-detector.js'
|
|
33
|
+
import { emitTrace } from '../utils/trace.js'
|
|
29
34
|
import type { ToolRegistry } from '../tool/framework.js'
|
|
30
35
|
import type { ToolExecutor } from '../tool/executor.js'
|
|
31
36
|
|
|
@@ -63,6 +68,8 @@ export interface RunnerOptions {
|
|
|
63
68
|
readonly agentName?: string
|
|
64
69
|
/** Short role description of the agent (used in tool context). */
|
|
65
70
|
readonly agentRole?: string
|
|
71
|
+
/** Loop detection configuration. When set, detects stuck agent loops. */
|
|
72
|
+
readonly loopDetection?: LoopDetectionConfig
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
/**
|
|
@@ -76,6 +83,24 @@ export interface RunOptions {
|
|
|
76
83
|
readonly onToolResult?: (name: string, result: ToolResult) => void
|
|
77
84
|
/** Fired after each complete {@link LLMMessage} is appended. */
|
|
78
85
|
readonly onMessage?: (message: LLMMessage) => void
|
|
86
|
+
/**
|
|
87
|
+
* Fired when the runner detects a potential configuration issue.
|
|
88
|
+
* For example, when a model appears to ignore tool definitions.
|
|
89
|
+
*/
|
|
90
|
+
readonly onWarning?: (message: string) => void
|
|
91
|
+
/** Trace callback for observability spans. Async callbacks are safe. */
|
|
92
|
+
readonly onTrace?: (event: TraceEvent) => void | Promise<void>
|
|
93
|
+
/** Run ID for trace correlation. */
|
|
94
|
+
readonly runId?: string
|
|
95
|
+
/** Task ID for trace correlation. */
|
|
96
|
+
readonly taskId?: string
|
|
97
|
+
/** Agent name for trace correlation (overrides RunnerOptions.agentName). */
|
|
98
|
+
readonly traceAgent?: string
|
|
99
|
+
/**
|
|
100
|
+
* Per-call abort signal. When set, takes precedence over the static
|
|
101
|
+
* {@link RunnerOptions.abortSignal}. Useful for per-run timeouts.
|
|
102
|
+
*/
|
|
103
|
+
readonly abortSignal?: AbortSignal
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
/** The aggregated result returned when a full run completes. */
|
|
@@ -90,6 +115,8 @@ export interface RunResult {
|
|
|
90
115
|
readonly tokenUsage: TokenUsage
|
|
91
116
|
/** Total number of LLM turns (including tool-call follow-ups). */
|
|
92
117
|
readonly turns: number
|
|
118
|
+
/** True when the run was terminated or warned due to loop detection. */
|
|
119
|
+
readonly loopDetected?: boolean
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
// ---------------------------------------------------------------------------
|
|
@@ -166,13 +193,7 @@ export class AgentRunner {
|
|
|
166
193
|
options: RunOptions = {},
|
|
167
194
|
): Promise<RunResult> {
|
|
168
195
|
// Collect everything yielded by the internal streaming loop.
|
|
169
|
-
const accumulated: {
|
|
170
|
-
messages: LLMMessage[]
|
|
171
|
-
output: string
|
|
172
|
-
toolCalls: ToolCallRecord[]
|
|
173
|
-
tokenUsage: TokenUsage
|
|
174
|
-
turns: number
|
|
175
|
-
} = {
|
|
196
|
+
const accumulated: RunResult = {
|
|
176
197
|
messages: [],
|
|
177
198
|
output: '',
|
|
178
199
|
toolCalls: [],
|
|
@@ -182,12 +203,7 @@ export class AgentRunner {
|
|
|
182
203
|
|
|
183
204
|
for await (const event of this.stream(messages, options)) {
|
|
184
205
|
if (event.type === 'done') {
|
|
185
|
-
|
|
186
|
-
accumulated.messages = result.messages
|
|
187
|
-
accumulated.output = result.output
|
|
188
|
-
accumulated.toolCalls = result.toolCalls
|
|
189
|
-
accumulated.tokenUsage = result.tokenUsage
|
|
190
|
-
accumulated.turns = result.turns
|
|
206
|
+
Object.assign(accumulated, event.data)
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
|
|
@@ -225,22 +241,33 @@ export class AgentRunner {
|
|
|
225
241
|
? allDefs.filter(d => this.options.allowedTools!.includes(d.name))
|
|
226
242
|
: allDefs
|
|
227
243
|
|
|
244
|
+
// Per-call abortSignal takes precedence over the static one.
|
|
245
|
+
const effectiveAbortSignal = options.abortSignal ?? this.options.abortSignal
|
|
246
|
+
|
|
228
247
|
const baseChatOptions: LLMChatOptions = {
|
|
229
248
|
model: this.options.model,
|
|
230
249
|
tools: toolDefs.length > 0 ? toolDefs : undefined,
|
|
231
250
|
maxTokens: this.options.maxTokens,
|
|
232
251
|
temperature: this.options.temperature,
|
|
233
252
|
systemPrompt: this.options.systemPrompt,
|
|
234
|
-
abortSignal:
|
|
253
|
+
abortSignal: effectiveAbortSignal,
|
|
235
254
|
}
|
|
236
255
|
|
|
256
|
+
// Loop detection state — only allocated when configured.
|
|
257
|
+
const detector = this.options.loopDetection
|
|
258
|
+
? new LoopDetector(this.options.loopDetection)
|
|
259
|
+
: null
|
|
260
|
+
let loopDetected = false
|
|
261
|
+
let loopWarned = false
|
|
262
|
+
const loopAction = this.options.loopDetection?.onLoopDetected ?? 'warn'
|
|
263
|
+
|
|
237
264
|
try {
|
|
238
265
|
// -----------------------------------------------------------------
|
|
239
266
|
// Main agentic loop — `while (true)` until end_turn or maxTurns
|
|
240
267
|
// -----------------------------------------------------------------
|
|
241
268
|
while (true) {
|
|
242
269
|
// Respect abort before each LLM call.
|
|
243
|
-
if (
|
|
270
|
+
if (effectiveAbortSignal?.aborted) {
|
|
244
271
|
break
|
|
245
272
|
}
|
|
246
273
|
|
|
@@ -254,7 +281,23 @@ export class AgentRunner {
|
|
|
254
281
|
// ------------------------------------------------------------------
|
|
255
282
|
// Step 1: Call the LLM and collect the full response for this turn.
|
|
256
283
|
// ------------------------------------------------------------------
|
|
284
|
+
const llmStartMs = Date.now()
|
|
257
285
|
const response = await this.adapter.chat(conversationMessages, baseChatOptions)
|
|
286
|
+
if (options.onTrace) {
|
|
287
|
+
const llmEndMs = Date.now()
|
|
288
|
+
emitTrace(options.onTrace, {
|
|
289
|
+
type: 'llm_call',
|
|
290
|
+
runId: options.runId ?? '',
|
|
291
|
+
taskId: options.taskId,
|
|
292
|
+
agent: options.traceAgent ?? this.options.agentName ?? 'unknown',
|
|
293
|
+
model: this.options.model,
|
|
294
|
+
turn: turns,
|
|
295
|
+
tokens: response.usage,
|
|
296
|
+
startMs: llmStartMs,
|
|
297
|
+
endMs: llmEndMs,
|
|
298
|
+
durationMs: llmEndMs - llmStartMs,
|
|
299
|
+
})
|
|
300
|
+
}
|
|
258
301
|
|
|
259
302
|
totalUsage = addTokenUsage(totalUsage, response.usage)
|
|
260
303
|
|
|
@@ -275,21 +318,77 @@ export class AgentRunner {
|
|
|
275
318
|
yield { type: 'text', data: turnText } satisfies StreamEvent
|
|
276
319
|
}
|
|
277
320
|
|
|
278
|
-
//
|
|
321
|
+
// Extract tool-use blocks for detection and execution.
|
|
279
322
|
const toolUseBlocks = extractToolUseBlocks(response.content)
|
|
280
|
-
|
|
281
|
-
|
|
323
|
+
|
|
324
|
+
// ------------------------------------------------------------------
|
|
325
|
+
// Step 2.5: Loop detection — check before yielding tool_use events
|
|
326
|
+
// so that terminate mode doesn't emit orphaned tool_use without
|
|
327
|
+
// matching tool_result.
|
|
328
|
+
// ------------------------------------------------------------------
|
|
329
|
+
let injectWarning = false
|
|
330
|
+
let injectWarningKind: 'tool_repetition' | 'text_repetition' = 'tool_repetition'
|
|
331
|
+
if (detector && toolUseBlocks.length > 0) {
|
|
332
|
+
const toolInfo = detector.recordToolCalls(toolUseBlocks)
|
|
333
|
+
const textInfo = turnText.length > 0 ? detector.recordText(turnText) : null
|
|
334
|
+
const info = toolInfo ?? textInfo
|
|
335
|
+
|
|
336
|
+
if (info) {
|
|
337
|
+
yield { type: 'loop_detected', data: info } satisfies StreamEvent
|
|
338
|
+
options.onWarning?.(info.detail)
|
|
339
|
+
|
|
340
|
+
const action = typeof loopAction === 'function'
|
|
341
|
+
? await loopAction(info)
|
|
342
|
+
: loopAction
|
|
343
|
+
|
|
344
|
+
if (action === 'terminate') {
|
|
345
|
+
loopDetected = true
|
|
346
|
+
finalOutput = turnText
|
|
347
|
+
break
|
|
348
|
+
} else if (action === 'warn' || action === 'inject') {
|
|
349
|
+
if (loopWarned) {
|
|
350
|
+
// Second detection after a warning — force terminate.
|
|
351
|
+
loopDetected = true
|
|
352
|
+
finalOutput = turnText
|
|
353
|
+
break
|
|
354
|
+
}
|
|
355
|
+
loopWarned = true
|
|
356
|
+
injectWarning = true
|
|
357
|
+
injectWarningKind = info.kind
|
|
358
|
+
// Fall through to execute tools, then inject warning.
|
|
359
|
+
}
|
|
360
|
+
// 'continue' — do nothing, let the loop proceed normally.
|
|
361
|
+
} else {
|
|
362
|
+
// No loop detected this turn — agent has recovered, so reset
|
|
363
|
+
// the warning state. A future loop gets a fresh warning cycle.
|
|
364
|
+
loopWarned = false
|
|
365
|
+
}
|
|
282
366
|
}
|
|
283
367
|
|
|
284
368
|
// ------------------------------------------------------------------
|
|
285
369
|
// Step 3: Decide whether to continue looping.
|
|
286
370
|
// ------------------------------------------------------------------
|
|
287
371
|
if (toolUseBlocks.length === 0) {
|
|
372
|
+
// Warn on first turn if tools were provided but model didn't use them.
|
|
373
|
+
if (turns === 1 && toolDefs.length > 0 && options.onWarning) {
|
|
374
|
+
const agentName = this.options.agentName ?? 'unknown'
|
|
375
|
+
options.onWarning(
|
|
376
|
+
`Agent "${agentName}" has ${toolDefs.length} tool(s) available but the model ` +
|
|
377
|
+
`returned no tool calls. If using a local model, verify it supports tool calling ` +
|
|
378
|
+
`(see https://ollama.com/search?c=tools).`,
|
|
379
|
+
)
|
|
380
|
+
}
|
|
288
381
|
// No tools requested — this is the terminal assistant turn.
|
|
289
382
|
finalOutput = turnText
|
|
290
383
|
break
|
|
291
384
|
}
|
|
292
385
|
|
|
386
|
+
// Announce each tool-use block the model requested (after loop
|
|
387
|
+
// detection, so terminate mode never emits unpaired events).
|
|
388
|
+
for (const block of toolUseBlocks) {
|
|
389
|
+
yield { type: 'tool_use', data: block } satisfies StreamEvent
|
|
390
|
+
}
|
|
391
|
+
|
|
293
392
|
// ------------------------------------------------------------------
|
|
294
393
|
// Step 4: Execute all tool calls in PARALLEL.
|
|
295
394
|
//
|
|
@@ -319,10 +418,25 @@ export class AgentRunner {
|
|
|
319
418
|
result = { data: message, isError: true }
|
|
320
419
|
}
|
|
321
420
|
|
|
322
|
-
const
|
|
421
|
+
const endTime = Date.now()
|
|
422
|
+
const duration = endTime - startTime
|
|
323
423
|
|
|
324
424
|
options.onToolResult?.(block.name, result)
|
|
325
425
|
|
|
426
|
+
if (options.onTrace) {
|
|
427
|
+
emitTrace(options.onTrace, {
|
|
428
|
+
type: 'tool_call',
|
|
429
|
+
runId: options.runId ?? '',
|
|
430
|
+
taskId: options.taskId,
|
|
431
|
+
agent: options.traceAgent ?? this.options.agentName ?? 'unknown',
|
|
432
|
+
tool: block.name,
|
|
433
|
+
isError: result.isError ?? false,
|
|
434
|
+
startMs: startTime,
|
|
435
|
+
endMs: endTime,
|
|
436
|
+
durationMs: duration,
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
|
|
326
440
|
const record: ToolCallRecord = {
|
|
327
441
|
toolName: block.name,
|
|
328
442
|
input: block.input,
|
|
@@ -354,6 +468,20 @@ export class AgentRunner {
|
|
|
354
468
|
yield { type: 'tool_result', data: resultBlock } satisfies StreamEvent
|
|
355
469
|
}
|
|
356
470
|
|
|
471
|
+
// Inject a loop-detection warning into the tool-result message so
|
|
472
|
+
// the LLM sees it alongside the results (avoids two consecutive user
|
|
473
|
+
// messages which violates the alternating-role constraint).
|
|
474
|
+
if (injectWarning) {
|
|
475
|
+
const warningText = injectWarningKind === 'text_repetition'
|
|
476
|
+
? 'WARNING: You appear to be generating the same response repeatedly. ' +
|
|
477
|
+
'This suggests you are stuck in a loop. Please try a different approach ' +
|
|
478
|
+
'or provide new information.'
|
|
479
|
+
: 'WARNING: You appear to be repeating the same tool calls with identical arguments. ' +
|
|
480
|
+
'This suggests you are stuck in a loop. Please try a different approach, use different ' +
|
|
481
|
+
'parameters, or explain what you are trying to accomplish.'
|
|
482
|
+
toolResultBlocks.push({ type: 'text' as const, text: warningText })
|
|
483
|
+
}
|
|
484
|
+
|
|
357
485
|
const toolResultMessage: LLMMessage = {
|
|
358
486
|
role: 'user',
|
|
359
487
|
content: toolResultBlocks,
|
|
@@ -387,6 +515,7 @@ export class AgentRunner {
|
|
|
387
515
|
toolCalls: allToolCalls,
|
|
388
516
|
tokenUsage: totalUsage,
|
|
389
517
|
turns,
|
|
518
|
+
...(loopDetected ? { loopDetected: true } : {}),
|
|
390
519
|
}
|
|
391
520
|
|
|
392
521
|
yield { type: 'done', data: runResult } satisfies StreamEvent
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,7 @@ export type { SchedulingStrategy } from './orchestrator/scheduler.js'
|
|
|
63
63
|
// ---------------------------------------------------------------------------
|
|
64
64
|
|
|
65
65
|
export { Agent } from './agent/agent.js'
|
|
66
|
+
export { LoopDetector } from './agent/loop-detector.js'
|
|
66
67
|
export { buildStructuredOutputInstruction, extractJSON, validateOutput } from './agent/structured-output.js'
|
|
67
68
|
export { AgentPool, Semaphore } from './agent/pool.js'
|
|
68
69
|
export type { PoolStatus } from './agent/pool.js'
|
|
@@ -147,7 +148,10 @@ export type {
|
|
|
147
148
|
AgentConfig,
|
|
148
149
|
AgentState,
|
|
149
150
|
AgentRunResult,
|
|
151
|
+
BeforeRunHookContext,
|
|
150
152
|
ToolCallRecord,
|
|
153
|
+
LoopDetectionConfig,
|
|
154
|
+
LoopDetectionInfo,
|
|
151
155
|
|
|
152
156
|
// Team
|
|
153
157
|
TeamConfig,
|
|
@@ -161,7 +165,18 @@ export type {
|
|
|
161
165
|
OrchestratorConfig,
|
|
162
166
|
OrchestratorEvent,
|
|
163
167
|
|
|
168
|
+
// Trace
|
|
169
|
+
TraceEventType,
|
|
170
|
+
TraceEventBase,
|
|
171
|
+
TraceEvent,
|
|
172
|
+
LLMCallTrace,
|
|
173
|
+
ToolCallTrace,
|
|
174
|
+
TaskTrace,
|
|
175
|
+
AgentTrace,
|
|
176
|
+
|
|
164
177
|
// Memory
|
|
165
178
|
MemoryEntry,
|
|
166
179
|
MemoryStore,
|
|
167
180
|
} from './types.js'
|
|
181
|
+
|
|
182
|
+
export { generateRunId } from './utils/trace.js'
|
package/src/llm/adapter.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* const anthropic = createAdapter('anthropic')
|
|
13
13
|
* const openai = createAdapter('openai', process.env.OPENAI_API_KEY)
|
|
14
|
+
* const gemini = createAdapter('gemini', process.env.GEMINI_API_KEY)
|
|
14
15
|
* ```
|
|
15
16
|
*/
|
|
16
17
|
|
|
@@ -37,7 +38,7 @@ import type { LLMAdapter } from '../types.js'
|
|
|
37
38
|
* Additional providers can be integrated by implementing {@link LLMAdapter}
|
|
38
39
|
* directly and bypassing this factory.
|
|
39
40
|
*/
|
|
40
|
-
export type SupportedProvider = 'anthropic' | 'copilot' | 'openai'
|
|
41
|
+
export type SupportedProvider = 'anthropic' | 'copilot' | 'grok' | 'openai' | 'gemini'
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Instantiate the appropriate {@link LLMAdapter} for the given provider.
|
|
@@ -46,6 +47,8 @@ export type SupportedProvider = 'anthropic' | 'copilot' | 'openai'
|
|
|
46
47
|
* explicitly:
|
|
47
48
|
* - `anthropic` → `ANTHROPIC_API_KEY`
|
|
48
49
|
* - `openai` → `OPENAI_API_KEY`
|
|
50
|
+
* - `gemini` → `GEMINI_API_KEY` / `GOOGLE_API_KEY`
|
|
51
|
+
* - `grok` → `XAI_API_KEY`
|
|
49
52
|
* - `copilot` → `GITHUB_COPILOT_TOKEN` / `GITHUB_TOKEN`, or interactive
|
|
50
53
|
* OAuth2 device flow if neither is set
|
|
51
54
|
*
|
|
@@ -74,10 +77,18 @@ export async function createAdapter(
|
|
|
74
77
|
const { CopilotAdapter } = await import('./copilot.js')
|
|
75
78
|
return new CopilotAdapter(apiKey)
|
|
76
79
|
}
|
|
80
|
+
case 'gemini': {
|
|
81
|
+
const { GeminiAdapter } = await import('./gemini.js')
|
|
82
|
+
return new GeminiAdapter(apiKey)
|
|
83
|
+
}
|
|
77
84
|
case 'openai': {
|
|
78
85
|
const { OpenAIAdapter } = await import('./openai.js')
|
|
79
86
|
return new OpenAIAdapter(apiKey, baseURL)
|
|
80
87
|
}
|
|
88
|
+
case 'grok': {
|
|
89
|
+
const { GrokAdapter } = await import('./grok.js')
|
|
90
|
+
return new GrokAdapter(apiKey, baseURL)
|
|
91
|
+
}
|
|
81
92
|
default: {
|
|
82
93
|
// The `never` cast here makes TypeScript enforce exhaustiveness.
|
|
83
94
|
const _exhaustive: never = provider
|
package/src/llm/copilot.ts
CHANGED
|
@@ -313,7 +313,8 @@ export class CopilotAdapter implements LLMAdapter {
|
|
|
313
313
|
},
|
|
314
314
|
)
|
|
315
315
|
|
|
316
|
-
|
|
316
|
+
const toolNames = options.tools?.map(t => t.name)
|
|
317
|
+
return fromOpenAICompletion(completion, toolNames)
|
|
317
318
|
}
|
|
318
319
|
|
|
319
320
|
// -------------------------------------------------------------------------
|