fix(core): Fix binary data helpers (like prepareBinaryData) with task runner (#12259)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run

This commit is contained in:
Tomi Turtiainen 2024-12-18 18:45:05 +02:00 committed by GitHub
parent 92af245d1a
commit 0f1461f2d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 495 additions and 143 deletions

View file

@ -38,6 +38,7 @@
"@sentry/node": "catalog:",
"acorn": "8.14.0",
"acorn-walk": "8.3.4",
"lodash.set": "4.3.2",
"n8n-core": "workspace:*",
"n8n-workflow": "workspace:*",
"nanoid": "catalog:",
@ -45,6 +46,7 @@
"ws": "^8.18.0"
},
"devDependencies": {
"@types/lodash.set": "4.3.9",
"luxon": "catalog:"
}
}

View file

@ -1,5 +1,6 @@
import { mock } from 'jest-mock-extended';
import { DateTime } from 'luxon';
import type { IBinaryData } from 'n8n-workflow';
import { setGlobalState, type CodeExecutionMode, type IDataObject } from 'n8n-workflow';
import fs from 'node:fs';
import { builtinModules } from 'node:module';
@ -8,10 +9,15 @@ import type { BaseRunnerConfig } from '@/config/base-runner-config';
import type { JsRunnerConfig } from '@/config/js-runner-config';
import { MainConfig } from '@/config/main-config';
import { ExecutionError } from '@/js-task-runner/errors/execution-error';
import { UnsupportedFunctionError } from '@/js-task-runner/errors/unsupported-function.error';
import { ValidationError } from '@/js-task-runner/errors/validation-error';
import type { JSExecSettings } from '@/js-task-runner/js-task-runner';
import { JsTaskRunner } from '@/js-task-runner/js-task-runner';
import type { DataRequestResponse, InputDataChunkDefinition } from '@/runner-types';
import {
UNSUPPORTED_HELPER_FUNCTIONS,
type DataRequestResponse,
type InputDataChunkDefinition,
} from '@/runner-types';
import type { Task } from '@/task-runner';
import {
@ -567,6 +573,120 @@ describe('JsTaskRunner', () => {
);
});
describe('helpers', () => {
const binaryDataFile: IBinaryData = {
data: 'data',
fileName: 'file.txt',
mimeType: 'text/plain',
};
const groups = [
{
method: 'helpers.assertBinaryData',
invocation: "helpers.assertBinaryData(0, 'binaryFile')",
expectedParams: [0, 'binaryFile'],
},
{
method: 'helpers.getBinaryDataBuffer',
invocation: "helpers.getBinaryDataBuffer(0, 'binaryFile')",
expectedParams: [0, 'binaryFile'],
},
{
method: 'helpers.prepareBinaryData',
invocation: "helpers.prepareBinaryData(Buffer.from('123'), 'file.txt', 'text/plain')",
expectedParams: [Buffer.from('123'), 'file.txt', 'text/plain'],
},
{
method: 'helpers.setBinaryDataBuffer',
invocation:
"helpers.setBinaryDataBuffer({ data: '123', mimeType: 'text/plain' }, Buffer.from('321'))",
expectedParams: [{ data: '123', mimeType: 'text/plain' }, Buffer.from('321')],
},
{
method: 'helpers.binaryToString',
invocation: "helpers.binaryToString(Buffer.from('123'), 'utf8')",
expectedParams: [Buffer.from('123'), 'utf8'],
},
{
method: 'helpers.httpRequest',
invocation: "helpers.httpRequest({ method: 'GET', url: 'http://localhost' })",
expectedParams: [{ method: 'GET', url: 'http://localhost' }],
},
];
for (const group of groups) {
it(`${group.method} for runOnceForAllItems`, async () => {
// Arrange
const rpcCallSpy = jest
.spyOn(defaultTaskRunner, 'makeRpcCall')
.mockResolvedValue(undefined);
// Act
await execTaskWithParams({
task: newTaskWithSettings({
code: `await ${group.invocation}; return []`,
nodeMode: 'runOnceForAllItems',
}),
taskData: newDataRequestResponse(
[{ json: {}, binary: { binaryFile: binaryDataFile } }],
{},
),
});
expect(rpcCallSpy).toHaveBeenCalledWith('1', group.method, group.expectedParams);
});
it(`${group.method} for runOnceForEachItem`, async () => {
// Arrange
const rpcCallSpy = jest
.spyOn(defaultTaskRunner, 'makeRpcCall')
.mockResolvedValue(undefined);
// Act
await execTaskWithParams({
task: newTaskWithSettings({
code: `await ${group.invocation}; return {}`,
nodeMode: 'runOnceForEachItem',
}),
taskData: newDataRequestResponse(
[{ json: {}, binary: { binaryFile: binaryDataFile } }],
{},
),
});
expect(rpcCallSpy).toHaveBeenCalledWith('1', group.method, group.expectedParams);
});
}
describe('unsupported methods', () => {
for (const unsupportedFunction of UNSUPPORTED_HELPER_FUNCTIONS) {
it(`should throw an error if ${unsupportedFunction} is used in runOnceForAllItems`, async () => {
// Act
await expect(
async () =>
await executeForAllItems({
code: `${unsupportedFunction}()`,
inputItems,
}),
).rejects.toThrow(UnsupportedFunctionError);
});
it(`should throw an error if ${unsupportedFunction} is used in runOnceForEachItem`, async () => {
// Act
await expect(
async () =>
await executeForEachItem({
code: `${unsupportedFunction}()`,
inputItems,
}),
).rejects.toThrow(UnsupportedFunctionError);
});
}
});
});
it('should allow access to Node.js Buffers', async () => {
const outcomeAll = await execTaskWithParams({
task: newTaskWithSettings({

View file

@ -0,0 +1,13 @@
import { ApplicationError } from 'n8n-workflow';
/**
* Error that indicates that a specific function is not available in the
* Code Node.
*/
export class UnsupportedFunctionError extends ApplicationError {
constructor(functionName: string) {
super(`The function "${functionName}" is not supported in the Code Node`, {
level: 'info',
});
}
}

View file

@ -1,3 +1,4 @@
import set from 'lodash.set';
import { getAdditionalKeys } from 'n8n-core';
import { WorkflowDataProxy, Workflow, ObservableObject } from 'n8n-workflow';
import type {
@ -19,11 +20,14 @@ import * as a from 'node:assert';
import { runInNewContext, type Context } from 'node:vm';
import type { MainConfig } from '@/config/main-config';
import type {
DataRequestResponse,
InputDataChunkDefinition,
PartialAdditionalData,
TaskResultData,
import { UnsupportedFunctionError } from '@/js-task-runner/errors/unsupported-function.error';
import {
EXPOSED_RPC_METHODS,
UNSUPPORTED_HELPER_FUNCTIONS,
type DataRequestResponse,
type InputDataChunkDefinition,
type PartialAdditionalData,
type TaskResultData,
} from '@/runner-types';
import { type Task, TaskRunner } from '@/task-runner';
@ -38,6 +42,10 @@ import { createRequireResolver } from './require-resolver';
import { validateRunForAllItemsOutput, validateRunForEachItemOutput } from './result-validation';
import { DataRequestResponseReconstruct } from '../data-request/data-request-response-reconstruct';
export interface RPCCallObject {
[name: string]: ((...args: unknown[]) => Promise<unknown>) | RPCCallObject;
}
export interface JSExecSettings {
code: string;
nodeMode: CodeExecutionMode;
@ -439,4 +447,24 @@ export class JsTaskRunner extends TaskRunner {
this.nodeTypes.addNodeTypeDescriptions(nodeTypes);
}
}
private buildRpcCallObject(taskId: string) {
const rpcObject: RPCCallObject = {};
for (const rpcMethod of EXPOSED_RPC_METHODS) {
set(
rpcObject,
rpcMethod.split('.'),
async (...args: unknown[]) => await this.makeRpcCall(taskId, rpcMethod, args),
);
}
for (const rpcMethod of UNSUPPORTED_HELPER_FUNCTIONS) {
set(rpcObject, rpcMethod.split('.'), () => {
throw new UnsupportedFunctionError(rpcMethod);
});
}
return rpcObject;
}
}

View file

@ -2,7 +2,7 @@ import type { INodeTypeBaseDescription } from 'n8n-workflow';
import type {
NeededNodeType,
RPC_ALLOW_LIST,
AVAILABLE_RPC_METHODS,
TaskDataRequestParams,
TaskResultData,
} from './runner-types';
@ -105,7 +105,7 @@ export namespace BrokerMessage {
type: 'broker:rpc';
callId: string;
taskId: string;
name: (typeof RPC_ALLOW_LIST)[number];
name: (typeof AVAILABLE_RPC_METHODS)[number];
params: unknown[];
}
@ -239,7 +239,7 @@ export namespace RunnerMessage {
type: 'runner:rpc';
callId: string;
taskId: string;
name: (typeof RPC_ALLOW_LIST)[number];
name: (typeof AVAILABLE_RPC_METHODS)[number];
params: unknown[];
}

View file

@ -100,31 +100,73 @@ export interface PartialAdditionalData {
variables: IDataObject;
}
export const RPC_ALLOW_LIST = [
/** RPC methods that are exposed directly to the Code Node */
export const EXPOSED_RPC_METHODS = [
// assertBinaryData(itemIndex: number, propertyName: string): Promise<IBinaryData>
'helpers.assertBinaryData',
// getBinaryDataBuffer(itemIndex: number, propertyName: string): Promise<Buffer>
'helpers.getBinaryDataBuffer',
// prepareBinaryData(binaryData: Buffer, fileName?: string, mimeType?: string): Promise<IBinaryData>
'helpers.prepareBinaryData',
// setBinaryDataBuffer(metadata: IBinaryData, buffer: Buffer): Promise<IBinaryData>
'helpers.setBinaryDataBuffer',
// binaryToString(body: Buffer, encoding?: string): string
'helpers.binaryToString',
// httpRequest(opts: IHttpRequestOptions): Promise<IN8nHttpFullResponse | IN8nHttpResponse>
'helpers.httpRequest',
];
/** Helpers that exist but that we are not exposing to the Code Node */
export const UNSUPPORTED_HELPER_FUNCTIONS = [
// These rely on checking the credentials from the current node type (Code Node)
// and hence they can't even work (Code Node doesn't have credentials)
'helpers.httpRequestWithAuthentication',
'helpers.requestWithAuthenticationPaginated',
// "helpers.normalizeItems"
// "helpers.constructExecutionMetaData"
// "helpers.assertBinaryData"
'helpers.getBinaryDataBuffer',
// "helpers.copyInputItems"
// "helpers.returnJsonArray"
'helpers.getSSHClient',
'helpers.createReadStream',
// "helpers.getStoragePath"
'helpers.writeContentToFile',
'helpers.prepareBinaryData',
'helpers.setBinaryDataBuffer',
// This has been removed
'helpers.copyBinaryFile',
'helpers.binaryToBuffer',
// "helpers.binaryToString"
// "helpers.getBinaryPath"
// We can't support streams over RPC without implementing it ourselves
'helpers.createReadStream',
'helpers.getBinaryStream',
// Makes no sense to support this, as it returns either a stream or a buffer
// and we can't support streams over RPC
'helpers.binaryToBuffer',
// These are pretty low-level, so we shouldn't expose them
// (require binary data id, which we don't expose)
'helpers.getBinaryMetadata',
'helpers.getStoragePath',
'helpers.getBinaryPath',
// We shouldn't allow arbitrary FS writes
'helpers.writeContentToFile',
// Not something we need to expose. Can be done in the node itself
// copyInputItems(items: INodeExecutionData[], properties: string[]): IDataObject[]
'helpers.copyInputItems',
// Code Node does these automatically already
'helpers.returnJsonArray',
'helpers.normalizeItems',
// The client is instantiated and lives on the n8n instance, so we can't
// expose it over RPC without implementing object marshalling
'helpers.getSSHClient',
// Doesn't make sense to expose
'helpers.createDeferredPromise',
'helpers.httpRequest',
'logNodeOutput',
] as const;
'helpers.constructExecutionMetaData',
];
/** List of all RPC methods that task runner supports */
export const AVAILABLE_RPC_METHODS = [...EXPOSED_RPC_METHODS, 'logNodeOutput'] as const;
/** Node types needed for the runner to execute a task. */
export type NeededNodeType = { name: string; version: number };

View file

@ -1,3 +1,4 @@
import { isSerializedBuffer, toBuffer } from 'n8n-core';
import { ApplicationError, ensureError, randomInt } from 'n8n-workflow';
import { nanoid } from 'nanoid';
import { EventEmitter } from 'node:events';
@ -6,7 +7,7 @@ import { type MessageEvent, WebSocket } from 'ws';
import type { BaseRunnerConfig } from '@/config/base-runner-config';
import type { BrokerMessage, RunnerMessage } from '@/message-types';
import { TaskRunnerNodeTypes } from '@/node-types';
import { RPC_ALLOW_LIST, type TaskResultData } from '@/runner-types';
import type { TaskResultData } from '@/runner-types';
import { TaskCancelledError } from './js-task-runner/errors/task-cancelled-error';
@ -42,10 +43,6 @@ interface RPCCall {
reject: (error: unknown) => void;
}
export interface RPCCallObject {
[name: string]: ((...args: unknown[]) => Promise<unknown>) | RPCCallObject;
}
const OFFER_VALID_TIME_MS = 5000;
const OFFER_VALID_EXTRA_MS = 100;
@ -464,7 +461,9 @@ export abstract class TaskRunner extends EventEmitter {
});
try {
return await dataPromise;
const returnValue = await dataPromise;
return isSerializedBuffer(returnValue) ? toBuffer(returnValue) : returnValue;
} finally {
this.rpcCalls.delete(callId);
}
@ -486,24 +485,6 @@ export abstract class TaskRunner extends EventEmitter {
}
}
buildRpcCallObject(taskId: string) {
const rpcObject: RPCCallObject = {};
for (const r of RPC_ALLOW_LIST) {
const splitPath = r.split('.');
let obj = rpcObject;
splitPath.forEach((s, index) => {
if (index !== splitPath.length - 1) {
obj[s] = {};
obj = obj[s];
return;
}
obj[s] = async (...args: unknown[]) => await this.makeRpcCall(taskId, r, args);
});
}
return rpcObject;
}
/** Close the connection gracefully and wait until has been closed */
async stop() {
this.clearIdleTimer();

View file

@ -0,0 +1,136 @@
import { mock } from 'jest-mock-extended';
import { get, set } from 'lodash';
import type { NodeTypes } from '@/node-types';
import type { Task } from '@/runners/task-managers/task-manager';
import { TaskManager } from '@/runners/task-managers/task-manager';
class TestTaskManager extends TaskManager {
sentMessages: unknown[] = [];
sendMessage(message: unknown) {
this.sentMessages.push(message);
}
}
describe('TaskManager', () => {
let instance: TestTaskManager;
const mockNodeTypes = mock<NodeTypes>();
beforeEach(() => {
instance = new TestTaskManager(mockNodeTypes);
});
describe('handleRpc', () => {
test.each([
['logNodeOutput', ['hello world']],
['helpers.assertBinaryData', [0, 'propertyName']],
['helpers.getBinaryDataBuffer', [0, 'propertyName']],
['helpers.prepareBinaryData', [Buffer.from('data').toJSON(), 'filename', 'mimetype']],
['helpers.setBinaryDataBuffer', [{ data: '123' }, Buffer.from('data').toJSON()]],
['helpers.binaryToString', [Buffer.from('data').toJSON(), 'utf8']],
['helpers.httpRequest', [{ url: 'http://localhost' }]],
])('should handle %s rpc call', async (methodName, args) => {
const executeFunctions = set({}, methodName.split('.'), jest.fn());
const mockTask = mock<Task>({
taskId: 'taskId',
data: {
executeFunctions,
},
});
instance.tasks.set('taskId', mockTask);
await instance.handleRpc('taskId', 'callId', methodName, args);
expect(instance.sentMessages).toEqual([
{
callId: 'callId',
data: undefined,
status: 'success',
taskId: 'taskId',
type: 'requester:rpcresponse',
},
]);
expect(get(executeFunctions, methodName.split('.'))).toHaveBeenCalledWith(...args);
});
it('converts any serialized buffer arguments into buffers', async () => {
const mockPrepareBinaryData = jest.fn().mockResolvedValue(undefined);
const mockTask = mock<Task>({
taskId: 'taskId',
data: {
executeFunctions: {
helpers: {
prepareBinaryData: mockPrepareBinaryData,
},
},
},
});
instance.tasks.set('taskId', mockTask);
await instance.handleRpc('taskId', 'callId', 'helpers.prepareBinaryData', [
Buffer.from('data').toJSON(),
'filename',
'mimetype',
]);
expect(mockPrepareBinaryData).toHaveBeenCalledWith(
Buffer.from('data'),
'filename',
'mimetype',
);
});
describe('errors', () => {
it('sends method not allowed error if method is not in the allow list', async () => {
const mockTask = mock<Task>({
taskId: 'taskId',
data: {
executeFunctions: {},
},
});
instance.tasks.set('taskId', mockTask);
await instance.handleRpc('taskId', 'callId', 'notAllowedMethod', []);
expect(instance.sentMessages).toEqual([
{
callId: 'callId',
data: 'Method not allowed',
status: 'error',
taskId: 'taskId',
type: 'requester:rpcresponse',
},
]);
});
it('sends error if method throws', async () => {
const error = new Error('Test error');
const mockTask = mock<Task>({
taskId: 'taskId',
data: {
executeFunctions: {
helpers: {
assertBinaryData: jest.fn().mockRejectedValue(error),
},
},
},
});
instance.tasks.set('taskId', mockTask);
await instance.handleRpc('taskId', 'callId', 'helpers.assertBinaryData', []);
expect(instance.sentMessages).toEqual([
{
callId: 'callId',
data: error,
status: 'error',
taskId: 'taskId',
type: 'requester:rpcresponse',
},
]);
});
});
});
});

View file

@ -1,5 +1,6 @@
import type { TaskResultData, RequesterMessage, BrokerMessage, TaskData } from '@n8n/task-runner';
import { RPC_ALLOW_LIST } from '@n8n/task-runner';
import { AVAILABLE_RPC_METHODS } from '@n8n/task-runner';
import { isSerializedBuffer, toBuffer } from 'n8n-core';
import { createResultOk, createResultError } from 'n8n-workflow';
import type {
EnvProviderState,
@ -288,7 +289,7 @@ export abstract class TaskManager {
}
try {
if (!RPC_ALLOW_LIST.includes(name)) {
if (!AVAILABLE_RPC_METHODS.includes(name)) {
this.sendMessage({
type: 'requester:rpcresponse',
taskId,
@ -322,6 +323,15 @@ export abstract class TaskManager {
});
return;
}
// Convert any serialized buffers back to buffers
for (let i = 0; i < params.length; i++) {
const paramValue = params[i];
if (isSerializedBuffer(paramValue)) {
params[i] = toBuffer(paramValue);
}
}
const data = (await func.call(funcs, ...params)) as unknown;
this.sendMessage({

View file

@ -0,0 +1,24 @@
/** A nodejs Buffer gone through JSON.stringify */
export type SerializedBuffer = {
type: 'Buffer';
data: number[]; // Array like Uint8Array, each item is uint8 (0-255)
};
/** Converts the given SerializedBuffer to nodejs Buffer */
export function toBuffer(serializedBuffer: SerializedBuffer): Buffer {
return Buffer.from(serializedBuffer.data);
}
function isObjectLiteral(item: unknown): item is { [key: string]: unknown } {
return typeof item === 'object' && item !== null && !Array.isArray(item);
}
export function isSerializedBuffer(candidate: unknown): candidate is SerializedBuffer {
return (
isObjectLiteral(candidate) &&
'type' in candidate &&
'data' in candidate &&
candidate.type === 'Buffer' &&
Array.isArray(candidate.data)
);
}

View file

@ -24,3 +24,4 @@ export * from './ExecutionMetadata';
export * from './node-execution-context';
export * from './PartialExecutionUtils';
export { ErrorReporter } from './error-reporter';
export * from './SerializedBuffer';

View file

@ -0,0 +1,55 @@
import type { SerializedBuffer } from '@/SerializedBuffer';
import { toBuffer, isSerializedBuffer } from '@/SerializedBuffer';
// Mock data for tests
const validSerializedBuffer: SerializedBuffer = {
type: 'Buffer',
data: [65, 66, 67], // Corresponds to 'ABC' in ASCII
};
describe('serializedBufferToBuffer', () => {
it('should convert a SerializedBuffer to a Buffer', () => {
const buffer = toBuffer(validSerializedBuffer);
expect(buffer).toBeInstanceOf(Buffer);
expect(buffer.toString()).toBe('ABC');
});
it('should serialize stringified buffer to the same buffer', () => {
const serializedBuffer = JSON.stringify(Buffer.from('n8n on the rocks'));
const buffer = toBuffer(JSON.parse(serializedBuffer));
expect(buffer).toBeInstanceOf(Buffer);
expect(buffer.toString()).toBe('n8n on the rocks');
});
});
describe('isSerializedBuffer', () => {
it('should return true for a valid SerializedBuffer', () => {
expect(isSerializedBuffer(validSerializedBuffer)).toBe(true);
});
test.each([
[{ data: [1, 2, 3] }],
[{ data: [1, 2, 256] }],
[{ type: 'Buffer', data: 'notAnArray' }],
[{ data: 42 }],
[{ data: 'test' }],
[{ data: true }],
[null],
[undefined],
[42],
[{}],
])('should return false for %s', (value) => {
expect(isSerializedBuffer(value)).toBe(false);
});
});
describe('Integration: serializedBufferToBuffer and isSerializedBuffer', () => {
it('should correctly validate and convert a SerializedBuffer', () => {
if (isSerializedBuffer(validSerializedBuffer)) {
const buffer = toBuffer(validSerializedBuffer);
expect(buffer.toString()).toBe('ABC');
} else {
fail('Expected validSerializedBuffer to be a SerializedBuffer');
}
});
});

View file

@ -672,6 +672,9 @@ importers:
acorn-walk:
specifier: 8.3.4
version: 8.3.4
lodash.set:
specifier: 4.3.2
version: 4.3.2
n8n-core:
specifier: workspace:*
version: link:../../core
@ -688,6 +691,9 @@ importers:
specifier: '>=8.17.1'
version: 8.17.1
devDependencies:
'@types/lodash.set':
specifier: 4.3.9
version: 4.3.9
luxon:
specifier: 'catalog:'
version: 3.4.4
@ -1114,7 +1120,7 @@ importers:
dependencies:
'@langchain/core':
specifier: 'catalog:'
version: 0.3.19(openai@4.73.1(zod@3.23.8))
version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
'@n8n/client-oauth2':
specifier: workspace:*
version: link:../@n8n/client-oauth2
@ -1966,7 +1972,7 @@ importers:
devDependencies:
'@langchain/core':
specifier: 'catalog:'
version: 0.3.19(openai@4.73.1)
version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
'@types/deep-equal':
specifier: ^1.0.1
version: 1.0.1
@ -5621,6 +5627,9 @@ packages:
'@types/lodash-es@4.17.6':
resolution: {integrity: sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg==}
'@types/lodash.set@4.3.9':
resolution: {integrity: sha512-KOxyNkZpbaggVmqbpr82N2tDVTx05/3/j0f50Es1prxrWB0XYf9p3QNxqcbWb7P1Q9wlvsUSlCFnwlPCIJ46PQ==}
'@types/lodash@4.14.195':
resolution: {integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==}
@ -9736,6 +9745,9 @@ packages:
lodash.orderby@4.6.0:
resolution: {integrity: sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==}
lodash.set@4.3.2:
resolution: {integrity: sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==}
lodash.throttle@4.1.1:
resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
@ -16206,38 +16218,6 @@ snapshots:
transitivePeerDependencies:
- openai
'@langchain/core@0.3.19(openai@4.73.1(zod@3.23.8))':
dependencies:
ansi-styles: 5.2.0
camelcase: 6.3.0
decamelize: 1.2.0
js-tiktoken: 1.0.12
langsmith: 0.2.3(openai@4.73.1(zod@3.23.8))
mustache: 4.2.0
p-queue: 6.6.2
p-retry: 4.6.2
uuid: 10.0.0
zod: 3.23.8
zod-to-json-schema: 3.23.3(zod@3.23.8)
transitivePeerDependencies:
- openai
'@langchain/core@0.3.19(openai@4.73.1)':
dependencies:
ansi-styles: 5.2.0
camelcase: 6.3.0
decamelize: 1.2.0
js-tiktoken: 1.0.12
langsmith: 0.2.3(openai@4.73.1)
mustache: 4.2.0
p-queue: 6.6.2
p-retry: 4.6.2
uuid: 10.0.0
zod: 3.23.8
zod-to-json-schema: 3.23.3(zod@3.23.8)
transitivePeerDependencies:
- openai
'@langchain/google-common@0.1.3(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(zod@3.23.8)':
dependencies:
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
@ -18361,6 +18341,10 @@ snapshots:
dependencies:
'@types/lodash': 4.14.195
'@types/lodash.set@4.3.9':
dependencies:
'@types/lodash': 4.14.195
'@types/lodash@4.14.195': {}
'@types/long@4.0.2': {}
@ -19466,14 +19450,6 @@ snapshots:
transitivePeerDependencies:
- debug
axios@1.7.7:
dependencies:
follow-redirects: 1.15.6(debug@4.3.6)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
axios@1.7.7(debug@4.3.6):
dependencies:
follow-redirects: 1.15.6(debug@4.3.6)
@ -21187,7 +21163,7 @@ snapshots:
eslint-import-resolver-node@0.3.9:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
is-core-module: 2.13.1
resolve: 1.22.8
transitivePeerDependencies:
@ -21212,7 +21188,7 @@ snapshots:
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.7.2)
eslint: 8.57.0
@ -21232,7 +21208,7 @@ snapshots:
array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@ -22011,7 +21987,7 @@ snapshots:
array-parallel: 0.1.3
array-series: 0.1.5
cross-spawn: 4.0.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@ -22392,7 +22368,7 @@ snapshots:
infisical-node@1.3.0:
dependencies:
axios: 1.7.7
axios: 1.7.7(debug@4.3.6)
dotenv: 16.3.1
tweetnacl: 1.0.3
tweetnacl-util: 0.15.1
@ -23369,28 +23345,6 @@ snapshots:
optionalDependencies:
openai: 4.73.1(encoding@0.1.13)(zod@3.23.8)
langsmith@0.2.3(openai@4.73.1(zod@3.23.8)):
dependencies:
'@types/uuid': 10.0.0
commander: 10.0.1
p-queue: 6.6.2
p-retry: 4.6.2
semver: 7.6.0
uuid: 10.0.0
optionalDependencies:
openai: 4.73.1(zod@3.23.8)
langsmith@0.2.3(openai@4.73.1):
dependencies:
'@types/uuid': 10.0.0
commander: 10.0.1
p-queue: 6.6.2
p-retry: 4.6.2
semver: 7.6.0
uuid: 10.0.0
optionalDependencies:
openai: 4.73.1(zod@3.23.8)
lazy-ass@1.6.0: {}
ldapts@4.2.6:
@ -23572,6 +23526,8 @@ snapshots:
lodash.orderby@4.6.0: {}
lodash.set@4.3.2: {}
lodash.throttle@4.1.1: {}
lodash@4.17.21: {}
@ -24723,22 +24679,6 @@ snapshots:
- encoding
- supports-color
openai@4.73.1(zod@3.23.8):
dependencies:
'@types/node': 18.16.16
'@types/node-fetch': 2.6.4
abort-controller: 3.0.0
agentkeepalive: 4.2.1
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0(encoding@0.1.13)
optionalDependencies:
zod: 3.23.8
transitivePeerDependencies:
- encoding
- supports-color
optional: true
openapi-sampler@1.5.1:
dependencies:
'@types/json-schema': 7.0.15
@ -24919,7 +24859,7 @@ snapshots:
pdf-parse@1.1.1:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
node-ensure: 0.0.0
transitivePeerDependencies:
- supports-color
@ -25121,7 +25061,7 @@ snapshots:
posthog-node@3.2.1:
dependencies:
axios: 1.7.7
axios: 1.7.7(debug@4.3.6)
rusha: 0.8.14
transitivePeerDependencies:
- debug
@ -25761,7 +25701,7 @@ snapshots:
rhea@1.0.24:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@ -26139,7 +26079,7 @@ snapshots:
asn1.js: 5.4.1
asn1.js-rfc2560: 5.0.1(asn1.js@5.4.1)
asn1.js-rfc5280: 3.0.0
axios: 1.7.7
axios: 1.7.7(debug@4.3.6)
big-integer: 1.6.51
bignumber.js: 9.1.2
binascii: 0.0.2