@copilotkit/runtime
Version:
<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />
146 lines (144 loc) • 4.75 kB
JavaScript
require("reflect-metadata");
const require_runtime = require('../../../../_virtual/_rolldown/runtime.cjs');
const require_agent_utils = require('../shared/agent-utils.cjs');
const require_json_response = require('../shared/json-response.cjs');
let _copilotkit_shared = require("@copilotkit/shared");
let node_crypto = require("node:crypto");
//#region src/v2/runtime/handlers/intelligence/thread-names.ts
const THREAD_NAME_SYSTEM_PROMPT = [
"You generate short, specific conversation titles.",
"Return JSON only in this exact shape: {\"title\":\"...\"}",
"The title must be 2 to 5 words.",
"Use sentence case.",
"No quotes.",
"No emoji.",
"No markdown characters or formatting.",
"Do not use *, _, #, `, [, ], (, ), !, ~, >, or |.",
"No trailing punctuation.",
"No explanations.",
"Do not call tools."
].join("\n");
const MAX_TITLE_LENGTH = 80;
const MAX_TITLE_WORDS = 8;
const MAX_TRANSCRIPT_MESSAGES = 8;
const MAX_TITLE_GENERATION_ATTEMPTS = 3;
const FALLBACK_THREAD_TITLE = "Untitled";
async function generateThreadNameForNewThread({ runtime, request, agentId, sourceInput, thread, userId }) {
if (!runtime.generateThreadNames || hasThreadName(thread.name)) return;
const prompt = buildThreadTitlePrompt(sourceInput.messages);
if (!prompt) return;
let generatedTitle = null;
for (let attempt = 1; attempt <= MAX_TITLE_GENERATION_ATTEMPTS; attempt++) try {
generatedTitle = await runTitleGenerationAttempt({
runtime,
request,
agentId,
threadId: thread.id,
prompt
});
if (generatedTitle) break;
_copilotkit_shared.logger.warn({
agentId,
attempt,
threadId: thread.id
}, "Thread name generation returned an empty or invalid title");
} catch (error) {
_copilotkit_shared.logger.warn({
err: error,
agentId,
attempt,
threadId: thread.id
}, "Thread name generation attempt failed");
}
await runtime.intelligence.updateThread({
threadId: thread.id,
userId,
agentId,
updates: { name: generatedTitle ?? FALLBACK_THREAD_TITLE }
});
}
async function runTitleGenerationAttempt(params) {
const { runtime, request, agentId, threadId, prompt } = params;
const agent = await require_agent_utils.cloneAgentForRequest(runtime, agentId, request);
if (require_json_response.isHandlerResponse(agent)) {
_copilotkit_shared.logger.warn({
agentId,
threadId
}, "Skipping thread naming because the agent could not be cloned");
return null;
}
require_agent_utils.configureAgentForRequest({
runtime,
request,
agentId,
agent
});
const messages = [{
id: (0, node_crypto.randomUUID)(),
role: "system",
content: THREAD_NAME_SYSTEM_PROMPT
}, {
id: (0, node_crypto.randomUUID)(),
role: "user",
content: prompt
}];
agent.setMessages(messages);
agent.setState({});
agent.threadId = (0, node_crypto.randomUUID)();
const { newMessages } = await agent.runAgent({
messages,
state: {},
tools: [],
context: [],
forwardedProps: {}
});
const lastMessage = newMessages.at(-1);
return normalizeGeneratedTitle(lastMessage ? stringifyMessageContent(lastMessage.content) : "");
}
function buildThreadTitlePrompt(messages) {
const transcript = (messages ?? []).filter((message) => [
"user",
"assistant",
"system",
"developer"
].includes(message.role)).map((message) => {
const content = stringifyMessageContent(message.content);
if (!content) return null;
return `${message.role}: ${content}`;
}).filter((message) => !!message).slice(-MAX_TRANSCRIPT_MESSAGES);
if (transcript.length === 0) return null;
return [
"Generate a short title for this conversation.",
"Conversation:",
transcript.join("\n")
].join("\n\n");
}
function stringifyMessageContent(content) {
if (typeof content === "string") return content.trim();
if (content == null) return "";
try {
return JSON.stringify(content).trim();
} catch {
return "";
}
}
function normalizeGeneratedTitle(rawTitle) {
let candidate = rawTitle.trim();
if (!candidate) return null;
candidate = candidate.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
try {
const parsed = JSON.parse(candidate);
if (typeof parsed.title === "string") candidate = parsed.title;
} catch {}
candidate = candidate.replace(/^["'`]+|["'`]+$/g, "").replace(/[*_#[\]()!~>|]+/g, "").replace(/[.!?,;:]+$/g, "").replace(/\s+/g, " ").trim();
if (!candidate) return null;
if (candidate.length > MAX_TITLE_LENGTH) candidate = candidate.slice(0, MAX_TITLE_LENGTH).trim();
if (candidate.split(/\s+/).length > MAX_TITLE_WORDS) return null;
return candidate;
}
function hasThreadName(name) {
return typeof name === "string" && name.trim().length > 0;
}
//#endregion
exports.generateThreadNameForNewThread = generateThreadNameForNewThread;
//# sourceMappingURL=thread-names.cjs.map