diff --git a/packages/@n8n/agents/src/__tests__/model-factory.test.ts b/packages/@n8n/agents/src/__tests__/model-factory.test.ts index fb25b071db5..ab237b09fd5 100644 --- a/packages/@n8n/agents/src/__tests__/model-factory.test.ts +++ b/packages/@n8n/agents/src/__tests__/model-factory.test.ts @@ -2,7 +2,12 @@ import type { LanguageModel } from 'ai'; import { createModel } from '../runtime/model-factory'; -type ProviderOpts = { apiKey?: string; baseURL?: string; fetch?: typeof globalThis.fetch }; +type ProviderOpts = { + apiKey?: string; + baseURL?: string; + fetch?: typeof globalThis.fetch; + headers?: Record; +}; jest.mock('@ai-sdk/anthropic', () => ({ createAnthropic: (opts?: ProviderOpts) => (model: string) => ({ @@ -11,6 +16,7 @@ jest.mock('@ai-sdk/anthropic', () => ({ apiKey: opts?.apiKey, baseURL: opts?.baseURL, fetch: opts?.fetch, + headers: opts?.headers, specificationVersion: 'v3', }), })); @@ -22,6 +28,7 @@ jest.mock('@ai-sdk/openai', () => ({ apiKey: opts?.apiKey, baseURL: opts?.baseURL, fetch: opts?.fetch, + headers: opts?.headers, specificationVersion: 'v3', }), })); @@ -105,6 +112,18 @@ describe('createModel', () => { expect(mockProxyAgent).toHaveBeenCalledWith('http://proxy:9090'); }); + it('should forward custom headers to the provider factory', () => { + const model = createModel({ + id: 'anthropic/claude-sonnet-4-5', + apiKey: 'sk-test', + headers: { 'x-proxy-auth': 'Bearer abc', 'anthropic-beta': 'tools-2024' }, + }) as unknown as Record; + expect(model.headers).toEqual({ + 'x-proxy-auth': 'Bearer abc', + 'anthropic-beta': 'tools-2024', + }); + }); + it('should prefer HTTPS_PROXY over HTTP_PROXY', () => { process.env.HTTPS_PROXY = 'http://https-proxy:8080'; process.env.HTTP_PROXY = 'http://http-proxy:9090'; diff --git a/packages/@n8n/agents/src/__tests__/title-generation.test.ts b/packages/@n8n/agents/src/__tests__/title-generation.test.ts new file mode 100644 index 00000000000..7c00431d9e6 --- /dev/null +++ b/packages/@n8n/agents/src/__tests__/title-generation.test.ts @@ -0,0 +1,123 @@ +import type * as AiImport from 'ai'; +import type { LanguageModel } from 'ai'; + +import { generateTitleFromMessage } from '../runtime/title-generation'; + +type GenerateTextCall = { + messages: Array<{ role: string; content: string }>; +}; + +const mockGenerateText = jest.fn, [GenerateTextCall]>(); + +jest.mock('ai', () => { + const actual = jest.requireActual('ai'); + return { + ...actual, + generateText: async (call: GenerateTextCall): Promise<{ text: string }> => + await mockGenerateText(call), + }; +}); + +const fakeModel = {} as LanguageModel; + +describe('generateTitleFromMessage', () => { + beforeEach(() => { + mockGenerateText.mockReset(); + }); + + it('returns null for empty input without calling the LLM', async () => { + const result = await generateTitleFromMessage(fakeModel, ' '); + expect(result).toBeNull(); + expect(mockGenerateText).not.toHaveBeenCalled(); + }); + + it('returns the message itself for trivial greetings without calling the LLM', async () => { + const result = await generateTitleFromMessage(fakeModel, 'hey'); + expect(result).toBe('hey'); + expect(mockGenerateText).not.toHaveBeenCalled(); + }); + + it('skips the LLM for short multi-word messages', async () => { + const result = await generateTitleFromMessage(fakeModel, 'hi there'); + expect(result).toBe('hi there'); + expect(mockGenerateText).not.toHaveBeenCalled(); + }); + + it('strips markdown heading prefixes from the LLM response', async () => { + mockGenerateText.mockResolvedValue({ text: '# Daily Berlin rain alert' }); + const result = await generateTitleFromMessage( + fakeModel, + 'Build a daily Berlin rain alert workflow', + ); + expect(result).toBe('Daily Berlin rain alert'); + }); + + it('strips inline emphasis markers from the LLM response', async () => { + mockGenerateText.mockResolvedValue({ text: 'Your **Berlin** rain alert' }); + const result = await generateTitleFromMessage( + fakeModel, + 'Build a daily Berlin rain alert workflow', + ); + expect(result).toBe('Your Berlin rain alert'); + }); + + it('strips reasoning blocks from the LLM response', async () => { + mockGenerateText.mockResolvedValue({ + text: 'Let me think about thisDeploy release pipeline', + }); + const result = await generateTitleFromMessage( + fakeModel, + 'Help me set up an automated deploy pipeline', + ); + expect(result).toBe('Deploy release pipeline'); + }); + + it('strips surrounding quotes from the LLM response', async () => { + mockGenerateText.mockResolvedValue({ text: '"Build Gmail to Slack workflow"' }); + const result = await generateTitleFromMessage( + fakeModel, + 'Build a workflow that forwards Gmail to Slack', + ); + expect(result).toBe('Build Gmail to Slack workflow'); + }); + + it('truncates titles longer than 80 characters at a word boundary', async () => { + mockGenerateText.mockResolvedValue({ + text: 'Create a data table for users, then build a workflow that syncs them to our CRM every hour', + }); + const result = await generateTitleFromMessage( + fakeModel, + 'Create a data table for users and sync them to our CRM every hour with error alerting', + ); + expect(result).not.toBeNull(); + expect(result!.length).toBeLessThanOrEqual(81); + expect(result!.endsWith('\u2026')).toBe(true); + }); + + it('returns null when the LLM returns empty text', async () => { + mockGenerateText.mockResolvedValue({ text: ' ' }); + const result = await generateTitleFromMessage( + fakeModel, + 'Build a daily Berlin rain alert workflow', + ); + expect(result).toBeNull(); + }); + + it('passes the default instructions to the LLM', async () => { + mockGenerateText.mockResolvedValue({ text: 'Berlin rain alert' }); + await generateTitleFromMessage(fakeModel, 'Build a daily Berlin rain alert workflow'); + const call = mockGenerateText.mock.calls[0][0]; + expect(call.messages[0].role).toBe('system'); + expect(call.messages[0].content).toContain('markdown'); + expect(call.messages[0].content).toContain('sentence case'); + }); + + it('accepts custom instructions', async () => { + mockGenerateText.mockResolvedValue({ text: 'Custom title' }); + await generateTitleFromMessage(fakeModel, 'Build a daily Berlin rain alert workflow', { + instructions: 'Custom system prompt', + }); + const call = mockGenerateText.mock.calls[0][0]; + expect(call.messages[0].content).toBe('Custom system prompt'); + }); +}); diff --git a/packages/@n8n/agents/src/index.ts b/packages/@n8n/agents/src/index.ts index 6725cc15139..540798ca800 100644 --- a/packages/@n8n/agents/src/index.ts +++ b/packages/@n8n/agents/src/index.ts @@ -114,6 +114,9 @@ export type { SqliteMemoryConfig } from './storage/sqlite-memory'; export { PostgresMemory } from './storage/postgres-memory'; export type { PostgresMemoryConfig } from './storage/postgres-memory'; +export { createModel } from './runtime/model-factory'; +export { generateTitleFromMessage } from './runtime/title-generation'; + export { Workspace } from './workspace'; export { BaseFilesystem } from './workspace'; export { BaseSandbox } from './workspace'; diff --git a/packages/@n8n/agents/src/runtime/model-factory.ts b/packages/@n8n/agents/src/runtime/model-factory.ts index 138ea6f6d52..8d77c4f00af 100644 --- a/packages/@n8n/agents/src/runtime/model-factory.ts +++ b/packages/@n8n/agents/src/runtime/model-factory.ts @@ -9,6 +9,7 @@ type CreateProviderFn = (opts?: { apiKey?: string; baseURL?: string; fetch?: FetchFn; + headers?: Record; }) => (model: string) => LanguageModel; type CreateEmbeddingProviderFn = (opts?: { apiKey?: string }) => { embeddingModel(model: string): EmbeddingModel; @@ -56,6 +57,7 @@ export function createModel(config: ModelConfig): LanguageModel { const modelId = stripEmpty(typeof config === 'string' ? config : config.id); const apiKey = stripEmpty(typeof config === 'string' ? undefined : config.apiKey); const baseURL = stripEmpty(typeof config === 'string' ? undefined : config.url); + const headers = typeof config === 'string' ? undefined : config.headers; if (!modelId) { throw new Error('Model ID is required'); @@ -70,25 +72,25 @@ export function createModel(config: ModelConfig): LanguageModel { const { createAnthropic } = require('@ai-sdk/anthropic') as { createAnthropic: CreateProviderFn; }; - return createAnthropic({ apiKey, baseURL, fetch })(modelName); + return createAnthropic({ apiKey, baseURL, fetch, headers })(modelName); } case 'openai': { const { createOpenAI } = require('@ai-sdk/openai') as { createOpenAI: CreateProviderFn; }; - return createOpenAI({ apiKey, baseURL, fetch })(modelName); + return createOpenAI({ apiKey, baseURL, fetch, headers })(modelName); } case 'google': { const { createGoogleGenerativeAI } = require('@ai-sdk/google') as { createGoogleGenerativeAI: CreateProviderFn; }; - return createGoogleGenerativeAI({ apiKey, baseURL, fetch })(modelName); + return createGoogleGenerativeAI({ apiKey, baseURL, fetch, headers })(modelName); } case 'xai': { const { createXai } = require('@ai-sdk/xai') as { createXai: CreateProviderFn; }; - return createXai({ apiKey, baseURL, fetch })(modelName); + return createXai({ apiKey, baseURL, fetch, headers })(modelName); } default: throw new Error( diff --git a/packages/@n8n/agents/src/runtime/title-generation.ts b/packages/@n8n/agents/src/runtime/title-generation.ts index 48b12b9540b..ff0597cb56b 100644 --- a/packages/@n8n/agents/src/runtime/title-generation.ts +++ b/packages/@n8n/agents/src/runtime/title-generation.ts @@ -1,4 +1,4 @@ -import { generateText } from 'ai'; +import { generateText, type LanguageModel } from 'ai'; import type { BuiltMemory, TitleGenerationConfig } from '../types'; import { createFilteredLogger } from './logger'; @@ -10,13 +10,83 @@ const logger = createFilteredLogger(); const DEFAULT_TITLE_INSTRUCTIONS = [ '- you will generate a short title based on the first message a user begins a conversation with', - "- the title should be a summary of the user's message", + '- the title should describe what the user asked for, not what an assistant might reply', '- 1 to 5 words, no more than 80 characters', '- use sentence case (e.g. "Conversation title" instead of "Conversation Title")', '- do not use quotes, colons, or markdown formatting', '- the entire text you return will be used directly as the title, so respond with the title only', ].join('\n'); +const TRIVIAL_MESSAGE_MAX_CHARS = 15; +const TRIVIAL_MESSAGE_MAX_WORDS = 3; +const MAX_TITLE_LENGTH = 80; + +/** + * Whether a user message is too trivial to bother sending to an LLM for + * title generation (e.g. "hey", "hello"). For these, the LLM tends to + * hallucinate an assistant-voice reply as the title instead of echoing + * the user intent — it's better to just use the message itself. + */ +function isTrivialMessage(message: string): boolean { + const normalized = message.trim(); + if (normalized.length <= TRIVIAL_MESSAGE_MAX_CHARS) return true; + const wordCount = normalized.split(/\s+/).filter(Boolean).length; + return wordCount <= TRIVIAL_MESSAGE_MAX_WORDS; +} + +function sanitizeTitle(raw: string): string { + // Strip ... blocks (e.g. from DeepSeek R1) + let title = raw.replace(/[\s\S]*?<\/think>/g, '').trim(); + // Strip markdown heading prefixes and inline emphasis markers + title = title + .replace(/^#{1,6}\s+/, '') + .replace(/\*+/g, '') + .trim(); + // Strip surrounding quotes + title = title.replace(/^["']|["']$/g, '').trim(); + if (title.length > MAX_TITLE_LENGTH) { + const truncated = title.slice(0, MAX_TITLE_LENGTH); + const lastSpace = truncated.lastIndexOf(' '); + title = (lastSpace > 20 ? truncated.slice(0, lastSpace) : truncated) + '\u2026'; + } + return title; +} + +/** + * Generate a sanitized thread title from a user message using an LLM. + * + * Returns `null` on empty input or empty LLM output. For trivial messages + * (e.g. greetings), returns the sanitized message itself without calling + * the LLM — this avoids the failure mode where the model responds with + * an assistant-voice reply as the title. + */ +export async function generateTitleFromMessage( + model: LanguageModel, + userMessage: string, + opts?: { instructions?: string }, +): Promise { + const trimmed = userMessage.trim(); + if (!trimmed) return null; + + if (isTrivialMessage(trimmed)) { + return sanitizeTitle(trimmed) || null; + } + + const result = await generateText({ + model, + messages: [ + { role: 'system', content: opts?.instructions ?? DEFAULT_TITLE_INSTRUCTIONS }, + { role: 'user', content: trimmed }, + ], + }); + + const raw = result.text?.trim(); + if (!raw) return null; + + const title = sanitizeTitle(raw); + return title || null; +} + /** * Generate a title for a thread if it doesn't already have one. * @@ -49,28 +119,9 @@ export async function generateThreadTitle(opts: { const titleModelId = opts.titleConfig.model ?? opts.agentModel; const titleModel = createModel(titleModelId); - const instructions = opts.titleConfig.instructions ?? DEFAULT_TITLE_INSTRUCTIONS; - - const result = await generateText({ - model: titleModel, - messages: [ - { role: 'system', content: instructions }, - { role: 'user', content: userText }, - ], + const title = await generateTitleFromMessage(titleModel, userText, { + instructions: opts.titleConfig.instructions, }); - - let title = result.text?.trim(); - if (!title) return; - - // Strip ... blocks (e.g. from DeepSeek R1) - title = title.replace(/[\s\S]*?<\/think>/g, '').trim(); - if (!title) return; - - // Strip markdown heading prefixes and inline formatting - title = title - .replace(/^#{1,6}\s+/, '') - .replace(/\*+/g, '') - .trim(); if (!title) return; await opts.memory.saveThread({ diff --git a/packages/@n8n/agents/src/types/sdk/agent.ts b/packages/@n8n/agents/src/types/sdk/agent.ts index 148def2a1da..5250dea89c1 100644 --- a/packages/@n8n/agents/src/types/sdk/agent.ts +++ b/packages/@n8n/agents/src/types/sdk/agent.ts @@ -27,8 +27,12 @@ export type TokenUsage = Record } + | LanguageModel; +/* eslint-enable @typescript-eslint/no-redundant-type-constituents */ export interface AgentResult { id?: string; diff --git a/packages/@n8n/instance-ai/src/index.ts b/packages/@n8n/instance-ai/src/index.ts index 453208bd280..bf5bd6e0b40 100644 --- a/packages/@n8n/instance-ai/src/index.ts +++ b/packages/@n8n/instance-ai/src/index.ts @@ -50,7 +50,7 @@ export type { ThreadPatch, WorkflowLoopWorkItemRecord, } from './storage'; -export { truncateToTitle, generateThreadTitle } from './memory/title-utils'; +export { truncateToTitle, generateTitleForRun } from './memory/title-utils'; export { McpClientManager } from './mcp/mcp-client-manager'; export { mapMastraChunkToEvent } from './stream/map-chunk'; export { isRecord, parseSuspension, asResumable } from './utils/stream-helpers'; diff --git a/packages/@n8n/instance-ai/src/memory/__tests__/title-utils.test.ts b/packages/@n8n/instance-ai/src/memory/__tests__/title-utils.test.ts index 0099efa0844..4751ce7b6e5 100644 --- a/packages/@n8n/instance-ai/src/memory/__tests__/title-utils.test.ts +++ b/packages/@n8n/instance-ai/src/memory/__tests__/title-utils.test.ts @@ -1,10 +1,3 @@ -jest.mock('@mastra/core/agent', () => { - const MockAgent = jest.fn(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - MockAgent.prototype.generate = jest.fn().mockResolvedValue({ text: '' }); - return { Agent: MockAgent }; -}); - import { truncateToTitle } from '../title-utils'; describe('truncateToTitle', () => { diff --git a/packages/@n8n/instance-ai/src/memory/title-utils.ts b/packages/@n8n/instance-ai/src/memory/title-utils.ts index 81539c64f5c..52e1374c019 100644 --- a/packages/@n8n/instance-ai/src/memory/title-utils.ts +++ b/packages/@n8n/instance-ai/src/memory/title-utils.ts @@ -1,4 +1,4 @@ -import { Agent } from '@mastra/core/agent'; +import { createModel, generateTitleFromMessage } from '@n8n/agents'; import type { ModelConfig } from '../types'; @@ -13,36 +13,20 @@ export function truncateToTitle(message: string): string { return (lastSpace > 20 ? truncated.slice(0, lastSpace) : truncated) + '\u2026'; } -const TITLE_SYSTEM_PROMPT = [ - 'Generate a concise title (max 60 chars) summarizing what the user wants.', - 'Return ONLY the title text. No quotes, colons, or explanation.', - 'Focus on the user intent, not what the assistant might reply.', - 'Examples: "Build Gmail to Slack workflow", "Debug failed execution", "Show project files"', -].join('\n'); - /** * Generate a polished thread title via a lightweight LLM call. * Returns the cleaned title string or null on failure. + * + * Wraps @n8n/agents' title generation so callers don't have to build a + * LanguageModel themselves. Fails soft — any error returns null. */ -export async function generateThreadTitle( +export async function generateTitleForRun( modelId: ModelConfig, userMessage: string, ): Promise { try { - const agent = new Agent({ - id: 'thread-title-generator', - name: 'Thread Title Generator', - instructions: { - role: 'system' as const, - content: TITLE_SYSTEM_PROMPT, - }, - model: modelId, - }); - - const result = await agent.generate(userMessage, { maxSteps: 1 }); - const title = result.text.trim().replace(/^["']|["']$/g, ''); - if (!title) return null; - return title.length > MAX_TITLE_LENGTH ? truncateToTitle(title) : title; + const model = createModel(modelId); + return await generateTitleFromMessage(model, userMessage); } catch { return null; } diff --git a/packages/cli/src/modules/instance-ai/instance-ai.service.ts b/packages/cli/src/modules/instance-ai/instance-ai.service.ts index 7714a639509..8512eacfa1e 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.service.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.service.ts @@ -47,7 +47,7 @@ import { startResearchAgentTask, streamAgentRun, truncateToTitle, - generateThreadTitle, + generateTitleForRun, patchThread, type ConfirmationData, type DomainAccessTracker, @@ -2466,7 +2466,7 @@ export class InstanceAiService { ? firstUserMsg.content : JSON.stringify(firstUserMsg.content); - const llmTitle = await generateThreadTitle(modelId, userText); + const llmTitle = await generateTitleForRun(modelId, userText); if (!llmTitle) return; await patchThread(memory, { diff --git a/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json index 3b7c5836ced..9f714d53333 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-auto-open-preview-panel-when-workflow-is-built/1775805992921-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json index f7bc4b4f28e..3cdd68e14e2 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-close-preview-panel-via-close-button/1775805998005-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json index 97c9577d981..3cb1b98bd4d 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-create-new-thread-via-sidebar-button/1775805937822-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json index b2bc2c07a3a..4681820dc0f 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-delete-thread-via-action-menu/1775805968314-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json index eaacbae6316..b6c22b5e727 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-display-artifact-card-in-timeline-after-workflow-build/1775805936944-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json index 55132848466..5059201e5dc 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-display-canvas-nodes-in-preview-iframe/1775805998608-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json index bfe7fad8af8..14e0fe61289 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-open-workflow-preview-when-clicking-artifact-card/1775805949876-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json index b4458f19df1..06ed1519e5a 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-persist-messages-after-page-reload/1775805923368-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json index f0c363b6667..97c799573a6 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-approve-workflow-execution/1775805949061-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json index 41ee4b41da3..8ee0eeee150 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-show-approval-panel-and-deny-workflow-execution/1775805967569-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json index d5b6d01eb8a..994d6d69b9f 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995521-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } }, diff --git a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json index e3f853c6af1..2bac77874a6 100644 --- a/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json +++ b/packages/testing/playwright/expectations/instance-ai/should-switch-between-threads/1775805995522-unknown-host-POST-_v1_messages-18622610.json @@ -4,7 +4,7 @@ "path": "/v1/messages", "body": { "type": "STRING", - "string": "[{\"type\":\"text\",\"text\":\"Generate a concise title (max 60 chars) summarizing what", + "string": "you will generate a short title based on the first message", "subString": true } },