/ commands / rename / generateSessionName.ts
generateSessionName.ts
 1  import { queryHaiku } from '../../services/api/claude.js'
 2  import type { Message } from '../../types/message.js'
 3  import { logForDebugging } from '../../utils/debug.js'
 4  import { errorMessage } from '../../utils/errors.js'
 5  import { safeParseJSON } from '../../utils/json.js'
 6  import { extractTextContent } from '../../utils/messages.js'
 7  import { extractConversationText } from '../../utils/sessionTitle.js'
 8  import { asSystemPrompt } from '../../utils/systemPromptType.js'
 9  
10  export async function generateSessionName(
11    messages: Message[],
12    signal: AbortSignal,
13  ): Promise<string | null> {
14    const conversationText = extractConversationText(messages)
15    if (!conversationText) {
16      return null
17    }
18  
19    try {
20      const result = await queryHaiku({
21        systemPrompt: asSystemPrompt([
22          'Generate a short kebab-case name (2-4 words) that captures the main topic of this conversation. Use lowercase words separated by hyphens. Examples: "fix-login-bug", "add-auth-feature", "refactor-api-client", "debug-test-failures". Return JSON with a "name" field.',
23        ]),
24        userPrompt: conversationText,
25        outputFormat: {
26          type: 'json_schema',
27          schema: {
28            type: 'object',
29            properties: {
30              name: { type: 'string' },
31            },
32            required: ['name'],
33            additionalProperties: false,
34          },
35        },
36        signal,
37        options: {
38          querySource: 'rename_generate_name',
39          agents: [],
40          isNonInteractiveSession: false,
41          hasAppendSystemPrompt: false,
42          mcpTools: [],
43        },
44      })
45  
46      const content = extractTextContent(result.message.content)
47  
48      const response = safeParseJSON(content)
49      if (
50        response &&
51        typeof response === 'object' &&
52        'name' in response &&
53        typeof (response as { name: unknown }).name === 'string'
54      ) {
55        return (response as { name: string }).name
56      }
57      return null
58    } catch (error) {
59      // Haiku timeout/rate-limit/network are expected operational failures —
60      // logForDebugging, not logError. Called automatically on every 3rd bridge
61      // message (initReplBridge.ts), so errors here would flood the error file.
62      logForDebugging(`generateSessionName failed: ${errorMessage(error)}`, {
63        level: 'error',
64      })
65      return null
66    }
67  }