@sylphx/flow 1.8.0 → 1.8.2

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.
Files changed (126) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/assets/output-styles/silent.md +145 -8
  3. package/assets/rules/core.md +19 -2
  4. package/package.json +2 -12
  5. package/src/commands/flow/execute.ts +470 -0
  6. package/src/commands/flow/index.ts +11 -0
  7. package/src/commands/flow/prompt.ts +35 -0
  8. package/src/commands/flow/setup.ts +312 -0
  9. package/src/commands/flow/targets.ts +18 -0
  10. package/src/commands/flow/types.ts +47 -0
  11. package/src/commands/flow-command.ts +18 -967
  12. package/src/commands/flow-orchestrator.ts +14 -5
  13. package/src/commands/hook-command.ts +1 -1
  14. package/src/commands/init-core.ts +12 -3
  15. package/src/commands/run-command.ts +1 -1
  16. package/src/config/rules.ts +1 -1
  17. package/src/core/error-handling.ts +1 -1
  18. package/src/core/loop-controller.ts +1 -1
  19. package/src/core/state-detector.ts +1 -1
  20. package/src/core/target-manager.ts +1 -1
  21. package/src/index.ts +1 -1
  22. package/src/shared/files/index.ts +1 -1
  23. package/src/shared/processing/index.ts +1 -1
  24. package/src/targets/claude-code.ts +3 -3
  25. package/src/targets/opencode.ts +3 -3
  26. package/src/utils/agent-enhancer.ts +2 -2
  27. package/src/utils/{mcp-config.ts → config/mcp-config.ts} +4 -4
  28. package/src/utils/{paths.ts → config/paths.ts} +1 -1
  29. package/src/utils/{settings.ts → config/settings.ts} +1 -1
  30. package/src/utils/{target-config.ts → config/target-config.ts} +5 -5
  31. package/src/utils/{target-utils.ts → config/target-utils.ts} +3 -3
  32. package/src/utils/display/banner.ts +25 -0
  33. package/src/utils/display/status.ts +55 -0
  34. package/src/utils/{file-operations.ts → files/file-operations.ts} +2 -2
  35. package/src/utils/files/jsonc.ts +36 -0
  36. package/src/utils/{sync-utils.ts → files/sync-utils.ts} +3 -3
  37. package/src/utils/index.ts +42 -61
  38. package/src/utils/version.ts +47 -0
  39. package/src/components/benchmark-monitor.tsx +0 -331
  40. package/src/components/reindex-progress.tsx +0 -261
  41. package/src/composables/functional/index.ts +0 -14
  42. package/src/composables/functional/useEnvironment.ts +0 -171
  43. package/src/composables/functional/useFileSystem.ts +0 -139
  44. package/src/composables/index.ts +0 -4
  45. package/src/composables/useEnv.ts +0 -13
  46. package/src/composables/useRuntimeConfig.ts +0 -27
  47. package/src/core/ai-sdk.ts +0 -603
  48. package/src/core/app-factory.ts +0 -381
  49. package/src/core/builtin-agents.ts +0 -9
  50. package/src/core/command-system.ts +0 -550
  51. package/src/core/config-system.ts +0 -550
  52. package/src/core/connection-pool.ts +0 -390
  53. package/src/core/di-container.ts +0 -155
  54. package/src/core/headless-display.ts +0 -96
  55. package/src/core/interfaces/index.ts +0 -22
  56. package/src/core/interfaces/repository.interface.ts +0 -91
  57. package/src/core/interfaces/service.interface.ts +0 -133
  58. package/src/core/interfaces.ts +0 -96
  59. package/src/core/result.ts +0 -351
  60. package/src/core/service-config.ts +0 -252
  61. package/src/core/session-service.ts +0 -121
  62. package/src/core/storage-factory.ts +0 -115
  63. package/src/core/stream-handler.ts +0 -288
  64. package/src/core/type-utils.ts +0 -427
  65. package/src/core/unified-storage.ts +0 -456
  66. package/src/core/validation/limit.ts +0 -46
  67. package/src/core/validation/query.ts +0 -20
  68. package/src/db/auto-migrate.ts +0 -322
  69. package/src/db/base-database-client.ts +0 -144
  70. package/src/db/cache-db.ts +0 -218
  71. package/src/db/cache-schema.ts +0 -75
  72. package/src/db/database.ts +0 -70
  73. package/src/db/index.ts +0 -252
  74. package/src/db/memory-db.ts +0 -153
  75. package/src/db/memory-schema.ts +0 -29
  76. package/src/db/schema.ts +0 -289
  77. package/src/db/session-repository.ts +0 -733
  78. package/src/domains/index.ts +0 -6
  79. package/src/domains/utilities/index.ts +0 -6
  80. package/src/domains/utilities/time/index.ts +0 -5
  81. package/src/domains/utilities/time/tools.ts +0 -291
  82. package/src/services/agent-service.ts +0 -273
  83. package/src/services/evaluation-service.ts +0 -271
  84. package/src/services/functional/evaluation-logic.ts +0 -296
  85. package/src/services/functional/file-processor.ts +0 -273
  86. package/src/services/functional/index.ts +0 -12
  87. package/src/services/memory.service.ts +0 -476
  88. package/src/types/api/batch.ts +0 -108
  89. package/src/types/api/errors.ts +0 -118
  90. package/src/types/api/index.ts +0 -55
  91. package/src/types/api/requests.ts +0 -76
  92. package/src/types/api/responses.ts +0 -180
  93. package/src/types/api/websockets.ts +0 -85
  94. package/src/types/benchmark.ts +0 -49
  95. package/src/types/database.types.ts +0 -510
  96. package/src/types/memory-types.ts +0 -63
  97. package/src/utils/advanced-tokenizer.ts +0 -191
  98. package/src/utils/ai-model-fetcher.ts +0 -19
  99. package/src/utils/async-file-operations.ts +0 -516
  100. package/src/utils/audio-player.ts +0 -345
  101. package/src/utils/codebase-helpers.ts +0 -211
  102. package/src/utils/console-ui.ts +0 -79
  103. package/src/utils/database-errors.ts +0 -140
  104. package/src/utils/debug-logger.ts +0 -49
  105. package/src/utils/file-scanner.ts +0 -259
  106. package/src/utils/help.ts +0 -20
  107. package/src/utils/immutable-cache.ts +0 -106
  108. package/src/utils/jsonc.ts +0 -158
  109. package/src/utils/memory-tui.ts +0 -414
  110. package/src/utils/models-dev.ts +0 -91
  111. package/src/utils/parallel-operations.ts +0 -487
  112. package/src/utils/process-manager.ts +0 -155
  113. package/src/utils/prompts.ts +0 -120
  114. package/src/utils/search-tool-builder.ts +0 -214
  115. package/src/utils/session-manager.ts +0 -168
  116. package/src/utils/session-title.ts +0 -87
  117. package/src/utils/simplified-errors.ts +0 -410
  118. package/src/utils/template-engine.ts +0 -94
  119. package/src/utils/test-audio.ts +0 -71
  120. package/src/utils/todo-context.ts +0 -46
  121. package/src/utils/token-counter.ts +0 -288
  122. /package/src/utils/{cli-output.ts → display/cli-output.ts} +0 -0
  123. /package/src/utils/{logger.ts → display/logger.ts} +0 -0
  124. /package/src/utils/{notifications.ts → display/notifications.ts} +0 -0
  125. /package/src/utils/{secret-utils.ts → security/secret-utils.ts} +0 -0
  126. /package/src/utils/{security.ts → security/security.ts} +0 -0
@@ -1,120 +0,0 @@
1
- /**
2
- * Modern CLI prompts with progressive output
3
- */
4
-
5
- import { createInterface } from 'node:readline';
6
- import chalk from 'chalk';
7
-
8
- export async function ask(question: string, defaultValue?: string): Promise<string> {
9
- const rl = createInterface({
10
- input: process.stdin,
11
- output: process.stdout,
12
- });
13
-
14
- const prompt = defaultValue
15
- ? `${chalk.cyan('❯')} ${question} ${chalk.gray(`(${defaultValue})`)}: `
16
- : `${chalk.cyan('❯')} ${question}: `;
17
-
18
- return new Promise((resolve) => {
19
- rl.question(prompt, (answer) => {
20
- rl.close();
21
- resolve(answer.trim() || defaultValue || '');
22
- });
23
- });
24
- }
25
-
26
- export async function askSecret(question: string): Promise<string> {
27
- const rl = createInterface({
28
- input: process.stdin,
29
- output: process.stdout,
30
- });
31
-
32
- const prompt = `${chalk.cyan('❯')} ${question}: `;
33
-
34
- return new Promise((resolve) => {
35
- // Hide input for secrets
36
- const stdin = process.stdin;
37
- const _onData = (char: Buffer) => {
38
- const str = char.toString('utf8');
39
- if (str === '\n' || str === '\r' || str === '\r\n') {
40
- (stdin as any).removeListener('data', _onData);
41
- process.stdout.write('\n');
42
- rl.close();
43
- } else {
44
- process.stdout.write('•');
45
- }
46
- };
47
-
48
- process.stdout.write(prompt);
49
- let input = '';
50
- stdin.on('data', (char) => {
51
- const str = char.toString('utf8');
52
- if (str === '\n' || str === '\r' || str === '\r\n') {
53
- process.stdout.write('\n');
54
- rl.close();
55
- resolve(input);
56
- } else if (str === '\x7f' || str === '\b') {
57
- // Backspace
58
- if (input.length > 0) {
59
- input = input.slice(0, -1);
60
- process.stdout.write('\b \b');
61
- }
62
- } else {
63
- input += str;
64
- process.stdout.write('•');
65
- }
66
- });
67
- stdin.setRawMode(true);
68
- stdin.resume();
69
- }).finally(() => {
70
- const stdin = process.stdin;
71
- stdin.setRawMode(false);
72
- stdin.pause();
73
- });
74
- }
75
-
76
- export async function select<T extends string>(question: string, choices: T[]): Promise<T> {
77
- console.log(`${chalk.cyan('❯')} ${question}`);
78
- choices.forEach((choice, index) => {
79
- console.log(chalk.gray(` ${index + 1}. ${choice}`));
80
- });
81
-
82
- const rl = createInterface({
83
- input: process.stdin,
84
- output: process.stdout,
85
- });
86
-
87
- return new Promise((resolve) => {
88
- rl.question(chalk.cyan(` Select (1-${choices.length}): `), (answer) => {
89
- rl.close();
90
- const index = Number.parseInt(answer.trim(), 10) - 1;
91
- if (index >= 0 && index < choices.length) {
92
- resolve(choices[index]);
93
- } else {
94
- resolve(choices[0]);
95
- }
96
- });
97
- });
98
- }
99
-
100
- export async function confirm(question: string, defaultValue = true): Promise<boolean> {
101
- const rl = createInterface({
102
- input: process.stdin,
103
- output: process.stdout,
104
- });
105
-
106
- const defaultText = defaultValue ? 'Y/n' : 'y/N';
107
- const prompt = `${chalk.cyan('❯')} ${question} ${chalk.gray(`(${defaultText})`)}: `;
108
-
109
- return new Promise((resolve) => {
110
- rl.question(prompt, (answer) => {
111
- rl.close();
112
- const input = answer.trim().toLowerCase();
113
- if (input) {
114
- resolve(input === 'y' || input === 'yes');
115
- } else {
116
- resolve(defaultValue);
117
- }
118
- });
119
- });
120
- }
@@ -1,214 +0,0 @@
1
- /**
2
- * Unified search tool builder
3
- * Creates consistent search and status tools for indexers
4
- */
5
-
6
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
- import { z } from 'zod';
8
- import type { BaseIndexer } from '../services/search/base-indexer.js';
9
- import { searchDocuments } from '../services/search/tfidf.js';
10
-
11
- export interface SearchToolConfig {
12
- indexer: BaseIndexer;
13
- toolName: string; // e.g., 'search_knowledge', 'search_codebase'
14
- statusToolName: string; // e.g., 'get_knowledge_status', 'get_indexing_status'
15
- description: string;
16
- searchDescription: string;
17
- examples: string[];
18
- }
19
-
20
- /**
21
- * Build search tool with consistent pattern
22
- */
23
- export function buildSearchTool(server: McpServer, config: SearchToolConfig) {
24
- const { indexer, toolName, statusToolName, description, searchDescription } = config;
25
-
26
- // Register search tool
27
- server.registerTool(
28
- toolName,
29
- {
30
- description: `${description}
31
-
32
- ${searchDescription}
33
-
34
- **Performance:**
35
- - First search: ~1-5s (indexing time)
36
- - Subsequent searches: <100ms (cached)
37
- - Background indexing: Starts automatically on server startup
38
-
39
- **Status:**
40
- - Use \`${statusToolName}\` to check indexing progress
41
- - If indexing in progress, returns progress message`,
42
- inputSchema: {
43
- query: z.string().describe('Search query'),
44
- limit: z.number().optional().describe('Maximum results (default: 5, max: 20)'),
45
- categories: z.array(z.string()).optional().describe('Filter by categories (optional)'),
46
- },
47
- },
48
- async (args) => {
49
- try {
50
- const query = args.query as string;
51
- const limit = Math.min((args.limit as number) || 5, 20);
52
- const categories = args.categories as string[] | undefined;
53
-
54
- // Check if indexing is in progress
55
- const status = indexer.getStatus();
56
- if (status.isIndexing) {
57
- const elapsed = Math.round((Date.now() - status.startTime) / 1000);
58
- return {
59
- content: [
60
- {
61
- type: 'text',
62
- text: `⏳ Indexing in progress...\n\n**Status:**\n- Progress: ${status.progress}%\n- Items indexed: ${status.indexedItems}/${status.totalItems}\n- Elapsed time: ${elapsed}s\n\n*Please wait and try again. Use \`${statusToolName}\` to check progress.*`,
63
- },
64
- ],
65
- };
66
- }
67
-
68
- // Check for errors
69
- if (status.error) {
70
- return {
71
- content: [
72
- {
73
- type: 'text',
74
- text: `✗ Indexing failed: ${status.error}\n\nPlease check the error and try again.`,
75
- },
76
- ],
77
- isError: true,
78
- };
79
- }
80
-
81
- // Perform search
82
- const startTime = Date.now();
83
- const index = await indexer.loadIndex();
84
- const indexTime = Date.now() - startTime;
85
-
86
- const searchStartTime = Date.now();
87
- const results = searchDocuments(query, index, {
88
- limit: limit * 2,
89
- minScore: 0.01,
90
- });
91
- const searchTime = Date.now() - searchStartTime;
92
-
93
- // Filter by categories if specified
94
- let filtered = results;
95
- if (categories && categories.length > 0) {
96
- filtered = results.filter((result) => {
97
- // Extract scheme/protocol from URI (e.g., 'knowledge' from 'knowledge://path')
98
- const category = result.uri.split('://')[0];
99
- return categories.includes(category);
100
- });
101
- }
102
-
103
- const finalResults = filtered.slice(0, limit);
104
-
105
- if (finalResults.length === 0) {
106
- return {
107
- content: [
108
- {
109
- type: 'text',
110
- text: `No results found for query: "${query}"\n\nTry:\n- Broader search terms\n- Different keywords\n- Check available categories`,
111
- },
112
- ],
113
- };
114
- }
115
-
116
- // Build response
117
- const resultTexts = finalResults.map((item, index) => {
118
- const filePath = item.uri.replace(/^(knowledge|file):\/\//, '');
119
- let text = `## ${index + 1}. ${filePath}\n`;
120
- text += `**Relevance**: ${(item.score * 100).toFixed(0)}%\n`;
121
- text += `**Matched terms**: ${item.matchedTerms.join(', ')}\n\n`;
122
- return text;
123
- });
124
-
125
- const summary = `Found ${finalResults.length} result(s) for "${query}":\n\n`;
126
- const stats = `\n---\n\n**Stats:**\n- Total items: ${index.totalDocuments}\n- Index time: ${indexTime}ms\n- Search time: ${searchTime}ms\n`;
127
-
128
- return {
129
- content: [
130
- {
131
- type: 'text',
132
- text: summary + resultTexts.join('\n---\n\n') + stats,
133
- },
134
- ],
135
- };
136
- } catch (error: unknown) {
137
- const errorMessage = error instanceof Error ? error.message : String(error);
138
- console.error(`[ERROR] ${toolName} failed:`, error);
139
- return {
140
- content: [
141
- {
142
- type: 'text',
143
- text: `✗ Search error: ${errorMessage}`,
144
- },
145
- ],
146
- isError: true,
147
- };
148
- }
149
- }
150
- );
151
-
152
- // Register status tool
153
- server.registerTool(
154
- statusToolName,
155
- {
156
- description: `Get indexing status for ${toolName}.
157
-
158
- Shows:
159
- - Whether indexing is in progress
160
- - Progress percentage
161
- - Number of items indexed
162
- - Any errors`,
163
- inputSchema: {},
164
- },
165
- async () => {
166
- const status = indexer.getStatus();
167
-
168
- if (status.isIndexing) {
169
- const elapsed = Math.round((Date.now() - status.startTime) / 1000);
170
- return {
171
- content: [
172
- {
173
- type: 'text',
174
- text: `⏳ **Indexing in Progress**\n\n- Progress: ${status.progress}%\n- Items indexed: ${status.indexedItems}/${status.totalItems}\n- Elapsed time: ${elapsed}s`,
175
- },
176
- ],
177
- };
178
- }
179
-
180
- if (status.error) {
181
- return {
182
- content: [
183
- {
184
- type: 'text',
185
- text: `✗ **Indexing Failed**\n\nError: ${status.error}`,
186
- },
187
- ],
188
- isError: true,
189
- };
190
- }
191
-
192
- if (indexer.isReady()) {
193
- const stats = await indexer.getStats();
194
- return {
195
- content: [
196
- {
197
- type: 'text',
198
- text: `✓ **Index Ready**\n\n- Total items: ${stats?.totalDocuments || 0}\n- Unique terms: ${stats?.uniqueTerms || 0}\n- Status: Ready for search`,
199
- },
200
- ],
201
- };
202
- }
203
-
204
- return {
205
- content: [
206
- {
207
- type: 'text',
208
- text: '⚠️ **Not Indexed**\n\nIndexing will start automatically on first search.',
209
- },
210
- ],
211
- };
212
- }
213
- );
214
- }
@@ -1,168 +0,0 @@
1
- /**
2
- * Session Manager
3
- * Manage chat sessions for headless mode
4
- */
5
-
6
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
7
- import { join } from 'node:path';
8
- import { homedir } from 'node:os';
9
- import type { ProviderId } from '../config/ai-config.js';
10
- import type { Session } from '../types/session.types.js';
11
-
12
- export type { Session } from '../types/session.types.js';
13
-
14
- const SESSION_DIR = join(homedir(), '.sylphx', 'sessions');
15
- const LAST_SESSION_FILE = join(SESSION_DIR, '.last-session');
16
-
17
- /**
18
- * Ensure session directory exists
19
- */
20
- async function ensureSessionDir(): Promise<void> {
21
- await mkdir(SESSION_DIR, { recursive: true });
22
- }
23
-
24
- /**
25
- * Get session file path
26
- */
27
- function getSessionPath(sessionId: string): string {
28
- return join(SESSION_DIR, `${sessionId}.json`);
29
- }
30
-
31
- /**
32
- * Create new session
33
- */
34
- export async function createSession(provider: ProviderId, model: string): Promise<Session> {
35
- await ensureSessionDir();
36
-
37
- const session: Session = {
38
- id: `session-${Date.now()}`,
39
- provider,
40
- model,
41
- messages: [],
42
- todos: [], // Initialize empty todos
43
- nextTodoId: 1, // Start from 1
44
- created: Date.now(),
45
- updated: Date.now(),
46
- };
47
-
48
- await saveSession(session);
49
- await setLastSession(session.id);
50
-
51
- return session;
52
- }
53
-
54
- /**
55
- * Save session to file
56
- */
57
- export async function saveSession(session: Session): Promise<void> {
58
- await ensureSessionDir();
59
- // Create a new object with updated timestamp (don't mutate readonly session from Zustand)
60
- const sessionToSave = {
61
- ...session,
62
- updated: Date.now(),
63
- };
64
- const path = getSessionPath(session.id);
65
- // Use compact JSON format for faster serialization and smaller file size
66
- await writeFile(path, JSON.stringify(sessionToSave), 'utf8');
67
- }
68
-
69
- /**
70
- * Load session from file with migration support
71
- * Automatically adds missing fields from newer schema versions
72
- */
73
- export async function loadSession(sessionId: string): Promise<Session | null> {
74
- try {
75
- const path = getSessionPath(sessionId);
76
- const content = await readFile(path, 'utf8');
77
- const rawSession = JSON.parse(content) as any;
78
-
79
- // Migration: Add todos/nextTodoId if missing
80
- if (!rawSession.todos) {
81
- rawSession.todos = [];
82
- }
83
- if (typeof rawSession.nextTodoId !== 'number') {
84
- rawSession.nextTodoId = 1;
85
- }
86
-
87
- // Migration: Normalize message content format
88
- // Old: { content: string }
89
- // New: { content: MessagePart[] }
90
- if (Array.isArray(rawSession.messages)) {
91
- rawSession.messages = rawSession.messages.map((msg: any) => {
92
- if (typeof msg.content === 'string') {
93
- return {
94
- ...msg,
95
- content: [{ type: 'text', content: msg.content }],
96
- };
97
- }
98
- return msg;
99
- });
100
- }
101
-
102
- return rawSession as Session;
103
- } catch {
104
- return null;
105
- }
106
- }
107
-
108
- /**
109
- * Get last session ID
110
- */
111
- export async function getLastSessionId(): Promise<string | null> {
112
- try {
113
- const content = await readFile(LAST_SESSION_FILE, 'utf8');
114
- return content.trim();
115
- } catch {
116
- return null;
117
- }
118
- }
119
-
120
- /**
121
- * Set last session ID
122
- */
123
- export async function setLastSession(sessionId: string): Promise<void> {
124
- await ensureSessionDir();
125
- await writeFile(LAST_SESSION_FILE, sessionId, 'utf8');
126
- }
127
-
128
- /**
129
- * Load last session
130
- */
131
- export async function loadLastSession(): Promise<Session | null> {
132
- const sessionId = await getLastSessionId();
133
- if (!sessionId) return null;
134
- return loadSession(sessionId);
135
- }
136
-
137
- /**
138
- * Add message to session (in-memory helper for headless mode)
139
- * Converts string content to MessagePart[] format
140
- */
141
- export function addMessage(
142
- session: Session,
143
- role: 'user' | 'assistant',
144
- content: string
145
- ): Session {
146
- return {
147
- ...session,
148
- messages: [
149
- ...session.messages,
150
- {
151
- role,
152
- content: [{ type: 'text', content }], // Convert to MessagePart[]
153
- timestamp: Date.now(),
154
- },
155
- ],
156
- };
157
- }
158
-
159
- /**
160
- * Clear session messages but keep metadata
161
- */
162
- export function clearSessionMessages(session: Session): Session {
163
- return {
164
- ...session,
165
- messages: [],
166
- updated: Date.now(),
167
- };
168
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * Session Title Generation Utility
3
- * Re-exports pure functions from feature and adds streaming functionality
4
- */
5
-
6
- import { createAIStream } from '../core/ai-sdk.js';
7
- import type { ProviderId } from '../types/config.types.js';
8
-
9
- // Re-export pure functions from feature
10
- export {
11
- generateSessionTitle,
12
- formatSessionDisplay,
13
- formatRelativeTime,
14
- cleanTitle,
15
- truncateTitle,
16
- } from '../features/session/utils/title.js';
17
-
18
- /**
19
- * Generate a session title using LLM with streaming
20
- */
21
- export async function generateSessionTitleWithStreaming(
22
- firstMessage: string,
23
- provider: ProviderId,
24
- modelName: string,
25
- providerConfig: any,
26
- onChunk: (chunk: string) => void
27
- ): Promise<string> {
28
- if (!firstMessage || firstMessage.trim().length === 0) {
29
- return 'New Chat';
30
- }
31
-
32
- try {
33
- // Get the provider instance and create the model
34
- const { getProvider } = await import('../providers/index.js');
35
- const providerInstance = getProvider(provider);
36
- const model = providerInstance.createClient(providerConfig, modelName);
37
-
38
- const streamGenerator = createAIStream({
39
- model,
40
- messages: [
41
- {
42
- role: 'user',
43
- content: `You need to generate a SHORT, DESCRIPTIVE title (maximum 50 characters) for a chat conversation.
44
-
45
- User's first message: "${firstMessage}"
46
-
47
- Requirements:
48
- - Summarize the TOPIC or INTENT, don't just copy the message
49
- - Be concise and descriptive
50
- - Maximum 50 characters
51
- - Output ONLY the title, nothing else
52
-
53
- Examples:
54
- - Message: "How do I implement authentication?" → Title: "Authentication Implementation"
55
- - Message: "你好,请帮我修复这个 bug" → Title: "Bug 修复请求"
56
- - Message: "Can you help me with React hooks?" → Title: "React Hooks Help"
57
-
58
- Now generate the title:`,
59
- },
60
- ],
61
- });
62
-
63
- let fullTitle = '';
64
-
65
- // Iterate the async generator and stream to UI
66
- for await (const chunk of streamGenerator) {
67
- if (chunk.type === 'text-delta' && chunk.textDelta) {
68
- fullTitle += chunk.textDelta;
69
- onChunk(chunk.textDelta);
70
- }
71
- }
72
-
73
- // Clean up title
74
- let cleaned = fullTitle.trim();
75
- cleaned = cleaned.replace(/^["'「『]+|["'」』]+$/g, ''); // Remove quotes
76
- cleaned = cleaned.replace(/^(Title:|标题:)\s*/i, ''); // Remove "Title:" prefix
77
- cleaned = cleaned.replace(/\n+/g, ' '); // Replace newlines with spaces
78
- cleaned = cleaned.trim();
79
-
80
- // Return truncated if needed
81
- return cleaned.length > 50 ? cleaned.substring(0, 50) + '...' : cleaned;
82
- } catch (error) {
83
- // Fallback to simple title generation on any error
84
- return generateSessionTitle(firstMessage);
85
- }
86
- }
87
-