@langchain/langgraph
Version:
1 lines • 19.8 kB
Source Map (JSON)
{"version":3,"file":"schema.cjs","names":["DeltaValue","getSchemaDefaultGetter","DeltaChannel","ReducedValue","BinaryOperatorAggregate","UntrackedValue","UntrackedValueChannel","isStandardSchema","LastValue","getJsonSchemaFromSchema","_getOverwriteValue","OVERWRITE"],"sources":["../../src/state/schema.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { JSONSchema } from \"@langchain/core/utils/json_schema\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nimport type { RunnableLike } from \"../pregel/runnable_types.js\";\nimport {\n BaseChannel,\n LastValue,\n BinaryOperatorAggregate,\n DeltaChannel,\n} from \"../channels/index.js\";\nimport { UntrackedValueChannel } from \"../channels/untracked_value.js\";\n\nimport type { SerializableSchema } from \"./types.js\";\nimport { isStandardSchema } from \"./types.js\";\nimport { getJsonSchemaFromSchema, getSchemaDefaultGetter } from \"./adapter.js\";\nimport {\n _getOverwriteValue,\n OVERWRITE,\n type OverwriteValue,\n} from \"../constants.js\";\nimport { ReducedValue } from \"./values/reduced.js\";\nimport { UntrackedValue } from \"./values/untracked.js\";\nimport { DeltaValue } from \"./values/delta.js\";\n\nconst STATE_SCHEMA_SYMBOL = Symbol.for(\"langgraph.state.state_schema\");\n\n/**\n * Maps a single StateSchema field definition to its corresponding Channel type.\n *\n * This utility type inspects the type of the field and returns an appropriate\n * `BaseChannel` type, parameterized with the state \"value\" and \"input\" types according to the field's shape.\n *\n * Rules:\n * - If the field (`F`) is a `DeltaValue<V, I>`, the channel will store values of type `V`\n * and accept input of type `I` (or an `Overwrite`).\n * - If the field (`F`) is a `ReducedValue<V, I>`, the channel will store values of type `V`\n * and accept input of type `I`.\n * - If the field is a `UntrackedValue<V>`, the channel will store and accept values of type `V`.\n * - If the field is a `SerializableSchema<I, O>`, the channel will store values of type `O`\n * (the schema's output/validated value) and accept input of type `I`.\n * - For all other types, a generic `BaseChannel<unknown, unknown>` is used as fallback.\n *\n * @template F - The StateSchema field type to map to a Channel type.\n *\n * @example\n * ```typescript\n * type MyField = ReducedValue<number, string>;\n * type ChannelType = StateSchemaFieldToChannel<MyField>;\n * // ChannelType is BaseChannel<number, string>\n * ```\n */\nexport type StateSchemaFieldToChannel<F> =\n F extends DeltaValue<infer V, infer I>\n ? BaseChannel<V, OverwriteValue<V> | I>\n : F extends ReducedValue<infer V, infer I>\n ? BaseChannel<V, OverwriteValue<V> | I>\n : F extends UntrackedValue<infer V>\n ? BaseChannel<V, V>\n : F extends SerializableSchema<infer I, infer O>\n ? BaseChannel<O, I>\n : BaseChannel<unknown, unknown>;\n\n/**\n * Converts StateSchema fields into a strongly-typed\n * State Definition object, where each field is mapped to its channel type.\n *\n * This utility type is used internally to create the shape of the state channels for a given schema,\n * substituting each field with the result of `StateSchemaFieldToChannel`.\n *\n * If you define a state schema as:\n * ```typescript\n * const fields = {\n * a: ReducedValue<number, string>(),\n * b: UntrackedValue<boolean>(),\n * c: SomeSerializableSchemaType, // SerializableSchema<in, out>\n * }\n * ```\n * then `StateSchemaFieldsToStateDefinition<typeof fields>` yields:\n * ```typescript\n * {\n * a: BaseChannel<number, string>;\n * b: BaseChannel<boolean, boolean>;\n * c: BaseChannel<typeof schema's output type, typeof schema's input type>;\n * }\n * ```\n *\n * @template TFields - The mapping of field names to StateSchema field types.\n * @returns An object type mapping field names to channel types.\n *\n * @see StateSchemaFieldToChannel\n */\nexport type StateSchemaFieldsToStateDefinition<\n TFields extends StateSchemaFields,\n> = {\n [K in keyof TFields]: StateSchemaFieldToChannel<TFields[K]>;\n};\n\n/**\n * Valid field types for StateSchema.\n * Either a LangGraph state value type or a raw schema (e.g., Zod schema).\n */\nexport type StateSchemaField<Input = unknown, Output = Input> =\n | DeltaValue<Input, Output>\n | ReducedValue<Input, Output>\n | UntrackedValue<Output>\n | SerializableSchema<Input, Output>;\n\n/**\n * Init object for StateSchema constructor.\n * Uses `any` to allow variance in generic types (e.g., ReducedValue<string, string[]>).\n */\nexport type StateSchemaFields = {\n [key: string]: StateSchemaField<any, any>;\n};\n\n/**\n * Infer the State type from a StateSchemaFields.\n * This is the type of the full state object.\n *\n * - ReducedValue<Value, Input> → Value (the stored type)\n * - UntrackedValue<Value> → Value\n * - SerializableSchema<Input, Output> → Output (the validated type)\n */\nexport type InferStateSchemaValue<TFields extends StateSchemaFields> = {\n [K in keyof TFields]: TFields[K] extends DeltaValue<any, any>\n ? TFields[K][\"ValueType\"]\n : TFields[K] extends ReducedValue<any, any>\n ? TFields[K][\"ValueType\"]\n : TFields[K] extends UntrackedValue<any>\n ? TFields[K][\"ValueType\"]\n : TFields[K] extends SerializableSchema<any, infer TOutput>\n ? TOutput\n : never;\n};\n\n/**\n * Infer the Update type from a StateSchemaFields.\n * This is the type for partial updates to state.\n *\n * - ReducedValue<Value, Input> → Input (the reducer input type)\n * - UntrackedValue<Value> → Value\n * - SerializableSchema<Input, Output> → Input (what you provide)\n */\nexport type InferStateSchemaUpdate<TFields extends StateSchemaFields> = {\n [K in keyof TFields]?: TFields[K] extends DeltaValue<infer V, infer I>\n ? OverwriteValue<V> | I\n : TFields[K] extends ReducedValue<infer V, infer I>\n ? OverwriteValue<V> | I\n : TFields[K] extends UntrackedValue<any>\n ? TFields[K][\"ValueType\"]\n : TFields[K] extends SerializableSchema<infer TInput, any>\n ? TInput\n : never;\n};\n\n/**\n * StateSchema provides a unified API for defining LangGraph state schemas.\n *\n * @example\n * ```ts\n * import { z } from \"zod\";\n * import { StateSchema, ReducedValue, MessagesValue } from \"@langchain/langgraph\";\n *\n * const AgentState = new StateSchema({\n * // Prebuilt messages value\n * messages: MessagesValue,\n * // Basic LastValue channel from any standard schema\n * currentStep: z.string(),\n * // LastValue with native default\n * count: z.number().default(0),\n * // ReducedValue for fields needing reducers\n * history: new ReducedValue(\n * z.array(z.string()).default(() => []),\n * {\n * inputSchema: z.string(),\n * reducer: (current, next) => [...current, next],\n * }\n * ),\n * });\n *\n * // Extract types\n * type State = typeof AgentState.State;\n * type Update = typeof AgentState.Update;\n *\n * // Use in StateGraph\n * const graph = new StateGraph(AgentState);\n * ```\n */\nexport class StateSchema<TFields extends StateSchemaFields> {\n /**\n * Symbol for runtime identification.\n * @internal Used by isInstance for runtime type checking\n */\n // @ts-expect-error - Symbol is read via `in` operator in isInstance\n private readonly [STATE_SCHEMA_SYMBOL] = true;\n\n /**\n * Type declaration for the full state type.\n * Use: `typeof myState.State`\n */\n declare State: InferStateSchemaValue<TFields>;\n\n /**\n * Type declaration for the update type.\n * Use: `typeof myState.Update`\n */\n declare Update: InferStateSchemaUpdate<TFields>;\n\n /**\n * Type declaration for node functions.\n * Use: `typeof myState.Node` to type node functions outside the graph builder.\n *\n * @example\n * ```typescript\n * const AgentState = new StateSchema({\n * count: z.number().default(0),\n * });\n *\n * const myNode: typeof AgentState.Node = (state) => {\n * return { count: state.count + 1 };\n * };\n * ```\n */\n declare Node: RunnableLike<\n InferStateSchemaValue<TFields>,\n InferStateSchemaUpdate<TFields>\n >;\n\n constructor(readonly fields: TFields) {}\n\n /**\n * Get the channel definitions for use with StateGraph.\n * This converts the StateSchema fields into BaseChannel instances.\n */\n getChannels(): Record<string, BaseChannel> {\n const channels: Record<string, BaseChannel> = {};\n\n for (const [key, value] of Object.entries(this.fields)) {\n if (DeltaValue.isInstance(value)) {\n // DeltaValue -> DeltaChannel. The value schema's default (if any) seeds\n // the channel's initial value; otherwise DeltaChannel falls back to [].\n const defaultGetter = getSchemaDefaultGetter(value.valueSchema);\n channels[key] = new DeltaChannel(value.reducer, {\n snapshotFrequency: value.snapshotFrequency,\n initialValueFactory: defaultGetter,\n });\n } else if (ReducedValue.isInstance(value)) {\n // ReducedValue -> BinaryOperatorAggregate\n const defaultGetter = getSchemaDefaultGetter(value.valueSchema);\n channels[key] = new BinaryOperatorAggregate(\n value.reducer,\n defaultGetter\n );\n } else if (UntrackedValue.isInstance(value)) {\n // UntrackedValue -> UntrackedValueChannel\n const defaultGetter = value.schema\n ? getSchemaDefaultGetter(value.schema)\n : undefined;\n channels[key] = new UntrackedValueChannel({\n guard: value.guard,\n initialValueFactory: defaultGetter,\n });\n } else if (isStandardSchema(value)) {\n // Plain schema -> LastValue channel\n const defaultGetter = getSchemaDefaultGetter(value);\n channels[key] = new LastValue(defaultGetter);\n } else {\n throw new Error(\n `Invalid state field \"${key}\": must be a schema, ReducedValue, DeltaValue, UntrackedValue, or ManagedValue`\n );\n }\n }\n\n return channels;\n }\n\n /**\n * Get the JSON schema for the full state type.\n * Used by Studio and API for schema introspection.\n */\n getJsonSchema(): JSONSchema {\n const properties: Record<string, JSONSchema> = {};\n const required: string[] = [];\n\n for (const [key, value] of Object.entries(this.fields)) {\n let fieldSchema: JSONSchema | undefined;\n\n if (DeltaValue.isInstance(value) || ReducedValue.isInstance(value)) {\n fieldSchema = getJsonSchemaFromSchema(value.valueSchema) as JSONSchema;\n // Merge jsonSchemaExtra (e.g. langgraph_type) into the field schema,\n // even if base schema is undefined\n if (value.jsonSchemaExtra) {\n fieldSchema = { ...(fieldSchema ?? {}), ...value.jsonSchemaExtra };\n }\n } else if (UntrackedValue.isInstance(value)) {\n fieldSchema = value.schema\n ? (getJsonSchemaFromSchema(value.schema) as JSONSchema)\n : undefined;\n } else if (isStandardSchema(value)) {\n fieldSchema = getJsonSchemaFromSchema(value) as JSONSchema;\n }\n\n if (fieldSchema) {\n properties[key] = fieldSchema;\n\n // Field is required if it doesn't have a default\n let hasDefault = false;\n if (DeltaValue.isInstance(value) || ReducedValue.isInstance(value)) {\n hasDefault = getSchemaDefaultGetter(value.valueSchema) !== undefined;\n } else if (UntrackedValue.isInstance(value)) {\n hasDefault = value.schema\n ? getSchemaDefaultGetter(value.schema) !== undefined\n : false;\n } else {\n hasDefault = getSchemaDefaultGetter(value) !== undefined;\n }\n\n if (!hasDefault) {\n required.push(key);\n }\n }\n }\n\n return {\n type: \"object\",\n properties,\n required: required.length > 0 ? required : undefined,\n };\n }\n\n /**\n * Get the JSON schema for the update/input type.\n * All fields are optional in updates.\n */\n getInputJsonSchema(): JSONSchema {\n const properties: Record<string, JSONSchema> = {};\n\n for (const [key, value] of Object.entries(this.fields)) {\n let fieldSchema: JSONSchema | undefined;\n\n if (DeltaValue.isInstance(value) || ReducedValue.isInstance(value)) {\n // Use input schema for updates\n fieldSchema = getJsonSchemaFromSchema(value.inputSchema) as JSONSchema;\n // Merge jsonSchemaExtra (e.g. langgraph_type) into the field schema,\n // even if base schema is undefined\n if (value.jsonSchemaExtra) {\n fieldSchema = { ...(fieldSchema ?? {}), ...value.jsonSchemaExtra };\n }\n } else if (UntrackedValue.isInstance(value)) {\n fieldSchema = value.schema\n ? (getJsonSchemaFromSchema(value.schema) as JSONSchema)\n : undefined;\n } else if (isStandardSchema(value)) {\n fieldSchema = getJsonSchemaFromSchema(value) as JSONSchema;\n }\n\n if (fieldSchema) {\n properties[key] = fieldSchema;\n }\n }\n\n return {\n type: \"object\",\n properties,\n };\n }\n\n /**\n * Get the list of channel keys (excluding managed values).\n */\n getChannelKeys(): string[] {\n return Object.entries(this.fields).map(([key]) => key);\n }\n\n /**\n * Get all keys (channels + managed values).\n */\n getAllKeys(): string[] {\n return Object.keys(this.fields);\n }\n\n /**\n * Validate input data against the schema.\n * This validates each field using its corresponding schema.\n *\n * @param data - The input data to validate\n * @returns The validated data with coerced types\n */\n async validateInput<T>(data: T): Promise<T> {\n if (data == null || typeof data !== \"object\") {\n return data;\n }\n\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(data)) {\n const fieldDef = this.fields[key];\n\n if (fieldDef === undefined) {\n // Unknown field, pass through\n result[key] = value;\n continue;\n }\n\n // Get the schema to use for validation\n let schema: StandardSchemaV1 | undefined;\n\n if (\n DeltaValue.isInstance(fieldDef) ||\n ReducedValue.isInstance(fieldDef)\n ) {\n const [isOverwrite, overwriteValue] = _getOverwriteValue(value);\n if (isOverwrite) {\n schema = fieldDef.valueSchema;\n const validationResult =\n await schema[\"~standard\"].validate(overwriteValue);\n if (validationResult.issues) {\n throw new Error(\n `Validation failed for field \"${key}\": ${JSON.stringify(\n validationResult.issues\n )}`\n );\n }\n result[key] = { [OVERWRITE]: validationResult.value };\n continue;\n }\n\n schema = fieldDef.inputSchema;\n } else if (UntrackedValue.isInstance(fieldDef)) {\n schema = fieldDef.schema;\n } else if (isStandardSchema(fieldDef)) {\n schema = fieldDef;\n }\n\n if (schema) {\n // Validate using standard schema\n const validationResult = await schema[\"~standard\"].validate(value);\n if (validationResult.issues) {\n throw new Error(\n `Validation failed for field \"${key}\": ${JSON.stringify(\n validationResult.issues\n )}`\n );\n }\n result[key] = validationResult.value;\n } else {\n // No schema or not a standard schema, pass through\n result[key] = value;\n }\n }\n\n return result as T;\n }\n\n /**\n * Type guard to check if a value is a StateSchema instance.\n *\n * @param value - The value to check.\n * @returns True if the value is a StateSchema instance with the correct runtime tag.\n */\n static isInstance<TFields extends StateSchemaFields>(\n value: StateSchema<TFields>\n ): value is StateSchema<TFields>;\n\n static isInstance(value: unknown): value is StateSchema<any>;\n\n static isInstance<TFields extends StateSchemaFields>(\n value: unknown\n ): value is StateSchema<TFields> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n STATE_SCHEMA_SYMBOL in value &&\n value[STATE_SCHEMA_SYMBOL] === true\n );\n }\n}\n\nexport type AnyStateSchema = StateSchema<any>;\n"],"mappings":"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"}