diff --git a/.changeset/honest-lions-observe.md b/.changeset/honest-lions-observe.md new file mode 100644 index 0000000000..d4873b4c1e --- /dev/null +++ b/.changeset/honest-lions-observe.md @@ -0,0 +1,5 @@ +--- +'@posthog/ai': minor +--- + +Add LangChain v1 agent middleware for AI observability. diff --git a/packages/ai/package.json b/packages/ai/package.json index 527f159586..05002bdae1 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -14,7 +14,7 @@ "scripts": { "clean": "rimraf dist", "test:unit": "jest && pnpm test:module-load", - "test:module-load": "node tests/otel-module-load.cjs", + "test:module-load": "node tests/otel-module-load.cjs && node tests/langchain-module-load.cjs", "lint": "eslint src tests", "lint:fix": "eslint src tests --fix", "build": "rollup -c", @@ -40,6 +40,7 @@ "@posthog-tooling/tsconfig-base": "workspace:*", "@types/jest": "catalog:", "jest": "catalog:", + "langchain": "^1.5.5", "node-fetch": "^3.3.2", "posthog-node": "workspace:^" }, @@ -71,6 +72,7 @@ "@opentelemetry/sdk-trace-base": "^2.0.0", "@ai-sdk/provider": "^2.0.0 || ^3.0.0 || ^4.0.0", "@openai/agents": "^0.8.0", + "langchain": ">=1.5.5 <2.0.0", "posthog-node": "^5.0.0" }, "peerDependenciesMeta": { @@ -83,6 +85,9 @@ "@langchain/core": { "optional": true }, + "langchain": { + "optional": true + }, "openai": { "optional": true }, @@ -141,6 +146,11 @@ "import": "./dist/langchain/index.mjs", "types": "./dist/langchain/index.d.ts" }, + "./langchain/middleware": { + "require": "./dist/langchain/middleware/index.cjs", + "import": "./dist/langchain/middleware/index.mjs", + "types": "./dist/langchain/middleware/index.d.ts" + }, "./openai-agents": { "require": "./dist/openai-agents/index.cjs", "import": "./dist/openai-agents/index.mjs", diff --git a/packages/ai/rollup.config.mjs b/packages/ai/rollup.config.mjs index bdb31441ea..db768f1273 100644 --- a/packages/ai/rollup.config.mjs +++ b/packages/ai/rollup.config.mjs @@ -36,7 +36,7 @@ configs.push({ }) // Add submodule builds for posthog-ai -const providers = ['anthropic', 'openai', 'vercel', 'langchain', 'gemini', 'otel', 'openai-agents'] +const providers = ['anthropic', 'openai', 'vercel', 'langchain', 'langchain/middleware', 'gemini', 'otel', 'openai-agents'] providers.forEach((provider) => { configs.push({ diff --git a/packages/ai/src/langchain/callbacks.ts b/packages/ai/src/langchain/callbacks.ts index 6ce1b0f940..8ff36047f5 100644 --- a/packages/ai/src/langchain/callbacks.ts +++ b/packages/ai/src/langchain/callbacks.ts @@ -53,6 +53,16 @@ type RunMetadata = SpanMetadata | GenerationMetadata /** Storage for run metadata */ type RunMetadataStorage = { [runId: string]: RunMetadata } +export interface LangChainCallbackHandlerOptions { + client: PostHog + distinctId?: string | number + traceId?: string | number + properties?: Record + privacyMode?: boolean + groups?: Record + debug?: boolean +} + export class LangChainCallbackHandler extends BaseCallbackHandler { public name = 'PosthogCallbackHandler' private client: PostHog @@ -66,15 +76,7 @@ export class LangChainCallbackHandler extends BaseCallbackHandler { private runs: RunMetadataStorage = {} private parentTree: { [runId: string]: string } = {} - constructor(options: { - client: PostHog - distinctId?: string | number - traceId?: string | number - properties?: Record - privacyMode?: boolean - groups?: Record - debug?: boolean - }) { + constructor(options: LangChainCallbackHandlerOptions) { if (!options.client) { throw new Error('PostHog client is required') } @@ -98,11 +100,15 @@ export class LangChainCallbackHandler extends BaseCallbackHandler { tags?: string[], metadata?: Record, _runType?: string, - runName?: string + runName?: string, + extra?: Record ): void { this._logDebugEvent('on_chain_start', runId, parentRunId, { inputs, tags }) this._setParentOfRun(runId, parentRunId) this._setTraceOrSpanMetadata(chain, inputs, runId, parentRunId, metadata, tags, runName) + if (typeof extra?.posthogStartTime === 'number' && Number.isFinite(extra.posthogStartTime)) { + this.runs[runId].startTime = extra.posthogStartTime + } } public handleChainEnd( @@ -379,7 +385,7 @@ export class LangChainCallbackHandler extends BaseCallbackHandler { return this.traceId ? String(this.traceId) : this._findRootRun(runId) } - private _getParentRunId(traceId: string, _runId: string, parentRunId?: string): string | undefined { + protected _getParentRunId(traceId: string, _runId: string, parentRunId?: string): string | undefined { // Replace the parent-run if not found in our stored parent tree. if (parentRunId && !this.parentTree[parentRunId]) { return traceId diff --git a/packages/ai/src/langchain/middleware/index.ts b/packages/ai/src/langchain/middleware/index.ts new file mode 100644 index 0000000000..725344d0ad --- /dev/null +++ b/packages/ai/src/langchain/middleware/index.ts @@ -0,0 +1,203 @@ +import { AIMessage, BaseMessage, ToolMessage } from '@langchain/core/messages' +import type { ChatGeneration, LLMResult } from '@langchain/core/outputs' +import type { Serialized } from '@langchain/core/load/serializable' +import { createMiddleware } from 'langchain' +import { v7 as uuidv7 } from 'uuid' +import { z } from 'zod' +import { toContentString } from '../../utils' +import { LangChainCallbackHandler, LangChainCallbackHandlerOptions } from '../callbacks' + +const postHogStateSchema = z.object({ + _posthogRunId: z.string().optional(), + _posthogStartTime: z.number().optional(), + _posthogInput: z.record(z.string(), z.unknown()).optional(), +}) + +type PostHogState = z.infer + +class LangChainMiddlewareCallbackHandler extends LangChainCallbackHandler { + protected override _getParentRunId(_traceId: string, _runId: string, parentRunId?: string): string | undefined { + return parentRunId + } +} + +const withoutPostHogState = >(state: T): Omit => { + const { _posthogRunId: _, _posthogStartTime: __, _posthogInput: ___, ...rest } = state + return rest +} + +const getRunId = (state: PostHogState): string => state._posthogRunId ?? uuidv7() + +const stringify = (value: unknown): string => { + try { + return JSON.stringify(value) ?? String(value) + } catch { + try { + return String(value) + } catch { + return '' + } + } +} + +const toError = (error: unknown): Error => (error instanceof Error ? error : new Error(stringify(error))) + +const safely = (callback: () => void): void => { + try { + callback() + } catch { + // Telemetry must never affect the LangChain middleware lifecycle. + } +} + +const serializeModel = (model: unknown): Serialized => { + if (model && typeof model === 'object' && 'toJSON' in model && typeof model.toJSON === 'function') { + try { + return model.toJSON() as Serialized + } catch { + // Fall back to a minimal LangChain serialization below. + } + } + + return { lc: 1, type: 'constructor', id: ['langchain', 'chat_models', 'unknown'], kwargs: {} } +} + +const getModelMetadata = (model: unknown, modelSettings: unknown): Record | undefined => { + if (model && typeof model === 'object' && 'getLsParams' in model && typeof model.getLsParams === 'function') { + try { + return model.getLsParams(modelSettings) as Record + } catch { + return undefined + } + } + return undefined +} + +const toLLMResult = (response: unknown): LLMResult => { + if (!AIMessage.isInstance(response)) { + return { generations: [] } + } + + const generation: ChatGeneration = { + text: toContentString(response.content), + message: response, + } + return { generations: [[generation]] } +} + +/** Options shared with the LangChain callback integration. */ +export type PostHogLangChainMiddlewareOptions = LangChainCallbackHandlerOptions + +/** + * Creates PostHog AI observability middleware for LangChain v1 agents. + * + * Use either this middleware or `LangChainCallbackHandler`, not both, to avoid + * capturing the same model and tool calls twice. + * + * LangChain only invokes `afterAgent` for completed runs. A terminal agent + * failure still captures the failed model or tool call, but not a root trace. + */ +export const createPostHogMiddleware = (options: PostHogLangChainMiddlewareOptions) => { + const callback = new LangChainMiddlewareCallbackHandler(options) + + return createMiddleware({ + name: 'PostHogMiddleware', + stateSchema: postHogStateSchema, + + beforeAgent: (state) => { + return { + _posthogRunId: uuidv7(), + _posthogStartTime: Date.now(), + _posthogInput: withoutPostHogState(state), + } + }, + + afterAgent: (state) => { + safely(() => { + const runId = getRunId(state) + callback.handleChainStart( + { lc: 1, type: 'constructor', id: ['langchain', 'agents', 'PostHogMiddleware'], kwargs: {} }, + state._posthogInput ?? withoutPostHogState(state), + runId, + undefined, + undefined, + undefined, + undefined, + 'LangChain Agent', + { posthogStartTime: state._posthogStartTime } + ) + callback.handleChainEnd(withoutPostHogState(state), runId) + }) + }, + + wrapModelCall: async (request, handler) => { + const runId = uuidv7() + const parentRunId = getRunId(request.state) + const messages = [request.systemMessage, ...request.messages].filter( + (message): message is BaseMessage => message !== undefined + ) + const invocationParams = { + ...request.modelSettings, + tools: request.tools, + } + + safely(() => + callback.handleChatModelStart( + serializeModel(request.model), + [messages], + runId, + parentRunId, + { invocation_params: invocationParams }, + undefined, + getModelMetadata(request.model, request.modelSettings) + ) + ) + + try { + const response = await handler(request) + safely(() => callback.handleLLMEnd(toLLMResult(response), runId, parentRunId)) + return response + } catch (error) { + safely(() => callback.handleLLMError(toError(error), runId, parentRunId)) + throw error + } + }, + + wrapToolCall: async (request, handler) => { + const runId = uuidv7() + const parentRunId = getRunId(request.state) + const toolName = String(request.tool?.name ?? request.toolCall.name) + const serializedTool: Serialized = { + lc: 1, + type: 'constructor', + id: ['langchain', 'tools', toolName], + kwargs: {}, + } + + safely(() => + callback.handleToolStart( + serializedTool, + stringify(request.toolCall.args), + runId, + parentRunId, + undefined, + undefined, + toolName + ) + ) + + try { + const result = await handler(request) + if (ToolMessage.isInstance(result) && result.status === 'error') { + safely(() => callback.handleToolError(new Error(toContentString(result.content)), runId, parentRunId)) + } else { + safely(() => callback.handleToolEnd(result, runId, parentRunId)) + } + return result + } catch (error) { + safely(() => callback.handleToolError(toError(error), runId, parentRunId)) + throw error + } + }, + }) +} diff --git a/packages/ai/tests/langchain-middleware.test.ts b/packages/ai/tests/langchain-middleware.test.ts new file mode 100644 index 0000000000..6dc571f840 --- /dev/null +++ b/packages/ai/tests/langchain-middleware.test.ts @@ -0,0 +1,248 @@ +import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages' +import type { PostHog } from 'posthog-node' +import { createAgent, FakeToolCallingModel, tool } from 'langchain' +import { z } from 'zod' +import { LangChainCallbackHandler } from '../src/langchain/callbacks' +import { createPostHogMiddleware } from '../src/langchain/middleware' + +const mockPostHogClient = { + capture: jest.fn(), +} as unknown as PostHog + +const model = { + toJSON: () => ({ id: ['langchain', 'chat_models', 'FakeChatModel'], kwargs: {} }), + getLsParams: () => ({ ls_model_name: 'test-model', ls_provider: 'test-provider' }), +} + +const runtime = {} + +const modelRequest = (state: Record) => ({ + model, + messages: [new HumanMessage('Hello')], + tools: [], + state, + runtime, +}) + +const toolRequest = (state: Record) => ({ + toolCall: { id: 'tool-call-id', name: 'weather', args: { city: 'London' }, type: 'tool_call' as const }, + tool: { name: 'weather' }, + state, + runtime, +}) + +describe('createPostHogMiddleware', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('captures an agent trace with model and tool children', async () => { + const middleware = createPostHogMiddleware({ + client: mockPostHogClient, + distinctId: 'user-id', + properties: { environment: 'test' }, + }) as any + const initialState = { messages: [new HumanMessage('Hello')] } + const privateState = middleware.beforeAgent(initialState, runtime) + const state = { ...initialState, ...privateState } + const modelResponse = new AIMessage({ + content: 'Checking the weather', + usage_metadata: { input_tokens: 10, output_tokens: 4, total_tokens: 14 }, + response_metadata: { finish_reason: 'tool_calls' }, + }) + const toolResponse = new ToolMessage({ + content: 'Sunny', + tool_call_id: 'tool-call-id', + name: 'weather', + }) + + await expect(middleware.wrapModelCall(modelRequest(state), () => Promise.resolve(modelResponse))).resolves.toBe( + modelResponse + ) + await expect(middleware.wrapToolCall(toolRequest(state), () => Promise.resolve(toolResponse))).resolves.toBe( + toolResponse + ) + middleware.afterAgent({ ...state, messages: [...state.messages, modelResponse, toolResponse] }, runtime) + + const events = (mockPostHogClient.capture as jest.Mock).mock.calls.map(([event]) => event) + expect(events.map(({ event }) => event)).toEqual(['$ai_generation', '$ai_span', '$ai_trace']) + + const [generation, toolSpan, trace] = events + expect(generation.properties).toMatchObject({ + $ai_trace_id: trace.properties.$ai_trace_id, + $ai_parent_id: trace.properties.$ai_span_id, + $ai_model: 'test-model', + $ai_provider: 'test-provider', + $ai_input_tokens: 10, + $ai_output_tokens: 4, + environment: 'test', + }) + expect(toolSpan.properties).toMatchObject({ + $ai_trace_id: trace.properties.$ai_trace_id, + $ai_parent_id: trace.properties.$ai_span_id, + $ai_span_name: 'weather', + environment: 'test', + }) + expect(trace.distinctId).toBe('user-id') + expect(trace.properties.$ai_output_state).not.toHaveProperty('_posthogRunId') + }) + + it('instruments a LangChain v1 agent without exposing private state', async () => { + const weather = tool(async ({ city }) => `Sunny in ${city}`, { + name: 'weather', + description: 'Get the weather for a city', + schema: z.object({ city: z.string() }), + }) + const agent = createAgent({ + model: new FakeToolCallingModel({ + toolCalls: [[{ id: 'weather-call', name: 'weather', args: { city: 'London' } }], []], + }), + tools: [weather], + middleware: [createPostHogMiddleware({ client: mockPostHogClient })], + }) + + const result = await agent.invoke({ messages: [new HumanMessage('Hello')] }) + + expect(result).not.toHaveProperty('_posthogRunId') + expect((mockPostHogClient.capture as jest.Mock).mock.calls.map(([event]) => event.event)).toEqual([ + '$ai_generation', + '$ai_span', + '$ai_generation', + '$ai_trace', + ]) + }) + + it('does not retain an unfinished root run when an agent fails', async () => { + const error = new Error('model failed') + class FailingModel extends FakeToolCallingModel { + bindTools(): this { + return this + } + + async _generate(): Promise { + throw error + } + } + + const chainStart = jest.spyOn(LangChainCallbackHandler.prototype, 'handleChainStart') + const agent = createAgent({ + model: new FailingModel(), + tools: [], + middleware: [createPostHogMiddleware({ client: mockPostHogClient })], + }) + + await expect(agent.invoke({ messages: [new HumanMessage('Hello')] })).rejects.toThrow('model failed') + + expect(chainStart).not.toHaveBeenCalled() + expect((mockPostHogClient.capture as jest.Mock).mock.calls.map(([event]) => event.event)).toEqual([ + '$ai_generation', + ]) + }) + + it('returns and throws the exact values produced by handlers', async () => { + const middleware = createPostHogMiddleware({ client: mockPostHogClient }) as any + const privateState = middleware.beforeAgent({ messages: [] }, runtime) + const response = new AIMessage('Hello') + const modelError = new Error('model failed') + const error = new Error('tool failed') + + await expect(middleware.wrapModelCall(modelRequest(privateState), () => Promise.resolve(response))).resolves.toBe( + response + ) + await expect(middleware.wrapModelCall(modelRequest(privateState), () => Promise.reject(modelError))).rejects.toBe( + modelError + ) + await expect(middleware.wrapToolCall(toolRequest(privateState), () => Promise.reject(error))).rejects.toBe(error) + + const toolEvent = (mockPostHogClient.capture as jest.Mock).mock.calls + .map(([event]) => event) + .find(({ event }) => event === '$ai_span') + expect(toolEvent.properties).toMatchObject({ + $ai_is_error: true, + $ai_error: expect.stringContaining('tool failed'), + }) + }) + + it('keeps concurrent agent invocations on separate traces', async () => { + const middleware = createPostHogMiddleware({ client: mockPostHogClient }) as any + const stateA = { messages: [new HumanMessage('A')], ...middleware.beforeAgent({ messages: [] }, runtime) } + const stateB = { messages: [new HumanMessage('B')], ...middleware.beforeAgent({ messages: [] }, runtime) } + + await Promise.all([ + middleware.wrapModelCall(modelRequest(stateA), () => Promise.resolve(new AIMessage('A response'))), + middleware.wrapModelCall(modelRequest(stateB), () => Promise.resolve(new AIMessage('B response'))), + ]) + middleware.afterAgent(stateB, runtime) + middleware.afterAgent(stateA, runtime) + + const events = (mockPostHogClient.capture as jest.Mock).mock.calls.map(([event]) => event) + const traceIds = events + .filter(({ event }) => event === '$ai_trace') + .map(({ properties }) => properties.$ai_trace_id) + const generationTraceIds = events + .filter(({ event }) => event === '$ai_generation') + .map(({ properties }) => properties.$ai_trace_id) + + expect(new Set(traceIds).size).toBe(2) + expect(new Set(generationTraceIds)).toEqual(new Set(traceIds)) + }) + + it('keeps unique run IDs when concurrent agents share an explicit trace ID', async () => { + const middleware = createPostHogMiddleware({ client: mockPostHogClient, traceId: 'shared-trace' }) as any + const stateA = middleware.beforeAgent({ messages: [] }, runtime) + const stateB = middleware.beforeAgent({ messages: [] }, runtime) + + await Promise.all([ + middleware.wrapModelCall(modelRequest(stateA), () => Promise.resolve(new AIMessage('A response'))), + middleware.wrapModelCall(modelRequest(stateB), () => Promise.resolve(new AIMessage('B response'))), + ]) + middleware.afterAgent(stateA, runtime) + middleware.afterAgent(stateB, runtime) + + const events = (mockPostHogClient.capture as jest.Mock).mock.calls.map(([event]) => event) + const traces = events.filter(({ event }) => event === '$ai_trace') + const generations = events.filter(({ event }) => event === '$ai_generation') + expect(traces).toHaveLength(2) + expect(new Set(traces.map(({ properties }) => properties.$ai_span_id)).size).toBe(2) + expect(events.every(({ properties }) => properties.$ai_trace_id === 'shared-trace')).toBe(true) + expect(new Set(generations.map(({ properties }) => properties.$ai_parent_id))).toEqual( + new Set(traces.map(({ properties }) => properties.$ai_span_id)) + ) + }) + + it('applies privacy mode to agent and model content', async () => { + const middleware = createPostHogMiddleware({ client: mockPostHogClient, privacyMode: true }) as any + const initialState = { messages: [new HumanMessage('private input')] } + const state = { ...initialState, ...middleware.beforeAgent(initialState, runtime) } + + await middleware.wrapModelCall(modelRequest(state), () => Promise.resolve(new AIMessage('private output'))) + middleware.afterAgent(state, runtime) + + const [generation, trace] = (mockPostHogClient.capture as jest.Mock).mock.calls.map(([event]) => event) + expect(generation.properties).toMatchObject({ $ai_input: null, $ai_output_choices: null }) + expect(trace.properties).toMatchObject({ $ai_input_state: null, $ai_output_state: null }) + }) + + it('marks returned tool error messages as failed without changing them', async () => { + const middleware = createPostHogMiddleware({ client: mockPostHogClient }) as any + const state = middleware.beforeAgent({ messages: [] }, runtime) + const response = new ToolMessage({ + content: 'Invalid arguments', + tool_call_id: 'tool-call-id', + name: 'weather', + status: 'error', + }) + + await expect(middleware.wrapToolCall(toolRequest(state), () => Promise.resolve(response))).resolves.toBe(response) + + const event = (mockPostHogClient.capture as jest.Mock).mock.calls[0][0] + expect(event.properties).toMatchObject({ + $ai_is_error: true, + $ai_error: expect.stringContaining('Invalid arguments'), + }) + }) +}) diff --git a/packages/ai/tests/langchain-module-load.cjs b/packages/ai/tests/langchain-module-load.cjs new file mode 100644 index 0000000000..05d02e0259 --- /dev/null +++ b/packages/ai/tests/langchain-module-load.cjs @@ -0,0 +1,23 @@ +const Module = require('node:module') +const { readFileSync } = require('node:fs') +const { join } = require('node:path') + +for (const entry of ['index.cjs', 'index.mjs']) { + const source = readFileSync(join(__dirname, '..', 'dist', 'langchain', entry), 'utf8') + if (/\b(?:from\s+|require\()['"]langchain(?:[/'"])/.test(source)) { + throw new Error(`The callback-only ${entry} bundle must not import langchain`) + } +} + +const resolveFilename = Module._resolveFilename +Module._resolveFilename = function (request, ...args) { + if (request === 'langchain' || request.startsWith('langchain/')) { + throw new Error('The callback-only entry point must not load langchain') + } + return resolveFilename.call(this, request, ...args) +} + +const { LangChainCallbackHandler } = require('../dist/langchain/index.cjs') +if (typeof LangChainCallbackHandler !== 'function') { + throw new Error('LangChainCallbackHandler was not exported') +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d052305ad..19bb3d8df0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,6 +271,9 @@ importers: jest: specifier: 'catalog:' version: 29.7.0(@types/node@22.20.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.8.2)) + langchain: + specifier: ^1.5.5 + version: 1.5.9(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3))(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.40(typescript@5.8.2))(ws@8.21.3) node-fetch: specifier: ^3.3.2 version: 3.3.2 @@ -10625,6 +10628,9 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} @@ -12054,6 +12060,10 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-network-error@1.3.2: + resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} + engines: {node: '>=16'} + is-node-process@1.2.0: resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} @@ -14301,14 +14311,26 @@ packages: resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} + p-queue@9.3.3: + resolution: {integrity: sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==} + engines: {node: '>=20'} + p-retry@4.6.2: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} + p-retry@7.1.1: + resolution: {integrity: sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==} + engines: {node: '>=20'} + p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} + p-timeout@7.0.1: + resolution: {integrity: sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==} + engines: {node: '>=20'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -18543,6 +18565,46 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + '@langchain/langgraph-checkpoint@1.1.3': + resolution: {integrity: sha512-wgzdQNeEsdw1e+4lvlj0tdq/RYR/k1vPin10g0ymGoehZDDgd9nvIllGXSXN4TFgF9sf5qQP/KTkOcLfeseIhA==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': ^1.1.48 + + '@langchain/langgraph-sdk@1.9.29': + resolution: {integrity: sha512-W+ccugM5EzBHvaOieyYxlSbhAy6HWF8Tyn6b/BlTWuFd8xtcnQOz2WuFyofcAc9+aQHE+6yHJfIywGOvjxS+bA==} + peerDependencies: + '@langchain/core': ^1.1.48 + react: ^18 || ^19 + react-dom: ^18 || ^19 + svelte: ^4.0.0 || ^5.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + svelte: + optional: true + vue: + optional: true + + '@langchain/langgraph@1.4.10': + resolution: {integrity: sha512-QBNbbIWDp3pcyvaINGEYHdekGYSQm6ZYuC9/h7arEAZckAl5mRLaoRGw5t61V4Bf+rspEj2VcylqNAN3fcdBNQ==} + engines: {node: '>=18'} + peerDependencies: + '@langchain/core': ^1.1.48 + zod: ^3.25.32 || ^4.2.0 + + '@langchain/protocol@0.0.18': + resolution: {integrity: sha512-XW1egQtPfsGI41w2AMZNFZrUIwFSQHTjVMZs0OaTpCAvht/QLoaPN8FQcsysMVypOhupG28J29yOorrc70otBQ==} + + langchain@1.5.9: + resolution: {integrity: sha512-267IPLnoCtyMbIl1ABedQHlJDUqboW7dUWYCQS0HXjUvQnUaYV+l7OIXaQnJUaodS+fDtk774Oo03n1NLHrPFA==} + engines: {node: '>=20'} + peerDependencies: + '@langchain/core': ^1.2.8 + snapshots: @@ -31191,6 +31253,8 @@ snapshots: eventemitter3@4.0.7: {} + eventemitter3@5.0.4: {} + events-universal@1.0.1: dependencies: bare-events: 2.7.0 @@ -32928,6 +32992,8 @@ snapshots: is-negative-zero@2.0.3: {} + is-network-error@1.3.2: {} + is-node-process@1.2.0: {} is-number-object@1.1.1: @@ -37057,15 +37123,26 @@ snapshots: eventemitter3: 4.0.7 p-timeout: 3.2.0 + p-queue@9.3.3: + dependencies: + eventemitter3: 5.0.4 + p-timeout: 7.0.1 + p-retry@4.6.2: dependencies: '@types/retry': 0.12.0 retry: 0.13.1 + p-retry@7.1.1: + dependencies: + is-network-error: 1.3.2 + p-timeout@3.2.0: dependencies: p-finally: 1.0.0 + p-timeout@7.0.1: {} + p-try@2.2.0: {} pac-proxy-agent@7.2.0: @@ -38406,10 +38483,8 @@ snapshots: which: 2.0.2 yargs: 17.7.2 transitivePeerDependencies: - - bufferutil - supports-color - typescript - - utf-8-validate react-native-codegen@0.69.2(@babel/preset-env@7.28.5(@babel/core@7.28.5)): dependencies: @@ -42440,3 +42515,52 @@ snapshots: zod@4.1.13: {} zod@4.4.3: {} + '@langchain/langgraph-checkpoint@1.1.3(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3))': + dependencies: + '@langchain/core': 1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3) + + '@langchain/langgraph-sdk@1.9.29(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.40(typescript@5.8.2))': + dependencies: + '@langchain/core': 1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3) + '@langchain/protocol': 0.0.18 + '@types/json-schema': 7.0.15 + p-queue: 9.3.3 + p-retry: 7.1.1 + optionalDependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + vue: 3.5.40(typescript@5.8.2) + + '@langchain/langgraph@1.4.10(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.40(typescript@5.8.2))(zod@4.4.3)': + dependencies: + '@langchain/core': 1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3)) + '@langchain/langgraph-sdk': 1.9.29(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.40(typescript@5.8.2)) + '@langchain/protocol': 0.0.18 + '@standard-schema/spec': 1.1.0 + zod: 4.4.3 + transitivePeerDependencies: + - react + - react-dom + - svelte + - vue + + '@langchain/protocol@0.0.18': {} + + langchain@1.5.9(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3))(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.40(typescript@5.8.2))(ws@8.21.3): + dependencies: + '@langchain/core': 1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3) + '@langchain/langgraph': 1.4.10(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vue@3.5.40(typescript@5.8.2))(zod@4.4.3) + '@langchain/langgraph-checkpoint': 1.1.3(@langchain/core@1.2.8(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3)) + langsmith: 0.8.11(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.49.0(@aws-sdk/credential-provider-node@3.972.30)(@smithy/signature-v4@5.7.2)(ws@8.21.3)(zod@4.1.13))(ws@8.21.3) + zod: 4.4.3 + transitivePeerDependencies: + - '@opentelemetry/api' + - '@opentelemetry/exporter-trace-otlp-proto' + - '@opentelemetry/sdk-trace-base' + - openai + - react + - react-dom + - svelte + - vue + - ws