@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.
- package/package.json +8 -2
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
- package/.github/pull_request_template.md +0 -14
- package/.github/workflows/ci.yml +0 -23
- package/CLAUDE.md +0 -80
- package/CODE_OF_CONDUCT.md +0 -48
- package/CONTRIBUTING.md +0 -72
- package/DECISIONS.md +0 -43
- package/README_zh.md +0 -277
- package/SECURITY.md +0 -17
- package/examples/01-single-agent.ts +0 -131
- package/examples/02-team-collaboration.ts +0 -167
- package/examples/03-task-pipeline.ts +0 -201
- package/examples/04-multi-model-team.ts +0 -261
- package/examples/05-copilot-test.ts +0 -49
- package/examples/06-local-model.ts +0 -200
- package/examples/07-fan-out-aggregate.ts +0 -209
- package/examples/08-gemma4-local.ts +0 -192
- package/examples/09-structured-output.ts +0 -73
- package/examples/10-task-retry.ts +0 -132
- package/examples/11-trace-observability.ts +0 -133
- package/examples/12-grok.ts +0 -154
- package/examples/13-gemini.ts +0 -48
- package/src/agent/agent.ts +0 -622
- package/src/agent/loop-detector.ts +0 -137
- package/src/agent/pool.ts +0 -285
- package/src/agent/runner.ts +0 -542
- package/src/agent/structured-output.ts +0 -126
- package/src/index.ts +0 -182
- package/src/llm/adapter.ts +0 -98
- package/src/llm/anthropic.ts +0 -389
- package/src/llm/copilot.ts +0 -552
- package/src/llm/gemini.ts +0 -378
- package/src/llm/grok.ts +0 -29
- package/src/llm/openai-common.ts +0 -294
- package/src/llm/openai.ts +0 -292
- package/src/memory/shared.ts +0 -181
- package/src/memory/store.ts +0 -124
- package/src/orchestrator/orchestrator.ts +0 -1071
- package/src/orchestrator/scheduler.ts +0 -352
- package/src/task/queue.ts +0 -464
- package/src/task/task.ts +0 -239
- package/src/team/messaging.ts +0 -232
- package/src/team/team.ts +0 -334
- package/src/tool/built-in/bash.ts +0 -187
- package/src/tool/built-in/file-edit.ts +0 -154
- package/src/tool/built-in/file-read.ts +0 -105
- package/src/tool/built-in/file-write.ts +0 -81
- package/src/tool/built-in/grep.ts +0 -362
- package/src/tool/built-in/index.ts +0 -50
- package/src/tool/executor.ts +0 -178
- package/src/tool/framework.ts +0 -557
- package/src/tool/text-tool-extractor.ts +0 -219
- package/src/types.ts +0 -542
- package/src/utils/semaphore.ts +0 -89
- package/src/utils/trace.ts +0 -34
- package/tests/agent-hooks.test.ts +0 -473
- package/tests/agent-pool.test.ts +0 -212
- package/tests/approval.test.ts +0 -464
- package/tests/built-in-tools.test.ts +0 -393
- package/tests/gemini-adapter.test.ts +0 -97
- package/tests/grok-adapter.test.ts +0 -74
- package/tests/llm-adapters.test.ts +0 -357
- package/tests/loop-detection.test.ts +0 -456
- package/tests/openai-fallback.test.ts +0 -159
- package/tests/orchestrator.test.ts +0 -281
- package/tests/scheduler.test.ts +0 -221
- package/tests/semaphore.test.ts +0 -57
- package/tests/shared-memory.test.ts +0 -122
- package/tests/structured-output.test.ts +0 -331
- package/tests/task-queue.test.ts +0 -244
- package/tests/task-retry.test.ts +0 -368
- package/tests/task-utils.test.ts +0 -155
- package/tests/team-messaging.test.ts +0 -329
- package/tests/text-tool-extractor.test.ts +0 -170
- package/tests/tool-executor.test.ts +0 -193
- package/tests/trace.test.ts +0 -453
- package/tsconfig.json +0 -25
- package/vitest.config.ts +0 -9
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Fallback tool-call extractor for local models.
|
|
3
|
-
*
|
|
4
|
-
* When a local model (Ollama, vLLM, LM Studio) returns tool calls as plain
|
|
5
|
-
* text instead of using the OpenAI `tool_calls` wire format, this module
|
|
6
|
-
* attempts to extract them from the text output.
|
|
7
|
-
*
|
|
8
|
-
* Common scenarios:
|
|
9
|
-
* - Ollama thinking-model bug: tool call JSON ends up inside unclosed `<think>` tags
|
|
10
|
-
* - Model outputs raw JSON tool calls without the server parsing them
|
|
11
|
-
* - Model wraps tool calls in markdown code fences
|
|
12
|
-
* - Hermes-format `<tool_call>` tags
|
|
13
|
-
*
|
|
14
|
-
* This is a **safety net**, not the primary path. Native `tool_calls` from
|
|
15
|
-
* the server are always preferred.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import type { ToolUseBlock } from '../types.js'
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// ID generation
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
let callCounter = 0
|
|
25
|
-
|
|
26
|
-
/** Generate a unique tool-call ID for extracted calls. */
|
|
27
|
-
function generateToolCallId(): string {
|
|
28
|
-
return `extracted_call_${Date.now()}_${++callCounter}`
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Internal parsers
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Try to parse a single JSON object as a tool call.
|
|
37
|
-
*
|
|
38
|
-
* Accepted shapes:
|
|
39
|
-
* ```json
|
|
40
|
-
* { "name": "bash", "arguments": { "command": "ls" } }
|
|
41
|
-
* { "name": "bash", "parameters": { "command": "ls" } }
|
|
42
|
-
* { "function": { "name": "bash", "arguments": { "command": "ls" } } }
|
|
43
|
-
* ```
|
|
44
|
-
*/
|
|
45
|
-
function parseToolCallJSON(
|
|
46
|
-
json: unknown,
|
|
47
|
-
knownToolNames: ReadonlySet<string>,
|
|
48
|
-
): ToolUseBlock | null {
|
|
49
|
-
if (json === null || typeof json !== 'object' || Array.isArray(json)) {
|
|
50
|
-
return null
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const obj = json as Record<string, unknown>
|
|
54
|
-
|
|
55
|
-
// Shape: { function: { name, arguments } }
|
|
56
|
-
if (typeof obj['function'] === 'object' && obj['function'] !== null) {
|
|
57
|
-
const fn = obj['function'] as Record<string, unknown>
|
|
58
|
-
return parseFlat(fn, knownToolNames)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Shape: { name, arguments|parameters }
|
|
62
|
-
return parseFlat(obj, knownToolNames)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function parseFlat(
|
|
66
|
-
obj: Record<string, unknown>,
|
|
67
|
-
knownToolNames: ReadonlySet<string>,
|
|
68
|
-
): ToolUseBlock | null {
|
|
69
|
-
const name = obj['name']
|
|
70
|
-
if (typeof name !== 'string' || name.length === 0) return null
|
|
71
|
-
|
|
72
|
-
// Whitelist check — don't treat arbitrary JSON as a tool call
|
|
73
|
-
if (knownToolNames.size > 0 && !knownToolNames.has(name)) return null
|
|
74
|
-
|
|
75
|
-
let input: Record<string, unknown> = {}
|
|
76
|
-
const args = obj['arguments'] ?? obj['parameters'] ?? obj['input']
|
|
77
|
-
if (args !== null && args !== undefined) {
|
|
78
|
-
if (typeof args === 'string') {
|
|
79
|
-
try {
|
|
80
|
-
const parsed = JSON.parse(args)
|
|
81
|
-
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
82
|
-
input = parsed as Record<string, unknown>
|
|
83
|
-
}
|
|
84
|
-
} catch {
|
|
85
|
-
// Malformed — use empty input
|
|
86
|
-
}
|
|
87
|
-
} else if (typeof args === 'object' && !Array.isArray(args)) {
|
|
88
|
-
input = args as Record<string, unknown>
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
type: 'tool_use',
|
|
94
|
-
id: generateToolCallId(),
|
|
95
|
-
name,
|
|
96
|
-
input,
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// JSON extraction from text
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Find all top-level JSON objects in a string by tracking brace depth.
|
|
106
|
-
* Returns the parsed objects (not sub-objects).
|
|
107
|
-
*/
|
|
108
|
-
function extractJSONObjects(text: string): unknown[] {
|
|
109
|
-
const results: unknown[] = []
|
|
110
|
-
let depth = 0
|
|
111
|
-
let start = -1
|
|
112
|
-
let inString = false
|
|
113
|
-
let escape = false
|
|
114
|
-
|
|
115
|
-
for (let i = 0; i < text.length; i++) {
|
|
116
|
-
const ch = text[i]!
|
|
117
|
-
|
|
118
|
-
if (escape) {
|
|
119
|
-
escape = false
|
|
120
|
-
continue
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (ch === '\\' && inString) {
|
|
124
|
-
escape = true
|
|
125
|
-
continue
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (ch === '"') {
|
|
129
|
-
inString = !inString
|
|
130
|
-
continue
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (inString) continue
|
|
134
|
-
|
|
135
|
-
if (ch === '{') {
|
|
136
|
-
if (depth === 0) start = i
|
|
137
|
-
depth++
|
|
138
|
-
} else if (ch === '}') {
|
|
139
|
-
depth--
|
|
140
|
-
if (depth === 0 && start !== -1) {
|
|
141
|
-
const candidate = text.slice(start, i + 1)
|
|
142
|
-
try {
|
|
143
|
-
results.push(JSON.parse(candidate))
|
|
144
|
-
} catch {
|
|
145
|
-
// Not valid JSON — skip
|
|
146
|
-
}
|
|
147
|
-
start = -1
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return results
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
// Hermes format: <tool_call>...</tool_call>
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
|
|
159
|
-
function extractHermesToolCalls(
|
|
160
|
-
text: string,
|
|
161
|
-
knownToolNames: ReadonlySet<string>,
|
|
162
|
-
): ToolUseBlock[] {
|
|
163
|
-
const results: ToolUseBlock[] = []
|
|
164
|
-
|
|
165
|
-
for (const match of text.matchAll(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/g)) {
|
|
166
|
-
const inner = match[1]!.trim()
|
|
167
|
-
try {
|
|
168
|
-
const parsed: unknown = JSON.parse(inner)
|
|
169
|
-
const block = parseToolCallJSON(parsed, knownToolNames)
|
|
170
|
-
if (block !== null) results.push(block)
|
|
171
|
-
} catch {
|
|
172
|
-
// Malformed hermes content — skip
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return results
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// Public API
|
|
181
|
-
// ---------------------------------------------------------------------------
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Attempt to extract tool calls from a model's text output.
|
|
185
|
-
*
|
|
186
|
-
* Tries multiple strategies in order:
|
|
187
|
-
* 1. Hermes `<tool_call>` tags
|
|
188
|
-
* 2. JSON objects in text (bare or inside code fences)
|
|
189
|
-
*
|
|
190
|
-
* @param text - The model's text output.
|
|
191
|
-
* @param knownToolNames - Whitelist of registered tool names. When non-empty,
|
|
192
|
-
* only JSON objects whose `name` matches a known tool
|
|
193
|
-
* are treated as tool calls.
|
|
194
|
-
* @returns Extracted {@link ToolUseBlock}s, or an empty array if none found.
|
|
195
|
-
*/
|
|
196
|
-
export function extractToolCallsFromText(
|
|
197
|
-
text: string,
|
|
198
|
-
knownToolNames: string[],
|
|
199
|
-
): ToolUseBlock[] {
|
|
200
|
-
if (text.length === 0) return []
|
|
201
|
-
|
|
202
|
-
const nameSet = new Set(knownToolNames)
|
|
203
|
-
|
|
204
|
-
// Strategy 1: Hermes format
|
|
205
|
-
const hermesResults = extractHermesToolCalls(text, nameSet)
|
|
206
|
-
if (hermesResults.length > 0) return hermesResults
|
|
207
|
-
|
|
208
|
-
// Strategy 2: Strip code fences, then extract JSON objects
|
|
209
|
-
const stripped = text.replace(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/g, '$1')
|
|
210
|
-
const jsonObjects = extractJSONObjects(stripped)
|
|
211
|
-
|
|
212
|
-
const results: ToolUseBlock[] = []
|
|
213
|
-
for (const obj of jsonObjects) {
|
|
214
|
-
const block = parseToolCallJSON(obj, nameSet)
|
|
215
|
-
if (block !== null) results.push(block)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return results
|
|
219
|
-
}
|