@flowcodex/core 0.3.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/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/index-LbxYtxxS.d.ts +560 -0
- package/dist/index.d.ts +995 -0
- package/dist/index.js +3840 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/index.d.ts +1 -0
- package/dist/kernel/index.js +551 -0
- package/dist/kernel/index.js.map +1 -0
- package/package.json +39 -0
- package/src/agent/agent-loop.ts +254 -0
- package/src/agent/context.ts +99 -0
- package/src/agent/conversation-state.ts +44 -0
- package/src/agent/provider-runner.ts +241 -0
- package/src/agent/system-prompt-builder.ts +193 -0
- package/src/execution/compactor.ts +256 -0
- package/src/execution/index.ts +7 -0
- package/src/execution/output-serializer.ts +90 -0
- package/src/execution/schema-validator.ts +124 -0
- package/src/execution/tool-executor.ts +276 -0
- package/src/execution/tool-registry.ts +104 -0
- package/src/index.ts +215 -0
- package/src/infrastructure/catalog-parser.ts +218 -0
- package/src/infrastructure/index.ts +16 -0
- package/src/infrastructure/path-resolver.ts +123 -0
- package/src/infrastructure/provider-factory.ts +116 -0
- package/src/infrastructure/provider-presets.ts +19 -0
- package/src/infrastructure/retry-policy.ts +50 -0
- package/src/infrastructure/secret-scrubber.ts +67 -0
- package/src/infrastructure/token-counter.ts +156 -0
- package/src/infrastructure/tracer.ts +23 -0
- package/src/kernel/container.ts +166 -0
- package/src/kernel/events.ts +323 -0
- package/src/kernel/index.ts +18 -0
- package/src/kernel/pipeline.ts +152 -0
- package/src/kernel/run-controller.ts +85 -0
- package/src/kernel/tokens.ts +21 -0
- package/src/security/index.ts +13 -0
- package/src/security/permission-policy.ts +273 -0
- package/src/session/audit-log.ts +201 -0
- package/src/session/auth-service.ts +178 -0
- package/src/session/index.ts +26 -0
- package/src/session/secret-vault.ts +183 -0
- package/src/session/session-store.ts +339 -0
- package/src/session/types.ts +100 -0
- package/src/types/blocks.ts +56 -0
- package/src/types/context.ts +54 -0
- package/src/types/errors.ts +359 -0
- package/src/types/index.ts +34 -0
- package/src/types/provider.ts +58 -0
- package/src/types/tool.ts +39 -0
- package/src/utils/error.ts +3 -0
- package/src/utils/fs.ts +185 -0
- package/src/utils/image-resize.ts +76 -0
- package/src/utils/ssrf-guard.ts +133 -0
- package/src/utils/ulid.ts +72 -0
- package/src/utils/version-check.ts +59 -0
- package/tests/agent-loop.test.ts +490 -0
- package/tests/audit-log.test.ts +199 -0
- package/tests/auth-service.test.ts +170 -0
- package/tests/blocks.test.ts +79 -0
- package/tests/catalog-parser.test.ts +174 -0
- package/tests/compactor.test.ts +180 -0
- package/tests/container.test.ts +224 -0
- package/tests/conversation-state.test.ts +75 -0
- package/tests/errors.test.ts +429 -0
- package/tests/events-v021.test.ts +60 -0
- package/tests/events-v022.test.ts +75 -0
- package/tests/events.test.ts +340 -0
- package/tests/fixtures/large-image.png +0 -0
- package/tests/fixtures/small-image.png +0 -0
- package/tests/fs-utils.test.ts +164 -0
- package/tests/image-resize.test.ts +51 -0
- package/tests/output-serializer.test.ts +79 -0
- package/tests/path-resolver.test.ts +91 -0
- package/tests/permission-policy.test.ts +174 -0
- package/tests/pipeline.test.ts +193 -0
- package/tests/provider-factory.test.ts +245 -0
- package/tests/provider-runner.test.ts +535 -0
- package/tests/retry-policy.test.ts +104 -0
- package/tests/run-controller.test.ts +115 -0
- package/tests/sanity.test.ts +26 -0
- package/tests/schema-validator.test.ts +109 -0
- package/tests/secret-scrubber.test.ts +133 -0
- package/tests/secret-vault.test.ts +130 -0
- package/tests/session-store.test.ts +429 -0
- package/tests/ssrf-guard.test.ts +112 -0
- package/tests/system-prompt-builder.test.ts +116 -0
- package/tests/token-counter.test.ts +163 -0
- package/tests/tokens.test.ts +42 -0
- package/tests/tool-executor.test.ts +452 -0
- package/tests/tool-registry.test.ts +143 -0
- package/tests/tracer.test.ts +32 -0
- package/tests/ulid.test.ts +53 -0
- package/tests/version-check.test.ts +57 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import type { ToolContext } from '../types/tool.js';
|
|
2
|
+
import type { Tool } from '../types/tool.js';
|
|
3
|
+
import type { Context } from '../types/context.js';
|
|
4
|
+
import type { ToolUse, ToolResult } from '../types/context.js';
|
|
5
|
+
import type { ToolRegistry } from './tool-registry.js';
|
|
6
|
+
import type { ConfirmAwaiter, PermissionPolicy } from '../security/permission-policy.js';
|
|
7
|
+
import { createToolOutputSerializer } from './output-serializer.js';
|
|
8
|
+
import type { OutputSerializer } from './output-serializer.js';
|
|
9
|
+
import { validateAgainstSchema } from './schema-validator.js';
|
|
10
|
+
import { toErrorMessage } from '../utils/error.js';
|
|
11
|
+
import { DefaultSecretScrubber } from '../infrastructure/secret-scrubber.js';
|
|
12
|
+
|
|
13
|
+
export type ToolExecutorStrategy = 'parallel' | 'sequential' | 'smart';
|
|
14
|
+
|
|
15
|
+
export interface ToolExecutorOptions {
|
|
16
|
+
perIterationOutputCapBytes?: number | undefined;
|
|
17
|
+
maxToolTimeoutMs?: number | undefined;
|
|
18
|
+
policy?: PermissionPolicy | undefined;
|
|
19
|
+
confirmAwaiter?: ConfirmAwaiter | undefined;
|
|
20
|
+
toolStreaming?: boolean | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ToolBatchResult {
|
|
24
|
+
results: ToolResult[];
|
|
25
|
+
remainingBudget: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_MAX_TIMEOUT = 300_000;
|
|
29
|
+
const DEFAULT_CAP = 100_000;
|
|
30
|
+
|
|
31
|
+
export class ToolExecutor {
|
|
32
|
+
private serializer: OutputSerializer;
|
|
33
|
+
private scrubber = new DefaultSecretScrubber();
|
|
34
|
+
private maxToolTimeoutMs: number;
|
|
35
|
+
private cap: number;
|
|
36
|
+
private policy?: PermissionPolicy | undefined;
|
|
37
|
+
private confirmAwaiter?: ConfirmAwaiter | undefined;
|
|
38
|
+
private toolStreamingEnabled: boolean;
|
|
39
|
+
|
|
40
|
+
constructor(
|
|
41
|
+
private registry: ToolRegistry,
|
|
42
|
+
opts: ToolExecutorOptions = {},
|
|
43
|
+
) {
|
|
44
|
+
this.cap = opts.perIterationOutputCapBytes ?? DEFAULT_CAP;
|
|
45
|
+
this.serializer = createToolOutputSerializer({ perIterationOutputCapBytes: this.cap });
|
|
46
|
+
this.maxToolTimeoutMs = opts.maxToolTimeoutMs ?? DEFAULT_MAX_TIMEOUT;
|
|
47
|
+
this.policy = opts.policy;
|
|
48
|
+
this.confirmAwaiter = opts.confirmAwaiter;
|
|
49
|
+
this.toolStreamingEnabled = opts.toolStreaming ?? false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async executeBatch(
|
|
53
|
+
toolUses: readonly ToolUse[],
|
|
54
|
+
ctx: Context,
|
|
55
|
+
strategy: ToolExecutorStrategy = 'smart',
|
|
56
|
+
): Promise<ToolBatchResult> {
|
|
57
|
+
const budget = { remaining: this.cap };
|
|
58
|
+
const indexed = toolUses.map((use, i) => ({ use, i }));
|
|
59
|
+
|
|
60
|
+
const runOne = (use: ToolUse): Promise<ToolResult> => this.executeTool(use, ctx, budget);
|
|
61
|
+
|
|
62
|
+
if (strategy === 'sequential') {
|
|
63
|
+
const out: Array<{ i: number; r: ToolResult }> = [];
|
|
64
|
+
for (const { use, i } of indexed) {
|
|
65
|
+
out.push({ i, r: await runOne(use) });
|
|
66
|
+
}
|
|
67
|
+
out.sort((a, b) => a.i - b.i);
|
|
68
|
+
return { results: out.map((o) => o.r), remainingBudget: budget.remaining };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (strategy === 'parallel') {
|
|
72
|
+
const proms = indexed.map(async ({ use, i }) => ({ i, r: await runOne(use) }));
|
|
73
|
+
const out = await Promise.all(proms);
|
|
74
|
+
out.sort((a, b) => a.i - b.i);
|
|
75
|
+
return { results: out.map((o) => o.r), remainingBudget: budget.remaining };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const ro: Array<{ use: ToolUse; i: number }> = [];
|
|
79
|
+
const mut: Array<{ use: ToolUse; i: number }> = [];
|
|
80
|
+
for (const item of indexed) {
|
|
81
|
+
const tool = this.registry.get(item.use.name);
|
|
82
|
+
if (tool && !tool.mutating) {
|
|
83
|
+
ro.push(item);
|
|
84
|
+
} else {
|
|
85
|
+
mut.push(item);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const roResults = await Promise.all(ro.map(async ({ use, i }) => ({ i, r: await runOne(use) })));
|
|
90
|
+
const mutResults: Array<{ i: number; r: ToolResult }> = [];
|
|
91
|
+
for (const { use, i } of mut) {
|
|
92
|
+
mutResults.push({ i, r: await runOne(use) });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const all = [...roResults, ...mutResults];
|
|
96
|
+
all.sort((a, b) => a.i - b.i);
|
|
97
|
+
return { results: all.map((o) => o.r), remainingBudget: budget.remaining };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async executeTool(
|
|
101
|
+
use: ToolUse,
|
|
102
|
+
ctx: Context,
|
|
103
|
+
budget: { remaining: number },
|
|
104
|
+
): Promise<ToolResult> {
|
|
105
|
+
const tool = this.registry.get(use.name);
|
|
106
|
+
|
|
107
|
+
if (!tool) {
|
|
108
|
+
const available = this.registry.list().map((t) => t.name).join(', ');
|
|
109
|
+
return this.makeErrorResult(use.id, use.name, `Tool "${use.name}" is not registered. Available tools: ${available}`, budget);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const validation = validateAgainstSchema(use.input, tool.inputSchema);
|
|
113
|
+
if (!validation.ok) {
|
|
114
|
+
const errorDetails = validation.errors
|
|
115
|
+
.map((e) => ` - ${e.path}: ${e.message}`)
|
|
116
|
+
.join('\n');
|
|
117
|
+
return this.makeErrorResult(
|
|
118
|
+
use.id,
|
|
119
|
+
tool.name,
|
|
120
|
+
`Invalid arguments for tool "${tool.name}".\n\nValidation errors:\n${errorDetails}`,
|
|
121
|
+
budget,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.policy) {
|
|
126
|
+
const toolCtxForPolicy: ToolContext = {
|
|
127
|
+
projectRoot: ctx.projectRoot,
|
|
128
|
+
workingDir: ctx.workingDir,
|
|
129
|
+
signal: ctx.signal,
|
|
130
|
+
registerAbortHook: (fn) => ctx.registerAbortHook(fn),
|
|
131
|
+
recordRead: (p, m) => ctx.recordRead(p, m),
|
|
132
|
+
hasRead: (p) => ctx.hasRead(p),
|
|
133
|
+
lastReadMtime: (p) => ctx.readFiles.get(p),
|
|
134
|
+
};
|
|
135
|
+
const decision = await this.policy.evaluate(tool, use.input, toolCtxForPolicy);
|
|
136
|
+
if (decision.permission === 'deny') {
|
|
137
|
+
return this.makeErrorResult(use.id, tool.name, `Tool "${tool.name}" denied: ${decision.reason ?? 'denied by policy'}`, budget);
|
|
138
|
+
}
|
|
139
|
+
if (decision.permission === 'confirm') {
|
|
140
|
+
const reply = await this.confirmAwaiter?.(tool, use.input, use.id, suggestPattern(tool, use.input));
|
|
141
|
+
if (!this.confirmAwaiter) {
|
|
142
|
+
return this.makeErrorResult(use.id, tool.name, `Tool "${tool.name}" requires confirmation (non-interactive mode).`, budget);
|
|
143
|
+
}
|
|
144
|
+
if (reply === 'no') {
|
|
145
|
+
return this.makeErrorResult(use.id, tool.name, `Tool "${tool.name}" denied by user.`, budget);
|
|
146
|
+
}
|
|
147
|
+
if (reply === 'deny') {
|
|
148
|
+
return this.makeErrorResult(use.id, tool.name, `Tool "${tool.name}" denied.`, budget);
|
|
149
|
+
}
|
|
150
|
+
// 'yes' | 'always' → proceed; persistence handled by the awaiter via policy.
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const toolCtx: ToolContext = {
|
|
155
|
+
projectRoot: ctx.projectRoot,
|
|
156
|
+
workingDir: ctx.workingDir,
|
|
157
|
+
signal: ctx.signal,
|
|
158
|
+
registerAbortHook: (fn) => ctx.registerAbortHook(fn),
|
|
159
|
+
recordRead: (p, m) => ctx.recordRead(p, m),
|
|
160
|
+
hasRead: (p) => ctx.hasRead(p),
|
|
161
|
+
lastReadMtime: (p) => ctx.readFiles.get(p),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const timeoutMs = Math.min(tool.timeoutMs ?? this.maxToolTimeoutMs, this.maxToolTimeoutMs);
|
|
165
|
+
const innerController = new AbortController();
|
|
166
|
+
const timer = setTimeout(() => innerController.abort(new Error('Tool timeout')), timeoutMs);
|
|
167
|
+
|
|
168
|
+
const onParentAbort = (): void => innerController.abort(ctx.signal.reason);
|
|
169
|
+
if (ctx.signal.aborted) {
|
|
170
|
+
innerController.abort(ctx.signal.reason);
|
|
171
|
+
} else {
|
|
172
|
+
ctx.signal.addEventListener('abort', onParentAbort, { once: true });
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
let output: unknown;
|
|
177
|
+
if (this.toolStreamingEnabled && typeof tool.executeStream === 'function') {
|
|
178
|
+
output = await this.runStreamedTool(tool, use.input, toolCtx, innerController.signal, use.id);
|
|
179
|
+
} else {
|
|
180
|
+
output = await tool.execute(use.input, toolCtx, { signal: innerController.signal });
|
|
181
|
+
}
|
|
182
|
+
const serialized = this.serializer.serialize(output);
|
|
183
|
+
const { text, newBudget } = this.serializer.enforceCap(serialized, budget.remaining);
|
|
184
|
+
budget.remaining = newBudget;
|
|
185
|
+
const scrubbed = this.scrubber.scrub(text);
|
|
186
|
+
return { toolUseId: use.id, block: { type: 'tool_result', tool_use_id: use.id, content: scrubbed } };
|
|
187
|
+
} catch (err) {
|
|
188
|
+
if (ctx.signal.aborted) {
|
|
189
|
+
return this.makeErrorResult(use.id, tool.name, `Tool "${tool.name}" was aborted.`, budget);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const isTimeout = innerController.signal.aborted && !ctx.signal.aborted;
|
|
193
|
+
const rawMsg = isTimeout
|
|
194
|
+
? `timed out after ${timeoutMs}ms`
|
|
195
|
+
: toErrorMessage(err);
|
|
196
|
+
const scrubbedMsg = this.scrubber.scrub(rawMsg);
|
|
197
|
+
return this.makeErrorResult(use.id, tool.name, `Tool "${tool.name}" threw: ${scrubbedMsg}`, budget);
|
|
198
|
+
} finally {
|
|
199
|
+
if (tool.cleanup) {
|
|
200
|
+
try {
|
|
201
|
+
await tool.cleanup(use.input, toolCtx);
|
|
202
|
+
} catch {
|
|
203
|
+
// best-effort: cleanup errors never mask the real result
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
clearTimeout(timer);
|
|
207
|
+
ctx.signal.removeEventListener('abort', onParentAbort);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async runStreamedTool<I>(
|
|
212
|
+
tool: Tool<I, unknown>,
|
|
213
|
+
input: I,
|
|
214
|
+
toolCtx: ToolContext,
|
|
215
|
+
signal: AbortSignal,
|
|
216
|
+
_toolUseId: string,
|
|
217
|
+
): Promise<unknown> {
|
|
218
|
+
const iter = tool.executeStream!(input, toolCtx, { signal });
|
|
219
|
+
let finalOutput: unknown | undefined;
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
for await (const ev of iter) {
|
|
223
|
+
if (ev.type === 'final') {
|
|
224
|
+
finalOutput = ev.output;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
// partial_output and other events are accumulated for the final result;
|
|
228
|
+
// live progress emission via EventBus is deferred to v0.4 TUI.
|
|
229
|
+
}
|
|
230
|
+
if (finalOutput === undefined) {
|
|
231
|
+
throw new Error(`tool "${tool.name}" executeStream completed without a 'final' event`);
|
|
232
|
+
}
|
|
233
|
+
return finalOutput;
|
|
234
|
+
} finally {
|
|
235
|
+
// best-effort: close the async iterator so the tool's own cleanup runs.
|
|
236
|
+
// AsyncIterable doesn't expose .return() in the type, but AsyncGenerator
|
|
237
|
+
// implementations do — the cast is safe for our use (bash.executeStream).
|
|
238
|
+
try {
|
|
239
|
+
await (iter as { return?: () => Promise<{ done: boolean; value: unknown }> }).return?.();
|
|
240
|
+
} catch {
|
|
241
|
+
// swallow
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private makeErrorResult(
|
|
247
|
+
toolUseId: string,
|
|
248
|
+
_toolName: string,
|
|
249
|
+
message: string,
|
|
250
|
+
budget: { remaining: number },
|
|
251
|
+
): ToolResult {
|
|
252
|
+
const { text, newBudget } = this.serializer.enforceCap(message, budget.remaining);
|
|
253
|
+
budget.remaining = newBudget;
|
|
254
|
+
return {
|
|
255
|
+
toolUseId,
|
|
256
|
+
block: { type: 'tool_result', tool_use_id: toolUseId, content: text, is_error: true },
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function suggestPattern(tool: Tool, input: unknown): string {
|
|
262
|
+
const subjectKey = tool.subjectKey;
|
|
263
|
+
if (!subjectKey) return '*';
|
|
264
|
+
const v = (input as Record<string, unknown> | null)?.[subjectKey];
|
|
265
|
+
if (typeof v !== 'string' || v === '') return '*';
|
|
266
|
+
if (tool.name === 'bash') {
|
|
267
|
+
const firstToken = v.trim().split(/\s+/)[0];
|
|
268
|
+
return firstToken ? `${firstToken} *` : '*';
|
|
269
|
+
}
|
|
270
|
+
if (subjectKey === 'path') {
|
|
271
|
+
const sep = v.lastIndexOf('/');
|
|
272
|
+
const dir = sep > 0 ? v.slice(0, sep) : '';
|
|
273
|
+
return dir ? `${dir}/**` : '*';
|
|
274
|
+
}
|
|
275
|
+
return '*';
|
|
276
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Tool } from '../types/tool.js';
|
|
2
|
+
|
|
3
|
+
export interface RegistryEntry {
|
|
4
|
+
tool: Tool;
|
|
5
|
+
owner: string;
|
|
6
|
+
estDefTokens: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ToolRegistry {
|
|
10
|
+
private tools = new Map<string, RegistryEntry>();
|
|
11
|
+
|
|
12
|
+
register(tool: Tool, owner: string): void {
|
|
13
|
+
if (this.tools.has(tool.name)) {
|
|
14
|
+
throw new Error(`Tool "${tool.name}" is already registered`);
|
|
15
|
+
}
|
|
16
|
+
this.tools.set(tool.name, {
|
|
17
|
+
tool,
|
|
18
|
+
owner,
|
|
19
|
+
estDefTokens: Math.ceil(JSON.stringify(tool.inputSchema).length / 3.5),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
tryRegister(tool: Tool, owner: string): boolean {
|
|
24
|
+
if (this.tools.has(tool.name)) return false;
|
|
25
|
+
this.register(tool, owner);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
registerAll(tools: readonly Tool[], owner: string): void {
|
|
30
|
+
for (const tool of tools) {
|
|
31
|
+
this.tryRegister(tool, owner);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
registerDefault(tool: Tool, owner: string): void {
|
|
36
|
+
if (this.tools.has(tool.name)) return;
|
|
37
|
+
this.register(tool, owner);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override(name: string, tool: Tool, owner: string): void {
|
|
41
|
+
const existing = this.tools.get(name);
|
|
42
|
+
if (!existing) {
|
|
43
|
+
throw new Error(`Tool "${name}" is not registered; cannot override`);
|
|
44
|
+
}
|
|
45
|
+
if (tool.name !== name) {
|
|
46
|
+
throw new Error(`Override tool name "${tool.name}" does not match registry key "${name}"`);
|
|
47
|
+
}
|
|
48
|
+
this.tools.set(name, {
|
|
49
|
+
tool,
|
|
50
|
+
owner,
|
|
51
|
+
estDefTokens: Math.ceil(JSON.stringify(tool.inputSchema).length / 3.5),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get(name: string): Tool | undefined {
|
|
56
|
+
return this.tools.get(name)?.tool;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
list(): Tool[] {
|
|
60
|
+
return [...this.tools.values()].map((e) => e.tool);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
listWithOwner(): Array<{ tool: Tool; owner: string }> {
|
|
64
|
+
return [...this.tools.values()].map((e) => ({ tool: e.tool, owner: e.owner }));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
ownerOf(name: string): string | undefined {
|
|
68
|
+
return this.tools.get(name)?.owner;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
has(name: string): boolean {
|
|
72
|
+
return this.tools.has(name);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get totalEstDefTokens(): number {
|
|
76
|
+
let total = 0;
|
|
77
|
+
for (const entry of this.tools.values()) {
|
|
78
|
+
total += entry.estDefTokens;
|
|
79
|
+
}
|
|
80
|
+
return total;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
filter(predicate: (tool: Tool) => boolean): { total: number; kept: number } {
|
|
84
|
+
const total = this.tools.size;
|
|
85
|
+
for (const [name, entry] of [...this.tools]) {
|
|
86
|
+
if (!predicate(entry.tool)) {
|
|
87
|
+
this.tools.delete(name);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { total, kept: this.tools.size };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
clone(): ToolRegistry {
|
|
94
|
+
const copy = new ToolRegistry();
|
|
95
|
+
for (const [name, entry] of this.tools) {
|
|
96
|
+
copy.tools.set(name, { ...entry });
|
|
97
|
+
}
|
|
98
|
+
return copy;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get size(): number {
|
|
102
|
+
return this.tools.size;
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
const pkgPath = fileURLToPath(new URL('../../../package.json', import.meta.url));
|
|
5
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { version: string; name: string };
|
|
6
|
+
|
|
7
|
+
export const FLOWCODEX_VERSION = pkg.version;
|
|
8
|
+
export const FLOWCODEX_NAME = 'flowcodex' as const;
|
|
9
|
+
|
|
10
|
+
export { toErrorMessage } from './utils/error.js';
|
|
11
|
+
export {
|
|
12
|
+
atomicWrite,
|
|
13
|
+
isBinaryBuffer,
|
|
14
|
+
detectNewlineStyle,
|
|
15
|
+
normalizeToLf,
|
|
16
|
+
toStyle,
|
|
17
|
+
compileGlob,
|
|
18
|
+
unifiedDiff,
|
|
19
|
+
stripAnsi,
|
|
20
|
+
} from './utils/fs.js';
|
|
21
|
+
export type { NewlineStyle, DiffOptions } from './utils/fs.js';
|
|
22
|
+
export {
|
|
23
|
+
FlowCodexError,
|
|
24
|
+
ToolError,
|
|
25
|
+
ConfigError,
|
|
26
|
+
PluginError,
|
|
27
|
+
AgentError,
|
|
28
|
+
PermissionError,
|
|
29
|
+
SessionError,
|
|
30
|
+
FsError,
|
|
31
|
+
toFlowCodexError,
|
|
32
|
+
isFlowCodexError,
|
|
33
|
+
isToolError,
|
|
34
|
+
isConfigError,
|
|
35
|
+
isPluginError,
|
|
36
|
+
isSessionError,
|
|
37
|
+
isAgentError,
|
|
38
|
+
isPermissionError,
|
|
39
|
+
isFsError,
|
|
40
|
+
ERROR_CODES,
|
|
41
|
+
} from './types/errors.js';
|
|
42
|
+
export type { ErrorCode, ErrorSubsystem, ErrorSeverity } from './types/errors.js';
|
|
43
|
+
|
|
44
|
+
export { Container, token, Pipeline, RunController, TOKENS, EventBus, ScopedEventBus } from './kernel/index.js';
|
|
45
|
+
export type {
|
|
46
|
+
Token,
|
|
47
|
+
Factory,
|
|
48
|
+
Decorator,
|
|
49
|
+
BindOptions,
|
|
50
|
+
NextFn,
|
|
51
|
+
MiddlewareHandler,
|
|
52
|
+
Middleware,
|
|
53
|
+
ReadonlyPipeline,
|
|
54
|
+
PipelineErrorHandler,
|
|
55
|
+
PipelineErrorEvent,
|
|
56
|
+
PipelineErrorPolicy,
|
|
57
|
+
PipelineOptions,
|
|
58
|
+
RunControllerOptions,
|
|
59
|
+
EventMap,
|
|
60
|
+
EventName,
|
|
61
|
+
Listener,
|
|
62
|
+
EventLogger,
|
|
63
|
+
} from './kernel/index.js';
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
DefaultPathResolver,
|
|
67
|
+
safeResolve,
|
|
68
|
+
safeResolveReal,
|
|
69
|
+
DefaultRetryPolicy,
|
|
70
|
+
parseRetryAfter,
|
|
71
|
+
NoopTracer,
|
|
72
|
+
NoopSpan,
|
|
73
|
+
DefaultTokenCounter,
|
|
74
|
+
DefaultSecretScrubber,
|
|
75
|
+
DefaultCatalogParser,
|
|
76
|
+
classifyFamily,
|
|
77
|
+
createProvider,
|
|
78
|
+
setProviderConstructors,
|
|
79
|
+
resolveFamily,
|
|
80
|
+
} from './infrastructure/index.js';
|
|
81
|
+
export type {
|
|
82
|
+
PathResolver,
|
|
83
|
+
RetryPolicy,
|
|
84
|
+
ProviderError as InfraProviderError,
|
|
85
|
+
Tracer,
|
|
86
|
+
Span,
|
|
87
|
+
TokenCounter,
|
|
88
|
+
Usage,
|
|
89
|
+
ModelPricing,
|
|
90
|
+
SecretScrubber,
|
|
91
|
+
CatalogParser,
|
|
92
|
+
CatalogEntry,
|
|
93
|
+
ModelInfo,
|
|
94
|
+
WireFamily as CatalogWireFamily,
|
|
95
|
+
ProviderConstructors,
|
|
96
|
+
ProviderConfigEntry,
|
|
97
|
+
CreateProviderOptions,
|
|
98
|
+
FlowCodexLikeConfig,
|
|
99
|
+
} from './infrastructure/index.js';
|
|
100
|
+
export { AgentContext } from './agent/context.js';
|
|
101
|
+
|
|
102
|
+
export type { ContextInit } from './agent/context.js';
|
|
103
|
+
export { DefaultConversationState } from './agent/conversation-state.js';
|
|
104
|
+
export type { ConversationState } from './agent/conversation-state.js';
|
|
105
|
+
export { DefaultSystemPromptBuilder, IDENTITY_PROMPT } from './agent/system-prompt-builder.js';
|
|
106
|
+
export type { SystemPromptBuilder, SystemPromptBuildContext } from './agent/system-prompt-builder.js';
|
|
107
|
+
export { HybridCompactor, MODE_CONFIGS, ACTIVE_MODE, FLOOR_PRESERVE_K, NOOP_RETRY_DELTA_TOKENS, repairToolUseAdjacency } from './execution/compactor.js';
|
|
108
|
+
export type { CompactionMode, ModeConfig, CompactionInput, CompactionResult, Compactor, RepairResult } from './execution/compactor.js';
|
|
109
|
+
export type { TextBlock, CacheControl } from './types/blocks.js';
|
|
110
|
+
export { runProviderWithRetry } from './agent/provider-runner.js';
|
|
111
|
+
export type { RunProviderOptions, FallbackEntry } from './agent/provider-runner.js';
|
|
112
|
+
export { runAgentLoop } from './agent/agent-loop.js';
|
|
113
|
+
export type { AgentLoopOptions } from './agent/agent-loop.js';
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
ToolRegistry,
|
|
117
|
+
createToolOutputSerializer,
|
|
118
|
+
validateAgainstSchema,
|
|
119
|
+
ToolExecutor,
|
|
120
|
+
} from './execution/index.js';
|
|
121
|
+
export type {
|
|
122
|
+
OutputSerializer,
|
|
123
|
+
SerializerOptions,
|
|
124
|
+
CapResult,
|
|
125
|
+
SchemaValidationResult,
|
|
126
|
+
SchemaValidationError,
|
|
127
|
+
ToolExecutorStrategy,
|
|
128
|
+
ToolExecutorOptions,
|
|
129
|
+
ToolBatchResult,
|
|
130
|
+
} from './execution/index.js';
|
|
131
|
+
|
|
132
|
+
export { ulid, ulidTime, isUlid } from './utils/ulid.js';
|
|
133
|
+
|
|
134
|
+
export { isPrivateIp, safeFetch } from './utils/ssrf-guard.js';
|
|
135
|
+
export type { SafeFetchOptions } from './utils/ssrf-guard.js';
|
|
136
|
+
|
|
137
|
+
export { PROVIDER_PRESETS } from './infrastructure/provider-presets.js';
|
|
138
|
+
export type { CompatibilityQuirks } from './infrastructure/provider-presets.js';
|
|
139
|
+
|
|
140
|
+
export { DefaultPermissionPolicy, matchGlob } from './security/permission-policy.js';
|
|
141
|
+
export type {
|
|
142
|
+
PermissionSource,
|
|
143
|
+
PermissionDecision,
|
|
144
|
+
TrustRule,
|
|
145
|
+
TrustFile,
|
|
146
|
+
PermissionPolicy,
|
|
147
|
+
ConfirmAwaiter,
|
|
148
|
+
DefaultPermissionPolicyOptions,
|
|
149
|
+
} from './security/permission-policy.js';
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
DefaultSecretVault,
|
|
154
|
+
ENCRYPTED_PREFIX,
|
|
155
|
+
restrictFilePermissions,
|
|
156
|
+
isSecretField,
|
|
157
|
+
DefaultSessionStore,
|
|
158
|
+
generateSessionId,
|
|
159
|
+
projectHash,
|
|
160
|
+
flowcodexHome,
|
|
161
|
+
sessionsDir,
|
|
162
|
+
recordsDir,
|
|
163
|
+
reconstructMessages,
|
|
164
|
+
keyFilePath,
|
|
165
|
+
configFilePath,
|
|
166
|
+
authFilePath,
|
|
167
|
+
DefaultAuditLog,
|
|
168
|
+
stableStringify,
|
|
169
|
+
DefaultAuthService,
|
|
170
|
+
} from './session/index.js';
|
|
171
|
+
export type {
|
|
172
|
+
SessionEvent,
|
|
173
|
+
SessionSummary,
|
|
174
|
+
AuditEntry,
|
|
175
|
+
VerifyResult,
|
|
176
|
+
SessionStore,
|
|
177
|
+
SecretVault,
|
|
178
|
+
AuditLog,
|
|
179
|
+
SecretVaultOptions,
|
|
180
|
+
SessionStoreOptions,
|
|
181
|
+
AuditLogOptions,
|
|
182
|
+
ApiKeyEntry,
|
|
183
|
+
AuthEntry,
|
|
184
|
+
AuthProviderStatus,
|
|
185
|
+
AuthService,
|
|
186
|
+
AuthServiceOptions,
|
|
187
|
+
} from './session/index.js';
|
|
188
|
+
|
|
189
|
+
export { fetchLatestVersion, compareVersions, checkForUpdate, type VersionInfo } from './utils/version-check.js';
|
|
190
|
+
|
|
191
|
+
export { resizeImageBuffer, type ResizeOptions, type ResizeResult } from './utils/image-resize.js';
|
|
192
|
+
|
|
193
|
+
export type {
|
|
194
|
+
ContentBlock,
|
|
195
|
+
Role,
|
|
196
|
+
Message,
|
|
197
|
+
ModelId,
|
|
198
|
+
ModelRef,
|
|
199
|
+
ToolUse,
|
|
200
|
+
ToolResult,
|
|
201
|
+
Budget,
|
|
202
|
+
AgentStatus,
|
|
203
|
+
RunResult,
|
|
204
|
+
Context,
|
|
205
|
+
Usage as ProviderUsage,
|
|
206
|
+
LLMRequest,
|
|
207
|
+
LLMResponse,
|
|
208
|
+
LLMEvent,
|
|
209
|
+
Provider,
|
|
210
|
+
Tool,
|
|
211
|
+
ToolStreamEvent,
|
|
212
|
+
Permission,
|
|
213
|
+
RiskTier,
|
|
214
|
+
ToolContext,
|
|
215
|
+
} from './types/index.js';
|