diff --git a/.fernignore b/.fernignore index 488dd907..19625584 100644 --- a/.fernignore +++ b/.fernignore @@ -25,7 +25,6 @@ tests/unit/fetcher/stream-wrappers/webpack.test.ts .prettierrc.yml .prettierignore .gitignore -babel.config.js # Package Scripts diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 3c2f6ced..00000000 --- a/babel.config.js +++ /dev/null @@ -1,10 +0,0 @@ -// Required by webpack: OTEL dependencies do not compile nicely otherwise - -module.exports = { - presets: [ - [ - "@babel/preset-env", - { targets: { node: "current" } } - ], - ], - }; \ No newline at end of file diff --git a/package.json b/package.json index 4678d1b3..8b19a698 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,7 @@ "jsonschema": "^1.4.1", "@types/cli-progress": "^3.11.6", "@types/lodash": "4.14.74", - "@trivago/prettier-plugin-sort-imports": "^4.3.0", - "babel-jest": "^29.7.0", - "@babel/core": "^7.26.0", - "@babel/preset-env": "^7.26.0" + "@trivago/prettier-plugin-sort-imports": "^4.3.0" }, "browser": { "fs": false, diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 00000000..11d65c47 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,147 @@ +import * as contextApi from "@opentelemetry/api"; + +import { HumanloopRuntimeError } from "./error"; +import { + HUMANLOOP_CONTEXT_DECORATOR, + HUMANLOOP_CONTEXT_EVALUATION, + HUMANLOOP_CONTEXT_TRACE_ID, +} from "./otel/constants"; + +export function getTraceId(): string | undefined { + const key = contextApi.createContextKey(HUMANLOOP_CONTEXT_TRACE_ID); + const value = contextApi.context.active().getValue(key); + return (value || undefined) as string | undefined; +} + +export function setTraceId(flowLogId: string): contextApi.Context { + const key = contextApi.createContextKey(HUMANLOOP_CONTEXT_TRACE_ID); + return contextApi.context.active().setValue(key, flowLogId); +} + +export type DecoratorContext = { + path: string; + type: "prompt" | "tool" | "flow"; + version: Record; +}; + +export function setDecoratorContext( + decoratorContext: DecoratorContext, +): contextApi.Context { + const key = contextApi.createContextKey(HUMANLOOP_CONTEXT_DECORATOR); + return contextApi.context.active().setValue(key, decoratorContext); +} + +export function getDecoratorContext(): DecoratorContext | undefined { + const key = contextApi.createContextKey(HUMANLOOP_CONTEXT_DECORATOR); + return (contextApi.context.active().getValue(key) || undefined) as + | DecoratorContext + | undefined; +} + +export class EvaluationContext { + public sourceDatapointId: string; + public runId: string; + public fileId: string; + public path: string; + private _logged: boolean; + private _callback: (log_id: string) => Promise; + + constructor({ + sourceDatapointId, + runId, + evalCallback, + fileId, + path, + }: { + sourceDatapointId: string; + runId: string; + evalCallback: (log_id: string) => Promise; + fileId: string; + path: string; + }) { + this.sourceDatapointId = sourceDatapointId; + this.runId = runId; + this._callback = evalCallback; + this.fileId = fileId; + this.path = path; + this._logged = false; + } + + public get logged(): boolean { + return this._logged; + } + + public logArgsWithContext({ + logArgs, + forOtel, + path, + fileId, + }: { + logArgs: Record; + forOtel: boolean; + path?: string; + fileId?: string; + }): [Record, ((log_id: string) => Promise) | null] { + if (path === undefined && fileId === undefined) { + throw new HumanloopRuntimeError( + "Internal error: Evaluation context called without providing a path or file_id", + ); + } + + if (this._logged) { + return [logArgs, null]; + } + + if (this.path !== undefined && this.path === path) { + this._logged = true; + return [ + forOtel + ? { + ...logArgs, + source_datapoint_id: this.sourceDatapointId, + run_id: this.runId, + } + : { + ...logArgs, + sourceDatapointId: this.sourceDatapointId, + runId: this.runId, + }, + this._callback, + ]; + } else if (this.fileId !== undefined && this.fileId === fileId) { + this._logged = true; + return [ + forOtel + ? { + ...logArgs, + sourceDatapointId: this.sourceDatapointId, + runId: this.runId, + } + : { + ...logArgs, + sourceDatapointId: this.sourceDatapointId, + runId: this.runId, + }, + this._callback, + ]; + } else { + return [logArgs, null]; + } + } +} + +// ... existing code ... + +export function setEvaluationContext( + evaluationContext: EvaluationContext, +): contextApi.Context { + const key = contextApi.createContextKey(HUMANLOOP_CONTEXT_EVALUATION); + return contextApi.context.active().setValue(key, evaluationContext); +} + +export function getEvaluationContext(): EvaluationContext | undefined { + const key = contextApi.createContextKey(HUMANLOOP_CONTEXT_EVALUATION); + return (contextApi.context.active().getValue(key) || undefined) as + | EvaluationContext + | undefined; +} diff --git a/src/decorators/flow.ts b/src/decorators/flow.ts new file mode 100644 index 00000000..30e6ab2d --- /dev/null +++ b/src/decorators/flow.ts @@ -0,0 +1,151 @@ +import * as contextApi from "@opentelemetry/api"; +import { ReadableSpan, Tracer } from "@opentelemetry/sdk-trace-node"; + +import { HumanloopClient } from "../Client"; +import { ChatMessage, FlowLogRequest, FlowLogResponse } from "../api"; +import { getTraceId, setDecoratorContext, setTraceId } from "../context"; +import { jsonifyIfNotString, writeToOpenTelemetrySpan } from "../otel"; +import { + HUMANLOOP_FILE_TYPE_KEY, + HUMANLOOP_FLOW_SPAN_NAME, + HUMANLOOP_LOG_KEY, + HUMANLOOP_PATH_KEY, +} from "../otel/constants"; + +export function flowUtilityFactory( + client: HumanloopClient, + opentelemetryTracer: Tracer, + callable: ( + args: I extends Record & { + messages?: ChatMessage[]; + } + ? I + : never, + ) => O, + path: string, + attributes?: Record, +): (args: I) => Promise & { + file: { + type: string; + version: { attributes?: Record }; + callable: (args: I) => Promise; + }; +} { + const flowKernel = { attributes: attributes || {} }; + const fileType = "flow"; + + const wrappedFunction = async ( + inputs: I extends Record & { + messages?: ChatMessage[]; + } + ? I + : never, + ) => { + return contextApi.context.with( + setDecoratorContext({ + path: path, + type: fileType, + version: flowKernel, + }), + async () => { + return opentelemetryTracer.startActiveSpan( + HUMANLOOP_FLOW_SPAN_NAME, + async (span) => { + span.setAttribute(HUMANLOOP_PATH_KEY, path); + span.setAttribute(HUMANLOOP_FILE_TYPE_KEY, fileType); + const traceId = getTraceId(); + + const logInputs = { ...inputs } as Record; + const logMessages = logInputs.messages as + | ChatMessage[] + | undefined; + delete logInputs.messages; + + const initLogInputs: FlowLogRequest = { + inputs: logInputs, + messages: logMessages, + traceParentId: traceId, + }; + + const flowLogResponse: FlowLogResponse = + // @ts-ignore + await client.flows._log({ + path, + flow: flowKernel, + logStatus: "incomplete", + ...initLogInputs, + }); + + return await contextApi.context.with( + setTraceId(flowLogResponse.id), + async () => { + let logOutput: string | undefined; + let outputMessage: ChatMessage | undefined; + let logError: string | undefined; + let funcOutput: O | undefined; + + try { + funcOutput = await callable(inputs); + if ( + // @ts-ignore + funcOutput instanceof Object && + "role" in funcOutput && + "content" in funcOutput + ) { + outputMessage = + funcOutput as unknown as ChatMessage; + logOutput = undefined; + } else { + logOutput = jsonifyIfNotString( + callable, + funcOutput, + ); + outputMessage = undefined; + } + logError = undefined; + } catch (err: any) { + console.error( + `Error calling ${callable.name}:`, + err, + ); + logOutput = undefined; + outputMessage = undefined; + logError = err.message || String(err); + funcOutput = undefined as unknown as O; + } + + const updatedFlowLog = { + log_status: "complete", + output: logOutput, + error: logError, + output_message: outputMessage, + id: flowLogResponse.id, + }; + + writeToOpenTelemetrySpan( + span as unknown as ReadableSpan, + // @ts-ignore + updatedFlowLog, + HUMANLOOP_LOG_KEY, + ); + + span.end(); + return funcOutput; + }, + ); + }, + ); + }, + ); + }; + + // @ts-ignore + return Object.assign(wrappedFunction, { + file: { + path: path, + type: fileType, + version: flowKernel, + callable: wrappedFunction, + }, + }); +} diff --git a/src/decorators/index.ts b/src/decorators/index.ts new file mode 100644 index 00000000..16cb79a1 --- /dev/null +++ b/src/decorators/index.ts @@ -0,0 +1,2 @@ +export { flowUtilityFactory } from "./flow"; +export { toolUtilityFactory } from "./tool"; diff --git a/src/decorators/prompt.ts b/src/decorators/prompt.ts new file mode 100644 index 00000000..c9b5e13b --- /dev/null +++ b/src/decorators/prompt.ts @@ -0,0 +1,35 @@ +import * as contextApi from "@opentelemetry/api"; + +import { setDecoratorContext } from "../evals"; + +export function promptDecoratorFactory(path: string, callable: (inputs: I) => O) { + const fileType = "prompt"; + + const wrappedFunction = (inputs: I) => { + return contextApi.context.with( + setDecoratorContext({ + path: path, + type: fileType, + version: { + // TODO: Implement reverse lookup of template + template: undefined, + }, + }), + async () => { + return await callable(inputs); + }, + ); + }; + + return Object.assign(wrappedFunction, { + file: { + path: path, + type: fileType, + version: { + // TODO: Implement reverse lookup of template + template: undefined, + }, + callable: wrappedFunction, + }, + }); +} diff --git a/src/decorators/tool.ts b/src/decorators/tool.ts new file mode 100644 index 00000000..54db8be6 --- /dev/null +++ b/src/decorators/tool.ts @@ -0,0 +1,132 @@ +import { ReadableSpan, Tracer } from "@opentelemetry/sdk-trace-node"; + +import { ToolKernelRequest } from "../api/types/ToolKernelRequest"; +import { getEvaluationContext, getTraceId } from "../context"; +import { File as EvalRunFile } from "../evals/types"; +import { NestedDict, jsonifyIfNotString, writeToOpenTelemetrySpan } from "../otel"; +import { + HUMANLOOP_FILE_KEY, + HUMANLOOP_FILE_TYPE_KEY, + HUMANLOOP_LOG_KEY, + HUMANLOOP_PATH_KEY, + HUMANLOOP_TOOL_SPAN_NAME, +} from "../otel/constants"; + +/** + * Higher-order function for wrapping a function with OpenTelemetry instrumentation. + * It inspects the argument signature of the wrapped function to build a JSON schema. + * + * @param func - The function to wrap + * @param opentelemetryTracer - The OpenTelemetry tracer instance + * @param path - Optional span path + * @param version - Additional metadata for the function + * @returns Wrapped function with OpenTelemetry instrumentation + */ +export function toolUtilityFactory( + opentelemetryTracer: Tracer, + callable: (inputs: I) => O, + version: ToolKernelRequest, + path: string, +): (inputs: I) => O & { + jsonSchema: Record; + file: EvalRunFile; +} { + const fileType = "tool"; + + const wrappedFunction = async (inputs: I) => { + const evaluationContext = getEvaluationContext(); + if (evaluationContext && evaluationContext.path === path) { + throw new Error( + "Tools cannot be evaluated with the `evaluations.run()` utility.", + ); + } + + validateArgumentsAgainstSchema(version, inputs); + + // @ts-ignore + return opentelemetryTracer.startActiveSpan( + HUMANLOOP_TOOL_SPAN_NAME, + async (span) => { + // Add span attributes + writeToOpenTelemetrySpan( + span as unknown as ReadableSpan, + { + ...version, + } as unknown as NestedDict, + HUMANLOOP_FILE_KEY, + ); + span = span.setAttribute(HUMANLOOP_FILE_TYPE_KEY, fileType); + span = span.setAttribute(HUMANLOOP_PATH_KEY, path); + + let logInputs = { ...inputs } as Record; + let logError: string | undefined; + let logOutput: string | undefined; + + let funcOutput: O | undefined; + try { + funcOutput = await callable(inputs); + logOutput = jsonifyIfNotString(callable, funcOutput); + logError = undefined; + } catch (err: any) { + console.error(`Error calling ${callable.name}:`, err); + funcOutput = undefined; + logOutput = undefined; + logError = err.message || String(err); + } + + const toolLog = { + inputs: logInputs, + output: logOutput, + error: logError, + trace_parent_id: getTraceId(), + }; + writeToOpenTelemetrySpan( + span as unknown as ReadableSpan, + toolLog as unknown as NestedDict, + HUMANLOOP_LOG_KEY, + ); + + span.end(); + return funcOutput; + }, + ); + }; + + // @ts-ignore Adding jsonSchema property to utility-wrapped function + return Object.assign(wrappedFunction, { + jsonSchema: version.function || {}, + decorator: { + type: fileType, + path: path, + version, + }, + }); +} + +function validateArgumentsAgainstSchema(toolKernel: ToolKernelRequest, inputs?: any) { + const parameters = + (toolKernel.function?.parameters?.properties as Record) || {}; + + if (!parameters || Object.keys(parameters).length === 0) { + if (inputs === undefined) { + return; + } + throw new Error( + `Tool function ${toolKernel.function?.name} received inputs when the JSON schema defines none`, + ); + } + + if (inputs === undefined) { + if (Object.keys(parameters).length > 0 || !parameters) { + throw new Error( + `Tool function ${toolKernel.function?.name} expected inputs but received none.`, + ); + } + } + + Object.keys(inputs!).forEach((inputKey) => { + if (!parameters.hasOwnProperty(inputKey)) { + throw new Error(`Inputs key '${inputKey}' does not match the JSON schema.`); + } + }); +} diff --git a/src/decorators/types.ts b/src/decorators/types.ts new file mode 100644 index 00000000..c4fc6f54 --- /dev/null +++ b/src/decorators/types.ts @@ -0,0 +1,3 @@ +export type InputsMessagesCallableType = (inputs: I, messages: M) => O; + +export type ToolCallableType = (inputs: I) => O; diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000..df5b0442 --- /dev/null +++ b/src/error.ts @@ -0,0 +1,19 @@ +export class HumanloopRuntimeError extends Error { + /** + * SDK custom code handles exceptions by populating Logs' `error` field. + * + * This exception signals an error severe enough to crash the execution + * e.g. illegal use of decorators. + */ + + constructor(message?: string) { + super(message); + } + + toString(): string { + if (this.message === undefined) { + return super.toString(); + } + return this.message; + } +} diff --git a/src/evals/index.ts b/src/evals/index.ts new file mode 100644 index 00000000..0a7ca070 --- /dev/null +++ b/src/evals/index.ts @@ -0,0 +1 @@ +export * from "../context"; diff --git a/src/evals/run.ts b/src/evals/run.ts new file mode 100644 index 00000000..87ad753e --- /dev/null +++ b/src/evals/run.ts @@ -0,0 +1,983 @@ +/** + * Evaluation utils for the Humanloop SDK. + * + * This module provides a set of utilities to aid running Eval workflows on Humanloop + * where you are managing the runtime of your application in your code. + * + * Functions in this module should be accessed via the Humanloop client. They should + * not be called directly. + */ +import * as contextApi from "@opentelemetry/api"; +import cliProgress from "cli-progress"; +import _, { capitalize } from "lodash"; + +import { + BooleanEvaluatorStatsResponse, + ChatMessage, + CreateEvaluationRequestEvaluatorsItem, + DatapointResponse, + DatasetResponse, + EvaluationResponse, + EvaluationRunResponse, + EvaluationStats, + EvaluatorArgumentsType, + EvaluatorRequest, + EvaluatorResponse, + EvaluatorReturnTypeEnum, + ExternalEvaluatorRequest, + FileType, + FlowLogRequest, + FlowRequest, + FlowResponse, + LogResponse, + NumericEvaluatorStatsResponse, + PromptLogRequest, + PromptRequest, + PromptResponse, + RunStatsResponse, + ToolKernelRequest, + ToolLogRequest, + ToolRequest, + ToolResponse, +} from "../api"; +import { + EvaluationContext, + getEvaluationContext, + setEvaluationContext, +} from "../context"; +import { HumanloopRuntimeError } from "../error"; +import { Humanloop, HumanloopClient } from "../index"; +import { jsonifyIfNotString } from "../otel/helpers"; +import { + Dataset, + Evaluator, + EvaluatorCheck, + File, + FileResponse, + LocalEvaluator, + LocalEvaluatorReturnTypeEnum, + OnlineEvaluator, +} from "./types"; + +// ANSI escape codes for logging colors +const YELLOW = "\x1b[93m"; +const CYAN = "\x1b[96m"; +const GREEN = "\x1b[92m"; +const RED = "\x1b[91m"; +const RESET = "\x1b[0m"; + +/** + * Maps over an array of items with a concurrency limit, applying an asynchronous mapper function to each item. + * + * @template T - The type of the items in the input array. + * @template O - The type of the items in the output array. + * + * @param {T[]} iterable - The array of items to be mapped. + * @param {(item: T) => Promise} mapper - The asynchronous function to apply to each item. + * @param {{ concurrency: number }} options - Options for the mapping operation. + * @param {number} options.concurrency - The maximum number of promises to resolve concurrently. + * + * @returns {Promise} A promise that resolves to an array of mapped items. + * + * @throws {TypeError} If the first argument is not an array. + * @throws {TypeError} If the second argument is not a function. + * @throws {TypeError} If the concurrency option is not a positive number. + * + * @description + * The `pMap` function processes the input array in chunks, where the size of each chunk is determined by the `concurrency` option. + * This controls how many promises are resolved at a time, which can help avoid issues such as rate limit errors when making server requests. + */ +async function pMap( + iterable: T[], + mapper: (item: T) => Promise, + options: { concurrency: number }, +): Promise { + const { concurrency } = options; + + if (!Array.isArray(iterable)) { + throw new TypeError("Expected the first argument to be an array"); + } + + if (typeof mapper !== "function") { + throw new TypeError("Expected the second argument to be a function"); + } + + if (typeof concurrency !== "number" || concurrency <= 0) { + throw new TypeError("Expected the concurrency option to be a positive number"); + } + + const result: O[] = []; + for (let i = 0; i < iterable.length; i += concurrency) { + const chunk = iterable.slice(i, i + concurrency); + try { + const chunkResults = await Promise.all(chunk.map(mapper)); + result.push(...chunkResults); + } catch (error) { + // Handle individual chunk errors if necessary + // For now, rethrow to reject the entire pMap promise + throw error; + } + } + return result; +} + +function callableIsHumanloopUtility(file: File): boolean { + return file.callable !== undefined && "decorator" in file.callable; +} + +function fileOrFileInsideHLUtility(file: File): File { + if (callableIsHumanloopUtility(file)) { + // @ts-ignore + const innerFile: File = file.callable!.file! as File; + if ("path" in file && innerFile.path !== file.path) { + throw new HumanloopRuntimeError( + "`path` attribute specified in the `file` does not match the path of the decorated function. " + + `Expected \`${innerFile.path}\`, got \`${file.path}\`.`, + ); + } + if ("id" in file) { + throw new HumanloopRuntimeError( + "Do not specify an `id` attribute in `file` argument when using a decorated function.", + ); + } + if ("version" in file) { + if (innerFile.type !== "prompt") { + throw new HumanloopRuntimeError( + `Do not specify a \`version\` attribute in \`file\` argument when using a ${capitalize(innerFile.type)} decorated function.`, + ); + } + } + if ("type" in file && innerFile.type !== file.type) { + throw new HumanloopRuntimeError( + "Attribute `type` of `file` argument does not match the file type of the decorated function. " + + `Expected \`${innerFile.type}\`, got \`${file.type}\`.`, + ); + } + const file_ = { ...innerFile }; + if (file_.type === "prompt") { + console.warn( + `${YELLOW}` + + "The @prompt decorator will not spy on provider calls when passed to `evaluations.run()`. " + + "Using the `version` in `file` argument instead.\n" + + `${RESET}`, + ); + file_.version = file.version; + } + return file_; + } else { + const file_ = { ...file }; + if (!file_.path && !file_.id) { + throw new HumanloopRuntimeError( + "You must provide a path or id in your `file`.", + ); + } + return file_; + } +} + +function getFileType(file: File): FileType { + // Determine the `type` of the `file` to Evaluate - if not `type` provided, default to `flow` + try { + const type_ = file.type as FileType; + console.info( + `${CYAN}Evaluating your ${type_} function corresponding to \`${file.path || file.id}\` on Humanloop${RESET}\n\n`, + ); + return type_ || "flow"; + } catch (error) { + const type_ = "flow"; + console.warn( + `${YELLOW}No \`file\` type specified, defaulting to flow.${RESET}\n`, + ); + return type_; + } +} + +function getFileCallable( + file: File, + type_: FileType, +): File["callable"] { + // Get the `callable` from the `file` to Evaluate + const function_ = file.callable; + if (!function_) { + if (type_ === "flow") { + throw new Error( + "You must provide a `callable` for your Flow `file` to run a local eval.", + ); + } else { + console.info( + `${CYAN}No \`callable\` provided for your ${type_} file - will attempt to generate logs on Humanloop.\n\n${RESET}`, + ); + } + } + return function_; +} + +export async function runEval( + client: HumanloopClient, + file: File, + dataset: Dataset, + name?: string, + evaluators: ( + | OnlineEvaluator + | LocalEvaluator + )[] = [], + concurrency: number = 8, +): Promise { + if (concurrency > 32) { + console.log("Warning: Too many parallel workers, capping the number to 32."); + } + concurrency = Math.min(concurrency, 32); + + const file_ = fileOrFileInsideHLUtility(file); + const type_ = getFileType(file_); + const function_ = getFileCallable(file_, type_); + + if (function_ && "file" in function_) { + // @ts-ignore + const decoratorType = (function_.file as File).type; + if (decoratorType !== type_) { + throw new HumanloopRuntimeError( + `The type of the decorated function does not match the type of the file. Expected \`${capitalize(type_)}\`, got \`${capitalize(decoratorType)}\`.`, + ); + } + } + + let hlFile: PromptResponse | FlowResponse | ToolResponse | EvaluatorResponse; + try { + hlFile = await upsertFile({ file: file_, type: type_, client: client }); + } catch (e: any) { + console.error( + `${RED}Error in your \`file\` argument:\n\n${e.constructor.name}: ${e.message}${RESET}`, + ); + return []; + } + + let hlDataset: DatasetResponse; + try { + hlDataset = await upsertDataset({ dataset: dataset, client: client }); + } catch (e: any) { + console.error( + `${RED}Error in your \`file\` argument:\n\n${e.constructor.name}: ${e.message}${RESET}`, + ); + return []; + } + + let localEvaluators: _LocalEvaluator[]; + try { + localEvaluators = await upsertLocalEvaluators({ + evaluators: evaluators.filter( + (evaluator) => "callable" in evaluator, + ) as LocalEvaluator[], + client: client, + // @ts-ignore + callable: function_, + type: type_, + }); + } catch (e: any) { + console.error( + `${RED}Error in your \`file\` argument:\n\n${e.constructor.name} ${e.message}${RESET}`, + ); + return []; + } + + assertDatasetEvaluatorsFit(hlDataset, localEvaluators); + + const { evaluation, run } = await getNewRun({ + client, + evaluationName: name, + evaluators, + hlFile, + hlDataset, + // @ts-ignore + func: function_, + }); + + const runId = run.id; + + function cancelEvaluation() { + client.evaluations.updateEvaluationRun(evaluation.id, runId, { + status: "cancelled", + }); + } + + function handleExitSignal(signum: number) { + process.stderr.write( + `\n${RED}Received signal ${signum}, cancelling Evaluation and shutting down threads...${RESET}\n`, + ); + cancelEvaluation(); + process.exit(signum); + } + + process.on("SIGINT", handleExitSignal); + process.on("SIGTERM", handleExitSignal); + + // Header of the CLI report + console.log(`\n${CYAN}Navigate to your Evaluation:${RESET}\n${evaluation.url}\n`); + console.log( + `${CYAN}${type_.charAt(0).toUpperCase() + type_.slice(1)} Version ID: ${ + hlFile.versionId + }${RESET}`, + ); + console.log(`${CYAN}Run ID: ${runId}${RESET}`); + + // Generate locally if a file `callable` is provided + if (function_ === undefined) { + // TODO: trigger run when updated API is available + process.stdout.write( + `${CYAN}\nRunning '${hlFile.name}' over the Dataset '${hlDataset.name}'${RESET}\n`, + ); + } else { + // Running the evaluation locally + process.stdout.write( + `${CYAN}\nRunning '${hlFile.name}' over the Dataset '${hlDataset.name}' locally...${RESET}\n\n`, + ); + } + + // Configure the progress bar + const progressBar = new cliProgress.SingleBar( + { + format: + "Progress |" + + "{bar}" + + "| {percentage}% || {value}/{total} Datapoints", + }, + cliProgress.Presets.shades_classic, + ); + + async function processDatapoint( + datapoint: DatapointResponse, + runId: string, + ): Promise { + async function uploadCallback(logId: string) { + await runLocalEvaluators(client, logId, datapoint, localEvaluators); + progressBar.increment(); + } + + if (function_ === undefined) { + throw new HumanloopRuntimeError( + `\`file.callable\` is undefined. Please provide a callable for your file.`, + ); + } + + const logFunc = getLogFunction( + client, + type_, + hlFile.id, + hlFile.versionId, + runId, + ); + + if (datapoint.inputs === undefined) { + throw new Error(`Datapoint 'inputs' attribute is undefined.`); + } + + contextApi.context.with( + setEvaluationContext( + new EvaluationContext({ + sourceDatapointId: datapoint.id, + runId, + evalCallback: uploadCallback, + fileId: hlFile.id, + path: hlFile.path, + }), + ), + async () => { + const startTime = new Date(); + let funcInputs: Record & { + messages?: Humanloop.ChatMessage[]; + } = { + ...datapoint.inputs, + }; + if ("messages" in datapoint) { + funcInputs = { + ...funcInputs, + messages: datapoint.messages, + }; + } + + const evaluationContext = getEvaluationContext(); + if (!evaluationContext) { + throw new Error( + "Internal error: evaluation context is not set while processing a datapoint.", + ); + } + + try { + const output = await callFunction(function_, datapoint); + + if (!evaluationContext.logged) { + const log = await logFunc({ + inputs: { ...datapoint.inputs }, + messages: datapoint.messages, + output: output, + sourceDatapointId: datapoint.id, + runId: runId, + startTime: startTime, + endTime: new Date(), + logStatus: "complete", + }); + // @ts-ignore + evaluationContext._callback(log.id); + } + } catch (e: any) { + if (e instanceof HumanloopRuntimeError) { + throw e; + } + const errorMessage = e instanceof Error ? e.message : String(e); + const log = await logFunc({ + inputs: { ...datapoint.inputs }, + messages: datapoint.messages, + error: errorMessage, + sourceDatapointId: datapoint.id, + runId: runId, + startTime: startTime, + endTime: new Date(), + logStatus: "complete", + }); + // @ts-ignore + evaluationContext._callback(log.id); + console.warn( + `\nYour ${type_}'s callable failed for Datapoint: ${datapoint.id}.\nError: ${errorMessage}`, + ); + } + }, + ); + } + + // Generate locally if a function is provided + if (function_) { + console.log( + `${CYAN}\nRunning ${hlFile.name} over the Dataset ${hlDataset.name}${RESET}`, + ); + const totalDatapoints = hlDataset.datapoints!.length; + progressBar.start(totalDatapoints, 0); + + await pMap( + hlDataset.datapoints!, + async (datapoint) => { + await processDatapoint(datapoint, runId); + }, + { concurrency: concurrency }, + ); + progressBar.stop(); + } else { + // TODO: trigger run when updated API is available + console.log( + `${CYAN}\nRunning ${hlFile.name} over the Dataset ${hlDataset.name}${RESET}`, + ); + } + + // Wait for the Evaluation to complete then print the results + let stats; + do { + stats = await client.evaluations.getStats(evaluation.id); + if (stats?.progress) { + const newLines = stats.progress.split("\n").length - 1; + process.stdout.write(`\r${stats.progress}`); + // Move the cursor up by the number of new lines + process.stdout.moveCursor(0, -newLines); + } + if (stats.status !== "completed") { + await new Promise((resolve) => setTimeout(resolve, 500)); + } + } while (stats.status !== "completed"); + console.log(stats.report); + + const checks: EvaluatorCheck[] = []; + if ( + evaluators.some((evaluator) => evaluator.threshold !== undefined) || + stats.runStats.length > 1 + ) { + for (const evaluator of evaluators) { + const [_, score, delta] = checkEvaluationImprovement( + evaluation, + evaluator.path!, + stats, + runId, + ); + let thresholdCheck = undefined; + if (evaluator.threshold !== undefined) { + thresholdCheck = score >= evaluator.threshold; + thresholdCheck = checkEvaluationThreshold( + evaluation, + stats, + evaluator.path!, + evaluator.threshold, + runId, + ); + } + checks.push({ + path: evaluator.path!, + // TODO: Add back in with number valence on Evaluators + // improvementCheck: improvementCheck, + score: score, + delta: delta, + threshold: evaluator.threshold, + thresholdCheck: thresholdCheck, + evaluationId: evaluation.id, + }); + } + } + + console.info(`\n${CYAN}View your Evaluation:${RESET}\n${evaluation.url}\n`); + return checks; +} + +async function callFunction( + callable: File["callable"], + datapoint: DatapointResponse, +): Promise { + const datapointDict = { ...datapoint }; + let output; + if (callable === undefined) { + throw new HumanloopRuntimeError( + `\`file.callable\` is undefined. Please provide a callable for your file.`, + ); + } + if ("messages" in datapointDict && !!datapointDict["messages"]) { + output = await callable({ + ...datapointDict["inputs"], + messages: datapointDict["messages"], + } as unknown as I); + } else { + output = await callable({ ...datapointDict["inputs"] } as unknown as I); + } + + if (typeof output !== "string") { + try { + output = JSON.stringify(output); + } catch (error) { + throw new HumanloopRuntimeError( + `\`file.callable\` must return a string or a JSON serializable object.`, + ); + } + } + return output; +} + +async function upsertFile({ + file, + type, + client, +}: { + file: File; + type: FileType; + client: HumanloopClient; +}): Promise { + // Get or create the file on Humanloop + const version = file.version || {}; + const fileDict = { ...file, ...version }; + + let hlFile: PromptResponse | FlowResponse | ToolResponse | EvaluatorResponse; + switch (type) { + case "flow": + // Be more lenient with Flow versions as they are arbitrary json + const flowVersion = { attributes: version }; + const fileDictWithFlowVersion = { ...file, ...flowVersion }; + hlFile = await client.flows.upsert(fileDictWithFlowVersion as FlowRequest); + break; + case "prompt": + hlFile = await client.prompts.upsert(fileDict as PromptRequest); + break; + case "tool": + hlFile = await client.tools.upsert(fileDict as ToolRequest); + break; + case "evaluator": + hlFile = await client.evaluators.upsert(fileDict as EvaluatorRequest); + break; + default: + throw new Error(`Unsupported File type: ${type}`); + } + + return hlFile; +} + +async function upsertDataset({ + dataset, + client, +}: { + dataset: Dataset; + client: HumanloopClient; +}): Promise { + // Upsert the Dataset + if (!dataset.action) { + dataset.action = "set"; + } + if (!dataset.datapoints) { + dataset.datapoints = []; + // Use `upsert` to get existing dataset ID if no datapoints provided, given we can't `get` on path. + dataset.action = "add"; + } + const hlDataset = await client.datasets.upsert({ + ...dataset, + datapoints: dataset.datapoints || [], + }); + return await client.datasets.get(hlDataset.id, { + versionId: hlDataset.versionId, + includeDatapoints: true, + }); +} + +type _LocalEvaluator = { + hlEvaluator: EvaluatorResponse; + // @ts-ignore + function: (params: { + log: LogResponse; + datapoint?: DatapointResponse; + }) => Promise; +}; + +async function getNewRun({ + client, + evaluationName, + evaluators, + hlFile, + hlDataset, + func, +}: { + client: HumanloopClient; + evaluationName: string | undefined; + evaluators: Evaluator[]; + hlFile: PromptResponse | FlowResponse | ToolResponse | EvaluatorResponse; + hlDataset: DatasetResponse; + func: ((inputs: Record) => Promise) | undefined; +}): Promise<{ evaluation: EvaluationResponse; run: EvaluationRunResponse }> { + // Get or create the Evaluation based on the name + let evaluation: EvaluationResponse | null = null; + try { + evaluation = await client.evaluations.create({ + name: evaluationName, + evaluators: evaluators.map( + (evaluator) => + ({ + path: evaluator.path, + id: evaluator.id, + }) as CreateEvaluationRequestEvaluatorsItem, + ), + file: { id: hlFile.id }, + }); + } catch (error: any) { + // If the name exists, go and get it + // TODO: Update API GET to allow querying by name and file. + if (error.statusCode === 409) { + const evals = await client.evaluations.list({ + fileId: hlFile.id, + size: 50, + }); + for await (const e of evals) { + if (e.name === evaluationName) { + evaluation = e; + break; + } + } + } else { + throw error; + } + if (!evaluation) { + throw new Error(`Evaluation with name ${evaluationName} not found.`); + } + } + + // Create a new Run + const run: EvaluationRunResponse = await client.evaluations.createRun( + evaluation.id, + { + dataset: { versionId: hlDataset.versionId }, + version: { versionId: hlFile.versionId }, + orchestrated: func ? false : true, + useExistingLogs: false, + }, + ); + + return { evaluation, run }; +} + +async function upsertLocalEvaluators({ + evaluators, + callable, + type, + client, +}: { + evaluators: LocalEvaluator[]; + callable: File["callable"]; + type: FileType; + client: HumanloopClient; +}): Promise<_LocalEvaluator[]> { + // Upsert the local Evaluators; other Evaluators are just referenced by `path` or `id` + const localEvaluators: _LocalEvaluator[] = []; + if (evaluators) { + for (const evaluatorRequest of evaluators) { + // If a callable is provided for an Evaluator, we treat it as External + const evalFunction = evaluatorRequest.callable; + if (evalFunction !== undefined) { + // TODO: support the case where `file` logs generated on Humanloop but Evaluator logs generated locally + if (callable === undefined) { + throw new Error( + `Local Evaluators are only supported when generating Logs locally using your ${type}'s callable. Please provide a callable for your file in order to run Evaluators locally.`, + ); + } + const spec: ExternalEvaluatorRequest = { + argumentsType: evaluatorRequest.argsType, + returnType: evaluatorRequest.returnType as EvaluatorReturnTypeEnum, + attributes: { code: evalFunction.toString() }, + evaluatorType: "external", + }; + const evaluator = await client.evaluators.upsert({ + id: evaluatorRequest.id, + path: evaluatorRequest.path, + spec, + }); + localEvaluators.push({ + hlEvaluator: evaluator, + // @ts-ignore + function: evalFunction, + }); + } + } + } + return localEvaluators; +} + +function assertDatasetEvaluatorsFit( + hlDataset: DatasetResponse, + localEvaluators: _LocalEvaluator[], +): void { + // Validate upfront that the local Evaluators and Dataset fit + const requiresTarget = localEvaluators.some( + (localEvaluator) => + localEvaluator.hlEvaluator.spec.argumentsType === "target_required", + ); + + if (requiresTarget) { + const missingTargets = (hlDataset.datapoints || []).filter( + (datapoint) => !datapoint.target, + ).length; + + if (missingTargets > 0) { + throw new Error( + `${missingTargets} Datapoints have no target. A target is required for the Evaluator: ${ + localEvaluators.find( + (localEvaluator) => + localEvaluator.hlEvaluator.spec.argumentsType === + "target_required", + )?.hlEvaluator.path + }`, + ); + } + } +} + +/** + * Returns the appropriate log function pre-filled with common parameters. + * + * @param client - The HumanloopClient instance used to make API calls. + * @param type - The type of file for which the log function is being generated. Can be "flow", "prompt", or "tool". + * @param fileId - The ID of the file. + * @param versionId - The version ID of the file. + * @param runId - The run ID associated with the log. + * @returns A function that logs data to the appropriate endpoint based on the file type. + * @throws {Error} If the provided file type is unsupported. + */ +function getLogFunction( + client: HumanloopClient, + type: FileType, + fileId: string, + versionId: string, + runId: string, +) { + const logRequest = { + // TODO: why does the Log `id` field refer to the file ID in the API? + // Why are both `id` and `version_id` needed in the API? + id: fileId, + versionId, + runId, + }; + + switch (type) { + case "flow": + return async (args: FlowLogRequest) => + // @ts-ignore Using the original method instead of the overloaded one + await client.flows._log({ + ...logRequest, + logStatus: "complete", + ...args, + }); + case "prompt": + return async (args: PromptLogRequest) => + // @ts-ignore Using the original method instead of the overloaded one + await client.prompts._log({ ...logRequest, ...args }); + // case "evaluator": + // return (args: CreateEvaluatorLogRequest) => + // client.evaluators._log({ ...logRequest, ...args }); + case "tool": + return async (args: ToolLogRequest) => + // @ts-ignore Using the original method instead of the overloaded one + await client.tools._log({ ...logRequest, ...args }); + default: + throw new Error(`Unsupported File version: ${type}`); + } +} + +async function runLocalEvaluators( + client: HumanloopClient, + logId: string, + datapoint: DatapointResponse | undefined, + localEvaluators: _LocalEvaluator[], +) { + const log = await client.logs.get(logId); + + const promises = localEvaluators.map( + async ({ hlEvaluator, function: evalFunction }) => { + const startTime = new Date(); + let judgment: any | undefined; + try { + if (hlEvaluator.spec.argumentsType === "target_required") { + judgment = await evalFunction({ log, datapoint }); + } else { + judgment = await evalFunction({ log }); + } + + // @ts-ignore Using the original method instead of the overloaded one + await client.evaluators._log({ + path: hlEvaluator.path, + versionId: hlEvaluator.versionId, + parentId: logId, + judgment: judgment, + startTime: startTime, + endTime: new Date(), + }); + } catch (e) { + // @ts-ignore Using the original method instead of the overloaded one + await client.evaluators._log({ + path: hlEvaluator.path, + versionId: hlEvaluator.versionId, + parentId: logId, + error: e instanceof Error ? e.message : String(e), + startTime: startTime, + endTime: new Date(), + }); + console.warn(`\nEvaluator ${hlEvaluator.path} failed with error ${e}`); + } + }, + ); + + await Promise.all(promises); +} + +function checkEvaluationImprovement( + evaluation: EvaluationResponse, + evaluatorPath: string, + stats: EvaluationStats, + runId: string, +): [boolean, number, number] { + const runStats = stats.runStats.find((run) => run.runId === runId); + if (!runStats) { + throw new Error(`Run ${runId} not found in Evaluation ${evaluation.id}`); + } + const latestEvaluatorStatsByPath = getEvaluatorStatsByPath(runStats, evaluation); + if (stats.runStats.length == 1) { + console.log(`${YELLOW}⚠️ No previous versions to compare with.${RESET}`); + return [true, 0, 0]; + } + + // Latest Run is at index 0, previous Run is at index 1 + const previousEvaluatorStatsByPath = getEvaluatorStatsByPath( + stats.runStats[1], + evaluation, + ); + if ( + evaluatorPath in latestEvaluatorStatsByPath && + evaluatorPath in previousEvaluatorStatsByPath + ) { + const latestEvaluatorStats = latestEvaluatorStatsByPath[evaluatorPath]; + const previousEvaluatorStats = previousEvaluatorStatsByPath[evaluatorPath]; + const latestScore = getScoreFromEvaluatorStat(latestEvaluatorStats); + const previousScore = getScoreFromEvaluatorStat(previousEvaluatorStats); + if (latestScore === null || previousScore === null) { + throw new Error(`Could not find score for Evaluator ${evaluatorPath}`); + } + let diff = latestScore - previousScore; + // Round to 2 decimal places + diff = Math.round(diff * 100) / 100; + console.log( + `${CYAN}Change of [${diff}] for Evaluator ${evaluatorPath}${RESET}`, + ); + return [diff >= 0, latestScore, diff]; + } else { + throw Error(`Evaluator ${evaluatorPath} not found in the stats.`); + } +} + +function getScoreFromEvaluatorStat( + stat: NumericEvaluatorStatsResponse | BooleanEvaluatorStatsResponse, +): number | null { + let score: number | null = null; + if ("numTrue" in stat) { + score = + ((stat as BooleanEvaluatorStatsResponse).numTrue as number) / + (stat as BooleanEvaluatorStatsResponse).totalLogs; + // Round to 2 decimal places + score = Math.round(score * 100) / 100; + } else if ("mean" in stat && stat.mean !== undefined) { + // Round to 2 decimal places + score = Math.round(stat.mean * 100) / 100; + } else { + throw new Error(`Unexpected EvaluatorStats type: ${stat}`); + } + return score; +} + +function getEvaluatorStatsByPath( + stats: RunStatsResponse, + evaluation: EvaluationResponse, +): { [key: string]: NumericEvaluatorStatsResponse | BooleanEvaluatorStatsResponse } { + const evaluatorsById = {} as { + [key: string]: Humanloop.EvaluationEvaluatorResponse; + }; + for (const evaluator of evaluation.evaluators) { + evaluatorsById[evaluator.version.versionId] = evaluator; + } + const evaluatorStatsByPath = {} as { + [key: string]: NumericEvaluatorStatsResponse | BooleanEvaluatorStatsResponse; + }; + for (const evaluatorStats of stats.evaluatorStats) { + const evaluator = evaluatorsById[evaluatorStats.evaluatorVersionId]; + evaluatorStatsByPath[evaluator.version.path] = evaluatorStats as + | NumericEvaluatorStatsResponse + | BooleanEvaluatorStatsResponse; + } + return evaluatorStatsByPath; +} + +function checkEvaluationThreshold( + evaluation: EvaluationResponse, + stats: EvaluationStats, + evaluatorPath: string, + threshold: number, + runId: string, +) { + const runStats = stats.runStats.find((run) => run.runId === runId); + if (!runStats) { + throw new Error(`Run ${runId} not found in Evaluation ${evaluation.id}`); + } + const evaluatorStatsByPath = getEvaluatorStatsByPath(runStats, evaluation); + if (evaluatorPath in evaluatorStatsByPath) { + const evaluatorStats = evaluatorStatsByPath[evaluatorPath]; + const score = getScoreFromEvaluatorStat(evaluatorStats); + if (score === null) { + throw new Error(`Could not find score for Evaluator ${evaluatorPath}`); + } + if (score >= threshold) { + console.log( + `${GREEN}✅ Latest eval [${score}] above threshold [${threshold}] for Evaluator ${evaluatorPath}.${RESET}`, + ); + } else { + console.log( + `${RED}❌ Latest eval [${score}] below threshold [${threshold}] for Evaluator ${evaluatorPath}.${RESET}`, + ); + } + return score >= threshold; + } else { + throw new Error(`Evaluator ${evaluatorPath} not found in the stats.`); + } +} diff --git a/src/evals/types.ts b/src/evals/types.ts new file mode 100644 index 00000000..4ec676e4 --- /dev/null +++ b/src/evals/types.ts @@ -0,0 +1,133 @@ +import { + CodeEvaluatorRequest, + CreateDatapointRequest as DatapointRequest, + EvaluatorResponse, + EvaluatorReturnTypeEnum, + EvaluatorsRequest, + ExternalEvaluatorRequest, + FlowKernelRequest, + FlowRequest, + FlowResponse, + HumanEvaluatorRequest, + LlmEvaluatorRequest, + PromptKernelRequest, + PromptRequest, + PromptResponse, + ToolKernelRequest, + ToolRequest, + ToolResponse, + UpdateDatesetAction as UpdateDatasetAction, +} from "../api"; +import { + ChatMessage, + DatapointResponse, + FlowLogResponse, + PromptLogResponse, +} from "../api/types"; + +type EvaluatorVersion = + | LlmEvaluatorRequest + | HumanEvaluatorRequest + | CodeEvaluatorRequest + | ExternalEvaluatorRequest; +export type Version = + | FlowKernelRequest + | PromptKernelRequest + | ToolKernelRequest + | EvaluatorVersion; +export type FileRequest = FlowRequest | PromptRequest | ToolRequest | EvaluatorsRequest; +export type FileResponse = + | FlowResponse + | PromptResponse + | ToolResponse + | EvaluatorResponse; + +interface Identifiers { + id?: string; + path?: string; +} + +export interface File extends Identifiers { + /** The type of File this callable relates to on Humanloop. */ + type?: "flow" | "prompt"; + /** The contents uniquely define the version of the File on Humanloop. */ + version?: Version; + /** + * The function being evaluated. + * It will be called using your Dataset inputs as follows: + * `output = callable(datapoint.inputs)`. + * If messages are defined in your Dataset, then + * `output = callable(datapoint.inputs, messages=datapoint.messages)`. + */ + callable?: I extends Record & { messages?: ChatMessage[] } + ? (args: I) => O + : never; +} + +export interface Dataset extends Identifiers { + /** The datapoints to map your function over to produce the outputs required by the evaluation. */ + datapoints?: DatapointRequest[]; + /** + * How to update the Dataset given the provided Datapoints; + * `set` replaces the existing Datapoints and `add` appends to the existing Datapoints. + */ + action?: UpdateDatasetAction; +} + +export interface Evaluator extends Identifiers { + /**The threshold to check the Evaluator against. If the aggregate value of the Evaluator is below this threshold, the check will fail.*/ + threshold?: number; +} + +/** + * Subtype of EvaluatorReturnTypeEnum without return types used by Human Evaluators. + */ +export type LocalEvaluatorReturnTypeEnum = Omit< + EvaluatorReturnTypeEnum, + "select" | "multi_select" +>; + +export interface OnlineEvaluator extends Evaluator {} + +export interface LocalEvaluator extends Evaluator { + /** The type of return value the Evaluator produces */ + returnType: ReturnType; + /** The type of arguments the Evaluator expects. */ + argsType: ArgsType; + /** The function to run on the logs to produce the judgment. */ + callable: ( + inputs: { + log: PromptLogResponse | FlowLogResponse; + } & (ArgsType extends "target_required" + ? { datapoint: DatapointResponse } + : {}), + ) => ReturnType extends "boolean" + ? boolean + : ReturnType extends "number" + ? number + : ReturnType extends "text" + ? string + : never; +} + +/** + * Evaluate your function for a given `Dataset` and set of `Evaluators`. + * + * @param client - The Humanloop API client. + * @param file - The Humanloop file being evaluated, including a function to run over the dataset. + * @param dataset - The dataset to map your function over to produce the outputs required by the Evaluation. + * @param name - The name of the Evaluation to run. If it does not exist, a new Evaluation will be created under your File. + * @param evaluators - Define how judgments are provided for this Evaluation. + * @param workers - The number of threads to process datapoints using your function concurrently. + * @returns Per Evaluator checks. + */ +export interface EvaluatorCheck { + path: string; + // TODO: Add number valence and improvement check + // improvementCheck: boolean; + score: number; + delta: number; + threshold?: number; + thresholdCheck?: boolean; + evaluationId: string; +} diff --git a/src/humanloop.client.ts b/src/humanloop.client.ts index fb683938..6352b2ae 100644 --- a/src/humanloop.client.ts +++ b/src/humanloop.client.ts @@ -2,21 +2,33 @@ import { NodeTracerProvider, Tracer } from "@opentelemetry/sdk-trace-node"; import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic"; import { CohereInstrumentation } from "@traceloop/instrumentation-cohere"; import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai"; +import { Evaluators } from "api/resources/evaluators/client/Client"; import { HumanloopClient as BaseHumanloopClient } from "./Client"; +import { ChatMessage } from "./api"; import { Evaluations as BaseEvaluations } from "./api/resources/evaluations/client/Client"; import { Flows } from "./api/resources/flows/client/Client"; import { Prompts } from "./api/resources/prompts/client/Client"; -import { FlowKernelRequest } from "./api/types/FlowKernelRequest"; +import { Tools } from "./api/resources/tools/client/Client"; import { ToolKernelRequest } from "./api/types/ToolKernelRequest"; -import { overloadLog, runEval } from "./eval_utils/run"; -import { Dataset, Evaluator, EvaluatorCheck, File } from "./eval_utils/types"; +import { flowUtilityFactory } from "./decorators/flow"; +import { promptDecoratorFactory } from "./decorators/prompt"; +import { toolUtilityFactory } from "./decorators/tool"; +import { HumanloopRuntimeError } from "./error"; +import { runEval } from "./evals/run"; +import { + Dataset, + EvaluatorCheck, + File, + LocalEvaluator, + OnlineEvaluator, +} from "./evals/types"; import { HumanloopSpanExporter } from "./otel/exporter"; import { HumanloopSpanProcessor } from "./otel/processor"; -import { flowUtilityFactory } from "./utilities/flow"; -import { UtilityPromptKernel, promptUtilityFactory } from "./utilities/prompt"; -import { toolUtilityFactory } from "./utilities/tool"; -import { InputsMessagesCallableType, ToolCallableType } from "./utilities/types"; +import { overloadCall, overloadLog } from "./overload"; + +const RED = "\x1b[91m"; +const RESET = "\x1b[0m"; class ExtendedEvaluations extends BaseEvaluations { protected readonly _client: HumanloopClient; @@ -82,20 +94,34 @@ class ExtendedEvaluations extends BaseEvaluations { * @param evaluators - List of evaluators to be. Can be ran on Humanloop if specified only by path, or locally if a callable is provided. * @param concurrency - Number of datapoints to process in parallel. */ - async run({ + async run({ file, dataset, name, evaluators = [], concurrency = 8, }: { - file: File; + file: File; dataset: Dataset; name?: string; - evaluators: Evaluator[]; + evaluators: ( + | OnlineEvaluator + | { + [R in "text" | "boolean" | "number"]: { + [A in "target_required" | "target_free"]: LocalEvaluator; + }["target_required" | "target_free"]; + }["text" | "boolean" | "number"] + )[]; concurrency?: number; }): Promise { - return runEval(this._client, file, dataset, name, evaluators, concurrency); + return runEval( + this._client, + file, + dataset, + name, + evaluators, + concurrency, + ); } } @@ -103,6 +129,8 @@ export class HumanloopClient extends BaseHumanloopClient { protected readonly _evaluations: ExtendedEvaluations; protected readonly _prompts_overloaded: Prompts; protected readonly _flows_overloaded: Flows; + protected readonly _tools_overloaded: Tools; + protected readonly _evaluators_overloaded: Evaluators; protected readonly OpenAI?: any; protected readonly Anthropic?: any; @@ -115,11 +143,24 @@ export class HumanloopClient extends BaseHumanloopClient { * Constructs a new instance of the Humanloop client. * * @param _options - The base options for the Humanloop client. - * @param providerModules - LLM provider modules to instrument. Pass all LLM providers that you will use in your program if you intend to use File utilities such as `prompt()`. + * @param providerModules - LLM provider modules to instrument. Allows prompt decorator to spy on LLM provider calls and log them to Humanloop. + * + * Pass LLM provider modules as such: + * + * ```typescript + * import { OpenAI } from "openai"; + * import { Anthropic } from "anthropic"; + * import { HumanloopClient } from "humanloop"; + * + * const humanloop = new HumanloopClient({apiKey: process.env.HUMANLOOP_KEY}, { OpenAI, Anthropic }); + * + * const openai = new OpenAI({apiKey: process.env.OPENAI_KEY}); + * const anthropic = new Anthropic({apiKey: process.env.ANTHROPIC_KEY}); + * ``` */ constructor( _options: BaseHumanloopClient.Options, - providerModules?: { + providers?: { OpenAI?: any; Anthropic?: any; CohereAI?: any; @@ -127,14 +168,21 @@ export class HumanloopClient extends BaseHumanloopClient { ) { super(_options); - this.OpenAI = providerModules?.OpenAI; - this.Anthropic = providerModules?.Anthropic; - this.CohereAI = providerModules?.CohereAI; + const { OpenAI, Anthropic, CohereAI } = providers ?? {}; + + this.OpenAI = OpenAI; + this.Anthropic = Anthropic; + this.CohereAI = CohereAI; this._prompts_overloaded = overloadLog(super.prompts); + this._prompts_overloaded = overloadCall(this._prompts_overloaded); + + this._tools_overloaded = overloadLog(super.tools); this._flows_overloaded = overloadLog(super.flows); + this._evaluators_overloaded = overloadLog(super.evaluators); + this._evaluations = new ExtendedEvaluations(_options, this); this.opentelemetryTracerProvider = new NodeTracerProvider({ @@ -143,25 +191,25 @@ export class HumanloopClient extends BaseHumanloopClient { ], }); - if (providerModules?.OpenAI) { + if (OpenAI) { const instrumentor = new OpenAIInstrumentation({ enrichTokens: true, }); - instrumentor.manuallyInstrument(providerModules?.OpenAI); + instrumentor.manuallyInstrument(OpenAI); instrumentor.setTracerProvider(this.opentelemetryTracerProvider); instrumentor.enable(); } - if (providerModules?.Anthropic) { + if (Anthropic) { const instrumentor = new AnthropicInstrumentation(); - instrumentor.manuallyInstrument(providerModules?.Anthropic); + instrumentor.manuallyInstrument(Anthropic); instrumentor.setTracerProvider(this.opentelemetryTracerProvider); instrumentor.enable(); } - if (providerModules?.CohereAI) { + if (CohereAI) { const instrumentor = new CohereInstrumentation(); - instrumentor.manuallyInstrument(providerModules?.CohereAI); + instrumentor.manuallyInstrument(CohereAI); instrumentor.setTracerProvider(this.opentelemetryTracerProvider); instrumentor.enable(); } @@ -172,6 +220,10 @@ export class HumanloopClient extends BaseHumanloopClient { this.opentelemetryTracerProvider.getTracer("humanloop.sdk"); } + public options(): BaseHumanloopClient.Options { + return this._options; + } + // Check if user has passed the LLM provider instrumentors private assertProviders(func: Function) { const noProviderInstrumented = [ @@ -180,292 +232,289 @@ export class HumanloopClient extends BaseHumanloopClient { this.CohereAI, ].every((p) => !p); if (noProviderInstrumented) { - throw new Error( - `${func.name}: To use the 'prompt()' utility, pass your LLM client library into the Humanloop client constructor; e.g. 'HumanloopClient(..., { providerModules: {OpenAI} } )'.`, + throw new HumanloopRuntimeError( + `${RED}To use the @prompt decorator, pass your LLM client library into the Humanloop client constructor. For example:\n\n +import { OpenAI } from "openai"; +import { HumanloopClient } from "humanloop"; + +const humanloop = new HumanloopClient({apiKey: process.env.HUMANLOOP_KEY}, { OpenAI }); +// Create the the OpenAI client after the client is initialized +const openai = new OpenAI(); +${RESET}`, ); } } /** - * Utility for managing a [Prompt](https://humanloop.com/docs/explanation/prompts) in code. + * Auto-instrument LLM provider calls and create [Prompt](https://humanloop.com/docs/explanation/prompts) + * Logs on Humanloop from them. * - * The utility intercepts calls to the LLM provider APIs and creates a new Prompt - * file based on the hyperparameters used in the call. If a hyperparameter in - * promptKernel parameter, then they override any value intercepted from the - * LLM provider call. + * ```typescript + * import { OpenAI } from "openai"; + * import { HumanloopClient } from "humanloop"; + * + * const humanloop = new HumanloopClient({apiKey: process.env.HUMANLOOP_KEY}, { OpenAI }); + * const openai = new OpenAI({apiKey: process.env.OPENAI_KEY}); + * + * const callOpenaiWithHumanloop = humanloop.prompt({ + * path: "Chat Bot", + * callable: (args: { + * messages: ChatMessage[] + * }) => { + * const response = await openai.chat.completions.create({ + * model: "gpt-4o", + * temperature: 0.8, + * frequency_penalty: 0.5, + * max_tokens: 200, + * messages: args.messages, + * }); + * return response.choices[0].message.content; + * }, + * }); * - * If the [Prompt](https://humanloop.com/docs/explanation/prompts) already exists - * on the specified path, a new version will be upserted when any of the above change. + * const answer = await callOpenaiWithHumanloop({ + * messages: [{ role: "user", content: "What is the capital of the moon?" }], + * }); * + * // Calling the function above creates a new Log on Humanloop + * // against this Prompt version: + * { + * provider: "openai", + * model: "gpt-4o", + * endpoint: "chat", + * max_tokens: 200, + * temperature: 0.8, + * frequency_penalty: 0.5, + * } + * ``` * - * Here's an example of declaring a [Prompt](https://humanloop.com/docs/explanation/prompts) in code: + * If a different model, endpoint, or hyperparameter is used, a new + * Prompt version is created. For example: * * ```typescript - * const openAIClient = new HumanloopClient({ - * apiKey: process.env.HUMANLOOP_API_KEY || "" - * }) - * - * const callModel = prompt({ - * callable: async (inputs, messages) => { - * output = await openAIClient.chat.completions.create({ - * model: inputs.model, - * temperature: inputs.temperature, - * messages: messages, - * frequencyPenalty: 1, - * presencePenalty: 1 - * }) - * return output.choices[0].message.content || ""; - * }, - * path: "Project/Call LLM" + * humanloopClient.prompt({ + * path: "My Prompt", + * callable: async (messages: ChatMessage[]) => { + * const openaiClient = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + * const openaiResponse = await openaiClient.chat.completions.create({ + * model: "gpt-4o-mini", + * temperature: 0.5, + * }); + * const openaiContent = openaiResponse.choices[0].message.content; + * + * const anthropicClient = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); + * const anthropicResponse = await anthropicClient.messages.create({ + * model: "claude-3-5-sonnet-20240620", + * temperature: 0.5, + * }); + * const anthropicContent = anthropicResponse.content; + * + * return { openaiContent, anthropicContent }; + * } * }); * - * await callModel( - * {model: 'gpt-4o-mini', temperature: 0.7}, - * [{ - * "role": "system", - * "content": "You are a helpful assistant" - * }, - * { - * "role": "user", - * "content": "Hi how are you?" - * } - * ], - * ); - * ``` - * - * The `callable` argument is expected to have the signature `(inputs, messages) => string | JSON-serializable`. - * Consider wrapping the function returned by the utility in an arrow function - * - * The utility expects the `callable` function to make a call to one of the supported - * LLM provider APIs through official client libraries. Alternatively, provide a custom - * `model` override in the `promptKernel` argument or the logging will fail with an error. + * Calling this function will create two versions of the same Prompt: + * { + * provider: "openai", + * model: "gpt-4o-mini", + * endpoint: "chat", + * max_tokens: 200, + * temperature: 0.5, + * frequency_penalty: 0.5, + * } * - * The decorated function should return a string or the output should be JSON serializable. If - * the output cannot be serialized, TypeError will be raised. + * { + * provider: "anthropic", + * model: "claude-3-5-sonnet-20240620", + * endpoint: "messages", + * temperature: 0.5, + * } * - * If the function raises an exception, the log created by the function will have the output - * field set to `null` and the error field set to the string representation of the exception. + * And one Log will be added to each version of the Prompt. + * ``` * - * @template I - The type of `inputs` argument of the callable. - * @template M - The type of `message` argument of the callable. - * @template O - The output type of the callable. Should be string or serializable to JSON. - * @param {Object} params - The arguments for the prompt utility. - * @param {InputsMessagesCallableType} params.callable - The callable the utility should wrap and instrument. - * @param {string} params.path - The path of the instrumented Prompt on Humanloop. - * @param {UtilityPromptKernel} [params.promptKernel] - Optional override values for the Prompt, overriding inferences made by the utility from provider call - * @returns An async function that wraps around the provided `callable`, adding Prompt instrumentation + * @param callable - The callable to wrap. + * @param path - The path to the Prompt. */ - public prompt({ - callable, - path, - version, - }: { - callable: InputsMessagesCallableType; + public prompt(args: { + callable: I extends Record & { messages?: ChatMessage[] } + ? (args: I) => O + : () => O; path: string; - version?: UtilityPromptKernel; - }) { - this.assertProviders(callable); - return promptUtilityFactory(this.opentelemetryTracer, callable, path, version); + }): I extends Record + ? ( + args: I, + ) => O extends Promise + ? Promise + : Promise + : () => O extends Promise + ? Promise + : Promise { + this.assertProviders(args.callable); + // @ts-ignore + return promptDecoratorFactory(args.path, args.callable); } /** - * Utility for managing a [Tool](https://humanloop.com/docs/explanation/tools) in code. - * - * If the [Tool](https://humanloop.com/docs/explanation/tools) already exists - * on the specified path, a new version will be upserted when the `toolKernel` - * utility argument or the source code of the callable changes. - * - * Here's an example of declaring a [Tool](https://humanloop.com/docs/explanation/tools) in code: - * - * ```typescript - * const calculator = tool({ - * callable: ({operation, num1, num2}: { - * operation: string, - * num1: number, - * num2: number - * }) => { - * switch (operation) { - * case "add": - * return num1 + num2; - * case "subtract": - * return num1 - num2; - * case "multiply": - * return num1 * num2; - * case "divide": - * if (num2 === 0) { - * throw new Error("Cannot divide by zero"); - * } - * return num1 / num2; - * default: - * throw new Error("Cannot divide by zero")l - * } - * }, - * toolKernel: { - * name: "calculator", - * description: "Perform arithmetic operations on two numbers", - * strict: true, - * parameters: { - * type: "object", - * properties: { - * operation: { - * type: "string", - * description: "The operation to perform", - * enum: ["add", "subtract", "multiply", "divide"], - * }, - * num1: { - * type: "number", - * description: "The first number", - * }, - * num2: { - * type: "number", - * description: "The second number", - * } - * }, - * required: ["operation", "num1", "num2"], - * additionalProperties: false, - * } - * }, - * path: "Project/Calculator" - * }) - * ``` + * Auto-instrument LLM provider calls and create [Tool](https://humanloop.com/docs/explanation/tools) + * Logs on Humanloop from them. * - * Every call to the decorated function will create a Log against the Tool. For example: + * You must provide a `version` argument specifying the JSON Schema of the Tool's inputs and outputs, + * along with a callable that accepts the inputs and returns the outputs. * * ```typescript - * await calculator({num1: 1, num2: 2}) - * ``` - * - * Will create the following Log: * - * ```typescript - * { - * inputs: { - * num1: 1, - * num2: 2 - * }, - * output: 3 - * } + * const calculator = humanloop_client.tool({ + * callable: (inputs: { a: number; b: number }) => inputs.a + inputs.b, + * path: "Andrei QA/SDK TS/Calculator", + * version: { + * function: { + * name: "calculator", + * description: "Add two numbers", + * parameters: { + * type: "object", + * properties: { + * a: { type: "number", required: true }, + * b: { type: "number", required: true }, + * }, + * }, + * }, + * }, + * }); * ``` * - * The `callable` argument is expected to have the signature `(inputs) => string | JSON-serializable`. - * - * The returned callable has a `jsonAttribute` attribute that can be used for function calling. - * - * - * @template I - The type of `inputs` argument of the callable. - * @template M - The type of `message` argument of the callable. - * @template O - The output type of the callable. Should be string or serializable to JSON. - * @param {Object} params - The arguments for the flow utility. - * @param {InputsMessagesCallableType} params.callable - The callable the utility should wrap and instrument. - * @param {string} params.path - The path of the instrumented Tool on Humanloop. - * @param {ToolKernelRequest} [params.version] - Optional override values for the Prompt, overriding inferences made by the utility from provider call. - * @returns An async function that wraps around the provided `callable`, adding Tool instrumentation. + * @param callable - The callable to wrap. + * @param path - The path to the Tool. + * @param version - The JSON Schema of the Tool's inputs and outputs, plus the optional Humanloop fields `attributes and `setupValues`. See API reference for details. */ - public tool({ - callable, - path, - version, - }: { - callable: ToolCallableType; + public tool(args: { + callable: I extends Record ? (args: I) => O : () => O; path: string; version: ToolKernelRequest; - }) { - return toolUtilityFactory(this.opentelemetryTracer, callable, version, path); + }): I extends Record + ? ( + args: I, + ) => O extends Promise + ? Promise + : Promise + : () => O extends Promise + ? Promise + : Promise { + // @ts-ignore + return toolUtilityFactory( + this.opentelemetryTracer, + args.callable, + args.version, + args.path, + ); } /** - * Utility for managing a [Flow](https://humanloop.com/docs/explanation/flows) in code. - * - * A [Flow](https://humanloop.com/docs/explanation/flows) callable should be added - * at the entrypoint of your LLM feature. Call other functions wrapped in Humanloop - * utilities to create a trace of Logs on Humanloop. + * Trace SDK logging calls through [Flows](https://humanloop.com/docs/explanation/flows). * - * If the [Flow](https://humanloop.com/docs/explanation/flows) already exists - * on the specified path, a new version will be upserted when the `flowUtility` - * argument changes. + * Use it as the entrypoint of your LLM feature. Logging calls like `prompts.call(...)`, + * `tools.call(...)`, or other Humanloop decorators will be automatically added to the trace. * - * Here's an example of declaring a [Flow](https://humanloop.com/docs/explanation/flows) in code: + * Example: * * ```typescript - * const callModel = humanloop.prompt({ - * callable: async (inputs, messages) => { - * output = await openAIClient.chat.completions.create({ - * model: inputs.model, - * temperature: inputs.temperature, - * messages: messages, - * frequencyPenalty: 1, - * presencePenalty: 1 - * }) - * return output.choices[0].message.content || ""; - * }, - * path: "Project/Call LLM" - * }); + * const callLLM = humanloop_client.prompt({ + * path: "My Prompt", + * callable: (messages: ChatMessage[]) => { + * const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + * return client.chat.completions.create({ + * model: "gpt-4o", + * temperature: 0.8, + * frequency_penalty: 0.5, + * max_tokens: 200, + * messages: messages, + * }).choices[0].message.content; + * } * - * // Pass `undefined` to unused inputs and messages parameters - * const entrypoint = () => humanloop.flow({ - * callable: async (inputs: any, messages: any) => { + * const agent = humanloop_client.flow({ + * callable: () => { * while (true) { - * const messages = [] - * // I/O operation - * const userInput = read_input("You: ") + * const messages: ChatMessage[] = []; + * const userInput = prompt("You: "); * if (userInput === "exit") { * break; * } - * messages.push({"role": "user", "content": userInput}) - * const response = await callLLM(messages) - * messages.append({"role": "assistant", "content": response}) - * console.log(f`Assistant: ${response}`) + * messages.push({ role: "user", content: userInput }); + * const response = callLLM(messages); + * messages.push({ role: "assistant", content: response }); + * console.log(`Assistant: ${response}`); * } - * } - * })(undefined, undefined) - * await entrypoint() + * }, + * path: "My Flow", + * attributes: { version: "v1" }, + * }); + * * ``` * - * In this example, the Flow instruments a conversational agent where the - * Prompt defined in `callModel` is called multiple times in a loop. Calling - * `entrypoint` will create a Flow Trace under which multiple Prompt Logs - * will be nested, allowing you to track the whole conversation session - * between the user and the assistant. + * Each call to `agent` will create a trace corresponding to the conversation + * session. Multiple Prompt Logs will be created as the LLM is called. They + * will be added to the trace, allowing you to see the whole conversation + * in the UI. * - * The decorated function should return a string or the output should be JSON serializable. If - * the output cannot be serialized, TypeError will be raised. + * If the function returns a ChatMessage-like object, the Log will + * populate the `outputMessage` field. Otherwise, it will serialize + * the return value and populate the `output` field. * - * If the function raises an exception, the log created by the function will have the `output` - * field set to None and the `error` field set to the string representation of the exception. + * If an exception is raised, the output fields will be set to None + * and the error message will be set in the Log's `error` field. * + * @param path - The path to the Flow. If not provided, the function name + * will be used as the path and the File will be created in the root + * of your organization workspace. * - * @template I - The type of `inputs` argument of the callable. - * @template M - The type of `message` argument of the callable. - * @template O - The output type of the callable. Should be string or serializable to JSON. - * @param {Object} params - The arguments for the prompt utility. - * @param {InputsMessagesCallableType} params.callable - The callable the utility should wrap and instrument. - * @param {string} params.path - The path of the instrumented Flow on Humanloop. - * @param {FlowKernelRequest} [params.flowKernel] - Versioning information for the Flow. - * @returns An async function that wraps around the provided `callable`, adding Tool instrumentation. + * @param attributes - Additional fields to describe the Flow. Helpful to separate Flow versions from each other with details on how they were created or used. */ - public flow({ + public flow({ callable, path, - version, + attributes, }: { - callable: InputsMessagesCallableType; + callable: I extends Record & { messages?: ChatMessage[] } + ? ((args: I) => O) | (() => O) + : never; path: string; - version?: FlowKernelRequest; - }) { - return flowUtilityFactory(this.opentelemetryTracer, callable, path, version); + attributes?: Record; + }): I extends Record & { messages?: ChatMessage[] } + ? ( + args: I, + ) => O extends Promise + ? Promise + : Promise + : () => O extends Promise + ? Promise + : Promise { + // @ts-ignore + return flowUtilityFactory( + this, + this.opentelemetryTracer, + callable, + path, + attributes, + ); } public get evaluations(): ExtendedEvaluations { return this._evaluations; } - // @ts-ignore public get prompts(): Prompts { return this._prompts_overloaded; } - // @ts-ignore public get flows(): Flows { return this._flows_overloaded; } + + public get tools(): Tools { + return this._tools_overloaded; + } + + public get evaluators(): Evaluators { + return this._evaluators_overloaded; + } } diff --git a/src/otel/constants.ts b/src/otel/constants.ts index baa463ca..1b71b336 100644 --- a/src/otel/constants.ts +++ b/src/otel/constants.ts @@ -2,12 +2,15 @@ export const HUMANLOOP_FILE_KEY = "humanloop.file"; // Attribute name prefix on Humanloop spans for log-related attributes export const HUMANLOOP_LOG_KEY = "humanloop.log"; -export const HUMANLOOP_FILE_TYPE_KEY = "humanloop.file_type"; +export const HUMANLOOP_FILE_TYPE_KEY = "humanloop.file.type"; export const HUMANLOOP_PATH_KEY = "humanloop.file.path"; -export const HUMANLOOP_META_FUNCTION_NAME = "humanloop.meta.function_name"; export const HUMANLOOP_FLOW_PREREQUISITES_KEY = "humanloop.flow.prerequisites"; export const HUMANLOOP_SPAN_PREFIX = "humanloop."; export const HUMANLOOP_FLOW_SPAN_NAME = `${HUMANLOOP_SPAN_PREFIX}flow`; export const HUMANLOOP_PROMPT_SPAN_NAME = `${HUMANLOOP_SPAN_PREFIX}prompt`; export const HUMANLOOP_TOOL_SPAN_NAME = `${HUMANLOOP_SPAN_PREFIX}tool`; +export const HUMANLOOP_CONTEXT_TRACE_ID = "humanloop.context.flow.trace_id"; +export const HUMANLOOP_CONTEXT_EVALUATION = "humanloop.context.evaluation"; +export const HUMANLOOP_CONTEXT_PROMPT = "humanloop.context.prompt"; +export const HUMANLOOP_CONTEXT_DECORATOR = "humanloop.context.decorator"; diff --git a/src/otel/exporter.ts b/src/otel/exporter.ts index 304897a6..0aedc20d 100644 --- a/src/otel/exporter.ts +++ b/src/otel/exporter.ts @@ -1,51 +1,38 @@ import { ExportResult, ExportResultCode } from "@opentelemetry/core"; import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base"; -import { FlowKernelRequest, PromptKernelRequest, ToolKernelRequest } from "../api"; +import { HumanloopRuntimeError } from "../error"; +import { getEvaluationContext } from "../evals"; import { HumanloopClient } from "../humanloop.client"; +import { SDK_VERSION } from "../version"; import { - HUMANLOOP_FILE_KEY, HUMANLOOP_FILE_TYPE_KEY, - HUMANLOOP_FLOW_PREREQUISITES_KEY, HUMANLOOP_LOG_KEY, HUMANLOOP_PATH_KEY, } from "./constants"; -import { isHumanloopSpan, readFromOpenTelemetrySpan } from "./helpers"; - -/** - * Converts a high-resolution time tuple to a JavaScript Date object. - * - * @param hrTime - A tuple containing the high-resolution time, where the first element is the number of seconds - * and the second element is the number of nanoseconds. - * @returns A Date object representing the high-resolution time. - */ -function hrTimeToDate(hrTime: [number, number]): Date { - const [seconds, nanoseconds] = hrTime; - const secondsTotal = seconds + nanoseconds / 1e9; - return new Date(secondsTotal * 1000); -} +import { + isLLMProviderCall, + readFromOpenTelemetrySpan, + writeToOpenTelemetrySpan, +} from "./helpers"; +import { TracesData } from "./proto/trace"; export class HumanloopSpanExporter implements SpanExporter { private readonly client: HumanloopClient; - private readonly spanIdToUploadedLogId: Map; private shutdownFlag: boolean; private readonly uploadPromises: Promise[]; - private readonly exportedSpans: ReadableSpan[]; - // List of spans that must be uploaded before completing the Flow log - // This maps [flow log span ID] -> [set of child span IDs] - private readonly prerequisites: Map>; constructor(client: HumanloopClient) { this.client = client; - this.spanIdToUploadedLogId = new Map(); this.shutdownFlag = false; this.uploadPromises = []; - this.exportedSpans = []; - this.prerequisites = new Map(); } export(spans: ReadableSpan[]): ExportResult { if (this.shutdownFlag) { + console.warn( + "[HumanloopSpanExporter] Shutting down, not accepting new spans", + ); return { code: ExportResultCode.FAILED, error: new Error("Exporter is shutting down"), @@ -53,16 +40,38 @@ export class HumanloopSpanExporter implements SpanExporter { } for (const span of spans) { - if (isHumanloopSpan(span)) { - this.uploadPromises.push(this.exportSpanDispatch(span)); + const fileType = span.attributes[HUMANLOOP_FILE_TYPE_KEY]; + if (!fileType) { + throw new Error("Internal error: Span does not have type set"); } - } - this.exportedSpans.push(...spans); + let logArgs = {}; + let evalCallback: ((log_id: string) => Promise) | null = null; + try { + logArgs = readFromOpenTelemetrySpan(span, HUMANLOOP_LOG_KEY); + const path = readFromOpenTelemetrySpan( + span, + HUMANLOOP_PATH_KEY, + ) as unknown as string; + const evaluationContext = getEvaluationContext(); + if (evaluationContext) { + [logArgs, evalCallback] = evaluationContext.logArgsWithContext({ + logArgs, + forOtel: true, + path, + }); + writeToOpenTelemetrySpan(span, logArgs, HUMANLOOP_LOG_KEY); + } + } catch (e) { + if (!(e instanceof Error)) { + evalCallback = null; + } + } - return { - code: ExportResultCode.SUCCESS, - }; + this.uploadPromises.push(this.exportSpan(span, evalCallback)); + } + + return { code: ExportResultCode.SUCCESS }; } async shutdown(): Promise { @@ -74,165 +83,105 @@ export class HumanloopSpanExporter implements SpanExporter { await this.shutdown(); } - /** - * Mark a span as uploaded to the Humanloop. - * - * A Log might be contained inside a Flow trace, which must be marked as complete - * when all its children are uploaded. Each Flow Log span contains a - * 'humanloop.flow.prerequisites' attribute, which is a list of all spans that must - * be uploaded before the Flow Log is marked as complete. - * - * This method finds the trace the Span belongs to and removes the Span from the list. - * Once all prerequisites are uploaded, the method marks the Flow Log as complete. - * - * @param spanId - The ID of the span that has been uploaded. - */ - private markSpanCompleted(spanId: string) { - for (const [flowLogSpanId, flowChildrenSpanIds] of this.prerequisites) { - if (flowChildrenSpanIds.has(spanId)) { - flowChildrenSpanIds.delete(spanId); - if (flowChildrenSpanIds.size === 0) { - const flowLogId = this.spanIdToUploadedLogId.get(flowLogSpanId)!; - this.client.flows.updateLog(flowLogId, { logStatus: "complete" }); - } - break; - } - } - } - - private async exportSpanDispatch(span: ReadableSpan): Promise { - const fileType = span.attributes[HUMANLOOP_FILE_TYPE_KEY]; - const parentSpanId = span.parentSpanId; - - while (parentSpanId && !this.spanIdToUploadedLogId.has(parentSpanId)) { - await new Promise((resolve) => setTimeout(resolve, 100)); + private async exportSpan( + span: ReadableSpan, + evalContextCallback: ((log_id: string) => Promise) | null, + ): Promise { + const response = await fetch( + `${this.client.options().baseUrl}/import/otel/v1/traces`, + { + method: "POST", + headers: { + "X-API-KEY": this.client.options().apiKey!.toString(), + "X-Fern-Language": "Typescript", + "X-Fern-SDK-Name": "humanloop", + "X-Fern-SDK-Version": SDK_VERSION, + }, + body: JSON.stringify(this.spanToPayload(span)), + }, + ); + + if (response.status !== 200) { + throw new HumanloopRuntimeError( + `Failed to upload OTEL span to Humanloop: ${JSON.stringify(await response.json())} ${response.status}`, + ); } - - try { - switch (fileType) { - case "prompt": - await this.exportPrompt(span); - break; - case "tool": - await this.exportTool(span); - break; - case "flow": - await this.exportFlow(span); - break; - default: - throw new Error(`Unknown span type: ${fileType}`); - } - } catch (error) { - console.error(`Failed to export span: ${error}`); + if (response.status === 200 && evalContextCallback) { + const responseBody = await response.json(); + const logId = responseBody.records[0]; + await evalContextCallback(logId); } } - public getExportedSpans(): ReadableSpan[] { - return this.exportedSpans; - } - - private async exportPrompt(span: ReadableSpan): Promise { - const fileObject = readFromOpenTelemetrySpan(span, HUMANLOOP_FILE_KEY); - const logObject = readFromOpenTelemetrySpan(span, HUMANLOOP_LOG_KEY) as { - [key: string]: unknown; - }; - logObject.startTime = hrTimeToDate(span.startTime); - logObject.endTime = hrTimeToDate(span.endTime); - logObject.createdAt = hrTimeToDate(span.endTime); - const path = span.attributes[HUMANLOOP_PATH_KEY] as string; - - const spanParentId = span.parentSpanId; - const traceParentId = - spanParentId !== undefined - ? (this.spanIdToUploadedLogId.get(spanParentId) as string) - : undefined; - - const prompt: PromptKernelRequest = (fileObject.prompt || - {}) as unknown as PromptKernelRequest; - - try { - const response = await this.client.prompts.log({ - path: path, - prompt, - traceParentId, - ...logObject, - }); - this.spanIdToUploadedLogId.set(span.spanContext().spanId, response.id); - } catch (error) { - console.error(`Error exporting prompt: ${error}`); - } - this.markSpanCompleted(span.spanContext().spanId); - } - - private async exportTool(span: ReadableSpan): Promise { - const fileObject = readFromOpenTelemetrySpan(span, HUMANLOOP_FILE_KEY); - const logObject = readFromOpenTelemetrySpan(span, HUMANLOOP_LOG_KEY) as { - [key: string]: unknown; + private spanToPayload(span: ReadableSpan): TracesData { + return { + resourceSpans: [ + { + scopeSpans: [ + { + scope: { + name: isLLMProviderCall(span) + ? "humanloop.sdk.provider" + : "humanloop.sdk.decorator", + }, + spans: [ + { + traceId: span.spanContext().traceId, + spanId: span.spanContext().spanId, + traceState: + span.spanContext().traceState?.serialize() || + "", + name: span.name, + kind: span.kind, + startTimeUnixNano: this.hrTimeToNanoseconds( + span.startTime, + ), + endTimeUnixNano: this.hrTimeToNanoseconds( + span.endTime, + ), + attributes: Object.entries(span.attributes) + .filter(([_, value]) => value !== undefined) + .map(([key, value]) => ({ + key, + value: { stringValue: value!.toString() }, + })), + droppedAttributesCount: span.droppedAttributesCount, + events: [], + links: span.links.map((link) => ({ + traceId: link.context.traceId, + spanId: link.context.spanId, + traceState: + link.context.traceState?.serialize() || "", + attributes: link.attributes + ? Object.entries(link.attributes) + .filter( + ([_, value]) => + value !== undefined, + ) + .map(([key, value]) => ({ + key, + value: { + stringValue: + value!.toString(), + }, + })) + : [], + droppedAttributesCount: + link.droppedAttributesCount || 0, + })), + droppedEventsCount: span.droppedEventsCount, + droppedLinksCount: span.droppedLinksCount, + }, + ], + }, + ], + }, + ], }; - logObject.startTime = hrTimeToDate(span.startTime); - logObject.endTime = hrTimeToDate(span.endTime); - logObject.createdAt = hrTimeToDate(span.endTime); - const path = span.attributes[HUMANLOOP_PATH_KEY] as string; - - const spanParentId = span.parentSpanId; - const traceParentId = spanParentId - ? (this.spanIdToUploadedLogId.get(spanParentId) as string) - : undefined; - - try { - const response = await this.client.tools.log({ - path: path, - tool: fileObject.tool as ToolKernelRequest, - traceParentId, - ...logObject, - }); - this.spanIdToUploadedLogId.set(span.spanContext().spanId, response.id); - } catch (error) { - console.error(`Error exporting tool: ${error}`); - } - this.markSpanCompleted(span.spanContext().spanId); } - private async exportFlow(span: ReadableSpan): Promise { - const fileObject = readFromOpenTelemetrySpan(span, HUMANLOOP_FILE_KEY); - const logObject = readFromOpenTelemetrySpan(span, HUMANLOOP_LOG_KEY) as { - [key: string]: unknown; - }; - logObject.startTime = hrTimeToDate(span.startTime); - logObject.endTime = hrTimeToDate(span.endTime); - logObject.createdAt = hrTimeToDate(span.endTime); - // Spans that must be uploaded before the Flow Span is completed - let prerequisites: string[] | undefined = undefined; - try { - prerequisites = readFromOpenTelemetrySpan( - span, - HUMANLOOP_FLOW_PREREQUISITES_KEY, - ) as unknown as string[]; - } catch (error) { - prerequisites = []; - } - - this.prerequisites.set(span.spanContext().spanId, new Set(prerequisites)); - - const spanParentId = span.parentSpanId; - const traceParentId = spanParentId - ? (this.spanIdToUploadedLogId.get(spanParentId) as string) - : undefined; - const path = span.attributes[HUMANLOOP_PATH_KEY] as string; - - try { - const response = await this.client.flows.log({ - path: path as string, - flow: (fileObject.flow as unknown as FlowKernelRequest) || { - attributes: {}, - }, - traceParentId, - ...logObject, - }); - this.spanIdToUploadedLogId.set(span.spanContext().spanId, response.id); - } catch (error) { - console.error("Error exporting flow: ", error, span.spanContext().spanId); - } - this.markSpanCompleted(span.spanContext().spanId); + private hrTimeToNanoseconds(hrTime: [number, number]): number { + const [seconds, nanoseconds] = hrTime; + return seconds * 1e9 + nanoseconds; } } diff --git a/src/otel/processor.ts b/src/otel/processor.ts index 4729ecfe..06513ddb 100644 --- a/src/otel/processor.ts +++ b/src/otel/processor.ts @@ -1,364 +1,71 @@ import { Context } from "@opentelemetry/api"; -import { ExportResult, ExportResultCode } from "@opentelemetry/core"; import { ReadableSpan, Span, SpanExporter, SpanProcessor, } from "@opentelemetry/sdk-trace-node"; -import { SpanAttributes as AiSemanticConventions } from "@traceloop/ai-semantic-conventions"; -import { ModelEndpoints, ModelProviders } from "../api"; -import { PromptKernelRequest } from "../api/types/PromptKernelRequest"; +import { getDecoratorContext, getEvaluationContext, getTraceId } from "../context"; import { HUMANLOOP_FILE_KEY, HUMANLOOP_FILE_TYPE_KEY, - HUMANLOOP_FLOW_SPAN_NAME, HUMANLOOP_LOG_KEY, - HUMANLOOP_META_FUNCTION_NAME, + HUMANLOOP_PATH_KEY, } from "./constants"; -import { - NestedDict, - isHumanloopSpan, - isLLMProviderCall, - readFromOpenTelemetrySpan, - writeToOpenTelemetrySpan, -} from "./helpers"; - -// Interface for waiting on child spans to complete -interface CompletableSpan { - span: ReadableSpan; - complete: boolean; -} +import { isLLMProviderCall } from "./helpers"; -/** - * Enriches Humanloop spans with data from their child spans. - */ export class HumanloopSpanProcessor implements SpanProcessor { private spanExporter: SpanExporter; - private children: Map; - // List of all span IDs that are contained in a Flow trace - // They are passed to the Exporter as a span attribute - // so the Exporter knows when to complete a trace - private prerequisites: Map; constructor(exporter: SpanExporter) { this.spanExporter = exporter; - this.children = new Map(); - this.prerequisites = new Map(); } - async forceFlush(): Promise {} - onStart(span: Span, _: Context): void { - const spanId = span.spanContext().spanId; - const parentSpanId = span.parentSpanId; - if (span.name === HUMANLOOP_FLOW_SPAN_NAME) { - this.prerequisites.set(spanId, []); - } - if (parentSpanId !== undefined && isHumanloopSpan(span)) { - for (const [traceHead, allTraceNodes] of this.prerequisites) { - if ( - parentSpanId === traceHead || - allTraceNodes.includes(parentSpanId) - ) { - allTraceNodes.push(spanId); - this.prerequisites.set(traceHead, allTraceNodes); - break; - } - } - } - // Handle stream case: when Prompt instrumented function calls a provider with streaming: true - // The instrumentor span will end only when the ChunksResponse is consumed, which can happen - // after the span created by the Prompt utility finishes. To handle this, we register all instrumentor - // spans belonging to a Humanloop span, and their parent will wait for them to complete in onEnd before - // exporting the Humanloop span. - if (span.parentSpanId !== undefined && this.isInstrumentorSpan(span)) { - this.children.set(span.parentSpanId, [ - ...(this.children.get(span.parentSpanId) || []), - { span, complete: false }, - ]); - } - } - - async shutdown(): Promise {} - - /** - * Handles spans at the end of their lifecycle. Enriches Humanloop spans and send both HL and - * non-HL spans to the exporter. - */ - onEnd(span: ReadableSpan): void { - if (isHumanloopSpan(span)) { - // Wait for children to complete asynchronously - new Promise((resolve) => { - const checkChildrenSpans = () => { - const childrenSpans = this.children.get(span.spanContext().spanId); - if ( - (childrenSpans || []).every((childSpan) => childSpan.complete) - ) { - resolve(); - } else { - setTimeout(checkChildrenSpans, 100); - } - }; - checkChildrenSpans(); - }).then((_) => { - // All instrumentor spans have arrived, we can process the - // Humanloop parent span owning them - if (span.name === HUMANLOOP_FLOW_SPAN_NAME) { - // If the span if a Flow Log, add attribute with all span IDs it - // needs to wait before completion - writeToOpenTelemetrySpan( - span, - this.prerequisites.get(span.spanContext().spanId) || [], - HUMANLOOP_LOG_KEY, + if (isLLMProviderCall(span)) { + const decoratorContext = getDecoratorContext(); + if (decoratorContext && decoratorContext.type === "prompt") { + const { path, version } = decoratorContext; + const template = version?.template; + span = span.setAttribute(HUMANLOOP_PATH_KEY, path); + span = span.setAttribute(HUMANLOOP_FILE_TYPE_KEY, "prompt"); + if (template !== undefined) { + span = span.setAttribute( + `${HUMANLOOP_FILE_KEY}.template`, + // @ts-ignore + template, ); - this.prerequisites.delete(span.spanContext().spanId); } - - this.processSpanDispatch( - span, - this.children.get(span.spanContext().spanId) || [], - ); - - // Release references - this.children.delete(span.spanContext().spanId); - - // Pass Humanloop span to Exporter - this.spanExporter.export([span], (result: ExportResult) => { - if (result.code !== ExportResultCode.SUCCESS) { - console.error("Failed to export span:", result.error); - } - }); - }); - } else if (span.parentSpanId !== undefined && this.isInstrumentorSpan(span)) { - // If this is one of the children spans waited upon, update its completion status - - // Type checks - const childrenSpans = this.children.get(span.parentSpanId); - if ( - childrenSpans === undefined || - !childrenSpans.some( - (childSpan) => - childSpan.span.spanContext().spanId === - span.spanContext().spanId, - ) - ) { - throw new Error( - `Internal error: Expected instrumentor span ${span.parentSpanId} to be already present in the list`, - ); } - - // Updating the child span status - this.children.set( - span.parentSpanId, - childrenSpans.map((childSpan) => - childSpan.span.spanContext().spanId === span.spanContext().spanId - ? { - // The child span will have extra information when it's marked - // as finished and sent to Processors.onEnd - span: span, - // Marked as completed - complete: true, - } - : childSpan, - ), - ); - - // Export the instrumentor span - this.spanExporter.export([span], (result: ExportResult) => { - if (result.code !== ExportResultCode.SUCCESS) { - console.error("Failed to export span:", result.error); - } - }); - } else { - // Unknown span, export as it is - this.spanExporter.export([span], (result: ExportResult) => { - if (result.code !== ExportResultCode.SUCCESS) { - console.error("Failed to export span:", result.error); - } - }); - } - } - - /** - * Determines if a span is created by an instrumentor of interest. - */ - private isInstrumentorSpan(span: ReadableSpan): boolean { - // Expand in the future with checks for non-Prompt Files - return isLLMProviderCall(span); - } - - /** - * Processes spans based on their type and enriches them if applicable. - */ - private processSpanDispatch( - span: ReadableSpan, - childrenSpans: CompletableSpan[], - ): void { - const fileType = span.attributes[HUMANLOOP_FILE_TYPE_KEY]; - - // Common processing for all Humanloop spans - if (span.startTime) { - span.attributes[`${HUMANLOOP_LOG_KEY}.startTime`] = span.startTime; - } - if (span.endTime) { - span.attributes[`${HUMANLOOP_LOG_KEY}.endTime`] = span.endTime; - span.attributes[`${HUMANLOOP_LOG_KEY}.createdAt`] = span.endTime; - } - - switch (fileType) { - case "prompt": - this.processPrompt( - span, - childrenSpans.map((span) => span.span), - ); - break; - case "tool": - case "flow": - // Placeholder for processing other file types - break; - default: - console.error("Unknown Humanloop File span", span); + const traceId = getTraceId(); + if (traceId) { + span.setAttribute(`${HUMANLOOP_LOG_KEY}.trace_parent_id`, traceId); + } } } - /** - * Processes and enriches spans of type "prompt". - */ - private processPrompt( - promptSpan: ReadableSpan, - childrenSpans: ReadableSpan[], - ): void { - if (childrenSpans.length === 0) { - const hlFile = - readFromOpenTelemetrySpan(promptSpan, HUMANLOOP_FILE_KEY) || {}; - const prompt = (hlFile.prompt || {}) as unknown as PromptKernelRequest; - if (!("model" in prompt) || !prompt.model) { - const functionName = - promptSpan.attributes[HUMANLOOP_META_FUNCTION_NAME]; - throw Error( - `Error in ${functionName}: the LLM provider and model could not be inferred. Call one of the supported providers in your prompt function definition or define them in the promptKernel argument of the prompt() function wrapper.`, - ); + onEnd(span: ReadableSpan): void { + if (isLLMProviderCall(span)) { + const decoratorContext = getDecoratorContext(); + if (!decoratorContext || decoratorContext.type !== "prompt") { + // User made a provider call outside a @prompt context, ignore the span + return; } - } - - for (const childSpan of childrenSpans) { - if (isLLMProviderCall(childSpan)) { - this.enrichPromptKernel(promptSpan, childSpan); - this.enrichPromptLog(promptSpan, childSpan); - break; // Only process the first LLM provider call + const evaluationContext = getEvaluationContext(); + if (evaluationContext && evaluationContext.path === decoratorContext.path) { + // User made a provider call inside an evaluation context + // Ignore the span, evaluations.run() will use the output + // of the decorated function to create the Log + return; } } + this.spanExporter.export([span], () => {}); } - /** - * Enriches the prompt kernel of a prompt span using information from a child span. - */ - private enrichPromptKernel( - promptSpan: ReadableSpan, - llmProviderCallSpan: ReadableSpan, - ): void { - const hlFile = readFromOpenTelemetrySpan(promptSpan, HUMANLOOP_FILE_KEY) || {}; - - const prompt = (hlFile.prompt || {}) as unknown as PromptKernelRequest; - - // Assign or infer values for the prompt kernel - prompt.model = - prompt.model || - (llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_REQUEST_MODEL - ] as string); - if (prompt.model === undefined) { - throw new Error( - "Could not infer required parameter `model`. Please provide it in the prompt kernel.", - ); - } - prompt.endpoint = - prompt.endpoint || - (llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_REQUEST_TYPE - ] as ModelEndpoints); - prompt.provider = - prompt.provider || - (( - llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_SYSTEM - ] as string - ).toLowerCase() as ModelProviders); - prompt.temperature = - prompt.temperature || - (llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_REQUEST_TEMPERATURE - ] as number); - prompt.topP = - prompt.topP || - (llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_REQUEST_TOP_P - ] as number); - prompt.maxTokens = - prompt.maxTokens || - (llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_REQUEST_MAX_TOKENS - ] as number); - prompt.frequencyPenalty = - prompt.frequencyPenalty || - (llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_FREQUENCY_PENALTY - ] as number); - prompt.tools = prompt.tools || []; - - // Write the enriched prompt kernel back to the span - writeToOpenTelemetrySpan( - promptSpan, - prompt as unknown as NestedDict, - `${HUMANLOOP_FILE_KEY}.prompt`, - ); + async shutdown(): Promise { + return this.spanExporter.shutdown(); } - /** - * Enriches the prompt log of a prompt span using information from a child span. - */ - private enrichPromptLog( - promptSpan: ReadableSpan, - llmProviderCallSpan: ReadableSpan, - ): void { - let hlLog = readFromOpenTelemetrySpan(promptSpan, HUMANLOOP_LOG_KEY) || {}; - - if (!hlLog.output_tokens) { - hlLog.output_tokens = llmProviderCallSpan.attributes[ - AiSemanticConventions.LLM_USAGE_COMPLETION_TOKENS - ] as number; - } - const completions = readFromOpenTelemetrySpan( - llmProviderCallSpan, - AiSemanticConventions.LLM_COMPLETIONS, - ) as unknown as { - finish_reason: string; - role: string; - content: string; - }[]; - if (completions.length > 0) { - // @ts-ignore - hlLog.finish_reason = completions[0].finish_reason; - } - // @ts-ignore - const messages = readFromOpenTelemetrySpan( - llmProviderCallSpan, - AiSemanticConventions.LLM_PROMPTS, - ) as unknown as { - role: string; - content: string; - }[]; - // @ts-ignore - hlLog.messages = messages; - - // Edge case: Prompt used in streaming mode - if (!("output" in hlLog) || hlLog.output === "{}") { - hlLog.output = completions[0].content; - } - - // Write the enriched prompt log back to the span - writeToOpenTelemetrySpan(promptSpan, hlLog, HUMANLOOP_LOG_KEY); - } + async forceFlush(): Promise {} } diff --git a/src/otel/proto/common.ts b/src/otel/proto/common.ts new file mode 100644 index 00000000..f33fb674 --- /dev/null +++ b/src/otel/proto/common.ts @@ -0,0 +1,32 @@ +// NOTE: Machine-translated from https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto + +export type AnyValue = { + stringValue?: string; + boolValue?: boolean; + intValue?: number; + doubleValue?: number; + arrayValue?: ArrayValue; + kvlistValue?: KeyValueList; + bytesValue?: Uint8Array; +}; + +export type ArrayValue = { + values: AnyValue[]; +}; + +export type KeyValueList = { + values: KeyValue[]; +}; + +export type KeyValue = { + key: string; + value: AnyValue; +}; + +export type InstrumentationScope = { + name: string; + // NOTE: deviation from spec - marking last three fields as optional + version?: string; + attributes?: KeyValue[]; + droppedAttributesCount?: number; +}; diff --git a/src/otel/proto/resource.ts b/src/otel/proto/resource.ts new file mode 100644 index 00000000..9e8aae3c --- /dev/null +++ b/src/otel/proto/resource.ts @@ -0,0 +1,15 @@ +// NOTE: Machine-translated from https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/resource/v1/resource.proto +import { KeyValue } from "./common"; + +export interface Resource { + /** + * Set of attributes that describe the resource. + * Attribute keys MUST be unique. + */ + attributes: KeyValue[]; + + /** + * Number of dropped attributes. If the value is 0, then no attributes were dropped. + */ + droppedAttributesCount: number; +} diff --git a/src/otel/proto/trace.ts b/src/otel/proto/trace.ts new file mode 100644 index 00000000..d7fa435c --- /dev/null +++ b/src/otel/proto/trace.ts @@ -0,0 +1,80 @@ +// NOTE: Machine-translated from https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto +import { InstrumentationScope, KeyValue } from "./common"; +import { Resource } from "./resource"; + +export interface TracesData { + resourceSpans: ResourceSpans[]; +} + +export interface ResourceSpans { + resource?: Resource; + scopeSpans: ScopeSpans[]; + schemaUrl?: string; +} + +export interface ScopeSpans { + scope?: InstrumentationScope; + spans: Span[]; + schemaUrl?: string; +} + +export interface Span { + traceId: string; + spanId: string; + traceState: string; + parentSpanId?: string; + flags?: number; + name: string; + kind: SpanKind; + startTimeUnixNano: number; + endTimeUnixNano: number; + attributes: KeyValue[]; + droppedAttributesCount: number; + events: Event[]; + droppedEventsCount: number; + links: Link[]; + droppedLinksCount: number; + status?: Status; +} + +export enum SpanKind { + INTERNAL = 0, + SERVER = 1, + CLIENT = 2, + PRODUCER = 3, + CONSUMER = 4, +} + +export interface Event { + timeUnixNano: number; + name: string; + attributes: KeyValue[]; + droppedAttributesCount: number; +} + +export interface Link { + traceId: string; + spanId: string; + traceState: string; + attributes: KeyValue[]; + droppedAttributesCount: number; + flags?: number; +} + +export interface Status { + message: string; + code: StatusCode; +} + +export enum StatusCode { + UNSET = 0, + OK = 1, + ERROR = 2, +} + +export enum SpanFlags { + DO_NOT_USE = 0, + TRACE_FLAGS_MASK = 0x000000ff, + CONTEXT_HAS_IS_REMOTE_MASK = 0x00000100, + CONTEXT_IS_REMOTE_MASK = 0x00000200, +} diff --git a/src/overload.ts b/src/overload.ts new file mode 100644 index 00000000..dd66643a --- /dev/null +++ b/src/overload.ts @@ -0,0 +1,130 @@ +import { + CreateEvaluatorLogRequest, + FlowLogRequest, + PromptCallResponse, + PromptLogRequest, + ToolLogRequest, +} from "./api"; +import { Evaluators } from "./api/resources/evaluators/client/Client"; +import { Flows } from "./api/resources/flows/client/Client"; +import { Prompts } from "./api/resources/prompts/client/Client"; +import { Tools } from "./api/resources/tools/client/Client"; +import { getDecoratorContext, getEvaluationContext, getTraceId } from "./context"; +import { HumanloopRuntimeError } from "./error"; + +export function overloadLog( + client: T, +): T { + const originalLog = client.log.bind(client); + + const _overloadedLog = async ( + request: T extends Flows + ? FlowLogRequest + : T extends Prompts + ? PromptLogRequest + : T extends Tools + ? ToolLogRequest + : T extends Evaluators + ? CreateEvaluatorLogRequest + : never, + options?: T extends Flows + ? Flows.RequestOptions + : T extends Prompts + ? Prompts.RequestOptions + : T extends Tools + ? Tools.RequestOptions + : T extends Evaluators + ? Evaluators.RequestOptions + : never, + ) => { + const traceId = getTraceId(); + if (traceId !== undefined && client instanceof Flows) { + const context = getDecoratorContext(); + if (context === undefined) { + throw new HumanloopRuntimeError( + "Internal error: trace_id context is set outside a decorator context.", + ); + } + throw new HumanloopRuntimeError( + `Using flows.log() is not allowed: Flow decorator for File ${context.path} manages the tracing and trace completion.`, + ); + } + + if (traceId !== undefined) { + if ("traceParentId" in request) { + console.warn( + "Ignoring trace_parent_id argument: the Flow decorator manages tracing.", + ); + } + request = { + ...request, + traceParentId: traceId, + }; + } + + const evaluationContext = getEvaluationContext(); + if (evaluationContext !== undefined) { + const [kwargsEval, evalCallback] = evaluationContext.logArgsWithContext({ + logArgs: request, + forOtel: true, + path: request.path, + }); + try { + // @ts-ignore Polymorphism alarms the type checker + const response = await originalLog(kwargsEval, options); + if (evalCallback !== null) { + await evalCallback(response.id); + } + return response; + } catch (e) { + throw new HumanloopRuntimeError(String(e)); + } + } else { + try { + // @ts-ignore Polymorphism alarms the type checker + return await originalLog(request, options); + } catch (e) { + throw new HumanloopRuntimeError(String(e)); + } + } + }; + + // @ts-ignore + client.log = _overloadedLog.bind(client); + // @ts-ignore + client._log = originalLog.bind(client); + + return client; +} + +export function overloadCall(client: Prompts): Prompts { + const originalCall = client.call.bind(client); + + const _overloadedCall = async ( + request: PromptLogRequest, + options?: Prompts.RequestOptions, + ): Promise => { + const traceId = getTraceId(); + if (traceId !== undefined) { + if ("traceParentId" in request) { + console.warn( + "Ignoring trace_parent_id argument: the Flow decorator manages tracing.", + ); + } + request = { + ...request, + traceParentId: traceId, + }; + } + + try { + return await originalCall(request, options); + } catch (e) { + throw new HumanloopRuntimeError(String(e)); + } + }; + + client.call = _overloadedCall.bind(client); + + return client; +} diff --git a/tests/unit/fetcher/createRequestUrl.test.ts b/tests/unit/fetcher/createRequestUrl.test.ts index 486e1e61..98e3e96c 100644 --- a/tests/unit/fetcher/createRequestUrl.test.ts +++ b/tests/unit/fetcher/createRequestUrl.test.ts @@ -9,13 +9,17 @@ describe("Test createRequestUrl", () => { it("should append simple query parameters", () => { const baseUrl = "https://api.example.com"; const queryParams = { key: "value", another: "param" }; - expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?key=value&another=param"); + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?key=value&another=param", + ); }); it("should handle array query parameters", () => { const baseUrl = "https://api.example.com"; const queryParams = { items: ["a", "b", "c"] }; - expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?items=a&items=b&items=c"); + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?items=a&items=b&items=c", + ); }); it("should handle object query parameters", () => { @@ -46,6 +50,8 @@ describe("Test createRequestUrl", () => { it("should encode special characters in query parameters", () => { const baseUrl = "https://api.example.com"; const queryParams = { special: "a&b=c d" }; - expect(createRequestUrl(baseUrl, queryParams)).toBe("https://api.example.com?special=a%26b%3Dc%20d"); + expect(createRequestUrl(baseUrl, queryParams)).toBe( + "https://api.example.com?special=a%26b%3Dc%20d", + ); }); }); diff --git a/tests/unit/fetcher/getRequestBody.test.ts b/tests/unit/fetcher/getRequestBody.test.ts index 919604c2..1e4a3d69 100644 --- a/tests/unit/fetcher/getRequestBody.test.ts +++ b/tests/unit/fetcher/getRequestBody.test.ts @@ -1,5 +1,5 @@ -import { RUNTIME } from "../../../src/core/runtime"; import { getRequestBody } from "../../../src/core/fetcher/getRequestBody"; +import { RUNTIME } from "../../../src/core/runtime"; describe("Test getRequestBody", () => { it("should return FormData as is in Node environment", async () => { diff --git a/tests/unit/fetcher/getResponseBody.test.ts b/tests/unit/fetcher/getResponseBody.test.ts index 1030c517..99da88fb 100644 --- a/tests/unit/fetcher/getResponseBody.test.ts +++ b/tests/unit/fetcher/getResponseBody.test.ts @@ -1,6 +1,6 @@ -import { RUNTIME } from "../../../src/core/runtime"; import { getResponseBody } from "../../../src/core/fetcher/getResponseBody"; import { chooseStreamWrapper } from "../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; +import { RUNTIME } from "../../../src/core/runtime"; describe("Test getResponseBody", () => { it("should handle blob response type", async () => { @@ -26,7 +26,9 @@ describe("Test getResponseBody", () => { const mockResponse = new Response(mockStream); const result = await getResponseBody(mockResponse, "streaming"); // need to reinstantiate string as a result of locked state in Readable Stream after registration with Response - expect(JSON.stringify(result)).toBe(JSON.stringify(await chooseStreamWrapper(new ReadableStream()))); + expect(JSON.stringify(result)).toBe( + JSON.stringify(await chooseStreamWrapper(new ReadableStream())), + ); } }); diff --git a/tests/unit/fetcher/makeRequest.test.ts b/tests/unit/fetcher/makeRequest.test.ts index 43ed9d11..701d55c9 100644 --- a/tests/unit/fetcher/makeRequest.test.ts +++ b/tests/unit/fetcher/makeRequest.test.ts @@ -10,11 +10,19 @@ describe("Test makeRequest", () => { beforeEach(() => { mockFetch = jest.fn(); - mockFetch.mockResolvedValue(new Response(JSON.stringify({ test: "successful" }), { status: 200 })); + mockFetch.mockResolvedValue( + new Response(JSON.stringify({ test: "successful" }), { status: 200 }), + ); }); it("should handle POST request correctly", async () => { - const response = await makeRequest(mockFetch, mockPostUrl, "POST", mockHeaders, mockBody); + const response = await makeRequest( + mockFetch, + mockPostUrl, + "POST", + mockHeaders, + mockBody, + ); const responseBody = await response.json(); expect(responseBody).toEqual({ test: "successful" }); expect(mockFetch).toHaveBeenCalledTimes(1); @@ -33,7 +41,13 @@ describe("Test makeRequest", () => { }); it("should handle GET request correctly", async () => { - const response = await makeRequest(mockFetch, mockGetUrl, "GET", mockHeaders, undefined); + const response = await makeRequest( + mockFetch, + mockGetUrl, + "GET", + mockHeaders, + undefined, + ); const responseBody = await response.json(); expect(responseBody).toEqual({ test: "successful" }); expect(mockFetch).toHaveBeenCalledTimes(1); diff --git a/tests/unit/fetcher/requestWithRetries.test.ts b/tests/unit/fetcher/requestWithRetries.test.ts index 3cdaa40a..8c04dd19 100644 --- a/tests/unit/fetcher/requestWithRetries.test.ts +++ b/tests/unit/fetcher/requestWithRetries.test.ts @@ -22,10 +22,12 @@ describe("requestWithRetries", () => { }); it("should retry on retryable status codes", async () => { - setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); + setTimeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); const retryableStatuses = [408, 429, 500, 502]; let callCount = 0; @@ -37,7 +39,10 @@ describe("requestWithRetries", () => { return new Response("", { status: 200 }); }); - const responsePromise = requestWithRetries(() => mockFetch(), retryableStatuses.length); + const responsePromise = requestWithRetries( + () => mockFetch(), + retryableStatuses.length, + ); await jest.runAllTimersAsync(); const response = await responsePromise; @@ -46,10 +51,12 @@ describe("requestWithRetries", () => { }); it("should respect maxRetries limit", async () => { - setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); + setTimeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); const maxRetries = 2; mockFetch.mockResolvedValue(new Response("", { status: 500 })); @@ -63,10 +70,12 @@ describe("requestWithRetries", () => { }); it("should not retry on success status codes", async () => { - setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); + setTimeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); const successStatuses = [200, 201, 202]; @@ -85,10 +94,12 @@ describe("requestWithRetries", () => { }); it("should apply correct exponential backoff with jitter", async () => { - setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); + setTimeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); mockFetch.mockResolvedValue(new Response("", { status: 500 })); const maxRetries = 3; @@ -102,17 +113,23 @@ describe("requestWithRetries", () => { expect(setTimeoutSpy).toHaveBeenCalledTimes(expectedDelays.length); expectedDelays.forEach((delay, index) => { - expect(setTimeoutSpy).toHaveBeenNthCalledWith(index + 1, expect.any(Function), delay); + expect(setTimeoutSpy).toHaveBeenNthCalledWith( + index + 1, + expect.any(Function), + delay, + ); }); expect(mockFetch).toHaveBeenCalledTimes(maxRetries + 1); }); it("should handle concurrent retries independently", async () => { - setTimeoutSpy = jest.spyOn(global, "setTimeout").mockImplementation((callback: (args: void) => void) => { - process.nextTick(callback); - return null as any; - }); + setTimeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: (args: void) => void) => { + process.nextTick(callback); + return null as any; + }); mockFetch .mockResolvedValueOnce(new Response("", { status: 500 })) diff --git a/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts b/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts index 172c1c26..f033793a 100644 --- a/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts +++ b/tests/unit/fetcher/stream-wrappers/Node18UniversalStreamWrapper.test.ts @@ -148,7 +148,9 @@ describe("Node18UniversalStreamWrapper", () => { it("should read the stream as json", async () => { const rawStream = new ReadableStream({ start(controller) { - controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.enqueue( + new TextEncoder().encode(JSON.stringify({ test: "test" })), + ); controller.close(); }, }); diff --git a/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts b/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts index 19c26668..460c03a7 100644 --- a/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts +++ b/tests/unit/fetcher/stream-wrappers/NodePre18StreamWrapper.test.ts @@ -2,7 +2,10 @@ import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrap describe("NodePre18StreamWrapper", () => { it("should set encoding to utf-8", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const setEncodingSpy = jest.spyOn(stream, "setEncoding"); @@ -12,7 +15,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should register an event listener for readable", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const onSpy = jest.spyOn(stream, "on"); @@ -22,7 +28,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should remove an event listener for data", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const offSpy = jest.spyOn(stream, "off"); @@ -34,7 +43,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should write to dest when calling pipe to node writable stream", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const dest = new (await import("readable-stream")).Writable({ write(chunk, encoding, callback) { @@ -47,7 +59,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should write nothing when calling pipe and unpipe", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const buffer: Uint8Array[] = []; const dest = new (await import("readable-stream")).Writable({ @@ -63,7 +78,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should destroy the stream", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const destroySpy = jest.spyOn(stream, "destroy"); @@ -73,7 +91,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should pause the stream and resume", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const pauseSpy = jest.spyOn(stream, "pause"); @@ -86,7 +107,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should read the stream", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); expect(await stream.read()).toEqual("test"); @@ -94,7 +118,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should read the stream as text", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); const stream = new NodePre18StreamWrapper(rawStream); const data = await stream.text(); @@ -103,7 +130,9 @@ describe("NodePre18StreamWrapper", () => { }); it("should read the stream as json", async () => { - const rawStream = (await import("readable-stream")).Readable.from([JSON.stringify({ test: "test" })]); + const rawStream = (await import("readable-stream")).Readable.from([ + JSON.stringify({ test: "test" }), + ]); const stream = new NodePre18StreamWrapper(rawStream); const data = await stream.json(); @@ -112,7 +141,10 @@ describe("NodePre18StreamWrapper", () => { }); it("should allow use with async iterable stream", async () => { - const rawStream = (await import("readable-stream")).Readable.from(["test", "test"]); + const rawStream = (await import("readable-stream")).Readable.from([ + "test", + "test", + ]); let data = ""; const stream = new NodePre18StreamWrapper(rawStream); for await (const chunk of stream) { diff --git a/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts b/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts index 0ea14835..d01f0906 100644 --- a/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts +++ b/tests/unit/fetcher/stream-wrappers/UndiciStreamWrapper.test.ts @@ -123,7 +123,9 @@ describe("UndiciStreamWrapper", () => { it("should read the stream as json", async () => { const rawStream = new ReadableStream({ start(controller) { - controller.enqueue(new TextEncoder().encode(JSON.stringify({ test: "test" }))); + controller.enqueue( + new TextEncoder().encode(JSON.stringify({ test: "test" })), + ); controller.close(); }, }); diff --git a/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts b/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts index 8004e9ab..5752c8af 100644 --- a/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts +++ b/tests/unit/fetcher/stream-wrappers/chooseStreamWrapper.test.ts @@ -1,8 +1,8 @@ -import { RUNTIME } from "../../../../src/core/runtime"; import { Node18UniversalStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/Node18UniversalStreamWrapper"; import { NodePre18StreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/NodePre18StreamWrapper"; import { UndiciStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/UndiciStreamWrapper"; import { chooseStreamWrapper } from "../../../../src/core/fetcher/stream-wrappers/chooseStreamWrapper"; +import { RUNTIME } from "../../../../src/core/runtime"; describe("chooseStreamWrapper", () => { beforeEach(() => { diff --git a/tests/unit/fetcher/stream-wrappers/webpack.test.ts b/tests/unit/fetcher/stream-wrappers/webpack.test.ts index 2a476ffb..df64022e 100644 --- a/tests/unit/fetcher/stream-wrappers/webpack.test.ts +++ b/tests/unit/fetcher/stream-wrappers/webpack.test.ts @@ -18,10 +18,9 @@ describe("test env compatibility", () => { ], }, resolve: { - extensions: [".tsx", ".ts", ".jsx", ".js"], - extensionAlias: { - ".js": [".ts", ".js"], - ".jsx": [".tsx", ".jsx"], + extensions: [".tsx", ".ts", ".js"], + fallback: { + async_hooks: false, }, }, }, diff --git a/tests/unit/form-data-utils/formDataWrapper.test.ts b/tests/unit/form-data-utils/formDataWrapper.test.ts index 61fd5bfc..dd77e835 100644 --- a/tests/unit/form-data-utils/formDataWrapper.test.ts +++ b/tests/unit/form-data-utils/formDataWrapper.test.ts @@ -1,5 +1,8 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import { Node18FormData, WebFormData } from "../../../src/core/form-data-utils/FormDataWrapper"; +import { + Node18FormData, + WebFormData, +} from "../../../src/core/form-data-utils/FormDataWrapper"; describe("CrossPlatformFormData", () => { describe("Node18FormData", () => { @@ -11,7 +14,9 @@ describe("CrossPlatformFormData", () => { }); it("should append a Readable stream with a specified filename", async () => { - const value = (await import("readable-stream")).Readable.from(["file content"]); + const value = (await import("readable-stream")).Readable.from([ + "file content", + ]); const filename = "testfile.txt"; await formData.appendFile("file", value, filename); @@ -80,7 +85,9 @@ describe("CrossPlatformFormData", () => { }); it("should append a Readable stream with a specified filename", async () => { - const value = (await import("readable-stream")).Readable.from(["file content"]); + const value = (await import("readable-stream")).Readable.from([ + "file content", + ]); const filename = "testfile.txt"; await formData.appendFile("file", value, filename); diff --git a/tests/unit/zurg/lazy/lazy.test.ts b/tests/unit/zurg/lazy/lazy.test.ts index 3a5a338d..b2a0c899 100644 --- a/tests/unit/zurg/lazy/lazy.test.ts +++ b/tests/unit/zurg/lazy/lazy.test.ts @@ -51,7 +51,9 @@ describe("lazy", () => { interface TreeNode { children: TreeNode[]; } - const TreeNode: Schema = lazy(() => object({ children: list(TreeNode) })); + const TreeNode: Schema = lazy(() => + object({ children: list(TreeNode) }), + ); }; }); }); diff --git a/tests/unit/zurg/lazy/lazyObject.test.ts b/tests/unit/zurg/lazy/lazyObject.test.ts index 9b443671..05c9e800 100644 --- a/tests/unit/zurg/lazy/lazyObject.test.ts +++ b/tests/unit/zurg/lazy/lazyObject.test.ts @@ -1,4 +1,9 @@ -import { lazyObject, number, object, string } from "../../../../src/core/schemas/builders"; +import { + lazyObject, + number, + object, + string, +} from "../../../../src/core/schemas/builders"; import { itSchemaIdentity } from "../utils/itSchema"; describe("lazy", () => { diff --git a/tests/unit/zurg/object/extend.test.ts b/tests/unit/zurg/object/extend.test.ts index 10954713..8e161d5e 100644 --- a/tests/unit/zurg/object/extend.test.ts +++ b/tests/unit/zurg/object/extend.test.ts @@ -1,4 +1,10 @@ -import { boolean, object, property, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { + boolean, + object, + property, + string, + stringLiteral, +} from "../../../../src/core/schemas/builders"; import { itSchema, itSchemaIdentity } from "../utils/itSchema"; describe("extend", () => { diff --git a/tests/unit/zurg/object/object.test.ts b/tests/unit/zurg/object/object.test.ts index a8d9fe0a..f992a961 100644 --- a/tests/unit/zurg/object/object.test.ts +++ b/tests/unit/zurg/object/object.test.ts @@ -1,4 +1,12 @@ -import { any, number, object, property, string, stringLiteral, unknown } from "../../../../src/core/schemas/builders"; +import { + any, + number, + object, + property, + string, + stringLiteral, + unknown, +} from "../../../../src/core/schemas/builders"; import { itJson, itParse, itSchema, itSchemaIdentity } from "../utils/itSchema"; import { itValidate } from "../utils/itValidate"; @@ -154,25 +162,39 @@ describe("object", () => { }); describe("nullish properties", () => { - itSchema("missing properties are not added", object({ foo: property("raw_foo", string().optional()) }), { - raw: {}, - parsed: {}, - }); + itSchema( + "missing properties are not added", + object({ foo: property("raw_foo", string().optional()) }), + { + raw: {}, + parsed: {}, + }, + ); - itSchema("undefined properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { - raw: { raw_foo: null }, - parsed: { foo: undefined }, - }); + itSchema( + "undefined properties are not dropped", + object({ foo: property("raw_foo", string().optional()) }), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }, + ); - itSchema("null properties are not dropped", object({ foo: property("raw_foo", string().optional()) }), { - raw: { raw_foo: null }, - parsed: { foo: undefined }, - }); + itSchema( + "null properties are not dropped", + object({ foo: property("raw_foo", string().optional()) }), + { + raw: { raw_foo: null }, + parsed: { foo: undefined }, + }, + ); describe("extensions", () => { itSchema( "undefined properties are not dropped", - object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + object({}).extend( + object({ foo: property("raw_foo", string().optional()) }), + ), { raw: { raw_foo: null }, parsed: { foo: undefined }, @@ -182,7 +204,9 @@ describe("object", () => { describe("parse()", () => { itParse( "null properties are not dropped", - object({}).extend(object({ foo: property("raw_foo", string().optional()) })), + object({}).extend( + object({ foo: property("raw_foo", string().optional()) }), + ), { raw: { raw_foo: null }, parsed: { foo: undefined }, diff --git a/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts b/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts index efcd83af..6d9366d7 100644 --- a/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts +++ b/tests/unit/zurg/object/objectWithoutOptionalProperties.test.ts @@ -1,4 +1,8 @@ -import { objectWithoutOptionalProperties, string, stringLiteral } from "../../../../src/core/schemas/builders"; +import { + objectWithoutOptionalProperties, + string, + stringLiteral, +} from "../../../../src/core/schemas/builders"; import { itSchema } from "../utils/itSchema"; describe("objectWithoutOptionalProperties", () => { diff --git a/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts b/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts index 09d594d0..4c3267eb 100644 --- a/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts +++ b/tests/unit/zurg/schema-utils/getSchemaUtils.test.ts @@ -35,8 +35,11 @@ describe("getSchemaUtils", () => { }); it("throws on invalid value", async () => { - const value = () => object({ a: string(), b: string() }).parseOrThrow({ a: 24 }); - expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + const value = () => + object({ a: string(), b: string() }).parseOrThrow({ a: 24 }); + expect(value).toThrowError( + new Error('a: Expected string. Received 24.; Missing required key "b"'), + ); }); }); @@ -47,8 +50,11 @@ describe("getSchemaUtils", () => { }); it("throws on invalid value", async () => { - const value = () => object({ a: string(), b: string() }).jsonOrThrow({ a: 24 }); - expect(value).toThrowError(new Error('a: Expected string. Received 24.; Missing required key "b"')); + const value = () => + object({ a: string(), b: string() }).jsonOrThrow({ a: 24 }); + expect(value).toThrowError( + new Error('a: Expected string. Received 24.; Missing required key "b"'), + ); }); }); diff --git a/tests/unit/zurg/skipValidation.test.ts b/tests/unit/zurg/skipValidation.test.ts index 32835559..42c275c2 100644 --- a/tests/unit/zurg/skipValidation.test.ts +++ b/tests/unit/zurg/skipValidation.test.ts @@ -1,5 +1,12 @@ /* eslint-disable no-console */ -import { boolean, number, object, property, string, undiscriminatedUnion } from "../../../src/core/schemas/builders"; +import { + boolean, + number, + object, + property, + string, + undiscriminatedUnion, +} from "../../../src/core/schemas/builders"; describe("skipValidation", () => { it("allows data that doesn't conform to the schema", async () => { diff --git a/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts b/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts index e0ddb21b..b5cd993d 100644 --- a/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts +++ b/tests/unit/zurg/undiscriminated-union/undiscriminatedUnion.test.ts @@ -1,16 +1,31 @@ -import { number, object, property, string, undiscriminatedUnion } from "../../../../src/core/schemas/builders"; +import { + number, + object, + property, + string, + undiscriminatedUnion, +} from "../../../../src/core/schemas/builders"; import { itSchema, itSchemaIdentity } from "../utils/itSchema"; describe("undiscriminatedUnion", () => { itSchemaIdentity(undiscriminatedUnion([string(), number()]), "hello world"); - itSchemaIdentity(undiscriminatedUnion([object({ hello: string() }), object({ goodbye: string() })]), { - goodbye: "foo", - }); + itSchemaIdentity( + undiscriminatedUnion([ + object({ hello: string() }), + object({ goodbye: string() }), + ]), + { + goodbye: "foo", + }, + ); itSchema( "Correctly transforms", - undiscriminatedUnion([object({ hello: string() }), object({ helloWorld: property("hello_world", string()) })]), + undiscriminatedUnion([ + object({ hello: string() }), + object({ helloWorld: property("hello_world", string()) }), + ]), { raw: { hello_world: "foo " }, parsed: { helloWorld: "foo " }, diff --git a/tests/unit/zurg/union/union.test.ts b/tests/unit/zurg/union/union.test.ts index 1f5d7a8f..15641487 100644 --- a/tests/unit/zurg/union/union.test.ts +++ b/tests/unit/zurg/union/union.test.ts @@ -1,4 +1,11 @@ -import { boolean, discriminant, number, object, string, union } from "../../../../src/core/schemas/builders"; +import { + boolean, + discriminant, + number, + object, + string, + union, +} from "../../../../src/core/schemas/builders"; import { itSchema, itSchemaIdentity } from "../utils/itSchema"; import { itValidate } from "../utils/itValidate"; diff --git a/tests/unit/zurg/utils/itSchema.ts b/tests/unit/zurg/utils/itSchema.ts index 82a53887..67a9f79c 100644 --- a/tests/unit/zurg/utils/itSchema.ts +++ b/tests/unit/zurg/utils/itSchema.ts @@ -4,7 +4,10 @@ import { Schema, SchemaOptions } from "../../../../src/core/schemas/Schema"; export function itSchemaIdentity( schema: Schema, value: T, - { title = "functions as identity", opts }: { title?: string; opts?: SchemaOptions } = {}, + { + title = "functions as identity", + opts, + }: { title?: string; opts?: SchemaOptions } = {}, ): void { itSchema(title, schema, { raw: value, parsed: value, opts }); } @@ -48,7 +51,9 @@ export function itParse( it(title, () => { const maybeValid = schema.parse(raw, opts); if (!maybeValid.ok) { - throw new Error("Failed to parse() " + JSON.stringify(maybeValid.errors, undefined, 4)); + throw new Error( + "Failed to parse() " + JSON.stringify(maybeValid.errors, undefined, 4), + ); } expect(maybeValid.value).toStrictEqual(parsed); }); @@ -71,7 +76,9 @@ export function itJson( it(title, () => { const maybeValid = schema.json(parsed, opts); if (!maybeValid.ok) { - throw new Error("Failed to json() " + JSON.stringify(maybeValid.errors, undefined, 4)); + throw new Error( + "Failed to json() " + JSON.stringify(maybeValid.errors, undefined, 4), + ); } expect(maybeValid.value).toStrictEqual(raw); }); diff --git a/tests/unit/zurg/utils/itValidate.ts b/tests/unit/zurg/utils/itValidate.ts index ead1ca70..c16a502d 100644 --- a/tests/unit/zurg/utils/itValidate.ts +++ b/tests/unit/zurg/utils/itValidate.ts @@ -1,5 +1,9 @@ /* eslint-disable jest/no-export */ -import { Schema, SchemaOptions, ValidationError } from "../../../../src/core/schemas/Schema"; +import { + Schema, + SchemaOptions, + ValidationError, +} from "../../../../src/core/schemas/Schema"; export function itValidate( title: string, diff --git a/yarn.lock b/yarn.lock index d16e9e1a..ba1d1f48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -557,12 +557,12 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.26.5", "@babel/compat-data@^7.26.8": +"@babel/compat-data@^7.26.5": version "7.26.8" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== -"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.26.0": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.9.tgz#71838542a4b1e49dfed353d7acbc6eb89f4a76f2" integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw== @@ -603,14 +603,7 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" -"@babel/helper-annotate-as-pure@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" - integrity sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.25.9", "@babel/helper-compilation-targets@^7.26.5": +"@babel/helper-compilation-targets@^7.26.5": version "7.26.5" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== @@ -621,39 +614,6 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.25.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71" - integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/helper-replace-supers" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/traverse" "^7.26.9" - semver "^6.3.1" - -"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.25.9": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0" - integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - regexpu-core "^6.2.0" - semver "^6.3.1" - -"@babel/helper-define-polyfill-provider@^0.6.3": - version "0.6.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz#f4f2792fae2ef382074bc2d713522cf24e6ddb21" - integrity sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg== - dependencies: - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-plugin-utils" "^7.22.5" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - "@babel/helper-environment-visitor@^7.22.20": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" @@ -676,14 +636,6 @@ dependencies: "@babel/types" "^7.24.7" -"@babel/helper-member-expression-to-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz#9dfffe46f727005a5ea29051ac835fb735e4c1a3" - integrity sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - "@babel/helper-module-imports@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" @@ -692,7 +644,7 @@ "@babel/traverse" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": +"@babel/helper-module-transforms@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== @@ -701,44 +653,11 @@ "@babel/helper-validator-identifier" "^7.25.9" "@babel/traverse" "^7.25.9" -"@babel/helper-optimise-call-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz#3324ae50bae7e2ab3c33f60c9a877b6a0146b54e" - integrity sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ== - dependencies: - "@babel/types" "^7.25.9" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5", "@babel/helper-plugin-utils@^7.8.0": +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.8.0": version "7.26.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== -"@babel/helper-remap-async-to-generator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" - integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-wrap-function" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/helper-replace-supers@^7.25.9", "@babel/helper-replace-supers@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d" - integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.25.9" - "@babel/helper-optimise-call-expression" "^7.25.9" - "@babel/traverse" "^7.26.5" - -"@babel/helper-skip-transparent-expression-wrappers@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz#0b2e1b62d560d6b1954893fd2b705dc17c91f0c9" - integrity sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA== - dependencies: - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - "@babel/helper-split-export-declaration@^7.22.6": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" @@ -761,15 +680,6 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== -"@babel/helper-wrap-function@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" - integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g== - dependencies: - "@babel/template" "^7.25.9" - "@babel/traverse" "^7.25.9" - "@babel/types" "^7.25.9" - "@babel/helpers@^7.26.9": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.9.tgz#28f3fb45252fc88ef2dc547c8a911c255fc9fef6" @@ -785,50 +695,6 @@ dependencies: "@babel/types" "^7.26.9" -"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" - integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30" - integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137" - integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1" - integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e" - integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": - version "7.21.0-placeholder-for-preset-env.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" - integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== - "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -857,14 +723,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-import-assertions@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f" - integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-syntax-import-attributes@^7.24.7", "@babel/plugin-syntax-import-attributes@^7.26.0": +"@babel/plugin-syntax-import-attributes@^7.24.7": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== @@ -955,493 +814,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" - integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-transform-arrow-functions@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" - integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-async-generator-functions@^7.26.8": - version "7.26.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz#5e3991135e3b9c6eaaf5eff56d1ae5a11df45ff8" - integrity sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-remap-async-to-generator" "^7.25.9" - "@babel/traverse" "^7.26.8" - -"@babel/plugin-transform-async-to-generator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71" - integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== - dependencies: - "@babel/helper-module-imports" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-remap-async-to-generator" "^7.25.9" - -"@babel/plugin-transform-block-scoped-functions@^7.26.5": - version "7.26.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz#3dc4405d31ad1cbe45293aa57205a6e3b009d53e" - integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-block-scoping@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1" - integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-class-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f" - integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-class-static-block@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0" - integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-classes@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52" - integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - "@babel/traverse" "^7.25.9" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b" - integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/template" "^7.25.9" - -"@babel/plugin-transform-destructuring@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1" - integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-dotall-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a" - integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-duplicate-keys@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d" - integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31" - integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-dynamic-import@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8" - integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-exponentiation-operator@^7.26.3": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc" - integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-export-namespace-from@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2" - integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-for-of@^7.26.9": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz#27231f79d5170ef33b5111f07fe5cafeb2c96a56" - integrity sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-function-name@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97" - integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== - dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-transform-json-strings@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660" - integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de" - integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-logical-assignment-operators@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7" - integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-member-expression-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de" - integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-amd@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5" - integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-commonjs@^7.26.3": - version "7.26.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" - integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== - dependencies: - "@babel/helper-module-transforms" "^7.26.0" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-modules-systemjs@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8" - integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-validator-identifier" "^7.25.9" - "@babel/traverse" "^7.25.9" - -"@babel/plugin-transform-modules-umd@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9" - integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== - dependencies: - "@babel/helper-module-transforms" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a" - integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-new-target@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd" - integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-nullish-coalescing-operator@^7.26.6": - version "7.26.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz#fbf6b3c92cb509e7b319ee46e3da89c5bedd31fe" - integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-numeric-separator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1" - integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-object-rest-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18" - integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== - dependencies: - "@babel/helper-compilation-targets" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" - -"@babel/plugin-transform-object-super@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03" - integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-replace-supers" "^7.25.9" - -"@babel/plugin-transform-optional-catch-binding@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3" - integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-optional-chaining@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" - integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-parameters@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257" - integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-private-methods@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" - integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-private-property-in-object@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33" - integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.25.9" - "@babel/helper-create-class-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-property-literals@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f" - integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-regenerator@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" - integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - regenerator-transform "^0.15.2" - -"@babel/plugin-transform-regexp-modifiers@^7.26.0": - version "7.26.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850" - integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-reserved-words@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce" - integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-shorthand-properties@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" - integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-spread@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9" - integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" - -"@babel/plugin-transform-sticky-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32" - integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-template-literals@^7.26.8": - version "7.26.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz#966b15d153a991172a540a69ad5e1845ced990b5" - integrity sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-typeof-symbol@^7.26.7": - version "7.26.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz#d0e33acd9223744c1e857dbd6fa17bd0a3786937" - integrity sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw== - dependencies: - "@babel/helper-plugin-utils" "^7.26.5" - -"@babel/plugin-transform-unicode-escapes@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82" - integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== - dependencies: - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-property-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3" - integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1" - integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/plugin-transform-unicode-sets-regex@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe" - integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.25.9" - "@babel/helper-plugin-utils" "^7.25.9" - -"@babel/preset-env@^7.26.0": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.9.tgz#2ec64e903d0efe743699f77a10bdf7955c2123c3" - integrity sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ== - dependencies: - "@babel/compat-data" "^7.26.8" - "@babel/helper-compilation-targets" "^7.26.5" - "@babel/helper-plugin-utils" "^7.26.5" - "@babel/helper-validator-option" "^7.25.9" - "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" - "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" - "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-import-assertions" "^7.26.0" - "@babel/plugin-syntax-import-attributes" "^7.26.0" - "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" - "@babel/plugin-transform-arrow-functions" "^7.25.9" - "@babel/plugin-transform-async-generator-functions" "^7.26.8" - "@babel/plugin-transform-async-to-generator" "^7.25.9" - "@babel/plugin-transform-block-scoped-functions" "^7.26.5" - "@babel/plugin-transform-block-scoping" "^7.25.9" - "@babel/plugin-transform-class-properties" "^7.25.9" - "@babel/plugin-transform-class-static-block" "^7.26.0" - "@babel/plugin-transform-classes" "^7.25.9" - "@babel/plugin-transform-computed-properties" "^7.25.9" - "@babel/plugin-transform-destructuring" "^7.25.9" - "@babel/plugin-transform-dotall-regex" "^7.25.9" - "@babel/plugin-transform-duplicate-keys" "^7.25.9" - "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-dynamic-import" "^7.25.9" - "@babel/plugin-transform-exponentiation-operator" "^7.26.3" - "@babel/plugin-transform-export-namespace-from" "^7.25.9" - "@babel/plugin-transform-for-of" "^7.26.9" - "@babel/plugin-transform-function-name" "^7.25.9" - "@babel/plugin-transform-json-strings" "^7.25.9" - "@babel/plugin-transform-literals" "^7.25.9" - "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" - "@babel/plugin-transform-member-expression-literals" "^7.25.9" - "@babel/plugin-transform-modules-amd" "^7.25.9" - "@babel/plugin-transform-modules-commonjs" "^7.26.3" - "@babel/plugin-transform-modules-systemjs" "^7.25.9" - "@babel/plugin-transform-modules-umd" "^7.25.9" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" - "@babel/plugin-transform-new-target" "^7.25.9" - "@babel/plugin-transform-nullish-coalescing-operator" "^7.26.6" - "@babel/plugin-transform-numeric-separator" "^7.25.9" - "@babel/plugin-transform-object-rest-spread" "^7.25.9" - "@babel/plugin-transform-object-super" "^7.25.9" - "@babel/plugin-transform-optional-catch-binding" "^7.25.9" - "@babel/plugin-transform-optional-chaining" "^7.25.9" - "@babel/plugin-transform-parameters" "^7.25.9" - "@babel/plugin-transform-private-methods" "^7.25.9" - "@babel/plugin-transform-private-property-in-object" "^7.25.9" - "@babel/plugin-transform-property-literals" "^7.25.9" - "@babel/plugin-transform-regenerator" "^7.25.9" - "@babel/plugin-transform-regexp-modifiers" "^7.26.0" - "@babel/plugin-transform-reserved-words" "^7.25.9" - "@babel/plugin-transform-shorthand-properties" "^7.25.9" - "@babel/plugin-transform-spread" "^7.25.9" - "@babel/plugin-transform-sticky-regex" "^7.25.9" - "@babel/plugin-transform-template-literals" "^7.26.8" - "@babel/plugin-transform-typeof-symbol" "^7.26.7" - "@babel/plugin-transform-unicode-escapes" "^7.25.9" - "@babel/plugin-transform-unicode-property-regex" "^7.25.9" - "@babel/plugin-transform-unicode-regex" "^7.25.9" - "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" - "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.11.0" - babel-plugin-polyfill-regenerator "^0.6.1" - core-js-compat "^3.40.0" - semver "^6.3.1" - -"@babel/preset-modules@0.1.6-no-external-plugins": - version "0.1.6-no-external-plugins" - resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" - integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/runtime@^7.8.4": - version "7.26.9" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.9.tgz#aa4c6facc65b9cb3f87d75125ffd47781b475433" - integrity sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg== - dependencies: - regenerator-runtime "^0.14.0" - -"@babel/template@^7.24.7", "@babel/template@^7.25.9", "@babel/template@^7.26.9", "@babel/template@^7.3.3": +"@babel/template@^7.24.7", "@babel/template@^7.26.9", "@babel/template@^7.3.3": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2" integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA== @@ -1466,7 +839,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8", "@babel/traverse@^7.26.9": +"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.9": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a" integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg== @@ -1487,7 +860,7 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.20.7", "@babel/types@^7.23.0", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.17.0", "@babel/types@^7.20.7", "@babel/types@^7.23.0", "@babel/types@^7.24.7", "@babel/types@^7.25.9", "@babel/types@^7.26.9", "@babel/types@^7.3.3": version "7.26.9" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce" integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw== @@ -3707,30 +3080,6 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" -babel-plugin-polyfill-corejs2@^0.4.10: - version "0.4.12" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz#ca55bbec8ab0edeeef3d7b8ffd75322e210879a9" - integrity sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og== - dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.6.3" - semver "^6.3.1" - -babel-plugin-polyfill-corejs3@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz#4e4e182f1bb37c7ba62e2af81d8dd09df31344f6" - integrity sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.3" - core-js-compat "^3.40.0" - -babel-plugin-polyfill-regenerator@^0.6.1: - version "0.6.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz#abeb1f3f1c762eace37587f42548b08b57789bc8" - integrity sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q== - dependencies: - "@babel/helper-define-polyfill-provider" "^0.6.3" - babel-preset-current-node-syntax@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" @@ -3802,7 +3151,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.24.4: +browserslist@^4.24.0: version "4.24.4" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== @@ -3990,13 +3339,6 @@ convict@^6.2.4: lodash.clonedeep "^4.5.0" yargs-parser "^20.2.7" -core-js-compat@^3.40.0: - version "3.41.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.41.0.tgz#4cdfce95f39a8f27759b667cf693d96e5dda3d17" - integrity sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A== - dependencies: - browserslist "^4.24.4" - create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -5163,11 +4505,6 @@ jsesc@^3.0.2: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== -jsesc@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" - integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== - json-bigint@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" @@ -5232,11 +4569,6 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -5700,54 +5032,6 @@ readable-stream@^4.5.2: process "^0.11.10" string_decoder "^1.3.0" -regenerate-unicode-properties@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" - integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" - integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== - -regenerator-runtime@^0.14.0: - version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" - integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== - -regenerator-transform@^0.15.2: - version "0.15.2" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" - integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== - dependencies: - "@babel/runtime" "^7.8.4" - -regexpu-core@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" - integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.2.0" - regjsgen "^0.8.0" - regjsparser "^0.12.0" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.1.0" - -regjsgen@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" - integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== - -regjsparser@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" - integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== - dependencies: - jsesc "~3.0.2" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5789,7 +5073,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.3.tgz#41955e6f1b4013b7586f873749a635dea07ebe3f" integrity sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A== -resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.8: +resolve@^1.20.0, resolve@^1.22.8: version "1.22.10" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== @@ -6172,29 +5456,6 @@ undici-types@~6.20.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz#cb3173fe47ca743e228216e4a3ddc4c84d628cc2" - integrity sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg== - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" - integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz#a0401aee72714598f739b68b104e4fe3a0cb3c71" - integrity sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg== - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" - integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== - universalify@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"