mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-28 10:49:56 +02:00
Remove sdks
This commit is contained in:
@@ -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 +0,0 @@
|
||||
{}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
@@ -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";
|
||||
@@ -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 +0,0 @@
|
||||
export { useStream, type MessageMetadata } from "./stream.js";
|
||||
@@ -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),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
};
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
>;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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))),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"declaration": false
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "**/tests"]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"pageTitleTemplates": {
|
||||
"index": "{projectName}/react"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
```
|
||||
@@ -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"]
|
||||
@@ -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
@@ -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.
|
||||
"""
|
||||
@@ -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
|
||||
Generated
-551
@@ -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"
|
||||
@@ -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"]
|
||||
Reference in New Issue
Block a user