@synergenius/flow-weaver 0.22.7 → 0.22.9
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/dist/agent/agent-loop.js +6 -0
- package/dist/agent/index.d.ts +3 -1
- package/dist/agent/index.js +2 -1
- package/dist/agent/providers/claude-cli.js +8 -8
- package/dist/agent/providers/openai-compat.d.ts +25 -0
- package/dist/agent/providers/openai-compat.js +204 -0
- package/dist/agent/types.d.ts +6 -0
- package/dist/cli/flow-weaver.mjs +2 -2
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/package.json +1 -1
package/dist/agent/agent-loop.js
CHANGED
|
@@ -30,6 +30,8 @@ export async function runAgentLoop(provider, tools, executor, messages, options)
|
|
|
30
30
|
model: options?.model,
|
|
31
31
|
maxTokens: options?.maxTokens,
|
|
32
32
|
signal,
|
|
33
|
+
executor,
|
|
34
|
+
onToolEvent,
|
|
33
35
|
});
|
|
34
36
|
for await (const event of stream) {
|
|
35
37
|
onStreamEvent?.(event);
|
|
@@ -51,6 +53,10 @@ export async function runAgentLoop(provider, tools, executor, messages, options)
|
|
|
51
53
|
});
|
|
52
54
|
activeToolNames.delete(event.id);
|
|
53
55
|
break;
|
|
56
|
+
case 'tool_result':
|
|
57
|
+
// CLI handled tool internally via MCP — count it
|
|
58
|
+
toolCallCount++;
|
|
59
|
+
break;
|
|
54
60
|
case 'usage':
|
|
55
61
|
totalPromptTokens += event.promptTokens;
|
|
56
62
|
totalCompletionTokens += event.completionTokens;
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
* @synergenius/flow-weaver/agent
|
|
3
3
|
*
|
|
4
4
|
* Provider-agnostic agent loop with MCP bridge for tool execution.
|
|
5
|
-
*
|
|
5
|
+
* Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
|
|
6
6
|
*/
|
|
7
7
|
export type { StreamEvent, AgentMessage, AgentProvider, ToolDefinition, ToolExecutor, ToolEvent, McpBridge, AgentLoopOptions, AgentLoopResult, StreamOptions, SpawnFn, ClaudeCliProviderOptions, CliSessionOptions, Logger, } from './types.js';
|
|
8
8
|
export { runAgentLoop } from './agent-loop.js';
|
|
9
9
|
export { AnthropicProvider, createAnthropicProvider } from './providers/anthropic.js';
|
|
10
10
|
export type { AnthropicProviderOptions } from './providers/anthropic.js';
|
|
11
11
|
export { ClaudeCliProvider, createClaudeCliProvider } from './providers/claude-cli.js';
|
|
12
|
+
export { OpenAICompatProvider, createOpenAICompatProvider } from './providers/openai-compat.js';
|
|
13
|
+
export type { OpenAICompatProviderOptions } from './providers/openai-compat.js';
|
|
12
14
|
export { createMcpBridge } from './mcp-bridge.js';
|
|
13
15
|
export { CliSession, getOrCreateCliSession, killCliSession, killAllCliSessions, } from './cli-session.js';
|
|
14
16
|
export { buildSafeEnv, buildSafeSpawnOpts, MINIMAL_PATH, ENV_ALLOWLIST } from './env-allowlist.js';
|
package/dist/agent/index.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
* @synergenius/flow-weaver/agent
|
|
3
3
|
*
|
|
4
4
|
* Provider-agnostic agent loop with MCP bridge for tool execution.
|
|
5
|
-
*
|
|
5
|
+
* Built-in providers: Anthropic API, Claude CLI, OpenAI-compatible (GPT-4o, Groq, Ollama, etc).
|
|
6
6
|
*/
|
|
7
7
|
// Agent loop
|
|
8
8
|
export { runAgentLoop } from './agent-loop.js';
|
|
9
9
|
// Providers
|
|
10
10
|
export { AnthropicProvider, createAnthropicProvider } from './providers/anthropic.js';
|
|
11
11
|
export { ClaudeCliProvider, createClaudeCliProvider } from './providers/claude-cli.js';
|
|
12
|
+
export { OpenAICompatProvider, createOpenAICompatProvider } from './providers/openai-compat.js';
|
|
12
13
|
// MCP bridge
|
|
13
14
|
export { createMcpBridge } from './mcp-bridge.js';
|
|
14
15
|
// CLI session (warm persistent sessions)
|
|
@@ -23,7 +23,7 @@ export class ClaudeCliProvider {
|
|
|
23
23
|
this.model = options.model;
|
|
24
24
|
this.mcpConfigPath = options.mcpConfigPath;
|
|
25
25
|
this.spawnFn = options.spawnFn ?? ((cmd, args, opts) => nodeSpawn(cmd, args, { ...opts, stdio: opts.stdio }));
|
|
26
|
-
this.timeout = options.timeout ??
|
|
26
|
+
this.timeout = options.timeout ?? 600_000;
|
|
27
27
|
}
|
|
28
28
|
async *stream(messages, tools, options) {
|
|
29
29
|
const model = options?.model ?? this.model;
|
|
@@ -34,11 +34,10 @@ export class ClaudeCliProvider {
|
|
|
34
34
|
let bridge = null;
|
|
35
35
|
let mcpConfigPath = this.mcpConfigPath;
|
|
36
36
|
if (tools.length > 0 && !mcpConfigPath) {
|
|
37
|
-
// Create
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
bridge = await createMcpBridge(tools, async () => ({ result: 'Not implemented', isError: true }));
|
|
37
|
+
// Create MCP bridge with real executor so CLI tool calls go through our
|
|
38
|
+
// safety guards (path traversal, shrink detection, shell blocklist, etc.)
|
|
39
|
+
const executor = options?.executor ?? (async () => ({ result: 'Tool executor not provided', isError: true }));
|
|
40
|
+
bridge = await createMcpBridge(tools, executor, options?.onToolEvent);
|
|
42
41
|
mcpConfigPath = bridge.configPath;
|
|
43
42
|
}
|
|
44
43
|
const args = [
|
|
@@ -70,10 +69,11 @@ export class ClaudeCliProvider {
|
|
|
70
69
|
}, 2000);
|
|
71
70
|
}, { once: true });
|
|
72
71
|
}
|
|
73
|
-
// Timeout
|
|
72
|
+
// Timeout — per-request override or provider default
|
|
73
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
74
74
|
const timer = setTimeout(() => {
|
|
75
75
|
child.kill('SIGTERM');
|
|
76
|
-
},
|
|
76
|
+
}, timeout);
|
|
77
77
|
child.stdin.write(prompt);
|
|
78
78
|
child.stdin.end();
|
|
79
79
|
// Collect events from the parser
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible API provider — works with any service that speaks
|
|
3
|
+
* the OpenAI chat completions API: OpenAI, Groq, Together, Ollama, etc.
|
|
4
|
+
*
|
|
5
|
+
* No SDK dependency. Uses only Node.js native fetch + SSE parsing.
|
|
6
|
+
* Converts OpenAI's delta format to the canonical StreamEvent union.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentProvider, AgentMessage, ToolDefinition, StreamEvent, StreamOptions } from '../types.js';
|
|
9
|
+
export interface OpenAICompatProviderOptions {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
model?: string;
|
|
12
|
+
maxTokens?: number;
|
|
13
|
+
/** Base URL for the API (default: https://api.openai.com). Include /v1 if needed. */
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class OpenAICompatProvider implements AgentProvider {
|
|
17
|
+
private apiKey;
|
|
18
|
+
private model;
|
|
19
|
+
private maxTokens;
|
|
20
|
+
private baseUrl;
|
|
21
|
+
constructor(options: OpenAICompatProviderOptions);
|
|
22
|
+
stream(messages: AgentMessage[], tools: ToolDefinition[], options?: StreamOptions): AsyncGenerator<StreamEvent>;
|
|
23
|
+
}
|
|
24
|
+
export declare function createOpenAICompatProvider(options: OpenAICompatProviderOptions): OpenAICompatProvider;
|
|
25
|
+
//# sourceMappingURL=openai-compat.d.ts.map
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible API provider — works with any service that speaks
|
|
3
|
+
* the OpenAI chat completions API: OpenAI, Groq, Together, Ollama, etc.
|
|
4
|
+
*
|
|
5
|
+
* No SDK dependency. Uses only Node.js native fetch + SSE parsing.
|
|
6
|
+
* Converts OpenAI's delta format to the canonical StreamEvent union.
|
|
7
|
+
*/
|
|
8
|
+
export class OpenAICompatProvider {
|
|
9
|
+
apiKey;
|
|
10
|
+
model;
|
|
11
|
+
maxTokens;
|
|
12
|
+
baseUrl;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
if (!options.apiKey) {
|
|
15
|
+
throw new Error('OpenAICompatProvider requires an API key');
|
|
16
|
+
}
|
|
17
|
+
this.apiKey = options.apiKey;
|
|
18
|
+
this.model = options.model ?? 'gpt-4o';
|
|
19
|
+
this.maxTokens = options.maxTokens ?? 4096;
|
|
20
|
+
this.baseUrl = (options.baseUrl ?? 'https://api.openai.com').replace(/\/+$/, '');
|
|
21
|
+
}
|
|
22
|
+
async *stream(messages, tools, options) {
|
|
23
|
+
const model = options?.model ?? this.model;
|
|
24
|
+
const maxTokens = options?.maxTokens ?? this.maxTokens;
|
|
25
|
+
// Build OpenAI-format messages
|
|
26
|
+
const apiMessages = messages.map((m) => formatMessage(m));
|
|
27
|
+
// Build OpenAI-format tools
|
|
28
|
+
const apiTools = tools.length > 0
|
|
29
|
+
? tools.map((t) => ({
|
|
30
|
+
type: 'function',
|
|
31
|
+
function: {
|
|
32
|
+
name: t.name,
|
|
33
|
+
description: t.description,
|
|
34
|
+
parameters: t.inputSchema,
|
|
35
|
+
},
|
|
36
|
+
}))
|
|
37
|
+
: undefined;
|
|
38
|
+
const body = {
|
|
39
|
+
model,
|
|
40
|
+
messages: [
|
|
41
|
+
...(options?.systemPrompt ? [{ role: 'system', content: options.systemPrompt }] : []),
|
|
42
|
+
...apiMessages,
|
|
43
|
+
],
|
|
44
|
+
max_tokens: maxTokens,
|
|
45
|
+
stream: true,
|
|
46
|
+
};
|
|
47
|
+
if (apiTools && apiTools.length > 0) {
|
|
48
|
+
body.tools = apiTools;
|
|
49
|
+
}
|
|
50
|
+
// Determine the completions endpoint
|
|
51
|
+
const url = this.baseUrl.includes('/v1/')
|
|
52
|
+
? `${this.baseUrl}chat/completions`
|
|
53
|
+
: `${this.baseUrl}/v1/chat/completions`;
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
signal: options?.signal,
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const errText = await response.text().catch(() => '');
|
|
65
|
+
yield { type: 'text_delta', text: `OpenAI API error ${response.status}: ${errText.slice(0, 300)}` };
|
|
66
|
+
yield { type: 'message_stop', finishReason: 'error' };
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!response.body) {
|
|
70
|
+
yield { type: 'message_stop', finishReason: 'error' };
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Parse SSE stream
|
|
74
|
+
const reader = response.body.getReader();
|
|
75
|
+
const decoder = new TextDecoder();
|
|
76
|
+
let buffer = '';
|
|
77
|
+
let hasToolCalls = false;
|
|
78
|
+
// Track active tool calls (OpenAI sends incremental deltas by index)
|
|
79
|
+
const activeToolCalls = new Map();
|
|
80
|
+
try {
|
|
81
|
+
while (true) {
|
|
82
|
+
const { done, value } = await reader.read();
|
|
83
|
+
if (done)
|
|
84
|
+
break;
|
|
85
|
+
buffer += decoder.decode(value, { stream: true });
|
|
86
|
+
const lines = buffer.split('\n');
|
|
87
|
+
buffer = lines.pop() || '';
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
if (!line.startsWith('data: '))
|
|
90
|
+
continue;
|
|
91
|
+
const data = line.slice(6).trim();
|
|
92
|
+
if (data === '[DONE]') {
|
|
93
|
+
// Flush any pending tool calls
|
|
94
|
+
for (const [, tc] of activeToolCalls) {
|
|
95
|
+
let args = {};
|
|
96
|
+
try {
|
|
97
|
+
args = JSON.parse(tc.argsJson);
|
|
98
|
+
}
|
|
99
|
+
catch { /* malformed */ }
|
|
100
|
+
yield { type: 'tool_use_end', id: tc.id, arguments: args };
|
|
101
|
+
}
|
|
102
|
+
yield { type: 'message_stop', finishReason: hasToolCalls ? 'tool_calls' : 'stop' };
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let parsed;
|
|
106
|
+
try {
|
|
107
|
+
parsed = JSON.parse(data);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Extract usage if present
|
|
113
|
+
if (parsed.usage) {
|
|
114
|
+
const usage = parsed.usage;
|
|
115
|
+
yield {
|
|
116
|
+
type: 'usage',
|
|
117
|
+
promptTokens: usage.prompt_tokens ?? 0,
|
|
118
|
+
completionTokens: usage.completion_tokens ?? 0,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const choices = parsed.choices;
|
|
122
|
+
if (!choices || choices.length === 0)
|
|
123
|
+
continue;
|
|
124
|
+
const choice = choices[0];
|
|
125
|
+
const delta = choice.delta;
|
|
126
|
+
if (!delta)
|
|
127
|
+
continue;
|
|
128
|
+
// Text content
|
|
129
|
+
if (delta.content && typeof delta.content === 'string') {
|
|
130
|
+
yield { type: 'text_delta', text: delta.content };
|
|
131
|
+
}
|
|
132
|
+
// Tool calls
|
|
133
|
+
const toolCalls = delta.tool_calls;
|
|
134
|
+
if (toolCalls) {
|
|
135
|
+
for (const tc of toolCalls) {
|
|
136
|
+
const index = tc.index ?? 0;
|
|
137
|
+
const fn = tc.function;
|
|
138
|
+
if (tc.id) {
|
|
139
|
+
// New tool call
|
|
140
|
+
hasToolCalls = true;
|
|
141
|
+
const name = fn?.name ? String(fn.name) : 'unknown';
|
|
142
|
+
activeToolCalls.set(index, { id: String(tc.id), name, argsJson: '' });
|
|
143
|
+
yield { type: 'tool_use_start', id: String(tc.id), name };
|
|
144
|
+
}
|
|
145
|
+
// Accumulate function arguments
|
|
146
|
+
if (fn?.arguments && typeof fn.arguments === 'string') {
|
|
147
|
+
const existing = activeToolCalls.get(index);
|
|
148
|
+
if (existing) {
|
|
149
|
+
existing.argsJson += fn.arguments;
|
|
150
|
+
yield { type: 'tool_use_delta', id: existing.id, partialJson: fn.arguments };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Finish reason
|
|
156
|
+
if (choice.finish_reason === 'tool_calls') {
|
|
157
|
+
hasToolCalls = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
reader.releaseLock();
|
|
164
|
+
}
|
|
165
|
+
// If we got here without [DONE], flush
|
|
166
|
+
for (const [, tc] of activeToolCalls) {
|
|
167
|
+
let args = {};
|
|
168
|
+
try {
|
|
169
|
+
args = JSON.parse(tc.argsJson);
|
|
170
|
+
}
|
|
171
|
+
catch { /* malformed */ }
|
|
172
|
+
yield { type: 'tool_use_end', id: tc.id, arguments: args };
|
|
173
|
+
}
|
|
174
|
+
yield { type: 'message_stop', finishReason: hasToolCalls ? 'tool_calls' : 'stop' };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function formatMessage(m) {
|
|
178
|
+
if (m.role === 'tool') {
|
|
179
|
+
return {
|
|
180
|
+
role: 'tool',
|
|
181
|
+
tool_call_id: m.toolCallId,
|
|
182
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (m.role === 'assistant' && m.toolCalls?.length) {
|
|
186
|
+
return {
|
|
187
|
+
role: 'assistant',
|
|
188
|
+
content: typeof m.content === 'string' && m.content ? m.content : null,
|
|
189
|
+
tool_calls: m.toolCalls.map((tc) => ({
|
|
190
|
+
id: tc.id,
|
|
191
|
+
type: 'function',
|
|
192
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
role: m.role,
|
|
198
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export function createOpenAICompatProvider(options) {
|
|
202
|
+
return new OpenAICompatProvider(options);
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=openai-compat.js.map
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -70,6 +70,12 @@ export interface StreamOptions {
|
|
|
70
70
|
model?: string;
|
|
71
71
|
maxTokens?: number;
|
|
72
72
|
signal?: AbortSignal;
|
|
73
|
+
/** Tool executor for providers that handle tool loops internally (e.g. CLI via MCP). */
|
|
74
|
+
executor?: ToolExecutor;
|
|
75
|
+
/** Event callback for tool events from internal tool loops. */
|
|
76
|
+
onToolEvent?: (event: ToolEvent) => void;
|
|
77
|
+
/** Per-request timeout in milliseconds (overrides provider default). */
|
|
78
|
+
timeout?: number;
|
|
73
79
|
}
|
|
74
80
|
export interface AgentProvider {
|
|
75
81
|
stream(messages: AgentMessage[], tools: ToolDefinition[], options?: StreamOptions): AsyncGenerator<StreamEvent>;
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -9886,7 +9886,7 @@ var VERSION;
|
|
|
9886
9886
|
var init_generated_version = __esm({
|
|
9887
9887
|
"src/generated-version.ts"() {
|
|
9888
9888
|
"use strict";
|
|
9889
|
-
VERSION = "0.22.
|
|
9889
|
+
VERSION = "0.22.9";
|
|
9890
9890
|
}
|
|
9891
9891
|
});
|
|
9892
9892
|
|
|
@@ -94939,7 +94939,7 @@ var {
|
|
|
94939
94939
|
// src/cli/index.ts
|
|
94940
94940
|
init_logger();
|
|
94941
94941
|
init_error_utils();
|
|
94942
|
-
var version2 = true ? "0.22.
|
|
94942
|
+
var version2 = true ? "0.22.9" : "0.0.0-dev";
|
|
94943
94943
|
var program2 = new Command();
|
|
94944
94944
|
program2.name("fw").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
|
|
94945
94945
|
logger.banner(version2);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.22.
|
|
1
|
+
export declare const VERSION = "0.22.9";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
package/package.json
CHANGED