From b29f38b646662ec43a7660fb7d2303c33b5f4951 Mon Sep 17 00:00:00 2001 From: "n8n-cat-bot[bot]" <283985454+n8n-cat-bot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:09:48 +0100 Subject: [PATCH 001/130] fix: Execute gated tool after approving Human Review in manual chat (#34881) Co-authored-by: n8n-cat-bot[bot] Co-authored-by: Claude Opus 4.8 Co-authored-by: Declan Carroll Co-authored-by: Arvin A <51036481+DeveloperTheExplorer@users.noreply.github.com> --- .../__tests__/chat-execution-manager.test.ts | 53 +++++++++++ .../cli/src/chat/chat-execution-manager.ts | 14 ++- .../tests/e2e/ai/hitl-for-tools.spec.ts | 89 +++++++++++++++++++ 3 files changed, 152 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/chat/__tests__/chat-execution-manager.test.ts b/packages/cli/src/chat/__tests__/chat-execution-manager.test.ts index 75e80585081..3d6518e1702 100644 --- a/packages/cli/src/chat/__tests__/chat-execution-manager.test.ts +++ b/packages/cli/src/chat/__tests__/chat-execution-manager.test.ts @@ -275,6 +275,59 @@ describe('ChatExecutionManager', () => { }); }); + describe('when node is a HITL tool', () => { + // HITL tools carry a generated `HitlTool` type (e.g. chatHitlTool), + // not CHAT_TOOL_NODE_TYPE, but must get the same resume fix-up so the + // approval is logged on the ai_tool channel and the gated tool executes. + const HITL_TOOL_NODE_TYPE = '@n8n/n8n-nodes-langchain.chatHitlTool'; + const message: ChatMessage = { sessionId: '123', action: 'sendMessage', chatInput: '' }; + + function makeHitlToolExecution() { + return { + id: '1', + workflowData: { id: 'workflowId' }, + data: { + resultData: { + pinData: {}, + lastNodeExecuted: 'HitlToolNode', + runData: { + HitlToolNode: [ + { startTime: 123, executionTime: 456, executionIndex: 0, source: [] }, + ], + }, + }, + executionData: { + nodeExecutionStack: [ + { + node: { name: 'HitlToolNode', type: HITL_TOOL_NODE_TYPE, disabled: false }, + data: { main: [[]] }, + }, + ], + }, + pushRef: 'pushRef', + waitTill: new Date(), + }, + mode: 'manual', + } as any; + } + + beforeEach(() => { + vi.spyOn(chatExecutionManager as any, 'runNode').mockResolvedValue(null); + ownershipService.getWorkflowProjectCached.mockResolvedValue({ id: 'projectId' } as any); + }); + + it('should disable node, clear waitTill and rewire output to AiTool', async () => { + const execution = makeHitlToolExecution(); + + await (chatExecutionManager as any).getRunData(execution, message); + + const resumingNode = execution.data.executionData.nodeExecutionStack[0].node; + expect(resumingNode.disabled).toBe(true); + expect(execution.data.waitTill).toBeUndefined(); + expect(resumingNode.rewireOutputLogTo).toBe(NodeConnectionTypes.AiTool); + }); + }); + describe('when node is a regular chat node', () => { const message: ChatMessage = { sessionId: '123', action: 'sendMessage', chatInput: 'input' }; diff --git a/packages/cli/src/chat/chat-execution-manager.ts b/packages/cli/src/chat/chat-execution-manager.ts index 7beef922b80..bcfc4d3b5ee 100644 --- a/packages/cli/src/chat/chat-execution-manager.ts +++ b/packages/cli/src/chat/chat-execution-manager.ts @@ -13,6 +13,7 @@ import { UnexpectedError, CHAT_TOOL_NODE_TYPE, NodeConnectionTypes, + isHitlToolType, } from 'n8n-workflow'; import { NotFoundError } from '../errors/response-errors/not-found.error'; @@ -158,11 +159,16 @@ export class ChatExecutionManager { [{ json: message }], ]; - if (runExecutionData.executionData!.nodeExecutionStack[0].node.type === CHAT_TOOL_NODE_TYPE) { + // The chat-based HITL tool resumes here too, but carries the generated + // `chatHitlTool` type rather than `chatTool`. Without this its output stays on + // the `main` channel instead of `ai_tool`, so the agent never sees the approval + // and the gated tool is never executed. The webhook resume path keys off the + // same `HitlTool` suffix. + const resumingNode = runExecutionData.executionData!.nodeExecutionStack[0].node; + if (resumingNode.type === CHAT_TOOL_NODE_TYPE || isHitlToolType(resumingNode.type)) { runExecutionData.waitTill = undefined; - runExecutionData.executionData!.nodeExecutionStack[0].node.disabled = true; - runExecutionData.executionData!.nodeExecutionStack[0].node.rewireOutputLogTo = - NodeConnectionTypes.AiTool; + resumingNode.disabled = true; + resumingNode.rewireOutputLogTo = NodeConnectionTypes.AiTool; const lastNodeExecuted = runExecutionData.resultData.lastNodeExecuted as string; const runDataArray = runExecutionData.resultData.runData[lastNodeExecuted]; diff --git a/packages/testing/playwright/tests/e2e/ai/hitl-for-tools.spec.ts b/packages/testing/playwright/tests/e2e/ai/hitl-for-tools.spec.ts index a950f6aed9f..b18586c8e4a 100644 --- a/packages/testing/playwright/tests/e2e/ai/hitl-for-tools.spec.ts +++ b/packages/testing/playwright/tests/e2e/ai/hitl-for-tools.spec.ts @@ -58,6 +58,10 @@ interface AnthropicMessagesRequest { tools?: Array<{ name?: string }>; } +interface OpenAIResponsesRequest { + input?: Array<{ type?: string; output?: string; call_id?: string }>; +} + const hitlForToolsTestConfig = { capability: { services: ['proxy'], @@ -166,6 +170,91 @@ test.describe( await waitForWorkflowSuccess(n8n); }); + // Regression test for the community forum issue AI-2656: after clicking + // Approve on a tool gated behind a Human Review node, the tool must actually + // execute and its real result must be returned to the agent. The bug returns + // the Human Review approval object (e.g. { approved: true }) to the agent + // instead of the gated tool's result, so the agent never sees the tool output + // and loops until it hits max iterations. + // https://community.n8n.io/t/human-review-before-ai-tool-works-in-n8n-2-6-0-but-does-not-execute-the-tool-in-2-27-5-hitl/304110/1 + test('should return the real tool result to the agent after approval, not the approval object', async ({ + n8n, + services, + }) => { + await n8n.canvas.addNode(AGENT_NODE_NAME, { closeNDV: true }); + + await addOpenAILanguageModelWithCredentials(n8n, AGENT_NODE_NAME); + + await n8n.canvas.addSupplementalNodeToParent( + MANUAL_CHAT_TRIGGER_NODE_NAME, + 'ai_tool', + AGENT_NODE_NAME, + { closeNDV: true, subcategory: HITL_TOOL_SUBCATEGORY, exactMatch: true }, + ); + + await n8n.canvas.addSupplementalNodeToParent( + AI_TOOL_CODE_NODE_NAME, + 'ai_tool', + MANUAL_CHAT_TRIGGER_NODE_NAME, + { closeNDV: false }, + ); + + // The gated tool returns a distinctive result we can assert reached the agent. + await n8n.ndv.getParameterTextarea('description').fill('Send email'); + await setEditorText(n8n, 'jsCode', 'return "Email sent";'); + + await n8n.ndv.setParameterSwitch('specifyInputSchema', true); + await setEditorText(n8n, 'jsonSchemaExample', '{"receiver": "", "body": ""}'); + + await n8n.ndv.clickBackToCanvasButton(); + + await n8n.canvas.addNode(MANUAL_CHAT_TRIGGER_NODE_NAME, { + closeNDV: false, + action: 'Send a message', + fromNode: AGENT_NODE_NAME, + }); + await n8n.ndv.openExpressionEditorModal('message'); + await n8n.ndv.fillExpressionEditorModalInput('{{ $json.output }}'); + await n8n.ndv.getExpressionEditorModalOutput().click(); + await n8n.page.keyboard.press('Escape'); + await n8n.ndv.clickBackToCanvasButton(); + + await n8n.canvas.openNode(CHAT_TRIGGER_NODE_DISPLAY_NAME); + await n8n.ndv.addParameterOptionByName('Response mode'); + await n8n.ndv.selectOptionInParameterDropdown('responseMode', 'Using Response Nodes'); + await n8n.ndv.clickBackToCanvasButton(); + + await n8n.canvas.clickManualChatButton(); + await n8n.canvas.logsPanel.sendManualChatMessage('Send welcome email to john@gmail.com'); + const approveButton = n8n.canvas.manualChat.getApproveButton(); + await expect(approveButton).toBeVisible({ timeout: 15000 }); + await approveButton.click({ button: 'middle' }); + + // After approval, the agent sends the model a follow-up request that carries + // the gated tool's result back as a `function_call_output`. Inspect what the + // agent actually sent to the model to verify the tool result — not the Human + // Review approval payload — reached it. + const getToolOutputs = async () => + (await services.proxy.getAllRequestsMade()) + .filter((request) => request.httpRequest?.path === '/v1/responses') + .flatMap((request) => { + const input = + (request.httpRequest?.body as { json?: OpenAIResponsesRequest })?.json?.input ?? []; + return input.filter((item) => item?.type === 'function_call_output'); + }); + + await expect + .poll(async () => (await getToolOutputs()).length, { timeout: 30_000 }) + .toBeGreaterThan(0); + + const combinedOutput = (await getToolOutputs()).map((item) => item.output ?? '').join('\n'); + + // The agent must receive the gated tool's real result... + expect(combinedOutput).toContain('Email sent'); + // ...and never the raw Human Review approval object. + expect(combinedOutput).not.toContain('approved'); + }); + // The duplicated-module bug this guards against only reproduces in the packaged image (container mode) test('agent should send HITL-wrapped tools to the model on the first turn, before approval', async ({ n8n, From 1f6cddd1abadc7a53bbe28b37c59a79a0913a09e Mon Sep 17 00:00:00 2001 From: Kai Date: Tue, 28 Jul 2026 17:03:18 +0200 Subject: [PATCH 002/130] feat(core): Add detail endpoint for workflow review requests (#35088) --- packages/@n8n/api-types/src/dto/index.ts | 1 + .../get-workflow-review-request-detail.dto.ts | 32 ++ packages/@n8n/db/src/repositories/index.ts | 1 + ...flow-review-request-workflow.repository.ts | 32 ++ ...flow-review-request.decide.service.test.ts | 3 + ...flow-review-request.detail.service.test.ts | 319 ++++++++++++++++++ ...rkflow-review-request.list.service.test.ts | 3 + .../workflow-review-request.service.test.ts | 3 + ...flow-review-request.update.service.test.ts | 3 + ...ew-requests.controller.integration.test.ts | 211 ++++++++++++ ...view-requests.env-gate.integration.test.ts | 4 + .../workflow-review-request.service.ts | 140 ++++++++ .../workflow-review-requests.controller.ts | 18 + 13 files changed, 770 insertions(+) create mode 100644 packages/@n8n/api-types/src/dto/workflow-reviews/get-workflow-review-request-detail.dto.ts create mode 100644 packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.detail.service.test.ts diff --git a/packages/@n8n/api-types/src/dto/index.ts b/packages/@n8n/api-types/src/dto/index.ts index 19a6abfa953..044b59c4e00 100644 --- a/packages/@n8n/api-types/src/dto/index.ts +++ b/packages/@n8n/api-types/src/dto/index.ts @@ -311,6 +311,7 @@ export { GetWorkflowReviewEligibleReviewersQueryDto } from './workflow-reviews/g export { ListWorkflowReviewRequestsQueryDto } from './workflow-reviews/list-workflow-review-requests-query.dto'; export { UpdateWorkflowReviewRequestVersionDto } from './workflow-reviews/update-workflow-review-request-version.dto'; export * from './workflow-reviews/list-workflow-review-inbox.dto'; +export type * from './workflow-reviews/get-workflow-review-request-detail.dto'; export { UpdateOtelSettingsDto } from './otel/update-otel-settings.dto'; export { TestOtelTraceDto } from './otel/test-otel-trace.dto'; diff --git a/packages/@n8n/api-types/src/dto/workflow-reviews/get-workflow-review-request-detail.dto.ts b/packages/@n8n/api-types/src/dto/workflow-reviews/get-workflow-review-request-detail.dto.ts new file mode 100644 index 00000000000..43ae2cdaa97 --- /dev/null +++ b/packages/@n8n/api-types/src/dto/workflow-reviews/get-workflow-review-request-detail.dto.ts @@ -0,0 +1,32 @@ +import type { IConnections, INode, IWorkflowGroup } from 'n8n-workflow'; + +import type { WorkflowReviewInboxItem } from './list-workflow-review-inbox.dto'; +import type { Iso8601DateTimeString } from '../../datetime'; + +/** Immutable content of one workflow-history version, as needed by the diff surface. */ +export interface WorkflowReviewVersionSnapshot { + versionId: string; + nodes: INode[]; + connections: IConnections; + nodeGroups: IWorkflowGroup[]; + createdAt: Iso8601DateTimeString; +} + +export interface WorkflowReviewRequestWorkflowDetail { + workflowId: string; + workflowName: string; + /** Pinned version id; null when the history entry was pruned (LIGO-879) */ + workflowVersionId: string | null; + /** Content of the pinned version; null when the history row no longer exists */ + pinnedVersion: WorkflowReviewVersionSnapshot | null; + /** + * Latest published version, resolved at read time; null = never published, + * i.e. the diff baseline is empty. + */ + baselineVersion: WorkflowReviewVersionSnapshot | null; +} + +export interface WorkflowReviewRequestDetail extends WorkflowReviewInboxItem { + description: string | null; + workflows: WorkflowReviewRequestWorkflowDetail[]; +} diff --git a/packages/@n8n/db/src/repositories/index.ts b/packages/@n8n/db/src/repositories/index.ts index 44342f673b4..19274545698 100644 --- a/packages/@n8n/db/src/repositories/index.ts +++ b/packages/@n8n/db/src/repositories/index.ts @@ -84,6 +84,7 @@ export { export { WorkflowReviewRequestWorkflowRepository, type WorkflowReviewRequestLinkedWorkflow, + type WorkflowReviewRequestWorkflowDetailRow, } from './workflow-review-request-workflow.repository'; export { WorkflowReviewRequestReviewerRepository } from './workflow-review-request-reviewer.repository'; export { WorkflowReviewRequestAuthorRepository } from './workflow-review-request-author.repository'; diff --git a/packages/@n8n/db/src/repositories/workflow-review-request-workflow.repository.ts b/packages/@n8n/db/src/repositories/workflow-review-request-workflow.repository.ts index 04eae0b052d..f6ed7ab1bcf 100644 --- a/packages/@n8n/db/src/repositories/workflow-review-request-workflow.repository.ts +++ b/packages/@n8n/db/src/repositories/workflow-review-request-workflow.repository.ts @@ -11,6 +11,12 @@ export type WorkflowReviewRequestLinkedWorkflow = { workflowVersionId: string | null; }; +export type WorkflowReviewRequestWorkflowDetailRow = { + workflowId: string; + workflowName: string; + workflowVersionId: string | null; +}; + @Service() export class WorkflowReviewRequestWorkflowRepository extends Repository { constructor(dataSource: DataSource) { @@ -96,4 +102,30 @@ export class WorkflowReviewRequestWorkflowRepository extends Repository { + // Join via entity so DB_TABLE_PREFIX is applied (postgres ITs). + const rows = await this.createQueryBuilder('wrw') + // The inner join is safe: `workflowId` FKs onto `workflow_entity` with + // `ON DELETE CASCADE`, so a child row never outlives its workflow. + .innerJoin(WorkflowEntity, 'workflow', 'workflow.id = wrw.workflowId') + .select('wrw.workflowId', 'workflowId') + .addSelect('workflow.name', 'workflowName') + .addSelect('wrw.workflowVersionId', 'workflowVersionId') + .where('wrw.workflowReviewRequestId = :requestId', { requestId }) + .orderBy('wrw.id', 'ASC') + .getRawMany<{ + workflowId: string; + workflowName: string; + workflowVersionId: string | null; + }>(); + + return rows.map((row) => ({ + workflowId: row.workflowId, + workflowName: row.workflowName, + workflowVersionId: row.workflowVersionId ?? null, + })); + } } diff --git a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.decide.service.test.ts b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.decide.service.test.ts index 65f99aa1cf5..c733340048a 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.decide.service.test.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.decide.service.test.ts @@ -7,6 +7,7 @@ import type { User, UserRepository, WorkflowEntity, + WorkflowPublishedVersionRepository, WorkflowReviewRequest, WorkflowReviewRequestAuthorRepository, WorkflowReviewRequestRepository, @@ -42,6 +43,7 @@ describe('WorkflowReviewRequestService.decide', () => { const workflowFinderService = mock(); const workflowHistoryService = mock(); const sharedWorkflowRepository = mock(); + const publishedVersionRepository = mock(); const requestRepository = mock(); const workflowRepository = mock(); const authorRepository = mock(); @@ -62,6 +64,7 @@ describe('WorkflowReviewRequestService.decide', () => { workflowFinderService, workflowHistoryService, sharedWorkflowRepository, + publishedVersionRepository, requestRepository, workflowRepository, authorRepository, diff --git a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.detail.service.test.ts b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.detail.service.test.ts new file mode 100644 index 00000000000..ab26143de3d --- /dev/null +++ b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.detail.service.test.ts @@ -0,0 +1,319 @@ +import type { LicenseState, Logger } from '@n8n/backend-common'; +import type { + DbLockService, + ProjectRelationRepository, + SharedWorkflowRepository, + User, + UserRepository, + WorkflowEntity, + WorkflowHistory, + WorkflowPublishedVersionRepository, + WorkflowReviewRequest, + WorkflowReviewRequestAuthorRepository, + WorkflowReviewRequestRepository, + WorkflowReviewRequestReviewerRepository, + WorkflowReviewRequestWorkflowRepository, +} from '@n8n/db'; +import { mock } from 'vitest-mock-extended'; + +import type { CollaborationService } from '@/collaboration/collaboration.service'; +import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; +import { NotFoundError } from '@/errors/response-errors/not-found.error'; +import type { ProjectService } from '@/services/project.service.ee'; +import type { RoleService } from '@/services/role.service'; +import type { WorkflowReviewPolicyService } from '@/services/workflow-review-policy.service'; +import type { WorkflowFinderService } from '@/workflows/workflow-finder.service'; +import type { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-history.service'; + +import { WorkflowReviewRequestService } from '../workflow-review-request.service'; + +const requestId = 'req-1'; +const workflowId = 'wf-1'; + +/** Plain member: no global scopes, so visibility falls through to project scopes. */ +const member = mock({ id: 'user-1', role: { slug: 'global:member', scopes: [] } }); +const requester = mock({ id: 'requester-1', role: { slug: 'global:member', scopes: [] } }); +const globalPublisher = mock({ + id: 'admin-1', + role: { slug: 'global:admin', scopes: [{ slug: 'workflow:publish' }] }, +}); + +function reviewRequest(overrides: Partial = {}) { + return mock({ + id: requestId, + projectId: 'proj-1', + state: 'open', + decision: 'pending', + title: 'Please review', + description: 'Some context', + createdById: requester.id, + createdAt: new Date('2026-07-01T00:00:00.000Z'), + updatedAt: new Date('2026-07-02T00:00:00.000Z'), + ...overrides, + }); +} + +// A plain object, not a deep mock: the snapshot mapper copies `connections` +// through verbatim, and a proxy there is not comparable with toEqual. +function historyVersion(versionId: string) { + return { + versionId, + workflowId, + nodes: [{ name: `node-${versionId}` }], + connections: {}, + nodeGroups: [], + authors: 'Some Author', + name: 'My workflow', + createdAt: new Date('2026-06-01T00:00:00.000Z'), + } as unknown as WorkflowHistory; +} + +describe('WorkflowReviewRequestService.getDetail', () => { + const logger = mock(); + const workflowReviewPolicyService = mock(); + const workflowFinderService = mock(); + const workflowHistoryService = mock(); + const sharedWorkflowRepository = mock(); + const publishedVersionRepository = mock(); + const requestRepository = mock(); + const workflowRepository = mock(); + const authorRepository = mock(); + const reviewerRepository = mock(); + const userRepository = mock(); + const projectRelationRepository = mock(); + const roleService = mock(); + const projectService = mock(); + const licenseState = mock(); + const dbLockService = mock(); + const collaborationService = mock(); + + const service = new WorkflowReviewRequestService( + logger, + workflowReviewPolicyService, + workflowFinderService, + workflowHistoryService, + sharedWorkflowRepository, + publishedVersionRepository, + requestRepository, + workflowRepository, + authorRepository, + reviewerRepository, + userRepository, + projectRelationRepository, + roleService, + projectService, + licenseState, + dbLockService, + collaborationService, + ); + + beforeEach(() => { + vi.resetAllMocks(); + process.env.N8N_ENV_FEAT_WORKFLOW_REVIEWS = 'true'; + licenseState.isWorkflowReviewsLicensed.mockReturnValue(true); + workflowReviewPolicyService.get.mockResolvedValue({ enabled: true }); + requestRepository.findById.mockResolvedValue(reviewRequest()); + // By default the caller can still read every workflow the review covers + workflowFinderService.findWorkflowForUser.mockResolvedValue(mock()); + workflowRepository.findLinkedWorkflowDetailsByRequestId.mockResolvedValue([]); + reviewerRepository.findByRequestIds.mockResolvedValue([]); + userRepository.findManyByIds.mockResolvedValue([]); + publishedVersionRepository.getPublishedVersionId.mockResolvedValue(null); + workflowHistoryService.findVersion.mockResolvedValue(null); + }); + + /** One child row pinned to `pinnedVersionId`. */ + function mockChildRow( + pinnedVersionId: string | null = 'ver-pinned', + workflowName = 'My workflow', + ) { + workflowRepository.findLinkedWorkflowDetailsByRequestId.mockResolvedValue([ + { workflowId, workflowName, workflowVersionId: pinnedVersionId }, + ]); + } + + describe('when reviews are unavailable', () => { + it('refuses to open a review on an instance without a workflow reviews licence', async () => { + licenseState.isWorkflowReviewsLicensed.mockReturnValue(false); + + await expect(service.getDetail(requester, requestId)).rejects.toThrow(ForbiddenError); + expect(requestRepository.findById).not.toHaveBeenCalled(); + }); + + it('refuses to open a review when an admin has turned reviews off', async () => { + workflowReviewPolicyService.get.mockResolvedValue({ enabled: false }); + + await expect(service.getDetail(requester, requestId)).rejects.toThrow(ForbiddenError); + expect(requestRepository.findById).not.toHaveBeenCalled(); + }); + }); + + describe('who is allowed to open a review', () => { + it('reports a review that does not exist as not found', async () => { + requestRepository.findById.mockResolvedValue(null); + + await expect(service.getDetail(requester, requestId)).rejects.toThrow(NotFoundError); + }); + + it('hides a review from someone who cannot publish in its project, without revealing that it exists', async () => { + projectService.getProjectIdsWithScope.mockResolvedValue(['other-proj']); + + // Same error as a review that does not exist: existence must not leak + await expect(service.getDetail(member, requestId)).rejects.toThrow(NotFoundError); + await expect(service.getDetail(member, requestId)).rejects.toThrow( + 'Could not find review request', + ); + // Nothing beyond the record itself is loaded for someone who cannot see it + expect(workflowRepository.findLinkedWorkflowDetailsByRequestId).not.toHaveBeenCalled(); + }); + + it('lets someone who can publish in the review project open it', async () => { + projectService.getProjectIdsWithScope.mockResolvedValue(['proj-1']); + + const detail = await service.getDetail(member, requestId); + + expect(detail.id).toBe(requestId); + }); + + it('always lets the person who asked for the review open it', async () => { + const detail = await service.getDetail(requester, requestId); + + expect(detail.id).toBe(requestId); + expect(projectService.getProjectIdsWithScope).not.toHaveBeenCalled(); + }); + + it('lets someone who can publish anywhere on the instance open any review', async () => { + const detail = await service.getDetail(globalPublisher, requestId); + + expect(detail.id).toBe(requestId); + expect(projectService.getProjectIdsWithScope).not.toHaveBeenCalled(); + }); + + it('leaves out a workflow the person who asked for the review can no longer read', async () => { + mockChildRow('ver-pinned'); + workflowFinderService.findWorkflowForUser.mockResolvedValue(null); + + const detail = await service.getDetail(requester, requestId); + + // They keep their own review, but none of the workflow content + expect(detail.id).toBe(requestId); + expect(detail.workflows).toEqual([]); + expect(workflowHistoryService.findVersion).not.toHaveBeenCalled(); + }); + + it('hides the review when someone else can read none of the workflows it covers', async () => { + projectService.getProjectIdsWithScope.mockResolvedValue(['proj-1']); + mockChildRow('ver-pinned'); + workflowFinderService.findWorkflowForUser.mockResolvedValue(null); + + await expect(service.getDetail(member, requestId)).rejects.toThrow(NotFoundError); + }); + }); + + describe('what the response contains', () => { + it('returns the review together with the workflows it covers', async () => { + mockChildRow('ver-pinned'); + workflowHistoryService.findVersion.mockResolvedValue(historyVersion('ver-pinned')); + + const detail = await service.getDetail(requester, requestId); + + expect(detail).toMatchObject({ + id: requestId, + projectId: 'proj-1', + state: 'open', + decision: 'pending', + title: 'Please review', + description: 'Some context', + workflowName: 'My workflow', + workflowVersionId: 'ver-pinned', + createdAt: '2026-07-01T00:00:00.000Z', + updatedAt: '2026-07-02T00:00:00.000Z', + }); + expect(detail.workflows).toHaveLength(1); + expect(detail.workflows[0]).toMatchObject({ workflowId, workflowName: 'My workflow' }); + }); + + // A covered workflow is removed along with the workflow itself, so a review can end up with none + it('returns no workflows when the review no longer covers any', async () => { + const detail = await service.getDetail(requester, requestId); + + expect(detail.workflows).toEqual([]); + expect(detail.workflowName).toBeNull(); + expect(detail.workflowVersionId).toBeNull(); + }); + }); + + describe('the two versions to compare', () => { + it('returns the version under review and the published version to compare it against', async () => { + mockChildRow('ver-pinned'); + publishedVersionRepository.getPublishedVersionId.mockResolvedValue('ver-published'); + workflowHistoryService.findVersion.mockImplementation(async (_workflowId, versionId) => + historyVersion(versionId), + ); + + const detail = await service.getDetail(requester, requestId); + + expect(detail.workflows[0]?.pinnedVersion).toEqual({ + versionId: 'ver-pinned', + nodes: [expect.objectContaining({ name: 'node-ver-pinned' })], + connections: {}, + nodeGroups: [], + createdAt: '2026-06-01T00:00:00.000Z', + }); + expect(detail.workflows[0]?.baselineVersion).toMatchObject({ versionId: 'ver-published' }); + }); + + it('has nothing to compare against when the workflow was never published', async () => { + mockChildRow('ver-pinned'); + publishedVersionRepository.getPublishedVersionId.mockResolvedValue(null); + workflowHistoryService.findVersion.mockResolvedValue(historyVersion('ver-pinned')); + + const detail = await service.getDetail(requester, requestId); + + expect(detail.workflows[0]?.pinnedVersion).toMatchObject({ versionId: 'ver-pinned' }); + expect(detail.workflows[0]?.baselineVersion).toBeNull(); + }); + + it('has nothing to compare against when the published version is no longer stored', async () => { + mockChildRow('ver-pinned'); + publishedVersionRepository.getPublishedVersionId.mockResolvedValue('ver-published'); + workflowHistoryService.findVersion.mockImplementation(async (_workflowId, versionId) => + versionId === 'ver-pinned' ? historyVersion(versionId) : null, + ); + + const detail = await service.getDetail(requester, requestId); + + expect(detail.workflows[0]?.baselineVersion).toBeNull(); + }); + + it('returns no version under review when the review does not point at one', async () => { + mockChildRow(null); + + const detail = await service.getDetail(requester, requestId); + + expect(detail.workflows[0]?.pinnedVersion).toBeNull(); + expect(detail.workflows[0]?.workflowVersionId).toBeNull(); + // Nothing is looked up for a workflow that points at no version + expect(workflowHistoryService.findVersion).not.toHaveBeenCalled(); + }); + + it('returns no version under review when it is no longer stored', async () => { + mockChildRow('ver-pruned'); + workflowHistoryService.findVersion.mockResolvedValue(null); + + const detail = await service.getDetail(requester, requestId); + + expect(detail.workflows[0]?.workflowVersionId).toBe('ver-pruned'); + expect(detail.workflows[0]?.pinnedVersion).toBeNull(); + }); + + it('does not reveal who edited a version', async () => { + mockChildRow('ver-pinned'); + workflowHistoryService.findVersion.mockResolvedValue(historyVersion('ver-pinned')); + + const detail = await service.getDetail(requester, requestId); + + expect(detail.workflows[0]?.pinnedVersion).not.toHaveProperty('authors'); + }); + }); +}); diff --git a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.list.service.test.ts b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.list.service.test.ts index e69b0814269..2e6a994ced9 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.list.service.test.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.list.service.test.ts @@ -6,6 +6,7 @@ import type { SharedWorkflowRepository, User, UserRepository, + WorkflowPublishedVersionRepository, WorkflowReviewRequest, WorkflowReviewRequestReviewerRepository, } from '@n8n/db'; @@ -31,6 +32,7 @@ describe('WorkflowReviewRequestService list', () => { const workflowFinderService = mock(); const workflowHistoryService = mock(); const sharedWorkflowRepository = mock(); + const publishedVersionRepository = mock(); const workflowReviewRequestRepository = mockInstance(WorkflowReviewRequestRepository); const workflowReviewRequestWorkflowRepository = mockInstance( WorkflowReviewRequestWorkflowRepository, @@ -63,6 +65,7 @@ describe('WorkflowReviewRequestService list', () => { workflowFinderService, workflowHistoryService, sharedWorkflowRepository, + publishedVersionRepository, workflowReviewRequestRepository, workflowReviewRequestWorkflowRepository, workflowReviewRequestAuthorRepository, diff --git a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.service.test.ts b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.service.test.ts index 9e20224c099..a55f56b8d8b 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.service.test.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.service.test.ts @@ -12,6 +12,7 @@ import type { SharedWorkflowRepository, UserRepository, WorkflowEntity, + WorkflowPublishedVersionRepository, WorkflowReviewRequest, WorkflowReviewRequestAuthorRepository, WorkflowReviewRequestRepository, @@ -55,6 +56,7 @@ describe('WorkflowReviewRequestService', () => { const workflowFinderService = mock(); const workflowHistoryService = mock(); const sharedWorkflowRepository = mock(); + const publishedVersionRepository = mock(); const requestRepository = mock(); const workflowRepository = mock(); const authorRepository = mock(); @@ -75,6 +77,7 @@ describe('WorkflowReviewRequestService', () => { workflowFinderService, workflowHistoryService, sharedWorkflowRepository, + publishedVersionRepository, requestRepository, workflowRepository, authorRepository, diff --git a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.update.service.test.ts b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.update.service.test.ts index 362b3315646..56a6e47c205 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.update.service.test.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-request.update.service.test.ts @@ -7,6 +7,7 @@ import type { User, UserRepository, WorkflowEntity, + WorkflowPublishedVersionRepository, WorkflowReviewRequest, WorkflowReviewRequestAuthorRepository, WorkflowReviewRequestRepository, @@ -44,6 +45,7 @@ describe('WorkflowReviewRequestService.updateVersion', () => { const workflowFinderService = mock(); const workflowHistoryService = mock(); const sharedWorkflowRepository = mock(); + const publishedVersionRepository = mock(); const requestRepository = mock(); const workflowRepository = mock(); const authorRepository = mock(); @@ -64,6 +66,7 @@ describe('WorkflowReviewRequestService.updateVersion', () => { workflowFinderService, workflowHistoryService, sharedWorkflowRepository, + publishedVersionRepository, requestRepository, workflowRepository, authorRepository, diff --git a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.controller.integration.test.ts b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.controller.integration.test.ts index 286908874e3..658a43143b1 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.controller.integration.test.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.controller.integration.test.ts @@ -10,6 +10,8 @@ import { import type { Project, User } from '@n8n/db'; import { UserRepository, + WorkflowPublishedVersionRepository, + WorkflowRepository, WorkflowReviewRequestAuthorRepository, WorkflowReviewRequestRepository, WorkflowReviewRequestReviewerRepository, @@ -43,6 +45,8 @@ let workflowRepository: WorkflowReviewRequestWorkflowRepository; let authorRepository: WorkflowReviewRequestAuthorRepository; let reviewerRepository: WorkflowReviewRequestReviewerRepository; let userRepository: UserRepository; +let publishedVersionRepository: WorkflowPublishedVersionRepository; +let workflowEntityRepository: WorkflowRepository; let policyService: WorkflowReviewPolicyService; beforeAll(() => { @@ -51,6 +55,8 @@ beforeAll(() => { authorRepository = Container.get(WorkflowReviewRequestAuthorRepository); reviewerRepository = Container.get(WorkflowReviewRequestReviewerRepository); userRepository = Container.get(UserRepository); + publishedVersionRepository = Container.get(WorkflowPublishedVersionRepository); + workflowEntityRepository = Container.get(WorkflowRepository); policyService = Container.get(WorkflowReviewPolicyService); }); @@ -64,6 +70,8 @@ beforeEach(async () => { 'WorkflowReviewRequestWorkflow', 'WorkflowReviewRequest', 'SharedWorkflow', + // Before WorkflowHistory: the published pointer FKs onto it with onDelete RESTRICT + 'WorkflowPublishedVersion', 'WorkflowHistory', 'WorkflowEntity', 'ProjectRelation', @@ -1567,3 +1575,206 @@ describe('GET /workflow-review-requests/inbox', () => { .expect(400); }); }); + +describe('GET /workflow-review-requests/:workflowReviewRequestId', () => { + /** Seed a review request in `projectId` pinned to `versionId`, authored by `author`. */ + async function seedRequest( + workflowId: string, + versionId: string | null, + author: User, + projectId = teamProject.id, + ) { + const request = await requestRepository.createRequest({ + projectId, + title: 'Please review', + description: 'Some context', + createdById: author.id, + }); + await workflowRepository.createWorkflowRow({ + workflowReviewRequestId: request.id, + workflowId, + workflowVersionId: versionId, + }); + await authorRepository.addAuthor({ workflowReviewRequestId: request.id, userId: author.id }); + return request; + } + + test('returns the review, the workflows it covers, and both versions to compare', async () => { + const workflow = await createWorkflow({ name: 'Reviewed workflow' }, teamProject); + const baseline = await createWorkflowHistoryItem(workflow.id, { + versionId: 'version-published', + }); + await createWorkflowHistoryItem(workflow.id, { versionId: 'version-pinned' }); + await publishedVersionRepository.setPublishedVersion(workflow.id, baseline.versionId); + const reviewer = await createAdmin(); + const request = await seedRequest(workflow.id, 'version-pinned', owner); + await reviewerRepository.addReviewers({ + workflowReviewRequestId: request.id, + userIds: [reviewer.id], + }); + + const response = await ownerAgent.get(`/workflow-review-requests/${request.id}`).expect(200); + + expect(response.body.data).toMatchObject({ + id: request.id, + projectId: teamProject.id, + state: 'open', + decision: 'pending', + title: 'Please review', + description: 'Some context', + workflowName: 'Reviewed workflow', + workflowVersionId: 'version-pinned', + requester: { id: owner.id, email: owner.email }, + reviewers: [{ id: reviewer.id, email: reviewer.email }], + }); + + expect(response.body.data.workflows).toHaveLength(1); + const [child] = response.body.data.workflows; + expect(child).toMatchObject({ + workflowId: workflow.id, + workflowName: 'Reviewed workflow', + workflowVersionId: 'version-pinned', + }); + expect(child.pinnedVersion).toMatchObject({ + versionId: 'version-pinned', + connections: {}, + nodeGroups: [], + }); + expect(child.pinnedVersion.nodes).toHaveLength(1); + expect(child.pinnedVersion.nodes[0]).toMatchObject({ name: 'Start' }); + expect(child.pinnedVersion).not.toHaveProperty('authors'); + expect(child.baselineVersion).toMatchObject({ versionId: 'version-published' }); + }); + + test('has nothing to compare against when the workflow was never published', async () => { + const workflow = await createWorkflow({}, teamProject); + await createWorkflowHistoryItem(workflow.id, { versionId: 'version-pinned' }); + const request = await seedRequest(workflow.id, 'version-pinned', owner); + + const response = await ownerAgent.get(`/workflow-review-requests/${request.id}`).expect(200); + + expect(response.body.data.workflows[0].pinnedVersion).toMatchObject({ + versionId: 'version-pinned', + }); + expect(response.body.data.workflows[0].baselineVersion).toBeNull(); + }); + + test('returns no version under review when the review does not point at one', async () => { + const workflow = await createWorkflow({}, teamProject); + const request = await seedRequest(workflow.id, null, owner); + + const response = await ownerAgent.get(`/workflow-review-requests/${request.id}`).expect(200); + + expect(response.body.data.workflows[0]).toMatchObject({ + workflowVersionId: null, + pinnedVersion: null, + baselineVersion: null, + }); + }); + + test('still opens the review after its workflow was deleted', async () => { + const workflow = await createWorkflow({}, teamProject); + await createWorkflowHistoryItem(workflow.id, { versionId: 'version-pinned' }); + const request = await seedRequest(workflow.id, 'version-pinned', owner); + + // Deleting the workflow removes the review's reference to it as well + await workflowEntityRepository.delete({ id: workflow.id }); + + const response = await ownerAgent.get(`/workflow-review-requests/${request.id}`).expect(200); + + expect(response.body.data.id).toBe(request.id); + expect(response.body.data.workflows).toEqual([]); + expect(response.body.data.workflowName).toBeNull(); + expect(response.body.data.workflowVersionId).toBeNull(); + }); + + test('lets an editor in the review project open it', async () => { + const workflow = await createWorkflow({}, teamProject); + const request = await seedRequest(workflow.id, null, owner); + + const response = await memberAgent.get(`/workflow-review-requests/${request.id}`).expect(200); + + expect(response.body.data.id).toBe(request.id); + }); + + test('hides the review from a viewer who cannot publish in the project', async () => { + const workflow = await createWorkflow({}, teamProject); + const request = await seedRequest(workflow.id, null, owner); + + await viewerAgent.get(`/workflow-review-requests/${request.id}`).expect(404); + }); + + test('hides the review from someone outside its project', async () => { + const otherProject = await createTeamProject('Unrelated Project', owner); + const workflow = await createWorkflow({}, otherProject); + const request = await seedRequest(workflow.id, null, owner, otherProject.id); + + await memberAgent.get(`/workflow-review-requests/${request.id}`).expect(404); + }); + + test('hides the review once its workflow moves to a project the user cannot see', async () => { + // The review still points at `teamProject`, where member is allowed to publish, + // while the workflow itself has moved to a project member has no access to + const destinationProject = await createTeamProject('Destination Project', owner); + const workflow = await createWorkflow({}, destinationProject); + await createWorkflowHistoryItem(workflow.id, { versionId: 'version-pinned' }); + const request = await seedRequest(workflow.id, 'version-pinned', owner, teamProject.id); + + await memberAgent.get(`/workflow-review-requests/${request.id}`).expect(404); + }); + + test('leaves out a workflow the requester can no longer see, but still opens the review', async () => { + // Viewer asked for the review while the workflow was reachable; it has since moved + // to a project they have no access to, so the content must not come back with it + const destinationProject = await createTeamProject('Moved Away', owner); + const workflow = await createWorkflow({}, destinationProject); + await createWorkflowHistoryItem(workflow.id, { versionId: 'version-pinned' }); + const request = await seedRequest(workflow.id, 'version-pinned', viewer, teamProject.id); + + const response = await viewerAgent.get(`/workflow-review-requests/${request.id}`).expect(200); + + expect(response.body.data.id).toBe(request.id); + expect(response.body.data.workflows).toEqual([]); + expect(response.body.data.workflowName).toBeNull(); + expect(response.body.data.workflowVersionId).toBeNull(); + }); + + test('always shows people the review they asked for themselves', async () => { + const workflow = await createWorkflow({}, teamProject); + const request = await seedRequest(workflow.id, null, viewer); + + const response = await viewerAgent.get(`/workflow-review-requests/${request.id}`).expect(200); + + expect(response.body.data.id).toBe(request.id); + }); + + test('reports a review that does not exist as not found', async () => { + await ownerAgent.get('/workflow-review-requests/unknown-request').expect(404); + }); + + test('does not shadow the inbox, summary, and eligible-reviewers endpoints', async () => { + const workflow = await createWorkflow({}, teamProject); + await seedRequest(workflow.id, null, owner); + + await ownerAgent.get('/workflow-review-requests/inbox').expect(200); + await ownerAgent.get('/workflow-review-requests/summary').expect(200); + // 400 (missing workflowId), not 404 — proves it still reaches its own handler + await ownerAgent.get('/workflow-review-requests/eligible-reviewers').expect(400); + }); + + test('refuses to open a review when an admin has turned reviews off', async () => { + const workflow = await createWorkflow({}, teamProject); + const request = await seedRequest(workflow.id, null, owner); + await policyService.set(false); + + await ownerAgent.get(`/workflow-review-requests/${request.id}`).expect(403); + }); + + test('refuses to open a review on an instance without a workflow reviews licence', async () => { + testServer.license.disable('feat:workflowReviews'); + + await ownerAgent.get('/workflow-review-requests/some-request').expect(403); + + testServer.license.enable('feat:workflowReviews'); + }); +}); diff --git a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.env-gate.integration.test.ts b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.env-gate.integration.test.ts index 38da8c43c2f..08412761d72 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.env-gate.integration.test.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/__tests__/workflow-review-requests.env-gate.integration.test.ts @@ -53,6 +53,10 @@ describe('workflow-review-requests (env flag off)', () => { test('GET summary is unreachable (404)', async () => { await ownerAgent.get('/workflow-review-requests/summary').expect(404); }); + + test('GET detail is unreachable (404)', async () => { + await ownerAgent.get('/workflow-review-requests/some-id').expect(404); + }); }); describe('GET /workflow-review-requests (env flag off)', () => { diff --git a/packages/cli/src/modules/workflow-reviews.ee/workflow-review-request.service.ts b/packages/cli/src/modules/workflow-reviews.ee/workflow-review-request.service.ts index 9d2f023d779..8cbbcc367fe 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/workflow-review-request.service.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/workflow-review-request.service.ts @@ -12,6 +12,9 @@ import type { GetWorkflowReviewInboxSummaryResponse, WorkflowReviewInboxItem, WorkflowReviewEligibleReviewer, + WorkflowReviewRequestDetail, + WorkflowReviewRequestWorkflowDetail, + WorkflowReviewVersionSnapshot, } from '@n8n/api-types'; import { LicenseState, Logger } from '@n8n/backend-common'; import { @@ -20,15 +23,18 @@ import { ProjectRelationRepository, SharedWorkflowRepository, UserRepository, + WorkflowPublishedVersionRepository, WorkflowReviewRequestAuthorRepository, WorkflowReviewRequestRepository, WorkflowReviewRequestReviewerRepository, WorkflowReviewRequestWorkflowRepository, type InboxCursor, type User, + type WorkflowHistory, type WorkflowReviewRequest, type WorkflowReviewRequestLinkedWorkflow, type WorkflowReviewRequestReviewer, + type WorkflowReviewRequestWorkflowDetailRow, } from '@n8n/db'; import { Service } from '@n8n/di'; import { @@ -58,6 +64,7 @@ export class WorkflowReviewRequestService { private readonly workflowFinderService: WorkflowFinderService, private readonly workflowHistoryService: WorkflowHistoryService, private readonly sharedWorkflowRepository: SharedWorkflowRepository, + private readonly workflowPublishedVersionRepository: WorkflowPublishedVersionRepository, private readonly workflowReviewRequestRepository: WorkflowReviewRequestRepository, private readonly workflowReviewRequestWorkflowRepository: WorkflowReviewRequestWorkflowRepository, private readonly workflowReviewRequestAuthorRepository: WorkflowReviewRequestAuthorRepository, @@ -666,6 +673,139 @@ export class WorkflowReviewRequestService { }); } + /** + * Visibility starts from the inbox rule (requester OR `workflow:publish` in the + * review's project OR globally), then narrows per workflow to what the caller can + * currently read — see {@link filterReadableWorkflowRows}. + */ + async getDetail( + user: User, + workflowReviewRequestId: string, + ): Promise { + await this.assertFeatureAvailable(); + + const request = await this.workflowReviewRequestRepository.findById(workflowReviewRequestId); + if (!request || !(await this.canAccessRequest(user, request))) { + throw new NotFoundError('Could not find review request'); + } + + const [workflowRows, reviewerRows] = await Promise.all([ + this.workflowReviewRequestWorkflowRepository.findLinkedWorkflowDetailsByRequestId(request.id), + this.workflowReviewRequestReviewerRepository.findByRequestIds([request.id]), + ]); + + const readableRows = await this.filterReadableWorkflowRows(user, workflowRows); + // Someone who reaches this review through its project has no reason to learn it + // exists once they can read none of the workflows it covers. The requester already + // knows, and their inbox still lists it, so they keep the record — narrowed to the + // workflows they can currently read. + if (request.createdById !== user.id && workflowRows.length > 0 && readableRows.length === 0) { + throw new NotFoundError('Could not find review request'); + } + + const [workflows, participantsByRequestId] = await Promise.all([ + Promise.all(readableRows.map(async (row) => await this.toWorkflowDetail(row))), + this.hydrateParticipants([request], reviewerRows), + ]); + + const { requester, reviewers } = participantsByRequestId.get(request.id) ?? { + requester: null, + reviewers: [], + }; + return { + // One workflow per review for now, so the summary fields mirror the first row + ...this.toInboxItem(request, workflows.at(0) ?? null, requester, reviewers), + description: request.description, + workflows, + }; + } + + /** Inbox visibility rule: requester, or `workflow:publish` in the review's project. */ + private async canAccessRequest(user: User, request: WorkflowReviewRequest): Promise { + if (request.createdById === user.id) { + return true; + } + + const projectIds = await this.resolveAccessibleProjectIds(user); + return projectIds === null || projectIds.includes(request.projectId); + } + + /** + * A review's `projectId` is fixed at creation and nothing closes open reviews when a + * workflow is transferred, so the stored project does not prove the caller may still + * read a covered workflow. Re-check every row against the workflow's *current* owner + * before returning its content. + * + * This applies to the requester too. They held publish rights when they opened the + * review, but may have lost them since — and because the baseline is resolved at read + * time, an exemption would leave them reading versions published after they lost + * access. + */ + private async filterReadableWorkflowRows( + user: User, + rows: WorkflowReviewRequestWorkflowDetailRow[], + ): Promise { + const readable = await Promise.all( + rows.map(async (row) => + (await this.workflowFinderService.findWorkflowForUser(row.workflowId, user, [ + 'workflow:read', + ])) + ? row + : null, + ), + ); + + return readable.filter((row): row is WorkflowReviewRequestWorkflowDetailRow => row !== null); + } + + /** + * Both diff sides for one child row. The baseline is resolved at read time, so + * a publish during an open review moves what reviewers are diffing against. + */ + private async toWorkflowDetail( + row: WorkflowReviewRequestWorkflowDetailRow, + ): Promise { + const publishedVersionId = await this.workflowPublishedVersionRepository.getPublishedVersionId( + row.workflowId, + ); + + const [pinnedVersion, baselineVersion] = await Promise.all([ + this.findVersionSnapshot(row.workflowId, row.workflowVersionId), + this.findVersionSnapshot(row.workflowId, publishedVersionId), + ]); + + return { + workflowId: row.workflowId, + workflowName: row.workflowName, + workflowVersionId: row.workflowVersionId, + pinnedVersion, + baselineVersion, + }; + } + + /** `null` version id, or a version whose history row was pruned, both mean "no content". */ + private async findVersionSnapshot( + workflowId: string, + versionId: string | null, + ): Promise { + if (!versionId) { + return null; + } + + const version = await this.workflowHistoryService.findVersion(workflowId, versionId); + return version ? this.toVersionSnapshot(version) : null; + } + + private toVersionSnapshot(version: WorkflowHistory): WorkflowReviewVersionSnapshot { + return { + versionId: version.versionId, + nodes: version.nodes, + connections: version.connections, + nodeGroups: version.nodeGroups, + createdAt: version.createdAt.toISOString(), + }; + } + private async assertFeatureAvailable(): Promise { if (!isWorkflowReviewsFeatureAvailable(this.licenseState.isWorkflowReviewsLicensed())) { throw new ForbiddenError('Workflow reviews are not available on this instance'); diff --git a/packages/cli/src/modules/workflow-reviews.ee/workflow-review-requests.controller.ts b/packages/cli/src/modules/workflow-reviews.ee/workflow-review-requests.controller.ts index 9d87749f84c..71e31ad03e9 100644 --- a/packages/cli/src/modules/workflow-reviews.ee/workflow-review-requests.controller.ts +++ b/packages/cli/src/modules/workflow-reviews.ee/workflow-review-requests.controller.ts @@ -7,6 +7,7 @@ import { type GetWorkflowReviewInboxSummaryResponse, type ListWorkflowReviewInboxResponse, ListWorkflowReviewInboxQueryDto, + type WorkflowReviewRequestDetail, } from '@n8n/api-types'; import { AuthenticatedRequest } from '@n8n/db'; import { Body, Get, Licensed, Param, Post, Query, RestController } from '@n8n/decorators'; @@ -100,4 +101,21 @@ export class WorkflowReviewRequestsController { ): Promise { return await this.workflowReviewRequestService.getInboxSummaryForUser(req.user); } + + /** + * Review detail, including the diff inputs per covered workflow. + * + * Keep this last: routes register in declaration order, so a `/:id` pattern + * declared earlier would swallow the literal paths above. Authorization lives + * in the service — `@ProjectScope` cannot resolve a project from a review id. + */ + @Get('/:workflowReviewRequestId') + @Licensed('feat:workflowReviews') + async getDetail( + req: AuthenticatedRequest, + _res: Response, + @Param('workflowReviewRequestId') workflowReviewRequestId: string, + ): Promise { + return await this.workflowReviewRequestService.getDetail(req.user, workflowReviewRequestId); + } } From 587b23d8866d2235575cb8f90cd07fe0ae9075c8 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 28 Jul 2026 17:47:24 +0200 Subject: [PATCH 003/130] fix(core): Block deletion of the SSO role mapping default condition role (#35086) Co-authored-by: Claude Sonnet 5 --- .../role-deletion-checker.ee.test.ts | 40 +++++++++++++++++-- .../role-deletion-checker.ee.ts | 26 +++++++++--- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/modules/provisioning.ee/__tests__/role-deletion-checker.ee.test.ts b/packages/cli/src/modules/provisioning.ee/__tests__/role-deletion-checker.ee.test.ts index 07cd60b89da..9e1875d0bd2 100644 --- a/packages/cli/src/modules/provisioning.ee/__tests__/role-deletion-checker.ee.test.ts +++ b/packages/cli/src/modules/provisioning.ee/__tests__/role-deletion-checker.ee.test.ts @@ -1,19 +1,27 @@ +import type { ProvisioningConfigDto } from '@n8n/api-types'; import type { RoleMappingRuleRepository } from '@n8n/db'; import { mock } from 'vitest-mock-extended'; import { ProvisioningRoleDeletionChecker } from '../role-deletion-checker.ee'; +import type { ProvisioningService } from '../provisioning.service.ee'; describe('ProvisioningRoleDeletionChecker', () => { const roleMappingRuleRepository = mock(); - const checker = new ProvisioningRoleDeletionChecker(roleMappingRuleRepository); + const provisioningService = mock(); + const checker = new ProvisioningRoleDeletionChecker( + roleMappingRuleRepository, + provisioningService, + ); beforeEach(() => { vi.clearAllMocks(); + roleMappingRuleRepository.count.mockResolvedValue(0); + provisioningService.getConfig.mockResolvedValue( + mock({ defaultInstanceRole: undefined }), + ); }); - it('reports no blockers when no mapping rule references the role', async () => { - roleMappingRuleRepository.count.mockResolvedValue(0); - + it('reports no blockers when the role is unreferenced', async () => { const blockers = await checker.findRoleDeletionBlockers('global:auditor'); expect(roleMappingRuleRepository.count).toHaveBeenCalledWith({ @@ -37,4 +45,28 @@ describe('ProvisioningRoleDeletionChecker', () => { expect(blockers).toEqual(['referenced by 3 role mapping rules']); }); + + it('reports a blocker when the role is the default condition role', async () => { + provisioningService.getConfig.mockResolvedValue( + mock({ defaultInstanceRole: 'global:auditor' }), + ); + + const blockers = await checker.findRoleDeletionBlockers('global:auditor'); + + expect(blockers).toEqual(['configured as the role mapping default condition role']); + }); + + it('reports both blockers when the role is referenced by a mapping rule and is the default condition role', async () => { + roleMappingRuleRepository.count.mockResolvedValue(1); + provisioningService.getConfig.mockResolvedValue( + mock({ defaultInstanceRole: 'global:auditor' }), + ); + + const blockers = await checker.findRoleDeletionBlockers('global:auditor'); + + expect(blockers).toEqual([ + 'referenced by 1 role mapping rule', + 'configured as the role mapping default condition role', + ]); + }); }); diff --git a/packages/cli/src/modules/provisioning.ee/role-deletion-checker.ee.ts b/packages/cli/src/modules/provisioning.ee/role-deletion-checker.ee.ts index 1441f5762f5..298fe22954c 100644 --- a/packages/cli/src/modules/provisioning.ee/role-deletion-checker.ee.ts +++ b/packages/cli/src/modules/provisioning.ee/role-deletion-checker.ee.ts @@ -3,20 +3,36 @@ import { Service } from '@n8n/di'; import type { RoleDeletionChecker } from '@/services/role-deletion-check-proxy.service'; +import { ProvisioningService } from './provisioning.service.ee'; + /** * Blocks deletion of a custom role while it is still targeted by an SSO role - * mapping rule. Registered on the core `RoleDeletionCheckProxy` by the - * provisioning module so `RoleService` stays decoupled from this module. + * mapping rule or configured as the role mapping default condition. + * Registered on the core `RoleDeletionCheckProxy` by the provisioning module + * so `RoleService` stays decoupled from this module. */ @Service() export class ProvisioningRoleDeletionChecker implements RoleDeletionChecker { - constructor(private readonly roleMappingRuleRepository: RoleMappingRuleRepository) {} + constructor( + private readonly roleMappingRuleRepository: RoleMappingRuleRepository, + private readonly provisioningService: ProvisioningService, + ) {} async findRoleDeletionBlockers(roleSlug: string): Promise { + const blockers: string[] = []; + const count = await this.roleMappingRuleRepository.count({ where: { role: { slug: roleSlug } }, }); - if (count === 0) return []; - return [`referenced by ${count} role mapping rule${count === 1 ? '' : 's'}`]; + if (count > 0) { + blockers.push(`referenced by ${count} role mapping rule${count === 1 ? '' : 's'}`); + } + + const { defaultInstanceRole } = await this.provisioningService.getConfig(); + if (defaultInstanceRole === roleSlug) { + blockers.push('configured as the role mapping default condition role'); + } + + return blockers; } } From 0ef049d12ae883035cd6c490fd3dfa0be266bb6f Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Tue, 28 Jul 2026 17:13:00 +0100 Subject: [PATCH 004/130] build(core): Upgrade agents to AI SDK 7 (#35072) --- package.json | 4 +- packages/@n8n/agents/package.json | 6 +- ...in-the-tool-result-visible-to-the-llm.json | 4 +- .../integration/telemetry-langsmith.test.ts | 4 +- .../integration/zod-validation-error.test.ts | 2 +- .../src/__tests__/langsmith-telemetry.test.ts | 2 +- packages/@n8n/agents/src/index.ts | 2 + .../agent-runtime-conversion.test.ts | 65 + .../runtime/__tests__/agent-runtime.test.ts | 186 +- .../runtime/__tests__/crash-resume.test.ts | 6 +- .../runtime/__tests__/model-factory.test.ts | 16 +- .../observation-log-observer.test.ts | 7 +- .../observation-log-reflector.test.ts | 7 +- .../runtime/__tests__/provider-quirks.test.ts | 2 +- .../__tests__/title-generation.test.ts | 14 +- .../agents/src/runtime/loop/agent-runtime.ts | 18 +- .../agents/src/runtime/loop/generate-sink.ts | 3 +- .../src/runtime/loop/run-output-sink.ts | 6 +- .../agents/src/runtime/loop/stream-sink.ts | 7 +- .../memory/episodic-memory-defaults.ts | 4 +- .../memory/observation-log-defaults.ts | 10 +- .../src/runtime/memory/title-generation.ts | 23 +- .../@n8n/agents/src/runtime/model/messages.ts | 62 +- .../agents/src/runtime/model/model-factory.ts | 9 +- .../src/runtime/model/provider-quirks.ts | 7 +- .../agents/src/runtime/streaming/stream.ts | 4 +- .../__tests__/sub-agent-telemetry.test.ts | 43 + .../runtime/telemetry/runtime-telemetry.ts | 12 +- .../runtime/telemetry/telemetry-options.ts | 17 +- .../agents/src/runtime/tools/tool-adapter.ts | 1 + .../src/sdk/__tests__/agent-telemetry.test.ts | 5 +- .../src/sdk/__tests__/telemetry.test.ts | 196 +- .../src/sdk/metadata-enriched-tracer.ts | 69 + packages/@n8n/agents/src/sdk/telemetry.ts | 143 +- packages/@n8n/agents/src/types/index.ts | 2 + packages/@n8n/agents/src/types/sdk/agent.ts | 15 +- packages/@n8n/agents/src/types/sdk/message.ts | 13 + packages/@n8n/agents/src/types/telemetry.ts | 8 +- packages/@n8n/instance-ai/package.json | 2 +- .../debug/__tests__/run-debug-buffer.test.ts | 42 +- .../instance-ai/src/debug/run-debug-buffer.ts | 41 +- packages/cli/package.json | 2 +- .../agent-run-tracing.service.test.ts | 2 +- .../__tests__/run-debug-gating.test.ts | 4 + pnpm-lock.yaml | 1889 +++++++++-------- pnpm-workspace.yaml | 34 +- 46 files changed, 1796 insertions(+), 1224 deletions(-) create mode 100644 packages/@n8n/agents/src/sdk/metadata-enriched-tracer.ts diff --git a/package.json b/package.json index ce948e4522d..25caa375586 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "@lezer/common": "^1.2.0", "@mistralai/mistralai": "^1.10.0", "@n8n/typeorm>@sentry/node": "catalog:sentry", - "@opentelemetry/api": "1.9.0", + "@opentelemetry/api": "catalog:", "@opentelemetry/core": "2.8.0", "@types/node": "^20.17.50", "chokidar": "4.0.3", @@ -141,7 +141,7 @@ "tmp": "0.2.7", "nodemailer": "8.0.10", "validator": "13.15.26", - "zod": "3.25.67", + "zod": "3.25.76", "js-yaml": "4.3.0", "body-parser": "2.3.0", "glob@10": "10.5.0", diff --git a/packages/@n8n/agents/package.json b/packages/@n8n/agents/package.json index c11a076745a..c1f6b44ba3e 100644 --- a/packages/@n8n/agents/package.json +++ b/packages/@n8n/agents/package.json @@ -2,6 +2,9 @@ "name": "@n8n/agents", "version": "0.18.0", "description": "AI agent SDK for n8n's code-first execution engine", + "engines": { + "node": ">=22.22" + }, "main": "dist/index.js", "module": "dist/index.js", "types": "dist/index.d.ts", @@ -99,6 +102,7 @@ "@ai-sdk/mistral": "catalog:", "@ai-sdk/openai": "catalog:", "@ai-sdk/openai-compatible": "catalog:", + "@ai-sdk/otel": "catalog:", "@ai-sdk/provider-utils": "catalog:", "@ai-sdk/xai": "catalog:", "@daytona/sdk": "catalog:", @@ -153,7 +157,7 @@ "devDependencies": { "@n8n/typescript-config": "workspace:*", "@n8n/vitest-config": "workspace:*", - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": "catalog:", "@opentelemetry/context-async-hooks": "^2.7.1", "@pinecone-database/pinecone": "catalog:", "@qdrant/js-client-rest": "catalog:", diff --git a/packages/@n8n/agents/src/__tests__/integration/cassettes/zod-validation-error/includes-the-zod-error-text-in-the-tool-result-visible-to-the-llm.json b/packages/@n8n/agents/src/__tests__/integration/cassettes/zod-validation-error/includes-the-zod-error-text-in-the-tool-result-visible-to-the-llm.json index ce22f4c1829..25bc64a1b85 100644 --- a/packages/@n8n/agents/src/__tests__/integration/cassettes/zod-validation-error/includes-the-zod-error-text-in-the-tool-result-visible-to-the-llm.json +++ b/packages/@n8n/agents/src/__tests__/integration/cassettes/zod-validation-error/includes-the-zod-error-text-in-the-tool-result-visible-to-the-llm.json @@ -127,7 +127,7 @@ { "type": "tool_result", "tool_use_id": "toolu_01J4prxB28Ksk13jkhDsHkMd", - "content": "Invalid input for tool find_user: Type validation failed: Value: {\"age\":150}.\nError message: [\n {\n \"code\": \"too_big\",\n \"maximum\": 99,\n \"type\": \"number\",\n \"inclusive\": true,\n \"exact\": false,\n \"message\": \"age must be at most 99\",\n \"path\": [\n \"age\"\n ]\n }\n]", + "content": "AI_InvalidToolInputError: Invalid input for tool find_user: AI_TypeValidationError: Type validation failed: Value: {\"age\":150}.\nError message: [\n {\n \"code\": \"too_big\",\n \"maximum\": 99,\n \"type\": \"number\",\n \"inclusive\": true,\n \"exact\": false,\n \"message\": \"age must be at most 99\",\n \"path\": [\n \"age\"\n ]\n }\n]", "is_error": true } ] @@ -237,7 +237,7 @@ { "type": "tool_result", "tool_use_id": "toolu_01J4prxB28Ksk13jkhDsHkMd", - "content": "Invalid input for tool find_user: Type validation failed: Value: {\"age\":150}.\nError message: [\n {\n \"code\": \"too_big\",\n \"maximum\": 99,\n \"type\": \"number\",\n \"inclusive\": true,\n \"exact\": false,\n \"message\": \"age must be at most 99\",\n \"path\": [\n \"age\"\n ]\n }\n]", + "content": "AI_InvalidToolInputError: Invalid input for tool find_user: AI_TypeValidationError: Type validation failed: Value: {\"age\":150}.\nError message: [\n {\n \"code\": \"too_big\",\n \"maximum\": 99,\n \"type\": \"number\",\n \"inclusive\": true,\n \"exact\": false,\n \"message\": \"age must be at most 99\",\n \"path\": [\n \"age\"\n ]\n }\n]", "is_error": true } ] diff --git a/packages/@n8n/agents/src/__tests__/integration/telemetry-langsmith.test.ts b/packages/@n8n/agents/src/__tests__/integration/telemetry-langsmith.test.ts index 6683a3c66cc..c592cfe160a 100644 --- a/packages/@n8n/agents/src/__tests__/integration/telemetry-langsmith.test.ts +++ b/packages/@n8n/agents/src/__tests__/integration/telemetry-langsmith.test.ts @@ -6,7 +6,7 @@ * LangSmith, and verifies the full pipeline works end-to-end. * * Pipeline under test: - * Agent.generate() → AI SDK (generateText with experimental_telemetry) + * Agent.generate() → AI SDK (generateText with telemetry) * → OTel spans with ai.operationId → LangSmithOTLPSpanProcessor * → LangSmithOTLPTraceExporter → HTTP POST → captured by local server */ @@ -176,7 +176,7 @@ describe('Telemetry → LangSmith integration', () => { onStart: () => { hookEvents.push('start'); }, - onFinish: () => { + onEnd: () => { hookEvents.push('finish'); }, }) diff --git a/packages/@n8n/agents/src/__tests__/integration/zod-validation-error.test.ts b/packages/@n8n/agents/src/__tests__/integration/zod-validation-error.test.ts index d89bd6d556e..b443365ae32 100644 --- a/packages/@n8n/agents/src/__tests__/integration/zod-validation-error.test.ts +++ b/packages/@n8n/agents/src/__tests__/integration/zod-validation-error.test.ts @@ -42,8 +42,8 @@ describe('Zod validation errors surface to LLM and allow self-correction', () => 'Find a user aged 150. If that age is invalid, use 25 instead and retry. You MUST find a user aged 150, and only then use 25', ); - expect(result.finishReason).toBe('stop'); expect(result.error).toBeUndefined(); + expect(result.finishReason).toBe('stop'); // At least two tool-call messages: one rejected, one resolved const allMessages = filterLlmMessages(result.messages); diff --git a/packages/@n8n/agents/src/__tests__/langsmith-telemetry.test.ts b/packages/@n8n/agents/src/__tests__/langsmith-telemetry.test.ts index a86cc1cd71f..ad83ccc09c3 100644 --- a/packages/@n8n/agents/src/__tests__/langsmith-telemetry.test.ts +++ b/packages/@n8n/agents/src/__tests__/langsmith-telemetry.test.ts @@ -16,7 +16,7 @@ const mockBatchProcessorInstances: Array<{ }> = []; const mockProviderConfigs: unknown[] = []; const mockAwaitPendingTraceBatches = vi.fn(async () => await Promise.resolve()); -const mockTracer = { startSpan: vi.fn() }; +const mockTracer = { startSpan: vi.fn(), startActiveSpan: vi.fn() }; const mockProvider = { getTracer: vi.fn(() => mockTracer), register: vi.fn(), diff --git a/packages/@n8n/agents/src/index.ts b/packages/@n8n/agents/src/index.ts index d61a7ffcfba..de88b2d395b 100644 --- a/packages/@n8n/agents/src/index.ts +++ b/packages/@n8n/agents/src/index.ts @@ -207,9 +207,11 @@ export { verify } from './sdk/verify'; export type { VerifyResult } from './sdk/verify'; export type { ContentCitation, + ContentCustom, ContentFile, ContentMetadata, ContentReasoning, + ContentReasoningFile, ContentText, ContentToolCall, Message, diff --git a/packages/@n8n/agents/src/runtime/__tests__/agent-runtime-conversion.test.ts b/packages/@n8n/agents/src/runtime/__tests__/agent-runtime-conversion.test.ts index b015222b2bf..9848e57f254 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/agent-runtime-conversion.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/agent-runtime-conversion.test.ts @@ -12,6 +12,7 @@ * The tool-result is inserted right after its tool-call, regardless of what * messages follow it in the n8n list. */ +import type { ModelMessage } from 'ai'; import { describe, it, expect } from 'vitest'; import type { Message } from '../../types/sdk/message'; @@ -154,6 +155,70 @@ describe('toAiMessages + fromAiMessages — round-trip', () => { }); }); + it('round-trips SDK 7 reasoning files with provider replay state', () => { + const aiMessage: ModelMessage = { + role: 'assistant', + content: [ + { + type: 'reasoning-file', + data: 'base64-reasoning-data', + mediaType: 'application/octet-stream', + providerOptions: { google: { thoughtSignature: 'gemini-signature' } }, + }, + ], + }; + + const agentMessages = fromAiMessages([aiMessage]); + + expect(agentMessages).toEqual([ + { + role: 'assistant', + content: [ + { + type: 'reasoning-file', + data: 'base64-reasoning-data', + mediaType: 'application/octet-stream', + providerOptions: { google: { thoughtSignature: 'gemini-signature' } }, + }, + ], + }, + ]); + expect(toAiMessages(agentMessages as Message[])).toEqual([aiMessage]); + }); + + it('round-trips SDK 7 custom provider state', () => { + const aiMessage: ModelMessage = { + role: 'assistant', + content: [ + { + type: 'custom', + kind: 'openai.compaction', + providerOptions: { + openai: { itemId: 'cmp_123', encryptedContent: 'encrypted' }, + }, + }, + ], + }; + + const agentMessages = fromAiMessages([aiMessage]); + + expect(agentMessages).toEqual([ + { + role: 'assistant', + content: [ + { + type: 'custom', + kind: 'openai.compaction', + providerOptions: { + openai: { itemId: 'cmp_123', encryptedContent: 'encrypted' }, + }, + }, + ], + }, + ]); + expect(toAiMessages(agentMessages as Message[])).toEqual([aiMessage]); + }); + it.each([ { name: 'no provider metadata', content: {} }, { diff --git a/packages/@n8n/agents/src/runtime/__tests__/agent-runtime.test.ts b/packages/@n8n/agents/src/runtime/__tests__/agent-runtime.test.ts index 2504b25dff1..735c09710bb 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/agent-runtime.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/agent-runtime.test.ts @@ -144,7 +144,7 @@ async function collectChunks(stream: ReadableStream): Promise { expect(counter.incrementTokenCount).toHaveBeenCalledWith(15); }); - it('forwards onStepStart and onStepFinish to generateText and streamText', async () => { + it('forwards onStepStart and onStepEnd to generateText and streamText', async () => { generateText.mockResolvedValue(makeGenerateSuccess()); streamText.mockReturnValue(makeStreamSuccess()); const onStepStart = vi.fn(); - const onStepFinish = vi.fn(); + const onStepEnd = vi.fn(); const { runtime } = createRuntime(); - await runtime.generate('hi', { onStepStart, onStepFinish }); - const streamResult = await runtime.stream('hi', { onStepStart, onStepFinish }); + await runtime.generate('hi', { onStepStart, onStepEnd }); + const streamResult = await runtime.stream('hi', { onStepStart, onStepEnd }); await collectChunks(streamResult.stream); for (const call of generateText.mock.calls) { const args = call[0] as Record; - expect(args.experimental_onStepStart).toBe(onStepStart); - expect(args.onStepFinish).toBe(onStepFinish); + expect(args.onStepStart).toBe(onStepStart); + expect(args.onStepEnd).toBe(onStepEnd); } for (const call of streamText.mock.calls) { const args = call[0] as Record; - expect(args.experimental_onStepStart).toBe(onStepStart); - expect(args.onStepFinish).toBe(onStepFinish); + expect(args.onStepStart).toBe(onStepStart); + expect(args.onStepEnd).toBe(onStepEnd); } }); + it('allows system-role messages in generateText and streamText history', async () => { + generateText.mockResolvedValue(makeGenerateSuccess()); + streamText.mockReturnValue(makeStreamSuccess()); + const input: Message[] = [ + { role: 'system', content: [{ type: 'text', text: 'Historical instruction' }] }, + { role: 'user', content: [{ type: 'text', text: 'Hello' }] }, + ]; + + const { runtime } = createRuntime(); + await runtime.generate(input); + const streamResult = await runtime.stream(input); + await collectChunks(streamResult.stream); + + expect(generateText.mock.calls[0]?.[0]).toEqual( + expect.objectContaining({ allowSystemInMessages: true }), + ); + expect(streamText.mock.calls[0]?.[0]).toEqual( + expect.objectContaining({ allowSystemInMessages: true }), + ); + }); + it('counts provider-executed tool calls when surfaced by the model', async () => { generateText .mockResolvedValueOnce({ @@ -545,7 +566,7 @@ describe('AgentRuntime — execution counters', () => { streamText .mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -949,7 +970,7 @@ describe('AgentRuntime — eager input persistence', () => { // Same shape as the generate case, on the streaming path (the one Instance AI uses): // the abort is handled by the StreamSession, which must persist the turn-so-far. streamText.mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'on it...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'on it...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -985,7 +1006,7 @@ describe('AgentRuntime — eager input persistence', () => { // The model streams two text deltas, then the stop lands — before the turn // completes, so its `newMessages` are never built (finishReason/response reject). streamText.mockReturnValueOnce({ - fullStream: (async function* () { + stream: (async function* () { yield { type: 'text-delta', id: 't1', text: 'Here is my ' }; yield { type: 'text-delta', id: 't1', text: 'partial answer' }; await Promise.resolve(); @@ -1020,7 +1041,7 @@ describe('AgentRuntime — eager input persistence', () => { // fires as the stream closes — before the loop folds the turn into the list. The // completed text must still be recovered (the clear is deferred until the fold). streamText.mockReturnValueOnce({ - fullStream: (async function* () { + stream: (async function* () { yield { type: 'text-delta', id: 't1', text: 'Complete answer' }; await Promise.resolve(); controller.abort(); @@ -1131,7 +1152,7 @@ describe('AgentRuntime.stream() — usage billing on abort', () => { // provider's `message_start` raw event already carried the input/cache + // initial output tokens. The run must bill those. streamText.mockReturnValue({ - fullStream: (async function* () { + stream: (async function* () { yield { type: 'raw', rawValue: { @@ -1187,7 +1208,7 @@ describe('AgentRuntime.stream() — usage billing on abort', () => { streamText // Turn 1 completes with a tool call; its usage is folded into the total. .mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -1207,7 +1228,7 @@ describe('AgentRuntime.stream() — usage billing on abort', () => { // Turn 2 aborts mid-stream after the provider reported its raw usage; the // SDK promises reject so the only signal is the captured raw event. .mockReturnValueOnce({ - fullStream: (async function* () { + stream: (async function* () { yield { type: 'raw', rawValue: { @@ -1258,7 +1279,7 @@ describe('AgentRuntime.stream() — usage billing on abort', () => { // Single turn: emits a raw usage event (captured) AND resolves its final // usage. The raw capture must not be re-added on top of the folded total. streamText.mockReturnValueOnce({ - fullStream: makeChunkStream([ + stream: makeChunkStream([ { type: 'raw', rawValue: { @@ -1310,7 +1331,7 @@ describe('AgentRuntime.stream() — usage billing on abort', () => { streamText.mockReturnValue({ // Sync generator: `for await` consumes it fine and there's nothing to await. - fullStream: (function* () { + stream: (function* () { yield { type: 'raw', rawValue: { @@ -1339,19 +1360,19 @@ describe('AgentRuntime.stream() — usage billing on abort', () => { expect(runtime.getState().status).toBe('cancelled'); }); - it('requests includeRawChunks only when recoverUsageOnAbort is set', async () => { + it('requests raw chunks only when recoverUsageOnAbort is set', async () => { streamText.mockReturnValue(makeStreamSuccess('ok')); const off = createRuntime(undefined, 'anthropic/claude-sonnet-4-6'); await collectChunks((await off.runtime.stream('hello')).stream); - expect(streamText.mock.calls.at(-1)?.[0]).not.toHaveProperty('includeRawChunks'); + expect(streamText.mock.calls.at(-1)?.[0]).not.toHaveProperty('include.rawChunks'); streamText.mockClear(); streamText.mockReturnValue(makeStreamSuccess('ok')); const on = createRuntime(undefined, 'anthropic/claude-sonnet-4-6'); await collectChunks((await on.runtime.stream('hello', { recoverUsageOnAbort: true })).stream); - expect(streamText.mock.calls.at(-1)?.[0]).toMatchObject({ includeRawChunks: true }); + expect(streamText.mock.calls.at(-1)?.[0]).toHaveProperty('include.rawChunks', true); }); }); @@ -1367,7 +1388,7 @@ describe('AgentRuntime.stream() — graceful error contract', () => { it('resolves (never rejects) when the LLM stream throws', async () => { const streamError = new Error('stream broke'); streamText.mockReturnValue({ - fullStream: makeErrorStream(streamError), + stream: makeErrorStream(streamError), finishReason: silentReject(streamError), usage: Promise.resolve(undefined), response: silentReject(streamError), @@ -1381,7 +1402,7 @@ describe('AgentRuntime.stream() — graceful error contract', () => { it('yields an error chunk when the LLM stream throws', async () => { const streamError = new Error('stream broke'); streamText.mockReturnValue({ - fullStream: makeErrorStream(streamError), + stream: makeErrorStream(streamError), finishReason: silentReject(streamError), usage: Promise.resolve(undefined), response: silentReject(streamError), @@ -1399,7 +1420,7 @@ describe('AgentRuntime.stream() — graceful error contract', () => { it('closes the stream cleanly (with a finish chunk) after an error', async () => { const streamError = new Error('stream broke'); streamText.mockReturnValue({ - fullStream: makeErrorStream(streamError), + stream: makeErrorStream(streamError), finishReason: silentReject(streamError), usage: Promise.resolve(undefined), response: silentReject(streamError), @@ -1423,7 +1444,7 @@ describe('AgentRuntime.stream() — graceful error contract', () => { it('sets state to "failed" after a stream error', async () => { const streamError = new Error('stream broke'); streamText.mockReturnValue({ - fullStream: makeErrorStream(streamError), + stream: makeErrorStream(streamError), finishReason: silentReject(streamError), usage: Promise.resolve(undefined), response: silentReject(streamError), @@ -1456,7 +1477,7 @@ describe('AgentRuntime.stream() — graceful error contract', () => { const streamError = new Error('transient'); streamText .mockReturnValueOnce({ - fullStream: makeErrorStream(streamError), + stream: makeErrorStream(streamError), finishReason: silentReject(streamError), usage: Promise.resolve(undefined), response: silentReject(streamError), @@ -1483,7 +1504,7 @@ describe('AgentRuntime.stream() — graceful error contract', () => { it('never throws when consuming the stream after an error', async () => { const streamError = new Error('stream broke'); streamText.mockReturnValue({ - fullStream: makeErrorStream(streamError), + stream: makeErrorStream(streamError), finishReason: silentReject(streamError), usage: Promise.resolve(undefined), response: silentReject(streamError), @@ -2700,7 +2721,7 @@ describe('AgentRuntime — concurrent tool execution', () => { const { runtime } = createRuntimeWithTools([suspendTool], 1); streamText.mockReturnValue({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -2763,7 +2784,7 @@ describe('AgentRuntime — concurrent tool execution', () => { const { runtime } = createRuntimeWithTools([suspendTool], Infinity); streamText.mockReturnValue({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -2812,7 +2833,7 @@ describe('AgentRuntime — concurrent tool execution', () => { const { runtime } = createRuntimeWithTools([suspendTool], 1); streamText.mockReturnValue({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -2887,7 +2908,7 @@ describe('AgentRuntime — concurrent tool execution', () => { streamText .mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -3225,7 +3246,7 @@ describe('AgentRuntime.stream() — structured output', () => { const expected = { answer: '5', score: 1 }; streamText .mockReturnValueOnce({ - fullStream: makeChunkStream([ + stream: makeChunkStream([ { type: 'tool-call', toolCallId: 'tc-1', toolName: 'add', args: { a: 2, b: 3 } }, ]), finishReason: Promise.resolve('tool-calls'), @@ -4049,7 +4070,7 @@ describe('AgentRuntime — tool approval (HITL wrapper)', () => { .build(); streamText.mockReturnValue({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'checking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'checking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -4119,7 +4140,7 @@ describe('AgentRuntime — tool approval (HITL wrapper)', () => { streamText .mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'checking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'checking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -4295,7 +4316,7 @@ describe('external abort signal', () => { streamText .mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'checking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'checking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -4405,7 +4426,7 @@ describe('AgentRuntime — abort during a tool batch', () => { toolCalls: Array<{ toolCallId: string; toolName: string; args: Record }>, ) { return { - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'calling tools...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'calling tools...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -4686,7 +4707,7 @@ describe('tool systemInstruction merging', () => { function getSystemMessageText(): string { const callArgs = generateText.mock.calls[0][0] as Record; - const systemMsg = callArgs.system; + const systemMsg = callArgs.instructions; if (Array.isArray(systemMsg)) { return systemMsg.map((entry) => String((entry as { content: string }).content)).join(''); } @@ -4819,10 +4840,10 @@ describe('tool systemInstruction merging', () => { await runtime.generate('load the deferred capability'); const calls = generateText.mock.calls as Array< - [{ system: Array<{ content: string }> | { content: string } }] + [{ instructions: Array<{ content: string }> | { content: string } }] >; - const beforeLoadSystem = calls[0][0].system; - const afterLoadSystem = calls[1][0].system; + const beforeLoadSystem = calls[0][0].instructions; + const afterLoadSystem = calls[1][0].instructions; const beforeFirst = Array.isArray(beforeLoadSystem) ? beforeLoadSystem[0].content @@ -4866,7 +4887,7 @@ describe('instruction providerOptions', () => { }); const callArgs = generateText.mock.calls[0][0] as Record; - const systemMsg = callArgs.system as Record; + const systemMsg = callArgs.instructions as Record; expect(systemMsg.role).toBe('system'); expect(systemMsg.providerOptions).toEqual({ anthropic: { cacheControl: { type: 'ephemeral' } }, @@ -4896,7 +4917,7 @@ describe('promptCaching', () => { await runtime.generate('hello'); const callArgs = generateText.mock.calls[0][0] as Record; - const systemMsg = callArgs.system as Record; + const systemMsg = callArgs.instructions as Record; expect(systemMsg.providerOptions).toEqual({ anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' } }, }); @@ -5297,10 +5318,10 @@ describe('AgentRuntime — observation log jobs', () => { }); const callArgs = (generateText.mock.calls[0] as [unknown])[0] as { - system: { content: string }; + instructions: { content: string }; tools: Record; }; - const systemPrompt = callArgs.system?.content ?? ''; + const systemPrompt = callArgs.instructions?.content ?? ''; expect(systemPrompt).not.toContain(''); expect(systemPrompt).not.toContain('Postgres'); expect(systemPrompt).not.toContain('SQLite'); @@ -5420,17 +5441,17 @@ describe('AgentRuntime — observation log jobs', () => { const generateTextMock = generateText as MockedFunction< (input: { - system: { content: string } | Array<{ content: string }>; + instructions: { content: string } | Array<{ content: string }>; messages: Array<{ role: string; content: unknown; }>; }) => unknown >; - const [{ system, messages }] = generateTextMock.mock.calls[0]; - const systemText = Array.isArray(system) - ? system.map((entry) => entry.content).join('') - : system.content; + const [{ instructions, messages }] = generateTextMock.mock.calls[0]; + const systemText = Array.isArray(instructions) + ? instructions.map((entry) => entry.content).join('') + : instructions.content; expect(systemText).toContain('Resource one memory.'); expect(systemText).toContain('Resource two memory.'); expect(JSON.stringify(messages)).not.toContain('remember resource-one preference'); @@ -5637,7 +5658,7 @@ describe('AgentRuntime — telemetry propagation', () => { tracer: { startSpan: vi.fn() }, }; - it('passes telemetry config into generateText as experimental_telemetry', async () => { + it('passes telemetry config into generateText as telemetry', async () => { generateText.mockResolvedValue(makeGenerateSuccess()); const runtime = new AgentRuntime({ @@ -5651,13 +5672,12 @@ describe('AgentRuntime — telemetry propagation', () => { await runtime.generate('hello'); const callArgs = generateText.mock.calls[0][0] as Record; - const expTelemetry = callArgs.experimental_telemetry as Record; - expect(expTelemetry).toBeDefined(); - expect(expTelemetry.isEnabled).toBe(true); - expect(expTelemetry.functionId).toBe('test-agent'); - expect(expTelemetry.tracer).toBe(baseTelemetry.tracer); - expect(expTelemetry.recordInputs).toBe(true); - expect(expTelemetry.recordOutputs).toBe(false); + const telemetry = callArgs.telemetry as Record; + expect(telemetry).toBeDefined(); + expect(telemetry.isEnabled).toBe(true); + expect(telemetry.functionId).toBe('test-agent'); + expect(telemetry.recordInputs).toBe(true); + expect(telemetry.recordOutputs).toBe(false); }); it('uses updated telemetry config for later runs', async () => { @@ -5680,9 +5700,8 @@ describe('AgentRuntime — telemetry propagation', () => { await runtime.generate('hello'); const callArgs = generateText.mock.calls[0][0] as Record; - const expTelemetry = callArgs.experimental_telemetry as Record; - expect(expTelemetry.functionId).toBe('updated-agent'); - expect(expTelemetry.metadata).toEqual({ env: 'updated' }); + const telemetry = callArgs.telemetry as Record; + expect(telemetry.functionId).toBe('updated-agent'); }); it('wraps generate calls in a telemetry root span when the tracer supports active spans', async () => { @@ -5741,6 +5760,7 @@ describe('AgentRuntime — telemetry propagation', () => { const telemetry: BuiltTelemetry = { ...baseTelemetry, runtimeRootSpanEnabled: false, + integrations: [{ onStart: vi.fn() }], tracer, }; @@ -5757,11 +5777,11 @@ describe('AgentRuntime — telemetry propagation', () => { expect(tracer.startActiveSpan).not.toHaveBeenCalled(); const callArgs = generateText.mock.calls[0][0] as Record; - expect(callArgs.experimental_telemetry).toEqual( + expect(callArgs.telemetry).toEqual( expect.objectContaining({ isEnabled: true, functionId: 'test-agent', - tracer, + integrations: telemetry.integrations, }), ); }); @@ -5827,7 +5847,7 @@ describe('AgentRuntime — telemetry propagation', () => { expect(attributes['gen_ai.prompt']).toEqual(expect.stringContaining('"input_schema"')); }); - it('passes telemetry config into streamText as experimental_telemetry', async () => { + it('passes telemetry config into streamText as telemetry', async () => { streamText.mockReturnValue(makeStreamSuccess()); const runtime = new AgentRuntime({ @@ -5842,11 +5862,10 @@ describe('AgentRuntime — telemetry propagation', () => { await collectChunks(stream); const callArgs = streamText.mock.calls[0][0] as Record; - const expTelemetry = callArgs.experimental_telemetry as Record; - expect(expTelemetry).toBeDefined(); - expect(expTelemetry.isEnabled).toBe(true); - expect(expTelemetry.functionId).toBe('test-agent'); - expect(expTelemetry.tracer).toBe(baseTelemetry.tracer); + const telemetry = callArgs.telemetry as Record; + expect(telemetry).toBeDefined(); + expect(telemetry.isEnabled).toBe(true); + expect(telemetry.functionId).toBe('test-agent'); }); it('enables smoothStream by default on streamText', async () => { @@ -5926,12 +5945,11 @@ describe('AgentRuntime — telemetry propagation', () => { }); const callArgs = generateText.mock.calls[0][0] as Record; - const expTelemetry = callArgs.experimental_telemetry as Record; - expect(expTelemetry).toBeDefined(); - expect(expTelemetry.isEnabled).toBe(true); + const telemetry = callArgs.telemetry as Record; + expect(telemetry).toBeDefined(); + expect(telemetry.isEnabled).toBe(true); // Inherited telemetry uses the child agent's name as functionId - expect(expTelemetry.functionId).toBe('child-agent'); - expect(expTelemetry.tracer).toBe(baseTelemetry.tracer); + expect(telemetry.functionId).toBe('child-agent'); }); it('passes resolved telemetry to tool handlers via parentTelemetry', async () => { @@ -6088,7 +6106,7 @@ describe('AgentRuntime — telemetry propagation', () => { expect(capturedTelemetry!.tracer).toBe(baseTelemetry.tracer); }); - it('does not include experimental_telemetry when telemetry is disabled', async () => { + it('does not include telemetry when telemetry is disabled', async () => { generateText.mockResolvedValue(makeGenerateSuccess()); const runtime = new AgentRuntime({ @@ -6102,7 +6120,7 @@ describe('AgentRuntime — telemetry propagation', () => { await runtime.generate('hello'); const callArgs = generateText.mock.calls[0][0] as Record; - expect(callArgs.experimental_telemetry).toBeUndefined(); + expect(callArgs.telemetry).toBeUndefined(); }); }); @@ -6211,7 +6229,7 @@ describe('AgentRuntime.resume() with createCancellation() — auto-bypass', () = streamText .mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -6397,7 +6415,7 @@ describe('AgentRuntime — toModelOutput error resilience', () => { streamText .mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -6473,7 +6491,7 @@ describe('AgentRuntime — toModelOutput error resilience', () => { // First stream: agent calls the tool and suspends streamText.mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'thinking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'thinking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -6544,7 +6562,7 @@ describe('AgentRuntime — empty model responses', () => { function makeEmptyStream(finishReason: string, extraChunks: Array> = []) { return { - fullStream: makeChunkStream(extraChunks), + stream: makeChunkStream(extraChunks), finishReason: Promise.resolve(finishReason), usage: Promise.resolve({ inputTokens: 10, outputTokens: 0, totalTokens: 10 }), response: Promise.resolve({ messages: [] }), @@ -6605,7 +6623,7 @@ describe('AgentRuntime — empty model responses', () => { await collectChunks(readableStream); const args = streamText.mock.calls[0][0] as Record; - expect(args.includeRawChunks).toBe(true); + expect(args).toHaveProperty('include.rawChunks', true); }); it('stream: an empty response finishing with "stop" is not treated as an error', async () => { @@ -6704,7 +6722,7 @@ describe('AgentRuntime — MCP connection failure warnings', () => { await collectChunks(readableStream); const callArgs = streamText.mock.calls.at(-1)![0] as Record; - const system = callArgs.system; + const system = callArgs.instructions; const systemText = Array.isArray(system) ? system.map((e) => String((e as { content: string }).content)).join('') : String((system as { content: string }).content); @@ -6723,7 +6741,7 @@ describe('AgentRuntime — MCP connection failure warnings', () => { await collectChunks(readableStream); const callArgs = streamText.mock.calls.at(-1)![0] as Record; - const system = callArgs.system; + const system = callArgs.instructions; const systemText = Array.isArray(system) ? system.map((e) => String((e as { content: string }).content)).join('') : String((system as { content: string }).content); diff --git a/packages/@n8n/agents/src/runtime/__tests__/crash-resume.test.ts b/packages/@n8n/agents/src/runtime/__tests__/crash-resume.test.ts index 982012bdd94..d82d91d662d 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/crash-resume.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/crash-resume.test.ts @@ -65,7 +65,7 @@ function* makeChunkStream( function makeStreamSuccess(text = 'Hello') { return { - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: text }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text }]), finishReason: Promise.resolve('stop'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -77,7 +77,7 @@ function makeStreamSuccess(text = 'Hello') { function makeStreamWithToolCall(toolCallId: string, args: Record) { return { - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'working...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'working...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ @@ -251,7 +251,7 @@ describe('step checkpoints + crash resume (durable-log RFC)', () => { const runtime2 = createRuntime(store, [lookupTool, approveTool]); streamText.mockReset(); streamText.mockReturnValueOnce({ - fullStream: makeChunkStream([{ type: 'text-delta', textDelta: 'asking...' }]), + stream: makeChunkStream([{ type: 'text-delta', id: 'text-1', text: 'asking...' }]), finishReason: Promise.resolve('tool-calls'), usage: Promise.resolve({ inputTokens: 10, outputTokens: 5, totalTokens: 15 }), response: Promise.resolve({ diff --git a/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts b/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts index c0f3d97b476..909d077de39 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/model-factory.test.ts @@ -58,7 +58,7 @@ vi.mock('@ai-sdk/openai', () => ({ })); vi.mock('@ai-sdk/google', () => ({ - createGoogleGenerativeAI: (opts?: ProviderOpts) => (model: string) => ({ + createGoogle: (opts?: ProviderOpts) => (model: string) => ({ provider: 'google', modelId: model, apiKey: opts?.apiKey, @@ -68,12 +68,14 @@ vi.mock('@ai-sdk/google', () => ({ })); vi.mock('@ai-sdk/xai', () => ({ - createXai: (opts?: ProviderOpts) => (model: string) => ({ - provider: 'xai', - modelId: model, - apiKey: opts?.apiKey, - fetch: opts?.fetch, - specificationVersion: 'v3', + createXai: (opts?: ProviderOpts) => ({ + chat: (model: string) => ({ + provider: 'xai', + modelId: model, + apiKey: opts?.apiKey, + fetch: opts?.fetch, + specificationVersion: 'v3', + }), }), })); diff --git a/packages/@n8n/agents/src/runtime/__tests__/observation-log-observer.test.ts b/packages/@n8n/agents/src/runtime/__tests__/observation-log-observer.test.ts index 860ac34cc1b..9913d6a1489 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/observation-log-observer.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/observation-log-observer.test.ts @@ -149,16 +149,15 @@ describe('observation-log observer defaults', () => { await observe({ ...baseInput, telemetry: { ...telemetry, enabled: false } }); expect(mockGenerateText.mock.calls[0][0]).toMatchObject({ - experimental_telemetry: { + telemetry: { isEnabled: true, functionId: 'my-agent.memory-observer', - metadata: { thread_id: 't1' }, recordInputs: true, recordOutputs: false, }, }); - expect(mockGenerateText.mock.calls[1][0].experimental_telemetry).toBeUndefined(); - expect(mockGenerateText.mock.calls[2][0].experimental_telemetry).toBeUndefined(); + expect(mockGenerateText.mock.calls[1][0].telemetry).toBeUndefined(); + expect(mockGenerateText.mock.calls[2][0].telemetry).toBeUndefined(); }); it('reports normalized, cache-aware usage through an async onUsage before the observer promise settles', async () => { diff --git a/packages/@n8n/agents/src/runtime/__tests__/observation-log-reflector.test.ts b/packages/@n8n/agents/src/runtime/__tests__/observation-log-reflector.test.ts index bae296f6697..52f7dc93fdf 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/observation-log-reflector.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/observation-log-reflector.test.ts @@ -132,16 +132,15 @@ describe('observation-log reflector defaults', () => { await reflect({ ...baseInput, telemetry: { ...telemetry, enabled: false } }); expect(mockGenerateText.mock.calls[0][0]).toMatchObject({ - experimental_telemetry: { + telemetry: { isEnabled: true, functionId: 'my-agent.memory-reflector', - metadata: { thread_id: 't1' }, recordInputs: true, recordOutputs: false, }, }); - expect(mockGenerateText.mock.calls[1][0].experimental_telemetry).toBeUndefined(); - expect(mockGenerateText.mock.calls[2][0].experimental_telemetry).toBeUndefined(); + expect(mockGenerateText.mock.calls[1][0].telemetry).toBeUndefined(); + expect(mockGenerateText.mock.calls[2][0].telemetry).toBeUndefined(); }); it('reports usage with task="reflector" and the configured model through onUsage', async () => { diff --git a/packages/@n8n/agents/src/runtime/__tests__/provider-quirks.test.ts b/packages/@n8n/agents/src/runtime/__tests__/provider-quirks.test.ts index 8d9c265eecd..b73a1325287 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/provider-quirks.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/provider-quirks.test.ts @@ -63,7 +63,7 @@ describe('thinkingToProviderOptions', () => { it('openai: defaults reasoningEffort to medium', () => { expect(getProviderQuirks('openai').thinkingToProviderOptions?.({})).toEqual({ - openai: { reasoningEffort: 'medium' }, + openai: { reasoningEffort: 'medium', reasoningSummary: null }, }); }); diff --git a/packages/@n8n/agents/src/runtime/__tests__/title-generation.test.ts b/packages/@n8n/agents/src/runtime/__tests__/title-generation.test.ts index db644c93cbc..db358f03698 100644 --- a/packages/@n8n/agents/src/runtime/__tests__/title-generation.test.ts +++ b/packages/@n8n/agents/src/runtime/__tests__/title-generation.test.ts @@ -8,9 +8,9 @@ import { } from '../memory/title-generation'; type GenerateTextCall = { - system?: string; + instructions?: string; messages: Array<{ role: string; content: string }>; - experimental_telemetry?: Record; + telemetry?: Record; }; type GenerateTextResult = { text: string; usage?: { totalTokens?: number } }; @@ -117,8 +117,8 @@ describe('generateTitleFromMessage', () => { mockGenerateText.mockResolvedValue({ text: 'Berlin rain alert' }); await generateTitleFromMessage(fakeModel, 'Build a daily Berlin rain alert workflow'); const call = mockGenerateText.mock.calls[0][0]; - expect(call.system).toContain('markdown'); - expect(call.system).toContain('sentence case'); + expect(call.instructions).toContain('markdown'); + expect(call.instructions).toContain('sentence case'); }); it('accepts custom instructions', async () => { @@ -127,7 +127,7 @@ describe('generateTitleFromMessage', () => { instructions: 'Custom system prompt', }); const call = mockGenerateText.mock.calls[0][0]; - expect(call.system).toBe('Custom system prompt'); + expect(call.instructions).toBe('Custom system prompt'); }); it('passes generic telemetry to the title LLM call', async () => { @@ -147,13 +147,11 @@ describe('generateTitleFromMessage', () => { }); const call = mockGenerateText.mock.calls[0][0]; - expect(call.experimental_telemetry).toEqual({ + expect(call.telemetry).toEqual({ isEnabled: true, functionId: 'instance-ai.thread-title', - metadata: { thread_id: 'thread-1' }, recordInputs: true, recordOutputs: false, - tracer: undefined, integrations: undefined, }); }); diff --git a/packages/@n8n/agents/src/runtime/loop/agent-runtime.ts b/packages/@n8n/agents/src/runtime/loop/agent-runtime.ts index a718e0bf049..b6d7c81ef4d 100644 --- a/packages/@n8n/agents/src/runtime/loop/agent-runtime.ts +++ b/packages/@n8n/agents/src/runtime/loop/agent-runtime.ts @@ -1,5 +1,5 @@ import type { ProviderOptions } from '@ai-sdk/provider-utils'; -import type { TelemetrySettings, ToolCallRepairFunction, ToolSet } from 'ai'; +import type { TelemetryOptions, ToolCallRepairFunction, ToolSet } from 'ai'; import type { JSONSchema7 } from 'json-schema'; import type { z } from 'zod'; @@ -618,16 +618,18 @@ export class AgentRuntime { toolMap: Map, options?: ExecutionOptions, ): { - experimental_telemetry?: TelemetrySettings; - experimental_repairToolCall?: ToolCallRepairFunction>; - experimental_onStepStart?: ExecutionOptions['onStepStart']; - onStepFinish?: ExecutionOptions['onStepFinish']; + telemetry?: TelemetryOptions; + repairToolCall?: ToolCallRepairFunction>; + onStepStart?: ExecutionOptions['onStepStart']; + onStepEnd?: ExecutionOptions['onStepEnd']; } { return { ...this.telemetry.buildTelemetryOptions(options), - ...(options?.onStepStart ? { experimental_onStepStart: options.onStepStart } : {}), - ...(options?.onStepFinish ? { onStepFinish: options.onStepFinish } : {}), - experimental_repairToolCall: async (options) => { + ...(options?.onStepStart ? { onStepStart: options.onStepStart } : {}), + ...(options?.onStepEnd || options?.onStepFinish + ? { onStepEnd: options.onStepEnd ?? options.onStepFinish } + : {}), + repairToolCall: async (options) => { return await fixToolCall( { toolCall: options.toolCall, diff --git a/packages/@n8n/agents/src/runtime/loop/generate-sink.ts b/packages/@n8n/agents/src/runtime/loop/generate-sink.ts index 6eafd67db5c..34e4568cfc6 100644 --- a/packages/@n8n/agents/src/runtime/loop/generate-sink.ts +++ b/packages/@n8n/agents/src/runtime/loop/generate-sink.ts @@ -31,8 +31,9 @@ export class GenerateSink implements RunOutputSink { const { generateText } = loadAi(); const result = await generateText({ model: ctx.model, - system: ctx.system, + instructions: ctx.system, messages: ctx.messages, + allowSystemInMessages: true, abortSignal: ctx.abortSignal, ...(ctx.hasTools ? { tools: ctx.aiTools } : {}), ...(ctx.providerOptions ? { providerOptions: ctx.providerOptions } : {}), diff --git a/packages/@n8n/agents/src/runtime/loop/run-output-sink.ts b/packages/@n8n/agents/src/runtime/loop/run-output-sink.ts index 07c98403ee2..3b50305c25f 100644 --- a/packages/@n8n/agents/src/runtime/loop/run-output-sink.ts +++ b/packages/@n8n/agents/src/runtime/loop/run-output-sink.ts @@ -3,7 +3,7 @@ import type { ModelMessage, Output, SystemModelMessage, - TelemetrySettings, + TelemetryOptions, ToolCallRepairFunction, ToolSet, } from 'ai'; @@ -68,8 +68,8 @@ export interface ModelCallContext { providerOptions?: Record; outputSpec?: ReturnType; aiSdkOptions: { - experimental_telemetry?: TelemetrySettings; - experimental_repairToolCall?: ToolCallRepairFunction>; + telemetry?: TelemetryOptions; + repairToolCall?: ToolCallRepairFunction>; }; } diff --git a/packages/@n8n/agents/src/runtime/loop/stream-sink.ts b/packages/@n8n/agents/src/runtime/loop/stream-sink.ts index 7b2d02d00c0..be59755d345 100644 --- a/packages/@n8n/agents/src/runtime/loop/stream-sink.ts +++ b/packages/@n8n/agents/src/runtime/loop/stream-sink.ts @@ -113,13 +113,14 @@ export class StreamSink implements RunOutputSink { const { streamText } = loadAi(); const result = streamText({ model: ctx.model, - system: ctx.system, + instructions: ctx.system, messages: ctx.messages, + allowSystemInMessages: true, abortSignal: ctx.abortSignal, // Surface the provider's raw message_start/message_delta events so an // aborted run can recover its usage — the SDK reports none on abort. ...(this.rawUsageReader !== undefined || this.rawErrorReader !== undefined - ? { includeRawChunks: true } + ? { include: { rawChunks: true } } : {}), ...(ctx.hasTools ? { tools: ctx.aiTools } : {}), ...(ctx.providerOptions ? { providerOptions: ctx.providerOptions } : {}), @@ -131,7 +132,7 @@ export class StreamSink implements RunOutputSink { // Consume the stream. When the AbortSignal fires mid-stream the AI SDK // cancels the underlying fetch and the async iterator throws; the error // propagates to the StreamSession which closes the consumer stream. - for await (const chunk of result.fullStream) { + for await (const chunk of result.stream) { // Track usage from raw provider events so an aborted turn (which never // reaches the post-loop awaits) can still be billed via getAbortFinish. if (chunk.type === 'raw') { diff --git a/packages/@n8n/agents/src/runtime/memory/episodic-memory-defaults.ts b/packages/@n8n/agents/src/runtime/memory/episodic-memory-defaults.ts index 3f6b8aea5e6..002e879c640 100644 --- a/packages/@n8n/agents/src/runtime/memory/episodic-memory-defaults.ts +++ b/packages/@n8n/agents/src/runtime/memory/episodic-memory-defaults.ts @@ -538,7 +538,7 @@ export function createEpisodicMemoryExtractFn( const { generateText, Output } = await import('ai'); const response = await generateText({ model: createModel(model), - system: options.extractionPrompt ?? DEFAULT_EPISODIC_MEMORY_EXTRACTION_PROMPT, + instructions: options.extractionPrompt ?? DEFAULT_EPISODIC_MEMORY_EXTRACTION_PROMPT, prompt: buildEpisodicMemoryExtractorPrompt(input), output: Output.object({ schema: EpisodicMemoryExtractionSchema }), }); @@ -564,7 +564,7 @@ export function createEpisodicMemoryReflectFn( const { generateText, Output } = await import('ai'); const response = await generateText({ model: createModel(model), - system: options.reflectionPrompt ?? DEFAULT_EPISODIC_MEMORY_REFLECTION_PROMPT, + instructions: options.reflectionPrompt ?? DEFAULT_EPISODIC_MEMORY_REFLECTION_PROMPT, prompt: buildEpisodicMemoryReflectorPrompt(input), output: Output.object({ schema: EpisodicMemoryReflectionSchema }), }); diff --git a/packages/@n8n/agents/src/runtime/memory/observation-log-defaults.ts b/packages/@n8n/agents/src/runtime/memory/observation-log-defaults.ts index 26b17d6fa35..9b1821e46be 100644 --- a/packages/@n8n/agents/src/runtime/memory/observation-log-defaults.ts +++ b/packages/@n8n/agents/src/runtime/memory/observation-log-defaults.ts @@ -13,7 +13,7 @@ import { getModelIdString } from '../loop/runtime-context'; import { loadAi } from '../model/lazy-ai'; import { createModel } from '../model/model-factory'; import { toTokenUsage } from '../streaming/stream'; -import { buildExperimentalTelemetry } from '../telemetry/telemetry-options'; +import { buildAiSdkTelemetry } from '../telemetry/telemetry-options'; // The observer's fixed prompt is a few thousand tokens, so firing per tiny delta // is majority overhead. 8k keeps that overhead ratio acceptable while still firing @@ -328,9 +328,9 @@ export function createObservationLogObserveFn( return async (input) => { const { text, usage, providerMetadata } = await loadAi().generateText({ model: createModel(model), - system: options.observerPrompt ?? DEFAULT_OBSERVATION_LOG_OBSERVER_PROMPT, + instructions: options.observerPrompt ?? DEFAULT_OBSERVATION_LOG_OBSERVER_PROMPT, prompt: buildObservationLogObserverPrompt(input), - ...buildExperimentalTelemetry(input.telemetry, { functionSuffix: 'memory-observer' }), + ...buildAiSdkTelemetry(input.telemetry, { functionSuffix: 'memory-observer' }), }); incrementTokenCountFromUsage(input.executionCounter, usage); @@ -582,9 +582,9 @@ export function createObservationLogReflectFn( return async (input) => { const { text, usage, providerMetadata } = await loadAi().generateText({ model: createModel(model), - system: options.reflectorPrompt ?? DEFAULT_OBSERVATION_LOG_REFLECTOR_PROMPT, + instructions: options.reflectorPrompt ?? DEFAULT_OBSERVATION_LOG_REFLECTOR_PROMPT, prompt: buildObservationLogReflectorPrompt(input), - ...buildExperimentalTelemetry(input.telemetry, { functionSuffix: 'memory-reflector' }), + ...buildAiSdkTelemetry(input.telemetry, { functionSuffix: 'memory-reflector' }), }); incrementTokenCountFromUsage(input.executionCounter, usage); diff --git a/packages/@n8n/agents/src/runtime/memory/title-generation.ts b/packages/@n8n/agents/src/runtime/memory/title-generation.ts index b7eeca97158..c6370f997c1 100644 --- a/packages/@n8n/agents/src/runtime/memory/title-generation.ts +++ b/packages/@n8n/agents/src/runtime/memory/title-generation.ts @@ -7,6 +7,7 @@ import { createFilteredLogger } from '../logger'; import { incrementTokenCountFromUsage } from '../loop/execution-counter'; import { loadAi } from '../model/lazy-ai'; import { createModel, type FetchFn } from '../model/model-factory'; +import { buildAiSdkTelemetry } from '../telemetry/telemetry-options'; const logger = createFilteredLogger(); @@ -55,22 +56,6 @@ interface GenerateTitleFromMessageOptions { executionCounter?: AgentExecutionCounter; } -function buildTelemetryOptions(telemetry: BuiltTelemetry | undefined): Record { - if (!telemetry?.enabled) return {}; - - return { - experimental_telemetry: { - isEnabled: true, - functionId: telemetry.functionId ?? 'title-generation', - metadata: telemetry.metadata, - recordInputs: telemetry.recordInputs, - recordOutputs: telemetry.recordOutputs, - tracer: telemetry.tracer, - integrations: telemetry.integrations.length > 0 ? telemetry.integrations : undefined, - }, - }; -} - /** * Whether a user message has too little substance to title a conversation * (e.g. "hey", "hello"). For these, the LLM tends to hallucinate an @@ -134,14 +119,14 @@ export async function generateTitleFromMessage( const result = await loadAi().generateText({ model, - system: opts?.instructions ?? DEFAULT_TITLE_INSTRUCTIONS, + instructions: opts?.instructions ?? DEFAULT_TITLE_INSTRUCTIONS, messages: [ { role: 'user', content: `Generate a title for the following first message of a conversation. Do not answer the message — only produce the title.\n\n\n${trimmed}\n`, }, ], - ...buildTelemetryOptions(opts?.telemetry), + ...buildAiSdkTelemetry(opts?.telemetry, { fallbackFunctionId: 'title-generation' }), }); incrementTokenCountFromUsage(opts?.executionCounter, result.usage); @@ -181,7 +166,7 @@ ${trimmed} const result = await loadAi().generateText({ model, - system: opts?.instructions ?? DEFAULT_TITLE_AND_EMOJI_INSTRUCTIONS, + instructions: opts?.instructions ?? DEFAULT_TITLE_AND_EMOJI_INSTRUCTIONS, messages: [{ role: 'user', content: wrappedMessage }], }); incrementTokenCountFromUsage(opts?.executionCounter, result.usage); diff --git a/packages/@n8n/agents/src/runtime/model/messages.ts b/packages/@n8n/agents/src/runtime/model/messages.ts index b059502ae1a..2842c26ebec 100644 --- a/packages/@n8n/agents/src/runtime/model/messages.ts +++ b/packages/@n8n/agents/src/runtime/model/messages.ts @@ -6,9 +6,6 @@ import type { TextPart, ToolCallPart, ToolResultPart, - ImagePart, - ToolApprovalRequest, - ToolApprovalResponse, FinishReason as AiFinishReason, } from 'ai'; @@ -16,8 +13,10 @@ import { getProviderQuirks, PROVIDER_QUIRKS } from './provider-quirks'; import type { FinishReason } from '../../types'; import type { AgentMessage, + ContentCustom, ContentFile, ContentReasoning, + ContentReasoningFile, ContentText, ContentToolCall, Message, @@ -25,18 +24,9 @@ import type { } from '../../types/sdk/message'; import type { JSONObject, JSONValue } from '../../types/utils/json'; -/** Reasoning content part — mirrors @ai-sdk/provider-utils ReasoningPart (not re-exported by 'ai'). */ -type ReasoningPart = { type: 'reasoning'; text: string }; - -type AiContentPart = - | TextPart - | FilePart - | ImagePart - | ReasoningPart - | ToolCallPart - | ToolResultPart - | ToolApprovalRequest - | ToolApprovalResponse; +// Used across all message roles; AssistantContent omits user images and tool approval responses. +type AiContentPart = Exclude[number]; +type AiAssistantContent = Exclude['content'], string>; // --- Type guards for MessageContent blocks --- @@ -48,10 +38,18 @@ function isReasoning(block: MessageContent): block is ContentReasoning { return block.type === 'reasoning'; } +function isReasoningFile(block: MessageContent): block is ContentReasoningFile { + return block.type === 'reasoning-file'; +} + function isFile(block: MessageContent): block is ContentFile { return block.type === 'file'; } +function isCustom(block: MessageContent): block is ContentCustom { + return block.type === 'custom'; +} + function isToolCall(block: MessageContent): block is ContentToolCall { return block.type === 'tool-call'; } @@ -125,10 +123,19 @@ function hasReplayableReasoningProviderOptions( type ContentToolResultOutput = Extract; -function isContentToolResultOutput(value: JSONValue): value is ContentToolResultOutput { +function isContentToolResultOutput(value: unknown): value is ContentToolResultOutput { return isRecord(value) && value.type === 'content' && Array.isArray(value.value); } +function normalizeReasoningFileData( + data: Extract['data'], +): ContentReasoningFile['data'] { + if (data instanceof URL) return data.toString(); + if (typeof data !== 'object' || data === null || !('type' in data)) return data; + if (data.type === 'data') return data.data; + return data.url.toString(); +} + /** * Providers replay reasoning from `providerOptions`, but the AI SDK exposes the * replay data in `providerMetadata` (see `PROVIDER_QUIRKS[provider].reasoningReplayKeys`). @@ -168,6 +175,14 @@ function toAiContent(block: MessageContent): AiContentPart | undefined { data: block.data, mediaType: block.mediaType ?? 'application/octet-stream', }; + } else if (isReasoningFile(block)) { + base = { + type: 'reasoning-file', + data: block.data, + mediaType: block.mediaType, + }; + } else if (isCustom(block)) { + base = { type: 'custom', kind: block.kind }; } else if (isToolCall(block)) { base = { type: 'tool-call', @@ -259,6 +274,17 @@ function fromAiContent(part: AiContentPart): MessageContent | undefined { case 'reasoning': base = { type: 'reasoning', text: part.text }; break; + case 'reasoning-file': { + base = { + type: 'reasoning-file', + data: normalizeReasoningFileData(part.data), + mediaType: part.mediaType, + }; + break; + } + case 'custom': + base = { type: 'custom', kind: part.kind }; + break; case 'tool-call': { const normalizedInput = normalizeToolInputForModel(part.input); base = { @@ -364,9 +390,7 @@ function toAiMessageList(msg: Message): ModelMessage[] { if (assistantParts.length > 0) { const assistantBase: ModelMessage = { role: 'assistant', - content: assistantParts as Array< - TextPart | ReasoningPart | ToolCallPart | ToolResultPart | FilePart - >, + content: assistantParts as AiAssistantContent, }; const assistantMsg: ModelMessage = msg.providerOptions ? { ...assistantBase, providerOptions: msg.providerOptions } diff --git a/packages/@n8n/agents/src/runtime/model/model-factory.ts b/packages/@n8n/agents/src/runtime/model/model-factory.ts index a157796e4a9..e1e3db53262 100644 --- a/packages/@n8n/agents/src/runtime/model/model-factory.ts +++ b/packages/@n8n/agents/src/runtime/model/model-factory.ts @@ -115,15 +115,14 @@ const LANGUAGE_PROVIDERS: ProviderRegistry = { }, google: { build: (creds, model, fetch) => { - const { createGoogleGenerativeAI } = - require('@ai-sdk/google') as typeof import('@ai-sdk/google'); - return createGoogleGenerativeAI({ ...creds, fetch })(model); + const { createGoogle } = require('@ai-sdk/google') as typeof import('@ai-sdk/google'); + return createGoogle({ ...creds, fetch })(model); }, }, xai: { build: (creds, model, fetch) => { const { createXai } = require('@ai-sdk/xai') as typeof import('@ai-sdk/xai'); - return createXai({ ...creds, fetch })(model); + return createXai({ ...creds, fetch }).chat(model); }, }, groq: { @@ -283,7 +282,7 @@ export function createModel(config: ModelConfig, fetch?: FetchFn): LanguageModel */ const EMBEDDING_PROVIDERS = { openai: { pkg: '@ai-sdk/openai', factory: 'createOpenAI' }, - google: { pkg: '@ai-sdk/google', factory: 'createGoogleGenerativeAI' }, + google: { pkg: '@ai-sdk/google', factory: 'createGoogle' }, mistral: { pkg: '@ai-sdk/mistral', factory: 'createMistral' }, cohere: { pkg: '@ai-sdk/cohere', factory: 'createCohere' }, amazon: { pkg: '@ai-sdk/amazon-bedrock', factory: 'createAmazonBedrock' }, diff --git a/packages/@n8n/agents/src/runtime/model/provider-quirks.ts b/packages/@n8n/agents/src/runtime/model/provider-quirks.ts index b428a2a545b..6efc882f59b 100644 --- a/packages/@n8n/agents/src/runtime/model/provider-quirks.ts +++ b/packages/@n8n/agents/src/runtime/model/provider-quirks.ts @@ -68,7 +68,12 @@ export const PROVIDER_QUIRKS: Partial> = { relaxStrictJsonSchemaForRawOutput: true, thinkingToProviderOptions: (thinking) => { const cfg = thinking as OpenAIThinkingConfig; - return { openai: { reasoningEffort: cfg.reasoningEffort ?? 'medium' } }; + return { + openai: { + reasoningEffort: cfg.reasoningEffort ?? 'medium', + reasoningSummary: null, + }, + }; }, }, groq: { diff --git a/packages/@n8n/agents/src/runtime/streaming/stream.ts b/packages/@n8n/agents/src/runtime/streaming/stream.ts index 38e91d83daf..e108b97330c 100644 --- a/packages/@n8n/agents/src/runtime/streaming/stream.ts +++ b/packages/@n8n/agents/src/runtime/streaming/stream.ts @@ -69,7 +69,7 @@ export function toTokenUsage( } /** - * Convert a single AI SDK v6 fullStream chunk to an n8n StreamChunk + * Convert a single AI SDK stream chunk to an n8n StreamChunk */ export function convertChunk(c: TextStreamPart): StreamChunk | undefined { switch (c.type) { @@ -125,7 +125,7 @@ export function convertChunk(c: TextStreamPart): StreamChunk | undefine } case 'tool-result': - // The fullStream emits the raw tool output here, not the + // The stream emits the raw tool output here, not the // `{ type, value }` ToolResultOutput wrapper used on the message // side — so pass it through verbatim. Only provider-executed tools // (e.g. native web search) reach this branch; local tool results are diff --git a/packages/@n8n/agents/src/runtime/telemetry/__tests__/sub-agent-telemetry.test.ts b/packages/@n8n/agents/src/runtime/telemetry/__tests__/sub-agent-telemetry.test.ts index 2eac64b8d5d..c12295889e8 100644 --- a/packages/@n8n/agents/src/runtime/telemetry/__tests__/sub-agent-telemetry.test.ts +++ b/packages/@n8n/agents/src/runtime/telemetry/__tests__/sub-agent-telemetry.test.ts @@ -1,5 +1,7 @@ +import { Telemetry } from '../../../sdk/telemetry'; import type { BuiltTelemetry } from '../../../types/telemetry'; import { deriveSubAgentTelemetry } from '../sub-agent-telemetry'; +import { buildAiSdkTelemetry } from '../telemetry-options'; function builtTelemetry(overrides: Partial = {}): BuiltTelemetry { return { @@ -42,4 +44,45 @@ describe('deriveSubAgentTelemetry()', () => { expect(derived?.metadata).toEqual({ source: 'sub-agent' }); }); + + it('uses derived metadata for AI SDK OpenTelemetry spans', async () => { + const tracer = { + startSpan: vi.fn(() => ({ end: vi.fn() })), + startActiveSpan: vi.fn(), + }; + const parentTelemetry = await new Telemetry() + .tracer(tracer) + .metadata({ source: 'workflow' }) + .build(); + const derived = deriveSubAgentTelemetry(parentTelemetry); + const options = buildAiSdkTelemetry(derived).telemetry; + const integrations = Array.isArray(options?.integrations) + ? options.integrations + : options?.integrations + ? [options.integrations] + : []; + + integrations[0]?.onStart?.({ + operationId: 'ai.generateText', + callId: 'call-1', + provider: 'openai.responses', + modelId: 'gpt-5', + instructions: 'test', + messages: [], + maxRetries: 2, + functionId: 'sub-agent', + recordInputs: true, + recordOutputs: true, + } as never); + + expect(tracer.startSpan).toHaveBeenCalledWith( + 'ai.generateText', + expect.objectContaining({ + attributes: expect.objectContaining({ + 'ai.telemetry.metadata.source': 'sub-agent', + }), + }), + undefined, + ); + }); }); diff --git a/packages/@n8n/agents/src/runtime/telemetry/runtime-telemetry.ts b/packages/@n8n/agents/src/runtime/telemetry/runtime-telemetry.ts index 04fdeea7b05..57cfa2f33d9 100644 --- a/packages/@n8n/agents/src/runtime/telemetry/runtime-telemetry.ts +++ b/packages/@n8n/agents/src/runtime/telemetry/runtime-telemetry.ts @@ -1,7 +1,7 @@ -import type { TelemetrySettings } from 'ai'; +import type { TelemetryOptions } from 'ai'; import { zodToJsonSchema } from 'zod-to-json-schema'; -import { buildExperimentalTelemetry } from './telemetry-options'; +import { buildAiSdkTelemetry } from './telemetry-options'; import { Telemetry } from '../../sdk/telemetry'; import type { AttributeValue, BuiltProviderTool, BuiltTelemetry, BuiltTool } from '../../types'; import type { ExecutionOptions } from '../../types/sdk/agent'; @@ -126,7 +126,7 @@ function buildGenAiRootAttributes( /** * Owns all telemetry concerns for a single agent runtime: resolving the * effective telemetry config, mapping it to the AI SDK's - * `experimental_telemetry` shape, building LangSmith/AI-SDK span attributes, + * `telemetry` shape, building LangSmith/AI-SDK span attributes, * and wrapping the generate/stream loops and tool calls in active spans. * * Keeps provider-specific attribute formatting out of the core loop. Holds a @@ -156,11 +156,11 @@ export class RuntimeTelemetry { await Telemetry.forceFlush(this.resolve(options)); } - /** Map resolved telemetry to AI SDK's experimental_telemetry shape. */ + /** Map resolved telemetry to the AI SDK's telemetry shape. */ buildTelemetryOptions(options?: ExecutionOptions): { - experimental_telemetry?: TelemetrySettings; + telemetry?: TelemetryOptions; } { - return buildExperimentalTelemetry(this.resolve(options), { + return buildAiSdkTelemetry(this.resolve(options), { fallbackFunctionId: this.config.name, }); } diff --git a/packages/@n8n/agents/src/runtime/telemetry/telemetry-options.ts b/packages/@n8n/agents/src/runtime/telemetry/telemetry-options.ts index 97ea444b1ed..a7373edb31c 100644 --- a/packages/@n8n/agents/src/runtime/telemetry/telemetry-options.ts +++ b/packages/@n8n/agents/src/runtime/telemetry/telemetry-options.ts @@ -1,34 +1,33 @@ -import type { TelemetrySettings } from 'ai'; +import type { TelemetryOptions } from 'ai'; import type { BuiltTelemetry } from '../../types/telemetry'; /** - * Map a resolved BuiltTelemetry to the AI SDK's `experimental_telemetry` + * Map a resolved BuiltTelemetry to the AI SDK's `telemetry` * option. Single source of truth for all runtime LLM calls (loop, memory * tasks). `functionSuffix` namespaces auxiliary calls (e.g. 'memory-observer') * under the parent functionId. */ -export function buildExperimentalTelemetry( +export function buildAiSdkTelemetry( telemetry: BuiltTelemetry | undefined, options: { fallbackFunctionId?: string; functionSuffix?: string } = {}, -): { experimental_telemetry?: TelemetrySettings } { +): { telemetry?: TelemetryOptions } { if (!telemetry?.enabled) return {}; const baseFunctionId = telemetry.functionId ?? options.fallbackFunctionId ?? 'agent'; const functionId = options.functionSuffix ? `${baseFunctionId}.${options.functionSuffix}` : baseFunctionId; + const integrations = + telemetry.resolveIntegrations?.(telemetry.metadata) ?? telemetry.integrations; return { - experimental_telemetry: { + telemetry: { isEnabled: true, functionId, - metadata: telemetry.metadata, recordInputs: telemetry.recordInputs, recordOutputs: telemetry.recordOutputs, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any - tracer: telemetry.tracer as any, - integrations: telemetry.integrations.length > 0 ? telemetry.integrations : undefined, + integrations: integrations.length > 0 ? integrations : undefined, }, }; } diff --git a/packages/@n8n/agents/src/runtime/tools/tool-adapter.ts b/packages/@n8n/agents/src/runtime/tools/tool-adapter.ts index 000df8e9e66..07177186158 100644 --- a/packages/@n8n/agents/src/runtime/tools/tool-adapter.ts +++ b/packages/@n8n/agents/src/runtime/tools/tool-adapter.ts @@ -54,6 +54,7 @@ export function toAiSdkProviderTools(tools?: BuiltProviderTool[]): Record { const firstCall = generateText.mock.calls[0][0] as Record; const secondCall = generateText.mock.calls[1][0] as Record; - const firstTelemetry = firstCall.experimental_telemetry as Record; - const secondTelemetry = secondCall.experimental_telemetry as Record; + const firstTelemetry = firstCall.telemetry as Record; + const secondTelemetry = secondCall.telemetry as Record; expect(firstTelemetry.functionId).toBe('initial-agent'); expect(secondTelemetry.functionId).toBe('updated-agent'); - expect(secondTelemetry.metadata).toEqual({ functionId: 'updated-agent' }); }); }); diff --git a/packages/@n8n/agents/src/sdk/__tests__/telemetry.test.ts b/packages/@n8n/agents/src/sdk/__tests__/telemetry.test.ts index 005f698b80a..d0e9b646449 100644 --- a/packages/@n8n/agents/src/sdk/__tests__/telemetry.test.ts +++ b/packages/@n8n/agents/src/sdk/__tests__/telemetry.test.ts @@ -1,4 +1,4 @@ -import type { TelemetryIntegration } from 'ai'; +import type { Telemetry as AiSdkTelemetry } from 'ai'; import { Telemetry } from '../telemetry'; @@ -35,9 +35,47 @@ describe('Telemetry builder', () => { }); it('accepts a pre-built tracer', async () => { - const fakeTracer = { startSpan: vi.fn() }; + const fakeTracer = { startSpan: vi.fn(), startActiveSpan: vi.fn() }; const built = await new Telemetry().tracer(fakeTracer).build(); expect(built.tracer).toBe(fakeTracer); + expect(built.integrations).toHaveLength(1); + expect(built.integrations[0]).toEqual( + expect.objectContaining({ + onStart: expect.any(Function), + executeLanguageModelCall: expect.any(Function), + }), + ); + }); + + it('preserves metadata on AI SDK 7 OpenTelemetry spans', async () => { + const fakeTracer = { + startSpan: vi.fn(() => ({ end: vi.fn() })), + startActiveSpan: vi.fn(), + }; + const built = await new Telemetry().metadata({ team: 'platform' }).tracer(fakeTracer).build(); + + built.integrations[0].onStart?.({ + operationId: 'ai.generateText', + callId: 'call-1', + provider: 'openai.responses', + modelId: 'gpt-5', + instructions: 'test', + messages: [], + maxRetries: 2, + functionId: 'agent', + recordInputs: true, + recordOutputs: true, + } as never); + + expect(fakeTracer.startSpan).toHaveBeenCalledWith( + 'ai.generateText', + expect.objectContaining({ + attributes: expect.objectContaining({ + 'ai.telemetry.metadata.team': 'platform', + }), + }), + undefined, + ); }); it('throws when both .tracer() and .otlpEndpoint() are set', async () => { @@ -47,21 +85,161 @@ describe('Telemetry builder', () => { }); it('collects multiple integrations', async () => { - const int1: TelemetryIntegration = { onStart: vi.fn() }; - const int2: TelemetryIntegration = { onFinish: vi.fn() }; + const int1: AiSdkTelemetry = { onStart: vi.fn() }; + const int2: AiSdkTelemetry = { onEnd: vi.fn() }; const built = await new Telemetry().integration(int1).integration(int2).build(); expect(built.integrations).toHaveLength(2); }); }); describe('Telemetry — redaction wrapping', () => { + it('redacts hooks on frozen integrations', async () => { + const onStart = vi.fn(); + const integration: AiSdkTelemetry = Object.freeze({ onStart }); + const built = await new Telemetry() + .redact((data) => { + const filtered = { ...data }; + delete filtered.secret; + return filtered; + }) + .integration(integration) + .build(); + + built.integrations[0].onStart?.({ secret: 'hidden', safe: 'ok' } as never); + + expect(onStart).toHaveBeenCalledWith({ safe: 'ok' }); + }); + + it('accepts immutable redactor results', async () => { + const onStart = vi.fn(); + const built = await new Telemetry() + .redact((data) => { + const filtered = { ...data }; + delete filtered.secret; + return Object.freeze(filtered); + }) + .integration({ onStart }) + .build(); + + built.integrations[0].onStart?.({ secret: 'hidden', safe: 'ok' } as never); + + expect(onStart).toHaveBeenCalledWith({ safe: 'ok' }); + }); + + it('redacts future event hooks without requiring an explicit wrapper', async () => { + type FutureTelemetry = AiSdkTelemetry & { + onFutureEvent: (event: Record) => void; + readonly receivedEvents: Array>; + }; + + class FutureIntegration { + readonly #receivedEvents: Array> = []; + + onStart() {} + + onFutureEvent(event: Record) { + this.#receivedEvents.push(event); + } + + get receivedEvents() { + return this.#receivedEvents; + } + } + + const integration: FutureTelemetry = new FutureIntegration(); + const built = await new Telemetry() + .redact((data) => { + const filtered = { ...data }; + delete filtered.secret; + return filtered; + }) + .integration(integration) + .build(); + + const wrapped = built.integrations[0] as FutureTelemetry; + wrapped.onFutureEvent({ secret: 'hidden', safe: 'ok' }); + + expect(integration.receivedEvents).toEqual([{ safe: 'ok' }]); + }); + + it('preserves executor control fields while redacting their event data', async () => { + let modelOptions: Record | undefined; + let toolOptions: Record | undefined; + const integration: AiSdkTelemetry = { + executeLanguageModelCall: (options) => { + modelOptions = options; + return options.execute(); + }, + executeTool: (options) => { + toolOptions = options; + return options.execute(); + }, + }; + const built = await new Telemetry() + .redact((data) => { + const filtered = { ...data }; + delete filtered.callId; + delete filtered.toolCallId; + delete filtered.execute; + delete filtered.secret; + return Object.freeze(filtered); + }) + .integration(integration) + .build(); + + const modelExecute = vi.fn().mockResolvedValue('model-result'); + await expect( + built.integrations[0].executeLanguageModelCall?.({ + callId: 'model-call', + execute: modelExecute, + secret: 'hidden', + } as never), + ).resolves.toBe('model-result'); + expect(modelOptions).toEqual({ callId: 'model-call', execute: modelExecute }); + + const toolExecute = vi.fn().mockResolvedValue('tool-result'); + await expect( + built.integrations[0].executeTool?.({ + callId: 'model-call', + toolCallId: 'tool-call', + execute: toolExecute, + secret: 'hidden', + } as never), + ).resolves.toBe('tool-result'); + expect(toolOptions).toEqual({ + callId: 'model-call', + toolCallId: 'tool-call', + execute: toolExecute, + }); + }); + + it('preserves the receiver for stateful integration methods', async () => { + class StatefulIntegration { + readonly receivedEvents: unknown[] = []; + + onStart(event: Parameters>[0]) { + this.receivedEvents.push(event); + } + } + + const integration = new StatefulIntegration(); + const built = await new Telemetry() + .redact((data) => data) + .integration(integration) + .build(); + + built.integrations[0].onStart!({ operationId: 'ai.generateText' } as never); + + expect(integration.receivedEvents).toHaveLength(1); + }); + it('wraps integrations with redaction when .redact() is set', async () => { const receivedEvents: unknown[] = []; - const integration: TelemetryIntegration = { + const integration: AiSdkTelemetry = { onStart: (event) => { receivedEvents.push(event); }, - onFinish: (event) => { + onEnd: (event) => { receivedEvents.push(event); }, }; @@ -86,7 +264,7 @@ describe('Telemetry — redaction wrapping', () => { }); it('does not wrap integrations when .redact() is not set', async () => { - const integration: TelemetryIntegration = { onStart: vi.fn() }; + const integration: AiSdkTelemetry = { onStart: vi.fn() }; const built = await new Telemetry().integration(integration).build(); // The integration should be a copy (not the same reference due to spread) but functionally identical expect(built.integrations[0].onStart).toBe(integration.onStart); @@ -94,7 +272,7 @@ describe('Telemetry — redaction wrapping', () => { it('redacts top-level scalar fields via the redact callback', async () => { const receivedEvents: unknown[] = []; - const integration: TelemetryIntegration = { + const integration: AiSdkTelemetry = { onStart: (event) => { receivedEvents.push(event); }, @@ -118,7 +296,7 @@ describe('Telemetry — redaction wrapping', () => { it('redacts objects inside arrays', async () => { const receivedEvents: unknown[] = []; - const integration: TelemetryIntegration = { + const integration: AiSdkTelemetry = { onStart: (event) => { receivedEvents.push(event); }, diff --git a/packages/@n8n/agents/src/sdk/metadata-enriched-tracer.ts b/packages/@n8n/agents/src/sdk/metadata-enriched-tracer.ts new file mode 100644 index 00000000000..877f6bf0ed6 --- /dev/null +++ b/packages/@n8n/agents/src/sdk/metadata-enriched-tracer.ts @@ -0,0 +1,69 @@ +import type { Attributes, Context, Span, SpanOptions, Tracer } from '@opentelemetry/api'; + +import type { AttributeValue } from '../types/telemetry'; + +class MetadataEnrichedTracer implements Tracer { + constructor( + private readonly delegate: Tracer, + private readonly attributes: Attributes, + ) {} + + startSpan(name: string, options?: SpanOptions, context?: Context): Span { + return this.delegate.startSpan( + name, + { + ...options, + attributes: { ...this.attributes, ...options?.attributes }, + }, + context, + ); + } + + startActiveSpan unknown>(name: string, fn: F): ReturnType; + startActiveSpan unknown>( + name: string, + options: SpanOptions, + fn: F, + ): ReturnType; + startActiveSpan unknown>( + name: string, + options: SpanOptions, + context: Context, + fn: F, + ): ReturnType; + startActiveSpan unknown>( + name: string, + optionsOrFn: SpanOptions | F, + contextOrFn?: Context | F, + fn?: F, + ): ReturnType { + if (typeof optionsOrFn === 'function') { + return this.delegate.startActiveSpan(name, optionsOrFn); + } + + const options = { + ...optionsOrFn, + attributes: { ...this.attributes, ...optionsOrFn.attributes }, + }; + if (typeof contextOrFn === 'function') { + return this.delegate.startActiveSpan(name, options, contextOrFn); + } + if (contextOrFn === undefined || fn === undefined) { + throw new Error('OpenTelemetry active span callback is required.'); + } + + return this.delegate.startActiveSpan(name, options, contextOrFn, fn); + } +} + +export function createMetadataEnrichedTracer( + tracer: Tracer, + metadata: Record | undefined, +): Tracer { + if (!metadata || Object.keys(metadata).length === 0) return tracer; + + const attributes = Object.fromEntries( + Object.entries(metadata).map(([key, value]) => [`ai.telemetry.metadata.${key}`, value]), + ); + return new MetadataEnrichedTracer(tracer, attributes); +} diff --git a/packages/@n8n/agents/src/sdk/telemetry.ts b/packages/@n8n/agents/src/sdk/telemetry.ts index 1435e42f1cb..6d6b7229f74 100644 --- a/packages/@n8n/agents/src/sdk/telemetry.ts +++ b/packages/@n8n/agents/src/sdk/telemetry.ts @@ -1,5 +1,8 @@ -import type { TelemetryIntegration } from 'ai'; +import { isRecord } from '@n8n/utils/is-record'; +import type { Tracer } from '@opentelemetry/api'; +import type { Telemetry as AiSdkTelemetry } from 'ai'; +import { createMetadataEnrichedTracer } from './metadata-enriched-tracer'; import type { AttributeValue, BuiltTelemetry, @@ -8,6 +11,16 @@ import type { } from '../types/telemetry'; type RedactFn = (data: Record) => Record; +type ExecuteHookKey = 'executeLanguageModelCall' | 'executeTool'; +// Future on* hooks are wrapped automatically; other SDK members must be classified explicitly. +type RedactableTelemetry = AiSdkTelemetry & + Record, never>; +type UnknownMethod = (this: unknown, ...args: unknown[]) => unknown; + +const EXECUTION_CONTROL_FIELDS: Record = { + executeLanguageModelCall: ['callId', 'execute'], + executeTool: ['callId', 'toolCallId', 'execute'], +}; /** * Recursively apply the redact function to plain objects found anywhere @@ -22,7 +35,7 @@ function redactValue(value: unknown, redact: RedactFn): unknown { value !== null && Object.getPrototypeOf(value) === Object.prototype ) { - const redacted = redact(value as Record); + const redacted = { ...redact(value as Record) }; // Recurse into the redacted result so deeply nested objects are also processed. for (const key of Object.keys(redacted)) { redacted[key] = redactValue(redacted[key], redact); @@ -41,7 +54,7 @@ function redactValue(value: unknown, redact: RedactFn): unknown { function redactEvent(event: T, redact: RedactFn): T { const cloned = { ...event }; // Redact the cloned event itself (it is a plain object). - const redacted = redact(cloned as unknown as Record); + const redacted = { ...redact(cloned as unknown as Record) }; // Then recurse into each value to handle arrays and nested objects. for (const key of Object.keys(redacted)) { const value = redacted[key]; @@ -50,42 +63,82 @@ function redactEvent(event: T, redact: RedactFn): T { return redacted as T; } +function isExecuteHookKey(property: PropertyKey): property is ExecuteHookKey { + return property === 'executeLanguageModelCall' || property === 'executeTool'; +} + +function isTelemetryHook(property: PropertyKey): boolean { + return (typeof property === 'string' && property.startsWith('on')) || isExecuteHookKey(property); +} + +function isMethod(value: unknown): value is UnknownMethod { + return typeof value === 'function'; +} + +function redactHookArgument(property: PropertyKey, argument: unknown, redact: RedactFn): unknown { + if (property === 'onError' || !isRecord(argument)) return redactValue(argument, redact); + + const redacted = redactEvent(argument, redact); + if (isExecuteHookKey(property)) { + const controlFields = Object.fromEntries( + EXECUTION_CONTROL_FIELDS[property].map((field) => [field, argument[field]]), + ); + return { ...redacted, ...controlFields }; + } + return redacted; +} + /** - * Wrap a TelemetryIntegration so every hook passes event data through + * Wrap an AI SDK telemetry integration so every hook passes event data through * the redact callback before forwarding to the original hook. */ function wrapIntegrationWithRedaction( - integration: TelemetryIntegration, + integration: RedactableTelemetry, redact: RedactFn, -): TelemetryIntegration { - const wrapped: TelemetryIntegration = {}; +): AiSdkTelemetry { + const methodCache = new Map(); + const facade: AiSdkTelemetry = {}; - if (integration.onStart) { - const orig = integration.onStart; - wrapped.onStart = (event) => orig(redactEvent(event, redact)); - } - if (integration.onStepStart) { - const orig = integration.onStepStart; - wrapped.onStepStart = (event) => orig(redactEvent(event, redact)); - } - if (integration.onToolCallStart) { - const orig = integration.onToolCallStart; - wrapped.onToolCallStart = (event) => orig(redactEvent(event, redact)); - } - if (integration.onToolCallFinish) { - const orig = integration.onToolCallFinish; - wrapped.onToolCallFinish = (event) => orig(redactEvent(event, redact)); - } - if (integration.onStepFinish) { - const orig = integration.onStepFinish; - wrapped.onStepFinish = (event) => orig(redactEvent(event, redact)); - } - if (integration.onFinish) { - const orig = integration.onFinish; - wrapped.onFinish = (event) => orig(redactEvent(event, redact)); + return new Proxy(facade, { + get(_target, property) { + const original: unknown = Reflect.get(integration, property, integration); + if (!isTelemetryHook(property) || !isMethod(original)) return original; + + const cached = methodCache.get(property); + if (cached?.original === original) return cached.wrapped; + + const wrapped: UnknownMethod = function (...args) { + const [argument, ...rest] = args; + return Reflect.apply(original, integration, [ + redactHookArgument(property, argument, redact), + ...rest, + ]); + }; + methodCache.set(property, { original, wrapped }); + return wrapped; + }, + }); +} + +function isOpenTelemetryTracer(value: unknown): value is Tracer { + return ( + value !== null && + typeof value === 'object' && + typeof Reflect.get(value, 'startSpan') === 'function' && + typeof Reflect.get(value, 'startActiveSpan') === 'function' + ); +} + +async function createAiSdkOpenTelemetryIntegrationFactory( + tracer: OpaqueTracer, +): Promise<(metadata: Record | undefined) => AiSdkTelemetry> { + if (!isOpenTelemetryTracer(tracer)) { + throw new Error('Telemetry tracer must implement startSpan() and startActiveSpan().'); } - return wrapped; + const { LegacyOpenTelemetry } = await import('@ai-sdk/otel'); + return (metadata) => + new LegacyOpenTelemetry({ tracer: createMetadataEnrichedTracer(tracer, metadata) }); } /** @@ -128,7 +181,7 @@ async function createOtlpTracer(endpoint: string): Promise<{ * * Use `.tracer()` with a pre-built integration (e.g. `LangSmithTelemetry`, * `integrations.langsmith()`) or `.otlpEndpoint()` for a generic OTLP - * collector. Add AI SDK `TelemetryIntegration` hooks via `.integration()`. + * collector. Add AI SDK `Telemetry` hooks via `.integration()`. * * @example * ```typescript @@ -157,7 +210,7 @@ export class Telemetry { protected redactFn?: RedactFn; - protected integrationsList: TelemetryIntegration[] = []; + protected integrationsList: AiSdkTelemetry[] = []; protected tracerValue?: OpaqueTracer; @@ -242,7 +295,7 @@ export class Telemetry { } /** Add a telemetry integration (e.g. for observability platforms). */ - integration(value: TelemetryIntegration): this { + integration(value: AiSdkTelemetry): this { this.integrationsList.push(value); return this; } @@ -285,9 +338,26 @@ export class Telemetry { provider = otlp.provider; } - const integrations = this.redactFn - ? this.integrationsList.map((i) => wrapIntegrationWithRedaction(i, this.redactFn!)) + const redactFn = this.redactFn; + const customIntegrations = redactFn + ? this.integrationsList.map((integration) => + wrapIntegrationWithRedaction(integration, redactFn), + ) : [...this.integrationsList]; + let resolveIntegrations: BuiltTelemetry['resolveIntegrations']; + let integrations = customIntegrations; + if (tracer !== undefined) { + const createOpenTelemetryIntegration = + await createAiSdkOpenTelemetryIntegrationFactory(tracer); + resolveIntegrations = (metadata) => { + const integration = createOpenTelemetryIntegration(metadata); + return [ + redactFn ? wrapIntegrationWithRedaction(integration, redactFn) : integration, + ...customIntegrations, + ]; + }; + integrations = resolveIntegrations(this.metadataValue); + } return { enabled: this.enabledValue, @@ -297,6 +367,7 @@ export class Telemetry { recordOutputs: this.recordOutputsValue, runtimeRootSpanEnabled: this.runtimeRootSpanEnabledValue, integrations, + ...(resolveIntegrations && { resolveIntegrations }), tracer, provider, credentialName: this.credentialNameValue, diff --git a/packages/@n8n/agents/src/types/index.ts b/packages/@n8n/agents/src/types/index.ts index 322c9d122ae..4cc72ba98ff 100644 --- a/packages/@n8n/agents/src/types/index.ts +++ b/packages/@n8n/agents/src/types/index.ts @@ -7,7 +7,9 @@ export type { ContentCitation, ContentText, ContentReasoning, + ContentReasoningFile, ContentFile, + ContentCustom, ContentToolCall, ContentInvalidToolCall, ContentProvider, diff --git a/packages/@n8n/agents/src/types/sdk/agent.ts b/packages/@n8n/agents/src/types/sdk/agent.ts index 80c565dbaee..116d731bb46 100644 --- a/packages/@n8n/agents/src/types/sdk/agent.ts +++ b/packages/@n8n/agents/src/types/sdk/agent.ts @@ -1,5 +1,10 @@ import type { ProviderOptions } from '@ai-sdk/provider-utils'; -import type { LanguageModel, OnStepFinishEvent, OnStepStartEvent, smoothStream } from 'ai'; +import type { + GenerateTextStepEndEvent, + GenerateTextStepStartEvent, + LanguageModel, + smoothStream, +} from 'ai'; import type { JsonSchema7Type } from 'zod-to-json-schema'; import type { AgentMessage, ContentMetadata } from './message'; @@ -91,7 +96,7 @@ export type StreamChunk = ContentMetadata & | { /** * Emitted just before a tool handler starts executing. Bridged from - * the runtime event bus (not part of the AI SDK fullStream). Pairs + * the runtime event bus (not part of the AI SDK stream). Pairs * with the subsequent `tool-result` to let consumers show a * mid-flight indicator between "LLM picked a tool" and "result arrived". */ @@ -185,8 +190,10 @@ export interface ExecutionOptions { telemetry?: BuiltTelemetry; /** Inherited execution counter from the host runtime. Used for aggregate heartbeat telemetry. */ executionCounter?: AgentExecutionCounter; - onStepStart?: (event: OnStepStartEvent) => void | Promise; - onStepFinish?: (event: OnStepFinishEvent) => void | Promise; + onStepStart?: (event: GenerateTextStepStartEvent) => void | Promise; + onStepEnd?: (event: GenerateTextStepEndEvent) => void | Promise; + /** @deprecated Use `onStepEnd` instead. */ + onStepFinish?: (event: GenerateTextStepEndEvent) => void | Promise; /** * Durable-log RFC (resilience phase), opt-in: persist a `running`-status * checkpoint at every step boundary (after a tool batch settles, before the diff --git a/packages/@n8n/agents/src/types/sdk/message.ts b/packages/@n8n/agents/src/types/sdk/message.ts index 6e62defd2cb..70f3330e8e4 100644 --- a/packages/@n8n/agents/src/types/sdk/message.ts +++ b/packages/@n8n/agents/src/types/sdk/message.ts @@ -9,8 +9,10 @@ export type MessageContent = | ContentToolCall | ContentInvalidToolCall | ContentReasoning + | ContentReasoningFile | ContentFile | ContentCitation + | ContentCustom | ContentProvider; export interface ContentMetadata { @@ -83,6 +85,17 @@ export type ContentFile = ContentMetadata & { data: Uint8Array | ArrayBuffer | Buffer | string; }; +export type ContentReasoningFile = ContentMetadata & { + type: 'reasoning-file'; + data: ContentFile['data']; + mediaType: string; +}; + +export type ContentCustom = ContentMetadata & { + type: 'custom'; + kind: `${string}.${string}`; +}; + export type ContentToolCall = ContentMetadata & { type: 'tool-call'; diff --git a/packages/@n8n/agents/src/types/telemetry.ts b/packages/@n8n/agents/src/types/telemetry.ts index fa974888bf8..42a7a9a0417 100644 --- a/packages/@n8n/agents/src/types/telemetry.ts +++ b/packages/@n8n/agents/src/types/telemetry.ts @@ -1,4 +1,4 @@ -import type { TelemetryIntegration } from 'ai'; +import type { Telemetry as AiSdkTelemetry } from 'ai'; /** OTel-compatible attribute values (no undefined). */ export type AttributeValue = string | number | boolean | string[] | number[] | boolean[]; @@ -43,7 +43,11 @@ export interface BuiltTelemetry { */ readonly rootAnchored?: boolean; /** Integrations are pre-wrapped with redaction if .redact() was set at build time. */ - readonly integrations: TelemetryIntegration[]; + readonly integrations: AiSdkTelemetry[]; + /** @internal Rebuild metadata-sensitive integrations for the effective run metadata. */ + readonly resolveIntegrations?: ( + metadata: Record | undefined, + ) => AiSdkTelemetry[]; readonly tracer?: OpaqueTracer; /** @internal Provider reference for flush/shutdown. Only set when .otlpEndpoint() is used. */ readonly provider?: OpaqueTracerProvider; diff --git a/packages/@n8n/instance-ai/package.json b/packages/@n8n/instance-ai/package.json index 44101c50647..591a8114ba6 100644 --- a/packages/@n8n/instance-ai/package.json +++ b/packages/@n8n/instance-ai/package.json @@ -64,7 +64,7 @@ "@n8n/mcp-browser": "workspace:*", "@n8n/utils": "workspace:*", "@n8n/workflow-sdk": "workspace:*", - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": "catalog:", "@thednp/dommatrix": "^2.0.12", "ai": "catalog:", "csv-parse": "catalog:", diff --git a/packages/@n8n/instance-ai/src/debug/__tests__/run-debug-buffer.test.ts b/packages/@n8n/instance-ai/src/debug/__tests__/run-debug-buffer.test.ts index 0fb83103f22..c5f92715cad 100644 --- a/packages/@n8n/instance-ai/src/debug/__tests__/run-debug-buffer.test.ts +++ b/packages/@n8n/instance-ai/src/debug/__tests__/run-debug-buffer.test.ts @@ -1,4 +1,4 @@ -import type { OnStepFinishEvent, OnStepStartEvent } from 'ai'; +import type { GenerateTextStepEndEvent, GenerateTextStepStartEvent } from 'ai'; import { describe, expect, it } from 'vitest'; import { mock } from 'vitest-mock-extended'; @@ -12,7 +12,7 @@ import { } from '../run-debug-buffer'; import { sanitizeDebugSnapshotValue } from '../sanitize-debug-snapshot'; -function makeFinishEvent(text: string): OnStepFinishEvent { +function makeStepEndEvent(text: string): GenerateTextStepEndEvent { return { stepNumber: 0, text, @@ -24,10 +24,10 @@ function makeFinishEvent(text: string): OnStepFinishEvent { timestamp: new Date('2026-01-01T00:00:00.000Z'), messages: [{ role: 'assistant', content: text }], }, - } as unknown as OnStepFinishEvent; + } as unknown as GenerateTextStepEndEvent; } -function makeStartEvent(): OnStepStartEvent { +function makeStepStartEvent(): GenerateTextStepStartEvent { return { stepNumber: 0, system: 'You are helpful', @@ -36,7 +36,7 @@ function makeStartEvent(): OnStepStartEvent { toolChoice: 'auto', activeTools: ['search'], abortSignal: new AbortController().signal, - } as unknown as OnStepStartEvent; + } as unknown as GenerateTextStepStartEvent; } describe('RunDebugBuffer', () => { @@ -44,8 +44,8 @@ describe('RunDebugBuffer', () => { const buffer = new RunDebugBuffer(); buffer.ensure('run-1', 'thread-1'); - buffer.recordStepStart('run-1', 0, makeStartEvent()); - buffer.recordStepFinish('run-1', 0, makeFinishEvent('done')); + buffer.recordStepStart('run-1', 0, makeStepStartEvent()); + buffer.recordStepFinish('run-1', 0, makeStepEndEvent('done')); const record = buffer.get('run-1'); expect(record?.steps).toHaveLength(1); @@ -60,8 +60,8 @@ describe('RunDebugBuffer', () => { const hooks = createRunDebugStepHooks(buffer, { runId: 'run-1', threadId: 'thread-1' }); for (const label of ['first', 'second', 'third']) { - hooks.onStepStart(makeStartEvent()); - hooks.onStepFinish(makeFinishEvent(label)); + hooks.onStepStart(makeStepStartEvent()); + hooks.onStepEnd(makeStepEndEvent(label)); } const record = buffer.get('run-1'); @@ -75,12 +75,12 @@ describe('RunDebugBuffer', () => { buffer.ensure('run-1', 'thread-1'); const firstPass = createRunDebugStepHooks(buffer, { runId: 'run-1', threadId: 'thread-1' }); - firstPass.onStepStart(makeStartEvent()); - firstPass.onStepFinish(makeFinishEvent('before suspend')); + firstPass.onStepStart(makeStepStartEvent()); + firstPass.onStepEnd(makeStepEndEvent('before suspend')); const resumePass = createRunDebugStepHooks(buffer, { runId: 'run-1', threadId: 'thread-1' }); - resumePass.onStepStart(makeStartEvent()); - resumePass.onStepFinish(makeFinishEvent('after resume')); + resumePass.onStepStart(makeStepStartEvent()); + resumePass.onStepEnd(makeStepEndEvent('after resume')); const record = buffer.get('run-1'); expect(record?.steps).toHaveLength(2); @@ -100,7 +100,7 @@ describe('RunDebugBuffer', () => { toolChoice: 'auto', activeTools: ['search'], abortSignal: new AbortController().signal, - } as unknown as OnStepStartEvent, + } as unknown as GenerateTextStepStartEvent, 4, ); @@ -146,7 +146,7 @@ describe('RunDebugBuffer', () => { messages: [{ role: 'assistant', content: '' }], body: { secret: 'raw-provider-body' }, }, - } as unknown as OnStepFinishEvent, + } as unknown as GenerateTextStepEndEvent, 0, ); @@ -177,7 +177,7 @@ describe('RunDebugBuffer', () => { buffer.recordStepStart(runId, 0, { stepNumber: 0, messages: [], - } as unknown as OnStepStartEvent); + } as unknown as GenerateTextStepStartEvent); } expect(buffer.get('run-0')).toBeUndefined(); @@ -193,12 +193,20 @@ describe('RunDebugBuffer', () => { hooks.onStepStart({ stepNumber: 2, messages: [{ role: 'user', content: 'ping' }], - } as unknown as OnStepStartEvent); + } as unknown as GenerateTextStepStartEvent); expect(buffer.get('run-1')?.steps[0]?.stepNumber).toBe(0); expect(buffer.get('run-1')?.steps[0]?.input?.sdkStepNumber).toBe(2); }); + it('exposes onStepEnd and keeps onStepFinish as a compatibility alias', () => { + const buffer = new RunDebugBuffer(); + const hooks = createRunDebugStepHooks(buffer, { runId: 'run-1', threadId: 'thread-1' }); + + expect(typeof hooks.onStepEnd).toBe('function'); + expect(hooks.onStepFinish).toBe(hooks.onStepEnd); + }); + it('stores a run label on first ensure', () => { const buffer = new RunDebugBuffer(); buffer.ensure('run-1', 'thread-1', 'build a weather workflow'); diff --git a/packages/@n8n/instance-ai/src/debug/run-debug-buffer.ts b/packages/@n8n/instance-ai/src/debug/run-debug-buffer.ts index 8e8e2250b35..fd749c8747a 100644 --- a/packages/@n8n/instance-ai/src/debug/run-debug-buffer.ts +++ b/packages/@n8n/instance-ai/src/debug/run-debug-buffer.ts @@ -1,5 +1,5 @@ import { scrubSecretsInText } from '@n8n/utils/scrub-secrets'; -import type { OnStepFinishEvent, OnStepStartEvent } from 'ai'; +import type { GenerateTextStepEndEvent, GenerateTextStepStartEvent } from 'ai'; import type { Logger } from '../logger'; import { sanitizeDebugSnapshotRecord, sanitizeDebugSnapshotValue } from './sanitize-debug-snapshot'; @@ -53,16 +53,18 @@ export interface RunDebugStepHookOptions { threadId: string; } -function captureStepStartPayload(event: OnStepStartEvent): Record { - const { abortSignal: _abortSignal, ...capturable } = event; - return sanitizeDebugSnapshotRecord(capturable); -} - -function captureStepFinishPayload(event: OnStepFinishEvent): Record { +function captureStepStartPayload(event: GenerateTextStepStartEvent): Record { return sanitizeDebugSnapshotRecord(event); } -export function sanitizeStepStart(event: OnStepStartEvent, stepNumber: number): SanitizedStepStart { +function captureStepFinishPayload(event: GenerateTextStepEndEvent): Record { + return sanitizeDebugSnapshotRecord(event); +} + +export function sanitizeStepStart( + event: GenerateTextStepStartEvent, + stepNumber: number, +): SanitizedStepStart { return { ...captureStepStartPayload(event), stepNumber, @@ -71,7 +73,7 @@ export function sanitizeStepStart(event: OnStepStartEvent, stepNumber: number): } export function sanitizeStepFinish( - event: OnStepFinishEvent, + event: GenerateTextStepEndEvent, stepNumber: number, ): SanitizedStepFinish { return { @@ -85,21 +87,26 @@ export function createRunDebugStepHooks( buffer: RunDebugBuffer, options: RunDebugStepHookOptions, ): { - onStepStart: (event: OnStepStartEvent) => void; - onStepFinish: (event: OnStepFinishEvent) => void; + onStepStart: (event: GenerateTextStepStartEvent) => void; + onStepEnd: (event: GenerateTextStepEndEvent) => void; + /** @deprecated Use `onStepEnd` instead. */ + onStepFinish: (event: GenerateTextStepEndEvent) => void; } { // The agent runtime calls streamText/generateText once per loop iteration. The AI SDK // resets stepNumber to 0 on each call, so we allocate a run-scoped sequence instead. let stepIndex = buffer.getNextStepIndex(options.runId); + const onStepEnd = (event: GenerateTextStepEndEvent) => { + buffer.recordStepFinish(options.runId, stepIndex, event); + stepIndex++; + }; + return { onStepStart: (event) => { buffer.recordStepStart(options.runId, stepIndex, event); }, - onStepFinish: (event) => { - buffer.recordStepFinish(options.runId, stepIndex, event); - stepIndex++; - }, + onStepEnd, + onStepFinish: onStepEnd, }; } @@ -153,7 +160,7 @@ export class RunDebugBuffer { return this.records.get(runId)?.nextStepIndex ?? 0; } - recordStepStart(runId: string, stepIndex: number, event: OnStepStartEvent): void { + recordStepStart(runId: string, stepIndex: number, event: GenerateTextStepStartEvent): void { const record = this.records.get(runId); if (!record) return; @@ -171,7 +178,7 @@ export class RunDebugBuffer { record.steps.sort((a, b) => a.stepNumber - b.stepNumber); } - recordStepFinish(runId: string, stepIndex: number, event: OnStepFinishEvent): void { + recordStepFinish(runId: string, stepIndex: number, event: GenerateTextStepEndEvent): void { const record = this.records.get(runId); if (!record) return; diff --git a/packages/cli/package.json b/packages/cli/package.json index c35fb182321..3c98a00107c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -173,7 +173,7 @@ "@n8n/workflow-sdk": "workspace:*", "@n8n_io/ai-assistant-sdk": "catalog:", "@n8n_io/license-sdk": "3.0.0", - "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api": "catalog:", "@opentelemetry/core": "2.7.1", "@opentelemetry/exporter-trace-otlp-proto": "^0.217.0", "@opentelemetry/instrumentation": "^0.217.0", diff --git a/packages/cli/src/modules/agents/__tests__/agent-run-tracing.service.test.ts b/packages/cli/src/modules/agents/__tests__/agent-run-tracing.service.test.ts index 7e6c2bb946d..0901a3d1033 100644 --- a/packages/cli/src/modules/agents/__tests__/agent-run-tracing.service.test.ts +++ b/packages/cli/src/modules/agents/__tests__/agent-run-tracing.service.test.ts @@ -5,7 +5,7 @@ import { mock } from 'vitest-mock-extended'; import { AgentRunTracingService, modelIdFromSnapshot } from '../agent-run-tracing.service'; vi.mock('@opentelemetry/api', () => ({ - trace: { getTracer: vi.fn(() => ({ startActiveSpan: vi.fn() })) }, + trace: { getTracer: vi.fn(() => ({ startSpan: vi.fn(), startActiveSpan: vi.fn() })) }, })); describe('AgentRunTracingService', () => { diff --git a/packages/cli/src/modules/instance-ai/__tests__/run-debug-gating.test.ts b/packages/cli/src/modules/instance-ai/__tests__/run-debug-gating.test.ts index 0301da092d0..deb5a644001 100644 --- a/packages/cli/src/modules/instance-ai/__tests__/run-debug-gating.test.ts +++ b/packages/cli/src/modules/instance-ai/__tests__/run-debug-gating.test.ts @@ -53,8 +53,10 @@ describe('InstanceAiService run debug gating', () => { ); expect(streamOptions.onStepStart).toBeUndefined(); + expect(streamOptions.onStepEnd).toBeUndefined(); expect(streamOptions.onStepFinish).toBeUndefined(); expect(resumeOptions.onStepStart).toBeUndefined(); + expect(resumeOptions.onStepEnd).toBeUndefined(); expect(resumeOptions.onStepFinish).toBeUndefined(); expect(service.getRunDebug(runId)).toBeUndefined(); // Both terminal paths opt into raw-usage recovery so stopped/errored runs bill. @@ -85,8 +87,10 @@ describe('InstanceAiService run debug gating', () => { ); expect(typeof streamOptions.onStepStart).toBe('function'); + expect(typeof streamOptions.onStepEnd).toBe('function'); expect(typeof streamOptions.onStepFinish).toBe('function'); expect(typeof resumeOptions.onStepStart).toBe('function'); + expect(typeof resumeOptions.onStepEnd).toBe('function'); expect(typeof resumeOptions.onStepFinish).toBe('function'); expect(service.getRunDebug(runId)).toEqual( expect.objectContaining({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f526c7e17f5..9eeb6c594df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,44 +7,47 @@ settings: catalogs: default: '@ai-sdk/amazon-bedrock': - specifier: ^4.0.113 - version: 4.0.113 + specifier: ^5.0.31 + version: 5.0.31 '@ai-sdk/anthropic': - specifier: ^3.0.81 - version: 3.0.81 + specifier: ^4.0.20 + version: 4.0.20 '@ai-sdk/azure': - specifier: ^3.0.70 - version: 3.0.70 + specifier: ^4.0.21 + version: 4.0.21 '@ai-sdk/cohere': - specifier: ^3.0.36 - version: 3.0.36 + specifier: ^4.0.12 + version: 4.0.12 '@ai-sdk/deepseek': - specifier: ^2.0.35 - version: 2.0.35 + specifier: ^3.0.13 + version: 3.0.13 '@ai-sdk/gateway': - specifier: ^3.0.125 - version: 3.0.125 + specifier: ^4.0.28 + version: 4.0.28 '@ai-sdk/google': - specifier: ^3.0.80 - version: 3.0.80 + specifier: ^4.0.24 + version: 4.0.24 '@ai-sdk/groq': - specifier: ^3.0.39 - version: 3.0.39 + specifier: ^4.0.13 + version: 4.0.13 '@ai-sdk/mistral': - specifier: ^3.0.37 - version: 3.0.37 + specifier: ^4.0.14 + version: 4.0.14 '@ai-sdk/openai': - specifier: ^3.0.68 - version: 3.0.68 + specifier: ^4.0.20 + version: 4.0.20 '@ai-sdk/openai-compatible': - specifier: ^2.0.48 - version: 2.0.48 + specifier: ^3.0.14 + version: 3.0.14 + '@ai-sdk/otel': + specifier: ^1.0.37 + version: 1.0.37 '@ai-sdk/provider-utils': - specifier: ^4.0.27 - version: 4.0.27 + specifier: ^5.0.12 + version: 5.0.12 '@ai-sdk/xai': - specifier: ^3.0.93 - version: 3.0.93 + specifier: ^4.0.18 + version: 4.0.18 '@azure/storage-blob': specifier: ^12.32.0 version: 12.32.0 @@ -148,8 +151,8 @@ catalogs: specifier: 1.25.0 version: 1.25.0 '@openrouter/ai-sdk-provider': - specifier: ^2.8.0 - version: 2.9.0 + specifier: ^3.0.0 + version: 3.0.0 '@pinecone-database/pinecone': specifier: ^5.0.2 version: 5.1.2 @@ -301,8 +304,8 @@ catalogs: specifier: 0.26.0 version: 0.26.0 ai: - specifier: ^6.0.197 - version: 6.0.197 + specifier: ^7.0.37 + version: 7.0.37 basic-auth: specifier: 2.0.1 version: 2.0.1 @@ -691,7 +694,7 @@ overrides: '@lezer/common': ^1.2.0 '@mistralai/mistralai': ^1.10.0 '@n8n/typeorm>@sentry/node': ^10.55.0 - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0 '@types/node': ^20.17.50 chokidar: 4.0.3 @@ -715,7 +718,7 @@ overrides: tmp: 0.2.7 nodemailer: 8.0.10 validator: 13.15.26 - zod: 3.25.67 + zod: 3.25.76 js-yaml: 4.3.0 body-parser: 2.3.0 glob@10: 10.5.0 @@ -918,49 +921,52 @@ importers: dependencies: '@ai-sdk/amazon-bedrock': specifier: 'catalog:' - version: 4.0.113(zod@3.25.67) + version: 5.0.31(zod@3.25.76) '@ai-sdk/anthropic': specifier: 'catalog:' - version: 3.0.81(zod@3.25.67) + version: 4.0.20(zod@3.25.76) '@ai-sdk/azure': specifier: 'catalog:' - version: 3.0.70(zod@3.25.67) + version: 4.0.21(zod@3.25.76) '@ai-sdk/cohere': specifier: 'catalog:' - version: 3.0.36(zod@3.25.67) + version: 4.0.12(zod@3.25.76) '@ai-sdk/deepseek': specifier: 'catalog:' - version: 2.0.35(zod@3.25.67) + version: 3.0.13(zod@3.25.76) '@ai-sdk/gateway': specifier: 'catalog:' - version: 3.0.125(zod@3.25.67) + version: 4.0.28(zod@3.25.76) '@ai-sdk/google': specifier: 'catalog:' - version: 3.0.80(zod@3.25.67) + version: 4.0.24(zod@3.25.76) '@ai-sdk/groq': specifier: 'catalog:' - version: 3.0.39(zod@3.25.67) + version: 4.0.13(zod@3.25.76) '@ai-sdk/mistral': specifier: 'catalog:' - version: 3.0.37(zod@3.25.67) + version: 4.0.14(zod@3.25.76) '@ai-sdk/openai': specifier: 'catalog:' - version: 3.0.68(zod@3.25.67) + version: 4.0.20(zod@3.25.76) '@ai-sdk/openai-compatible': specifier: 'catalog:' - version: 2.0.48(zod@3.25.67) + version: 3.0.14(zod@3.25.76) + '@ai-sdk/otel': + specifier: 'catalog:' + version: 1.0.37(zod@3.25.76) '@ai-sdk/provider-utils': specifier: 'catalog:' - version: 4.0.27(zod@3.25.67) + version: 5.0.12(zod@3.25.76) '@ai-sdk/xai': specifier: 'catalog:' - version: 3.0.93(zod@3.25.67) + version: 4.0.18(zod@3.25.76) '@daytona/sdk': specifier: 'catalog:' version: 0.187.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@modelcontextprotocol/sdk': specifier: 'catalog:' - version: 1.26.0(zod@3.25.67) + version: 1.26.0(zod@3.25.76) '@n8n/ai-utilities': specifier: workspace:* version: link:../ai-utilities @@ -972,25 +978,25 @@ importers: version: link:../utils '@openrouter/ai-sdk-provider': specifier: 'catalog:' - version: 2.9.0(ai@6.0.197(zod@3.25.67))(zod@3.25.67) + version: 3.0.0(ai@7.0.37(zod@3.25.76))(zod@3.25.76) '@opentelemetry/exporter-trace-otlp-http': specifier: '>=0.50.0' - version: 0.217.0(@opentelemetry/api@1.9.0) + version: 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': specifier: '>=1.0.0' - version: 2.7.1(@opentelemetry/api@1.9.0) + version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-node': specifier: '>=1.0.0' - version: 2.7.1(@opentelemetry/api@1.9.0) + version: 2.7.1(@opentelemetry/api@1.9.1) ai: specifier: 'catalog:' - version: 6.0.197(zod@3.25.67) + version: 7.0.37(zod@3.25.76) ajv: specifier: 8.18.0 version: 8.18.0 langsmith: specifier: 0.6.0 - version: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) undici: specifier: catalog:undici-v7 version: 7.28.0 @@ -999,7 +1005,7 @@ importers: version: 2.8.3 zod-to-json-schema: specifier: 'catalog:' - version: 3.23.3(zod@3.25.67) + version: 3.23.3(zod@3.25.76) devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -1008,11 +1014,11 @@ importers: specifier: workspace:* version: link:../vitest-config '@opentelemetry/api': - specifier: 1.9.0 - version: 1.9.0 + specifier: 1.9.1 + version: 1.9.1 '@opentelemetry/context-async-hooks': specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.0) + version: 2.7.1(@opentelemetry/api@1.9.1) '@pinecone-database/pinecone': specifier: 'catalog:' version: 5.1.2 @@ -1054,13 +1060,13 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages/@n8n/ai-node-sdk: dependencies: @@ -1079,25 +1085,25 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/ai-utilities: dependencies: '@langchain/classic': specifier: 1.0.27 - version: 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/community': specifier: 'catalog:' - version: 1.1.27(cad6e30ee219fb5df7e5cc892428da20) + version: 1.1.27(b1822a993b35582954040a82820e4d06) '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/openai': specifier: 'catalog:' - version: 1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/textsplitters': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@n8n/api-types': specifier: workspace:* version: link:../api-types @@ -1121,7 +1127,7 @@ importers: version: 1.0.21 langchain: specifier: 'catalog:' - version: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) lodash: specifier: 4.18.1 version: 4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b) @@ -1138,11 +1144,11 @@ importers: specifier: catalog:undici-v6 version: 6.27.0 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 zod-to-json-schema: specifier: 'catalog:' - version: 3.23.3(zod@3.25.67) + version: 3.23.3(zod@3.25.76) devDependencies: '@n8n/vitest-config': specifier: workspace:* @@ -1182,7 +1188,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -1191,19 +1197,19 @@ importers: dependencies: '@langchain/anthropic': specifier: 'catalog:' - version: 1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/langgraph': specifier: 'catalog:' - version: 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.25.2(zod@3.25.67))(zod@3.25.67) + version: 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.25.2(zod@3.25.76))(zod@3.25.76) '@langchain/langgraph-checkpoint': specifier: 'catalog:' - version: 1.0.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.0.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/openai': specifier: 'catalog:' - version: 1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@mozilla/readability': specifier: 'catalog:' version: 0.6.0 @@ -1245,10 +1251,10 @@ importers: version: 23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) langchain: specifier: 'catalog:' - version: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) langsmith: specifier: 0.6.0 - version: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) lodash: specifier: 4.18.1 version: 4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b) @@ -1271,8 +1277,8 @@ importers: specifier: 'catalog:' version: 11.1.1 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -1321,7 +1327,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -1364,13 +1370,13 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages/@n8n/backend-common: dependencies: @@ -1437,13 +1443,13 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages/@n8n/backend-network: dependencies: @@ -1496,8 +1502,8 @@ importers: specifier: catalog:undici-v7 version: 7.28.0 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -1522,7 +1528,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -1561,7 +1567,7 @@ importers: version: 11.1.1 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -1640,8 +1646,8 @@ importers: specifier: 'catalog:' version: 0.2.2 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/eslint-config': specifier: workspace:* @@ -1669,7 +1675,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -1694,7 +1700,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/cli: dependencies: @@ -1716,7 +1722,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/client-oauth2: dependencies: @@ -1753,7 +1759,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -1787,7 +1793,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/codemirror-lang-html: dependencies: @@ -1845,7 +1851,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/codemirror-lang-sql: dependencies: @@ -1885,7 +1891,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/computer-use: dependencies: @@ -1900,7 +1906,7 @@ importers: version: 0.6.21 '@modelcontextprotocol/sdk': specifier: 'catalog:' - version: 1.26.0(zod@3.25.67) + version: 1.26.0(zod@3.25.76) '@n8n/mcp-browser': specifier: workspace:* version: link:../mcp-browser @@ -1929,11 +1935,11 @@ importers: specifier: 21.1.1 version: 21.1.1 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 zod-to-json-schema: specifier: 'catalog:' - version: 3.23.3(zod@3.25.67) + version: 3.23.3(zod@3.25.76) devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -1961,7 +1967,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/config: dependencies: @@ -1975,8 +1981,8 @@ importers: specifier: 'catalog:' version: 0.2.2 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -1995,7 +2001,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -2029,7 +2035,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-websocket-mock: specifier: ^0.5.0 version: 0.5.0(vitest@4.1.9) @@ -2104,8 +2110,8 @@ importers: specifier: 'catalog:' version: 1.0.15 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -2142,7 +2148,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(@typescript/typescript6@6.0.2)(vitest@4.1.9) @@ -2185,10 +2191,10 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages/@n8n/di: dependencies: @@ -2210,7 +2216,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -2239,8 +2245,8 @@ importers: specifier: 'catalog:' version: 11.1.1 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -2271,7 +2277,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/errors: dependencies: @@ -2284,7 +2290,7 @@ importers: version: link:../typescript-config '@sentry/node': specifier: catalog:sentry - version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) typescript: specifier: catalog:typescript version: 7.0.2 @@ -2366,7 +2372,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/eslint-plugin-community-nodes: dependencies: @@ -2418,7 +2424,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/expression-runtime: dependencies: @@ -2456,8 +2462,8 @@ importers: specifier: 2.3.5 version: 2.3.5 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -2485,13 +2491,13 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/extension-sdk: dependencies: zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -2534,7 +2540,7 @@ importers: version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(@typescript/typescript6@6.0.2) zod-to-json-schema: specifier: 'catalog:' - version: 3.23.3(zod@3.25.67) + version: 3.23.3(zod@3.25.76) packages/@n8n/imap: dependencies: @@ -2574,7 +2580,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -2592,7 +2598,7 @@ importers: version: 1.0.64 '@langchain/anthropic': specifier: 'catalog:' - version: 1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@mozilla/readability': specifier: 'catalog:' version: 0.6.0 @@ -2615,14 +2621,14 @@ importers: specifier: workspace:* version: link:../workflow-sdk '@opentelemetry/api': - specifier: 1.9.0 - version: 1.9.0 + specifier: 1.9.1 + version: 1.9.1 '@thednp/dommatrix': specifier: ^2.0.12 version: 2.0.12 ai: specifier: 'catalog:' - version: 6.0.197(zod@3.25.67) + version: 7.0.37(zod@3.25.76) csv-parse: specifier: 'catalog:' version: 6.2.1 @@ -2634,7 +2640,7 @@ importers: version: 3.4.2 langsmith: specifier: 0.6.0 - version: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) linkedom: specifier: ^0.18.9 version: 0.18.12 @@ -2669,18 +2675,18 @@ importers: specifier: catalog:undici-v7 version: 7.28.0 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 zod-from-json-schema-v3: specifier: npm:zod-from-json-schema@^0.0.5 version: zod-from-json-schema@0.0.5 devDependencies: '@ai-sdk/anthropic': specifier: 'catalog:' - version: 3.0.81(zod@3.25.67) + version: 4.0.20(zod@3.25.76) '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@n8n/typescript-config': specifier: workspace:* version: link:../typescript-config @@ -2710,7 +2716,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -2734,13 +2740,13 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages/@n8n/local-gateway: dependencies: @@ -2786,13 +2792,13 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/mcp-apps: dependencies: '@modelcontextprotocol/ext-apps': specifier: 'catalog:' - version: 1.3.2(@modelcontextprotocol/sdk@1.26.0(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod@3.25.67) + version: 1.3.2(@modelcontextprotocol/sdk@1.26.0(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod@3.25.76) '@n8n/design-system': specifier: workspace:* version: link:../../frontend/@n8n/design-system @@ -2806,15 +2812,15 @@ importers: specifier: catalog:frontend version: 11.4.5(vue@3.5.26(@typescript/typescript6@6.0.2)) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@iconify-json/lucide': specifier: 'catalog:' version: 1.2.114 '@modelcontextprotocol/sdk': specifier: 'catalog:' - version: 1.26.0(zod@3.25.67) + version: 1.26.0(zod@3.25.76) '@n8n/eslint-config': specifier: workspace:* version: link:../eslint-config @@ -2859,7 +2865,7 @@ importers: version: 5.1.0(vue@3.5.26(@typescript/typescript6@6.0.2)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(@typescript/typescript6@6.0.2) @@ -2871,7 +2877,7 @@ importers: version: 1.0.64 '@modelcontextprotocol/sdk': specifier: 'catalog:' - version: 1.26.0(zod@3.25.67) + version: 1.26.0(zod@3.25.76) '@mozilla/readability': specifier: 'catalog:' version: 0.6.0 @@ -2903,8 +2909,8 @@ importers: specifier: 21.1.1 version: 21.1.1 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -2935,7 +2941,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/mcp-browser-extension: dependencies: @@ -2981,7 +2987,7 @@ importers: version: 5.1.0(vue@3.5.26(@typescript/typescript6@6.0.2)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/node-cli: dependencies: @@ -3060,7 +3066,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(@typescript/typescript6@6.0.2)(vitest@4.1.9) @@ -3091,7 +3097,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/nodes-langchain: dependencies: @@ -3112,7 +3118,7 @@ importers: version: 12.1.0 '@getzep/zep-cloud': specifier: 1.0.6 - version: 1.0.6(f7636f1681b49d771ee2a293a491ade5) + version: 1.0.6(149dddd2ec2380c7920fcf767cbe6f0b) '@getzep/zep-js': specifier: 0.9.0 version: 0.9.0 @@ -3121,7 +3127,7 @@ importers: version: 5.3.0(encoding@0.1.13) '@google/genai': specifier: 1.19.0 - version: 1.19.0(@modelcontextprotocol/sdk@1.26.0(zod@3.25.67))(bufferutil@4.0.9)(utf-8-validate@5.0.10) + version: 1.19.0(@modelcontextprotocol/sdk@1.26.0(zod@3.25.76))(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@google/generative-ai': specifier: 0.24.0 version: 0.24.0 @@ -3130,94 +3136,94 @@ importers: version: 4.0.5 '@langchain/anthropic': specifier: 'catalog:' - version: 1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/aws': specifier: 1.0.3 - version: 1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/classic': specifier: 1.0.27 - version: 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/cohere': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13) '@langchain/community': specifier: 'catalog:' - version: 1.1.27(5a58ffcca9c8ab8d1fbf31dd7fb3c90d) + version: 1.1.27(58b3539f609126398d3a2334339488d1) '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/google-common': specifier: 2.1.24 - version: 2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/google-genai': specifier: 2.1.24 - version: 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/google-vertexai': specifier: 2.1.24 - version: 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/groq': specifier: 1.0.2 - version: 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13) + version: 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13) '@langchain/langgraph': specifier: 'catalog:' - version: 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67) + version: 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76) '@langchain/langgraph-checkpoint': specifier: 'catalog:' - version: 1.0.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.0.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/mistralai': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/mongodb': specifier: 1.0.1 - version: 1.0.1(@aws-sdk/credential-providers@3.808.0)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(gcp-metadata@5.3.0)(socks@2.8.3) + version: 1.0.1(@aws-sdk/credential-providers@3.808.0)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(gcp-metadata@5.3.0)(socks@2.8.3) '@langchain/ollama': specifier: 1.2.6 - version: 1.2.6(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.2.6(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/openai': specifier: 'catalog:' - version: 1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/pinecone': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@pinecone-database/pinecone@5.1.2) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@pinecone-database/pinecone@5.1.2) '@langchain/qdrant': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@typescript/typescript6@6.0.2) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@typescript/typescript6@6.0.2) '@langchain/redis': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/textsplitters': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) '@langchain/weaviate': specifier: 1.0.1 - version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13) + version: 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13) '@microsoft/agents-a365-notifications': specifier: 1.0.0 - version: 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + version: 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) '@microsoft/agents-a365-observability': specifier: 1.0.0 version: 1.0.0(@opentelemetry/api-logs@0.217.0) '@microsoft/agents-a365-runtime': specifier: 1.0.0 - version: 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + version: 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) '@microsoft/agents-a365-tooling': specifier: 1.0.0 - version: 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)(zod@3.25.67) + version: 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)(zod@3.25.76) '@microsoft/agents-a365-tooling-extensions-langchain': specifier: 1.0.0 - version: 1.0.0(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67))(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) + version: 1.0.0(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76))(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) '@microsoft/agents-activity': specifier: 1.6.1 version: 1.6.1 '@microsoft/agents-hosting': specifier: 1.6.1 - version: 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + version: 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) '@mistralai/mistralai': specifier: ^1.10.0 version: 1.10.0 '@modelcontextprotocol/sdk': specifier: 'catalog:' - version: 1.26.0(zod@3.25.67) + version: 1.26.0(zod@3.25.76) '@mozilla/readability': specifier: 'catalog:' version: 0.6.0 @@ -3241,7 +3247,7 @@ importers: version: link:../json-schema-to-zod '@n8n/typeorm': specifier: 0.3.20-16 - version: 0.3.20-16(@sentry/node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)))(mysql2@3.17.0)(pg-native@3.8.0)(pg-query-stream@4.16.0(pg@8.21.0(pg-native@3.8.0)))(pg@8.21.0(pg-native@3.8.0))(sqlite3@5.1.7) + version: 0.3.20-16(@sentry/node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)))(mysql2@3.17.0)(pg-native@3.8.0)(pg-query-stream@4.16.0(pg@8.21.0(pg-native@3.8.0)))(pg@8.21.0(pg-native@3.8.0))(sqlite3@5.1.7) '@n8n/typescript-config': specifier: workspace:* version: link:../typescript-config @@ -3250,7 +3256,7 @@ importers: version: link:../utils '@oracle/langchain-oracledb': specifier: 0.2.0 - version: 0.2.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/textsplitters@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))))(oracledb@6.10.0) + version: 0.2.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/textsplitters@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))))(oracledb@6.10.0) '@pinecone-database/pinecone': specifier: 'catalog:' version: 5.1.2 @@ -3316,7 +3322,7 @@ importers: version: 23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) langchain: specifier: 'catalog:' - version: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) lodash: specifier: 4.18.1 version: 4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b) @@ -3343,7 +3349,7 @@ importers: version: link:../../workflow openai: specifier: 'catalog:' - version: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) + version: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) oracledb: specifier: 'catalog:' version: 6.10.0 @@ -3375,11 +3381,11 @@ importers: specifier: 3.9.0 version: 3.9.0(encoding@0.1.13) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 zod-to-json-schema: specifier: 'catalog:' - version: 3.23.3(zod@3.25.67) + version: 3.23.3(zod@3.25.76) devDependencies: '@n8n/eslint-plugin-community-nodes': specifier: workspace:* @@ -3443,7 +3449,7 @@ importers: version: '@typescript/typescript6@6.0.2' vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(@typescript/typescript6@6.0.2)(vitest@4.1.9) @@ -3451,8 +3457,8 @@ importers: packages/@n8n/permissions: dependencies: zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -3471,7 +3477,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -3511,7 +3517,7 @@ importers: version: link:../vitest-config vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/scheduler: dependencies: @@ -3557,7 +3563,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -3600,7 +3606,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -3608,8 +3614,8 @@ importers: packages/@n8n/syslog-client: dependencies: zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/typescript-config': specifier: workspace:* @@ -3631,7 +3637,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -3655,7 +3661,7 @@ importers: version: 10.55.0 '@sentry/node': specifier: catalog:sentry - version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) acorn: specifier: 8.14.0 version: 8.14.0 @@ -3707,7 +3713,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -3715,8 +3721,8 @@ importers: packages/@n8n/telemetry: dependencies: zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/eslint-config': specifier: workspace:* @@ -3744,7 +3750,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/tournament: dependencies: @@ -3775,7 +3781,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -3947,7 +3953,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/vitest-config: devDependencies: @@ -3962,7 +3968,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/@n8n/workflow-sdk: dependencies: @@ -3985,8 +3991,8 @@ importers: specifier: 'catalog:' version: 11.1.1 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/eslint-plugin-community-nodes': specifier: workspace:* @@ -4017,7 +4023,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/cli: dependencies: @@ -4026,7 +4032,7 @@ importers: version: 1.4.2 '@ai-sdk/anthropic': specifier: 'catalog:' - version: 3.0.81(zod@3.25.67) + version: 4.0.20(zod@3.25.76) '@apidevtools/json-schema-ref-parser': specifier: 12.0.2 version: 12.0.2 @@ -4062,10 +4068,10 @@ importers: version: 1.0.64 '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@modelcontextprotocol/sdk': specifier: 'catalog:' - version: 1.26.0(zod@3.25.67) + version: 1.26.0(zod@3.25.76) '@mozilla/readability': specifier: 'catalog:' version: 0.6.0 @@ -4163,29 +4169,29 @@ importers: specifier: 3.0.0 version: 3.0.0 '@opentelemetry/api': - specifier: 1.9.0 - version: 1.9.0 + specifier: 1.9.1 + version: 1.9.1 '@opentelemetry/core': specifier: 2.8.0 - version: 2.8.0(@opentelemetry/api@1.9.0) + version: 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-trace-otlp-proto': specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.0) + version: 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation': specifier: ^0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.0) + version: 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/resources': specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.0) + version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-node': specifier: 0.217.0 - version: 0.217.0(@opentelemetry/api@1.9.0) + version: 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.0) + version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-node': specifier: ^2.7.1 - version: 2.7.1(@opentelemetry/api@1.9.0) + version: 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': specifier: ^1.40.0 version: 1.40.0 @@ -4203,13 +4209,13 @@ importers: version: 3.0.5 '@sentry/node': specifier: catalog:sentry - version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) '@supabase/supabase-js': specifier: 'catalog:' version: 2.50.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) ai: specifier: 'catalog:' - version: 6.0.197(zod@3.25.67) + version: 7.0.37(zod@3.25.76) aws4: specifier: 1.11.0 version: 1.11.0 @@ -4326,7 +4332,7 @@ importers: version: 9.0.3 langsmith: specifier: 0.6.0 - version: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) ldapts: specifier: 'catalog:' version: 8.1.7 @@ -4478,11 +4484,11 @@ importers: specifier: 21.1.1 version: 21.1.1 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 zod-to-json-schema: specifier: 'catalog:' - version: 3.23.3(zod@3.25.67) + version: 3.23.3(zod@3.25.76) devDependencies: '@faker-js/faker': specifier: ^8.0.2 @@ -4633,7 +4639,7 @@ importers: version: 14.0.14 openai: specifier: 'catalog:' - version: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) + version: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) openapi-types: specifier: ^12.1.3 version: 12.1.3 @@ -4657,7 +4663,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(@typescript/typescript6@6.0.2)(vitest@4.1.9) @@ -4666,7 +4672,7 @@ importers: dependencies: '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@n8n/backend-common': specifier: workspace:* version: link:../@n8n/backend-common @@ -4705,13 +4711,13 @@ importers: version: 10.55.0 '@sentry/node': specifier: catalog:sentry - version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) '@sentry/node-native': specifier: catalog:sentry - version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) '@sentry/profiling-node': specifier: catalog:sentry - version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) callsites: specifier: 'catalog:' version: 3.1.0 @@ -4841,13 +4847,13 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages/extensions/insights: dependencies: @@ -4893,7 +4899,7 @@ importers: version: 4.5.4(@types/node@20.19.41)(rollup@4.52.4)(typescript@6.0.2)(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -4987,7 +4993,7 @@ importers: version: 4.5.4(@types/node@20.19.41)(rollup@4.52.4)(typescript@6.0.2)(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -5075,7 +5081,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -5292,7 +5298,7 @@ importers: version: 5.1.0(vue@3.5.26(typescript@6.0.2)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@6.0.2)(vitest@4.1.9) @@ -5325,7 +5331,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -5365,7 +5371,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -5405,7 +5411,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -5463,7 +5469,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -5527,7 +5533,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vue-tsc: specifier: ^2.2.8 version: 2.2.8(patch_hash=e2aee939ccac8a57fe449bfd92bedd8117841579526217bc39aca26c6b8c317f)(typescript@6.0.2) @@ -5609,7 +5615,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@6.0.2)(vitest@4.1.9) @@ -5730,7 +5736,7 @@ importers: version: 5.1.0(vue@3.5.26(typescript@6.0.2)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/frontend/editor-ui: dependencies: @@ -6050,8 +6056,8 @@ importers: specifier: ^0.0.35 version: 0.0.35(patch_hash=bbd573be7e456224f369aafb4d66374523c4847964a4655a6a1654fd40e8b0f8)(vue@3.5.26(typescript@6.0.2)) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@codecov/vite-plugin': specifier: ^1.9.1 @@ -6193,7 +6199,7 @@ importers: version: 5.1.0(vue@3.5.26(typescript@6.0.2)) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@6.0.2)(vitest@4.1.9) @@ -6245,7 +6251,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/nodes-base: dependencies: @@ -6263,7 +6269,7 @@ importers: version: 3.8.0 '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@mozilla/readability': specifier: 'catalog:' version: 0.6.0 @@ -6503,7 +6509,7 @@ importers: version: 3.0.3 ts-ics: specifier: 1.2.2 - version: 1.2.2(date-fns@2.30.0)(lodash@4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b))(zod@3.25.67) + version: 1.2.2(date-fns@2.30.0)(lodash@4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b))(zod@3.25.76) undici: specifier: catalog:undici-v6 version: 6.27.0 @@ -6520,8 +6526,8 @@ importers: specifier: 3.1.0 version: 3.1.0 zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 devDependencies: '@n8n/client-oauth2': specifier: workspace:* @@ -6648,7 +6654,7 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) @@ -6685,7 +6691,7 @@ importers: version: 3.23.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/testing/containers: dependencies: @@ -6762,7 +6768,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/testing/performance: devDependencies: @@ -6780,13 +6786,13 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/testing/playwright: devDependencies: '@anthropic-ai/sdk': specifier: 0.91.1 - version: 0.91.1(zod@3.25.67) + version: 0.91.1(zod@3.25.76) '@currents/playwright': specifier: catalog:e2e version: 2.0.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) @@ -6855,7 +6861,7 @@ importers: version: 9.0.3 langsmith: specifier: 0.6.0 - version: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) mockserver-client: specifier: ^5.15.0 version: 5.15.0 @@ -6891,10 +6897,10 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages/testing/rules-engine: dependencies: @@ -6916,7 +6922,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/testing/test-impact: dependencies: @@ -6938,7 +6944,7 @@ importers: version: 7.0.2 vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) packages/workflow: dependencies: @@ -6962,7 +6968,7 @@ importers: version: 10.55.0 '@sentry/node': specifier: catalog:sentry - version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + version: 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) ast-types: specifier: 0.16.1 version: 0.16.1 @@ -7023,7 +7029,7 @@ importers: devDependencies: '@langchain/core': specifier: 'catalog:' - version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + version: 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@n8n/config': specifier: workspace:* version: link:../@n8n/config @@ -7080,13 +7086,13 @@ importers: version: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + version: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended: specifier: 'catalog:' version: 3.1.0(typescript@7.0.2)(vitest@4.1.9) zod: - specifier: 3.25.67 - version: 3.25.67 + specifier: 3.25.76 + version: 3.25.76 packages: @@ -7122,87 +7128,91 @@ packages: '@adobe/css-tools@4.4.0': resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} - '@ai-sdk/amazon-bedrock@4.0.113': - resolution: {integrity: sha512-qoeF2ghkYqHY4u68rasZopYsRuGnRwgqSfe6rhC/jM6W73Z7TU5v9QcfDYKt9dgp19YHY9EqiX3SXVC+uPGkRQ==} - engines: {node: '>=18'} + '@ai-sdk/amazon-bedrock@5.0.31': + resolution: {integrity: sha512-RI/kvkQR//m8LXSl1bfJkx064gnV78FU3ZSYoQBDfu9zDTrSQtQNGw1BHqveWGoKVnRuhhWfXeaLsy9qwJ5Vlw==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/anthropic@3.0.81': - resolution: {integrity: sha512-B1JDd9Ugq9R5AgIaW3674lhGCMMYJcPUxnrZh8fzbGojgg4QvHFRv6eZahGQAUsmGHbcf74G9bdSBDLWQGY2GA==} - engines: {node: '>=18'} + '@ai-sdk/anthropic@4.0.20': + resolution: {integrity: sha512-5QdZt6AkWIKIKuwRSjAQIBhN0JSSvthaIcAJKTHP7HPCvWMcNJFF6rz0uKfUlpI3h7nVbY+1Cy5dNl5zjzJ6qA==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/azure@3.0.70': - resolution: {integrity: sha512-FGwFAt1k/ZU90DydRqpAg6eFjmFxhaBh2rlI3kAIsCrbM6bbQAdYhmQJ3hlR0289uSDzsW0jRk7lDYCW1kSXRQ==} - engines: {node: '>=18'} + '@ai-sdk/azure@4.0.21': + resolution: {integrity: sha512-2DbEPXIdj6dvc30tPE7Rz+t3ELbjOvIDawAHUvLIipc15sY7+uvS203jh1cjiPUyZ+NAIa3B+HWEpHrjlAoyKg==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/cohere@3.0.36': - resolution: {integrity: sha512-a3xUVhBYP5FuThYA3Q7NjuEyl/ObBhx9z0zFDRebW0UqEoExS3zJwtE9OB+gHc/uvwx93hNg5ZITAFAvqG3GMQ==} - engines: {node: '>=18'} + '@ai-sdk/cohere@4.0.12': + resolution: {integrity: sha512-b8FI0+uh6UsRbMjXASShNQbZRW+RBENA5EKR6MZFy4x3G2ibDxyA5v9YA23eYU+JD4b6C1LcIXY1X5vsnj8Fpg==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/deepseek@2.0.35': - resolution: {integrity: sha512-9DhYurbAvcurOEGN6u2myYDybrrzGfcrkG8hwmFjwTrePW6KCMggm0YxP7e8RkLYcQKqCEMgFlyEB4BM6EmiKg==} - engines: {node: '>=18'} + '@ai-sdk/deepseek@3.0.13': + resolution: {integrity: sha512-sX05lJncM1xvZ8PBi3Lq2ixl9RScMcX6w9YbmkCE5Fp28g9t+tVNATEFy8aVWEInH7yxvdEL4M15/9Uq63ECpg==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/gateway@3.0.125': - resolution: {integrity: sha512-tocl7cUDoTpmhZqeW8XVKMMznZQwwQAEunF0VyNKmf64qt8NbMIAEiet/vRMzh7Jr9WcFeb6EZjmhLTP4Qx2Og==} - engines: {node: '>=18'} + '@ai-sdk/gateway@4.0.28': + resolution: {integrity: sha512-ee9TsNO3mkgHDWmTmJ8Fvltr6PFh52zhrV2+FaKJY3F3iu7kWZi5tCRJCR1HVhhlpj6lHniUb28nLQdPHkA/1Q==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/google@3.0.80': - resolution: {integrity: sha512-5ORbm/yFUPO0MEvZsxBMN0cdKw2+lwU/wVn5KN3KF8Dmk1LughuDuUohMh/7iU/XFTiyB0OvmTW/tdV/J7O9zg==} - engines: {node: '>=18'} + '@ai-sdk/google@4.0.24': + resolution: {integrity: sha512-W+kTGb/RRe2SEYwbKcKYM3N2EQryS2Iqjgou6wPsyP/2BwBU7Q23CF6/YWE8k7xtseDLcSY59jsR2MDpY4azRg==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/groq@3.0.39': - resolution: {integrity: sha512-BZAr6DjCbzWQ0Qn1/TSsHo/bmCt4JaAMb4A7HCSUZBQCAcOjne/03D0sVjHnQhUC3TpwcmYiv7tHAviK7BluRw==} - engines: {node: '>=18'} + '@ai-sdk/groq@4.0.13': + resolution: {integrity: sha512-bEUGDm3v3X/LvrpMioPg8NhaFBmwt+MjFa0cFdQVOJOT6LBLlGEYwr70g8B2/ttjZj4fW2qIhM/m8DDoYpXdrg==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/mistral@3.0.37': - resolution: {integrity: sha512-KkdaMjs4C2y+vrZWJE990E3ZxBFiOTHQ94ZlquuIttpphcqJTMxNoIpnKT/4UzMVWXL0BUEE2vs+1UEVXkN8Kg==} - engines: {node: '>=18'} + '@ai-sdk/mistral@4.0.14': + resolution: {integrity: sha512-7BcH5pV2cUdApvOY6H1Z0Da9Go08KjLI1jw8IYFRr8XBlvDAt3pBhaccT8vB1VFzHFlSpEX2KSu4akr8Uvm3uA==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/openai-compatible@2.0.48': - resolution: {integrity: sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q==} - engines: {node: '>=18'} + '@ai-sdk/openai-compatible@3.0.14': + resolution: {integrity: sha512-nUl7dBHUDTqcnuWGnEbKMwDDqL94BxRfibLwYv8uWp+kZn8Zqdxy7tUBt4sv2hpHQi+S2P6bUyqW18xmbykErw==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/openai@3.0.68': - resolution: {integrity: sha512-FCs/DPr4M95UyZ/ABHJmTmCEYRCka/4J0Bna0nsd78QCdGIS0X/zhn+fVzB7mZJo7464uOWYUjROx9PGNGOb0w==} - engines: {node: '>=18'} + '@ai-sdk/openai@4.0.20': + resolution: {integrity: sha512-/Hu+btLIO2zuYqCp//vBBAGdM/BeN4gds+NWrlv7Y9WrcTXwnEEwh6P3QZIWh2Tt1I1lswuDIpAJ4t/c/rws3Q==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/provider-utils@4.0.27': - resolution: {integrity: sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw==} - engines: {node: '>=18'} + '@ai-sdk/otel@1.0.37': + resolution: {integrity: sha512-qaXUeOTZfbugaYKcqgqho79YOdvLWFlBQt984n5fOozChfgkybJJHotkqsKy3aJGU5GcgJuMM+oamb9JtPQnwA==} + engines: {node: '>=22'} + + '@ai-sdk/provider-utils@5.0.12': + resolution: {integrity: sha512-bbhlOgHeYwrIGheLkM6fhS8hVger8uFPmcOLg+kxc9EFh7y30XYorWhthlYAgpadO3SJhFZrIcEknN7qEqEVvA==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/provider@3.0.10': - resolution: {integrity: sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw==} - engines: {node: '>=18'} + '@ai-sdk/provider@4.0.3': + resolution: {integrity: sha512-e0CpNWJUY7OxAFAnCZkw+ri9QOHWwTs1tXP42782KFGCU07qt8NiXCrCVowyCB5dP2r5/Uls+g2oPd8kOJn9dw==} + engines: {node: '>=22'} - '@ai-sdk/xai@3.0.93': - resolution: {integrity: sha512-HxazLIcSTgI0UQoq6ua0rcSR8+eXuNy0Qh4jkCY9EAWedYn6CQ0XD/j34U4/JYtO738xJdY+tE95okdqWqXkHA==} - engines: {node: '>=18'} + '@ai-sdk/xai@4.0.18': + resolution: {integrity: sha512-2hrfujGCzNj89+BkREXU3siHlutdWcDMMkKc/+RITRk8T8NPIuAZ4yF8g1/2W+Xessbqz+4PQP7al9HPTfbimg==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} @@ -7229,7 +7239,7 @@ packages: resolution: {integrity: sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==} hasBin: true peerDependencies: - zod: 3.25.67 + zod: 3.25.76 peerDependenciesMeta: zod: optional: true @@ -8286,7 +8296,7 @@ packages: deepmerge: ^4.3.1 dotenv: ^16.4.5 openai: ^4.62.1 - zod: 3.25.67 + zod: 3.25.76 '@bufbuild/protobuf@2.11.0': resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} @@ -9657,7 +9667,7 @@ packages: engines: {node: '>=18'} peerDependencies: '@langchain/core': ^1.0.1 - zod: 3.25.67 + zod: 3.25.76 zod-to-json-schema: ^3.x peerDependenciesMeta: zod-to-json-schema: @@ -9668,7 +9678,7 @@ packages: engines: {node: '>=18'} peerDependencies: '@langchain/core': ^1.1.48 - zod: 3.25.67 + zod: 3.25.76 '@langchain/mcp-adapters@1.1.3': resolution: {integrity: sha512-OPHIQNkTUJjnRj1pr+cp2nguMBZeF3Q1pVT1hCbgU7BrHgV7lov99wbU8po8Cm4zZzmeRtVO/T9X1SrDD1ogtQ==} @@ -9897,7 +9907,7 @@ packages: resolution: {integrity: sha512-ugbvUyP0PupHcFXTYy3+mXE2QfW13mRY4hrY7HyS3ZWWL20L7RUyg27nXqL4ndaqtuoEt+MPkluEfabG3iJVSg==} engines: {node: '>=20.0.0'} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': '>=0.214.0 <1' peerDependenciesMeta: '@opentelemetry/api': @@ -9937,7 +9947,7 @@ packages: '@modelcontextprotocol/sdk': ^1.24.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - zod: 3.25.67 + zod: 3.25.76 peerDependenciesMeta: react: optional: true @@ -9949,7 +9959,7 @@ packages: engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 - zod: 3.25.67 + zod: 3.25.76 peerDependenciesMeta: '@cfworker/json-schema': optional: true @@ -10319,12 +10329,12 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@openrouter/ai-sdk-provider@2.9.0': - resolution: {integrity: sha512-Seva+NCa0WUQnJIUE5GzHsUv1WTIeyqwz0ELl2VtS6NP+eF+77yCXGFVOMbvoCM7QMjlnhv7931e89R+8pJdcQ==} - engines: {node: '>=18'} + '@openrouter/ai-sdk-provider@3.0.0': + resolution: {integrity: sha512-m9XTSWoODH2RM5OsZpaGiN7QRR8cdP5paBWq699Tu3JVmGPBKT8xF8XwV0ZBVVsjikD/JgWfak4VSsTR4wAVbg==} + engines: {node: '>=22'} peerDependencies: - ai: ^6.0.0 - zod: 3.25.67 + ai: ^7.0.0 + zod: 3.25.76 '@opentelemetry/api-logs@0.213.0': resolution: {integrity: sha512-zRM5/Qj6G84Ej3F1yt33xBVY/3tnMxtL1fiDIxYbDWYaZ/eudVw3/PBiZ8G7JwUxXxjW8gU4g6LnOyfGKYHYgw==} @@ -10338,225 +10348,225 @@ packages: resolution: {integrity: sha512-Cdq0jW2lknrNfrAm92MyEAvpe2cRsKjdnQLHUL6xRA4IVUnsWx6P65E7NcUO0Y+L4w1Aee5iV8FvjSwd+lrs9A==} engines: {node: '>=8.0.0'} - '@opentelemetry/api@1.9.0': - resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + '@opentelemetry/api@1.9.1': + resolution: {integrity: sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==} engines: {node: '>=8.0.0'} '@opentelemetry/configuration@0.217.0': resolution: {integrity: sha512-xCtrYOhBqdy6ZOMfe0Oa73ZKF+2LMhoOv4L5vmwAHVvOXUg+V3fvKuEIr9ZyD0Ow+vxllEjWO6PV1wd0DOtyvw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/context-async-hooks@2.7.1': resolution: {integrity: sha512-OPFBYuXEn1E4ja3Y6eeA7O+ZnLBNcXTV5Cgsn1VaqBZ6hC5FnpZPLBNme1LJY8ZtF4aOujPKFoeWN4ik487KuQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/core@2.8.0': resolution: {integrity: sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-logs-otlp-grpc@0.217.0': resolution: {integrity: sha512-vC5S0Dc+noxD86CVtNu1+awCHPA5Kewi1Sg23ps+9lh4YifwsKXh3pe4XTNEKtUJiAcjpJ5dqStGakLbrSE+YQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-logs-otlp-http@0.217.0': resolution: {integrity: sha512-KfLAdt1uilVE+3FxbgVnp2ZrzqbIawzcesnRoi+Kh9ckB5Ld5D8btUgoBvwTbdmuNx1j6b132Wsh72azq+pPNQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-logs-otlp-proto@0.217.0': resolution: {integrity: sha512-Se0GG/ZO24mQTlQj7zprR4pNI0nKe4lPDPBsuJmi6508b9TlZEuUd3EfyuHk6oJxzL7fGyDFYAbxNigQvRP2ZQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0': resolution: {integrity: sha512-0GpJKnCoVaVA1rKBMVPHziznfOQlXgH72S9ktjBAF1AnAVPzX7vVEBGrhwiSxxHDAiefXk+J8znApsMb/K6Z3w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-metrics-otlp-http@0.217.0': resolution: {integrity: sha512-1zkMzzhiNJdVmLxuwkltqWGw4fOOam47bqRxmuQNjyKJe/9NmY5cIrZ4kiQV7sVGxoOgT0ZvGUfLcjvtpC/b9Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-metrics-otlp-proto@0.217.0': resolution: {integrity: sha512-nfxt/KxVGFkjkO/M+58y1ugHu/dwPtxG4eYq0KApcQ7xk5CHzhdn+IuLZfDSvNDrJ3Uy5q++Fj/wbK7i8yryfQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-prometheus@0.217.0': resolution: {integrity: sha512-U9MCXxJu0sBCh5aEkylYRR4xVIL8D1CW6dGwvYXbfFr0qveSorfD0XJchCAWoW6QfAAIcY/yxjf4Dj8OgkHBPw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-trace-otlp-grpc@0.217.0': resolution: {integrity: sha512-fPZs2fw7veLH3pEKu8vSepUa2fQpAE2P7al6qU10aH9GrEJJ8YaPgsd5xON7by5rbcEVS71FOU2aWyK6nzB7VQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-trace-otlp-http@0.213.0': resolution: {integrity: sha512-tnRmJD39aWrE/Sp7F6AbRNAjKHToDkAqBi6i0lESpGWz3G+f4bhVAV6mgSXH2o18lrDVJXo6jf9bAywQw43wRA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-trace-otlp-http@0.217.0': resolution: {integrity: sha512-38YQoqtYjglz2GV94LGUN/djLvxtvGIQO68o6qAFPVshjmwSdX1F2i0c7vn3lEl1L5B/YqjB/bgKXaVx7KO+RQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-trace-otlp-proto@0.217.0': resolution: {integrity: sha512-nPV8gKHUiSuTZpQcnZU3/pBlK7crSyEGpZuh5MtWySB0vv6NNG0QvvfKitQt+Fc2Mc6qfyU54KlZcurwoTbrVg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-zipkin@2.7.1': resolution: {integrity: sha512-mfsD9bKAxcKrh5+y08TPodvClBO0CznBE3p79YAGnO81WI4LrdsGA65T53e4iTSbCalW4WaUpkbeJcbpyIUHfg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/instrumentation-http@0.217.0': resolution: {integrity: sha512-B88Y7k5A9a60pHUboFoeJlgVwXq2T0rsZKj6dTwzSMKSOsNXR4Jz5ovwprVn3kHLAZrkyLEjQtBJ34DYHs1U4Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/instrumentation@0.213.0': resolution: {integrity: sha512-3i9NdkET/KvQomeh7UaR/F4r9P25Rx6ooALlWXPIjypcEOUxksCmVu0zA70NBJWlrMW1rPr/LRidFAflLI+s/w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/instrumentation@0.214.0': resolution: {integrity: sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/instrumentation@0.217.0': resolution: {integrity: sha512-24ucQMjz7Y34Kw3trbxL2ZrssbtgWnR+Clpaa+YdeWuuyH3Cvk23Q03PcQvqiZrDvt8AmQmjgg9v6Y9PHoxG7w==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/otlp-exporter-base@0.213.0': resolution: {integrity: sha512-MegxAP1/n09Ob2dQvY5NBDVjAFkZRuKtWKxYev1R2M8hrsgXzQGkaMgoEKeUOyQ0FUyYcO29UOnYdQWmWa0PXg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/otlp-exporter-base@0.217.0': resolution: {integrity: sha512-eYfqnB3UhKu/5frhd1R6+FprKygbhkomuaceMXDyzxbfXB9tKgZOVmjaJ02CkLA6Tdzumxl+e2H+vo2a8jiMPQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/otlp-grpc-exporter-base@0.217.0': resolution: {integrity: sha512-7RTAdZuOsCDnsyqTCG4+bDzrfnsWdzkRs7z0AVi/V3tEQx0oKeyc+OuRWYxnRsmaJXgxcmB8vb/lfxn58Dj6Ag==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/otlp-transformer@0.213.0': resolution: {integrity: sha512-RSuAlxFFPjeK4d5Y6ps8L2WhaQI6CXWllIjvo5nkAlBpmq2XdYWEBGiAbOF4nDs8CX4QblJDv5BbMUft3sEfDw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/otlp-transformer@0.217.0': resolution: {integrity: sha512-MKK8UHKFUOGAvbZRWh90MhwHG+Fxm6OROBdjKPCF+HQobjuJ/Kuf8Chs8CR45X1aqotxrMj7OxTdsXe8sXuGVA==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/propagator-b3@2.7.1': resolution: {integrity: sha512-RJid6E2CKyeGfKBzXKF21ejabGMHypFkPAh3qZ+NvI+SGjuIye79t3PmiqcDgtRzdKH6ynXzbfslQ8DfpRUg2A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/propagator-jaeger@2.7.1': resolution: {integrity: sha512-KMjVBHzP4N60bOzxja76M1F1hZZ43lGPga5ix+mkv9+kk1nx9SbkxSvJsMbuVUxdPQmsPTqGShmhN8ulrMOg6Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/resources@2.6.0': resolution: {integrity: sha512-D4y/+OGe3JSuYUCBxtH5T9DSAWNcvCb/nQWIga8HNtXTVPQn59j0nTBAgaAXxUVBDl40mG3Tc76b46wPlZaiJQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/resources@2.7.1': resolution: {integrity: sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-logs@0.213.0': resolution: {integrity: sha512-00xlU3GZXo3kXKve4DLdrAL0NAFUaZ9appU/mn00S/5kSUdAvyYsORaDUfR04Mp2CLagAOhrzfUvYozY/EZX2g==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-logs@0.217.0': resolution: {integrity: sha512-BB+PcHItcZDL63dPMW+mJvwN9rk37wuIDjRxbVlg6pPDvDR/7GL7UJHbGsllgoggOoTimsKgENaWPoGch/oE1A==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-metrics@2.6.0': resolution: {integrity: sha512-CicxWZxX6z35HR83jl+PLgtFgUrKRQ9LCXyxgenMnz5A1lgYWfAog7VtdOvGkJYyQgMNPhXQwkYrDLujk7z1Iw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-metrics@2.7.1': resolution: {integrity: sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-node@0.217.0': resolution: {integrity: sha512-K/60pSv42+NQiZKy1pAH18nYDkxltsDV4O3SJ233J0E9raU1ksyL9gsKuS8p30bYBb4AMPCfDuutHQaHYpcv0Q==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-trace-base@2.6.0': resolution: {integrity: sha512-g/OZVkqlxllgFM7qMKqbPV9c1DUPhQ7d4n3pgZFcrnrNft9eJXZM2TNHTPYREJBrtNdRytYyvwjgL5geDKl3EQ==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-trace-base@2.7.1': resolution: {integrity: sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/sdk-trace-node@2.7.1': resolution: {integrity: sha512-pCpQxU68lV+I9s9svqMyVu5iHdDDUnqUpSxqwyCU8A9ejEsSnMPCbearwsUO4yk08ZJzAIUCFuReMdVQvHrdvg==} engines: {node: ^18.19.0 || >=20.6.0} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions@1.40.0': resolution: {integrity: sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==} @@ -11552,7 +11562,7 @@ packages: resolution: {integrity: sha512-M8XMMIk9Y0PGZoEt37Oe5dQCdqDdJlBcwLXidpz/s5k4QtJvCO/BbtcivcuKI2htw5FwxJkSrHUzRvT36tlDpg==} engines: {node: '>=18'} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0 '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' '@opentelemetry/instrumentation': '>=0.57.1 <1' @@ -11584,7 +11594,7 @@ packages: resolution: {integrity: sha512-0+YrNmVNrttki4rWP4DW+UTt5MziepwDLNBde39tgc3cGCcy5fLSdDfhb4JfTaE5TXt4kd5XrkgvS/sDgm3RZg==} engines: {node: '>=18'} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0 '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.39.0 @@ -11682,6 +11692,10 @@ packages: resolution: {integrity: sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w==} engines: {node: '>=18.0.0'} + '@smithy/core@3.30.0': + resolution: {integrity: sha512-dl2yRglDxfzH9uJ4fSo4zTaAHa0zH7+V7BZMRWy8hEYIKT1BiqMUK/CN6T3ADQ3kbA5N1tmUulroJ2UtONS7Kw==} + engines: {node: '>=18.0.0'} + '@smithy/credential-provider-imds@4.2.5': resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} engines: {node: '>=18.0.0'} @@ -11693,6 +11707,10 @@ packages: resolution: {integrity: sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==} engines: {node: '>=18.0.0'} + '@smithy/eventstream-codec@4.4.14': + resolution: {integrity: sha512-dk/8H8vjgsghRKq0Euout/Q5iQMGVw8pUVSs2gY7DFjZtg1+RB12Q/TNh4Uph6E9D46mKr161WobWFyAQWJdjQ==} + engines: {node: '>=18.0.0'} + '@smithy/eventstream-serde-browser@4.2.5': resolution: {integrity: sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==} engines: {node: '>=18.0.0'} @@ -11821,6 +11839,10 @@ packages: resolution: {integrity: sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==} engines: {node: '>=18.0.0'} + '@smithy/types@4.16.1': + resolution: {integrity: sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==} + engines: {node: '>=18.0.0'} + '@smithy/url-parser@4.2.12': resolution: {integrity: sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA==} engines: {node: '>=18.0.0'} @@ -11909,6 +11931,10 @@ packages: resolution: {integrity: sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw==} engines: {node: '>=18.0.0'} + '@smithy/util-utf8@4.4.14': + resolution: {integrity: sha512-zpHIvgr2NWASkgWgv1O0zPPX7VFdkHKa9NxxjGseGvSY2DRZgRsnoXuEcjETwPeqP92blsINwfbIJz0F0/0Skg==} + engines: {node: '>=18.0.0'} + '@smithy/util-waiter@4.0.3': resolution: {integrity: sha512-JtaY3FxmD+te+KSI2FJuEcfNC9T/DGGVf551babM7fAaXhjJUt7oSYurH1Devxd2+BOSUACCgt3buinx4UnmEA==} engines: {node: '>=18.0.0'} @@ -13550,6 +13576,9 @@ packages: '@vvo/tzdb@6.141.0': resolution: {integrity: sha512-vpuv8Eo75WwW6oHygfSSJ8J2AIADlqoHu9HmKxD8Ey/hmRhxt0+/nEGbzBy/Y/Q1qsdgDj3kP3NRLr2082VzgA==} + '@workflow/serde@4.1.0': + resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} + '@workflow/serde@4.1.0-beta.2': resolution: {integrity: sha512-8kkeoQKLDaKXefjV5dbhBj2aErfKp1Mc4pb6tj8144cF+Em5SPbyMbyLCHp+BVrFfFVCBluCtMx+jjvaFVZGww==} @@ -13791,11 +13820,11 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ai@6.0.197: - resolution: {integrity: sha512-U3KsjkqwQXGHC0u0VeUDqUaNaBS/uQc7v4Vj92Cjv5lPx5DIyRBQYk4Hipy5vwD9AQKIG8uRvdaN9R+pAvrtcQ==} - engines: {node: '>=18'} + ai@7.0.37: + resolution: {integrity: sha512-stF+SEQJgKY3Qfe3FwNzqUrehHviOp2l7LemoI8YMOa0Zk5PxKsRNgUk3cHXz0RAue9RRyCAis2fz6LSCP6EKw==} + engines: {node: '>=22'} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} @@ -17685,7 +17714,7 @@ packages: langsmith@0.6.0: resolution: {integrity: sha512-GGaj5IMRfLv2HXXFzGk9diISMYLTpSTh6fzCZGKxWYW/NqEztIFtnXLq6G/RVhzFRmCykLap1fuC67LVKoQLcg==} peerDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/exporter-trace-otlp-proto': '*' '@opentelemetry/sdk-trace-base': '*' openai: '*' @@ -19296,7 +19325,7 @@ packages: '@smithy/hash-node': '>=4.3.0 <5' '@smithy/signature-v4': '>=5.4.0 <6' ws: '>=8.20.1' - zod: 3.25.67 + zod: 3.25.76 peerDependenciesMeta: '@aws-sdk/credential-provider-node': optional: true @@ -21705,7 +21734,7 @@ packages: peerDependencies: date-fns: 2.30.0 lodash: 4.18.1 - zod: 3.25.67 + zod: 3.25.76 ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -22361,7 +22390,7 @@ packages: hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@types/node': ^20.17.50 '@vitest/browser-playwright': 4.1.9 '@vitest/browser-preview': 4.1.9 @@ -22928,15 +22957,15 @@ packages: zod-to-json-schema@3.23.3: resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 zod-to-json-schema@3.25.2: resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} peerDependencies: - zod: 3.25.67 + zod: 3.25.76 - zod@3.25.67: - resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -22998,96 +23027,106 @@ snapshots: '@adobe/css-tools@4.4.0': {} - '@ai-sdk/amazon-bedrock@4.0.113(zod@3.25.67)': + '@ai-sdk/amazon-bedrock@5.0.31(zod@3.25.76)': dependencies: - '@ai-sdk/anthropic': 3.0.81(zod@3.25.67) - '@ai-sdk/openai': 3.0.68(zod@3.25.67) - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - '@smithy/eventstream-codec': 4.2.5 - '@smithy/util-utf8': 4.2.2 + '@ai-sdk/anthropic': 4.0.20(zod@3.25.76) + '@ai-sdk/openai': 4.0.20(zod@3.25.76) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + '@smithy/eventstream-codec': 4.4.14 + '@smithy/util-utf8': 4.4.14 aws4fetch: 1.0.20 - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/anthropic@3.0.81(zod@3.25.67)': + '@ai-sdk/anthropic@4.0.20(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/azure@3.0.70(zod@3.25.67)': + '@ai-sdk/azure@4.0.21(zod@3.25.76)': dependencies: - '@ai-sdk/openai': 3.0.68(zod@3.25.67) - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/deepseek': 3.0.13(zod@3.25.76) + '@ai-sdk/openai': 4.0.20(zod@3.25.76) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/cohere@3.0.36(zod@3.25.67)': + '@ai-sdk/cohere@4.0.12(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/deepseek@2.0.35(zod@3.25.67)': + '@ai-sdk/deepseek@3.0.13(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/gateway@3.0.125(zod@3.25.67)': + '@ai-sdk/gateway@4.0.28(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) '@vercel/oidc': 3.2.0 - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/google@3.0.80(zod@3.25.67)': + '@ai-sdk/google@4.0.24(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/groq@3.0.39(zod@3.25.67)': + '@ai-sdk/groq@4.0.13(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/mistral@3.0.37(zod@3.25.67)': + '@ai-sdk/mistral@4.0.14(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/openai-compatible@2.0.48(zod@3.25.67)': + '@ai-sdk/openai-compatible@3.0.14(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/openai@3.0.68(zod@3.25.67)': + '@ai-sdk/openai@4.0.20(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 - '@ai-sdk/provider-utils@4.0.27(zod@3.25.67)': + '@ai-sdk/otel@1.0.37(zod@3.25.76)': dependencies: - '@ai-sdk/provider': 3.0.10 + '@ai-sdk/provider': 4.0.3 + '@opentelemetry/api': 1.9.1 + ai: 7.0.37(zod@3.25.76) + transitivePeerDependencies: + - zod + + '@ai-sdk/provider-utils@5.0.12(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.3 '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 eventsource-parser: 3.0.8 - zod: 3.25.67 + zod: 3.25.76 - '@ai-sdk/provider@3.0.10': + '@ai-sdk/provider@4.0.3': dependencies: json-schema: 0.4.0 - '@ai-sdk/xai@3.0.93(zod@3.25.67)': + '@ai-sdk/xai@4.0.18(zod@3.25.76)': dependencies: - '@ai-sdk/openai-compatible': 2.0.48(zod@3.25.67) - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - zod: 3.25.67 + '@ai-sdk/openai-compatible': 3.0.14(zod@3.25.76) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 '@alloc/quick-lru@5.2.0': {} @@ -23113,13 +23152,13 @@ snapshots: commander: 12.1.0 node-forge: 1.4.0 shell-quote: 1.9.0 - zod: 3.25.67 + zod: 3.25.76 - '@anthropic-ai/sdk@0.91.1(zod@3.25.67)': + '@anthropic-ai/sdk@0.91.1(zod@3.25.76)': dependencies: json-schema-to-ts: 3.1.1 optionalDependencies: - zod: 3.25.67 + zod: 3.25.76 '@apidevtools/json-schema-ref-parser@12.0.2': dependencies: @@ -25299,17 +25338,17 @@ snapshots: transitivePeerDependencies: - encoding - '@browserbasehq/stagehand@1.14.0(@playwright/test@1.60.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.4.2)(encoding@0.1.13)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(utf-8-validate@5.0.10)(zod@3.25.67)': + '@browserbasehq/stagehand@1.14.0(@playwright/test@1.60.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.4.2)(encoding@0.1.13)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(utf-8-validate@5.0.10)(zod@3.25.76)': dependencies: - '@anthropic-ai/sdk': 0.91.1(zod@3.25.67) + '@anthropic-ai/sdk': 0.91.1(zod@3.25.76) '@browserbasehq/sdk': 2.12.0(encoding@0.1.13) '@playwright/test': 1.60.0 deepmerge: 4.3.1 dotenv: 17.4.2 - openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) + openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - zod: 3.25.67 - zod-to-json-schema: 3.25.2(zod@3.25.67) + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) transitivePeerDependencies: - bufferutil - encoding @@ -25389,7 +25428,7 @@ snapshots: chalk: 4.1.2 semver: 7.7.3 unplugin: 1.11.0 - zod: 3.25.67 + zod: 3.25.76 '@codecov/vite-plugin@1.9.1(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))': dependencies: @@ -25508,7 +25547,7 @@ snapshots: '@codspeed/core': 5.2.0 tinybench: 2.9.0 vite: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) transitivePeerDependencies: - debug - supports-color @@ -25622,13 +25661,13 @@ snapshots: '@daytona/api-client': 0.187.0 '@daytona/toolbox-api-client': 0.187.0 '@iarna/toml': 2.2.5 - '@opentelemetry/api': 1.9.0 - '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-node': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-node': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 axios: 1.18.0(debug@4.4.3) busboy: 1.6.0 @@ -26008,33 +26047,33 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@getzep/zep-cloud@1.0.6(65badca2f9a18f9477bcb77e95c88e85)': + '@getzep/zep-cloud@1.0.6(149dddd2ec2380c7920fcf767cbe6f0b)': dependencies: form-data: 4.0.6 node-fetch: 2.7.0(encoding@0.1.13) qs: 6.15.2 url-join: 4.0.1 - zod: 3.25.67 + zod: 3.25.76 optionalDependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - langchain: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + langchain: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + transitivePeerDependencies: + - encoding + + '@getzep/zep-cloud@1.0.6(40d462dc04f2b28317190740cba7d89c)': + dependencies: + form-data: 4.0.6 + node-fetch: 2.7.0(encoding@0.1.13) + qs: 6.15.2 + url-join: 4.0.1 + zod: 3.25.76 + optionalDependencies: + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + langchain: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) transitivePeerDependencies: - encoding optional: true - '@getzep/zep-cloud@1.0.6(f7636f1681b49d771ee2a293a491ade5)': - dependencies: - form-data: 4.0.6 - node-fetch: 2.7.0(encoding@0.1.13) - qs: 6.15.2 - url-join: 4.0.1 - zod: 3.25.67 - optionalDependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - langchain: 1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - transitivePeerDependencies: - - encoding - '@getzep/zep-js@0.9.0': dependencies: '@supercharge/promise-pool': 3.1.0 @@ -26085,12 +26124,12 @@ snapshots: - encoding - supports-color - '@google/genai@1.19.0(@modelcontextprotocol/sdk@1.26.0(zod@3.25.67))(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@google/genai@1.19.0(@modelcontextprotocol/sdk@1.26.0(zod@3.25.76))(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: google-auth-library: 9.15.1 ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) optionalDependencies: - '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.67) + '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) transitivePeerDependencies: - bufferutil - supports-color @@ -26447,37 +26486,37 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} - '@langchain/anthropic@1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/anthropic@1.3.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@anthropic-ai/sdk': 0.91.1(zod@3.25.67) - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - zod: 3.25.67 + '@anthropic-ai/sdk': 0.91.1(zod@3.25.76) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + zod: 3.25.76 - '@langchain/aws@1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/aws@1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: '@aws-sdk/client-bedrock-agent-runtime': 3.808.0 '@aws-sdk/client-bedrock-runtime': 3.938.0 '@aws-sdk/client-kendra': 3.808.0 '@aws-sdk/credential-provider-node': 3.936.0 - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) transitivePeerDependencies: - aws-crt - '@langchain/classic@1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@langchain/classic@1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/openai': 1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/textsplitters': 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/openai': 1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/textsplitters': 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) handlebars: 4.7.9 js-yaml: 4.3.0 jsonpointer: 5.0.1 openapi-types: 12.1.3 uuid: 13.0.1 yaml: 2.8.3 - zod: 3.25.67 + zod: 3.25.76 optionalDependencies: cheerio: 1.1.0 - langsmith: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + langsmith: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) transitivePeerDependencies: - '@aws-sdk/credential-provider-node' - '@opentelemetry/api' @@ -26488,37 +26527,37 @@ snapshots: - openai - ws - '@langchain/cohere@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13)': + '@langchain/cohere@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) cohere-ai: 7.14.0(encoding@0.1.13) uuid: 13.0.1 transitivePeerDependencies: - aws-crt - encoding - '@langchain/community@1.1.27(5a58ffcca9c8ab8d1fbf31dd7fb3c90d)': + '@langchain/community@1.1.27(58b3539f609126398d3a2334339488d1)': dependencies: - '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.60.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.4.2)(encoding@0.1.13)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(utf-8-validate@5.0.10)(zod@3.25.67) + '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.60.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.4.2)(encoding@0.1.13)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(utf-8-validate@5.0.10)(zod@3.25.76) '@ibm-cloud/watsonx-ai': 1.1.2 - '@langchain/classic': 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/openai': 1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/classic': 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/openai': 1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) binary-extensions: 2.2.0 flat: 5.0.2 ibm-cloud-sdk-core: 5.5.0 js-yaml: 4.3.0 - langsmith: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + langsmith: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) math-expression-evaluator: 2.0.7 - openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) + openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) uuid: 13.0.1 - zod: 3.25.67 + zod: 3.25.76 optionalDependencies: '@aws-crypto/sha256-js': 5.2.0 '@azure/search-documents': 12.1.0 '@azure/storage-blob': 12.32.0 '@browserbasehq/sdk': 2.12.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.6(f7636f1681b49d771ee2a293a491ade5) + '@getzep/zep-cloud': 1.0.6(149dddd2ec2380c7920fcf767cbe6f0b) '@getzep/zep-js': 0.9.0 '@google-cloud/storage': 7.12.1(encoding@0.1.13) '@huggingface/inference': 4.0.5 @@ -26526,8 +26565,9 @@ snapshots: '@mozilla/readability': 0.6.0 '@pinecone-database/pinecone': 5.1.2 '@qdrant/js-client-rest': 1.16.2(@typescript/typescript6@6.0.2) + '@smithy/eventstream-codec': 4.4.14 '@smithy/protocol-http': 5.3.12 - '@smithy/util-utf8': 4.2.2 + '@smithy/util-utf8': 4.4.14 '@supabase/supabase-js': 2.50.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@xata.io/client': 0.28.4(@typescript/typescript6@6.0.2) '@zilliz/milvus2-sdk-node': 2.5.7 @@ -26562,32 +26602,33 @@ snapshots: - '@smithy/hash-node' - peggy - '@langchain/community@1.1.27(cad6e30ee219fb5df7e5cc892428da20)': + '@langchain/community@1.1.27(b1822a993b35582954040a82820e4d06)': dependencies: - '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.60.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.4.2)(encoding@0.1.13)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(utf-8-validate@5.0.10)(zod@3.25.67) + '@browserbasehq/stagehand': 1.14.0(@playwright/test@1.60.0)(bufferutil@4.0.9)(deepmerge@4.3.1)(dotenv@17.4.2)(encoding@0.1.13)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(utf-8-validate@5.0.10)(zod@3.25.76) '@ibm-cloud/watsonx-ai': 1.1.2 - '@langchain/classic': 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/openai': 1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/classic': 1.0.27(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(cheerio@1.1.0)(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/openai': 1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) binary-extensions: 2.2.0 flat: 5.0.2 ibm-cloud-sdk-core: 5.5.0 js-yaml: 4.3.0 - langsmith: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + langsmith: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) math-expression-evaluator: 2.0.7 - openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) + openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) uuid: 13.0.1 - zod: 3.25.67 + zod: 3.25.76 optionalDependencies: '@aws-crypto/sha256-js': 5.2.0 '@azure/storage-blob': 12.32.0 '@browserbasehq/sdk': 2.12.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.6(65badca2f9a18f9477bcb77e95c88e85) + '@getzep/zep-cloud': 1.0.6(40d462dc04f2b28317190740cba7d89c) '@google-cloud/storage': 7.12.1(encoding@0.1.13) '@libsql/client': 0.17.2(bufferutil@4.0.9)(encoding@0.1.13)(utf-8-validate@5.0.10) '@mozilla/readability': 0.6.0 + '@smithy/eventstream-codec': 4.4.14 '@smithy/protocol-http': 5.3.12 - '@smithy/util-utf8': 4.2.2 + '@smithy/util-utf8': 4.4.14 '@supabase/supabase-js': 2.50.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@zilliz/milvus2-sdk-node': 2.5.7 chromadb: 3.2.0 @@ -26614,15 +26655,15 @@ snapshots: - '@smithy/hash-node' - peggy - '@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: '@cfworker/json-schema': 4.1.0 '@standard-schema/spec': 1.1.0 js-tiktoken: 1.0.21 - langsmith: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + langsmith: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) mustache: 4.2.0 p-queue: 6.6.2 - zod: 3.25.67 + zod: 3.25.76 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/exporter-trace-otlp-proto' @@ -26630,61 +26671,61 @@ snapshots: - openai - ws - '@langchain/google-common@2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/google-common@2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) uuid: 13.0.1 - '@langchain/google-gauth@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/google-gauth@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/google-common': 2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/google-common': 2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) google-auth-library: 10.1.0 transitivePeerDependencies: - '@langchain/core' - supports-color - '@langchain/google-genai@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/google-genai@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: '@google/generative-ai': 0.24.0 - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) uuid: 13.0.1 - '@langchain/google-vertexai@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/google-vertexai@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/google-gauth': 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/google-gauth': 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) transitivePeerDependencies: - '@langchain/core' - supports-color - '@langchain/groq@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13)': + '@langchain/groq@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) groq-sdk: 0.19.0(encoding@0.1.13) transitivePeerDependencies: - encoding - '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/langgraph-checkpoint@1.0.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) uuid: 13.0.1 - '@langchain/langgraph-checkpoint@1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/langgraph-checkpoint@1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph-sdk@1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@langchain/langgraph-sdk@1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: p-queue: 6.6.2 p-retry: 4.6.2 uuid: 13.0.1 optionalDependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@langchain/langgraph-sdk@1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))': + '@langchain/langgraph-sdk@1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/protocol': 0.0.18 '@types/json-schema': 7.0.15 p-queue: 9.1.0 @@ -26694,9 +26735,9 @@ snapshots: react-dom: 18.2.0(react@18.2.0) vue: 3.5.26(@typescript/typescript6@6.0.2) - '@langchain/langgraph-sdk@1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))': + '@langchain/langgraph-sdk@1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@langchain/protocol': 0.0.18 '@types/json-schema': 7.0.15 p-queue: 9.1.0 @@ -26706,82 +26747,82 @@ snapshots: react-dom: 18.2.0(react@18.2.0) vue: 3.5.26(typescript@7.0.2) - '@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67)': + '@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@langchain/langgraph-sdk': 1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/langgraph-sdk': 1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) uuid: 13.0.1 - zod: 3.25.67 + zod: 3.25.76 optionalDependencies: - zod-to-json-schema: 3.23.3(zod@3.25.67) + zod-to-json-schema: 3.23.3(zod@3.25.76) transitivePeerDependencies: - react - react-dom - '@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.25.2(zod@3.25.67))(zod@3.25.67)': + '@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.25.2(zod@3.25.76))(zod@3.25.76)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@langchain/langgraph-sdk': 1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/langgraph-sdk': 1.0.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) uuid: 13.0.1 - zod: 3.25.67 + zod: 3.25.76 optionalDependencies: - zod-to-json-schema: 3.25.2(zod@3.25.67) + zod-to-json-schema: 3.25.2(zod@3.25.76) transitivePeerDependencies: - react - react-dom - '@langchain/langgraph@1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(zod@3.25.67)': + '@langchain/langgraph@1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(zod@3.25.76)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@langchain/langgraph-sdk': 1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/langgraph-sdk': 1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2)) '@langchain/protocol': 0.0.18 '@standard-schema/spec': 1.1.0 - zod: 3.25.67 + zod: 3.25.76 transitivePeerDependencies: - react - react-dom - svelte - vue - '@langchain/langgraph@1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(zod@3.25.67)': + '@langchain/langgraph@1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(zod@3.25.76)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - '@langchain/langgraph-sdk': 1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/langgraph-sdk': 1.9.25(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2)) '@langchain/protocol': 0.0.18 '@standard-schema/spec': 1.1.0 - zod: 3.25.67 + zod: 3.25.76 transitivePeerDependencies: - react - react-dom - svelte - vue - '@langchain/mcp-adapters@1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67))': + '@langchain/mcp-adapters@1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph': 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67) - '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.67) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph': 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) debug: 4.4.3(supports-color@8.1.1) - zod: 3.25.67 + zod: 3.25.76 optionalDependencies: extended-eventsource: 1.7.0 transitivePeerDependencies: - '@cfworker/json-schema' - supports-color - '@langchain/mistralai@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/mistralai@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@mistralai/mistralai': 1.10.0 uuid: 13.0.1 - '@langchain/mongodb@1.0.1(@aws-sdk/credential-providers@3.808.0)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(gcp-metadata@5.3.0)(socks@2.8.3)': + '@langchain/mongodb@1.0.1(@aws-sdk/credential-providers@3.808.0)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(gcp-metadata@5.3.0)(socks@2.8.3)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) mongodb: 6.21.0(@aws-sdk/credential-providers@3.808.0)(gcp-metadata@5.3.0)(socks@2.8.3) transitivePeerDependencies: - '@aws-sdk/credential-providers' @@ -26792,66 +26833,66 @@ snapshots: - snappy - socks - '@langchain/ollama@1.2.6(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/ollama@1.2.6(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) ollama: 0.6.3 uuid: 13.0.1 - '@langchain/openai@1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@langchain/openai@1.4.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) js-tiktoken: 1.0.21 - openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) - zod: 3.25.67 + openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) + zod: 3.25.76 transitivePeerDependencies: - '@aws-sdk/credential-provider-node' - '@smithy/hash-node' - '@smithy/signature-v4' - ws - '@langchain/openai@1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': + '@langchain/openai@1.4.4(patch_hash=0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87)(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) js-tiktoken: 1.0.21 - openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) - zod: 3.25.67 + openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) + zod: 3.25.76 transitivePeerDependencies: - '@aws-sdk/credential-provider-node' - '@smithy/hash-node' - '@smithy/signature-v4' - ws - '@langchain/pinecone@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@pinecone-database/pinecone@5.1.2)': + '@langchain/pinecone@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@pinecone-database/pinecone@5.1.2)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@pinecone-database/pinecone': 5.1.2 flat: 5.0.2 uuid: 13.0.1 '@langchain/protocol@0.0.18': {} - '@langchain/qdrant@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@typescript/typescript6@6.0.2)': + '@langchain/qdrant@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@typescript/typescript6@6.0.2)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) '@qdrant/js-client-rest': 1.16.2(@typescript/typescript6@6.0.2) uuid: 13.0.1 transitivePeerDependencies: - typescript - '@langchain/redis@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/redis@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) redis: 4.6.14 - '@langchain/textsplitters@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': + '@langchain/textsplitters@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) js-tiktoken: 1.0.21 - '@langchain/weaviate@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13)': + '@langchain/weaviate@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(encoding@0.1.13)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) uuid: 13.0.1 weaviate-client: 3.9.0(encoding@0.1.13) transitivePeerDependencies: @@ -27072,11 +27113,11 @@ snapshots: '@memlab/lens@2.0.1': {} - '@microsoft/agents-a365-notifications@1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)': + '@microsoft/agents-a365-notifications@1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)': dependencies: - '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) '@microsoft/agents-activity': 1.6.1 - '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/api-logs' @@ -27084,40 +27125,40 @@ snapshots: '@microsoft/agents-a365-observability@1.0.0(@opentelemetry/api-logs@0.217.0)': dependencies: - '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) - '@opentelemetry/api': 1.9.0 - '@opentelemetry/exporter-trace-otlp-http': 0.213.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.213.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-node': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/exporter-trace-otlp-http': 0.213.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.213.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-node': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - '@opentelemetry/api-logs' - supports-color - '@microsoft/agents-a365-runtime@1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)': + '@microsoft/agents-a365-runtime@1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)': dependencies: '@azure/identity': 4.13.0 - '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) jsonwebtoken: 9.0.3 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/api-logs' - supports-color - '@microsoft/agents-a365-tooling-extensions-langchain@1.0.0(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67))(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67)': + '@microsoft/agents-a365-tooling-extensions-langchain@1.0.0(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76))(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/mcp-adapters': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67)) - '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) - '@microsoft/agents-a365-tooling': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)(zod@3.25.67) - '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/mcp-adapters': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/langgraph@1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76)) + '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) + '@microsoft/agents-a365-tooling': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)(zod@3.25.76) + '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) hono: 4.12.27 - langchain: 1.5.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + langchain: 1.5.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) uuid: 13.0.1 optionalDependencies: - '@langchain/langgraph': 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.67))(zod@3.25.67) + '@langchain/langgraph': 1.0.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod-to-json-schema@3.23.3(zod@3.25.76))(zod@3.25.76) transitivePeerDependencies: - '@cfworker/json-schema' - '@opentelemetry/api' @@ -27133,11 +27174,11 @@ snapshots: - ws - zod - '@microsoft/agents-a365-tooling@1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)(zod@3.25.67)': + '@microsoft/agents-a365-tooling@1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)(zod@3.25.76)': dependencies: - '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) - '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) - '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.67) + '@microsoft/agents-a365-runtime': 1.0.0(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) + '@microsoft/agents-hosting': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) + '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) express: 5.2.1 hono: 4.12.27 transitivePeerDependencies: @@ -27149,27 +27190,27 @@ snapshots: '@microsoft/agents-activity@1.6.1': dependencies: - zod: 3.25.67 + zod: 3.25.76 - '@microsoft/agents-hosting@1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)': + '@microsoft/agents-hosting@1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)': dependencies: '@azure/core-auth': 1.10.1 '@azure/msal-node': 5.1.5 '@microsoft/agents-activity': 1.6.1 - '@microsoft/agents-telemetry': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0) + '@microsoft/agents-telemetry': 1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1) jsonwebtoken: 9.0.3 jwks-rsa: 4.0.1 - zod: 3.25.67 + zod: 3.25.76 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/api-logs' - supports-color - '@microsoft/agents-telemetry@1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.0)': + '@microsoft/agents-telemetry@1.6.1(@opentelemetry/api-logs@0.217.0)(@opentelemetry/api@1.9.1)': dependencies: debug: 4.4.3(supports-color@8.1.1) optionalDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.217.0 transitivePeerDependencies: - supports-color @@ -27217,20 +27258,20 @@ snapshots: '@mistralai/mistralai@1.10.0': dependencies: - zod: 3.25.67 - zod-to-json-schema: 3.25.2(zod@3.25.67) + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) '@mixmark-io/domino@2.2.0': {} - '@modelcontextprotocol/ext-apps@1.3.2(@modelcontextprotocol/sdk@1.26.0(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod@3.25.67)': + '@modelcontextprotocol/ext-apps@1.3.2(@modelcontextprotocol/sdk@1.26.0(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(zod@3.25.76)': dependencies: - '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.67) - zod: 3.25.67 + '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) + zod: 3.25.76 optionalDependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - '@modelcontextprotocol/sdk@1.26.0(zod@3.25.67)': + '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.13(hono@4.12.27) ajv: 8.18.0 @@ -27247,8 +27288,8 @@ snapshots: json-schema-typed: 8.0.2 pkce-challenge: 5.0.0(patch_hash=651e785d0b7bbf5be9210e1e895c39a16dc3ce8a5a3843b4819565fb6e175b90) raw-body: 3.0.0 - zod: 3.25.67 - zod-to-json-schema: 3.25.2(zod@3.25.67) + zod: 3.25.76 + zod-to-json-schema: 3.25.2(zod@3.25.76) transitivePeerDependencies: - supports-color @@ -27292,7 +27333,7 @@ snapshots: - debug - supports-color - '@n8n/typeorm@0.3.20-16(@sentry/node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)))(mysql2@3.17.0)(pg-native@3.8.0)(pg-query-stream@4.16.0(pg@8.21.0(pg-native@3.8.0)))(pg@8.21.0(pg-native@3.8.0))(sqlite3@5.1.7)': + '@n8n/typeorm@0.3.20-16(@sentry/node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)))(mysql2@3.17.0)(pg-native@3.8.0)(pg-query-stream@4.16.0(pg@8.21.0(pg-native@3.8.0)))(pg@8.21.0(pg-native@3.8.0))(sqlite3@5.1.7)': dependencies: app-root-path: 3.1.0 async-mutex: 0.5.0 @@ -27308,7 +27349,7 @@ snapshots: tslib: 2.8.1 uuid: 13.0.1 optionalDependencies: - '@sentry/node': 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + '@sentry/node': 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) mysql2: 3.17.0 pg: 8.21.0(pg-native@3.8.0) pg-native: 3.8.0 @@ -27608,342 +27649,342 @@ snapshots: '@open-draft/until@2.1.0': {} - '@openrouter/ai-sdk-provider@2.9.0(ai@6.0.197(zod@3.25.67))(zod@3.25.67)': + '@openrouter/ai-sdk-provider@3.0.0(ai@7.0.37(zod@3.25.76))(zod@3.25.76)': dependencies: - ai: 6.0.197(zod@3.25.67) - zod: 3.25.67 + ai: 7.0.37(zod@3.25.76) + zod: 3.25.76 '@opentelemetry/api-logs@0.213.0': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs@0.214.0': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs@0.217.0': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 - '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api@1.9.1': {} - '@opentelemetry/configuration@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/configuration@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) yaml: 2.8.3 - '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/context-async-hooks@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 - '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/exporter-logs-otlp-grpc@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-http@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-logs-otlp-proto@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-logs-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-http@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-metrics-otlp-proto@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-metrics-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-prometheus@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-prometheus@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-grpc-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.213.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-http@0.213.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/exporter-zipkin@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/instrumentation-http@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-http@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 forwarded-parse: 2.1.2 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.213.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.213.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.213.0 import-in-the-middle: 3.0.0 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.214.0 import-in-the-middle: 3.0.0 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.217.0 import-in-the-middle: 3.0.0 require-in-the-middle: 8.0.1 transitivePeerDependencies: - supports-color - '@opentelemetry/otlp-exporter-base@0.213.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-exporter-base@0.213.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-grpc-exporter-base@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-grpc-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': dependencies: '@grpc/grpc-js': 1.14.4 - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-transformer': 0.217.0(@opentelemetry/api@1.9.1) - '@opentelemetry/otlp-transformer@0.213.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-transformer@0.213.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.213.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.6.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.6.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.1) protobufjs: 7.6.5 - '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/otlp-transformer@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) protobufjs: 7.6.5 - '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/propagator-jaeger@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/resources@2.6.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/resources@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/sdk-logs@0.213.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-logs@0.213.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.213.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-logs@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/sdk-metrics@2.6.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-metrics@2.6.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-metrics@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) - '@opentelemetry/sdk-node@0.217.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-node@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@opentelemetry/api-logs': 0.217.0 - '@opentelemetry/configuration': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-logs-otlp-proto': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-metrics-otlp-proto': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-prometheus': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/configuration': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-logs-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-metrics-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-prometheus': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-grpc': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-zipkin': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-b3': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/propagator-jaeger': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-node': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 transitivePeerDependencies: - supports-color - '@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@2.6.0(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/resources': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-node@2.7.1(@opentelemetry/api@1.9.1)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/context-async-hooks': 2.7.1(@opentelemetry/api@1.9.1) + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions@1.40.0': {} - '@oracle/langchain-oracledb@0.2.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/textsplitters@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))))(oracledb@6.10.0)': + '@oracle/langchain-oracledb@0.2.0(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@langchain/textsplitters@1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))))(oracledb@6.10.0)': dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/textsplitters': 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/textsplitters': 1.0.1(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) htmlparser2: 10.1.0 oracledb: 6.10.0 uuid: 13.0.1 @@ -28699,56 +28740,56 @@ snapshots: '@sentry/core@10.55.0': {} - '@sentry/node-core@10.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)': + '@sentry/node-core@10.55.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0)': dependencies: '@sentry/core': 10.55.0 - '@sentry/opentelemetry': 10.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) + '@sentry/opentelemetry': 10.55.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) import-in-the-middle: 3.0.0 optionalDependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/exporter-trace-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 - '@sentry/node-native@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0))': + '@sentry/node-native@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))': dependencies: '@sentry-internal/node-native-stacktrace': 0.5.0 '@sentry/core': 10.55.0 - '@sentry/node': 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + '@sentry/node': 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) transitivePeerDependencies: - '@opentelemetry/exporter-trace-otlp-http' - supports-color - '@sentry/node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0))': + '@sentry/node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 '@sentry/core': 10.55.0 - '@sentry/node-core': 10.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) - '@sentry/opentelemetry': 10.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0) + '@sentry/node-core': 10.55.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) + '@sentry/opentelemetry': 10.55.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0) import-in-the-middle: 3.0.0 transitivePeerDependencies: - '@opentelemetry/exporter-trace-otlp-http' - supports-color - '@sentry/opentelemetry@10.55.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)': + '@sentry/opentelemetry@10.55.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.40.0)': dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.40.0 '@sentry/core': 10.55.0 - '@sentry/profiling-node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0))': + '@sentry/profiling-node@10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1))': dependencies: '@sentry-internal/node-cpu-profiler': 2.4.1 '@sentry/core': 10.55.0 - '@sentry/node': 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.0)) + '@sentry/node': 10.55.0(@opentelemetry/exporter-trace-otlp-http@0.217.0(@opentelemetry/api@1.9.1)) transitivePeerDependencies: - '@opentelemetry/exporter-trace-otlp-http' - supports-color @@ -28874,6 +28915,11 @@ snapshots: '@smithy/uuid': 1.1.2 tslib: 2.8.1 + '@smithy/core@3.30.0': + dependencies: + '@smithy/types': 4.16.1 + tslib: 2.8.1 + '@smithy/credential-provider-imds@4.2.5': dependencies: '@smithy/node-config-provider': 4.3.12 @@ -28896,6 +28942,11 @@ snapshots: '@smithy/util-hex-encoding': 4.2.2 tslib: 2.8.1 + '@smithy/eventstream-codec@4.4.14': + dependencies: + '@smithy/core': 3.30.0 + tslib: 2.8.1 + '@smithy/eventstream-serde-browser@4.2.5': dependencies: '@smithy/eventstream-serde-universal': 4.2.5 @@ -29101,6 +29152,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@smithy/types@4.16.1': + dependencies: + tslib: 2.8.1 + '@smithy/url-parser@4.2.12': dependencies: '@smithy/querystring-parser': 4.2.12 @@ -29220,6 +29275,11 @@ snapshots: '@smithy/util-buffer-from': 4.2.2 tslib: 2.8.1 + '@smithy/util-utf8@4.4.14': + dependencies: + '@smithy/core': 3.30.0 + tslib: 2.8.1 + '@smithy/util-waiter@4.0.3': dependencies: '@smithy/abort-controller': 4.2.12 @@ -29264,7 +29324,7 @@ snapshots: '@vitest/browser': 4.1.9(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))(vitest@4.1.9) '@vitest/browser-playwright': 4.1.9(bufferutil@4.0.9)(playwright@1.60.0)(utf-8-validate@5.0.10)(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))(vitest@4.1.9) '@vitest/runner': 4.1.9 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) transitivePeerDependencies: - react - react-dom @@ -29398,7 +29458,7 @@ snapshots: '@stryker-mutator/util': 9.6.1 semver: 7.7.3 tslib: 2.8.1 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) '@stylistic/eslint-plugin@5.0.0(eslint@9.29.0(jiti@2.6.1))': dependencies: @@ -30909,7 +30969,7 @@ snapshots: '@vitest/mocker': 4.1.9(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) playwright: 1.60.0 tinyrainbow: 3.1.0 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) transitivePeerDependencies: - bufferutil - msw @@ -30925,7 +30985,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil @@ -30945,7 +31005,7 @@ snapshots: obug: 2.1.3 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) optionalDependencies: '@vitest/browser': 4.1.9(bufferutil@4.0.9)(utf-8-validate@5.0.10)(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))(vitest@4.1.9) @@ -31313,6 +31373,8 @@ snapshots: '@vvo/tzdb@6.141.0': {} + '@workflow/serde@4.1.0': {} + '@workflow/serde@4.1.0-beta.2': {} '@xata.io/client@0.28.4(@typescript/typescript6@6.0.2)': @@ -31395,7 +31457,7 @@ snapshots: dependencies: '@grpc/grpc-js': 1.14.4 '@grpc/proto-loader': 0.7.13 - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@petamoriken/float16': 3.9.2 dayjs: 1.11.10 generic-pool: 3.9.0 @@ -31488,13 +31550,12 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ai@6.0.197(zod@3.25.67): + ai@7.0.37(zod@3.25.76): dependencies: - '@ai-sdk/gateway': 3.0.125(zod@3.25.67) - '@ai-sdk/provider': 3.0.10 - '@ai-sdk/provider-utils': 4.0.27(zod@3.25.67) - '@opentelemetry/api': 1.9.0 - zod: 3.25.67 + '@ai-sdk/gateway': 4.0.28(zod@3.25.76) + '@ai-sdk/provider': 4.0.3 + '@ai-sdk/provider-utils': 5.0.12(zod@3.25.76) + zod: 3.25.76 ajv-draft-04@1.0.0(ajv@8.18.0): optionalDependencies: @@ -32486,7 +32547,7 @@ snapshots: dependencies: devtools-protocol: 0.0.1595872 mitt: 3.0.1 - zod: 3.25.67 + zod: 3.25.76 chromium-pickle-js@0.2.0: {} @@ -36117,14 +36178,14 @@ snapshots: kuler@2.0.0: {} - langchain@1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + langchain@1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph': 1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(zod@3.25.67) - '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - langsmith: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph': 1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(zod@3.25.76) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + langsmith: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) uuid: 13.0.1 - zod: 3.25.67 + zod: 3.25.76 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/exporter-trace-otlp-proto' @@ -36136,14 +36197,14 @@ snapshots: - vue - ws - langchain@1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + langchain@1.2.30(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph': 1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(zod@3.25.67) - '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - langsmith: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph': 1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(typescript@7.0.2))(zod@3.25.76) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + langsmith: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) uuid: 13.0.1 - zod: 3.25.67 + zod: 3.25.76 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/exporter-trace-otlp-proto' @@ -36155,13 +36216,13 @@ snapshots: - vue - ws - langchain@1.5.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + langchain@1.5.2(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: - '@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - '@langchain/langgraph': 1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(zod@3.25.67) - '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) - langsmith: 0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) - zod: 3.25.67 + '@langchain/core': 1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + '@langchain/langgraph': 1.4.7(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(vue@3.5.26(@typescript/typescript6@6.0.2))(zod@3.25.76) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))) + langsmith: 0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)) + zod: 3.25.76 transitivePeerDependencies: - '@opentelemetry/api' - '@opentelemetry/exporter-trace-otlp-proto' @@ -36173,14 +36234,14 @@ snapshots: - vue - ws - langsmith@0.6.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): + langsmith@0.6.0(@opentelemetry/api@1.9.1)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.1))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)): dependencies: p-queue: 6.6.2 optionalDependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.0) - openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67) + '@opentelemetry/api': 1.9.1 + '@opentelemetry/exporter-trace-otlp-proto': 0.217.0(@opentelemetry/api@1.9.1) + '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) + openai: 6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76) ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) launder@1.7.1: @@ -37632,7 +37693,7 @@ snapshots: mutation-server-protocol@0.4.1: dependencies: - zod: 3.25.67 + zod: 3.25.76 mutation-testing-elements@3.7.3: {} @@ -38143,11 +38204,11 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67): + openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.76): optionalDependencies: '@smithy/signature-v4': 5.3.5 ws: 8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10) - zod: 3.25.67 + zod: 3.25.76 openapi-sampler@1.5.1: dependencies: @@ -38775,7 +38836,7 @@ snapshots: prom-client@15.1.3: dependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 tdigest: 0.1.2 promise-ftp-common@1.1.5: {} @@ -41162,12 +41223,12 @@ snapshots: '@ts-graphviz/common': 2.1.5 '@ts-graphviz/core': 2.0.7 - ts-ics@1.2.2(date-fns@2.30.0)(lodash@4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b))(zod@3.25.67): + ts-ics@1.2.2(date-fns@2.30.0)(lodash@4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b))(zod@3.25.76): dependencies: date-fns: 2.30.0 date-fns-tz: 2.0.0(date-fns@2.30.0) lodash: 4.18.1(patch_hash=81ead1b07fc8efda87bd2724dbd876ab49f4a0aa03f96c3770060b2ea47d9e0b) - zod: 3.25.67 + zod: 3.25.76 ts-interface-checker@0.1.13: {} @@ -41889,27 +41950,27 @@ snapshots: dependencies: ts-essentials: 10.0.2(@typescript/typescript6@6.0.2) typescript: '@typescript/typescript6@6.0.2' - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended@3.1.0(typescript@6.0.2)(vitest@4.1.9): dependencies: ts-essentials: 10.0.2(typescript@6.0.2) typescript: 6.0.2 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-mock-extended@3.1.0(typescript@7.0.2)(vitest@4.1.9): dependencies: ts-essentials: 10.0.2(typescript@7.0.2) typescript: 7.0.2 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) vitest-websocket-mock@0.5.0(vitest@4.1.9): dependencies: '@vitest/utils': 3.2.4 mock-socket: 9.3.1 - vitest: 4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) + vitest: 4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) - vitest@4.1.9(@opentelemetry/api@1.9.0)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)): + vitest@4.1.9(@opentelemetry/api@1.9.1)(@types/node@20.19.41)(@vitest/browser-playwright@4.1.9)(@vitest/coverage-v8@4.1.9)(jsdom@23.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10))(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.9 '@vitest/mocker': 4.1.9(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3)) @@ -41932,7 +41993,7 @@ snapshots: vite: 8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: - '@opentelemetry/api': 1.9.0 + '@opentelemetry/api': 1.9.1 '@types/node': 20.19.41 '@vitest/browser-playwright': 4.1.9(bufferutil@4.0.9)(playwright@1.60.0)(utf-8-validate@5.0.10)(vite@8.0.2(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@types/node@20.19.41)(esbuild@0.28.1)(jiti@2.6.1)(sass-embedded@1.98.0)(sass@1.98.0)(terser@5.16.1)(tsx@4.19.3)(yaml@2.8.3))(vitest@4.1.9) '@vitest/coverage-v8': 4.1.9(@vitest/browser@4.1.9)(vitest@4.1.9) @@ -42583,17 +42644,17 @@ snapshots: zod-from-json-schema@0.0.5: dependencies: - zod: 3.25.67 + zod: 3.25.76 - zod-to-json-schema@3.23.3(zod@3.25.67): + zod-to-json-schema@3.23.3(zod@3.25.76): dependencies: - zod: 3.25.67 + zod: 3.25.76 - zod-to-json-schema@3.25.2(zod@3.25.67): + zod-to-json-schema@3.25.2(zod@3.25.76): dependencies: - zod: 3.25.67 + zod: 3.25.76 - zod@3.25.67: {} + zod@3.25.76: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 61fdce779de..68dbc212ec4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,19 +6,20 @@ packages: - packages/testing/** catalog: - '@ai-sdk/amazon-bedrock': ^4.0.113 - '@ai-sdk/anthropic': ^3.0.81 - '@ai-sdk/azure': ^3.0.70 - '@ai-sdk/cohere': ^3.0.36 - '@ai-sdk/deepseek': ^2.0.35 - '@ai-sdk/gateway': ^3.0.125 - '@ai-sdk/google': ^3.0.80 - '@ai-sdk/groq': ^3.0.39 - '@ai-sdk/mistral': ^3.0.37 - '@ai-sdk/openai': ^3.0.68 - '@ai-sdk/openai-compatible': ^2.0.48 - '@ai-sdk/provider-utils': ^4.0.27 - '@ai-sdk/xai': ^3.0.93 + '@ai-sdk/amazon-bedrock': ^5.0.31 + '@ai-sdk/anthropic': ^4.0.20 + '@ai-sdk/azure': ^4.0.21 + '@ai-sdk/cohere': ^4.0.12 + '@ai-sdk/deepseek': ^3.0.13 + '@ai-sdk/gateway': ^4.0.28 + '@ai-sdk/google': ^4.0.24 + '@ai-sdk/groq': ^4.0.13 + '@ai-sdk/mistral': ^4.0.14 + '@ai-sdk/openai': ^4.0.20 + '@ai-sdk/openai-compatible': ^3.0.14 + '@ai-sdk/otel': ^1.0.37 + '@ai-sdk/provider-utils': ^5.0.12 + '@ai-sdk/xai': ^4.0.18 '@azure/identity': 4.13.0 '@azure/storage-blob': ^12.32.0 '@chat-adapter/linear': ^4.28.1 @@ -57,7 +58,8 @@ catalog: '@modelcontextprotocol/sdk': 1.26.0 '@mozilla/readability': ^0.6.0 '@n8n_io/ai-assistant-sdk': 1.25.0 - '@openrouter/ai-sdk-provider': ^2.8.0 + '@openrouter/ai-sdk-provider': ^3.0.0 + '@opentelemetry/api': 1.9.1 '@pinecone-database/pinecone': ^5.0.2 '@qdrant/js-client-rest': ^1.16.2 '@rudderstack/rudder-sdk-node': 3.0.5 @@ -113,7 +115,7 @@ catalog: '@vitest/coverage-v8': 4.1.9 '@e965/xlsx': 0.20.3 agent-browser: 0.26.0 - ai: ^6.0.197 + ai: ^7.0.37 axios: 1.18.0 basic-auth: 2.0.1 cache-manager: 5.2.3 @@ -209,7 +211,7 @@ catalog: xml2js: 0.6.2 xss: 1.0.15 yaml: 2.8.3 - zod: 3.25.67 + zod: 3.25.76 zod-to-json-schema: 3.23.3 catalogMode: strict From 1cc76becce519aed7961d571dd6e74bf66bc3fe4 Mon Sep 17 00:00:00 2001 From: Yuliia Pominchuk <31064937+yuliia-pominchuk@users.noreply.github.com> Date: Tue, 28 Jul 2026 18:15:34 +0200 Subject: [PATCH 005/130] feat(core): Add n8n user auth (OAuth2) authentication mode for webhook node (#35100) --- .../nodes/mcp/McpTrigger/McpTrigger.node.ts | 5 +- .../__tests__/n8n-oauth2-auth.test.ts | 36 ------ .../__tests__/webhook-helpers.test.ts | 105 ++++++++++++++++ packages/cli/src/webhooks/webhook-helpers.ts | 60 +++++++-- .../app/utils/nodes/nodeTransforms.test.ts | 41 ++++++ .../src/app/utils/nodes/nodeTransforms.ts | 8 ++ .../nodes-base/nodes/Webhook/Webhook.node.ts | 18 ++- .../nodes-base/nodes/Webhook/description.ts | 35 +++++- .../nodes/Webhook/test/Webhook.test.ts | 117 ++++++++++++++++++ packages/workflow/src/index.ts | 1 + packages/workflow/src/interfaces.ts | 3 + .../src}/n8n-oauth2-auth.ts | 35 ++++-- packages/workflow/src/trigger-identity.ts | 13 +- .../workflow/test/n8n-oauth2-auth.test.ts | 96 ++++++++++++++ .../workflow/test/trigger-identity.test.ts | 14 +++ 15 files changed, 524 insertions(+), 63 deletions(-) delete mode 100644 packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/__tests__/n8n-oauth2-auth.test.ts rename packages/{@n8n/nodes-langchain/nodes/mcp/McpTrigger => workflow/src}/n8n-oauth2-auth.ts (52%) create mode 100644 packages/workflow/test/n8n-oauth2-auth.test.ts diff --git a/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/McpTrigger.node.ts b/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/McpTrigger.node.ts index f63cfb6c8d7..71b6daba027 100644 --- a/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/McpTrigger.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/McpTrigger.node.ts @@ -6,12 +6,11 @@ import type { IWebhookFunctions, IWebhookResponseData, } from 'n8n-workflow'; -import { NodeConnectionTypes, Node, nodeNameToToolName } from 'n8n-workflow'; +import { NodeConnectionTypes, Node, nodeNameToToolName, n8nOAuth2Auth } from 'n8n-workflow'; import { getConnectedTools } from '@utils/helpers'; import { McpServer, MCP_LIST_TOOLS_REQUEST_MARKER } from './McpServer'; -import { n8nOAuth2Auth } from './n8n-oauth2-auth'; import { MessageParser } from './protocol/MessageParser'; import type { CompressionResponse } from './transport'; @@ -177,7 +176,7 @@ export class McpTrigger extends Node { resp.end('OAuth2 authentication requires mcp trigger node v2.0 or higher'); return { noWebhookResponse: true }; } - const authResult = await n8nOAuth2Auth(context); + const authResult = await n8nOAuth2Auth(context, { realm: 'n8n MCP Server' }); if (authResult === 'handled') { return { noWebhookResponse: true }; } diff --git a/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/__tests__/n8n-oauth2-auth.test.ts b/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/__tests__/n8n-oauth2-auth.test.ts deleted file mode 100644 index fb8f51e9095..00000000000 --- a/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/__tests__/n8n-oauth2-auth.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { IWebhookFunctions, N8nOAuth2ValidationResult } from 'n8n-workflow'; - -import { createMockRequest, createMockResponse } from './helpers'; -import { n8nOAuth2Auth } from '../n8n-oauth2-auth'; - -const buildContext = (validation: N8nOAuth2ValidationResult) => { - const response = createMockResponse(); - const request = createMockRequest({ headers: { authorization: 'Bearer test-token' } }); - - const context = { - getNodeWebhookUrl: () => 'https://n8n.example.com/mcp/protected-path', - getResponseObject: () => response, - getRequestObject: () => request, - validateN8nOAuth2Token: vi.fn().mockResolvedValue(validation), - } as unknown as IWebhookFunctions; - - return { context, response }; -}; - -describe('n8nOAuth2Auth', () => { - // An authenticated user lacking execute access (`insufficient_scope`) is rejected with 403; - // an unverifiable token (`invalid_token`) with 401. Both carry the reason in WWW-Authenticate. - test.each([ - ['insufficient_scope', 403], - ['invalid_token', 401], - ] as const)('maps a %s validation failure to a %i response', async (reason, code) => { - const { context, response } = buildContext({ valid: false, reason }); - - const result = await n8nOAuth2Auth(context); - - expect(result).toBe('handled'); - expect(response.writeHead).toHaveBeenCalledWith(code, { - 'WWW-Authenticate': expect.stringContaining(`error="${reason}"`), - }); - }); -}); diff --git a/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts b/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts index 4f8797e4e05..3d333783bea 100644 --- a/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts +++ b/packages/cli/src/webhooks/__tests__/webhook-helpers.test.ts @@ -759,6 +759,111 @@ describe('prepareExecutionData', () => { ]); }); + test('should replace the seeded stack (not merge) for a Webhook node using n8nOAuth2 auth, preserving runtimeData', () => { + const identityWebhookNode = mock({ + name: 'Webhook', + type: 'n8n-nodes-base.webhook', + parameters: { authentication: 'n8nOAuth2' }, + }); + + // After the node's webhook() call the identity is already established: the + // seeder's placeholder item has been consumed by the hook (leaving an empty + // item) and the resolved credentials live on executionData.runtimeData. + const existingNodeExecutionStack: IExecuteData[] = [ + { + node: mock({ name: 'ExistingNode' }), + data: { + main: [[{ json: {} }]], + }, + source: null, + }, + ]; + + const existingRunExecutionData: IRunExecutionData = { + version: 1, + startData: {}, + resultData: { runData: {} }, + executionData: { + contextData: {}, + metadata: {}, + nodeExecutionStack: existingNodeExecutionStack, + waitingExecution: {}, + waitingExecutionSource: {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + runtimeData: { version: 1, credentials: { source: 'n8n-oauth' } } as any, + }, + } as IRunExecutionData; + + const { runExecutionData } = prepareExecutionData( + 'trigger', + identityWebhookNode, + webhookResultData, + existingRunExecutionData, + ); + + expect(runExecutionData.executionData?.nodeExecutionStack).toHaveLength(1); + // The seeded placeholder is discarded; only the webhook's real output remains. + expect(runExecutionData.executionData?.nodeExecutionStack[0].data.main).toEqual([ + [{ json: { data: 'test' } }], + ]); + // The established identity (runtimeData) is preserved across the replace. + expect(runExecutionData.executionData?.runtimeData).toEqual({ + version: 1, + credentials: { source: 'n8n-oauth' }, + }); + }); + + test('should not leak the seeded placeholder into output slot 0 for a multi-method n8nOAuth2 webhook', () => { + const identityWebhookNode = mock({ + name: 'Webhook', + type: 'n8n-nodes-base.webhook', + parameters: { authentication: 'n8nOAuth2' }, + }); + + // Seeded placeholder sits in output slot 0. + const existingNodeExecutionStack: IExecuteData[] = [ + { + node: mock({ name: 'ExistingNode' }), + data: { + main: [[{ json: {} }]], + }, + source: null, + }, + ]; + + const existingRunExecutionData: IRunExecutionData = { + version: 1, + startData: {}, + resultData: { runData: {} }, + executionData: { + contextData: {}, + metadata: {}, + nodeExecutionStack: existingNodeExecutionStack, + waitingExecution: {}, + waitingExecutionSource: {}, + }, + } as IRunExecutionData; + + // A multi-method webhook routes the request to a non-first output slot; e.g. + // a POST on a ['GET','POST'] node puts the item in slot 1, slot 0 stays empty. + const multiMethodResult: IWebhookResponseData = { + workflowData: [[], [{ json: { method: 'POST' } }]], + }; + + const { runExecutionData } = prepareExecutionData( + 'trigger', + identityWebhookNode, + multiMethodResult, + existingRunExecutionData, + ); + + // Slot 0 must be empty (no phantom placeholder firing the GET branch). + expect(runExecutionData.executionData?.nodeExecutionStack[0].data.main).toEqual([ + [], + [{ json: { method: 'POST' } }], + ]); + }); + test('should merge existing data with new data for MICROSOFT_AGENT365_TRIGGER_NODE_TYPE', () => { const microsoftAgentNode = mock({ name: 'Microsoft Agent 365', diff --git a/packages/cli/src/webhooks/webhook-helpers.ts b/packages/cli/src/webhooks/webhook-helpers.ts index 3b0331402fa..2fb1fd5670d 100644 --- a/packages/cli/src/webhooks/webhook-helpers.ts +++ b/packages/cli/src/webhooks/webhook-helpers.ts @@ -52,6 +52,7 @@ import { tryToParseUrl, UnexpectedError, WAIT_NODE_TYPE, + WEBHOOK_NODE_TYPE, WorkflowConfigurationError, } from 'n8n-workflow'; import { finished } from 'stream/promises'; @@ -360,6 +361,53 @@ export function setupResponseNodePromise( }); } +/** + * Predicate (not an action): checks whether the start node will establish a + * triggering-user identity from within its `webhook()` method (via + * `context.establishTriggerIdentity`). Such nodes need their `runExecutionData` + * created before the webhook runs, and the webhook output merged into the seeded + * execution stack afterwards. + * + * The Webhook node does this only when its opt-in "n8n User Auth (OAuth2)" mode + * (`n8nOAuth2`) is selected; the MCP / chat / Agent365 triggers always do. + */ +function shouldEstablishTriggerIdentity(workflowStartNode: INode): boolean { + return ( + workflowStartNode.type === WEBHOOK_NODE_TYPE && + workflowStartNode.parameters?.authentication === 'n8nOAuth2' + ); +} + +/** + * Reconciles a pre-seeded execution stack (identity/context trigger flows) with the + * webhook node's real output. No-op unless the start node seeded execution data. + * + * - MCP / chat / Agent365 (single-output triggers): index-merge the webhook output + * into the seeded item so seeded input data is preserved. + * - n8n Identity webhook: the identity was already established from the seeded + * placeholder during the node's `webhook()` call (credentials now live on + * `executionData.runtimeData`, a sibling that survives the reassignment). Replace + * the seeded stack with the real output instead of index-merging — a naive merge + * keeps the empty placeholder in output slot 0 and would spuriously fire that + * branch on multi-method webhooks. + */ +function reconcileSeededExecutionStack( + workflowStartNode: INode, + runExecutionData: IRunExecutionData | undefined, + nodeExecutionStack: IExecuteData[], +): void { + const executionData = runExecutionData?.executionData; + if (!executionData?.nodeExecutionStack) return; + + if ( + [MICROSOFT_AGENT365_TRIGGER_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE].includes(workflowStartNode.type) + ) { + merge(executionData.nodeExecutionStack, nodeExecutionStack); + } else if (shouldEstablishTriggerIdentity(workflowStartNode)) { + executionData.nodeExecutionStack = nodeExecutionStack; + } +} + export function prepareExecutionData( executionMode: WorkflowExecuteMode, workflowStartNode: INode, @@ -382,14 +430,7 @@ export function prepareExecutionData( }, ]; - if ( - [MICROSOFT_AGENT365_TRIGGER_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE].includes( - workflowStartNode.type, - ) && - runExecutionData?.executionData?.nodeExecutionStack - ) { - merge(runExecutionData.executionData.nodeExecutionStack, nodeExecutionStack); - } + reconcileSeededExecutionStack(workflowStartNode, runExecutionData, nodeExecutionStack); runExecutionData ??= createRunExecutionData({ executionData: { @@ -619,7 +660,8 @@ export async function executeWebhook( MCP_TRIGGER_NODE_TYPE, MICROSOFT_AGENT365_TRIGGER_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE, - ].includes(workflowStartNode.type) + ].includes(workflowStartNode.type) || + shouldEstablishTriggerIdentity(workflowStartNode) ) { // Initialize the data of the webhook node const nodeExecutionStack: IExecuteData[] = []; diff --git a/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.test.ts b/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.test.ts index 06ff83b85a1..419f62e2d4e 100644 --- a/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.test.ts +++ b/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.test.ts @@ -7,6 +7,14 @@ import type { INodePropertyOptions, INodeTypeDescription } from 'n8n-workflow'; import { getParameterDisplayableOptions, serializeNode } from './nodeTransforms'; import type { INodeUi } from '@/Interface'; import { useNodeTypesStore } from '@/app/stores/nodeTypes.store'; +import { useEnvFeatureFlag } from '@/features/shared/envFeatureFlag/useEnvFeatureFlag'; + +vi.mock('@/features/shared/envFeatureFlag/useEnvFeatureFlag', () => ({ + useEnvFeatureFlag: vi.fn(), +})); + +// Controls which env feature flags the mocked composable reports as enabled. +const enabledEnvFeatureFlags = new Set(); vi.mock('n8n-workflow', async (importOriginal) => ({ ...(await importOriginal()), @@ -32,6 +40,12 @@ describe('getParameterDisplayableOptions', () => { setActivePinia(pinia); vi.clearAllMocks(); + enabledEnvFeatureFlags.clear(); + vi.mocked(useEnvFeatureFlag).mockReturnValue({ + check: { + value: (flag: string) => enabledEnvFeatureFlags.has(flag), + }, + } as unknown as ReturnType); mockNodeType = { name: 'testNode', @@ -229,6 +243,33 @@ describe('getParameterDisplayableOptions', () => { }); }); + describe('envFeatureFlag gating', () => { + const flaggedOption: INodePropertyOptions = { + name: 'Flagged Option', + value: 'flagged', + envFeatureFlag: 'SOME_FLAG', + }; + + beforeEach(() => { + vi.mocked(NodeHelpers.displayParameter).mockReturnValue(true); + }); + + it('hides an option whose env feature flag is disabled', () => { + const result = getParameterDisplayableOptions([testOptions[0], flaggedOption], mockNode); + + expect(result).toContainEqual(testOptions[0]); + expect(result).not.toContainEqual(flaggedOption); + }); + + it('shows an option whose env feature flag is enabled', () => { + enabledEnvFeatureFlags.add('SOME_FLAG'); + + const result = getParameterDisplayableOptions([testOptions[0], flaggedOption], mockNode); + + expect(result).toContainEqual(flaggedOption); + }); + }); + describe('edge cases', () => { it('should handle complex node parameters', () => { const complexNode = { diff --git a/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.ts b/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.ts index bbf6a6e871b..aa316fe5c46 100644 --- a/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.ts +++ b/packages/frontend/editor-ui/src/app/utils/nodes/nodeTransforms.ts @@ -21,6 +21,7 @@ import { import { useNodeTypesStore } from '@/app/stores/nodeTypes.store'; import { getCredentialTypeName, isCredentialOnlyNodeType } from '@/app/utils/credentialOnlyNodes'; import { hasProxyAuth } from '@/app/utils/nodeTypesUtils'; +import { useEnvFeatureFlag } from '@/features/shared/envFeatureFlag/useEnvFeatureFlag'; /** * Assigns a freshly generated id to the given node and returns it. @@ -143,6 +144,8 @@ export function getParameterDisplayableOptions( if (!nodeType || !Array.isArray(nodeType.properties)) return options; + const { check: envFeatureFlag } = useEnvFeatureFlag(); + const nodeParameters = NodeHelpers.getNodeParameters( nodeType.properties, @@ -154,6 +157,11 @@ export function getParameterDisplayableOptions( ) ?? node.parameters; return options.filter((option) => { + // Options gated behind an env feature flag are hidden until the flag is enabled. + if (option.envFeatureFlag && !envFeatureFlag.value(option.envFeatureFlag)) { + return false; + } + if (!option.displayOptions && !option.disabledOptions) return true; return NodeHelpers.displayParameter( diff --git a/packages/nodes-base/nodes/Webhook/Webhook.node.ts b/packages/nodes-base/nodes/Webhook/Webhook.node.ts index 9a12a95964b..0d6ff17553a 100644 --- a/packages/nodes-base/nodes/Webhook/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook/Webhook.node.ts @@ -10,7 +10,7 @@ import type { IWebhookResponseData, INodeProperties, } from 'n8n-workflow'; -import { BINARY_ENCODING, NodeOperationError, Node } from 'n8n-workflow'; +import { BINARY_ENCODING, NodeOperationError, Node, n8nOAuth2Auth } from 'n8n-workflow'; import { pipeline } from 'stream/promises'; import { file as tmpFile } from 'tmp-promise'; import { v4 as uuid } from 'uuid'; @@ -143,7 +143,7 @@ export class Webhook extends Node { "The path to listen to, dynamic values could be specified by using ':', e.g. 'your-path/:dynamic-value'. If dynamic values are set 'webhookId' would be prepended to path.", }, { - ...authenticationProperty(this.authPropertyName), + ...authenticationProperty(this.authPropertyName, true), builderHint: inboundTriggerAuthenticationBuilderHint, }, responseModeProperty, @@ -242,7 +242,19 @@ export class Webhook extends Node { try { if (options.ignoreBots && isbot(req.headers['user-agent'])) throw new WebhookAuthorizationError(403); - validationData = await this.validateAuth(context); + if (context.getNodeParameter('authentication', 'none') === 'n8nOAuth2') { + // Two-step n8n user-auth flow: (1) validate the bearer token and resolve + // the caller to an n8n user, then (2) seed the execution context so the + // run happens as that user (merging their private credentials). + const authResult = await n8nOAuth2Auth(context, { realm: 'n8n Webhook' }); + if (authResult === 'handled') { + // Token missing/invalid: the helper already sent the response. + return { noWebhookResponse: true }; + } + await context.establishTriggerIdentity(authResult.token, authResult.resource); + } else { + validationData = await this.validateAuth(context); + } } catch (error) { if (error instanceof WebhookAuthorizationError) { resp.writeHead(error.responseCode, { 'WWW-Authenticate': 'Basic realm="Webhook"' }); diff --git a/packages/nodes-base/nodes/Webhook/description.ts b/packages/nodes-base/nodes/Webhook/description.ts index 0def2dbc59b..ea5be6b0a45 100644 --- a/packages/nodes-base/nodes/Webhook/description.ts +++ b/packages/nodes-base/nodes/Webhook/description.ts @@ -1,7 +1,25 @@ -import type { INodeProperties, INodeTypeDescription, IWebhookDescription } from 'n8n-workflow'; +import type { + INodeProperties, + INodePropertyOptions, + INodeTypeDescription, + IWebhookDescription, +} from 'n8n-workflow'; import { getResponseCode, getResponseData } from './utils'; +// The Webhook node's opt-in "n8n User Auth (OAuth2)" mode. Seeds the triggering +// user's identity into the execution so the workflow can use that user's private +// credentials. Shares the `n8nOAuth2` value with the MCP trigger's equivalent mode. +// Hidden in the editor until N8N_ENV_FEAT_WEBHOOK_PRIVATE_CREDENTIALS is enabled, +// and only offered by nodes that pass `includeN8nOAuth2` (not Wait). +const n8nOAuth2AuthOption: INodePropertyOptions = { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + name: 'n8n User Auth (OAuth2)', + value: 'n8nOAuth2', + description: 'Require user to give consent to use their n8n account', + envFeatureFlag: 'WEBHOOK_PRIVATE_CREDENTIALS', +}; + export const defaultWebhookDescription: IWebhookDescription = { name: 'default', httpMethod: '={{$parameter["httpMethod"] || "GET"}}', @@ -53,7 +71,13 @@ export const inboundTriggerAuthenticationBuilderHint = { "Default to 'none'. n8n exposes inbound trigger URLs publicly by design. Only select an authentication method when the user explicitly asks to authenticate inbound traffic.", }; -export const authenticationProperty = (propertyName = 'authentication'): INodeProperties => ({ +export const authenticationProperty = ( + propertyName = 'authentication', + // The "n8n User Auth (OAuth2)" mode seeds the triggering user's identity into + // the execution, which only the Webhook node's `webhook()` flow supports. It is + // opt-in so it isn't exposed on nodes that reuse this property (e.g. Wait). + includeN8nOAuth2 = false, +): INodeProperties => ({ displayName: 'Authentication', name: propertyName, type: 'options', @@ -70,12 +94,19 @@ export const authenticationProperty = (propertyName = 'authentication'): INodePr name: 'JWT Auth', value: 'jwtAuth', }, + ...(includeN8nOAuth2 ? [n8nOAuth2AuthOption] : []), { name: 'None', value: 'none', }, ], default: 'none', + // The auth method is a mode selector, never a runtime value. Disallowing + // expressions keeps the stored parameter equal to the resolved one, so the + // webhook execution layer (which reads the raw parameter to decide whether to + // pre-initialize execution data for the n8n Identity flow) can't diverge from + // what the node resolves at runtime. + noDataExpression: true, description: 'The way to authenticate', }); diff --git a/packages/nodes-base/nodes/Webhook/test/Webhook.test.ts b/packages/nodes-base/nodes/Webhook/test/Webhook.test.ts index a876d60985e..6025b063e70 100644 --- a/packages/nodes-base/nodes/Webhook/test/Webhook.test.ts +++ b/packages/nodes-base/nodes/Webhook/test/Webhook.test.ts @@ -29,6 +29,24 @@ describe('Test Webhook Node', () => { }, }); }); + + it('disallows expressions on the authentication selector so raw and resolved values cannot diverge', () => { + const node = new Webhook(); + const authParam = node.description.properties.find( + (property) => property.name === 'authentication', + ); + + expect(authParam?.noDataExpression).toBe(true); + }); + + it('exposes the n8nOAuth2 authentication option', () => { + const node = new Webhook(); + const authParam = node.description.properties.find( + (property) => property.name === 'authentication', + ); + + expect(authParam?.options).toContainEqual(expect.objectContaining({ value: 'n8nOAuth2' })); + }); }); describe('handleFormData', () => { @@ -154,6 +172,105 @@ describe('Test Webhook Node', () => { }); }); + describe('n8n User Auth (OAuth2) authentication', () => { + const node = new Webhook(); + let context: ReturnType>; + let req: ReturnType>; + let res: ReturnType>; + + const WEBHOOK_URL = 'https://n8n.test/webhook/abc'; + + beforeEach(() => { + vi.clearAllMocks(); + context = mock({ nodeHelpers: mock(), logger: mock() }); + req = mock(); + res = mock(); + context.getRequestObject.mockReturnValue(req); + context.getResponseObject.mockReturnValue(res); + context.getChildNodes.mockReturnValue([]); + context.getNode.mockReturnValue({ + type: 'n8n-nodes-base.webhook', + typeVersion: 2.1, + name: 'Webhook', + } as any); + context.getNodeWebhookUrl.calledWith('default').mockReturnValue(WEBHOOK_URL); + context.getNodeParameter.mockImplementation((paramName: string) => { + if (paramName === 'options') return {}; + if (paramName === 'responseMode') return 'onReceived'; + if (paramName === 'authentication') return 'n8nOAuth2'; + return undefined; + }); + req.headers = {}; + req.params = {}; + req.query = {}; + req.method = 'GET'; + req.body = { hello: 'world' }; + Object.defineProperty(req, 'ips', { value: [], configurable: true }); + Object.defineProperty(req, 'ip', { value: '127.0.0.1', configurable: true }); + res.writeHead.mockImplementation(() => res); + res.end.mockImplementation(() => res); + }); + + it('rejects a request with no bearer token and does not establish identity', async () => { + const result = await node.webhook(context); + + expect(res.writeHead).toHaveBeenCalledWith(401, expect.any(Object)); + expect(context.validateN8nOAuth2Token).not.toHaveBeenCalled(); + expect(context.establishTriggerIdentity).not.toHaveBeenCalled(); + expect(result).toEqual({ noWebhookResponse: true }); + }); + + it('rejects an invalid token with 401 and does not establish identity', async () => { + req.headers.authorization = 'Bearer bad-token'; + context.validateN8nOAuth2Token.mockResolvedValue({ valid: false, reason: 'invalid_token' }); + + const result = await node.webhook(context); + + expect(context.validateN8nOAuth2Token).toHaveBeenCalledWith('bad-token', WEBHOOK_URL); + expect(res.writeHead).toHaveBeenCalledWith( + 401, + expect.objectContaining({ + 'WWW-Authenticate': expect.stringContaining('error="invalid_token"'), + }), + ); + expect(context.establishTriggerIdentity).not.toHaveBeenCalled(); + expect(result).toEqual({ noWebhookResponse: true }); + }); + + it('rejects insufficient scope with 403', async () => { + req.headers.authorization = 'Bearer scoped-token'; + context.validateN8nOAuth2Token.mockResolvedValue({ + valid: false, + reason: 'insufficient_scope', + }); + + await node.webhook(context); + + expect(res.writeHead).toHaveBeenCalledWith( + 403, + expect.objectContaining({ + 'WWW-Authenticate': expect.stringContaining('error="insufficient_scope"'), + }), + ); + expect(context.establishTriggerIdentity).not.toHaveBeenCalled(); + }); + + it('establishes the trigger identity for a valid token and runs the workflow', async () => { + req.headers.authorization = 'Bearer good-token'; + context.validateN8nOAuth2Token.mockResolvedValue({ + valid: true, + user: { id: 'user-1', email: 'a@b.c', firstName: 'A', lastName: 'B' }, + }); + + const result = await node.webhook(context); + + expect(context.validateN8nOAuth2Token).toHaveBeenCalledWith('good-token', WEBHOOK_URL); + expect(context.establishTriggerIdentity).toHaveBeenCalledWith('good-token', WEBHOOK_URL); + expect(result.workflowData).toBeDefined(); + expect(result.workflowData?.[0][0].json.body).toEqual({ hello: 'world' }); + }); + }); + describe('sensitiveOutputFields', () => { it('declares authorization and cookie headers as sensitive', () => { const node = new Webhook(); diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index fb2340c985f..97eb33d79e4 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -28,6 +28,7 @@ export * from './node-grouping-validation'; export * from './mcp-helpers'; export * from './tool-helpers'; export * from './trigger-identity'; +export * from './n8n-oauth2-auth'; export * from './node-reference-parser-utils'; export * from './metadata-utils'; export * from './highlighted-data'; diff --git a/packages/workflow/src/interfaces.ts b/packages/workflow/src/interfaces.ts index 19228eeb144..2c6170248be 100644 --- a/packages/workflow/src/interfaces.ts +++ b/packages/workflow/src/interfaces.ts @@ -2115,6 +2115,9 @@ export interface INodePropertyOptions { // disabledOptions added for compatibility with INodeProperties and INodeCredentialDescription types // it needs to be implemented, if needed disabledOptions?: undefined; + // When set, the option is hidden in the editor unless the matching + // `N8N_ENV_FEAT_` flag is enabled. + envFeatureFlag?: Uppercase; } export interface INodeListSearchItems extends INodePropertyOptions { diff --git a/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/n8n-oauth2-auth.ts b/packages/workflow/src/n8n-oauth2-auth.ts similarity index 52% rename from packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/n8n-oauth2-auth.ts rename to packages/workflow/src/n8n-oauth2-auth.ts index 099b1c4a6b5..69cfc77efff 100644 --- a/packages/@n8n/nodes-langchain/nodes/mcp/McpTrigger/n8n-oauth2-auth.ts +++ b/packages/workflow/src/n8n-oauth2-auth.ts @@ -1,4 +1,5 @@ -import { UnexpectedError, type IWebhookFunctions } from 'n8n-workflow'; +import { UnexpectedError } from './errors'; +import type { IWebhookFunctions } from './interfaces'; function trimTrailingSlash(url: string): string { return url.endsWith('/') ? url.slice(0, -1) : url; @@ -17,19 +18,36 @@ function sendUnauthorizedResponse( resp: ReturnType, code: number, prmUrl: string, + realm: string, error?: string, ) { const authenticateHeader = - `Bearer realm="n8n MCP Server", resource_metadata="${prmUrl}"` + - (error ? `, error="${error}"` : ''); + `Bearer realm="${realm}", resource_metadata="${prmUrl}"` + (error ? `, error="${error}"` : ''); resp.writeHead(code, { 'WWW-Authenticate': authenticateHeader, }); resp.end(); } +/** + * Validates a webhook caller's OAuth2 bearer token and resolves it to an n8n user — + * the first step of the "identity" webhook auth flow shared by the Webhook node + * and the MCP trigger — both expose it as the `n8nOAuth2` authentication mode. + * + * On success returns the validated `token` and `resource` URL, which the caller + * passes to `context.establishTriggerIdentity` to run the workflow as that user. + * When the token is missing or invalid the response is written here (401/403/503, + * advertising the OAuth protected-resource metadata URL in `WWW-Authenticate`) and + * `'handled'` is returned, so the request never reaches workflow execution. + * + * The only per-node difference is the `realm` shown in the `WWW-Authenticate` + * header (e.g. `n8n Webhook` vs `n8n MCP Server`); everything else — token + * parsing, protected-resource-metadata URL, error-code mapping — is identical and + * kept here so the two auth modes can't drift. + */ export const n8nOAuth2Auth = async ( context: IWebhookFunctions, + options: { realm: string }, ): Promise< | { status: 'ok'; @@ -46,30 +64,29 @@ export const n8nOAuth2Auth = async ( const resourceUrl = trimTrailingSlash(webhookUrl); const u = new URL(resourceUrl); - const prmUrl = `${u.origin}/.well-known/oauth-protected-resource${u.pathname}`; const resp = context.getResponseObject(); const req = context.getRequestObject(); - const authHeader = req.headers['authorization']; - const token = getBearerToken(authHeader); + const token = getBearerToken(req.headers.authorization); if (!token) { - sendUnauthorizedResponse(resp, 401, prmUrl); + sendUnauthorizedResponse(resp, 401, prmUrl, options.realm); return 'handled'; } const validationResult = await context.validateN8nOAuth2Token(token, resourceUrl); if (!validationResult.valid) { if (validationResult.reason === 'invalid_token') { - sendUnauthorizedResponse(resp, 401, prmUrl, 'invalid_token'); + sendUnauthorizedResponse(resp, 401, prmUrl, options.realm, 'invalid_token'); } else if (validationResult.reason === 'insufficient_scope') { - sendUnauthorizedResponse(resp, 403, prmUrl, 'insufficient_scope'); + sendUnauthorizedResponse(resp, 403, prmUrl, options.realm, 'insufficient_scope'); } else { resp.status(503).send('OAuth token validation is not available'); } return 'handled'; } + return { status: 'ok', token, diff --git a/packages/workflow/src/trigger-identity.ts b/packages/workflow/src/trigger-identity.ts index fd734e52447..09f572210f3 100644 --- a/packages/workflow/src/trigger-identity.ts +++ b/packages/workflow/src/trigger-identity.ts @@ -4,6 +4,7 @@ import { FORM_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPES, MCP_TRIGGER_NODE_TYPE, + WEBHOOK_NODE_TYPE, } from './constants'; import { toExecutionContextEstablishmentHookParameter } from './execution-context-establishment-hooks'; import type { INodeParameters } from './interfaces'; @@ -56,13 +57,23 @@ export function classifyTriggerIdentity( nodeType === CHAT_TRIGGER_NODE_TYPE && parameters?.availableInChat === true; const isMcpTrigger = nodeType === MCP_TRIGGER_NODE_TYPE && parameters?.authentication === 'n8nOAuth2'; + // The Webhook node's opt-in "n8n User Auth (OAuth2)" mode injects the caller's + // n8n identity the same way the MCP trigger does — sharing the `n8nOAuth2` value. + const isOAuth2Webhook = + nodeType === WEBHOOK_NODE_TYPE && parameters?.authentication === 'n8nOAuth2'; // Not gated by N8N_ENV_FEAT_FORM_TRIGGER_OAUTH2: this shared classification describes the // trigger's capability when the OAuth feature is enabled, and reading the env flag here // would mean threading it through both callers of a low-level FE/BE-shared function. The // only inconsistency is the narrow dynamic-credentials + form + flag-off combination. const isFormTrigger = nodeType === FORM_TRIGGER_NODE_TYPE && parameters?.authentication === 'n8nUserAuth'; - if (isSubWorkflowTrigger || isChatHubTrigger || isMcpTrigger || isFormTrigger) { + if ( + isSubWorkflowTrigger || + isChatHubTrigger || + isMcpTrigger || + isFormTrigger || + isOAuth2Webhook + ) { return { providesN8nIdentity: true, providesExternalIdentity: true }; } diff --git a/packages/workflow/test/n8n-oauth2-auth.test.ts b/packages/workflow/test/n8n-oauth2-auth.test.ts new file mode 100644 index 00000000000..0fa3661d012 --- /dev/null +++ b/packages/workflow/test/n8n-oauth2-auth.test.ts @@ -0,0 +1,96 @@ +import type { Response } from 'express'; +import { mock } from 'vitest-mock-extended'; + +import type { IWebhookFunctions, N8nOAuth2ValidationResult } from '../src/interfaces'; +import { n8nOAuth2Auth } from '../src/n8n-oauth2-auth'; + +const WEBHOOK_URL = 'https://n8n.example.com/webhook/protected-path'; + +const buildContext = (opts: { + authorization?: string; + validation?: N8nOAuth2ValidationResult; + webhookUrl?: string | undefined; +}) => { + const response = mock(); + response.writeHead.mockReturnValue(response); + response.end.mockReturnValue(response); + response.status.mockReturnValue(response); + response.send.mockReturnValue(response); + + const context = mock(); + context.getNodeWebhookUrl.mockReturnValue('webhookUrl' in opts ? opts.webhookUrl : WEBHOOK_URL); + context.getResponseObject.mockReturnValue(response); + context.getRequestObject.mockReturnValue({ + headers: opts.authorization ? { authorization: opts.authorization } : {}, + } as never); + context.validateN8nOAuth2Token.mockResolvedValue( + opts.validation ?? { valid: true, user: { id: 'u1' } as never }, + ); + + return { context, response, validateN8nOAuth2Token: context.validateN8nOAuth2Token }; +}; + +describe('n8nOAuth2Auth', () => { + it('returns the token and resource for a valid bearer token', async () => { + const { context, validateN8nOAuth2Token } = buildContext({ + authorization: 'Bearer good-token', + validation: { valid: true, user: { id: 'u1' } as never }, + }); + + const result = await n8nOAuth2Auth(context, { realm: 'n8n Webhook' }); + + expect(validateN8nOAuth2Token).toHaveBeenCalledWith('good-token', WEBHOOK_URL); + expect(result).toEqual({ status: 'ok', token: 'good-token', resource: WEBHOOK_URL }); + }); + + it('responds 401 without WWW-Authenticate error when no bearer token is present', async () => { + const { context, response, validateN8nOAuth2Token } = buildContext({}); + + const result = await n8nOAuth2Auth(context, { realm: 'n8n Webhook' }); + + expect(result).toBe('handled'); + expect(validateN8nOAuth2Token).not.toHaveBeenCalled(); + expect(response.writeHead).toHaveBeenCalledWith(401, { + 'WWW-Authenticate': expect.stringContaining('realm="n8n Webhook"'), + }); + }); + + // insufficient_scope → 403, invalid_token → 401; both carry the reason in WWW-Authenticate. + it.each([ + ['insufficient_scope', 403], + ['invalid_token', 401], + ] as const)('maps a %s validation failure to a %i response', async (reason, code) => { + const { context, response } = buildContext({ + authorization: 'Bearer test-token', + validation: { valid: false, reason }, + }); + + const result = await n8nOAuth2Auth(context, { realm: 'n8n MCP Server' }); + + expect(result).toBe('handled'); + expect(response.writeHead).toHaveBeenCalledWith(code, { + 'WWW-Authenticate': expect.stringContaining(`error="${reason}"`), + }); + }); + + it('responds 503 when the token verifier is unavailable', async () => { + const { context, response } = buildContext({ + authorization: 'Bearer test-token', + validation: { valid: false, reason: 'verifier_unavailable' }, + }); + + const result = await n8nOAuth2Auth(context, { realm: 'n8n Webhook' }); + + expect(result).toBe('handled'); + expect(response.status).toHaveBeenCalledWith(503); + expect(response.send).toHaveBeenCalledWith('OAuth token validation is not available'); + }); + + it('throws when the webhook URL is unavailable', async () => { + const { context } = buildContext({ webhookUrl: undefined }); + + await expect(n8nOAuth2Auth(context, { realm: 'n8n Webhook' })).rejects.toThrow( + 'Webhook URL is not available', + ); + }); +}); diff --git a/packages/workflow/test/trigger-identity.test.ts b/packages/workflow/test/trigger-identity.test.ts index 2d1436bd740..33f8c6a228a 100644 --- a/packages/workflow/test/trigger-identity.test.ts +++ b/packages/workflow/test/trigger-identity.test.ts @@ -60,6 +60,20 @@ describe('classifyTriggerIdentity', () => { }); }); + describe('Webhook node', () => { + it('provides both identities when authentication is n8nOAuth2', () => { + expect( + classifyTriggerIdentity('n8n-nodes-base.webhook', { authentication: 'n8nOAuth2' }), + ).toEqual({ providesN8nIdentity: true, providesExternalIdentity: true }); + }); + + it('provides no identity for other authentication modes', () => { + expect(classifyTriggerIdentity('n8n-nodes-base.webhook', { authentication: 'none' })).toEqual( + { providesN8nIdentity: false, providesExternalIdentity: false }, + ); + }); + }); + describe('other triggers', () => { it('provides the external identity only when a context establishment hook is configured', () => { expect(classifyTriggerIdentity('n8n-nodes-base.webhook', hooksParameters)).toEqual({ From 632ef22c0cc605079b0160fe15f1c124bbeea61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Braulio=20Gonz=C3=A1lez=20Valido?= Date: Tue, 28 Jul 2026 17:26:08 +0100 Subject: [PATCH 006/130] feat(ai-builder): Carry an eval case's conversation seed in the case body (no-changelog) (#35074) --- .../skills/create-instance-ai-eval/SKILL.md | 10 +- .../create-instance-ai-eval/case-shapes.md | 31 ++-- .../create-instance-ai-eval/sourcing-cases.md | 2 +- .../@n8n/instance-ai/evaluations/README.md | 21 ++- .../build-workflow-conversation-seed.test.ts | 135 ++++++++++++++++++ .../__tests__/conversation-seed.test.ts | 38 ----- .../__tests__/data-workflows-schema.test.ts | 36 ++++- .../__tests__/langtracer-provider.test.ts | 31 ++++ .../__tests__/langtracer-to-exported.test.ts | 24 +++- .../evaluations/__tests__/mcp-builder.test.ts | 13 +- .../evaluations/cli/mcp-builder.ts | 2 +- .../evaluations/harness/build-workflow.ts | 9 +- .../evaluations/harness/conversation-seed.ts | 26 +--- .../instance-ai/evaluations/harness/schema.ts | 16 ++- .../evaluations/langtracer/provider.ts | 10 ++ .../evaluations/langtracer/to-exported.ts | 4 +- .../evaluations/outcome/event-parser.ts | 6 +- .../evaluations/run/build-orchestrator.ts | 4 +- .../evaluations/run/eval-session.ts | 2 +- .../@n8n/instance-ai/evaluations/types.ts | 9 +- .../evaluations/utils/load-eval-cases.ts | 19 +-- 21 files changed, 321 insertions(+), 127 deletions(-) create mode 100644 packages/@n8n/instance-ai/evaluations/__tests__/build-workflow-conversation-seed.test.ts diff --git a/.agents/skills/create-instance-ai-eval/SKILL.md b/.agents/skills/create-instance-ai-eval/SKILL.md index 73ef5c27688..a4ecc4b8271 100644 --- a/.agents/skills/create-instance-ai-eval/SKILL.md +++ b/.agents/skills/create-instance-ai-eval/SKILL.md @@ -31,7 +31,7 @@ exhaustive field reference; this skill is the opinionated *how*. > > **Exception — seeded cases.** The case-write API can't hold any seeding mode, so > seeded cases are never pushed. A `seedThread` case is a local throwaway (don't -> commit it either — it dies when its trace is pruned); a `seedFile` or +> commit it either — it dies when its trace is pruned); a `conversationSeed` or > `priorConversation` case isn't transient and has no suite home, so it's the one > sanctioned exception — it lives as committed JSON. See [`case-shapes.md`](case-shapes.md). @@ -104,7 +104,7 @@ case can still assert outcome), but the primary shape drives the work. | **Build** (default) | Does the workflow the agent builds actually *work*? | `outcomeExpectations` + `executionScenarios` | | **Behaviour / process** | Does the agent *converse* correctly (ask the right clarifying question, not re-ask, honour a correction, respect plan approval)? | `processExpectations` + multi-turn director script; often **build-only** | | **Credential** | Does the build behave correctly given a specific credential view? | `credentials[]` | -| **Seeded** | Reproduce a conversation mid-thread and drive the turn under test | `seedThread` / `priorConversation` / `seedFile` | +| **Seeded** | Reproduce a conversation mid-thread and drive the turn under test | `seedThread` / `priorConversation` / `conversationSeed` | **Build** is documented in full below. The other three, the director-script vocabulary, and the seeding modes are in [`case-shapes.md`](case-shapes.md). @@ -207,7 +207,7 @@ calibration you hand the driver the thread link + login to review the real build case's home, not the repo. Leave the `data/workflows/*.json` file uncommitted (or delete it once it's in the suite). Committing new case JSONs into the repo is no longer the approach. (Exception: seeded cases can't be pushed — a - `seedFile`/`priorConversation` case stays committed JSON, a `seedThread` case is + `conversationSeed`/`priorConversation` case stays committed JSON, a `seedThread` case is a local throwaway; see [`case-shapes.md`](case-shapes.md).) For a sourced case, finish by **linking it to its source thread/finding** over the MCP — see [Link the pushed case to its source](#link-the-pushed-case-to-its-source-provenance-step--always-do-this). @@ -661,10 +661,10 @@ npx dotenvx run -f .env.eval -- pnpm eval:langtracer-push --suite baseline --cha deployment predating that change silently ignores the key; if a pushed scenario edit doesn't land, update the scenario in the lang-tracer UI. - **Seeded cases can't be pushed:** the case-write API rejects every seeding mode - (`seedThread` / `seedFile` / `priorConversation`), so the push lists them under + (`seedThread` / `conversationSeed` / `priorConversation`), so the push lists them under `skipped:` and they never reach the suite. A `seedThread` case shouldn't be committed either — it dies when its trace is pruned or deleted — so derive a - durable synthetic case as the artifact instead. A `seedFile`/`priorConversation` + durable synthetic case as the artifact instead. A `conversationSeed`/`priorConversation` case isn't transient and has no suite home, so it's the one exception to "don't commit the JSON" — it lives as a committed artifact. diff --git a/.agents/skills/create-instance-ai-eval/case-shapes.md b/.agents/skills/create-instance-ai-eval/case-shapes.md index ff10c7c27ef..a4ddbd37400 100644 --- a/.agents/skills/create-instance-ai-eval/case-shapes.md +++ b/.agents/skills/create-instance-ai-eval/case-shapes.md @@ -16,7 +16,7 @@ The schema ([`harness/schema.ts`](../../../packages/@n8n/instance-ai/evaluations/harness/schema.ts)) enforces the rules you must respect: -- `seedFile`, `priorConversation`, `seedThread` are **mutually exclusive** — pick +- `conversationSeed`, `priorConversation`, `seedThread` are **mutually exclusive** — pick one seeding mode. - A case needs a `conversation` **or** a `seedThread` (which supplies the live turn from the trace). @@ -149,7 +149,7 @@ Pick the lightest mode that fits: |---|---|---| | Reproduce a real conversation (common case) | `seedThread` — fetch + reconstruct its LangSmith trace at run time; nothing committed | supplies its own live turn (omit `conversation`) | | Prelude is just "what was discussed" (no tool calls, no workflows) | `priorConversation` — prose turns, authored inline | a normal `conversation` for the live turn | -| A synthetic/sanitised fixture you want durable in git | `seedFile` — a committed seed JSON (never real conversation data) | a normal `conversation` for the live turn | +| Prior work already exists (a workflow to repair) | `conversationSeed` — prior messages + the workflows they reference, in the case body | a normal `conversation` for the live turn | | Shallow 2–3 turn prelude where the agent's live replies matter | none — a plain multi-turn `conversation` re-drives it live | — | All three modes are implemented and wired (`harness/conversation-seed.ts` + @@ -175,7 +175,7 @@ after the correction. Asserting on the seeded prelude itself proves nothing. ### Which mode — and when to avoid seedThread Default to a **synthetic** case (an authored prompt + director script, or a -`priorConversation` / `seedFile` prelude): it's durable, carries no real user +`priorConversation` / `conversationSeed` prelude): it's durable, carries no real user data, never expires, and you control the setup exactly. Reach for **`seedThread`** only when the misbehaviour genuinely needs real prior context that's impractical to synthesize — a long accumulated thread, specific built workflows/tables — **and** @@ -236,10 +236,10 @@ which user turn goes live. (a synthetic case whose `executionScenarios` precondition builds the stand-in), or grade the live turn with `processExpectations` only. - **Can't be pushed to a lang-tracer suite either.** The case-write API rejects - every seeding mode (`seedThread` / `seedFile` / `priorConversation`), so + every seeding mode (`seedThread` / `conversationSeed` / `priorConversation`), so `eval:langtracer-push` silently lists them under `skipped:`. Combined with the don't-commit rule above, a `seedThread` case has **no durable home by design** — - the durable artifact is always the synthetic case you derive from it. (`seedFile` + the durable artifact is always the synthetic case you derive from it. (`conversationSeed` and `priorConversation` carry no thread dependency and can't be pushed either, so — unlike a normal case — they're the one exception to the skill's "push, don't commit the JSON" rule: they live as committed artifacts.) @@ -256,11 +256,20 @@ which user turn goes live. Plain text only — no tool calls, no restored workflows. Paired with a normal `conversation` for the live turn. -### `seedFile` — durable synthetic fixture +### `conversationSeed` — durable synthetic fixture -For a **synthetic, sanitised** fixture pinned in git (never a real user's -conversation): hand-author `data/workflows/seeds/.seed.json` (schema in +For a **synthetic, sanitised** seed pinned in git (never a real user's +conversation): author the prior messages, plus the workflows they reference, in +the case body (schema in [`harness/conversation-seed.ts`](../../../packages/@n8n/instance-ai/evaluations/harness/conversation-seed.ts) -— `messages` + optional `workflows` + `dataTables`) and point `seedFile` at it. -Real conversations belong in `seedThread`, which keeps their content out of the -repo. +— `messages` + optional `workflows` + `dataTables`). Real conversations belong in +`seedThread`, which keeps their content out of the repo. + +Two constraints that bite: a workflow `id` must be ≥8 characters (the id remap +refuses shorter ones), and a seeded `build-workflow` tool call's +`output.workflowId` must match the seeded workflow's `id` — otherwise the remap +separates them and the agent can't find the workflow it should act on. + +The seed sits in the case body, not a sibling file, so it travels with the case +whether it comes off disk, out of a LangTracer suite, or from a dispatched case +body. diff --git a/.agents/skills/create-instance-ai-eval/sourcing-cases.md b/.agents/skills/create-instance-ai-eval/sourcing-cases.md index 2b6a16ad6e8..aec94e5732d 100644 --- a/.agents/skills/create-instance-ai-eval/sourcing-cases.md +++ b/.agents/skills/create-instance-ai-eval/sourcing-cases.md @@ -56,7 +56,7 @@ connected.) 5. **Push it to a curated suite** (don't commit the JSON) with `eval:langtracer-push` — see [Push to a lang-tracer suite](SKILL.md#push-to-a-lang-tracer-suite). Exception: - seeded cases (`seedThread` / `seedFile` / `priorConversation`) can't be pushed — + seeded cases (`seedThread` / `conversationSeed` / `priorConversation`) can't be pushed — the case-write API rejects every seeding mode, so the push lists them under `skipped:`. And a `seedThread` case shouldn't be committed either — it dies when its trace is pruned or deleted — so it has no durable home; that's exactly why diff --git a/packages/@n8n/instance-ai/evaluations/README.md b/packages/@n8n/instance-ai/evaluations/README.md index d186a7bfbc3..a89dd573a46 100644 --- a/packages/@n8n/instance-ai/evaluations/README.md +++ b/packages/@n8n/instance-ai/evaluations/README.md @@ -672,7 +672,7 @@ To record an isolated cohort without touching the shared dataset or baseline — ## Adding test cases -The corpus lives in **LangTracer** — suite `baseline` is what CI runs. Author a case as a local JSON file in `evaluations/data/workflows/` (disk mode picks it up, no registration step), calibrate it against a real build, then push it to the suite with `pnpm eval:langtracer-push --suite baseline ` and delete the local file rather than committing it. Seeded cases (`priorConversation`/`seedFile`) are the exception — the case-write API can't represent them yet, so they stay as committed JSON. Every case is validated against `harness/schema.ts`. +The corpus lives in **LangTracer** — suite `baseline` is what CI runs. Author a case as a local JSON file in `evaluations/data/workflows/` (disk mode picks it up, no registration step), calibrate it against a real build, then push it to the suite with `pnpm eval:langtracer-push --suite baseline ` and delete the local file rather than committing it. Seeded cases (`priorConversation`/`conversationSeed`) are the exception — the case-write API can't represent them yet, so they stay as committed JSON. Every case is validated against `harness/schema.ts`. > The essentials are below. For the full authoring guide — picking a case archetype, sizing assertions so a wrong build fails, multi-turn director scripts, seeding vs synthetic, and calibrating against a real build — follow the [`create-instance-ai-eval` skill](../../../../.agents/skills/create-instance-ai-eval/SKILL.md) (with [`case-shapes.md`](../../../../.agents/skills/create-instance-ai-eval/case-shapes.md) and [`running-evals.md`](../../../../.agents/skills/create-instance-ai-eval/running-evals.md)). @@ -759,7 +759,7 @@ Pick the lightest path that fits: |---|---| | Reproduce a real conversation (the common case) | `seedThread` — fetch + reconstruct its LangSmith trace at run time; nothing committed | | Prelude is just "what was discussed" (no tool calls, no workflows) | `priorConversation` — prose turns, authored inline | -| A synthetic/sanitized fixture you want durable | `seedFile` — a committed seed JSON (no real conversation data) | +| Prior work already exists (a workflow to repair) | `conversationSeed` — prior messages + the workflows they reference, in the case body | | Shallow 2–3 turn prelude where the agent's live replies matter | Neither — a plain multi-turn `conversation` script re-drives it live | #### `seedThread` — reproduce a real conversation (no repo content) @@ -805,9 +805,20 @@ To find the thread id, open the conversation's trace in LangSmith (or read it fr Paired with a normal `conversation` for the live turn. Plain text only — no tool calls, no restored workflows. -#### `seedFile` — durable synthetic fixture +#### `conversationSeed` — durable synthetic fixture -For a **synthetic, sanitized** fixture you want pinned in git (never a real user's conversation): hand-author a `data/workflows/seeds/.seed.json` (schema in `harness/conversation-seed.ts` — `messages` + optional `workflows`) and point `seedFile` at it. Real conversations belong in `seedThread`, which keeps their content out of the repo entirely. Paired with a normal `conversation` for the live turn. +For a **synthetic, sanitized** seed you want pinned in git (never a real user's conversation): author the prior messages, plus the workflows they reference, in the case body. Real conversations belong in `seedThread`, which keeps their content out of the repo entirely. Paired with a normal `conversation` for the live turn. + +```json +"conversationSeed": { + "messages": [ … ], + "workflows": [ { "id": "wKk3RmT9xQ2bVn7L", "name": "Batch loop", "nodes": [], "connections": {} } ] +} +``` + +Schema in `harness/conversation-seed.ts` — `messages` plus optional `workflows` and `dataTables`. Two constraints worth knowing: a workflow `id` must be ≥8 characters (`remapSeedWorkflowIds` refuses to rewrite shorter ids safely), and a seeded `build-workflow` tool call's `output.workflowId` must match the seeded workflow's `id`, or the remap separates them and the agent can't find the workflow it's meant to act on. + +The seed lives **in the case body** rather than in a sibling file, so it travels with the case whatever the source — a JSON on disk, a suite pulled with `--source langtracer`, or a case body handed to a dispatcher. (There used to be a `seedFile` path pointing at a sibling JSON. Only the disk loader could resolve it, so a case delivered any other way lost its seed; the key is gone and a case still carrying it fails at load.) #### How restore works (all paths) @@ -816,7 +827,7 @@ At build time the seed is restored right after the credential pin: seeded workfl Rules of thumb: - **A seeded case is only worth shipping with `buildExpectations` that detect the misbehaviour recurring** — without them it passes vacuously. Sanity-check by running the case once with the seed removed: it should fail. -- `seedThread`, `priorConversation` and `seedFile` are mutually exclusive; all order strictly before the live turn. `seedThread` provides its own live turn (omit `conversation`); the other two pair with `conversation`. +- `seedThread`, `priorConversation` and `conversationSeed` are mutually exclusive; all order strictly before the live turn. `seedThread` provides its own live turn (omit `conversation`); the other two pair with `conversation`. ## Failure categories diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/build-workflow-conversation-seed.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/build-workflow-conversation-seed.test.ts new file mode 100644 index 00000000000..3ffa406264c --- /dev/null +++ b/packages/@n8n/instance-ai/evaluations/__tests__/build-workflow-conversation-seed.test.ts @@ -0,0 +1,135 @@ +import { vi } from 'vitest'; + +import type { N8nClient } from '../clients/n8n-client'; +import { buildWorkflow } from '../harness/build-workflow'; +import type { ConversationSeed } from '../harness/conversation-seed'; +import type { EvalLogger } from '../harness/logger'; + +// Stubbed as in build-workflow-seed-cleanup: only the restore call matters here. +vi.mock('../harness/chat-loop', () => ({ + SSE_SETTLE_DELAY_MS: 0, + startSseConnection: vi.fn().mockResolvedValue(undefined), + waitForAllActivity: vi.fn().mockResolvedValue(undefined), + runMultiTurnConversation: vi.fn().mockResolvedValue(undefined), + recordUserTurn: vi.fn(), +})); + +vi.mock('../outcome/workflow-discovery', () => ({ + buildAgentOutcome: vi.fn().mockResolvedValue({ + workflowsCreated: [{ id: 'built-wf-1', name: 'Built', nodeCount: 3, active: false }], + executionsRun: [], + dataTablesCreated: [], + finalText: 'done', + workflowJsons: [{ id: 'built-wf-1', name: 'Built', nodes: [], connections: {} }], + }), + extractWorkflowIdsFromMessages: vi.fn().mockReturnValue([]), +})); + +const silentLogger: EvalLogger = { + info: () => {}, + verbose: () => {}, + success: () => {}, + warn: () => {}, + error: () => {}, + isVerbose: false, +}; + +const SEED_WF_ID = 'wKk3RmT9xQ2bVn7L'; + +function inlineSeed(): ConversationSeed { + return { + messages: [ + { + id: 'seed-1', + type: 'llm', + role: 'assistant', + createdAt: '2026-06-29T09:00:01.000Z', + content: [ + { + type: 'tool-call', + toolCallId: 'tc-1', + toolName: 'build-workflow', + state: 'resolved', + input: {}, + output: { success: true, workflowId: SEED_WF_ID }, + }, + ], + }, + ], + workflows: [{ id: SEED_WF_ID, name: 'Batch loop', nodes: [], connections: {} }], + dataTables: [], + }; +} + +function makeClient(restoreThread: ReturnType): N8nClient { + return { + getPersonalProjectId: vi.fn().mockResolvedValue('project-1'), + ensureThread: vi.fn().mockResolvedValue(undefined), + setThreadCredentialAllowlist: vi.fn().mockResolvedValue(undefined), + sendMessage: vi.fn().mockResolvedValue(undefined), + getThreadMessages: vi.fn().mockResolvedValue({ messages: [] }), + restoreThread, + } as unknown as N8nClient; +} + +const baseConfig = { + conversation: [{ role: 'user' as const, text: 'the loop never runs twice — fix it' }], + outcomeExpectations: ['the processing branch hangs off the loop output'], + skipWorkflowChecks: true, + preRunWorkflowIds: new Set(), + claimedWorkflowIds: new Set(), + logger: silentLogger, +}; + +describe('buildWorkflow with a conversationSeed', () => { + it('restores the inline seed before the live turn', async () => { + const restoreThread = vi + .fn() + .mockResolvedValue({ restored: 1, workflowIds: ['restored-wf-1'], dataTableIds: [] }); + + const build = await buildWorkflow({ + client: makeClient(restoreThread), + ...baseConfig, + conversationSeed: inlineSeed(), + }); + + expect(build.success).toBe(true); + expect(restoreThread).toHaveBeenCalledTimes(1); + const [, messages, workflows] = restoreThread.mock.calls[0] as [ + string, + Array>, + Array<{ id: string }>, + ]; + expect(messages).toHaveLength(1); + expect(workflows).toHaveLength(1); + // Remapped to a fresh id so parallel iterations can't share one row. + const newId = workflows[0].id; + expect(newId).not.toBe(SEED_WF_ID); + expect(JSON.stringify(messages)).toContain(newId); + expect(JSON.stringify(messages)).not.toContain(SEED_WF_ID); + }); + + it('fails as a seeding problem when the restore fails — never runs unseeded', async () => { + const restoreThread = vi.fn().mockRejectedValue(new Error('restore rejected')); + + const build = await buildWorkflow({ + client: makeClient(restoreThread), + ...baseConfig, + conversationSeed: inlineSeed(), + }); + + expect(build.success).toBe(false); + expect(build.seedingFailed).toBe(true); + }); + + it('does not restore anything for a case with no seed', async () => { + const restoreThread = vi + .fn() + .mockResolvedValue({ restored: 0, workflowIds: [], dataTableIds: [] }); + + const build = await buildWorkflow({ client: makeClient(restoreThread), ...baseConfig }); + + expect(build.success).toBe(true); + expect(restoreThread).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/conversation-seed.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/conversation-seed.test.ts index 8550b0e2ab4..bb5d92b5d64 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/conversation-seed.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/conversation-seed.test.ts @@ -1,9 +1,4 @@ -import { mkdtempSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; - import { - loadConversationSeed, remapSeedWorkflowIds, seedFromProse, transcriptPrefixFromSeed, @@ -46,39 +41,6 @@ function makeSeed(): ConversationSeed { }; } -describe('loadConversationSeed', () => { - const dir = mkdtempSync(join(tmpdir(), 'conversation-seed-')); - - it('loads and validates a seed file', () => { - const path = join(dir, 'valid.seed.json'); - writeFileSync(path, JSON.stringify(makeSeed())); - - const seed = loadConversationSeed(path); - expect(seed.messages).toHaveLength(2); - expect(seed.workflows[0].id).toBe(WF_ID); - }); - - it('defaults workflows to an empty array', () => { - const path = join(dir, 'no-workflows.seed.json'); - writeFileSync(path, JSON.stringify({ messages: [{ id: 'm1' }] })); - - expect(loadConversationSeed(path).workflows).toEqual([]); - }); - - it('rejects a seed without messages, naming the file', () => { - const path = join(dir, 'empty.seed.json'); - writeFileSync(path, JSON.stringify({ messages: [] })); - - expect(() => loadConversationSeed(path)).toThrow(/Invalid conversation seed .*empty\.seed/); - }); - - it('rejects a missing file with a readable error', () => { - expect(() => loadConversationSeed(join(dir, 'nope.seed.json'))).toThrow( - /Failed to read conversation seed/, - ); - }); -}); - describe('seedFromProse', () => { it('converts turns to llm text messages with ascending past timestamps', () => { const seed = seedFromProse([ diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/data-workflows-schema.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/data-workflows-schema.test.ts index 3f6446c51a6..6167bad27ec 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/data-workflows-schema.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/data-workflows-schema.test.ts @@ -90,11 +90,41 @@ describe('EvalTestCaseSchema', () => { expect(parsed.priorConversation).toHaveLength(1); }); - it('rejects seedFile combined with priorConversation', () => { + it('rejects a case still pointing at a seedFile', () => { + expect(() => + EvalTestCaseSchema.parse({ ...validFixture(), seedFile: 'seeds/some-thread.seed.json' }), + ).toThrow(/seedFile/); + }); + + it('accepts an inline conversationSeed and defaults its optional arrays', () => { + const parsed = EvalTestCaseSchema.parse({ + ...validFixture(), + conversationSeed: { + messages: [ + { id: 'm1', type: 'llm', role: 'user', content: [{ type: 'text', text: 'build it' }] }, + ], + }, + }); + expect(parsed.conversationSeed?.messages).toHaveLength(1); + expect(parsed.conversationSeed?.workflows).toEqual([]); + expect(parsed.conversationSeed?.dataTables).toEqual([]); + }); + + it('rejects an inline conversationSeed with no messages', () => { + expect(() => + EvalTestCaseSchema.parse({ ...validFixture(), conversationSeed: { messages: [] } }), + ).toThrow(); + }); + + it('rejects conversationSeed combined with another seeding mode', () => { expect(() => EvalTestCaseSchema.parse({ ...validFixture(), - seedFile: 'seeds/some-thread.seed.json', + conversationSeed: { + messages: [ + { id: 'm1', type: 'llm', role: 'user', content: [{ type: 'text', text: 'build it' }] }, + ], + }, priorConversation: [{ role: 'user', text: 'prelude' }], }), ).toThrow(/mutually exclusive/); @@ -164,7 +194,7 @@ describe('EvalTestCaseSchema', () => { EvalTestCaseSchema.parse({ ...rest, seedThread: { threadId: 't1' }, - seedFile: 'seeds/x.seed.json', + priorConversation: [{ role: 'user', text: 'prelude' }], }), ).toThrow(/mutually exclusive/); }); diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-provider.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-provider.test.ts index 0c2e2087eb1..01841858490 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-provider.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-provider.test.ts @@ -29,6 +29,37 @@ describe('casesFromExportedFiles', () => { expect(cases[0].testCase.outcomeExpectations).toEqual(['has a trigger']); }); + it('keeps an inline conversationSeed on a suite-sourced case', () => { + const cases = casesFromExportedFiles( + { + 'repair-it.json': validCase({ + conversationSeed: { + messages: [ + { + id: 'm1', + type: 'llm', + role: 'user', + content: [{ type: 'text', text: 'build it' }], + }, + ], + workflows: [{ id: 'wKk3RmT9xQ2bVn7L', name: 'Batch loop', nodes: [], connections: {} }], + }, + }), + }, + { suite: 'demo' }, + ); + expect(cases[0].testCase.conversationSeed?.workflows[0].id).toBe('wKk3RmT9xQ2bVn7L'); + }); + + it('refuses a suite-sourced case that points at a seed FILE', () => { + expect(() => + casesFromExportedFiles( + { 'repair-it.json': validCase({ seedFile: 'repair-it.seed' }) }, + { suite: 'demo' }, + ), + ).toThrow(/seedFile/); + }); + it('aggregates validation errors and names the offending file', () => { expect(() => casesFromExportedFiles( diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-to-exported.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-to-exported.test.ts index 9e150f69875..b5af4d83913 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-to-exported.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/langtracer-to-exported.test.ts @@ -110,13 +110,33 @@ describe('unsupportedPushReason', () => { expect(unsupportedPushReason(diskCase())).toBeNull(); }); - it('flags seedThread, seedFile and priorConversation as unsupported', () => { + it('flags seedThread and priorConversation as unsupported', () => { expect(unsupportedPushReason(diskCase({ seedThread: { threadId: 't' } }))).toMatch( /seedThread/, ); - expect(unsupportedPushReason(diskCase({ seedFile: '/tmp/seed.json' }))).toMatch(/seedFile/); expect( unsupportedPushReason(diskCase({ priorConversation: [{ role: 'user', text: 'earlier' }] })), ).toMatch(/priorConversation/); }); + + it('flags an inline conversationSeed as unsupported', () => { + expect( + unsupportedPushReason( + diskCase({ + conversationSeed: { + messages: [ + { + id: 'm1', + type: 'llm', + role: 'user', + content: [{ type: 'text', text: 'build it' }], + }, + ], + workflows: [], + dataTables: [], + }, + }), + ), + ).toMatch(/conversationSeed/); + }); }); diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/mcp-builder.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/mcp-builder.test.ts index acf4d23abd4..4b88650da04 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/mcp-builder.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/mcp-builder.test.ts @@ -135,7 +135,18 @@ describe('unsupportedMcpBuildSetupFields', () => { it.each<[string, Partial]>([ ['credentials', { credentials: [{ type: 'slackApi' }] }], - ['seedFile', { seedFile: 'seeds/some-thread.seed.json' }], + [ + 'conversationSeed', + { + conversationSeed: { + messages: [ + { id: 'm1', type: 'llm', role: 'user', content: [{ type: 'text', text: 'build it' }] }, + ], + workflows: [], + dataTables: [], + }, + }, + ], ['priorConversation', { priorConversation: [user('We already agreed on #alerts')] }], ['seedThread', { seedThread: { threadId: 't1' } }], ])('flags %s', (field, overrides) => { diff --git a/packages/@n8n/instance-ai/evaluations/cli/mcp-builder.ts b/packages/@n8n/instance-ai/evaluations/cli/mcp-builder.ts index f8a5cbe5f22..22551ca93d7 100644 --- a/packages/@n8n/instance-ai/evaluations/cli/mcp-builder.ts +++ b/packages/@n8n/instance-ai/evaluations/cli/mcp-builder.ts @@ -166,7 +166,7 @@ export const MCP_BUILD_KEY_SUPPORT: Record< // reach this check; classified only to keep the map schema-complete. buildExpectations: 'supported', credentials: 'orchestrator-only', - seedFile: 'orchestrator-only', + conversationSeed: 'orchestrator-only', priorConversation: 'orchestrator-only', seedThread: 'orchestrator-only', datasets: 'supported', diff --git a/packages/@n8n/instance-ai/evaluations/harness/build-workflow.ts b/packages/@n8n/instance-ai/evaluations/harness/build-workflow.ts index 73bad8cd4a7..a96c29001d5 100644 --- a/packages/@n8n/instance-ai/evaluations/harness/build-workflow.ts +++ b/packages/@n8n/instance-ai/evaluations/harness/build-workflow.ts @@ -21,7 +21,6 @@ import { } from './chat-loop'; import { runWorkflowChecks, summarizeMissingWorkflowError } from './cleanup'; import { - loadConversationSeed, remapSeedWorkflowIds, seedFromProse, transcriptPrefixFromSeed, @@ -192,8 +191,8 @@ export interface BuildWorkflowConfig { credentials?: TestCaseCredential[]; /** Run-level registry the created credential IDs are added to for cleanup. */ createdCredentialIds?: Set; - /** Synthetic seed file (path) restored before the live message. */ - seedFile?: string; + /** Prior messages + workflows restored before the live message. */ + conversationSeed?: ConversationSeed; /** Prose turns seeded as plain-text history. */ priorConversation?: ConversationTurn[]; /** Reproduce a real conversation from its LangSmith trace (seed = before the @@ -299,8 +298,8 @@ export async function buildWorkflow(config: BuildWorkflowConfig): Promise 0) { seed = seedFromProse(config.priorConversation); } diff --git a/packages/@n8n/instance-ai/evaluations/harness/conversation-seed.ts b/packages/@n8n/instance-ai/evaluations/harness/conversation-seed.ts index 03dedd19f11..a9ba9b76e90 100644 --- a/packages/@n8n/instance-ai/evaluations/harness/conversation-seed.ts +++ b/packages/@n8n/instance-ai/evaluations/harness/conversation-seed.ts @@ -1,12 +1,11 @@ -// Conversation seeding for eval builds — backs the `seedFile` (synthetic) and -// `priorConversation` (prose) paths. Real conversations use `seedThread` +// Conversation seeding for eval builds — backs the `conversationSeed` (synthetic) +// and `priorConversation` (prose) paths. Real conversations use `seedThread` // (reconstructed from a LangSmith trace; see langsmith-seed.ts). import { generateNanoId } from '@n8n/utils/generate-nano-id'; import { isRecord } from '@n8n/utils/is-record'; import { jsonParse } from 'n8n-workflow'; import { randomUUID } from 'node:crypto'; -import { readFileSync } from 'node:fs'; import { z } from 'zod'; import { DOMAIN_TOOL_IDS, ORCHESTRATION_TOOL_IDS } from '../../src/tools/tool-ids'; @@ -20,7 +19,7 @@ import { import type { ConversationTurn, TranscriptStep, TranscriptTurn } from '../types'; // --------------------------------------------------------------------------- -// Seed file schema +// Seed schema // --------------------------------------------------------------------------- const SeedWorkflowSchema = z.object({ @@ -57,25 +56,6 @@ export const ConversationSeedSchema = z.object({ export type ConversationSeed = z.infer; -export function loadConversationSeed(filePath: string): ConversationSeed { - let raw: unknown; - try { - raw = JSON.parse(readFileSync(filePath, 'utf-8')); - } catch (error) { - throw new Error( - `Failed to read conversation seed ${filePath}: ${error instanceof Error ? error.message : String(error)}`, - ); - } - const parsed = ConversationSeedSchema.safeParse(raw); - if (!parsed.success) { - const issues = parsed.error.issues - .map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`) - .join('\n'); - throw new Error(`Invalid conversation seed ${filePath}:\n${issues}`); - } - return parsed.data; -} - // --------------------------------------------------------------------------- // Prose prior turns → seed messages // --------------------------------------------------------------------------- diff --git a/packages/@n8n/instance-ai/evaluations/harness/schema.ts b/packages/@n8n/instance-ai/evaluations/harness/schema.ts index 2ab5cae2425..410be70c2a4 100644 --- a/packages/@n8n/instance-ai/evaluations/harness/schema.ts +++ b/packages/@n8n/instance-ai/evaluations/harness/schema.ts @@ -1,6 +1,7 @@ import { instanceAiEvalSeedDataTableSchema } from '@n8n/api-types'; import { z } from 'zod'; +import { ConversationSeedSchema } from './conversation-seed'; import { SUPPORTED_CREDENTIAL_TYPES } from '../credentials/seeder'; /** Default `datasets` grouping for a case that omits the field — the single @@ -86,9 +87,9 @@ const evalTestCaseObjectSchema = z }), ) .optional(), - /** Synthetic seed file (relative path), resolved + validated at case load. + /** Prior messages + the workflows they reference, restored before the live turn. * Synthetic fixtures only; real conversations use `seedThread`. */ - seedFile: z.string().min(1).optional(), + conversationSeed: ConversationSeedSchema.optional(), /** Prose turns seeded as plain-text history (no tool calls / workflows). */ priorConversation: z.array(ConversationTurnSchema).min(1).optional(), /** Reproduce a real conversation from its LangSmith trace at run time (seed = @@ -130,10 +131,13 @@ export const WORKFLOW_TEST_CASE_KEYS = Object.keys(evalTestCaseObjectSchema.shap // At most one seeding mode, and a source for the live turn. export const EvalTestCaseSchema = evalTestCaseObjectSchema - .refine((c) => [c.seedFile, c.priorConversation, c.seedThread].filter(Boolean).length <= 1, { - message: - 'seedFile, priorConversation and seedThread are mutually exclusive — pick one seeding mode', - }) + .refine( + (c) => [c.conversationSeed, c.priorConversation, c.seedThread].filter(Boolean).length <= 1, + { + message: + 'conversationSeed, priorConversation and seedThread are mutually exclusive — pick one seeding mode', + }, + ) .refine((c) => c.seedThread !== undefined || c.conversation !== undefined, { message: 'a case needs a conversation, or a seedThread (which supplies the live turn from the trace)', diff --git a/packages/@n8n/instance-ai/evaluations/langtracer/provider.ts b/packages/@n8n/instance-ai/evaluations/langtracer/provider.ts index 70abaec0550..a6e53241493 100644 --- a/packages/@n8n/instance-ai/evaluations/langtracer/provider.ts +++ b/packages/@n8n/instance-ai/evaluations/langtracer/provider.ts @@ -1,6 +1,8 @@ // lang-tracer test-case provider; `casesFromExportedFiles` is split from the network // call so the normalize + validate contract is unit-testable without a server. +import { isRecord } from '@n8n/utils/is-record'; + import { LangTracerClient, type ExportedSuite } from './client'; import { resolveLangTracerConfig } from './config'; import { normalizeExportedCase } from './normalize'; @@ -30,6 +32,14 @@ export function casesFromExportedFiles( for (const [filename, raw] of Object.entries(files)) { const fileSlug = filename.replace(/\.json$/i, ''); + // Checked on the RAW body: the normalizer whitelists to the schema's keys, so a + // leftover `seedFile` would be stripped and the case would run unseeded. + if (isRecord(raw) && raw.seedFile !== undefined) { + errors.push( + `${filename}:\n - seedFile: no longer supported — carry the seed in the case body as \`conversationSeed\``, + ); + continue; + } const parsed = EvalTestCaseSchema.safeParse(normalizeExportedCase(raw)); if (!parsed.success) { const issues = parsed.error.issues diff --git a/packages/@n8n/instance-ai/evaluations/langtracer/to-exported.ts b/packages/@n8n/instance-ai/evaluations/langtracer/to-exported.ts index 56fd768e94d..f21f93fdd70 100644 --- a/packages/@n8n/instance-ai/evaluations/langtracer/to-exported.ts +++ b/packages/@n8n/instance-ai/evaluations/langtracer/to-exported.ts @@ -43,7 +43,9 @@ export interface ToLangTracerOptions { * using any of them can't be pushed. Returns a human-readable reason, else null. */ export function unsupportedPushReason(testCase: EvalTestCaseInput): string | null { if (testCase.seedThread) return 'uses seedThread (not supported by the case-write API)'; - if (testCase.seedFile) return 'uses seedFile (not supported by the case-write API)'; + if (testCase.conversationSeed) { + return 'uses conversationSeed (not supported by the case-write API)'; + } if (testCase.priorConversation) { return 'uses priorConversation (not supported by the case-write API)'; } diff --git a/packages/@n8n/instance-ai/evaluations/outcome/event-parser.ts b/packages/@n8n/instance-ai/evaluations/outcome/event-parser.ts index 1c5b5b7debb..c498adbb9f8 100644 --- a/packages/@n8n/instance-ai/evaluations/outcome/event-parser.ts +++ b/packages/@n8n/instance-ai/evaluations/outcome/event-parser.ts @@ -4,7 +4,11 @@ import { isRecord } from '@n8n/utils/is-record'; -import { DATA_TABLES_TOOL_ID, DOMAIN_TOOL_IDS, EVAL_CONFIG_TOOL_ID } from '../../src/tools/tool-ids'; +import { + DATA_TABLES_TOOL_ID, + DOMAIN_TOOL_IDS, + EVAL_CONFIG_TOOL_ID, +} from '../../src/tools/tool-ids'; import type { AgentActivity, ArtifactRef, diff --git a/packages/@n8n/instance-ai/evaluations/run/build-orchestrator.ts b/packages/@n8n/instance-ai/evaluations/run/build-orchestrator.ts index 2018709a581..7e8e46a48d1 100644 --- a/packages/@n8n/instance-ai/evaluations/run/build-orchestrator.ts +++ b/packages/@n8n/instance-ai/evaluations/run/build-orchestrator.ts @@ -75,7 +75,7 @@ export type BuildArgs = Pick< | 'conversation' | 'messageBudget' | 'credentials' - | 'seedFile' + | 'conversationSeed' | 'priorConversation' | 'seedThread' | 'executionScenarios' @@ -450,7 +450,7 @@ export function createBuildOrchestrator(deps: BuildOrchestratorDeps): BuildOrche conversation: entry.conversation, messageBudget: entry.messageBudget, credentials: entry.credentials, - seedFile: entry.seedFile, + conversationSeed: entry.conversationSeed, priorConversation: entry.priorConversation, seedThread: entry.seedThread, executionScenarios: entry.executionScenarios, diff --git a/packages/@n8n/instance-ai/evaluations/run/eval-session.ts b/packages/@n8n/instance-ai/evaluations/run/eval-session.ts index 93b472a6679..ed228f16aad 100644 --- a/packages/@n8n/instance-ai/evaluations/run/eval-session.ts +++ b/packages/@n8n/instance-ai/evaluations/run/eval-session.ts @@ -140,7 +140,7 @@ export function createEvalSession(config: EvalSessionConfig): EvalSession { conversation: buildArgs.conversation, messageBudget: buildArgs.messageBudget, credentials: buildArgs.credentials, - seedFile: buildArgs.seedFile, + conversationSeed: buildArgs.conversationSeed, priorConversation: buildArgs.priorConversation, seedThread: buildArgs.seedThread, executionScenarios: buildArgs.executionScenarios, diff --git a/packages/@n8n/instance-ai/evaluations/types.ts b/packages/@n8n/instance-ai/evaluations/types.ts index acabb25f8f1..ba5efca43b9 100644 --- a/packages/@n8n/instance-ai/evaluations/types.ts +++ b/packages/@n8n/instance-ai/evaluations/types.ts @@ -11,6 +11,7 @@ import type { import type { CheckOutcome } from './binaryChecks/types'; import type { WorkflowResponse } from './clients/n8n-client'; +import type { ConversationSeed } from './harness/conversation-seed'; // --------------------------------------------------------------------------- // Checklist items and verification @@ -238,11 +239,11 @@ export interface WorkflowTestCase { * field build with an empty view (everything mocks). */ credentials?: TestCaseCredential[]; - /** Synthetic seed file (messages + workflows) restored before the live turn. - * Synthetic fixtures only; mutually exclusive with the other seeds. */ - seedFile?: string; + /** Prior messages + the workflows they reference, restored before the live turn. + * Mutually exclusive with the other seeds. */ + conversationSeed?: ConversationSeed; /** Prose turns seeded as plain-text history (no tool calls/workflows). - * Mutually exclusive with `seedFile`. */ + * Mutually exclusive with the other seeds. */ priorConversation?: ConversationTurn[]; /** Reproduce a real conversation from its LangSmith trace at run time: restore * up to the live turn (the last user message, or one pinned by `liveTurnRunId`) diff --git a/packages/@n8n/instance-ai/evaluations/utils/load-eval-cases.ts b/packages/@n8n/instance-ai/evaluations/utils/load-eval-cases.ts index f36c61b4eea..3af860a5c2c 100644 --- a/packages/@n8n/instance-ai/evaluations/utils/load-eval-cases.ts +++ b/packages/@n8n/instance-ai/evaluations/utils/load-eval-cases.ts @@ -1,8 +1,7 @@ import { readFileSync } from 'fs'; -import { basename, dirname, resolve } from 'path'; +import { basename } from 'path'; import { getJsonFiles } from './get-json-files'; -import { loadConversationSeed } from '../harness/conversation-seed'; import { EvalTestCaseSchema } from '../harness/schema'; import type { WorkflowTestCase } from '../types'; @@ -32,21 +31,7 @@ function parseTestCaseFile(filePath: string): WorkflowTestCase { throw new Error(`Invalid test case ${filePath}:\n${issues}`); } - const testCase = parsed.data; - if (testCase.seedFile) { - // Resolve relative to the case file and validate now, so an authoring - // typo fails at load time instead of per-build as an agent failure. - const resolved = resolve(dirname(filePath), testCase.seedFile); - try { - loadConversationSeed(resolved); - } catch (error) { - throw new Error( - `Invalid test case ${filePath}: ${error instanceof Error ? error.message : String(error)}`, - ); - } - testCase.seedFile = resolved; - } - return testCase; + return parsed.data; } /** Load test cases with their file slugs (for LangSmith dataset sync derived IDs). */ From b964ed6f4b572a9743bf46d97008528d861d0cdf Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:47:18 +0300 Subject: [PATCH 007/130] refactor(core): Move sleep into @n8n/utils and unify sleep/sleepWithAbort (#35057) --- packages/@n8n/eslint-config/src/plugin.ts | 2 + .../@n8n/eslint-config/src/rules/index.ts | 4 ++ .../no-restricted-sleep-definition.test.ts | 43 ++++++++++++ .../rules/no-restricted-sleep-definition.ts | 51 ++++++++++++++ .../rules/no-restricted-sleep-import.test.ts | 32 +++++++++ .../src/rules/no-restricted-sleep-import.ts | 48 +++++++++++++ .../evaluations/checklist/verifier.ts | 5 +- .../workspace/builder-templates-service.ts | 5 +- .../instance-ai/src/workspace/sandbox-fs.ts | 4 +- .../src/workspace/snapshot-manager.ts | 4 +- .../Agent/agents/ToolsAgent/V2/execute.ts | 3 +- .../Agent/agents/ToolsAgent/V3/execute.ts | 3 +- .../test/ToolsAgent/ToolsAgentV3.test.ts | 12 ++-- .../nodes/chains/ChainLLM/ChainLlm.node.ts | 3 +- .../ChainRetrievalQA/ChainRetrievalQa.node.ts | 3 +- .../V2/ChainSummarizationV2.node.ts | 3 +- .../InformationExtractor.node.ts | 3 +- .../SentimentAnalysis.node.ts | 3 +- .../TextClassifier/TextClassifier.node.ts | 3 +- .../test/TextClassifier.node.test.ts | 16 ++--- .../ToolWorkflow/v2/ToolWorkflowV2.test.ts | 29 ++++---- .../v2/utils/WorkflowToolService.ts | 4 +- .../AlibabaCloud/test/transport.test.ts | 10 +-- .../vendors/AlibabaCloud/transport/index.ts | 3 +- .../vendors/MiniMax/test/transport.test.ts | 10 +-- .../nodes/vendors/MiniMax/transport/index.ts | 3 +- packages/@n8n/utils/src/sleep.test.ts | 68 +++++++++++++++++++ packages/@n8n/utils/src/sleep.ts | 30 ++++++++ .../src/__tests__/active-executions.test.ts | 5 +- .../__tests__/active-workflow-manager.test.ts | 3 +- packages/cli/src/active-executions.ts | 7 +- packages/cli/src/commands/base-command.ts | 3 +- packages/cli/src/commands/start.ts | 3 +- .../__tests__/concurrency-queue.test.ts | 2 +- packages/cli/src/crash-journal.ts | 2 +- .../executions/execution-recovery.service.ts | 3 +- .../platforms/slack-bridge-behavior.ts | 23 ++----- .../chat-hub/chat-hub-execution.service.ts | 2 +- .../insights/insights-compaction.service.ts | 2 +- .../__tests__/credit-counting.test.ts | 3 +- .../instance-ai/instance-ai-credit.service.ts | 3 +- packages/cli/src/scaling/scaling.service.ts | 3 +- .../cache/__tests__/cache.service.test.ts | 2 +- .../workflow-history-compaction.service.ts | 3 +- .../task-broker-auth.service.test.ts | 2 +- .../task-broker/task-broker-ws-server.ts | 3 +- .../src/task-runners/task-runner-module.ts | 2 +- packages/cli/src/utils/ai-service-retry.ts | 5 +- packages/cli/src/wait-tracker.ts | 2 +- .../trigger-activation-retry.test.ts | 3 +- .../trigger-execution-context.factory.test.ts | 3 +- .../workflow-trigger-activator.test.ts | 3 +- .../triggers/trigger-activation-retry.ts | 3 +- .../database/services/db-lock.service.test.ts | 3 +- .../scheduling/scheduler-benchmarks.test.ts | 3 +- ...orkflow-history-compaction.service.test.ts | 3 +- .../__tests__/routing-node.test.ts | 4 +- .../get-input-connection-data.test.ts | 41 ++++++----- .../utils/get-input-connection-data.ts | 4 +- .../utils/request-helpers/pagination.ts | 3 +- .../core/src/execution-engine/routing-node.ts | 2 +- .../src/execution-engine/workflow-execute.ts | 2 +- .../@n8n/rest-api-client/src/api/nodeTypes.ts | 2 +- .../components/InstanceAiPreviewCanvas.vue | 6 +- .../components/WorkflowPreviewCanvas.vue | 4 +- .../features/credentials/credentials.api.ts | 2 +- .../databricksSql/executeQuery.operation.ts | 3 +- .../Databricks/test/Databricks.node.test.ts | 13 ++-- .../test/v2/transport/discord.api.test.ts | 7 +- .../nodes/Discord/v1/DiscordV1.node.ts | 10 +-- .../nodes/Discord/v2/transport/helpers.ts | 2 +- .../database/executeQuery.operation.ts | 3 +- .../nodes/Google/Calendar/GenericFunctions.ts | 4 +- .../HttpRequest/V1/HttpRequestV1.node.ts | 3 +- .../HttpRequest/V2/HttpRequestV2.node.ts | 3 +- .../HttpRequest/V3/HttpRequestV3.node.ts | 2 +- .../Kafka/test/KafkaTrigger.node.test.ts | 11 +-- .../nodes-base/nodes/Kafka/test/utils.test.ts | 14 ++-- packages/nodes-base/nodes/Kafka/utils.ts | 3 +- .../Microsoft/Teams/v2/methods/listSearch.ts | 2 +- .../nodes/RabbitMQ/GenericFunctions.ts | 3 +- .../nodes/Simulate/Simulate.node.ts | 4 +- .../nodes/Simulate/SimulateTrigger.node.ts | 4 +- .../nodes/Slack/V2/GenericFunctions.ts | 3 +- .../Slack/test/v2/GenericFunctions.test.ts | 6 +- .../nodes/Splunk/v1/GenericFunctions.ts | 3 +- .../nodes/Splunk/v2/transport/index.ts | 3 +- .../nodes/Twitter/V1/GenericFunctions.ts | 4 +- .../nodes-base/nodes/Zoom/GenericFunctions.ts | 11 +-- .../testing/containers/pull-test-images.ts | 4 +- packages/testing/playwright/package.json | 1 + .../schedule-trigger-durable.spec.ts | 4 +- .../schedule-trigger-multimain.spec.ts | 4 +- packages/workflow/src/index.ts | 1 - packages/workflow/src/utils.ts | 26 ++----- packages/workflow/test/utils.test.ts | 67 ------------------ pnpm-lock.yaml | 3 + 97 files changed, 489 insertions(+), 323 deletions(-) create mode 100644 packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.test.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.test.ts create mode 100644 packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.ts create mode 100644 packages/@n8n/utils/src/sleep.test.ts create mode 100644 packages/@n8n/utils/src/sleep.ts diff --git a/packages/@n8n/eslint-config/src/plugin.ts b/packages/@n8n/eslint-config/src/plugin.ts index 91bfa09ca2f..0f0d383d0e3 100644 --- a/packages/@n8n/eslint-config/src/plugin.ts +++ b/packages/@n8n/eslint-config/src/plugin.ts @@ -28,6 +28,8 @@ export const localRulesPlugin = { 'n8n-local-rules/no-type-only-import-in-di': 'error', 'n8n-local-rules/no-aws-credential-discovery-imports': 'error', 'n8n-local-rules/no-application-error': 'error', + 'n8n-local-rules/no-restricted-sleep-definition': 'error', + 'n8n-local-rules/no-restricted-sleep-import': 'error', }, }, }, diff --git a/packages/@n8n/eslint-config/src/rules/index.ts b/packages/@n8n/eslint-config/src/rules/index.ts index 4862dbaad06..ac48f15be11 100644 --- a/packages/@n8n/eslint-config/src/rules/index.ts +++ b/packages/@n8n/eslint-config/src/rules/index.ts @@ -23,6 +23,8 @@ import { NoAwsCredentialDiscoveryImportsRule } from './no-aws-credential-discove import { NoUncentralizedHttpRule } from './no-uncentralized-http.js'; import { NoApplicationErrorRule } from './no-application-error.js'; import { ProjectOwnedEntityTransferRule } from './project-owned-entity-transfer.js'; +import { NoRestrictedSleepDefinitionRule } from './no-restricted-sleep-definition.js'; +import { NoRestrictedSleepImportRule } from './no-restricted-sleep-import.js'; import { NoRepositoryInPublicApiHandlerRule } from './no-repository-in-public-api-handler.js'; import { RequirePublicApiControllerRule } from './require-public-api-controller.js'; import { NoPublicApiGuardrailDisableRule } from './no-public-api-guardrail-disable.js'; @@ -52,6 +54,8 @@ export const rules = { 'no-uncentralized-http': NoUncentralizedHttpRule, 'no-application-error': NoApplicationErrorRule, 'project-owned-entity-transfer': ProjectOwnedEntityTransferRule, + 'no-restricted-sleep-definition': NoRestrictedSleepDefinitionRule, + 'no-restricted-sleep-import': NoRestrictedSleepImportRule, 'no-repository-in-public-api-handler': NoRepositoryInPublicApiHandlerRule, 'require-public-api-controller': RequirePublicApiControllerRule, 'no-public-api-guardrail-disable': NoPublicApiGuardrailDisableRule, diff --git a/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.test.ts b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.test.ts new file mode 100644 index 00000000000..e3e5a998c56 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.test.ts @@ -0,0 +1,43 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { NoRestrictedSleepDefinitionRule } from './no-restricted-sleep-definition.js'; + +const ruleTester = new RuleTester(); + +ruleTester.run('no-restricted-sleep-definition', NoRestrictedSleepDefinitionRule, { + valid: [ + { code: 'import { sleep } from "@n8n/utils/sleep"' }, + { code: 'import { sleep } from "n8n-workflow"' }, + { code: 'import { sleep } from "zx"' }, + { code: 'import { sleep } from "./sleep"' }, + { code: 'import { retry } from "@n8n/utils/retry"' }, + { code: 'import { something } from "n8n-workflow"' }, + { code: 'function sleep() {}', filename: '/repo/packages/@n8n/utils/src/sleep.ts' }, + { + code: 'const sleepWithAbort = () => {};', + filename: '/repo/packages/@n8n/utils/src/sleep.ts', + }, + { + code: 'function sleep(ms: number) {}', + filename: '/repo/packages/@n8n/typeorm/test/utils/test-utils.ts', + }, + { + code: 'export async function sleep(ms: number) {}', + filename: '/repo/packages/@n8n/node-cli/src/commands/dev/utils.ts', + }, + ], + + invalid: [ + { + code: 'function sleep(ms: number) {}', + errors: [{ messageId: 'noRestrictedSleepDefinition' }], + }, + { + code: 'async function sleepWithAbort(ms: number, signal: AbortSignal) {}', + errors: [{ messageId: 'noRestrictedSleepDefinition' }], + }, + { + code: 'const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));', + errors: [{ messageId: 'noRestrictedSleepDefinition' }], + }, + ], +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.ts b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.ts new file mode 100644 index 00000000000..feb7c328fa7 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-definition.ts @@ -0,0 +1,51 @@ +import { ESLintUtils, TSESTree } from '@typescript-eslint/utils'; + +const RESTRICTED_NAMES = new Set(['sleep', 'sleepWithAbort']); +const CANONICAL_FILE = /\/@n8n\/utils\/src\/sleep\.ts$/; +const EXEMPT_PACKAGES = [/\/@n8n\/typeorm\//, /\/@n8n\/node-cli\//]; + +export const NoRestrictedSleepDefinitionRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Disallow local definitions of `sleep` and `sleepWithAbort`.', + }, + messages: { + noRestrictedSleepDefinition: + 'Do not define your own `{{ name }}`. Import `sleep` from `@n8n/utils/sleep` instead.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const filename = context.filename.replace(/\\/g, '/'); + if (CANONICAL_FILE.test(filename) || EXEMPT_PACKAGES.some((re) => re.test(filename))) { + return {}; + } + + return { + FunctionDeclaration(node) { + if (node.id && RESTRICTED_NAMES.has(node.id.name)) { + context.report({ + node: node.id, + messageId: 'noRestrictedSleepDefinition', + data: { name: node.id.name }, + }); + } + }, + + VariableDeclarator(node) { + if ( + node.id.type === TSESTree.AST_NODE_TYPES.Identifier && + RESTRICTED_NAMES.has(node.id.name) + ) { + context.report({ + node: node.id, + messageId: 'noRestrictedSleepDefinition', + data: { name: node.id.name }, + }); + } + }, + }; + }, +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.test.ts b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.test.ts new file mode 100644 index 00000000000..82bdeac9398 --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.test.ts @@ -0,0 +1,32 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { NoRestrictedSleepImportRule } from './no-restricted-sleep-import.js'; + +const ruleTester = new RuleTester(); + +ruleTester.run('no-restricted-sleep-import', NoRestrictedSleepImportRule, { + valid: [ + { code: 'import { sleep } from "@n8n/utils/sleep"' }, + { code: 'import { sleep } from "zx"' }, + { code: 'import { sleep } from "./sleep"' }, + { code: 'import { jsonParse } from "n8n-workflow"' }, + // Namespace imports say nothing about what they use — the generated + // `vi.importActual` shape relies on this. + { code: 'import type * as _importType0 from "n8n-workflow"' }, + { code: 'import * as n8nWorkflow from "n8n-workflow"' }, + ], + + invalid: [ + { + code: 'import { sleep } from "n8n-workflow"', + errors: [{ messageId: 'noRestrictedSleepImport' }], + }, + { + code: 'import { sleep as delay } from "n8n-workflow"', + errors: [{ messageId: 'noRestrictedSleepImport' }], + }, + { + code: 'import { jsonParse, sleep } from "n8n-workflow"', + errors: [{ messageId: 'noRestrictedSleepImport' }], + }, + ], +}); diff --git a/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.ts b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.ts new file mode 100644 index 00000000000..12e5c0e61dc --- /dev/null +++ b/packages/@n8n/eslint-config/src/rules/no-restricted-sleep-import.ts @@ -0,0 +1,48 @@ +// `n8n-workflow` re-exports `sleep` only as a backwards-compat layer for community +// nodes. Internal code must reach the canonical implementation in `@n8n/utils/sleep` +// directly, so the shim stays inert and can be dropped once it is no longer needed. +// +// Scope (intentional): only a named `sleep` specifier is reported. A namespace import +// (`import * as x from 'n8n-workflow'`) is left alone — it is the generated +// `import type * as _importType0` shape used with `vi.importActual`, and flagging it +// would report every namespace import of the package regardless of what it uses. +import { ESLintUtils } from '@typescript-eslint/utils'; + +const RESTRICTED_SOURCE = 'n8n-workflow'; + +export const NoRestrictedSleepImportRule = ESLintUtils.RuleCreator.withoutDocs({ + meta: { + type: 'problem', + docs: { + description: 'Disallow importing `sleep` from `n8n-workflow`.', + }, + messages: { + noRestrictedSleepImport: + 'Do not import `sleep` from `n8n-workflow`. Import it from `@n8n/utils/sleep` instead.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + return { + ImportDeclaration(node) { + if (node.source.value !== RESTRICTED_SOURCE) return; + + for (const specifier of node.specifiers) { + if (specifier.type !== 'ImportSpecifier') continue; + + // `imported` is the name in `n8n-workflow`, so an alias + // (`sleep as delay`) is still reported. + const importedName = + specifier.imported.type === 'Identifier' + ? specifier.imported.name + : specifier.imported.value; + + if (importedName === 'sleep') { + context.report({ node: specifier, messageId: 'noRestrictedSleepImport' }); + } + } + }, + }; + }, +}); diff --git a/packages/@n8n/instance-ai/evaluations/checklist/verifier.ts b/packages/@n8n/instance-ai/evaluations/checklist/verifier.ts index 71723e16e0d..edf58839959 100644 --- a/packages/@n8n/instance-ai/evaluations/checklist/verifier.ts +++ b/packages/@n8n/instance-ai/evaluations/checklist/verifier.ts @@ -1,4 +1,5 @@ import type { Message, StreamChunk } from '@n8n/agents'; +import { sleep } from '@n8n/utils/sleep'; import { z } from 'zod'; import { @@ -41,10 +42,6 @@ function jitteredPauseMs(attempt: number): number { return 1_000 * attempt + Math.random() * 1_000; } -async function sleep(ms: number): Promise { - await new Promise((resolve) => setTimeout(resolve, ms)); -} - export interface VerifierAttemptDebug { attempt: number; status: 'threw' | 'model_error' | 'no_parseable_results' | 'success'; diff --git a/packages/@n8n/instance-ai/src/workspace/builder-templates-service.ts b/packages/@n8n/instance-ai/src/workspace/builder-templates-service.ts index fecd7785bee..ec1a85f7c15 100644 --- a/packages/@n8n/instance-ai/src/workspace/builder-templates-service.ts +++ b/packages/@n8n/instance-ai/src/workspace/builder-templates-service.ts @@ -45,6 +45,7 @@ * * Never throws. */ +import { sleep } from '@n8n/utils/sleep'; import workflowSdkPackage from '@n8n/workflow-sdk/package.json'; import { createHash } from 'node:crypto'; import * as fsp from 'node:fs/promises'; @@ -542,10 +543,6 @@ function isRetryableStatus(status: number): boolean { return status === 408 || status === 429; } -async function sleep(ms: number): Promise { - await new Promise((resolve) => setTimeout(resolve, ms)); -} - async function atomicWriteFile(target: string, contents: Buffer | string): Promise { const tmp = `${target}.tmp-${process.pid}-${Date.now()}`; try { diff --git a/packages/@n8n/instance-ai/src/workspace/sandbox-fs.ts b/packages/@n8n/instance-ai/src/workspace/sandbox-fs.ts index 6efcac05a41..95598e0ab52 100644 --- a/packages/@n8n/instance-ai/src/workspace/sandbox-fs.ts +++ b/packages/@n8n/instance-ai/src/workspace/sandbox-fs.ts @@ -57,7 +57,7 @@ function ioResourceLabel(options?: SandboxIoRetryOptions): string { return options?.resourceLabel ?? 'Sandbox file'; } -async function sleep(ms: number, abortSignal?: AbortSignal): Promise { +async function sleepUntilAbort(ms: number, abortSignal?: AbortSignal): Promise { throwIfAborted(abortSignal); await new Promise((resolve, reject) => { const timer = setTimeout(() => { @@ -96,7 +96,7 @@ export async function retryTransientSandboxIo( attempt, error: formatErrorForLog(error), }); - await sleep( + await sleepUntilAbort( Math.min(baseMs * 2 ** (attempt - 1), SANDBOX_IO_RETRY_BACKOFF_CAP_MS), options?.abortSignal, ); diff --git a/packages/@n8n/instance-ai/src/workspace/snapshot-manager.ts b/packages/@n8n/instance-ai/src/workspace/snapshot-manager.ts index 47089493e33..e6ee599328a 100644 --- a/packages/@n8n/instance-ai/src/workspace/snapshot-manager.ts +++ b/packages/@n8n/instance-ai/src/workspace/snapshot-manager.ts @@ -15,6 +15,7 @@ import type { Daytona, DaytonaError as TDaytonaError, Image } from '@daytona/sdk'; import type { RuntimeSkillSource } from '@n8n/agents'; import { DAYTONA_WORKSPACE_ROOT, loadDaytona } from '@n8n/agents/sandbox'; +import { sleep } from '@n8n/utils/sleep'; import { buildKnowledgeBaseWorkspaceBundle, @@ -41,9 +42,6 @@ const SNAPSHOT_BUILDING_STATES = new Set(['building', 'pending', 'pulling']); const SNAPSHOT_VERIFY_POLL_MS = 5_000; const DEFAULT_SNAPSHOT_VERIFY_TIMEOUT_S = 1_800; -async function sleep(ms: number): Promise { - await new Promise((resolve) => setTimeout(resolve, ms)); -} function isAlreadyExistsError(error: unknown): error is TDaytonaError { const { DaytonaError } = loadDaytona(); if (!(error instanceof DaytonaError)) return false; diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V2/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V2/execute.ts index 63b2155849c..d2f7711565b 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V2/execute.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V2/execute.ts @@ -14,7 +14,8 @@ import type { StreamEvent } from '@langchain/core/types/stream'; import type { IterableReadableStream } from '@langchain/core/utils/stream'; import { ChatOpenAI } from '@langchain/openai'; import omit from 'lodash/omit'; -import { jsonParse, NodeOperationError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { jsonParse, NodeOperationError } from 'n8n-workflow'; import type { IExecuteFunctions, INodeExecutionData, ISupplyDataFunctions } from 'n8n-workflow'; import assert from 'node:assert'; diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V3/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V3/execute.ts index 928fe07f0de..8e2034d1a4c 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V3/execute.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/V3/execute.ts @@ -6,7 +6,8 @@ import type { INodeExecutionData, ISupplyDataFunctions, } from 'n8n-workflow'; -import { getHighlightedResponseKey, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { getHighlightedResponseKey } from 'n8n-workflow'; import { buildExecutionContext, executeBatch, resolveSubAgentRequest } from './helpers'; import { isExecuteFunctions } from '../../utils'; diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent/ToolsAgentV3.test.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent/ToolsAgentV3.test.ts index e787876784b..58c64a7e427 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent/ToolsAgentV3.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent/ToolsAgentV3.test.ts @@ -1,10 +1,5 @@ -import { - sleep, - type IExecuteFunctions, - type INode, - type EngineRequest, - type EngineResponse, -} from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import type { IExecuteFunctions, INode, EngineRequest, EngineResponse } from 'n8n-workflow'; import { mock, type MockProxy } from 'vitest-mock-extended'; import type { RequestResponseMetadata } from '@utils/agent-execution'; @@ -35,6 +30,9 @@ vi.mock('@langchain/core/runnables', () => ({ vi.mock('n8n-workflow', async () => ({ ...(await vi.importActual('n8n-workflow')), +})); + +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn(), })); diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts index a78e0233f02..d2c0f7fefda 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainLLM/ChainLlm.node.ts @@ -4,7 +4,8 @@ import type { INodeType, INodeTypeDescription, } from 'n8n-workflow'; -import { NodeApiError, NodeConnectionTypes, NodeOperationError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeApiError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; import { getOptionalOutputParser } from '@utils/output_parsers/N8nOutputParser'; diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts index 991e1c8fcbc..7b9bed4b38c 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainRetrievalQA/ChainRetrievalQa.node.ts @@ -1,4 +1,5 @@ -import { NodeConnectionTypes, parseErrorMetadata, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeConnectionTypes, parseErrorMetadata } from 'n8n-workflow'; import { type IExecuteFunctions, type INodeExecutionData, diff --git a/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/V2/ChainSummarizationV2.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/V2/ChainSummarizationV2.node.ts index e9d734d26dc..a925497c2cf 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/V2/ChainSummarizationV2.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/ChainSummarization/V2/ChainSummarizationV2.node.ts @@ -7,7 +7,8 @@ import type { IDataObject, INodeInputConfiguration, } from 'n8n-workflow'; -import { NodeConnectionTypes, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeConnectionTypes } from 'n8n-workflow'; import { getBatchingOptionFields, getTemplateNoticeField } from '@n8n/ai-utilities'; diff --git a/packages/@n8n/nodes-langchain/nodes/chains/InformationExtractor/InformationExtractor.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/InformationExtractor/InformationExtractor.node.ts index 9096117eac8..f0aa0a41d14 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/InformationExtractor/InformationExtractor.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/InformationExtractor/InformationExtractor.node.ts @@ -1,7 +1,8 @@ import type { BaseLanguageModel } from '@langchain/core/language_models/base'; import type { JSONSchema7 } from 'json-schema'; import { OutputFixingParser, StructuredOutputParser } from '@langchain/classic/output_parsers'; -import { jsonParse, NodeConnectionTypes, NodeOperationError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { jsonParse, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; import type { INodeType, INodeTypeDescription, diff --git a/packages/@n8n/nodes-langchain/nodes/chains/SentimentAnalysis/SentimentAnalysis.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/SentimentAnalysis/SentimentAnalysis.node.ts index 6e38d73860f..818ac3bb4bf 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/SentimentAnalysis/SentimentAnalysis.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/SentimentAnalysis/SentimentAnalysis.node.ts @@ -2,7 +2,8 @@ import type { BaseLanguageModel } from '@langchain/core/language_models/base'; import { HumanMessage } from '@langchain/core/messages'; import { SystemMessagePromptTemplate, ChatPromptTemplate } from '@langchain/core/prompts'; import { OutputFixingParser, StructuredOutputParser } from '@langchain/classic/output_parsers'; -import { NodeConnectionTypes, NodeOperationError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; import type { IDataObject, IExecuteFunctions, diff --git a/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts index 114b8a8776c..30f75569dc3 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts @@ -1,6 +1,7 @@ import type { BaseLanguageModel } from '@langchain/core/language_models/base'; import { OutputFixingParser, StructuredOutputParser } from '@langchain/classic/output_parsers'; -import { NodeOperationError, NodeConnectionTypes, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeOperationError, NodeConnectionTypes } from 'n8n-workflow'; import type { IDataObject, IExecuteFunctions, diff --git a/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/test/TextClassifier.node.test.ts b/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/test/TextClassifier.node.test.ts index f1f69021353..293695717fc 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/test/TextClassifier.node.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/test/TextClassifier.node.test.ts @@ -1,5 +1,5 @@ import { FakeChatModel } from '@langchain/core/utils/testing'; -import * as n8nWorkflow from 'n8n-workflow'; +import * as n8nUtilsSleep from '@n8n/utils/sleep'; import type { IExecuteFunctions, INode } from 'n8n-workflow'; import type { Mock, Mocked } from 'vitest'; import { mock } from 'vitest-mock-extended'; @@ -11,13 +11,9 @@ vi.mock('../processItem', () => ({ processItem: vi.fn(), })); -vi.mock('n8n-workflow', async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - sleep: vi.fn().mockResolvedValue(undefined), - }; -}); +vi.mock('@n8n/utils/sleep', () => ({ + sleep: vi.fn().mockResolvedValue(undefined), +})); describe('TextClassifier Node', () => { let node: TextClassifier; @@ -158,8 +154,8 @@ describe('TextClassifier Node', () => { await node.execute.call(mockExecuteFunction); // 6 items with batchSize 2 => 3 batches => a delay after every batch but the last - expect(n8nWorkflow.sleep).toHaveBeenCalledTimes(2); - expect(n8nWorkflow.sleep).toHaveBeenCalledWith(100); + expect(n8nUtilsSleep.sleep).toHaveBeenCalledTimes(2); + expect(n8nUtilsSleep.sleep).toHaveBeenCalledWith(100); }); it('should handle errors in batch processing', async () => { diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.test.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.test.ts index 4d0b380042b..f45aa6f5caf 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/ToolWorkflowV2.test.ts @@ -1,5 +1,6 @@ import { DynamicTool } from '@langchain/core/tools'; -import { NodeOperationError, sleepWithAbort, UnexpectedError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeOperationError, UnexpectedError } from 'n8n-workflow'; import type { ISupplyDataFunctions, INodeExecutionData, @@ -11,14 +12,12 @@ import type { import { WorkflowToolService } from './utils/WorkflowToolService'; import type { MockedFunction } from 'vitest'; -// Mock the sleep functions -vi.mock('n8n-workflow', async () => ({ - ...(await vi.importActual('n8n-workflow')), +// Mock the sleep function +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn().mockResolvedValue(undefined), - sleepWithAbort: vi.fn().mockResolvedValue(undefined), })); -const sleepWithAbortMock = vi.mocked(sleepWithAbort); +const sleepMock = vi.mocked(sleep); function createMockClonedContext( baseContext: ISupplyDataFunctions, @@ -762,8 +761,8 @@ describe('WorkflowTool::WorkflowToolService', () => { expect(executeWorkflowMock).toHaveBeenCalledTimes(expected); }); - it('should respect waitBetweenTries with sleepWithAbort', async () => { - sleepWithAbortMock.mockClear(); + it('should respect waitBetweenTries with sleep', async () => { + sleepMock.mockClear(); const executeWorkflowMock = vi.fn().mockRejectedValue(new Error('Test error')); const contextWithRetryNode = createMockContext({ @@ -803,7 +802,7 @@ describe('WorkflowTool::WorkflowToolService', () => { await tool.func('test query'); - expect(sleepWithAbortMock).toHaveBeenCalledWith(1500, undefined); + expect(sleepMock).toHaveBeenCalledWith(1500, undefined); }); }); @@ -878,7 +877,7 @@ describe('WorkflowTool::WorkflowToolService', () => { }); it('should handle abort signal during retry wait', async () => { - sleepWithAbortMock.mockRejectedValue(new Error('Execution was cancelled')); + sleepMock.mockRejectedValue(new Error('Execution was cancelled')); const executeWorkflowMock = vi .fn() @@ -904,7 +903,7 @@ describe('WorkflowTool::WorkflowToolService', () => { const result = await tool.func('test query'); expect(result).toBe('There was an error: "Execution was cancelled"'); - expect(sleepWithAbortMock).toHaveBeenCalledWith(100, abortController.signal); + expect(sleepMock).toHaveBeenCalledWith(100, abortController.signal); expect(executeWorkflowMock).toHaveBeenCalledTimes(1); // Only first attempt }); @@ -935,7 +934,7 @@ describe('WorkflowTool::WorkflowToolService', () => { }); it('should complete successfully if not aborted', async () => { - sleepWithAbortMock.mockClear().mockResolvedValue(undefined); + sleepMock.mockClear().mockResolvedValue(undefined); const executeWorkflowMock = vi .fn() @@ -962,11 +961,11 @@ describe('WorkflowTool::WorkflowToolService', () => { expect(result).toBe(JSON.stringify({ result: 'success' }, null, 2)); expect(executeWorkflowMock).toHaveBeenCalledTimes(2); - expect(sleepWithAbortMock).toHaveBeenCalledWith(100, abortController.signal); + expect(sleepMock).toHaveBeenCalledWith(100, abortController.signal); }); it('should work when getExecutionCancelSignal is not available', async () => { - sleepWithAbortMock.mockClear().mockResolvedValue(undefined); + sleepMock.mockClear().mockResolvedValue(undefined); const executeWorkflowMock = vi .fn() @@ -990,7 +989,7 @@ describe('WorkflowTool::WorkflowToolService', () => { const result = await tool.func('test query'); expect(result).toBe(JSON.stringify({ result: 'success' }, null, 2)); - expect(sleepWithAbortMock).toHaveBeenCalledWith(100, undefined); + expect(sleepMock).toHaveBeenCalledWith(100, undefined); }); }); }); diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts index 2ffc0100bf3..01946b87ad3 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolWorkflow/v2/utils/WorkflowToolService.ts @@ -24,10 +24,10 @@ import { NodeConnectionTypes, NodeOperationError, parseErrorMetadata, - sleepWithAbort, } from 'n8n-workflow'; import { createZodSchemaFromArgs, extractFromAIParameters } from '@n8n/ai-utilities'; +import { sleep } from '@n8n/utils/sleep'; function isNodeExecutionData(data: unknown): data is INodeExecutionData[] { return isArray(data) && Boolean(data.length) && isObject(data[0]) && 'json' in data[0]; @@ -123,7 +123,7 @@ export class WorkflowToolService { lastError = undefined; if (waitBetweenTries !== 0) { try { - await sleepWithAbort(waitBetweenTries, abortSignal); + await sleep(waitBetweenTries, abortSignal); } catch (abortError) { return 'There was an error: "Execution was cancelled"'; } diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/test/transport.test.ts b/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/test/transport.test.ts index e2532fc9daa..ea24dada8b8 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/test/transport.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/test/transport.test.ts @@ -4,13 +4,9 @@ import { mockDeep } from 'vitest-mock-extended'; import { apiRequest, pollTaskResult } from '../transport'; -vi.mock('n8n-workflow', async () => { - const actual = await vi.importActual('n8n-workflow'); - return { - ...actual, - sleep: vi.fn(), - }; -}); +vi.mock('@n8n/utils/sleep', () => ({ + sleep: vi.fn(), +})); describe('AlicloudModelStudio Transport', () => { let mockExecuteFunctions: ReturnType>; diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/transport/index.ts b/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/transport/index.ts index 30808f01b15..55e13b1724b 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/transport/index.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/AlibabaCloud/transport/index.ts @@ -4,7 +4,8 @@ import type { IHttpRequestMethods, ILoadOptionsFunctions, } from 'n8n-workflow'; -import { NodeOperationError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeOperationError } from 'n8n-workflow'; type RequestParameters = { headers?: IDataObject; diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/test/transport.test.ts b/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/test/transport.test.ts index fb2102fe131..909c2eb137c 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/test/transport.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/test/transport.test.ts @@ -4,13 +4,9 @@ import { NodeOperationError } from 'n8n-workflow'; import { apiRequest, pollVideoTask, getVideoDownloadUrl } from '../transport'; -vi.mock('n8n-workflow', async () => { - const actual = await import('n8n-workflow'); - return { - ...actual, - sleep: vi.fn(), - }; -}); +vi.mock('@n8n/utils/sleep', () => ({ + sleep: vi.fn(), +})); describe('MiniMax Transport', () => { let mockExecuteFunctions: ReturnType>; diff --git a/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/transport/index.ts b/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/transport/index.ts index 082e3dcdce8..d43578a50dd 100644 --- a/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/transport/index.ts +++ b/packages/@n8n/nodes-langchain/nodes/vendors/MiniMax/transport/index.ts @@ -4,7 +4,8 @@ import type { IHttpRequestMethods, ILoadOptionsFunctions, } from 'n8n-workflow'; -import { NodeOperationError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeOperationError } from 'n8n-workflow'; type RequestParameters = { headers?: IDataObject; diff --git a/packages/@n8n/utils/src/sleep.test.ts b/packages/@n8n/utils/src/sleep.test.ts new file mode 100644 index 00000000000..95da1bab86c --- /dev/null +++ b/packages/@n8n/utils/src/sleep.test.ts @@ -0,0 +1,68 @@ +import { sleep } from './sleep'; + +describe('sleep', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should resolve after the specified time when no abort signal is given', async () => { + const onResolve = vi.fn(); + const sleepPromise = sleep(100).then(onResolve); + + await vi.advanceTimersByTimeAsync(99); + expect(onResolve).not.toHaveBeenCalled(); + + await vi.advanceTimersByTimeAsync(1); + await sleepPromise; + expect(onResolve).toHaveBeenCalled(); + }); + + it('should work without abort signal', async () => { + const onResolve = vi.fn(); + const sleepPromise = sleep(100, undefined).then(onResolve); + + await vi.advanceTimersByTimeAsync(100); + await sleepPromise; + expect(onResolve).toHaveBeenCalled(); + }); + + it('should reject immediately if abort signal is already aborted', async () => { + const abortController = new AbortController(); + abortController.abort(); + + await expect(sleep(1000, abortController.signal)).rejects.toThrow('Aborted'); + }); + + it('should reject when abort signal is triggered during sleep', async () => { + const abortController = new AbortController(); + const onSettled = vi.fn(); + + const promise = sleep(1000, abortController.signal).catch(onSettled); + + await vi.advanceTimersByTimeAsync(50); + abortController.abort(); + await promise; + + expect(onSettled).toHaveBeenCalledWith(expect.objectContaining({ message: 'Aborted' })); + }); + + it('should clean up timeout when aborted during sleep', async () => { + const abortController = new AbortController(); + const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout'); + + const sleepPromise = sleep(1000, abortController.signal); + + await vi.advanceTimersByTimeAsync(50); + abortController.abort(); + + await expect(sleepPromise).rejects.toThrow('Aborted'); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + + clearTimeoutSpy.mockRestore(); + }); +}); diff --git a/packages/@n8n/utils/src/sleep.ts b/packages/@n8n/utils/src/sleep.ts new file mode 100644 index 00000000000..3a7f7c4bc42 --- /dev/null +++ b/packages/@n8n/utils/src/sleep.ts @@ -0,0 +1,30 @@ +async function sleepWithAbort(ms: number, abortSignal: AbortSignal): Promise { + return await new Promise((resolve, reject) => { + if (abortSignal.aborted) { + reject(new Error('Aborted')); + return; + } + + const timeout = setTimeout(resolve, ms); + + abortSignal.addEventListener( + 'abort', + () => { + clearTimeout(timeout); + reject(new Error('Aborted')); + }, + { once: true }, + ); + }); +} + +/** + * Resolves after `ms` milliseconds, or rejects early if `abortSignal` is aborted. + */ +export async function sleep(ms: number, abortSignal?: AbortSignal): Promise { + if (!abortSignal) { + return await new Promise((resolve) => setTimeout(resolve, ms)); + } + + return await sleepWithAbort(ms, abortSignal); +} diff --git a/packages/cli/src/__tests__/active-executions.test.ts b/packages/cli/src/__tests__/active-executions.test.ts index 1cd38adc7a0..2008b373369 100644 --- a/packages/cli/src/__tests__/active-executions.test.ts +++ b/packages/cli/src/__tests__/active-executions.test.ts @@ -11,10 +11,10 @@ import type { IWorkflowExecutionDataProcess, StructuredChunk, } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { createEmptyRunExecutionData, ManualExecutionCancelledError, - sleep, SystemShutdownExecutionCancelledError, } from 'n8n-workflow'; import PCancelable from 'p-cancelable'; @@ -29,8 +29,7 @@ import type { ExecutionPersistence } from '@/executions/execution-persistence'; import type { License } from '@/license'; import type { Telemetry } from '@/telemetry'; -vi.mock('n8n-workflow', async () => ({ - ...(await vi.importActual('n8n-workflow')), +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn(), })); diff --git a/packages/cli/src/__tests__/active-workflow-manager.test.ts b/packages/cli/src/__tests__/active-workflow-manager.test.ts index 7605b272134..4ed71f8285d 100644 --- a/packages/cli/src/__tests__/active-workflow-manager.test.ts +++ b/packages/cli/src/__tests__/active-workflow-manager.test.ts @@ -26,7 +26,8 @@ import type { WorkflowActivateMode, WorkflowExecuteMode, } from 'n8n-workflow'; -import { sleep, Workflow, WorkflowActivationError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { Workflow, WorkflowActivationError } from 'n8n-workflow'; import { mock } from 'vitest-mock-extended'; import type { ActivationErrorsService } from '@/activation-errors.service'; diff --git a/packages/cli/src/active-executions.ts b/packages/cli/src/active-executions.ts index e260c163486..5ad0c03d87e 100644 --- a/packages/cli/src/active-executions.ts +++ b/packages/cli/src/active-executions.ts @@ -12,11 +12,8 @@ import type { StructuredChunk, WebhookResponseMode, } from 'n8n-workflow'; -import { - ExecutionCancelledError, - sleep, - SystemShutdownExecutionCancelledError, -} from 'n8n-workflow'; +import { ExecutionCancelledError, SystemShutdownExecutionCancelledError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { strict as assert } from 'node:assert'; import type PCancelable from 'p-cancelable'; diff --git a/packages/cli/src/commands/base-command.ts b/packages/cli/src/commands/base-command.ts index 8c7655e567f..45a89664d82 100644 --- a/packages/cli/src/commands/base-command.ts +++ b/packages/cli/src/commands/base-command.ts @@ -25,7 +25,8 @@ import { ExecutionContextHookRegistry, StorageConfig, } from 'n8n-core'; -import { Expression, sleep, UnexpectedError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { Expression, UnexpectedError } from 'n8n-workflow'; import type { AbstractServer } from '@/abstract-server'; import * as CrashJournal from '@/crash-journal'; diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index f2573a2255f..d8d1fea6add 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -14,7 +14,8 @@ import glob from 'fast-glob'; import { createReadStream, createWriteStream, existsSync } from 'fs'; import { mkdir } from 'fs/promises'; import { BinaryDataConfig } from 'n8n-core'; -import { jsonParse, sleep, type IWorkflowExecutionDataProcess } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { jsonParse, type IWorkflowExecutionDataProcess } from 'n8n-workflow'; import path from 'path'; import replaceStream from 'replacestream'; import { pipeline } from 'stream/promises'; diff --git a/packages/cli/src/concurrency/__tests__/concurrency-queue.test.ts b/packages/cli/src/concurrency/__tests__/concurrency-queue.test.ts index c8fb3c0c616..6a5e43cdc6f 100644 --- a/packages/cli/src/concurrency/__tests__/concurrency-queue.test.ts +++ b/packages/cli/src/concurrency/__tests__/concurrency-queue.test.ts @@ -1,4 +1,4 @@ -import { sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { ConcurrencyQueue } from '../concurrency-queue'; diff --git a/packages/cli/src/crash-journal.ts b/packages/cli/src/crash-journal.ts index 23c32a60988..0632aafda92 100644 --- a/packages/cli/src/crash-journal.ts +++ b/packages/cli/src/crash-journal.ts @@ -2,8 +2,8 @@ import { inProduction, Logger } from '@n8n/backend-common'; import { Container } from '@n8n/di'; import { existsSync } from 'fs'; import { mkdir, utimes, open, rm } from 'fs/promises'; +import { sleep } from '@n8n/utils/sleep'; import { InstanceSettings } from 'n8n-core'; -import { sleep } from 'n8n-workflow'; import { join, dirname } from 'path'; export const touchFile = async (filePath: string): Promise => { diff --git a/packages/cli/src/executions/execution-recovery.service.ts b/packages/cli/src/executions/execution-recovery.service.ts index 0a71f6bc79a..64282b2d1d3 100644 --- a/packages/cli/src/executions/execution-recovery.service.ts +++ b/packages/cli/src/executions/execution-recovery.service.ts @@ -11,8 +11,9 @@ import { ExecutionRepository, WorkflowRepository } from '@n8n/db'; import { Service } from '@n8n/di'; import { PROJECT_ADMIN_ROLE_SLUG, PROJECT_OWNER_ROLE_SLUG } from '@n8n/permissions'; import type { DateTime } from 'luxon'; +import { sleep } from '@n8n/utils/sleep'; import { InstanceSettings } from 'n8n-core'; -import { createEmptyRunExecutionData, sleep } from 'n8n-workflow'; +import { createEmptyRunExecutionData } from 'n8n-workflow'; import { ExecutionStatus, type IRun, type ITaskData } from 'n8n-workflow'; import { ARTIFICIAL_TASK_DATA } from '@/constants'; diff --git a/packages/cli/src/modules/agents/integrations/platforms/slack-bridge-behavior.ts b/packages/cli/src/modules/agents/integrations/platforms/slack-bridge-behavior.ts index 6e31fb118d8..cde317bb5d7 100644 --- a/packages/cli/src/modules/agents/integrations/platforms/slack-bridge-behavior.ts +++ b/packages/cli/src/modules/agents/integrations/platforms/slack-bridge-behavior.ts @@ -1,4 +1,5 @@ import { isRecord } from '@n8n/utils/is-record'; +import { sleep } from '@n8n/utils/sleep'; import type { Message, Thread } from 'chat'; import type { @@ -218,7 +219,11 @@ async function setSlackAssistantStatusWithRetry( } } - if (!(await sleep(SLACK_STATUS_RETRY_DELAY_MS, options.statusRetry?.signal))) return; + try { + await sleep(SLACK_STATUS_RETRY_DELAY_MS, options.statusRetry?.signal); + } catch { + return; + } // The status may have been cleared while we were sleeping. Bail out so the // retry doesn't re-set "Thinking..." over an already-cleared status. if (options.statusRetry?.signal.aborted) return; @@ -383,19 +388,3 @@ function getSlackErrorCode(error: unknown): string | undefined { if (!isRecord(data)) return undefined; return stringValue(data.error); } - -async function sleep(ms: number, signal?: AbortSignal): Promise { - if (signal?.aborted) return false; - return await new Promise((resolve) => { - const timeout = setTimeout(() => { - signal?.removeEventListener('abort', abort); - resolve(true); - }, ms); - const abort = () => { - clearTimeout(timeout); - signal?.removeEventListener('abort', abort); - resolve(false); - }; - signal?.addEventListener('abort', abort, { once: true }); - }); -} diff --git a/packages/cli/src/modules/chat-hub/chat-hub-execution.service.ts b/packages/cli/src/modules/chat-hub/chat-hub-execution.service.ts index 24c8d1e19a3..0497a305b01 100644 --- a/packages/cli/src/modules/chat-hub/chat-hub-execution.service.ts +++ b/packages/cli/src/modules/chat-hub/chat-hub-execution.service.ts @@ -8,6 +8,7 @@ import { Logger } from '@n8n/backend-common'; import { ExecutionRepository, IExecutionResponse, User } from '@n8n/db'; import { Service } from '@n8n/di'; import type { Response } from 'express'; +import { sleep } from '@n8n/utils/sleep'; import { InstanceSettings } from 'n8n-core'; import { OperationalError, @@ -17,7 +18,6 @@ import { StructuredChunk, IRunExecutionData, WorkflowExecuteMode, - sleep, NodeConnectionTypes, INodeExecutionData, jsonStringify, diff --git a/packages/cli/src/modules/insights/insights-compaction.service.ts b/packages/cli/src/modules/insights/insights-compaction.service.ts index ae9634efb0b..5c19fff8898 100644 --- a/packages/cli/src/modules/insights/insights-compaction.service.ts +++ b/packages/cli/src/modules/insights/insights-compaction.service.ts @@ -1,7 +1,7 @@ import { Logger } from '@n8n/backend-common'; import { Time } from '@n8n/constants'; import { Service } from '@n8n/di'; -import { sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { InsightsByPeriodRepository } from './database/repositories/insights-by-period.repository'; import { InsightsRawRepository } from './database/repositories/insights-raw.repository'; diff --git a/packages/cli/src/modules/instance-ai/__tests__/credit-counting.test.ts b/packages/cli/src/modules/instance-ai/__tests__/credit-counting.test.ts index c577957762d..2e099b562b6 100644 --- a/packages/cli/src/modules/instance-ai/__tests__/credit-counting.test.ts +++ b/packages/cli/src/modules/instance-ai/__tests__/credit-counting.test.ts @@ -6,8 +6,7 @@ import { InstanceAiCreditService } from '../instance-ai-credit.service'; import type { InstanceAiThreadRepository } from '../repositories/instance-ai-thread.repository'; // Skip the real backoff sleeps so retry tests run instantly. -vi.mock('n8n-workflow', async () => ({ - ...(await vi.importActual('n8n-workflow')), +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn().mockResolvedValue(undefined), })); diff --git a/packages/cli/src/modules/instance-ai/instance-ai-credit.service.ts b/packages/cli/src/modules/instance-ai/instance-ai-credit.service.ts index e3856a76ea1..edc785ead1f 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai-credit.service.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai-credit.service.ts @@ -2,8 +2,9 @@ import { Logger } from '@n8n/backend-common'; import type { User } from '@n8n/db'; import { Service } from '@n8n/di'; import type { BuilderUsageItem, TraceStatus } from '@n8n/instance-ai'; +import { sleep } from '@n8n/utils/sleep'; import { InstanceSettings } from 'n8n-core'; -import { sleep, UnexpectedError } from 'n8n-workflow'; +import { UnexpectedError } from 'n8n-workflow'; import { nanoid } from 'nanoid'; import { Push } from '@/push'; diff --git a/packages/cli/src/scaling/scaling.service.ts b/packages/cli/src/scaling/scaling.service.ts index bfa5826703d..839230ba8a7 100644 --- a/packages/cli/src/scaling/scaling.service.ts +++ b/packages/cli/src/scaling/scaling.service.ts @@ -6,7 +6,8 @@ import { OnLeaderStepdown, OnLeaderTakeover, OnShutdown } from '@n8n/decorators' import { Container, Service } from '@n8n/di'; import { ErrorReporter, InstanceSettings } from 'n8n-core'; import { ensureError } from '@n8n/utils/errors/ensure-error'; -import { BINARY_ENCODING, sleep, jsonStringify, UnexpectedError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { BINARY_ENCODING, jsonStringify, UnexpectedError } from 'n8n-workflow'; import type { IExecuteResponsePromiseData, IRun } from 'n8n-workflow'; import assert, { strict } from 'node:assert'; diff --git a/packages/cli/src/services/cache/__tests__/cache.service.test.ts b/packages/cli/src/services/cache/__tests__/cache.service.test.ts index 475667532c9..31119daaf55 100644 --- a/packages/cli/src/services/cache/__tests__/cache.service.test.ts +++ b/packages/cli/src/services/cache/__tests__/cache.service.test.ts @@ -1,7 +1,7 @@ import { GlobalConfig } from '@n8n/config'; import { Container } from '@n8n/di'; +import { sleep } from '@n8n/utils/sleep'; import random from 'lodash/random'; -import { sleep } from 'n8n-workflow'; import config from '@/config'; import { CacheService } from '@/services/cache/cache.service'; diff --git a/packages/cli/src/services/pruning/workflow-history-compaction.service.ts b/packages/cli/src/services/pruning/workflow-history-compaction.service.ts index 95a212c3a38..6de4b49cb56 100644 --- a/packages/cli/src/services/pruning/workflow-history-compaction.service.ts +++ b/packages/cli/src/services/pruning/workflow-history-compaction.service.ts @@ -6,7 +6,8 @@ import { OnLeaderStepdown, OnLeaderTakeover, OnShutdown } from '@n8n/decorators' import { Service } from '@n8n/di'; import { InstanceSettings } from 'n8n-core'; import { ensureError } from '@n8n/utils/errors/ensure-error'; -import { DiffMetaData, DiffRule, RULES, SKIP_RULES, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { DiffMetaData, DiffRule, RULES, SKIP_RULES } from 'n8n-workflow'; import { strict } from 'node:assert'; import { EventService } from '@/events/event.service'; diff --git a/packages/cli/src/task-runners/task-broker/auth/__tests__/task-broker-auth.service.test.ts b/packages/cli/src/task-runners/task-broker/auth/__tests__/task-broker-auth.service.test.ts index 32602ffc4bf..b9df4725f52 100644 --- a/packages/cli/src/task-runners/task-broker/auth/__tests__/task-broker-auth.service.test.ts +++ b/packages/cli/src/task-runners/task-broker/auth/__tests__/task-broker-auth.service.test.ts @@ -1,7 +1,7 @@ import { mockInstance } from '@n8n/backend-test-utils'; import { GlobalConfig } from '@n8n/config'; import { Time } from '@n8n/constants'; -import { sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import config from '@/config'; import { CacheService } from '@/services/cache/cache.service'; diff --git a/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts b/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts index ec7484efff9..012919486da 100644 --- a/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts +++ b/packages/cli/src/task-runners/task-broker/task-broker-ws-server.ts @@ -3,7 +3,8 @@ import { GlobalConfig, TaskRunnersConfig } from '@n8n/config'; import { Time } from '@n8n/constants'; import { Service } from '@n8n/di'; import type { BrokerMessage, RunnerMessage } from '@n8n/task-runner'; -import { jsonStringify, sleep, UserError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { jsonStringify, UserError } from 'n8n-workflow'; import type WebSocket from 'ws'; import { WsStatusCodes } from '@/constants'; diff --git a/packages/cli/src/task-runners/task-runner-module.ts b/packages/cli/src/task-runners/task-runner-module.ts index ffb829f5631..b4185b8a988 100644 --- a/packages/cli/src/task-runners/task-runner-module.ts +++ b/packages/cli/src/task-runners/task-runner-module.ts @@ -3,8 +3,8 @@ import { TaskRunnersConfig } from '@n8n/config'; import { OnShutdown } from '@n8n/decorators'; import { Container, Service } from '@n8n/di'; import type { ServiceIdentifier } from '@n8n/di'; +import { sleep } from '@n8n/utils/sleep'; import { ErrorReporter } from 'n8n-core'; -import { sleep } from 'n8n-workflow'; import * as a from 'node:assert/strict'; import { EventService } from '@/events/event.service'; diff --git a/packages/cli/src/utils/ai-service-retry.ts b/packages/cli/src/utils/ai-service-retry.ts index 50c1901849b..895f8b10cda 100644 --- a/packages/cli/src/utils/ai-service-retry.ts +++ b/packages/cli/src/utils/ai-service-retry.ts @@ -1,3 +1,4 @@ +import { sleep } from '@n8n/utils/sleep'; import { OperationalError } from 'n8n-workflow'; const AI_SERVICE_MAX_ATTEMPTS = 3; @@ -22,10 +23,6 @@ type RetryOptions = { class AiServiceCallTimeoutError extends Error {} -async function sleep(ms: number): Promise { - await new Promise((resolve) => setTimeout(resolve, ms)); -} - // The SDK's fetch calls carry no timeout, so a stalled connection would hang the caller // forever and never reach the retry loop; the race unblocks the caller (the underlying // request cannot be aborted without SDK AbortSignal support). diff --git a/packages/cli/src/wait-tracker.ts b/packages/cli/src/wait-tracker.ts index 82962f38498..c4b52295927 100644 --- a/packages/cli/src/wait-tracker.ts +++ b/packages/cli/src/wait-tracker.ts @@ -4,8 +4,8 @@ import { OnLeaderStepdown, OnLeaderTakeover } from '@n8n/decorators'; import { Service } from '@n8n/di'; import { InstanceSettings } from 'n8n-core'; import { ensureError } from '@n8n/utils/errors/ensure-error'; +import { sleep } from '@n8n/utils/sleep'; import { - sleep, UnexpectedError, UserError, type IRun, diff --git a/packages/cli/src/workflows/triggers/__tests__/trigger-activation-retry.test.ts b/packages/cli/src/workflows/triggers/__tests__/trigger-activation-retry.test.ts index 22aeba2981e..2590fc14e59 100644 --- a/packages/cli/src/workflows/triggers/__tests__/trigger-activation-retry.test.ts +++ b/packages/cli/src/workflows/triggers/__tests__/trigger-activation-retry.test.ts @@ -5,8 +5,7 @@ import { retryTriggerActivation, } from '@/workflows/triggers/trigger-activation-retry'; -vi.mock('n8n-workflow', async () => ({ - ...(await vi.importActual('n8n-workflow')), +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn(), })); diff --git a/packages/cli/src/workflows/triggers/__tests__/trigger-execution-context.factory.test.ts b/packages/cli/src/workflows/triggers/__tests__/trigger-execution-context.factory.test.ts index 88d282ba95a..944711c24cf 100644 --- a/packages/cli/src/workflows/triggers/__tests__/trigger-execution-context.factory.test.ts +++ b/packages/cli/src/workflows/triggers/__tests__/trigger-execution-context.factory.test.ts @@ -2,8 +2,9 @@ import type { Logger } from '@n8n/backend-common'; import type { Project, WorkflowEntity } from '@n8n/db'; import { createDeferredPromise } from '@n8n/utils/promise/deferred-promise'; +import { sleep } from '@n8n/utils/sleep'; import type { ErrorReporter, IGetExecutePollFunctions, StorageConfig } from 'n8n-core'; -import { sleep, UnexpectedError, Workflow } from 'n8n-workflow'; +import { UnexpectedError, Workflow } from 'n8n-workflow'; import type { Cron, CronExpression, diff --git a/packages/cli/src/workflows/triggers/__tests__/workflow-trigger-activator.test.ts b/packages/cli/src/workflows/triggers/__tests__/workflow-trigger-activator.test.ts index 7347b0d854c..c6601587053 100644 --- a/packages/cli/src/workflows/triggers/__tests__/workflow-trigger-activator.test.ts +++ b/packages/cli/src/workflows/triggers/__tests__/workflow-trigger-activator.test.ts @@ -23,8 +23,7 @@ import type { WorkflowStaticDataService } from '@/workflows/workflow-static-data import { createNodeTypes, logger, node } from './trigger-test-utils'; -vi.mock('n8n-workflow', async () => ({ - ...(await vi.importActual('n8n-workflow')), +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn(), })); diff --git a/packages/cli/src/workflows/triggers/trigger-activation-retry.ts b/packages/cli/src/workflows/triggers/trigger-activation-retry.ts index c76694d38a0..32b1b70d172 100644 --- a/packages/cli/src/workflows/triggers/trigger-activation-retry.ts +++ b/packages/cli/src/workflows/triggers/trigger-activation-retry.ts @@ -1,5 +1,6 @@ import { ensureError } from '@n8n/utils/errors/ensure-error'; -import { WebhookPathTakenError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { WebhookPathTakenError } from 'n8n-workflow'; import { WORKFLOW_REACTIVATE_INITIAL_TIMEOUT, WORKFLOW_REACTIVATE_MAX_TIMEOUT } from '@/constants'; diff --git a/packages/cli/test/integration/database/services/db-lock.service.test.ts b/packages/cli/test/integration/database/services/db-lock.service.test.ts index c7dd46bd1ca..49e7e2f40b7 100644 --- a/packages/cli/test/integration/database/services/db-lock.service.test.ts +++ b/packages/cli/test/integration/database/services/db-lock.service.test.ts @@ -9,7 +9,8 @@ import { } from '@n8n/db'; import { Container } from '@n8n/di'; import { DataSource } from '@n8n/typeorm'; -import { OperationalError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { OperationalError } from 'n8n-workflow'; import { randomUUID } from 'node:crypto'; let dbLockService: DbLockService; diff --git a/packages/cli/test/integration/scheduling/scheduler-benchmarks.test.ts b/packages/cli/test/integration/scheduling/scheduler-benchmarks.test.ts index d77349e8b96..46fd7fa1189 100644 --- a/packages/cli/test/integration/scheduling/scheduler-benchmarks.test.ts +++ b/packages/cli/test/integration/scheduling/scheduler-benchmarks.test.ts @@ -10,6 +10,7 @@ import { import { Container } from '@n8n/di'; import { DataSource } from '@n8n/typeorm'; import type { QueryDeepPartialEntity } from '@n8n/typeorm/query-builder/QueryPartialEntity'; +import { sleep } from '@n8n/utils/sleep'; /** * Durable-scheduler performance benchmarks (CAT-3623), written to answer the @@ -120,8 +121,6 @@ const INSERT_CHUNK = 500; const SEED_TX_CHUNK = 5_000; const TEST_TIMEOUT_MS = 600_000; -const sleep = async (ms: number) => await new Promise((resolve) => setTimeout(resolve, ms)); - const commas = (n: number) => Math.round(n).toLocaleString('en-US'); /** p50/p95/p99 over per-item latency samples (ms). */ diff --git a/packages/cli/test/integration/workflow-history-compaction.service.test.ts b/packages/cli/test/integration/workflow-history-compaction.service.test.ts index f8538ae83d2..2ee9d54d703 100644 --- a/packages/cli/test/integration/workflow-history-compaction.service.test.ts +++ b/packages/cli/test/integration/workflow-history-compaction.service.test.ts @@ -3,9 +3,10 @@ import { GlobalConfig } from '@n8n/config'; import { Time } from '@n8n/constants'; import { DbConnection, WorkflowHistoryRepository } from '@n8n/db'; import { Container } from '@n8n/di'; +import { sleep } from '@n8n/utils/sleep'; import repeat from 'lodash/repeat'; import { InstanceSettings } from 'n8n-core'; -import { sleep, type INode } from 'n8n-workflow'; +import type { INode } from 'n8n-workflow'; import { v4 as uuid } from 'uuid'; import { EventService } from '@/events/event.service'; diff --git a/packages/core/src/execution-engine/__tests__/routing-node.test.ts b/packages/core/src/execution-engine/__tests__/routing-node.test.ts index cd553dc542f..f43ac27be45 100644 --- a/packages/core/src/execution-engine/__tests__/routing-node.test.ts +++ b/packages/core/src/execution-engine/__tests__/routing-node.test.ts @@ -2337,8 +2337,8 @@ describe('RoutingNode', () => { } } - const workflowPackage = await import('n8n-workflow'); - const spy = vi.spyOn(workflowPackage, 'sleep').mockReturnValue( + const sleepModule = await import('@n8n/utils/sleep'); + const spy = vi.spyOn(sleepModule, 'sleep').mockReturnValue( new Promise((resolve) => { resolve(); }), diff --git a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-input-connection-data.test.ts b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-input-connection-data.test.ts index 8d0fb80d8c1..efa7b8da525 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-input-connection-data.test.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/__tests__/get-input-connection-data.test.ts @@ -1,5 +1,6 @@ import type { Tool } from '@langchain/core/tools'; import { DynamicStructuredTool } from '@langchain/core/tools'; +import * as n8nUtilsSleep from '@n8n/utils/sleep'; import type { INode, ITaskDataConnections, @@ -18,7 +19,6 @@ import type { CloseFunction, } from 'n8n-workflow'; import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; -import * as n8nWorkflow from 'n8n-workflow'; import type { Mock } from 'vitest'; import { mock } from 'vitest-mock-extended'; import { z } from 'zod'; @@ -40,14 +40,13 @@ vi.mock('../create-node-as-tool', async (importActual) => ({ getSchema: vi.fn(), })); -// n8n-workflow is externalized (see vite.config.ts), so its CJS exports are -// getter-only and Vite SSR wraps the namespace separately from `require`. -// Replace the namespace's `sleepWithAbort` so both source code (via Vite SSR -// `import`) and tests see the mock. -const sleepWithAbort = vi.fn(); -Object.defineProperty(n8nWorkflow, 'sleepWithAbort', { +// ESM module namespace exports are read-only, so `vi.spyOn` can't reassign +// `sleep` directly. Replace it via `Object.defineProperty` so both source +// code (via its `import`) and tests see the mock. +const sleepMock = vi.fn(); +Object.defineProperty(n8nUtilsSleep, 'sleep', { configurable: true, - get: () => sleepWithAbort, + get: () => sleepMock, }); describe('getInputConnectionData', () => { @@ -878,7 +877,7 @@ describe('makeHandleToolInvocation', () => { }); it('should respect waitBetweenTries limits (0-5000ms)', async () => { - const sleepWithAbortSpy = (sleepWithAbort as Mock).mockResolvedValue(undefined); + const sleepSpy = (sleepMock as Mock).mockResolvedValue(undefined); const connectedNode = mock({ name: 'Test Tool', @@ -901,8 +900,8 @@ describe('makeHandleToolInvocation', () => { await expect(result).rejects.toThrow('Test error'); - expect(sleepWithAbortSpy).toHaveBeenCalledWith(1500, undefined); - sleepWithAbortSpy.mockRestore(); + expect(sleepSpy).toHaveBeenCalledWith(1500, undefined); + sleepSpy.mockRestore(); }); }); @@ -953,9 +952,7 @@ describe('makeHandleToolInvocation', () => { }); it('should handle abort signal during retry wait', async () => { - const sleepWithAbortSpy = (sleepWithAbort as Mock).mockRejectedValue( - new Error('Execution was cancelled'), - ); + const sleepSpy = (sleepMock as Mock).mockRejectedValue(new Error('Execution was cancelled')); const connectedNode = mock({ name: 'Test Tool', @@ -980,10 +977,10 @@ describe('makeHandleToolInvocation', () => { const result = await handleToolInvocation(toolArgs); expect(result).toBe('Error during node execution: Execution was cancelled'); - expect(sleepWithAbortSpy).toHaveBeenCalledWith(1000, abortController.signal); + expect(sleepSpy).toHaveBeenCalledWith(1000, abortController.signal); expect(connectedNodeType.execute).toHaveBeenCalledTimes(1); // Only first attempt - sleepWithAbortSpy.mockRestore(); + sleepSpy.mockRestore(); }); it('should handle abort signal during execution', async () => { @@ -1028,7 +1025,7 @@ describe('makeHandleToolInvocation', () => { .mockResolvedValueOnce([[{ json: { result: 'success' } }]]), }); - const sleepWithAbortSpy = (sleepWithAbort as Mock).mockResolvedValue(undefined); + const sleepSpy = (sleepMock as Mock).mockResolvedValue(undefined); handleToolInvocation = makeHandleToolInvocation( contextFactory, @@ -1041,9 +1038,9 @@ describe('makeHandleToolInvocation', () => { expect(result).toBe(JSON.stringify([{ result: 'success' }])); expect(connectedNodeType.execute).toHaveBeenCalledTimes(2); - expect(sleepWithAbortSpy).toHaveBeenCalledWith(10, abortController.signal); + expect(sleepSpy).toHaveBeenCalledWith(10, abortController.signal); - sleepWithAbortSpy.mockRestore(); + sleepSpy.mockRestore(); }); it('should work when getExecutionCancelSignal is not available', async () => { @@ -1068,7 +1065,7 @@ describe('makeHandleToolInvocation', () => { .mockResolvedValueOnce([[{ json: { result: 'success' } }]]), }); - const sleepWithAbortSpy = (sleepWithAbort as Mock).mockResolvedValue(undefined); + const sleepSpy = (sleepMock as Mock).mockResolvedValue(undefined); handleToolInvocation = makeHandleToolInvocation( contextFactory, @@ -1080,9 +1077,9 @@ describe('makeHandleToolInvocation', () => { const result = await handleToolInvocation(toolArgs); expect(result).toBe(JSON.stringify([{ result: 'success' }])); - expect(sleepWithAbortSpy).toHaveBeenCalledWith(10, undefined); + expect(sleepSpy).toHaveBeenCalledWith(10, undefined); - sleepWithAbortSpy.mockRestore(); + sleepSpy.mockRestore(); }); }); diff --git a/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts b/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts index 204bdede837..12b41e69edb 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/get-input-connection-data.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ import { DynamicStructuredTool, StructuredTool, Tool } from '@langchain/core/tools'; +import { sleep } from '@n8n/utils/sleep'; import type { AINodeConnectionType, ChatNodeMessageWithButtons, @@ -28,7 +29,6 @@ import { NodeConnectionTypes, NodeOperationError, UserError, - sleepWithAbort, isHitlToolType, } from 'n8n-workflow'; import z, { ZodType } from 'zod'; @@ -275,7 +275,7 @@ export function makeHandleToolInvocation( lastError = undefined; if (waitBetweenTries !== 0) { try { - await sleepWithAbort(waitBetweenTries, abortSignal); + await sleep(waitBetweenTries, abortSignal); } catch (abortError) { return 'Error during node execution: Execution was cancelled'; } diff --git a/packages/core/src/execution-engine/node-execution-context/utils/request-helpers/pagination.ts b/packages/core/src/execution-engine/node-execution-context/utils/request-helpers/pagination.ts index d00a7d2cc5c..55e62709c9b 100644 --- a/packages/core/src/execution-engine/node-execution-context/utils/request-helpers/pagination.ts +++ b/packages/core/src/execution-engine/node-execution-context/utils/request-helpers/pagination.ts @@ -3,10 +3,11 @@ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ import { binaryToString, tryParseUrl } from '@n8n/backend-network'; +import { sleep } from '@n8n/utils/sleep'; import crypto from 'crypto'; import merge from 'lodash/merge'; import pick from 'lodash/pick'; -import { NodeOperationError, jsonParse, sleep } from 'n8n-workflow'; +import { NodeOperationError, jsonParse } from 'n8n-workflow'; import type { IAdditionalCredentialOptions, IDataObject, diff --git a/packages/core/src/execution-engine/routing-node.ts b/packages/core/src/execution-engine/routing-node.ts index 2ab4b746949..1042a9673e9 100644 --- a/packages/core/src/execution-engine/routing-node.ts +++ b/packages/core/src/execution-engine/routing-node.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { sleep } from '@n8n/utils/sleep'; import get from 'lodash/get'; import merge from 'lodash/merge'; import set from 'lodash/set'; @@ -10,7 +11,6 @@ import { NodeHelpers, NodeApiError, NodeOperationError, - sleep, NodeConnectionTypes, getCredentialAllowedDomains, } from 'n8n-workflow'; diff --git a/packages/core/src/execution-engine/workflow-execute.ts b/packages/core/src/execution-engine/workflow-execute.ts index 9533fe43045..da6dd6ca6b3 100644 --- a/packages/core/src/execution-engine/workflow-execute.ts +++ b/packages/core/src/execution-engine/workflow-execute.ts @@ -5,6 +5,7 @@ import { isAxiosError } from '@n8n/backend-network'; import { TOOL_EXECUTOR_NODE_NAME } from '@n8n/constants'; import { Container } from '@n8n/di'; +import { sleep } from '@n8n/utils/sleep'; import * as assert from 'assert/strict'; import { setMaxListeners } from 'events'; import get from 'lodash/get'; @@ -50,7 +51,6 @@ import { NodeConnectionTypes, ApplicationError, BaseError, - sleep, isNodeClassInstance, UnexpectedError, UserError, diff --git a/packages/frontend/@n8n/rest-api-client/src/api/nodeTypes.ts b/packages/frontend/@n8n/rest-api-client/src/api/nodeTypes.ts index 40ecdd6a533..e5566b9954d 100644 --- a/packages/frontend/@n8n/rest-api-client/src/api/nodeTypes.ts +++ b/packages/frontend/@n8n/rest-api-client/src/api/nodeTypes.ts @@ -7,6 +7,7 @@ import type { ResourceMapperFieldsRequestDto, } from '@n8n/api-types'; import type { INodeTranslationHeaders } from '@n8n/i18n'; +import { sleep } from '@n8n/utils/sleep'; import axios from 'axios'; import type { INodeListSearchResult, @@ -16,7 +17,6 @@ import type { NodeParameterValueType, ResourceMapperFields, } from 'n8n-workflow'; -import { sleep } from 'n8n-workflow'; import type { IRestApiContext } from '../types'; import { makeRestApiRequest } from '../utils'; diff --git a/packages/frontend/editor-ui/src/experiments/instanceAiSplitEmptyState/components/InstanceAiPreviewCanvas.vue b/packages/frontend/editor-ui/src/experiments/instanceAiSplitEmptyState/components/InstanceAiPreviewCanvas.vue index 85fd5953c97..27e69894115 100644 --- a/packages/frontend/editor-ui/src/experiments/instanceAiSplitEmptyState/components/InstanceAiPreviewCanvas.vue +++ b/packages/frontend/editor-ui/src/experiments/instanceAiSplitEmptyState/components/InstanceAiPreviewCanvas.vue @@ -176,7 +176,7 @@ function resetStates() { let animationTimer: ReturnType | null = null; let stopped = false; -async function sleep(ms: number): Promise { +async function delayAnimation(ms: number): Promise { return new Promise((resolve) => { animationTimer = setTimeout(resolve, ms); }); @@ -185,12 +185,12 @@ async function sleep(ms: number): Promise { async function runAnimation() { stopped = false; resetStates(); - await sleep(ANIMATION_START_DELAY_MS); + await delayAnimation(ANIMATION_START_DELAY_MS); if (stopped) return; for (const step of executionSteps.value) { if (stopped) return; for (const id of step) nodeStates[id] = 'running'; - await sleep(NODE_RUNNING_DURATION_MS); + await delayAnimation(NODE_RUNNING_DURATION_MS); if (stopped) return; for (const id of step) nodeStates[id] = 'success'; } diff --git a/packages/frontend/editor-ui/src/experiments/instanceAiWorkflowPreviewSuggestions/components/WorkflowPreviewCanvas.vue b/packages/frontend/editor-ui/src/experiments/instanceAiWorkflowPreviewSuggestions/components/WorkflowPreviewCanvas.vue index 602a32a77a5..c8bd869e41d 100644 --- a/packages/frontend/editor-ui/src/experiments/instanceAiWorkflowPreviewSuggestions/components/WorkflowPreviewCanvas.vue +++ b/packages/frontend/editor-ui/src/experiments/instanceAiWorkflowPreviewSuggestions/components/WorkflowPreviewCanvas.vue @@ -119,7 +119,7 @@ function resetStates() { let animationTimer: ReturnType | null = null; let stopped = false; -async function sleep(ms: number): Promise { +async function delayAnimation(ms: number): Promise { return new Promise((resolve) => { animationTimer = setTimeout(resolve, ms); }); @@ -131,7 +131,7 @@ async function runNodeAnimation() { for (const step of executionSteps.value) { if (stopped) return; for (const id of step) nodeStates[id] = 'running'; - await sleep(NODE_RUNNING_DURATION_MS); + await delayAnimation(NODE_RUNNING_DURATION_MS); if (stopped) return; for (const id of step) nodeStates[id] = 'success'; } diff --git a/packages/frontend/editor-ui/src/features/credentials/credentials.api.ts b/packages/frontend/editor-ui/src/features/credentials/credentials.api.ts index cf9d8eb56ee..ca07bf09d95 100644 --- a/packages/frontend/editor-ui/src/features/credentials/credentials.api.ts +++ b/packages/frontend/editor-ui/src/features/credentials/credentials.api.ts @@ -1,6 +1,7 @@ import type { ICredentialsDecryptedResponse, ICredentialsResponse } from './credentials.types'; import type { IRestApiContext } from '@n8n/rest-api-client'; import { makeRestApiRequest } from '@n8n/rest-api-client'; +import { sleep } from '@n8n/utils/sleep'; import type { ICredentialsDecrypted, ICredentialType, @@ -9,7 +10,6 @@ import type { INodeCredentialTestResult, } from 'n8n-workflow'; import axios from 'axios'; -import { sleep } from 'n8n-workflow'; import type { CreateCredentialDto } from '@n8n/api-types'; async function fetchCredentialTypesJsonWithRetry(url: string, retries = 5, delay = 500) { diff --git a/packages/nodes-base/nodes/Databricks/actions/databricksSql/executeQuery.operation.ts b/packages/nodes-base/nodes/Databricks/actions/databricksSql/executeQuery.operation.ts index c24ce607453..0771e7739a0 100644 --- a/packages/nodes-base/nodes/Databricks/actions/databricksSql/executeQuery.operation.ts +++ b/packages/nodes-base/nodes/Databricks/actions/databricksSql/executeQuery.operation.ts @@ -1,6 +1,7 @@ -import { NodeOperationError, sleep } from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; import type { IDataObject, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { extractResourceLocatorValue, getActiveCredentialType, getHost } from '../helpers'; import type { DatabricksStatementResponse } from '../interfaces'; diff --git a/packages/nodes-base/nodes/Databricks/test/Databricks.node.test.ts b/packages/nodes-base/nodes/Databricks/test/Databricks.node.test.ts index 15efd6f7d06..d9cffe7e251 100644 --- a/packages/nodes-base/nodes/Databricks/test/Databricks.node.test.ts +++ b/packages/nodes-base/nodes/Databricks/test/Databricks.node.test.ts @@ -1,15 +1,10 @@ import { NodeTestHarness } from '@nodes-testing/node-test-harness'; -import type * as nWorkflow from 'n8n-workflow'; import nock from 'nock'; -// Mock sleep from n8n-workflow so polling tests run without real delays -vi.mock('n8n-workflow', async () => { - const actual = await vi.importActual('n8n-workflow'); - return { - ...actual, - sleep: vi.fn().mockResolvedValue(undefined), - }; -}); +// Mock sleep from @n8n/utils so polling tests run without real delays +vi.mock('@n8n/utils/sleep', () => ({ + sleep: vi.fn().mockResolvedValue(undefined), +})); const HOST = 'https://adb-1234567890.1.azuredatabricks.net'; diff --git a/packages/nodes-base/nodes/Discord/test/v2/transport/discord.api.test.ts b/packages/nodes-base/nodes/Discord/test/v2/transport/discord.api.test.ts index dd25625ea72..a562ffa2579 100644 --- a/packages/nodes-base/nodes/Discord/test/v2/transport/discord.api.test.ts +++ b/packages/nodes-base/nodes/Discord/test/v2/transport/discord.api.test.ts @@ -1,13 +1,12 @@ import type { IExecuteFunctions } from 'n8n-workflow'; -import { sleep } from 'n8n-workflow'; + +import { sleep } from '@n8n/utils/sleep'; import { discordApiMultiPartRequest, discordApiRequest } from '../../../v2/transport/discord.api'; import { handleRateLimitHeaders, requestApi } from '../../../v2/transport/helpers'; import type { Mock, Mocked } from 'vitest'; -import type * as _importType0 from 'n8n-workflow'; -vi.mock('n8n-workflow', async () => ({ - ...(await vi.importActual('n8n-workflow')), +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn().mockResolvedValue(undefined), })); diff --git a/packages/nodes-base/nodes/Discord/v1/DiscordV1.node.ts b/packages/nodes-base/nodes/Discord/v1/DiscordV1.node.ts index b45f385a612..6b08375f714 100644 --- a/packages/nodes-base/nodes/Discord/v1/DiscordV1.node.ts +++ b/packages/nodes-base/nodes/Discord/v1/DiscordV1.node.ts @@ -6,13 +6,9 @@ import type { INodeTypeDescription, IRequestOptions, } from 'n8n-workflow'; -import { - jsonParse, - NodeApiError, - NodeConnectionTypes, - NodeOperationError, - sleep, -} from 'n8n-workflow'; +import { jsonParse, NodeApiError, NodeConnectionTypes, NodeOperationError } from 'n8n-workflow'; + +import { sleep } from '@n8n/utils/sleep'; import type { DiscordAttachment, DiscordWebhook } from './Interfaces'; import { oldVersionNotice } from '../../../utils/descriptions'; diff --git a/packages/nodes-base/nodes/Discord/v2/transport/helpers.ts b/packages/nodes-base/nodes/Discord/v2/transport/helpers.ts index e0346226ea2..297358bb3eb 100644 --- a/packages/nodes-base/nodes/Discord/v2/transport/helpers.ts +++ b/packages/nodes-base/nodes/Discord/v2/transport/helpers.ts @@ -6,7 +6,7 @@ import type { ILoadOptionsFunctions, IRequestOptions, } from 'n8n-workflow'; -import { sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; const MAX_RATE_LIMIT_RETRIES = 3; const MAX_RATE_LIMIT_SLEEP_MS = 60_000; diff --git a/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts b/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts index c9fbf363fc6..4e1cba338b2 100644 --- a/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts +++ b/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts @@ -4,8 +4,9 @@ import type { INodeExecutionData, INodeProperties, } from 'n8n-workflow'; -import { NodeOperationError, sleep } from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { getResolvables, updateDisplayOptions } from '@utils/utilities'; import type { ResponseWithJobReference } from '../../helpers/interfaces'; diff --git a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts index 100800c9f65..ce7dadd4f9e 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts @@ -12,9 +12,11 @@ import type { IRequestOptions, JsonObject, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError, sleep } from 'n8n-workflow'; +import { NodeApiError, NodeOperationError } from 'n8n-workflow'; import { RRule } from 'rrule'; +import { sleep } from '@n8n/utils/sleep'; + import type { RecurringEventInstance } from './EventInterface'; export async function googleApiRequest( diff --git a/packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts b/packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts index 7dde07468f7..04213c6b208 100644 --- a/packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts +++ b/packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts @@ -12,12 +12,13 @@ import type { import { NodeApiError, NodeOperationError, - sleep, removeCircularRefs, NodeConnectionTypes, } from 'n8n-workflow'; import type { Readable } from 'stream'; +import { sleep } from '@n8n/utils/sleep'; + import type { IAuthDataSanitizeKeys } from '../GenericFunctions'; import { getAllowedDomains, diff --git a/packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts b/packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts index 470e65f8511..431249ee545 100644 --- a/packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts +++ b/packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts @@ -13,12 +13,13 @@ import type { import { NodeApiError, NodeOperationError, - sleep, removeCircularRefs, NodeConnectionTypes, } from 'n8n-workflow'; import type { Readable } from 'stream'; +import { sleep } from '@n8n/utils/sleep'; + import type { IAuthDataSanitizeKeys } from '../GenericFunctions'; import { getAllowedDomains, diff --git a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts index 00415f07727..c812b08a5cd 100644 --- a/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts +++ b/packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts @@ -16,6 +16,7 @@ import type { ICredentialDataDecryptedObject, } from 'n8n-workflow'; import { ensureError } from '@n8n/utils/errors/ensure-error'; +import { sleep } from '@n8n/utils/sleep'; import { BINARY_ENCODING, NodeApiError, @@ -23,7 +24,6 @@ import { NodeOperationError, jsonParse, removeCircularRefs, - sleep, setSafeObjectProperty, } from 'n8n-workflow'; import type { Readable } from 'stream'; diff --git a/packages/nodes-base/nodes/Kafka/test/KafkaTrigger.node.test.ts b/packages/nodes-base/nodes/Kafka/test/KafkaTrigger.node.test.ts index f2e25d31e51..3c9ced7d5c5 100644 --- a/packages/nodes-base/nodes/Kafka/test/KafkaTrigger.node.test.ts +++ b/packages/nodes-base/nodes/Kafka/test/KafkaTrigger.node.test.ts @@ -17,17 +17,12 @@ import { testTriggerNode } from '@test/nodes/TriggerHelpers'; import { KafkaTrigger } from '../KafkaTrigger.node'; import type { Mock, Mocked } from 'vitest'; -import type * as _importType0 from 'n8n-workflow'; vi.mock('kafkajs'); vi.mock('@kafkajs/confluent-schema-registry'); -vi.mock('n8n-workflow', async () => { - const actual = await vi.importActual('n8n-workflow'); - return { - ...actual, - sleep: vi.fn().mockResolvedValue(undefined), - }; -}); +vi.mock('@n8n/utils/sleep', () => ({ + sleep: vi.fn().mockResolvedValue(undefined), +})); describe('KafkaTrigger Node', () => { let mockKafka: Mocked; diff --git a/packages/nodes-base/nodes/Kafka/test/utils.test.ts b/packages/nodes-base/nodes/Kafka/test/utils.test.ts index e562c0453b4..7d90dbf9764 100644 --- a/packages/nodes-base/nodes/Kafka/test/utils.test.ts +++ b/packages/nodes-base/nodes/Kafka/test/utils.test.ts @@ -1,7 +1,6 @@ import { SchemaRegistry } from '@kafkajs/confluent-schema-registry'; import type { IDeferredPromise } from '@n8n/utils/promise/deferred-promise'; import { createResultError, createResultOk } from '@n8n/utils/result'; -import type * as _importType0 from 'n8n-workflow'; import type { ITriggerFunctions, IRun, @@ -10,7 +9,8 @@ import type { ICredentialDataDecryptedObject, NodeEgressFilter, } from 'n8n-workflow'; -import { NodeOperationError, sleep } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { NodeOperationError } from 'n8n-workflow'; import http from 'node:http'; import https from 'node:https'; import type { LookupFunction } from 'node:net'; @@ -29,13 +29,9 @@ import { } from '../utils'; vi.mock('@kafkajs/confluent-schema-registry'); -vi.mock('n8n-workflow', async () => { - const actual = await vi.importActual('n8n-workflow'); - return { - ...actual, - sleep: vi.fn().mockResolvedValue(undefined), - }; -}); +vi.mock('@n8n/utils/sleep', () => ({ + sleep: vi.fn().mockResolvedValue(undefined), +})); const mockedSleep = vi.mocked(sleep); diff --git a/packages/nodes-base/nodes/Kafka/utils.ts b/packages/nodes-base/nodes/Kafka/utils.ts index 7f16e991170..8485cdb55b0 100644 --- a/packages/nodes-base/nodes/Kafka/utils.ts +++ b/packages/nodes-base/nodes/Kafka/utils.ts @@ -21,7 +21,8 @@ import type { RequestHelperFunctions, } from 'n8n-workflow'; import { ensureError } from '@n8n/utils/errors/ensure-error'; -import { jsonParse, NodeOperationError, sleep, UserError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; +import { jsonParse, NodeOperationError, UserError } from 'n8n-workflow'; import http from 'node:http'; import https from 'node:https'; import type { ConnectionOptions } from 'node:tls'; diff --git a/packages/nodes-base/nodes/Microsoft/Teams/v2/methods/listSearch.ts b/packages/nodes-base/nodes/Microsoft/Teams/v2/methods/listSearch.ts index e8e7abc5607..02ded4fabab 100644 --- a/packages/nodes-base/nodes/Microsoft/Teams/v2/methods/listSearch.ts +++ b/packages/nodes-base/nodes/Microsoft/Teams/v2/methods/listSearch.ts @@ -4,9 +4,9 @@ import { type ILoadOptionsFunctions, type INodeListSearchItems, type INodeListSearchResult, - sleep, } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { filterSortSearchListItems } from '../helpers/utils'; import { buildTeamsPath, diff --git a/packages/nodes-base/nodes/RabbitMQ/GenericFunctions.ts b/packages/nodes-base/nodes/RabbitMQ/GenericFunctions.ts index 45a8dbb2f02..50c3fb14f9c 100644 --- a/packages/nodes-base/nodes/RabbitMQ/GenericFunctions.ts +++ b/packages/nodes-base/nodes/RabbitMQ/GenericFunctions.ts @@ -9,8 +9,9 @@ import type { IRun, ITriggerFunctions, } from 'n8n-workflow'; -import { jsonParse, sleep } from 'n8n-workflow'; +import { jsonParse } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import type { ExchangeType, Options, RabbitMQCredentials, TriggerOptions } from './types'; const credentialKeys = ['hostname', 'port', 'username', 'password', 'vhost'] as const; diff --git a/packages/nodes-base/nodes/Simulate/Simulate.node.ts b/packages/nodes-base/nodes/Simulate/Simulate.node.ts index 5d656656619..9f644845ba9 100644 --- a/packages/nodes-base/nodes/Simulate/Simulate.node.ts +++ b/packages/nodes-base/nodes/Simulate/Simulate.node.ts @@ -1,4 +1,4 @@ -import { sleep, jsonParse, NodeOperationError, NodeConnectionTypes } from 'n8n-workflow'; +import { jsonParse, NodeOperationError, NodeConnectionTypes } from 'n8n-workflow'; import type { IExecuteFunctions, INodeExecutionData, @@ -7,6 +7,8 @@ import type { IDataObject, } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; + import { executionDurationProperty, iconSelector, diff --git a/packages/nodes-base/nodes/Simulate/SimulateTrigger.node.ts b/packages/nodes-base/nodes/Simulate/SimulateTrigger.node.ts index 21ac4122083..1bb28999d7b 100644 --- a/packages/nodes-base/nodes/Simulate/SimulateTrigger.node.ts +++ b/packages/nodes-base/nodes/Simulate/SimulateTrigger.node.ts @@ -1,4 +1,4 @@ -import { sleep, NodeOperationError, jsonParse, NodeConnectionTypes } from 'n8n-workflow'; +import { NodeOperationError, jsonParse, NodeConnectionTypes } from 'n8n-workflow'; import type { IDataObject, ITriggerFunctions, @@ -8,6 +8,8 @@ import type { ITriggerResponse, } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; + import { executionDurationProperty, iconSelector, diff --git a/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts b/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts index f36d62cc567..cd9d9de5b9f 100644 --- a/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts @@ -10,8 +10,9 @@ import type { IRequestOptions, IWebhookFunctions, } from 'n8n-workflow'; -import { NodeOperationError, sleep } from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { HITL_APPROVE_ACTION_ID, HITL_DECLINE_ACTION_ID, diff --git a/packages/nodes-base/nodes/Slack/test/v2/GenericFunctions.test.ts b/packages/nodes-base/nodes/Slack/test/v2/GenericFunctions.test.ts index 1909a44fdea..408b5c2272c 100644 --- a/packages/nodes-base/nodes/Slack/test/v2/GenericFunctions.test.ts +++ b/packages/nodes-base/nodes/Slack/test/v2/GenericFunctions.test.ts @@ -1,6 +1,7 @@ import type { IExecuteFunctions } from 'n8n-workflow'; -import { NodeOperationError, sleep } from 'n8n-workflow'; +import { NodeOperationError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import { slackApiRequest, slackApiRequestAllItems, @@ -14,6 +15,9 @@ import type * as _importType0 from 'n8n-workflow'; vi.mock('n8n-workflow', async () => ({ ...(await vi.importActual('n8n-workflow')), NodeApiError: vi.fn(), +})); + +vi.mock('@n8n/utils/sleep', () => ({ sleep: vi.fn().mockResolvedValue(undefined), })); diff --git a/packages/nodes-base/nodes/Splunk/v1/GenericFunctions.ts b/packages/nodes-base/nodes/Splunk/v1/GenericFunctions.ts index c434e129f95..2b2cf8d77e8 100644 --- a/packages/nodes-base/nodes/Splunk/v1/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Splunk/v1/GenericFunctions.ts @@ -6,9 +6,10 @@ import type { IRequestOptions, IHttpRequestMethods, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError, sanitizeXmlName, sleep } from 'n8n-workflow'; +import { NodeApiError, NodeOperationError, sanitizeXmlName } from 'n8n-workflow'; import { parseString } from 'xml2js'; +import { sleep } from '@n8n/utils/sleep'; import { SPLUNK, type SplunkError, diff --git a/packages/nodes-base/nodes/Splunk/v2/transport/index.ts b/packages/nodes-base/nodes/Splunk/v2/transport/index.ts index 665121eeb5e..7a6ecdf9ad4 100644 --- a/packages/nodes-base/nodes/Splunk/v2/transport/index.ts +++ b/packages/nodes-base/nodes/Splunk/v2/transport/index.ts @@ -7,8 +7,9 @@ import type { IHttpRequestOptions, IRequestOptions, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError, sleep } from 'n8n-workflow'; +import { NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { sleep } from '@n8n/utils/sleep'; import type { SplunkCredentials, SplunkError } from '../helpers/interfaces'; import { extractErrorDescription, formatEntry, parseXml } from '../helpers/utils'; diff --git a/packages/nodes-base/nodes/Twitter/V1/GenericFunctions.ts b/packages/nodes-base/nodes/Twitter/V1/GenericFunctions.ts index 40a19fea007..5dd75432f2f 100644 --- a/packages/nodes-base/nodes/Twitter/V1/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Twitter/V1/GenericFunctions.ts @@ -7,7 +7,9 @@ import type { IRequestOptions, IHttpRequestMethods, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError, sleep } from 'n8n-workflow'; +import { NodeApiError, NodeOperationError } from 'n8n-workflow'; + +import { sleep } from '@n8n/utils/sleep'; export async function twitterApiRequest( this: IExecuteFunctions | ILoadOptionsFunctions | IHookFunctions, diff --git a/packages/nodes-base/nodes/Zoom/GenericFunctions.ts b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts index e02bbf642ac..620c350182c 100644 --- a/packages/nodes-base/nodes/Zoom/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Zoom/GenericFunctions.ts @@ -1,3 +1,4 @@ +import { sleep } from '@n8n/utils/sleep'; import type { IExecuteFunctions, ILoadOptionsFunctions, @@ -48,14 +49,6 @@ export async function zoomApiRequest( } } -async function wait() { - return await new Promise((resolve, _reject) => { - setTimeout(() => { - resolve(true); - }, 1000); - }); -} - export async function zoomApiRequestAllItems( this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, @@ -73,7 +66,7 @@ export async function zoomApiRequestAllItems( returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]); // zoom free plan rate limit is 1 request/second // TODO just wait when the plan is free - await wait(); + await sleep(1000); } while (responseData.page_count !== responseData.page_number); return returnData; diff --git a/packages/testing/containers/pull-test-images.ts b/packages/testing/containers/pull-test-images.ts index a6668907cf1..9046d83ca37 100755 --- a/packages/testing/containers/pull-test-images.ts +++ b/packages/testing/containers/pull-test-images.ts @@ -27,7 +27,7 @@ interface PullResult { const MAX_RETRIES = 3; const RETRY_DELAY_MS = 10_000; -async function sleep(ms: number): Promise { +async function wait(ms: number): Promise { return await new Promise((resolve) => setTimeout(resolve, ms)); } @@ -51,7 +51,7 @@ async function pullImage(image: string): Promise { ` ⚠️ Pull attempt ${attempt}/${MAX_RETRIES} failed for ${image}: ${message}`, ); console.warn(` ⏳ Retrying in ${RETRY_DELAY_MS / 1000}s...`); - await sleep(RETRY_DELAY_MS); + await wait(RETRY_DELAY_MS); } else { console.error(` ❌ Pull failed for ${image} after ${MAX_RETRIES} attempts: ${message}`); } diff --git a/packages/testing/playwright/package.json b/packages/testing/playwright/package.json index 986f9dbc80c..ee52b745450 100644 --- a/packages/testing/playwright/package.json +++ b/packages/testing/playwright/package.json @@ -61,6 +61,7 @@ "@n8n/permissions": "workspace:*", "@n8n/playwright-janitor": "workspace:*", "@n8n/test-impact": "workspace:*", + "@n8n/utils": "workspace:*", "@n8n/workflow-sdk": "workspace:*", "@playwright/cli": "catalog:e2e", "@playwright/test": "catalog:e2e", diff --git a/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-durable.spec.ts b/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-durable.spec.ts index b9e16354aeb..cd8840ff4f0 100644 --- a/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-durable.spec.ts +++ b/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-durable.spec.ts @@ -1,3 +1,5 @@ +import { sleep } from '@n8n/utils/sleep'; + import { expectScheduleTriggerFires } from './schedule-trigger-helpers'; import { makeScheduleTriggerWorkflow, @@ -5,8 +7,6 @@ import { } from './schedule-trigger-workflow'; import { test, expect } from '../../../fixtures/base'; -const sleep = async (ms: number) => await new Promise((resolve) => setTimeout(resolve, ms)); - // Durable scheduler path. Both flags are required: with only // `N8N_SCHEDULER_ENABLED` the job registrar early-returns and activation falls // back to the legacy in-memory timer. With both set the registrar intercepts and diff --git a/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-multimain.spec.ts b/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-multimain.spec.ts index f099837798e..b3c8925c961 100644 --- a/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-multimain.spec.ts +++ b/packages/testing/playwright/tests/e2e/scheduling/schedule-trigger-multimain.spec.ts @@ -1,9 +1,9 @@ +import { sleep } from '@n8n/utils/sleep'; + import { expectScheduleTriggerFires } from './schedule-trigger-helpers'; import { makeScheduleTriggerWorkflow } from './schedule-trigger-workflow'; import { test, expect } from '../../../fixtures/base'; -const sleep = async (ms: number) => await new Promise((resolve) => setTimeout(resolve, ms)); - // Durable scheduler under a multi-main cluster. The scheduler has no leader: the // sweep, executor and reaper loops run on every main, and correctness comes from // the DB claim (`FOR UPDATE SKIP LOCKED` plus a `claimedBy`/`leaseEpoch` fence) diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 97eb33d79e4..e97d0ecf800 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -53,7 +53,6 @@ export { jsonStringify, replaceCircularReferences, sleep, - sleepWithAbort, fileTypeFromMimeType, assert, removeCircularRefs, diff --git a/packages/workflow/src/utils.ts b/packages/workflow/src/utils.ts index eee97b2d549..e4d0720c9f1 100644 --- a/packages/workflow/src/utils.ts +++ b/packages/workflow/src/utils.ts @@ -7,7 +7,6 @@ import path from 'path'; import { ALPHABET } from './constants'; import { UserError } from './errors/base/user.error'; -import { ManualExecutionCancelledError } from './errors/execution-cancelled.error'; import type { BinaryFileType, IDisplayOptions, INodeProperties, JsonObject } from './interfaces'; import * as LoggerProxy from './logger-proxy'; @@ -242,27 +241,10 @@ export const jsonStringify = (obj: unknown, options: JSONStringifyOptions = {}): return JSON.stringify(options?.replaceCircularRefs ? replaceCircularReferences(obj) : obj); }; -export const sleep = async (ms: number): Promise => - await new Promise((resolve) => { - setTimeout(resolve, ms); - }); - -export const sleepWithAbort = async (ms: number, abortSignal?: AbortSignal): Promise => - await new Promise((resolve, reject) => { - if (abortSignal?.aborted) { - reject(new ManualExecutionCancelledError('')); - return; - } - - const timeout = setTimeout(resolve, ms); - - const abortHandler = () => { - clearTimeout(timeout); - reject(new ManualExecutionCancelledError('')); - }; - - abortSignal?.addEventListener('abort', abortHandler, { once: true }); - }); +// Kept only as a backwards-compat layer for community nodes — internal code +// must import `sleep` from `@n8n/utils/sleep` (enforced by eslint rule +// no-restricted-sleep-import). +export { sleep } from '@n8n/utils/sleep'; export function fileTypeFromMimeType(mimeType: string): BinaryFileType | undefined { if (mimeType.startsWith('application/json')) return 'json'; diff --git a/packages/workflow/test/utils.test.ts b/packages/workflow/test/utils.test.ts index f6ecbf10265..0c841125603 100644 --- a/packages/workflow/test/utils.test.ts +++ b/packages/workflow/test/utils.test.ts @@ -1,6 +1,5 @@ import { ALPHABET } from '../src/constants'; import { UnexpectedError } from '../src/errors'; -import { ManualExecutionCancelledError } from '../src/errors/execution-cancelled.error'; import { jsonParse, jsonStringify, @@ -14,7 +13,6 @@ import { isSafeObjectProperty, containsUnsafeObjectPropertyToken, setSafeObjectProperty, - sleepWithAbort, isCommunityPackageName, sanitizeFilename, sanitizeXmlName, @@ -682,71 +680,6 @@ describe('sanitizeXmlName', () => { ); }); -describe('sleepWithAbort', () => { - it('should resolve after the specified time when not aborted', async () => { - const start = Date.now(); - await sleepWithAbort(100); - const end = Date.now(); - const elapsed = end - start; - - // Allow some tolerance for timing - expect(elapsed).toBeGreaterThanOrEqual(90); - expect(elapsed).toBeLessThan(200); - }); - - it('should reject immediately if abort signal is already aborted', async () => { - const abortController = new AbortController(); - abortController.abort(); - - await expect(sleepWithAbort(1000, abortController.signal)).rejects.toThrow( - ManualExecutionCancelledError, - ); - }); - - it('should reject when abort signal is triggered during sleep', async () => { - const abortController = new AbortController(); - - // Start the sleep and abort after 50ms - setTimeout(() => abortController.abort(), 50); - - const start = Date.now(); - await expect(sleepWithAbort(1000, abortController.signal)).rejects.toThrow( - ManualExecutionCancelledError, - ); - const end = Date.now(); - const elapsed = end - start; - - // Should have been aborted after ~50ms, not the full 1000ms - expect(elapsed).toBeLessThan(200); - }); - - it('should work without abort signal', async () => { - const start = Date.now(); - await sleepWithAbort(100, undefined); - const end = Date.now(); - const elapsed = end - start; - - expect(elapsed).toBeGreaterThanOrEqual(90); - expect(elapsed).toBeLessThan(200); - }); - - it('should clean up timeout when aborted during sleep', async () => { - const abortController = new AbortController(); - const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout'); - - // Start the sleep and abort after 50ms - const sleepPromise = sleepWithAbort(1000, abortController.signal); - setTimeout(() => abortController.abort(), 50); - - await expect(sleepPromise).rejects.toThrow(ManualExecutionCancelledError); - - // clearTimeout should have been called to clean up - expect(clearTimeoutSpy).toHaveBeenCalled(); - - clearTimeoutSpy.mockRestore(); - }); -}); - describe('isCommunityPackageName', () => { // Standard community package names it('should identify standard community node package names', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9eeb6c594df..2841a213de2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6823,6 +6823,9 @@ importers: '@n8n/test-impact': specifier: workspace:* version: link:../test-impact + '@n8n/utils': + specifier: workspace:* + version: link:../../@n8n/utils '@n8n/workflow-sdk': specifier: workspace:* version: link:../../@n8n/workflow-sdk From 650d89315121694f99b625c14cbf527aecd1e5ea Mon Sep 17 00:00:00 2001 From: Luka Zivkovic <32032339+luka-zivkovic@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:25:22 +0200 Subject: [PATCH 008/130] feat(ai-builder): Emit transcriptPerRun and buildErrorPerRun in eval-results.json (no-changelog) (#35078) Co-authored-by: Claude Fable 5 --- .../eval-results-dispatcher-contract.test.ts | 44 ++++++++- .../fixtures/eval-results.partial-abort.json | 8 +- .../evaluations/__tests__/redact.test.ts | 91 ++++++++++++++++++- .../__tests__/transcript-from-events.test.ts | 90 ++++++++++++++++++ .../instance-ai/evaluations/harness/redact.ts | 33 +++++++ .../outcome/transcript-from-events.ts | 27 ++++-- .../instance-ai/evaluations/run/persist.ts | 5 + 7 files changed, 288 insertions(+), 10 deletions(-) diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/eval-results-dispatcher-contract.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/eval-results-dispatcher-contract.test.ts index 14119182e89..8ca7c34b314 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/eval-results-dispatcher-contract.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/eval-results-dispatcher-contract.test.ts @@ -7,7 +7,12 @@ import { join } from 'path'; import type { CheckOutcome } from '../binaryChecks/types'; import { aggregateResults } from '../run/aggregator'; import { writeEvalResults } from '../run/persist'; -import type { ExecutionScenario, WorkflowTestCase, WorkflowTestCaseResult } from '../types'; +import type { + ExecutionScenario, + TranscriptTurn, + WorkflowTestCase, + WorkflowTestCaseResult, +} from '../types'; // Pins the `eval-results.json` fields the lang-tracer dispatcher ingests // (lang-tracer-dispatcher `src/lib/runner.ts`): it spawns this CLI per case in @@ -39,10 +44,26 @@ const passingCheck: CheckOutcome = { status: 'pass', }; +const transcript: TranscriptTurn[] = [ + { + userMessage: 'send me a daily digest', + steps: [ + { kind: 'agent-text', text: 'Building the digest workflow.' }, + { + kind: 'tool-call', + toolName: 'add-nodes', + args: { nodeType: 'n8n-nodes-base.scheduleTrigger' }, + result: { added: true }, + }, + ], + }, +]; + function iteration1(): WorkflowTestCaseResult { return { testCase, workflowBuildSuccess: true, + transcript, workflowChecks: [passingCheck], workflowJson: { id: 'wf-1', @@ -63,6 +84,7 @@ function iteration2(): WorkflowTestCaseResult { return { testCase, workflowBuildSuccess: true, + buildError: 'agent stopped before producing a workflow', buildExpectationResults: [ { expectation: 'sends a digest', pass: false, reason: 'digest node missing' }, ], @@ -99,6 +121,8 @@ interface DispatcherView { }> | null>; buildCostUsdPerRun?: Array; buildTurnsPerRun?: Array; + transcriptPerRun: Array; + buildErrorPerRun: Array; scenarios: Array<{ name: string; passCount: number; @@ -164,6 +188,24 @@ describe('eval-results.json — dispatcher contract', () => { expect(tc).not.toHaveProperty('buildCostUsdPerRun'); expect(tc).not.toHaveProperty('buildTurnsPerRun'); + // Per-iteration conversation transcript — one entry per run, null when + // the iteration captured none. A present transcript keeps the full + // step detail (tool calls with args + results) the dispatcher renders. + expect(tc.transcriptPerRun).toHaveLength(2); + expect(tc.transcriptPerRun[1]).toBeNull(); + const turn = tc.transcriptPerRun[0]?.[0]; + expect(turn?.userMessage).toBe('send me a daily digest'); + expect(turn?.steps[0]).toEqual({ kind: 'agent-text', text: 'Building the digest workflow.' }); + expect(turn?.steps[1]).toEqual({ + kind: 'tool-call', + toolName: 'add-nodes', + args: { nodeType: 'n8n-nodes-base.scheduleTrigger' }, + result: { added: true }, + }); + + // Per-iteration build-failure reason — one `string | null` per run. + expect(tc.buildErrorPerRun).toEqual([null, 'agent stopped before producing a workflow']); + // Scenario blocks serialize under the flat `scenarios` key with a flat // `name` — the shape the dispatcher's fallback reader consumes today. expect(tc.scenarios).toHaveLength(1); diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/fixtures/eval-results.partial-abort.json b/packages/@n8n/instance-ai/evaluations/__tests__/fixtures/eval-results.partial-abort.json index ec5d36cf3e7..61641847a0b 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/fixtures/eval-results.partial-abort.json +++ b/packages/@n8n/instance-ai/evaluations/__tests__/fixtures/eval-results.partial-abort.json @@ -38,7 +38,9 @@ } ] } - ] + ], + "transcriptPerRun": [null], + "buildErrorPerRun": [null] }, { "name": "build me something", @@ -69,7 +71,9 @@ } ] } - ] + ], + "transcriptPerRun": [null], + "buildErrorPerRun": ["TimeoutError: The operation was aborted due to timeout"] } ] } diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/redact.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/redact.test.ts index f4d8a96ca14..86447f0e380 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/redact.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/redact.test.ts @@ -1,4 +1,10 @@ -import { redactSecrets, redactSecretsInText, stringifyError, truncate } from '../harness/redact'; +import { + redactSecrets, + redactSecretsInText, + redactSecretsInTextDeep, + stringifyError, + truncate, +} from '../harness/redact'; describe('redactSecrets', () => { it('redacts values under secret-shaped keys', () => { @@ -104,6 +110,38 @@ describe('redactSecretsInText', () => { expect(redactSecretsInText('password: hunter2')).toBe('password: [REDACTED]'); }); + it('masks bare well-known credential formats with no surrounding key', () => { + // OpenAI/Anthropic-style + expect(redactSecretsInText('the run used sk-abc123DEF456ghi789jkl012 for calls')).toBe( + 'the run used [REDACTED] for calls', + ); + expect(redactSecretsInText('key sk-ant-api03-Zm9vYmFyYmF6cXV4 set')).toBe('key [REDACTED] set'); + // Slack bot/user/app tokens + expect(redactSecretsInText('posted with xoxb-1234567890-abcdefghijkl')).toBe( + 'posted with [REDACTED]', + ); + // Slack app-level tokens use the xapp- prefix, not xox?- + expect(redactSecretsInText('socket mode via xapp-1-A012-3456-abcdef')).toBe( + 'socket mode via [REDACTED]', + ); + // GitHub tokens (classic + fine-grained prefixes) + expect(redactSecretsInText('cloned using ghp_ABCdef123456789012345678901234567890')).toBe( + 'cloned using [REDACTED]', + ); + // AWS access key id + expect(redactSecretsInText('signed as AKIAIOSFODNN7EXAMPLE today')).toBe( + 'signed as [REDACTED] today', + ); + }); + + it('leaves lookalike prose untouched (short or non-token shapes)', () => { + expect(redactSecretsInText('we use sk-learn for clustering')).toBe( + 'we use sk-learn for clustering', + ); + expect(redactSecretsInText('the xoxo sign-off stays')).toBe('the xoxo sign-off stays'); + expect(redactSecretsInText('AKIA is an AWS prefix')).toBe('AKIA is an AWS prefix'); + }); + it('leaves secret words used as prose untouched (no separator → no match)', () => { expect(redactSecretsInText('Invalid token format in the request')).toBe( 'Invalid token format in the request', @@ -114,6 +152,57 @@ describe('redactSecretsInText', () => { }); }); +describe('redactSecretsInTextDeep', () => { + it('scrubs inline tokens in string leaves of nested objects and arrays', () => { + const input = { + log: 'Request failed: Authorization: Bearer sk-abc.def (401)', + nested: { note: 'retry with api_key=sk-inline-1' }, + list: ['header was Bearer eyJ0eXAi.payload', 'plain entry'], + }; + + expect(redactSecretsInTextDeep(input)).toEqual({ + log: 'Request failed: Authorization: Bearer [REDACTED] (401)', + nested: { note: 'retry with api_key=[REDACTED]' }, + list: ['header was Bearer [REDACTED]', 'plain entry'], + }); + }); + + it('passes non-string primitives, null, and undefined through unchanged', () => { + expect(redactSecretsInTextDeep(42)).toBe(42); + expect(redactSecretsInTextDeep(true)).toBe(true); + expect(redactSecretsInTextDeep(null)).toBeNull(); + expect(redactSecretsInTextDeep(undefined)).toBeUndefined(); + }); + + it('leaves benign strings untouched', () => { + expect(redactSecretsInTextDeep({ msg: 'Invalid token format in the request' })).toEqual({ + msg: 'Invalid token format in the request', + }); + }); + + it('does not mutate the original object', () => { + const original = { note: 'api_key=sk-real' }; + redactSecretsInTextDeep(original); + expect(original.note).toBe('api_key=sk-real'); + }); + + it('caps recursion depth so deeply nested input cannot blow the stack', () => { + let nested: unknown = { note: 'api_key=sk-leaf' }; + for (let i = 0; i < 12; i += 1) { + nested = { wrap: nested }; + } + expect(() => redactSecretsInTextDeep(nested)).not.toThrow(); + }); + + it('leaves class instances untouched (only walks plain objects)', () => { + class WithText { + constructor(public note: string) {} + } + const instance = new WithText('api_key=sk-keep'); + expect(redactSecretsInTextDeep(instance)).toBe(instance); + }); +}); + describe('truncate', () => { it('passes short values through after redaction', () => { expect(truncate({ name: 'a', token: 't' }, 200)).toEqual({ name: 'a', token: '[REDACTED]' }); diff --git a/packages/@n8n/instance-ai/evaluations/__tests__/transcript-from-events.test.ts b/packages/@n8n/instance-ai/evaluations/__tests__/transcript-from-events.test.ts index 4ccf4f77cc2..f71b78ba963 100644 --- a/packages/@n8n/instance-ai/evaluations/__tests__/transcript-from-events.test.ts +++ b/packages/@n8n/instance-ai/evaluations/__tests__/transcript-from-events.test.ts @@ -1,5 +1,6 @@ import { buildTranscriptFromEvents } from '../outcome/transcript-from-events'; import type { CapturedEvent } from '../types'; +import { USER_TURN_EVENT } from '../types'; function evt(type: string, data: Record = {}): CapturedEvent { return { timestamp: 0, type, data }; @@ -91,6 +92,95 @@ describe('buildTranscriptFromEvents', () => { }); }); + it('scrubs an inline token in agent narration', () => { + const turns = buildTranscriptFromEvents({ + events: [ + RUN_START, + evt('text-delta', { text: 'Calling the API with api_key=sk-narrated-123 now.' }), + ], + }); + const step = turns[0].steps[0]; + expect(step.kind).toBe('agent-text'); + const text = step.kind === 'agent-text' ? step.text : ''; + expect(text).not.toContain('sk-narrated-123'); + expect(text).toContain('api_key=[REDACTED]'); + expect(text).toContain('Calling the API'); + }); + + it('scrubs an inline token in the user turn message', () => { + const turns = buildTranscriptFromEvents({ + events: [ + evt(USER_TURN_EVENT, { payload: { text: 'use token=abc-user-999 for the request' } }), + RUN_START, + evt('text-delta', { text: 'ok' }), + ], + }); + expect(turns[0].userMessage).not.toContain('abc-user-999'); + expect(turns[0].userMessage).toContain('token=[REDACTED]'); + expect(turns[0].userMessage).toContain('for the request'); + }); + + it('scrubs an inline token in fallback (no-marker) opening and follow-up messages', () => { + const turns = buildTranscriptFromEvents({ + events: [ + RUN_START, + evt('text-delta', { text: 'first' }), + RUN_START, + evt('text-delta', { text: 'second' }), + ], + openingMessage: 'use token=abc-open-111 to authenticate', + followUpMessages: ['and api_key=abc-follow-222 for the sync'], + }); + expect(turns).toHaveLength(2); + expect(turns[0].userMessage).not.toContain('abc-open-111'); + expect(turns[0].userMessage).toContain('token=[REDACTED]'); + expect(turns[1].userMessage).not.toContain('abc-follow-222'); + expect(turns[1].userMessage).toContain('api_key=[REDACTED]'); + }); + + it('scrubs an inline token inside a string value of tool-call args', () => { + const turns = buildTranscriptFromEvents({ + events: [ + RUN_START, + evt('tool-call', { + payload: { + toolName: 'httpRequest', + toolCallId: 'tc-args', + args: { note: 'send with Authorization: Bearer sk-args-leak' }, + }, + }), + ], + }); + const step = turns[0].steps[0]; + expect(step.kind).toBe('tool-call'); + const args = step.kind === 'tool-call' ? (step.args ?? {}) : {}; + expect(JSON.stringify(args)).not.toContain('sk-args-leak'); + expect(args.note).toContain('Bearer [REDACTED]'); + }); + + it('scrubs an inline token inside a string value of a paired tool-result', () => { + const turns = buildTranscriptFromEvents({ + events: [ + RUN_START, + evt('tool-call', { + payload: { toolName: 'httpRequest', toolCallId: 'tc-res', args: { url: 'x' } }, + }), + evt('tool-result', { + payload: { + toolName: 'httpRequest', + toolCallId: 'tc-res', + result: { log: 'upstream said api_key=sk-result-leak was invalid' }, + }, + }), + ], + }); + const step = turns[0].steps[0]; + expect(step.kind).toBe('tool-call'); + const result = step.kind === 'tool-call' ? step.result : undefined; + expect(JSON.stringify(result)).not.toContain('sk-result-leak'); + expect(JSON.stringify(result)).toContain('api_key=[REDACTED]'); + }); + it('scrubs a secret embedded in a paired tool-error string', () => { const turns = buildTranscriptFromEvents({ events: [ diff --git a/packages/@n8n/instance-ai/evaluations/harness/redact.ts b/packages/@n8n/instance-ai/evaluations/harness/redact.ts index 6e9dd2fa568..77b23d779ca 100644 --- a/packages/@n8n/instance-ai/evaluations/harness/redact.ts +++ b/packages/@n8n/instance-ai/evaluations/harness/redact.ts @@ -63,6 +63,17 @@ const SECRET_TEXT_PATTERNS: ReadonlyArray = [ /\b(api[-_]?key|access[-_]?key|x-api-key|access[-_]?token|refresh[-_]?token|client[-_]?secret|private[-_]?key|secret|token|password|passwd|cookie|set-cookie|session[-_]?id)("?\s*[:=]\s*"?)[^\s"',&}]+/gi, '$1$2[REDACTED]', ], + // Bare well-known credential formats — recognizable with no key or scheme + // around them. Length floors keep prose lookalikes (sk-learn, xoxo, the + // AKIA prefix mentioned in text) unmatched. + // OpenAI/Anthropic-style: sk-…, sk-ant-…, sk-proj-…. + [/\bsk-(?:[a-z0-9]+-)*[A-Za-z0-9_-]{16,}\b/g, '[REDACTED]'], + // Slack tokens: xoxb-/xoxp-/xoxa-/xoxs-… plus app-level xapp-…. + [/\b(?:xox[a-z]|xapp)-[A-Za-z0-9-]{8,}\b/gi, '[REDACTED]'], + // GitHub tokens: ghp_/gho_/ghu_/ghs_/ghr_ + fine-grained github_pat_. + [/\b(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,}\b/g, '[REDACTED]'], + // AWS access key ids. + [/\bAKIA[0-9A-Z]{16}\b/g, '[REDACTED]'], ]; /** Mask secrets embedded inline in free text (e.g. a token in a tool-error string). */ @@ -73,6 +84,28 @@ export function redactSecretsInText(text: string): string { ); } +/** + * Content-based pass over a value tree: applies `redactSecretsInText` to every + * string leaf. Complements the key-based `redactSecrets` for payloads where a + * token sits inline in a value under a non-secret-shaped key. Same walk rules + * as `redactSecrets`: plain objects and arrays only, depth-capped. + */ +export function redactSecretsInTextDeep(value: unknown, depth = 0): unknown { + if (depth > 10 || value === null || value === undefined) return value; + if (typeof value === 'string') return redactSecretsInText(value); + if (Array.isArray(value)) { + return value.map((entry) => redactSecretsInTextDeep(entry, depth + 1)); + } + if (typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) { + const out: Record = {}; + for (const [k, v] of Object.entries(value as Record)) { + out[k] = redactSecretsInTextDeep(v, depth + 1); + } + return out; + } + return value; +} + /** * Truncates serializable values to a max stringified length, redacting * secrets first. Returns the redacted value when it fits, the truncated diff --git a/packages/@n8n/instance-ai/evaluations/outcome/transcript-from-events.ts b/packages/@n8n/instance-ai/evaluations/outcome/transcript-from-events.ts index a28b87c898d..ce4e6f834c9 100644 --- a/packages/@n8n/instance-ai/evaluations/outcome/transcript-from-events.ts +++ b/packages/@n8n/instance-ai/evaluations/outcome/transcript-from-events.ts @@ -9,7 +9,7 @@ import type { InstanceAiConfirmRequest } from '@n8n/api-types'; import { isRecord } from '@n8n/utils/is-record'; -import { redactSecrets, redactSecretsInText } from '../harness/redact'; +import { redactSecrets, redactSecretsInText, redactSecretsInTextDeep } from '../harness/redact'; import type { AskUserAnswer, AskUserQuestion, @@ -87,6 +87,8 @@ function splitEventsByUserTurn( function extractUserTurnText(event: CapturedEvent): string | undefined { const payload = getRecord(event.data, 'payload'); + // Raw here — buildTurn content-scrubs every user message at the shared + // boundary, covering this path and the legacy fallback alike. return payload ? getString(payload, 'text') : undefined; } @@ -111,7 +113,9 @@ function buildTurn( const flushText = () => { if (textBuffer.length > 0) { - steps.push({ kind: 'agent-text', text: textBuffer }); + // Agent narration can echo a token verbatim (e.g. from a tool result) — + // content-scrub before the text lands in the persisted transcript. + steps.push({ kind: 'agent-text', text: redactSecretsInText(textBuffer) }); textBuffer = ''; } }; @@ -144,7 +148,14 @@ function buildTurn( } flushText(); - return { userMessage, steps }; + // The transcript leaves the machine via eval-results.json — content-scrub + // the user message HERE, the boundary both capture paths share (the + // marker path's extracted turn text AND the legacy fallback's raw + // opening/follow-up messages). + return { + userMessage: userMessage === undefined ? undefined : redactSecretsInText(userMessage), + steps, + }; } interface ToolOutcome { @@ -164,8 +175,9 @@ function collectToolOutcomes(events: CapturedEvent[]): Map // Flat string, so content-scrub (key-based redaction can't reach an inline token). map.set(callId, { error: redactSecretsInText(getString(payload, 'error') ?? 'tool error') }); } else { - // Redact secret-shaped keys before the result reaches the report/judge. - map.set(callId, { result: redactSecrets(payload.result) }); + // Redact secret-shaped keys, then content-scrub string leaves — a token + // inlined in a value under a benign key survives the key-based pass. + map.set(callId, { result: redactSecretsInTextDeep(redactSecrets(payload.result)) }); } } return map; @@ -200,7 +212,10 @@ function interpretToolCall( toolName, toolCallId: callId, args: - Object.keys(args).length > 0 ? (redactSecrets(args) as Record) : undefined, + Object.keys(args).length > 0 + ? // Key-based pass, then a content pass over string leaves (see collectToolOutcomes). + (redactSecretsInTextDeep(redactSecrets(args)) as Record) + : undefined, result, error: outcome?.error, }; diff --git a/packages/@n8n/instance-ai/evaluations/run/persist.ts b/packages/@n8n/instance-ai/evaluations/run/persist.ts index ccb0066e94a..4e4f808db31 100644 --- a/packages/@n8n/instance-ai/evaluations/run/persist.ts +++ b/packages/@n8n/instance-ai/evaluations/run/persist.ts @@ -429,6 +429,11 @@ export function writeEvalResults( passHatK: terminalRate(ea.passHatK), })), threadIds: tc.runs.map((run) => run.threadId ?? null), + // Per-iteration conversation transcript + build-failure reason — the + // LangTracer dispatcher ingests both (null when an iteration has none). + // Transcript content is redacted at capture (transcript-from-events). + transcriptPerRun: tc.runs.map((run) => run.transcript ?? null), + buildErrorPerRun: tc.runs.map((run) => run.buildError ?? null), // `claude` build spend per iteration (--build-via-mcp only) — the // dedupe-safe source for per-case cost (LangSmith feedback repeats the // value on every row of a case's build). From 2837a68101103fae202e972328b72923d2b717c2 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Tue, 28 Jul 2026 22:22:25 +0200 Subject: [PATCH 009/130] fix(editor): Fix icon overlap in end-user credential connection banner (#35130) Co-authored-by: Claude Sonnet 5 --- .../editor-ui/src/app/components/Banner.vue | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/frontend/editor-ui/src/app/components/Banner.vue b/packages/frontend/editor-ui/src/app/components/Banner.vue index 0173c7836d9..55447540be9 100644 --- a/packages/frontend/editor-ui/src/app/components/Banner.vue +++ b/packages/frontend/editor-ui/src/app/components/Banner.vue @@ -76,15 +76,17 @@ const messageClass = computed>(() => ({ :disable-transitions="true" :class="[$style.container, theme === 'info' && $style.containerInfo]" > -
- {{ message }}  - - More details - -
+
+ + {{ message }}  + + More details + +
+
@@ -113,11 +115,7 @@ const messageClass = computed>(() => ({ From bcbea0ab6c38e05654f2cd626bc14b320470467d Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Tue, 28 Jul 2026 23:14:14 +0100 Subject: [PATCH 010/130] fix(core): Settle interrupted agent preview runs (no-changelog) (#35043) --- packages/@n8n/api-types/src/agents/types.ts | 2 + .../__tests__/agent-chat.controller.test.ts | 146 ++++ .../agent-checkpoint.repository.test.ts | 21 + ...ent-execution-orchestrator.service.test.ts | 125 +++- .../execution-to-message-mapper.test.ts | 26 + .../modules/agents/agent-chat.controller.ts | 105 ++- .../agent-execution-orchestrator.service.ts | 48 +- .../__tests__/n8n-checkpoint-storage.test.ts | 15 +- .../integrations/n8n-checkpoint-storage.ts | 13 + .../agent-checkpoint.repository.ts | 16 +- .../utils/execution-to-message-mapper.ts | 3 +- .../frontend/@n8n/i18n/src/locales/en.json | 1 + .../agents/__tests__/AgentChatPanel.test.ts | 61 ++ .../__tests__/AgentChatToolSteps.test.ts | 27 +- .../__tests__/agentChatMessages.test.ts | 69 +- .../__tests__/useAgentChatStream.test.ts | 685 ++++++++++++++++++ .../agents/components/AgentChatPanel.vue | 52 +- .../agents/components/AgentChatToolSteps.vue | 8 +- .../agents/composables/useAgentApi.ts | 13 + .../agents/composables/useAgentChatStream.ts | 201 ++++- .../ai/shared/agentsChat/messageMappers.ts | 66 +- .../features/ai/shared/agentsChat/types.ts | 2 + 22 files changed, 1598 insertions(+), 107 deletions(-) diff --git a/packages/@n8n/api-types/src/agents/types.ts b/packages/@n8n/api-types/src/agents/types.ts index 2e4caca7296..88bbbe5218d 100644 --- a/packages/@n8n/api-types/src/agents/types.ts +++ b/packages/@n8n/api-types/src/agents/types.ts @@ -212,6 +212,8 @@ export interface AgentPersistedMessageDto { content: AgentPersistedMessageContentPart[]; /** Agent-execution turn id when this message was produced from an execution transcript. */ executionId?: string; + /** Outcome of the execution that produced this message. */ + executionStatus?: 'success' | 'error'; } export const AGENT_BUILDER_DEFAULT_MODEL = 'claude-sonnet-4-6' as const; diff --git a/packages/cli/src/modules/agents/__tests__/agent-chat.controller.test.ts b/packages/cli/src/modules/agents/__tests__/agent-chat.controller.test.ts index f85cf504efc..aa30fac7b78 100644 --- a/packages/cli/src/modules/agents/__tests__/agent-chat.controller.test.ts +++ b/packages/cli/src/modules/agents/__tests__/agent-chat.controller.test.ts @@ -85,6 +85,7 @@ describe('AgentChatController route access scopes', () => { it.each([ ['chat', 'agent:execute'], ['chatResume', 'agent:execute'], + ['cancelChatRun', 'agent:execute'], ['getChatMessages', 'agent:read'], ['getTestChatMessages', 'agent:read'], ['clearTestChatMessages', 'agent:update'], @@ -150,6 +151,71 @@ describe('AgentChatController chat message history', () => { }); describe('AgentChatController SSE done payload', () => { + it('does not start a chat after the response closes during session lookup', async () => { + const { + controller, + agentExecutionOrchestratorService, + agentExecutionService, + agentValidationService, + } = makeController(); + let resolveLookup = (_value: null) => {}; + agentExecutionService.findThreadById.mockReturnValue( + new Promise((resolve) => { + resolveLookup = resolve; + }), + ); + agentExecutionOrchestratorService.executeForChat.mockImplementation(async function* () { + yield* []; + }); + + const res = makeSseResponse([]); + const request = controller.chat( + { params: { projectId: 'project-1' }, user: { id: 'user-1' } } as never, + res, + 'agent-1', + { message: 'hi', sessionId: 'thread-1' } as never, + ); + await vi.waitFor(() => expect(agentExecutionService.findThreadById).toHaveBeenCalled()); + + (res as unknown as EventEmitter).emit('close'); + resolveLookup(null); + await request; + + expect(agentValidationService.validateAgentIsRunnable).not.toHaveBeenCalled(); + expect(agentExecutionOrchestratorService.executeForChat).not.toHaveBeenCalled(); + }); + + it('does not start a chat after the response closes during validation', async () => { + const { controller, agentExecutionOrchestratorService, agentValidationService } = + makeController(); + let resolveValidation = (_value: { missing: string[] }) => {}; + agentValidationService.validateAgentIsRunnable.mockReturnValue( + new Promise((resolve) => { + resolveValidation = resolve; + }), + ); + agentExecutionOrchestratorService.executeForChat.mockImplementation(async function* () { + yield* []; + }); + + const res = makeSseResponse([]); + const request = controller.chat( + { params: { projectId: 'project-1' }, user: { id: 'user-1' } } as never, + res, + 'agent-1', + { message: 'hi' } as never, + ); + await vi.waitFor(() => + expect(agentValidationService.validateAgentIsRunnable).toHaveBeenCalled(), + ); + + (res as unknown as EventEmitter).emit('close'); + resolveValidation({ missing: [] }); + await request; + + expect(agentExecutionOrchestratorService.executeForChat).not.toHaveBeenCalled(); + }); + it('includes executionId on done when recorded', async () => { const { controller, agentExecutionOrchestratorService, agentExecutionService } = makeController(); @@ -206,4 +272,84 @@ describe('AgentChatController SSE done payload', () => { executionId: 'exec-resume-1', }); }); + + it.each([ + { + name: 'new chat', + start: async (controller: AgentChatController, res: FlushableResponse) => + await controller.chat( + { params: { projectId: 'project-1' }, user: { id: 'user-1' } } as never, + res, + 'agent-1', + { message: 'hi' } as never, + ), + method: 'executeForChat' as const, + }, + { + name: 'resumed chat', + start: async (controller: AgentChatController, res: FlushableResponse) => + await controller.chatResume( + { params: { projectId: 'project-1' }, user: { id: 'user-1' } } as never, + res, + 'agent-1', + { runId: 'run-1', toolCallId: 'tc-1', resumeData: { approved: true } } as never, + ), + method: 'resumeForChat' as const, + }, + ])('aborts the $name when its SSE response closes early', async ({ start, method }) => { + const { controller, agentExecutionOrchestratorService, agentExecutionService } = + makeController(); + agentExecutionService.findThreadById.mockResolvedValue(null); + + let receivedSignal: AbortSignal | undefined; + let releaseRun = () => {}; + let markStarted = () => {}; + const runStarted = new Promise((resolve) => { + markStarted = resolve; + }); + const runBlocked = new Promise((resolve) => { + releaseRun = resolve; + }); + agentExecutionOrchestratorService[method].mockImplementation(async function* (config) { + receivedSignal = (config as { abortSignal?: AbortSignal }).abortSignal; + markStarted(); + await runBlocked; + yield* []; + }); + + const res = makeSseResponse([]); + const request = start(controller, res); + await runStarted; + (res as unknown as EventEmitter).emit('close'); + releaseRun(); + await request; + + expect(receivedSignal?.aborted).toBe(true); + }); +}); + +describe('AgentChatController HITL cancellation', () => { + it('cancels a suspended run for the current preview user', async () => { + const { controller, agentExecutionOrchestratorService, agentsService } = makeController(); + agentsService.findById.mockResolvedValue({ id: 'agent-1' } as never); + agentExecutionOrchestratorService.cancelChatRun.mockResolvedValue(true); + + await expect( + controller.cancelChatRun( + { + params: { projectId: 'project-1' }, + user: { id: 'user-1' }, + } as never, + {} as never, + 'agent-1', + 'run-1', + ), + ).resolves.toEqual({ cancelled: true }); + + expect(agentExecutionOrchestratorService.cancelChatRun).toHaveBeenCalledWith({ + agentId: 'agent-1', + runId: 'run-1', + resourceId: 'draft-chat:user-1', + }); + }); }); diff --git a/packages/cli/src/modules/agents/__tests__/agent-checkpoint.repository.test.ts b/packages/cli/src/modules/agents/__tests__/agent-checkpoint.repository.test.ts index ed3bf76f38e..9f1d98cbc5b 100644 --- a/packages/cli/src/modules/agents/__tests__/agent-checkpoint.repository.test.ts +++ b/packages/cli/src/modules/agents/__tests__/agent-checkpoint.repository.test.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/unbound-method -- mock-based tests intentionally reference unbound methods */ +import { Equal, IsNull, Or } from '@n8n/typeorm'; import { mockEntityManager } from '@test/mocking'; import { AgentCheckpoint } from '../entities/agent-checkpoint.entity'; @@ -37,4 +38,24 @@ describe('AgentCheckpointRepository', () => { ).resolves.toBe(false); }); }); + + describe('cancelSuspended', () => { + it('matches checkpoints scoped to the current agent or unscoped', async () => { + vi.spyOn(repository, 'update').mockResolvedValue({ affected: 1 } as never); + + await expect( + repository.cancelSuspended('run-1', 'agent-1', '{"status":"suspended"}'), + ).resolves.toBe(true); + + expect(repository.update).toHaveBeenCalledWith( + { + runId: 'run-1', + agentId: Or(Equal('agent-1'), IsNull()), + expired: false, + state: '{"status":"suspended"}', + }, + { expired: true, state: null }, + ); + }); + }); }); diff --git a/packages/cli/src/modules/agents/__tests__/agent-execution-orchestrator.service.test.ts b/packages/cli/src/modules/agents/__tests__/agent-execution-orchestrator.service.test.ts index 38458d42371..bee0609617e 100644 --- a/packages/cli/src/modules/agents/__tests__/agent-execution-orchestrator.service.test.ts +++ b/packages/cli/src/modules/agents/__tests__/agent-execution-orchestrator.service.test.ts @@ -1,4 +1,4 @@ -import type { Agent as RuntimeAgent, StreamChunk } from '@n8n/agents'; +import type { Agent as RuntimeAgent, SerializableAgentState, StreamChunk } from '@n8n/agents'; import { N8N_CHAT_INTEGRATION_TYPE, type AgentJsonConfig } from '@n8n/api-types'; import { mockLogger } from '@n8n/backend-test-utils'; import type { User } from '@n8n/db'; @@ -133,6 +133,7 @@ describe('AgentExecutionOrchestratorService', () => { it('streams chat responses and records suspended executions', async () => { const { service, executionService } = makeService(); + const abortController = new AbortController(); const runtime = makeRuntime([ { type: 'text-start', id: 'text-1' }, { type: 'text-delta', id: 'text-1', delta: 'Choose one' }, @@ -153,6 +154,7 @@ describe('AgentExecutionOrchestratorService', () => { message: 'hello', memory: { threadId: 'thread-1', resourceId: 'resource-1' }, projectId, + abortSignal: abortController.signal, }), ); @@ -162,6 +164,7 @@ describe('AgentExecutionOrchestratorService', () => { expect.objectContaining({ persistence: { threadId: 'thread-1', resourceId: 'resource-1' }, executionCounter: expect.any(Object), + abortSignal: abortController.signal, }), ); expect(executionService.recordMessage).toHaveBeenCalledWith( @@ -485,6 +488,44 @@ describe('AgentExecutionOrchestratorService', () => { ); }); + it('persists an aborted chat stream as cancelled without discarding partial output', async () => { + const { service, executionService } = makeService(); + const abortController = new AbortController(); + const runtime = makeRuntime([ + { type: 'text-start', id: 'text-1' }, + { type: 'text-delta', id: 'text-1', delta: 'partial answer' }, + { type: 'error', error: new Error('This operation was aborted') }, + { type: 'finish', finishReason: 'error' }, + ]); + const stream = service.streamChatResponse({ + agentInstance: runtime.agent, + toolRegistry: runtime.toolRegistry, + agentId, + message: 'hello', + memory: { threadId: 'thread-1', resourceId: 'resource-1' }, + projectId, + abortSignal: abortController.signal, + onExecutionRecorded: vi.fn(), + }); + + await stream.next(); + await stream.next(); + abortController.abort(); + await collect(stream); + + expect(executionService.recordMessage).toHaveBeenCalledWith( + expect.objectContaining({ + userMessage: 'hello', + record: expect.objectContaining({ + assistantResponse: 'partial answer', + finishReason: 'cancelled', + error: null, + timeline: [expect.objectContaining({ type: 'text', content: 'partial answer' })], + }), + }), + ); + }); + it('maps persisted execution history to chat DTOs', async () => { const { service, executionService } = makeService(); executionService.getThreadDetail.mockResolvedValue({ @@ -540,6 +581,7 @@ describe('AgentExecutionOrchestratorService', () => { } as never); runtimeCacheService.getRuntime.mockResolvedValue(runtime); + const abortController = new AbortController(); await collect( service.resumeForChat({ agentId, @@ -548,13 +590,18 @@ describe('AgentExecutionOrchestratorService', () => { toolCallId: 'tc-1', resumeData: { value: 'yes' }, integrationType: 'slack', + abortSignal: abortController.signal, }), ); expect(runtime.agent.resume).toHaveBeenCalledWith( 'stream', { value: 'yes' }, - expect.objectContaining({ runId: 'run-1', toolCallId: 'tc-1' }), + expect.objectContaining({ + runId: 'run-1', + toolCallId: 'tc-1', + abortSignal: abortController.signal, + }), ); expect(externalHooks.run).not.toHaveBeenCalled(); expect(JSON.stringify(runtime.agent.resume.mock.calls[0])).not.toContain('platform-user-1'); @@ -571,6 +618,80 @@ describe('AgentExecutionOrchestratorService', () => { ); }); + it('persists an aborted resumed stream as cancelled without discarding partial output', async () => { + const { service, checkpointStorage, runtimeCacheService, executionService } = makeService(); + const abortController = new AbortController(); + const runtime = makeRuntime([ + { type: 'text-start', id: 'text-1' }, + { type: 'text-delta', id: 'text-1', delta: 'partial resumed answer' }, + { type: 'error', error: new Error('This operation was aborted') }, + { type: 'finish', finishReason: 'error' }, + ]); + checkpointStorage.getStatus.mockResolvedValue({ + status: 'active', + checkpoint: { persistence: { threadId: 'thread-1', resourceId: 'resource-1' } }, + } as never); + runtimeCacheService.getRuntime.mockResolvedValue(runtime); + const stream = service.resumeForChat({ + agentId, + projectId, + runId: 'run-1', + toolCallId: 'tc-1', + resumeData: { value: 'yes' }, + abortSignal: abortController.signal, + onExecutionRecorded: vi.fn(), + }); + + await stream.next(); + await stream.next(); + abortController.abort(); + await collect(stream); + + expect(executionService.recordMessage).toHaveBeenCalledWith( + expect.objectContaining({ + userMessage: null, + hitlStatus: 'resumed', + record: expect.objectContaining({ + assistantResponse: 'partial resumed answer', + finishReason: 'cancelled', + error: null, + timeline: [expect.objectContaining({ type: 'text', content: 'partial resumed answer' })], + }), + }), + ); + }); + + it('atomically cancels only suspended checkpoints owned by the preview user', async () => { + const { service, checkpointStorage } = makeService(); + const checkpoint: SerializableAgentState = { + status: 'suspended', + persistence: { threadId: 'thread-1', resourceId: 'draft-chat:user-1' }, + messageList: { messages: [], historyIds: [], inputIds: [], responseIds: [] }, + pendingToolCalls: {}, + }; + checkpointStorage.getStatus.mockResolvedValue({ status: 'active', checkpoint }); + checkpointStorage.cancelSuspended.mockResolvedValue(true); + + await expect( + service.cancelChatRun({ + agentId, + runId: 'run-1', + resourceId: 'draft-chat:user-1', + }), + ).resolves.toBe(true); + expect(checkpointStorage.cancelSuspended).toHaveBeenCalledWith('run-1', checkpoint, agentId); + + checkpointStorage.cancelSuspended.mockClear(); + await expect( + service.cancelChatRun({ + agentId, + runId: 'run-1', + resourceId: 'draft-chat:another-user', + }), + ).resolves.toBe(false); + expect(checkpointStorage.cancelSuspended).not.toHaveBeenCalled(); + }); + it('passes tracing telemetry returned by AgentRunTracingService into stream() and resume()', async () => { const { service, checkpointStorage, runtimeCacheService, agentRunTracingService } = makeService(); diff --git a/packages/cli/src/modules/agents/__tests__/execution-to-message-mapper.test.ts b/packages/cli/src/modules/agents/__tests__/execution-to-message-mapper.test.ts index 744aac084e1..845dd2a076a 100644 --- a/packages/cli/src/modules/agents/__tests__/execution-to-message-mapper.test.ts +++ b/packages/cli/src/modules/agents/__tests__/execution-to-message-mapper.test.ts @@ -112,6 +112,32 @@ describe('execution-to-message-mapper', () => { ]); }); + it('includes the execution outcome on assistant messages', () => { + const result = executionToMessagesDto( + execution({ + status: 'error', + timeline: [ + { + type: 'tool-call', + kind: 'tool', + name: 'slow_tool', + toolCallId: 'call-1', + input: {}, + output: undefined, + startTime: 100, + endTime: 0, + success: false, + }, + ], + }), + ); + + expect(result[1]).toMatchObject({ + role: 'assistant', + executionStatus: 'error', + }); + }); + it('flattens multiple executions into a single message list', () => { const result = executionsToMessagesDto([ execution({ diff --git a/packages/cli/src/modules/agents/agent-chat.controller.ts b/packages/cli/src/modules/agents/agent-chat.controller.ts index efab0d247c1..29d96cdfa68 100644 --- a/packages/cli/src/modules/agents/agent-chat.controller.ts +++ b/packages/cli/src/modules/agents/agent-chat.controller.ts @@ -7,6 +7,7 @@ import { import type { AuthenticatedRequest } from '@n8n/db'; import { Body, Delete, Get, Param, Post, ProjectScope, RestController } from '@n8n/decorators'; import { randomUUID } from 'crypto'; +import type { Response } from 'express'; import { CredentialsService } from '@/credentials/credentials.service'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; @@ -53,39 +54,40 @@ export class AgentChatController { ); const { send } = initSseStream(res); + const abortController = new AbortController(); + const abortOnClose = () => abortController.abort(); + res.once('close', abortOnClose); + try { + // If the client supplied a sessionId and a thread already exists under that id, + // the thread must belong to this (project, agent). Otherwise a caller could + // append messages to another user's thread. A non-existent id is fine - + // executeForChat will create the thread on first persisted message. + if (sessionId) { + const existing = await this.agentExecutionService.findThreadById(sessionId); + if (abortController.signal.aborted) return; + if (existing && !threadBelongsTo(existing, projectId, agentId)) { + send({ type: 'error', message: 'Session not found' }); + return; + } + } - // If the client supplied a sessionId and a thread already exists under that id, - // the thread must belong to this (project, agent). Otherwise a caller could - // append messages to another user's thread. A non-existent id is fine - - // executeForChat will create the thread on first persisted message. - if (sessionId) { - const existing = await this.agentExecutionService.findThreadById(sessionId); - if (existing && !threadBelongsTo(existing, projectId, agentId)) { - send({ type: 'error', message: 'Session not found' }); - res.end(); + const threadId = sessionId ?? randomUUID(); + const { missing } = await this.agentValidationService.validateAgentIsRunnable( + agentId, + projectId, + credentialProvider, + ); + if (abortController.signal.aborted) return; + if (missing.length > 0) { + send({ + type: 'error', + message: 'This agent is not ready to run yet.', + errorCode: 'agent_misconfigured', + missing, + }); return; } - } - const threadId = sessionId ?? randomUUID(); - - const { missing } = await this.agentValidationService.validateAgentIsRunnable( - agentId, - projectId, - credentialProvider, - ); - if (missing.length > 0) { - send({ - type: 'error', - message: 'This agent is not ready to run yet.', - errorCode: 'agent_misconfigured', - missing, - }); - res.end(); - return; - } - - try { let executionId: string | undefined; const suspended = await pumpChunks( this.agentExecutionOrchestratorService.executeForChat({ @@ -100,6 +102,7 @@ export class AgentChatController { onExecutionRecorded: (id) => { executionId = id; }, + abortSignal: abortController.signal, }), send, ); @@ -107,11 +110,14 @@ export class AgentChatController { send({ type: 'done', sessionId: threadId, ...(executionId ? { executionId } : {}) }); } } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Chat failed'; - send({ type: 'error', message: errorMessage }); + if (!abortController.signal.aborted) { + const errorMessage = error instanceof Error ? error.message : 'Chat failed'; + send({ type: 'error', message: errorMessage }); + } + } finally { + res.off('close', abortOnClose); + res.end(); } - - res.end(); } @Post('/:agentId/chat/resume', { usesTemplates: true }) @@ -126,6 +132,9 @@ export class AgentChatController { const { runId, toolCallId, resumeData } = payload; const { send } = initSseStream(res); + const abortController = new AbortController(); + const abortOnClose = () => abortController.abort(); + res.once('close', abortOnClose); try { let executionId: string | undefined; const suspended = await pumpChunks( @@ -141,6 +150,7 @@ export class AgentChatController { onExecutionRecorded: (id) => { executionId = id; }, + abortSignal: abortController.signal, }), send, ); @@ -148,11 +158,34 @@ export class AgentChatController { send({ type: 'done', ...(executionId ? { executionId } : {}) }); } } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Resume failed'; - send({ type: 'error', message: errorMessage }); + if (!abortController.signal.aborted) { + const errorMessage = error instanceof Error ? error.message : 'Resume failed'; + send({ type: 'error', message: errorMessage }); + } + } finally { + res.off('close', abortOnClose); + res.end(); } + } - res.end(); + @Delete('/:agentId/chat/runs/:runId') + @ProjectScope('agent:execute') + async cancelChatRun( + req: AuthenticatedRequest<{ projectId: string }>, + _res: Response, + @Param('agentId') agentId: string, + @Param('runId') runId: string, + ) { + const { projectId } = req.params; + const agent = await this.agentsService.findById(agentId, projectId); + if (!agent) throw new NotFoundError(`Agent "${agentId}" not found`); + + const cancelled = await this.agentExecutionOrchestratorService.cancelChatRun({ + agentId, + runId, + resourceId: draftChatMemoryResourceId(req.user.id), + }); + return { cancelled }; } @Get('/:agentId/chat/:threadId/messages') diff --git a/packages/cli/src/modules/agents/agent-execution-orchestrator.service.ts b/packages/cli/src/modules/agents/agent-execution-orchestrator.service.ts index 34a7fefa3b1..808f40aff63 100644 --- a/packages/cli/src/modules/agents/agent-execution-orchestrator.service.ts +++ b/packages/cli/src/modules/agents/agent-execution-orchestrator.service.ts @@ -9,10 +9,11 @@ import { UserError } from 'n8n-workflow'; import { ExternalHooks } from '@/external-hooks'; import type { AgentRunTelemetryType, IAgentConfigurationTelemetryProperties } from '@/interfaces'; import { Telemetry } from '@/telemetry'; + import { AgentExecutionService, type RecordMessageParams } from './agent-execution.service'; import { AgentRunTracingService, modelIdFromSnapshot } from './agent-run-tracing.service'; import { AgentRuntimeCacheService } from './agent-runtime-cache.service'; -import { ExecutionRecorder } from './execution-recorder'; +import { ExecutionRecorder, type MessageRecord } from './execution-recorder'; import { IntegrationMessageContextService } from './integrations/integration-message-context.service'; import { N8NCheckpointStorage } from './integrations/n8n-checkpoint-storage'; import type { ToolRegistry } from './tool-registry'; @@ -40,6 +41,7 @@ export interface ExecuteForChatConfig { memory: AgentMemoryScope; /** Fired after the turn is persisted; used to attach `executionId` to SSE `done`. */ onExecutionRecorded?: (executionId: string) => void; + abortSignal?: AbortSignal; } export interface ExecuteForChatPublishedConfig { @@ -81,6 +83,7 @@ export interface ResumeForChatConfig { integrationType?: string; /** Fired after the resumed turn is persisted; used to attach `executionId` to SSE `done`. */ onExecutionRecorded?: (executionId: string) => void; + abortSignal?: AbortSignal; } export interface ExecuteForTaskPublishedConfig { @@ -134,6 +137,7 @@ export interface StreamChatResponseConfig { }; /** Fired after the turn is persisted; used to attach `executionId` to SSE `done`. */ onExecutionRecorded?: (executionId: string) => void; + abortSignal?: AbortSignal; } function getMaxIterationsChunks(): StreamChunk[] { @@ -149,6 +153,14 @@ function getMaxIterationsChunks(): StreamChunk[] { ]; } +function normalizeAbortedMessageRecord( + record: MessageRecord, + abortSignal?: AbortSignal, +): MessageRecord { + if (!abortSignal?.aborted) return record; + return { ...record, finishReason: 'cancelled', error: null }; +} + /** * Executes agents for the interactive surfaces — in-app test chat, published * chat integrations (Slack, Telegram, …), and scheduled/manual tasks — as @@ -187,6 +199,29 @@ export class AgentExecutionOrchestratorService { return executionsToMessagesDto(detail.executions); } + async cancelChatRun(params: { + agentId: string; + runId: string; + resourceId: string; + }): Promise { + const checkpointStatus = await this.n8nCheckpointStorage.getStatus(params.runId); + if (checkpointStatus.status !== 'active') return false; + + const { checkpoint } = checkpointStatus; + if ( + checkpoint.status !== 'suspended' || + checkpoint.persistence?.resourceId !== params.resourceId + ) { + return false; + } + + return await this.n8nCheckpointStorage.cancelSuspended( + params.runId, + checkpoint, + params.agentId, + ); + } + /** * Resume a suspended tool call and yield the resulting stream chunks. * Used by chat integration handlers to continue an agent run after @@ -203,6 +238,7 @@ export class AgentExecutionOrchestratorService { user, usePublishedVersion = true, onExecutionRecorded, + abortSignal, } = config; const checkpointStatus = await this.n8nCheckpointStorage.getStatus(runId); @@ -266,6 +302,7 @@ export class AgentExecutionOrchestratorService { userId: user?.id, }), ...(tracing ? { telemetry: tracing } : {}), + ...(abortSignal ? { abortSignal } : {}), }); for await (const value of streamAgentChunks(resultStream.stream)) { @@ -280,7 +317,7 @@ export class AgentExecutionOrchestratorService { // Always record resumed executions — even if they suspend again (chained HITL) // or fail while streaming. Don't repeat the original user message — the // pre-suspension execution already has it. - const messageRecord = recorder.getMessageRecord(); + const messageRecord = normalizeAbortedMessageRecord(recorder.getMessageRecord(), abortSignal); await this.persistRecordedExecution({ onExecutionRecorded, failureMessage: 'Failed to record resumed agent execution', @@ -305,7 +342,7 @@ export class AgentExecutionOrchestratorService { * Execute an agent for the in-app test chat and yield stream chunks. */ async *executeForChat(config: ExecuteForChatConfig): AsyncGenerator { - const { agentId, projectId, message, user, memory, onExecutionRecorded } = config; + const { agentId, projectId, message, user, memory, onExecutionRecorded, abortSignal } = config; // `user` is always set (see ExecuteForChatConfig) — this builds/reuses a // runtime scoped to this specific user's tool access. @@ -337,6 +374,7 @@ export class AgentExecutionOrchestratorService { configuration: runtime.telemetryConfiguration, }, onExecutionRecorded, + abortSignal, }); } @@ -463,6 +501,7 @@ export class AgentExecutionOrchestratorService { taskVersionId, telemetry, onExecutionRecorded, + abortSignal, } = config; const { threadId, resourceId } = memory; @@ -482,6 +521,7 @@ export class AgentExecutionOrchestratorService { persistence: { threadId, resourceId }, executionCounter: createAgentExecutionCounter(this.telemetry, { agentId, userId }), ...(tracing ? { telemetry: tracing } : {}), + ...(abortSignal ? { abortSignal } : {}), }); for await (const value of streamAgentChunks(resultStream.stream)) { @@ -508,7 +548,7 @@ export class AgentExecutionOrchestratorService { } finally { // Always record — even if suspended or failed, the pre-suspension/error // response text and tool calls are valuable. - const messageRecord = recorder.getMessageRecord(); + const messageRecord = normalizeAbortedMessageRecord(recorder.getMessageRecord(), abortSignal); await this.persistRecordedExecution({ onExecutionRecorded, failureMessage: 'Failed to record agent execution', diff --git a/packages/cli/src/modules/agents/integrations/__tests__/n8n-checkpoint-storage.test.ts b/packages/cli/src/modules/agents/integrations/__tests__/n8n-checkpoint-storage.test.ts index b1f54acc57b..0bd868e5f18 100644 --- a/packages/cli/src/modules/agents/integrations/__tests__/n8n-checkpoint-storage.test.ts +++ b/packages/cli/src/modules/agents/integrations/__tests__/n8n-checkpoint-storage.test.ts @@ -1,7 +1,7 @@ import type { SerializableAgentState } from '@n8n/agents'; +import type { ModuleRegistry } from '@n8n/backend-common'; import { mockLogger } from '@n8n/backend-test-utils'; import type { AgentsConfig } from '@n8n/config'; -import type { ModuleRegistry } from '@n8n/backend-common'; import type { InstanceSettings } from 'n8n-core'; import { mock } from 'vitest-mock-extended'; @@ -64,6 +64,19 @@ describe('N8NCheckpointStorage', () => { await expect(service.claimForResume('run-1', suspendedState)).resolves.toBe(false); }); + it('atomically expires a suspended checkpoint for an agent', async () => { + const { service, repository } = makeService(); + repository.cancelSuspended.mockResolvedValue(true); + + await expect(service.cancelSuspended('run-1', suspendedState, 'agent-1')).resolves.toBe(true); + + expect(repository.cancelSuspended).toHaveBeenCalledWith( + 'run-1', + 'agent-1', + JSON.stringify(suspendedState), + ); + }); + it('expires a checkpoint by runId when no agentId is given', async () => { const { service, repository } = makeService(); diff --git a/packages/cli/src/modules/agents/integrations/n8n-checkpoint-storage.ts b/packages/cli/src/modules/agents/integrations/n8n-checkpoint-storage.ts index 2ba299918db..e750a3b7d73 100644 --- a/packages/cli/src/modules/agents/integrations/n8n-checkpoint-storage.ts +++ b/packages/cli/src/modules/agents/integrations/n8n-checkpoint-storage.ts @@ -102,6 +102,19 @@ export class N8NCheckpointStorage { ); } + async cancelSuspended( + key: string, + state: SerializableAgentState, + agentId: string, + ): Promise { + if (state.status !== 'suspended') return false; + return await this.agentCheckpointRepository.cancelSuspended( + key, + agentId, + JSON.stringify(state), + ); + } + async getStatus(key: string): Promise { const checkpoint = await this.agentCheckpointRepository.findOneBy({ runId: key }); if (!checkpoint) return { status: 'not-found' }; diff --git a/packages/cli/src/modules/agents/repositories/agent-checkpoint.repository.ts b/packages/cli/src/modules/agents/repositories/agent-checkpoint.repository.ts index d6a604ad24a..b0efcf67c15 100644 --- a/packages/cli/src/modules/agents/repositories/agent-checkpoint.repository.ts +++ b/packages/cli/src/modules/agents/repositories/agent-checkpoint.repository.ts @@ -1,5 +1,5 @@ import { Service } from '@n8n/di'; -import { DataSource, Repository } from '@n8n/typeorm'; +import { DataSource, Equal, IsNull, Or, Repository } from '@n8n/typeorm'; import { AgentCheckpoint } from '../entities/agent-checkpoint.entity'; @@ -22,6 +22,20 @@ export class AgentCheckpointRepository extends Repository { return (result.affected ?? 0) > 0; } + async cancelSuspended(runId: string, agentId: string, suspendedState: string): Promise { + const result = await this.update( + { + runId, + agentId: Or(Equal(agentId), IsNull()), + expired: false, + state: suspendedState, + }, + { expired: true, state: null }, + ); + + return (result.affected ?? 0) > 0; + } + async markExpired(olderThan: Date): Promise { const result = await this.createQueryBuilder() .update() diff --git a/packages/cli/src/modules/agents/utils/execution-to-message-mapper.ts b/packages/cli/src/modules/agents/utils/execution-to-message-mapper.ts index fc2ee2ba818..64f201adea9 100644 --- a/packages/cli/src/modules/agents/utils/execution-to-message-mapper.ts +++ b/packages/cli/src/modules/agents/utils/execution-to-message-mapper.ts @@ -4,7 +4,7 @@ import { isRecord } from '@n8n/utils/is-record'; import type { AgentExecution } from '../entities/agent-execution.entity'; import type { TimelineEvent } from '../execution-recorder'; -type ExecutionTranscript = Pick; +type ExecutionTranscript = Pick; type ToolCallTimelineEvent = Extract; type ToolCallContentPart = AgentPersistedMessageContentPart & { @@ -155,6 +155,7 @@ export function executionToMessagesDto(execution: ExecutionTranscript): AgentPer role: 'assistant', content: assistantContent, executionId: execution.id, + ...(execution.status ? { executionStatus: execution.status } : {}), }); } diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index 2d11cdfd8e6..98c120a909e 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -7128,6 +7128,7 @@ "agents.builder.chat.newChat.label": "New session", "agents.chat.loadHistory.error": "Could not load chat history", "agents.chat.clearHistory.error": "Could not clear chat history", + "agents.chat.stop.error": "Couldn't stop the agent. Try again.", "agents.chat.streamInterrupted": "The connection was interrupted. Refresh the page to see the latest response.", "agents.chat.toolError.generic": "Something went wrong while running this tool", "agents.chat.clearHistory": "Clear chat history", diff --git a/packages/frontend/editor-ui/src/features/agents/__tests__/AgentChatPanel.test.ts b/packages/frontend/editor-ui/src/features/agents/__tests__/AgentChatPanel.test.ts index 0d56ae80144..a6d49b7a8a6 100644 --- a/packages/frontend/editor-ui/src/features/agents/__tests__/AgentChatPanel.test.ts +++ b/packages/frontend/editor-ui/src/features/agents/__tests__/AgentChatPanel.test.ts @@ -11,6 +11,7 @@ const loadHistoryMock = vi.fn(); const cancelAndSteerMock = vi.fn(); const messagesMock = ref([]); const isStreamingMock = ref(false); +const isCancellingMock = ref(false); const fatalErrorMock = ref<{ missing: string[] } | null>(null); @@ -32,6 +33,12 @@ vi.mock('@n8n/design-system', () => ({ N8nButton: { template: '' }, N8nCallout: { template: '
' }, N8nIconButton: { template: '
diff --git a/packages/frontend/editor-ui/src/features/agents/components/AgentChatToolSteps.vue b/packages/frontend/editor-ui/src/features/agents/components/AgentChatToolSteps.vue index 2e5488f8142..113ca680415 100644 --- a/packages/frontend/editor-ui/src/features/agents/components/AgentChatToolSteps.vue +++ b/packages/frontend/editor-ui/src/features/agents/components/AgentChatToolSteps.vue @@ -185,15 +185,17 @@ function groupLabel(): string { }); } -function hasLoadingToolCall(): boolean { - return props.toolCalls.some((tc) => tc.state === 'running' || tc.state === 'suspended'); +function hasActiveToolCall(): boolean { + return props.toolCalls.some( + (tc) => tc.state === TOOL_CALL_STATE.PENDING || tc.state === TOOL_CALL_STATE.RUNNING, + ); } diff --git a/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/messageMappers.ts b/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/messageMappers.ts index 581191bc296..a593f72fc9d 100644 --- a/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/messageMappers.ts +++ b/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/messageMappers.ts @@ -235,6 +235,7 @@ export function convertDbMessages(dbMessages: AgentPersistedMessageDto[]): ChatM state, ...(part.startTime !== undefined && { startTime: part.startTime }), ...(part.endTime !== undefined && { endTime: part.endTime }), + ...(part.childTrace && { childProgress: part.childTrace }), displaySummary: summariseToolCall(part.toolName, output, part.input), }; toolCalls.push(toolCall); diff --git a/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/types.ts b/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/types.ts index 1efe24acca4..e08aa8afe29 100644 --- a/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/types.ts +++ b/packages/frontend/editor-ui/src/features/ai/shared/agentsChat/types.ts @@ -6,6 +6,13 @@ import type { ChatMessageStatus, ToolCallState } from './constants'; export type { ChatMessageStatus, ToolCallState }; +export interface ThinkingSegment { + id: string; + content: string; + startTime?: number; + endTime?: number; +} + export interface ToolCall { tool: string; toolCallId: string; @@ -32,6 +39,14 @@ export interface ToolCall { * input). */ suspendPayload?: unknown; + /** Live progress of a delegated child, streamed while the delegation runs + * and restored from history when a `childTrace` was persisted on the + * parent's execution timeline. */ + childProgress?: { + text: string; + reasoningSegments: ThinkingSegment[]; + steps: Array<{ toolCallId: string; toolName: string; running: boolean }>; + }; } interface InteractivePayloadBase { @@ -93,13 +108,6 @@ export interface ChatMessageAttachment { file?: File; } -export interface ThinkingSegment { - id: string; - content: string; - startTime?: number; - endTime?: number; -} - export interface AgentsChatMessage { id: string; role: 'user' | 'assistant'; From 703c600b0af4995fa184f501a89bbded8d1a7fdd Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Thu, 30 Jul 2026 17:26:37 +0100 Subject: [PATCH 101/130] fix(editor): Rename preview AI Agent node to V2 (#35285) --- .../nodeCreator/components/ItemTypes/NodeItem.test.ts | 2 +- .../nodeCreator/components/Modes/AgentsMode.test.ts | 2 +- .../shared/nodeCreator/components/Modes/NodesMode.test.ts | 6 +++--- .../features/shared/nodeCreator/nodeCreator.utils.test.ts | 8 ++++---- .../features/shared/nodeCreator/views/viewsData.test.ts | 2 +- .../nodes/MessageAnAgent/MessageAnAgent.node.ts | 2 +- .../MessageAnAgent/__tests__/MessageAnAgent.node.test.ts | 8 ++++---- packages/nodes-base/nodes/MessageAnAgent/shared.ts | 2 +- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/ItemTypes/NodeItem.test.ts b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/ItemTypes/NodeItem.test.ts index 6071be1f700..5f8d2ceacd7 100644 --- a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/ItemTypes/NodeItem.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/ItemTypes/NodeItem.test.ts @@ -76,7 +76,7 @@ describe('NodeItem', () => { props: { nodeType: mockSimplifiedNodeType({ name: MESSAGE_AN_AGENT_NODE_TYPE, - displayName: 'AI Agent V1', + displayName: 'AI Agent V2', group: ['transform'], }), }, diff --git a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/AgentsMode.test.ts b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/AgentsMode.test.ts index e2b5248c09f..0c7cf26c0d8 100644 --- a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/AgentsMode.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/AgentsMode.test.ts @@ -57,7 +57,7 @@ const render = createComponentRenderer(AgentsMode); function pushAgentsViewStack() { useViewStacks().pushViewStack({ - title: 'AI Agent V1', + title: 'AI Agent V2', hasSearch: true, mode: 'agents', items: [], diff --git a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/NodesMode.test.ts b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/NodesMode.test.ts index 677f52f5cc7..29e08783ad0 100644 --- a/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/NodesMode.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/nodeCreator/components/Modes/NodesMode.test.ts @@ -42,7 +42,7 @@ function messageAnAgentElement(): NodeCreateElement { subcategory: '*', properties: mockSimplifiedNodeType({ name: MESSAGE_AN_AGENT_NODE_TYPE, - displayName: 'AI Agent V1', + displayName: 'AI Agent V2', group: ['transform'], }), }; @@ -69,13 +69,13 @@ describe('NodesMode', () => { const { emitted } = render({ pinia }); await nextTick(); - await userEvent.click(screen.getByText('AI Agent V1')); + await userEvent.click(screen.getByText('AI Agent V2')); expect(emitted('nodeTypeSelected')).toBeUndefined(); const activeStack = useViewStacks().activeViewStack; expect(activeStack.mode).toBe('agents'); - expect(activeStack.title).toBe('AI Agent V1'); + expect(activeStack.title).toBe('AI Agent V2'); expect(activeStack.hasSearch).toBe(true); expect(activeStack.rootView).toBe(REGULAR_NODE_CREATOR_VIEW); }); diff --git a/packages/frontend/editor-ui/src/features/shared/nodeCreator/nodeCreator.utils.test.ts b/packages/frontend/editor-ui/src/features/shared/nodeCreator/nodeCreator.utils.test.ts index 1c5c373d551..6b145497bca 100644 --- a/packages/frontend/editor-ui/src/features/shared/nodeCreator/nodeCreator.utils.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/nodeCreator/nodeCreator.utils.test.ts @@ -916,7 +916,7 @@ describe('NodeCreator - utils', () => { }, ); - it('should show Preview badge on the AI Agent V1 node', () => { + it('should show Preview badge on the AI Agent V2 node', () => { mockSettingsStore(true); const [result] = finalizeItems([ makeAgentNode(MESSAGE_AN_AGENT_NODE_TYPE), @@ -1214,9 +1214,9 @@ describe('NodeCreator - utils', () => { }); // The legacy node is an exact "AI Agent" match and carries the popularity factor, - // so the AI Agent V1 successor ranking first proves the boost outweighs both. + // so the AI Agent V2 successor ranking first proves the boost outweighs both. const legacyAgent = makeNode(AGENT_NODE_TYPE, 'AI Agent', ['agent']); - const messageAnAgent = makeNode(MESSAGE_AN_AGENT_NODE_TYPE, 'AI Agent V1', [ + const messageAnAgent = makeNode(MESSAGE_AN_AGENT_NODE_TYPE, 'AI Agent V2', [ 'agent', 'ai', 'sdk', @@ -1224,7 +1224,7 @@ describe('NodeCreator - utils', () => { ]); const popularity = { [AGENT_NODE_TYPE]: 98.2 }; - it('should rank the AI Agent V1 node above the legacy agent despite its popularity', () => { + it('should rank the AI Agent V2 node above the legacy agent despite its popularity', () => { const result = searchNodes('AI Agent', [legacyAgent, messageAnAgent], { popularity }); expect(result.map((item) => item.key)).toEqual([MESSAGE_AN_AGENT_NODE_TYPE, AGENT_NODE_TYPE]); }); diff --git a/packages/frontend/editor-ui/src/features/shared/nodeCreator/views/viewsData.test.ts b/packages/frontend/editor-ui/src/features/shared/nodeCreator/views/viewsData.test.ts index 971658da94a..bc369066f51 100644 --- a/packages/frontend/editor-ui/src/features/shared/nodeCreator/views/viewsData.test.ts +++ b/packages/frontend/editor-ui/src/features/shared/nodeCreator/views/viewsData.test.ts @@ -19,7 +19,7 @@ const getNodeType = vi.fn(); const aiTransformNode = mockNodeTypeDescription({ name: AI_TRANSFORM_NODE_TYPE }); const messageAnAgentNode = mockNodeTypeDescription({ name: MESSAGE_AN_AGENT_NODE_TYPE, - displayName: 'AI Agent V1', + displayName: 'AI Agent V2', hidden: true, }); diff --git a/packages/nodes-base/nodes/MessageAnAgent/MessageAnAgent.node.ts b/packages/nodes-base/nodes/MessageAnAgent/MessageAnAgent.node.ts index 1ec8bab5fd4..1639c301a0b 100644 --- a/packages/nodes-base/nodes/MessageAnAgent/MessageAnAgent.node.ts +++ b/packages/nodes-base/nodes/MessageAnAgent/MessageAnAgent.node.ts @@ -9,7 +9,7 @@ import { MessageAnAgentV2 } from './v2/MessageAnAgentV2.node'; * the `agentId` property, and (v1 only) the `listAgents` search method. */ export const baseDescription: INodeTypeBaseDescription = { - displayName: 'AI Agent V1', + displayName: 'AI Agent V2', name: 'messageAnAgent', icon: 'node:ai-agent', group: ['transform'], diff --git a/packages/nodes-base/nodes/MessageAnAgent/__tests__/MessageAnAgent.node.test.ts b/packages/nodes-base/nodes/MessageAnAgent/__tests__/MessageAnAgent.node.test.ts index 2ec2cd843c9..80557180050 100644 --- a/packages/nodes-base/nodes/MessageAnAgent/__tests__/MessageAnAgent.node.test.ts +++ b/packages/nodes-base/nodes/MessageAnAgent/__tests__/MessageAnAgent.node.test.ts @@ -810,10 +810,10 @@ describe('MessageAnAgent Node', () => { }); describe('MessageAnAgent versioning', () => { - it('uses AI Agent V1 as the display and default name', () => { - expect(baseDescription.displayName).toBe('AI Agent V1'); - expect(new MessageAnAgentV1(baseDescription).description.defaults.name).toBe('AI Agent V1'); - expect(new MessageAnAgentV2(baseDescription).description.defaults.name).toBe('AI Agent V1'); + it('uses AI Agent V2 as the display and default name', () => { + expect(baseDescription.displayName).toBe('AI Agent V2'); + expect(new MessageAnAgentV1(baseDescription).description.defaults.name).toBe('AI Agent V2'); + expect(new MessageAnAgentV2(baseDescription).description.defaults.name).toBe('AI Agent V2'); }); it('exposes v1, v2, and v3 with v3 as the default', () => { diff --git a/packages/nodes-base/nodes/MessageAnAgent/shared.ts b/packages/nodes-base/nodes/MessageAnAgent/shared.ts index d21045b13c5..54098697d87 100644 --- a/packages/nodes-base/nodes/MessageAnAgent/shared.ts +++ b/packages/nodes-base/nodes/MessageAnAgent/shared.ts @@ -20,7 +20,7 @@ export const sharedVersionDescription: Pick< > = { hidden: true, defaults: { - name: 'AI Agent V1', + name: 'AI Agent V2', }, codex: { categories: ['AI'], From ca54f325bd0954ea41640b241da256c63546d3ee Mon Sep 17 00:00:00 2001 From: Tomi Turtiainen <10324676+tomi@users.noreply.github.com> Date: Fri, 31 Jul 2026 00:03:19 +0300 Subject: [PATCH 102/130] fix: Validate Send and Wait custom forms before sending (#35262) --- packages/nodes-base/nodes/Form/utils/utils.ts | 41 ++++-- .../utils/sendAndWait/test/util.test.ts | 129 ++++++++++++++++++ .../nodes-base/utils/sendAndWait/utils.ts | 21 +++ 3 files changed, 181 insertions(+), 10 deletions(-) diff --git a/packages/nodes-base/nodes/Form/utils/utils.ts b/packages/nodes-base/nodes/Form/utils/utils.ts index da53b6819e0..774c42575f3 100644 --- a/packages/nodes-base/nodes/Form/utils/utils.ts +++ b/packages/nodes-base/nodes/Form/utils/utils.ts @@ -1,4 +1,5 @@ import { Container } from '@n8n/di'; +import { ensureError } from '@n8n/utils/errors/ensure-error'; import type { Request, Response } from 'express'; import { rm } from 'fs/promises'; import isbot from 'isbot'; @@ -1105,7 +1106,9 @@ export async function formWebhook( }; } -export function resolveRawData(context: IWebhookFunctions, rawData: string) { +type ExpressionResolutionContext = Pick; + +export function resolveRawData(context: ExpressionResolutionContext, rawData: string) { if (!rawData) return rawData; const resolvables = getResolvables(rawData); @@ -1136,21 +1139,16 @@ type ParseFormFieldsOptions = { fieldsParameterName: string; mode?: 'test' | 'production'; }; + export function parseFormFields(context: IWebhookFunctions, options: ParseFormFieldsOptions) { let fields: FormFieldsParameter = []; if (options.defineForm === 'json') { - try { - const jsonOutput = context.getNodeParameter(options.fieldsParameterName, '', { + const getJsonOutput = () => + context.getNodeParameter(options.fieldsParameterName, '', { rawExpressions: true, }) as string; - fields = tryToParseJsonToFormFields(resolveRawData(context, jsonOutput)); - } catch (error) { - throw new NodeOperationError(context.getNode(), error.message, { - description: error.message, - type: options.mode === 'test' ? 'manual-form-test' : undefined, - }); - } + fields = parseJsonFormFields(context, getJsonOutput, options.mode); } else { fields = context.getNodeParameter(options.fieldsParameterName, []) as FormFieldsParameter; for (const field of fields) { @@ -1165,3 +1163,26 @@ export function parseFormFields(context: IWebhookFunctions, options: ParseFormFi } return fields; } + +type ParseJsonFormFieldsCtx = Pick; + +/** + * @throws {NodeOperationError} if the JSON is invalid or cannot be parsed into form fields + */ +export function parseJsonFormFields( + context: ParseJsonFormFieldsCtx, + getJsonOutput: () => string, + mode?: 'test' | 'production', +) { + try { + const jsonOutput = getJsonOutput(); + + return tryToParseJsonToFormFields(resolveRawData(context, jsonOutput)); + } catch (e) { + const error = ensureError(e); + throw new NodeOperationError(context.getNode(), error.message, { + description: error.message, + type: mode === 'test' ? 'manual-form-test' : undefined, + }); + } +} diff --git a/packages/nodes-base/utils/sendAndWait/test/util.test.ts b/packages/nodes-base/utils/sendAndWait/test/util.test.ts index 5e7f5fea73d..774c1c8a139 100644 --- a/packages/nodes-base/utils/sendAndWait/test/util.test.ts +++ b/packages/nodes-base/utils/sendAndWait/test/util.test.ts @@ -176,6 +176,91 @@ describe('Send and Wait utils tests', () => { ]), ); }); + + describe('customForm validation', () => { + const mockCustomFormParameters = (params: { [key: string]: any }) => + mockExecuteFunctions.getNodeParameter.mockImplementation( + (parameterName: string) => + ({ + message: 'Pick a customer', + responseType: 'customForm', + ...params, + })[parameterName], + ); + + it('should throw when the form JSON resolves to invalid form fields', () => { + mockExecuteFunctions.getNode.mockReturnValue({ name: 'Send Email' } as any); + mockCustomFormParameters({ + defineForm: 'json', + jsonOutput: '={{ JSON.stringify($json.formFields) }}', + }); + // A customer row without a name resolves to `{ option: null }` + mockExecuteFunctions.evaluateExpression.mockReturnValue([ + { + fieldLabel: 'Customer', + fieldType: 'dropdown', + fieldOptions: { values: [{ option: null }] }, + }, + ] as any); + + expect(() => getSendAndWaitConfig(mockExecuteFunctions)).toThrow( + 'Field dropdown in field 0 has an invalid option 0', + ); + }); + + it('should return the config when the form JSON resolves to valid form fields', () => { + mockCustomFormParameters({ + defineForm: 'json', + jsonOutput: '={{ JSON.stringify($json.formFields) }}', + 'options.messageButtonLabel': 'Respond', + }); + mockExecuteFunctions.evaluateExpression.mockReturnValue([ + { + fieldLabel: 'Customer', + fieldType: 'dropdown', + fieldOptions: { values: [{ option: 'Acme Corp' }] }, + }, + ] as any); + mockExecuteFunctions.getSignedResumeUrl.mockReturnValue( + 'http://localhost/waiting-webhook/nodeID?approved=true&signature=abc', + ); + + const config = getSendAndWaitConfig(mockExecuteFunctions); + + expect(config.options).toEqual([ + { + label: 'Respond', + style: 'primary', + url: 'http://localhost/waiting-webhook/nodeID?approved=true&signature=abc', + approved: true, + }, + ]); + }); + + const readParameterNames = () => + mockExecuteFunctions.getNodeParameter.mock.calls.map(([name]) => name); + + it('should not read the form JSON when the form is defined with fields', () => { + mockCustomFormParameters({ defineForm: 'fields' }); + + getSendAndWaitConfig(mockExecuteFunctions); + + expect(readParameterNames()).not.toContain('jsonOutput'); + }); + + it('should not read the form JSON for approval response types', () => { + mockCustomFormParameters({ + responseType: 'approval', + 'approvalOptions.values': { approvalType: 'single' }, + defineForm: 'json', + jsonOutput: '={{ JSON.stringify($json.formFields) }}', + }); + + getSendAndWaitConfig(mockExecuteFunctions); + + expect(readParameterNames()).not.toContain('jsonOutput'); + }); + }); }); describe('createEmail', () => { @@ -450,6 +535,50 @@ describe('Send and Wait utils tests', () => { ); }); + // Form fields are re-resolved from upstream data when the form is rendered, so an execution + // that is already waiting with bad data still breaks the read-only GET. Pins the current + // behaviour; unmasking the reason at render time is a separate follow-up. + it('should throw when a data-driven customForm resolves to invalid form fields', async () => { + mockWebhookFunctions.getRequestObject.mockReturnValue({ method: 'GET' } as any); + mockWebhookFunctions.getResponseObject.mockReturnValue({ + render: vi.fn(), + setHeader: vi.fn(), + } as any); + mockWebhookFunctions.getNode.mockReturnValue({ name: 'Dropdown' } as any); + + // A customer row without a name resolves to `{ option: null }` + mockWebhookFunctions.evaluateExpression.mockReturnValue([ + { + fieldLabel: 'Customer', + fieldType: 'dropdown', + fieldOptions: { values: [{ option: 'Acme Corp' }, { option: null }] }, + }, + ] as any); + + mockWebhookFunctions.getNodeParameter.mockImplementation((parameterName: string) => { + const params: { [key: string]: any } = { + responseType: 'customForm', + message: 'Pick a customer', + defineForm: 'json', + jsonOutput: '={{ JSON.stringify($json.formFields) }}', + options: {}, + }; + return params[parameterName]; + }); + + const error = await sendAndWaitWebhook + .call(mockWebhookFunctions) + .catch((e: NodeOperationError) => e); + + expect(error).toBeInstanceOf(NodeOperationError); + expect((error as NodeOperationError).message).toBe( + 'Field dropdown in field 0 has an invalid option 1', + ); + // Without `type: 'manual-form-test'` the reason is masked as + // "Workflow Webhook Error: Workflow could not be started!" by webhook-helpers + expect((error as NodeOperationError).type).toBeUndefined(); + }); + it('should handle customForm POST webhook', async () => { mockWebhookFunctions.getRequestObject.mockReturnValue({ method: 'POST', diff --git a/packages/nodes-base/utils/sendAndWait/utils.ts b/packages/nodes-base/utils/sendAndWait/utils.ts index 8c559395a1e..96de977a496 100644 --- a/packages/nodes-base/utils/sendAndWait/utils.ts +++ b/packages/nodes-base/utils/sendAndWait/utils.ts @@ -13,6 +13,7 @@ import { cssVariables } from '../../nodes/Form/cssVariables'; import { formFieldsProperties } from '../../nodes/Form/Form.node'; import { parseFormFields, + parseJsonFormFields, prepareFormData, prepareFormFields, prepareFormReturnItem, @@ -493,6 +494,22 @@ export async function sendAndWaitWebhook(this: IWebhookFunctions) { } // Send and Wait Config ----------------------------------------------------------- + +// The response form is only built when it is requested, from data that may no longer resolve +// by then. Parse it here, exactly as the webhook will, so a form that cannot be built fails +// the node instead of sending a message with a link that can never render. +function validateCustomFormFields(context: IExecuteFunctions) { + const defineForm = context.getNodeParameter('defineForm', 0, 'fields') as 'fields' | 'json'; + // The 'fields' branch has nothing that needs to be validated + if (defineForm !== 'json') return; + + const getJsonOutput = () => + context.getNodeParameter('jsonOutput', 0, '', { + rawExpressions: true, + }) as string; + parseJsonFormFields(context, getJsonOutput); +} + export function getSendAndWaitConfig(context: IExecuteFunctions): SendAndWaitConfig { const message = escapeHtml((context.getNodeParameter('message', 0, '') as string).trim()) .replace(/\\n/g, '\n') @@ -517,6 +534,10 @@ export function getSendAndWaitConfig(context: IExecuteFunctions): SendAndWaitCon const responseType = context.getNodeParameter('responseType', 0, 'approval') as string; + if (responseType === 'customForm') { + validateCustomFormFields(context); + } + const approvedSignedResumeUrl = context.getSignedResumeUrl({ approved: 'true' }); if (responseType === 'freeText' || responseType === 'customForm') { From d4902d58f39c852bffa79855b15edb535e45a907 Mon Sep 17 00:00:00 2001 From: Svetoslav Dekov Date: Fri, 31 Jul 2026 00:33:15 +0300 Subject: [PATCH 103/130] feat(editor): Add per-agent MCP access management with agent scopes (#34853) Co-authored-by: Ricardo Espinoza Co-authored-by: Claude Fable 5 --- packages/@n8n/api-types/src/agents/dto.ts | 13 + packages/@n8n/api-types/src/index.ts | 1 + .../@n8n/api-types/src/schemas/mcp.schema.ts | 3 + .../agent-mcp-access.service.test.ts | 218 ++++++++++++ .../agents/__tests__/agent.repository.test.ts | 43 +++ .../agents/__tests__/agents.service.test.ts | 1 + .../agents/agent-mcp-access.controller.ts | 34 ++ .../agents/agent-mcp-access.service.ts | 123 +++++++ .../cli/src/modules/agents/agents.module.ts | 1 + .../cli/src/modules/agents/agents.service.ts | 9 +- .../modules/agents/entities/agent.entity.ts | 4 + .../agents/repositories/agent.repository.ts | 61 +++- .../mcp/__tests__/agent-tools.service.test.ts | 125 ++++++- .../__tests__/mcp-protected-resource.test.ts | 21 ++ .../modules/mcp/__tests__/mcp-scopes.test.ts | 16 +- .../src/modules/mcp/mcp-protected-resource.ts | 32 +- packages/cli/src/modules/mcp/mcp-scopes.ts | 32 ++ .../src/modules/mcp/mcp-tool-availability.ts | 9 + packages/cli/src/modules/mcp/mcp.service.ts | 5 +- .../mcp/tools/agents/agent-tools.service.ts | 87 +++-- .../oauth-consent.controller.api.test.ts | 5 +- .../__tests__/oauth-server.service.test.ts | 3 +- .../__tests__/oauth-token.service.test.ts | 3 +- .../__tests__/oauth.controller.api.test.ts | 25 +- .../__tests__/project-scope.service.test.ts | 52 +++ .../permissions.ee/project-scope.service.ts | 24 ++ .../frontend/@n8n/i18n/src/locales/en.json | 36 ++ .../ExposeAllWorkflowsToMcpModal.vue | 70 +++- .../useExposeAllWorkflowsToMcpOffer.ts | 15 +- .../agents/__tests__/AgentBuilderView.test.ts | 32 ++ .../agents/__tests__/AgentCard.test.ts | 98 ++++++ .../src/features/agents/agent.types.ts | 5 + .../components/AgentBuilderEditorColumn.vue | 21 ++ .../features/agents/components/AgentCard.vue | 73 +++- .../agents/components/AgentMcpPanel.vue | 63 ++++ .../agents/composables/useAgentApi.ts | 1 + .../agents/views/AgentBuilderView.vue | 98 +++++- .../mcpAccess/SettingsMCPAgentsView.test.ts | 275 +++++++++++++++ .../ai/mcpAccess/SettingsMCPAgentsView.vue | 226 +++++++++++++ .../features/ai/mcpAccess/SettingsMCPView.vue | 56 +++- .../ai/mcpAccess/clients.utils.test.ts | 25 +- .../features/ai/mcpAccess/clients.utils.ts | 14 +- .../mcpAccess/components/MCPAgentsSelect.vue | 165 +++++++++ .../components/MCPWorkflowsSelect.vue | 5 - .../mcpAccess/components/tabs/AgentsTable.vue | 312 ++++++++++++++++++ .../components/tabs/OAuthClientsTable.vue | 5 +- .../ai/mcpAccess/composables/useMcp.ts | 5 + .../src/features/ai/mcpAccess/mcp.api.test.ts | 42 +++ .../src/features/ai/mcpAccess/mcp.api.ts | 51 +++ .../features/ai/mcpAccess/mcp.constants.ts | 4 + .../features/ai/mcpAccess/mcp.store.test.ts | 33 ++ .../src/features/ai/mcpAccess/mcp.store.ts | 100 +++++- .../modals/MCPConnectAgentsModal.vue | 149 +++++++++ .../modals/MCPConnectWorkflowsModal.vue | 2 +- .../ai/mcpAccess/module.descriptor.ts | 21 ++ 55 files changed, 2809 insertions(+), 143 deletions(-) create mode 100644 packages/cli/src/modules/agents/__tests__/agent-mcp-access.service.test.ts create mode 100644 packages/cli/src/modules/agents/agent-mcp-access.controller.ts create mode 100644 packages/cli/src/modules/agents/agent-mcp-access.service.ts create mode 100644 packages/cli/src/modules/mcp/mcp-tool-availability.ts create mode 100644 packages/cli/src/permissions.ee/__tests__/project-scope.service.test.ts create mode 100644 packages/cli/src/permissions.ee/project-scope.service.ts create mode 100644 packages/frontend/editor-ui/src/features/agents/components/AgentMcpPanel.vue create mode 100644 packages/frontend/editor-ui/src/features/ai/mcpAccess/SettingsMCPAgentsView.test.ts create mode 100644 packages/frontend/editor-ui/src/features/ai/mcpAccess/SettingsMCPAgentsView.vue create mode 100644 packages/frontend/editor-ui/src/features/ai/mcpAccess/components/MCPAgentsSelect.vue create mode 100644 packages/frontend/editor-ui/src/features/ai/mcpAccess/components/tabs/AgentsTable.vue create mode 100644 packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.api.test.ts create mode 100644 packages/frontend/editor-ui/src/features/ai/mcpAccess/modals/MCPConnectAgentsModal.vue diff --git a/packages/@n8n/api-types/src/agents/dto.ts b/packages/@n8n/api-types/src/agents/dto.ts index 5c7aaaba4b3..d0009a99e7f 100644 --- a/packages/@n8n/api-types/src/agents/dto.ts +++ b/packages/@n8n/api-types/src/agents/dto.ts @@ -26,6 +26,7 @@ export const AGENTS_LIST_SORT_OPTIONS = [ const agentListFilterSchema = z .object({ query: z.string().trim().min(1).max(128).optional(), + availableInMCP: z.boolean().optional(), }) .strict(); @@ -66,6 +67,18 @@ export class AgentProviderModelsQueryDto extends Z.class({ credentialId: z.string().min(1).max(64).optional(), }) {} +/** + * Target selector for bulk-toggling agents' MCP availability. Exactly one of + * `agentIds`, `projectId`, or `allAgents` must be provided (mirrors the + * workflows equivalent, `UpdateWorkflowsAvailabilityDto`). + */ +export class UpdateAgentsMcpAvailabilityDto extends Z.class({ + availableInMCP: z.boolean(), + agentIds: z.array(z.string().min(1)).min(1).max(100).optional(), + projectId: z.string().min(1).optional(), + allAgents: z.literal(true).optional(), +}) {} + export class CreateAgentDto extends Z.class({ name: z.string().min(1), }) {} diff --git a/packages/@n8n/api-types/src/index.ts b/packages/@n8n/api-types/src/index.ts index 656703bfd8b..4a4ef4f01ac 100644 --- a/packages/@n8n/api-types/src/index.ts +++ b/packages/@n8n/api-types/src/index.ts @@ -530,6 +530,7 @@ export { MCP_APPS_VARIANT_CONTROL, MCP_APPS_VARIANT_ENABLED, MCP_CANVAS_GROUPS_FLAG, + MCP_AGENT_SCOPES, MCP_INSTANCE_SCOPES, MCP_CLIENT_BRAND_MATCHERS, MCP_CLIENT_TYPE_FILTERS, diff --git a/packages/@n8n/api-types/src/schemas/mcp.schema.ts b/packages/@n8n/api-types/src/schemas/mcp.schema.ts index 41796b6d369..2265f1f2424 100644 --- a/packages/@n8n/api-types/src/schemas/mcp.schema.ts +++ b/packages/@n8n/api-types/src/schemas/mcp.schema.ts @@ -16,6 +16,8 @@ export const MCP_APPS_VARIANT_ENABLED = 'variant'; // current behaviour. export const MCP_CANVAS_GROUPS_FLAG = '102_mcp_canvas_groups'; +export const MCP_AGENT_SCOPES = ['agent:read', 'agent:write'] as const; + /** * OAuth scopes a user can grant to an MCP client on the consent screen for * the instance-level MCP server. Each scope gates a set of MCP tools; the @@ -27,6 +29,7 @@ export const MCP_INSTANCE_SCOPES = [ 'workflow:write', 'workflow:execute', 'execution:read', + ...MCP_AGENT_SCOPES, 'credential:read', 'dataTable:read', 'dataTable:write', diff --git a/packages/cli/src/modules/agents/__tests__/agent-mcp-access.service.test.ts b/packages/cli/src/modules/agents/__tests__/agent-mcp-access.service.test.ts new file mode 100644 index 00000000000..2194e8fa848 --- /dev/null +++ b/packages/cli/src/modules/agents/__tests__/agent-mcp-access.service.test.ts @@ -0,0 +1,218 @@ +import { mockInstance } from '@n8n/backend-test-utils'; +import { User } from '@n8n/db'; + +import { ProjectScopeService } from '@/permissions.ee/project-scope.service'; + +import { AgentMcpAccessService } from '../agent-mcp-access.service'; +import { AgentRepository } from '../repositories/agent.repository'; + +const user = Object.assign(new User(), { id: 'user-1' }); + +const candidate = (id: string, projectId: string, availableInMCP: boolean) => ({ + id, + projectId, + availableInMCP, +}); + +describe('AgentMcpAccessService', () => { + const agentRepository = mockInstance(AgentRepository); + const projectScopeService = mockInstance(ProjectScopeService); + const service = new AgentMcpAccessService(agentRepository, projectScopeService); + + beforeEach(() => { + vi.clearAllMocks(); + projectScopeService.getProjectIds.mockResolvedValue(['project-1']); + }); + + describe('getAgents', () => { + it('lists non-exposed agents from projects where the user holds agent:update', async () => { + agentRepository.findByProjectIdsPaginated.mockResolvedValue({ count: 0, data: [] }); + + await service.getAgents(user, { skip: 0, take: 10 } as never); + + expect(projectScopeService.getProjectIds).toHaveBeenCalledWith(user, ['agent:update']); + expect(agentRepository.findByProjectIdsPaginated).toHaveBeenCalledWith( + ['project-1'], + expect.objectContaining({ filter: { availableInMCP: false } }), + { withProject: true }, + ); + }); + + it('lists agents without a project restriction for global agent:update', async () => { + projectScopeService.getProjectIds.mockResolvedValue(null); + agentRepository.findByProjectIdsPaginated.mockResolvedValue({ count: 0, data: [] }); + + await service.getAgents(user, { skip: 0, take: 10 } as never); + + expect(agentRepository.findByProjectIdsPaginated).toHaveBeenCalledWith( + null, + expect.objectContaining({ filter: { availableInMCP: false } }), + { withProject: true }, + ); + }); + + it('lists exposed agents only from projects where the user holds agent:update', async () => { + projectScopeService.getProjectIds.mockResolvedValue(['project-1']); + agentRepository.findByProjectIdsPaginated.mockResolvedValue({ count: 0, data: [] }); + + await service.getAgents(user, { + skip: 0, + take: 10, + filter: { availableInMCP: true }, + } as never); + + expect(agentRepository.findByProjectIdsPaginated).toHaveBeenCalledWith( + ['project-1'], + expect.objectContaining({ filter: { availableInMCP: true } }), + { withProject: true }, + ); + }); + + it('lists exposed agents without a project restriction for global agent:update', async () => { + projectScopeService.getProjectIds.mockResolvedValue(null); + agentRepository.findByProjectIdsPaginated.mockResolvedValue({ count: 0, data: [] }); + + await service.getAgents(user, { + skip: 0, + take: 10, + filter: { availableInMCP: true }, + } as never); + + expect(agentRepository.findByProjectIdsPaginated).toHaveBeenCalledWith( + null, + expect.objectContaining({ filter: { availableInMCP: true } }), + { withProject: true }, + ); + }); + }); + + describe('bulkSetAvailableInMCP', () => { + it('rejects when no target is provided', async () => { + await expect( + service.bulkSetAvailableInMCP(user, { availableInMCP: true } as never), + ).rejects.toThrow('exactly one'); + }); + + it('rejects when multiple targets are provided', async () => { + await expect( + service.bulkSetAvailableInMCP(user, { + availableInMCP: true, + agentIds: ['a1'], + allAgents: true, + } as never), + ).rejects.toThrow('exactly one'); + }); + + it('updates only accessible agents not already in the requested state', async () => { + agentRepository.findMcpAvailabilityCandidates.mockResolvedValue([ + candidate('a1', 'project-1', false), + candidate('a2', 'project-1', true), + candidate('a3', 'project-2', false), + ]); + + const result = await service.bulkSetAvailableInMCP(user, { + availableInMCP: true, + agentIds: ['a1', 'a2', 'a3'], + } as never); + + expect(agentRepository.setAvailableInMCP).toHaveBeenCalledWith(['a1'], true); + expect(result).toEqual({ + updatedCount: 1, + updatedIds: ['a1'], + unchangedIds: ['a2'], + }); + }); + + it('resolves candidates from every user project for allAgents and omits id lists', async () => { + projectScopeService.getProjectIds.mockResolvedValue(['p1', 'p2']); + agentRepository.findMcpAvailabilityCandidates.mockResolvedValue([ + candidate('a1', 'p1', true), + ]); + + const result = await service.bulkSetAvailableInMCP(user, { + availableInMCP: false, + allAgents: true, + } as never); + + expect(agentRepository.findMcpAvailabilityCandidates).toHaveBeenCalledWith({ + projectIds: ['p1', 'p2'], + }); + expect(agentRepository.setAvailableInMCP).toHaveBeenCalledWith(['a1'], false); + expect(result.updatedCount).toBe(1); + expect(result.updatedIds).toBeUndefined(); + expect(result.unchangedIds).toBeUndefined(); + }); + + it('resolves all candidates for allAgents with global agent:update', async () => { + projectScopeService.getProjectIds.mockResolvedValue(null); + agentRepository.findMcpAvailabilityCandidates.mockResolvedValue([ + candidate('a1', 'p1', false), + candidate('a2', 'p2', false), + ]); + + const result = await service.bulkSetAvailableInMCP(user, { + availableInMCP: true, + allAgents: true, + } as never); + + expect(agentRepository.findMcpAvailabilityCandidates).toHaveBeenCalledWith({ all: true }); + expect(agentRepository.setAvailableInMCP).toHaveBeenCalledWith(['a1', 'a2'], true); + expect(result.updatedCount).toBe(2); + }); + + it('chunks large updates to stay within database parameter limits', async () => { + projectScopeService.getProjectIds.mockResolvedValue(null); + const candidates = Array.from({ length: 600 }, (_, index) => + candidate(`a${index}`, `p${index}`, false), + ); + agentRepository.findMcpAvailabilityCandidates.mockResolvedValue(candidates); + + const result = await service.bulkSetAvailableInMCP(user, { + availableInMCP: true, + allAgents: true, + } as never); + + expect(agentRepository.setAvailableInMCP).toHaveBeenCalledTimes(2); + expect(agentRepository.setAvailableInMCP).toHaveBeenNthCalledWith( + 1, + candidates.slice(0, 500).map(({ id }) => id), + true, + ); + expect(agentRepository.setAvailableInMCP).toHaveBeenNthCalledWith( + 2, + candidates.slice(500).map(({ id }) => id), + true, + ); + expect(result.updatedCount).toBe(600); + }); + + it('does not load agents from a project the user cannot update', async () => { + projectScopeService.getProjectIds.mockResolvedValue([]); + + const result = await service.bulkSetAvailableInMCP(user, { + availableInMCP: true, + projectId: 'p1', + } as never); + + expect(agentRepository.findMcpAvailabilityCandidates).not.toHaveBeenCalled(); + expect(agentRepository.setAvailableInMCP).not.toHaveBeenCalled(); + expect(result).toEqual({ updatedCount: 0 }); + }); + + it('resolves candidates from the given project only', async () => { + projectScopeService.getProjectIds.mockResolvedValue(['p1']); + agentRepository.findMcpAvailabilityCandidates.mockResolvedValue([ + candidate('a1', 'p1', false), + ]); + + await service.bulkSetAvailableInMCP(user, { + availableInMCP: true, + projectId: 'p1', + } as never); + + expect(agentRepository.findMcpAvailabilityCandidates).toHaveBeenCalledWith({ + projectIds: ['p1'], + }); + }); + }); +}); diff --git a/packages/cli/src/modules/agents/__tests__/agent.repository.test.ts b/packages/cli/src/modules/agents/__tests__/agent.repository.test.ts index e01c3875a4a..a0ef21892a5 100644 --- a/packages/cli/src/modules/agents/__tests__/agent.repository.test.ts +++ b/packages/cli/src/modules/agents/__tests__/agent.repository.test.ts @@ -135,6 +135,7 @@ describe('AgentRepository', () => { 'agent.name', 'agent.projectId', 'agent.activeVersionId', + 'agent.availableInMCP', 'agent.updatedAt', ]); expect(mockQb.where).toHaveBeenCalledWith('agent.projectId IN (:...projectIds)', { @@ -144,6 +145,15 @@ describe('AgentRepository', () => { expect(mockQb.take).not.toHaveBeenCalled(); }); + it('omits the project filter when project access is global', async () => { + const mockQb = makeQb(); + vi.spyOn(repository, 'createQueryBuilder').mockReturnValue(mockQb as never); + + await repository.findSummariesByProjectIds(null); + + expect(mockQb.where).not.toHaveBeenCalled(); + }); + it('pushes all filters and the limit into the query', async () => { const mockQb = makeQb(); vi.spyOn(repository, 'createQueryBuilder').mockReturnValue(mockQb as never); @@ -204,6 +214,27 @@ describe('AgentRepository', () => { expect(result).toEqual({ count: 1, data: agents }); }); + it('omits the project filter when project access is global', async () => { + const mockQb = { + leftJoinAndSelect: vi.fn().mockReturnThis(), + where: vi.fn().mockReturnThis(), + andWhere: vi.fn().mockReturnThis(), + addSelect: vi.fn().mockReturnThis(), + orderBy: vi.fn().mockReturnThis(), + skip: vi.fn().mockReturnThis(), + take: vi.fn().mockReturnThis(), + getManyAndCount: vi.fn().mockResolvedValue([[], 0]), + }; + vi.spyOn(repository, 'createQueryBuilder').mockReturnValue(mockQb as never); + + await repository.findByProjectIdsPaginated(null, { + skip: 0, + take: 25, + } as never); + + expect(mockQb.where).not.toHaveBeenCalled(); + }); + it('applies the name search filter', async () => { const mockQb = { leftJoinAndSelect: vi.fn().mockReturnThis(), @@ -229,6 +260,18 @@ describe('AgentRepository', () => { }); }); + describe('findMcpAvailabilityCandidates', () => { + it('omits the where clause when all agents are requested', async () => { + const find = vi.spyOn(repository, 'find').mockResolvedValue([]); + + await repository.findMcpAvailabilityCandidates({ all: true }); + + expect(find).toHaveBeenCalledWith({ + select: ['id', 'projectId', 'availableInMCP'], + }); + }); + }); + describe('findByIntegrationCredential', () => { const makeAgent = (id: string, integrations: AgentIntegrationConfig[]) => ({ id, integrations }) as Agent; diff --git a/packages/cli/src/modules/agents/__tests__/agents.service.test.ts b/packages/cli/src/modules/agents/__tests__/agents.service.test.ts index 8e232281de3..0f8cd7599d5 100644 --- a/packages/cli/src/modules/agents/__tests__/agents.service.test.ts +++ b/packages/cli/src/modules/agents/__tests__/agents.service.test.ts @@ -122,6 +122,7 @@ describe('AgentsService', () => { skills: [], }, versionId: expect.any(String), + availableInMCP: false, }); }); diff --git a/packages/cli/src/modules/agents/agent-mcp-access.controller.ts b/packages/cli/src/modules/agents/agent-mcp-access.controller.ts new file mode 100644 index 00000000000..2be16535b46 --- /dev/null +++ b/packages/cli/src/modules/agents/agent-mcp-access.controller.ts @@ -0,0 +1,34 @@ +import { ListAgentsQueryDto, UpdateAgentsMcpAvailabilityDto } from '@n8n/api-types'; +import type { AuthenticatedRequest } from '@n8n/db'; +import { Body, Get, Patch, Query, RestController } from '@n8n/decorators'; +import type { Response } from 'express'; + +import { AgentMcpAccessService } from './agent-mcp-access.service'; + +/** + * Per-agent MCP availability endpoints, mirroring the per-workflow ones on + * `McpSettingsController` (`/mcp/workflows`). They live in the agents module + * so they only exist when the module is active. + * + * No `@ProjectScope` decorators: these routes carry no project in their URL, + * so `AgentMcpAccessService` enforces the `agent:update` scope per project + * instead — same reasoning as the workflow toggle endpoint. + */ +@RestController('/mcp/agents') +export class AgentMcpAccessController { + constructor(private readonly agentMcpAccessService: AgentMcpAccessService) {} + + @Get('/') + async getMcpAgents(req: AuthenticatedRequest, res: Response, @Query query: ListAgentsQueryDto) { + res.json(await this.agentMcpAccessService.getAgents(req.user, query)); + } + + @Patch('/toggle-access') + async toggleAgentsMCPAccess( + req: AuthenticatedRequest, + _res: Response, + @Body dto: UpdateAgentsMcpAvailabilityDto, + ) { + return await this.agentMcpAccessService.bulkSetAvailableInMCP(req.user, dto); + } +} diff --git a/packages/cli/src/modules/agents/agent-mcp-access.service.ts b/packages/cli/src/modules/agents/agent-mcp-access.service.ts new file mode 100644 index 00000000000..fdff07bad95 --- /dev/null +++ b/packages/cli/src/modules/agents/agent-mcp-access.service.ts @@ -0,0 +1,123 @@ +import type { ListAgentsQueryDto, UpdateAgentsMcpAvailabilityDto } from '@n8n/api-types'; +import type { User } from '@n8n/db'; +import { Service } from '@n8n/di'; + +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import { ProjectScopeService } from '@/permissions.ee/project-scope.service'; + +import type { Agent } from './entities/agent.entity'; +import { AgentRepository } from './repositories/agent.repository'; + +const BULK_CHUNK_SIZE = 500; + +type BulkSetAvailableInMCPResult = { + updatedCount: number; + /** Only present when the request targeted explicit `agentIds`. */ + updatedIds?: string[]; + unchangedIds?: string[]; +}; + +/** + * Grants and revokes per-agent MCP availability (the `availableInMCP` flag), + * mirroring the per-workflow flow in `McpSettingsService`. Project access is + * resolved here because the REST routes have no project in their URL for a + * `@ProjectScope` gate. + */ +@Service() +export class AgentMcpAccessService { + constructor( + private readonly agentRepository: AgentRepository, + private readonly projectScopeService: ProjectScopeService, + ) {} + + /** + * Paginated list of agents in projects where the user holds `agent:update`. + * Defaults to agents that are not yet available to MCP. + */ + async getAgents( + user: User, + options: ListAgentsQueryDto, + ): Promise<{ count: number; data: Agent[] }> { + const projectIds = await this.projectScopeService.getProjectIds(user, ['agent:update']); + return await this.agentRepository.findByProjectIdsPaginated( + projectIds, + { + ...options, + filter: { + ...options.filter, + availableInMCP: options.filter?.availableInMCP ?? false, + }, + }, + { withProject: true }, + ); + } + + async bulkSetAvailableInMCP( + user: User, + dto: UpdateAgentsMcpAvailabilityDto, + ): Promise { + const targets = [dto.agentIds, dto.projectId, dto.allAgents].filter( + (target) => target !== undefined, + ); + if (targets.length !== 1) { + throw new BadRequestError('Provide exactly one of "agentIds", "projectId", or "allAgents".'); + } + + const projectIds = await this.projectScopeService.getProjectIds(user, ['agent:update']); + const allowedProjectIds = projectIds === null ? null : new Set(projectIds); + if (dto.projectId && allowedProjectIds !== null && !allowedProjectIds.has(dto.projectId)) { + return { updatedCount: 0 }; + } + + const candidates = await this.resolveCandidates(dto, projectIds); + const accessible = + allowedProjectIds === null + ? candidates + : candidates.filter((agent) => allowedProjectIds.has(agent.projectId)); + + const toUpdate = accessible.filter((agent) => agent.availableInMCP !== dto.availableInMCP); + + const agentIds = toUpdate.map((agent) => agent.id); + for (let start = 0; start < agentIds.length; start += BULK_CHUNK_SIZE) { + await this.agentRepository.setAvailableInMCP( + agentIds.slice(start, start + BULK_CHUNK_SIZE), + dto.availableInMCP, + ); + } + + return { + updatedCount: toUpdate.length, + // Per-id breakdown only for explicit `agentIds` requests; the caller + // uses it to confirm each requested agent landed in a known state. + ...(dto.agentIds + ? { + updatedIds: agentIds, + unchangedIds: accessible + .filter((agent) => agent.availableInMCP === dto.availableInMCP) + .map((agent) => agent.id), + } + : {}), + }; + } + + private async resolveCandidates( + dto: UpdateAgentsMcpAvailabilityDto, + projectIds: string[] | null, + ): Promise>> { + if (dto.agentIds) { + return await this.agentRepository.findMcpAvailabilityCandidates({ + ids: [...new Set(dto.agentIds)], + }); + } + + if (dto.projectId) { + return await this.agentRepository.findMcpAvailabilityCandidates({ + projectIds: [dto.projectId], + }); + } + + return await this.agentRepository.findMcpAvailabilityCandidates( + projectIds === null ? { all: true } : { projectIds }, + ); + } +} diff --git a/packages/cli/src/modules/agents/agents.module.ts b/packages/cli/src/modules/agents/agents.module.ts index a85466cc896..2f728468d2d 100644 --- a/packages/cli/src/modules/agents/agents.module.ts +++ b/packages/cli/src/modules/agents/agents.module.ts @@ -21,6 +21,7 @@ export class AgentsModule implements ModuleInterface { await import('./agent-tasks.controller.js'); await import('./agent-sandbox.controller.js'); await import('./agents-list.controller.js'); + await import('./agent-mcp-access.controller.js'); await import('./builder/agents-builder-settings.controller.js'); const { AgentsService } = await import('./agents.service.js'); diff --git a/packages/cli/src/modules/agents/agents.service.ts b/packages/cli/src/modules/agents/agents.service.ts index 702576cd773..e8a6ce0397f 100644 --- a/packages/cli/src/modules/agents/agents.service.ts +++ b/packages/cli/src/modules/agents/agents.service.ts @@ -45,7 +45,11 @@ export class AgentsService { private readonly agentExecutionService: AgentExecutionService, ) {} - async create(projectId: string, name: string): Promise { + async create( + projectId: string, + name: string, + { availableInMCP = false }: { availableInMCP?: boolean } = {}, + ): Promise { const defaultConfig: AgentJsonConfig = { name, model: '', @@ -59,6 +63,7 @@ export class AgentsService { projectId, schema: defaultConfig, versionId: uuid(), + availableInMCP, }); const saved = await this.agentRepository.save(agent); @@ -173,7 +178,7 @@ export class AgentsService { * filters and limit applied in the database. */ async findSummariesInProjects( - projectIds: string[], + projectIds: string[] | null, options: AgentSummaryFilters = {}, ): Promise { return await this.agentRepository.findSummariesByProjectIds(projectIds, options); diff --git a/packages/cli/src/modules/agents/entities/agent.entity.ts b/packages/cli/src/modules/agents/entities/agent.entity.ts index 15ed6303283..d8c929c02b4 100644 --- a/packages/cli/src/modules/agents/entities/agent.entity.ts +++ b/packages/cli/src/modules/agents/entities/agent.entity.ts @@ -35,6 +35,10 @@ export class Agent extends WithTimestampsAndStringId { @JsonColumn({ default: '{}' }) skills: Record; + /** Whether MCP clients granted agent scopes may operate on this agent. */ + @Column({ default: false }) + availableInMCP: boolean; + /** UUID identifying the current draft; bumped on the first config save after each publish. */ @Column({ type: 'varchar', length: 36, nullable: true }) versionId: string | null; diff --git a/packages/cli/src/modules/agents/repositories/agent.repository.ts b/packages/cli/src/modules/agents/repositories/agent.repository.ts index 9792c3835fd..abd04cb309c 100644 --- a/packages/cli/src/modules/agents/repositories/agent.repository.ts +++ b/packages/cli/src/modules/agents/repositories/agent.repository.ts @@ -6,7 +6,7 @@ import { Agent } from '../entities/agent.entity'; export type AgentSummary = Pick< Agent, - 'id' | 'name' | 'projectId' | 'activeVersionId' | 'updatedAt' + 'id' | 'name' | 'projectId' | 'activeVersionId' | 'availableInMCP' | 'updatedAt' >; export type AgentSummaryFilters = { @@ -36,10 +36,10 @@ export class AgentRepository extends Repository { * filters and the limit into the query. */ async findSummariesByProjectIds( - projectIds: string[], + projectIds: string[] | null, options: AgentSummaryFilters = {}, ): Promise { - if (projectIds.length === 0) return []; + if (projectIds?.length === 0) return []; const query = this.createQueryBuilder('agent') .select([ @@ -47,11 +47,14 @@ export class AgentRepository extends Repository { 'agent.name', 'agent.projectId', 'agent.activeVersionId', + 'agent.availableInMCP', 'agent.updatedAt', ]) - .where('agent.projectId IN (:...projectIds)', { projectIds }) .orderBy('agent.updatedAt', 'DESC'); + if (projectIds !== null) { + query.where('agent.projectId IN (:...projectIds)', { projectIds }); + } if (options.query) { query.andWhere('LOWER(agent.name) LIKE LOWER(:query)', { query: `%${options.query}%` }); } @@ -69,15 +72,26 @@ export class AgentRepository extends Repository { } async findByProjectIdsPaginated( - projectIds: string[], + projectIds: string[] | null, options: ListAgentsQueryDto, + { withProject = false }: { withProject?: boolean } = {}, ): Promise<{ count: number; data: Agent[] }> { - if (projectIds.length === 0) return { count: 0, data: [] }; + if (projectIds?.length === 0) return { count: 0, data: [] }; - const query = this.createQueryBuilder('agent') - .leftJoinAndSelect('agent.activeVersion', 'activeVersion') - .where('agent.projectId IN (:...projectIds)', { projectIds }); + const query = this.createQueryBuilder('agent').leftJoinAndSelect( + 'agent.activeVersion', + 'activeVersion', + ); + // Only cross-project consumers (MCP settings) label each agent by its home + // project; the overview lists don't read it, so they skip the extra join. + if (withProject) { + query.leftJoinAndSelect('agent.project', 'project'); + } + + if (projectIds !== null) { + query.where('agent.projectId IN (:...projectIds)', { projectIds }); + } this.applyFilters(query, options.filter); this.applySorting(query, options.sortBy); query.skip(options.skip).take(options.take); @@ -93,6 +107,11 @@ export class AgentRepository extends Repository { if (filter?.query) { query.andWhere('LOWER(agent.name) LIKE LOWER(:query)', { query: `%${filter.query}%` }); } + if (filter?.availableInMCP !== undefined) { + query.andWhere('agent.availableInMCP = :availableInMCP', { + availableInMCP: filter.availableInMCP, + }); + } } private applySorting( @@ -159,6 +178,30 @@ export class AgentRepository extends Repository { }); } + async findMcpAvailabilityCandidates( + where: { ids: string[] } | { projectIds: string[] } | { all: true }, + ): Promise>> { + if ('ids' in where && where.ids.length === 0) return []; + if ('projectIds' in where && where.projectIds.length === 0) return []; + + const criteria = + 'ids' in where + ? { id: In(where.ids) } + : 'projectIds' in where + ? { projectId: In(where.projectIds) } + : undefined; + + return await this.find({ + select: ['id', 'projectId', 'availableInMCP'], + where: criteria, + }); + } + + async setAvailableInMCP(agentIds: string[], availableInMCP: boolean): Promise { + if (agentIds.length === 0) return; + await this.update({ id: In(agentIds) }, { availableInMCP }); + } + async findPublished(): Promise { return await this.createQueryBuilder('agent') .innerJoinAndSelect('agent.activeVersion', 'activeVersion') diff --git a/packages/cli/src/modules/mcp/__tests__/agent-tools.service.test.ts b/packages/cli/src/modules/mcp/__tests__/agent-tools.service.test.ts index fbcf87ecb6c..423b827edf2 100644 --- a/packages/cli/src/modules/mcp/__tests__/agent-tools.service.test.ts +++ b/packages/cli/src/modules/mcp/__tests__/agent-tools.service.test.ts @@ -3,7 +3,7 @@ import type { AgentJsonConfig } from '@n8n/api-types'; import { mockInstance } from '@n8n/backend-test-utils'; import { OutboundHttp, SsrfProtectionService } from '@n8n/backend-network'; import { SsrfProtectionConfig } from '@n8n/config'; -import { ProjectRelationRepository, User } from '@n8n/db'; +import { User } from '@n8n/db'; import type { Mock } from 'vitest'; vi.mock('@/permissions.ee/check-access', () => ({ @@ -42,9 +42,11 @@ import { McpRegistryService } from '@/modules/mcp-registry/registry/mcp-registry import { NodeTypes } from '@/node-types'; import { OauthService } from '@/oauth/oauth.service'; import { userHasScopes } from '@/permissions.ee/check-access'; +import { ProjectScopeService } from '@/permissions.ee/project-scope.service'; import { UrlService } from '@/services/url.service'; import { Telemetry } from '@/telemetry'; +import { AGENT_TOOLS, TOOLS_BY_SCOPE } from '../mcp-scopes'; import { USER_CALLED_MCP_TOOL_EVENT } from '../mcp.constants'; import { McpAgentToolsService } from '../tools/agents/agent-tools.service'; @@ -81,6 +83,7 @@ const agentEntity = (overrides: Record = {}): Agent => projectId: 'project-1', versionId: 'v1', activeVersionId: null, + availableInMCP: true, createdAt: new Date('2026-01-01T00:00:00.000Z'), updatedAt: new Date('2026-01-02T00:00:00.000Z'), schema: baseConfig, @@ -105,7 +108,7 @@ describe('McpAgentToolsService', () => { const mcpRegistryService = mockInstance(McpRegistryService); const outboundHttp = mockInstance(OutboundHttp); const urlService = mockInstance(UrlService); - const projectRelationRepository = mockInstance(ProjectRelationRepository); + const projectScopeService = mockInstance(ProjectScopeService); const service = new McpAgentToolsService( telemetry, @@ -130,7 +133,7 @@ describe('McpAgentToolsService', () => { mockInstance(SsrfProtectionConfig), mockInstance(SsrfProtectionService), urlService, - projectRelationRepository, + projectScopeService, ); let tools: Map; @@ -142,6 +145,7 @@ describe('McpAgentToolsService', () => { agentsService.findByIdForUser.mockResolvedValue(agentEntity()); credentialsService.getCredentialsAUserCanUseInAWorkflow.mockResolvedValue([] as never); urlService.getInstanceBaseUrl.mockReturnValue('https://n8n.test'); + projectScopeService.getProjectIds.mockResolvedValue(['project-1']); tools = new Map(); registerResource = vi.fn(); @@ -187,6 +191,45 @@ describe('McpAgentToolsService', () => { expect.any(Function), ); }); + + it('matches AGENT_TOOLS in the scope map (drift guard)', () => { + expect(new Set(tools.keys())).toEqual(new Set(AGENT_TOOLS)); + }); + + const registerFiltered = (allowedToolNames?: Set) => { + const filteredTools = new Map(); + const resource = vi.fn(); + const server = { + registerTool: (name: string, config: RegisteredTool['config'], handler: unknown) => { + filteredTools.set(name, { config, handler: handler as RegisteredTool['handler'] }); + }, + resource, + } as unknown as McpServer; + service.registerTools(server, user, allowedToolNames); + return { tools: filteredTools, resource }; + }; + + it('registers only the tools allowed by the granted scopes', () => { + const allowed = new Set(TOOLS_BY_SCOPE['agent:read']); + const { tools: filteredTools, resource } = registerFiltered(allowed); + + expect(new Set(filteredTools.keys())).toEqual(allowed); + expect(resource).toHaveBeenCalledTimes(1); + }); + + it('registers no tools and no resource for an empty allow-list', () => { + const { tools: filteredTools, resource } = registerFiltered(new Set()); + + expect(filteredTools.size).toBe(0); + expect(resource).not.toHaveBeenCalled(); + }); + + it('skips the reference resource when the reference tool is out of scope', () => { + const { tools: filteredTools, resource } = registerFiltered(new Set(['search_agents'])); + + expect([...filteredTools.keys()]).toEqual(['search_agents']); + expect(resource).not.toHaveBeenCalled(); + }); }); describe('scope enforcement', () => { @@ -245,6 +288,37 @@ describe('McpAgentToolsService', () => { }); }); + describe('availableInMCP enforcement', () => { + const identity = { projectId: 'project-1', agentId: 'agent-1' }; + + test.each<[string, Record]>([ + ['get_agent', identity], + [ + 'mutate_agent', + { ...identity, baseConfigHash: 'hash', operation: { type: 'config.replace', config: {} } }, + ], + ['validate_agent', identity], + ['publish_agent', identity], + ['unpublish_agent', identity], + ['revert_agent', identity], + ['list_agent_versions', identity], + ['delete_agent', identity], + [ + 'update_agent_integration', + { ...identity, action: 'disconnect', type: 'slack', credentialId: 'cred-1' }, + ], + ])('%s refuses an agent that is not available in MCP', async (toolName, input) => { + agentsService.findByIdForUser.mockResolvedValue(agentEntity({ availableInMCP: false })); + + const result = await callTool(toolName, input); + + expect(result.isError).toBe(true); + expect(result.structuredContent).toMatchObject({ + error: expect.stringContaining('not available in MCP'), + }); + }); + }); + describe('mutate_agent', () => { const mutateInput = (operation: Record, baseConfigHash?: string) => ({ projectId: 'project-1', @@ -627,6 +701,9 @@ describe('McpAgentToolsService', () => { ...initialConfig, name: 'My Agent', }); + expect(agentsService.create).toHaveBeenCalledWith('project-1', 'My Agent', { + availableInMCP: true, + }); expect(agentConfigService.updateConfig).toHaveBeenCalledWith( 'agent-1', 'project-1', @@ -685,34 +762,35 @@ describe('McpAgentToolsService', () => { describe('search_agents', () => { it('only searches projects where the user has agent:list', async () => { - projectRelationRepository.findAllByUser.mockResolvedValue([ - { projectId: 'project-1' }, - { projectId: 'project-2' }, - ] as never); - userHasScopesMock.mockImplementation( - async ( - _user: User, - _scopes: string[], - _global: boolean, - { projectId }: { projectId: string }, - ) => projectId === 'project-1', - ); agentsService.findSummariesInProjects.mockResolvedValue([ agentEntity({ id: 'agent-1', projectId: 'project-1' }), ]); const result = await callTool('search_agents', {}); + expect(projectScopeService.getProjectIds).toHaveBeenCalledWith(user, ['agent:list']); expect(agentsService.findSummariesInProjects).toHaveBeenCalledWith( ['project-1'], expect.any(Object), ); expect(result.structuredContent).toMatchObject({ ok: true, count: 1 }); expect(result.structuredContent.data).toEqual([ - expect.objectContaining({ id: 'agent-1', projectId: 'project-1' }), + expect.objectContaining({ id: 'agent-1', projectId: 'project-1', availableInMCP: true }), ]); }); + it('searches without a project restriction for global agent:list', async () => { + projectScopeService.getProjectIds.mockResolvedValue(null); + agentsService.findSummariesInProjects.mockResolvedValue([ + agentEntity({ id: 'agent-1', projectId: 'project-2' }), + ]); + + const result = await callTool('search_agents', {}); + + expect(agentsService.findSummariesInProjects).toHaveBeenCalledWith(null, expect.any(Object)); + expect(result.structuredContent).toMatchObject({ ok: true, count: 1 }); + }); + it('pushes query, publishedOnly, excludeAgentId, and limit filters into the lookup', async () => { agentsService.findSummariesInProjects.mockResolvedValue([ agentEntity({ id: 'agent-1', name: 'Sales Helper', activeVersionId: 'v1' }), @@ -1305,6 +1383,21 @@ describe('McpAgentToolsService', () => { }); }); + it('denies connect without the agent:publish scope even when update is allowed', async () => { + userHasScopesMock.mockImplementation( + async (_user: unknown, scopes: string[]) => !scopes.includes('agent:publish'), + ); + + const result = await callTool('update_agent_integration', input); + + expect(userHasScopesMock).toHaveBeenCalledWith(user, ['agent:publish'], false, { + projectId: 'project-1', + }); + expect(result.isError).toBe(true); + expect(integrationPersistenceService.saveCredentialIntegration).not.toHaveBeenCalled(); + expect(agentPublishService.publishAgent).not.toHaveBeenCalled(); + }); + it('requires settings for telegram integrations', async () => { const result = await callTool('update_agent_integration', { ...input, type: 'telegram' }); diff --git a/packages/cli/src/modules/mcp/__tests__/mcp-protected-resource.test.ts b/packages/cli/src/modules/mcp/__tests__/mcp-protected-resource.test.ts index f856ab7fb4c..a51d25e295f 100644 --- a/packages/cli/src/modules/mcp/__tests__/mcp-protected-resource.test.ts +++ b/packages/cli/src/modules/mcp/__tests__/mcp-protected-resource.test.ts @@ -1,3 +1,4 @@ +import type { ModuleRegistry } from '@n8n/backend-common'; import type { GlobalConfig } from '@n8n/config'; import { mock } from 'vitest-mock-extended'; @@ -17,24 +18,30 @@ describe('McpProtectedResource', () => { const urlService = mock(); const mcpSettingsService = mock(); const mcpConfig = mock(); + const moduleRegistry = mock(); const resource = new McpProtectedResource( urlService, mcpSettingsService, mcpConfig, makeGlobalConfig(), + moduleRegistry, ); beforeEach(() => { vi.clearAllMocks(); mcpConfig.baseUrl = ''; + moduleRegistry.isActive.mockReturnValue(true); }); describe('getScopeTools', () => { it('should expose the full tool mapping when all features are enabled', () => { const scopeTools = resource.getScopeTools(); + expect(resource.scopes).toContain('agent:read'); + expect(resource.scopes).toContain('agent:write'); expect(scopeTools['workflow:read']).toContain('search_workflows'); expect(scopeTools['workflow:read']).toContain('search_nodes'); + expect(scopeTools['agent:read']).toContain('search_agents'); expect(scopeTools['tag:read']).toContain('list_workflow_tags'); }); @@ -44,18 +51,32 @@ describe('McpProtectedResource', () => { mcpSettingsService, mcpConfig, makeGlobalConfig({ builderEnabled: false, tagsDisabled: true }), + moduleRegistry, ); const scopeTools = limitedResource.getScopeTools(); + expect(limitedResource.scopes).not.toContain('agent:read'); + expect(limitedResource.scopes).not.toContain('agent:write'); expect(scopeTools['workflow:read']).toContain('search_workflows'); // builder-only tools are hidden when the builder is off expect(scopeTools['workflow:read']).not.toContain('search_nodes'); expect(scopeTools['workflow:write']).not.toContain('create_workflow_from_code'); expect(scopeTools['project:read']).toEqual([]); + expect(scopeTools['agent:read']).toBeUndefined(); + expect(scopeTools['agent:write']).toBeUndefined(); // list_workflow_tags is hidden when tags are disabled expect(scopeTools['tag:read']).toEqual([]); }); + + it('should drop agent scopes and tools when the agents module is inactive', () => { + moduleRegistry.isActive.mockReturnValue(false); + + expect(resource.scopes).not.toContain('agent:read'); + expect(resource.scopes).not.toContain('agent:write'); + expect(resource.getScopeTools()).not.toHaveProperty('agent:read'); + expect(resource.getScopeTools()).not.toHaveProperty('agent:write'); + }); }); describe('getResourceUrl', () => { diff --git a/packages/cli/src/modules/mcp/__tests__/mcp-scopes.test.ts b/packages/cli/src/modules/mcp/__tests__/mcp-scopes.test.ts index 18e06c08e57..dc40b2b1526 100644 --- a/packages/cli/src/modules/mcp/__tests__/mcp-scopes.test.ts +++ b/packages/cli/src/modules/mcp/__tests__/mcp-scopes.test.ts @@ -44,7 +44,7 @@ import { WorkflowHistoryService } from '@/workflows/workflow-history/workflow-hi import { WorkflowPublishedDataService } from '@/workflows/workflow-published-data.service'; import { WorkflowService } from '@/workflows/workflow.service'; -import { BUILDER_TOOLS, getAllowedToolNames, TOOLS_BY_SCOPE } from '../mcp-scopes'; +import { AGENT_TOOLS, BUILDER_TOOLS, getAllowedToolNames, TOOLS_BY_SCOPE } from '../mcp-scopes'; import { McpService, type McpFeatureFlags } from '../mcp.service'; const ALL_MAPPED_TOOLS = new Set(Object.values(TOOLS_BY_SCOPE).flat()); @@ -77,6 +77,14 @@ describe('getAllowedToolNames', () => { it('ignores unknown scopes', () => { expect(getAllowedToolNames(['tool:listWorkflows', 'openid'])).toEqual(new Set()); }); + + it('allows integration updates and publishing with agent:write', () => { + const allowed = getAllowedToolNames(['agent:write']); + + expect(allowed).toContain('update_agent_integration'); + expect(allowed).toContain('publish_agent'); + expect(allowed).toContain('unpublish_agent'); + }); }); describe('McpService scope enforcement', () => { @@ -147,7 +155,11 @@ describe('McpService scope enforcement', () => { const server = await buildService().getServer(user, mcpFeatureFlags()); const registered = getRegisteredToolNames(server); - const unregistered = [...ALL_MAPPED_TOOLS].filter((name) => !registered.has(name)); + // Agent tools require the agents module (inactive here); their own + // drift guard lives in agent-tools.service.test.ts. + const unregistered = [...ALL_MAPPED_TOOLS].filter( + (name) => !registered.has(name) && !AGENT_TOOLS.has(name), + ); expect(unregistered).toEqual([]); }); diff --git a/packages/cli/src/modules/mcp/mcp-protected-resource.ts b/packages/cli/src/modules/mcp/mcp-protected-resource.ts index de631581db3..e6a4ac28aec 100644 --- a/packages/cli/src/modules/mcp/mcp-protected-resource.ts +++ b/packages/cli/src/modules/mcp/mcp-protected-resource.ts @@ -1,8 +1,10 @@ -import { MCP_INSTANCE_SCOPES } from '@n8n/api-types'; +import { MCP_AGENT_SCOPES, MCP_INSTANCE_SCOPES } from '@n8n/api-types'; +import { ModuleRegistry } from '@n8n/backend-common'; import { GlobalConfig } from '@n8n/config'; import { Service } from '@n8n/di'; import { BUILDER_TOOLS, TOOLS_BY_SCOPE } from './mcp-scopes'; +import { areAgentToolsAvailable } from './mcp-tool-availability'; import { McpConfig } from './mcp.config'; import { McpSettingsService } from './mcp.settings.service'; import type { ProtectedResource } from '@/services/protected-resource.registry'; @@ -16,6 +18,7 @@ export const INSTANCE_MCP_RESOURCE_ID = 'instance-mcp'; * mapping in `mcp-scopes.ts` when the MCP server registers tools. */ export const SUPPORTED_SCOPES: string[] = [...MCP_INSTANCE_SCOPES]; +const AGENT_SCOPES = new Set(MCP_AGENT_SCOPES); const MCP_RESOURCE_PATH = '/mcp-server/http'; @@ -36,8 +39,6 @@ const LEGACY_MCP_AUDIENCE = 'mcp-server-api'; export class McpProtectedResource implements ProtectedResource { readonly id = INSTANCE_MCP_RESOURCE_ID; - readonly scopes = SUPPORTED_SCOPES; - /** * Fallback audience for token requests without an RFC 8707 resource * indicator — the instance MCP server predates resource indicators, so @@ -50,8 +51,14 @@ export class McpProtectedResource implements ProtectedResource { private readonly mcpSettingsService: McpSettingsService, private readonly mcpConfig: McpConfig, private readonly globalConfig: GlobalConfig, + private readonly moduleRegistry: ModuleRegistry, ) {} + get scopes(): string[] { + if (areAgentToolsAvailable(this.globalConfig, this.moduleRegistry)) return SUPPORTED_SCOPES; + return SUPPORTED_SCOPES.filter((scope) => !AGENT_SCOPES.has(scope)); + } + /** * Filtered to the tools this instance actually exposes, so the consent * screen never advertises tools a grant cannot deliver. @@ -59,16 +66,19 @@ export class McpProtectedResource implements ProtectedResource { getScopeTools(): Record { const builderEnabled = this.globalConfig.endpoints.mcpBuilderEnabled; const tagsDisabled = this.globalConfig.tags.disabled; + const supportedScopes = new Set(this.scopes); return Object.fromEntries( - Object.entries(TOOLS_BY_SCOPE).map(([scope, tools]) => [ - scope, - tools.filter( - (tool) => - (builderEnabled || !BUILDER_TOOLS.has(tool)) && - (!tagsDisabled || tool !== 'list_workflow_tags'), - ), - ]), + Object.entries(TOOLS_BY_SCOPE) + .filter(([scope]) => supportedScopes.has(scope)) + .map(([scope, tools]) => [ + scope, + tools.filter( + (tool) => + (builderEnabled || !BUILDER_TOOLS.has(tool)) && + (!tagsDisabled || tool !== 'list_workflow_tags'), + ), + ]), ); } diff --git a/packages/cli/src/modules/mcp/mcp-scopes.ts b/packages/cli/src/modules/mcp/mcp-scopes.ts index f77a4148730..afa74e82001 100644 --- a/packages/cli/src/modules/mcp/mcp-scopes.ts +++ b/packages/cli/src/modules/mcp/mcp-scopes.ts @@ -41,6 +41,33 @@ export const TOOLS_BY_SCOPE: Record = { ], 'workflow:execute': ['execute_workflow', 'test_workflow', 'prepare_workflow_pin_data'], 'execution:read': ['get_workflow_execution', 'search_workflow_executions'], + 'agent:read': [ + 'search_agents', + 'get_agent', + 'list_agent_versions', + 'discover_agent_assets', + 'validate_agent', + 'get_agent_builder_reference', + ], + // The read tools ride along on a write-only grant: mutate_agent's + // configHash handshake starts at get_agent, and building needs search + // (sub-agents), asset discovery, validation, and the reference. + 'agent:write': [ + 'create_agent', + 'mutate_agent', + 'revert_agent', + 'delete_agent', + 'verify_agent_mcp_server', + 'search_agents', + 'get_agent', + 'list_agent_versions', + 'discover_agent_assets', + 'validate_agent', + 'get_agent_builder_reference', + 'update_agent_integration', + 'publish_agent', + 'unpublish_agent', + ], // explore_node_resources queries external services with stored credentials, // so it must sit behind the credential scope rather than a workflow one. 'credential:read': ['list_credentials', 'list_n8n_connect_services', 'explore_node_resources'], @@ -80,6 +107,11 @@ export const BUILDER_TOOLS: ReadonlySet = new Set([ 'search_folders', ]); +export const AGENT_TOOLS: ReadonlySet = new Set([ + ...TOOLS_BY_SCOPE['agent:read'], + ...TOOLS_BY_SCOPE['agent:write'], +]); + function isMcpScope(scope: string): scope is McpScope { return (MCP_INSTANCE_SCOPES as readonly string[]).includes(scope); } diff --git a/packages/cli/src/modules/mcp/mcp-tool-availability.ts b/packages/cli/src/modules/mcp/mcp-tool-availability.ts new file mode 100644 index 00000000000..79468982185 --- /dev/null +++ b/packages/cli/src/modules/mcp/mcp-tool-availability.ts @@ -0,0 +1,9 @@ +import type { ModuleRegistry } from '@n8n/backend-common'; +import type { GlobalConfig } from '@n8n/config'; + +export function areAgentToolsAvailable( + globalConfig: GlobalConfig, + moduleRegistry: ModuleRegistry, +): boolean { + return globalConfig.endpoints.mcpBuilderEnabled && moduleRegistry.isActive('agents'); +} diff --git a/packages/cli/src/modules/mcp/mcp.service.ts b/packages/cli/src/modules/mcp/mcp.service.ts index cc54ace88f3..820e79ed6fa 100644 --- a/packages/cli/src/modules/mcp/mcp.service.ts +++ b/packages/cli/src/modules/mcp/mcp.service.ts @@ -52,6 +52,7 @@ import { WorkflowService } from '@/workflows/workflow.service'; import { MCP_CREATE_AGENT_TOOL_NAME, MCP_PREVIEW_RENDER_REQUESTED_EVENT } from './mcp.constants'; import { getAllowedToolNames } from './mcp-scopes'; +import { areAgentToolsAvailable } from './mcp-tool-availability'; import type { McpAppsTelemetryVariant, McpClientInfo, RegisterToolFn } from './mcp.types'; import { createAddDataTableColumnTool, @@ -269,7 +270,7 @@ export class McpService { const builderInstructionsEnabled = builderEnabled && (allowedToolNames?.has(MCP_CREATE_WORKFLOW_FROM_CODE_TOOL.toolName) ?? true); - const agentsEnabled = builderEnabled && this.moduleRegistry.isActive('agents'); + const agentsEnabled = areAgentToolsAvailable(this.globalConfig, this.moduleRegistry); // Same rationale as builderInstructionsEnabled: a grant that cannot call // the agent tools gets no agent build walkthrough. const agentInstructionsEnabled = @@ -468,7 +469,7 @@ export class McpService { if (agentsEnabled) { const { McpAgentToolsService } = await import('./tools/agents/agent-tools.service.js'); - Container.get(McpAgentToolsService).registerTools(server, user); + Container.get(McpAgentToolsService).registerTools(server, user, allowedToolNames); } return server; diff --git a/packages/cli/src/modules/mcp/tools/agents/agent-tools.service.ts b/packages/cli/src/modules/mcp/tools/agents/agent-tools.service.ts index bf219c76364..9a81d3a4308 100644 --- a/packages/cli/src/modules/mcp/tools/agents/agent-tools.service.ts +++ b/packages/cli/src/modules/mcp/tools/agents/agent-tools.service.ts @@ -21,7 +21,7 @@ import { } from '@n8n/api-types'; import { OutboundHttp, SsrfProtectionService } from '@n8n/backend-network'; import { SsrfProtectionConfig } from '@n8n/config'; -import { ProjectRelationRepository, type User } from '@n8n/db'; +import type { User } from '@n8n/db'; import { Service } from '@n8n/di'; import type { Scope } from '@n8n/permissions'; import { isRecord } from '@n8n/utils/is-record'; @@ -55,6 +55,7 @@ import { McpRegistryService } from '@/modules/mcp-registry/registry/mcp-registry import { NodeTypes } from '@/node-types'; import { OauthService } from '@/oauth/oauth.service'; import { userHasScopes } from '@/permissions.ee/check-access'; +import { ProjectScopeService } from '@/permissions.ee/project-scope.service'; import { UrlService } from '@/services/url.service'; import { Telemetry } from '@/telemetry'; import { createAiMcpFetch } from '@/utils/ai-proxy-fetch'; @@ -362,24 +363,37 @@ export class McpAgentToolsService { private readonly ssrfConfig: SsrfProtectionConfig, private readonly ssrfProtectionService: SsrfProtectionService, private readonly urlService: UrlService, - private readonly projectRelationRepository: ProjectRelationRepository, + private readonly projectScopeService: ProjectScopeService, ) {} - registerTools(server: McpServer, user: User): void { - this.register(server, this.searchAgentsTool(user)); - this.register(server, this.getAgentTool(user)); - this.register(server, this.createAgentTool(user)); - this.register(server, this.mutateAgentTool(user)); - this.register(server, this.validateAgentTool(user)); - this.register(server, this.publishAgentTool(user)); - this.register(server, this.unpublishAgentTool(user)); - this.register(server, this.revertAgentTool(user)); - this.register(server, this.listAgentVersionsTool(user)); - this.register(server, this.deleteAgentTool(user)); - this.register(server, this.discoverAssetsTool(user)); - this.register(server, this.verifyMcpServerTool(user)); - this.register(server, this.updateIntegrationTool(user)); - this.register(server, this.referenceTool(user)); + /** + * `allowedToolNames` carries the OAuth grant's scope-derived allow-list + * (undefined means a non-scope-bearing credential with full access). + */ + registerTools(server: McpServer, user: User, allowedToolNames?: Set): void { + const registerIfAllowed = (tool: ToolDefinition): void => { + if (allowedToolNames && !allowedToolNames.has(tool.name)) return; + this.register(server, tool); + }; + + registerIfAllowed(this.searchAgentsTool(user)); + registerIfAllowed(this.getAgentTool(user)); + registerIfAllowed(this.createAgentTool(user)); + registerIfAllowed(this.mutateAgentTool(user)); + registerIfAllowed(this.validateAgentTool(user)); + registerIfAllowed(this.publishAgentTool(user)); + registerIfAllowed(this.unpublishAgentTool(user)); + registerIfAllowed(this.revertAgentTool(user)); + registerIfAllowed(this.listAgentVersionsTool(user)); + registerIfAllowed(this.deleteAgentTool(user)); + registerIfAllowed(this.discoverAssetsTool(user)); + registerIfAllowed(this.verifyMcpServerTool(user)); + registerIfAllowed(this.updateIntegrationTool(user)); + registerIfAllowed(this.referenceTool(user)); + + // The reference resource complements get_agent_builder_reference, so it + // follows that tool's scope gate. + if (allowedToolNames && !allowedToolNames.has('get_agent_builder_reference')) return; server.resource( 'agent-builder-reference', @@ -409,7 +423,7 @@ export class McpAgentToolsService { name: 'search_agents', config: { description: - 'Search Agents the current user can access. Use publishedOnly and excludeAgentId to discover saved sub-agents.', + 'Search Agents the current user can access. Use publishedOnly and excludeAgentId to discover saved sub-agents. Other agent tools only operate on agents with availableInMCP: true.', inputSchema: searchAgentsInput, annotations: { title: 'Search Agents', @@ -421,12 +435,12 @@ export class McpAgentToolsService { }, handler: async (input: SearchAgentsInput) => await this.run(user, 'search_agents', { projectId: input.projectId }, async () => { - let projectIds: string[]; + let projectIds: string[] | null; if (input.projectId) { await this.assertScope(user, input.projectId, 'agent:list'); projectIds = [input.projectId]; } else { - projectIds = await this.listProjectIdsWithAgentList(user); + projectIds = await this.projectScopeService.getProjectIds(user, ['agent:list']); } const agents = await this.agentsService.findSummariesInProjects(projectIds, { query: input.query?.trim() || undefined, @@ -439,6 +453,7 @@ export class McpAgentToolsService { name: agent.name, projectId: agent.projectId, published: agent.activeVersionId !== null, + availableInMCP: agent.availableInMCP, updatedAt: agent.updatedAt.toISOString(), })); return { ok: true, data, count: data.length }; @@ -505,7 +520,10 @@ export class McpAgentToolsService { await this.assertAccessibleCredentials(initialConfig, user, projectId); } - const agent = await this.agentsService.create(projectId, name); + // Agents created over MCP stay operable over MCP. + const agent = await this.agentsService.create(projectId, name, { + availableInMCP: true, + }); let configHash: string | null; let versionId = agent.versionId; try { @@ -921,18 +939,6 @@ export class McpAgentToolsService { }; } - /** Projects from the user's relations where the user holds agent:list. */ - private async listProjectIdsWithAgentList(user: User): Promise { - const relations = await this.projectRelationRepository.findAllByUser(user.id); - const projectIds = [...new Set(relations.map((relation) => relation.projectId))]; - const allowed = await Promise.all( - projectIds.map( - async (projectId) => await userHasScopes(user, ['agent:list'], false, { projectId }), - ), - ); - return projectIds.filter((_, index) => allowed[index]); - } - private async getAgentSnapshot(user: User, agent: Agent) { const { id: agentId, projectId } = agent; const config = this.configFromEntity(agent); @@ -1376,6 +1382,11 @@ export class McpAgentToolsService { const agent = await this.resolveAgent(user, input.agentId); const projectId = agent.projectId; await this.assertScope(user, projectId, 'agent:update'); + if (input.action === 'connect') { + // Connecting publishes the current draft, so it needs the publish + // scope on top of update (mirrors the builder's connect flow). + await this.assertScope(user, projectId, 'agent:publish'); + } return input.action === 'disconnect' ? await this.disconnectIntegration(input, agent) : await this.connectIntegration(user, input, agent, projectId); @@ -1496,10 +1507,20 @@ export class McpAgentToolsService { * from the agentId rather than making the client supply it, scoped to the * projects the user can access. Returns the loaded entity so callers don't * re-fetch the same row. + * + * Mirrors the per-workflow `availableInMCP` guard in `getMcpWorkflow`: MCP + * tools may only operate on agents explicitly made available in MCP. + * `search_agents` intentionally still sees every accessible agent so + * clients can tell the user what exists. */ private async resolveAgent(user: User, agentId: string): Promise { const agent = await this.agentsService.findByIdForUser(agentId, user); if (!agent) throw new UserError(`Agent "${agentId}" not found`); + if (!agent.availableInMCP) { + throw new UserError( + 'Agent is not available in MCP. Enable MCP access from the agents list, or from the MCP settings page.', + ); + } return agent; } diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts index 23328ba7daf..b49e0706fde 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-consent.controller.api.test.ts @@ -1,4 +1,3 @@ -import { MCP_INSTANCE_SCOPES } from '@n8n/api-types'; import { testDb } from '@n8n/backend-test-utils'; import type { User } from '@n8n/db'; import { Container } from '@n8n/di'; @@ -18,6 +17,7 @@ const testServer = setupTestServer({ endpointGroups: ['mcp'], modules: ['oauth-s let owner: User; let member: User; let jwtService: JwtService; +let supportedScopes: string[]; const createSessionToken = (payload: OAuthSessionPayload): string => { return jwtService.sign(payload, { expiresIn: '10m' }); @@ -29,6 +29,7 @@ beforeAll(async () => { member = await createMember(); jwtService = Container.get(JwtService); oauthClientRepository = Container.get(OAuthClientRepository); + supportedScopes = Container.get(ProtectedResourceRegistry).getDefaultResource()?.scopes ?? []; }); afterEach(async () => { @@ -64,7 +65,7 @@ describe('GET /rest/consent/details', () => { clientName: 'Test OAuth Client', clientId: 'test-client-id', redirectUri: 'https://example.com/callback', - scopes: [...MCP_INSTANCE_SCOPES], + scopes: supportedScopes, scopeTools: expect.objectContaining({ 'workflow:read': expect.arrayContaining(['search_workflows']), }), diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts index 66bec0e1d74..b88f44b7662 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-server.service.test.ts @@ -2,7 +2,7 @@ import { InvalidGrantError, InvalidTargetError, } from '@modelcontextprotocol/sdk/server/auth/errors.js'; -import { Logger } from '@n8n/backend-common'; +import { Logger, type ModuleRegistry } from '@n8n/backend-common'; import { mockInstance } from '@n8n/backend-test-utils'; import { GlobalConfig } from '@n8n/config'; import type { Response } from 'express'; @@ -1348,6 +1348,7 @@ describe('OAuthServerService', () => { mock(), mcpConfig, mock(), + mock(), ); expect(mcpResource.getResourceUrl()).toBe('https://n8n-mcp.example.com/mcp-server/http'); diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth-token.service.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth-token.service.test.ts index f148575d682..b777de851a1 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth-token.service.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth-token.service.test.ts @@ -1,5 +1,5 @@ import type { Mocked } from 'vitest'; -import { Logger } from '@n8n/backend-common'; +import { Logger, type ModuleRegistry } from '@n8n/backend-common'; import { mockInstance } from '@n8n/backend-test-utils'; import type { GlobalConfig } from '@n8n/config'; import type { OperationContext, TransactionRunner, User } from '@n8n/db'; @@ -771,6 +771,7 @@ describe('OAuthTokenService', () => { mock(), mcpConfig, mock(), + mock(), ); const configuredRegistry = new ProtectedResourceRegistry(mock()); diff --git a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts index caab7f69921..0ac5a2a4ed9 100644 --- a/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts +++ b/packages/cli/src/modules/oauth-server/__tests__/oauth.controller.api.test.ts @@ -4,13 +4,12 @@ import type { User } from '@n8n/db'; import { ControllerRegistryMetadata, type Controller } from '@n8n/decorators'; import { Container } from '@n8n/di'; +import { McpSettingsService } from '@/modules/mcp/mcp.settings.service'; +import { ProtectedResourceRegistry } from '@/services/protected-resource.registry'; +import { UrlService } from '@/services/url.service'; import { createOwner } from '@test-integration/db/users'; import { setupTestServer } from '@test-integration/utils'; -import { SUPPORTED_SCOPES } from '@/modules/mcp/mcp-protected-resource'; -import { McpSettingsService } from '@/modules/mcp/mcp.settings.service'; -import { UrlService } from '@/services/url.service'; - import { OAuthServerConfig } from '../oauth-server.config'; import type { OAuthController as OAuthControllerClass } from '../oauth.controller'; @@ -18,10 +17,12 @@ const testServer = setupTestServer({ modules: ['oauth-server', 'mcp'], endpointG let owner: User; let mcpSettingsService: McpSettingsService; +let supportedScopes: string[]; beforeAll(async () => { owner = await createOwner(); mcpSettingsService = Container.get(McpSettingsService); + supportedScopes = Container.get(ProtectedResourceRegistry).getAllScopes(); }); afterEach(async () => { @@ -44,7 +45,7 @@ describe('GET /.well-known/oauth-authorization-server', () => { token_endpoint_auth_methods_supported: ['none', 'client_secret_post', 'client_secret_basic'], code_challenge_methods_supported: ['S256'], authorization_response_iss_parameter_supported: true, - ...(SUPPORTED_SCOPES.length > 0 && { scopes_supported: SUPPORTED_SCOPES }), + ...(supportedScopes.length > 0 && { scopes_supported: supportedScopes }), }); }); @@ -101,7 +102,7 @@ describe('GET /.well-known/oauth-protected-resource/mcp-server/http', () => { resource: expect.stringContaining('/mcp-server/http'), bearer_methods_supported: ['header'], authorization_servers: [expect.any(String)], - ...(SUPPORTED_SCOPES.length > 0 && { scopes_supported: SUPPORTED_SCOPES }), + ...(supportedScopes.length > 0 && { scopes_supported: supportedScopes }), }); }); @@ -135,7 +136,7 @@ describe('GET /.well-known/oauth-protected-resource/mcp-server/http', () => { ); expect(response.statusCode).toBe(200); - expect(response.body.scopes_supported).toEqual(SUPPORTED_SCOPES); + expect(response.body.scopes_supported).toEqual(supportedScopes); }); test('should be accessible without authentication', async () => { @@ -156,7 +157,7 @@ describe('GET /.well-known/oauth-protected-resource (bare path)', () => { resource: expect.stringContaining('/mcp-server/http'), bearer_methods_supported: ['header'], authorization_servers: [expect.any(String)], - ...(SUPPORTED_SCOPES.length > 0 && { scopes_supported: SUPPORTED_SCOPES }), + ...(supportedScopes.length > 0 && { scopes_supported: supportedScopes }), }); }); @@ -663,7 +664,7 @@ describe('Full authorization-code flow (PKCE)', () => { authAgent.jar.setCookie(sessionCookie ?? ''); const consentResponse = await authAgent .post('/consent/approve') - .send({ approved: true, scopes: SUPPORTED_SCOPES }); + .send({ approved: true, scopes: supportedScopes }); expect(consentResponse.statusCode).toBe(200); const redirectUrl = new URL(consentResponse.body.data.redirectUrl); @@ -690,7 +691,7 @@ describe('Full authorization-code flow (PKCE)', () => { token_type: 'Bearer', expires_in: 3600, refresh_token: expect.stringMatching(/^[a-f0-9]{64}$/), - scope: SUPPORTED_SCOPES.join(' '), + scope: supportedScopes.join(' '), }); expect(tokenResponse.statusCode).toBe(200); @@ -842,7 +843,7 @@ describe('OAuth server decoupled from MCP access (IAM-798)', () => { authAgent.jar.setCookie(sessionCookie ?? ''); const consentResponse = await authAgent .post('/consent/approve') - .send({ approved: true, scopes: SUPPORTED_SCOPES }); + .send({ approved: true, scopes: supportedScopes }); expect(consentResponse.statusCode).toBe(200); const redirectUrl = new URL(consentResponse.body.data.redirectUrl); @@ -866,7 +867,7 @@ describe('OAuth server decoupled from MCP access (IAM-798)', () => { token_type: 'Bearer', expires_in: 3600, refresh_token: expect.stringMatching(/^[a-f0-9]{64}$/), - scope: SUPPORTED_SCOPES.join(' '), + scope: supportedScopes.join(' '), }); }); }); diff --git a/packages/cli/src/permissions.ee/__tests__/project-scope.service.test.ts b/packages/cli/src/permissions.ee/__tests__/project-scope.service.test.ts new file mode 100644 index 00000000000..c7040089a10 --- /dev/null +++ b/packages/cli/src/permissions.ee/__tests__/project-scope.service.test.ts @@ -0,0 +1,52 @@ +import { mockInstance } from '@n8n/backend-test-utils'; +import { ProjectRelationRepository, User } from '@n8n/db'; + +import { RoleService } from '@/services/role.service'; + +import { ProjectScopeService } from '../project-scope.service'; + +const makeUser = (globalScopes: string[] = []) => + Object.assign(new User(), { + id: 'user-1', + role: { + slug: 'global:member', + scopes: globalScopes.map((slug) => ({ slug })), + }, + }); + +describe('ProjectScopeService', () => { + const roleService = mockInstance(RoleService); + const projectRelationRepository = mockInstance(ProjectRelationRepository); + const service = new ProjectScopeService(roleService, projectRelationRepository); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns no project restriction when the user has the global scope', async () => { + const result = await service.getProjectIds(makeUser(['agent:update']), ['agent:update']); + + expect(result).toBeNull(); + expect(roleService.rolesWithScope).not.toHaveBeenCalled(); + expect(projectRelationRepository.getAccessibleProjectsByRoles).not.toHaveBeenCalled(); + }); + + it('resolves scoped project access with one project-relation query', async () => { + roleService.rolesWithScope.mockResolvedValue(['project:admin', 'project:editor']); + projectRelationRepository.getAccessibleProjectsByRoles.mockResolvedValue([ + 'project-1', + 'project-2', + ]); + + const result = await service.getProjectIds(makeUser(), ['agent:update']); + + expect(roleService.rolesWithScope).toHaveBeenCalledOnce(); + expect(roleService.rolesWithScope).toHaveBeenCalledWith('project', ['agent:update']); + expect(projectRelationRepository.getAccessibleProjectsByRoles).toHaveBeenCalledOnce(); + expect(projectRelationRepository.getAccessibleProjectsByRoles).toHaveBeenCalledWith('user-1', [ + 'project:admin', + 'project:editor', + ]); + expect(result).toEqual(['project-1', 'project-2']); + }); +}); diff --git a/packages/cli/src/permissions.ee/project-scope.service.ts b/packages/cli/src/permissions.ee/project-scope.service.ts new file mode 100644 index 00000000000..e9daf8c8eff --- /dev/null +++ b/packages/cli/src/permissions.ee/project-scope.service.ts @@ -0,0 +1,24 @@ +import { ProjectRelationRepository, type User } from '@n8n/db'; +import { Service } from '@n8n/di'; +import { hasGlobalScope, type Scope } from '@n8n/permissions'; + +import { RoleService } from '@/services/role.service'; + +/** + * Resolves the project restriction for a scope-aware query. + * `null` means the user's global role grants access to every project. + */ +@Service() +export class ProjectScopeService { + constructor( + private readonly roleService: RoleService, + private readonly projectRelationRepository: ProjectRelationRepository, + ) {} + + async getProjectIds(user: User, scopes: Scope[]): Promise { + if (hasGlobalScope(user, scopes, { mode: 'allOf' })) return null; + + const roles = await this.roleService.rolesWithScope('project', scopes); + return await this.projectRelationRepository.getAccessibleProjectsByRoles(user.id, roles); + } +} diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index 1cee4554ec2..3ff26ee2a86 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -2521,6 +2521,7 @@ "oauth.consentView.scopes.badge.write": "write", "oauth.consentView.scopes.group.workflows": "Workflows", "oauth.consentView.scopes.group.executions": "Executions", + "oauth.consentView.scopes.group.agents": "Agents", "oauth.consentView.scopes.group.credentials": "Credentials", "oauth.consentView.scopes.group.dataTables": "Data tables", "oauth.consentView.scopes.group.projectsAndFolders": "Projects and folders", @@ -3232,6 +3233,11 @@ "settings.mcp.workflowsExposed.count": "{count} workflow | {count} workflows", "settings.mcp.workflowsExposed.page.title": "Workflows exposed", "settings.mcp.workflowsExposed.page.description": "Choose which workflows connected clients can reach over MCP. Changes apply immediately.", + "settings.mcp.agentsExposed.title": "Agents exposed", + "settings.mcp.agentsExposed.description": "Choose which agents connected clients can access.", + "settings.mcp.agentsExposed.count": "{count} agent | {count} agents", + "settings.mcp.agentsExposed.page.title": "Agents exposed", + "settings.mcp.agentsExposed.page.description": "Choose which agents connected clients can reach over MCP. Changes apply immediately.", "settings.mcp.callbackUrls.title": "Allowed callback URLs", "settings.mcp.callbackUrls.description": "Restrict OAuth sign-in redirects to trusted URLs. Allowing all URLs (the default) is less secure.", "settings.mcp.callbackUrls.value.all": "All", @@ -3288,6 +3294,11 @@ "settings.mcp.workflows.table.column.description.editTooltip": "Click to edit", "settings.mcp.workflows.table.empty.title": "No workflows enabled", "settings.mcp.workflows.table.empty.description": "Add compatible workflows so MCP clients can discover and execute them", + "settings.mcp.agents.table.action.removeMCPAccess": "Remove access", + "settings.mcp.agents.table.column.name": "Name", + "settings.mcp.agents.table.column.location": "Location", + "settings.mcp.agents.table.empty.title": "No agents enabled", + "settings.mcp.agents.table.empty.description": "Add agents so MCP clients can read and manage them", "settings.mcp.oauth.table.empty.title": "No OAuth clients connected", "settings.mcp.oauth.table.empty.description": "Clients that connect via OAuth will show up here", "settings.mcp.oauth.table.empty.button": "See connection instructions", @@ -3311,6 +3322,8 @@ "settings.mcp.oAuthClients.scope.workflow.write": "Create and update workflows", "settings.mcp.oAuthClients.scope.workflow.execute": "Run workflows", "settings.mcp.oAuthClients.scope.execution.read": "Get execution details", + "settings.mcp.oAuthClients.scope.agent.read": "List and read agents", + "settings.mcp.oAuthClients.scope.agent.write": "Create and update agents", "settings.mcp.oAuthClients.scope.credential.read": "List credentials", "settings.mcp.oAuthClients.scope.dataTable.read": "List data tables", "settings.mcp.oAuthClients.scope.dataTable.write": "Create and update data tables", @@ -3328,6 +3341,7 @@ "settings.mcp.oAuthClients.details.badge.write": "Write", "settings.mcp.oAuthClients.resource.workflow": "Workflow", "settings.mcp.oAuthClients.resource.execution": "Execution", + "settings.mcp.oAuthClients.resource.agent": "Agent", "settings.mcp.oAuthClients.resource.credential": "Credential", "settings.mcp.oAuthClients.resource.dataTable": "Data table", "settings.mcp.oAuthClients.resource.project": "Project", @@ -3366,6 +3380,16 @@ "settings.mcp.workflows.enableAccess.success.title": "MCP access enabled for {count} workflow | MCP access enabled for {count} workflows", "settings.mcp.workflows.removeAccess.success.title": "MCP access removed for {count} workflow | MCP access removed for {count} workflows", "settings.mcp.connectWorkflows.emptyState": "No available workflows", + "settings.mcp.connectAgents": "Enable agents", + "settings.mcp.connectAgents.modalTitle": "Enable agent MCP access", + "settings.mcp.connectAgents.notice": "Agents you enable can be read and managed by connected MCP clients that were granted agent access.", + "settings.mcp.connectAgents.input.placeholder": "Search agents to connect", + "settings.mcp.connectAgents.confirm.label": "Enable", + "settings.mcp.connectAgents.error": "Error fetching available agents", + "settings.mcp.connectAgents.emptyState": "No available agents", + "settings.mcp.agents.enableAccess.success.title": "MCP access enabled for {count} agent | MCP access enabled for {count} agents", + "settings.mcp.agents.removeAccess.success.title": "MCP access removed for {count} agent | MCP access removed for {count} agents", + "settings.mcp.agents.list.error.fetching": "Error fetching agents", "settings.mcp.connectDialog.tab.accessToken": "Access token", "settings.mcp.connectDialog.serverUrl": "Server URL", "settings.mcp.connectDialog.jsonConfig": "Configuration JSON", @@ -3413,6 +3437,11 @@ "experiments.surfaceMcpToNewCloudUsers.emptyState.tile.badge.new": "New", "experiments.surfaceMcpToNewCloudUsers.emptyState.tile.badge.enabled": "Enabled", "experiments.surfaceMcpToNewCloudUsers.emptyState.reminder": "You can enable this later in Settings > MCP.", + "experiments.exposeAllWorkflowsToMcp.modal.withAgents.title": "Expose all workflows and agents to MCP?", + "experiments.exposeAllWorkflowsToMcp.modal.withAgents.description": "This lets connected clients reach every workflow and agent on this instance right away. You can remove MCP access for individual workflows and agents at any time.", + "experiments.exposeAllWorkflowsToMcp.modal.withAgents.confirm": "Expose all workflows and agents", + "experiments.exposeAllWorkflowsToMcp.modal.withAgents.success.title": "Workflows and agents exposed to MCP", + "experiments.exposeAllWorkflowsToMcp.modal.withAgents.success.message": "{workflows} and {agents} are now available to connected MCP clients.", "experiments.exposeAllWorkflowsToMcp.modal.title": "Expose all workflows to MCP?", "experiments.exposeAllWorkflowsToMcp.modal.description": "This lets connected clients reach every workflow on this instance right away. You can remove MCP access for individual workflows at any time.", "experiments.exposeAllWorkflowsToMcp.modal.notNow": "Not now", @@ -4525,6 +4554,8 @@ "workflowSettings.toggleMCP.error.title": "Error updating MCP settings", "workflowSettings.toggleMCP.notFoundError": "Workflow not found", "workflowSettings.toggleMCP.updateSkippedError": "Workflow {workflowId} could not be updated. It may be archived or you may no longer have permission to edit it.", + "agents.toggleMCP.error.title": "Error updating MCP settings", + "agents.toggleMCP.updateSkippedError": "Agent {agentId} could not be updated. You may no longer have permission to edit it.", "workflowHistory.title": "Version History", "workflowHistory.tab.history": "Versions", "workflowHistory.tab.publishTimeline": "Publish Timeline", @@ -7210,7 +7241,10 @@ "agents.list.empty.button.disabled.tooltip": "Your current role in the project does not allow you to create agents", "agents.list.actions.publish": "Publish", "agents.list.actions.unpublish": "Unpublish", + "agents.list.actions.enableMCPAccess": "Enable MCP access", + "agents.list.actions.disableMCPAccess": "Remove MCP access", "agents.list.actions.delete": "Delete", + "agents.list.availableInMCP": "Available in MCP", "agents.list.readonly": "Read only", "agents.publish.button.publish": "Publish", "agents.publish.button.published": "Published", @@ -7334,6 +7368,8 @@ "agents.builder.advanced.recentMessages.memoryDisabledTooltip": "Enable Session Memory in the Memory section to configure the window.", "agents.builder.advanced.maxIterations.label": "Max iterations", "agents.builder.advanced.maxIterations.hint": "Maximum number of agent loop iterations per run (1–200).", + "agents.builder.mcp.availableInMCP.label": "Available in MCP", + "agents.builder.mcp.availableInMCP.hint": "Make this agent visible to AI Agents through n8n MCP", "agents.builder.memory.title": "Memory", "agents.builder.memory.description": "Session memory is on by default", "agents.builder.memory.settings.title": "Memory settings", diff --git a/packages/frontend/editor-ui/src/experiments/exposeAllWorkflowsToMcp/components/ExposeAllWorkflowsToMcpModal.vue b/packages/frontend/editor-ui/src/experiments/exposeAllWorkflowsToMcp/components/ExposeAllWorkflowsToMcpModal.vue index 361dd59e79b..486412d9340 100644 --- a/packages/frontend/editor-ui/src/experiments/exposeAllWorkflowsToMcp/components/ExposeAllWorkflowsToMcpModal.vue +++ b/packages/frontend/editor-ui/src/experiments/exposeAllWorkflowsToMcp/components/ExposeAllWorkflowsToMcpModal.vue @@ -1,13 +1,14 @@