{"version":3,"file":"thread-names.cjs","names":["cloneAgentForRequest","isHandlerResponse"],"sources":["../../../../../src/v2/runtime/handlers/intelligence/thread-names.ts"],"sourcesContent":["import { AbstractAgent, Message, RunAgentInput } from \"@ag-ui/client\";\nimport { logger } from \"@copilotkit/shared\";\nimport { randomUUID } from \"node:crypto\";\nimport { CopilotIntelligenceRuntimeLike } from \"../../core/runtime\";\nimport {\n  cloneAgentForRequest,\n  configureAgentForRequest,\n} from \"../shared/agent-utils\";\nimport { ThreadSummary } from \"../../intelligence-platform\";\nimport { isHandlerResponse } from \"../shared/json-response\";\n\nconst THREAD_NAME_SYSTEM_PROMPT = [\n  \"You generate short, specific conversation titles.\",\n  'Return JSON only in this exact shape: {\"title\":\"...\"}',\n  \"The title must be 2 to 5 words.\",\n  \"Use sentence case.\",\n  \"No quotes.\",\n  \"No emoji.\",\n  \"No markdown characters or formatting.\",\n  \"Do not use *, _, #, `, [, ], (, ), !, ~, >, or |.\",\n  \"No trailing punctuation.\",\n  \"No explanations.\",\n  \"Do not call tools.\",\n].join(\"\\n\");\n\nconst MAX_TITLE_LENGTH = 80;\nconst MAX_TITLE_WORDS = 8;\nconst MAX_TRANSCRIPT_MESSAGES = 8;\nconst MAX_TITLE_GENERATION_ATTEMPTS = 3;\nconst FALLBACK_THREAD_TITLE = \"Untitled\";\n\ninterface GenerateThreadNameParams {\n  runtime: CopilotIntelligenceRuntimeLike;\n  request: Request;\n  agentId: string;\n  sourceInput: RunAgentInput;\n  thread: ThreadSummary;\n  userId: string;\n}\n\nexport async function generateThreadNameForNewThread({\n  runtime,\n  request,\n  agentId,\n  sourceInput,\n  thread,\n  userId,\n}: GenerateThreadNameParams): Promise<void> {\n  if (!runtime.generateThreadNames || hasThreadName(thread.name)) {\n    return;\n  }\n\n  const prompt = buildThreadTitlePrompt(sourceInput.messages);\n  if (!prompt) {\n    return;\n  }\n\n  let generatedTitle: string | null = null;\n\n  for (let attempt = 1; attempt <= MAX_TITLE_GENERATION_ATTEMPTS; attempt++) {\n    try {\n      generatedTitle = await runTitleGenerationAttempt({\n        runtime,\n        request,\n        agentId,\n        threadId: thread.id,\n        prompt,\n      });\n\n      if (generatedTitle) {\n        break;\n      }\n\n      logger.warn(\n        { agentId, attempt, threadId: thread.id },\n        \"Thread name generation returned an empty or invalid title\",\n      );\n    } catch (error) {\n      logger.warn(\n        { err: error, agentId, attempt, threadId: thread.id },\n        \"Thread name generation attempt failed\",\n      );\n    }\n  }\n\n  await runtime.intelligence.updateThread({\n    threadId: thread.id,\n    userId,\n    agentId,\n    updates: { name: generatedTitle ?? FALLBACK_THREAD_TITLE },\n  });\n}\n\nasync function runTitleGenerationAttempt(params: {\n  runtime: CopilotIntelligenceRuntimeLike;\n  request: Request;\n  agentId: string;\n  threadId: string;\n  prompt: string;\n}): Promise<string | null> {\n  const { runtime, request, agentId, threadId, prompt } = params;\n  const agent = await cloneAgentForRequest(runtime, agentId);\n  if (isHandlerResponse(agent)) {\n    logger.warn(\n      { agentId, threadId },\n      \"Skipping thread naming because the agent could not be cloned\",\n    );\n    return null;\n  }\n\n  configureAgentForRequest({\n    runtime,\n    request,\n    agentId,\n    agent,\n  });\n\n  const messages: Message[] = [\n    {\n      id: randomUUID(),\n      role: \"system\",\n      content: THREAD_NAME_SYSTEM_PROMPT,\n    },\n    {\n      id: randomUUID(),\n      role: \"user\",\n      content: prompt,\n    },\n  ];\n\n  agent.setMessages(messages);\n  agent.setState({});\n  agent.threadId = randomUUID();\n  const { newMessages } = await agent.runAgent({\n    messages,\n    state: {},\n    tools: [],\n    context: [],\n    forwardedProps: {},\n  });\n\n  const lastMessage = newMessages.at(-1);\n  const titleContent = lastMessage\n    ? stringifyMessageContent(lastMessage.content)\n    : \"\";\n\n  return normalizeGeneratedTitle(titleContent);\n}\n\nfunction buildThreadTitlePrompt(\n  messages: Message[] | undefined,\n): string | null {\n  const transcript = (messages ?? [])\n    .filter((message) =>\n      [\"user\", \"assistant\", \"system\", \"developer\"].includes(message.role),\n    )\n    .map((message) => {\n      const content = stringifyMessageContent(message.content);\n      if (!content) {\n        return null;\n      }\n\n      return `${message.role}: ${content}`;\n    })\n    .filter((message): message is string => !!message)\n    .slice(-MAX_TRANSCRIPT_MESSAGES);\n\n  if (transcript.length === 0) {\n    return null;\n  }\n\n  return [\n    \"Generate a short title for this conversation.\",\n    \"Conversation:\",\n    transcript.join(\"\\n\"),\n  ].join(\"\\n\\n\");\n}\n\nfunction stringifyMessageContent(content: Message[\"content\"]): string {\n  if (typeof content === \"string\") {\n    return content.trim();\n  }\n\n  if (content == null) {\n    return \"\";\n  }\n\n  try {\n    return JSON.stringify(content).trim();\n  } catch {\n    return \"\";\n  }\n}\n\nfunction normalizeGeneratedTitle(rawTitle: string): string | null {\n  let candidate = rawTitle.trim();\n  if (!candidate) {\n    return null;\n  }\n\n  candidate = candidate\n    .replace(/^```(?:json)?\\s*/i, \"\")\n    .replace(/\\s*```$/, \"\")\n    .trim();\n\n  try {\n    const parsed = JSON.parse(candidate) as { title?: unknown };\n    if (typeof parsed.title === \"string\") {\n      candidate = parsed.title;\n    }\n  } catch {\n    // Fall back to using the raw text.\n  }\n\n  candidate = candidate\n    .replace(/^[\"'`]+|[\"'`]+$/g, \"\")\n    .replace(/[*_#[\\]()!~>|]+/g, \"\")\n    .replace(/[.!?,;:]+$/g, \"\")\n    .replace(/\\s+/g, \" \")\n    .trim();\n\n  if (!candidate) {\n    return null;\n  }\n\n  if (candidate.length > MAX_TITLE_LENGTH) {\n    candidate = candidate.slice(0, MAX_TITLE_LENGTH).trim();\n  }\n\n  if (candidate.split(/\\s+/).length > MAX_TITLE_WORDS) {\n    return null;\n  }\n\n  return candidate;\n}\n\nfunction hasThreadName(name: string | null | undefined): boolean {\n  return typeof name === \"string\" && name.trim().length > 0;\n}\n\n/** @internal Exported for testing only. */\nexport const ɵnormalizeGeneratedTitle = normalizeGeneratedTitle;\n/** @internal Exported for testing only. */\nexport const ɵbuildThreadTitlePrompt = buildThreadTitlePrompt;\n/** @internal Exported for testing only. */\nexport const ɵhasThreadName = hasThreadName;\n"],"mappings":"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"}