Remove sdks

This commit is contained in:
Nuno Campos
2025-03-01 10:13:36 -08:00
parent 3458a3cecb
commit e0fb56c6a3
41 changed files with 0 additions and 16370 deletions
-16
View File
@@ -1,16 +0,0 @@
index.cjs
index.js
index.d.ts
index.d.cts
client.cjs
client.js
client.d.ts
client.d.cts
react.cjs
react.js
react.d.ts
react.d.cts
node_modules
dist
.yarn
docs
-1
View File
@@ -1 +0,0 @@
{}
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 LangChain, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-64
View File
@@ -1,64 +0,0 @@
# LangGraph JS/TS SDK
This repository contains the JS/TS SDK for interacting with the LangGraph REST API.
## Quick Start
To get started with the JS/TS SDK, [install the package](https://www.npmjs.com/package/@langchain/langgraph-sdk)
```bash
yarn add @langchain/langgraph-sdk
```
You will need a running LangGraph API server. If you're running a server locally using `langgraph-cli`, SDK will automatically point at `http://localhost:8123`, otherwise
you would need to specify the server URL when creating a client.
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client();
// List all assistants
const assistants = await client.assistants.search({
metadata: null,
offset: 0,
limit: 10,
});
// We auto-create an assistant for each graph you register in config.
const agent = assistants[0];
// Start a new thread
const thread = await client.threads.create();
// Start a streaming run
const messages = [{ role: "human", content: "what's the weather in la" }];
const streamResponse = client.runs.stream(
thread["thread_id"],
agent["assistant_id"],
{
input: { messages },
}
);
for await (const chunk of streamResponse) {
console.log(chunk);
}
```
## Documentation
To generate documentation, run the following commands:
1. Generate docs.
yarn typedoc
1. Consolidate doc files into one markdown file.
npx concat-md --decrease-title-levels --ignore=js_ts_sdk_ref.md --start-title-level-at 2 docs > docs/js_ts_sdk_ref.md
1. Copy `js_ts_sdk_ref.md` to MkDocs directory.
cp docs/js_ts_sdk_ref.md ../../docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md
-17
View File
@@ -1,17 +0,0 @@
/** @type {import('jest').Config} */
export default {
preset: 'ts-jest',
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};
-20
View File
@@ -1,20 +0,0 @@
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
/**
* @param {string} relativePath
* @returns {string}
*/
function abs(relativePath) {
return resolve(dirname(fileURLToPath(import.meta.url)), relativePath);
}
export const config = {
internals: [/react/],
entrypoints: { index: "index", client: "client", react: "react/index" },
tsConfigPath: resolve("./tsconfig.json"),
cjsSource: "./dist-cjs",
cjsDestination: "./dist",
additionalGitignorePaths: ["docs"],
abs,
};
-99
View File
@@ -1,99 +0,0 @@
{
"name": "@langchain/langgraph-sdk",
"version": "0.0.45",
"description": "Client library for interacting with the LangGraph API",
"type": "module",
"packageManager": "yarn@1.22.19",
"scripts": {
"clean": "rm -rf dist/ dist-cjs/",
"build": "yarn clean && yarn lc_build --create-entrypoints --pre --tree-shaking",
"prepublish": "yarn run build",
"format": "prettier --write src",
"lint": "prettier --check src && tsc --noEmit",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts",
"typedoc": "typedoc && typedoc src/react/index.ts --out docs/react --options typedoc.react.json"
},
"main": "index.js",
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.15",
"p-queue": "^6.6.2",
"p-retry": "4",
"uuid": "^9.0.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@langchain/core": "^0.3.31",
"@langchain/scripts": "^0.1.4",
"@tsconfig/recommended": "^1.0.2",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.12",
"@types/uuid": "^9.0.1",
"@types/react": "18.3.2",
"concat-md": "^0.5.1",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"typedoc": "^0.27.7",
"typedoc-plugin-markdown": "^4.4.2",
"typescript": "^5.4.5",
"react": "^18.3.1"
},
"peerDependencies": {
"react": "^18 || ^19",
"@langchain/core": ">=0.2.31 <0.4.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"@langchain/core": {
"optional": true
}
},
"exports": {
".": {
"types": {
"import": "./index.d.ts",
"require": "./index.d.cts",
"default": "./index.d.ts"
},
"import": "./index.js",
"require": "./index.cjs"
},
"./client": {
"types": {
"import": "./client.d.ts",
"require": "./client.d.cts",
"default": "./client.d.ts"
},
"import": "./client.js",
"require": "./client.cjs"
},
"./react": {
"types": {
"import": "./react.d.ts",
"require": "./react.d.cts",
"default": "./react.d.ts"
},
"import": "./react.js",
"require": "./react.cjs"
},
"./package.json": "./package.json"
},
"files": [
"dist/",
"index.cjs",
"index.js",
"index.d.ts",
"index.d.cts",
"client.cjs",
"client.js",
"client.d.ts",
"client.d.cts",
"react.cjs",
"react.js",
"react.d.ts",
"react.d.cts"
]
}
File diff suppressed because it is too large Load Diff
-50
View File
@@ -1,50 +0,0 @@
export { Client } from "./client.js";
export type {
Assistant,
AssistantVersion,
AssistantGraph,
Config,
DefaultValues,
GraphSchema,
Metadata,
Run,
Thread,
ThreadTask,
ThreadState,
ThreadStatus,
Cron,
Checkpoint,
Interrupt,
ListNamespaceResponse,
Item,
SearchItem,
SearchItemsResponse,
CronCreateResponse,
CronCreateForThreadResponse,
} from "./schema.js";
export { overrideFetchImplementation } from "./singletons/fetch.js";
export type { OnConflictBehavior, Command } from "./types.js";
export type { StreamMode } from "./types.stream.js";
export type {
ValuesStreamEvent,
MessagesTupleStreamEvent,
MetadataStreamEvent,
UpdatesStreamEvent,
CustomStreamEvent,
MessagesStreamEvent,
DebugStreamEvent,
EventsStreamEvent,
ErrorStreamEvent,
FeedbackStreamEvent,
} from "./types.stream.js";
export type {
Message,
HumanMessage,
AIMessage,
ToolMessage,
SystemMessage,
FunctionMessage,
RemoveMessage,
} from "./types.messages.js";
-102
View File
@@ -1,102 +0,0 @@
import { ThreadState } from "../schema.js";
interface Node<StateType = any> {
type: "node";
value: ThreadState<StateType>;
path: string[];
}
interface Fork<StateType = any> {
type: "fork";
items: Array<Sequence<StateType>>;
}
interface Sequence<StateType = any> {
type: "sequence";
items: Array<Node<StateType> | Fork<StateType>>;
}
interface ValidFork<StateType = any> {
type: "fork";
items: Array<ValidSequence<StateType>>;
}
interface ValidSequence<StateType = any> {
type: "sequence";
items: [Node<StateType>, ...(Node<StateType> | ValidFork<StateType>)[]];
}
// forks
export type CheckpointBranchPath = string[];
export type MessageBranch = {
current: CheckpointBranchPath;
options: CheckpointBranchPath[];
};
export function DebugSegmentsView(props: {
sequence: ValidSequence<ThreadState>;
}) {
const concatContent = (value: ThreadState<any>) => {
let content;
try {
content = value.values?.messages?.at(-1)?.content ?? "";
} catch {
content = JSON.stringify(value.values);
}
content = content.replace(/(\n|\r\n)/g, "");
if (content.length <= 23) return content;
return `${content.slice(0, 10)}...${content.slice(-10)}`;
};
return (
<div>
{props.sequence.items.map((item, index) => {
if (item.type === "fork") {
return (
<div key={index}>
{item.items.map((fork, idx) => {
const [first] = fork.items;
return (
<details key={idx}>
<summary>
Fork{" "}
<span className="font-mono">
...{first.path.at(-1)?.slice(-4)}
</span>
</summary>
<div className="ml-4">
<DebugSegmentsView sequence={fork} />
</div>
</details>
);
})}
</div>
);
}
if (item.type === "node") {
return (
<div key={index} className="flex items-center gap-2">
<pre>
({item.value.metadata?.step}) ...
{item.value.checkpoint.checkpoint_id?.slice(-4)} (
{item.value.metadata?.source}): {concatContent(item.value)}
</pre>
<button
type="button"
className="border rounded-sm text-sm py-0.5 px-1 text-muted-foreground"
onClick={() => console.log(item.path, item.value)}
>
console.log
</button>
</div>
);
}
return null;
})}
</div>
);
}
-1
View File
@@ -1 +0,0 @@
export { useStream, type MessageMetadata } from "./stream.js";
-925
View File
@@ -1,925 +0,0 @@
/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */
"use client";
import { Client, type ClientConfig } from "../client.js";
import type {
Command,
DisconnectMode,
MultitaskStrategy,
OnCompletionBehavior,
} from "../types.js";
import type { Message } from "../types.messages.js";
import type {
Checkpoint,
Config,
Interrupt,
Metadata,
ThreadState,
} from "../schema.js";
import type {
CustomStreamEvent,
DebugStreamEvent,
ErrorStreamEvent,
EventsStreamEvent,
FeedbackStreamEvent,
MessagesStreamEvent,
MessagesTupleStreamEvent,
MetadataStreamEvent,
StreamMode,
UpdatesStreamEvent,
ValuesStreamEvent,
} from "../types.stream.js";
import {
type MutableRefObject,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
type BaseMessageChunk,
type BaseMessage,
coerceMessageLikeToMessage,
convertToChunk,
isBaseMessageChunk,
} from "@langchain/core/messages";
class StreamError extends Error {
constructor(data: { error?: string; name?: string; message: string }) {
super(data.message);
this.name = data.name ?? data.error ?? "StreamError";
}
static isStructuredError(error: unknown): error is {
error?: string;
name?: string;
message: string;
} {
return typeof error === "object" && error != null && "message" in error;
}
}
function tryConvertToChunk(message: BaseMessage): BaseMessageChunk | null {
try {
return convertToChunk(message);
} catch {
return null;
}
}
class MessageTupleManager {
chunks: Record<
string,
{ chunk?: BaseMessageChunk | BaseMessage; index?: number }
> = {};
constructor() {
this.chunks = {};
}
add(serialized: Message): string | null {
// TODO: this is sometimes sent from the API
// figure out how to prevent this or move this to LC.js
if (serialized.type.endsWith("MessageChunk")) {
serialized.type = serialized.type
.slice(0, -"MessageChunk".length)
.toLowerCase() as Message["type"];
}
const message = coerceMessageLikeToMessage(serialized);
const chunk = tryConvertToChunk(message);
const id = (chunk ?? message).id;
if (!id) {
console.warn(
"No message ID found for chunk, ignoring in state",
serialized,
);
return null;
}
this.chunks[id] ??= {};
if (chunk) {
const prev = this.chunks[id].chunk;
this.chunks[id].chunk =
(isBaseMessageChunk(prev) ? prev : null)?.concat(chunk) ?? chunk;
} else {
this.chunks[id].chunk = message;
}
return id;
}
clear() {
this.chunks = {};
}
get(id: string, defaultIndex: number) {
if (this.chunks[id] == null) return null;
this.chunks[id].index ??= defaultIndex;
return this.chunks[id];
}
}
const toMessageDict = (chunk: BaseMessage): Message => {
const { type, data } = chunk.toDict();
return { ...data, type } as Message;
};
function unique<T>(array: T[]) {
return [...new Set(array)] as T[];
}
function findLastIndex<T>(array: T[], predicate: (item: T) => boolean) {
for (let i = array.length - 1; i >= 0; i--) {
if (predicate(array[i])) return i;
}
return -1;
}
interface Node<StateType = any> {
type: "node";
value: ThreadState<StateType>;
path: string[];
}
interface Fork<StateType = any> {
type: "fork";
items: Array<Sequence<StateType>>;
}
interface Sequence<StateType = any> {
type: "sequence";
items: Array<Node<StateType> | Fork<StateType>>;
}
interface ValidFork<StateType = any> {
type: "fork";
items: Array<ValidSequence<StateType>>;
}
interface ValidSequence<StateType = any> {
type: "sequence";
items: [Node<StateType>, ...(Node<StateType> | ValidFork<StateType>)[]];
}
export type MessageMetadata<StateType extends Record<string, unknown>> = {
/**
* The ID of the message used.
*/
messageId: string;
/**
* The first thread state the message was seen in.
*/
firstSeenState: ThreadState<StateType> | undefined;
/**
* The branch of the message.
*/
branch: string | undefined;
/**
* The list of branches this message is part of.
* This is useful for displaying branching controls.
*/
branchOptions: string[] | undefined;
};
function getBranchSequence<StateType extends Record<string, unknown>>(
history: ThreadState<StateType>[],
) {
const childrenMap: Record<string, ThreadState<StateType>[]> = {};
// First pass - collect nodes for each checkpoint
history.forEach((state) => {
const checkpointId = state.parent_checkpoint?.checkpoint_id ?? "$";
childrenMap[checkpointId] ??= [];
childrenMap[checkpointId].push(state);
});
// Second pass - create a tree of sequences
type Task = { id: string; sequence: Sequence; path: string[] };
const rootSequence: Sequence = { type: "sequence", items: [] };
const queue: Task[] = [{ id: "$", sequence: rootSequence, path: [] }];
const paths: string[][] = [];
const visited = new Set<string>();
while (queue.length > 0) {
const task = queue.shift()!;
if (visited.has(task.id)) continue;
visited.add(task.id);
const children = childrenMap[task.id];
if (children == null || children.length === 0) continue;
// If we've encountered a fork (2+ children), push the fork
// to the sequence and add a new sequence for each child
let fork: Fork | undefined;
if (children.length > 1) {
fork = { type: "fork", items: [] };
task.sequence.items.push(fork);
}
for (const value of children) {
const id = value.checkpoint.checkpoint_id!;
let sequence = task.sequence;
let path = task.path;
if (fork != null) {
sequence = { type: "sequence", items: [] };
fork.items.unshift(sequence);
path = path.slice();
path.push(id);
paths.push(path);
}
sequence.items.push({ type: "node", value, path });
queue.push({ id, sequence, path });
}
}
return { rootSequence, paths };
}
const PATH_SEP = ">";
const ROOT_ID = "$";
// Get flat view
function getBranchView<StateType extends Record<string, unknown>>(
sequence: Sequence<StateType>,
paths: string[][],
branch: string,
) {
const path = branch.split(PATH_SEP);
const pathMap: Record<string, string[][]> = {};
for (const path of paths) {
const parent = path.at(-2) ?? ROOT_ID;
pathMap[parent] ??= [];
pathMap[parent].unshift(path);
}
const history: ThreadState<StateType>[] = [];
const branchByCheckpoint: Record<
string,
{ branch: string | undefined; branchOptions: string[] | undefined }
> = {};
const forkStack = path.slice();
const queue: (Node<StateType> | Fork<StateType>)[] = [...sequence.items];
while (queue.length > 0) {
const item = queue.shift()!;
if (item.type === "node") {
history.push(item.value);
branchByCheckpoint[item.value.checkpoint.checkpoint_id!] = {
branch: item.path.join(PATH_SEP),
branchOptions: (item.path.length > 0
? pathMap[item.path.at(-2) ?? ROOT_ID] ?? []
: []
).map((p) => p.join(PATH_SEP)),
};
}
if (item.type === "fork") {
const forkId = forkStack.shift();
const index =
forkId != null
? item.items.findIndex((value) => {
const firstItem = value.items.at(0);
if (!firstItem || firstItem.type !== "node") return false;
return firstItem.value.checkpoint.checkpoint_id === forkId;
})
: -1;
const nextItems = item.items.at(index)?.items ?? [];
queue.push(...nextItems);
}
}
return { history, branchByCheckpoint };
}
function fetchHistory<StateType extends Record<string, unknown>>(
client: Client,
threadId: string,
) {
return client.threads.getHistory<StateType>(threadId, { limit: 1000 });
}
function useThreadHistory<StateType extends Record<string, unknown>>(
threadId: string | undefined | null,
client: Client,
clearCallbackRef: MutableRefObject<(() => void) | undefined>,
submittingRef: MutableRefObject<boolean>,
) {
const [history, setHistory] = useState<ThreadState<StateType>[]>([]);
const fetcher = useCallback(
(
threadId: string | undefined | null,
): Promise<ThreadState<StateType>[]> => {
if (threadId != null) {
return fetchHistory<StateType>(client, threadId).then((history) => {
setHistory(history);
return history;
});
}
setHistory([]);
clearCallbackRef.current?.();
return Promise.resolve([]);
},
[],
);
useEffect(() => {
if (submittingRef.current) return;
fetcher(threadId);
}, [fetcher, submittingRef, threadId]);
return {
data: history,
mutate: (mutateId?: string) => fetcher(mutateId ?? threadId),
};
}
const useControllableThreadId = (options?: {
threadId?: string | null;
onThreadId?: (threadId: string) => void;
}): [string | null, (threadId: string) => void] => {
const [localThreadId, _setLocalThreadId] = useState<string | null>(
options?.threadId ?? null,
);
const onThreadIdRef = useRef(options?.onThreadId);
onThreadIdRef.current = options?.onThreadId;
const onThreadId = useCallback((threadId: string) => {
_setLocalThreadId(threadId);
onThreadIdRef.current?.(threadId);
}, []);
if (typeof options?.threadId === "undefined") {
return [localThreadId, onThreadId];
}
return [options.threadId, onThreadId];
};
type BagTemplate = {
ConfigurableType?: Record<string, unknown>;
InterruptType?: unknown;
CustomEventType?: unknown;
UpdateType?: unknown;
};
type GetUpdateType<
Bag extends BagTemplate,
StateType extends Record<string, unknown>,
> = Bag extends { UpdateType: unknown }
? Bag["UpdateType"]
: Partial<StateType>;
type GetConfigurableType<Bag extends BagTemplate> = Bag extends {
ConfigurableType: Record<string, unknown>;
}
? Bag["ConfigurableType"]
: Record<string, unknown>;
type GetInterruptType<Bag extends BagTemplate> = Bag extends {
InterruptType: unknown;
}
? Bag["InterruptType"]
: unknown;
type GetCustomEventType<Bag extends BagTemplate> = Bag extends {
CustomEventType: unknown;
}
? Bag["CustomEventType"]
: unknown;
interface UseStreamOptions<
StateType extends Record<string, unknown> = Record<string, unknown>,
Bag extends BagTemplate = BagTemplate,
> {
/**
* The ID of the assistant to use.
*/
assistantId: string;
/**
* The URL of the API to use.
*/
apiUrl: ClientConfig["apiUrl"];
/**
* The API key to use.
*/
apiKey?: ClientConfig["apiKey"];
/**
* Specify the key within the state that contains messages.
* Defaults to "messages".
*
* @default "messages"
*/
messagesKey?: string;
/**
* Callback that is called when an error occurs.
*/
onError?: (error: unknown) => void;
/**
* Callback that is called when the stream is finished.
*/
onFinish?: (state: ThreadState<StateType>) => void;
/**
* Callback that is called when an update event is received.
*/
onUpdateEvent?: (
data: UpdatesStreamEvent<GetUpdateType<Bag, StateType>>["data"],
) => void;
/**
* Callback that is called when a custom event is received.
*/
onCustomEvent?: (
data: CustomStreamEvent<GetCustomEventType<Bag>>["data"],
) => void;
/**
* Callback that is called when a metadata event is received.
*/
onMetadataEvent?: (data: MetadataStreamEvent["data"]) => void;
/**
* The ID of the thread to fetch history and current values from.
*/
threadId?: string | null;
/**
* Callback that is called when the thread ID is updated (ie when a new thread is created).
*/
onThreadId?: (threadId: string) => void;
}
interface UseStream<
StateType extends Record<string, unknown> = Record<string, unknown>,
Bag extends BagTemplate = BagTemplate,
> {
/**
* The current values of the thread.
*/
values: StateType;
/**
* Last seen error from the thread or during streaming.
*/
error: unknown;
/**
* Whether the stream is currently running.
*/
isLoading: boolean;
/**
* Stops the stream.
*/
stop: () => void;
/**
* Create and stream a run to the thread.
*/
submit: (
values: GetUpdateType<Bag, StateType> | null | undefined,
options?: SubmitOptions<StateType, GetConfigurableType<Bag>>,
) => void;
/**
* The current branch of the thread.
*/
branch: string;
/**
* Set the branch of the thread.
*/
setBranch: (branch: string) => void;
/**
* Flattened history of thread states of a thread.
*/
history: ThreadState<StateType>[];
/**
* Tree of all branches for the thread.
* @experimental
*/
experimental_branchTree: Sequence<StateType>;
/**
* Get the interrupt value for the stream if interrupted.
*/
interrupt: Interrupt<GetInterruptType<Bag>> | undefined;
/**
* Messages inferred from the thread.
* Will automatically update with incoming message chunks.
*/
messages: Message[];
/**
* Get the metadata for a message, such as first thread state the message
* was seen in and branch information.
* @param message - The message to get the metadata for.
* @param index - The index of the message in the thread.
* @returns The metadata for the message.
*/
getMessagesMetadata: (
message: Message,
index?: number,
) => MessageMetadata<StateType> | undefined;
}
type ConfigWithConfigurable<ConfigurableType extends Record<string, unknown>> =
Config & { configurable?: ConfigurableType };
interface SubmitOptions<
StateType extends Record<string, unknown> = Record<string, unknown>,
ConfigurableType extends Record<string, unknown> = Record<string, unknown>,
> {
config?: ConfigWithConfigurable<ConfigurableType>;
checkpoint?: Omit<Checkpoint, "thread_id"> | null;
command?: Command;
interruptBefore?: "*" | string[];
interruptAfter?: "*" | string[];
metadata?: Metadata;
multitaskStrategy?: MultitaskStrategy;
onCompletion?: OnCompletionBehavior;
onDisconnect?: DisconnectMode;
feedbackKeys?: string[];
streamMode?: Array<StreamMode>;
optimisticValues?:
| Partial<StateType>
| ((prev: StateType) => Partial<StateType>);
}
export function useStream<
StateType extends Record<string, unknown> = Record<string, unknown>,
Bag extends {
ConfigurableType?: Record<string, unknown>;
InterruptType?: unknown;
CustomEventType?: unknown;
UpdateType?: unknown;
} = BagTemplate,
>(options: UseStreamOptions<StateType, Bag>): UseStream<StateType, Bag> {
type UpdateType = GetUpdateType<Bag, StateType>;
type CustomType = GetCustomEventType<Bag>;
type InterruptType = GetInterruptType<Bag>;
type ConfigurableType = GetConfigurableType<Bag>;
type EventStreamEvent =
| ValuesStreamEvent<StateType>
| UpdatesStreamEvent<UpdateType>
| CustomStreamEvent<CustomType>
| DebugStreamEvent
| MessagesStreamEvent
| MessagesTupleStreamEvent
| EventsStreamEvent
| MetadataStreamEvent
| ErrorStreamEvent
| FeedbackStreamEvent;
let { assistantId, messagesKey, onError, onFinish } = options;
messagesKey ??= "messages";
const client = useMemo(
() => new Client({ apiUrl: options.apiUrl, apiKey: options.apiKey }),
[options.apiKey, options.apiUrl],
);
const [threadId, onThreadId] = useControllableThreadId(options);
const [branch, setBranch] = useState<string>("");
const [isLoading, setIsLoading] = useState(false);
const [streamError, setStreamError] = useState<unknown>(undefined);
const [streamValues, setStreamValues] = useState<StateType | null>(null);
const messageManagerRef = useRef(new MessageTupleManager());
const submittingRef = useRef(false);
const abortRef = useRef<AbortController | null>(null);
const trackStreamModeRef = useRef<
Array<"values" | "updates" | "events" | "custom" | "messages-tuple">
>([]);
const trackStreamMode = useCallback(
(mode: Exclude<StreamMode, "debug" | "messages">) => {
if (!trackStreamModeRef.current.includes(mode))
trackStreamModeRef.current.push(mode);
},
[],
);
const hasUpdateListener = options.onUpdateEvent != null;
const hasCustomListener = options.onCustomEvent != null;
const callbackStreamMode = useMemo(() => {
const modes: Exclude<StreamMode, "debug" | "messages">[] = [];
if (hasUpdateListener) modes.push("updates");
if (hasCustomListener) modes.push("custom");
return modes;
}, [hasUpdateListener, hasCustomListener]);
const clearCallbackRef = useRef<() => void>(null!);
clearCallbackRef.current = () => {
setStreamError(undefined);
setStreamValues(null);
};
// TODO: this should be done on the server to avoid pagination
// TODO: should we permit adapter? SWR / React Query?
const history = useThreadHistory<StateType>(
threadId,
client,
clearCallbackRef,
submittingRef,
);
const getMessages = useMemo(() => {
return (value: StateType) =>
Array.isArray(value[messagesKey])
? (value[messagesKey] as Message[])
: [];
}, [messagesKey]);
const { rootSequence, paths } = getBranchSequence(history.data);
const { history: flatHistory, branchByCheckpoint } = getBranchView(
rootSequence,
paths,
branch,
);
const threadHead: ThreadState<StateType> | undefined = flatHistory.at(-1);
const historyValues = threadHead?.values ?? ({} as StateType);
const historyError = (() => {
const error = threadHead?.tasks?.at(-1)?.error;
if (error == null) return undefined;
try {
const parsed = JSON.parse(error) as unknown;
if (StreamError.isStructuredError(parsed)) {
return new StreamError(parsed);
}
return parsed;
} catch {
// do nothing
}
return error;
})();
const messageMetadata = (() => {
const alreadyShown = new Set<string>();
return getMessages(historyValues).map(
(message, idx): MessageMetadata<StateType> => {
const messageId = message.id ?? idx;
const firstSeenIdx = findLastIndex(history.data, (state) =>
getMessages(state.values)
.map((m, idx) => m.id ?? idx)
.includes(messageId),
);
const firstSeen = history.data[firstSeenIdx] as
| ThreadState<StateType>
| undefined;
let branch = firstSeen
? branchByCheckpoint[firstSeen.checkpoint.checkpoint_id!]
: undefined;
if (!branch?.branch?.length) branch = undefined;
// serialize branches
const optionsShown = branch?.branchOptions?.flat(2).join(",");
if (optionsShown) {
if (alreadyShown.has(optionsShown)) branch = undefined;
alreadyShown.add(optionsShown);
}
return {
messageId: messageId.toString(),
firstSeenState: firstSeen,
branch: branch?.branch,
branchOptions: branch?.branchOptions,
};
},
);
})();
const stop = useCallback(() => {
if (abortRef.current != null) abortRef.current.abort();
abortRef.current = null;
}, []);
const submit = async (
values: UpdateType | null | undefined,
submitOptions?: SubmitOptions<StateType, ConfigurableType>,
) => {
try {
setIsLoading(true);
setStreamError(undefined);
submittingRef.current = true;
abortRef.current = new AbortController();
let usableThreadId = threadId;
if (!usableThreadId) {
const thread = await client.threads.create();
onThreadId(thread.thread_id);
usableThreadId = thread.thread_id;
}
const streamMode = unique([
...(submitOptions?.streamMode ?? []),
...trackStreamModeRef.current,
...callbackStreamMode,
]);
const checkpoint =
submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
// @ts-expect-error
if (checkpoint != null) delete checkpoint.thread_id;
const run = (await client.runs.stream(usableThreadId, assistantId, {
input: values as Record<string, unknown>,
config: submitOptions?.config,
command: submitOptions?.command,
interruptBefore: submitOptions?.interruptBefore,
interruptAfter: submitOptions?.interruptAfter,
metadata: submitOptions?.metadata,
multitaskStrategy: submitOptions?.multitaskStrategy,
onCompletion: submitOptions?.onCompletion,
onDisconnect: submitOptions?.onDisconnect ?? "cancel",
signal: abortRef.current.signal,
checkpoint,
streamMode,
})) as AsyncGenerator<EventStreamEvent>;
// Unbranch things
const newPath = submitOptions?.checkpoint?.checkpoint_id
? branchByCheckpoint[submitOptions?.checkpoint?.checkpoint_id]?.branch
: undefined;
if (newPath != null) setBranch(newPath ?? "");
// Assumption: we're setting the initial value
// Used for instant feedback
setStreamValues(() => {
const values = { ...historyValues };
if (submitOptions?.optimisticValues != null) {
return {
...values,
...(typeof submitOptions.optimisticValues === "function"
? submitOptions.optimisticValues(values)
: submitOptions.optimisticValues),
};
}
return values;
});
let streamError: StreamError | undefined;
for await (const { event, data } of run) {
if (event === "error") {
streamError = new StreamError(data);
break;
}
if (event === "updates") options.onUpdateEvent?.(data);
if (event === "custom") options.onCustomEvent?.(data);
if (event === "metadata") options.onMetadataEvent?.(data);
if (event === "values") setStreamValues(data);
if (event === "messages") {
const [serialized] = data;
const messageId = messageManagerRef.current.add(serialized);
if (!messageId) {
console.warn(
"Failed to add message to manager, no message ID found",
);
continue;
}
setStreamValues((streamValues) => {
const values = { ...historyValues, ...streamValues };
// Assumption: we're concatenating the message
const messages = getMessages(values).slice();
const { chunk, index } =
messageManagerRef.current.get(messageId, messages.length) ?? {};
if (!chunk || index == null) return values;
messages[index] = toMessageDict(chunk);
return { ...values, [messagesKey!]: messages };
});
}
}
// TODO: stream created checkpoints to avoid an unnecessary network request
const result = await history.mutate(usableThreadId);
setStreamValues(null);
if (streamError != null) throw streamError;
const lastHead = result.at(0);
if (lastHead) onFinish?.(lastHead);
} catch (error) {
if (
!(
error instanceof Error &&
(error.name === "AbortError" || error.name === "TimeoutError")
)
) {
console.error(error);
setStreamError(error);
onError?.(error);
}
} finally {
setIsLoading(false);
// Assumption: messages are already handled, we can clear the manager
messageManagerRef.current.clear();
submittingRef.current = false;
abortRef.current = null;
}
};
const error = streamError ?? historyError;
const values = streamValues ?? historyValues;
return {
get values() {
trackStreamMode("values");
return values;
},
error,
isLoading,
stop,
submit,
branch,
setBranch,
history: flatHistory,
experimental_branchTree: rootSequence,
get interrupt() {
// Don't show the interrupt if the stream is loading
if (isLoading) return undefined;
const interrupts = threadHead?.tasks?.at(-1)?.interrupts;
if (interrupts == null || interrupts.length === 0) {
// check if there's a next task present
const next = threadHead?.next ?? [];
if (!next.length || error != null) return undefined;
return { when: "breakpoint" };
}
// Return only the current interrupt
return interrupts.at(-1) as Interrupt<InterruptType> | undefined;
},
get messages() {
trackStreamMode("messages-tuple");
return getMessages(values);
},
getMessagesMetadata(
message: Message,
index?: number,
): MessageMetadata<StateType> | undefined {
trackStreamMode("messages-tuple");
return messageMetadata?.find(
(m) => m.messageId === (message.id ?? index),
);
},
};
}
-300
View File
@@ -1,300 +0,0 @@
import type { JSONSchema7 } from "json-schema";
type Optional<T> = T | null | undefined;
export type RunStatus =
| "pending"
| "running"
| "error"
| "success"
| "timeout"
| "interrupted";
export type ThreadStatus = "idle" | "busy" | "interrupted" | "error";
type MultitaskStrategy = "reject" | "interrupt" | "rollback" | "enqueue";
export type CancelAction = "interrupt" | "rollback";
export type Config = {
/**
* Tags for this call and any sub-calls (eg. a Chain calling an LLM).
* You can use these to filter calls.
*/
tags?: string[];
/**
* Maximum number of times a call can recurse.
* If not provided, defaults to 25.
*/
recursion_limit?: number;
/**
* Runtime values for attributes previously made configurable on this Runnable.
*/
configurable?: {
/**
* ID of the thread
*/
thread_id?: Optional<string>;
/**
* Timestamp of the state checkpoint
*/
checkpoint_id?: Optional<string>;
[key: string]: unknown;
};
};
export interface GraphSchema {
/**
* The ID of the graph.
*/
graph_id: string;
/**
* The schema for the input state.
* Missing if unable to generate JSON schema from graph.
*/
input_schema?: JSONSchema7;
/**
* The schema for the output state.
* Missing if unable to generate JSON schema from graph.
*/
output_schema?: JSONSchema7;
/**
* The schema for the graph state.
* Missing if unable to generate JSON schema from graph.
*/
state_schema?: JSONSchema7;
/**
* The schema for the graph config.
* Missing if unable to generate JSON schema from graph.
*/
config_schema?: JSONSchema7;
}
export type Subgraphs = Record<string, GraphSchema>;
export type Metadata = Optional<{
source?: "input" | "loop" | "update" | (string & {});
step?: number;
writes?: Record<string, unknown> | null;
parents?: Record<string, string>;
[key: string]: unknown;
}>;
export interface AssistantBase {
/** The ID of the assistant. */
assistant_id: string;
/** The ID of the graph. */
graph_id: string;
/** The assistant config. */
config: Config;
/** The time the assistant was created. */
created_at: string;
/** The assistant metadata. */
metadata: Metadata;
/** The version of the assistant. */
version: number;
}
export interface AssistantVersion extends AssistantBase {}
export interface Assistant extends AssistantBase {
/** The last time the assistant was updated. */
updated_at: string;
/** The name of the assistant */
name: string;
}
export interface AssistantGraph {
nodes: Array<{
id: string | number;
name?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?: Record<string, any> | string;
metadata?: unknown;
}>;
edges: Array<{
source: string;
target: string;
data?: string;
conditional?: boolean;
}>;
}
/**
* An interrupt thrown inside a thread.
*/
export interface Interrupt<TValue = unknown> {
value?: TValue;
when: "during" | (string & {});
resumable?: boolean;
ns?: string[];
}
export interface Thread<ValuesType = DefaultValues> {
/** The ID of the thread. */
thread_id: string;
/** The time the thread was created. */
created_at: string;
/** The last time the thread was updated. */
updated_at: string;
/** The thread metadata. */
metadata: Metadata;
/** The status of the thread */
status: ThreadStatus;
/** The current state of the thread. */
values: ValuesType;
/** Interrupts which were thrown in this thread */
interrupts: Record<string, Array<Interrupt>>;
}
export interface Cron {
/** The ID of the cron */
cron_id: string;
/** The ID of the thread */
thread_id: Optional<string>;
/** The end date to stop running the cron. */
end_time: Optional<string>;
/** The schedule to run, cron format. */
schedule: string;
/** The time the cron was created. */
created_at: string;
/** The last time the cron was updated. */
updated_at: string;
/** The run payload to use for creating new run. */
payload: Record<string, unknown>;
}
export type DefaultValues = Record<string, unknown>[] | Record<string, unknown>;
export interface ThreadState<ValuesType = DefaultValues> {
/** The state values */
values: ValuesType;
/** The next nodes to execute. If empty, the thread is done until new input is received */
next: string[];
/** Checkpoint of the thread state */
checkpoint: Checkpoint;
/** Metadata for this state */
metadata: Metadata;
/** Time of state creation */
created_at: Optional<string>;
/** The parent checkpoint. If missing, this is the root checkpoint */
parent_checkpoint: Optional<Checkpoint>;
/** Tasks to execute in this step. If already attempted, may contain an error */
tasks: Array<ThreadTask>;
}
export interface ThreadTask {
id: string;
name: string;
result?: unknown;
error: Optional<string>;
interrupts: Array<Interrupt>;
checkpoint: Optional<Checkpoint>;
state: Optional<ThreadState>;
}
export interface Run {
/** The ID of the run */
run_id: string;
/** The ID of the thread */
thread_id: string;
/** The assistant that wwas used for this run */
assistant_id: string;
/** The time the run was created */
created_at: string;
/** The last time the run was updated */
updated_at: string;
/** The status of the run. */
status: RunStatus;
/** Run metadata */
metadata: Metadata;
/** Strategy to handle concurrent runs on the same thread */
multitask_strategy: Optional<MultitaskStrategy>;
}
export type Checkpoint = {
thread_id: string;
checkpoint_ns: string;
checkpoint_id: Optional<string>;
checkpoint_map: Optional<Record<string, unknown>>;
};
export interface ListNamespaceResponse {
namespaces: string[][];
}
export interface Item {
namespace: string[];
key: string;
value: Record<string, any>;
createdAt: string;
updatedAt: string;
}
export interface SearchItem extends Item {
score?: number;
}
export interface SearchItemsResponse {
items: SearchItem[];
}
export interface CronCreateResponse {
cron_id: string;
assistant_id: string;
thread_id: string | undefined;
user_id: string;
payload: Record<string, unknown>;
schedule: string;
next_run_date: string;
end_time: string | undefined;
created_at: string;
updated_at: string;
metadata: Metadata;
}
export interface CronCreateForThreadResponse
extends Omit<CronCreateResponse, "thread_id"> {
thread_id: string;
}
-29
View File
@@ -1,29 +0,0 @@
// Wrap the default fetch call due to issues with illegal invocations
// in some environments:
// https://stackoverflow.com/questions/69876859/why-does-bind-fix-failed-to-execute-fetch-on-window-illegal-invocation-err
// @ts-expect-error Broad typing to support a range of fetch implementations
const DEFAULT_FETCH_IMPLEMENTATION = (...args: any[]) => fetch(...args);
const LANGSMITH_FETCH_IMPLEMENTATION_KEY = Symbol.for(
"lg:fetch_implementation",
);
/**
* Overrides the fetch implementation used for LangSmith calls.
* You should use this if you need to use an implementation of fetch
* other than the default global (e.g. for dealing with proxies).
* @param fetch The new fetch function to use.
*/
export const overrideFetchImplementation = (fetch: (...args: any[]) => any) => {
(globalThis as any)[LANGSMITH_FETCH_IMPLEMENTATION_KEY] = fetch;
};
/**
* @internal
*/
export const _getFetchImplementation: () => (...args: any[]) => any = () => {
return (
(globalThis as any)[LANGSMITH_FETCH_IMPLEMENTATION_KEY] ??
DEFAULT_FETCH_IMPLEMENTATION
);
};
-74
View File
@@ -1,74 +0,0 @@
/* eslint-disable no-process-env */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { jest } from "@jest/globals";
import { Client } from "../client.js";
import { overrideFetchImplementation } from "../singletons/fetch.js";
describe.each([[""], ["mocked"]])("Client uses %s fetch", (description) => {
let globalFetchMock: jest.Mock;
let overriddenFetch: jest.Mock;
let expectedFetchMock: jest.Mock;
let unexpectedFetchMock: jest.Mock;
beforeEach(() => {
globalFetchMock = jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
batch_ingest_config: {
use_multipart_endpoint: true,
},
}),
text: () => Promise.resolve(""),
}),
);
overriddenFetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
batch_ingest_config: {
use_multipart_endpoint: true,
},
}),
text: () => Promise.resolve(""),
}),
);
expectedFetchMock =
description === "mocked" ? overriddenFetch : globalFetchMock;
unexpectedFetchMock =
description === "mocked" ? globalFetchMock : overriddenFetch;
if (description === "mocked") {
overrideFetchImplementation(overriddenFetch);
} else {
overrideFetchImplementation(globalFetchMock);
}
// Mock global fetch
(globalThis as any).fetch = globalFetchMock;
});
afterEach(() => {
jest.restoreAllMocks();
});
describe("createRuns", () => {
it("should create an example with the given input and generation", async () => {
const client = new Client({ apiKey: "test-api-key" });
const thread = await client.threads.create();
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
expect(unexpectedFetchMock).not.toHaveBeenCalled();
jest.clearAllMocks(); // Clear all mocks before the next operation
// Then clear & run the function
await client.runs.create(thread.thread_id, "somegraph", {
input: { foo: "bar" },
});
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
expect(unexpectedFetchMock).not.toHaveBeenCalled();
});
});
});
-182
View File
@@ -1,182 +0,0 @@
import { Readable } from "node:stream";
import { IterableReadableStream } from "../utils/stream.js";
import { BytesLineDecoder, SSEDecoder } from "../utils/sse.js";
const gather = async <T>(stream: ReadableStream<T>): Promise<T[]> => {
const results: T[] = [];
const iterator = IterableReadableStream.fromReadableStream(stream);
for await (const chunk of iterator) results.push(chunk);
return results;
};
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
describe("BytesLineDecoder", () => {
const createStream = (chunks: Uint8Array[]) => {
return Readable.toWeb(Readable.from(chunks)) as ReadableStream<Uint8Array>;
};
test("handles single line with newline", async () => {
const input = createStream([textEncoder.encode("hello\n")]);
const decoded = input.pipeThrough(new BytesLineDecoder());
const results = await gather(decoded);
expect(results.length).toBe(1);
expect(textDecoder.decode(results[0])).toBe("hello");
});
test("handles multiple lines", async () => {
const input = createStream([textEncoder.encode("line1\nline2\nline3\n")]);
const decoded = input.pipeThrough(new BytesLineDecoder());
const results = await gather(decoded);
expect(results.length).toBe(3);
expect(textDecoder.decode(results[0])).toBe("line1");
expect(textDecoder.decode(results[1])).toBe("line2");
expect(textDecoder.decode(results[2])).toBe("line3");
});
test("handles split chunks", async () => {
const input = createStream([
textEncoder.encode("li"),
textEncoder.encode("ne1\nli"),
textEncoder.encode("ne2\n"),
]);
const decoded = input.pipeThrough(new BytesLineDecoder());
const results = await gather(decoded);
expect(results.length).toBe(2);
expect(textDecoder.decode(results[0])).toBe("line1");
expect(textDecoder.decode(results[1])).toBe("line2");
});
test("handles CR LF line endings", async () => {
const input = createStream([textEncoder.encode("line1\r\nline2\r\n")]);
const decoded = input.pipeThrough(new BytesLineDecoder());
const results = await gather(decoded);
expect(results.length).toBe(2);
expect(textDecoder.decode(results[0])).toBe("line1");
expect(textDecoder.decode(results[1])).toBe("line2");
});
test("handles split CR LF", async () => {
const input = createStream([
textEncoder.encode("line1\r"),
textEncoder.encode("\nline2\r\n"),
]);
const decoded = input.pipeThrough(new BytesLineDecoder());
const results = await gather(decoded);
expect(results.length).toBe(2);
expect(textDecoder.decode(results[0])).toBe("line1");
expect(textDecoder.decode(results[1])).toBe("line2");
});
test("handles stale line", async () => {
const input = createStream([textEncoder.encode("hello")]);
const decoded = input.pipeThrough(new BytesLineDecoder());
const results = await gather(decoded);
expect(results.length).toBe(1);
expect(textDecoder.decode(results[0])).toBe("hello");
});
});
describe("SSEDecoder", () => {
const createStream = (lines: string[]) => {
return Readable.toWeb(
Readable.from(lines.map((line) => textEncoder.encode(line))),
) as ReadableStream<Uint8Array>;
};
test("decodes simple event", async () => {
const input = createStream([
"event: test\n",
'data: {"message": "hello"}\n',
"\n",
]);
const decoded = input
.pipeThrough(new BytesLineDecoder())
.pipeThrough(new SSEDecoder());
const results = await gather(decoded);
expect(results.length).toBe(1);
expect(results[0]).toEqual({
event: "test",
data: { message: "hello" },
});
});
test("ignores comments", async () => {
const input = createStream([
": this is a comment\n",
"event: test\n",
'data: {"message": "hello"}\n',
]);
const decoded = input
.pipeThrough(new BytesLineDecoder())
.pipeThrough(new SSEDecoder());
const results = await gather(decoded);
expect(results.length).toBe(1);
expect(results[0]).toEqual({
event: "test",
data: { message: "hello" },
});
});
test("handles multiple events", async () => {
const input = createStream([
"event: test1\n",
'data: {"message": "hello"}\n',
"\n",
"event: test2\n",
'data: {"message": "world"}\n',
"\n",
]);
const decoded = input
.pipeThrough(new BytesLineDecoder())
.pipeThrough(new SSEDecoder());
const results = await gather(decoded);
expect(results.length).toBe(2);
expect(results[0]).toEqual({
event: "test1",
data: { message: "hello" },
});
expect(results[1]).toEqual({
event: "test2",
data: { message: "world" },
});
});
test("end event without data", async () => {
const input = createStream(["event: test\n"]);
const decoded = input
.pipeThrough(new BytesLineDecoder())
.pipeThrough(new SSEDecoder());
const results = await gather(decoded);
expect(results.length).toBe(1);
expect(results[0]).toEqual({
event: "test",
data: null,
});
});
test("end event without newline", async () => {
const input = createStream(["event: end"]);
const decoded = input
.pipeThrough(new BytesLineDecoder())
.pipeThrough(new SSEDecoder());
const results = await gather(decoded);
expect(results.length).toBe(1);
expect(results[0]).toEqual({
event: "end",
data: null,
});
});
});
-99
View File
@@ -1,99 +0,0 @@
type ImageDetail = "auto" | "low" | "high";
type MessageContentImageUrl = {
type: "image_url";
image_url: string | { url: string; detail?: ImageDetail | undefined };
};
type MessageContentText = { type: "text"; text: string };
type MessageContentComplex = MessageContentText | MessageContentImageUrl;
type MessageContent = string | MessageContentComplex[];
/**
* Model-specific additional kwargs, which is passed back to the underlying LLM.
*/
type MessageAdditionalKwargs = Record<string, unknown>;
export type HumanMessage = {
type: "human";
id?: string | undefined;
content: MessageContent;
};
export type AIMessage = {
type: "ai";
id?: string | undefined;
content: MessageContent;
tool_calls?:
| {
name: string;
args: { [x: string]: { [x: string]: any } };
id?: string | undefined;
type?: "tool_call" | undefined;
}[]
| undefined;
invalid_tool_calls?:
| {
name?: string | undefined;
args?: string | undefined;
id?: string | undefined;
error?: string | undefined;
type?: "invalid_tool_call" | undefined;
}[]
| undefined;
usage_metadata?:
| {
input_tokens: number;
output_tokens: number;
total_tokens: number;
input_token_details?:
| {
audio?: number | undefined;
cache_read?: number | undefined;
cache_creation?: number | undefined;
}
| undefined;
output_token_details?:
| { audio?: number | undefined; reasoning?: number | undefined }
| undefined;
}
| undefined;
additional_kwargs?: MessageAdditionalKwargs | undefined;
response_metadata?: Record<string, unknown> | undefined;
};
export type ToolMessage = {
type: "tool";
name?: string | undefined;
id?: string | undefined;
content: MessageContent;
status?: "error" | "success" | undefined;
tool_call_id: string;
additional_kwargs?: MessageAdditionalKwargs | undefined;
response_metadata?: Record<string, unknown> | undefined;
};
export type SystemMessage = {
type: "system";
id?: string | undefined;
content: MessageContent;
};
export type FunctionMessage = {
type: "function";
id?: string | undefined;
content: MessageContent;
};
export type RemoveMessage = {
type: "remove";
id: string;
content: MessageContent;
};
export type Message =
| HumanMessage
| AIMessage
| ToolMessage
| SystemMessage
| FunctionMessage
| RemoveMessage;
-204
View File
@@ -1,204 +0,0 @@
import type { Message } from "./types.messages.js";
/**
* Stream modes
* - "values": Stream only the state values.
* - "messages": Stream complete messages.
* - "messages-tuple": Stream (message chunk, metadata) tuples.
* - "updates": Stream updates to the state.
* - "events": Stream events occurring during execution.
* - "debug": Stream detailed debug information.
* - "custom": Stream custom events.
*/
export type StreamMode =
| "values"
| "messages"
| "updates"
| "events"
| "debug"
| "custom"
| "messages-tuple";
type MessageTupleMetadata = {
tags: string[];
[key: string]: unknown;
};
type AsSubgraph<TEvent extends { event: string; data: unknown }> = {
event: TEvent["event"] | `${TEvent["event"]}|${string}`;
data: TEvent["data"];
};
/**
* Stream event with values after completion of each step.
*/
export type ValuesStreamEvent<StateType> = { event: "values"; data: StateType };
/** @internal */
export type SubgraphValuesStreamEvent<StateType> = AsSubgraph<
ValuesStreamEvent<StateType>
>;
/**
* Stream event with message chunks coming from LLM invocations inside nodes.
*/
export type MessagesTupleStreamEvent = {
event: "messages";
// TODO: add types for message and config, which do not depend on LangChain
// while making sure it's easy to keep them in sync.
data: [message: Message, config: MessageTupleMetadata];
};
/** @internal */
export type SubgraphMessagesTupleStreamEvent =
AsSubgraph<MessagesTupleStreamEvent>;
/**
* Metadata stream event with information about the run and thread
*/
export type MetadataStreamEvent = {
event: "metadata";
data: { run_id: string; thread_id: string };
};
/**
* Stream event with error information.
*/
export type ErrorStreamEvent = {
event: "error";
data: { error: string; message: string };
};
/** @internal */
export type SubgraphErrorStreamEvent = AsSubgraph<ErrorStreamEvent>;
/**
* Stream event with updates to the state after each step.
* The streamed outputs include the name of the node that
* produced the update as well as the update.
*/
export type UpdatesStreamEvent<UpdateType> = {
event: "updates";
data: { [node: string]: UpdateType };
};
/** @internal */
export type SubgraphUpdatesStreamEvent<UpdateType> = AsSubgraph<
UpdatesStreamEvent<UpdateType>
>;
/**
* Streaming custom data from inside the nodes.
*/
export type CustomStreamEvent<T> = { event: "custom"; data: T };
/** @internal */
export type SubgraphCustomStreamEvent<T> = AsSubgraph<CustomStreamEvent<T>>;
type MessagesMetadataStreamEvent = {
event: "messages/metadata";
data: { [messageId: string]: { metadata: unknown } };
};
type MessagesCompleteStreamEvent = {
event: "messages/complete";
data: Message[];
};
type MessagesPartialStreamEvent = {
event: "messages/partial";
data: Message[];
};
/**
* Message stream event specific to LangGraph Server.
* @deprecated Use `streamMode: "messages-tuple"` instead.
*/
export type MessagesStreamEvent =
| MessagesMetadataStreamEvent
| MessagesCompleteStreamEvent
| MessagesPartialStreamEvent;
/** @internal */
export type SubgraphMessagesStreamEvent =
| AsSubgraph<MessagesMetadataStreamEvent>
| AsSubgraph<MessagesCompleteStreamEvent>
| AsSubgraph<MessagesPartialStreamEvent>;
/**
* Stream event with detailed debug information.
*/
export type DebugStreamEvent = { event: "debug"; data: unknown };
/** @internal */
export type SubgraphDebugStreamEvent = AsSubgraph<DebugStreamEvent>;
/**
* Stream event with events occurring during execution.
*/
export type EventsStreamEvent = { event: "events"; data: unknown };
/** @internal */
export type SubgraphEventsStreamEvent = AsSubgraph<EventsStreamEvent>;
/**
* Stream event with a feedback key to signed URL map. Set `feedbackKeys` in
* the `RunsStreamPayload` to receive this event.
*/
export type FeedbackStreamEvent = {
event: "feedback";
data: { [feedbackKey: string]: string };
};
type GetStreamModeMap<
TStreamMode extends StreamMode | StreamMode[],
TStateType = unknown,
TUpdateType = TStateType,
TCustomType = unknown,
> =
| {
values: ValuesStreamEvent<TStateType>;
updates: UpdatesStreamEvent<TUpdateType>;
custom: CustomStreamEvent<TCustomType>;
debug: DebugStreamEvent;
messages: MessagesStreamEvent;
"messages-tuple": MessagesTupleStreamEvent;
events: EventsStreamEvent;
}[TStreamMode extends StreamMode[] ? TStreamMode[number] : TStreamMode]
| ErrorStreamEvent
| MetadataStreamEvent
| FeedbackStreamEvent;
type GetSubgraphsStreamModeMap<
TStreamMode extends StreamMode | StreamMode[],
TStateType = unknown,
TUpdateType = TStateType,
TCustomType = unknown,
> =
| {
values: SubgraphValuesStreamEvent<TStateType>;
updates: SubgraphUpdatesStreamEvent<TUpdateType>;
custom: SubgraphCustomStreamEvent<TCustomType>;
debug: SubgraphDebugStreamEvent;
messages: SubgraphMessagesStreamEvent;
"messages-tuple": SubgraphMessagesTupleStreamEvent;
events: SubgraphEventsStreamEvent;
}[TStreamMode extends StreamMode[] ? TStreamMode[number] : TStreamMode]
| SubgraphErrorStreamEvent
| MetadataStreamEvent
| FeedbackStreamEvent;
export type TypedAsyncGenerator<
TStreamMode extends StreamMode | StreamMode[] = [],
TSubgraphs extends boolean = false,
TStateType = unknown,
TUpdateType = TStateType,
TCustomType = unknown,
> = AsyncGenerator<
TSubgraphs extends true
? GetSubgraphsStreamModeMap<
TStreamMode,
TStateType,
TUpdateType,
TCustomType
>
: GetStreamModeMap<TStreamMode, TStateType, TUpdateType, TCustomType>
>;
-180
View File
@@ -1,180 +0,0 @@
import { Checkpoint, Config, Metadata } from "./schema.js";
import { StreamMode } from "./types.stream.js";
export type MultitaskStrategy = "reject" | "interrupt" | "rollback" | "enqueue";
export type OnConflictBehavior = "raise" | "do_nothing";
export type OnCompletionBehavior = "complete" | "continue";
export type DisconnectMode = "cancel" | "continue";
export type StreamEvent =
| "events"
| "metadata"
| "debug"
| "updates"
| "values"
| "messages/partial"
| "messages/metadata"
| "messages/complete"
| "messages"
| (string & {});
export interface Send {
node: string;
input: Record<string, unknown> | null;
}
export interface Command {
/**
* An object to update the thread state with.
*/
update?: Record<string, unknown> | [string, unknown][] | null;
/**
* The value to return from an `interrupt` function call.
*/
resume?: unknown;
/**
* Determine the next node to navigate to. Can be one of the following:
* - Name(s) of the node names to navigate to next.
* - `Send` command(s) to execute node(s) with provided input.
*/
goto?: Send | Send[] | string | string[];
}
interface RunsInvokePayload {
/**
* Input to the run. Pass `null` to resume from the current state of the thread.
*/
input?: Record<string, unknown> | null;
/**
* Metadata for the run.
*/
metadata?: Metadata;
/**
* Additional configuration for the run.
*/
config?: Config;
/**
* Checkpoint ID for when creating a new run.
*/
checkpointId?: string;
/**
* Checkpoint for when creating a new run.
*/
checkpoint?: Omit<Checkpoint, "thread_id">;
/**
* Interrupt execution before entering these nodes.
*/
interruptBefore?: "*" | string[];
/**
* Interrupt execution after leaving these nodes.
*/
interruptAfter?: "*" | string[];
/**
* Strategy to handle concurrent runs on the same thread. Only relevant if
* there is a pending/inflight run on the same thread. One of:
* - "reject": Reject the new run.
* - "interrupt": Interrupt the current run, keeping steps completed until now,
and start a new one.
* - "rollback": Cancel and delete the existing run, rolling back the thread to
the state before it had started, then start the new run.
* - "enqueue": Queue up the new run to start after the current run finishes.
*/
multitaskStrategy?: MultitaskStrategy;
/**
* Abort controller signal to cancel the run.
*/
signal?: AbortController["signal"];
/**
* Behavior to handle run completion. Only relevant if
* there is a pending/inflight run on the same thread. One of:
* - "complete": Complete the run.
* - "continue": Continue the run.
*/
onCompletion?: OnCompletionBehavior;
/**
* Webhook to call when the run is complete.
*/
webhook?: string;
/**
* Behavior to handle disconnection. Only relevant if
* there is a pending/inflight run on the same thread. One of:
* - "cancel": Cancel the run.
* - "continue": Continue the run.
*/
onDisconnect?: DisconnectMode;
/**
* The number of seconds to wait before starting the run.
* Use to schedule future runs.
*/
afterSeconds?: number;
/**
* Behavior if the specified run doesn't exist. Defaults to "reject".
*/
ifNotExists?: "create" | "reject";
/**
* One or more commands to invoke the graph with.
*/
command?: Command;
}
export interface RunsStreamPayload<
TStreamMode extends StreamMode | StreamMode[] = [],
TSubgraphs extends boolean = false,
> extends RunsInvokePayload {
/**
* One of `"values"`, `"messages"`, `"messages-tuple"`, `"updates"`, `"events"`, `"debug"`, `"custom"`.
*/
streamMode?: TStreamMode;
/**
* Stream output from subgraphs. By default, streams only the top graph.
*/
streamSubgraphs?: TSubgraphs;
/**
* Pass one or more feedbackKeys if you want to request short-lived signed URLs
* for submitting feedback to LangSmith with this key for this run.
*/
feedbackKeys?: string[];
}
export interface RunsCreatePayload extends RunsInvokePayload {
/**
* One of `"values"`, `"messages"`, `"messages-tuple"`, `"updates"`, `"events"`, `"debug"`, `"custom"`.
*/
streamMode?: StreamMode | Array<StreamMode>;
/**
* Stream output from subgraphs. By default, streams only the top graph.
*/
streamSubgraphs?: boolean;
}
export interface CronsCreatePayload extends RunsCreatePayload {
/**
* Schedule for running the Cron Job
*/
schedule: string;
}
export interface RunsWaitPayload extends RunsStreamPayload {
/**
* Raise errors returned by the run. Default is `true`.
*/
raiseError?: boolean;
}
-220
View File
@@ -1,220 +0,0 @@
import pRetry from "p-retry";
import PQueueMod from "p-queue";
import { _getFetchImplementation } from "../singletons/fetch.js";
const STATUS_NO_RETRY = [
400, // Bad Request
401, // Unauthorized
402, // Payment required
403, // Forbidden
404, // Not Found
405, // Method Not Allowed
406, // Not Acceptable
407, // Proxy Authentication Required
408, // Request Timeout
422, // Unprocessable Entity
];
const STATUS_IGNORE = [
409, // Conflict
];
type ResponseCallback = (response?: Response) => Promise<boolean>;
export interface AsyncCallerParams {
/**
* The maximum number of concurrent calls that can be made.
* Defaults to `Infinity`, which means no limit.
*/
maxConcurrency?: number;
/**
* The maximum number of retries that can be made for a single call,
* with an exponential backoff between each attempt. Defaults to 6.
*/
maxRetries?: number;
onFailedResponseHook?: ResponseCallback;
/**
* Specify a custom fetch implementation.
*
* By default we expect the `fetch` is available in the global scope.
*/
fetch?: typeof fetch | ((...args: any[]) => any);
}
export interface AsyncCallerCallOptions {
signal?: AbortSignal;
}
/**
* Do not rely on globalThis.Response, rather just
* do duck typing
*/
function isResponse(x: unknown): x is Response {
if (x == null || typeof x !== "object") return false;
return "status" in x && "statusText" in x && "text" in x;
}
/**
* Utility error to properly handle failed requests
*/
class HTTPError extends Error {
status: number;
text: string;
response?: Response;
constructor(status: number, message: string, response?: Response) {
super(`HTTP ${status}: ${message}`);
this.status = status;
this.text = message;
this.response = response;
}
static async fromResponse(
response: Response,
options?: { includeResponse?: boolean },
): Promise<HTTPError> {
try {
return new HTTPError(
response.status,
await response.text(),
options?.includeResponse ? response : undefined,
);
} catch {
return new HTTPError(
response.status,
response.statusText,
options?.includeResponse ? response : undefined,
);
}
}
}
/**
* A class that can be used to make async calls with concurrency and retry logic.
*
* This is useful for making calls to any kind of "expensive" external resource,
* be it because it's rate-limited, subject to network issues, etc.
*
* Concurrent calls are limited by the `maxConcurrency` parameter, which defaults
* to `Infinity`. This means that by default, all calls will be made in parallel.
*
* Retries are limited by the `maxRetries` parameter, which defaults to 5. This
* means that by default, each call will be retried up to 5 times, with an
* exponential backoff between each attempt.
*/
export class AsyncCaller {
protected maxConcurrency: AsyncCallerParams["maxConcurrency"];
protected maxRetries: AsyncCallerParams["maxRetries"];
private queue: (typeof import("p-queue"))["default"]["prototype"];
private onFailedResponseHook?: ResponseCallback;
private customFetch?: typeof fetch;
constructor(params: AsyncCallerParams) {
this.maxConcurrency = params.maxConcurrency ?? Infinity;
this.maxRetries = params.maxRetries ?? 4;
if ("default" in PQueueMod) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.queue = new (PQueueMod.default as any)({
concurrency: this.maxConcurrency,
});
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.queue = new (PQueueMod as any)({ concurrency: this.maxConcurrency });
}
this.onFailedResponseHook = params?.onFailedResponseHook;
this.customFetch = params.fetch;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
call<A extends any[], T extends (...args: A) => Promise<any>>(
callable: T,
...args: Parameters<T>
): Promise<Awaited<ReturnType<T>>> {
const onFailedResponseHook = this.onFailedResponseHook;
return this.queue.add(
() =>
pRetry(
() =>
callable(...(args as Parameters<T>)).catch(async (error) => {
// eslint-disable-next-line no-instanceof/no-instanceof
if (error instanceof Error) {
throw error;
} else if (isResponse(error)) {
throw await HTTPError.fromResponse(error, {
includeResponse: !!onFailedResponseHook,
});
} else {
throw new Error(error);
}
}),
{
async onFailedAttempt(error) {
if (
error.message.startsWith("Cancel") ||
error.message.startsWith("TimeoutError") ||
error.message.startsWith("AbortError")
) {
throw error;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((error as any)?.code === "ECONNABORTED") {
throw error;
}
if (error instanceof HTTPError) {
if (STATUS_NO_RETRY.includes(error.status)) {
throw error;
} else if (STATUS_IGNORE.includes(error.status)) {
return;
}
if (onFailedResponseHook && error.response) {
await onFailedResponseHook(error.response);
}
}
},
// If needed we can change some of the defaults here,
// but they're quite sensible.
retries: this.maxRetries,
randomize: true,
},
),
{ throwOnTimeout: true },
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callWithOptions<A extends any[], T extends (...args: A) => Promise<any>>(
options: AsyncCallerCallOptions,
callable: T,
...args: Parameters<T>
): Promise<Awaited<ReturnType<T>>> {
// Note this doesn't cancel the underlying request,
// when available prefer to use the signal option of the underlying call
if (options.signal) {
return Promise.race([
this.call<A, T>(callable, ...args),
new Promise<never>((_, reject) => {
options.signal?.addEventListener("abort", () => {
reject(new Error("AbortError"));
});
}),
]);
}
return this.call<A, T>(callable, ...args);
}
fetch(...args: Parameters<typeof fetch>): ReturnType<typeof fetch> {
const fetchFn =
this.customFetch ?? (_getFetchImplementation() as typeof fetch);
return this.call(() =>
fetchFn(...args).then((res) => (res.ok ? res : Promise.reject(res))),
);
}
}
-11
View File
@@ -1,11 +0,0 @@
export function getEnvironmentVariable(name: string): string | undefined {
// Certain setups (Deno, frontend) will throw an error if you try to access environment variables
try {
return typeof process !== "undefined"
? // eslint-disable-next-line no-process-env
process.env?.[name]
: undefined;
} catch (e) {
return undefined;
}
}
-22
View File
@@ -1,22 +0,0 @@
export function mergeSignals(...signals: (AbortSignal | null | undefined)[]) {
const nonZeroSignals = signals.filter(
(signal): signal is AbortSignal => signal != null,
);
if (nonZeroSignals.length === 0) return undefined;
if (nonZeroSignals.length === 1) return nonZeroSignals[0];
const controller = new AbortController();
for (const signal of signals) {
if (signal?.aborted) {
controller.abort(signal.reason);
return controller.signal;
}
signal?.addEventListener("abort", () => controller.abort(signal.reason), {
once: true,
});
}
return controller.signal;
}
-176
View File
@@ -1,176 +0,0 @@
const CR = "\r".charCodeAt(0);
const LF = "\n".charCodeAt(0);
const NULL = "\0".charCodeAt(0);
const COLON = ":".charCodeAt(0);
const SPACE = " ".charCodeAt(0);
const TRAILING_NEWLINE = [CR, LF];
export class BytesLineDecoder extends TransformStream<Uint8Array, Uint8Array> {
constructor() {
let buffer: Uint8Array[] = [];
let trailingCr = false;
super({
start() {
buffer = [];
trailingCr = false;
},
transform(chunk, controller) {
// See https://docs.python.org/3/glossary.html#term-universal-newlines
let text = chunk;
// Handle trailing CR from previous chunk
if (trailingCr) {
text = joinArrays([[CR], text]);
trailingCr = false;
}
// Check for trailing CR in current chunk
if (text.length > 0 && text.at(-1) === CR) {
trailingCr = true;
text = text.subarray(0, -1);
}
if (!text.length) return;
const trailingNewline = TRAILING_NEWLINE.includes(text.at(-1)!);
const lastIdx = text.length - 1;
const { lines } = text.reduce<{ lines: Uint8Array[]; from: number }>(
(acc, cur, idx) => {
if (acc.from > idx) return acc;
if (cur === CR || cur === LF) {
acc.lines.push(text.subarray(acc.from, idx));
if (cur === CR && text[idx + 1] === LF) {
acc.from = idx + 2;
} else {
acc.from = idx + 1;
}
}
if (idx === lastIdx && acc.from <= lastIdx) {
acc.lines.push(text.subarray(acc.from));
}
return acc;
},
{ lines: [], from: 0 },
);
if (lines.length === 1 && !trailingNewline) {
buffer.push(lines[0]);
return;
}
if (buffer.length) {
// Include existing buffer in first line
buffer.push(lines[0]);
lines[0] = joinArrays(buffer);
buffer = [];
}
if (!trailingNewline) {
// If the last segment is not newline terminated,
// buffer it for the next chunk
if (lines.length) buffer = [lines.pop()!];
}
// Enqueue complete lines
for (const line of lines) {
controller.enqueue(line);
}
},
flush(controller) {
if (buffer.length) {
controller.enqueue(joinArrays(buffer));
}
},
});
}
}
interface StreamPart {
event: string;
data: unknown;
}
export class SSEDecoder extends TransformStream<Uint8Array, StreamPart> {
constructor() {
let event = "";
let data: Uint8Array[] = [];
let lastEventId = "";
let retry: number | null = null;
const decoder = new TextDecoder();
super({
transform(chunk, controller) {
// Handle empty line case
if (!chunk.length) {
if (!event && !data.length && !lastEventId && retry == null) return;
const sse = {
event,
data: data.length ? decodeArraysToJson(decoder, data) : null,
};
// NOTE: as per the SSE spec, do not reset lastEventId
event = "";
data = [];
retry = null;
controller.enqueue(sse);
return;
}
// Ignore comments
if (chunk[0] === COLON) return;
const sepIdx = chunk.indexOf(COLON);
if (sepIdx === -1) return;
const fieldName = decoder.decode(chunk.subarray(0, sepIdx));
let value = chunk.subarray(sepIdx + 1);
if (value[0] === SPACE) value = value.subarray(1);
if (fieldName === "event") {
event = decoder.decode(value);
} else if (fieldName === "data") {
data.push(value);
} else if (fieldName === "id") {
if (value.indexOf(NULL) === -1) lastEventId = decoder.decode(value);
} else if (fieldName === "retry") {
const retryNum = Number.parseInt(decoder.decode(value));
if (!Number.isNaN(retryNum)) retry = retryNum;
}
},
flush(controller) {
if (event) {
controller.enqueue({
event,
data: data.length ? decodeArraysToJson(decoder, data) : null,
});
}
},
});
}
}
function joinArrays(data: ArrayLike<number>[]) {
const totalLength = data.reduce((acc, curr) => acc + curr.length, 0);
let merged = new Uint8Array(totalLength);
let offset = 0;
for (const c of data) {
merged.set(c, offset);
offset += c.length;
}
return merged;
}
function decodeArraysToJson(decoder: TextDecoder, data: ArrayLike<number>[]) {
return JSON.parse(decoder.decode(joinArrays(data)));
}
-115
View File
@@ -1,115 +0,0 @@
// in this case don't quite match.
type IterableReadableStreamInterface<T> = ReadableStream<T> & AsyncIterable<T>;
/*
* Support async iterator syntax for ReadableStreams in all environments.
* Source: https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export class IterableReadableStream<T>
extends ReadableStream<T>
implements IterableReadableStreamInterface<T>
{
public reader: ReadableStreamDefaultReader<T>;
ensureReader() {
if (!this.reader) {
this.reader = this.getReader();
}
}
async next(): Promise<IteratorResult<T>> {
this.ensureReader();
try {
const result = await this.reader.read();
if (result.done) {
this.reader.releaseLock(); // release lock when stream becomes closed
return {
done: true,
value: undefined,
};
} else {
return {
done: false,
value: result.value,
};
}
} catch (e) {
this.reader.releaseLock(); // release lock when stream becomes errored
throw e;
}
}
async return(): Promise<IteratorResult<T>> {
this.ensureReader();
// If wrapped in a Node stream, cancel is already called.
if (this.locked) {
const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
this.reader.releaseLock(); // release lock first
await cancelPromise; // now await it
}
return { done: true, value: undefined };
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async throw(e: any): Promise<IteratorResult<T>> {
this.ensureReader();
if (this.locked) {
const cancelPromise = this.reader.cancel(); // cancel first, but don't await yet
this.reader.releaseLock(); // release lock first
await cancelPromise; // now await it
}
throw e;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Not present in Node 18 types, required in latest Node 22
async [Symbol.asyncDispose]() {
await this.return();
}
[Symbol.asyncIterator]() {
return this;
}
static fromReadableStream<T>(stream: ReadableStream<T>) {
// From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream
const reader = stream.getReader();
return new IterableReadableStream<T>({
start(controller) {
return pump();
function pump(): Promise<T | undefined> {
return reader.read().then(({ done, value }) => {
// When no more data needs to be consumed, close the stream
if (done) {
controller.close();
return;
}
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
return pump();
});
}
},
cancel() {
reader.releaseLock();
},
});
}
static fromAsyncGenerator<T>(generator: AsyncGenerator<T>) {
return new IterableReadableStream<T>({
async pull(controller) {
const { value, done } = await generator.next();
// When no more data needs to be consumed, close the stream
if (done) {
controller.close();
}
// Fix: `else if (value)` will hang the streaming when nullish value (e.g. empty string) is pulled
controller.enqueue(value);
},
async cancel(reason) {
await generator.return(reason);
},
});
}
}
-9
View File
@@ -1,9 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "CommonJS",
"moduleResolution": "Node",
"declaration": false
},
"exclude": ["node_modules", "dist", "**/tests"]
}
-47
View File
@@ -1,47 +0,0 @@
{
"extends": "@tsconfig/recommended",
"compilerOptions": {
"target": "ES2021",
"lib": [
"ES2021",
"ES2022.Object",
"DOM"
],
"module": "NodeNext",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"declaration": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"useDefineForClassFields": true,
"strictPropertyInitialization": false,
"allowJs": true,
"strict": true,
"jsx": "react-jsx",
"outDir": "dist"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
"coverage"
],
"includeVersion": true,
"typedocOptions": {
"entryPoints": [
"src/client.ts"
],
"readme": "none",
"out": "docs",
"plugin": [
"typedoc-plugin-markdown"
],
"excludePrivate": true,
"excludeProtected": true,
"excludeExternals": false
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"pageTitleTemplates": {
"index": "{projectName}/react"
}
}
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 LangChain, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-24
View File
@@ -1,24 +0,0 @@
.PHONY: lint format
test:
echo "No tests to run"
######################
# LINTING AND FORMATTING
######################
# Define a variable for Python and notebook files.
PYTHON_FILES=.
MYPY_CACHE=.mypy_cache
lint format: PYTHON_FILES=.
lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$')
lint lint_diff:
poetry run ruff check .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff check --select I $(PYTHON_FILES)
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
poetry run ruff check --select I --fix $(PYTHON_FILES)
poetry run ruff format $(PYTHON_FILES)
-35
View File
@@ -1,35 +0,0 @@
# LangGraph Python SDK
This repository contains the Python SDK for interacting with the LangGraph Cloud REST API.
## Quick Start
To get started with the Python SDK, [install the package](https://pypi.org/project/langgraph-sdk/)
```bash
pip install -U langgraph-sdk
```
You will need a running LangGraph API server. If you're running a server locally using `langgraph-cli`, SDK will automatically point at `http://localhost:8123`, otherwise
you would need to specify the server URL when creating a client.
```python
from langgraph_sdk import get_client
# If you're using a remote server, initialize the client with `get_client(url=REMOTE_URL)`
client = get_client()
# List all assistants
assistants = await client.assistants.search()
# We auto-create an assistant for each graph you register in config.
agent = assistants[0]
# Start a new thread
thread = await client.threads.create()
# Start a streaming run
input = {"messages": [{"role": "human", "content": "what's the weather in la"}]}
async for chunk in client.runs.stream(thread['thread_id'], agent['assistant_id'], input=input):
print(chunk)
```
-11
View File
@@ -1,11 +0,0 @@
from langgraph_sdk.auth import Auth
from langgraph_sdk.client import get_client, get_sync_client
try:
from importlib import metadata
__version__ = metadata.version(__package__)
except metadata.PackageNotFoundError:
__version__ = "unknown"
__all__ = ["Auth", "get_client", "get_sync_client"]
-727
View File
@@ -1,727 +0,0 @@
from __future__ import annotations
import inspect
import typing
from collections.abc import Callable, Sequence
from langgraph_sdk.auth import exceptions, types
TH = typing.TypeVar("TH", bound=types.Handler)
AH = typing.TypeVar("AH", bound=types.Authenticator)
class Auth:
"""Add custom authentication and authorization management to your LangGraph application.
The Auth class provides a unified system for handling authentication and
authorization in LangGraph applications. It supports custom user authentication
protocols and fine-grained authorization rules for different resources and
actions.
To use, create a separate python file and add the path to the file to your
LangGraph API configuration file (`langgraph.json`). Within that file, create
an instance of the Auth class and register authentication and authorization
handlers as needed.
Example `langgraph.json` file:
```json
{
"dependencies": ["."],
"graphs": {
"agent": "./my_agent/agent.py:graph"
},
"env": ".env",
"auth": {
"path": "./auth.py:my_auth"
}
```
Then the LangGraph server will load your auth file and run it server-side whenever a request comes in.
???+ example "Basic Usage"
```python
from langgraph_sdk import Auth
my_auth = Auth()
async def verify_token(token: str) -> str:
# Verify token and return user_id
# This would typically be a call to your auth server
return "user_id"
@auth.authenticate
async def authenticate(authorization: str) -> str:
# Verify token and return user_id
result = await verify_token(authorization)
if result != "user_id":
raise Auth.exceptions.HTTPException(
status_code=401, detail="Unauthorized"
)
return result
# Global fallback handler
@auth.on
async def authorize_default(params: Auth.on.value):
return False # Reject all requests (default behavior)
@auth.on.threads.create
async def authorize_thread_create(params: Auth.on.threads.create.value):
# Allow the allowed user to create a thread
assert params.get("metadata", {}).get("owner") == "allowed_user"
@auth.on.store
async def authorize_store(ctx: Auth.types.AuthContext, value: Auth.types.on):
assert ctx.user.identity in value["namespace"], "Not authorized"
```
???+ note "Request Processing Flow"
1. Authentication (your `@auth.authenticate` handler) is performed first on **every request**
2. For authorization, the most specific matching handler is called:
* If a handler exists for the exact resource and action, it is used (e.g., `@auth.on.threads.create`)
* Otherwise, if a handler exists for the resource with any action, it is used (e.g., `@auth.on.threads`)
* Finally, if no specific handlers match, the global handler is used (e.g., `@auth.on`)
* If no global handler is set, the request is accepted
This allows you to set default behavior with a global handler while
overriding specific routes as needed.
"""
__slots__ = (
"on",
"_handlers",
"_global_handlers",
"_authenticate_handler",
"_handler_cache",
)
types = types
"""Reference to auth type definitions.
Provides access to all type definitions used in the auth system,
like ThreadsCreate, AssistantsRead, etc."""
exceptions = exceptions
"""Reference to auth exception definitions.
Provides access to all exception definitions used in the auth system,
like HTTPException, etc.
"""
def __init__(self) -> None:
self.on = _On(self)
"""Entry point for authorization handlers that control access to specific resources.
The on class provides a flexible way to define authorization rules for different
resources and actions in your application. It supports three main usage patterns:
1. Global handlers that run for all resources and actions
2. Resource-specific handlers that run for all actions on a resource
3. Resource and action specific handlers for fine-grained control
Each handler must be an async function that accepts two parameters:
- ctx (AuthContext): Contains request context and authenticated user info
- value: The data being authorized (type varies by endpoint)
The handler should return one of:
- None or True: Accept the request
- False: Reject with 403 error
- FilterType: Apply filtering rules to the response
???+ example "Examples"
Global handler for all requests:
```python
@auth.on
async def reject_unhandled_requests(ctx: AuthContext, value: Any) -> None:
print(f"Request to {ctx.path} by {ctx.user.identity}")
return False
```
Resource-specific handler. This would take precedence over the global handler
for all actions on the `threads` resource:
```python
@auth.on.threads
async def check_thread_access(ctx: AuthContext, value: Any) -> bool:
# Allow access only to threads created by the user
return value.get("created_by") == ctx.user.identity
```
Resource and action specific handler:
```python
@auth.on.threads.delete
async def prevent_thread_deletion(ctx: AuthContext, value: Any) -> bool:
# Only admins can delete threads
return "admin" in ctx.user.permissions
```
Multiple resources or actions:
```python
@auth.on(resources=["threads", "runs"], actions=["create", "update"])
async def rate_limit_writes(ctx: AuthContext, value: Any) -> bool:
# Implement rate limiting for write operations
return await check_rate_limit(ctx.user.identity)
```
Auth for the `store` resource is a bit different since its structure is developer defined.
You typically want to enforce user creds in the namespace. Y
```python
@auth.on.store
async def check_store_access(ctx: AuthContext, value: Auth.types.on) -> bool:
# Assuming you structure your store like (store.aput((user_id, application_context), key, value))
assert value["namespace"][0] == ctx.user.identity
```
"""
# These are accessed by the API. Changes to their names or types is
# will be considered a breaking change.
self._handlers: dict[tuple[str, str], list[types.Handler]] = {}
self._global_handlers: list[types.Handler] = []
self._authenticate_handler: typing.Optional[types.Authenticator] = None
self._handler_cache: dict[tuple[str, str], types.Handler] = {}
def authenticate(self, fn: AH) -> AH:
"""Register an authentication handler function.
The authentication handler is responsible for verifying credentials
and returning user scopes. It can accept any of the following parameters
by name:
- request (Request): The raw ASGI request object
- body (dict): The parsed request body
- path (str): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
- method (str): The HTTP method, e.g., "GET"
- path_params (dict[str, str]): URL path parameters, e.g., {"thread_id": "abcd-1234-abcd-1234", "run_id": "abcd-1234-abcd-1234"}
- query_params (dict[str, str]): URL query parameters, e.g., {"stream": "true"}
- headers (dict[bytes, bytes]): Request headers
- authorization (str | None): The Authorization header value (e.g., "Bearer <token>")
Args:
fn (Callable): The authentication handler function to register.
Must return a representation of the user. This could be a:
- string (the user id)
- dict containing {"identity": str, "permissions": list[str]}
- or an object with identity and permissions properties
Permissions can be optionally used by your handlers downstream.
Returns:
The registered handler function.
Raises:
ValueError: If an authentication handler is already registered.
???+ example "Examples"
Basic token authentication:
```python
@auth.authenticate
async def authenticate(authorization: str) -> str:
user_id = verify_token(authorization)
return user_id
```
Accept the full request context:
```python
@auth.authenticate
async def authenticate(
method: str,
path: str,
headers: dict[str, bytes]
) -> str:
user = await verify_request(method, path, headers)
return user
```
Return user name and permissions:
```python
@auth.authenticate
async def authenticate(
method: str,
path: str,
headers: dict[str, bytes]
) -> Auth.types.MinimalUserDict:
permissions, user = await verify_request(method, path, headers)
# Permissions could be things like ["runs:read", "runs:write", "threads:read", "threads:write"]
return {
"identity": user["id"],
"permissions": permissions,
"display_name": user["name"],
}
```
"""
if self._authenticate_handler is not None:
raise ValueError(
"Authentication handler already set as {self._authenticate_handler}."
)
self._authenticate_handler = fn
return fn
## Helper types & utilities
V = typing.TypeVar("V", contravariant=True)
class _ActionHandler(typing.Protocol[V]):
async def __call__(
self, *, ctx: types.AuthContext, value: V
) -> types.HandlerResult: ...
T = typing.TypeVar("T", covariant=True)
class _ResourceActionOn(typing.Generic[T]):
def __init__(
self,
auth: Auth,
resource: typing.Literal["threads", "crons", "assistants"],
action: typing.Literal[
"create", "read", "update", "delete", "search", "create_run"
],
value: type[T],
) -> None:
self.auth = auth
self.resource = resource
self.action = action
self.value = value
def __call__(self, fn: _ActionHandler[T]) -> _ActionHandler[T]:
_validate_handler(fn)
_register_handler(self.auth, self.resource, self.action, fn)
return fn
VCreate = typing.TypeVar("VCreate", covariant=True)
VUpdate = typing.TypeVar("VUpdate", covariant=True)
VRead = typing.TypeVar("VRead", covariant=True)
VDelete = typing.TypeVar("VDelete", covariant=True)
VSearch = typing.TypeVar("VSearch", covariant=True)
class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
"""
Generic base class for resource-specific handlers.
"""
value: type[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]
Create: type[VCreate]
Read: type[VRead]
Update: type[VUpdate]
Delete: type[VDelete]
Search: type[VSearch]
def __init__(
self,
auth: Auth,
resource: typing.Literal["threads", "crons", "assistants"],
) -> None:
self.auth = auth
self.resource = resource
self.create: _ResourceActionOn[VCreate] = _ResourceActionOn(
auth, resource, "create", self.Create
)
self.read: _ResourceActionOn[VRead] = _ResourceActionOn(
auth, resource, "read", self.Read
)
self.update: _ResourceActionOn[VUpdate] = _ResourceActionOn(
auth, resource, "update", self.Update
)
self.delete: _ResourceActionOn[VDelete] = _ResourceActionOn(
auth, resource, "delete", self.Delete
)
self.search: _ResourceActionOn[VSearch] = _ResourceActionOn(
auth, resource, "search", self.Search
)
@typing.overload
def __call__(
self,
fn: typing.Union[
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
_ActionHandler[dict[str, typing.Any]],
],
) -> _ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]: ...
@typing.overload
def __call__(
self,
*,
resources: typing.Union[str, Sequence[str]],
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
) -> Callable[
[_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]],
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
]: ...
def __call__(
self,
fn: typing.Union[
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
_ActionHandler[dict[str, typing.Any]],
None,
] = None,
*,
resources: typing.Union[str, Sequence[str], None] = None,
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
) -> typing.Union[
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
Callable[
[_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]],
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
],
]:
if fn is not None:
_validate_handler(fn)
return typing.cast(
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
_register_handler(self.auth, self.resource, "*", fn),
)
def decorator(
handler: _ActionHandler[
typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]
],
) -> _ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]:
_validate_handler(handler)
return typing.cast(
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
_register_handler(self.auth, self.resource, "*", handler),
)
return decorator
class _AssistantsOn(
_ResourceOn[
types.AssistantsCreate,
types.AssistantsRead,
types.AssistantsUpdate,
types.AssistantsDelete,
types.AssistantsSearch,
]
):
value = typing.Union[
types.AssistantsCreate,
types.AssistantsRead,
types.AssistantsUpdate,
types.AssistantsDelete,
types.AssistantsSearch,
]
Create = types.AssistantsCreate
Read = types.AssistantsRead
Update = types.AssistantsUpdate
Delete = types.AssistantsDelete
Search = types.AssistantsSearch
class _ThreadsOn(
_ResourceOn[
types.ThreadsCreate,
types.ThreadsRead,
types.ThreadsUpdate,
types.ThreadsDelete,
types.ThreadsSearch,
]
):
value = typing.Union[
type[types.ThreadsCreate],
type[types.ThreadsRead],
type[types.ThreadsUpdate],
type[types.ThreadsDelete],
type[types.ThreadsSearch],
type[types.RunsCreate],
]
Create = types.ThreadsCreate
Read = types.ThreadsRead
Update = types.ThreadsUpdate
Delete = types.ThreadsDelete
Search = types.ThreadsSearch
CreateRun = types.RunsCreate
def __init__(
self,
auth: Auth,
resource: typing.Literal["threads", "crons", "assistants"],
) -> None:
super().__init__(auth, resource)
self.create_run: _ResourceActionOn[types.RunsCreate] = _ResourceActionOn(
auth, resource, "create_run", self.CreateRun
)
class _CronsOn(
_ResourceOn[
types.CronsCreate,
types.CronsRead,
types.CronsUpdate,
types.CronsDelete,
types.CronsSearch,
]
):
value = type[
typing.Union[
types.CronsCreate,
types.CronsRead,
types.CronsUpdate,
types.CronsDelete,
types.CronsSearch,
]
]
Create = types.CronsCreate
Read = types.CronsRead
Update = types.CronsUpdate
Delete = types.CronsDelete
Search = types.CronsSearch
class _StoreOn:
def __init__(self, auth: Auth) -> None:
self._auth = auth
@typing.overload
def __call__(
self,
*,
actions: typing.Optional[
typing.Union[
typing.Literal["put", "get", "search", "list_namespaces", "delete"],
Sequence[
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
],
]
] = None,
) -> Callable[[AHO], AHO]: ...
@typing.overload
def __call__(self, fn: AHO) -> AHO: ...
def __call__(
self,
fn: typing.Optional[AHO] = None,
*,
actions: typing.Optional[
typing.Union[
typing.Literal["put", "get", "search", "list_namespaces", "delete"],
Sequence[
typing.Literal["put", "get", "search", "list_namespaces", "delete"]
],
]
] = None,
) -> typing.Union[AHO, Callable[[AHO], AHO]]:
"""Register a handler for specific resources and actions.
Can be used as a decorator or with explicit resource/action parameters:
@auth.on.store
async def handler(): ... # Handle all store ops
@auth.on.store(actions=("put", "get", "search", "delete"))
async def handler(): ... # Handle specific store ops
@auth.on.store.put
async def handler(): ... # Handle store.put ops
"""
if fn is not None:
# Used as a plain decorator
_register_handler(self._auth, "store", None, fn)
return fn
# Used with parameters, return a decorator
def decorator(
handler: AHO,
) -> AHO:
if isinstance(actions, str):
action_list = [actions]
else:
action_list = list(actions) if actions is not None else ["*"]
for action in action_list:
_register_handler(self._auth, "store", action, handler)
return handler
return decorator
AHO = typing.TypeVar("AHO", bound=_ActionHandler[dict[str, typing.Any]])
class _On:
"""Entry point for authorization handlers that control access to specific resources.
The _On class provides a flexible way to define authorization rules for different resources
and actions in your application. It supports three main usage patterns:
1. Global handlers that run for all resources and actions
2. Resource-specific handlers that run for all actions on a resource
3. Resource and action specific handlers for fine-grained control
Each handler must be an async function that accepts two parameters:
- ctx (AuthContext): Contains request context and authenticated user info
- value: The data being authorized (type varies by endpoint)
The handler should return one of:
- None or True: Accept the request
- False: Reject with 403 error
- FilterType: Apply filtering rules to the response
???+ example "Examples"
Global handler for all requests:
```python
@auth.on
async def log_all_requests(ctx: AuthContext, value: Any) -> None:
print(f"Request to {ctx.path} by {ctx.user.identity}")
return True
```
Resource-specific handler:
```python
@auth.on.threads
async def check_thread_access(ctx: AuthContext, value: Any) -> bool:
# Allow access only to threads created by the user
return value.get("created_by") == ctx.user.identity
```
Resource and action specific handler:
```python
@auth.on.threads.delete
async def prevent_thread_deletion(ctx: AuthContext, value: Any) -> bool:
# Only admins can delete threads
return "admin" in ctx.user.permissions
```
Multiple resources or actions:
```python
@auth.on(resources=["threads", "runs"], actions=["create", "update"])
async def rate_limit_writes(ctx: AuthContext, value: Any) -> bool:
# Implement rate limiting for write operations
return await check_rate_limit(ctx.user.identity)
```
"""
__slots__ = (
"_auth",
"assistants",
"threads",
"runs",
"crons",
"store",
"value",
)
def __init__(self, auth: Auth) -> None:
self._auth = auth
self.assistants = _AssistantsOn(auth, "assistants")
self.threads = _ThreadsOn(auth, "threads")
self.crons = _CronsOn(auth, "crons")
self.store = _StoreOn(auth)
self.value = dict[str, typing.Any]
@typing.overload
def __call__(
self,
*,
resources: typing.Union[str, Sequence[str]],
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
) -> Callable[[AHO], AHO]: ...
@typing.overload
def __call__(self, fn: AHO) -> AHO: ...
def __call__(
self,
fn: typing.Optional[AHO] = None,
*,
resources: typing.Union[str, Sequence[str], None] = None,
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
) -> typing.Union[AHO, Callable[[AHO], AHO]]:
"""Register a handler for specific resources and actions.
Can be used as a decorator or with explicit resource/action parameters:
@auth.on
async def handler(): ... # Global handler
@auth.on(resources="threads")
async def handler(): ... # types.Handler for all thread actions
@auth.on(resources="threads", actions="create")
async def handler(): ... # types.Handler for thread creation
"""
if fn is not None:
# Used as a plain decorator
_register_handler(self._auth, None, None, fn)
return fn
# Used with parameters, return a decorator
def decorator(
handler: AHO,
) -> AHO:
if isinstance(resources, str):
resource_list = [resources]
else:
resource_list = list(resources) if resources is not None else ["*"]
if isinstance(actions, str):
action_list = [actions]
else:
action_list = list(actions) if actions is not None else ["*"]
for resource in resource_list:
for action in action_list:
_register_handler(self._auth, resource, action, handler)
return handler
return decorator
def _register_handler(
auth: Auth,
resource: typing.Optional[str],
action: typing.Optional[str],
fn: types.Handler,
) -> types.Handler:
_validate_handler(fn)
resource = resource or "*"
action = action or "*"
if resource == "*" and action == "*":
if auth._global_handlers:
raise ValueError("Global handler already set.")
auth._global_handlers.append(fn)
else:
r = resource if resource is not None else "*"
a = action if action is not None else "*"
if (r, a) in auth._handlers:
raise ValueError(f"types.Handler already set for {r}, {a}.")
auth._handlers[(r, a)] = [fn]
return fn
def _validate_handler(fn: Callable[..., typing.Any]) -> None:
"""Validates that an auth handler function meets the required signature.
Auth handlers must:
1. Be async functions
2. Accept a ctx parameter of type AuthContext
3. Accept a value parameter for the data being authorized
"""
if not inspect.iscoroutinefunction(fn):
raise ValueError(
f"Auth handler '{fn.__name__}' must be an async function. "
"Add 'async' before 'def' to make it asynchronous and ensure"
" any IO operations are non-blocking."
)
sig = inspect.signature(fn)
if "ctx" not in sig.parameters:
raise ValueError(
f"Auth handler '{fn.__name__}' must have a 'ctx: AuthContext' parameter. "
"Update the function signature to include this required parameter."
)
if "value" not in sig.parameters:
raise ValueError(
f"Auth handler '{fn.__name__}' must have a 'value' parameter. "
" The value contains the mutable data being sent to the endpoint."
"Update the function signature to include this required parameter."
)
__all__ = ["Auth", "types", "exceptions"]
@@ -1,58 +0,0 @@
"""Exceptions used in the auth system."""
import http
import typing
class HTTPException(Exception):
"""HTTP exception that you can raise to return a specific HTTP error response.
Since this is defined in the auth module, we default to a 401 status code.
Args:
status_code (int, optional): HTTP status code for the error. Defaults to 401 "Unauthorized".
detail (str | None, optional): Detailed error message. If None, uses a default
message based on the status code.
headers (typing.Mapping[str, str] | None, optional): Additional HTTP headers to
include in the error response.
Example:
Default:
```python
raise HTTPException()
# HTTPException(status_code=401, detail='Unauthorized')
```
Add headers:
```python
raise HTTPException(headers={"X-Custom-Header": "Custom Value"})
# HTTPException(status_code=401, detail='Unauthorized', headers={"WWW-Authenticate": "Bearer"})
```
Custom error:
```python
raise HTTPException(status_code=404, detail="Not found")
```
"""
def __init__(
self,
status_code: int = 401,
detail: typing.Optional[str] = None,
headers: typing.Optional[typing.Mapping[str, str]] = None,
) -> None:
if detail is None:
detail = http.HTTPStatus(status_code).phrase
self.status_code = status_code
self.detail = detail
self.headers = headers
def __str__(self) -> str:
return f"{self.status_code}: {self.detail}"
def __repr__(self) -> str:
class_name = self.__class__.__name__
return f"{class_name}(status_code={self.status_code!r}, detail={self.detail!r})"
__all__ = ["HTTPException"]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
View File
-419
View File
@@ -1,419 +0,0 @@
"""Data models for interacting with the LangGraph API."""
from datetime import datetime
from typing import (
Any,
Dict,
Literal,
NamedTuple,
Optional,
Sequence,
Tuple,
TypedDict,
Union,
)
Json = Optional[dict[str, Any]]
"""Represents a JSON-like structure, which can be None or a dictionary with string keys and any values."""
RunStatus = Literal["pending", "error", "success", "timeout", "interrupted"]
"""
Represents the status of a run:
- "pending": The run is waiting to start.
- "error": The run encountered an error and stopped.
- "success": The run completed successfully.
- "timeout": The run exceeded its time limit.
- "interrupted": The run was manually stopped or interrupted.
"""
ThreadStatus = Literal["idle", "busy", "interrupted", "error"]
"""
Represents the status of a thread:
- "idle": The thread is not currently processing any task.
- "busy": The thread is actively processing a task.
- "interrupted": The thread's execution was interrupted.
- "error": An exception occurred during task processing.
"""
StreamMode = Literal[
"values", "messages", "updates", "events", "debug", "custom", "messages-tuple"
]
"""
Defines the mode of streaming:
- "values": Stream only the values.
- "messages": Stream complete messages.
- "updates": Stream updates to the state.
- "events": Stream events occurring during execution.
- "debug": Stream detailed debug information.
- "custom": Stream custom events.
"""
DisconnectMode = Literal["cancel", "continue"]
"""
Specifies behavior on disconnection:
- "cancel": Cancel the operation on disconnection.
- "continue": Continue the operation even if disconnected.
"""
MultitaskStrategy = Literal["reject", "interrupt", "rollback", "enqueue"]
"""
Defines how to handle multiple tasks:
- "reject": Reject new tasks when busy.
- "interrupt": Interrupt current task for new ones.
- "rollback": Roll back current task and start new one.
- "enqueue": Queue new tasks for later execution.
"""
OnConflictBehavior = Literal["raise", "do_nothing"]
"""
Specifies behavior on conflict:
- "raise": Raise an exception when a conflict occurs.
- "do_nothing": Ignore conflicts and proceed.
"""
OnCompletionBehavior = Literal["delete", "keep"]
"""
Defines action after completion:
- "delete": Delete resources after completion.
- "keep": Retain resources after completion.
"""
All = Literal["*"]
"""Represents a wildcard or 'all' selector."""
IfNotExists = Literal["create", "reject"]
"""
Specifies behavior if the thread doesn't exist:
- "create": Create a new thread if it doesn't exist.
- "reject": Reject the operation if the thread doesn't exist.
"""
CancelAction = Literal["interrupt", "rollback"]
"""
Action to take when cancelling the run.
- "interrupt": Simply cancel the run.
- "rollback": Cancel the run. Then delete the run and associated checkpoints.
"""
class Config(TypedDict, total=False):
"""Configuration options for a call."""
tags: list[str]
"""
Tags for this call and any sub-calls (eg. a Chain calling an LLM).
You can use these to filter calls.
"""
recursion_limit: int
"""
Maximum number of times a call can recurse. If not provided, defaults to 25.
"""
configurable: dict[str, Any]
"""
Runtime values for attributes previously made configurable on this Runnable,
or sub-Runnables, through .configurable_fields() or .configurable_alternatives().
Check .output_schema() for a description of the attributes that have been made
configurable.
"""
class Checkpoint(TypedDict):
"""Represents a checkpoint in the execution process."""
thread_id: str
"""Unique identifier for the thread associated with this checkpoint."""
checkpoint_ns: str
"""Namespace for the checkpoint, used for organization and retrieval."""
checkpoint_id: Optional[str]
"""Optional unique identifier for the checkpoint itself."""
checkpoint_map: Optional[dict[str, Any]]
"""Optional dictionary containing checkpoint-specific data."""
class GraphSchema(TypedDict):
"""Defines the structure and properties of a graph."""
graph_id: str
"""The ID of the graph."""
input_schema: Optional[dict]
"""The schema for the graph input.
Missing if unable to generate JSON schema from graph."""
output_schema: Optional[dict]
"""The schema for the graph output.
Missing if unable to generate JSON schema from graph."""
state_schema: Optional[dict]
"""The schema for the graph state.
Missing if unable to generate JSON schema from graph."""
config_schema: Optional[dict]
"""The schema for the graph config.
Missing if unable to generate JSON schema from graph."""
Subgraphs = dict[str, GraphSchema]
class AssistantBase(TypedDict):
"""Base model for an assistant."""
assistant_id: str
"""The ID of the assistant."""
graph_id: str
"""The ID of the graph."""
config: Config
"""The assistant config."""
created_at: datetime
"""The time the assistant was created."""
metadata: Json
"""The assistant metadata."""
version: int
"""The version of the assistant"""
class AssistantVersion(AssistantBase):
"""Represents a specific version of an assistant."""
pass
class Assistant(AssistantBase):
"""Represents an assistant with additional properties."""
updated_at: datetime
"""The last time the assistant was updated."""
name: str
"""The name of the assistant"""
class Interrupt(TypedDict, total=False):
"""Represents an interruption in the execution flow."""
value: Any
"""The value associated with the interrupt."""
when: Literal["during"]
"""When the interrupt occurred."""
resumable: bool
"""Whether the interrupt can be resumed."""
ns: Optional[list[str]]
"""Optional namespace for the interrupt."""
class Thread(TypedDict):
"""Represents a conversation thread."""
thread_id: str
"""The ID of the thread."""
created_at: datetime
"""The time the thread was created."""
updated_at: datetime
"""The last time the thread was updated."""
metadata: Json
"""The thread metadata."""
status: ThreadStatus
"""The status of the thread, one of 'idle', 'busy', 'interrupted'."""
values: Json
"""The current state of the thread."""
interrupts: Dict[str, list[Interrupt]]
"""Interrupts which were thrown in this thread"""
class ThreadTask(TypedDict):
"""Represents a task within a thread."""
id: str
name: str
error: Optional[str]
interrupts: list[Interrupt]
checkpoint: Optional[Checkpoint]
state: Optional["ThreadState"]
result: Optional[dict[str, Any]]
class ThreadState(TypedDict):
"""Represents the state of a thread."""
values: Union[list[dict], dict[str, Any]]
"""The state values."""
next: Sequence[str]
"""The next nodes to execute. If empty, the thread is done until new input is
received."""
checkpoint: Checkpoint
"""The ID of the checkpoint."""
metadata: Json
"""Metadata for this state"""
created_at: Optional[str]
"""Timestamp of state creation"""
parent_checkpoint: Optional[Checkpoint]
"""The ID of the parent checkpoint. If missing, this is the root checkpoint."""
tasks: Sequence[ThreadTask]
"""Tasks to execute in this step. If already attempted, may contain an error."""
class ThreadUpdateStateResponse(TypedDict):
"""Represents the response from updating a thread's state."""
checkpoint: Checkpoint
"""Checkpoint of the latest state."""
class Run(TypedDict):
"""Represents a single execution run."""
run_id: str
"""The ID of the run."""
thread_id: str
"""The ID of the thread."""
assistant_id: str
"""The assistant that was used for this run."""
created_at: datetime
"""The time the run was created."""
updated_at: datetime
"""The last time the run was updated."""
status: RunStatus
"""The status of the run. One of 'pending', 'running', "error", 'success', "timeout", "interrupted"."""
metadata: Json
"""The run metadata."""
multitask_strategy: MultitaskStrategy
"""Strategy to handle concurrent runs on the same thread."""
class Cron(TypedDict):
"""Represents a scheduled task."""
cron_id: str
"""The ID of the cron."""
thread_id: Optional[str]
"""The ID of the thread."""
end_time: Optional[datetime]
"""The end date to stop running the cron."""
schedule: str
"""The schedule to run, cron format."""
created_at: datetime
"""The time the cron was created."""
updated_at: datetime
"""The last time the cron was updated."""
payload: dict
"""The run payload to use for creating new run."""
class RunCreate(TypedDict):
"""Defines the parameters for initiating a background run."""
thread_id: Optional[str]
"""The identifier of the thread to run. If not provided, the run is stateless."""
assistant_id: str
"""The identifier of the assistant to use for this run."""
input: Optional[dict]
"""Initial input data for the run."""
metadata: Optional[dict]
"""Additional metadata to associate with the run."""
config: Optional[Config]
"""Configuration options for the run."""
checkpoint_id: Optional[str]
"""The identifier of a checkpoint to resume from."""
interrupt_before: Optional[list[str]]
"""List of node names to interrupt execution before."""
interrupt_after: Optional[list[str]]
"""List of node names to interrupt execution after."""
webhook: Optional[str]
"""URL to send webhook notifications about the run's progress."""
multitask_strategy: Optional[MultitaskStrategy]
"""Strategy for handling concurrent runs on the same thread."""
class Item(TypedDict):
"""Represents a single document or data entry in the graph's Store.
Items are used to store cross-thread memories.
"""
namespace: list[str]
"""The namespace of the item. A namespace is analogous to a document's directory."""
key: str
"""The unique identifier of the item within its namespace.
In general, keys needn't be globally unique.
"""
value: dict[str, Any]
"""The value stored in the item. This is the document itself."""
created_at: datetime
"""The timestamp when the item was created."""
updated_at: datetime
"""The timestamp when the item was last updated."""
class ListNamespaceResponse(TypedDict):
"""Response structure for listing namespaces."""
namespaces: list[list[str]]
"""A list of namespace paths, where each path is a list of strings."""
class SearchItem(Item, total=False):
"""Item with an optional relevance score from search operations.
Attributes:
score (Optional[float]): Relevance/similarity score. Included when
searching a compatible store with a natural language query.
"""
score: Optional[float]
class SearchItemsResponse(TypedDict):
"""Response structure for searching items."""
items: list[SearchItem]
"""A list of items matching the search criteria."""
class StreamPart(NamedTuple):
"""Represents a part of a stream response."""
event: str
"""The type of event for this stream part."""
data: dict
"""The data payload associated with the event."""
class Send(TypedDict):
"""Represents a message to be sent to a specific node in the graph.
This type is used to explicitly send messages to nodes in the graph, typically
used within Command objects to control graph execution flow.
"""
node: str
"""The name of the target node to send the message to."""
input: Optional[dict[str, Any]]
"""Optional dictionary containing the input data to be passed to the node.
If None, the node will be called with no input."""
class Command(TypedDict, total=False):
"""Represents one or more commands to control graph execution flow and state.
This type defines the control commands that can be returned by nodes to influence
graph execution. It lets you navigate to other nodes, update graph state,
and resume from interruptions.
"""
goto: Union[Send, str, Sequence[Union[Send, str]]]
"""Specifies where execution should continue. Can be:
- A string node name to navigate to
- A Send object to execute a node with specific input
- A sequence of node names or Send objects to execute in order
"""
update: Union[dict[str, Any], Sequence[Tuple[str, Any]]]
"""Updates to apply to the graph's state. Can be:
- A dictionary of state updates to merge
- A sequence of (key, value) tuples for ordered updates
"""
resume: Any
"""Value to resume execution with after an interruption.
Used in conjunction with interrupt() to implement control flow.
"""
-148
View File
@@ -1,148 +0,0 @@
"""Adapted from httpx_sse to split lines on \n, \r, \r\n per the SSE spec."""
from typing import AsyncIterator, Iterator, Optional, Union
import httpx
import orjson
from langgraph_sdk.schema import StreamPart
BytesLike = Union[bytes, bytearray, memoryview]
class BytesLineDecoder:
"""
Handles incrementally reading lines from text.
Has the same behaviour as the stdllib bytes splitlines,
but handling the input iteratively.
"""
def __init__(self) -> None:
self.buffer = bytearray()
self.trailing_cr: bool = False
def decode(self, text: bytes) -> list[BytesLike]:
# See https://docs.python.org/3/glossary.html#term-universal-newlines
NEWLINE_CHARS = b"\n\r"
# We always push a trailing `\r` into the next decode iteration.
if self.trailing_cr:
text = b"\r" + text
self.trailing_cr = False
if text.endswith(b"\r"):
self.trailing_cr = True
text = text[:-1]
if not text:
# NOTE: the edge case input of empty text doesn't occur in practice,
# because other httpx internals filter out this value
return [] # pragma: no cover
trailing_newline = text[-1] in NEWLINE_CHARS
lines = text.splitlines()
if len(lines) == 1 and not trailing_newline:
# No new lines, buffer the input and continue.
self.buffer.extend(lines[0])
return []
if self.buffer:
# Include any existing buffer in the first portion of the
# splitlines result.
self.buffer.extend(lines[0])
lines = [self.buffer] + lines[1:]
self.buffer = bytearray()
if not trailing_newline:
# If the last segment of splitlines is not newline terminated,
# then drop it from our output and start a new buffer.
self.buffer.extend(lines.pop())
return lines
def flush(self) -> list[BytesLike]:
if not self.buffer and not self.trailing_cr:
return []
lines = [self.buffer]
self.buffer = bytearray()
self.trailing_cr = False
return lines
class SSEDecoder:
def __init__(self) -> None:
self._event = ""
self._data = bytearray()
self._last_event_id = ""
self._retry: Optional[int] = None
def decode(self, line: bytes) -> Optional[StreamPart]:
# See: https://html.spec.whatwg.org/multipage/server-sent-events.html#event-stream-interpretation # noqa: E501
if not line:
if (
not self._event
and not self._data
and not self._last_event_id
and self._retry is None
):
return None
sse = StreamPart(
event=self._event,
data=orjson.loads(self._data) if self._data else None,
)
# NOTE: as per the SSE spec, do not reset last_event_id.
self._event = ""
self._data = bytearray()
self._retry = None
return sse
if line.startswith(b":"):
return None
fieldname, _, value = line.partition(b":")
if value.startswith(b" "):
value = value[1:]
if fieldname == b"event":
self._event = value.decode()
elif fieldname == b"data":
self._data.extend(value)
elif fieldname == b"id":
if b"\0" in value:
pass
else:
self._last_event_id = value.decode()
elif fieldname == b"retry":
try:
self._retry = int(value)
except (TypeError, ValueError):
pass
else:
pass # Field is ignored.
return None
async def aiter_lines_raw(response: httpx.Response) -> AsyncIterator[BytesLike]:
decoder = BytesLineDecoder()
async for chunk in response.aiter_bytes():
for line in decoder.decode(chunk):
yield line
for line in decoder.flush():
yield line
def iter_lines_raw(response: httpx.Response) -> Iterator[BytesLike]:
decoder = BytesLineDecoder()
for chunk in response.iter_bytes():
for line in decoder.decode(chunk):
yield line
for line in decoder.flush():
yield line
-551
View File
@@ -1,551 +0,0 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "anyio"
version = "4.7.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.9"
files = [
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
trio = ["trio (>=0.26.1)"]
[[package]]
name = "certifi"
version = "2024.8.30"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
]
[[package]]
name = "codespell"
version = "2.3.0"
description = "Codespell"
optional = false
python-versions = ">=3.8"
files = [
{file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"},
{file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"},
]
[package.extras]
dev = ["Pygments", "build", "chardet", "pre-commit", "pytest", "pytest-cov", "pytest-dependency", "ruff", "tomli", "twine"]
hard-encoding-detection = ["chardet"]
toml = ["tomli"]
types = ["chardet (>=5.1.0)", "mypy", "pytest", "pytest-cov", "pytest-dependency"]
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "docopt"
version = "0.6.2"
description = "Pythonic argument parser, that will make you smile"
optional = false
python-versions = "*"
files = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
]
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "1.0.7"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "idna"
version = "3.10"
description = "Internationalized Domain Names in Applications (IDNA)"
optional = false
python-versions = ">=3.6"
files = [
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
]
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "mypy"
version = "1.13.0"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
{file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
{file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
{file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
{file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
{file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
{file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
{file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
{file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
{file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
{file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
{file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
{file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
{file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
{file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
{file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
{file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
{file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
{file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
{file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
{file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
{file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
{file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
{file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
{file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
{file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
{file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
{file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
{file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
{file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
{file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
{file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
]
[package.dependencies]
mypy-extensions = ">=1.0.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = ">=4.6.0"
[package.extras]
dmypy = ["psutil (>=4.0)"]
faster-cache = ["orjson"]
install-types = ["pip"]
mypyc = ["setuptools (>=50)"]
reports = ["lxml"]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "orjson"
version = "3.10.12"
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
optional = false
python-versions = ">=3.8"
files = [
{file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"},
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"},
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"},
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"},
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"},
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"},
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"},
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"},
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"},
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"},
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"},
{file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"},
{file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"},
{file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"},
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"},
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"},
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"},
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"},
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"},
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"},
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"},
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"},
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"},
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"},
{file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"},
{file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"},
{file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"},
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"},
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"},
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"},
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"},
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"},
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"},
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"},
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"},
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"},
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"},
{file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"},
{file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"},
{file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"},
{file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"},
{file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"},
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"},
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"},
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"},
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"},
{file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"},
{file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"},
{file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"},
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"},
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"},
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"},
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"},
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"},
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"},
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"},
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"},
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"},
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"},
{file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"},
{file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"},
{file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"},
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"},
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"},
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"},
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"},
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"},
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"},
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"},
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"},
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"},
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"},
{file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"},
{file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"},
{file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"},
]
[[package]]
name = "packaging"
version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
name = "pluggy"
version = "1.5.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pytest"
version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-asyncio"
version = "0.21.2"
description = "Pytest support for asyncio"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest_asyncio-0.21.2-py3-none-any.whl", hash = "sha256:ab664c88bb7998f711d8039cacd4884da6430886ae8bbd4eded552ed2004f16b"},
{file = "pytest_asyncio-0.21.2.tar.gz", hash = "sha256:d67738fc232b94b326b9d060750beb16e0074210b98dd8b58a5239fa2a154f45"},
]
[package.dependencies]
pytest = ">=7.0.0"
[package.extras]
docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
[[package]]
name = "pytest-mock"
version = "3.14.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
optional = false
python-versions = ">=3.8"
files = [
{file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
{file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
]
[package.dependencies]
pytest = ">=6.2.5"
[package.extras]
dev = ["pre-commit", "pytest-asyncio", "tox"]
[[package]]
name = "pytest-watch"
version = "4.2.0"
description = "Local continuous test runner with pytest and watchdog."
optional = false
python-versions = "*"
files = [
{file = "pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9"},
]
[package.dependencies]
colorama = ">=0.3.3"
docopt = ">=0.4.0"
pytest = ">=2.6.4"
watchdog = ">=0.6.0"
[[package]]
name = "ruff"
version = "0.6.9"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
{file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
{file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"},
{file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"},
{file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"},
{file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"},
{file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"},
{file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"},
{file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]]
name = "tomli"
version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "watchdog"
version = "6.0.0"
description = "Filesystem events monitoring"
optional = false
python-versions = ">=3.9"
files = [
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"},
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"},
{file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"},
{file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"},
{file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"},
{file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"},
{file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"},
{file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"},
{file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"},
{file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"},
{file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"},
{file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"},
{file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"},
{file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"},
{file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"},
{file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"},
{file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"},
{file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"},
{file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"},
{file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"},
{file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"},
{file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"},
{file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"},
{file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"},
{file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"},
{file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"},
{file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"},
{file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"},
{file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"},
{file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"},
]
[package.extras]
watchmedo = ["PyYAML (>=3.10)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.9.0,<4.0"
content-hash = "1262a6148df18cc44ade00466b6e0f8305897a460eea370c8de649d8d20cd7a2"
-48
View File
@@ -1,48 +0,0 @@
[tool.poetry]
name = "langgraph-sdk"
version = "0.1.53"
description = "SDK for interacting with LangGraph API"
authors = []
license = "MIT"
readme = "README.md"
repository = "https://www.github.com/langchain-ai/langgraph"
packages = [{ include = "langgraph_sdk" }]
[tool.poetry.dependencies]
python = "^3.9.0,<4.0"
httpx = ">=0.25.2"
orjson = ">=3.10.1"
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.2"
codespell = "^2.2.0"
pytest = "^7.2.1"
pytest-asyncio = "^0.21.1"
pytest-mock = "^3.11.1"
pytest-watch = "^4.2.0"
mypy = "^1.10.0"
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
addopts = "--strict-markers --strict-config --durations=5 -vv"
asyncio_mode = "auto"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
lint.select = [
"E", # pycodestyle
"F", # Pyflakes
"UP", # pyupgrade
"B", # flake8-bugbear
"I", # isort
]
lint.ignore = ["E501", "B008", "UP007", "UP006"]