chore(editor): Refactoring instance-ai workflow setup FE code (no-changelog) (#30012)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Charlie Kolb <charlie@n8n.io>
This commit is contained in:
Svetoslav Dekov 2026-05-11 16:33:15 +03:00 committed by GitHub
parent 2e046d5b7f
commit 3df6611fb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
135 changed files with 10823 additions and 6694 deletions

View File

@ -250,6 +250,17 @@ export const workflowSetupNodeSchema = z.object({
'Whether this node still requires user intervention. ' +
'False when credentials are set and valid, parameters are resolved, etc.',
),
subnodeRootNode: z
.object({
name: z.string(),
type: z.string(),
typeVersion: z.number(),
id: z.string(),
})
.optional()
.describe(
'Snapshot of the root node for this sub-node connected via a non-Main port (e.g. ai_languageModel, ai_memory, ai_tool). Carries the metadata needed to render the group header even when the root node itself has no setup request.',
),
});
export type InstanceAiWorkflowSetupNode = z.infer<typeof workflowSetupNodeSchema>;

View File

@ -562,6 +562,42 @@ describe('credentials tool', () => {
expect(suspendFn.mock.calls[0][0]).toEqual(expect.objectContaining({ projectId: 'proj-1' }));
});
it('should scope credential lookup to projectId when provided', async () => {
const context = createMockContext();
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const tool = createCredentialsTool(context);
await tool.execute!(
{
action: 'setup' as const,
credentials: [{ credentialType: 'slackApi' }],
projectId: 'proj-1',
},
suspendCtx(jest.fn()),
);
expect(context.credentialService.list).toHaveBeenCalledWith({
type: 'slackApi',
projectId: 'proj-1',
});
});
it('should omit projectId from credential lookup when not provided', async () => {
const context = createMockContext();
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const tool = createCredentialsTool(context);
await tool.execute!(
{
action: 'setup' as const,
credentials: [{ credentialType: 'slackApi' }],
},
suspendCtx(jest.fn()),
);
expect(context.credentialService.list).toHaveBeenCalledWith({ type: 'slackApi' });
});
it('should include credentialFlow in suspend payload for finalize stage', async () => {
const context = createMockContext();
(context.credentialService.list as jest.Mock).mockResolvedValue([]);

View File

@ -543,11 +543,18 @@ describe('workflows tool', () => {
describe('setup action', () => {
it('should analyze workflow and suspend for user setup', async () => {
const actionableSetupRequest = {
node: { name: 'Slack', type: 'n8n-nodes-base.slack' },
credentialType: 'slackApi',
needsAction: true,
};
const setupRequests = [
actionableSetupRequest,
{
node: { name: 'Slack', type: 'n8n-nodes-base.slack' },
credentialType: 'slackApi',
needsAction: true,
node: { name: 'Webhook', type: 'n8n-nodes-base.webhook' },
isTrigger: true,
isTestable: true,
needsAction: false,
},
];
(analyzeWorkflow as jest.Mock).mockResolvedValue(setupRequests);
@ -565,11 +572,33 @@ describe('workflows tool', () => {
expect(suspend.mock.calls[0][0]).toMatchObject({
message: 'Configure credentials for your workflow',
severity: 'info',
setupRequests,
setupRequests: [actionableSetupRequest],
workflowId: 'wf1',
});
});
it('should return success when setup analysis only has non-actionable requests', async () => {
(analyzeWorkflow as jest.Mock).mockResolvedValue([
{
node: { name: 'Webhook', type: 'n8n-nodes-base.webhook' },
isTrigger: true,
isTestable: true,
needsAction: false,
},
]);
const context = createMockContext();
const suspend = jest.fn();
const tool = createWorkflowsTool(context, 'full');
const result = await tool.execute!({ action: 'setup', workflowId: 'wf1' }, {
agent: { suspend, resumeData: undefined },
} as never);
expect(result).toEqual({ success: true, reason: 'No nodes require setup.' });
expect(suspend).not.toHaveBeenCalled();
});
it('should return success when no nodes need setup', async () => {
(analyzeWorkflow as jest.Mock).mockResolvedValue([]);

View File

@ -259,12 +259,17 @@ async function handleSetup(
const suspend = ctx?.agent?.suspend as ((payload: unknown) => Promise<void>) | undefined;
const isFinalize = input.credentialFlow?.stage === 'finalize';
// State 1: First call — look up existing credentials per type and suspend
// State 1: First call — look up existing credentials per type and suspend.
// Scope the lookup to `projectId` when provided so the candidates match what
// the workflow being built can actually use.
if (resumeData === undefined || resumeData === null) {
const credentialRequests = await Promise.all(
input.credentials.map(
async (req: { credentialType: string; reason?: string; suggestedName?: string }) => {
const existing = await context.credentialService.list({ type: req.credentialType });
const existing = await context.credentialService.list({
type: req.credentialType,
...(input.projectId ? { projectId: input.projectId } : {}),
});
return {
credentialType: req.credentialType,
reason: req.reason ?? `Required for ${req.credentialType}`,

View File

@ -11,7 +11,11 @@ import { z } from 'zod';
import { sanitizeInputSchema } from '../agent/sanitize-mcp-schemas';
import type { InstanceAiContext } from '../types';
import { formatTimestamp } from '../utils/format-timestamp';
import { setupSuspendSchema, setupResumeSchema } from './workflows/setup-workflow.schema';
import {
setupSuspendSchema,
setupResumeSchema,
type SetupRequest,
} from './workflows/setup-workflow.schema';
import {
analyzeWorkflow,
applyNodeChanges,
@ -287,6 +291,13 @@ async function handleUnarchive(
return { success: true };
}
function isActionableSetupRequest(req: SetupRequest): boolean {
return (
req.needsAction === true &&
(req.credentialType !== undefined || (req.editableParameters?.length ?? 0) > 0)
);
}
async function handleSetup(
context: InstanceAiContext,
input: Extract<Input, { action: 'setup' }>,
@ -305,7 +316,9 @@ async function handleSetup(
// State 1: Analyze workflow and suspend for user setup
if (resumeData === undefined || resumeData === null) {
const setupRequests = await analyzeWorkflow(context, input.workflowId);
const setupRequests = (await analyzeWorkflow(context, input.workflowId)).filter(
isActionableSetupRequest,
);
if (setupRequests.length === 0) {
return { success: true, reason: 'No nodes require setup.' };

View File

@ -377,6 +377,45 @@ describe('buildSetupRequests', () => {
expect(context.credentialService.list).toHaveBeenCalledTimes(1);
});
it('forwards workflowId to credentialService.list so candidates match save-time scope', async () => {
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const node = makeNode();
await buildSetupRequests(context, node, undefined, undefined, 'wf-1');
expect(context.credentialService.list).toHaveBeenCalledWith({
type: 'slackApi',
workflowId: 'wf-1',
});
});
it('omits workflowId from credentialService.list when not provided', async () => {
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const node = makeNode();
await buildSetupRequests(context, node);
expect(context.credentialService.list).toHaveBeenCalledWith({ type: 'slackApi' });
});
it('cache discriminates by workflowId so a shared cache stays correct across workflows', async () => {
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const cache = createCredentialCache();
const node = makeNode();
await buildSetupRequests(context, node, undefined, cache, 'wf-1');
await buildSetupRequests(context, node, undefined, cache, 'wf-1');
expect(context.credentialService.list).toHaveBeenCalledTimes(1);
await buildSetupRequests(context, node, undefined, cache, 'wf-2');
expect(context.credentialService.list).toHaveBeenCalledTimes(2);
expect(context.credentialService.list).toHaveBeenLastCalledWith({
type: 'slackApi',
workflowId: 'wf-2',
});
});
it('does not generate credential request for HTTP Request with auth=none and stale node.credentials', async () => {
(context.nodeService as unknown as Record<string, unknown>).getNodeCredentialTypes = jest
.fn()
@ -697,6 +736,257 @@ describe('analyzeWorkflow', () => {
const names = result.map((r) => r.node.name);
expect(names.indexOf('Webhook')).toBeLessThan(names.indexOf('Slack'));
});
describe('subnodeRootNode stamping for sub-nodes', () => {
it('stamps subnodeRootNode on every sub-node connected to an agent', async () => {
const agent = makeNode({
name: 'Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'agent-1',
});
const model = makeNode({
name: 'OpenAI Model',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
typeVersion: 1,
id: 'model-1',
});
const memory = makeNode({
name: 'Memory',
type: '@n8n/n8n-nodes-langchain.memoryBufferWindow',
typeVersion: 1,
id: 'memory-1',
});
(context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue(
makeWorkflowJSON([agent, model, memory], {
'OpenAI Model': {
ai_languageModel: [[{ node: 'Agent', type: 'ai_languageModel', index: 0 }]],
},
Memory: { ai_memory: [[{ node: 'Agent', type: 'ai_memory', index: 0 }]] },
}),
);
(context.nodeService.getDescription as jest.Mock).mockImplementation(async (type: string) => {
if (type === '@n8n/n8n-nodes-langchain.lmChatOpenAi') {
return await Promise.resolve({
group: [],
credentials: [{ name: 'openAiApi' }],
});
}
if (type === '@n8n/n8n-nodes-langchain.memoryBufferWindow') {
return await Promise.resolve({ group: [], credentials: [] });
}
return await Promise.resolve({ group: [], credentials: [] });
});
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const result = await analyzeWorkflow(context, 'wf-1');
const modelReq = result.find((r) => r.node.name === 'OpenAI Model');
expect(modelReq?.subnodeRootNode).toEqual({
name: 'Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'agent-1',
});
});
it('stamps the topmost root node for transitively nested sub-agents', async () => {
const agent = makeNode({
name: 'Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'agent-1',
});
const tool = makeNode({
name: 'Tool',
type: '@n8n/n8n-nodes-langchain.toolWorkflow',
typeVersion: 1,
id: 'tool-1',
});
const subModel = makeNode({
name: 'Sub Model',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
typeVersion: 1,
id: 'sub-model-1',
});
(context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue(
makeWorkflowJSON([agent, tool, subModel], {
Tool: { ai_tool: [[{ node: 'Agent', type: 'ai_tool', index: 0 }]] },
'Sub Model': {
ai_languageModel: [[{ node: 'Tool', type: 'ai_languageModel', index: 0 }]],
},
}),
);
(context.nodeService.getDescription as jest.Mock).mockImplementation(async (type: string) => {
if (type === '@n8n/n8n-nodes-langchain.lmChatOpenAi') {
return await Promise.resolve({
group: [],
credentials: [{ name: 'openAiApi' }],
});
}
return await Promise.resolve({ group: [], credentials: [] });
});
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const result = await analyzeWorkflow(context, 'wf-1');
const subModelReq = result.find((r) => r.node.name === 'Sub Model');
expect(subModelReq?.subnodeRootNode?.name).toBe('Agent');
});
it('keeps subnodeRootNode metadata even when the root node itself produced no setup request', async () => {
const agent = makeNode({
name: 'Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'agent-1',
});
const model = makeNode({
name: 'Model',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
typeVersion: 1,
id: 'model-1',
});
(context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue(
makeWorkflowJSON([agent, model], {
Model: { ai_languageModel: [[{ node: 'Agent', type: 'ai_languageModel', index: 0 }]] },
}),
);
(context.nodeService.getDescription as jest.Mock).mockImplementation(async (type: string) => {
if (type === '@n8n/n8n-nodes-langchain.lmChatOpenAi') {
return await Promise.resolve({
group: [],
credentials: [{ name: 'openAiApi' }],
});
}
// Agent itself returns no credentials → no setup request for it.
return await Promise.resolve({ group: [], credentials: [] });
});
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const result = await analyzeWorkflow(context, 'wf-1');
expect(result.find((r) => r.node.name === 'Agent')).toBeUndefined();
const modelReq = result.find((r) => r.node.name === 'Model');
expect(modelReq?.subnodeRootNode).toEqual({
name: 'Agent',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'agent-1',
});
});
it('attaches a multi-root sub-node to the first root node in execution order', async () => {
const trigger = makeNode({
name: 'Trigger',
type: 'n8n-nodes-base.webhook',
id: 'trigger-1',
position: [0, 0] as [number, number],
});
const agentA = makeNode({
name: 'Agent A',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'agent-a',
position: [200, 0] as [number, number],
});
const agentB = makeNode({
name: 'Agent B',
type: '@n8n/n8n-nodes-langchain.agent',
typeVersion: 1,
id: 'agent-b',
position: [400, 0] as [number, number],
});
const sharedModel = makeNode({
name: 'Shared Model',
type: '@n8n/n8n-nodes-langchain.lmChatOpenAi',
typeVersion: 1,
id: 'shared-1',
});
(context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue(
makeWorkflowJSON([trigger, agentA, agentB, sharedModel], {
Trigger: {
main: [
[
{ node: 'Agent A', type: 'main', index: 0 },
{ node: 'Agent B', type: 'main', index: 0 },
],
],
},
'Shared Model': {
ai_languageModel: [
[
{ node: 'Agent A', type: 'ai_languageModel', index: 0 },
{ node: 'Agent B', type: 'ai_languageModel', index: 0 },
],
],
},
}),
);
(context.nodeService.getDescription as jest.Mock).mockImplementation(async (type: string) => {
if (type === 'n8n-nodes-base.webhook') {
return await Promise.resolve({
group: ['trigger'],
credentials: [],
webhooks: [{}],
});
}
if (type === '@n8n/n8n-nodes-langchain.lmChatOpenAi') {
return await Promise.resolve({
group: [],
credentials: [{ name: 'openAiApi' }],
});
}
return await Promise.resolve({ group: [], credentials: [] });
});
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const result = await analyzeWorkflow(context, 'wf-1');
const sharedReq = result.find((r) => r.node.name === 'Shared Model');
// Agent A executes first (left-most), so it claims the shared sub-node.
expect(sharedReq?.subnodeRootNode?.name).toBe('Agent A');
});
it('does not classify a sub-node by following a Main edge', async () => {
const trigger = makeNode({
name: 'Trigger',
type: 'n8n-nodes-base.webhook',
id: 'trigger-1',
position: [0, 0] as [number, number],
});
const httpAction = makeNode({
name: 'HTTP',
type: 'n8n-nodes-base.httpRequest',
id: 'http-1',
position: [200, 0] as [number, number],
});
(context.workflowService.getAsWorkflowJSON as jest.Mock).mockResolvedValue(
makeWorkflowJSON([trigger, httpAction], {
Trigger: { main: [[{ node: 'HTTP', type: 'main', index: 0 }]] },
}),
);
(context.nodeService.getDescription as jest.Mock).mockImplementation(async (type: string) => {
if (type === 'n8n-nodes-base.webhook') {
return await Promise.resolve({
group: ['trigger'],
credentials: [],
webhooks: [{}],
});
}
return await Promise.resolve({
group: [],
credentials: [{ name: 'httpBasicAuth' }],
});
});
(context.credentialService.list as jest.Mock).mockResolvedValue([]);
const result = await analyzeWorkflow(context, 'wf-1');
const httpReq = result.find((r) => r.node.name === 'HTTP');
expect(httpReq?.subnodeRootNode).toBeUndefined();
});
});
});
// ---------------------------------------------------------------------------

View File

@ -8,6 +8,8 @@
import { findPlaceholderDetails } from '@n8n/utils';
import type { IDataObject, NodeJSON, DisplayOptions, WorkflowJSON } from '@n8n/workflow-sdk';
import { matchesDisplayOptions } from '@n8n/workflow-sdk';
import type { IConnections, INode } from 'n8n-workflow';
import { getParentNodes, mapConnectionsByDestination } from 'n8n-workflow';
import { nanoid } from 'nanoid';
import type { SetupRequest } from './setup-workflow.schema';
@ -17,11 +19,12 @@ import type { InstanceAiContext } from '../../types';
/** Cache for deduplicating credential fetches across nodes with the same types. */
export interface CredentialCache {
/** Credential list promises, keyed by credential type. */
/** Credential list promises, keyed by `${workflowId ?? ''}|${credentialType}`
* scope is part of the key so a cache shared across workflows stays correct. */
lists: Map<string, Promise<Array<{ id: string; name: string }>>>;
/** Testability check promises, keyed by credential type. */
/** Testability check promises, keyed by credential type (workflow-independent). */
testability: Map<string, Promise<boolean>>;
/** Credential test result promises, keyed by credential ID. */
/** Credential test result promises, keyed by credential ID (workflow-independent). */
tests: Map<string, Promise<{ success: boolean; message?: string }>>;
}
@ -29,6 +32,10 @@ export function createCredentialCache(): CredentialCache {
return { lists: new Map(), testability: new Map(), tests: new Map() };
}
function listCacheKey(workflowId: string | undefined, credentialType: string): string {
return `${workflowId ?? ''}|${credentialType}`;
}
// ── Node analysis ───────────────────────────────────────────────────────────
/**
@ -158,6 +165,7 @@ export async function buildSetupRequests(
node: NodeJSON,
triggerTestResult?: { status: 'success' | 'error' | 'listening'; error?: string },
cache?: CredentialCache,
workflowId?: string,
): Promise<SetupRequest[]> {
if (!node.name) return [];
if (node.disabled) return [];
@ -292,13 +300,17 @@ export async function buildSetupRequests(
: undefined;
if (credentialType) {
// Use cache to avoid duplicate fetches for the same credential type across nodes
let listPromise = cache?.lists.get(credentialType);
// Use cache to avoid duplicate fetches for the same credential type across nodes.
// Scope to the workflow so we list only credentials the save path will accept —
// the editor's credential picker uses the same scoping. The cache key includes
// workflowId so a cache shared across workflows stays correct.
const cacheKey = listCacheKey(workflowId, credentialType);
let listPromise = cache?.lists.get(cacheKey);
if (!listPromise) {
listPromise = context.credentialService
.list({ type: credentialType })
.list({ type: credentialType, ...(workflowId ? { workflowId } : {}) })
.then((creds) => creds.map((c) => ({ id: c.id, name: c.name })));
cache?.lists.set(credentialType, listPromise);
cache?.lists.set(cacheKey, listPromise);
}
const sortedCreds = await listPromise;
existingCredentials = sortedCreds.map((c) => ({ id: c.id, name: c.name }));
@ -467,7 +479,7 @@ export function sortByExecutionOrder(
if (visited.has(nodeName)) return;
visited.add(nodeName);
// Visit AI sub-nodes BEFORE the parent (non-main incoming connections)
// Visit AI sub-nodes before the node that consumes them (non-main incoming connections).
const subNodes = nonMainIncoming.get(nodeName) ?? [];
for (const subNode of subNodes) {
dfs(subNode);
@ -746,6 +758,77 @@ export function buildCompletedReport(
return result;
}
// ── Sub-node grouping ───────────────────────────────────────────────────────
type SubnodeRootNode = Pick<INode, 'name' | 'type' | 'typeVersion' | 'id'>;
export function buildSubnodeToRootNodeMap(
nodes: NodeJSON[],
connections: IConnections,
executionOrder: string[],
): Map<string, SubnodeRootNode> {
const connectionsByDestination = mapConnectionsByDestination(connections);
const directSubnodesByNodeName = new Map<string, string[]>();
for (const node of nodes) {
if (!node.name) continue;
// Non-main upstream nodes are direct sub-nodes of this node.
const subs = getParentNodes(connectionsByDestination, node.name, 'ALL_NON_MAIN', 1);
if (subs.length > 0) directSubnodesByNodeName.set(node.name, subs);
}
if (directSubnodesByNodeName.size === 0) return new Map();
const allSubnodeNames = new Set<string>();
for (const subs of directSubnodesByNodeName.values()) {
for (const name of subs) allSubnodeNames.add(name);
}
const rootNodeNames = [...directSubnodesByNodeName.keys()].filter((n) => !allSubnodeNames.has(n));
if (rootNodeNames.length === 0) return new Map();
const nodeByName = new Map<string, NodeJSON>();
for (const node of nodes) {
if (node.name) nodeByName.set(node.name, node);
}
// Sort root nodes by execution order so the first to claim a sub-node
// is the deterministic "owner" when multi-root ambiguity exists.
const orderIndex = new Map<string, number>();
for (let i = 0; i < executionOrder.length; i++) {
orderIndex.set(executionOrder[i], i);
}
const sortedRootNodes = [...rootNodeNames].sort(
(a, b) =>
(orderIndex.get(a) ?? Number.MAX_SAFE_INTEGER) -
(orderIndex.get(b) ?? Number.MAX_SAFE_INTEGER),
);
const subnodeToRootNode = new Map<string, SubnodeRootNode>();
for (const rootNodeName of sortedRootNodes) {
const rootNode = nodeByName.get(rootNodeName);
if (!rootNode) continue;
const subnodeRootNode: SubnodeRootNode = {
name: rootNodeName,
type: rootNode.type,
typeVersion: rootNode.typeVersion ?? 1,
id: rootNode.id ?? '',
};
const transitiveSubs = getParentNodes(
connectionsByDestination,
rootNodeName,
'ALL_NON_MAIN',
-1,
);
for (const subName of transitiveSubs) {
if (subnodeToRootNode.has(subName)) continue;
subnodeToRootNode.set(subName, subnodeRootNode);
}
}
return subnodeToRootNode;
}
// ── Full workflow analysis ──────────────────────────────────────────────────
/**
@ -762,7 +845,13 @@ export async function analyzeWorkflow(
const cache = createCredentialCache();
const allRequestArrays = await Promise.all(
workflowJson.nodes.map(async (node) => {
return await buildSetupRequests(context, node, triggerResults?.[node.name ?? ''], cache);
return await buildSetupRequests(
context,
node,
triggerResults?.[node.name ?? ''],
cache,
workflowId,
);
}),
);
@ -785,5 +874,23 @@ export async function analyzeWorkflow(
workflowJson.connections as unknown as Record<string, unknown>,
);
// Stamp `subnodeRootNode` on every sub-node setup request so the frontend can
// render the group header even when the root node has no setup request of
// its own. Sub-node membership is derived from the full workflow graph,
// not just the (filtered) setup requests.
const subnodeToRootNode = buildSubnodeToRootNodeMap(
workflowJson.nodes,
workflowJson.connections as unknown as IConnections,
setupRequests.map((req) => req.node.name),
);
if (subnodeToRootNode.size > 0) {
for (const req of setupRequests) {
const subnodeRootNode = subnodeToRootNode.get(req.node.name);
if (subnodeRootNode) {
req.subnodeRootNode = subnodeRootNode;
}
}
}
return setupRequests;
}

View File

@ -251,7 +251,22 @@ export interface CredentialTypeSearchResult {
}
export interface InstanceAiCredentialService {
list(options?: { type?: string }): Promise<CredentialSummary[]>;
/**
* List credentials.
*
* Without `workflowId` / `projectId`: returns every credential the user has
* read access to anywhere in the instance. Use this for informational lookups.
*
* With `workflowId` or `projectId`: returns only credentials usable in that
* workflow / project (the same scoping the editor's credential picker uses).
* Use this whenever the result feeds a setup card the user will pick from
* the save path enforces the same scope and will reject anything outside it.
*/
list(options?: {
type?: string;
workflowId?: string;
projectId?: string;
}): Promise<CredentialSummary[]>;
get(credentialId: string): Promise<CredentialDetail>;
delete(credentialId: string): Promise<void>;
test(credentialId: string): Promise<{ success: boolean; message?: string }>;

View File

@ -378,13 +378,59 @@ describe('cleanupTestExecutions — scope and deletion pipeline', () => {
});
// ---------------------------------------------------------------------------
// Credential adapter — IDOR boundary for confirmation payload credential IDs
//
// `setupWorkflowApply` and `credentialSelection` confirmation payloads carry
// client-supplied credential IDs. The credential adapter resolves them
// through `credentialsService.getOne(user, ...)`, which the underlying
// service binds to the requesting user — IDs the user can't access throw
// rather than leaking the credential.
// Credential listing — workflow/project scoping
// ---------------------------------------------------------------------------
describe('credentialService.list — scoping', () => {
it('uses getCredentialsAUserCanUseInAWorkflow when workflowId is provided', async () => {
credentialsService.getCredentialsAUserCanUseInAWorkflow.mockResolvedValue([
{ id: 'c1', name: 'Slack Shared', type: 'slackApi' },
{ id: 'c2', name: 'Notion', type: 'notionApi' },
] as never);
const ctx = service.createContext(user);
const result = await ctx.credentialService.list({ type: 'slackApi', workflowId: 'wf-1' });
expect(credentialsService.getCredentialsAUserCanUseInAWorkflow).toHaveBeenCalledWith(user, {
workflowId: 'wf-1',
});
expect(credentialsService.getMany).not.toHaveBeenCalled();
// type filter applied post-fetch
expect(result).toEqual([{ id: 'c1', name: 'Slack Shared', type: 'slackApi' }]);
});
it('uses getCredentialsAUserCanUseInAWorkflow when projectId is provided', async () => {
credentialsService.getCredentialsAUserCanUseInAWorkflow.mockResolvedValue([
{ id: 'c1', name: 'Slack Shared', type: 'slackApi' },
] as never);
const ctx = service.createContext(user);
await ctx.credentialService.list({ projectId: 'proj-1' });
expect(credentialsService.getCredentialsAUserCanUseInAWorkflow).toHaveBeenCalledWith(user, {
projectId: 'proj-1',
});
expect(credentialsService.getMany).not.toHaveBeenCalled();
});
it('falls back to getMany (broad) when neither workflowId nor projectId is provided', async () => {
credentialsService.getMany.mockResolvedValue([
{ id: 'c1', name: 'Slack', type: 'slackApi' },
] as never);
const ctx = service.createContext(user);
await ctx.credentialService.list({ type: 'slackApi' });
expect(credentialsService.getMany).toHaveBeenCalledWith(user, {
listQueryOptions: { filter: { type: 'slackApi' } },
includeGlobal: true,
});
expect(credentialsService.getCredentialsAUserCanUseInAWorkflow).not.toHaveBeenCalled();
});
});
// ---------------------------------------------------------------------------
// Credential adapter — credential ownership revalidation
// ---------------------------------------------------------------------------
describe('credentialService.get — credential ownership revalidation', () => {

View File

@ -125,6 +125,40 @@ import { Telemetry } from '@/telemetry';
import { WorkflowRunner } from '@/workflow-runner';
import { getBase } from '@/workflow-execute-additional-data';
/**
* Fill in defaults for properties whose visibility depends on sibling values
* (e.g. OpenAI v2's per-resource `operation`). A naive single-pass loop picks
* the first variant of a duplicated property name, which leaves dependent
* properties (like `modelId` for `text`/`response`) out of view of the issue
* and credential checkers. `getNodeParameters` walks the dependency graph and
* fills only displayed properties.
*/
function resolveDisplayedDefaults(
nodeProperties: INodeProperties[],
parameters: Record<string, unknown>,
nodeType: string,
typeVersion: number,
desc: INodeTypeDescription,
): INodeParameters {
const stubNode: INode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: parameters as INodeParameters,
position: [0, 0],
};
const resolved = NodeHelpers.getNodeParameters(
nodeProperties,
parameters as INodeParameters,
true,
false,
stubNode,
desc,
);
return resolved ?? (parameters as INodeParameters);
}
@Service()
export class InstanceAiAdapterService {
private readonly logger: Logger;
@ -963,6 +997,30 @@ export class InstanceAiAdapterService {
return {
async list(options) {
// Setup flows scope to a workflow or project so the candidates match what
// the save path will accept. `preventTampering` (workflow.service.ee.ts)
// uses `getCredentialsAUserCanUseInAWorkflow` for the same intersection,
// so the editor's credential picker and the AI's setup card stay aligned.
if (options?.workflowId || options?.projectId) {
const scoped = options.workflowId
? await credentialsService.getCredentialsAUserCanUseInAWorkflow(user, {
workflowId: options.workflowId,
})
: await credentialsService.getCredentialsAUserCanUseInAWorkflow(user, {
projectId: options.projectId!,
});
const filtered = options.type ? scoped.filter((c) => c.type === options.type) : scoped;
return filtered.map(
(c): CredentialSummary => ({
id: c.id,
name: c.name,
type: c.type,
}),
);
}
const credentials = await credentialsService.getMany(user, {
listQueryOptions: {
filter: options?.type ? { type: options.type } : undefined,
@ -1834,21 +1892,20 @@ export class InstanceAiAdapterService {
if (!desc) return {};
const nodeProperties = desc.properties;
// Fill in default values for parameters not explicitly set
const paramsWithDefaults: Record<string, unknown> = { ...parameters };
for (const prop of nodeProperties) {
if (!(prop.name in paramsWithDefaults) && prop.default !== undefined) {
paramsWithDefaults[prop.name] = prop.default;
}
}
const paramsWithDefaults = resolveDisplayedDefaults(
nodeProperties,
parameters,
nodeType,
typeVersion,
desc as unknown as INodeTypeDescription,
);
const minimalNode: INode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: paramsWithDefaults as INodeParameters,
parameters: paramsWithDefaults,
position: [0, 0],
};
@ -1880,7 +1937,7 @@ export class InstanceAiAdapterService {
if (
prop.displayOptions &&
!NodeHelpers.displayParameter(
paramsWithDefaults as INodeParameters,
paramsWithDefaults,
prop,
minimalNode,
desc as unknown as INodeTypeDescription,
@ -1904,31 +1961,32 @@ export class InstanceAiAdapterService {
const credentialTypes = new Set<string>();
// 1. Displayable credentials from node type description
const nodeCredentials = desc.credentials ?? [];
// Fill defaults before evaluating display options
const paramsWithDefaultsForCreds: Record<string, unknown> = { ...parameters };
for (const prop of desc.properties) {
if (!(prop.name in paramsWithDefaultsForCreds) && prop.default !== undefined) {
paramsWithDefaultsForCreds[prop.name] = prop.default;
}
}
const credCheckNode: INode = {
const paramsWithDefaults = resolveDisplayedDefaults(
desc.properties,
parameters,
nodeType,
typeVersion,
desc as unknown as INodeTypeDescription,
);
const minimalNode: INode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: paramsWithDefaultsForCreds as INodeParameters,
parameters: paramsWithDefaults,
position: [0, 0],
};
// 1. Displayable credentials from node type description
const nodeCredentials = desc.credentials ?? [];
for (const cred of nodeCredentials) {
// Check if credential is displayable given current parameters
if (cred.displayOptions) {
if (
!NodeHelpers.displayParameter(
paramsWithDefaultsForCreds as INodeParameters,
paramsWithDefaults,
cred,
credCheckNode,
minimalNode,
desc as unknown as INodeTypeDescription,
)
) {
@ -1939,20 +1997,6 @@ export class InstanceAiAdapterService {
}
// 2. Node issues for dynamic credentials (e.g. HTTP Request missing auth)
const paramsWithDefaults: Record<string, unknown> = { ...parameters };
for (const prop of desc.properties) {
if (!(prop.name in paramsWithDefaults) && prop.default !== undefined) {
paramsWithDefaults[prop.name] = prop.default;
}
}
const minimalNode: INode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: paramsWithDefaults as INodeParameters,
position: [0, 0],
};
const issues = NodeHelpers.getNodeParametersIssues(
desc.properties,
minimalNode,

View File

@ -5450,6 +5450,7 @@
"instanceAi.workflowSetup.triggerError": "Trigger test failed",
"instanceAi.workflowSetup.triggerListening": "Listening for events…",
"instanceAi.workflowSetup.apply": "Apply",
"instanceAi.workflowSetup.credentialTestFailedTooltip": "Credential test failed. Update the credential and try again.",
"instanceAi.workflowSetup.applyCompleted": "Apply completed",
"instanceAi.workflowSetup.later": "Skip setup for now",
"instanceAi.workflowSetup.loading": "Loading setup…",
@ -5457,10 +5458,14 @@
"instanceAi.workflowSetup.applied": "Workflow configured",
"instanceAi.workflowSetup.partiallyApplied": "Partially configured — some nodes skipped",
"instanceAi.workflowSetup.deferred": "Skipped — configure later",
"instanceAi.workflowSetup.cardSkipped": "Skipped",
"instanceAi.workflowSetup.selectOption": "Select an option",
"instanceAi.workflowSetup.confirmTitle": "Ready to apply",
"instanceAi.workflowSetup.confirmDescription": "{count} item configured | {count} items configured",
"instanceAi.workflowSetup.reviewDetails": "Review details",
"instanceAi.workflowSetup.stepCounter": "{current} of {total}",
"instanceAi.workflowSetup.prevStep": "Previous step",
"instanceAi.workflowSetup.nextStep": "Next step",
"instanceAi.debug.title": "Debug",
"instanceAi.debug.copyTrace": "Copy trace",
"instanceAi.debug.timing": "Timing",

View File

@ -644,6 +644,7 @@ export interface NewCredentialsModal extends ModalState {
projectId?: string;
suggestedName?: string;
nodeName?: string;
contextNode?: INodeUi;
hideAskAssistant?: boolean;
}

View File

@ -88,6 +88,7 @@ import type {
ModalKey,
AppliedThemeOption,
TabOptions,
INodeUi,
} from '@/Interface';
import { defineStore } from 'pinia';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
@ -541,6 +542,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
modalsById.value[CREDENTIAL_EDIT_MODAL_KEY] = {
...modalsById.value[CREDENTIAL_EDIT_MODAL_KEY],
projectId: undefined,
contextNode: undefined,
hideAskAssistant: options.hideAskAssistant,
} as NewCredentialsModal;
openModal(CREDENTIAL_EDIT_MODAL_KEY);
@ -553,6 +555,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
projectId?: string,
suggestedName?: string,
nodeName?: string,
contextNode?: INodeUi,
options: { hideAskAssistant?: boolean } = {},
) => {
setActiveId(CREDENTIAL_EDIT_MODAL_KEY, type);
@ -563,6 +566,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
projectId,
suggestedName,
nodeName,
contextNode,
hideAskAssistant: options.hideAskAssistant,
} as NewCredentialsModal;
setMode(CREDENTIAL_EDIT_MODAL_KEY, 'new');

View File

@ -186,6 +186,7 @@ describe('agent integration credential picker usage', () => {
'project-1',
undefined,
undefined,
undefined,
{ hideAskAssistant: true },
);
});
@ -217,6 +218,7 @@ describe('agent integration credential picker usage', () => {
'project-1',
undefined,
undefined,
undefined,
{ hideAskAssistant: true },
);
});

View File

@ -380,6 +380,7 @@ function onCreateCredential(integration: ChatIntegrationDescriptor) {
props.data.projectId,
undefined,
undefined,
undefined,
{
hideAskAssistant: true,
},

View File

@ -316,6 +316,7 @@ function onCreateCredential(type: string) {
props.projectId,
undefined,
undefined,
undefined,
{
hideAskAssistant: true,
},

View File

@ -0,0 +1,133 @@
import { computed, ref } from 'vue';
import { mount } from '@vue/test-utils';
import { describe, expect, it, vi } from 'vitest';
import type { INodeUi } from '@/Interface';
import WorkflowSetupSectionBody from '../workflowSetup/components/WorkflowSetupSectionBody.vue';
import { makeWorkflowSetupSection } from '../workflowSetup/__tests__/factories';
import type { WorkflowSetupContext } from '../workflowSetup/composables/useWorkflowSetupContext';
import type { WorkflowSetupSection } from '../workflowSetup/workflowSetup.types';
const workflowSetupContext = vi.hoisted(() => ({
current: undefined as unknown as WorkflowSetupContext,
}));
vi.mock('../workflowSetup/composables/useWorkflowSetupContext', () => ({
useWorkflowSetupContext: () => workflowSetupContext.current,
}));
vi.mock('@/features/credentials/components/NodeCredentials.vue', () => ({
default: { template: '<div><slot name="label-postfix" /></div>' },
}));
vi.mock('@/features/ndv/parameters/components/ParameterInputList.vue', () => ({
default: { template: '<div />' },
}));
vi.mock('@n8n/i18n', async (importOriginal) => ({
...(await importOriginal()),
useI18n: () => ({
baseText: (key: string, options?: { interpolate?: Record<string, string | number> }) => {
if (key === 'instanceAi.workflowSetup.usedByNodes') {
return `Used by ${options?.interpolate?.count} nodes`;
}
return key;
},
}),
}));
vi.mock('@/features/credentials/credentials.store', () => ({
useCredentialsStore: () => ({
getCredentialTypeByName: () => ({ displayName: 'HTTP Basic Auth' }),
getCredentialById: () => undefined,
}),
}));
vi.mock('@/app/stores/nodeTypes.store', () => ({
useNodeTypesStore: () => ({
getNodeType: () => null,
communityNodeType: () => null,
}),
}));
vi.mock('@/features/settings/environments.ee/environments.store', () => ({
default: () => ({ variablesAsObject: {} }),
}));
function makeContext(): WorkflowSetupContext {
return {
sections: computed(() => []),
steps: computed(() => []),
currentStepIndex: ref(0),
activeStep: computed(() => undefined),
hasOtherUnhandledSteps: computed(() => false),
canAdvanceToNextIncomplete: computed(() => false),
credentialSelections: ref({}),
terminalState: ref(null),
isReady: ref(true),
projectId: computed(() => undefined),
credentialFlow: computed(() => undefined),
isActionPending: ref(false),
setCredential: vi.fn(),
setParameterValue: vi.fn(),
getDisplayNode: (section) => section.node as INodeUi,
isSectionComplete: () => false,
isCredentialTestFailed: () => false,
isSectionSkipped: () => false,
isStepComplete: () => false,
isStepSkipped: () => false,
isStepHandled: () => false,
goToStep: vi.fn(),
goToNext: vi.fn(),
goToPrev: vi.fn(),
goToNextIncomplete: vi.fn(),
apply: vi.fn(async () => {}),
skipCurrentStep: vi.fn(async () => {}),
};
}
function renderComponent(section: WorkflowSetupSection) {
workflowSetupContext.current = makeContext();
return mount(WorkflowSetupSectionBody, {
props: { section },
global: {
stubs: {
N8nText: { template: '<span><slot /></span>' },
N8nTooltip: {
template:
'<div data-test-id="instance-ai-workflow-setup-card-tooltip"><span data-test-id="instance-ai-workflow-setup-card-tooltip-content"><slot name="content" /></span><slot /></div>',
},
},
},
});
}
describe('WorkflowSetupSectionBody', () => {
it('hides the grouped nodes hint for single-target sections', () => {
const section = makeWorkflowSetupSection();
const wrapper = renderComponent(section);
expect(
wrapper.find('[data-test-id="instance-ai-workflow-setup-card-nodes-hint"]').exists(),
).toBe(false);
});
it('shows grouped nodes count and tooltip for multi-target sections', () => {
const section = makeWorkflowSetupSection({
credentialTargetNodes: [
{ id: 'primary', name: 'Primary', type: 'n8n-nodes-base.httpRequest' },
{ id: 'follower', name: 'Follower', type: 'n8n-nodes-base.httpRequest' },
],
});
const wrapper = renderComponent(section);
expect(wrapper.get('[data-test-id="instance-ai-workflow-setup-card-nodes-hint"]').text()).toBe(
'Used by 2 nodes',
);
expect(
wrapper.get('[data-test-id="instance-ai-workflow-setup-card-tooltip-content"]').text(),
).toBe('Primary, Follower');
});
});

View File

@ -0,0 +1,225 @@
import { computed, nextTick, ref, type Ref } from 'vue';
import { describe, expect, it, vi } from 'vitest';
import { createComponentRenderer } from '@/__tests__/render';
import WorkflowSetupWizard from '../workflowSetup/components/WorkflowSetupWizard.vue';
import type { WorkflowSetupContext } from '../workflowSetup/composables/useWorkflowSetupContext';
import { makeWorkflowSetupSection } from '../workflowSetup/__tests__/factories';
import type { WorkflowSetupSection, WorkflowSetupStep } from '../workflowSetup/workflowSetup.types';
import type { INodeUi } from '@/Interface';
const workflowSetupContext = vi.hoisted(() => ({
current: undefined as unknown as WorkflowSetupContext,
}));
vi.mock('../workflowSetup/composables/useWorkflowSetupContext', () => ({
useWorkflowSetupContext: () => workflowSetupContext.current,
}));
vi.mock('@n8n/i18n', async (importOriginal) => ({
...(await importOriginal()),
useI18n: () => ({
baseText: (key: string) => key,
}),
}));
const renderComponent = createComponentRenderer(WorkflowSetupWizard, {
global: {
stubs: {
ConfirmationFooter: {
template: '<footer><slot /></footer>',
},
WorkflowSetupCard: {
template:
'<section data-test-id="instance-ai-workflow-setup-card"><slot name="footer" /></section>',
},
WorkflowSetupGroupCard: {
template:
'<section data-test-id="instance-ai-workflow-setup-group-card"><slot name="footer" /></section>',
},
N8nButton: {
props: ['disabled', 'label'],
emits: ['click'],
template:
'<button :disabled="disabled" @click="$emit(\'click\')">{{ label }}<slot /></button>',
},
N8nIconButton: {
props: ['disabled', 'icon'],
emits: ['click'],
template: '<button :disabled="disabled" @click="$emit(\'click\')">{{ icon }}</button>',
},
N8nTooltip: {
props: ['content', 'disabled'],
template:
'<div data-test-id="instance-ai-workflow-setup-tooltip" :data-content="content" :data-disabled="disabled"><slot /></div>',
},
N8nIcon: {
template: '<span />',
},
N8nText: {
template: '<span><slot /></span>',
},
},
},
});
const sectionA = makeWorkflowSetupSection();
const sectionB = makeWorkflowSetupSection({
id: 'Slack:slackApi',
credentialType: 'slackApi',
targetNodeName: 'Slack',
node: {
id: 'slack',
type: 'n8n-nodes-base.slack',
},
});
interface ContextOptions {
sections?: WorkflowSetupSection[];
currentStepIndex?: Ref<number>;
isSkipped?: Ref<boolean>;
isCredentialTestFailed?: Ref<boolean>;
isActionPending?: Ref<boolean>;
hasOtherUnhandledSteps?: Ref<boolean>;
}
function makeContext(isComplete: Ref<boolean>, options: ContextOptions = {}): WorkflowSetupContext {
const sections = options.sections ?? [sectionA];
const currentStepIndex = options.currentStepIndex ?? ref(0);
const steps = computed<WorkflowSetupStep[]>(() =>
sections.map((section) => ({ kind: 'section', section })),
);
const isStepHandled = (step: WorkflowSetupStep): boolean => {
if (step.kind !== 'section') return false;
return isComplete.value || (options.isSkipped?.value ?? false);
};
return {
sections: computed(() => sections),
steps,
currentStepIndex,
activeStep: computed(() => steps.value[currentStepIndex.value]),
hasOtherUnhandledSteps: computed(() => options.hasOtherUnhandledSteps?.value ?? false),
canAdvanceToNextIncomplete: computed(() => false),
credentialSelections: ref({}),
terminalState: ref(null),
isReady: ref(true),
projectId: computed(() => undefined),
credentialFlow: computed(() => undefined),
isActionPending: options.isActionPending ?? ref(false),
setCredential: vi.fn(),
setParameterValue: vi.fn(),
getDisplayNode: (section) => section.node as INodeUi,
isSectionComplete: () => isComplete.value,
isCredentialTestFailed: () => options.isCredentialTestFailed?.value ?? false,
isSectionSkipped: () => options.isSkipped?.value ?? false,
isStepComplete: () => isComplete.value,
isStepSkipped: () => options.isSkipped?.value ?? false,
isStepHandled,
goToStep: vi.fn(),
goToNext: vi.fn(),
goToPrev: vi.fn(),
goToNextIncomplete: vi.fn(),
apply: vi.fn(async () => {}),
skipCurrentStep: vi.fn(async () => {}),
};
}
describe('WorkflowSetupWizard', () => {
it('hides the skip action once the active setup step is complete', async () => {
const isComplete = ref(false);
workflowSetupContext.current = makeContext(isComplete);
const { queryByTestId } = renderComponent();
expect(queryByTestId('instance-ai-workflow-setup-later')).not.toBeNull();
isComplete.value = true;
await nextTick();
expect(queryByTestId('instance-ai-workflow-setup-later')).toBeNull();
expect(queryByTestId('instance-ai-workflow-setup-apply')).not.toBeNull();
});
it('hides footer navigation arrows when there is only one setup step', () => {
workflowSetupContext.current = makeContext(ref(false));
const { queryByTestId } = renderComponent();
expect(queryByTestId('instance-ai-workflow-setup-prev')).toBeNull();
expect(queryByTestId('instance-ai-workflow-setup-next')).toBeNull();
});
it('disables footer navigation arrows at the first and last setup steps', async () => {
const currentStepIndex = ref(0);
workflowSetupContext.current = makeContext(ref(false), {
sections: [sectionA, sectionB],
currentStepIndex,
});
const { getByTestId } = renderComponent();
expect(getByTestId('instance-ai-workflow-setup-prev')).toHaveAttribute('disabled');
expect(getByTestId('instance-ai-workflow-setup-next')).not.toHaveAttribute('disabled');
currentStepIndex.value = 1;
await nextTick();
expect(getByTestId('instance-ai-workflow-setup-prev')).not.toHaveAttribute('disabled');
expect(getByTestId('instance-ai-workflow-setup-next')).toHaveAttribute('disabled');
});
it('disables the skip action while a footer action is pending', async () => {
const isActionPending = ref(true);
workflowSetupContext.current = makeContext(ref(false), { isActionPending });
const { getByTestId } = renderComponent();
expect(getByTestId('instance-ai-workflow-setup-later')).toHaveAttribute('disabled');
isActionPending.value = false;
await nextTick();
expect(getByTestId('instance-ai-workflow-setup-later')).not.toHaveAttribute('disabled');
});
it('enables the primary footer action for skipped setup steps', async () => {
const isSkipped = ref(false);
workflowSetupContext.current = makeContext(ref(false), { isSkipped });
const { getByTestId, queryByTestId } = renderComponent();
expect(getByTestId('instance-ai-workflow-setup-apply')).toHaveAttribute('disabled');
expect(queryByTestId('instance-ai-workflow-setup-later')).not.toBeNull();
isSkipped.value = true;
await nextTick();
expect(getByTestId('instance-ai-workflow-setup-apply')).not.toHaveAttribute('disabled');
expect(queryByTestId('instance-ai-workflow-setup-later')).toBeNull();
});
it('shows the continue footer action instead of apply when there are other unhandled steps', () => {
workflowSetupContext.current = makeContext(ref(true), { hasOtherUnhandledSteps: ref(true) });
const { getByTestId, queryByTestId } = renderComponent();
expect(getByTestId('instance-ai-workflow-setup-continue')).not.toHaveAttribute('disabled');
expect(queryByTestId('instance-ai-workflow-setup-apply')).toBeNull();
});
it('enables the footer tooltip when the active credential test failed', () => {
workflowSetupContext.current = makeContext(ref(true), { isCredentialTestFailed: ref(true) });
const { getByTestId } = renderComponent();
expect(getByTestId('instance-ai-workflow-setup-tooltip')).toHaveAttribute(
'data-content',
'instanceAi.workflowSetup.credentialTestFailedTooltip',
);
expect(getByTestId('instance-ai-workflow-setup-tooltip')).toHaveAttribute(
'data-disabled',
'false',
);
});
});

View File

@ -1,392 +0,0 @@
import { describe, test, expect } from 'vitest';
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import {
credGroupKey,
isParamValueSet,
isNestedParam,
toNodeUi,
isTriggerOnly,
shouldUseCredentialIcon,
type SetupCard,
} from '../instanceAiWorkflowSetup.utils';
import type { INodeProperties } from 'n8n-workflow';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeSetupNode(
overrides: Partial<InstanceAiWorkflowSetupNode> = {},
): InstanceAiWorkflowSetupNode {
return {
node: {
name: 'Slack',
type: 'n8n-nodes-base.slack',
typeVersion: 2,
parameters: {},
position: [250, 300] as [number, number],
id: 'node-1',
},
isTrigger: false,
...overrides,
} as InstanceAiWorkflowSetupNode;
}
function makeCard(overrides: Partial<SetupCard> = {}): SetupCard {
return {
id: 'card-1',
nodes: [makeSetupNode()],
isTrigger: false,
isFirstTrigger: false,
isTestable: false,
isAutoApplied: false,
hasParamIssues: false,
...overrides,
};
}
// ---------------------------------------------------------------------------
// credGroupKey
// ---------------------------------------------------------------------------
describe('credGroupKey', () => {
test('returns credential type as key for standard nodes', () => {
const req = makeSetupNode({ credentialType: 'slackApi' });
expect(credGroupKey(req)).toBe('slackApi');
});
test('returns node name when credentialType is missing', () => {
const req = makeSetupNode({ credentialType: undefined });
expect(credGroupKey(req)).toBe('Slack');
});
test('includes URL for HTTP Request nodes', () => {
const req = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: 'https://api.example.com' },
position: [0, 0] as [number, number],
id: 'node-http',
},
});
expect(credGroupKey(req)).toBe('httpBasicAuth:http:https://api.example.com');
});
test('uses expression-specific key for HTTP Request with expression URL', () => {
const req = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: '={{ $json.url }}' },
position: [0, 0] as [number, number],
id: 'node-http',
},
});
expect(credGroupKey(req)).toBe('httpBasicAuth:http:expr:HTTP Request');
});
test('handles HTTP Request Tool node type', () => {
const req = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP Tool',
type: 'n8n-nodes-base.httpRequestTool',
typeVersion: 1,
parameters: { url: 'https://api.test.com' },
position: [0, 0] as [number, number],
id: 'node-http-tool',
},
});
expect(credGroupKey(req)).toContain('httpBasicAuth:http:');
});
test('groups two non-HTTP nodes sharing a credential type under one key', () => {
const slackA = makeSetupNode({
credentialType: 'slackApi',
node: {
name: 'Slack A',
type: 'n8n-nodes-base.slack',
typeVersion: 2,
parameters: {},
position: [0, 0] as [number, number],
id: 'a',
},
});
const slackB = makeSetupNode({
credentialType: 'slackApi',
node: {
name: 'Slack B',
type: 'n8n-nodes-base.slack',
typeVersion: 2,
parameters: {},
position: [0, 0] as [number, number],
id: 'b',
},
});
expect(credGroupKey(slackA)).toBe(credGroupKey(slackB));
});
test('splits HTTP Request nodes with different static URLs even for same credential type', () => {
const httpA = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP A',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: 'https://a.example.com' },
position: [0, 0] as [number, number],
id: 'a',
},
});
const httpB = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP B',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: 'https://b.example.com' },
position: [0, 0] as [number, number],
id: 'b',
},
});
expect(credGroupKey(httpA)).not.toBe(credGroupKey(httpB));
});
test('groups HTTP Request nodes with the same static URL and credential type', () => {
const httpA = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP A',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: 'https://api.example.com' },
position: [0, 0] as [number, number],
id: 'a',
},
});
const httpB = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP B',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: 'https://api.example.com' },
position: [0, 0] as [number, number],
id: 'b',
},
});
expect(credGroupKey(httpA)).toBe(credGroupKey(httpB));
});
test('splits HTTP Request nodes with expression URLs regardless of string equality', () => {
const exprA = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP A',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: '={{ $json.url }}' },
position: [0, 0] as [number, number],
id: 'a',
},
});
const exprB = makeSetupNode({
credentialType: 'httpBasicAuth',
node: {
name: 'HTTP B',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4,
parameters: { url: '={{ $json.url }}' },
position: [0, 0] as [number, number],
id: 'b',
},
});
expect(credGroupKey(exprA)).not.toBe(credGroupKey(exprB));
});
});
// ---------------------------------------------------------------------------
// isParamValueSet
// ---------------------------------------------------------------------------
describe('isParamValueSet', () => {
test('returns false for undefined', () => {
expect(isParamValueSet(undefined)).toBe(false);
});
test('returns false for null', () => {
expect(isParamValueSet(null)).toBe(false);
});
test('returns false for empty string', () => {
expect(isParamValueSet('')).toBe(false);
});
test('returns true for non-empty string', () => {
expect(isParamValueSet('hello')).toBe(true);
});
test('returns true for number', () => {
expect(isParamValueSet(42)).toBe(true);
});
test('returns true for boolean', () => {
expect(isParamValueSet(false)).toBe(true);
});
test('returns false for empty resource locator', () => {
expect(isParamValueSet({ __rl: true, value: '', mode: 'list' })).toBe(false);
});
test('returns true for non-empty resource locator', () => {
expect(isParamValueSet({ __rl: true, value: 'some-id', mode: 'list' })).toBe(true);
});
test('returns false for placeholder sentinel string', () => {
expect(isParamValueSet('<__PLACEHOLDER_VALUE__your_email__>')).toBe(false);
});
test('returns false for resource locator with placeholder sentinel value', () => {
expect(
isParamValueSet({
__rl: true,
value: '<__PLACEHOLDER_VALUE__channel_name__>',
mode: 'list',
}),
).toBe(false);
});
});
// ---------------------------------------------------------------------------
// isNestedParam
// ---------------------------------------------------------------------------
describe('isNestedParam', () => {
test('returns true for collection type', () => {
expect(isNestedParam({ type: 'collection' } as INodeProperties)).toBe(true);
});
test('returns true for fixedCollection type', () => {
expect(isNestedParam({ type: 'fixedCollection' } as INodeProperties)).toBe(true);
});
test('returns true for multipleValues typeOption', () => {
expect(
isNestedParam({
type: 'string',
typeOptions: { multipleValues: true },
} as INodeProperties),
).toBe(true);
});
test('returns false for simple string type', () => {
expect(isNestedParam({ type: 'string' } as INodeProperties)).toBe(false);
});
});
// ---------------------------------------------------------------------------
// toNodeUi
// ---------------------------------------------------------------------------
describe('toNodeUi', () => {
test('maps setup node fields to INodeUi', () => {
const req = makeSetupNode({
node: {
id: 'abc',
name: 'Test',
type: 'n8n-nodes-base.test',
typeVersion: 3,
position: [100, 200] as [number, number],
parameters: { foo: 'bar' },
},
});
const result = toNodeUi(req);
expect(result.id).toBe('abc');
expect(result.name).toBe('Test');
expect(result.type).toBe('n8n-nodes-base.test');
expect(result.typeVersion).toBe(3);
expect(result.position).toEqual([100, 200]);
expect(result.parameters).toEqual({ foo: 'bar' });
});
test('includes credentials when defined', () => {
const req = makeSetupNode({
node: {
id: 'abc',
name: 'Test',
type: 'n8n-nodes-base.test',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: {},
credentials: { slackApi: { id: 'cred-1', name: 'My Slack' } },
},
});
const result = toNodeUi(req);
expect(result.credentials).toEqual({ slackApi: { id: 'cred-1', name: 'My Slack' } });
});
test('omits credentials when undefined', () => {
const req = makeSetupNode({
node: {
id: 'abc',
name: 'Test',
type: 'n8n-nodes-base.test',
typeVersion: 1,
position: [0, 0] as [number, number],
parameters: {},
credentials: undefined,
},
});
const result = toNodeUi(req);
expect(result.credentials).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// isTriggerOnly / shouldUseCredentialIcon
// ---------------------------------------------------------------------------
describe('isTriggerOnly', () => {
const noParamWork = () => false;
const hasParamWork = () => true;
test('returns true for trigger without credential or param work', () => {
const card = makeCard({ isTrigger: true, credentialType: undefined });
expect(isTriggerOnly(card, noParamWork)).toBe(true);
});
test('returns false if card has credential type', () => {
const card = makeCard({ isTrigger: true, credentialType: 'slackApi' });
expect(isTriggerOnly(card, noParamWork)).toBe(false);
});
test('returns false if card has param work', () => {
const card = makeCard({ isTrigger: true });
expect(isTriggerOnly(card, hasParamWork)).toBe(false);
});
test('returns false if not a trigger', () => {
const card = makeCard({ isTrigger: false });
expect(isTriggerOnly(card, noParamWork)).toBe(false);
});
});
describe('shouldUseCredentialIcon', () => {
test('returns true for multi-node credential-grouping cards', () => {
const card = makeCard({
credentialType: 'googleOAuth2Api',
nodes: [makeSetupNode(), makeSetupNode(), makeSetupNode()],
});
expect(shouldUseCredentialIcon(card)).toBe(true);
});
test('returns false for single-node cards', () => {
const card = makeCard({ credentialType: 'slackApi', nodes: [makeSetupNode()] });
expect(shouldUseCredentialIcon(card)).toBe(false);
});
});

View File

@ -1,338 +0,0 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import { computed } from 'vue';
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import type { INodeTypeDescription } from 'n8n-workflow';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useUIStore } from '@/app/stores/ui.store';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useCredentialGroupSelection } from '../composables/useCredentialGroupSelection';
import type { SetupCard } from '../instanceAiWorkflowSetup.utils';
import {
createWorkflowDocumentId,
useWorkflowDocumentStore,
} from '@/app/stores/workflowDocument.store';
vi.mock('@/app/stores/workflows.store', () => ({
useWorkflowsStore: vi.fn(() => ({ workflowId: '' })),
}));
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeSetupNode(
overrides: Partial<InstanceAiWorkflowSetupNode> = {},
): InstanceAiWorkflowSetupNode {
return {
node: {
name: 'Slack',
type: 'n8n-nodes-base.slack',
typeVersion: 2,
parameters: {},
position: [0, 0] as [number, number],
id: 'node-1',
},
isTrigger: false,
...overrides,
} as InstanceAiWorkflowSetupNode;
}
function makeCard(overrides: Partial<SetupCard> = {}): SetupCard {
return {
id: 'card-1',
credentialType: 'slackApi',
nodes: [makeSetupNode()],
isTrigger: false,
isFirstTrigger: false,
isTestable: false,
isAutoApplied: false,
hasParamIssues: false,
...overrides,
};
}
describe('useCredentialGroupSelection', () => {
let workflowDocumentStore: ReturnType<typeof useWorkflowDocumentStore>;
let credentialsStore: ReturnType<typeof useCredentialsStore>;
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
let uiStore: ReturnType<typeof useUIStore>;
beforeEach(() => {
const pinia = createTestingPinia({ stubActions: false });
setActivePinia(pinia);
workflowDocumentStore = useWorkflowDocumentStore(createWorkflowDocumentId(''));
credentialsStore = useCredentialsStore();
nodeTypesStore = useNodeTypesStore();
uiStore = useUIStore();
});
describe('initCredGroupSelections', () => {
test('picks up existing credential from node', () => {
const card = makeCard({
nodes: [
makeSetupNode({
credentialType: 'slackApi',
node: {
name: 'Slack',
type: 'n8n-nodes-base.slack',
typeVersion: 2,
parameters: {},
position: [0, 0] as [number, number],
id: 'node-1',
credentials: { slackApi: { id: 'cred-1', name: 'My Slack' } },
},
}),
],
});
const cards = computed(() => [card]);
const { initCredGroupSelections, getCardCredentialId } = useCredentialGroupSelection(
cards,
vi.fn(),
);
initCredGroupSelections();
expect(getCardCredentialId(card)).toBe('cred-1');
});
test('auto-selects when single existing credential is available', () => {
const card = makeCard({
nodes: [
makeSetupNode({
credentialType: 'slackApi',
existingCredentials: [{ id: 'cred-1', name: 'My Slack' }],
}),
],
});
const cards = computed(() => [card]);
const { initCredGroupSelections, getCardCredentialId } = useCredentialGroupSelection(
cards,
vi.fn(),
);
initCredGroupSelections();
expect(getCardCredentialId(card)).toBe('cred-1');
});
test('auto-selects first credential when auto-applied', () => {
const card = makeCard({
isAutoApplied: true,
nodes: [
makeSetupNode({
credentialType: 'slackApi',
existingCredentials: [
{ id: 'cred-1', name: 'Slack 1' },
{ id: 'cred-2', name: 'Slack 2' },
],
}),
],
});
const cards = computed(() => [card]);
const { initCredGroupSelections, getCardCredentialId } = useCredentialGroupSelection(
cards,
vi.fn(),
);
initCredGroupSelections();
expect(getCardCredentialId(card)).toBe('cred-1');
});
test('returns null when no credential is available', () => {
const card = makeCard({
nodes: [makeSetupNode({ credentialType: 'slackApi' })],
});
const cards = computed(() => [card]);
const { initCredGroupSelections, getCardCredentialId } = useCredentialGroupSelection(
cards,
vi.fn(),
);
initCredGroupSelections();
expect(getCardCredentialId(card)).toBeNull();
});
});
describe('setCredentialForGroup', () => {
test('updates shared group state and syncs to store nodes', () => {
const card = makeCard({
nodes: [makeSetupNode({ credentialType: 'slackApi' })],
});
const storeNode = { name: 'Slack', credentials: {} };
workflowDocumentStore.getNodeByName = vi.fn().mockReturnValue(storeNode);
const mockGetById = (id: string) =>
id === 'cred-2' ? { id: 'cred-2', name: 'New Slack' } : undefined;
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getCredentialById', 'get').mockReturnValue(mockGetById);
const testFn = vi.fn();
const cards = computed(() => [card]);
const { initCredGroupSelections, setCredentialForGroup, getCardCredentialId } =
useCredentialGroupSelection(cards, testFn);
initCredGroupSelections();
setCredentialForGroup('slackApi', 'slackApi', 'cred-2');
expect(getCardCredentialId(card)).toBe('cred-2');
expect(storeNode.credentials).toEqual({
slackApi: { id: 'cred-2', name: 'New Slack' },
});
});
test('triggers background credential test', () => {
const card = makeCard({
nodes: [makeSetupNode({ credentialType: 'slackApi' })],
});
workflowDocumentStore.getNodeByName = vi.fn().mockReturnValue(undefined);
const mockGetById = () => ({ id: 'cred-2', name: 'New Slack' });
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getCredentialById', 'get').mockReturnValue(mockGetById);
const testFn = vi.fn().mockResolvedValue(undefined);
const cards = computed(() => [card]);
const { initCredGroupSelections, setCredentialForGroup } = useCredentialGroupSelection(
cards,
testFn,
);
initCredGroupSelections();
setCredentialForGroup('slackApi', 'slackApi', 'cred-2');
expect(testFn).toHaveBeenCalledWith('cred-2', 'New Slack', 'slackApi');
});
});
describe('clearCredentialForGroup', () => {
test('clears group state and removes credential from store nodes', () => {
const card = makeCard({
nodes: [makeSetupNode({ credentialType: 'slackApi' })],
});
const storeNode = {
name: 'Slack',
credentials: { slackApi: { id: 'cred-1', name: 'Old' } },
};
workflowDocumentStore.getNodeByName = vi.fn().mockReturnValue(storeNode);
const cards = computed(() => [card]);
const { initCredGroupSelections, clearCredentialForGroup, getCardCredentialId } =
useCredentialGroupSelection(cards, vi.fn());
initCredGroupSelections();
clearCredentialForGroup('slackApi', 'slackApi');
expect(getCardCredentialId(card)).toBeNull();
expect(storeNode.credentials).not.toHaveProperty('slackApi');
});
});
describe('isFirstCardInCredGroup', () => {
test('returns true for the first card in a group', () => {
const card1 = makeCard({ id: 'card-1', credentialType: 'slackApi' });
const card2 = makeCard({ id: 'card-2', credentialType: 'slackApi' });
const cards = computed(() => [card1, card2]);
const { isFirstCardInCredGroup } = useCredentialGroupSelection(cards, vi.fn());
expect(isFirstCardInCredGroup(card1)).toBe(true);
expect(isFirstCardInCredGroup(card2)).toBe(false);
});
});
describe('openNewCredentialForSection', () => {
function mockNodeTypeWithAuth(authOptions: Array<{ name: string; value: string }>) {
const nodeType = {
name: 'n8n-nodes-base.slack',
properties: [
{
name: 'authentication',
displayName: 'Authentication',
type: 'options',
default: authOptions[0]?.value ?? '',
options: authOptions,
},
],
credentials: [
{
name: 'slackApi',
displayOptions: {
show: { authentication: [authOptions[0]?.value ?? ''] },
},
},
],
} as unknown as INodeTypeDescription;
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(nodeTypesStore, 'getNodeType', 'get').mockReturnValue(() => nodeType);
}
test('passes showAuthOptions=true and the node name when node has multiple auth options', () => {
mockNodeTypeWithAuth([
{ name: 'API Token', value: 'apiToken' },
{ name: 'OAuth2', value: 'oAuth2' },
]);
const card = makeCard({
nodes: [makeSetupNode({ credentialType: 'slackApi' })],
});
const cards = computed(() => [card]);
const { openNewCredentialForSection } = useCredentialGroupSelection(cards, vi.fn());
const openSpy = vi.spyOn(uiStore, 'openNewCredential');
openNewCredentialForSection('slackApi', 'slackApi');
expect(openSpy).toHaveBeenCalledWith('slackApi', true, false, undefined, undefined, 'Slack');
});
test('passes showAuthOptions=false when node has no main auth field', () => {
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(nodeTypesStore, 'getNodeType', 'get').mockReturnValue(() => null);
const card = makeCard({
nodes: [makeSetupNode({ credentialType: 'slackApi' })],
});
const cards = computed(() => [card]);
const { openNewCredentialForSection } = useCredentialGroupSelection(cards, vi.fn());
const openSpy = vi.spyOn(uiStore, 'openNewCredential');
openNewCredentialForSection('slackApi', 'slackApi');
expect(openSpy).toHaveBeenCalledWith('slackApi', false, false, undefined, undefined, 'Slack');
});
});
describe('cardHasExistingCredentials', () => {
test('returns true when node has existing credentials', () => {
const card = makeCard({
nodes: [
makeSetupNode({
credentialType: 'slackApi',
existingCredentials: [{ id: 'cred-1', name: 'My Slack' }],
}),
],
});
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getUsableCredentialByType', 'get').mockReturnValue(() => []);
const cards = computed(() => [card]);
const { cardHasExistingCredentials } = useCredentialGroupSelection(cards, vi.fn());
expect(cardHasExistingCredentials(card)).toBe(true);
});
test('returns false when no credentials exist', () => {
const card = makeCard({
nodes: [makeSetupNode({ credentialType: 'slackApi' })],
});
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getUsableCredentialByType', 'get').mockReturnValue(() => []);
const cards = computed(() => [card]);
const { cardHasExistingCredentials } = useCredentialGroupSelection(cards, vi.fn());
expect(cardHasExistingCredentials(card)).toBe(false);
});
});
});

View File

@ -1,197 +0,0 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useCredentialTesting } from '../composables/useCredentialTesting';
import type { SetupCard } from '../instanceAiWorkflowSetup.utils';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function makeCard(overrides: Partial<SetupCard> = {}): SetupCard {
return {
id: 'card-1',
credentialType: 'slackApi',
nodes: [
{
node: {
name: 'Slack',
type: 'n8n-nodes-base.slack',
typeVersion: 2,
parameters: {},
position: [0, 0] as [number, number],
id: 'node-1',
credentials: { slackApi: { id: 'cred-1', name: 'My Slack' } },
},
isTrigger: false,
},
],
isTrigger: false,
isFirstTrigger: false,
isTestable: false,
isAutoApplied: false,
hasParamIssues: false,
...overrides,
} as SetupCard;
}
describe('useCredentialTesting', () => {
let credentialsStore: ReturnType<typeof useCredentialsStore>;
beforeEach(() => {
const pinia = createTestingPinia({ stubActions: false });
setActivePinia(pinia);
credentialsStore = useCredentialsStore();
});
describe('testCredentialInBackground', () => {
function setupTestableType() {
const mockType = {
name: 'slackApi',
displayName: 'Slack API',
properties: [],
test: { request: { method: 'GET', url: '/test' } },
};
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getCredentialTypeByName', 'get').mockReturnValue(() => mockType);
}
test('writes error to store when getCredentialData throws', async () => {
setupTestableType();
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(false);
vi.spyOn(credentialsStore, 'isCredentialTestPending').mockReturnValue(false);
vi.spyOn(credentialsStore, 'getCredentialData').mockRejectedValue(new Error('Network error'));
const { testCredentialInBackground } = useCredentialTesting(() => null);
await testCredentialInBackground('cred-1', 'My Slack', 'slackApi');
// Should write 'error' to prevent stuck spinner
expect(credentialsStore.credentialTestResults.get('cred-1')).toBe('error');
});
test('writes error to store when testCredential throws', async () => {
setupTestableType();
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(false);
vi.spyOn(credentialsStore, 'isCredentialTestPending').mockReturnValue(false);
vi.spyOn(credentialsStore, 'getCredentialData').mockResolvedValue({
data: { apiKey: 'test-key' },
} as never);
vi.spyOn(credentialsStore, 'testCredential').mockRejectedValue(new Error('Test failed'));
const { testCredentialInBackground } = useCredentialTesting(() => null);
await testCredentialInBackground('cred-1', 'My Slack', 'slackApi');
expect(credentialsStore.credentialTestResults.get('cred-1')).toBe('error');
});
test('skips already-successful credentials', async () => {
setupTestableType();
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(true);
const getDataSpy = vi.spyOn(credentialsStore, 'getCredentialData');
const { testCredentialInBackground } = useCredentialTesting(() => null);
await testCredentialInBackground('cred-1', 'My Slack', 'slackApi');
expect(getDataSpy).not.toHaveBeenCalled();
});
test('skips non-testable credential types', async () => {
const mockType = { name: 'slackOAuth2Api', displayName: 'Slack OAuth2', properties: [] };
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getCredentialTypeByName', 'get').mockReturnValue(() => mockType);
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getNodesWithAccess', 'get').mockReturnValue(() => []);
const getDataSpy = vi.spyOn(credentialsStore, 'getCredentialData');
const { testCredentialInBackground } = useCredentialTesting(() => null);
await testCredentialInBackground('cred-1', 'My Slack', 'slackOAuth2Api');
expect(getDataSpy).not.toHaveBeenCalled();
});
});
describe('getCredTestIcon', () => {
test('returns null when card has no credential type', () => {
const { getCredTestIcon } = useCredentialTesting(() => null);
const card = makeCard({ credentialType: undefined });
expect(getCredTestIcon(card)).toBeNull();
});
test('returns null when no credential is selected', () => {
const { getCredTestIcon } = useCredentialTesting(() => null);
const card = makeCard();
expect(getCredTestIcon(card)).toBeNull();
});
test('returns check when credential test passed', () => {
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(true);
const { getCredTestIcon } = useCredentialTesting(() => 'cred-1');
const card = makeCard();
expect(getCredTestIcon(card)).toBe('check');
});
test('returns triangle-alert when credential test failed', () => {
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(false);
vi.spyOn(credentialsStore, 'isCredentialTestPending').mockReturnValue(false);
credentialsStore.credentialTestResults.set('cred-1', 'error');
const { getCredTestIcon } = useCredentialTesting(() => 'cred-1');
const card = makeCard();
expect(getCredTestIcon(card)).toBe('triangle-alert');
});
test('returns spinner when test is pending for testable type', () => {
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(false);
vi.spyOn(credentialsStore, 'isCredentialTestPending').mockReturnValue(false);
const mockType = {
name: 'slackApi',
displayName: 'Slack API',
properties: [],
test: { request: { method: 'GET', url: '/test' } },
};
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(credentialsStore, 'getCredentialTypeByName', 'get').mockReturnValue(() => mockType);
const { getCredTestIcon } = useCredentialTesting(() => 'cred-1');
const card = makeCard();
expect(getCredTestIcon(card)).toBe('spinner');
});
});
describe('getEffectiveCredTestResult', () => {
test('trusts store result over backend result', () => {
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(true);
const { getEffectiveCredTestResult } = useCredentialTesting(() => 'cred-1');
const card = makeCard({
credentialTestResult: { success: false, message: 'Backend says failed' },
});
const result = getEffectiveCredTestResult(card);
expect(result).toEqual({ success: true });
});
test('falls back to backend result when selection matches original', () => {
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(false);
vi.spyOn(credentialsStore, 'isCredentialTestPending').mockReturnValue(false);
const { getEffectiveCredTestResult } = useCredentialTesting(() => 'cred-1');
const card = makeCard({
credentialTestResult: { success: true },
});
const result = getEffectiveCredTestResult(card);
expect(result).toEqual({ success: true });
});
test('returns undefined when no result is available', () => {
vi.spyOn(credentialsStore, 'isCredentialTestedOk').mockReturnValue(false);
vi.spyOn(credentialsStore, 'isCredentialTestPending').mockReturnValue(false);
const { getEffectiveCredTestResult } = useCredentialTesting(() => 'new-cred');
const card = makeCard();
const result = getEffectiveCredTestResult(card);
expect(result).toBeUndefined();
});
});
});

View File

@ -1,199 +0,0 @@
import { describe, test, expect, vi, beforeEach } from 'vitest';
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import { ref } from 'vue';
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import type { INodeTypeDescription } from 'n8n-workflow';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import {
useWorkflowDocumentStore,
createWorkflowDocumentId,
} from '@/app/stores/workflowDocument.store';
import { useSetupCards } from '../composables/useSetupCards';
vi.mock('@/features/setupPanel/setupPanel.utils', () => ({
getNodeParametersIssues: () => ({}),
}));
function makeSetupNode(
overrides: Partial<InstanceAiWorkflowSetupNode> = {},
): InstanceAiWorkflowSetupNode {
return {
node: {
name: 'DataTable',
type: 'n8n-nodes-base.dataTable',
typeVersion: 1,
parameters: {},
position: [0, 0] as [number, number],
id: 'node-1',
},
isTrigger: false,
...overrides,
} as InstanceAiWorkflowSetupNode;
}
function mockNodeType(properties: INodeTypeDescription['properties']) {
const nodeType = { name: 'n8n-nodes-base.dataTable', properties } as INodeTypeDescription;
const nodeTypesStore = useNodeTypesStore();
// @ts-expect-error Known pinia issue when spying on store getters
vi.spyOn(nodeTypesStore, 'getNodeType', 'get').mockReturnValue(() => nodeType);
}
describe('useSetupCards', () => {
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
beforeEach(() => {
const pinia = createTestingPinia({ stubActions: false });
setActivePinia(pinia);
workflowsStore = useWorkflowsStore();
workflowsStore.setWorkflowId('test-workflow');
const workflowDocumentStore = useWorkflowDocumentStore(
createWorkflowDocumentId('test-workflow'),
);
workflowDocumentStore.setNodes([
{
name: 'DataTable',
type: 'n8n-nodes-base.dataTable',
typeVersion: 1,
parameters: {},
position: [0, 0] as [number, number],
id: 'node-1',
},
]);
});
describe('param-issue card creation', () => {
test('skips the card when the only outstanding issue is a nested param (fixedCollection)', () => {
mockNodeType([
{
name: 'filters',
displayName: 'Filters',
type: 'fixedCollection',
default: {},
},
]);
const setupRequests = ref<InstanceAiWorkflowSetupNode[]>([
makeSetupNode({ parameterIssues: { filters: ['Filters are required'] } }),
]);
const { cards } = useSetupCards(
setupRequests,
() => null,
() => false,
);
expect(cards.value).toHaveLength(0);
});
test('creates a card when the tracked issue maps to a simple (renderable) param', () => {
mockNodeType([
{
name: 'tableName',
displayName: 'Table Name',
type: 'string',
default: '',
},
]);
const setupRequests = ref<InstanceAiWorkflowSetupNode[]>([
makeSetupNode({ parameterIssues: { tableName: ['Table Name is required'] } }),
]);
const { cards } = useSetupCards(
setupRequests,
() => null,
() => false,
);
expect(cards.value).toHaveLength(1);
expect(cards.value[0].hasParamIssues).toBe(true);
});
test('creates a card when mixing simple and nested issues', () => {
mockNodeType([
{
name: 'tableName',
displayName: 'Table Name',
type: 'string',
default: '',
},
{
name: 'filters',
displayName: 'Filters',
type: 'fixedCollection',
default: {},
},
]);
const setupRequests = ref<InstanceAiWorkflowSetupNode[]>([
makeSetupNode({
parameterIssues: {
tableName: ['Table Name is required'],
filters: ['Filters are required'],
},
}),
]);
const { cards } = useSetupCards(
setupRequests,
() => null,
() => false,
);
expect(cards.value).toHaveLength(1);
expect(cards.value[0].hasParamIssues).toBe(true);
});
test('keeps the card for a trigger node even when its only issue is nested', () => {
mockNodeType([
{
name: 'filters',
displayName: 'Filters',
type: 'fixedCollection',
default: {},
},
]);
const setupRequests = ref<InstanceAiWorkflowSetupNode[]>([
makeSetupNode({
isTrigger: true,
parameterIssues: { filters: ['Filters are required'] },
}),
]);
const { cards } = useSetupCards(
setupRequests,
() => null,
() => false,
);
expect(cards.value).toHaveLength(1);
});
test('skips the card when the tracked issue is a param with multipleValues: true', () => {
mockNodeType([
{
name: 'conditions',
displayName: 'Conditions',
type: 'string',
default: '',
typeOptions: { multipleValues: true },
},
]);
const setupRequests = ref<InstanceAiWorkflowSetupNode[]>([
makeSetupNode({ parameterIssues: { conditions: ['Conditions are required'] } }),
]);
const { cards } = useSetupCards(
setupRequests,
() => null,
() => false,
);
expect(cards.value).toHaveLength(0);
});
});
});

View File

@ -50,7 +50,11 @@ function handleClick(e: MouseEvent) {
</script>
<template>
<N8nCard :class="[$style.card, props.archived && $style.cardArchived]" @click="handleClick">
<N8nCard
data-test-id="instance-ai-artifact-card"
:class="[$style.card, props.archived && $style.cardArchived]"
@click="handleClick"
>
<template #prepend>
<N8nIcon :icon="icon" size="large" :class="$style.icon" />
</template>

View File

@ -13,7 +13,7 @@ import GatewayResourceDecision from './GatewayResourceDecision.vue';
import InstanceAiCredentialSetup from './InstanceAiCredentialSetup.vue';
import type { QuestionAnswer } from './InstanceAiQuestions.vue';
import InstanceAiQuestions from './InstanceAiQuestions.vue';
import InstanceAiWorkflowSetup from './InstanceAiWorkflowSetup.vue';
import InstanceAiWorkflowSetup from '../workflowSetup/InstanceAiWorkflowSetup.vue';
import ConfirmationPreview from './ConfirmationPreview.vue';
import PlanReviewPanel, { type PlannedTaskArg } from './PlanReviewPanel.vue';
@ -304,10 +304,9 @@ function isAllGenericApproval(items: PendingConfirmationItem[]): boolean {
:class="$style.confirmation"
:request-id="chunk.item.toolCall.confirmation.requestId"
:setup-requests="chunk.item.toolCall.confirmation.setupRequests!"
:workflow-id="chunk.item.toolCall.confirmation.workflowId ?? ''"
:message="chunk.item.toolCall.confirmation.message"
:project-id="chunk.item.toolCall.confirmation.projectId"
:credential-flow="chunk.item.toolCall.confirmation.credentialFlow"
:workflow-id="chunk.item.toolCall.confirmation.workflowId"
/>
<!-- Credential setup -->

View File

@ -347,6 +347,7 @@ async function handleLater() {
:suggested-credential-name="currentRequest.suggestedName"
standalone
hide-issues
hide-ask-assistant
@credential-selected="onCredentialSelected(currentRequest.credentialType, $event)"
/>
<N8nButton

View File

@ -1,186 +0,0 @@
import type { ComputedRef } from 'vue';
import { ref } from 'vue';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useUIStore } from '@/app/stores/ui.store';
import { getMainAuthField } from '@/app/utils/nodeTypesUtils';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { credGroupKey, type SetupCard } from '../instanceAiWorkflowSetup.utils';
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
export function useCredentialGroupSelection(
cards: ComputedRef<SetupCard[]>,
testCredentialInBackground: (id: string, name: string, type: string) => Promise<void>,
projectId?: string,
) {
const uiStore = useUIStore();
const workflowDocumentStore = injectWorkflowDocumentStore();
const credentialsStore = useCredentialsStore();
const nodeTypesStore = useNodeTypesStore();
// Shared credential selection keyed by credGroupKey — single source of truth
// for all cards in the same credential group (including escalated per-node cards).
const credGroupSelections = ref<Record<string, string | null>>({});
// Tracks which credential group key (or section node name) initiated a "create credential" action.
const activeCredentialTarget = ref<{ groupKey: string; credentialType: string } | null>(null);
function initCredGroupSelections() {
// Build a group map first so we scan ALL cards per group, not bail early.
const groupMap = new Map<string, SetupCard[]>();
for (const card of cards.value) {
if (!card.credentialType) continue;
const key = card.nodes[0] ? credGroupKey(card.nodes[0]) : card.credentialType;
const existing = groupMap.get(key);
if (existing) existing.push(card);
else groupMap.set(key, [card]);
}
for (const [key, groupCards] of groupMap) {
// Search ALL cards in the group for an assigned credential
let selectedId: string | null = null;
for (const card of groupCards) {
for (const req of card.nodes) {
const credId = req.node.credentials?.[card.credentialType!]?.id;
if (credId) {
selectedId = credId;
break;
}
}
if (selectedId) break;
}
if (!selectedId) {
// Auto-select: check first card's existing credentials
const firstCard = groupCards[0];
const firstReq = firstCard.nodes[0];
if (firstReq.existingCredentials?.length === 1) {
selectedId = firstReq.existingCredentials[0].id;
} else if (firstCard.isAutoApplied && firstReq.existingCredentials?.length) {
selectedId = firstReq.existingCredentials[0].id;
}
}
credGroupSelections.value[key] = selectedId;
}
}
function getCardCredentialId(card: SetupCard): string | null {
if (!card.credentialType) return null;
const key = card.nodes[0] ? credGroupKey(card.nodes[0]) : card.credentialType;
return credGroupSelections.value[key] ?? null;
}
function isFirstCardInCredGroup(card: SetupCard): boolean {
if (!card.credentialType || !card.nodes[0]) return true;
const key = credGroupKey(card.nodes[0]);
return (
cards.value.find((c) => c.credentialType && c.nodes[0] && credGroupKey(c.nodes[0]) === key)
?.id === card.id
);
}
function setCredentialForGroup(groupKey: string, credentialType: string, credentialId: string) {
// 1. Update shared group state
credGroupSelections.value[groupKey] = credentialId;
// 2. Sync credential to workflow store nodes for ALL nodes in the group
for (const c of cards.value) {
if (!c.credentialType || !c.nodes[0]) continue;
if (credGroupKey(c.nodes[0]) !== groupKey) continue;
for (const req of c.nodes) {
const storeNode = workflowDocumentStore.value.getNodeByName(req.node.name);
if (storeNode) {
const cred =
req.existingCredentials?.find((cr) => cr.id === credentialId) ??
credentialsStore.getCredentialById(credentialId);
if (cred) {
storeNode.credentials = {
...storeNode.credentials,
[credentialType]: { id: cred.id, name: cred.name },
};
}
}
}
}
// 3. Trigger background test
const cred = credentialsStore.getCredentialById(credentialId);
if (cred) {
void testCredentialInBackground(credentialId, cred.name, credentialType);
}
}
function clearCredentialForGroup(groupKey: string, credentialType: string) {
// 1. Clear shared group state
credGroupSelections.value[groupKey] = null;
// 2. Remove credential from workflow store nodes for ALL nodes in the group
for (const c of cards.value) {
if (!c.credentialType || !c.nodes[0]) continue;
if (credGroupKey(c.nodes[0]) !== groupKey) continue;
for (const req of c.nodes) {
const storeNode = workflowDocumentStore.value.getNodeByName(req.node.name);
if (storeNode?.credentials?.[credentialType]) {
const { [credentialType]: _removed, ...remaining } = storeNode.credentials;
storeNode.credentials = remaining as typeof storeNode.credentials;
}
}
}
}
function cardHasExistingCredentials(card: SetupCard): boolean {
if (!card.credentialType) return false;
const firstReq = card.nodes[0];
return (
(firstReq?.existingCredentials?.length ?? 0) > 0 ||
(credentialsStore.getUsableCredentialByType(card.credentialType)?.length ?? 0) > 0
);
}
function findCardForGroup(credentialType: string, groupKey: string): SetupCard | undefined {
return cards.value.find(
(c) =>
c.credentialType === credentialType && c.nodes[0] && credGroupKey(c.nodes[0]) === groupKey,
);
}
function shouldShowAuthOptions(card: SetupCard | undefined): boolean {
const setupNode = card?.nodes[0]?.node;
if (!setupNode) return false;
const nodeType = nodeTypesStore.getNodeType(setupNode.type, setupNode.typeVersion);
const mainAuthField = getMainAuthField(nodeType);
return (
mainAuthField !== null &&
Array.isArray(mainAuthField.options) &&
mainAuthField.options.length > 0
);
}
function openNewCredentialForSection(credentialType: string, groupKey: string) {
activeCredentialTarget.value = { groupKey, credentialType };
const card = findCardForGroup(credentialType, groupKey);
const showAuthOptions = shouldShowAuthOptions(card);
const nodeName = card?.nodes[0]?.node.name;
uiStore.openNewCredential(
credentialType,
showAuthOptions,
false,
projectId,
undefined,
nodeName,
);
}
return {
credGroupSelections,
activeCredentialTarget,
initCredGroupSelections,
getCardCredentialId,
isFirstCardInCredGroup,
setCredentialForGroup,
clearCredentialForGroup,
cardHasExistingCredentials,
openNewCredentialForSection,
};
}

View File

@ -1,122 +0,0 @@
import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import type { SetupCard } from '../instanceAiWorkflowSetup.utils';
export function useCredentialTesting(getCardCredentialId: (card: SetupCard) => string | null) {
const credentialsStore = useCredentialsStore();
function isCredentialTypeTestable(credentialTypeName: string): boolean {
const credType = credentialsStore.getCredentialTypeByName(credentialTypeName);
if (credType?.test) return true;
const nodesWithAccess = credentialsStore.getNodesWithAccess(credentialTypeName);
return nodesWithAccess.some((node) =>
node.credentials?.some((cred) => cred.name === credentialTypeName && cred.testedBy),
);
}
async function testCredentialInBackground(
credentialId: string,
credentialName: string,
credentialType: string,
) {
if (!isCredentialTypeTestable(credentialType)) return;
if (
credentialsStore.isCredentialTestedOk(credentialId) ||
credentialsStore.isCredentialTestPending(credentialId)
) {
return;
}
try {
const credentialResponse = await credentialsStore.getCredentialData({ id: credentialId });
if (!credentialResponse?.data || typeof credentialResponse.data === 'string') return;
// Re-check after the async fetch
if (
credentialsStore.isCredentialTestedOk(credentialId) ||
credentialsStore.isCredentialTestPending(credentialId)
) {
return;
}
const {
ownedBy: _ownedBy,
sharedWithProjects: _sharedWithProjects,
oauthTokenData,
...data
} = credentialResponse.data as Record<string, unknown>;
// OAuth credentials: token presence = success
if (oauthTokenData) {
credentialsStore.credentialTestResults.set(credentialId, 'success');
return;
}
await credentialsStore.testCredential({
id: credentialId,
name: credentialName,
type: credentialType,
data: data as ICredentialDataDecryptedObject,
});
} catch {
// Ensure the credential is not stuck in a pending/unknown state —
// write 'error' so the card shows a warning icon instead of an eternal spinner.
credentialsStore.credentialTestResults.set(credentialId, 'error');
}
}
/**
* Returns the credential test result for a card.
* - Store result (keyed by credential ID): authoritative for any credential the client has tested
* - Backend result (card.credentialTestResult): only valid when the selected credential
* matches the original backend-assigned credential (unchanged selection)
* - Returns undefined when no result is available yet (triggers spinner for testable types)
*/
function getEffectiveCredTestResult(
card: SetupCard,
): { success: boolean; message?: string } | undefined {
const selectedId = getCardCredentialId(card);
if (!selectedId) return undefined;
// 1. Store has a definitive result for this credential — always trust it
if (credentialsStore.isCredentialTestedOk(selectedId)) {
return { success: true };
}
if (credentialsStore.isCredentialTestPending(selectedId)) {
return undefined; // in-progress
}
const storeResult = credentialsStore.credentialTestResults.get(selectedId);
if (storeResult === 'error') {
return { success: false };
}
// 2. Backend-provided result — only valid if the selection hasn't changed.
const originalCredId = card.nodes[0]?.node.credentials?.[card.credentialType!]?.id;
if (card.credentialTestResult && selectedId === originalCredId) {
return card.credentialTestResult;
}
// 3. No result available
return undefined;
}
function getCredTestIcon(card: SetupCard): 'spinner' | 'check' | 'triangle-alert' | null {
if (!card.credentialType) return null;
const selectedId = getCardCredentialId(card);
if (!selectedId) return null;
const testResult = getEffectiveCredTestResult(card);
if (testResult === undefined) {
return isCredentialTypeTestable(card.credentialType) ? 'spinner' : null;
}
if (testResult.success) return 'check';
return 'triangle-alert';
}
return {
isCredentialTypeTestable,
testCredentialInBackground,
getEffectiveCredTestResult,
getCredTestIcon,
};
}

View File

@ -1,338 +0,0 @@
import type { ComputedRef, Ref } from 'vue';
import { ref, watch, onUnmounted } from 'vue';
import type { InstanceAiToolCallState, InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
import { useTelemetry } from '@/app/composables/useTelemetry';
import type { INodeUi } from '@/Interface';
import { useRootStore } from '@n8n/stores/useRootStore';
import type { ThreadRuntime } from '../instanceAi.store';
import type { DisplayCard, SetupCard } from '../instanceAiWorkflowSetup.utils';
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
export function useSetupActions(deps: {
requestId: Ref<string>;
thread: ThreadRuntime;
cards: ComputedRef<SetupCard[]>;
currentDisplayCard: ComputedRef<DisplayCard | undefined>;
displayCards: ComputedRef<DisplayCard[]>;
buildNodeCredentials: () => Record<string, Record<string, string>>;
buildNodeParameters: () => Record<string, Record<string, unknown>> | undefined;
isCardComplete: (card: SetupCard) => boolean;
anyCardComplete: ComputedRef<boolean>;
allPreResolved: ComputedRef<boolean>;
showFullWizard: Ref<boolean>;
setCredentialForGroup: (groupKey: string, credentialType: string, credentialId: string) => void;
clearCredentialForGroup: (groupKey: string, credentialType: string) => void;
goToNext: () => void;
isNextDisabled: ComputedRef<boolean>;
credGroupKey: (req: InstanceAiWorkflowSetupNode) => string;
setupRequests: Ref<InstanceAiWorkflowSetupNode[]>;
onApplySuccess?: () => void;
}) {
const telemetry = useTelemetry();
const workflowDocumentStore = injectWorkflowDocumentStore();
const nodeHelpers = useNodeHelpers();
const isSubmitted = ref(false);
const isDeferred = ref(false);
const isPartial = ref(false);
const isApplying = ref(false);
const applyError = ref<string | null>(null);
function isToolResult(val: unknown): val is Record<string, unknown> {
return typeof val === 'object' && val !== null && !Array.isArray(val);
}
let cancelApplyWait: (() => void) | null = null;
onUnmounted(() => {
cancelApplyWait?.();
});
function trackSetupInput() {
const tc = deps.thread.findToolCallByRequestId(deps.requestId.value);
const inputThreadId = tc?.confirmation?.inputThreadId ?? '';
const provided: Array<{ label: string; options: string[]; option_chosen: string }> = [];
const skipped: Array<{ label: string; options: string[] }> = [];
for (const card of deps.cards.value) {
const name = card.nodes[0]?.node.name ?? card.id;
if (deps.isCardComplete(card)) {
provided.push({ label: name, options: [], option_chosen: 'configured' });
} else {
skipped.push({ label: name, options: [] });
}
}
telemetry.track('User finished providing input', {
thread_id: deps.thread.currentThreadId,
input_thread_id: inputThreadId,
instance_id: useRootStore().instanceId,
type: 'setup',
provided_inputs: provided,
skipped_inputs: skipped,
num_tasks: deps.cards.value.length,
});
}
/** Watch for the tool-result SSE event and resolve when it arrives. */
function waitForToolResult(
requestId: string,
timeoutMs = 60_000,
): { promise: Promise<Record<string, unknown> | null>; cancel: () => void } {
let stopWatch: (() => void) | null = null;
let timeoutId: ReturnType<typeof setTimeout> | null = null;
const promise = new Promise<Record<string, unknown> | null>((resolve) => {
const existing = deps.thread.findToolCallByRequestId(requestId);
if (existing?.result !== undefined) {
resolve(isToolResult(existing.result) ? existing.result : null);
return;
}
stopWatch = watch(
() => {
const tc: InstanceAiToolCallState | undefined =
deps.thread.findToolCallByRequestId(requestId);
return tc?.result;
},
(result) => {
if (result !== undefined) {
cleanup();
resolve(isToolResult(result) ? result : null);
}
},
);
timeoutId = setTimeout(() => {
cleanup();
resolve(null);
}, timeoutMs);
});
function cleanup() {
if (stopWatch) {
stopWatch();
stopWatch = null;
}
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
}
return { promise, cancel: cleanup };
}
/**
* Apply the server's authoritative updatedNodes to the canvas.
*/
function applyServerResultToCanvas(toolResult: Record<string, unknown>) {
const updatedNodes = toolResult.updatedNodes as
| Array<{
id: string;
name?: string;
type: string;
typeVersion: number;
position: [number, number];
parameters?: Record<string, unknown>;
credentials?: Record<string, { id?: string; name: string }>;
}>
| undefined;
if (!updatedNodes) return;
for (const serverNode of updatedNodes) {
const canvasNode = workflowDocumentStore.value.getNodeByName(serverNode.name ?? '');
if (!canvasNode) continue;
if (serverNode.credentials) {
canvasNode.credentials = serverNode.credentials as INodeUi['credentials'];
}
if (serverNode.parameters) {
canvasNode.parameters = serverNode.parameters as INodeUi['parameters'];
}
if (serverNode.name) {
nodeHelpers.updateNodeParameterIssuesByName(serverNode.name);
nodeHelpers.updateNodeCredentialIssuesByName(serverNode.name);
}
}
}
async function handleApply() {
const nodeCredentials = deps.buildNodeCredentials();
const nodeParameters = deps.buildNodeParameters();
trackSetupInput();
isApplying.value = true;
applyError.value = null;
const postSuccess = await deps.thread.confirmAction(deps.requestId.value, {
kind: 'setupWorkflowApply',
nodeCredentials,
nodeParameters,
});
if (!postSuccess) {
isApplying.value = false;
applyError.value = 'Failed to send confirmation. Try again.';
return;
}
const { promise, cancel } = waitForToolResult(deps.requestId.value);
cancelApplyWait = cancel;
const toolResult = await promise;
cancelApplyWait = null;
isApplying.value = false;
if (toolResult && toolResult.success === true) {
applyServerResultToCanvas(toolResult);
isSubmitted.value = true;
isPartial.value = toolResult.partial === true;
deps.onApplySuccess?.();
deps.thread.resolveConfirmation(deps.requestId.value, 'approved');
} else if (toolResult) {
applyError.value = typeof toolResult.error === 'string' ? toolResult.error : 'Apply failed';
} else {
applyError.value = 'Apply timed out — please try again.';
}
}
async function handleTestTrigger(nodeName: string) {
const nodeCredentials = deps.buildNodeCredentials();
const nodeParameters = deps.buildNodeParameters();
applyError.value = null;
const postSuccess = await deps.thread.confirmAction(deps.requestId.value, {
kind: 'setupWorkflowTestTrigger',
testTriggerNode: nodeName,
nodeCredentials,
nodeParameters,
});
if (!postSuccess) {
applyError.value = 'Failed to send trigger test request. Try again.';
return;
}
const { promise, cancel } = waitForToolResult(deps.requestId.value);
cancelApplyWait = cancel;
const toolResult = await promise;
cancelApplyWait = null;
if (toolResult === null) {
// Timeout — the backend likely re-suspended with a new confirmation-request
// that will replace this component. Nothing to do here.
} else if (typeof toolResult.error === 'string') {
applyError.value = toolResult.error;
} else if (toolResult.success !== true) {
applyError.value = 'Trigger test failed';
}
}
async function handleContinue() {
if ((!deps.allPreResolved.value || deps.showFullWizard.value) && !deps.isNextDisabled.value) {
deps.goToNext();
return;
}
await handleApply();
}
async function handleLater() {
if (!deps.allPreResolved.value || deps.showFullWizard.value) {
const dc = deps.currentDisplayCard.value;
if (dc?.type === 'single' && dc.card.credentialType && dc.card.nodes[0]) {
const key = deps.credGroupKey(dc.card.nodes[0]);
deps.clearCredentialForGroup(key, dc.card.credentialType);
}
if (dc?.type === 'group') {
// Clear credentials for all cards in the group (parent + subnodes)
const allGroupCards = [
...(dc.group.parentCard ? [dc.group.parentCard] : []),
...dc.group.subnodeCards,
];
for (const groupCard of allGroupCards) {
if (groupCard.credentialType && groupCard.nodes[0]) {
const key = deps.credGroupKey(groupCard.nodes[0]);
deps.clearCredentialForGroup(key, groupCard.credentialType);
}
}
if (!deps.isNextDisabled.value) {
deps.goToNext();
return;
}
if (deps.anyCardComplete.value) {
void handleApply();
return;
}
}
if (!deps.isNextDisabled.value) {
deps.goToNext();
return;
}
if (deps.anyCardComplete.value) {
void handleApply();
return;
}
}
// No cards completed at all (or confirm mode) — defer the whole setup
trackSetupInput();
isSubmitted.value = true;
isDeferred.value = true;
const success = await deps.thread.confirmAction(deps.requestId.value, {
kind: 'approval',
approved: false,
});
if (success) {
deps.thread.resolveConfirmation(deps.requestId.value, 'deferred');
} else {
isSubmitted.value = false;
isDeferred.value = false;
}
}
function onCredentialSelected(
card: SetupCard,
updateInfo: { properties: { credentials?: Record<string, unknown> } },
) {
if (!card.credentialType) return;
const credentialData = updateInfo.properties.credentials?.[card.credentialType];
const credentialId =
typeof credentialData === 'object' &&
credentialData !== null &&
'id' in credentialData &&
typeof credentialData.id === 'string'
? credentialData.id
: undefined;
const key = card.nodes[0] ? deps.credGroupKey(card.nodes[0]) : card.credentialType;
if (credentialId) {
deps.setCredentialForGroup(key, card.credentialType, credentialId);
} else {
deps.clearCredentialForGroup(key, card.credentialType);
}
}
return {
isSubmitted,
isDeferred,
isPartial,
isApplying,
applyError,
handleApply,
handleContinue,
handleLater,
handleTestTrigger,
onCredentialSelected,
};
}

View File

@ -1,139 +0,0 @@
import type { ComputedRef, Ref } from 'vue';
import { ref } from 'vue';
import { hasPlaceholderDeep } from '@n8n/utils';
import { NodeHelpers, type INodeProperties } from 'n8n-workflow';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import type { IUpdateInformation } from '@/Interface';
import { isNestedParam, isParamValueSet, type SetupCard } from '../instanceAiWorkflowSetup.utils';
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
/** Check if the original node parameter value was a placeholder sentinel. */
function isOriginalValuePlaceholder(req: SetupCard['nodes'][0], paramName: string): boolean {
return hasPlaceholderDeep(req.node.parameters[paramName]);
}
export function useSetupCardParameters(
cards: ComputedRef<SetupCard[]>,
trackedParamNames: Ref<Map<string, Set<string>>>,
cardHasParamWork: (card: SetupCard) => boolean,
) {
const workflowDocumentStore = injectWorkflowDocumentStore();
const nodeTypesStore = useNodeTypesStore();
const paramValues = ref<Record<string, Record<string, unknown>>>({});
/** Seed parameter values from existing node parameters for cards with param issues. */
function initParamValues() {
for (const card of cards.value) {
if (!cardHasParamWork(card)) continue;
const req = card.nodes[0];
const nodeName = req.node.name;
if (paramValues.value[nodeName]) continue;
const issueParamNames = Object.keys(req.parameterIssues ?? {});
const nodeParams = req.node.parameters;
const seeded: Record<string, unknown> = {};
for (const paramName of issueParamNames) {
const existing = nodeParams[paramName];
if (isParamValueSet(existing)) {
seeded[paramName] = existing;
}
}
if (Object.keys(seeded).length > 0) {
paramValues.value[nodeName] = seeded;
}
}
}
initParamValues();
function getCardParameters(card: SetupCard): INodeProperties[] {
if (!cardHasParamWork(card)) return [];
const req = card.nodes[0];
const nodeType = nodeTypesStore.getNodeType(req.node.type, req.node.typeVersion);
if (!nodeType?.properties) return [];
const nodeName = req.node.name;
const tracked =
trackedParamNames.value.get(nodeName) ?? new Set(Object.keys(req.parameterIssues ?? {}));
const node = workflowDocumentStore.value.getNodeByName(nodeName);
if (!node) return [];
return nodeType.properties.filter(
(prop) =>
tracked.has(prop.name) &&
NodeHelpers.displayParameter(node.parameters, prop, node, nodeType),
);
}
function getCardSimpleParameters(card: SetupCard): INodeProperties[] {
return getCardParameters(card).filter((p) => !isNestedParam(p));
}
/** Set a parameter value. */
function setParamValue(nodeName: string, paramName: string, value: unknown): void {
if (!paramValues.value[nodeName]) {
paramValues.value[nodeName] = {};
}
paramValues.value[nodeName][paramName] = value;
}
/** Bridge ParameterInputList events to both local paramValues AND the workflow store node. */
function onParameterValueChanged(card: SetupCard, parameterData: IUpdateInformation): void {
const nodeName = card.nodes[0].node.name;
const paramName = parameterData.name.replace(/^parameters\./, '');
// 1. Update local paramValues (used by buildNodeParameters on Apply)
setParamValue(nodeName, paramName, parameterData.value);
// 2. Update workflow store node (needed for ParameterInputList reactivity,
// dependent param resolution, and loadOptions calls)
workflowDocumentStore.value.setNodeParameters(
{ name: nodeName, value: { [paramName]: parameterData.value } },
true,
);
}
/** Build nodeParameters from paramValues + store node (for NDV-edited params). */
function buildNodeParameters(): Record<string, Record<string, unknown>> | undefined {
const result: Record<string, Record<string, unknown>> = {};
let hasValues = false;
for (const card of cards.value) {
if (!cardHasParamWork(card)) continue;
for (const req of card.nodes) {
const nodeName = req.node.name;
const paramNames =
trackedParamNames.value.get(nodeName) ?? new Set(Object.keys(req.parameterIssues ?? {}));
if (paramNames.size === 0) continue;
const merged: Record<string, unknown> = {};
for (const paramName of paramNames) {
let val = paramValues.value[nodeName]?.[paramName];
if (!isParamValueSet(val)) {
val = workflowDocumentStore.value.getNodeByName(nodeName)?.parameters[paramName];
}
if (isParamValueSet(val)) {
merged[paramName] = val;
hasValues = true;
} else if (isOriginalValuePlaceholder(req, paramName)) {
// Explicitly send empty string to clear the placeholder sentinel on the backend
merged[paramName] = '';
hasValues = true;
}
}
if (Object.keys(merged).length > 0) {
result[nodeName] = merged;
}
}
}
return hasValues ? result : undefined;
}
return {
paramValues,
getCardParameters,
getCardSimpleParameters,
setParamValue,
onParameterValueChanged,
buildNodeParameters,
};
}

View File

@ -1,434 +0,0 @@
import type { Ref } from 'vue';
import { computed, ref, watch } from 'vue';
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import { hasPlaceholderDeep } from '@n8n/utils';
import { NodeConnectionTypes, NodeHelpers } from 'n8n-workflow';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { getNodeParametersIssues } from '@/features/setupPanel/setupPanel.utils';
import {
credGroupKey,
isNestedParam,
type DisplayCard,
type SetupCard,
type SetupCardGroup,
} from '../instanceAiWorkflowSetup.utils';
import { injectWorkflowDocumentStore } from '@/app/stores/workflowDocument.store';
export function useSetupCards(
setupRequests: Ref<InstanceAiWorkflowSetupNode[]>,
getCardCredentialId: (card: SetupCard) => string | null,
isCredentialTypeTestable: (name: string) => boolean,
) {
const workflowDocumentStore = injectWorkflowDocumentStore();
const nodeTypesStore = useNodeTypesStore();
const credentialsStore = useCredentialsStore();
// ---------------------------------------------------------------------------
// Tracked parameter names — grows over time as live validation discovers new fields
// ---------------------------------------------------------------------------
const trackedParamNames = ref(new Map<string, Set<string>>());
function initTrackedParamNames() {
for (const req of setupRequests.value) {
if (req.parameterIssues && Object.keys(req.parameterIssues).length > 0) {
trackedParamNames.value.set(req.node.name, new Set(Object.keys(req.parameterIssues)));
}
}
}
initTrackedParamNames();
// ---------------------------------------------------------------------------
// Card grouping
// ---------------------------------------------------------------------------
// Credential group keys that need per-node splitting due to live-discovered param work.
const liveEscalatedCredTypes = computed(() => {
const escalated = new Set<string>();
const groupMembers = new Map<string, InstanceAiWorkflowSetupNode[]>();
for (const req of setupRequests.value) {
if (!req.credentialType) continue;
const key = credGroupKey(req);
const existing = groupMembers.get(key);
if (existing) existing.push(req);
else groupMembers.set(key, [req]);
}
for (const [key, members] of groupMembers) {
if (members.length <= 1) continue;
for (const req of members) {
if (trackedParamNames.value.has(req.node.name)) {
escalated.add(key);
break;
}
}
}
return escalated;
});
/**
* True if at least one tracked parameter on this node resolves to a property
* the wizard can render inline. The AI Assistant lives on its own route,
* with the workflow canvas shown in an iframe there is no NDV to fall back
* to for nested-type issues. If nothing is renderable inline, the wizard
* should skip the card and let the backend's re-analyze loop hand the
* unresolved issue back to the LLM via `partial/skippedNodes`.
*/
function hasRenderableParamIssue(req: InstanceAiWorkflowSetupNode): boolean {
const nodeType = nodeTypesStore.getNodeType(req.node.type, req.node.typeVersion);
if (!nodeType?.properties) return false;
const node = workflowDocumentStore.value.getNodeByName(req.node.name);
if (!node) return false;
const tracked =
trackedParamNames.value.get(req.node.name) ?? new Set(Object.keys(req.parameterIssues ?? {}));
if (tracked.size === 0) return false;
for (const prop of nodeType.properties) {
if (!tracked.has(prop.name)) continue;
if (isNestedParam(prop)) continue;
if (!NodeHelpers.displayParameter(node.parameters, prop, node, nodeType)) continue;
return true;
}
return false;
}
const cards = computed((): SetupCard[] => {
const escalatedCredTypes = new Set<string>();
for (const req of setupRequests.value) {
if (
req.credentialType &&
req.parameterIssues &&
Object.keys(req.parameterIssues).length > 0
) {
escalatedCredTypes.add(credGroupKey(req));
}
}
for (const key of liveEscalatedCredTypes.value) {
escalatedCredTypes.add(key);
}
const ordered: SetupCard[] = [];
const credCardByKey = new Map<string, SetupCard>();
for (const req of setupRequests.value) {
const hasParamIssues =
req.parameterIssues !== undefined && Object.keys(req.parameterIssues).length > 0;
if (req.credentialType) {
const key = credGroupKey(req);
if (escalatedCredTypes.has(key)) {
ordered.push({
id: `node-${req.node.id}`,
credentialType: req.credentialType,
nodes: [req],
isTrigger: req.isTrigger,
isFirstTrigger: req.isFirstTrigger ?? false,
isTestable: req.isTestable ?? false,
credentialTestResult: req.credentialTestResult,
isAutoApplied: req.isAutoApplied ?? false,
hasParamIssues,
});
} else {
const existing = credCardByKey.get(key);
if (existing) {
existing.nodes.push(req);
if (req.isTrigger) existing.isTrigger = true;
if (req.isFirstTrigger) existing.isFirstTrigger = true;
if (req.isTestable) existing.isTestable = true;
if (req.isAutoApplied) existing.isAutoApplied = true;
if (req.credentialTestResult && !existing.credentialTestResult) {
existing.credentialTestResult = req.credentialTestResult;
}
} else {
const card: SetupCard = {
id: `cred-${key}`,
credentialType: req.credentialType,
nodes: [req],
isTrigger: req.isTrigger,
isFirstTrigger: req.isFirstTrigger ?? false,
isTestable: req.isTestable ?? false,
credentialTestResult: req.credentialTestResult,
isAutoApplied: req.isAutoApplied ?? false,
hasParamIssues: false,
};
credCardByKey.set(key, card);
ordered.push(card);
}
}
} else if (req.isTrigger || hasParamIssues) {
// Skip param-only cards whose issues are all non-renderable (e.g. an
// empty `fixedCollection`). There's nothing the user can edit inline,
// and this wizard has no path to NDV. The backend will re-analyze on
// Apply and feed the unresolved issue back to the LLM.
if (!req.isTrigger && hasParamIssues && !hasRenderableParamIssue(req)) continue;
ordered.push({
id: hasParamIssues ? `param-${req.node.id}` : `trigger-${req.node.id}`,
nodes: [req],
isTrigger: req.isTrigger,
isFirstTrigger: req.isFirstTrigger ?? false,
isTestable: req.isTestable ?? false,
isAutoApplied: false,
hasParamIssues,
});
}
}
return ordered;
});
// ---------------------------------------------------------------------------
// Display cards — groups AI parent nodes with their sub-nodes for rendering
// ---------------------------------------------------------------------------
const displayCards = computed((): DisplayCard[] => {
const directSubnodes = new Map<string, Set<string>>();
const connectionsByDest = workflowDocumentStore.value.connectionsByDestinationNode;
for (const [destName, conns] of Object.entries(connectionsByDest)) {
for (const connType of Object.keys(conns)) {
if (connType === NodeConnectionTypes.Main) continue;
for (const group of conns[connType]) {
if (!group) continue;
for (const conn of group) {
if (!directSubnodes.has(destName)) {
directSubnodes.set(destName, new Set());
}
directSubnodes.get(destName)!.add(conn.node);
}
}
}
}
const allSubnodeNames = new Set<string>();
for (const subs of directSubnodes.values()) {
for (const s of subs) allSubnodeNames.add(s);
}
const rootParents = new Set<string>();
for (const parentName of directSubnodes.keys()) {
if (!allSubnodeNames.has(parentName)) {
rootParents.add(parentName);
}
}
const transitiveSubnodes = new Map<string, Set<string>>();
for (const root of rootParents) {
const collected = new Set<string>();
const queue = [...(directSubnodes.get(root) ?? [])];
while (queue.length > 0) {
const name = queue.pop()!;
if (collected.has(name)) continue;
collected.add(name);
const children = directSubnodes.get(name);
if (children) queue.push(...children);
}
if (collected.size > 0) {
transitiveSubnodes.set(root, collected);
}
}
const cardsByNodeName = new Map<string, SetupCard>();
for (const card of cards.value) {
for (const req of card.nodes) {
cardsByNodeName.set(req.node.name, card);
}
}
const usedCardIds = new Set<string>();
const deferredSubnodeCardIds = new Set<string>();
const result: DisplayCard[] = [];
for (const card of cards.value) {
if (usedCardIds.has(card.id)) continue;
const primaryNodeName = card.nodes[0]?.node.name;
const subnodeNames = primaryNodeName ? transitiveSubnodes.get(primaryNodeName) : undefined;
if (subnodeNames && subnodeNames.size > 0) {
const subnodeCards: SetupCard[] = [];
for (const subName of subnodeNames) {
const subCard = cardsByNodeName.get(subName);
if (subCard && !usedCardIds.has(subCard.id)) {
subnodeCards.push(subCard);
usedCardIds.add(subCard.id);
deferredSubnodeCardIds.delete(subCard.id);
}
}
if (subnodeCards.length > 0) {
usedCardIds.add(card.id);
result.push({
type: 'group',
group: {
parentNode: card.nodes[0].node,
parentCard: card,
subnodeCards,
},
});
continue;
}
}
let isSubnodeOfRoot = false;
for (const [rootName, subs] of transitiveSubnodes) {
if (primaryNodeName && subs.has(primaryNodeName)) {
const rootCard = cardsByNodeName.get(rootName);
if (rootCard && !usedCardIds.has(rootCard.id)) {
isSubnodeOfRoot = true;
}
break;
}
}
if (!isSubnodeOfRoot) {
usedCardIds.add(card.id);
result.push({ type: 'single', card });
} else {
// Track deferred subnodes so we can recover them if their root never collects them
deferredSubnodeCardIds.add(card.id);
}
}
// Emit any deferred subnode cards that were never consumed by a group
for (const card of cards.value) {
if (deferredSubnodeCardIds.has(card.id) && !usedCardIds.has(card.id)) {
usedCardIds.add(card.id);
result.push({ type: 'single', card });
}
}
return result;
});
// ---------------------------------------------------------------------------
// Param work and completion
// ---------------------------------------------------------------------------
function cardHasParamWork(card: SetupCard): boolean {
for (const req of card.nodes) {
const nodeName = req.node.name;
if (trackedParamNames.value.has(nodeName)) return true;
const storeNode = workflowDocumentStore.value.getNodeByName(nodeName);
if (storeNode) {
const liveIssues = getNodeParametersIssues(nodeTypesStore, storeNode);
if (Object.keys(liveIssues).length > 0) return true;
}
}
return false;
}
function isCardComplete(card: SetupCard): boolean {
if (card.credentialType) {
const selectedId = getCardCredentialId(card);
if (!selectedId) return false;
if (isCredentialTypeTestable(card.credentialType)) {
if (!credentialsStore.isCredentialTestedOk(selectedId)) {
const originalCredId = card.nodes[0]?.node.credentials?.[card.credentialType]?.id;
if (!(card.credentialTestResult?.success && selectedId === originalCredId)) {
return false;
}
}
}
}
if (cardHasParamWork(card)) {
for (const req of card.nodes) {
const storeNode = workflowDocumentStore.value.getNodeByName(req.node.name);
if (storeNode) {
const liveIssues = getNodeParametersIssues(nodeTypesStore, storeNode);
if (Object.keys(liveIssues).length > 0) return false;
// Check for remaining placeholder values only in tracked parameters
const tracked = trackedParamNames.value.get(req.node.name);
if (tracked) {
for (const paramName of tracked) {
if (hasPlaceholderDeep(storeNode.parameters[paramName])) return false;
}
}
}
}
}
if (card.isTestable && card.isTrigger && card.isFirstTrigger) {
const triggerTestResults: Record<string, InstanceAiWorkflowSetupNode['triggerTestResult']> =
{};
for (const req of setupRequests.value) {
if (req.triggerTestResult) {
triggerTestResults[req.node.name] = req.triggerTestResult;
}
}
const triggerNode = card.nodes.find((n) => n.isTrigger);
const result = triggerNode ? triggerTestResults[triggerNode.node.name] : undefined;
if (!result || result.status !== 'success') return false;
}
return true;
}
const anyCardComplete = computed(() => cards.value.some((c) => isCardComplete(c)));
const allPreResolved = computed(() => setupRequests.value.every((r) => !r.needsAction));
function isDisplayCardComplete(dc: DisplayCard): boolean {
if (dc.type === 'single') return isCardComplete(dc.card);
const { group } = dc;
if (group.parentCard && !isCardComplete(group.parentCard)) return false;
return group.subnodeCards.every((card) => isCardComplete(card));
}
function getGroupPrimaryTriggerCard(group: SetupCardGroup): SetupCard | null {
const allCards = group.parentCard
? [group.parentCard, ...group.subnodeCards]
: group.subnodeCards;
return allCards.find((c) => c.isTestable && c.isTrigger && c.isFirstTrigger) ?? null;
}
// Live parameter issue watcher — discovers new conditional required fields
watch(
() => {
const result = new Map<string, string[]>();
for (const card of cards.value) {
for (const req of card.nodes) {
const nodeName = req.node.name;
const storeNode = workflowDocumentStore.value.getNodeByName(nodeName);
if (!storeNode) continue;
const liveIssues = getNodeParametersIssues(nodeTypesStore, storeNode);
if (Object.keys(liveIssues).length > 0) {
result.set(nodeName, Object.keys(liveIssues));
}
}
}
return result;
},
(liveIssuesByNode) => {
for (const [nodeName, issueNames] of liveIssuesByNode) {
const existing = trackedParamNames.value.get(nodeName);
if (existing) {
for (const name of issueNames) existing.add(name);
} else {
trackedParamNames.value.set(nodeName, new Set(issueNames));
}
}
},
{ immediate: true },
);
return {
trackedParamNames,
cards,
displayCards,
cardHasParamWork,
isCardComplete,
isDisplayCardComplete,
anyCardComplete,
allPreResolved,
getGroupPrimaryTriggerCard,
};
}

View File

@ -1,117 +0,0 @@
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import { isPlaceholderString } from '@n8n/utils';
import type { INodeProperties } from 'n8n-workflow';
import { isResourceLocatorValue } from 'n8n-workflow';
import type { INodeUi } from '@/Interface';
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface SetupCard {
id: string;
credentialType?: string;
nodes: InstanceAiWorkflowSetupNode[];
isTrigger: boolean;
isFirstTrigger: boolean;
isTestable: boolean;
credentialTestResult?: { success: boolean; message?: string };
isAutoApplied: boolean;
hasParamIssues: boolean;
}
export interface SetupCardGroup {
parentNode: InstanceAiWorkflowSetupNode['node'];
parentCard?: SetupCard;
subnodeCards: SetupCard[];
}
export type DisplayCard =
| { type: 'single'; card: SetupCard }
| { type: 'group'; group: SetupCardGroup };
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
export const HTTP_REQUEST_NODE_TYPE = 'n8n-nodes-base.httpRequest';
export const HTTP_REQUEST_TOOL_NODE_TYPE = 'n8n-nodes-base.httpRequestTool';
export const NESTED_PARAM_TYPES = new Set([
'collection',
'fixedCollection',
'resourceMapper',
'filter',
'assignmentCollection',
]);
// ---------------------------------------------------------------------------
// Pure functions
// ---------------------------------------------------------------------------
export function credGroupKey(req: InstanceAiWorkflowSetupNode): string {
if (!req.credentialType) {
return req.node.name;
}
const credType = req.credentialType;
const isHttpRequest =
req.node.type === HTTP_REQUEST_NODE_TYPE || req.node.type === HTTP_REQUEST_TOOL_NODE_TYPE;
if (isHttpRequest) {
const url = String(req.node.parameters.url ?? '');
if (url.startsWith('=')) {
return `${credType}:http:expr:${req.node.name}`;
}
return `${credType}:http:${url}`;
}
return credType;
}
/** Check if a parameter value is meaningfully set (not empty, null, placeholder, or an empty resource locator). */
export function isParamValueSet(val: unknown): boolean {
if (val === undefined || val === null || val === '') return false;
if (isPlaceholderString(val)) return false;
if (isResourceLocatorValue(val)) {
return (
val.value !== '' &&
val.value !== null &&
val.value !== undefined &&
!isPlaceholderString(val.value)
);
}
return true;
}
export function isNestedParam(p: INodeProperties): boolean {
return NESTED_PARAM_TYPES.has(p.type) || p.typeOptions?.multipleValues === true;
}
export function toNodeUi(setupNode: InstanceAiWorkflowSetupNode): INodeUi {
return {
id: setupNode.node.id,
name: setupNode.node.name,
type: setupNode.node.type,
typeVersion: setupNode.node.typeVersion,
position: setupNode.node.position,
parameters: setupNode.node.parameters as INodeUi['parameters'],
...(setupNode.node.credentials !== undefined
? { credentials: setupNode.node.credentials }
: {}),
} satisfies INodeUi;
}
/** True when this card only has a trigger (no credentials and no param work) */
export function isTriggerOnly(
card: SetupCard,
cardHasParamWork: (c: SetupCard) => boolean,
): boolean {
return card.isTrigger && !card.credentialType && !cardHasParamWork(card);
}
/**
* Use credential icon only for multi-node credential-grouping cards, where the card
* title is the credential display name. By construction (see `useSetupCards`), cards
* with more than one node always have a credentialType.
*/
export function shouldUseCredentialIcon(card: SetupCard): boolean {
return card.nodes.length > 1;
}

View File

@ -0,0 +1,33 @@
<script lang="ts" setup>
import { toRef } from 'vue';
import type { InstanceAiCredentialFlow, InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import WorkflowSetupWizard from './components/WorkflowSetupWizard.vue';
import WorkflowSetupStatus from './components/WorkflowSetupStatus.vue';
import { provideWorkflowSetupContext } from './composables/useWorkflowSetupContext';
const props = defineProps<{
requestId: string;
setupRequests: InstanceAiWorkflowSetupNode[];
projectId?: string;
workflowId?: string;
credentialFlow?: InstanceAiCredentialFlow;
}>();
const ctx = provideWorkflowSetupContext({
requestId: toRef(props, 'requestId'),
setupRequests: toRef(props, 'setupRequests'),
projectId: toRef(props, 'projectId'),
workflowId: toRef(props, 'workflowId'),
credentialFlow: toRef(props, 'credentialFlow'),
});
</script>
<template>
<div data-test-id="instance-ai-workflow-setup">
<WorkflowSetupStatus
v-if="ctx.isReady.value && ctx.terminalState.value"
:state="ctx.terminalState.value"
/>
<WorkflowSetupWizard v-else-if="ctx.isReady.value && ctx.steps.value.length" />
</div>
</template>

View File

@ -0,0 +1,64 @@
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import type { WorkflowSetupSection } from '../workflowSetup.types';
import { buildSectionId } from '../workflowSetup.helpers';
export function makeSetupRequest(
overrides: Omit<Partial<InstanceAiWorkflowSetupNode>, 'node'> & {
node?: Partial<InstanceAiWorkflowSetupNode['node']>;
} = {},
): InstanceAiWorkflowSetupNode {
const { node: nodeOverrides, ...requestOverrides } = overrides;
const node: InstanceAiWorkflowSetupNode['node'] = {
id: 'http-request',
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4.2,
position: [0, 0],
parameters: {},
};
return {
credentialType: 'httpBasicAuth',
existingCredentials: [],
isTrigger: false,
...requestOverrides,
node: {
...node,
...nodeOverrides,
},
};
}
export function makeWorkflowSetupSection(
overrides: Omit<Partial<WorkflowSetupSection>, 'node'> & {
node?: Partial<WorkflowSetupSection['node']>;
} = {},
): WorkflowSetupSection {
const node: WorkflowSetupSection['node'] = {
id: 'http-request',
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4.2,
position: [0, 0],
parameters: {},
};
const credentialType = 'credentialType' in overrides ? overrides.credentialType : 'httpBasicAuth';
const targetNodeName = overrides.targetNodeName ?? overrides.node?.name ?? node.name;
const finalNode = {
...node,
name: targetNodeName,
...overrides.node,
};
return {
id: overrides.id ?? buildSectionId(targetNodeName, credentialType),
...(credentialType ? { credentialType } : {}),
targetNodeName,
node: finalNode,
currentCredentialId: overrides.currentCredentialId ?? null,
parameterNames: overrides.parameterNames ?? [],
credentialTargetNodes: overrides.credentialTargetNodes ?? [
{ id: finalNode.id, name: finalNode.name, type: finalNode.type },
],
};
}

View File

@ -0,0 +1,150 @@
import { computed, ref } from 'vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createComponentRenderer } from '@/__tests__/render';
import type { INodeUi } from '@/Interface';
import WorkflowSetupCard from './WorkflowSetupCard.vue';
import { makeWorkflowSetupSection } from '../__tests__/factories';
import type { WorkflowSetupContext } from '../composables/useWorkflowSetupContext';
import type { WorkflowSetupSection } from '../workflowSetup.types';
const workflowSetupContext = vi.hoisted(() => ({
current: undefined as unknown as WorkflowSetupContext,
}));
const credentialsStore = vi.hoisted(() => ({
getCredentialTypeByName: vi.fn(),
getCredentialById: vi.fn(),
}));
const nodeTypesStore = vi.hoisted(() => ({
getNodeType: vi.fn(),
communityNodeType: vi.fn(() => null),
}));
vi.mock('../composables/useWorkflowSetupContext', () => ({
useWorkflowSetupContext: () => workflowSetupContext.current,
}));
vi.mock('@n8n/i18n', async (importOriginal) => ({
...(await importOriginal()),
useI18n: () => ({
baseText: (key: string, opts?: { interpolate?: Record<string, string> }) => {
if (key === 'instanceAi.credential.setupTitle' && opts?.interpolate?.name) {
return `Set up ${opts.interpolate.name}`;
}
return key;
},
}),
}));
vi.mock('@/features/credentials/credentials.store', () => ({
useCredentialsStore: () => credentialsStore,
}));
vi.mock('@/app/stores/nodeTypes.store', () => ({
useNodeTypesStore: () => nodeTypesStore,
}));
vi.mock('@/features/settings/environments.ee/environments.store', () => ({
default: () => ({ variablesAsObject: {} }),
}));
vi.mock('@/app/components/NodeIcon.vue', () => ({
default: {
props: ['nodeType', 'size'],
template: '<span data-test-id="node-icon" />',
},
}));
vi.mock('@/features/credentials/components/CredentialIcon.vue', () => ({
default: {
props: ['credentialTypeName', 'size'],
template: '<span data-test-id="credential-icon" />',
},
}));
vi.mock('./WorkflowSetupSectionBody.vue', () => ({
default: {
props: ['section'],
template: '<div data-test-id="workflow-setup-section-body" />',
},
}));
const renderComponent = createComponentRenderer(WorkflowSetupCard);
function makeContext(section: WorkflowSetupSection): WorkflowSetupContext {
return {
sections: computed(() => [section]),
steps: computed(() => [{ kind: 'section', section }]),
currentStepIndex: ref(0),
activeStep: computed(() => ({ kind: 'section', section })),
hasOtherUnhandledSteps: computed(() => false),
canAdvanceToNextIncomplete: computed(() => false),
credentialSelections: ref({}),
terminalState: ref(null),
isReady: ref(true),
projectId: computed(() => undefined),
credentialFlow: computed(() => undefined),
isActionPending: ref(false),
setCredential: vi.fn(),
setParameterValue: vi.fn(),
getDisplayNode: (setupSection) => setupSection.node as INodeUi,
isSectionComplete: () => false,
isCredentialTestFailed: () => false,
isSectionSkipped: () => false,
isStepComplete: () => false,
isStepSkipped: () => false,
isStepHandled: () => false,
goToStep: vi.fn(),
goToNext: vi.fn(),
goToPrev: vi.fn(),
goToNextIncomplete: vi.fn(),
apply: vi.fn(async () => {}),
skipCurrentStep: vi.fn(async () => {}),
};
}
describe('WorkflowSetupCard', () => {
beforeEach(() => {
vi.clearAllMocks();
credentialsStore.getCredentialTypeByName.mockReturnValue({ displayName: 'Header Auth' });
nodeTypesStore.getNodeType.mockReturnValue({
name: 'n8n-nodes-base.httpRequest',
displayName: 'HTTP Request',
properties: [{ displayName: 'URL', name: 'url', type: 'string', default: '' }],
});
});
it('shows the node name when the section includes parameters and credentials', () => {
const section = makeWorkflowSetupSection({
credentialType: 'httpHeaderAuth',
parameterNames: ['url'],
});
workflowSetupContext.current = makeContext(section);
const { getByText, getByTestId, queryByText, queryByTestId } = renderComponent({
props: { section },
});
expect(getByText('HTTP Request')).toBeInTheDocument();
expect(queryByText('Set up Header Auth')).not.toBeInTheDocument();
expect(getByTestId('node-icon')).toBeInTheDocument();
expect(queryByTestId('credential-icon')).not.toBeInTheDocument();
});
it('shows the credential app name when the section only needs credentials', () => {
const section = makeWorkflowSetupSection({
credentialType: 'httpHeaderAuth',
});
workflowSetupContext.current = makeContext(section);
const { getByText, getByTestId, queryByText, queryByTestId } = renderComponent({
props: { section },
});
expect(getByText('Set up Header Auth')).toBeInTheDocument();
expect(queryByText('HTTP Request')).not.toBeInTheDocument();
expect(getByTestId('credential-icon')).toBeInTheDocument();
expect(queryByTestId('node-icon')).not.toBeInTheDocument();
});
});

View File

@ -0,0 +1,120 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { N8nIcon, N8nText } from '@n8n/design-system';
import { useI18n } from '@n8n/i18n';
import { getAppNameFromCredType } from '@/app/utils/nodeTypesUtils';
import NodeIcon from '@/app/components/NodeIcon.vue';
import CredentialIcon from '@/features/credentials/components/CredentialIcon.vue';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import type { WorkflowSetupSection } from '../workflowSetup.types';
import { useWorkflowSetupContext } from '../composables/useWorkflowSetupContext';
import WorkflowSetupSectionBody from './WorkflowSetupSectionBody.vue';
const props = defineProps<{
section: WorkflowSetupSection;
}>();
const ctx = useWorkflowSetupContext();
const i18n = useI18n();
const credentialsStore = useCredentialsStore();
const nodeTypesStore = useNodeTypesStore();
const credentialType = computed(() => props.section.credentialType);
const isComplete = computed(() => ctx.isSectionComplete(props.section));
const isSkipped = computed(() => ctx.isSectionSkipped(props.section));
const nodeType = computed(() =>
nodeTypesStore.getNodeType(props.section.node.type, props.section.node.typeVersion),
);
const isCredentialOnlySection = computed(
() => !!credentialType.value && props.section.parameterNames.length === 0,
);
const displayName = computed(() => {
const credentialTypeName = credentialType.value;
if (!isCredentialOnlySection.value || !credentialTypeName) return props.section.node.name;
const raw =
credentialsStore.getCredentialTypeByName(credentialTypeName)?.displayName ?? credentialTypeName;
const appName = getAppNameFromCredType(raw);
return i18n.baseText('instanceAi.credential.setupTitle', { interpolate: { name: appName } });
});
</script>
<template>
<div :class="$style.card" data-test-id="instance-ai-workflow-setup-card">
<header :class="$style.header">
<CredentialIcon
v-if="isCredentialOnlySection"
:credential-type-name="credentialType ?? null"
:size="16"
/>
<NodeIcon v-else :node-type="nodeType" :size="16" />
<N8nText :class="$style.title" size="medium" color="text-dark" bold>
{{ displayName }}
</N8nText>
<N8nText
v-if="isComplete"
data-test-id="instance-ai-workflow-setup-card-check"
:class="$style.statusLabel"
size="medium"
color="success"
>
<N8nIcon icon="check" size="large" />
{{ i18n.baseText('generic.complete') }}
</N8nText>
<N8nText
v-else-if="isSkipped"
data-test-id="instance-ai-workflow-setup-card-skipped"
:class="$style.statusLabel"
size="medium"
color="text-light"
>
<N8nIcon icon="arrow-right" size="large" />
{{ i18n.baseText('instanceAi.workflowSetup.cardSkipped') }}
</N8nText>
</header>
<div :class="$style.bodyWrapper">
<WorkflowSetupSectionBody :section="section" />
</div>
<slot name="footer" />
</div>
</template>
<style lang="scss" module>
.card {
display: flex;
flex-direction: column;
gap: var(--spacing--sm);
padding-top: var(--spacing--sm);
border: var(--border);
border-radius: var(--radius);
background-color: var(--color--background--light-3);
}
.header {
display: flex;
align-items: center;
gap: var(--spacing--2xs);
padding: 0 var(--spacing--sm);
}
.title {
flex: 1;
}
.statusLabel {
display: flex;
align-items: center;
gap: var(--spacing--4xs);
white-space: nowrap;
}
.bodyWrapper {
padding: 0 var(--spacing--sm);
}
</style>

View File

@ -0,0 +1,226 @@
import { computed, ref } from 'vue';
import { fireEvent } from '@testing-library/vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createComponentRenderer } from '@/__tests__/render';
import type { INodeUi } from '@/Interface';
import WorkflowSetupGroupCard from './WorkflowSetupGroupCard.vue';
import { makeWorkflowSetupSection } from '../__tests__/factories';
import type { WorkflowSetupContext } from '../composables/useWorkflowSetupContext';
import type { WorkflowSetupGroup, WorkflowSetupSection } from '../workflowSetup.types';
const workflowSetupContext = vi.hoisted(() => ({
current: undefined as unknown as WorkflowSetupContext,
}));
const nodeTypesStore = vi.hoisted(() => ({
getNodeType: vi.fn(),
communityNodeType: vi.fn(() => null),
}));
vi.mock('../composables/useWorkflowSetupContext', () => ({
useWorkflowSetupContext: () => workflowSetupContext.current,
}));
vi.mock('@/app/stores/nodeTypes.store', () => ({
useNodeTypesStore: () => nodeTypesStore,
}));
vi.mock('@n8n/i18n', async (importOriginal) => ({
...(await importOriginal()),
useI18n: () => ({
baseText: (key: string) => key,
}),
}));
vi.mock('@/app/components/NodeIcon.vue', () => ({
default: {
props: ['nodeType', 'size'],
template: '<span data-test-id="node-icon" :data-node-type="nodeType?.name" />',
},
}));
vi.mock('./WorkflowSetupSectionBody.vue', () => ({
default: {
props: ['section'],
template: '<div data-test-id="workflow-setup-section-body" :data-section-id="section.id" />',
},
}));
const renderComponent = createComponentRenderer(WorkflowSetupGroupCard);
interface ContextOverrides {
completedIds?: Set<string>;
}
function makeContext(overrides: ContextOverrides = {}): WorkflowSetupContext {
const completedIds = overrides.completedIds ?? new Set<string>();
return {
sections: computed(() => []),
steps: computed(() => []),
currentStepIndex: ref(0),
activeStep: computed(() => undefined),
hasOtherUnhandledSteps: computed(() => false),
canAdvanceToNextIncomplete: computed(() => false),
credentialSelections: ref({}),
terminalState: ref(null),
isReady: ref(true),
projectId: computed(() => undefined),
credentialFlow: computed(() => undefined),
isActionPending: ref(false),
setCredential: vi.fn(),
setParameterValue: vi.fn(),
getDisplayNode: (section) => section.node as INodeUi,
isSectionComplete: (section: WorkflowSetupSection) => completedIds.has(section.id),
isCredentialTestFailed: () => false,
isSectionSkipped: () => false,
isStepComplete: () => false,
isStepSkipped: () => false,
isStepHandled: () => false,
goToStep: vi.fn(),
goToNext: vi.fn(),
goToPrev: vi.fn(),
goToNextIncomplete: vi.fn(),
apply: vi.fn(async () => {}),
skipCurrentStep: vi.fn(async () => {}),
};
}
const subnodeRootNode = { name: 'Agent', type: 'agentType', typeVersion: 1, id: 'agent-1' };
describe('WorkflowSetupGroupCard', () => {
beforeEach(() => {
vi.clearAllMocks();
nodeTypesStore.getNodeType.mockImplementation((type: string) => ({
name: type,
properties: [],
}));
});
it('renders the root node header even when there is no root section', () => {
const sub = makeWorkflowSetupSection({
id: 'Model:openAiApi',
targetNodeName: 'Model',
});
const group: WorkflowSetupGroup = {
subnodeRootNode,
subnodeSections: [sub],
};
workflowSetupContext.current = makeContext();
const { getByText, getAllByTestId } = renderComponent({ props: { group } });
expect(getByText('Agent')).toBeInTheDocument();
const icons = getAllByTestId('node-icon');
expect(icons[0].getAttribute('data-node-type')).toBe('agentType');
});
it('renders the root section inline without a collapsible header', async () => {
const rootSection = makeWorkflowSetupSection({
id: 'Agent:openAiApi',
targetNodeName: 'Agent',
credentialType: 'openAiApi',
});
const sub = makeWorkflowSetupSection({
id: 'Model:openAiApi',
targetNodeName: 'Model',
});
const group: WorkflowSetupGroup = {
subnodeRootNode,
rootSection,
subnodeSections: [sub],
};
workflowSetupContext.current = makeContext();
const { getAllByTestId, queryAllByTestId } = renderComponent({ props: { group } });
const sectionWrappers = getAllByTestId('instance-ai-workflow-setup-section');
expect(sectionWrappers).toHaveLength(2);
// Only sub-node sections render a collapsible header.
const sectionHeaders = queryAllByTestId('instance-ai-workflow-setup-section-header');
expect(sectionHeaders).toHaveLength(1);
// Root body is always rendered; the (incomplete) sub-node is
// auto-expanded since it is the first incomplete sub-node.
const bodies = getAllByTestId('workflow-setup-section-body');
const renderedIds = bodies.map((body) => body.getAttribute('data-section-id'));
expect(renderedIds).toEqual(expect.arrayContaining(['Agent:openAiApi', 'Model:openAiApi']));
expect(bodies).toHaveLength(2);
});
it('toggles sub-node sections via the section header', async () => {
const rootSection = makeWorkflowSetupSection({
id: 'Agent:openAiApi',
targetNodeName: 'Agent',
credentialType: 'openAiApi',
});
const sub = makeWorkflowSetupSection({
id: 'Model:openAiApi',
targetNodeName: 'Model',
});
const group: WorkflowSetupGroup = {
subnodeRootNode,
rootSection,
subnodeSections: [sub],
};
workflowSetupContext.current = makeContext();
const { getAllByTestId } = renderComponent({ props: { group } });
// Initial state: root body always rendered + sub-node auto-expanded.
let bodies = getAllByTestId('workflow-setup-section-body');
expect(bodies).toHaveLength(2);
// Only the sub-node has a header; clicking it collapses the sub-node body.
const headers = getAllByTestId('instance-ai-workflow-setup-section-header');
expect(headers).toHaveLength(1);
await fireEvent.click(headers[0]);
bodies = getAllByTestId('workflow-setup-section-body');
expect(bodies).toHaveLength(1);
expect(bodies[0].getAttribute('data-section-id')).toBe('Agent:openAiApi');
});
it('shows the complete badge only when every section is complete', () => {
const rootSection = makeWorkflowSetupSection({
id: 'Agent:openAiApi',
targetNodeName: 'Agent',
credentialType: 'openAiApi',
});
const sub = makeWorkflowSetupSection({
id: 'Model:openAiApi',
targetNodeName: 'Model',
});
const group: WorkflowSetupGroup = {
subnodeRootNode,
rootSection,
subnodeSections: [sub],
};
workflowSetupContext.current = makeContext({
completedIds: new Set([rootSection.id, sub.id]),
});
const { getByTestId } = renderComponent({ props: { group } });
expect(getByTestId('instance-ai-workflow-setup-group-card-check')).toBeInTheDocument();
});
it('renders the footer slot', () => {
const sub = makeWorkflowSetupSection({
id: 'Model:openAiApi',
targetNodeName: 'Model',
});
const group: WorkflowSetupGroup = {
subnodeRootNode,
subnodeSections: [sub],
};
workflowSetupContext.current = makeContext();
const { getByTestId } = renderComponent({
props: { group },
slots: { footer: '<div data-test-id="footer-slot">footer</div>' },
});
expect(getByTestId('footer-slot')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,198 @@
<script lang="ts" setup>
import { computed, toRef } from 'vue';
import { N8nIcon, N8nText } from '@n8n/design-system';
import { useI18n } from '@n8n/i18n';
import NodeIcon from '@/app/components/NodeIcon.vue';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import type { WorkflowSetupGroup, WorkflowSetupSection } from '../workflowSetup.types';
import { getGroupSections } from '../workflowSetup.helpers';
import { useWorkflowSetupContext } from '../composables/useWorkflowSetupContext';
import { useWorkflowSetupGroupSections } from '../composables/useWorkflowSetupGroupSections';
import WorkflowSetupSectionBody from './WorkflowSetupSectionBody.vue';
const props = defineProps<{
group: WorkflowSetupGroup;
}>();
const i18n = useI18n();
const ctx = useWorkflowSetupContext();
const nodeTypesStore = useNodeTypesStore();
const groupRef = toRef(props, 'group');
const { expandedSections, toggleSection } = useWorkflowSetupGroupSections(groupRef);
const subnodeRootNodeType = computed(() =>
nodeTypesStore.getNodeType(
props.group.subnodeRootNode.type,
props.group.subnodeRootNode.typeVersion,
),
);
const isGroupComplete = computed(() => getGroupSections(props.group).every(ctx.isSectionComplete));
function getSectionNodeType(section: WorkflowSetupSection) {
return nodeTypesStore.getNodeType(section.node.type, section.node.typeVersion);
}
</script>
<template>
<div :class="$style.card" data-test-id="instance-ai-workflow-setup-group-card">
<header :class="$style.header">
<NodeIcon :node-type="subnodeRootNodeType" :size="16" />
<N8nText :class="$style.title" size="medium" color="text-dark" bold>
{{ group.subnodeRootNode.name }}
</N8nText>
<N8nText
v-if="isGroupComplete"
data-test-id="instance-ai-workflow-setup-group-card-check"
:class="$style.completeLabel"
size="medium"
color="success"
>
<N8nIcon icon="check" size="large" />
{{ i18n.baseText('generic.complete') }}
</N8nText>
</header>
<div :class="$style.sections">
<div
v-if="group.rootSection"
:key="group.rootSection.id"
:class="[$style.section, $style.rootSection]"
data-test-id="instance-ai-workflow-setup-section"
>
<WorkflowSetupSectionBody :section="group.rootSection" />
</div>
<div
v-for="section in group.subnodeSections"
:key="section.id"
:class="$style.section"
data-test-id="instance-ai-workflow-setup-section"
>
<button
type="button"
:class="$style.sectionHeader"
:aria-expanded="!!expandedSections[section.id]"
:aria-controls="`workflow-setup-section-body-${section.id}`"
data-test-id="instance-ai-workflow-setup-section-header"
@click="toggleSection(section.id)"
>
<N8nIcon
v-if="ctx.isSectionComplete(section)"
icon="check"
size="small"
:class="$style.sectionCheck"
/>
<NodeIcon v-else :node-type="getSectionNodeType(section)" :size="14" />
<N8nText :class="$style.sectionTitle" size="medium" color="text-dark">
{{ section.node.name }}
</N8nText>
<N8nIcon
:class="$style.sectionChevron"
:icon="expandedSections[section.id] ? 'chevrons-down-up' : 'chevrons-up-down'"
size="large"
color="text-light"
/>
</button>
<div
v-if="expandedSections[section.id]"
:id="`workflow-setup-section-body-${section.id}`"
:class="$style.sectionContent"
data-test-id="instance-ai-workflow-setup-section-body"
>
<WorkflowSetupSectionBody :section="section" />
</div>
</div>
</div>
<slot name="footer" />
</div>
</template>
<style lang="scss" module>
.card {
display: flex;
flex-direction: column;
border: var(--border);
border-radius: var(--radius);
background-color: var(--color--background--light-3);
}
.header {
display: flex;
align-items: center;
gap: var(--spacing--2xs);
padding: var(--spacing--sm);
}
.title {
flex: 1;
}
.completeLabel {
display: flex;
align-items: center;
gap: var(--spacing--4xs);
white-space: nowrap;
}
.sections {
display: flex;
flex-direction: column;
}
.section {
border-top: var(--border);
padding: var(--spacing--sm);
}
// The root section renders inline as the group card's primary body and
// belongs with the group header, so no separator is drawn above it.
.rootSection {
border-top: none;
}
// The root section sits directly under the group header; collapse its top
// padding so the header's own bottom padding dictates the gap.
.rootSection:first-child {
padding-top: 0;
}
.sectionHeader {
display: flex;
align-items: center;
gap: var(--spacing--2xs);
width: 100%;
padding: 0;
border: none;
background: transparent;
color: inherit;
font: inherit;
text-align: left;
cursor: pointer;
user-select: none;
.sectionChevron {
display: none;
}
&:hover .sectionChevron,
&:focus-visible .sectionChevron {
display: block;
}
}
.sectionTitle {
flex: 1;
font-weight: var(--font-weight--medium);
}
.sectionCheck {
color: var(--color--success);
}
.sectionContent {
padding-top: var(--spacing--xs);
}
</style>

View File

@ -0,0 +1,192 @@
<script lang="ts" setup>
import { computed, provide, ref, watch } from 'vue';
import { N8nText, N8nTooltip } from '@n8n/design-system';
import { useI18n } from '@n8n/i18n';
import NodeCredentials from '@/features/credentials/components/NodeCredentials.vue';
import ParameterInputList from '@/features/ndv/parameters/components/ParameterInputList.vue';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import useEnvironmentsStore from '@/features/settings/environments.ee/environments.store';
import { ExpressionLocalResolveContextSymbol } from '@/app/constants';
import { Workflow, type IConnections, type INodeProperties } from 'n8n-workflow';
import type { ExpressionLocalResolveContext } from '@/app/types/expressions';
import type { INodeUi, INodeUpdatePropertiesInformation, IUpdateInformation } from '@/Interface';
import type { WorkflowSetupSection } from '../workflowSetup.types';
import { useWorkflowSetupContext } from '../composables/useWorkflowSetupContext';
const props = defineProps<{
section: WorkflowSetupSection;
}>();
const ctx = useWorkflowSetupContext();
const i18n = useI18n();
const credentialsStore = useCredentialsStore();
const nodeTypesStore = useNodeTypesStore();
const environmentsStore = useEnvironmentsStore();
const credentialType = computed(() => props.section.credentialType);
const selectedCredentialId = computed(() =>
credentialType.value
? (ctx.credentialSelections.value[props.section.targetNodeName]?.[credentialType.value] ?? null)
: null,
);
const targetNodeNames = computed(() =>
props.section.credentialTargetNodes.map((node) => node.name),
);
const targetNodeNamesTooltip = computed(() => targetNodeNames.value.join(', '));
const usedByNodesLabel = computed(() =>
i18n.baseText('instanceAi.workflowSetup.usedByNodes', {
adjustToNumber: targetNodeNames.value.length,
interpolate: { count: targetNodeNames.value.length },
}),
);
const nodeType = computed(() =>
nodeTypesStore.getNodeType(props.section.node.type, props.section.node.typeVersion),
);
const parameterDefinitions = computed<INodeProperties[]>(() => {
if (!nodeType.value || props.section.parameterNames.length === 0) return [];
const names = new Set(props.section.parameterNames);
return nodeType.value.properties.filter((property) => names.has(property.name));
});
const revealedIssues = ref(new Set<string>());
watch(
() => props.section.id,
() => {
revealedIssues.value = new Set();
},
);
const hiddenIssuesInputs = computed(() =>
parameterDefinitions.value
.filter((param) => !revealedIssues.value.has(param.name))
.map((param) => param.name),
);
function revealParameterIssues(parameterName: string) {
revealedIssues.value.add(parameterName);
}
function getRootParameterName(parameterName: string) {
return parameterName.split(/[.[\]]/)[0] ?? parameterName;
}
const displayNode = computed<INodeUi>(() => {
const node = ctx.getDisplayNode(props.section);
if (!credentialType.value) return node;
const cred = selectedCredentialId.value
? credentialsStore.getCredentialById(selectedCredentialId.value)
: undefined;
return {
...node,
credentials: cred ? { [credentialType.value]: { id: cred.id, name: cred.name } } : {},
} as INodeUi;
});
const expressionContext = computed<ExpressionLocalResolveContext | undefined>(() => {
const node = displayNode.value;
const connections: IConnections = {};
const workflow = new Workflow({
id: 'instance-ai-workflow-setup',
name: 'Instance AI workflow setup',
nodes: [node],
connections,
active: false,
nodeTypes: nodeTypesStore.getAllNodeTypes(),
});
return {
localResolve: true,
envVars: environmentsStore.variablesAsObject,
workflow,
execution: null,
nodeName: node.name,
additionalKeys: {},
connections,
};
});
provide(ExpressionLocalResolveContextSymbol, expressionContext);
function onCredentialSelected(update: INodeUpdatePropertiesInformation) {
if (!credentialType.value) return;
const data = update.properties.credentials?.[credentialType.value];
ctx.setCredential(props.section, data?.id ?? null);
}
function onParameterValueChanged(update: IUpdateInformation) {
const parameterName = update.name.replace(/^parameters\./, '');
ctx.setParameterValue(props.section, parameterName, update.value);
revealParameterIssues(getRootParameterName(parameterName));
}
</script>
<template>
<div :class="$style.body">
<NodeCredentials
v-if="credentialType"
:node="displayNode"
:override-cred-type="credentialType"
:project-id="ctx.projectId.value"
standalone
hide-issues
hide-ask-assistant
@credential-selected="onCredentialSelected"
>
<template v-if="section.credentialTargetNodes.length > 1" #label-postfix>
<N8nTooltip placement="top">
<template #content>{{ targetNodeNamesTooltip }}</template>
<N8nText
size="small"
color="text-light"
data-test-id="instance-ai-workflow-setup-card-nodes-hint"
>
{{ usedByNodesLabel }}
</N8nText>
</N8nTooltip>
</template>
</NodeCredentials>
<div
v-if="parameterDefinitions.length > 0"
:class="$style.parameters"
data-test-id="instance-ai-workflow-setup-parameters"
>
<ParameterInputList
:parameters="parameterDefinitions"
:node-values="{ parameters: displayNode.parameters }"
:node="displayNode"
path="parameters"
:hide-delete="true"
:hidden-issues-inputs="hiddenIssuesInputs"
:remove-first-parameter-margin="true"
:remove-last-parameter-margin="true"
:options-overrides="{ hideExpressionSelector: true, hideFocusPanelButton: true }"
@value-changed="onParameterValueChanged"
@parameter-blur="revealParameterIssues"
/>
</div>
</div>
</template>
<style lang="scss" module>
.body {
display: flex;
flex-direction: column;
gap: var(--spacing--xs);
:global(.node-credentials) {
margin-top: 0;
}
}
.parameters {
display: flex;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,85 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { N8nIcon, N8nText, type IconName } from '@n8n/design-system';
import { i18n, type BaseTextKey } from '@n8n/i18n';
import type { TerminalState } from '../workflowSetup.types';
const props = defineProps<{ state: TerminalState }>();
type IconClass = 'iconLoading' | 'iconSuccess' | 'iconWarning' | 'iconMuted';
type StatusView = {
icon: IconName;
iconClass: IconClass;
labelKey: BaseTextKey;
spin?: boolean;
};
const STATUS_BY_STATE: Record<TerminalState, StatusView> = {
applying: {
icon: 'spinner',
iconClass: 'iconLoading',
labelKey: 'instanceAi.workflowSetup.applying',
spin: true,
},
applied: {
icon: 'check',
iconClass: 'iconSuccess',
labelKey: 'instanceAi.workflowSetup.applied',
},
partial: {
icon: 'triangle-alert',
iconClass: 'iconWarning',
labelKey: 'instanceAi.workflowSetup.partiallyApplied',
},
deferred: {
icon: 'arrow-right',
iconClass: 'iconMuted',
labelKey: 'instanceAi.workflowSetup.deferred',
},
};
const statusView = computed(() => STATUS_BY_STATE[props.state]);
</script>
<template>
<div :class="$style.status" :data-test-id="`instance-ai-workflow-setup-status-${state}`">
<N8nIcon
:icon="statusView.icon"
size="small"
:spin="!!statusView.spin"
:class="$style[statusView.iconClass]"
/>
<N8nText size="small" color="text-light">
{{ i18n.baseText(statusView.labelKey) }}
</N8nText>
</div>
</template>
<style lang="scss" module>
.status {
display: flex;
align-items: center;
gap: var(--spacing--2xs);
padding: var(--spacing--xs) var(--spacing--sm);
border: var(--border);
border-radius: var(--radius);
background-color: var(--color--background--light-3);
font-size: var(--font-size--2xs);
}
.iconLoading {
color: var(--color--text--tint-1);
}
.iconSuccess {
color: var(--color--success);
}
.iconWarning {
color: var(--color--warning);
}
.iconMuted {
color: var(--color--text--tint-2);
}
</style>

View File

@ -0,0 +1,36 @@
<script lang="ts" setup>
import { computed } from 'vue';
import WorkflowSetupCard from './WorkflowSetupCard.vue';
import WorkflowSetupGroupCard from './WorkflowSetupGroupCard.vue';
import WorkflowSetupWizardFooter from './WorkflowSetupWizardFooter.vue';
import { useWorkflowSetupContext } from '../composables/useWorkflowSetupContext';
const ctx = useWorkflowSetupContext();
const activeGroup = computed(() =>
ctx.activeStep.value?.kind === 'group' ? ctx.activeStep.value.group : undefined,
);
const activeSection = computed(() =>
ctx.activeStep.value?.kind === 'section' ? ctx.activeStep.value.section : undefined,
);
const groupKey = computed(() => {
return activeGroup.value ? `group:${activeGroup.value.subnodeRootNode.name}` : undefined;
});
const sectionKey = computed(() => activeSection.value?.id);
</script>
<template>
<WorkflowSetupGroupCard v-if="activeGroup" :key="groupKey" :group="activeGroup">
<template #footer>
<WorkflowSetupWizardFooter />
</template>
</WorkflowSetupGroupCard>
<WorkflowSetupCard v-else-if="activeSection" :key="sectionKey" :section="activeSection">
<template #footer>
<WorkflowSetupWizardFooter />
</template>
</WorkflowSetupCard>
</template>

View File

@ -0,0 +1,144 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { N8nButton, N8nIconButton, N8nText, N8nTooltip } from '@n8n/design-system';
import { useI18n } from '@n8n/i18n';
import ConfirmationFooter from '../../components/ConfirmationFooter.vue';
import { useWorkflowSetupContext } from '../composables/useWorkflowSetupContext';
import { getStepSections } from '../workflowSetup.helpers';
const ctx = useWorkflowSetupContext();
const i18n = useI18n();
const totalSteps = computed(() => ctx.steps.value.length);
const showArrows = computed(() => totalSteps.value > 1);
const isPrevDisabled = computed(() => ctx.currentStepIndex.value === 0);
const isNextDisabled = computed(() => ctx.currentStepIndex.value >= totalSteps.value - 1);
const isActiveStepHandled = computed(() =>
ctx.activeStep.value ? ctx.isStepHandled(ctx.activeStep.value) : false,
);
const isPrimaryActionBlockedByCredentialTest = computed(() => {
const step = ctx.activeStep.value;
if (!step) return false;
return getStepSections(step).some(
(section) => !ctx.isSectionSkipped(section) && ctx.isCredentialTestFailed(section),
);
});
const isPrimaryActionDisabled = computed(
() => ctx.activeStep.value !== undefined && !isActiveStepHandled.value,
);
const isFinalize = computed(() => ctx.credentialFlow.value?.stage === 'finalize');
const showSkipButton = computed(
() => ctx.activeStep.value !== undefined && !isActiveStepHandled.value,
);
const showContinueButton = computed(() => ctx.hasOtherUnhandledSteps.value);
const skipLabel = computed(() =>
i18n.baseText(
isFinalize.value ? 'instanceAi.credential.finalize.later' : 'instanceAi.workflowSetup.later',
),
);
const primaryActionLabel = computed(() => {
if (showContinueButton.value) return i18n.baseText('instanceAi.credential.continueButton');
return i18n.baseText(
isFinalize.value
? 'instanceAi.credential.finalize.applyCredentials'
: 'instanceAi.workflowSetup.apply',
);
});
const primaryActionTestId = computed(() =>
showContinueButton.value
? 'instance-ai-workflow-setup-continue'
: 'instance-ai-workflow-setup-apply',
);
function onPrimaryAction() {
if (showContinueButton.value) {
ctx.goToNextIncomplete();
return;
}
void ctx.apply();
}
</script>
<template>
<ConfirmationFooter layout="row-between" bordered>
<div :class="$style.nav">
<N8nIconButton
v-if="showArrows"
variant="ghost"
size="medium"
icon="chevron-left"
:disabled="isPrevDisabled"
data-test-id="instance-ai-workflow-setup-prev"
:aria-label="i18n.baseText('instanceAi.workflowSetup.prevStep')"
@click="ctx.goToPrev"
/>
<N8nText size="small" color="text-light">
{{
i18n.baseText('instanceAi.workflowSetup.stepCounter', {
interpolate: { current: ctx.currentStepIndex.value + 1, total: totalSteps },
})
}}
</N8nText>
<N8nIconButton
v-if="showArrows"
variant="ghost"
size="medium"
icon="chevron-right"
:disabled="isNextDisabled"
data-test-id="instance-ai-workflow-setup-next"
:aria-label="i18n.baseText('instanceAi.workflowSetup.nextStep')"
@click="ctx.goToNext"
/>
</div>
<div :class="$style.actions">
<N8nButton
v-if="showSkipButton"
variant="outline"
size="medium"
:label="skipLabel"
:disabled="ctx.isActionPending.value"
data-test-id="instance-ai-workflow-setup-later"
@click="ctx.skipCurrentStep"
/>
<N8nTooltip
:disabled="!isPrimaryActionBlockedByCredentialTest"
:content="i18n.baseText('instanceAi.workflowSetup.credentialTestFailedTooltip')"
>
<N8nButton
size="medium"
:label="primaryActionLabel"
:disabled="isPrimaryActionDisabled"
:data-test-id="primaryActionTestId"
@click="onPrimaryAction"
/>
</N8nTooltip>
</div>
</ConfirmationFooter>
</template>
<style lang="scss" module>
.nav {
display: flex;
flex: 1;
align-items: center;
gap: var(--spacing--4xs);
}
.actions {
display: flex;
align-items: center;
gap: var(--spacing--2xs);
}
</style>

View File

@ -0,0 +1,510 @@
import { computed, nextTick, ref, type ComputedRef, type Ref } from 'vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { WorkflowSetupSection, WorkflowSetupStep } from '../workflowSetup.types';
import { makeWorkflowSetupSection } from '../__tests__/factories';
import { useWorkflowSetupActions } from './useWorkflowSetupActions';
const telemetryTrack = vi.fn();
vi.mock('@/app/composables/useTelemetry', () => ({
useTelemetry: () => ({ track: telemetryTrack }),
}));
const rootStoreState = { instanceId: 'instance-1' };
vi.mock('@n8n/stores/useRootStore', () => ({
useRootStore: () => rootStoreState,
}));
interface Harness {
sectionA: WorkflowSetupSection;
sectionB: WorkflowSetupSection;
sections: ComputedRef<WorkflowSetupSection[]>;
steps: ComputedRef<WorkflowSetupStep[]>;
currentStepIndex: Ref<number>;
isReady: Ref<boolean>;
activeStep: ComputedRef<WorkflowSetupStep | undefined>;
completedSet: Set<string>;
skippedSet: Set<string>;
credentialSelections: Ref<Record<string, Record<string, string>>>;
skippedSectionIds: Ref<Set<string>>;
goToStep: ReturnType<typeof vi.fn>;
apply: ReturnType<typeof vi.fn>;
defer: ReturnType<typeof vi.fn>;
markSectionSkipped: ReturnType<typeof vi.fn>;
buildCompletedSetupPayload: ReturnType<typeof vi.fn>;
thread: { currentThreadId: string; findToolCallByRequestId: ReturnType<typeof vi.fn> };
actions: ReturnType<typeof useWorkflowSetupActions>;
}
function setupHarness(): Harness {
const sectionA = makeWorkflowSetupSection({
id: 'A:typeA',
targetNodeName: 'A',
credentialType: 'typeA',
});
const sectionB = makeWorkflowSetupSection({
id: 'B:typeB',
targetNodeName: 'B',
credentialType: 'typeB',
});
const sections = computed(() => [sectionA, sectionB]);
const steps = computed<WorkflowSetupStep[]>(() => [
{ kind: 'section', section: sectionA },
{ kind: 'section', section: sectionB },
]);
const currentStepIndex = ref(0);
const isReady = ref(true);
const activeStep = computed<WorkflowSetupStep | undefined>(
() => steps.value[currentStepIndex.value],
);
const completedSet = new Set<string>();
const skippedSet = new Set<string>();
const credentialSelections = ref<Record<string, Record<string, string>>>({});
const skippedSectionIds = ref<Set<string>>(skippedSet);
const goToStep = vi.fn((idx: number) => {
currentStepIndex.value = idx;
});
const apply = vi.fn().mockResolvedValue(undefined);
const defer = vi.fn().mockResolvedValue(undefined);
const markSectionSkipped = vi.fn((section: WorkflowSetupSection) => {
skippedSet.add(section.id);
skippedSectionIds.value = new Set(skippedSet);
});
const buildCompletedSetupPayload = vi.fn(() => {
const out: Record<string, Record<string, string>> = {};
for (const section of sections.value) {
if (completedSet.has(section.id) && section.credentialType) {
out[section.targetNodeName] = {
...(out[section.targetNodeName] ?? {}),
[section.credentialType]: 'cred-id',
};
}
}
return { nodeCredentials: out };
});
const thread = {
currentThreadId: 'thread-1',
findToolCallByRequestId: vi.fn(() => ({
confirmation: { inputThreadId: 'input-thread-1' },
})),
};
const actions = useWorkflowSetupActions({
requestId: ref('req-1'),
sections,
steps,
activeStep,
currentStepIndex,
isReady,
goToStep,
inputs: {
credentialSelections,
isSectionComplete: (section) => completedSet.has(section.id),
isSectionSkipped: (section) => skippedSet.has(section.id),
markSectionSkipped,
buildCompletedSetupPayload,
},
applyMachine: { apply, defer },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
thread: thread as any,
});
return {
sectionA,
sectionB,
sections,
steps,
currentStepIndex,
isReady,
activeStep,
completedSet,
skippedSet,
credentialSelections,
skippedSectionIds,
goToStep,
apply,
defer,
markSectionSkipped,
buildCompletedSetupPayload,
thread,
actions,
};
}
function getTelemetryCalls(eventName: string) {
return telemetryTrack.mock.calls.filter(([event]) => event === eventName);
}
describe('useWorkflowSetupActions', () => {
beforeEach(() => {
telemetryTrack.mockReset();
});
it('tracks the active setup step when it is shown', () => {
setupHarness();
const shownPayload = getTelemetryCalls('Instance AI workflow setup step shown')[0]?.[1];
expect(telemetryTrack).toHaveBeenCalledWith(
'Instance AI workflow setup step shown',
expect.objectContaining({
thread_id: 'thread-1',
input_thread_id: 'input-thread-1',
instance_id: 'instance-1',
type: 'setup',
request_id: 'req-1',
step_index: 1,
step_count: 2,
step_kind: 'section',
setup_inputs: [
expect.objectContaining({
input_type: 'credential',
node_type: 'n8n-nodes-base.httpRequest',
credential_type: 'typeA',
}),
],
}),
);
expect(shownPayload).toEqual(expect.not.objectContaining({ sections: expect.anything() }));
expect(shownPayload.setup_inputs[0]).toEqual(
expect.not.objectContaining({
node_name: expect.anything(),
credential_target_nodes: expect.anything(),
label: expect.anything(),
}),
);
});
it('marks the active section skipped and advances to the next unhandled step without calling the API', async () => {
const h = setupHarness();
await h.actions.skipCurrentStep();
expect(h.markSectionSkipped).toHaveBeenCalledWith(h.sectionA);
expect(h.goToStep).toHaveBeenCalledWith(1);
expect(h.apply).not.toHaveBeenCalled();
expect(h.defer).not.toHaveBeenCalled();
expect(getTelemetryCalls('User finished providing input')).toHaveLength(0);
expect(telemetryTrack).toHaveBeenCalledWith(
'Instance AI workflow setup step handled',
expect.objectContaining({
outcome: 'skipped',
}),
);
});
it('routes through partial apply when terminal skip happens with at least one completion', async () => {
const h = setupHarness();
// section A is completed (with a real selection so telemetry can read it),
// section B is the active one and the only unhandled step.
h.completedSet.add(h.sectionA.id);
h.credentialSelections.value = { A: { typeA: 'cred-id' } };
h.currentStepIndex.value = 1;
await nextTick();
await h.actions.skipCurrentStep();
expect(h.markSectionSkipped).toHaveBeenCalledWith(h.sectionB);
expect(h.apply).toHaveBeenCalledWith({ nodeCredentials: { A: { typeA: 'cred-id' } } });
expect(h.defer).not.toHaveBeenCalled();
expect(getTelemetryCalls('User finished providing input')).toHaveLength(1);
expect(telemetryTrack).toHaveBeenCalledWith(
'User finished providing input',
expect.objectContaining({
type: 'setup',
explicitly_skipped_inputs: [{ label: 'n8n-nodes-base.httpRequest - typeB', options: [] }],
provided_inputs: [
expect.objectContaining({
label: 'n8n-nodes-base.httpRequest - typeA',
option_chosen: 'true',
}),
],
}),
);
});
it('routes through defer when terminal skip happens with zero completions', async () => {
const h = setupHarness();
// pre-skip section A so section B is the only unhandled step on a fresh skip.
h.skippedSet.add(h.sectionA.id);
h.skippedSectionIds.value = new Set(h.skippedSet);
h.currentStepIndex.value = 1;
await nextTick();
await h.actions.skipCurrentStep();
expect(h.markSectionSkipped).toHaveBeenCalledWith(h.sectionB);
expect(h.defer).toHaveBeenCalledTimes(1);
expect(h.apply).not.toHaveBeenCalled();
});
it('does not skip a section with selected-but-not-complete credential into a partial apply', async () => {
const h = setupHarness();
// pre-skip section A so section B is the active and only unhandled step,
// section B has a selection that hasn't completed (e.g. cred test pending).
h.skippedSet.add(h.sectionA.id);
h.skippedSectionIds.value = new Set(h.skippedSet);
h.credentialSelections.value = { B: { typeB: 'cred-id-pending' } };
h.currentStepIndex.value = 1;
await nextTick();
await h.actions.skipCurrentStep();
// B is not complete, A is skipped → no completed sections → defer path.
expect(h.defer).toHaveBeenCalledTimes(1);
expect(h.apply).not.toHaveBeenCalled();
});
it('routes back to an earlier unhandled step when current is later', async () => {
const h = setupHarness();
// User manually navigated to step B without handling step A.
h.currentStepIndex.value = 1;
await nextTick();
await h.actions.skipCurrentStep();
expect(h.markSectionSkipped).toHaveBeenCalledWith(h.sectionB);
// Step A is still unhandled → wizard should route back to it.
expect(h.goToStep).toHaveBeenCalledWith(0);
expect(h.apply).not.toHaveBeenCalled();
expect(h.defer).not.toHaveBeenCalled();
});
it('guards against rapid double-clicks via isActionPending', async () => {
const h = setupHarness();
// Make apply take a tick.
let resolveApply: () => void = () => {};
h.apply.mockReturnValueOnce(
new Promise<void>((r) => {
resolveApply = r;
}),
);
h.completedSet.add(h.sectionA.id);
h.currentStepIndex.value = 1;
await nextTick();
// First call → terminal skip, schedules apply (pending).
const first = h.actions.skipCurrentStep();
// Concurrent call should be ignored.
const second = h.actions.skipCurrentStep();
await second; // returns immediately — guard short-circuits
expect(h.apply).toHaveBeenCalledTimes(1);
resolveApply();
await first;
expect(h.apply).toHaveBeenCalledTimes(1);
});
it('nextUnhandledIndex skips both complete and skipped steps', async () => {
const h = setupHarness();
h.completedSet.add(h.sectionA.id);
h.skippedSet.add(h.sectionB.id);
h.skippedSectionIds.value = new Set(h.skippedSet);
await nextTick();
expect(h.actions.nextUnhandledIndex.value).toBe(-1);
expect(h.actions.hasOtherUnhandledSteps.value).toBe(false);
});
it('apply() reports completed sections via partial credential map and tracks telemetry', async () => {
const h = setupHarness();
h.completedSet.add(h.sectionA.id);
await h.actions.apply();
expect(h.apply).toHaveBeenCalledWith({ nodeCredentials: { A: { typeA: 'cred-id' } } });
expect(getTelemetryCalls('User finished providing input')).toHaveLength(1);
expect(telemetryTrack).toHaveBeenCalledWith(
'Instance AI workflow setup step handled',
expect.objectContaining({
outcome: 'completed',
}),
);
});
it('tracks both credential and parameter inputs for a completed mixed section', async () => {
const h = setupHarness();
h.sectionA.parameterNames = ['url', 'method'];
h.completedSet.add(h.sectionA.id);
h.credentialSelections.value = { A: { typeA: 'cred-id' } };
await h.actions.apply();
expect(telemetryTrack).toHaveBeenCalledWith(
'User finished providing input',
expect.objectContaining({
provided_inputs: [
{
label: 'n8n-nodes-base.httpRequest - typeA',
options: [],
option_chosen: 'true',
},
{
label: 'n8n-nodes-base.httpRequest - url',
options: [],
option_chosen: 'true',
},
{
label: 'n8n-nodes-base.httpRequest - method',
options: [],
option_chosen: 'true',
},
],
num_tasks: 2,
}),
);
});
describe('group steps', () => {
it('skips only incomplete sections in a group, preserving complete ones', async () => {
const sectionA = makeWorkflowSetupSection({
id: 'Sub1:credA',
targetNodeName: 'Sub1',
credentialType: 'credA',
});
const sectionB = makeWorkflowSetupSection({
id: 'Sub2:credB',
targetNodeName: 'Sub2',
credentialType: 'credB',
});
const sections = computed(() => [sectionA, sectionB]);
const steps = computed<WorkflowSetupStep[]>(() => [
{
kind: 'group',
group: {
subnodeRootNode: { name: 'Agent', type: 'agent', typeVersion: 1, id: 'agent-1' },
subnodeSections: [sectionA, sectionB],
},
},
]);
const currentStepIndex = ref(0);
const activeStep = computed<WorkflowSetupStep | undefined>(() => steps.value[0]);
const completedSet = new Set<string>([sectionA.id]);
const skippedSet = new Set<string>();
const credentialSelections = ref<Record<string, Record<string, string>>>({
Sub1: { credA: 'cred-a' },
});
const skippedSectionIds = ref<Set<string>>(skippedSet);
const isReady = ref(true);
const goToStep = vi.fn();
const apply = vi.fn().mockResolvedValue(undefined);
const defer = vi.fn().mockResolvedValue(undefined);
const markSectionSkipped = vi.fn((section: WorkflowSetupSection) => {
skippedSet.add(section.id);
skippedSectionIds.value = new Set(skippedSet);
});
const buildCompletedSetupPayload = vi.fn(() => ({
nodeCredentials: { Sub1: { credA: 'cred-a' } },
}));
const thread = {
currentThreadId: 'thread-1',
findToolCallByRequestId: vi.fn(() => ({
confirmation: { inputThreadId: 'input-thread-1' },
})),
};
const actions = useWorkflowSetupActions({
requestId: ref('req-1'),
sections,
steps,
activeStep,
currentStepIndex,
isReady,
goToStep,
inputs: {
credentialSelections,
isSectionComplete: (section) => completedSet.has(section.id),
isSectionSkipped: (section) => skippedSet.has(section.id),
markSectionSkipped,
buildCompletedSetupPayload,
},
applyMachine: { apply, defer },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
thread: thread as any,
});
await actions.skipCurrentStep();
// Only the incomplete sectionB was marked skipped — sectionA remains complete.
expect(markSectionSkipped).toHaveBeenCalledTimes(1);
expect(markSectionSkipped).toHaveBeenCalledWith(sectionB);
// Terminal — only one step. apply runs since at least one section is complete.
expect(apply).toHaveBeenCalledWith({ nodeCredentials: { Sub1: { credA: 'cred-a' } } });
expect(telemetryTrack).toHaveBeenCalledWith(
'Instance AI workflow setup step handled',
expect.objectContaining({
step_kind: 'group',
outcome: 'mixed',
setup_inputs: [
expect.objectContaining({
input_type: 'credential',
node_type: 'n8n-nodes-base.httpRequest',
credential_type: 'credA',
}),
expect.objectContaining({
input_type: 'credential',
node_type: 'n8n-nodes-base.httpRequest',
credential_type: 'credB',
}),
],
}),
);
});
it('reports group step as handled when every member is complete or skipped', () => {
const sectionA = makeWorkflowSetupSection({
id: 'Sub1:credA',
targetNodeName: 'Sub1',
credentialType: 'credA',
});
const sectionB = makeWorkflowSetupSection({
id: 'Sub2:credB',
targetNodeName: 'Sub2',
credentialType: 'credB',
});
const sections = computed(() => [sectionA, sectionB]);
const steps = computed<WorkflowSetupStep[]>(() => [
{
kind: 'group',
group: {
subnodeRootNode: { name: 'Agent', type: 'agent', typeVersion: 1, id: 'agent-1' },
subnodeSections: [sectionA, sectionB],
},
},
]);
const currentStepIndex = ref(0);
const isReady = ref(true);
const activeStep = computed<WorkflowSetupStep | undefined>(() => steps.value[0]);
const completedSet = new Set<string>([sectionA.id]);
const skippedSet = new Set<string>([sectionB.id]);
const actions = useWorkflowSetupActions({
requestId: ref('req-1'),
sections,
steps,
activeStep,
currentStepIndex,
isReady,
goToStep: vi.fn(),
inputs: {
credentialSelections: ref({}),
isSectionComplete: (s) => completedSet.has(s.id),
isSectionSkipped: (s) => skippedSet.has(s.id),
markSectionSkipped: vi.fn(),
buildCompletedSetupPayload: vi.fn(() => ({})),
},
applyMachine: { apply: vi.fn(), defer: vi.fn() },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
thread: { currentThreadId: 't', findToolCallByRequestId: vi.fn() } as any,
});
expect(actions.isStepHandled(steps.value[0])).toBe(true);
});
});
});

View File

@ -0,0 +1,157 @@
import { computed, ref, type ComputedRef, type Ref } from 'vue';
import type { ThreadRuntime } from '../../instanceAi.store';
import type {
WorkflowSetupApplyPayload,
WorkflowSetupSection,
WorkflowSetupStep,
} from '../workflowSetup.types';
import { getStepSections } from '../workflowSetup.helpers';
import type { CredentialSelectionsMap } from './useWorkflowSetupInputs';
import { useWorkflowSetupTelemetry } from './useWorkflowSetupTelemetry';
interface WorkflowSetupInputAccessors {
credentialSelections: Ref<CredentialSelectionsMap>;
isSectionComplete: (section: WorkflowSetupSection) => boolean;
isSectionSkipped: (section: WorkflowSetupSection) => boolean;
markSectionSkipped: (section: WorkflowSetupSection) => void;
buildCompletedSetupPayload: () => WorkflowSetupApplyPayload;
}
interface ApplyMachine {
apply: (payload: WorkflowSetupApplyPayload) => Promise<void>;
defer: () => Promise<void>;
}
export interface WorkflowSetupActions {
nextUnhandledIndex: ComputedRef<number>;
hasOtherUnhandledSteps: ComputedRef<boolean>;
canAdvanceToNextIncomplete: ComputedRef<boolean>;
isStepHandled: (step: WorkflowSetupStep) => boolean;
isActionPending: Ref<boolean>;
apply: () => Promise<void>;
skipCurrentStep: () => Promise<void>;
goToNextIncomplete: () => void;
}
export function useWorkflowSetupActions(deps: {
requestId: Ref<string>;
sections: ComputedRef<WorkflowSetupSection[]>;
steps: ComputedRef<WorkflowSetupStep[]>;
activeStep: ComputedRef<WorkflowSetupStep | undefined>;
currentStepIndex: Ref<number>;
isReady: Ref<boolean>;
goToStep: (index: number) => void;
inputs: WorkflowSetupInputAccessors;
applyMachine: ApplyMachine;
thread: ThreadRuntime;
}): WorkflowSetupActions {
const isActionPending = ref(false);
const workflowSetupTelemetry = useWorkflowSetupTelemetry({
requestId: deps.requestId,
sections: deps.sections,
steps: deps.steps,
activeStep: deps.activeStep,
currentStepIndex: deps.currentStepIndex,
isReady: deps.isReady,
inputs: {
isSectionComplete: deps.inputs.isSectionComplete,
isSectionSkipped: deps.inputs.isSectionSkipped,
},
thread: deps.thread,
});
function isStepHandled(step: WorkflowSetupStep): boolean {
const sections = getStepSections(step);
if (sections.length === 0) return true;
return sections.every(
(section) => deps.inputs.isSectionComplete(section) || deps.inputs.isSectionSkipped(section),
);
}
/**
* Globally find the first unhandled step. Prefer indices after the current
* step; fall back to indices before. Returns -1 if every step is handled.
*/
const nextUnhandledIndex = computed(() => {
const steps = deps.steps.value;
const current = deps.currentStepIndex.value;
for (let i = current + 1; i < steps.length; i++) {
if (!isStepHandled(steps[i])) return i;
}
for (let i = 0; i < Math.min(current, steps.length); i++) {
if (!isStepHandled(steps[i])) return i;
}
return -1;
});
const hasOtherUnhandledSteps = computed(() => nextUnhandledIndex.value >= 0);
const canAdvanceToNextIncomplete = computed(() => {
const step = deps.activeStep.value;
return step !== undefined && isStepHandled(step) && nextUnhandledIndex.value >= 0;
});
function goToNextIncomplete(): void {
if (canAdvanceToNextIncomplete.value) {
const step = deps.activeStep.value;
if (step) workflowSetupTelemetry.trackStepHandled(step);
deps.goToStep(nextUnhandledIndex.value);
}
}
async function apply(): Promise<void> {
const step = deps.activeStep.value;
if (step) workflowSetupTelemetry.trackStepHandled(step);
workflowSetupTelemetry.trackSetupInput();
await deps.applyMachine.apply(deps.inputs.buildCompletedSetupPayload());
}
async function skipCurrentStep(): Promise<void> {
if (isActionPending.value) return;
const step = deps.activeStep.value;
if (!step) return;
isActionPending.value = true;
try {
// Skipping the active step only marks its incomplete sections — already-
// complete sections still contribute to the apply payload.
const stepSections = getStepSections(step);
for (const section of stepSections) {
if (!deps.inputs.isSectionComplete(section)) {
deps.inputs.markSectionSkipped(section);
}
}
workflowSetupTelemetry.trackStepHandled(step);
const next = nextUnhandledIndex.value;
if (next >= 0) {
deps.goToStep(next);
return;
}
workflowSetupTelemetry.trackSetupInput();
const completedPayload = deps.inputs.buildCompletedSetupPayload();
const hasAnyCompleted =
Object.keys(completedPayload.nodeCredentials ?? {}).length > 0 ||
Object.keys(completedPayload.nodeParameters ?? {}).length > 0;
if (hasAnyCompleted) {
await deps.applyMachine.apply(completedPayload);
} else {
await deps.applyMachine.defer();
}
} finally {
isActionPending.value = false;
}
}
return {
nextUnhandledIndex,
hasOtherUnhandledSteps,
canAdvanceToNextIncomplete,
isStepHandled,
isActionPending,
apply,
skipCurrentStep,
goToNextIncomplete,
};
}

View File

@ -0,0 +1,243 @@
import { defineComponent, nextTick, ref, type Ref } from 'vue';
import { mount } from '@vue/test-utils';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import type { ThreadRuntime } from '../../instanceAi.store';
import { useWorkflowSetupApply } from './useWorkflowSetupApply';
const toast = vi.hoisted(() => ({
showError: vi.fn(),
}));
vi.mock('@/app/composables/useToast', () => ({
useToast: () => toast,
}));
interface Harness {
requestId: Ref<string>;
result: Ref<unknown>;
thread: {
confirmAction: ReturnType<typeof vi.fn>;
resolveConfirmation: ReturnType<typeof vi.fn>;
findToolCallByRequestId: ReturnType<typeof vi.fn>;
};
applyMachine: ReturnType<typeof useWorkflowSetupApply>;
}
function setupHarness(initialResult?: unknown): Harness {
const requestId = ref('req-1');
const result = ref<unknown>(initialResult);
const thread = {
confirmAction: vi.fn().mockResolvedValue(true),
resolveConfirmation: vi.fn(),
findToolCallByRequestId: vi.fn(() => ({ result: result.value })),
};
const applyMachine = useWorkflowSetupApply({
requestId,
thread: thread as unknown as ThreadRuntime,
});
return { requestId, result, thread, applyMachine };
}
function setupMountedHarness(initialResult?: unknown): Harness & { unmount: () => void } {
let harness!: Harness;
const Component = defineComponent({
setup() {
harness = setupHarness(initialResult);
return () => null;
},
});
const wrapper = mount(Component);
return { ...harness, unmount: () => wrapper.unmount() };
}
describe('useWorkflowSetupApply', () => {
beforeEach(() => {
toast.showError.mockClear();
});
afterEach(() => {
vi.useRealTimers();
});
it('ignores concurrent apply calls while already applying', async () => {
const h = setupHarness();
let resolveConfirm: (success: boolean) => void = () => {};
h.thread.confirmAction.mockReturnValueOnce(
new Promise<boolean>((resolve) => {
resolveConfirm = resolve;
}),
);
const first = h.applyMachine.apply({});
await h.applyMachine.apply({});
expect(h.applyMachine.terminalState.value).toBe('applying');
expect(h.thread.confirmAction).toHaveBeenCalledTimes(1);
resolveConfirm(false);
await first;
});
it('posts apply confirmation with node credentials', async () => {
const h = setupHarness({ success: true });
const nodeCredentials = { 'HTTP Request': { httpBasicAuth: 'cred-1' } };
await h.applyMachine.apply({ nodeCredentials });
expect(h.thread.confirmAction).toHaveBeenCalledWith('req-1', {
kind: 'setupWorkflowApply',
nodeCredentials,
});
});
it('posts apply confirmation with node parameters', async () => {
const h = setupHarness({ success: true });
const nodeParameters = { 'HTTP Request': { url: 'https://example.com/api' } };
await h.applyMachine.apply({ nodeParameters });
expect(h.thread.confirmAction).toHaveBeenCalledWith('req-1', {
kind: 'setupWorkflowApply',
nodeParameters,
});
});
it('resets state when posting apply confirmation fails', async () => {
const h = setupHarness();
h.thread.confirmAction.mockResolvedValueOnce(false);
await h.applyMachine.apply({});
expect(h.applyMachine.terminalState.value).toBeNull();
expect(toast.showError).not.toHaveBeenCalled();
expect(h.thread.resolveConfirmation).not.toHaveBeenCalled();
});
it('sets applied terminal state and resolves confirmation on success', async () => {
const h = setupHarness({ success: true });
await h.applyMachine.apply({});
expect(h.applyMachine.terminalState.value).toBe('applied');
expect(h.thread.resolveConfirmation).toHaveBeenCalledWith('req-1', 'approved');
});
it('sets partial terminal state when the result is partial', async () => {
const h = setupHarness({ success: true, partial: true });
await h.applyMachine.apply({});
expect(h.applyMachine.terminalState.value).toBe('partial');
expect(h.thread.resolveConfirmation).toHaveBeenCalledWith('req-1', 'approved');
});
it('shows an error and resets state when apply result fails', async () => {
const h = setupHarness({ success: false, error: 'Could not apply credentials' });
await h.applyMachine.apply({});
expect(h.applyMachine.terminalState.value).toBeNull();
expect(toast.showError).toHaveBeenCalledWith(expect.any(Error), 'Setup failed');
expect(toast.showError.mock.calls[0][0].message).toBe('Could not apply credentials');
expect(h.thread.resolveConfirmation).not.toHaveBeenCalled();
});
it('uses the latest request id when applying', async () => {
const h = setupHarness({ success: true });
h.requestId.value = 'req-2';
await h.applyMachine.apply({});
expect(h.thread.confirmAction).toHaveBeenCalledWith(
'req-2',
expect.objectContaining({ kind: 'setupWorkflowApply' }),
);
expect(h.thread.resolveConfirmation).toHaveBeenCalledWith('req-2', 'approved');
});
it('waits for an async tool result after posting apply confirmation', async () => {
const h = setupHarness();
const pendingApply = h.applyMachine.apply({});
await nextTick();
expect(h.applyMachine.terminalState.value).toBe('applying');
h.result.value = { success: true };
await pendingApply;
expect(h.applyMachine.terminalState.value).toBe('applied');
});
it('times out waiting for an apply result', async () => {
vi.useFakeTimers();
const h = setupHarness();
const pendingApply = h.applyMachine.apply({});
await vi.advanceTimersByTimeAsync(60_000);
await pendingApply;
expect(h.applyMachine.terminalState.value).toBeNull();
expect(toast.showError).toHaveBeenCalledWith(expect.any(Error), 'Setup failed');
expect(toast.showError.mock.calls[0][0].message).toBe('Apply timed out — please try again.');
});
it('settles a pending apply when unmounted', async () => {
const h = setupMountedHarness();
const pendingApply = h.applyMachine.apply({});
await nextTick();
expect(h.applyMachine.terminalState.value).toBe('applying');
h.unmount();
await pendingApply;
expect(h.applyMachine.terminalState.value).toBeNull();
expect(toast.showError).not.toHaveBeenCalled();
expect(h.thread.resolveConfirmation).not.toHaveBeenCalled();
});
it('ignores defer calls while already applying', async () => {
const h = setupHarness();
let resolveConfirm: (success: boolean) => void = () => {};
h.thread.confirmAction.mockReturnValueOnce(
new Promise<boolean>((resolve) => {
resolveConfirm = resolve;
}),
);
const first = h.applyMachine.defer();
await h.applyMachine.defer();
expect(h.applyMachine.terminalState.value).toBe('applying');
expect(h.thread.confirmAction).toHaveBeenCalledTimes(1);
resolveConfirm(false);
await first;
});
it('sets deferred terminal state and resolves confirmation on defer success', async () => {
const h = setupHarness();
await h.applyMachine.defer();
expect(h.thread.confirmAction).toHaveBeenCalledWith('req-1', {
kind: 'approval',
approved: false,
});
expect(h.applyMachine.terminalState.value).toBe('deferred');
expect(h.thread.resolveConfirmation).toHaveBeenCalledWith('req-1', 'deferred');
});
it('resets state when defer confirmation fails', async () => {
const h = setupHarness();
h.thread.confirmAction.mockResolvedValueOnce(false);
await h.applyMachine.defer();
expect(h.applyMachine.terminalState.value).toBeNull();
expect(h.thread.resolveConfirmation).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,153 @@
import { ref, watch, onUnmounted, type Ref } from 'vue';
import type { InstanceAiToolCallState } from '@n8n/api-types';
import { useToast } from '@/app/composables/useToast';
import type { ThreadRuntime } from '../../instanceAi.store';
import type { TerminalState, WorkflowSetupApplyPayload } from '../workflowSetup.types';
const APPLY_TIMEOUT_MS = 60_000;
const WAIT_CANCELLED = Symbol('wait-cancelled');
type WaitForToolResult = Record<string, unknown> | null | typeof WAIT_CANCELLED;
export function useWorkflowSetupApply(deps: {
requestId: Ref<string>;
thread: ThreadRuntime;
}): {
terminalState: Ref<TerminalState | null>;
apply: (payload: WorkflowSetupApplyPayload) => Promise<void>;
defer: () => Promise<void>;
} {
const toast = useToast();
const terminalState = ref<TerminalState | null>(null);
let cancelWait: (() => void) | null = null;
onUnmounted(() => {
cancelWait?.();
});
function isToolResult(val: unknown): val is Record<string, unknown> {
return typeof val === 'object' && val !== null && !Array.isArray(val);
}
function waitForToolResult(requestId: string): {
promise: Promise<WaitForToolResult>;
cancel: () => void;
} {
let stopWatch: (() => void) | null = null;
let timeoutId: ReturnType<typeof setTimeout> | null = null;
let resolveWait: ((result: WaitForToolResult) => void) | null = null;
function cleanup() {
if (stopWatch) {
stopWatch();
stopWatch = null;
}
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
}
function finish(result: WaitForToolResult) {
cleanup();
resolveWait?.(result);
resolveWait = null;
}
const promise = new Promise<WaitForToolResult>((resolve) => {
resolveWait = resolve;
const existing = deps.thread.findToolCallByRequestId(requestId);
if (existing?.result !== undefined) {
finish(isToolResult(existing.result) ? existing.result : null);
return;
}
stopWatch = watch(
() => {
const tc: InstanceAiToolCallState | undefined =
deps.thread.findToolCallByRequestId(requestId);
return tc?.result;
},
(result) => {
if (result !== undefined) {
finish(isToolResult(result) ? result : null);
}
},
);
timeoutId = setTimeout(() => {
finish(null);
}, APPLY_TIMEOUT_MS);
});
return { promise, cancel: () => finish(WAIT_CANCELLED) };
}
async function apply(payload: WorkflowSetupApplyPayload): Promise<void> {
if (terminalState.value === 'applying') return;
terminalState.value = 'applying';
const postSuccess = await deps.thread.confirmAction(deps.requestId.value, {
kind: 'setupWorkflowApply',
...payload,
});
// confirmAction already toasts on POST failure; just reset so the wizard
// re-renders with the user's selections and they can try again.
if (!postSuccess) {
terminalState.value = null;
return;
}
cancelWait?.();
const { promise, cancel } = waitForToolResult(deps.requestId.value);
cancelWait = cancel;
const result = await promise;
cancelWait = null;
if (result === WAIT_CANCELLED) {
terminalState.value = null;
return;
}
if (result === null) {
toast.showError(new Error('Apply timed out — please try again.'), 'Setup failed');
terminalState.value = null;
return;
}
if (result.success === true) {
terminalState.value = result.partial === true ? 'partial' : 'applied';
deps.thread.resolveConfirmation(deps.requestId.value, 'approved');
return;
}
const message = typeof result.error === 'string' ? result.error : 'Apply failed.';
toast.showError(new Error(message), 'Setup failed');
terminalState.value = null;
}
async function defer(): Promise<void> {
if (terminalState.value === 'applying') return;
terminalState.value = 'applying';
const success = await deps.thread.confirmAction(deps.requestId.value, {
kind: 'approval',
approved: false,
});
if (success) {
terminalState.value = 'deferred';
deps.thread.resolveConfirmation(deps.requestId.value, 'deferred');
return;
}
// confirmAction already toasted. Reset so the wizard re-renders.
terminalState.value = null;
}
return {
terminalState,
apply,
defer,
};
}

View File

@ -0,0 +1,73 @@
import { ref } from 'vue';
import { createTestingPinia } from '@pinia/testing';
import { mockedStore } from '@/__tests__/utils';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useWorkflowSetupBootstrap } from './useWorkflowSetupBootstrap';
describe('useWorkflowSetupBootstrap', () => {
let credentialsStore: ReturnType<typeof mockedStore<typeof useCredentialsStore>>;
let nodeTypesStore: ReturnType<typeof mockedStore<typeof useNodeTypesStore>>;
beforeEach(() => {
createTestingPinia();
credentialsStore = mockedStore(useCredentialsStore);
nodeTypesStore = mockedStore(useNodeTypesStore);
credentialsStore.fetchAllCredentialsForWorkflow = vi.fn().mockResolvedValue([]);
credentialsStore.fetchCredentialTypes = vi.fn().mockResolvedValue(undefined);
nodeTypesStore.loadNodeTypesIfNotLoaded = vi.fn().mockResolvedValue(undefined);
});
it('throws when workflowId is missing', async () => {
const { bootstrap } = useWorkflowSetupBootstrap(ref(undefined));
await expect(bootstrap()).rejects.toThrow('useWorkflowSetupBootstrap: workflowId is required');
expect(credentialsStore.fetchAllCredentialsForWorkflow).not.toHaveBeenCalled();
});
it('fetches workflow-scoped credentials and flips isReady after success', async () => {
const { isReady, bootstrap } = useWorkflowSetupBootstrap(ref('workflow-1'));
expect(isReady.value).toBe(false);
await bootstrap();
expect(credentialsStore.fetchAllCredentialsForWorkflow).toHaveBeenCalledWith({
workflowId: 'workflow-1',
});
expect(credentialsStore.fetchCredentialTypes).toHaveBeenCalled();
expect(nodeTypesStore.loadNodeTypesIfNotLoaded).toHaveBeenCalled();
expect(isReady.value).toBe(true);
});
it('flips isReady to true even when one of the parallel calls fails', async () => {
credentialsStore.fetchAllCredentialsForWorkflow = vi
.fn()
.mockRejectedValue(new Error('network'));
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const { isReady, bootstrap } = useWorkflowSetupBootstrap(ref('workflow-1'));
await bootstrap();
expect(isReady.value).toBe(true);
expect(warnSpy).toHaveBeenCalled();
warnSpy.mockRestore();
});
it('uses the latest projectId from the ref at the time bootstrap is called', async () => {
const workflowId = ref<string | undefined>('workflow-1');
const { bootstrap } = useWorkflowSetupBootstrap(workflowId);
workflowId.value = 'workflow-2';
await bootstrap();
expect(credentialsStore.fetchAllCredentialsForWorkflow).toHaveBeenCalledWith({
workflowId: 'workflow-2',
});
});
});

View File

@ -0,0 +1,34 @@
import { ref, type Ref } from 'vue';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
export function useWorkflowSetupBootstrap(workflowId: Ref<string | undefined>): {
isReady: Ref<boolean>;
bootstrap: () => Promise<void>;
} {
const credentialsStore = useCredentialsStore();
const nodeTypesStore = useNodeTypesStore();
const isReady = ref(false);
async function bootstrap() {
isReady.value = false;
const wid = workflowId.value;
if (!wid) {
throw new Error('useWorkflowSetupBootstrap: workflowId is required');
}
try {
await Promise.all([
credentialsStore.fetchAllCredentialsForWorkflow({ workflowId: wid }),
credentialsStore.fetchCredentialTypes(false),
nodeTypesStore.loadNodeTypesIfNotLoaded(),
]);
} catch (error) {
console.warn('[InstanceAI] Workflow setup bootstrap partial failure', error);
} finally {
isReady.value = true;
}
}
return { isReady, bootstrap };
}

View File

@ -0,0 +1,207 @@
import {
computed,
inject,
onMounted,
provide,
ref,
watch,
type ComputedRef,
type InjectionKey,
type Ref,
} from 'vue';
import type { InstanceAiCredentialFlow, InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import { useCredentialTestInBackground } from '@/features/credentials/composables/useCredentialTestInBackground';
import type { INodeUi } from '@/Interface';
import { useThread } from '../../instanceAi.store';
import type {
TerminalState,
WorkflowSetupSection,
WorkflowSetupStep,
} from '../workflowSetup.types';
import { getStepSections } from '../workflowSetup.helpers';
import { useWorkflowSetupActions } from './useWorkflowSetupActions';
import { useWorkflowSetupApply } from './useWorkflowSetupApply';
import { useWorkflowSetupBootstrap } from './useWorkflowSetupBootstrap';
import { useWorkflowSetupSections } from './useWorkflowSetupSections';
import { useWorkflowSetupSteps } from './useWorkflowSetupSteps';
import { useWorkflowSetupInputs, type CredentialSelectionsMap } from './useWorkflowSetupInputs';
export interface WorkflowSetupContext {
sections: ComputedRef<WorkflowSetupSection[]>;
steps: ComputedRef<WorkflowSetupStep[]>;
currentStepIndex: Ref<number>;
activeStep: ComputedRef<WorkflowSetupStep | undefined>;
hasOtherUnhandledSteps: ComputedRef<boolean>;
canAdvanceToNextIncomplete: ComputedRef<boolean>;
credentialSelections: Ref<CredentialSelectionsMap>;
terminalState: Ref<TerminalState | null>;
isReady: Ref<boolean>;
projectId: ComputedRef<string | undefined>;
credentialFlow: ComputedRef<InstanceAiCredentialFlow | undefined>;
isActionPending: Ref<boolean>;
setCredential: (section: WorkflowSetupSection, credId: string | null) => void;
setParameterValue: (section: WorkflowSetupSection, parameterName: string, value: unknown) => void;
getDisplayNode: (section: WorkflowSetupSection) => INodeUi;
isSectionComplete: (section: WorkflowSetupSection) => boolean;
isCredentialTestFailed: (section: WorkflowSetupSection) => boolean;
isSectionSkipped: (section: WorkflowSetupSection) => boolean;
isStepComplete: (step: WorkflowSetupStep) => boolean;
isStepSkipped: (step: WorkflowSetupStep) => boolean;
isStepHandled: (step: WorkflowSetupStep) => boolean;
goToStep: (index: number) => void;
goToNext: () => void;
goToPrev: () => void;
goToNextIncomplete: () => void;
apply: () => Promise<void>;
skipCurrentStep: () => Promise<void>;
}
const WorkflowSetupContextKey: InjectionKey<WorkflowSetupContext> = Symbol('WorkflowSetupContext');
interface ProvideOptions {
requestId: Ref<string>;
setupRequests: Ref<InstanceAiWorkflowSetupNode[]>;
projectId: Ref<string | undefined>;
workflowId: Ref<string | undefined>;
credentialFlow: Ref<InstanceAiCredentialFlow | undefined>;
}
export function provideWorkflowSetupContext(opts: ProvideOptions): WorkflowSetupContext {
const thread = useThread();
const { hydrateCredentialTestResults } = useCredentialTestInBackground();
hydrateCredentialTestResults(
opts.setupRequests.value.flatMap((req) => {
const credType = req.credentialType;
const credId = credType ? req.node.credentials?.[credType]?.id : undefined;
const result = req.credentialTestResult;
return credId && result ? [{ id: credId, success: result.success }] : [];
}),
);
const { sections } = useWorkflowSetupSections(opts.setupRequests);
const { steps } = useWorkflowSetupSteps({ sections, setupRequests: opts.setupRequests });
const bootstrap = useWorkflowSetupBootstrap(opts.workflowId);
const applyMachine = useWorkflowSetupApply({
requestId: opts.requestId,
thread,
});
const currentStepIndex = ref(0);
const activeStep = computed(() => steps.value[currentStepIndex.value]);
const inputsState = useWorkflowSetupInputs({ sections });
const projectId = computed(() => opts.projectId.value);
const credentialFlow = computed(() => opts.credentialFlow.value);
function goToStep(index: number) {
if (index >= 0 && index < steps.value.length) {
currentStepIndex.value = index;
}
}
function goToNext() {
if (currentStepIndex.value < steps.value.length - 1) {
currentStepIndex.value++;
}
}
function goToPrev() {
if (currentStepIndex.value > 0) {
currentStepIndex.value--;
}
}
const actions = useWorkflowSetupActions({
requestId: opts.requestId,
sections,
steps,
activeStep,
currentStepIndex,
isReady: bootstrap.isReady,
goToStep,
inputs: {
credentialSelections: inputsState.credentialSelections,
isSectionComplete: inputsState.isSectionComplete,
isSectionSkipped: inputsState.isSectionSkipped,
markSectionSkipped: inputsState.markSectionSkipped,
buildCompletedSetupPayload: inputsState.buildCompletedSetupPayload,
},
applyMachine: {
apply: applyMachine.apply,
defer: applyMachine.defer,
},
thread,
});
function isStepComplete(step: WorkflowSetupStep): boolean {
const stepSections = getStepSections(step);
if (stepSections.length === 0) return false;
return stepSections.every(inputsState.isSectionComplete);
}
function isStepSkipped(step: WorkflowSetupStep): boolean {
const stepSections = getStepSections(step);
if (stepSections.length === 0) return false;
return stepSections.every(inputsState.isSectionSkipped);
}
// Clamp currentStepIndex when the step list shrinks beneath it.
watch(
() => steps.value.length,
(len) => {
if (currentStepIndex.value >= len) {
currentStepIndex.value = Math.max(0, len - 1);
}
},
);
onMounted(async () => {
await bootstrap.bootstrap();
});
const context: WorkflowSetupContext = {
sections,
steps,
currentStepIndex,
activeStep,
hasOtherUnhandledSteps: actions.hasOtherUnhandledSteps,
canAdvanceToNextIncomplete: actions.canAdvanceToNextIncomplete,
credentialSelections: inputsState.credentialSelections,
terminalState: applyMachine.terminalState,
isReady: bootstrap.isReady,
projectId,
credentialFlow,
isActionPending: actions.isActionPending,
setCredential: inputsState.setCredential,
setParameterValue: inputsState.setParameterValue,
getDisplayNode: inputsState.getDisplayNode,
isSectionComplete: inputsState.isSectionComplete,
isCredentialTestFailed: inputsState.isCredentialTestFailed,
isSectionSkipped: inputsState.isSectionSkipped,
isStepComplete,
isStepSkipped,
isStepHandled: actions.isStepHandled,
goToStep,
goToNext,
goToPrev,
goToNextIncomplete: actions.goToNextIncomplete,
apply: actions.apply,
skipCurrentStep: actions.skipCurrentStep,
};
provide(WorkflowSetupContextKey, context);
return context;
}
export function useWorkflowSetupContext(): WorkflowSetupContext {
const ctx = inject(WorkflowSetupContextKey);
if (!ctx) {
throw new Error(
'useWorkflowSetupContext: called outside of a provideWorkflowSetupContext scope',
);
}
return ctx;
}

View File

@ -0,0 +1,67 @@
import { computed, reactive, watch, type Ref } from 'vue';
import type { WorkflowSetupGroup, WorkflowSetupSection } from '../workflowSetup.types';
import { getGroupSections } from '../workflowSetup.helpers';
import { useWorkflowSetupContext } from './useWorkflowSetupContext';
/**
* Manages per-section expansion state inside a `WorkflowSetupGroup`.
*
* Only sub-node sections are collapsible: the root section renders inline
* as the group card's primary body and is always visible.
*
* Sections that contain parameters stay open so the user isn't interrupted
* mid-typing only credential-only sections auto-collapse on completion.
*/
export function useWorkflowSetupGroupSections(group: Ref<WorkflowSetupGroup>) {
const ctx = useWorkflowSetupContext();
const allSections = computed<WorkflowSetupSection[]>(() => getGroupSections(group.value));
const expandedSections = reactive<Record<string, boolean>>({});
function initExpandState() {
for (const section of group.value.subnodeSections) {
if (!(section.id in expandedSections)) {
expandedSections[section.id] = false;
}
}
const firstIncomplete = group.value.subnodeSections.find(
(section) => !ctx.isSectionComplete(section),
);
if (firstIncomplete) {
expandedSections[firstIncomplete.id] = true;
}
}
initExpandState();
function toggleSection(sectionId: string) {
expandedSections[sectionId] = !expandedSections[sectionId];
}
watch(
() => allSections.value.map((section) => [section.id, ctx.isSectionComplete(section)] as const),
(states, prevStates) => {
const prev = new Map(prevStates ?? []);
for (const [sectionId, isComplete] of states) {
if (!isComplete || prev.get(sectionId)) continue;
const section = allSections.value.find((s) => s.id === sectionId);
if (!section || section.parameterNames.length > 0) continue;
if (sectionId in expandedSections) {
expandedSections[sectionId] = false;
}
const nextIncomplete = group.value.subnodeSections.find(
(s) => !ctx.isSectionComplete(s) && s.id !== sectionId,
);
if (nextIncomplete) {
expandedSections[nextIncomplete.id] = true;
}
}
},
{ immediate: true },
);
return {
expandedSections,
toggleSection,
};
}

View File

@ -0,0 +1,488 @@
import { computed, nextTick, ref, type ComputedRef, type Ref } from 'vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeWorkflowSetupSection } from '../__tests__/factories';
import type { WorkflowSetupSection } from '../workflowSetup.types';
import { useWorkflowSetupInputs } from './useWorkflowSetupInputs';
interface TestCredential {
id: string;
type: string;
name: string;
}
const credentialsStore = vi.hoisted(() => ({
credentials: new Map<string, TestCredential>(),
credentialTestResults: new Map<string, string>(),
getCredentialById: vi.fn((id: string) => credentialsStore.credentials.get(id)),
isCredentialTestedOk: vi.fn(
(id: string) => credentialsStore.credentialTestResults.get(id) === 'success',
),
}));
const credentialTest = vi.hoisted(() => ({
testableTypes: new Set<string>(),
isCredentialTypeTestable: vi.fn((type: string) => credentialTest.testableTypes.has(type)),
testCredentialInBackground: vi.fn(),
}));
const nodeTypesStore = vi.hoisted(() => ({
getNodeType: vi.fn((): unknown => null),
}));
vi.mock('@/features/credentials/credentials.store', () => ({
useCredentialsStore: () => credentialsStore,
}));
vi.mock('@/features/credentials/composables/useCredentialTestInBackground', () => ({
useCredentialTestInBackground: () => ({
isCredentialTypeTestable: credentialTest.isCredentialTypeTestable,
testCredentialInBackground: credentialTest.testCredentialInBackground,
}),
}));
vi.mock('@/app/stores/nodeTypes.store', () => ({
useNodeTypesStore: () => nodeTypesStore,
}));
interface Harness {
sectionA: WorkflowSetupSection;
sectionB: WorkflowSetupSection;
sectionsRef: Ref<WorkflowSetupSection[]>;
sections: ComputedRef<WorkflowSetupSection[]>;
inputs: ReturnType<typeof useWorkflowSetupInputs>;
}
function addCredential(credential: TestCredential): void {
credentialsStore.credentials.set(credential.id, credential);
}
function setupHarness(sections?: WorkflowSetupSection[]): Harness {
const sectionA = makeWorkflowSetupSection({
id: 'HTTP Request:httpBasicAuth',
targetNodeName: 'HTTP Request',
credentialType: 'httpBasicAuth',
});
const sectionB = makeWorkflowSetupSection({
id: 'Slack:slackApi',
targetNodeName: 'Slack',
credentialType: 'slackApi',
});
const sectionsRef = ref(sections ?? [sectionA, sectionB]);
const sectionsComputed = computed(() => sectionsRef.value);
const inputs = useWorkflowSetupInputs({
sections: sectionsComputed,
});
return {
sectionA,
sectionB,
sectionsRef,
sections: sectionsComputed,
inputs,
};
}
describe('useWorkflowSetupInputs', () => {
beforeEach(() => {
credentialsStore.credentials.clear();
credentialsStore.credentialTestResults.clear();
credentialsStore.getCredentialById.mockClear();
credentialsStore.isCredentialTestedOk.mockClear();
credentialTest.testableTypes.clear();
credentialTest.isCredentialTypeTestable.mockClear();
credentialTest.testCredentialInBackground.mockClear();
nodeTypesStore.getNodeType.mockReset();
nodeTypesStore.getNodeType.mockReturnValue(null);
});
it('sets a selection, tests it in the background, and clears a previous skip', () => {
addCredential({ id: 'cred-1', type: 'httpBasicAuth', name: 'HTTP credential' });
const h = setupHarness();
h.inputs.markSectionSkipped(h.sectionA);
h.inputs.setCredential(h.sectionA, 'cred-1');
expect(h.inputs.credentialSelections.value).toEqual({
'HTTP Request': { httpBasicAuth: 'cred-1' },
});
expect(credentialTest.testCredentialInBackground).toHaveBeenCalledWith(
'cred-1',
'HTTP credential',
'httpBasicAuth',
);
expect(h.inputs.isSectionSkipped(h.sectionA)).toBe(false);
});
it('removes a selected credential when set to null', () => {
const h = setupHarness();
h.inputs.setCredential(h.sectionA, 'cred-1');
h.inputs.setCredential(h.sectionA, null);
expect(h.inputs.credentialSelections.value).toEqual({ 'HTTP Request': {} });
expect(h.inputs.buildCompletedSetupPayload()).toEqual({});
});
it('reports section completion based on testability and credential test result', () => {
const h = setupHarness();
expect(h.inputs.isSectionComplete(h.sectionA)).toBe(false);
h.inputs.setCredential(h.sectionA, 'cred-1');
expect(h.inputs.isSectionComplete(h.sectionA)).toBe(true);
credentialTest.testableTypes.add('httpBasicAuth');
expect(h.inputs.isSectionComplete(h.sectionA)).toBe(false);
credentialsStore.credentialTestResults.set('cred-1', 'success');
expect(h.inputs.isSectionComplete(h.sectionA)).toBe(true);
});
it('reports credential test failures only for selected testable credentials', () => {
const h = setupHarness();
h.inputs.setCredential(h.sectionA, 'cred-1');
credentialsStore.credentialTestResults.set('cred-1', 'error');
expect(h.inputs.isCredentialTestFailed(h.sectionA)).toBe(false);
credentialTest.testableTypes.add('httpBasicAuth');
expect(h.inputs.isCredentialTestFailed(h.sectionA)).toBe(true);
expect(h.inputs.isCredentialTestFailed(h.sectionB)).toBe(false);
});
it('marks skipped sections idempotently and clears them when input is provided', () => {
const h = setupHarness();
h.inputs.markSectionSkipped(h.sectionA);
h.inputs.markSectionSkipped(h.sectionA);
expect(h.inputs.isSectionSkipped(h.sectionA)).toBe(true);
expect(h.inputs.skippedSectionIds.value).toEqual(new Set([h.sectionA.id]));
h.inputs.setCredential(h.sectionA, 'cred-1');
expect(h.inputs.isSectionSkipped(h.sectionA)).toBe(false);
expect(h.inputs.skippedSectionIds.value).toEqual(new Set());
});
it('builds only completed selections into the payload', () => {
const h = setupHarness();
credentialTest.testableTypes.add('httpBasicAuth');
h.inputs.setCredential(h.sectionA, 'cred-1');
h.inputs.setCredential(h.sectionB, 'cred-2');
expect(h.inputs.buildCompletedSetupPayload()).toEqual({
nodeCredentials: {
Slack: { slackApi: 'cred-2' },
},
});
credentialsStore.credentialTestResults.set('cred-1', 'success');
expect(h.inputs.buildCompletedSetupPayload()).toEqual({
nodeCredentials: {
'HTTP Request': { httpBasicAuth: 'cred-1' },
Slack: { slackApi: 'cred-2' },
},
});
});
it('seeds selections from current credentials and tests seeded credentials', async () => {
addCredential({ id: 'current-cred', type: 'httpBasicAuth', name: 'Current credential' });
const section = makeWorkflowSetupSection({
targetNodeName: 'HTTP Request',
credentialType: 'httpBasicAuth',
currentCredentialId: 'current-cred',
});
const h = setupHarness([section]);
await nextTick();
expect(h.inputs.credentialSelections.value).toEqual({
'HTTP Request': { httpBasicAuth: 'current-cred' },
});
expect(credentialTest.testCredentialInBackground).toHaveBeenCalledWith(
'current-cred',
'Current credential',
'httpBasicAuth',
);
});
it('does not overwrite an existing user selection when sections refresh', async () => {
const h = setupHarness();
h.inputs.setCredential(h.sectionA, 'user-cred');
h.sectionsRef.value = [
{
...h.sectionA,
currentCredentialId: 'refreshed-current-cred',
},
];
await nextTick();
expect(h.inputs.credentialSelections.value).toEqual({
'HTTP Request': { httpBasicAuth: 'user-cred' },
});
});
it('does not re-seed a credential after the user explicitly clears it', async () => {
addCredential({ id: 'current-cred', type: 'httpBasicAuth', name: 'Current credential' });
const section = makeWorkflowSetupSection({
targetNodeName: 'HTTP Request',
credentialType: 'httpBasicAuth',
currentCredentialId: 'current-cred',
});
const h = setupHarness([section]);
await nextTick();
expect(h.inputs.credentialSelections.value).toEqual({
'HTTP Request': { httpBasicAuth: 'current-cred' },
});
h.inputs.setCredential(section, null);
expect(h.inputs.credentialSelections.value).toEqual({ 'HTTP Request': {} });
// Sections re-emit with the same content (e.g. due to upstream reactive recomputation).
h.sectionsRef.value = [...h.sectionsRef.value];
await nextTick();
expect(h.inputs.credentialSelections.value).toEqual({ 'HTTP Request': {} });
});
it('seeds only newly added sections without overwriting existing user selections', async () => {
addCredential({ id: 'b-cred', type: 'slackApi', name: 'B credential' });
const h = setupHarness();
h.inputs.setCredential(h.sectionA, 'user-cred-a');
const sectionC = makeWorkflowSetupSection({
id: 'GitHub:githubApi',
targetNodeName: 'GitHub',
credentialType: 'githubApi',
currentCredentialId: 'github-cred',
});
addCredential({ id: 'github-cred', type: 'githubApi', name: 'GitHub credential' });
h.sectionsRef.value = [...h.sectionsRef.value, sectionC];
await nextTick();
expect(h.inputs.credentialSelections.value).toEqual({
'HTTP Request': { httpBasicAuth: 'user-cred-a' },
GitHub: { githubApi: 'github-cred' },
});
});
it('re-seeds a section that is removed and later added back', async () => {
addCredential({ id: 'current-cred', type: 'httpBasicAuth', name: 'Current credential' });
const section = makeWorkflowSetupSection({
targetNodeName: 'HTTP Request',
credentialType: 'httpBasicAuth',
currentCredentialId: 'current-cred',
});
const h = setupHarness([section]);
await nextTick();
h.inputs.setCredential(section, null);
expect(h.inputs.credentialSelections.value).toEqual({ 'HTTP Request': {} });
h.sectionsRef.value = [];
await nextTick();
h.sectionsRef.value = [section];
await nextTick();
expect(h.inputs.credentialSelections.value).toEqual({
'HTTP Request': { httpBasicAuth: 'current-cred' },
});
});
it('prunes skipped section ids that no longer correspond to a section', async () => {
const h = setupHarness();
h.inputs.markSectionSkipped(h.sectionA);
h.inputs.markSectionSkipped(h.sectionB);
h.sectionsRef.value = [h.sectionB];
await nextTick();
expect(h.inputs.isSectionSkipped(h.sectionA)).toBe(false);
expect(h.inputs.isSectionSkipped(h.sectionB)).toBe(true);
});
it('clears a skip when the skipped section later becomes complete', async () => {
const h = setupHarness();
credentialTest.testableTypes.add('httpBasicAuth');
h.inputs.setCredential(h.sectionA, 'cred-1');
h.inputs.markSectionSkipped(h.sectionA);
credentialsStore.credentialTestResults.set('cred-1', 'success');
h.sectionsRef.value = [...h.sectionsRef.value];
await nextTick();
expect(h.inputs.isSectionSkipped(h.sectionA)).toBe(false);
});
it('tracks parameter values and builds nodeParameters after issues clear', () => {
nodeTypesStore.getNodeType.mockReturnValue({
name: 'n8n-nodes-base.httpRequest',
properties: [
{ displayName: 'URL', name: 'url', type: 'string', default: '', required: true },
],
});
const parameterSection = makeWorkflowSetupSection({
id: 'HTTP Request:parameters',
credentialType: undefined,
parameterNames: ['url'],
node: { parameters: { url: '' } },
});
const h = setupHarness([parameterSection]);
expect(h.inputs.isSectionComplete(parameterSection)).toBe(false);
h.inputs.setParameterValue(parameterSection, 'url', 'https://example.com/api');
expect(h.inputs.isSectionComplete(parameterSection)).toBe(true);
expect(h.inputs.buildCompletedSetupPayload()).toEqual({
nodeParameters: {
'HTTP Request': { url: 'https://example.com/api' },
},
});
});
it('updates nested parameter values without flattening the path', () => {
const parameterSection = makeWorkflowSetupSection({
id: 'HTTP Request:parameters',
credentialType: undefined,
parameterNames: ['options'],
node: { parameters: { options: { path: 'old', keep: true } } },
});
const h = setupHarness([parameterSection]);
h.inputs.setParameterValue(parameterSection, 'options.path', 'new');
expect(h.inputs.getDisplayNode(parameterSection).parameters).toEqual({
options: { path: 'new', keep: true },
});
expect(parameterSection.node.parameters).toEqual({ options: { path: 'old', keep: true } });
expect(h.inputs.buildCompletedSetupPayload()).toEqual({
nodeParameters: {
'HTTP Request': { options: { path: 'new', keep: true } },
},
});
});
it('mirrors a primary credential selection across grouped target nodes', () => {
const groupedSection = makeWorkflowSetupSection({
id: 'Primary:httpBasicAuth',
targetNodeName: 'Primary',
credentialType: 'httpBasicAuth',
credentialTargetNodes: [
{ id: 'primary', name: 'Primary', type: 'n8n-nodes-base.httpRequest' },
{ id: 'follower', name: 'Follower', type: 'n8n-nodes-base.httpRequest' },
],
});
const h = setupHarness([groupedSection]);
h.inputs.setCredential(groupedSection, 'cred-1');
expect(h.inputs.credentialSelections.value).toEqual({
Primary: { httpBasicAuth: 'cred-1' },
Follower: { httpBasicAuth: 'cred-1' },
});
expect(h.inputs.buildCompletedSetupPayload()).toEqual({
nodeCredentials: {
Primary: { httpBasicAuth: 'cred-1' },
Follower: { httpBasicAuth: 'cred-1' },
},
});
});
it('clears mirrored credential selections across grouped target nodes', () => {
const groupedSection = makeWorkflowSetupSection({
id: 'Primary:httpBasicAuth',
targetNodeName: 'Primary',
credentialType: 'httpBasicAuth',
credentialTargetNodes: [
{ id: 'primary', name: 'Primary', type: 'n8n-nodes-base.httpRequest' },
{ id: 'follower', name: 'Follower', type: 'n8n-nodes-base.httpRequest' },
],
});
const h = setupHarness([groupedSection]);
h.inputs.setCredential(groupedSection, 'cred-1');
h.inputs.setCredential(groupedSection, null);
expect(h.inputs.credentialSelections.value).toEqual({
Primary: {},
Follower: {},
});
expect(h.inputs.buildCompletedSetupPayload()).toEqual({});
});
it('seeds current credentials across grouped target nodes', async () => {
addCredential({ id: 'current-cred', type: 'httpBasicAuth', name: 'Current credential' });
const groupedSection = makeWorkflowSetupSection({
id: 'Primary:httpBasicAuth',
targetNodeName: 'Primary',
credentialType: 'httpBasicAuth',
currentCredentialId: 'current-cred',
credentialTargetNodes: [
{ id: 'primary', name: 'Primary', type: 'n8n-nodes-base.httpRequest' },
{ id: 'follower', name: 'Follower', type: 'n8n-nodes-base.httpRequest' },
],
});
const h = setupHarness([groupedSection]);
await nextTick();
expect(h.inputs.credentialSelections.value).toEqual({
Primary: { httpBasicAuth: 'current-cred' },
Follower: { httpBasicAuth: 'current-cred' },
});
});
it('writes only to an independent params-bearing section target', () => {
const groupedSection = makeWorkflowSetupSection({
id: 'Primary:httpBasicAuth',
targetNodeName: 'Primary',
credentialType: 'httpBasicAuth',
credentialTargetNodes: [
{ id: 'primary', name: 'Primary', type: 'n8n-nodes-base.httpRequest' },
{ id: 'follower', name: 'Follower', type: 'n8n-nodes-base.httpRequest' },
],
});
const paramsSection = makeWorkflowSetupSection({
id: 'Params:httpBasicAuth',
targetNodeName: 'Params',
credentialType: 'httpBasicAuth',
parameterNames: ['url'],
credentialTargetNodes: [{ id: 'params', name: 'Params', type: 'n8n-nodes-base.httpRequest' }],
});
const h = setupHarness([groupedSection, paramsSection]);
h.inputs.setCredential(paramsSection, 'cred-params');
expect(h.inputs.credentialSelections.value).toEqual({
Params: { httpBasicAuth: 'cred-params' },
});
});
it('excludes skipped grouped sections from completed setup payloads', () => {
const groupedSection = makeWorkflowSetupSection({
id: 'Primary:httpBasicAuth',
targetNodeName: 'Primary',
credentialType: 'httpBasicAuth',
credentialTargetNodes: [
{ id: 'primary', name: 'Primary', type: 'n8n-nodes-base.httpRequest' },
{ id: 'follower', name: 'Follower', type: 'n8n-nodes-base.httpRequest' },
],
});
const h = setupHarness([groupedSection]);
h.inputs.setCredential(groupedSection, 'cred-1');
h.inputs.markSectionSkipped(groupedSection);
expect(h.inputs.buildCompletedSetupPayload()).toEqual({});
});
});

View File

@ -0,0 +1,282 @@
import { computed, ref, watch, type ComputedRef, type Ref } from 'vue';
import type { INodeUi } from '@/Interface';
import { deepCopy, type INodeParameters } from 'n8n-workflow';
import { setParameterValue as setParameterValueByPath } from '@/app/utils/parameterUtils';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
import { useCredentialTestInBackground } from '@/features/credentials/composables/useCredentialTestInBackground';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import type { WorkflowSetupApplyPayload, WorkflowSetupSection } from '../workflowSetup.types';
import { getWorkflowSetupParameterIssues } from '../workflowSetupParameterIssues';
export type CredentialSelectionsMap = Record<string, Record<string, string>>;
type ParameterValuesMap = Record<string, INodeParameters>;
export function useWorkflowSetupInputs(deps: {
sections: ComputedRef<WorkflowSetupSection[]>;
}): {
credentialSelections: Ref<CredentialSelectionsMap>;
skippedSectionIds: Ref<Set<string>>;
setCredential: (section: WorkflowSetupSection, credId: string | null) => void;
setParameterValue: (section: WorkflowSetupSection, parameterName: string, value: unknown) => void;
getDisplayNode: (section: WorkflowSetupSection) => INodeUi;
isSectionComplete: (section: WorkflowSetupSection) => boolean;
isCredentialTestFailed: (section: WorkflowSetupSection) => boolean;
isSectionSkipped: (section: WorkflowSetupSection) => boolean;
markSectionSkipped: (section: WorkflowSetupSection) => void;
buildCompletedSetupPayload: () => WorkflowSetupApplyPayload;
} {
const credentialsStore = useCredentialsStore();
const nodeTypesStore = useNodeTypesStore();
const { isCredentialTypeTestable, testCredentialInBackground } = useCredentialTestInBackground();
const credentialSelections = ref<CredentialSelectionsMap>({});
const parameterValues = ref<ParameterValuesMap>({});
const skippedSectionIds = ref<Set<string>>(new Set());
function testCredential(credId: string, credType: string) {
const credential = credentialsStore.getCredentialById(credId);
if (!credential) return;
void testCredentialInBackground(credId, credential.name, credType);
}
function setCredential(section: WorkflowSetupSection, credId: string | null) {
if (!section.credentialType) return;
const targetNames = section.credentialTargetNodes.map((target) => target.name);
const nextCredentialSelections = setCredentialSelectionForTargetNames(
credentialSelections.value,
targetNames,
section.credentialType,
credId,
);
if (credId) {
testCredential(credId, section.credentialType);
clearSectionSkipped(section);
}
credentialSelections.value = nextCredentialSelections;
}
function setParameterValue(section: WorkflowSetupSection, parameterName: string, value: unknown) {
const next = deepCopy(getParameterValues(section));
setParameterValueByPath(next, parameterName, value);
parameterValues.value = {
...parameterValues.value,
[section.targetNodeName]: next,
};
clearSectionSkipped(section);
}
function isSectionSkipped(section: WorkflowSetupSection): boolean {
return skippedSectionIds.value.has(section.id);
}
function markSectionSkipped(section: WorkflowSetupSection): void {
skippedSectionIds.value.add(section.id);
}
function clearSectionSkipped(section: WorkflowSetupSection): void {
skippedSectionIds.value.delete(section.id);
}
function getDisplayNode(section: WorkflowSetupSection): INodeUi {
return {
...section.node,
parameters: getParameterValues(section),
} as INodeUi;
}
function getParameterValues(section: WorkflowSetupSection): INodeParameters {
return (
parameterValues.value[section.targetNodeName] ?? (section.node.parameters as INodeParameters)
);
}
function getSelectedCredentialId(section: WorkflowSetupSection): string | undefined {
if (!section.credentialType) return undefined;
return credentialSelections.value[section.targetNodeName]?.[section.credentialType];
}
const parameterIssuesBySectionId = computed(() => {
const result = new Map<string, Record<string, string[]>>();
for (const section of deps.sections.value) {
if (section.parameterNames.length === 0) {
result.set(section.id, {});
continue;
}
const node = getDisplayNode(section);
const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
result.set(
section.id,
getWorkflowSetupParameterIssues(node, nodeType, section.parameterNames),
);
}
return result;
});
function getParameterIssues(section: WorkflowSetupSection): Record<string, string[]> {
return parameterIssuesBySectionId.value.get(section.id) ?? {};
}
function isCredentialComplete(section: WorkflowSetupSection): boolean {
if (!section.credentialType) return true;
const selectedCredentialId = getSelectedCredentialId(section);
if (!selectedCredentialId) return false;
if (!isCredentialTypeTestable(section.credentialType)) return true;
return credentialsStore.isCredentialTestedOk(selectedCredentialId);
}
function areParametersComplete(section: WorkflowSetupSection): boolean {
return Object.keys(getParameterIssues(section)).length === 0;
}
function isSectionComplete(section: WorkflowSetupSection): boolean {
return isCredentialComplete(section) && areParametersComplete(section);
}
function isCredentialTestFailed(section: WorkflowSetupSection): boolean {
if (!section.credentialType) return false;
const selectedCredentialId = getSelectedCredentialId(section);
if (!selectedCredentialId || !isCredentialTypeTestable(section.credentialType)) return false;
return credentialsStore.credentialTestResults.get(selectedCredentialId) === 'error';
}
function buildCompletedSetupPayload(): WorkflowSetupApplyPayload {
const includeCredential = (section: WorkflowSetupSection) =>
!isSectionSkipped(section) && isCredentialComplete(section);
const includeParams = (section: WorkflowSetupSection) =>
!isSectionSkipped(section) && areParametersComplete(section);
const nodeCredentials = buildNodeCredentials(includeCredential);
const nodeParameters = buildNodeParameters(includeParams);
return {
...(Object.keys(nodeCredentials).length > 0 ? { nodeCredentials } : {}),
...(Object.keys(nodeParameters).length > 0 ? { nodeParameters } : {}),
};
}
function buildNodeCredentials(
shouldInclude: (section: WorkflowSetupSection) => boolean,
): Record<string, Record<string, string>> {
const out: Record<string, Record<string, string>> = {};
for (const section of deps.sections.value) {
if (!section.credentialType || !shouldInclude(section)) continue;
for (const target of section.credentialTargetNodes) {
const credId = credentialSelections.value[target.name]?.[section.credentialType];
if (!credId) continue;
const perType = out[target.name] ?? {};
perType[section.credentialType] = credId;
out[target.name] = perType;
}
}
return out;
}
function buildNodeParameters(
shouldInclude: (section: WorkflowSetupSection) => boolean,
): Record<string, INodeParameters> {
const out: Record<string, INodeParameters> = {};
for (const section of deps.sections.value) {
if (section.parameterNames.length === 0 || !shouldInclude(section)) continue;
const values = getParameterValues(section);
const params: INodeParameters = {};
for (const name of section.parameterNames) {
if (values[name] !== undefined) params[name] = values[name];
}
if (Object.keys(params).length > 0) out[section.targetNodeName] = params;
}
return out;
}
function seedCredentialSelectionsForNewSections(
newSections: WorkflowSetupSection[],
): Array<{ id: string; type: string }> {
let nextCredentialSelections: CredentialSelectionsMap | null = null;
const credentialsToTest: Array<{ id: string; type: string }> = [];
for (const section of newSections) {
if (!section.credentialType) continue;
if (!section.currentCredentialId) continue;
nextCredentialSelections = setCredentialSelectionForTargetNames(
nextCredentialSelections ?? credentialSelections.value,
section.credentialTargetNodes.map((target) => target.name),
section.credentialType,
section.currentCredentialId,
);
credentialsToTest.push({ id: section.currentCredentialId, type: section.credentialType });
}
if (nextCredentialSelections) credentialSelections.value = nextCredentialSelections;
return credentialsToTest;
}
function pruneSkippedSectionsMissingFrom(sections: WorkflowSetupSection[]) {
if (skippedSectionIds.value.size === 0) return;
const knownIds = new Set(sections.map((s) => s.id));
for (const id of skippedSectionIds.value) {
if (!knownIds.has(id)) skippedSectionIds.value.delete(id);
}
}
watch(
deps.sections,
(sections, oldSections) => {
const previousSectionIds = new Set(oldSections?.map((section) => section.id) ?? []);
const newSections = sections.filter((section) => !previousSectionIds.has(section.id));
const credentialsToTest = seedCredentialSelectionsForNewSections(newSections);
for (const credential of credentialsToTest) {
testCredential(credential.id, credential.type);
}
pruneSkippedSectionsMissingFrom(sections);
},
{ immediate: true },
);
watch(
() =>
deps.sections.value
.filter((section) => skippedSectionIds.value.has(section.id) && isSectionComplete(section))
.map((s) => s.id),
(completedSkippedIds) => {
for (const id of completedSkippedIds) {
skippedSectionIds.value.delete(id);
}
},
);
return {
credentialSelections,
skippedSectionIds,
setCredential,
setParameterValue,
getDisplayNode,
isSectionComplete,
isCredentialTestFailed,
isSectionSkipped,
markSectionSkipped,
buildCompletedSetupPayload,
};
}
function setCredentialSelectionForTargetNames(
currentCredentialSelections: CredentialSelectionsMap,
targetNames: string[],
credentialType: string,
credentialId: string | null,
): CredentialSelectionsMap {
const nextCredentialSelections = { ...currentCredentialSelections };
for (const targetName of targetNames) {
const nodeCredentialSelections = { ...(nextCredentialSelections[targetName] ?? {}) };
if (credentialId) nodeCredentialSelections[credentialType] = credentialId;
else delete nodeCredentialSelections[credentialType];
nextCredentialSelections[targetName] = nodeCredentialSelections;
}
return nextCredentialSelections;
}

View File

@ -0,0 +1,422 @@
import { ref } from 'vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { makeSetupRequest } from '../__tests__/factories';
import { useWorkflowSetupSections } from './useWorkflowSetupSections';
const credentialsStore = vi.hoisted(() => ({
allCredentials: [] as Array<{ id: string; type: string; name: string }>,
}));
const nodeTypesStore = vi.hoisted(() => ({
getNodeType: vi.fn((): unknown => null),
}));
vi.mock('@/features/credentials/credentials.store', () => ({
useCredentialsStore: () => credentialsStore,
}));
vi.mock('@/app/stores/nodeTypes.store', () => ({
useNodeTypesStore: () => nodeTypesStore,
}));
describe('useWorkflowSetupSections', () => {
beforeEach(() => {
credentialsStore.allCredentials = [];
nodeTypesStore.getNodeType.mockReset();
nodeTypesStore.getNodeType.mockReturnValue(null);
});
it('skips setup requests without a credential type', () => {
const setupRequests = ref([
makeSetupRequest({ credentialType: undefined }),
makeSetupRequest({ credentialType: 'httpBasicAuth' }),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(1);
expect(sections.value[0].credentialType).toBe('httpBasicAuth');
});
it('creates sections for editable parameter-only setup requests', () => {
const setupRequests = ref([
makeSetupRequest({
credentialType: undefined,
parameterIssues: { url: ['URL is required'] },
editableParameters: [{ name: 'url', displayName: 'URL', type: 'string' }],
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(1);
expect(sections.value[0]).toMatchObject({
id: 'HTTP Request:parameters',
parameterNames: ['url'],
});
expect(sections.value[0].credentialType).toBeUndefined();
});
it('does not create parameter-only sections for non-editable issues', () => {
const setupRequests = ref([
makeSetupRequest({
credentialType: undefined,
parameterIssues: { url: ['URL is required'] },
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(0);
});
it('resolves hidden parameter defaults from the node type', () => {
nodeTypesStore.getNodeType.mockReturnValue({
name: 'n8n-nodes-base.httpRequest',
properties: [
{ displayName: 'Method', name: 'method', type: 'options', default: 'GET' },
{ displayName: 'URL', name: 'url', type: 'string', default: '' },
],
});
const setupRequests = ref([
makeSetupRequest({
credentialType: undefined,
parameterIssues: { url: ['URL is required'] },
editableParameters: [{ name: 'url', displayName: 'URL', type: 'string' }],
node: { parameters: { url: '' } },
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value[0].node.parameters).toMatchObject({ method: 'GET', url: '' });
});
it('uses a stable node-name and credential-type id', () => {
const setupRequests = ref([
makeSetupRequest({
credentialType: 'slackApi',
node: { name: 'Slack' },
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value[0]).toMatchObject({
id: 'Slack:slackApi',
credentialType: 'slackApi',
targetNodeName: 'Slack',
});
});
it('prefers the credential already assigned on the node', () => {
credentialsStore.allCredentials = [
{ id: 'store-cred', type: 'httpBasicAuth', name: 'Store credential' },
];
const setupRequests = ref([
makeSetupRequest({
credentialType: 'httpBasicAuth',
node: {
credentials: {
httpBasicAuth: { id: 'node-cred', name: 'Node credential' },
},
},
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value[0].currentCredentialId).toBe('node-cred');
});
it('does not preselect unrelated store credentials', () => {
credentialsStore.allCredentials = [
{ id: 'other-cred', type: 'slackApi', name: 'Slack credential' },
{ id: 'matching-cred', type: 'httpBasicAuth', name: 'HTTP credential' },
];
const setupRequests = ref([makeSetupRequest({ credentialType: 'httpBasicAuth' })]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value[0].currentCredentialId).toBeNull();
});
it('uses null when no assigned or matching store credential exists', () => {
credentialsStore.allCredentials = [
{ id: 'other-cred', type: 'slackApi', name: 'Slack credential' },
];
const setupRequests = ref([makeSetupRequest({ credentialType: 'httpBasicAuth' })]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value[0].currentCredentialId).toBeNull();
});
it('updates when setup requests change', () => {
const setupRequests = ref([makeSetupRequest({ credentialType: 'httpBasicAuth' })]);
const { sections } = useWorkflowSetupSections(setupRequests);
setupRequests.value = [
makeSetupRequest({ credentialType: 'slackApi', node: { name: 'Slack' } }),
];
expect(sections.value).toHaveLength(1);
expect(sections.value[0]).toMatchObject({
id: 'Slack:slackApi',
credentialType: 'slackApi',
});
});
it('groups cred-only requests with the same credential type into one primary section', () => {
const setupRequests = ref([
makeSetupRequest({ node: { id: 'first', name: 'First' } }),
makeSetupRequest({ node: { id: 'second', name: 'Second' } }),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(1);
expect(sections.value[0]).toMatchObject({ id: 'First:httpBasicAuth', targetNodeName: 'First' });
expect(sections.value[0].credentialTargetNodes).toEqual([
{ id: 'first', name: 'First', type: 'n8n-nodes-base.httpRequest' },
{ id: 'second', name: 'Second', type: 'n8n-nodes-base.httpRequest' },
]);
});
it('creates one primary per credential type', () => {
const setupRequests = ref([
makeSetupRequest({ credentialType: 'httpBasicAuth', node: { id: 'first', name: 'First' } }),
makeSetupRequest({ credentialType: 'slackApi', node: { id: 'second', name: 'Second' } }),
makeSetupRequest({ credentialType: 'httpBasicAuth', node: { id: 'third', name: 'Third' } }),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(2);
expect(sections.value.map((section) => section.credentialType)).toEqual([
'httpBasicAuth',
'slackApi',
]);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'First',
'Third',
]);
expect(sections.value[1].credentialTargetNodes.map((target) => target.name)).toEqual([
'Second',
]);
});
it('groups a cred-only follower into a primary section that has parameters', () => {
const setupRequests = ref([
makeSetupRequest({
node: { id: 'primary', name: 'Primary' },
editableParameters: [{ name: 'url', displayName: 'URL', type: 'string' }],
}),
makeSetupRequest({ node: { id: 'follower', name: 'Follower' } }),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(1);
expect(sections.value[0].parameterNames).toEqual(['url']);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'Primary',
'Follower',
]);
});
it('keeps a params-bearing follower independent from a cred-only primary', () => {
const setupRequests = ref([
makeSetupRequest({ node: { id: 'primary', name: 'Primary' } }),
makeSetupRequest({
node: { id: 'follower', name: 'Follower' },
editableParameters: [{ name: 'url', displayName: 'URL', type: 'string' }],
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(2);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'Primary',
]);
expect(sections.value[1].parameterNames).toEqual(['url']);
expect(sections.value[1].credentialTargetNodes.map((target) => target.name)).toEqual([
'Follower',
]);
});
it('keeps params-bearing requests with the same credential type independent', () => {
const setupRequests = ref([
makeSetupRequest({
node: { id: 'primary', name: 'Primary' },
editableParameters: [{ name: 'url', displayName: 'URL', type: 'string' }],
}),
makeSetupRequest({
node: { id: 'follower', name: 'Follower' },
editableParameters: [{ name: 'path', displayName: 'Path', type: 'string' }],
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(2);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'Primary',
]);
expect(sections.value[1].credentialTargetNodes.map((target) => target.name)).toEqual([
'Follower',
]);
});
it('groups HTTP requests by the same literal URL and splits different URLs', () => {
const setupRequests = ref([
makeSetupRequest({
node: { id: 'first', name: 'First', parameters: { url: 'https://a.test' } },
}),
makeSetupRequest({
node: { id: 'second', name: 'Second', parameters: { url: 'https://a.test' } },
}),
makeSetupRequest({
node: { id: 'third', name: 'Third', parameters: { url: 'https://b.test' } },
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(2);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'First',
'Second',
]);
expect(sections.value[1].credentialTargetNodes.map((target) => target.name)).toEqual(['Third']);
});
it('splits HTTP expression URLs per node even when the expression string matches', () => {
const setupRequests = ref([
makeSetupRequest({
node: { id: 'first', name: 'First', parameters: { url: '={{ $json.url }}' } },
}),
makeSetupRequest({
node: { id: 'second', name: 'Second', parameters: { url: '={{ $json.url }}' } },
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(2);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual(['First']);
expect(sections.value[1].credentialTargetNodes.map((target) => target.name)).toEqual([
'Second',
]);
});
it('groups HTTP requests with missing or non-string URLs', () => {
const setupRequests = ref([
makeSetupRequest({ node: { id: 'first', name: 'First', parameters: {} } }),
makeSetupRequest({ node: { id: 'second', name: 'Second', parameters: { url: 123 } } }),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(1);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'First',
'Second',
]);
});
it('groups non-HTTP requests only by credential type', () => {
const setupRequests = ref([
makeSetupRequest({
node: { id: 'first', name: 'First', type: 'n8n-nodes-base.slack' },
credentialType: 'slackApi',
}),
makeSetupRequest({
node: { id: 'second', name: 'Second', type: 'n8n-nodes-base.slack' },
credentialType: 'slackApi',
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(1);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'First',
'Second',
]);
});
describe('subnode-root-aware credential merging', () => {
const agentA = { name: 'Agent A', type: 'agent', typeVersion: 1, id: 'a' };
const agentB = { name: 'Agent B', type: 'agent', typeVersion: 1, id: 'b' };
it('keeps two agents with HTTP-credential sub-nodes in separate sections', () => {
const setupRequests = ref([
makeSetupRequest({
subnodeRootNode: agentA,
node: { id: 'tool-a', name: 'Tool A', type: 'n8n-nodes-base.httpRequestTool' },
}),
makeSetupRequest({
subnodeRootNode: agentB,
node: { id: 'tool-b', name: 'Tool B', type: 'n8n-nodes-base.httpRequestTool' },
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(2);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'Tool A',
]);
expect(sections.value[1].credentialTargetNodes.map((target) => target.name)).toEqual([
'Tool B',
]);
});
it('merges sub-nodes of the same agent that share a credential type', () => {
const setupRequests = ref([
makeSetupRequest({
subnodeRootNode: agentA,
node: { id: 'tool-1', name: 'Tool 1', type: 'n8n-nodes-base.slack' },
credentialType: 'slackApi',
}),
makeSetupRequest({
subnodeRootNode: agentA,
node: { id: 'tool-2', name: 'Tool 2', type: 'n8n-nodes-base.slack' },
credentialType: 'slackApi',
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(1);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'Tool 1',
'Tool 2',
]);
});
it('keeps a standalone HTTP node and an agent sub-node in separate sections', () => {
const setupRequests = ref([
makeSetupRequest({
node: { id: 'standalone', name: 'Standalone' },
}),
makeSetupRequest({
subnodeRootNode: agentA,
node: { id: 'tool-a', name: 'Tool A', type: 'n8n-nodes-base.httpRequestTool' },
}),
]);
const { sections } = useWorkflowSetupSections(setupRequests);
expect(sections.value).toHaveLength(2);
expect(sections.value[0].credentialTargetNodes.map((target) => target.name)).toEqual([
'Standalone',
]);
expect(sections.value[1].credentialTargetNodes.map((target) => target.name)).toEqual([
'Tool A',
]);
});
});
});

View File

@ -0,0 +1,103 @@
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import { computed, type ComputedRef, type Ref } from 'vue';
import { isExpression } from '@/app/utils/expressions';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { isHttpRequestNodeType } from '@/features/setupPanel/setupPanel.utils';
import { NodeHelpers, type INodeParameters } from 'n8n-workflow';
import type { WorkflowSetupSection } from '../workflowSetup.types';
import { buildSectionId } from '../workflowSetup.helpers';
export function useWorkflowSetupSections(
setupRequests: Ref<InstanceAiWorkflowSetupNode[]> | ComputedRef<InstanceAiWorkflowSetupNode[]>,
): { sections: ComputedRef<WorkflowSetupSection[]> } {
const nodeTypesStore = useNodeTypesStore();
const sections = computed<WorkflowSetupSection[]>(() => {
const result: WorkflowSetupSection[] = [];
const primaryByGroupKey = new Map<string, WorkflowSetupSection>();
for (const req of setupRequests.value) {
const parameterNames = (req.editableParameters ?? []).map((parameter) => parameter.name);
if (!req.credentialType && parameterNames.length === 0) continue;
const credentialType = req.credentialType;
const hasParams = parameterNames.length > 0;
const groupKey = buildGroupKey(req, credentialType);
const existingPrimary = groupKey ? primaryByGroupKey.get(groupKey) : undefined;
if (existingPrimary && !hasParams) {
existingPrimary.credentialTargetNodes.push({
id: req.node.id,
name: req.node.name,
type: req.node.type,
});
continue;
}
const node = {
...req.node,
parameters: resolveParameterDefaults(req.node),
};
const currentCredentialId =
credentialType === undefined ? null : (req.node.credentials?.[credentialType]?.id ?? null);
const section: WorkflowSetupSection = {
id: buildSectionId(req.node.name, credentialType),
...(credentialType ? { credentialType } : {}),
targetNodeName: req.node.name,
node,
currentCredentialId,
parameterNames,
credentialTargetNodes: [{ id: req.node.id, name: req.node.name, type: req.node.type }],
};
result.push(section);
if (groupKey && !existingPrimary) primaryByGroupKey.set(groupKey, section);
}
return result;
});
function resolveParameterDefaults(node: InstanceAiWorkflowSetupNode['node']): INodeParameters {
const nodeType = nodeTypesStore.getNodeType(node.type, node.typeVersion);
if (!nodeType) return node.parameters as INodeParameters;
return (NodeHelpers.getNodeParameters(
nodeType.properties,
node.parameters as INodeParameters,
true,
true,
node,
nodeType,
) ?? node.parameters) as INodeParameters;
}
return { sections };
}
/**
* Build a merge key for credential-only sections.
*
* When the request is a sub-node (has `subnodeRootNode`), the root node's name is
* prepended so credential sections never merge across different root nodes
* sub-nodes of two different agents stay separate even when they share a
* credential type. Standalone nodes (no subnode root) keep the original
* credentialType+URL merging behaviour to preserve the existing UX
* optimisation of configuring a shared credential once.
*/
function buildGroupKey(
req: InstanceAiWorkflowSetupNode,
credentialType: string | undefined,
): string | null {
if (!credentialType) return null;
const rootPrefix = req.subnodeRootNode?.name ? `${req.subnodeRootNode.name}|` : '';
const baseKey = `${rootPrefix}${credentialType}`;
if (!isHttpRequestNodeType(req.node.type)) return baseKey;
const url = req.node.parameters?.url;
if (typeof url !== 'string') return `${baseKey}|http|none`;
if (isExpression(url)) return `${baseKey}|http|expr|${req.node.name}`;
return `${baseKey}|http|${url}`;
}

View File

@ -0,0 +1,148 @@
import { computed, ref } from 'vue';
import { describe, expect, it } from 'vitest';
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import { makeSetupRequest, makeWorkflowSetupSection } from '../__tests__/factories';
import type { WorkflowSetupSection } from '../workflowSetup.types';
import { useWorkflowSetupSteps } from './useWorkflowSetupSteps';
const agent = { name: 'Agent', type: 'agent', typeVersion: 1, id: 'agent-1' };
const agentB = { name: 'Agent B', type: 'agent', typeVersion: 1, id: 'agent-b' };
function harness(sections: WorkflowSetupSection[], setupRequests: InstanceAiWorkflowSetupNode[]) {
const sectionsRef = computed(() => sections);
const setupRequestsRef = ref(setupRequests);
return useWorkflowSetupSteps({ sections: sectionsRef, setupRequests: setupRequestsRef });
}
describe('useWorkflowSetupSteps', () => {
it('returns an empty list when there are no sections', () => {
const { steps } = harness([], []);
expect(steps.value).toEqual([]);
});
it('mirrors sections 1:1 when no groupable connections exist', () => {
const sections = [
makeWorkflowSetupSection({ id: 'A:slackApi', targetNodeName: 'A' }),
makeWorkflowSetupSection({ id: 'B:gmailApi', targetNodeName: 'B' }),
];
const requests = [
makeSetupRequest({ node: { name: 'A' } }),
makeSetupRequest({ node: { name: 'B' } }),
];
const { steps } = harness(sections, requests);
expect(steps.value).toHaveLength(2);
expect(steps.value.every((s) => s.kind === 'section')).toBe(true);
});
it('inserts the group at the first sub-node position when the root node comes after the sub-nodes', () => {
const sections = [
makeWorkflowSetupSection({ id: 'Model:openAiApi', targetNodeName: 'Model' }),
makeWorkflowSetupSection({ id: 'Standalone:slackApi', targetNodeName: 'Standalone' }),
makeWorkflowSetupSection({ id: 'Agent:foo', targetNodeName: 'Agent' }),
];
const requests = [
makeSetupRequest({ node: { name: 'Model' }, subnodeRootNode: agent }),
makeSetupRequest({ node: { name: 'Standalone' } }),
makeSetupRequest({ node: { name: 'Agent' } }),
];
const { steps } = harness(sections, requests);
expect(steps.value).toHaveLength(2);
const groupStep = steps.value[0];
if (groupStep.kind !== 'group') throw new Error('expected group step');
expect(groupStep.group.subnodeRootNode.name).toBe('Agent');
expect(groupStep.group.subnodeSections.map((s) => s.id)).toEqual(['Model:openAiApi']);
expect(groupStep.group.rootSection?.id).toBe('Agent:foo');
const sectionStep = steps.value[1];
if (sectionStep.kind !== 'section') throw new Error('expected section step');
expect(sectionStep.section.targetNodeName).toBe('Standalone');
});
it('inserts the group at the root node position when the root comes before its sub-nodes', () => {
const sections = [
makeWorkflowSetupSection({ id: 'Agent:foo', targetNodeName: 'Agent' }),
makeWorkflowSetupSection({ id: 'Standalone:slackApi', targetNodeName: 'Standalone' }),
makeWorkflowSetupSection({ id: 'Model:openAiApi', targetNodeName: 'Model' }),
];
const requests = [
makeSetupRequest({ node: { name: 'Agent' } }),
makeSetupRequest({ node: { name: 'Standalone' } }),
makeSetupRequest({ node: { name: 'Model' }, subnodeRootNode: agent }),
];
const { steps } = harness(sections, requests);
expect(steps.value).toHaveLength(2);
expect(steps.value[0].kind).toBe('group');
const sectionStep = steps.value[1];
if (sectionStep.kind !== 'section') throw new Error('expected section step');
expect(sectionStep.section.targetNodeName).toBe('Standalone');
});
it('emits a group with no rootSection when the root node has no setup request', () => {
const sections = [makeWorkflowSetupSection({ id: 'Model:openAiApi', targetNodeName: 'Model' })];
const requests = [makeSetupRequest({ node: { name: 'Model' }, subnodeRootNode: agent })];
const { steps } = harness(sections, requests);
expect(steps.value).toHaveLength(1);
const step = steps.value[0];
if (step.kind !== 'group') throw new Error('expected group');
expect(step.group.rootSection).toBeUndefined();
expect(step.group.subnodeSections.map((s) => s.id)).toEqual(['Model:openAiApi']);
expect(step.group.subnodeRootNode).toEqual(agent);
});
it('attaches multiple sections from the same sub-node into subnodeSections', () => {
const sections = [
makeWorkflowSetupSection({ id: 'Model:openAiApi', targetNodeName: 'Model' }),
makeWorkflowSetupSection({
id: 'Model:parameters',
targetNodeName: 'Model',
credentialType: undefined,
parameterNames: ['temperature'],
}),
];
const requests = [
makeSetupRequest({ node: { name: 'Model' }, subnodeRootNode: agent }),
makeSetupRequest({
node: { name: 'Model' },
subnodeRootNode: agent,
credentialType: undefined,
editableParameters: [{ name: 'temperature', displayName: 'Temperature', type: 'number' }],
}),
];
const { steps } = harness(sections, requests);
expect(steps.value).toHaveLength(1);
const step = steps.value[0];
if (step.kind !== 'group') throw new Error('expected group');
expect(step.group.subnodeSections.map((s) => s.id)).toEqual([
'Model:openAiApi',
'Model:parameters',
]);
expect(step.group.rootSection).toBeUndefined();
});
it('preserves execution order between two non-overlapping groups', () => {
const sections = [
makeWorkflowSetupSection({ id: 'ModelA:openAiApi', targetNodeName: 'Model A' }),
makeWorkflowSetupSection({ id: 'ModelB:openAiApi', targetNodeName: 'Model B' }),
];
const requests = [
makeSetupRequest({ node: { name: 'Model A' }, subnodeRootNode: agent }),
makeSetupRequest({ node: { name: 'Model B' }, subnodeRootNode: agentB }),
];
const { steps } = harness(sections, requests);
expect(steps.value).toHaveLength(2);
const [first, second] = steps.value;
if (first.kind !== 'group' || second.kind !== 'group') throw new Error('expected groups');
expect(first.group.subnodeRootNode.name).toBe('Agent');
expect(second.group.subnodeRootNode.name).toBe('Agent B');
});
});

View File

@ -0,0 +1,65 @@
import { computed, type ComputedRef, type Ref } from 'vue';
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import type {
WorkflowSetupGroup,
WorkflowSetupSection,
WorkflowSetupStep,
} from '../workflowSetup.types';
/**
* Combines flat sections with subnode-root metadata from setup requests to
* produce a list of wizard steps. Sections that share a root node (an agent)
* are folded into a single `{ group }` step emitted at the position of the
* group's earliest section. Ungrouped sections pass through.
*/
export function useWorkflowSetupSteps(deps: {
sections: ComputedRef<WorkflowSetupSection[]>;
setupRequests: Ref<InstanceAiWorkflowSetupNode[]> | ComputedRef<InstanceAiWorkflowSetupNode[]>;
}): { steps: ComputedRef<WorkflowSetupStep[]> } {
const steps = computed<WorkflowSetupStep[]>(() => {
const sections = deps.sections.value;
const requests = deps.setupRequests.value;
if (sections.length === 0) return [];
const rootBySubnodeName = new Map<string, WorkflowSetupGroup['subnodeRootNode']>();
const rootMetaByName = new Map<string, WorkflowSetupGroup['subnodeRootNode']>();
for (const req of requests) {
if (!req.subnodeRootNode) continue;
rootBySubnodeName.set(req.node.name, req.subnodeRootNode);
if (!rootMetaByName.has(req.subnodeRootNode.name)) {
rootMetaByName.set(req.subnodeRootNode.name, req.subnodeRootNode);
}
}
const result: WorkflowSetupStep[] = [];
const groupByRootName = new Map<string, WorkflowSetupGroup>();
for (const section of sections) {
const subnodeRoot = rootBySubnodeName.get(section.targetNodeName);
const selfAsRoot = rootMetaByName.get(section.targetNodeName);
const root = subnodeRoot ?? selfAsRoot;
if (!root) {
result.push({ kind: 'section', section });
continue;
}
let group = groupByRootName.get(root.name);
if (!group) {
group = { subnodeRootNode: root, subnodeSections: [] };
groupByRootName.set(root.name, group);
result.push({ kind: 'group', group });
}
if (subnodeRoot) {
group.subnodeSections.push(section);
} else if (!group.rootSection) {
group.rootSection = section;
}
}
return result;
});
return { steps };
}

View File

@ -0,0 +1,238 @@
import { watch, type ComputedRef, type Ref } from 'vue';
import { useTelemetry } from '@/app/composables/useTelemetry';
import { useRootStore } from '@n8n/stores/useRootStore';
import type { ThreadRuntime } from '../../instanceAi.store';
import type { WorkflowSetupSection, WorkflowSetupStep } from '../workflowSetup.types';
import { getStepSections } from '../workflowSetup.helpers';
type ProvidedSetupInput = { label: string; options: string[]; option_chosen: string };
type SkippedSetupInput = { label: string; options: string[] };
type SetupStepOutcome = 'completed' | 'skipped' | 'mixed';
type WorkflowSetupStepTelemetryInput = {
input_type: 'credential' | 'parameter';
node_type: string;
credential_type?: string;
parameter_name?: string;
};
type WorkflowSetupStepTelemetryPayload = {
thread_id: string;
input_thread_id: string;
instance_id: string;
type: 'setup';
request_id: string;
step_index: number;
step_count: number;
step_kind: WorkflowSetupStep['kind'];
setup_inputs: WorkflowSetupStepTelemetryInput[];
outcome?: SetupStepOutcome;
};
type SetupTelemetryContext = Pick<
WorkflowSetupStepTelemetryPayload,
'thread_id' | 'input_thread_id' | 'instance_id' | 'type'
>;
interface WorkflowSetupTelemetryInputAccessors {
isSectionComplete: (section: WorkflowSetupSection) => boolean;
isSectionSkipped: (section: WorkflowSetupSection) => boolean;
}
export function useWorkflowSetupTelemetry(deps: {
requestId: Ref<string>;
sections: ComputedRef<WorkflowSetupSection[]>;
steps: ComputedRef<WorkflowSetupStep[]>;
activeStep: ComputedRef<WorkflowSetupStep | undefined>;
currentStepIndex: Ref<number>;
isReady: Ref<boolean>;
inputs: WorkflowSetupTelemetryInputAccessors;
thread: ThreadRuntime;
}) {
const telemetry = useTelemetry();
const rootStore = useRootStore();
const shownStepKeys = new Set<string>();
const handledStepKeys = new Set<string>();
function getSetupTelemetryContext(): SetupTelemetryContext {
const tc = deps.thread.findToolCallByRequestId(deps.requestId.value);
return {
thread_id: deps.thread.currentThreadId,
input_thread_id: tc?.confirmation?.inputThreadId ?? '',
instance_id: rootStore.instanceId,
type: 'setup',
};
}
function getStepKey(step: WorkflowSetupStep): string {
const inputKeys = getStepTelemetryInputs(step).map(
(input) =>
`${input.input_type}:${input.node_type}:${input.credential_type ?? input.parameter_name}`,
);
return `${step.kind}:${deps.steps.value.indexOf(step)}:${inputKeys.join('|')}`;
}
function getTrackingStepKey(step: WorkflowSetupStep): string {
return `${deps.requestId.value}:${getStepKey(step)}`;
}
function getStepOutcome(step: WorkflowSetupStep): SetupStepOutcome | undefined {
const sections = getStepSections(step);
if (sections.length === 0) return undefined;
let completedCount = 0;
let skippedCount = 0;
for (const section of sections) {
if (deps.inputs.isSectionComplete(section)) {
completedCount++;
} else if (deps.inputs.isSectionSkipped(section)) {
skippedCount++;
}
}
if (completedCount + skippedCount !== sections.length) return undefined;
if (completedCount === sections.length) return 'completed';
if (skippedCount === sections.length) return 'skipped';
return 'mixed';
}
function getStepTelemetryInputs(step: WorkflowSetupStep): WorkflowSetupStepTelemetryInput[] {
const inputs: WorkflowSetupStepTelemetryInput[] = [];
for (const section of getStepSections(step)) {
if (section.credentialType) {
inputs.push({
input_type: 'credential',
node_type: section.node.type,
credential_type: section.credentialType,
});
}
for (const parameterName of section.parameterNames) {
inputs.push({
input_type: 'parameter',
node_type: section.node.type,
parameter_name: parameterName,
});
}
}
return inputs;
}
function getStepTelemetryPayload(
step: WorkflowSetupStep,
outcome?: SetupStepOutcome,
): WorkflowSetupStepTelemetryPayload {
return {
...getSetupTelemetryContext(),
request_id: deps.requestId.value,
step_index: deps.steps.value.indexOf(step) + 1,
step_count: deps.steps.value.length,
step_kind: step.kind,
setup_inputs: getStepTelemetryInputs(step),
...(outcome ? { outcome } : {}),
};
}
function trackStepShown(step: WorkflowSetupStep): void {
const stepKey = getTrackingStepKey(step);
if (shownStepKeys.has(stepKey)) return;
shownStepKeys.add(stepKey);
telemetry.track('Instance AI workflow setup step shown', getStepTelemetryPayload(step));
}
function trackStepHandled(step: WorkflowSetupStep): void {
const stepKey = getTrackingStepKey(step);
if (!shownStepKeys.has(stepKey) || handledStepKeys.has(stepKey)) return;
const outcome = getStepOutcome(step);
if (!outcome) return;
handledStepKeys.add(stepKey);
telemetry.track(
'Instance AI workflow setup step handled',
getStepTelemetryPayload(step, outcome),
);
}
watch(
() => {
const step = deps.isReady.value ? deps.activeStep.value : undefined;
if (!step) return undefined;
return {
step,
key: getTrackingStepKey(step),
index: deps.currentStepIndex.value,
count: deps.steps.value.length,
states: getStepSections(step).map((section) => ({
isComplete: deps.inputs.isSectionComplete(section),
isSkipped: deps.inputs.isSectionSkipped(section),
})),
};
},
(snapshot) => {
if (!snapshot) return;
trackStepShown(snapshot.step);
trackStepHandled(snapshot.step);
},
{ immediate: true },
);
function trackSetupInput(): void {
const provided: ProvidedSetupInput[] = [];
const skipped: SkippedSetupInput[] = [];
const explicitlySkipped: SkippedSetupInput[] = [];
for (const section of deps.sections.value) {
const sectionInputs = getSectionTelemetryInputs(section);
if (deps.inputs.isSectionComplete(section)) {
provided.push(...sectionInputs);
} else {
const skippedInputs = sectionInputs.map(toSkippedInput);
skipped.push(...skippedInputs);
if (deps.inputs.isSectionSkipped(section)) {
explicitlySkipped.push(...skippedInputs);
}
}
}
telemetry.track('User finished providing input', {
...getSetupTelemetryContext(),
provided_inputs: provided,
skipped_inputs: skipped,
explicitly_skipped_inputs: explicitlySkipped,
num_tasks: deps.sections.value.length,
});
}
function getSectionTelemetryInputs(section: WorkflowSetupSection): ProvidedSetupInput[] {
const inputs: ProvidedSetupInput[] = [];
if (section.credentialType) {
inputs.push({
label: getSetupInputLabel(section.node.type, section.credentialType),
options: [],
option_chosen: 'true',
});
}
for (const parameterName of section.parameterNames) {
inputs.push({
label: getSetupInputLabel(section.node.type, parameterName),
options: [],
option_chosen: 'true',
});
}
return inputs;
}
function getSetupInputLabel(nodeType: string, inputName: string): string {
return `${nodeType} - ${inputName}`;
}
function toSkippedInput(input: ProvidedSetupInput): SkippedSetupInput {
return { label: input.label, options: input.options };
}
return {
trackSetupInput,
trackStepHandled,
};
}

View File

@ -0,0 +1,23 @@
import type {
WorkflowSetupGroup,
WorkflowSetupSection,
WorkflowSetupStep,
} from './workflowSetup.types';
/** Stable section identity used as the key everywhere section state is tracked. */
export function buildSectionId(targetNodeName: string, credentialType?: string): string {
return `${targetNodeName}:${credentialType ?? 'parameters'}`;
}
/**
* Returns the root + sub-node sections in display order. Centralizes the
* optional-root handling so callers don't repeat the same spread.
*/
export function getGroupSections(group: WorkflowSetupGroup): WorkflowSetupSection[] {
return group.rootSection ? [group.rootSection, ...group.subnodeSections] : group.subnodeSections;
}
/** Returns every section a step represents (one for section steps, root+subnodes for group steps). */
export function getStepSections(step: WorkflowSetupStep): WorkflowSetupSection[] {
return step.kind === 'group' ? getGroupSections(step.group) : [step.section];
}

View File

@ -0,0 +1,44 @@
import type { InstanceAiWorkflowSetupNode } from '@n8n/api-types';
import type { INodeParameters } from 'n8n-workflow';
/**
* One form unit (per `node × credential-or-parameters`).
* Backs all input/skip/payload state.
*/
export interface WorkflowSetupSection {
id: string;
credentialType?: string;
targetNodeName: string;
node: InstanceAiWorkflowSetupNode['node'];
currentCredentialId: string | null;
parameterNames: string[];
credentialTargetNodes: Array<{ id: string; name: string; type: string }>;
}
/**
* Root node + its setup/sub-node sections grouped under a single wizard step.
*
* `rootSection` is optional because the root node may not have a setup
* request of its own its sub-nodes can carry all the configuration. When
* the root node does have a setup request, credentials and parameters from
* that request are bundled into a single section.
*/
export interface WorkflowSetupGroup {
subnodeRootNode: { name: string; type: string; typeVersion: number; id: string };
rootSection?: WorkflowSetupSection;
subnodeSections: WorkflowSetupSection[];
}
/**
* What the wizard navigates: either a single section or a grouped step.
*/
export type WorkflowSetupStep =
| { kind: 'section'; section: WorkflowSetupSection }
| { kind: 'group'; group: WorkflowSetupGroup };
export interface WorkflowSetupApplyPayload {
nodeCredentials?: Record<string, Record<string, string>>;
nodeParameters?: Record<string, INodeParameters>;
}
export type TerminalState = 'applying' | 'applied' | 'partial' | 'deferred';

View File

@ -0,0 +1,141 @@
import type { INodeUi } from '@/Interface';
import type { INodeProperties, INodeTypeDescription } from 'n8n-workflow';
import { NodeHelpers } from 'n8n-workflow';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getWorkflowSetupParameterIssues } from './workflowSetupParameterIssues';
function makeNode(overrides: Partial<INodeUi> = {}): INodeUi {
return {
id: 'http-request',
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
typeVersion: 4.2,
position: [0, 0],
parameters: {},
...overrides,
} as INodeUi;
}
function makeNodeType(properties: INodeProperties[]): INodeTypeDescription {
return {
displayName: 'HTTP Request',
name: 'n8n-nodes-base.httpRequest',
group: ['transform'],
version: 4.2,
description: 'Makes HTTP requests',
defaults: { name: 'HTTP Request' },
inputs: ['main'],
outputs: ['main'],
properties,
} as INodeTypeDescription;
}
describe('getWorkflowSetupParameterIssues', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it('returns no issues without a node type or requested parameters', () => {
const getNodeParametersIssuesSpy = vi.spyOn(NodeHelpers, 'getNodeParametersIssues');
const node = makeNode();
const nodeType = makeNodeType([
{ displayName: 'URL', name: 'url', type: 'string', default: '' },
]);
expect(getWorkflowSetupParameterIssues(node, null, ['url'])).toEqual({});
expect(getWorkflowSetupParameterIssues(node, nodeType, [])).toEqual({});
expect(getNodeParametersIssuesSpy).not.toHaveBeenCalled();
});
it('returns validation issues only for requested visible parameters', () => {
vi.spyOn(NodeHelpers, 'getNodeParametersIssues').mockReturnValue({
parameters: {
url: ['URL is required'],
method: ['Method is required'],
ignored: ['Ignored issue'],
},
});
const node = makeNode({ parameters: { url: '', method: '' } });
const nodeType = makeNodeType([
{ displayName: 'URL', name: 'url', type: 'string', default: '' },
{ displayName: 'Method', name: 'method', type: 'string', default: '' },
{ displayName: 'Ignored', name: 'ignored', type: 'string', default: '' },
]);
expect(getWorkflowSetupParameterIssues(node, nodeType, ['url', 'method'])).toEqual({
url: ['URL is required'],
method: ['Method is required'],
});
});
it('adds placeholder issues for requested visible parameters', () => {
vi.spyOn(NodeHelpers, 'getNodeParametersIssues').mockReturnValue({ parameters: {} });
const node = makeNode({ parameters: { url: '<__PLACEHOLDER_VALUE__Enter URL__>' } });
const nodeType = makeNodeType([
{ displayName: 'URL', name: 'url', type: 'string', default: '' },
]);
expect(getWorkflowSetupParameterIssues(node, nodeType, ['url'])).toEqual({
url: ['Placeholder "Enter URL" - please provide the real value'],
});
});
it('skips hidden parameters and parameters hidden by display options', () => {
vi.spyOn(NodeHelpers, 'getNodeParametersIssues').mockReturnValue({
parameters: {
hidden: ['Hidden is required'],
conditional: ['Conditional is required'],
},
});
const displayParameterSpy = vi.spyOn(NodeHelpers, 'displayParameter').mockReturnValue(false);
const node = makeNode({ parameters: { hidden: '', conditional: '' } });
const conditionalProperty: INodeProperties = {
displayName: 'Conditional',
name: 'conditional',
type: 'string',
default: '',
displayOptions: { show: { mode: ['advanced'] } },
};
const nodeType = makeNodeType([
{ displayName: 'Hidden', name: 'hidden', type: 'hidden', default: '' },
conditionalProperty,
]);
expect(getWorkflowSetupParameterIssues(node, nodeType, ['hidden', 'conditional'])).toEqual({});
expect(displayParameterSpy).toHaveBeenCalledWith(
node.parameters,
conditionalProperty,
node,
nodeType,
);
});
it('keeps issues when any duplicate parameter definition is visible', () => {
vi.spyOn(NodeHelpers, 'getNodeParametersIssues').mockReturnValue({
parameters: { value: ['Value is required'] },
});
const hiddenDefinition: INodeProperties = {
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
displayOptions: { show: { mode: ['advanced'] } },
};
const visibleDefinition: INodeProperties = {
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
displayOptions: { show: { mode: ['basic'] } },
};
vi.spyOn(NodeHelpers, 'displayParameter').mockImplementation((_parameters, property) => {
return property === visibleDefinition;
});
const node = makeNode({ parameters: { value: '' } });
const nodeType = makeNodeType([hiddenDefinition, visibleDefinition]);
expect(getWorkflowSetupParameterIssues(node, nodeType, ['value'])).toEqual({
value: ['Value is required'],
});
});
});

View File

@ -0,0 +1,68 @@
import { findPlaceholderDetails } from '@n8n/utils';
import type { INodeUi } from '@/Interface';
import type { INodeParameters, INodeProperties, INodeTypeDescription } from 'n8n-workflow';
import { NodeHelpers } from 'n8n-workflow';
export function getWorkflowSetupParameterIssues(
node: INodeUi,
nodeType: INodeTypeDescription | null,
parameterNames: string[],
): Record<string, string[]> {
if (!nodeType || parameterNames.length === 0) return {};
const wanted = new Set(parameterNames);
const allIssues =
NodeHelpers.getNodeParametersIssues(nodeType.properties, node, nodeType)?.parameters ?? {};
// A parameter name can have multiple definitions with different displayOptions.
// Keep all variants so issues are shown when any matching definition is visible.
const parameterDefinitionsByName = groupParameterDefinitionsByName(nodeType.properties);
const filtered: Record<string, string[]> = {};
for (const name of wanted) {
const parameterDefinitions = parameterDefinitionsByName.get(name);
if (
!parameterDefinitions ||
!parameterDefinitions.some((definition) =>
isParameterDefinitionVisible(node.parameters, definition, node, nodeType),
)
) {
continue;
}
const issues = [...(allIssues[name] ?? [])];
const placeholderDetails = findPlaceholderDetails(node.parameters[name]);
if (placeholderDetails.length > 0) {
issues.push(`Placeholder "${placeholderDetails[0].label}" - please provide the real value`);
}
if (issues.length > 0) filtered[name] = issues;
}
return filtered;
}
function groupParameterDefinitionsByName(
properties: INodeProperties[],
): Map<string, INodeProperties[]> {
const result = new Map<string, INodeProperties[]>();
for (const prop of properties) {
const existing = result.get(prop.name);
if (existing) {
existing.push(prop);
} else {
result.set(prop.name, [prop]);
}
}
return result;
}
function isParameterDefinitionVisible(
parameters: INodeParameters,
property: INodeProperties,
node: INodeUi,
nodeType: INodeTypeDescription,
): boolean {
if (property.type === 'hidden') return false;
if (!property.displayOptions) return true;
return NodeHelpers.displayParameter(parameters, property, node, nodeType);
}

View File

@ -131,6 +131,9 @@ const workflowDocumentStore = injectWorkflowDocumentStore();
const contextNode = computed<INode | null>(() => {
if (ndvStore.activeNode) return ndvStore.activeNode;
const modalState = uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY];
if (isCredentialModalState(modalState) && modalState.contextNode) {
return modalState.contextNode;
}
const fallbackName = isCredentialModalState(modalState) ? modalState.nodeName : undefined;
return fallbackName ? (workflowDocumentStore.value?.getNodeByName(fallbackName) ?? null) : null;
});

View File

@ -254,7 +254,8 @@ describe('NodeCredentials', () => {
false,
undefined,
undefined,
undefined,
httpNode.name,
httpNode,
{ hideAskAssistant: false },
);
});
@ -673,7 +674,7 @@ describe('NodeCredentials', () => {
await userEvent.click(screen.getByTestId('setup-manually-link'));
// createNewCredential calls openNewCredential with (type, showAuthOptions, forceManualMode, projectId)
// createNewCredential calls openNewCredential with context for auth-option resolution
// "setup manually" passes forceManualMode=true
expect(uiStore.openNewCredential).toHaveBeenCalledWith(
'slackOAuth2Api',
@ -681,7 +682,12 @@ describe('NodeCredentials', () => {
true,
undefined,
undefined,
undefined,
slackNode.name,
expect.objectContaining({
id: slackNode.id,
name: slackNode.name,
type: slackNode.type,
}),
{ hideAskAssistant: false },
);
});

View File

@ -396,7 +396,8 @@ function createNewCredential(
forceManualMode,
props.projectId,
props.suggestedCredentialName,
undefined,
props.node.name,
props.node,
{ hideAskAssistant: props.hideAskAssistant },
);
telemetry.track('User opened Credential modal', {

View File

@ -0,0 +1,48 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createTestingPinia } from '@pinia/testing';
import { setActivePinia } from 'pinia';
import { mockedStore } from '@/__tests__/utils';
import { useCredentialsStore } from '../../credentials.store';
import { useCredentialTestInBackground } from '../useCredentialTestInBackground';
describe('useCredentialTestInBackground', () => {
let credentialsStore: ReturnType<typeof mockedStore<typeof useCredentialsStore>>;
beforeEach(() => {
setActivePinia(createTestingPinia({ stubActions: false }));
credentialsStore = mockedStore(useCredentialsStore);
credentialsStore.credentialTestResults = new Map();
});
describe('hydrateCredentialTestResults', () => {
it('writes success and error entries to the credentials store', () => {
const { hydrateCredentialTestResults } = useCredentialTestInBackground();
hydrateCredentialTestResults([
{ id: 'cred-ok', success: true },
{ id: 'cred-bad', success: false },
]);
expect(credentialsStore.credentialTestResults.get('cred-ok')).toBe('success');
expect(credentialsStore.credentialTestResults.get('cred-bad')).toBe('error');
});
it('overwrites existing entries when re-hydrating with a new result', () => {
credentialsStore.credentialTestResults.set('cred-1', 'error');
const { hydrateCredentialTestResults } = useCredentialTestInBackground();
hydrateCredentialTestResults([{ id: 'cred-1', success: true }]);
expect(credentialsStore.credentialTestResults.get('cred-1')).toBe('success');
});
it('is a no-op for an empty list', () => {
const { hydrateCredentialTestResults } = useCredentialTestInBackground();
hydrateCredentialTestResults([]);
expect(credentialsStore.credentialTestResults.size).toBe(0);
});
});
});

View File

@ -0,0 +1,90 @@
import type { ICredentialDataDecryptedObject } from 'n8n-workflow';
import { useCredentialsStore } from '@/features/credentials/credentials.store';
export function useCredentialTestInBackground() {
const credentialsStore = useCredentialsStore();
/**
* Checks whether a credential type has a test mechanism defined.
* Returns true if either the credential type itself defines a `test` block
* or any node with access declares `testedBy` for it.
*/
const isCredentialTypeTestable = (credentialTypeName: string): boolean => {
const credType = credentialsStore.getCredentialTypeByName(credentialTypeName);
if (credType?.test) return true;
const nodesWithAccess = credentialsStore.getNodesWithAccess(credentialTypeName);
return nodesWithAccess.some((node) =>
node.credentials?.some((cred) => cred.name === credentialTypeName && cred.testedBy),
);
};
/**
* Tests a saved credential in the background.
* Fetches the credential's redacted data first so the backend can unredact and test.
* Skips if the credential is already tested OK or has a test in flight.
* The result is tracked automatically in the credentials store as a side effect of testCredential.
*/
async function testCredentialInBackground(
credentialId: string,
credentialName: string,
credentialType: string,
) {
if (!isCredentialTypeTestable(credentialType)) {
return;
}
if (
credentialsStore.isCredentialTestedOk(credentialId) ||
credentialsStore.isCredentialTestPending(credentialId)
) {
return;
}
try {
const credentialResponse = await credentialsStore.getCredentialData({ id: credentialId });
if (!credentialResponse?.data || typeof credentialResponse.data === 'string') {
return;
}
// Re-check after the async fetch — another caller (e.g. CredentialEdit) may have
// started or completed a test while we were fetching credential data.
if (
credentialsStore.isCredentialTestedOk(credentialId) ||
credentialsStore.isCredentialTestPending(credentialId)
) {
return;
}
const { ownedBy, sharedWithProjects, oauthTokenData, ...data } = credentialResponse.data;
// OAuth credentials can't be tested via the API — the presence of token data
// means the OAuth flow completed successfully, which is the equivalent of a passing test.
if (oauthTokenData) {
credentialsStore.credentialTestResults.set(credentialId, 'success');
return;
}
await credentialsStore.testCredential({
id: credentialId,
name: credentialName,
type: credentialType,
data: data as ICredentialDataDecryptedObject,
});
} catch {
// Test failure is tracked in the store as a side effect
}
}
function hydrateCredentialTestResults(results: Array<{ id: string; success: boolean }>) {
for (const { id, success } of results) {
credentialsStore.credentialTestResults.set(id, success ? 'success' : 'error');
}
}
return {
isCredentialTypeTestable,
testCredentialInBackground,
hydrateCredentialTestResults,
};
}

View File

@ -1,11 +1,7 @@
import { computed, ref, watch, type Ref } from 'vue';
import type { INodeUi } from '@/Interface';
import {
type ICredentialDataDecryptedObject,
type INode,
isResourceLocatorValue,
} from 'n8n-workflow';
import { type INode, isResourceLocatorValue } from 'n8n-workflow';
import type { SetupCardItem, NodeSetupState } from '@/features/setupPanel/setupPanel.types';
import { isCardComplete } from '@/features/setupPanel/setupPanel.utils';
@ -39,6 +35,7 @@ import { sortNodesByExecutionOrder } from '@/app/utils/workflowUtils';
import { useUIStore } from '@/app/stores/ui.store';
import { useTemplatesStore } from '@/features/workflows/templates/templates.store';
import { groupSetupCards } from '@/features/setupPanel/composables/groupSetupCards';
import { useCredentialTestInBackground } from '@/features/credentials/composables/useCredentialTestInBackground';
/**
* Composable that manages workflow setup state for credential configuration.
@ -60,6 +57,7 @@ export const useWorkflowSetupState = (
const nodeHelpers = useNodeHelpers();
const environmentsStore = useEnvironmentsStore();
const templatesStore = useTemplatesStore();
const { isCredentialTypeTestable, testCredentialInBackground } = useCredentialTestInBackground();
const workflowDocumentStore = injectWorkflowDocumentStore();
const sourceNodes = computed(() => nodes?.value ?? workflowDocumentStore.value.allNodes);
@ -226,22 +224,6 @@ export const useWorkflowSetupState = (
return credentialTypeInfo?.displayName ?? credentialType;
};
/**
* Checks whether a credential type has a test mechanism defined.
* Returns true if either the credential type itself defines a `test` block
* or any node with access declares `testedBy` for it.
* Non-testable types (e.g. Header Auth) are considered complete when just set.
*/
const isCredentialTypeTestable = (credentialTypeName: string): boolean => {
const credType = credentialsStore.getCredentialTypeByName(credentialTypeName);
if (credType?.test) return true;
const nodesWithAccess = credentialsStore.getNodesWithAccess(credentialTypeName);
return nodesWithAccess.some((node) =>
node.credentials?.some((cred) => cred.name === credentialTypeName && cred.testedBy),
);
};
const isTriggerNode = (node: INodeUi): boolean => {
return nodeTypesStore.isTriggerNode(node.type);
};
@ -703,63 +685,6 @@ export const useWorkflowSetupState = (
return setupCards.value.length > 0 && setupCards.value.every((card) => isCardComplete(card));
});
/**
* Tests a saved credential in the background.
* Fetches the credential's redacted data first so the backend can unredact and test.
* Skips if the credential is already tested OK or has a test in flight.
* The result is tracked automatically in the credentials store as a side effect of testCredential.
*/
async function testCredentialInBackground(
credentialId: string,
credentialName: string,
credentialType: string,
) {
if (!isCredentialTypeTestable(credentialType)) {
return;
}
if (
credentialsStore.isCredentialTestedOk(credentialId) ||
credentialsStore.isCredentialTestPending(credentialId)
) {
return;
}
try {
const credentialResponse = await credentialsStore.getCredentialData({ id: credentialId });
if (!credentialResponse?.data || typeof credentialResponse.data === 'string') {
return;
}
// Re-check after the async fetch — another caller (e.g. CredentialEdit) may have
// started or completed a test while we were fetching credential data.
if (
credentialsStore.isCredentialTestedOk(credentialId) ||
credentialsStore.isCredentialTestPending(credentialId)
) {
return;
}
const { ownedBy, sharedWithProjects, oauthTokenData, ...data } = credentialResponse.data;
// OAuth credentials can't be tested via the API — the presence of token data
// means the OAuth flow completed successfully, which is the equivalent of a passing test.
if (oauthTokenData) {
credentialsStore.credentialTestResults.set(credentialId, 'success');
return;
}
await credentialsStore.testCredential({
id: credentialId,
name: credentialName,
type: credentialType,
data: data as ICredentialDataDecryptedObject,
});
} catch {
// Test failure is tracked in the store as a side effect
}
}
/**
* Resolves the node names affected by a credential operation.
* Checks credentialTypeStates first (grouped cards), then falls back to

View File

@ -825,6 +825,65 @@ describe('setupPanel.utils', () => {
expect(issues).toHaveProperty('event');
});
it('should detect required parameter issues when a controlling default has same-named siblings', () => {
// Mirrors OpenAI v2's shape: a single `resource` parameter controls
// which `operation` (and which `modelId`) is displayed. Naively
// filling defaults picks the first `operation` regardless of resource.
const nodeType = {
properties: [
createTestNodeProperties({
displayName: 'Resource',
name: 'resource',
type: 'options',
default: 'text',
options: [
{ name: 'Audio', value: 'audio' },
{ name: 'Text', value: 'text' },
],
}),
createTestNodeProperties({
displayName: 'Operation',
name: 'operation',
type: 'options',
default: 'generate',
displayOptions: { show: { resource: ['audio'] } },
}),
createTestNodeProperties({
displayName: 'Operation',
name: 'operation',
type: 'options',
default: 'response',
displayOptions: { show: { resource: ['text'] } },
}),
createTestNodeProperties({
displayName: 'Model',
name: 'modelId',
type: 'resourceLocator',
required: true,
default: { mode: 'list', value: '' },
modes: [
{ displayName: 'From List', name: 'list', type: 'list' },
{ displayName: 'ID', name: 'id', type: 'string' },
],
displayOptions: { show: { resource: ['text'], operation: ['response'] } },
}),
],
} as unknown as INodeTypeDescription;
mockNodeTypeProvider.getNodeType.mockReturnValue(nodeType);
const node = createTestNode({
type: '@n8n/n8n-nodes-langchain.openAi',
parameters: {
modelId: { __rl: true, value: '', mode: 'id' },
},
});
const issues = getNodeParametersIssues(mockNodeTypeProvider, node);
expect(issues).toHaveProperty('modelId');
});
it('should not include issues for parameter variants that are not displayed', () => {
const nodeType = {
properties: [

View File

@ -54,13 +54,20 @@ export function getNodeParametersIssues(nodeTypesStore: NodeTypeProvider, node:
// Fill in default values for parameters not explicitly set on the node.
// Required parameters with valid defaults (e.g. binaryPropertyName: 'data')
// are not stored in node.parameters when the user hasn't changed them.
// Without this, the issue checker flags them as missing.
const paramsWithDefaults: INodeParameters = { ...node.parameters };
for (const prop of nodeType.properties) {
if (!(prop.name in paramsWithDefaults) && prop.default !== undefined) {
paramsWithDefaults[prop.name] = prop.default;
}
}
// Without this, the issue checker flags them as missing. We delegate to
// `getNodeParameters` so duplicate property names gated by sibling values
// (e.g. OpenAI v2's per-resource `operation`) resolve to the displayed
// variant rather than whichever appears first.
const resolved =
NodeHelpers.getNodeParameters(
nodeType.properties,
node.parameters,
true,
false,
node,
nodeType,
) ?? node.parameters;
const paramsWithDefaults: INodeParameters = resolved;
const nodeWithDefaults: INode = { ...node, parameters: paramsWithDefaults };
const issues = NodeHelpers.getNodeParametersIssues(

View File

@ -0,0 +1,111 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*Set up the workflow named \\\\\"B3 Full Wizard Apply\\\\\"\\.[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1537"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-7b6848ead6ef5299b7c92d8647b466a9-f2c7a00c5b42fbf1-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=MBWXL_g7CBwGNs50BPJBIaysg9mWasTGrLZPFtzgigU-1777974532.0862312-1.0.1.1-HY1cG9pPyykspwrSUnBkELqKKkO.v.ZlF97l1SzvQrU; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CajDGBCfXLU1Zk33EGnAW"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:48:52Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:48:52Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:48:52Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:48:52Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:48:53 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed4f98ebfbda6-SOF"
]
},
"cookies": {
"_cfuvid": "MBWXL_g7CBwGNs50BPJBIaysg9mWasTGrLZPFtzgigU-1777974532.0862312-1.0.1.1-HY1cG9pPyykspwrSUnBkELqKKkO.v.ZlF97l1SzvQrU"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01DA2PqfvA5THfKrLKuUZ7oD\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":337,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":13705,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":45,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_012sbwda3uPiXbX9AbYTKP8D\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"action\\\": \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"list\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"q\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"uery\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\": \\\"B3 F\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ull Wizard \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Apply\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":337,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"output_tokens\":75} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxREEyUHFmdkE1VEhmS3JMS3VVWjdvRCIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMzNywiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjEzNzA1LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6NDUsInNlcnZpY2VfdGllciI6InN0YW5kYXJkIiwiaW5mZXJlbmNlX2dlbyI6Imdsb2JhbCJ9fSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRvb2xfdXNlIiwiaWQiOiJ0b29sdV8wMTJzYndkYTN1UGlYYlg5QWJZVEtQOEQiLCJuYW1lIjoid29ya2Zsb3dzIiwiaW5wdXQiOnt9LCJjYWxsZXIiOnsidHlwZSI6ImRpcmVjdCJ9fSAgICAgICAgIH0KCmV2ZW50OiBwaW5nCmRhdGE6IHsidHlwZSI6ICJwaW5nIn0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIifSAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IntcImFjdGlvblwiOiBcIiJ9ICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoibGlzdFwiIn0gICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcInEifSAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ1ZXJ5XCIifSAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiI6IFwiQjMgRiJ9IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ1bGwgV2l6YXJkICJ9ICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IkFwcGx5XCJ9In0gICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RvcApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0b3AiLCJpbmRleCI6MCAgICAgICAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoidG9vbF91c2UiLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMzNywiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsIm91dHB1dF90b2tlbnMiOjc1fSAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0000-1777974540866-unknown-host-POST-_v1_messages-abc49c4f.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,111 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"list\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1158"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-ecd5ea4d95aa8ba662ef9b7a504d11ec-f600a43185e6a636-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=XDew3.HSrrls1SiRYRK1WKQULHmprCKZSXH7EdLYsZM-1777974534.2739832-1.0.1.1-ortTOOCCp0oe6wj0TwrvU5aPn5bpVWPIu0gRjQfKFuU; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CajDGLZUvRhvUR6SYgZLq"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:48:54Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:48:54Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:48:54Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:48:54Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:48:55 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed507383b4d27-SOF"
]
},
"cookies": {
"_cfuvid": "XDew3.HSrrls1SiRYRK1WKQULHmprCKZSXH7EdLYsZM-1777974534.2739832-1.0.1.1-ortTOOCCp0oe6wj0TwrvU5aPn5bpVWPIu0gRjQfKFuU"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_0154YYfT5XLEVhvpAuFUgPYS\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":118,\"cache_creation_input_tokens\":412,\"cache_read_input_tokens\":13705,\"cache_creation\":{\"ephemeral_5m_input_tokens\":412,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":58,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_014sXMa88NbSX5wTn3Y5CLuK\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"actio\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"n\\\": \\\"s\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"et\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"up\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"workf\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"lowId\\\": \\\"T\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"qaMpj2\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"sVOinXnc2\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":118,\"cache_creation_input_tokens\":412,\"cache_read_input_tokens\":13705,\"output_tokens\":84} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxNTRZWWZUNVhMRVZodnBBdUZVZ1BZUyIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjExOCwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjo0MTIsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6NDEyLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6NTgsInNlcnZpY2VfdGllciI6InN0YW5kYXJkIiwiaW5mZXJlbmNlX2dlbyI6Imdsb2JhbCJ9fSAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RhcnQKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdGFydCIsImluZGV4IjowLCJjb250ZW50X2Jsb2NrIjp7InR5cGUiOiJ0b29sX3VzZSIsImlkIjoidG9vbHVfMDE0c1hNYTg4TmJTWDV3VG4zWTVDTHVLIiwibmFtZSI6IndvcmtmbG93cyIsImlucHV0Ijp7fSwiY2FsbGVyIjp7InR5cGUiOiJkaXJlY3QifX0gICAgIH0KCmV2ZW50OiBwaW5nCmRhdGE6IHsidHlwZSI6ICJwaW5nIn0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIifSB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoie1wiYWN0aW8ifX0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJuXCI6IFwicyJ9ICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiZXQifSAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoidXBcIiJ9ICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcIndvcmtmIn0gICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoibG93SWRcIjogXCJUIn0gICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoicWFNcGoyIn0gICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoic1ZPaW5YbmMyXCJ9In0gICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdG9wCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RvcCIsImluZGV4IjowIH0KCmV2ZW50OiBtZXNzYWdlX2RlbHRhCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2VfZGVsdGEiLCJkZWx0YSI6eyJzdG9wX3JlYXNvbiI6InRvb2xfdXNlIiwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsfSwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjoxMTgsImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6NDEyLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MTM3MDUsIm91dHB1dF90b2tlbnMiOjg0fSAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgICAgICB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0001-1777974540867-unknown-host-POST-_v1_messages-c17e02f9.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,111 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"setup\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"700"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-2ac185af1f347fae7ab105c38dbaabb7-54ca64405ec77f42-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=Jd8ptkQ9X5Ym.YDeRPTnxb4AKwlyzj3Y_vWPCCrk4e0-1777974537.9154794-1.0.1.1-7_1kJLhFqe3Ilrhve0tfVLyjgCiVx7l9MvmovIEhVHI; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CajDGc8rScY2mmHZa3yvq"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:48:58Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:48:58Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:48:58Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:48:58Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:48:58 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed51dfef23134-SOF"
]
},
"cookies": {
"_cfuvid": "Jd8ptkQ9X5Ym.YDeRPTnxb4AKwlyzj3Y_vWPCCrk4e0-1777974537.9154794-1.0.1.1-7_1kJLhFqe3Ilrhve0tfVLyjgCiVx7l9MvmovIEhVHI"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01G8T58XDzfc5iQ6xxVo58yX\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":439,\"cache_creation_input_tokens\":412,\"cache_read_input_tokens\":13705,\"cache_creation\":{\"ephemeral_5m_input_tokens\":412,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"B3 Full Wizard Apply\\\" workflow has been set up successfully. Here's what was configured:\\n\\n- **HTTP\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Request node** — pointed to `https://example.com/api` using the **B3 Parameter Header Auth**\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" credential for authentication.\\n\\nThe workflow is saved and ready. Let me know if you'd like to\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" test it or make any changes.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":439,\"cache_creation_input_tokens\":412,\"cache_read_input_tokens\":13705,\"output_tokens\":82} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxRzhUNThYRHpmYzVpUTZ4eFZvNTh5WCIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjQzOSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjo0MTIsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6NDEyLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RhcnQKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdGFydCIsImluZGV4IjowLCJjb250ZW50X2Jsb2NrIjp7InR5cGUiOiJ0ZXh0IiwidGV4dCI6IiJ9ICAgICAgICAgICAgICB9CgpldmVudDogcGluZwpkYXRhOiB7InR5cGUiOiAicGluZyJ9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IlRoZSJ9ICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgXCJCMyBGdWxsIFdpemFyZCBBcHBseVwiIHdvcmtmbG93IGhhcyBiZWVuIHNldCB1cCBzdWNjZXNzZnVsbHkuIEhlcmUncyB3aGF0IHdhcyBjb25maWd1cmVkOlxuXG4tICoqSFRUUCJ9ICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiBSZXF1ZXN0IG5vZGUqKiDigJQgcG9pbnRlZCB0byBgaHR0cHM6Ly9leGFtcGxlLmNvbS9hcGlgIHVzaW5nIHRoZSAqKkIzIFBhcmFtZXRlciBIZWFkZXIgQXV0aCoqIn0gICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgY3JlZGVudGlhbCBmb3IgYXV0aGVudGljYXRpb24uXG5cblRoZSB3b3JrZmxvdyBpcyBzYXZlZCBhbmQgcmVhZHkuIExldCBtZSBrbm93IGlmIHlvdSdkIGxpa2UgdG8ifSAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgdGVzdCBpdCBvciBtYWtlIGFueSBjaGFuZ2VzLiJ9ICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoiZW5kX3R1cm4iLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjQzOSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjo0MTIsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMzcwNSwib3V0cHV0X3Rva2VucyI6ODJ9ICB9CgpldmVudDogbWVzc2FnZV9zdG9wCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2Vfc3RvcCIgICAgICB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0002-1777974540867-unknown-host-POST-_v1_messages-97aea00c.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,111 +0,0 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are an expert n8n workflow builder\\. You generate com[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"build-workflow\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1027"
],
"vary": [
"Accept-Encoding"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=dR33gMXkzAc6j93mq2ZP3OP4FErgVaPt3SdJgr02y6Q-1777373966.9623053-1.0.1.1-53qNyPi9.nhalsyxtD_3E9Hd2o3Oa.lByByK6yec8yk; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1031"
],
"request-id": [
"req_011CaW3ywEz1up5JV3ACYHNi"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-28T10:59:27Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26966000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-28T10:59:27Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-28T10:59:27Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-28T10:59:27Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22466000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 28 Apr 2026 10:59:28 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f358ebd8c03b58b-PRG"
]
},
"cookies": {
"_cfuvid": "dR33gMXkzAc6j93mq2ZP3OP4FErgVaPt3SdJgr02y6Q-1777373966.9623053-1.0.1.1-53qNyPi9.nhalsyxtD_3E9Hd2o3Oa.lByByK6yec8yk"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01TMAHdn4NsZzBxnR9hqa9JW\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":695,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":21523,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":3,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Workflow is\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" ready. Click the Manual Trigger and hit \\\"Test workflow\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\\" to run it — then customize the Set node to add whatever fields you need.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":695,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":21523,\"output_tokens\":35} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxVE1BSGRuNE5zWnpCeG5SOWhxYTlKVyIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjY5NSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MjE1MjMsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjAsImVwaGVtZXJhbF8xaF9pbnB1dF90b2tlbnMiOjB9LCJvdXRwdXRfdG9rZW5zIjozLCJzZXJ2aWNlX3RpZXIiOiJzdGFuZGFyZCIsImluZmVyZW5jZV9nZW8iOiJnbG9iYWwifX0gICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gICAgICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJXb3JrZmxvdyBpcyJ9fQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgcmVhZHkuIENsaWNrIHRoZSBNYW51YWwgVHJpZ2dlciBhbmQgaGl0IFwiVGVzdCB3b3JrZmxvdyJ9ICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJcIiB0byBydW4gaXQg4oCUIHRoZW4gY3VzdG9taXplIHRoZSBTZXQgbm9kZSB0byBhZGQgd2hhdGV2ZXIgZmllbGRzIHlvdSBuZWVkLiJ9ICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICAgICAgICB9CgpldmVudDogbWVzc2FnZV9kZWx0YQpkYXRhOiB7InR5cGUiOiJtZXNzYWdlX2RlbHRhIiwiZGVsdGEiOnsic3RvcF9yZWFzb24iOiJlbmRfdHVybiIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInN0b3BfZGV0YWlscyI6bnVsbH0sInVzYWdlIjp7ImlucHV0X3Rva2VucyI6Njk1LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjAsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoyMTUyMywib3V0cHV0X3Rva2VucyI6MzV9ICAgfQoKZXZlbnQ6IG1lc3NhZ2Vfc3RvcApkYXRhOiB7InR5cGUiOiJtZXNzYWdlX3N0b3AiICAgICAgICAgfQoK",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "1777373979523-unknown-host-POST-_v1_messages-e7a67275.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,111 +0,0 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*Set up the workflow named \\\\\"B3 Full Wizard Apply\\\\\"\\.[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1129"
],
"vary": [
"Accept-Encoding"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=pnbMU0JKL6VIeGtoQmnMJjyYTJBjJYTB19126AmlRvI-1777373973.2591949-1.0.1.1-JHRmYS_Sh_ZJas5yscrNAHLciVGqvmac3_N.GZu4DgI; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1131"
],
"request-id": [
"req_011CaW3zQAEertU27yw7WpZR"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-28T10:59:33Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-28T10:59:33Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-28T10:59:33Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-28T10:59:33Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 28 Apr 2026 10:59:34 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f358ee4d86ddf72-PRG"
]
},
"cookies": {
"_cfuvid": "pnbMU0JKL6VIeGtoQmnMJjyYTJBjJYTB19126AmlRvI-1777373973.2591949-1.0.1.1-JHRmYS_Sh_ZJas5yscrNAHLciVGqvmac3_N.GZu4DgI"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01KTnakuKpWLGnRMwaQzgKdV\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":337,\"cache_creation_input_tokens\":14091,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":14091,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Let\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" me look up that workflow first.\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01A94TsW82xTinxnAX4yAVF6\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"act\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"io\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"n\\\": \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"list\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"query\\\": \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"B3 \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Full Wiza\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"rd Apply\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":337,\"cache_creation_input_tokens\":14091,\"cache_read_input_tokens\":0,\"output_tokens\":83} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxS1RuYWt1S3BXTEduUk13YVF6Z0tkViIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMzNywiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxNDA5MSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjE0MDkxLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gICAgICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJMZXQifX0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6InRleHRfZGVsdGEiLCJ0ZXh0IjoiIG1lIGxvb2sgdXAgdGhhdCB3b3JrZmxvdyBmaXJzdC4ifX0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RhcnQKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdGFydCIsImluZGV4IjoxLCJjb250ZW50X2Jsb2NrIjp7InR5cGUiOiJ0b29sX3VzZSIsImlkIjoidG9vbHVfMDFBOTRUc1c4MnhUaW54bkFYNHlBVkY2IiwibmFtZSI6IndvcmtmbG93cyIsImlucHV0Ijp7fSwiY2FsbGVyIjp7InR5cGUiOiJkaXJlY3QifX0gICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiJ9fQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IntcImFjdCJ9ICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJpbyJ9ICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJuXCI6ICJ9ICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiXCJsaXN0XCIifSAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIsIFwicXVlcnlcIjogIn0gIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJcIkIzICJ9IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJGdWxsIFdpemEifSAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6InJkIEFwcGx5In0gICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiXCJ9In0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdG9wCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RvcCIsImluZGV4IjoxICAgICAgICAgICB9CgpldmVudDogbWVzc2FnZV9kZWx0YQpkYXRhOiB7InR5cGUiOiJtZXNzYWdlX2RlbHRhIiwiZGVsdGEiOnsic3RvcF9yZWFzb24iOiJ0b29sX3VzZSIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInN0b3BfZGV0YWlscyI6bnVsbH0sInVzYWdlIjp7ImlucHV0X3Rva2VucyI6MzM3LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjE0MDkxLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MCwib3V0cHV0X3Rva2VucyI6ODN9ICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgIH0KCg==",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "1777373979524-unknown-host-POST-_v1_messages-abc49c4f.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,105 +0,0 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"list\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1417"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=7zOcfTVfjLjlOdvU7FDFjOiBGngj.K.vU1IjWO.Cf3U-1777373975.836431-1.0.1.1-VJw6NTGZjJMQCc02tAtKZbsaW0ISTztfRQAjr6vOBEg; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CaW3zbFjE4WwUkSNComLY"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-28T10:59:35Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-28T10:59:35Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-28T10:59:35Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-28T10:59:36Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 28 Apr 2026 10:59:37 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f358ef4fb3cbb9c-PRG"
]
},
"cookies": {
"_cfuvid": "7zOcfTVfjLjlOdvU7FDFjOiBGngj.K.vU1IjWO.Cf3U-1777373975.836431-1.0.1.1-VJw6NTGZjJMQCc02tAtKZbsaW0ISTztfRQAjr6vOBEg"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_018sM6MviEiG3T1QP1ktkjqj\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":541,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":14091,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":68,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01Qm84eUUf1P3CfD4Yk56bSc\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"act\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ion\\\": \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"setup\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"work\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"flow\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Id\\\": \\\"cKA\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"KMg8cquA\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"9y35\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"K\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":541,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":14091,\"output_tokens\":84} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxOHNNNk12aUVpRzNUMVFQMWt0a2pxaiIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjU0MSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MTQwOTEsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjAsImVwaGVtZXJhbF8xaF9pbnB1dF90b2tlbnMiOjB9LCJvdXRwdXRfdG9rZW5zIjo2OCwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RhcnQKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdGFydCIsImluZGV4IjowLCJjb250ZW50X2Jsb2NrIjp7InR5cGUiOiJ0b29sX3VzZSIsImlkIjoidG9vbHVfMDFRbTg0ZVVVZjFQM0NmRDRZazU2YlNjIiwibmFtZSI6IndvcmtmbG93cyIsImlucHV0Ijp7fSwiY2FsbGVyIjp7InR5cGUiOiJkaXJlY3QifX0gICAgICAgICAgICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiJ9IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ7XCIifSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiYWN0In0gICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiaW9uXCI6IFwiIn0gICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6InNldHVwXCIifSAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiwgXCJ3b3JrIn0gICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJmbG93In0gICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJJZFwiOiBcImNLQSJ9IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJLTWc4Y3F1QSJ9ICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiI5eTM1In0gICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiS1wifSJ9ICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdG9wCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RvcCIsImluZGV4IjowICAgICAgICAgICAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoidG9vbF91c2UiLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjU0MSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MTQwOTEsIm91dHB1dF90b2tlbnMiOjg0fSAgICAgICB9CgpldmVudDogbWVzc2FnZV9zdG9wCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2Vfc3RvcCJ9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "1777373979525-unknown-host-POST-_v1_messages-c17e02f9.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,4 +1,4 @@
{"kind":"header","version":1,"testName":"recording","recordedAt":"2026-04-28T10:59:33.145Z"}
{"kind":"tool-call","stepId":1,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"list","query":"B3 Full Wizard Apply"},"output":{"workflows":[{"id":"cKAKMg8cquA9y35K","name":"B3 Full Wizard Apply","versionId":"1e867e5e-50fd-4326-8c0f-6ad69d9616aa","activeVersionId":null,"createdAt":"2026-04-28T10:59:29.306Z","updatedAt":"2026-04-28T10:59:29.306Z"}]}}
{"kind":"tool-call","stepId":2,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"cKAKMg8cquA9y35K"},"output":{"success":false}}
{"kind":"tool-resume","stepId":3,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"cKAKMg8cquA9y35K"},"output":{"success":true,"completedNodes":[{"nodeName":"HTTP Request","parametersSet":["url"]}],"updatedNodes":[{"id":"trigger","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[0,0],"parameters":{}},{"id":"http","name":"HTTP Request","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[220,-120],"parameters":{"method":"GET","url":"https://example.com/api","authentication":"none"}},{"id":"slack","name":"Slack","type":"n8n-nodes-base.slack","typeVersion":2.3,"position":[220,120],"parameters":{"resource":"message","operation":"post","select":"channel","channelId":{"__rl":true,"value":"C0000000000","mode":"id"},"text":"hello from setup e2e"},"credentials":{"slackApi":{"id":"oR4HYz4azgE1NUGj","name":"B3 Apply Slack Primary"}}}],"updatedConnections":{"Manual Trigger":{"main":[[{"node":"HTTP Request","type":"main","index":0},{"node":"Slack","type":"main","index":0}]]}}},"resumeData":{"approved":true,"credentials":{},"action":"apply","nodeParameters":{"HTTP Request":{"url":"https://example.com/api"}}}}
{"kind":"header","version":1,"testName":"recording","recordedAt":"2026-05-05T09:48:51.817Z"}
{"kind":"tool-call","stepId":1,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"list","query":"B3 Full Wizard Apply"},"output":{"workflows":[{"id":"TqaMpj2sVOinXnc2","name":"B3 Full Wizard Apply","versionId":"ba829dcd-395c-4fe4-8f23-43f8ca963363","activeVersionId":null,"createdAt":"2026-05-05T09:48:47.635Z","updatedAt":"2026-05-05T09:48:47.635Z"}]}}
{"kind":"tool-call","stepId":2,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"TqaMpj2sVOinXnc2"},"output":{"success":false}}
{"kind":"tool-resume","stepId":3,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"TqaMpj2sVOinXnc2"},"output":{"success":true,"completedNodes":[{"nodeName":"HTTP Request","credentialType":"httpHeaderAuth","parametersSet":["url"]}],"updatedNodes":[{"id":"trigger","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[0,0],"parameters":{}},{"id":"http","name":"HTTP Request","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[220,0],"parameters":{"method":"GET","url":"https://example.com/api","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"00vl3Jz8mVoLa7Vx","name":"B3 Parameter Header Auth"}}}],"updatedConnections":{"Manual Trigger":{"main":[[{"node":"HTTP Request","type":"main","index":0}]]}}},"resumeData":{"approved":true,"credentials":{"HTTP Request":{"httpHeaderAuth":"00vl3Jz8mVoLa7Vx"}},"action":"apply","nodeParameters":{"HTTP Request":{"url":"https://example.com/api"}}}}

View File

@ -0,0 +1,114 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*Set up the workflow named \\\\\"B3 Workflow Setup Required Parameter\\\\\"\\.[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1097"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-055e95bb61ad3a581def7cc481d142c3-c5c8f23e44a58a6d-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=25c5Yn3ZqlCuityQInH8XiuYNdLtqIj7W8cmunRCqT4-1777974520.6361456-1.0.1.1-SB7A0ms2uQtXbGXDPhfow7KZVLZ2tnn0Fdx.lvfG00w; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1106"
],
"request-id": [
"req_011CajDFLCNVz755J5ZKu2hP"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:48:40Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:48:40Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:48:40Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:48:40Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:48:41 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed4b1fd9f3402-SOF"
]
},
"cookies": {
"_cfuvid": "25c5Yn3ZqlCuityQInH8XiuYNdLtqIj7W8cmunRCqT4-1777974520.6361456-1.0.1.1-SB7A0ms2uQtXbGXDPhfow7KZVLZ2tnn0Fdx.lvfG00w"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_013EmqGt1UJyrCs1kdNCcZ1B\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":338,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":13705,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"I\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"'ll look up that workflow for you right away.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01Anjf1vTeRd67d4z6cvPFdX\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"action\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\": \\\"list\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"query\\\":\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" \\\"B3\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" Workflo\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"w Setup Re\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"qu\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ired\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" Param\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"eter\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":338,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"output_tokens\":87} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxM0VtcUd0MVVKeXJDczFrZE5DY1oxQiIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMzOCwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjEzNzA1LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gICAgICAgICAgICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJJIn0gICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IidsbCBsb29rIHVwIHRoYXQgd29ya2Zsb3cgZm9yIHlvdSByaWdodCBhd2F5LiJ9IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0YXJ0CmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RhcnQiLCJpbmRleCI6MSwiY29udGVudF9ibG9jayI6eyJ0eXBlIjoidG9vbF91c2UiLCJpZCI6InRvb2x1XzAxQW5qZjF2VGVSZDY3ZDR6NmN2UEZkWCIsIm5hbWUiOiJ3b3JrZmxvd3MiLCJpbnB1dCI6e30sImNhbGxlciI6eyJ0eXBlIjoiZGlyZWN0In19ICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIifSAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IntcImFjdGlvbiJ9IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJcIjogXCJsaXN0XCIifSAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcInF1ZXJ5XCI6In19CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIFwiQjMifSAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiBXb3JrZmxvIn0gICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ3IFNldHVwIFJlIn0gICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6InF1In0gfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImlyZWQifSAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIgUGFyYW0ifSAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImV0ZXJcIn0ifSAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjEgICAgICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX2RlbHRhCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2VfZGVsdGEiLCJkZWx0YSI6eyJzdG9wX3JlYXNvbiI6InRvb2xfdXNlIiwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsfSwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjozMzgsImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MTM3MDUsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjowLCJvdXRwdXRfdG9rZW5zIjo4N30gICAgICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgICAgICAgIH0KCg==",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0000-1777974525416-unknown-host-POST-_v1_messages-60adeed8.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,114 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"list\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1465"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-c4bf6a54621c37b6ccb45ce230081995-7ad5ba507c1529cb-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=ttfH162zobw9.tTADu7gmQVr0clci6NMsjoDRvAJ69g-1777974522.8463345-1.0.1.1-YIIhigPWxl_LGPv8xmi6PxCPYnP16T4ijo6LJwwWAJQ; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1477"
],
"request-id": [
"req_011CajDFVbvKiNGWLhrALLnn"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:48:42Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:48:42Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:48:42Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:48:43Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:48:44 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed4bfcf508693-SOF"
]
},
"cookies": {
"_cfuvid": "ttfH162zobw9.tTADu7gmQVr0clci6NMsjoDRvAJ69g-1777974522.8463345-1.0.1.1-YIIhigPWxl_LGPv8xmi6PxCPYnP16T4ijo6LJwwWAJQ"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01Eeute14cfYF47DGKWT5UGz\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":201,\"cache_creation_input_tokens\":349,\"cache_read_input_tokens\":13705,\"cache_creation\":{\"ephemeral_5m_input_tokens\":349,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":68,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01VErWsfZtqVuBTh3XZ3LKDA\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"action\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\": \\\"setup\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"workflow\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Id\\\": \\\"3jbqn6\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Rx4TScS58\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"P\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":201,\"cache_creation_input_tokens\":349,\"cache_read_input_tokens\":13705,\"output_tokens\":84} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxRWV1dGUxNGNmWUY0N0RHS1dUNVVHeiIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjIwMSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjozNDksImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6MzQ5LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6NjgsInNlcnZpY2VfdGllciI6InN0YW5kYXJkIiwiaW5mZXJlbmNlX2dlbyI6Imdsb2JhbCJ9fSAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0YXJ0CmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RhcnQiLCJpbmRleCI6MCwiY29udGVudF9ibG9jayI6eyJ0eXBlIjoidG9vbF91c2UiLCJpZCI6InRvb2x1XzAxVkVyV3NmWnRxVnVCVGgzWFozTEtEQSIsIm5hbWUiOiJ3b3JrZmxvd3MiLCJpbnB1dCI6e30sImNhbGxlciI6eyJ0eXBlIjoiZGlyZWN0In19ICAgICAgIH0KCmV2ZW50OiBwaW5nCmRhdGE6IHsidHlwZSI6ICJwaW5nIn0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIifSAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoie1wiYWN0aW9uXCIifSAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IjogXCJzZXR1cFwiIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcIndvcmtmbG93In0gICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IklkXCI6IFwiM2picW42In0gICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IlJ4NFRTY1M1OCJ9ICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJQXCJ9In0gICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICAgICB9CgpldmVudDogbWVzc2FnZV9kZWx0YQpkYXRhOiB7InR5cGUiOiJtZXNzYWdlX2RlbHRhIiwiZGVsdGEiOnsic3RvcF9yZWFzb24iOiJ0b29sX3VzZSIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInN0b3BfZGV0YWlscyI6bnVsbH0sInVzYWdlIjp7ImlucHV0X3Rva2VucyI6MjAxLCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjM0OSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjEzNzA1LCJvdXRwdXRfdG9rZW5zIjo4NH0gICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgICAgIH0KCg==",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0001-1777974525417-unknown-host-POST-_v1_messages-c17e02f9.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,112 +0,0 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "STRING",
"string": "[{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde",
"subString": true
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1080"
],
"vary": [
"Accept-Encoding"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=bHDKEwSZj0UbZxAc3kzMgNWY36YeQ_Wkck4I_6ElBE0-1776687502.979579-1.0.1.1-HpcePCCZ_s.G1MNIw7PTRA2yHfehuuwEuXx1Xd9o.9w; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1082"
],
"request-id": [
"req_011CaF1XisaG1B6X4RNVqcHC"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-20T12:18:23Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26981000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-20T12:18:23Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-20T12:18:23Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-20T12:18:23Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22481000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Mon, 20 Apr 2026 12:18:24 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9ef4175d980bdc1a-SOF"
]
},
"cookies": {
"_cfuvid": "bHDKEwSZj0UbZxAc3kzMgNWY36YeQ_Wkck4I_6ElBE0-1776687502.979579-1.0.1.1-HpcePCCZ_s.G1MNIw7PTRA2yHfehuuwEuXx1Xd9o.9w"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01C2H8f7mG5vTLLQhGQsBNBb\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":892,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10216,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" build finished. Let me check the result and open the setup UI.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_018UpsQynyaMDwpXbo1TLB5g\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"action\\\": \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"list\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"qu\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ery\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\": \\\"Manu\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"al Tri\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"gger HTTP Re\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"quest\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":892,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10216,\"output_tokens\":88} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxQzJIOGY3bUc1dlRMTFFoR1FzQk5CYiIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjg5MiwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MTAyMTYsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjAsImVwaGVtZXJhbF8xaF9pbnB1dF90b2tlbnMiOjB9LCJvdXRwdXRfdG9rZW5zIjoxLCJzZXJ2aWNlX3RpZXIiOiJzdGFuZGFyZCIsImluZmVyZW5jZV9nZW8iOiJnbG9iYWwifX0gICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RhcnQKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdGFydCIsImluZGV4IjowLCJjb250ZW50X2Jsb2NrIjp7InR5cGUiOiJ0ZXh0IiwidGV4dCI6IiJ9ICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJUaGUifSAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiBidWlsZCBmaW5pc2hlZC4gTGV0IG1lIGNoZWNrIHRoZSByZXN1bHQgYW5kIG9wZW4gdGhlIHNldHVwIFVJLiJ9ICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RvcApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0b3AiLCJpbmRleCI6MCAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0YXJ0CmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RhcnQiLCJpbmRleCI6MSwiY29udGVudF9ibG9jayI6eyJ0eXBlIjoidG9vbF91c2UiLCJpZCI6InRvb2x1XzAxOFVwc1F5bnlhTUR3cFhibzFUTEI1ZyIsIm5hbWUiOiJ3b3JrZmxvd3MiLCJpbnB1dCI6e30sImNhbGxlciI6eyJ0eXBlIjoiZGlyZWN0In19ICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoie1wiIn0gICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJhY3Rpb25cIjogIn0gICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJcImxpc3RcIiJ9ICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiwgXCJxdSJ9ICAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImVyeVwiIn0gfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IjogXCJNYW51In0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiYWwgVHJpIn19CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiZ2dlciBIVFRQIFJlIn0gICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6InF1ZXN0XCJ9In0gIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjEgICAgICAgICAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoidG9vbF91c2UiLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjg5MiwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MTAyMTYsIm91dHB1dF90b2tlbnMiOjg4fSAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgfQoK",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "1776687509148-unknown-host-POST-_v1_messages-8a23f6c2.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,109 +0,0 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "STRING",
"string": "[{\"type\":\"text\",\"text\":\"You are an expert n8n workflow builder. You generate com",
"subString": true
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1840"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=B3YlQaTOUCq5YX.6Q.6NOvaLP44CbxrEhBLuzoU0H0E-1776687504.8906143-1.0.1.1-tL12YVyB0aycHSm2yFU11Sbbgt.YrN6jEC.9NHgqs78; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1844"
],
"request-id": [
"req_011CaF1Xs7BL1UZrtDShLWKY"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26964000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22464000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Mon, 20 Apr 2026 12:18:26 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9ef417698faadc3a-SOF"
]
},
"cookies": {
"_cfuvid": "B3YlQaTOUCq5YX.6Q.6NOvaLP44CbxrEhBLuzoU0H0E-1776687504.8906143-1.0.1.1-tL12YVyB0aycHSm2yFU11Sbbgt.YrN6jEC.9NHgqs78"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01AxZQvjdQ5bp7n8XudaqJTu\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":723,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":23018,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Done\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\". The workflow is built with a Manual Trigger connected to an HTTP Request node. The URL field is left blank\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" for you to fill in. You can open the HTTP Request node and enter your URL whenever\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" you're ready.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":723,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":23018,\"output_tokens\":49} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxQXhaUXZqZFE1YnA3bjhYdWRhcUpUdSIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjcyMywiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MjMwMTgsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjAsImVwaGVtZXJhbF8xaF9pbnB1dF90b2tlbnMiOjB9LCJvdXRwdXRfdG9rZW5zIjoxLCJzZXJ2aWNlX3RpZXIiOiJzdGFuZGFyZCIsImluZmVyZW5jZV9nZW8iOiJnbG9iYWwifX0gICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RhcnQKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdGFydCIsImluZGV4IjowLCJjb250ZW50X2Jsb2NrIjp7InR5cGUiOiJ0ZXh0IiwidGV4dCI6IiJ9ICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJEb25lIn0gICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIuIFRoZSB3b3JrZmxvdyBpcyBidWlsdCB3aXRoIGEgTWFudWFsIFRyaWdnZXIgY29ubmVjdGVkIHRvIGFuIEhUVFAgUmVxdWVzdCBub2RlLiBUaGUgVVJMIGZpZWxkIGlzIGxlZnQgYmxhbmsifSAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgZm9yIHlvdSB0byBmaWxsIGluLiBZb3UgY2FuIG9wZW4gdGhlIEhUVFAgUmVxdWVzdCBub2RlIGFuZCBlbnRlciB5b3VyIFVSTCB3aGVuZXZlciJ9ICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiB5b3UncmUgcmVhZHkuIn0gICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX2RlbHRhCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2VfZGVsdGEiLCJkZWx0YSI6eyJzdG9wX3JlYXNvbiI6ImVuZF90dXJuIiwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsfSwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjo3MjMsImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MCwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjIzMDE4LCJvdXRwdXRfdG9rZW5zIjo0OX0gICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "1776687509149-unknown-host-POST-_v1_messages-4d1c93f7.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,112 +0,0 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "STRING",
"string": "[{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde",
"subString": true
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1499"
],
"vary": [
"Accept-Encoding"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=nFEe9FD2a3EYbnGXy3muJuoPJomRyAN9n_YU8BntQCk-1776687505.58011-1.0.1.1-vMaUc8nGBB7TrIV0zjzo6tvmL214gtdAWNqRUkY41xs; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1501"
],
"request-id": [
"req_011CaF1Xv1Jm8rBhNF3rYNfz"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26981000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-20T12:18:25Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22481000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Mon, 20 Apr 2026 12:18:27 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9ef4176dd8013130-SOF"
]
},
"cookies": {
"_cfuvid": "nFEe9FD2a3EYbnGXy3muJuoPJomRyAN9n_YU8BntQCk-1776687505.58011-1.0.1.1-vMaUc8nGBB7TrIV0zjzo6tvmL214gtdAWNqRUkY41xs"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_014fQPmj4epDVaCfp2Ny5gyf\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":1101,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10216,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":57,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01GKcDBgzprj2kT4zpjV9W3M\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"act\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ion\\\": \\\"se\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"tup\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"wor\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"kflo\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"wI\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"d\\\": \\\"vZSmhQk\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Ge5S\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"p1s\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Op\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"work\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"flowNa\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"me\\\":\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" \\\"Manual Tri\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"gger - HTT\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"P Request\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":1101,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10216,\"output_tokens\":110} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxNGZRUG1qNGVwRFZhQ2ZwMk55NWd5ZiIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjExMDEsImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MCwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjEwMjE2LCJjYWNoZV9jcmVhdGlvbiI6eyJlcGhlbWVyYWxfNW1faW5wdXRfdG9rZW5zIjowLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6NTcsInNlcnZpY2VfdGllciI6InN0YW5kYXJkIiwiaW5mZXJlbmNlX2dlbyI6Imdsb2JhbCJ9fSAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RhcnQKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdGFydCIsImluZGV4IjowLCJjb250ZW50X2Jsb2NrIjp7InR5cGUiOiJ0b29sX3VzZSIsImlkIjoidG9vbHVfMDFHS2NEQmd6cHJqMmtUNHpwalY5VzNNIiwibmFtZSI6IndvcmtmbG93cyIsImlucHV0Ijp7fSwiY2FsbGVyIjp7InR5cGUiOiJkaXJlY3QifX0gICAgICAgICAgIH0KCmV2ZW50OiBwaW5nCmRhdGE6IHsidHlwZSI6ICJwaW5nIn0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIifSAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ7XCJhY3QifSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiaW9uXCI6IFwic2UifSAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6InR1cFwiIn0gICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiwgXCJ3b3IifSAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoia2ZsbyJ9ICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IndJIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiZFwiOiBcInZaU21oUWsifSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiR2U1UyJ9ICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJwMXMifSAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJPcFwiIn0gfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiwgXCJ3b3JrIn0gICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJmbG93TmEifSAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoibWVcIjoifSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIFwiTWFudWFsIFRyaSJ9ICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImdnZXIgLSBIVFQifSAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IlAgUmVxdWVzdFwifSJ9ICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICB9CgpldmVudDogbWVzc2FnZV9kZWx0YQpkYXRhOiB7InR5cGUiOiJtZXNzYWdlX2RlbHRhIiwiZGVsdGEiOnsic3RvcF9yZWFzb24iOiJ0b29sX3VzZSIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInN0b3BfZGV0YWlscyI6bnVsbH0sInVzYWdlIjp7ImlucHV0X3Rva2VucyI6MTEwMSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MTAyMTYsIm91dHB1dF90b2tlbnMiOjExMH0gICAgICB9CgpldmVudDogbWVzc2FnZV9zdG9wCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2Vfc3RvcCIgICAgICAgICAgICB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "1776687509149-unknown-host-POST-_v1_messages-8a23f6c2.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -1,7 +1,3 @@
{"kind":"header","version":1,"testName":"recording","recordedAt":"2026-04-20T12:18:08.934Z"}
{"kind":"tool-call","stepId":1,"agentRole":"orchestrator","toolName":"build-workflow-with-agent","input":{"task":"Build a workflow with a Manual Trigger node connected to an HTTP Request node. Leave the URL field on the HTTP Request node empty/blank — do not fill in any URL. The user will configure it themselves after the workflow is built.","conversationContext":"User wants a simple workflow: Manual Trigger → HTTP Request node, with the URL left empty for them to fill in later. After building, the setup UI should be opened."},"output":{"result":"Workflow build started (task: build-JQ794Gxg). Reply with one short sentence — e.g. name what's being built. Do NOT summarize the plan or list details.","taskId":"build-JQ794Gxg"}}
{"kind":"tool-call","stepId":2,"agentRole":"orchestrator","toolName":"task-control","input":{"action":"update-checklist","tasks":[{"id":"1","description":"Build workflow with Manual Trigger and HTTP Request node (URL left empty)","status":"in_progress"},{"id":"2","description":"Open setup UI for the workflow","status":"todo"}]},"output":{"saved":true}}
{"kind":"tool-call","stepId":3,"agentRole":"orchestrator","toolName":"task-control","input":{"action":"update-checklist","tasks":[{"id":"1","description":"Build workflow with Manual Trigger and HTTP Request node (URL left empty)","status":"done"},{"id":"2","description":"Open setup UI for the workflow","status":"in_progress"}]},"output":{"saved":true}}
{"kind":"tool-call","stepId":4,"agentRole":"workflow-builder","toolName":"build-workflow","input":{"code":"\nimport { workflow, node, trigger } from '@n8n/workflow-sdk';\n\nconst manualTrigger = trigger({\n type: 'n8n-nodes-base.manualTrigger',\n version: 1,\n config: { name: 'Manual Trigger' }\n});\n\nconst httpRequest = node({\n type: 'n8n-nodes-base.httpRequest',\n version: 4.3,\n config: {\n name: 'HTTP Request',\n parameters: {\n method: 'GET',\n url: ''\n }\n }\n});\n\nexport default workflow('manual-http', 'Manual Trigger - HTTP Request')\n .add(manualTrigger)\n .to(httpRequest);\n","name":"Manual Trigger - HTTP Request"},"output":{"success":true,"workflowId":"vZSmhQkGe5Sp1sOp"}}
{"kind":"tool-call","stepId":5,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"list","query":"Manual Trigger HTTP Request"},"output":{"workflows":[{"id":"vZSmhQkGe5Sp1sOp","name":"Manual Trigger - HTTP Request","versionId":"fe5da20e-be6e-47f5-bf23-6baa86c174a0","activeVersionId":null,"createdAt":"2026-04-20T12:18:23.979Z","updatedAt":"2026-04-20T12:18:24.662Z"}]}}
{"kind":"tool-call","stepId":6,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"vZSmhQkGe5Sp1sOp","workflowName":"Manual Trigger - HTTP Request"},"output":{"success":false}}
{"kind":"header","version":1,"testName":"recording","recordedAt":"2026-05-05T09:48:40.369Z"}
{"kind":"tool-call","stepId":1,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"list","query":"B3 Workflow Setup Required Parameter"},"output":{"workflows":[{"id":"3jbqn6Rx4TScS58P","name":"B3 Workflow Setup Required Parameter","versionId":"cb6f9a5e-1491-4d19-a1f9-98663eeea4f2","activeVersionId":null,"createdAt":"2026-05-05T09:48:36.418Z","updatedAt":"2026-05-05T09:48:36.418Z"}]}}
{"kind":"tool-call","stepId":2,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"3jbqn6Rx4TScS58P"},"output":{"success":false}}

View File

@ -0,0 +1,114 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*Set up the workflow named \\\\\"B3 Workflow Setup Slack Credential Test\\\\\"\\.[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"724"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-557c876fd60eb3af3ac698bff7cda58d-197afb4db1546736-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=ExuHe4fibDRZVW53_CUUmseaqSsNQb.2BcmRbIFoR00-1777974547.672693-1.0.1.1-nd52Sh2u8G3QW.N35H2DUkleDZkmwrJ7jiYm6wVne4o; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=732"
],
"request-id": [
"req_011CajDHKjuCc56JNr1mFWE5"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:49:07Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:49:07Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:49:07Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:49:07Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:49:08 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed55af81c2ba5-SOF"
]
},
"cookies": {
"_cfuvid": "ExuHe4fibDRZVW53_CUUmseaqSsNQb.2BcmRbIFoR00-1777974547.672693-1.0.1.1-nd52Sh2u8G3QW.N35H2DUkleDZkmwrJ7jiYm6wVne4o"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01MXmaHCX5YrhS7captkqTza\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":341,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":13705,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Let\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" me find that workflow first.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01S2pJnjacMXRybkqtH8Dnko\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"action\\\": \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"li\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"st\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"quer\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"y\\\": \\\"B3 Work\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"flow\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" S\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"etup Slack\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" C\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"redenti\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"al Te\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"st\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":341,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"output_tokens\":86} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxTVhtYUhDWDVZcmhTN2NhcHRrcVR6YSIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjM0MSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjEzNzA1LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gICAgICAgICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJMZXQifSAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiBtZSBmaW5kIHRoYXQgd29ya2Zsb3cgZmlyc3QuIn0gIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjEsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRvb2xfdXNlIiwiaWQiOiJ0b29sdV8wMVMycEpuamFjTVhSeWJrcXRIOERua28iLCJuYW1lIjoid29ya2Zsb3dzIiwiaW5wdXQiOnt9LCJjYWxsZXIiOnsidHlwZSI6ImRpcmVjdCJ9fSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIn19CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoie1wiYWN0aW9uXCI6ICJ9ICAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IlwibGkifSAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoic3RcIiJ9ICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcInF1ZXIifSAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoieVwiOiBcIkIzIFdvcmsifX0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJmbG93In19CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIFMifSAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiZXR1cCBTbGFjayJ9ICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiBDIn19CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoicmVkZW50aSJ9ICAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImFsIFRlIn0gICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJzdFwifSJ9ICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RvcApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0b3AiLCJpbmRleCI6MSAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoidG9vbF91c2UiLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjM0MSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsIm91dHB1dF90b2tlbnMiOjg2fSAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgICB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0000-1777974556274-unknown-host-POST-_v1_messages-cd560ab0.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,111 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"list\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1200"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-33057778e4db9a3cf76b3142c14a3dd1-b75041e007a2329b-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=94hDettW.Ao0R7qDRThqdQFRFClOv6K8EClHAYDKakE-1777974549.6707172-1.0.1.1-hKAhZHmfSuFixJDKHxmivT9ZDeI9jfhazRMxpCMODBI; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CajDHUPoSVTnLuQX3mByj"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:49:09Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26940000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:49:09Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:49:09Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:49:09Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22440000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:49:11 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed5676e803135-SOF"
]
},
"cookies": {
"_cfuvid": "94hDettW.Ao0R7qDRThqdQFRFClOv6K8EClHAYDKakE-1777974549.6707172-1.0.1.1-hKAhZHmfSuFixJDKHxmivT9ZDeI9jfhazRMxpCMODBI"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01SpZzridkSEzGXUrdF6wmfu\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":204,\"cache_creation_input_tokens\":14053,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":14053,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":58,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01VAiV26VCFcz6GfUHbLTQuL\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"action\\\":\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" \\\"setup\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"wor\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"kflowId\\\": \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"fF6E\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"AxQoY1E6TMjK\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":204,\"cache_creation_input_tokens\":14053,\"cache_read_input_tokens\":0,\"output_tokens\":84} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxU3BaenJpZGtTRXpHWFVyZEY2d21mdSIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjIwNCwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxNDA1MywiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjE0MDUzLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6NTgsInNlcnZpY2VfdGllciI6InN0YW5kYXJkIiwiaW5mZXJlbmNlX2dlbyI6Imdsb2JhbCJ9fSAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRvb2xfdXNlIiwiaWQiOiJ0b29sdV8wMVZBaVYyNlZDRmN6NkdmVUhiTFRRdUwiLCJuYW1lIjoid29ya2Zsb3dzIiwiaW5wdXQiOnt9LCJjYWxsZXIiOnsidHlwZSI6ImRpcmVjdCJ9fSAgICAgICAgICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiJ9ICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoie1wiYWN0aW9uXCI6In0gICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIgXCJzZXR1cCJ9ICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IlwiIn0gICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcIndvciJ9IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJrZmxvd0lkXCI6IFwiIn0gICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiZkY2RSJ9fQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IkF4UW9ZMUU2VE1qSyJ9ICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiXCJ9In0gICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICB9CgpldmVudDogbWVzc2FnZV9kZWx0YQpkYXRhOiB7InR5cGUiOiJtZXNzYWdlX2RlbHRhIiwiZGVsdGEiOnsic3RvcF9yZWFzb24iOiJ0b29sX3VzZSIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInN0b3BfZGV0YWlscyI6bnVsbH0sInVzYWdlIjp7ImlucHV0X3Rva2VucyI6MjA0LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjE0MDUzLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MCwib3V0cHV0X3Rva2VucyI6ODR9ICAgICAgICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgICAgICAgICAgfQoK",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0001-1777974556275-unknown-host-POST-_v1_messages-c17e02f9.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,111 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"setup\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"590"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-f2a31e23c17caa48469882f5934cd46d-4f55b71500112ad2-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=vx7p2XTKVCp8ymxCyY1Bf1uDntU.2lsVb5X3TtBBY8A-1777974553.6039832-1.0.1.1-m.FrPJVtR30V6V66YfGzigadwEH.6O1ClMIT7BTAU14; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CajDHmCLnzy9u6DgTCKTa"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:49:13Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:49:13Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:49:13Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:49:13Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:49:14 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed5800ca3313f-SOF"
]
},
"cookies": {
"_cfuvid": "vx7p2XTKVCp8ymxCyY1Bf1uDntU.2lsVb5X3TtBBY8A-1777974553.6039832-1.0.1.1-m.FrPJVtR30V6V66YfGzigadwEH.6O1ClMIT7BTAU14"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01EsNoXS2Gk17WzZSyi3WX4T\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":539,\"cache_creation_input_tokens\":348,\"cache_read_input_tokens\":13705,\"cache_creation\":{\"ephemeral_5m_input_tokens\":348,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" workflow **\\\"B3 Workflow Setup Slack Credential Test\\\"** has been set up successfully. Here's a summary of what was configured:\\n\\n-\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" **Slack node** — connected to the credential **\\\"B3 Slack\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Credential Test\\\"** (using access token authentication), configured to send a message to channel\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" `C01234567`.\\n\\nThe workflow is ready to use. Let me know if you'd like to test it or make any changes!\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":539,\"cache_creation_input_tokens\":348,\"cache_read_input_tokens\":13705,\"output_tokens\":101} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxRXNOb1hTMkdrMTdXelpTeWkzV1g0VCIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjUzOSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjozNDgsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6MzQ4LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gICAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJUaGUifSAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiB3b3JrZmxvdyAqKlwiQjMgV29ya2Zsb3cgU2V0dXAgU2xhY2sgQ3JlZGVudGlhbCBUZXN0XCIqKiBoYXMgYmVlbiBzZXQgdXAgc3VjY2Vzc2Z1bGx5LiBIZXJlJ3MgYSBzdW1tYXJ5IG9mIHdoYXQgd2FzIGNvbmZpZ3VyZWQ6XG5cbi0ifSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiAqKlNsYWNrIG5vZGUqKiDigJQgY29ubmVjdGVkIHRvIHRoZSBjcmVkZW50aWFsICoqXCJCMyBTbGFjayJ9ICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6InRleHRfZGVsdGEiLCJ0ZXh0IjoiIENyZWRlbnRpYWwgVGVzdFwiKiogKHVzaW5nIGFjY2VzcyB0b2tlbiBhdXRoZW50aWNhdGlvbiksIGNvbmZpZ3VyZWQgdG8gc2VuZCBhIG1lc3NhZ2UgdG8gY2hhbm5lbCJ9ICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiBgQzAxMjM0NTY3YC5cblxuVGhlIHdvcmtmbG93IGlzIHJlYWR5IHRvIHVzZS4gTGV0IG1lIGtub3cgaWYgeW91J2QgbGlrZSB0byB0ZXN0IGl0IG9yIG1ha2UgYW55IGNoYW5nZXMhIn0gICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdG9wCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RvcCIsImluZGV4IjowICB9CgpldmVudDogbWVzc2FnZV9kZWx0YQpkYXRhOiB7InR5cGUiOiJtZXNzYWdlX2RlbHRhIiwiZGVsdGEiOnsic3RvcF9yZWFzb24iOiJlbmRfdHVybiIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInN0b3BfZGV0YWlscyI6bnVsbH0sInVzYWdlIjp7ImlucHV0X3Rva2VucyI6NTM5LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjM0OCwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjEzNzA1LCJvdXRwdXRfdG9rZW5zIjoxMDF9ICAgfQoKZXZlbnQ6IG1lc3NhZ2Vfc3RvcApkYXRhOiB7InR5cGUiOiJtZXNzYWdlX3N0b3AiICAgICAgICAgfQoK",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0002-1777974556275-unknown-host-POST-_v1_messages-97aea00c.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,4 @@
{"kind":"header","version":1,"testName":"recording","recordedAt":"2026-05-05T09:49:07.380Z"}
{"kind":"tool-call","stepId":1,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"list","query":"B3 Workflow Setup Slack Credential Test"},"output":{"workflows":[{"id":"fF6EAxQoY1E6TMjK","name":"B3 Workflow Setup Slack Credential Test","versionId":"fe8d3d23-dbd4-4088-9518-e61108c27c3c","activeVersionId":null,"createdAt":"2026-05-05T09:49:03.177Z","updatedAt":"2026-05-05T09:49:03.177Z"}]}}
{"kind":"tool-call","stepId":2,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"fF6EAxQoY1E6TMjK"},"output":{"success":false}}
{"kind":"tool-resume","stepId":3,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"fF6EAxQoY1E6TMjK"},"output":{"success":true,"completedNodes":[{"nodeName":"Slack","credentialType":"slackApi"}],"updatedNodes":[{"id":"trigger","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[0,0],"parameters":{}},{"id":"slack","name":"Slack","type":"n8n-nodes-base.slack","typeVersion":2.2,"position":[220,0],"parameters":{"authentication":"accessToken","resource":"message","operation":"send","channelId":{"__rl":true,"mode":"id","value":"C01234567"},"messageType":"text","text":"Hello from instance AI workflow setup!"},"credentials":{"slackApi":{"id":"iR7AUjf5J6PiCmOb","name":"B3 Slack Credential Test"}}}],"updatedConnections":{"Manual Trigger":{"main":[[{"node":"Slack","type":"main","index":0}]]}}},"resumeData":{"approved":true,"credentials":{"Slack":{"slackApi":"iR7AUjf5J6PiCmOb"}},"action":"apply"}}

View File

@ -0,0 +1,112 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "STRING",
"string": "[{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde",
"subString": true
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1959"
],
"vary": [
"Accept-Encoding"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=1KDH2MR.suKQ3b0.cik0WD14ylcnLCsk4Bhdbe3YFYk-1777299224.7456198-1.0.1.1-h63Gud9zuljUvnQEfUsASs9Z54vlJiAQCLbtqNWJ3xE; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1962"
],
"request-id": [
"req_011CaUQzNJoRD9bpdJmLb78e"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-27T14:13:44Z"
],
"anthropic-ratelimit-tokens-remaining": [
"17987000"
],
"anthropic-ratelimit-tokens-limit": [
"18000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-27T14:13:44Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-27T14:13:44Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"3000000"
],
"anthropic-ratelimit-output-tokens-limit": [
"3000000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-27T14:13:45Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"14987000"
],
"anthropic-ratelimit-input-tokens-limit": [
"15000000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Mon, 27 Apr 2026 14:13:46 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f2e6dfaaa46313c-SOF"
]
},
"cookies": {
"_cfuvid": "1KDH2MR.suKQ3b0.cik0WD14ylcnLCsk4Bhdbe3YFYk-1777299224.7456198-1.0.1.1-h63Gud9zuljUvnQEfUsASs9Z54vlJiAQCLbtqNWJ3xE"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01X6vzwdYSbJqZrQZPDU6FCw\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":339,\"cache_creation_input_tokens\":10602,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":10602,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":45,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01TotoiHcgF9vU5dm1bvk2KX\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"action\\\": \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"list\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"query\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\": \\\"B3 W\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ork\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"flow S\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"etup\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" Apply Cred\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"entials\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":339,\"cache_creation_input_tokens\":10602,\"cache_read_input_tokens\":0,\"output_tokens\":77} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxWDZ2endkWVNiSnFaclFaUERVNkZDdyIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMzOSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMDYwMiwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjEwNjAyLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6NDUsInNlcnZpY2VfdGllciI6InN0YW5kYXJkIiwiaW5mZXJlbmNlX2dlbyI6Imdsb2JhbCJ9fSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRvb2xfdXNlIiwiaWQiOiJ0b29sdV8wMVRvdG9pSGNnRjl2VTVkbTFidmsyS1giLCJuYW1lIjoid29ya2Zsb3dzIiwiaW5wdXQiOnt9LCJjYWxsZXIiOnsidHlwZSI6ImRpcmVjdCJ9fSAgICAgICB9CgpldmVudDogcGluZwpkYXRhOiB7InR5cGUiOiAicGluZyJ9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoie1wiYWN0aW9uXCI6ICJ9ICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJcImxpc3RcIiJ9ICAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiwgXCJxdWVyeSJ9ICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJcIjogXCJCMyBXIn0gICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoib3JrIn0gICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImZsb3cgUyJ9ICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImV0dXAifSAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIgQXBwbHkgQ3JlZCJ9ICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJlbnRpYWxzIn0gICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJcIn0ifSB9CgpldmVudDogY29udGVudF9ibG9ja19zdG9wCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RvcCIsImluZGV4IjowICAgICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX2RlbHRhCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2VfZGVsdGEiLCJkZWx0YSI6eyJzdG9wX3JlYXNvbiI6InRvb2xfdXNlIiwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsfSwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjozMzksImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MTA2MDIsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjowLCJvdXRwdXRfdG9rZW5zIjo3N30gIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgIH0KCg==",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0000-1777299238013-unknown-host-POST-_v1_messages-8a23f6c2.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,106 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "STRING",
"string": "[{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde",
"subString": true
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1341"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=mJt0G.fjcBkfZwj_M0NEOa6u9SyH8jPnnXCTaKS76Os-1777299228.416283-1.0.1.1-4ph.ZluCTS2n1aD6gJPlO5Uuj9u4s9yegRSSw8QJpzI; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CaUQzdxdsUCM1pUKxHFNj"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-27T14:13:48Z"
],
"anthropic-ratelimit-tokens-remaining": [
"17987000"
],
"anthropic-ratelimit-tokens-limit": [
"18000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-27T14:13:48Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-27T14:13:48Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"3000000"
],
"anthropic-ratelimit-output-tokens-limit": [
"3000000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-27T14:13:48Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"14987000"
],
"anthropic-ratelimit-input-tokens-limit": [
"15000000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Mon, 27 Apr 2026 14:13:49 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f2e6e11988f9e0c-SOF"
]
},
"cookies": {
"_cfuvid": "mJt0G.fjcBkfZwj_M0NEOa6u9SyH8jPnnXCTaKS76Os-1777299228.416283-1.0.1.1-4ph.ZluCTS2n1aD6gJPlO5Uuj9u4s9yegRSSw8QJpzI"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01Y3oswJ9Pe1Uw7zWKTKhJXC\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":535,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10602,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":51,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01Mii4hvsHEjrZUfwPuaiATG\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"ac\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"tion\\\": \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"setup\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"workflow\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Id\\\": \\\"3\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"CpDPARR6\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"8X3ie\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"lq\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":535,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10602,\"output_tokens\":83} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxWTNvc3dKOVBlMVV3N3pXS1RLaEpYQyIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjUzNSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjowLCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MTA2MDIsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjAsImVwaGVtZXJhbF8xaF9pbnB1dF90b2tlbnMiOjB9LCJvdXRwdXRfdG9rZW5zIjo1MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRvb2xfdXNlIiwiaWQiOiJ0b29sdV8wMU1paTRodnNIRWpyWlVmd1B1YWlBVEciLCJuYW1lIjoid29ya2Zsb3dzIiwiaW5wdXQiOnt9LCJjYWxsZXIiOnsidHlwZSI6ImRpcmVjdCJ9fSB9CgpldmVudDogcGluZwpkYXRhOiB7InR5cGUiOiAicGluZyJ9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIn0gICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IntcImFjIn0gICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ0aW9uXCI6IFwiIn0gIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJzZXR1cFwiIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcIiJ9ICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IndvcmtmbG93In19CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiSWRcIjogXCIzIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiQ3BEUEFSUjYifSAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IjhYM2llIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoibHFcIn0ifSAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX2RlbHRhCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2VfZGVsdGEiLCJkZWx0YSI6eyJzdG9wX3JlYXNvbiI6InRvb2xfdXNlIiwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsfSwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjo1MzUsImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MCwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjEwNjAyLCJvdXRwdXRfdG9rZW5zIjo4M30gICAgICAgICAgICAgfQoKZXZlbnQ6IG1lc3NhZ2Vfc3RvcApkYXRhOiB7InR5cGUiOiJtZXNzYWdlX3N0b3AiICAgICAgfQoK",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0001-1777299238014-unknown-host-POST-_v1_messages-8a23f6c2.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,112 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "STRING",
"string": "[{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde",
"subString": true
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1380"
],
"vary": [
"Accept-Encoding"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=OAtAhVpx.bRPrHAiZ8hDnSvoYd6eLb6NiDPgqpkRwYw-1777299233.9456034-1.0.1.1-OIilSrguVmoa_8IU7i5Csl38F41Hx0v.x7Cnsq25Vj0; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1383"
],
"request-id": [
"req_011CaUR13fQXufE2RaDtbTVe"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-04-27T14:13:54Z"
],
"anthropic-ratelimit-tokens-remaining": [
"17987000"
],
"anthropic-ratelimit-tokens-limit": [
"18000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-04-27T14:13:54Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-04-27T14:13:54Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"3000000"
],
"anthropic-ratelimit-output-tokens-limit": [
"3000000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-04-27T14:13:54Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"14987000"
],
"anthropic-ratelimit-input-tokens-limit": [
"15000000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Mon, 27 Apr 2026 14:13:55 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f2e6e342970dc1a-SOF"
]
},
"cookies": {
"_cfuvid": "OAtAhVpx.bRPrHAiZ8hDnSvoYd6eLb6NiDPgqpkRwYw-1777299233.9456034-1.0.1.1-OIilSrguVmoa_8IU7i5Csl38F41Hx0v.x7Cnsq25Vj0"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_018fjjz7X8qzejn5PXWyJ5xd\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":1000,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10602,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" workflow **\\\"B3 Workflow Setup Apply Credentials\\\"** has been set up successfully. Here's a summary of what was configured:\\n\\n- **HTTP\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Request Basic** node — connected with the **\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"B3 Apply Basic Auth\\\"** credential (HTTP Basic Auth).\\n- **HTTP Request Header** node — connected with the **\\\"B3 Apply Header Auth\\\"** credential (HTTP Header Auth).\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\nBoth credentials were applied automatically. The workflow is ready to use — let\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" me know if you'd like to test or publish it!\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":1000,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":10602,\"output_tokens\":116} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxOGZqano3WDhxemVqbjVQWFd5SjV4ZCIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjEwMDAsImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MCwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjEwNjAyLCJjYWNoZV9jcmVhdGlvbiI6eyJlcGhlbWVyYWxfNW1faW5wdXRfdG9rZW5zIjowLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJUaGUifSAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IiB3b3JrZmxvdyAqKlwiQjMgV29ya2Zsb3cgU2V0dXAgQXBwbHkgQ3JlZGVudGlhbHNcIioqIGhhcyBiZWVuIHNldCB1cCBzdWNjZXNzZnVsbHkuIEhlcmUncyBhIHN1bW1hcnkgb2Ygd2hhdCB3YXMgY29uZmlndXJlZDpcblxuLSAqKkhUVFAifSAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgUmVxdWVzdCBCYXNpYyoqIG5vZGUg4oCUIGNvbm5lY3RlZCB3aXRoIHRoZSAqKlwiIn0gICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IkIzIEFwcGx5IEJhc2ljIEF1dGhcIioqIGNyZWRlbnRpYWwgKEhUVFAgQmFzaWMgQXV0aCkuXG4tICoqSFRUUCBSZXF1ZXN0IEhlYWRlcioqIG5vZGUg4oCUIGNvbm5lY3RlZCB3aXRoIHRoZSAqKlwiQjMgQXBwbHkgSGVhZGVyIEF1dGhcIioqIGNyZWRlbnRpYWwgKEhUVFAgSGVhZGVyIEF1dGgpLiJ9ICAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiJcblxuQm90aCBjcmVkZW50aWFscyB3ZXJlIGFwcGxpZWQgYXV0b21hdGljYWxseS4gVGhlIHdvcmtmbG93IGlzIHJlYWR5IHRvIHVzZSDigJQgbGV0In0gICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgbWUga25vdyBpZiB5b3UnZCBsaWtlIHRvIHRlc3Qgb3IgcHVibGlzaCBpdCEifSAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICAgICAgIH0KCmV2ZW50OiBtZXNzYWdlX2RlbHRhCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2VfZGVsdGEiLCJkZWx0YSI6eyJzdG9wX3JlYXNvbiI6ImVuZF90dXJuIiwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsfSwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjoxMDAwLCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjAsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMDYwMiwib3V0cHV0X3Rva2VucyI6MTE2fSAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgICAgICAgICB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0002-1777299238015-unknown-host-POST-_v1_messages-8a23f6c2.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,4 @@
{"kind":"header","version":1,"testName":"recording","recordedAt":"2026-04-27T14:13:42.821Z"}
{"kind":"tool-call","stepId":1,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"list","query":"B3 Workflow Setup Apply Credentials"},"output":{"workflows":[{"id":"3CpDPARR68X3ielq","name":"B3 Workflow Setup Apply Credentials","versionId":"48419b03-0714-46e8-965f-030f5611eb4e","activeVersionId":null,"createdAt":"2026-04-27T14:13:40.988Z","updatedAt":"2026-04-27T14:13:40.988Z"}]}}
{"kind":"tool-call","stepId":2,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"3CpDPARR68X3ielq"},"output":{"success":false}}
{"kind":"tool-resume","stepId":3,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"3CpDPARR68X3ielq"},"output":{"success":true,"completedNodes":[{"nodeName":"HTTP Request Basic","credentialType":"httpBasicAuth"},{"nodeName":"HTTP Request Header","credentialType":"httpHeaderAuth"}],"updatedNodes":[{"id":"trigger","name":"Manual Trigger","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[0,0],"parameters":{}},{"id":"basic","name":"HTTP Request Basic","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[220,0],"parameters":{"method":"GET","url":"https://example.com/basic","authentication":"genericCredentialType","genericAuthType":"httpBasicAuth"},"credentials":{"httpBasicAuth":{"id":"PBD5pMhDsNsHtRcg","name":"B3 Apply Basic Auth"}}},{"id":"header","name":"HTTP Request Header","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[440,0],"parameters":{"method":"GET","url":"https://example.com/header","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"credentials":{"httpHeaderAuth":{"id":"GODaVzk969uA7AAo","name":"B3 Apply Header Auth"}}}],"updatedConnections":{"Manual Trigger":{"main":[[{"node":"HTTP Request Basic","type":"main","index":0}]]},"HTTP Request Basic":{"main":[[{"node":"HTTP Request Header","type":"main","index":0}]]}}},"resumeData":{"approved":true,"credentials":{"HTTP Request Basic":{"httpBasicAuth":"PBD5pMhDsNsHtRcg"},"HTTP Request Header":{"httpHeaderAuth":"GODaVzk969uA7AAo"}},"action":"apply"}}

View File

@ -0,0 +1,114 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*Set up the workflow named \\\\\"B3 Workflow Setup Defer Credentials\\\\\"\\.[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1078"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-3e487f75a38dc5f8a1e7682c970babd0-dc6d7edcaa9ba207-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=6GAXic1opEKA6IHlLHZg_s2WU6lQM9KCHqd9Ly4dTTo-1777974466.5777943-1.0.1.1-6wF1aFN0NmwjTA1nohOdtmNxjMcKW3E3u8dXQoAmkZU; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1101"
],
"request-id": [
"req_011CajDBM2sKQMzZ82EFHMKF"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:47:46Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:47:46Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:47:46Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:47:46Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:47:47 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed3601af3313c-SOF"
]
},
"cookies": {
"_cfuvid": "6GAXic1opEKA6IHlLHZg_s2WU6lQM9KCHqd9Ly4dTTo-1777974466.5777943-1.0.1.1-6wF1aFN0NmwjTA1nohOdtmNxjMcKW3E3u8dXQoAmkZU"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_015G5aMpJRAyCpeHcv4QZzTY\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":340,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":13705,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Let\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" me look up that workflow first.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_012AReqYbRXUt1kKA2w8kAbZ\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"act\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ion\\\": \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"li\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"st\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"query\\\": \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"B3 Workfl\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ow S\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"etup Defe\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"r Credentia\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ls\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":340,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"output_tokens\":86} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxNUc1YU1wSlJBeUNwZUhjdjRRWnpUWSIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjM0MCwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjEzNzA1LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19IH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0YXJ0CmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RhcnQiLCJpbmRleCI6MCwiY29udGVudF9ibG9jayI6eyJ0eXBlIjoidGV4dCIsInRleHQiOiIifSAgIH0KCmV2ZW50OiBwaW5nCmRhdGE6IHsidHlwZSI6ICJwaW5nIn0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6InRleHRfZGVsdGEiLCJ0ZXh0IjoiTGV0In0gICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgbWUgbG9vayB1cCB0aGF0IHdvcmtmbG93IGZpcnN0LiJ9ICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjEsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRvb2xfdXNlIiwiaWQiOiJ0b29sdV8wMTJBUmVxWWJSWFV0MWtLQTJ3OGtBYloiLCJuYW1lIjoid29ya2Zsb3dzIiwiaW5wdXQiOnt9LCJjYWxsZXIiOnsidHlwZSI6ImRpcmVjdCJ9fSAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIifSAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoie1wiIn0gICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImFjdCJ9ICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiaW9uXCI6IFwiIn0gICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJsaSJ9ICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJzdFwiIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiLCBcInF1ZXJ5XCI6IFwiIn0gICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiQjMgV29ya2ZsIn0gIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJvdyBTIn0gIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJldHVwIERlZmUifSAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiciBDcmVkZW50aWEifSAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImxzXCJ9In0gIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjEgICAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoidG9vbF91c2UiLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjM0MCwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsIm91dHB1dF90b2tlbnMiOjg2fSAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgICB9Cgo=",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0000-1777974474128-unknown-host-POST-_v1_messages-b39899ad.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,111 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"list\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1502"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-1085d60f4a362e7390c5feb6875bafd2-0ae3b110883d8b49-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=vCPFHZtvW_DRxETwdACxZadvCNzkrRlANYljYk5qYIM-1777974468.848351-1.0.1.1-ADUWIW9UEzyJPTgV8mky5rCF11LzQ3q7RcfNnnzQt28; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"request-id": [
"req_011CajDBWqivYggYoCGkZt43"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:47:49Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:47:49Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:47:49Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:47:49Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:47:50 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed36e4d304d27-SOF"
]
},
"cookies": {
"_cfuvid": "vCPFHZtvW_DRxETwdACxZadvCNzkrRlANYljYk5qYIM-1777974468.848351-1.0.1.1-ADUWIW9UEzyJPTgV8mky5rCF11LzQ3q7RcfNnnzQt28"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_013DrvEBkXjQY7kj61xBd6Fm\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":195,\"cache_creation_input_tokens\":14053,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":14053,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":67,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_013C6mmLw13ebcMsZAhtXA2T\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"action\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\": \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"set\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"up\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"wor\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"kflowI\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"d\\\": \\\"TXSVC\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"xTExNA9UskZ\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":195,\"cache_creation_input_tokens\":14053,\"cache_read_input_tokens\":0,\"output_tokens\":83} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxM0RydkVCa1hqUVk3a2o2MXhCZDZGbSIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjE5NSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxNDA1MywiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjE0MDUzLCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6NjcsInNlcnZpY2VfdGllciI6InN0YW5kYXJkIiwiaW5mZXJlbmNlX2dlbyI6Imdsb2JhbCJ9fSAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRvb2xfdXNlIiwiaWQiOiJ0b29sdV8wMTNDNm1tTHcxM2ViY01zWkFodFhBMlQiLCJuYW1lIjoid29ya2Zsb3dzIiwiaW5wdXQiOnt9LCJjYWxsZXIiOnsidHlwZSI6ImRpcmVjdCJ9fSAgfQoKZXZlbnQ6IHBpbmcKZGF0YTogeyJ0eXBlIjogInBpbmcifQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiJ9ICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ7XCJhY3Rpb25cIiJ9ICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiI6IFwiIn0gICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJzZXQifSAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ1cCJ9ICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IlwiIn0gICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6IiwgXCJ3b3IifSAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoia2Zsb3dJIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiZFwiOiBcIlRYU1ZDIn0gICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ4VEV4TkE5VXNrWlwifSJ9ICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19zdG9wCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RvcCIsImluZGV4IjowIH0KCmV2ZW50OiBtZXNzYWdlX2RlbHRhCmRhdGE6IHsidHlwZSI6Im1lc3NhZ2VfZGVsdGEiLCJkZWx0YSI6eyJzdG9wX3JlYXNvbiI6InRvb2xfdXNlIiwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsfSwidXNhZ2UiOnsiaW5wdXRfdG9rZW5zIjoxOTUsImNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyI6MTQwNTMsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjowLCJvdXRwdXRfdG9rZW5zIjo4M30gIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgICAgfQoK",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0001-1777974474129-unknown-host-POST-_v1_messages-c17e02f9.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,114 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*\"type\"\\s*:\\s*\"tool_use\"[\\s\\S]{0,300}\"name\"\\s*:\\s*\"workflows\"[\\s\\S]{0,500}\"action\"\\s*:\\s*\"setup\"[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1091"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-63a0d00446f0c08b48aff3c44201bb0c-1fcd42c6541d8efa-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=xaTD7jfqz3st54WpwpJTateVwJDO5mSgcZtKWlBgtIs-1777974471.5852163-1.0.1.1-9QsKC0UXcBLahIeqWuJIlqDjFMI9qQi3.2EJ44ujngw; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1107"
],
"request-id": [
"req_011CajDBiSEFtLXpdg4kYfYn"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:47:51Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:47:51Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:47:51Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:47:51Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:47:52 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed37f6a95dc1a-SOF"
]
},
"cookies": {
"_cfuvid": "xaTD7jfqz3st54WpwpJTateVwJDO5mSgcZtKWlBgtIs-1777974471.5852163-1.0.1.1-9QsKC0UXcBLahIeqWuJIlqDjFMI9qQi3.2EJ44ujngw"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_015ea3zyDkR9JA5w5mHjYP4j\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":310,\"cache_creation_input_tokens\":348,\"cache_read_input_tokens\":13705,\"cache_creation\":{\"ephemeral_5m_input_tokens\":348,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" setup was deferred — you chose to skip credential and parameter configuration for now.\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" Whenever you're ready to set it up, just let me know and I'll open the setup wizard again.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":310,\"cache_creation_input_tokens\":348,\"cache_read_input_tokens\":13705,\"output_tokens\":43} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxNWVhM3p5RGtSOUpBNXc1bUhqWVA0aiIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMxMCwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjozNDgsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfY3JlYXRpb24iOnsiZXBoZW1lcmFsXzVtX2lucHV0X3Rva2VucyI6MzQ4LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICB9CgpldmVudDogY29udGVudF9ibG9ja19zdGFydApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0YXJ0IiwiaW5kZXgiOjAsImNvbnRlbnRfYmxvY2siOnsidHlwZSI6InRleHQiLCJ0ZXh0IjoiIn0gICAgICAgIH0KCmV2ZW50OiBwaW5nCmRhdGE6IHsidHlwZSI6ICJwaW5nIn0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MCwiZGVsdGEiOnsidHlwZSI6InRleHRfZGVsdGEiLCJ0ZXh0IjoiVGhlIn0gICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgc2V0dXAgd2FzIGRlZmVycmVkIOKAlCB5b3UgY2hvc2UgdG8gc2tpcCBjcmVkZW50aWFsIGFuZCBwYXJhbWV0ZXIgY29uZmlndXJhdGlvbiBmb3Igbm93LiJ9ICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiIgV2hlbmV2ZXIgeW91J3JlIHJlYWR5IHRvIHNldCBpdCB1cCwganVzdCBsZXQgbWUga25vdyBhbmQgSSdsbCBvcGVuIHRoZSBzZXR1cCB3aXphcmQgYWdhaW4uIn0gICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RvcApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0b3AiLCJpbmRleCI6MCAgfQoKZXZlbnQ6IG1lc3NhZ2VfZGVsdGEKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9kZWx0YSIsImRlbHRhIjp7InN0b3BfcmVhc29uIjoiZW5kX3R1cm4iLCJzdG9wX3NlcXVlbmNlIjpudWxsLCJzdG9wX2RldGFpbHMiOm51bGx9LCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMxMCwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjozNDgsImNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zIjoxMzcwNSwib3V0cHV0X3Rva2VucyI6NDN9ICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIn0KCg==",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0002-1777974474129-unknown-host-POST-_v1_messages-97aea00c.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

View File

@ -0,0 +1,4 @@
{"kind":"header","version":1,"testName":"recording","recordedAt":"2026-05-05T09:47:46.281Z"}
{"kind":"tool-call","stepId":1,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"list","query":"B3 Workflow Setup Defer Credentials"},"output":{"workflows":[{"id":"TXSVCxTExNA9UskZ","name":"B3 Workflow Setup Defer Credentials","versionId":"5b012327-ac57-4188-885e-238d30843694","activeVersionId":null,"createdAt":"2026-05-05T09:47:42.087Z","updatedAt":"2026-05-05T09:47:42.087Z"}]}}
{"kind":"tool-call","stepId":2,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"TXSVCxTExNA9UskZ"},"output":{"success":false}}
{"kind":"tool-resume","stepId":3,"agentRole":"orchestrator","toolName":"workflows","input":{"action":"setup","workflowId":"TXSVCxTExNA9UskZ"},"output":{"success":true,"deferred":true,"reason":"User skipped workflow setup for now."},"resumeData":{"approved":false}}

View File

@ -0,0 +1,114 @@
{
"httpRequest": {
"method": "POST",
"path": "/v1/messages",
"body": {
"type": "REGEX",
"regex": "[\\s\\S]*\\[\\{\"type\":\"text\",\"text\":\"You are the n8n Instance Agent — an AI assistant embedde[\\s\\S]*Set up the workflow named \\\\\"B3 Workflow Setup Apply Credentials\\\\\"\\.[\\s\\S]*"
}
},
"httpResponse": {
"statusCode": 200,
"reasonPhrase": "OK",
"headers": {
"x-envoy-upstream-service-time": [
"1052"
],
"vary": [
"Accept-Encoding"
],
"traceresponse": [
"00-2668ca2226c99a70ec74890b0f4bcc29-4bc9a6b3b1d55bbc-01"
],
"strict-transport-security": [
"max-age=31536000; includeSubDomains; preload"
],
"set-cookie": [
"_cfuvid=dv8sDGFw6P1PZ_BtIoA3IfN1.6gDBSxPYbT3wiHls7Q-1777974439.1522524-1.0.1.1-uJZxg5J7MJDm67DswPe8FpgfB9Y7CTs6pcTOMb2HJYM; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com"
],
"server-timing": [
"x-originResponse;dur=1055"
],
"request-id": [
"req_011CajD9KmueJ78rX83WbYeo"
],
"cf-cache-status": [
"DYNAMIC"
],
"anthropic-ratelimit-tokens-reset": [
"2026-05-05T09:47:19Z"
],
"anthropic-ratelimit-tokens-remaining": [
"26975000"
],
"anthropic-ratelimit-tokens-limit": [
"27000000"
],
"anthropic-ratelimit-requests-reset": [
"2026-05-05T09:47:19Z"
],
"anthropic-ratelimit-requests-remaining": [
"19998"
],
"anthropic-ratelimit-requests-limit": [
"20000"
],
"anthropic-ratelimit-output-tokens-reset": [
"2026-05-05T09:47:19Z"
],
"anthropic-ratelimit-output-tokens-remaining": [
"4500000"
],
"anthropic-ratelimit-output-tokens-limit": [
"4500000"
],
"anthropic-ratelimit-input-tokens-reset": [
"2026-05-05T09:47:19Z"
],
"anthropic-ratelimit-input-tokens-remaining": [
"22475000"
],
"anthropic-ratelimit-input-tokens-limit": [
"22500000"
],
"X-Robots-Tag": [
"none"
],
"Server": [
"cloudflare"
],
"Date": [
"Tue, 05 May 2026 09:47:20 GMT"
],
"Content-Type": [
"text/event-stream; charset=utf-8"
],
"Content-Security-Policy": [
"default-src 'none'; frame-ancestors 'none'"
],
"Cache-Control": [
"no-cache"
],
"CF-RAY": [
"9f6ed2b4bdd54d27-SOF"
]
},
"cookies": {
"_cfuvid": "dv8sDGFw6P1PZ_BtIoA3IfN1.6gDBSxPYbT3wiHls7Q-1777974439.1522524-1.0.1.1-uJZxg5J7MJDm67DswPe8FpgfB9Y7CTs6pcTOMb2HJYM"
},
"body": {
"type": "STRING",
"string": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_016wuAtv2VDFXyDLdgZ5KKye\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":339,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":13705,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"I\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"'ll look up that workflow right away.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_01V2BzMfTbgkRZf2G9gXsmTe\",\"name\":\"workflows\",\"input\":{},\"caller\":{\"type\":\"direct\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"act\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ion\\\": \\\"list\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\", \\\"query\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\": \\\"B3 Work\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"flow Setup\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" Apply Crede\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ntials\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null},\"usage\":{\"input_tokens\":339,\"cache_creation_input_tokens\":13705,\"cache_read_input_tokens\":0,\"output_tokens\":86} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n",
"rawBytes": "ZXZlbnQ6IG1lc3NhZ2Vfc3RhcnQKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdGFydCIsIm1lc3NhZ2UiOnsibW9kZWwiOiJjbGF1ZGUtc29ubmV0LTQtNiIsImlkIjoibXNnXzAxNnd1QXR2MlZERlh5RExkZ1o1S0t5ZSIsInR5cGUiOiJtZXNzYWdlIiwicm9sZSI6ImFzc2lzdGFudCIsImNvbnRlbnQiOltdLCJzdG9wX3JlYXNvbiI6bnVsbCwic3RvcF9zZXF1ZW5jZSI6bnVsbCwic3RvcF9kZXRhaWxzIjpudWxsLCJ1c2FnZSI6eyJpbnB1dF90b2tlbnMiOjMzOSwiY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zIjoxMzcwNSwiY2FjaGVfcmVhZF9pbnB1dF90b2tlbnMiOjAsImNhY2hlX2NyZWF0aW9uIjp7ImVwaGVtZXJhbF81bV9pbnB1dF90b2tlbnMiOjEzNzA1LCJlcGhlbWVyYWxfMWhfaW5wdXRfdG9rZW5zIjowfSwib3V0cHV0X3Rva2VucyI6MSwic2VydmljZV90aWVyIjoic3RhbmRhcmQiLCJpbmZlcmVuY2VfZ2VvIjoiZ2xvYmFsIn19ICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0YXJ0CmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RhcnQiLCJpbmRleCI6MCwiY29udGVudF9ibG9jayI6eyJ0eXBlIjoidGV4dCIsInRleHQiOiIifSAgICAgICAgICB9CgpldmVudDogcGluZwpkYXRhOiB7InR5cGUiOiAicGluZyJ9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjAsImRlbHRhIjp7InR5cGUiOiJ0ZXh0X2RlbHRhIiwidGV4dCI6IkkifSAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjowLCJkZWx0YSI6eyJ0eXBlIjoidGV4dF9kZWx0YSIsInRleHQiOiInbGwgbG9vayB1cCB0aGF0IHdvcmtmbG93IHJpZ2h0IGF3YXkuIn0gICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0b3AKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19zdG9wIiwiaW5kZXgiOjAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX3N0YXJ0CmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfc3RhcnQiLCJpbmRleCI6MSwiY29udGVudF9ibG9jayI6eyJ0eXBlIjoidG9vbF91c2UiLCJpZCI6InRvb2x1XzAxVjJCek1mVGJna1JaZjJHOWdYc21UZSIsIm5hbWUiOiJ3b3JrZmxvd3MiLCJpbnB1dCI6e30sImNhbGxlciI6eyJ0eXBlIjoiZGlyZWN0In19ICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiIn0gICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJ7XCJhY3QifSAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6ImlvblwiOiBcImxpc3RcIiJ9ICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIsIFwicXVlcnkifSAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiJcIjogXCJCMyBXb3JrIn0gICAgICAgICAgICAgICB9CgpldmVudDogY29udGVudF9ibG9ja19kZWx0YQpkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX2RlbHRhIiwiaW5kZXgiOjEsImRlbHRhIjp7InR5cGUiOiJpbnB1dF9qc29uX2RlbHRhIiwicGFydGlhbF9qc29uIjoiZmxvdyBTZXR1cCJ9ICAgICAgICAgICAgIH0KCmV2ZW50OiBjb250ZW50X2Jsb2NrX2RlbHRhCmRhdGE6IHsidHlwZSI6ImNvbnRlbnRfYmxvY2tfZGVsdGEiLCJpbmRleCI6MSwiZGVsdGEiOnsidHlwZSI6ImlucHV0X2pzb25fZGVsdGEiLCJwYXJ0aWFsX2pzb24iOiIgQXBwbHkgQ3JlZGUifSAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfZGVsdGEKZGF0YTogeyJ0eXBlIjoiY29udGVudF9ibG9ja19kZWx0YSIsImluZGV4IjoxLCJkZWx0YSI6eyJ0eXBlIjoiaW5wdXRfanNvbl9kZWx0YSIsInBhcnRpYWxfanNvbiI6Im50aWFsc1wifSJ9ICAgICAgICAgICAgICAgfQoKZXZlbnQ6IGNvbnRlbnRfYmxvY2tfc3RvcApkYXRhOiB7InR5cGUiOiJjb250ZW50X2Jsb2NrX3N0b3AiLCJpbmRleCI6MSAgICAgICAgICB9CgpldmVudDogbWVzc2FnZV9kZWx0YQpkYXRhOiB7InR5cGUiOiJtZXNzYWdlX2RlbHRhIiwiZGVsdGEiOnsic3RvcF9yZWFzb24iOiJ0b29sX3VzZSIsInN0b3Bfc2VxdWVuY2UiOm51bGwsInN0b3BfZGV0YWlscyI6bnVsbH0sInVzYWdlIjp7ImlucHV0X3Rva2VucyI6MzM5LCJjYWNoZV9jcmVhdGlvbl9pbnB1dF90b2tlbnMiOjEzNzA1LCJjYWNoZV9yZWFkX2lucHV0X3Rva2VucyI6MCwib3V0cHV0X3Rva2VucyI6ODZ9ICAgIH0KCmV2ZW50OiBtZXNzYWdlX3N0b3AKZGF0YTogeyJ0eXBlIjoibWVzc2FnZV9zdG9wIiAgIH0KCg==",
"contentType": "text/event-stream; charset=utf-8"
}
},
"id": "0000-1777974459341-unknown-host-POST-_v1_messages-43762f74.json",
"priority": 0,
"timeToLive": {
"unlimited": true
},
"times": {
"unlimited": true
}
}

Some files were not shown because too many files have changed in this diff Show More