@sylphx/flow 1.7.0 → 1.8.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/CHANGELOG.md +78 -0
- package/assets/agents/coder.md +72 -119
- package/assets/agents/orchestrator.md +26 -90
- package/assets/agents/reviewer.md +76 -47
- package/assets/agents/writer.md +82 -63
- package/assets/output-styles/silent.md +141 -8
- package/assets/rules/code-standards.md +9 -33
- package/assets/rules/core.md +67 -59
- package/package.json +2 -12
- package/src/commands/flow/execute.ts +470 -0
- package/src/commands/flow/index.ts +11 -0
- package/src/commands/flow/prompt.ts +35 -0
- package/src/commands/flow/setup.ts +312 -0
- package/src/commands/flow/targets.ts +18 -0
- package/src/commands/flow/types.ts +47 -0
- package/src/commands/flow-command.ts +18 -967
- package/src/commands/flow-orchestrator.ts +14 -5
- package/src/commands/hook-command.ts +1 -1
- package/src/commands/init-core.ts +12 -3
- package/src/commands/run-command.ts +1 -1
- package/src/config/rules.ts +1 -1
- package/src/core/error-handling.ts +1 -1
- package/src/core/loop-controller.ts +1 -1
- package/src/core/state-detector.ts +1 -1
- package/src/core/target-manager.ts +1 -1
- package/src/index.ts +1 -1
- package/src/shared/files/index.ts +1 -1
- package/src/shared/processing/index.ts +1 -1
- package/src/targets/claude-code.ts +3 -3
- package/src/targets/opencode.ts +3 -3
- package/src/utils/agent-enhancer.ts +2 -2
- package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
- package/src/utils/{paths.ts → config/paths.ts} +1 -1
- package/src/utils/{settings.ts → config/settings.ts} +1 -1
- package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
- package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
- package/src/utils/display/banner.ts +25 -0
- package/src/utils/display/status.ts +55 -0
- package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
- package/src/utils/files/jsonc.ts +36 -0
- package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
- package/src/utils/index.ts +42 -61
- package/src/utils/version.ts +47 -0
- package/src/components/benchmark-monitor.tsx +0 -331
- package/src/components/reindex-progress.tsx +0 -261
- package/src/composables/functional/index.ts +0 -14
- package/src/composables/functional/useEnvironment.ts +0 -171
- package/src/composables/functional/useFileSystem.ts +0 -139
- package/src/composables/index.ts +0 -4
- package/src/composables/useEnv.ts +0 -13
- package/src/composables/useRuntimeConfig.ts +0 -27
- package/src/core/ai-sdk.ts +0 -603
- package/src/core/app-factory.ts +0 -381
- package/src/core/builtin-agents.ts +0 -9
- package/src/core/command-system.ts +0 -550
- package/src/core/config-system.ts +0 -550
- package/src/core/connection-pool.ts +0 -390
- package/src/core/di-container.ts +0 -155
- package/src/core/headless-display.ts +0 -96
- package/src/core/interfaces/index.ts +0 -22
- package/src/core/interfaces/repository.interface.ts +0 -91
- package/src/core/interfaces/service.interface.ts +0 -133
- package/src/core/interfaces.ts +0 -96
- package/src/core/result.ts +0 -351
- package/src/core/service-config.ts +0 -252
- package/src/core/session-service.ts +0 -121
- package/src/core/storage-factory.ts +0 -115
- package/src/core/stream-handler.ts +0 -288
- package/src/core/type-utils.ts +0 -427
- package/src/core/unified-storage.ts +0 -456
- package/src/core/validation/limit.ts +0 -46
- package/src/core/validation/query.ts +0 -20
- package/src/db/auto-migrate.ts +0 -322
- package/src/db/base-database-client.ts +0 -144
- package/src/db/cache-db.ts +0 -218
- package/src/db/cache-schema.ts +0 -75
- package/src/db/database.ts +0 -70
- package/src/db/index.ts +0 -252
- package/src/db/memory-db.ts +0 -153
- package/src/db/memory-schema.ts +0 -29
- package/src/db/schema.ts +0 -289
- package/src/db/session-repository.ts +0 -733
- package/src/domains/index.ts +0 -6
- package/src/domains/utilities/index.ts +0 -6
- package/src/domains/utilities/time/index.ts +0 -5
- package/src/domains/utilities/time/tools.ts +0 -291
- package/src/services/agent-service.ts +0 -273
- package/src/services/evaluation-service.ts +0 -271
- package/src/services/functional/evaluation-logic.ts +0 -296
- package/src/services/functional/file-processor.ts +0 -273
- package/src/services/functional/index.ts +0 -12
- package/src/services/memory.service.ts +0 -476
- package/src/types/api/batch.ts +0 -108
- package/src/types/api/errors.ts +0 -118
- package/src/types/api/index.ts +0 -55
- package/src/types/api/requests.ts +0 -76
- package/src/types/api/responses.ts +0 -180
- package/src/types/api/websockets.ts +0 -85
- package/src/types/benchmark.ts +0 -49
- package/src/types/database.types.ts +0 -510
- package/src/types/memory-types.ts +0 -63
- package/src/utils/advanced-tokenizer.ts +0 -191
- package/src/utils/ai-model-fetcher.ts +0 -19
- package/src/utils/async-file-operations.ts +0 -516
- package/src/utils/audio-player.ts +0 -345
- package/src/utils/codebase-helpers.ts +0 -211
- package/src/utils/console-ui.ts +0 -79
- package/src/utils/database-errors.ts +0 -140
- package/src/utils/debug-logger.ts +0 -49
- package/src/utils/file-scanner.ts +0 -259
- package/src/utils/help.ts +0 -20
- package/src/utils/immutable-cache.ts +0 -106
- package/src/utils/jsonc.ts +0 -158
- package/src/utils/memory-tui.ts +0 -414
- package/src/utils/models-dev.ts +0 -91
- package/src/utils/parallel-operations.ts +0 -487
- package/src/utils/process-manager.ts +0 -155
- package/src/utils/prompts.ts +0 -120
- package/src/utils/search-tool-builder.ts +0 -214
- package/src/utils/session-manager.ts +0 -168
- package/src/utils/session-title.ts +0 -87
- package/src/utils/simplified-errors.ts +0 -410
- package/src/utils/template-engine.ts +0 -94
- package/src/utils/test-audio.ts +0 -71
- package/src/utils/todo-context.ts +0 -46
- package/src/utils/token-counter.ts +0 -288
- /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
- /package/src/utils/{logger.ts → display/logger.ts} +0 -0
- /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
- /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
- /package/src/utils/{security.ts → security/security.ts} +0 -0
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stream Handler
|
|
3
|
-
* Unified stream processing for both headless and TUI modes
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { StreamChunk } from './ai-sdk.js';
|
|
7
|
-
import type { MessagePart, TokenUsage } from '../types/session.types.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Callbacks for stream events
|
|
11
|
-
*/
|
|
12
|
-
export interface StreamCallbacks {
|
|
13
|
-
onTextStart?: () => void;
|
|
14
|
-
onTextDelta?: (text: string) => void;
|
|
15
|
-
onTextEnd?: () => void;
|
|
16
|
-
onReasoningStart?: () => void;
|
|
17
|
-
onReasoningDelta?: (text: string) => void;
|
|
18
|
-
onReasoningEnd?: (duration: number) => void;
|
|
19
|
-
onToolCall?: (toolCallId: string, toolName: string, args: unknown) => void;
|
|
20
|
-
onToolInputStart?: (toolCallId: string, toolName: string) => void;
|
|
21
|
-
onToolInputDelta?: (toolCallId: string, toolName: string, argsTextDelta: string) => void;
|
|
22
|
-
onToolInputEnd?: (toolCallId: string, toolName: string, args: unknown) => void;
|
|
23
|
-
onToolResult?: (toolCallId: string, toolName: string, result: unknown, duration: number) => void;
|
|
24
|
-
onToolError?: (toolCallId: string, toolName: string, error: string, duration: number) => void;
|
|
25
|
-
onAbort?: () => void;
|
|
26
|
-
onError?: (error: string) => void;
|
|
27
|
-
onFinish?: (usage: TokenUsage, finishReason: string) => void;
|
|
28
|
-
onComplete?: () => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Stream processing result
|
|
33
|
-
*/
|
|
34
|
-
export interface StreamResult {
|
|
35
|
-
fullResponse: string;
|
|
36
|
-
messageParts: MessagePart[];
|
|
37
|
-
usage?: TokenUsage;
|
|
38
|
-
finishReason?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Process AI stream and collect response with parts
|
|
43
|
-
*/
|
|
44
|
-
export async function processStream(
|
|
45
|
-
stream: AsyncIterable<StreamChunk>,
|
|
46
|
-
callbacks: StreamCallbacks = {}
|
|
47
|
-
): Promise<StreamResult> {
|
|
48
|
-
const { onTextStart, onTextDelta, onTextEnd, onReasoningStart, onReasoningDelta, onReasoningEnd, onToolCall, onToolInputStart, onToolInputDelta, onToolInputEnd, onToolResult, onToolError, onAbort, onError, onFinish, onComplete } = callbacks;
|
|
49
|
-
|
|
50
|
-
let fullResponse = '';
|
|
51
|
-
const messageParts: MessagePart[] = [];
|
|
52
|
-
const activeTools = new Map<string, { name: string; startTime: number; args: unknown }>();
|
|
53
|
-
let currentTextContent = '';
|
|
54
|
-
let currentReasoningContent = '';
|
|
55
|
-
let reasoningStartTime: number | null = null;
|
|
56
|
-
let usage: TokenUsage | undefined;
|
|
57
|
-
let finishReason: string | undefined;
|
|
58
|
-
|
|
59
|
-
for await (const chunk of stream) {
|
|
60
|
-
switch (chunk.type) {
|
|
61
|
-
case 'text-start': {
|
|
62
|
-
// Text generation started - notify immediately
|
|
63
|
-
onTextStart?.();
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
case 'text-delta': {
|
|
68
|
-
fullResponse += chunk.textDelta;
|
|
69
|
-
currentTextContent += chunk.textDelta;
|
|
70
|
-
onTextDelta?.(chunk.textDelta);
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
case 'text-end': {
|
|
75
|
-
// Text generation finished - save text part if any
|
|
76
|
-
if (currentTextContent) {
|
|
77
|
-
messageParts.push({ type: 'text', content: currentTextContent, status: 'completed' });
|
|
78
|
-
currentTextContent = '';
|
|
79
|
-
}
|
|
80
|
-
onTextEnd?.();
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
case 'reasoning-start': {
|
|
85
|
-
// Save current text part if any
|
|
86
|
-
if (currentTextContent) {
|
|
87
|
-
messageParts.push({ type: 'text', content: currentTextContent, status: 'completed' });
|
|
88
|
-
currentTextContent = '';
|
|
89
|
-
}
|
|
90
|
-
reasoningStartTime = Date.now();
|
|
91
|
-
onReasoningStart?.();
|
|
92
|
-
break;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
case 'reasoning-delta': {
|
|
96
|
-
currentReasoningContent += chunk.textDelta;
|
|
97
|
-
onReasoningDelta?.(chunk.textDelta);
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
case 'reasoning-end': {
|
|
102
|
-
// Save reasoning part with duration
|
|
103
|
-
const duration = reasoningStartTime ? Date.now() - reasoningStartTime : 0;
|
|
104
|
-
if (currentReasoningContent || reasoningStartTime) {
|
|
105
|
-
messageParts.push({
|
|
106
|
-
type: 'reasoning',
|
|
107
|
-
content: currentReasoningContent,
|
|
108
|
-
status: 'completed', // All saved parts are completed
|
|
109
|
-
duration
|
|
110
|
-
});
|
|
111
|
-
currentReasoningContent = '';
|
|
112
|
-
reasoningStartTime = null;
|
|
113
|
-
}
|
|
114
|
-
// Pass duration to callback so UI can display it
|
|
115
|
-
onReasoningEnd?.(duration);
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
case 'tool-call': {
|
|
120
|
-
// Save current text part if any
|
|
121
|
-
if (currentTextContent) {
|
|
122
|
-
messageParts.push({ type: 'text', content: currentTextContent, status: 'completed' });
|
|
123
|
-
currentTextContent = '';
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Add tool part (may not have complete args yet if streaming)
|
|
127
|
-
messageParts.push({
|
|
128
|
-
type: 'tool',
|
|
129
|
-
toolId: chunk.toolCallId,
|
|
130
|
-
name: chunk.toolName,
|
|
131
|
-
status: 'active', // Match MessagePart type
|
|
132
|
-
args: chunk.args,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Track tool start time
|
|
136
|
-
activeTools.set(chunk.toolCallId, {
|
|
137
|
-
name: chunk.toolName,
|
|
138
|
-
startTime: Date.now(),
|
|
139
|
-
args: chunk.args,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
onToolCall?.(chunk.toolCallId, chunk.toolName, chunk.args);
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
case 'tool-input-start': {
|
|
147
|
-
// Tool input streaming started - notify callback
|
|
148
|
-
onToolInputStart?.(chunk.toolCallId, chunk.toolName);
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
case 'tool-input-delta': {
|
|
153
|
-
// Update tool args as they stream in
|
|
154
|
-
// Find the active tool part and update its args
|
|
155
|
-
const toolPart = messageParts.find(
|
|
156
|
-
(p) => p.type === 'tool' && p.name === chunk.toolName && p.status === 'active'
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
if (toolPart && toolPart.type === 'tool') {
|
|
160
|
-
// Append args delta (args are streaming as JSON text)
|
|
161
|
-
const currentArgsText = typeof toolPart.args === 'string' ? toolPart.args : '';
|
|
162
|
-
toolPart.args = currentArgsText + chunk.argsTextDelta;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Notify callback for real-time UI update
|
|
166
|
-
onToolInputDelta?.(chunk.toolCallId, chunk.toolName, chunk.argsTextDelta);
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
case 'tool-input-end': {
|
|
171
|
-
// Tool input streaming complete - args are ready
|
|
172
|
-
// Find tool part to get final args
|
|
173
|
-
const toolPart = messageParts.find(
|
|
174
|
-
(p) => p.type === 'tool' && p.name === chunk.toolName && p.status === 'active'
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
if (toolPart && toolPart.type === 'tool') {
|
|
178
|
-
onToolInputEnd?.(chunk.toolCallId, chunk.toolName, toolPart.args);
|
|
179
|
-
}
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
case 'tool-result': {
|
|
184
|
-
const tool = activeTools.get(chunk.toolCallId);
|
|
185
|
-
if (tool) {
|
|
186
|
-
const duration = Date.now() - tool.startTime;
|
|
187
|
-
activeTools.delete(chunk.toolCallId);
|
|
188
|
-
|
|
189
|
-
// Update tool part status and result
|
|
190
|
-
const toolPart = messageParts.find(
|
|
191
|
-
(p) => p.type === 'tool' && p.name === chunk.toolName && p.status === 'active'
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
if (toolPart && toolPart.type === 'tool') {
|
|
195
|
-
toolPart.status = 'completed';
|
|
196
|
-
toolPart.duration = duration;
|
|
197
|
-
toolPart.result = chunk.result;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
onToolResult?.(chunk.toolCallId, chunk.toolName, chunk.result, duration);
|
|
201
|
-
}
|
|
202
|
-
break;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
case 'tool-error': {
|
|
206
|
-
// Save current text part if any
|
|
207
|
-
if (currentTextContent) {
|
|
208
|
-
messageParts.push({ type: 'text', content: currentTextContent, status: 'completed' });
|
|
209
|
-
currentTextContent = '';
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const tool = activeTools.get(chunk.toolCallId);
|
|
213
|
-
if (tool) {
|
|
214
|
-
const duration = Date.now() - tool.startTime;
|
|
215
|
-
activeTools.delete(chunk.toolCallId);
|
|
216
|
-
|
|
217
|
-
// Update tool part status and error
|
|
218
|
-
const toolPart = messageParts.find(
|
|
219
|
-
(p) => p.type === 'tool' && p.name === chunk.toolName && p.status === 'active'
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
if (toolPart && toolPart.type === 'tool') {
|
|
223
|
-
toolPart.status = 'error';
|
|
224
|
-
toolPart.duration = duration;
|
|
225
|
-
toolPart.error = chunk.error;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Notify callback
|
|
229
|
-
onToolError?.(chunk.toolCallId, chunk.toolName, chunk.error, duration);
|
|
230
|
-
}
|
|
231
|
-
break;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
case 'abort': {
|
|
235
|
-
// Save current text part if any
|
|
236
|
-
if (currentTextContent) {
|
|
237
|
-
messageParts.push({ type: 'text', content: currentTextContent, status: 'completed' });
|
|
238
|
-
currentTextContent = '';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Mark all active parts as 'abort'
|
|
242
|
-
messageParts.forEach(part => {
|
|
243
|
-
if (part.status === 'active') {
|
|
244
|
-
part.status = 'abort';
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// Notify callback
|
|
249
|
-
onAbort?.();
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
case 'error': {
|
|
254
|
-
// Save current text part if any
|
|
255
|
-
if (currentTextContent) {
|
|
256
|
-
messageParts.push({ type: 'text', content: currentTextContent, status: 'completed' });
|
|
257
|
-
currentTextContent = '';
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Add error part
|
|
261
|
-
messageParts.push({ type: 'error', error: chunk.error, status: 'completed' });
|
|
262
|
-
|
|
263
|
-
// Notify callback
|
|
264
|
-
onError?.(chunk.error);
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
case 'finish': {
|
|
269
|
-
usage = chunk.usage;
|
|
270
|
-
finishReason = chunk.finishReason;
|
|
271
|
-
onFinish?.(chunk.usage, chunk.finishReason);
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Save final text part if any
|
|
278
|
-
if (currentTextContent) {
|
|
279
|
-
messageParts.push({ type: 'text', content: currentTextContent, status: 'completed' });
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
fullResponse,
|
|
284
|
-
messageParts,
|
|
285
|
-
usage,
|
|
286
|
-
finishReason,
|
|
287
|
-
};
|
|
288
|
-
}
|
package/src/core/type-utils.ts
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type Utilities - 類型安全工具
|
|
3
|
-
* Functional approach to type safety and validation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { z } from 'zod';
|
|
7
|
-
|
|
8
|
-
// Re-export unified Result type and utilities
|
|
9
|
-
export {
|
|
10
|
-
Result,
|
|
11
|
-
AsyncResult,
|
|
12
|
-
ok,
|
|
13
|
-
err,
|
|
14
|
-
isOk,
|
|
15
|
-
isErr,
|
|
16
|
-
map,
|
|
17
|
-
flatMap,
|
|
18
|
-
mapError,
|
|
19
|
-
getOrElse,
|
|
20
|
-
getOrElseLazy,
|
|
21
|
-
match,
|
|
22
|
-
unwrap,
|
|
23
|
-
tryCatch,
|
|
24
|
-
tryCatchAsync,
|
|
25
|
-
safeAsync,
|
|
26
|
-
safeSync,
|
|
27
|
-
all,
|
|
28
|
-
allAsync,
|
|
29
|
-
tap,
|
|
30
|
-
tapError,
|
|
31
|
-
type SuccessType,
|
|
32
|
-
type ErrorType,
|
|
33
|
-
type SafeResult,
|
|
34
|
-
} from './result.js';
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Common validation schemas
|
|
38
|
-
*/
|
|
39
|
-
export const Schemas = {
|
|
40
|
-
/** Non-empty string validation */
|
|
41
|
-
nonEmptyString: z.string().min(1, 'String cannot be empty'),
|
|
42
|
-
|
|
43
|
-
/** Optional non-empty string */
|
|
44
|
-
optionalNonEmptyString: z.string().min(1).optional(),
|
|
45
|
-
|
|
46
|
-
/** Positive number validation */
|
|
47
|
-
positiveNumber: z.number().positive('Number must be positive'),
|
|
48
|
-
|
|
49
|
-
/** Optional positive number */
|
|
50
|
-
optionalPositiveNumber: z.number().positive().optional(),
|
|
51
|
-
|
|
52
|
-
/** Array validation */
|
|
53
|
-
nonEmptyArray: z.array(z.any()).min(1, 'Array cannot be empty'),
|
|
54
|
-
|
|
55
|
-
/** Email validation */
|
|
56
|
-
email: z.string().email('Invalid email format'),
|
|
57
|
-
|
|
58
|
-
/** URL validation */
|
|
59
|
-
url: z.string().url('Invalid URL format'),
|
|
60
|
-
|
|
61
|
-
/** Object validation */
|
|
62
|
-
object: z.object({}).passthrough(),
|
|
63
|
-
|
|
64
|
-
/** Storage configuration */
|
|
65
|
-
storageConfig: z.object({
|
|
66
|
-
type: z.enum(['memory', 'cache', 'vector', 'drizzle']),
|
|
67
|
-
connectionString: z.string().optional(),
|
|
68
|
-
defaultTTL: z.number().positive().optional(),
|
|
69
|
-
maxCacheSize: z.number().positive().optional(),
|
|
70
|
-
vectorDimensions: z.number().positive().optional(),
|
|
71
|
-
storageDir: z.string().optional(),
|
|
72
|
-
}),
|
|
73
|
-
|
|
74
|
-
/** CLI options */
|
|
75
|
-
cliOptions: z.object({
|
|
76
|
-
target: z.string().optional(),
|
|
77
|
-
verbose: z.boolean().default(false),
|
|
78
|
-
dryRun: z.boolean().default(false),
|
|
79
|
-
clear: z.boolean().default(false),
|
|
80
|
-
mcp: z.union([z.array(z.string()), z.boolean(), z.null()]).default(null),
|
|
81
|
-
quiet: z.boolean().default(false),
|
|
82
|
-
agent: z.string().optional(),
|
|
83
|
-
}),
|
|
84
|
-
} as const;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Type guards
|
|
88
|
-
*/
|
|
89
|
-
export const TypeGuards = {
|
|
90
|
-
/** Check if value is a non-empty string */
|
|
91
|
-
isNonEmptyString: (value: unknown): value is string => {
|
|
92
|
-
return typeof value === 'string' && value.length > 0;
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
/** Check if value is a positive number */
|
|
96
|
-
isPositiveNumber: (value: unknown): value is number => {
|
|
97
|
-
return typeof value === 'number' && value > 0;
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
/** Check if value is a non-empty array */
|
|
101
|
-
isNonEmptyArray: (value: unknown): value is unknown[] => {
|
|
102
|
-
return Array.isArray(value) && value.length > 0;
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
/** Check if value is an object */
|
|
106
|
-
isObject: (value: unknown): value is Record<string, unknown> => {
|
|
107
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
/** Check if value is a function */
|
|
111
|
-
isFunction: (value: unknown): value is Function => {
|
|
112
|
-
return typeof value === 'function';
|
|
113
|
-
},
|
|
114
|
-
|
|
115
|
-
/** Check if value is a Date */
|
|
116
|
-
isDate: (value: unknown): value is Date => {
|
|
117
|
-
return value instanceof Date && !isNaN(value.getTime());
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
/** Check if value is a Buffer */
|
|
121
|
-
isBuffer: (value: unknown): value is Buffer => {
|
|
122
|
-
return Buffer.isBuffer(value);
|
|
123
|
-
},
|
|
124
|
-
} as const;
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Safe parsing utilities
|
|
128
|
-
*/
|
|
129
|
-
export const SafeParse = {
|
|
130
|
-
/** Parse JSON safely */
|
|
131
|
-
json: <T = unknown>(str: string): Result<T> => {
|
|
132
|
-
return safeSync(() => JSON.parse(str) as T,
|
|
133
|
-
error => new Error(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`));
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
/** Parse number safely */
|
|
137
|
-
number: (str: string, radix = 10): Result<number> => {
|
|
138
|
-
return safeSync(() => {
|
|
139
|
-
const num = parseInt(str, radix);
|
|
140
|
-
if (isNaN(num)) throw new Error(`Invalid number: ${str}`);
|
|
141
|
-
return num;
|
|
142
|
-
}, error => new Error(`Failed to parse number: ${error instanceof Error ? error.message : String(error)}`));
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
/** Parse float safely */
|
|
146
|
-
float: (str: string): Result<number> => {
|
|
147
|
-
return safeSync(() => {
|
|
148
|
-
const num = parseFloat(str);
|
|
149
|
-
if (isNaN(num)) throw new Error(`Invalid float: ${str}`);
|
|
150
|
-
return num;
|
|
151
|
-
}, error => new Error(`Failed to parse float: ${error instanceof Error ? error.message : String(error)}`));
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
/** Parse boolean safely */
|
|
155
|
-
boolean: (str: string): Result<boolean> => {
|
|
156
|
-
const lower = str.toLowerCase();
|
|
157
|
-
if (['true', '1', 'yes', 'on'].includes(lower)) return ok(true);
|
|
158
|
-
if (['false', '0', 'no', 'off'].includes(lower)) return ok(false);
|
|
159
|
-
return err(new Error(`Invalid boolean: ${str}`));
|
|
160
|
-
},
|
|
161
|
-
} as const;
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* String utilities
|
|
165
|
-
*/
|
|
166
|
-
export const StringUtils = {
|
|
167
|
-
/** Truncate string to max length */
|
|
168
|
-
truncate: (str: string, maxLength: number, suffix = '...'): string => {
|
|
169
|
-
if (str.length <= maxLength) return str;
|
|
170
|
-
return str.slice(0, maxLength - suffix.length) + suffix;
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
/** Convert to kebab-case */
|
|
174
|
-
kebabCase: (str: string): string => {
|
|
175
|
-
return str
|
|
176
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
177
|
-
.replace(/[\s_]+/g, '-')
|
|
178
|
-
.toLowerCase();
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
/** Convert to camelCase */
|
|
182
|
-
camelCase: (str: string): string => {
|
|
183
|
-
return str
|
|
184
|
-
.replace(/[-_\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
|
|
185
|
-
.replace(/^[A-Z]/, char => char.toLowerCase());
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
/** Convert to PascalCase */
|
|
189
|
-
pascalCase: (str: string): string => {
|
|
190
|
-
const camel = StringUtils.camelCase(str);
|
|
191
|
-
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
/** Capitalize first letter */
|
|
195
|
-
capitalize: (str: string): string => {
|
|
196
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
197
|
-
},
|
|
198
|
-
|
|
199
|
-
/** Check if string is empty or whitespace */
|
|
200
|
-
isEmpty: (str: string): boolean => {
|
|
201
|
-
return str.trim().length === 0;
|
|
202
|
-
},
|
|
203
|
-
|
|
204
|
-
/** Generate random string */
|
|
205
|
-
random: (length = 8): string => {
|
|
206
|
-
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
207
|
-
let result = '';
|
|
208
|
-
for (let i = 0; i < length; i++) {
|
|
209
|
-
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
210
|
-
}
|
|
211
|
-
return result;
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
/** Escape regex special characters */
|
|
215
|
-
escapeRegex: (str: string): string => {
|
|
216
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
217
|
-
},
|
|
218
|
-
} as const;
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Array utilities
|
|
222
|
-
*/
|
|
223
|
-
export const ArrayUtils = {
|
|
224
|
-
/** Check if array is empty */
|
|
225
|
-
isEmpty: <T>(arr: T[]): boolean => arr.length === 0,
|
|
226
|
-
|
|
227
|
-
/** Remove duplicates */
|
|
228
|
-
unique: <T>(arr: T[]): T[] => [...new Set(arr)],
|
|
229
|
-
|
|
230
|
-
/** Group array by key */
|
|
231
|
-
groupBy: <T, K extends string | number>(
|
|
232
|
-
arr: T[],
|
|
233
|
-
keyFn: (item: T) => K
|
|
234
|
-
): Record<K, T[]> => {
|
|
235
|
-
return arr.reduce((groups, item) => {
|
|
236
|
-
const key = keyFn(item);
|
|
237
|
-
if (!groups[key]) groups[key] = [];
|
|
238
|
-
groups[key].push(item);
|
|
239
|
-
return groups;
|
|
240
|
-
}, {} as Record<K, T[]>);
|
|
241
|
-
},
|
|
242
|
-
|
|
243
|
-
/** Chunk array into smaller arrays */
|
|
244
|
-
chunk: <T>(arr: T[], size: number): T[][] => {
|
|
245
|
-
const chunks: T[][] = [];
|
|
246
|
-
for (let i = 0; i < arr.length; i += size) {
|
|
247
|
-
chunks.push(arr.slice(i, i + size));
|
|
248
|
-
}
|
|
249
|
-
return chunks;
|
|
250
|
-
},
|
|
251
|
-
|
|
252
|
-
/** Flatten nested arrays */
|
|
253
|
-
flatten: <T>(arr: (T | T[])[]): T[] => {
|
|
254
|
-
return arr.reduce<T[]>((flat, item) => {
|
|
255
|
-
return flat.concat(Array.isArray(item) ? ArrayUtils.flatten(item) : item);
|
|
256
|
-
}, []);
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
/** Pick random element */
|
|
260
|
-
sample: <T>(arr: T[]): T | undefined => {
|
|
261
|
-
return arr[Math.floor(Math.random() * arr.length)];
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
/** Shuffle array */
|
|
265
|
-
shuffle: <T>(arr: T[]): T[] => {
|
|
266
|
-
const shuffled = [...arr];
|
|
267
|
-
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
268
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
269
|
-
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
270
|
-
}
|
|
271
|
-
return shuffled;
|
|
272
|
-
},
|
|
273
|
-
} as const;
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Object utilities
|
|
277
|
-
*/
|
|
278
|
-
export const ObjectUtils = {
|
|
279
|
-
/** Check if object is empty */
|
|
280
|
-
isEmpty: (obj: Record<string, unknown>): boolean => {
|
|
281
|
-
return Object.keys(obj).length === 0;
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
/** Pick specific keys from object */
|
|
285
|
-
pick: <T extends Record<string, unknown>, K extends keyof T>(
|
|
286
|
-
obj: T,
|
|
287
|
-
keys: K[]
|
|
288
|
-
): Pick<T, K> => {
|
|
289
|
-
return keys.reduce((picked, key) => {
|
|
290
|
-
if (key in obj) {
|
|
291
|
-
picked[key] = obj[key];
|
|
292
|
-
}
|
|
293
|
-
return picked;
|
|
294
|
-
}, {} as Pick<T, K>);
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
/** Omit specific keys from object */
|
|
298
|
-
omit: <T extends Record<string, unknown>, K extends keyof T>(
|
|
299
|
-
obj: T,
|
|
300
|
-
keys: K[]
|
|
301
|
-
): Omit<T, K> => {
|
|
302
|
-
const result = { ...obj };
|
|
303
|
-
keys.forEach(key => delete result[key]);
|
|
304
|
-
return result as Omit<T, K>;
|
|
305
|
-
},
|
|
306
|
-
|
|
307
|
-
/** Deep clone object */
|
|
308
|
-
deepClone: <T>(obj: T): T => {
|
|
309
|
-
return JSON.parse(JSON.stringify(obj));
|
|
310
|
-
},
|
|
311
|
-
|
|
312
|
-
/** Merge objects */
|
|
313
|
-
merge: <T extends Record<string, unknown>>(
|
|
314
|
-
...objects: Partial<T>[]
|
|
315
|
-
): T => {
|
|
316
|
-
return objects.reduce((merged, obj) => ({ ...merged, ...obj }), {} as T);
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
/** Get nested value from object */
|
|
320
|
-
get: (obj: any, path: string, defaultValue?: unknown): unknown => {
|
|
321
|
-
const keys = path.split('.');
|
|
322
|
-
let current = obj;
|
|
323
|
-
|
|
324
|
-
for (const key of keys) {
|
|
325
|
-
if (current == null || typeof current !== 'object') {
|
|
326
|
-
return defaultValue;
|
|
327
|
-
}
|
|
328
|
-
current = current[key];
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return current !== undefined ? current : defaultValue;
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
/** Set nested value in object */
|
|
335
|
-
set: (obj: any, path: string, value: unknown): void => {
|
|
336
|
-
const keys = path.split('.');
|
|
337
|
-
let current = obj;
|
|
338
|
-
|
|
339
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
340
|
-
const key = keys[i];
|
|
341
|
-
if (!(key in current) || typeof current[key] !== 'object') {
|
|
342
|
-
current[key] = {};
|
|
343
|
-
}
|
|
344
|
-
current = current[key];
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
current[keys[keys.length - 1]] = value;
|
|
348
|
-
},
|
|
349
|
-
} as const;
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Function utilities
|
|
353
|
-
*/
|
|
354
|
-
export const FunctionUtils = {
|
|
355
|
-
/** Debounce function */
|
|
356
|
-
debounce: <T extends (...args: any[]) => any>(
|
|
357
|
-
fn: T,
|
|
358
|
-
delay: number
|
|
359
|
-
): ((...args: Parameters<T>) => void) => {
|
|
360
|
-
let timeoutId: NodeJS.Timeout;
|
|
361
|
-
return (...args: Parameters<T>) => {
|
|
362
|
-
clearTimeout(timeoutId);
|
|
363
|
-
timeoutId = setTimeout(() => fn(...args), delay);
|
|
364
|
-
};
|
|
365
|
-
},
|
|
366
|
-
|
|
367
|
-
/** Throttle function */
|
|
368
|
-
throttle: <T extends (...args: any[]) => any>(
|
|
369
|
-
fn: T,
|
|
370
|
-
delay: number
|
|
371
|
-
): ((...args: Parameters<T>) => void) => {
|
|
372
|
-
let lastCall = 0;
|
|
373
|
-
return (...args: Parameters<T>) => {
|
|
374
|
-
const now = Date.now();
|
|
375
|
-
if (now - lastCall >= delay) {
|
|
376
|
-
lastCall = now;
|
|
377
|
-
fn(...args);
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
},
|
|
381
|
-
|
|
382
|
-
/** Memoize function */
|
|
383
|
-
memoize: <T extends (...args: any[]) => any>(
|
|
384
|
-
fn: T,
|
|
385
|
-
keyFn?: (...args: Parameters<T>) => string
|
|
386
|
-
): T => {
|
|
387
|
-
const cache = new Map<string, ReturnType<T>>();
|
|
388
|
-
|
|
389
|
-
return ((...args: Parameters<T>) => {
|
|
390
|
-
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
|
|
391
|
-
|
|
392
|
-
if (cache.has(key)) {
|
|
393
|
-
return cache.get(key);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const result = fn(...args);
|
|
397
|
-
cache.set(key, result);
|
|
398
|
-
return result;
|
|
399
|
-
}) as T;
|
|
400
|
-
},
|
|
401
|
-
|
|
402
|
-
/** Retry function */
|
|
403
|
-
retry: async <T>(
|
|
404
|
-
fn: () => Promise<T>,
|
|
405
|
-
maxAttempts = 3,
|
|
406
|
-
delay = 1000
|
|
407
|
-
): Promise<T> => {
|
|
408
|
-
let lastError: Error;
|
|
409
|
-
|
|
410
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
411
|
-
try {
|
|
412
|
-
return await fn();
|
|
413
|
-
} catch (error) {
|
|
414
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
415
|
-
|
|
416
|
-
if (attempt === maxAttempts) {
|
|
417
|
-
throw lastError;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Exponential backoff
|
|
421
|
-
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt - 1)));
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
throw lastError!;
|
|
426
|
-
},
|
|
427
|
-
} as const;
|