mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
chore: move sdk-js to langgraphjs (#5334)
This commit is contained in:
@@ -24,7 +24,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
python: ${{ steps.filter.outputs.python }}
|
||||
sdk-js: ${{ steps.filter.outputs.sdk-js }}
|
||||
deps: ${{ steps.filter.outputs.deps }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -40,8 +39,6 @@ jobs:
|
||||
- 'libs/checkpoint-sqlite/**'
|
||||
- 'libs/checkpoint-postgres/**'
|
||||
- 'libs/prebuilt/**'
|
||||
sdk-js:
|
||||
- 'libs/sdk-js/**'
|
||||
deps:
|
||||
- '**/pyproject.toml'
|
||||
- '**/uv.lock'
|
||||
@@ -152,68 +149,16 @@ jobs:
|
||||
uses: ./.github/workflows/_integration_test.yml
|
||||
secrets: inherit
|
||||
|
||||
lint-js:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.sdk-js == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
working-directory:
|
||||
- "libs/sdk-js"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Run lint
|
||||
run: yarn lint
|
||||
- name: Build
|
||||
run: yarn build
|
||||
|
||||
test-js:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.sdk-js == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
working-directory:
|
||||
- "libs/sdk-js"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js (LTS)
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Run tests
|
||||
run: yarn test
|
||||
|
||||
ci_success:
|
||||
name: "CI Success"
|
||||
needs:
|
||||
[
|
||||
lint,
|
||||
lint-js,
|
||||
test,
|
||||
test-langgraph,
|
||||
check-sdk-methods,
|
||||
check-schema,
|
||||
integration-test,
|
||||
test-js,
|
||||
]
|
||||
if: |
|
||||
always()
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
name: JS Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
# Disallow publishing from branches that aren't `main`.
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
working-directory:
|
||||
- "libs/sdk-js"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ matrix.working-directory }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# JS Build
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "yarn"
|
||||
cache-dependency-path: ${{ matrix.working-directory }}/yarn.lock
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Publish package to NPM
|
||||
run: |
|
||||
echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc
|
||||
npm publish
|
||||
@@ -1,4 +1,3 @@
|
||||
site/
|
||||
docs/cloud/reference/sdk/js_ts_sdk_ref.md
|
||||
|
||||
.vercel
|
||||
|
||||
+3
-9
@@ -1,10 +1,4 @@
|
||||
.PHONY: lint-docs format-docs build-docs serve-docs serve-clean-docs clean-docs codespell build-typedoc llms-text build-prebuilt tests
|
||||
|
||||
build-typedoc:
|
||||
cd ../libs/sdk-js && yarn install --include-dev && yarn typedoc
|
||||
cd ../libs/sdk-js && yarn --silent concat-md --decrease-title-levels --ignore=js_ts_sdk_ref.md --start-title-level-at 2 docs > ../../docs/docs/cloud/reference/sdk/js_ts_sdk_ref.md 2>/dev/null
|
||||
# Add links to the monorepo
|
||||
sed -e '1,10s|@langchain/langgraph-sdk|[@langchain/langgraph-sdk](https://github.com/langchain-ai/langgraph/tree/main/libs/sdk-js)|g' docs/cloud/reference/sdk/js_ts_sdk_ref.md > temp_file && mv temp_file docs/cloud/reference/sdk/js_ts_sdk_ref.md
|
||||
.PHONY: lint-docs format-docs build-docs serve-docs serve-clean-docs clean-docs codespell llms-text build-prebuilt tests
|
||||
|
||||
build-prebuilt:
|
||||
# Use to create an update to date prebuilt page.
|
||||
@@ -21,7 +15,7 @@ build-prebuilt:
|
||||
fi
|
||||
uv run python -m _scripts.third_party_page.create_third_party_page stats.yml docs/agents/prebuilt.md --language python
|
||||
|
||||
build-docs: build-typedoc build-prebuilt
|
||||
build-docs: build-prebuilt
|
||||
uv run python -m mkdocs build --clean -f mkdocs.yml --strict
|
||||
|
||||
llms-text:
|
||||
@@ -45,7 +39,7 @@ vercel-build-docs: install-vercel-deps
|
||||
serve-clean-docs: clean-docs
|
||||
uv run python -m mkdocs serve -c -f mkdocs.yml --strict -w ../libs/langgraph
|
||||
|
||||
serve-docs: build-typedoc
|
||||
serve-docs:
|
||||
uv run python -m mkdocs serve -f mkdocs.yml -w ../libs/langgraph -w ../libs/checkpoint -w ../libs/sdk-py --dirty
|
||||
|
||||
clean-docs:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -263,7 +263,7 @@ nav:
|
||||
- Control Plane API: cloud/reference/api/api_ref_control_plane.md
|
||||
- CLI: cloud/reference/cli.md
|
||||
- SDK (Python): cloud/reference/sdk/python_sdk_ref.md
|
||||
- SDK (JS/TS): cloud/reference/sdk/js_ts_sdk_ref.md
|
||||
- SDK (JS/TS): https://langchain-ai.github.io/langgraphjs/reference/modules/sdk.html
|
||||
- RemoteGraph: reference/remote_graph.md
|
||||
- Environment variables: cloud/reference/env_var.md
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
index.cjs
|
||||
index.js
|
||||
index.d.ts
|
||||
index.d.cts
|
||||
client.cjs
|
||||
client.js
|
||||
client.d.ts
|
||||
client.d.cts
|
||||
auth.cjs
|
||||
auth.js
|
||||
auth.d.ts
|
||||
auth.d.cts
|
||||
react.cjs
|
||||
react.js
|
||||
react.d.ts
|
||||
react.d.cts
|
||||
react-ui.cjs
|
||||
react-ui.js
|
||||
react-ui.d.ts
|
||||
react-ui.d.cts
|
||||
react-ui/server.cjs
|
||||
react-ui/server.js
|
||||
react-ui/server.d.ts
|
||||
react-ui/server.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
@@ -1,64 +1 @@
|
||||
# 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
|
||||
This repository has been moved to [langchain-ai/langgraphjs](https://github.com/langchain-ai/langgraphjs/tree/main/libs/sdk).
|
||||
@@ -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,27 +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",
|
||||
auth: "auth/index",
|
||||
react: "react/index",
|
||||
"react-ui": "react-ui/index",
|
||||
"react-ui/server": "react-ui/server/index",
|
||||
},
|
||||
tsConfigPath: resolve("./tsconfig.json"),
|
||||
cjsSource: "./dist-cjs",
|
||||
cjsDestination: "./dist",
|
||||
additionalGitignorePaths: ["docs"],
|
||||
abs,
|
||||
};
|
||||
@@ -1,147 +0,0 @@
|
||||
{
|
||||
"name": "@langchain/langgraph-sdk",
|
||||
"version": "0.0.89",
|
||||
"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",
|
||||
"prepack": "yarn run build",
|
||||
"format": "prettier --write src",
|
||||
"lint": "prettier --check src && tsc --noEmit",
|
||||
"test": "vitest",
|
||||
"typedoc": "typedoc && typedoc src/react/index.ts --out docs/react --options typedoc.react.json && typedoc src/auth/index.ts --out docs/auth --options typedoc.auth.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": {
|
||||
"@langchain/langgraph-api": "~0.0.41",
|
||||
"@langchain/core": "^0.3.61",
|
||||
"@langchain/langgraph": "^0.3.5",
|
||||
"@langchain/scripts": "^0.1.4",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@tsconfig/recommended": "^1.0.2",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"concat-md": "^0.5.1",
|
||||
"hono": "^4.8.2",
|
||||
"jsdom": "^26.1.0",
|
||||
"msw": "^2.8.2",
|
||||
"prettier": "^3.2.5",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"typedoc": "^0.27.7",
|
||||
"typedoc-plugin-markdown": "^4.4.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": ">=0.2.31 <0.4.0",
|
||||
"react": "^18 || ^19"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"./auth": {
|
||||
"types": {
|
||||
"import": "./auth.d.ts",
|
||||
"require": "./auth.d.cts",
|
||||
"default": "./auth.d.ts"
|
||||
},
|
||||
"import": "./auth.js",
|
||||
"require": "./auth.cjs"
|
||||
},
|
||||
"./react": {
|
||||
"types": {
|
||||
"import": "./react.d.ts",
|
||||
"require": "./react.d.cts",
|
||||
"default": "./react.d.ts"
|
||||
},
|
||||
"import": "./react.js",
|
||||
"require": "./react.cjs"
|
||||
},
|
||||
"./react-ui": {
|
||||
"types": {
|
||||
"import": "./react-ui.d.ts",
|
||||
"require": "./react-ui.d.cts",
|
||||
"default": "./react-ui.d.ts"
|
||||
},
|
||||
"import": "./react-ui.js",
|
||||
"require": "./react-ui.cjs"
|
||||
},
|
||||
"./react-ui/server": {
|
||||
"types": {
|
||||
"import": "./react-ui/server.d.ts",
|
||||
"require": "./react-ui/server.d.cts",
|
||||
"default": "./react-ui/server.d.ts"
|
||||
},
|
||||
"import": "./react-ui/server.js",
|
||||
"require": "./react-ui/server.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",
|
||||
"auth.cjs",
|
||||
"auth.js",
|
||||
"auth.d.ts",
|
||||
"auth.d.cts",
|
||||
"react.cjs",
|
||||
"react.js",
|
||||
"react.d.ts",
|
||||
"react.d.cts",
|
||||
"react-ui.cjs",
|
||||
"react-ui.js",
|
||||
"react-ui.d.ts",
|
||||
"react-ui.d.cts",
|
||||
"react-ui/server.cjs",
|
||||
"react-ui/server.js",
|
||||
"react-ui/server.d.ts",
|
||||
"react-ui/server.d.cts"
|
||||
]
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
const HTTP_STATUS_MAPPING: { [key: number]: string } = {
|
||||
100: "Continue",
|
||||
101: "Switching Protocols",
|
||||
102: "Processing",
|
||||
103: "Early Hints",
|
||||
200: "OK",
|
||||
201: "Created",
|
||||
202: "Accepted",
|
||||
203: "Non-Authoritative Information",
|
||||
204: "No Content",
|
||||
205: "Reset Content",
|
||||
206: "Partial Content",
|
||||
207: "Multi-Status",
|
||||
208: "Already Reported",
|
||||
226: "IM Used",
|
||||
300: "Multiple Choices",
|
||||
301: "Moved Permanently",
|
||||
302: "Found",
|
||||
303: "See Other",
|
||||
304: "Not Modified",
|
||||
305: "Use Proxy",
|
||||
307: "Temporary Redirect",
|
||||
308: "Permanent Redirect",
|
||||
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",
|
||||
409: "Conflict",
|
||||
410: "Gone",
|
||||
411: "Length Required",
|
||||
412: "Precondition Failed",
|
||||
413: "Request Entity Too Large",
|
||||
414: "Request-URI Too Long",
|
||||
415: "Unsupported Media Type",
|
||||
416: "Requested Range Not Satisfiable",
|
||||
417: "Expectation Failed",
|
||||
418: "I'm a Teapot",
|
||||
421: "Misdirected Request",
|
||||
422: "Unprocessable Entity",
|
||||
423: "Locked",
|
||||
424: "Failed Dependency",
|
||||
425: "Too Early",
|
||||
426: "Upgrade Required",
|
||||
428: "Precondition Required",
|
||||
429: "Too Many Requests",
|
||||
431: "Request Header Fields Too Large",
|
||||
451: "Unavailable For Legal Reasons",
|
||||
500: "Internal Server Error",
|
||||
501: "Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Gateway Timeout",
|
||||
505: "HTTP Version Not Supported",
|
||||
506: "Variant Also Negotiates",
|
||||
507: "Insufficient Storage",
|
||||
508: "Loop Detected",
|
||||
510: "Not Extended",
|
||||
511: "Network Authentication Required",
|
||||
};
|
||||
|
||||
export class HTTPException extends Error {
|
||||
status: number;
|
||||
headers: HeadersInit;
|
||||
|
||||
constructor(
|
||||
status: number,
|
||||
options?: { message?: string; headers?: HeadersInit; cause?: unknown },
|
||||
) {
|
||||
super(options?.message ?? HTTP_STATUS_MAPPING[status] ?? "Unknown error", {
|
||||
cause: options?.cause,
|
||||
});
|
||||
this.status = status;
|
||||
this.headers = options?.headers ?? {};
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import type {
|
||||
AuthenticateCallback,
|
||||
AnyCallback,
|
||||
CallbackEvent,
|
||||
OnCallback,
|
||||
BaseAuthReturn,
|
||||
ToUserLike,
|
||||
BaseUser,
|
||||
} from "./types.js";
|
||||
|
||||
export class Auth<
|
||||
TExtra = {},
|
||||
TAuthReturn extends BaseAuthReturn = BaseAuthReturn,
|
||||
TUser extends BaseUser = ToUserLike<TAuthReturn>,
|
||||
> {
|
||||
/**
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
"~handlerCache": {
|
||||
authenticate?: AuthenticateCallback<BaseAuthReturn>;
|
||||
callbacks?: Record<string, AnyCallback>;
|
||||
} = {};
|
||||
|
||||
authenticate<T extends BaseAuthReturn>(
|
||||
cb: AuthenticateCallback<T>,
|
||||
): Auth<TExtra, T> {
|
||||
this["~handlerCache"].authenticate = cb;
|
||||
return this as unknown as Auth<TExtra, T>;
|
||||
}
|
||||
|
||||
on<T extends CallbackEvent>(event: T, callback: OnCallback<T, TUser>): this {
|
||||
this["~handlerCache"].callbacks ??= {};
|
||||
const events: string[] = Array.isArray(event) ? event : [event];
|
||||
for (const event of events) {
|
||||
this["~handlerCache"].callbacks[event] = callback as AnyCallback;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export type {
|
||||
Filters as AuthFilters,
|
||||
EventValueMap as AuthEventValueMap,
|
||||
} from "./types.js";
|
||||
export { HTTPException } from "./error.js";
|
||||
@@ -1,411 +0,0 @@
|
||||
type Maybe<T> = T | null | undefined;
|
||||
type PromiseMaybe<T> = Promise<T> | T;
|
||||
|
||||
interface AssistantConfig {
|
||||
tags?: Maybe<string[]>;
|
||||
recursion_limit?: Maybe<number>;
|
||||
configurable?: Maybe<{
|
||||
thread_id?: Maybe<string>;
|
||||
thread_ts?: Maybe<string>;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface AssistantCreate {
|
||||
assistant_id?: Maybe<string>;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
config?: Maybe<AssistantConfig>;
|
||||
if_exists?: Maybe<"raise" | "do_nothing">;
|
||||
name?: Maybe<string>;
|
||||
graph_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface AssistantRead {
|
||||
assistant_id: string;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface AssistantUpdate {
|
||||
assistant_id: string;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
config?: Maybe<AssistantConfig>;
|
||||
graph_id?: Maybe<string>;
|
||||
name?: Maybe<string>;
|
||||
version?: Maybe<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface AssistantDelete {
|
||||
assistant_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface AssistantSearch {
|
||||
graph_id?: Maybe<string>;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
limit?: Maybe<number>;
|
||||
offset?: Maybe<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface ThreadCreate {
|
||||
thread_id?: Maybe<string>;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
if_exists?: Maybe<"raise" | "do_nothing">;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface ThreadRead {
|
||||
thread_id?: Maybe<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface ThreadUpdate {
|
||||
thread_id?: Maybe<string>;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
action?: Maybe<"interrupt" | "rollback">;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface ThreadDelete {
|
||||
thread_id?: Maybe<string>;
|
||||
run_id?: Maybe<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface ThreadSearch {
|
||||
thread_id?: Maybe<string>;
|
||||
status?: Maybe<"idle" | "busy" | "interrupted" | "error" | (string & {})>;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
values?: Maybe<Record<string, unknown>>;
|
||||
limit?: Maybe<number>;
|
||||
offset?: Maybe<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface CronCreate {
|
||||
payload?: Maybe<Record<string, unknown>>;
|
||||
schedule: string;
|
||||
cron_id?: Maybe<string>;
|
||||
thread_id?: Maybe<string>;
|
||||
user_id?: Maybe<string>;
|
||||
end_time?: Maybe<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface CronRead {
|
||||
cron_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface CronUpdate {
|
||||
cron_id: string;
|
||||
payload?: Maybe<Record<string, unknown>>;
|
||||
schedule?: Maybe<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface CronDelete {
|
||||
cron_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface CronSearch {
|
||||
assistant_id?: Maybe<string>;
|
||||
thread_id?: Maybe<string>;
|
||||
limit?: Maybe<number>;
|
||||
offset?: Maybe<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface StorePut {
|
||||
namespace: string[];
|
||||
key: string;
|
||||
value: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface StoreGet {
|
||||
namespace: Maybe<string[]>;
|
||||
key: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface StoreSearch {
|
||||
namespace?: Maybe<string[]>;
|
||||
filter?: Maybe<Record<string, unknown>>;
|
||||
limit?: Maybe<number>;
|
||||
offset?: Maybe<number>;
|
||||
query?: Maybe<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface StoreListNamespaces {
|
||||
namespace?: Maybe<string[]>;
|
||||
suffix?: Maybe<string[]>;
|
||||
max_depth?: Maybe<number>;
|
||||
limit?: Maybe<number>;
|
||||
offset?: Maybe<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface StoreDelete {
|
||||
namespace?: Maybe<string[]>;
|
||||
key: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inline
|
||||
*/
|
||||
interface RunsCreate {
|
||||
thread_id?: Maybe<string>;
|
||||
assistant_id: string;
|
||||
run_id: string;
|
||||
status: Maybe<
|
||||
"pending" | "running" | "error" | "success" | "timeout" | "interrupted"
|
||||
>;
|
||||
metadata?: Maybe<Record<string, unknown>>;
|
||||
prevent_insert_if_inflight?: Maybe<boolean>;
|
||||
multitask_strategy?: Maybe<"interrupt" | "rollback" | "reject" | "enqueue">;
|
||||
if_not_exists?: Maybe<"reject" | "create">;
|
||||
after_seconds?: Maybe<number>;
|
||||
kwargs: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface EventValueMap {
|
||||
["threads:create"]: ThreadCreate;
|
||||
["threads:read"]: ThreadRead;
|
||||
["threads:update"]: ThreadUpdate;
|
||||
["threads:delete"]: ThreadDelete;
|
||||
["threads:search"]: ThreadSearch;
|
||||
["threads:create_run"]: RunsCreate;
|
||||
|
||||
["assistants:create"]: AssistantCreate;
|
||||
["assistants:read"]: AssistantRead;
|
||||
["assistants:update"]: AssistantUpdate;
|
||||
["assistants:delete"]: AssistantDelete;
|
||||
["assistants:search"]: AssistantSearch;
|
||||
|
||||
["crons:create"]: CronCreate;
|
||||
["crons:read"]: CronRead;
|
||||
["crons:update"]: CronUpdate;
|
||||
["crons:delete"]: CronDelete;
|
||||
["crons:search"]: CronSearch;
|
||||
|
||||
["store:put"]: StorePut;
|
||||
["store:get"]: StoreGet;
|
||||
["store:search"]: StoreSearch;
|
||||
["store:list_namespaces"]: StoreListNamespaces;
|
||||
["store:delete"]: StoreDelete;
|
||||
}
|
||||
interface ResourceType {
|
||||
threads:
|
||||
| "threads:create"
|
||||
| "threads:read"
|
||||
| "threads:update"
|
||||
| "threads:delete"
|
||||
| "threads:search"
|
||||
| "threads:create_run";
|
||||
|
||||
assistants:
|
||||
| "assistants:create"
|
||||
| "assistants:read"
|
||||
| "assistants:update"
|
||||
| "assistants:delete"
|
||||
| "assistants:search";
|
||||
crons:
|
||||
| "crons:create"
|
||||
| "crons:read"
|
||||
| "crons:update"
|
||||
| "crons:delete"
|
||||
| "crons:search";
|
||||
|
||||
store:
|
||||
| "store:put"
|
||||
| "store:get"
|
||||
| "store:search"
|
||||
| "store:list_namespaces"
|
||||
| "store:delete";
|
||||
}
|
||||
interface ActionType {
|
||||
"*:create": "threads:create" | "assistants:create" | "crons:create";
|
||||
|
||||
"*:read": "threads:read" | "assistants:read" | "crons:read";
|
||||
|
||||
"*:update": "threads:update" | "assistants:update" | "crons:update";
|
||||
|
||||
"*:delete":
|
||||
| "threads:delete"
|
||||
| "assistants:delete"
|
||||
| "crons:delete"
|
||||
| "store:delete";
|
||||
|
||||
"*:search":
|
||||
| "threads:search"
|
||||
| "assistants:search"
|
||||
| "crons:search"
|
||||
| "store:search";
|
||||
|
||||
"*:create_run": "threads:create_run";
|
||||
|
||||
"*:put": "store:put";
|
||||
|
||||
"*:get": "store:get";
|
||||
|
||||
"*:list_namespaces": "store:list_namespaces";
|
||||
}
|
||||
|
||||
export type BaseAuthReturn =
|
||||
| {
|
||||
is_authenticated?: boolean;
|
||||
display_name?: string;
|
||||
identity: string;
|
||||
permissions: string[];
|
||||
}
|
||||
| string;
|
||||
|
||||
export interface BaseUser {
|
||||
is_authenticated: boolean;
|
||||
display_name: string;
|
||||
identity: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export type ToUserLike<T extends BaseAuthReturn> = T extends string
|
||||
? {
|
||||
is_authenticated: boolean;
|
||||
display_name: string;
|
||||
identity: string;
|
||||
permissions: string[];
|
||||
}
|
||||
: Omit<T, "is_authenticated" | "display_name"> & {
|
||||
is_authenticated: boolean;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
type CallbackParameter<
|
||||
Event extends string = string,
|
||||
Resource extends string = string,
|
||||
Action extends string = string,
|
||||
Value extends unknown = unknown,
|
||||
TUser extends BaseUser = BaseUser,
|
||||
> = {
|
||||
event: Event;
|
||||
resource: Resource;
|
||||
action: Action;
|
||||
value: Value;
|
||||
user: TUser;
|
||||
permissions: string[];
|
||||
};
|
||||
|
||||
type ContextMap = {
|
||||
[EventType in keyof EventValueMap]: CallbackParameter<
|
||||
EventType,
|
||||
EventType extends `${infer Resource}:${string}` ? Resource : never,
|
||||
EventType extends `${string}:${infer Action}` ? Action : never,
|
||||
EventValueMap[EventType],
|
||||
BaseUser
|
||||
>;
|
||||
};
|
||||
|
||||
type ActionCallbackParameter<
|
||||
T extends keyof ActionType,
|
||||
TUser extends BaseUser = BaseUser,
|
||||
> = ContextMap[ActionType[T]] & { user: TUser };
|
||||
type AuthCallbackParameter<
|
||||
T extends keyof EventValueMap,
|
||||
TUser extends BaseUser = BaseUser,
|
||||
> = ContextMap[T] & { user: TUser };
|
||||
type ResourceCallbackParameter<
|
||||
T extends keyof ResourceType,
|
||||
TUser extends BaseUser = BaseUser,
|
||||
> = ContextMap[ResourceType[T]] & { user: TUser };
|
||||
|
||||
export type Filters<TKey extends string | number | symbol> = {
|
||||
[key in TKey]: string | { [op in "$contains" | "$eq"]?: string };
|
||||
};
|
||||
|
||||
export interface AuthenticateCallback<T extends BaseAuthReturn> {
|
||||
(request: Request): PromiseMaybe<T>;
|
||||
}
|
||||
|
||||
type OnKey = keyof ResourceType | keyof ActionType | keyof EventValueMap;
|
||||
|
||||
type OnSingleParameter<
|
||||
T extends OnKey,
|
||||
TUser extends BaseUser = BaseUser,
|
||||
> = T extends keyof ResourceType
|
||||
? ResourceCallbackParameter<T, TUser>
|
||||
: T extends keyof ActionType
|
||||
? ActionCallbackParameter<T, TUser>
|
||||
: T extends keyof EventValueMap
|
||||
? AuthCallbackParameter<T, TUser>
|
||||
: never;
|
||||
|
||||
type OnParameter<
|
||||
T extends "*" | OnKey | OnKey[],
|
||||
TUser extends BaseUser = BaseUser,
|
||||
> = T extends OnKey[]
|
||||
? OnSingleParameter<T[number], TUser>
|
||||
: T extends "*"
|
||||
? AuthCallbackParameter<keyof EventValueMap, TUser>
|
||||
: T extends OnKey
|
||||
? OnSingleParameter<T, TUser>
|
||||
: never;
|
||||
|
||||
export type AnyCallback = (
|
||||
request: CallbackParameter,
|
||||
) => void | boolean | Filters<string>;
|
||||
|
||||
export type CallbackEvent = "*" | OnKey | OnKey[];
|
||||
|
||||
export type OnCallback<
|
||||
T extends CallbackEvent,
|
||||
TUser extends BaseUser = BaseUser,
|
||||
TMetadata extends Record<string, unknown> = Record<string, unknown>,
|
||||
> = (
|
||||
request: OnParameter<T, TUser>,
|
||||
) => void | boolean | Filters<keyof TMetadata>;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,56 +0,0 @@
|
||||
export { Client, getApiKey } from "./client.js";
|
||||
export type { ClientConfig, RequestHook } from "./client.js";
|
||||
|
||||
export type {
|
||||
Assistant,
|
||||
AssistantBase,
|
||||
AssistantGraph,
|
||||
AssistantVersion,
|
||||
Checkpoint,
|
||||
Config,
|
||||
Cron,
|
||||
CronCreateForThreadResponse,
|
||||
CronCreateResponse,
|
||||
DefaultValues,
|
||||
GraphSchema,
|
||||
Interrupt,
|
||||
Item,
|
||||
ListNamespaceResponse,
|
||||
Metadata,
|
||||
Run,
|
||||
SearchItem,
|
||||
SearchItemsResponse,
|
||||
Thread,
|
||||
ThreadState,
|
||||
ThreadStatus,
|
||||
ThreadTask,
|
||||
} from "./schema.js";
|
||||
export { overrideFetchImplementation } from "./singletons/fetch.js";
|
||||
|
||||
export type {
|
||||
Command,
|
||||
OnConflictBehavior,
|
||||
RunsInvokePayload,
|
||||
} from "./types.js";
|
||||
export type {
|
||||
AIMessage,
|
||||
FunctionMessage,
|
||||
HumanMessage,
|
||||
Message,
|
||||
RemoveMessage,
|
||||
SystemMessage,
|
||||
ToolMessage,
|
||||
} from "./types.messages.js";
|
||||
export type {
|
||||
CustomStreamEvent,
|
||||
DebugStreamEvent,
|
||||
ErrorStreamEvent,
|
||||
EventsStreamEvent,
|
||||
FeedbackStreamEvent,
|
||||
MessagesStreamEvent,
|
||||
MessagesTupleStreamEvent,
|
||||
MetadataStreamEvent,
|
||||
StreamMode,
|
||||
UpdatesStreamEvent,
|
||||
ValuesStreamEvent,
|
||||
} from "./types.stream.js";
|
||||
@@ -1,279 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useStream } from "../react/index.js";
|
||||
import type { UIMessage } from "./types.js";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import * as JsxRuntime from "react/jsx-runtime";
|
||||
import type { UseStream } from "../react/stream.js";
|
||||
|
||||
const UseStreamContext = React.createContext<{
|
||||
stream: ReturnType<typeof useStream>;
|
||||
meta: unknown;
|
||||
}>(null!);
|
||||
|
||||
type BagTemplate = {
|
||||
ConfigurableType?: Record<string, unknown>;
|
||||
InterruptType?: unknown;
|
||||
CustomEventType?: unknown;
|
||||
UpdateType?: unknown;
|
||||
MetaType?: unknown;
|
||||
};
|
||||
|
||||
type GetMetaType<Bag extends BagTemplate> = Bag extends { MetaType: unknown }
|
||||
? Bag["MetaType"]
|
||||
: unknown;
|
||||
|
||||
interface UseStreamContext<
|
||||
StateType extends Record<string, unknown> = Record<string, unknown>,
|
||||
Bag extends BagTemplate = BagTemplate,
|
||||
> extends UseStream<StateType, Bag> {
|
||||
meta?: GetMetaType<Bag>;
|
||||
}
|
||||
|
||||
export function useStreamContext<
|
||||
StateType extends Record<string, unknown> = Record<string, unknown>,
|
||||
Bag extends {
|
||||
ConfigurableType?: Record<string, unknown>;
|
||||
InterruptType?: unknown;
|
||||
CustomEventType?: unknown;
|
||||
UpdateType?: unknown;
|
||||
MetaType?: unknown;
|
||||
} = BagTemplate,
|
||||
>(): UseStreamContext<StateType, Bag> {
|
||||
const ctx = React.useContext(UseStreamContext);
|
||||
if (!ctx) {
|
||||
throw new Error(
|
||||
"useStreamContext must be used within a LoadExternalComponent",
|
||||
);
|
||||
}
|
||||
|
||||
return new Proxy(ctx, {
|
||||
get(target, prop: keyof UseStreamContext<StateType, Bag>) {
|
||||
if (prop === "meta") return target.meta;
|
||||
return target.stream[prop];
|
||||
},
|
||||
}) as unknown as UseStreamContext<StateType, Bag>;
|
||||
}
|
||||
|
||||
interface ComponentTarget {
|
||||
comp: React.FunctionComponent | React.ComponentClass;
|
||||
target: HTMLElement;
|
||||
}
|
||||
|
||||
class ComponentStore {
|
||||
private cache: Record<string, ComponentTarget> = {};
|
||||
private boundCache: Record<
|
||||
string,
|
||||
{
|
||||
subscribe: (onStoreChange: () => void) => () => void;
|
||||
getSnapshot: () => ComponentTarget | undefined;
|
||||
}
|
||||
> = {};
|
||||
private callbacks: Record<
|
||||
string,
|
||||
((
|
||||
comp: React.FunctionComponent | React.ComponentClass,
|
||||
el: HTMLElement,
|
||||
) => void)[]
|
||||
> = {};
|
||||
|
||||
respond(
|
||||
shadowRootId: string,
|
||||
comp: React.FunctionComponent | React.ComponentClass,
|
||||
targetElement: HTMLElement,
|
||||
) {
|
||||
this.cache[shadowRootId] = { comp, target: targetElement };
|
||||
this.callbacks[shadowRootId]?.forEach((c) => c(comp, targetElement));
|
||||
}
|
||||
|
||||
getBoundStore(shadowRootId: string) {
|
||||
this.boundCache[shadowRootId] ??= {
|
||||
subscribe: (onStoreChange: () => void) => {
|
||||
this.callbacks[shadowRootId] ??= [];
|
||||
this.callbacks[shadowRootId].push(onStoreChange);
|
||||
return () => {
|
||||
this.callbacks[shadowRootId] = this.callbacks[shadowRootId].filter(
|
||||
(c) => c !== onStoreChange,
|
||||
);
|
||||
};
|
||||
},
|
||||
getSnapshot: () => this.cache[shadowRootId],
|
||||
};
|
||||
|
||||
return this.boundCache[shadowRootId];
|
||||
}
|
||||
}
|
||||
|
||||
const COMPONENT_STORE = new ComponentStore();
|
||||
const EXT_STORE_SYMBOL = Symbol.for("LGUI_EXT_STORE");
|
||||
const REQUIRE_SYMBOL = Symbol.for("LGUI_REQUIRE");
|
||||
const REQUIRE_EXTRA_SYMBOL = Symbol.for("LGUI_REQUIRE_EXTRA");
|
||||
|
||||
interface LoadExternalComponentProps
|
||||
extends Pick<React.HTMLAttributes<HTMLDivElement>, "style" | "className"> {
|
||||
/** Stream of the assistant */
|
||||
stream: ReturnType<typeof useStream>;
|
||||
|
||||
/** Namespace of UI components. Defaults to assistant ID. */
|
||||
namespace?: string;
|
||||
|
||||
/** UI message to be rendered */
|
||||
message: UIMessage;
|
||||
|
||||
/** Additional context to be passed to the child component */
|
||||
meta?: unknown;
|
||||
|
||||
/** Fallback to be rendered when the component is loading */
|
||||
fallback?: React.ReactNode | Record<string, React.ReactNode>;
|
||||
|
||||
/**
|
||||
* Map of components that can be rendered directly without fetching the UI code
|
||||
* from the server.
|
||||
*/
|
||||
components?: Record<string, React.FunctionComponent | React.ComponentClass>;
|
||||
}
|
||||
|
||||
const isIterable = (value: unknown): value is Iterable<unknown> =>
|
||||
value != null && typeof value === "object" && Symbol.iterator in value;
|
||||
|
||||
const isPromise = (value: unknown): value is Promise<unknown> =>
|
||||
value != null &&
|
||||
typeof value === "object" &&
|
||||
"then" in value &&
|
||||
typeof value.then === "function";
|
||||
|
||||
const isReactNode = (value: unknown): value is React.ReactNode => {
|
||||
if (React.isValidElement(value)) return true;
|
||||
if (value == null) return true;
|
||||
if (
|
||||
typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "bigint" ||
|
||||
typeof value === "boolean"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isIterable(value)) return true;
|
||||
if (isPromise(value)) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export function LoadExternalComponent({
|
||||
stream,
|
||||
namespace,
|
||||
message,
|
||||
meta,
|
||||
fallback,
|
||||
components,
|
||||
...props
|
||||
}: LoadExternalComponentProps) {
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const id = React.useId();
|
||||
const shadowRootId = `child-shadow-${id}`;
|
||||
|
||||
const store = React.useMemo(
|
||||
() => COMPONENT_STORE.getBoundStore(shadowRootId),
|
||||
[shadowRootId],
|
||||
);
|
||||
const state = React.useSyncExternalStore(store.subscribe, store.getSnapshot);
|
||||
|
||||
const clientComponent = components?.[message.name];
|
||||
const hasClientComponent = clientComponent != null;
|
||||
|
||||
const fallbackComponent = isReactNode(fallback)
|
||||
? fallback
|
||||
: typeof fallback === "object" && fallback != null
|
||||
? fallback?.[message.name]
|
||||
: null;
|
||||
|
||||
const uiNamespace = namespace ?? stream.assistantId;
|
||||
const uiClient = stream.client["~ui"];
|
||||
React.useEffect(() => {
|
||||
if (hasClientComponent) return;
|
||||
uiClient.getComponent(uiNamespace, message.name).then((html) => {
|
||||
const dom = ref.current;
|
||||
if (!dom) return;
|
||||
const root = dom.shadowRoot ?? dom.attachShadow({ mode: "open" });
|
||||
const fragment = document
|
||||
.createRange()
|
||||
.createContextualFragment(
|
||||
html.replace("{{shadowRootId}}", shadowRootId),
|
||||
);
|
||||
root.appendChild(fragment);
|
||||
});
|
||||
}, [uiClient, uiNamespace, message.name, shadowRootId, hasClientComponent]);
|
||||
|
||||
if (hasClientComponent) {
|
||||
return (
|
||||
<UseStreamContext.Provider value={{ stream, meta }}>
|
||||
{React.createElement(clientComponent, message.props)}
|
||||
</UseStreamContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id={shadowRootId} ref={ref} {...props} />
|
||||
|
||||
<UseStreamContext.Provider value={{ stream, meta }}>
|
||||
{state?.target != null
|
||||
? ReactDOM.createPortal(
|
||||
React.createElement(state.comp, message.props),
|
||||
state.target,
|
||||
)
|
||||
: fallbackComponent}
|
||||
</UseStreamContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
[EXT_STORE_SYMBOL]: ComponentStore;
|
||||
[REQUIRE_SYMBOL]: (name: string) => unknown;
|
||||
[REQUIRE_EXTRA_SYMBOL]: Record<string, unknown>;
|
||||
}
|
||||
}
|
||||
|
||||
export function experimental_loadShare(name: string, module: unknown) {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
window[REQUIRE_EXTRA_SYMBOL] ??= {};
|
||||
window[REQUIRE_EXTRA_SYMBOL][name] = module;
|
||||
}
|
||||
|
||||
export function bootstrapUiContext() {
|
||||
if (typeof window === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
window[EXT_STORE_SYMBOL] = COMPONENT_STORE;
|
||||
window[REQUIRE_SYMBOL] = (name: string) => {
|
||||
if (name === "react") return React;
|
||||
if (name === "react-dom") return ReactDOM;
|
||||
if (name === "react/jsx-runtime") return JsxRuntime;
|
||||
if (name === "@langchain/langgraph-sdk/react") return { useStream };
|
||||
if (name === "@langchain/langgraph-sdk/react-ui") {
|
||||
return {
|
||||
useStreamContext,
|
||||
LoadExternalComponent: () => {
|
||||
throw new Error("Nesting LoadExternalComponent is not supported");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
window[REQUIRE_EXTRA_SYMBOL] != null &&
|
||||
typeof window[REQUIRE_EXTRA_SYMBOL] === "object" &&
|
||||
name in window[REQUIRE_EXTRA_SYMBOL]
|
||||
) {
|
||||
return window[REQUIRE_EXTRA_SYMBOL][name];
|
||||
}
|
||||
|
||||
throw new Error(`Unknown module...: ${name}`);
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { bootstrapUiContext } from "./client.js";
|
||||
bootstrapUiContext();
|
||||
|
||||
export {
|
||||
useStreamContext,
|
||||
LoadExternalComponent,
|
||||
experimental_loadShare,
|
||||
} from "./client.js";
|
||||
export {
|
||||
uiMessageReducer,
|
||||
isUIMessage,
|
||||
isRemoveUIMessage,
|
||||
type UIMessage,
|
||||
type RemoveUIMessage,
|
||||
} from "./types.js";
|
||||
@@ -1,6 +0,0 @@
|
||||
export { typedUi } from "./server.js";
|
||||
export {
|
||||
uiMessageReducer,
|
||||
type UIMessage,
|
||||
type RemoveUIMessage,
|
||||
} from "../types.js";
|
||||
@@ -1,99 +0,0 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import type { ComponentPropsWithoutRef, ElementType } from "react";
|
||||
import type { RemoveUIMessage, UIMessage } from "../types.js";
|
||||
|
||||
interface MessageLike {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to send and persist UI messages. Accepts a map of component names to React components
|
||||
* as type argument to provide type safety. Will also write to the `options?.stateKey` state.
|
||||
*
|
||||
* @param config LangGraphRunnableConfig
|
||||
* @param options
|
||||
* @returns
|
||||
*/
|
||||
export const typedUi = <Decl extends Record<string, ElementType>>(
|
||||
config: {
|
||||
writer?: (chunk: unknown) => void;
|
||||
runId?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
tags?: string[];
|
||||
runName?: string;
|
||||
configurable?: {
|
||||
__pregel_send?: (writes_: [string, unknown][]) => void;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
},
|
||||
options?: {
|
||||
/** The key to write the UI messages to. Defaults to `ui`. */
|
||||
stateKey?: string;
|
||||
},
|
||||
) => {
|
||||
type PropMap = { [K in keyof Decl]: ComponentPropsWithoutRef<Decl[K]> };
|
||||
let items: (UIMessage | RemoveUIMessage)[] = [];
|
||||
const stateKey = options?.stateKey ?? "ui";
|
||||
|
||||
const runId = (config.metadata?.run_id as string | undefined) ?? config.runId;
|
||||
if (!runId) throw new Error("run_id is required");
|
||||
|
||||
function handlePush<K extends keyof PropMap & string>(
|
||||
message: {
|
||||
id?: string;
|
||||
name: K;
|
||||
props: PropMap[K];
|
||||
metadata?: Record<string, unknown>;
|
||||
},
|
||||
options?: { message?: MessageLike; merge?: boolean },
|
||||
): UIMessage<K, PropMap[K]>;
|
||||
|
||||
function handlePush<K extends keyof PropMap & string>(
|
||||
message: {
|
||||
id?: string;
|
||||
name: K;
|
||||
props: Partial<PropMap[K]>;
|
||||
metadata?: Record<string, unknown>;
|
||||
},
|
||||
options: { message?: MessageLike; merge: true },
|
||||
): UIMessage<K, Partial<PropMap[K]>>;
|
||||
|
||||
function handlePush<K extends keyof PropMap & string>(
|
||||
message: {
|
||||
id?: string;
|
||||
name: K;
|
||||
props: PropMap[K] | Partial<PropMap[K]>;
|
||||
metadata?: Record<string, unknown>;
|
||||
},
|
||||
options?: { message?: MessageLike; merge?: boolean },
|
||||
): UIMessage<K, PropMap[K] | Partial<PropMap[K]>> {
|
||||
const evt: UIMessage<K, PropMap[K] | Partial<PropMap[K]>> = {
|
||||
type: "ui" as const,
|
||||
id: message?.id ?? uuidv4(),
|
||||
name: message?.name,
|
||||
props: message?.props,
|
||||
metadata: {
|
||||
merge: options?.merge || undefined,
|
||||
run_id: runId,
|
||||
tags: config.tags,
|
||||
name: config.runName,
|
||||
...message?.metadata,
|
||||
...(options?.message ? { message_id: options.message.id } : null),
|
||||
},
|
||||
};
|
||||
items.push(evt);
|
||||
config.writer?.(evt);
|
||||
config.configurable?.__pregel_send?.([[stateKey, evt]]);
|
||||
return evt;
|
||||
}
|
||||
|
||||
const handleDelete = (id: string): RemoveUIMessage => {
|
||||
const evt: RemoveUIMessage = { type: "remove-ui", id };
|
||||
items.push(evt);
|
||||
config.writer?.(evt);
|
||||
config.configurable?.__pregel_send?.([[stateKey, evt]]);
|
||||
return evt;
|
||||
};
|
||||
|
||||
return { push: handlePush, delete: handleDelete, items };
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
export interface UIMessage<
|
||||
TName extends string = string,
|
||||
TProps extends Record<string, unknown> = Record<string, unknown>,
|
||||
> {
|
||||
type: "ui";
|
||||
|
||||
id: string;
|
||||
name: TName;
|
||||
props: TProps;
|
||||
metadata?: {
|
||||
merge?: boolean;
|
||||
run_id?: string;
|
||||
name?: string;
|
||||
tags?: string[];
|
||||
message_id?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RemoveUIMessage {
|
||||
type: "remove-ui";
|
||||
id: string;
|
||||
}
|
||||
|
||||
export function isUIMessage(message: unknown): message is UIMessage {
|
||||
if (typeof message !== "object" || message == null) return false;
|
||||
if (!("type" in message)) return false;
|
||||
return message.type === "ui";
|
||||
}
|
||||
|
||||
export function isRemoveUIMessage(
|
||||
message: unknown,
|
||||
): message is RemoveUIMessage {
|
||||
if (typeof message !== "object" || message == null) return false;
|
||||
if (!("type" in message)) return false;
|
||||
return message.type === "remove-ui";
|
||||
}
|
||||
|
||||
export function uiMessageReducer(
|
||||
state: UIMessage[],
|
||||
update: UIMessage | RemoveUIMessage | (UIMessage | RemoveUIMessage)[],
|
||||
) {
|
||||
const events = Array.isArray(update) ? update : [update];
|
||||
let newState = state.slice();
|
||||
|
||||
for (const event of events) {
|
||||
if (event.type === "remove-ui") {
|
||||
newState = newState.filter((ui) => ui.id !== event.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
const index = state.findIndex((ui) => ui.id === event.id);
|
||||
if (index !== -1) {
|
||||
newState[index] =
|
||||
typeof event.metadata === "object" &&
|
||||
event.metadata != null &&
|
||||
event.metadata.merge
|
||||
? { ...event, props: { ...state[index].props, ...event.props } }
|
||||
: event;
|
||||
} else {
|
||||
newState.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
@@ -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,6 +0,0 @@
|
||||
export {
|
||||
useStream,
|
||||
type MessageMetadata,
|
||||
type UseStream,
|
||||
type UseStreamOptions,
|
||||
} from "./stream.js";
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,334 +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 | null | undefined;
|
||||
|
||||
/**
|
||||
* The schema for the output state.
|
||||
* Missing if unable to generate JSON schema from graph.
|
||||
*/
|
||||
output_schema?: JSONSchema7 | null | undefined;
|
||||
|
||||
/**
|
||||
* The schema for the graph state.
|
||||
* Missing if unable to generate JSON schema from graph.
|
||||
*/
|
||||
state_schema?: JSONSchema7 | null | undefined;
|
||||
|
||||
/**
|
||||
* The schema for the graph config.
|
||||
* Missing if unable to generate JSON schema from graph.
|
||||
*/
|
||||
config_schema?: JSONSchema7 | null | undefined;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/** The name of the assistant */
|
||||
name: string;
|
||||
|
||||
/** The description of the assistant */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface AssistantVersion extends AssistantBase {}
|
||||
|
||||
export interface Assistant extends AssistantBase {
|
||||
/** The last time the assistant was updated. */
|
||||
updated_at: 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 assistant */
|
||||
assistant_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>;
|
||||
|
||||
/** The user ID of the cron */
|
||||
user_id: Optional<string>;
|
||||
|
||||
/** The next run date of the cron */
|
||||
next_run_date: Optional<string>;
|
||||
|
||||
/** The metadata of the cron */
|
||||
metadata: 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;
|
||||
}
|
||||
|
||||
export type AssistantSortBy =
|
||||
| "assistant_id"
|
||||
| "graph_id"
|
||||
| "name"
|
||||
| "created_at"
|
||||
| "updated_at";
|
||||
|
||||
export type ThreadSortBy = "thread_id" | "status" | "created_at" | "updated_at";
|
||||
|
||||
export type CronSortBy =
|
||||
| "cron_id"
|
||||
| "assistant_id"
|
||||
| "thread_id"
|
||||
| "created_at"
|
||||
| "updated_at"
|
||||
| "next_run_date";
|
||||
|
||||
export type SortOrder = "asc" | "desc";
|
||||
@@ -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,201 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { Client } from "../client.js";
|
||||
import { overrideFetchImplementation } from "../singletons/fetch.js";
|
||||
|
||||
describe.each([["global"], ["mocked"]])(
|
||||
"Client uses %s fetch",
|
||||
(description: string) => {
|
||||
let globalFetchMock: ReturnType<typeof vi.fn>;
|
||||
let overriddenFetch: ReturnType<typeof vi.fn>;
|
||||
|
||||
let expectedFetchMock: ReturnType<typeof vi.fn>;
|
||||
let unexpectedFetchMock: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
globalFetchMock = vi.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
batch_ingest_config: {
|
||||
use_multipart_endpoint: true,
|
||||
},
|
||||
}),
|
||||
text: () => Promise.resolve(""),
|
||||
headers: new Headers({}),
|
||||
}),
|
||||
);
|
||||
overriddenFetch = vi.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
batch_ingest_config: {
|
||||
use_multipart_endpoint: true,
|
||||
},
|
||||
}),
|
||||
text: () => Promise.resolve(""),
|
||||
headers: new Headers({}),
|
||||
}),
|
||||
);
|
||||
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(() => {
|
||||
vi.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();
|
||||
|
||||
vi.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();
|
||||
});
|
||||
});
|
||||
|
||||
describe("header coalescing", () => {
|
||||
it("should properly merge headers with conflicting name casing", async () => {
|
||||
const client = new Client({ apiKey: "test-api-key" });
|
||||
await (client.threads as any).fetch("/test", {
|
||||
headers: { "X-Api-Key": "custom-value" },
|
||||
});
|
||||
expect(expectedFetchMock).toHaveBeenCalledWith(
|
||||
expect.any(URL),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-api-key": "custom-value",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should properly merge headers from multiple sources", async () => {
|
||||
const client = new Client({
|
||||
apiKey: "test-api-key",
|
||||
defaultHeaders: {
|
||||
"x-default": "default-value",
|
||||
"x-override": "default-value",
|
||||
},
|
||||
});
|
||||
|
||||
await (client.threads as any).fetch("/test", {
|
||||
headers: {
|
||||
"x-custom": "custom-value",
|
||||
"x-override": "custom-value",
|
||||
},
|
||||
});
|
||||
|
||||
expect(expectedFetchMock).toHaveBeenCalledWith(
|
||||
expect.any(URL),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-api-key": "test-api-key",
|
||||
"x-default": "default-value",
|
||||
"x-custom": "custom-value",
|
||||
"x-override": "custom-value",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Test with null/undefined values
|
||||
await (client.threads as any).fetch("/test", {
|
||||
headers: {
|
||||
"x-null": null,
|
||||
"x-undefined": undefined,
|
||||
"x-empty": "",
|
||||
},
|
||||
});
|
||||
|
||||
expect(expectedFetchMock).toHaveBeenCalledWith(
|
||||
expect.any(URL),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-api-key": "test-api-key",
|
||||
"x-default": "default-value",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(expectedFetchMock).not.toHaveBeenCalledWith(
|
||||
expect.any(URL),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-null": null,
|
||||
"x-undefined": undefined,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle Headers object input", async () => {
|
||||
const client = new Client({ apiKey: "test-api-key" });
|
||||
const headers = new Headers();
|
||||
headers.append("x-custom", "custom-value");
|
||||
headers.append("x-multi", "value1");
|
||||
headers.append("x-multi", "value2");
|
||||
|
||||
await (client.threads as any).fetch("/test", { headers });
|
||||
|
||||
expect(expectedFetchMock).toHaveBeenCalledWith(
|
||||
expect.any(URL),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-api-key": "test-api-key",
|
||||
"x-custom": "custom-value",
|
||||
"x-multi": "value1, value2",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle array of header tuples", async () => {
|
||||
const client = new Client({
|
||||
apiKey: "test-api-key",
|
||||
defaultHeaders: {
|
||||
"x-custom": "custom-value",
|
||||
},
|
||||
});
|
||||
const headers = [
|
||||
["x-multi", "value1"],
|
||||
["x-multi", "value2"],
|
||||
];
|
||||
|
||||
await (client.threads as any).fetch("/test", { headers });
|
||||
|
||||
expect(expectedFetchMock).toHaveBeenCalledWith(
|
||||
expect.any(URL),
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
"x-api-key": "test-api-key",
|
||||
"x-custom": "custom-value",
|
||||
"x-multi": "value1, value2",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -1,183 +0,0 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
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(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(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(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(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(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(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(BytesLineDecoder())
|
||||
.pipeThrough(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(BytesLineDecoder())
|
||||
.pipeThrough(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(BytesLineDecoder())
|
||||
.pipeThrough(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(BytesLineDecoder())
|
||||
.pipeThrough(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(BytesLineDecoder())
|
||||
.pipeThrough(SSEDecoder());
|
||||
|
||||
const results = await gather(decoded);
|
||||
expect(results.length).toBe(1);
|
||||
expect(results[0]).toEqual({
|
||||
event: "end",
|
||||
data: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,450 +0,0 @@
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { setupServer } from "msw/node";
|
||||
import { http } from "msw";
|
||||
import { useStream } from "../react/stream.js";
|
||||
import type { Message } from "../types.messages.js";
|
||||
|
||||
import { StateGraph, MessagesAnnotation, START } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph-checkpoint";
|
||||
import { FakeStreamingChatModel } from "@langchain/core/utils/testing";
|
||||
import { AIMessage } from "@langchain/core/messages";
|
||||
import { createEmbedServer } from "@langchain/langgraph-api/experimental/embed";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { useState } from "react";
|
||||
|
||||
const threads = (() => {
|
||||
const THREADS: Record<
|
||||
string,
|
||||
{ thread_id: string; metadata: Record<string, unknown> }
|
||||
> = {};
|
||||
|
||||
return {
|
||||
get: async (id: string) => THREADS[id],
|
||||
put: async (
|
||||
threadId: string,
|
||||
{ metadata }: { metadata?: Record<string, unknown> },
|
||||
) => {
|
||||
THREADS[threadId] = { thread_id: threadId, metadata: metadata ?? {} };
|
||||
},
|
||||
delete: async (threadId: string) => {
|
||||
delete THREADS[threadId];
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
const model = new FakeStreamingChatModel({ responses: [new AIMessage("Hey")] });
|
||||
const agent = new StateGraph(MessagesAnnotation)
|
||||
.addNode("agent", async (state: { messages: Message[] }) => {
|
||||
const response = await model.invoke(state.messages);
|
||||
return { messages: [response] };
|
||||
})
|
||||
.addEdge(START, "agent")
|
||||
.compile();
|
||||
|
||||
const app = createEmbedServer({ graph: { agent }, checkpointer, threads });
|
||||
const server = setupServer(http.all("*", (ctx) => app.fetch(ctx.request)));
|
||||
|
||||
function TestChatComponent() {
|
||||
const { messages, isLoading, error, submit, stop } = useStream({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="messages">
|
||||
{messages.map((msg, i) => (
|
||||
<div key={msg.id ?? i} data-testid={`message-${i}`}>
|
||||
{typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div data-testid="loading">
|
||||
{isLoading ? "Loading..." : "Not loading"}
|
||||
</div>
|
||||
{error ? <div data-testid="error">{String(error)}</div> : null}
|
||||
<button
|
||||
data-testid="submit"
|
||||
onClick={() =>
|
||||
submit({ messages: [{ content: "Hello", type: "human" }] })
|
||||
}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe("useStream", () => {
|
||||
beforeEach(() => server.listen());
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
server.close();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders initial state correctly", () => {
|
||||
render(<TestChatComponent />);
|
||||
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
expect(screen.getByTestId("messages")).toBeEmptyDOMElement();
|
||||
expect(screen.queryByTestId("error")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles message submission and streaming", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<TestChatComponent />);
|
||||
|
||||
// Check loading state
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Loading...");
|
||||
|
||||
// Wait for messages to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent("Hello");
|
||||
expect(screen.getByTestId("message-1")).toHaveTextContent("Hey");
|
||||
});
|
||||
|
||||
// Check final state
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
});
|
||||
|
||||
it("handles stop functionality", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestChatComponent />);
|
||||
|
||||
// Start streaming and stop immediately
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Check loading state is reset
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
});
|
||||
});
|
||||
|
||||
it("displays initial values immediately and clears them when submitting", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
function TestCachedComponent() {
|
||||
const { messages, values, submit } = useStream<{
|
||||
messages: Message[];
|
||||
}>({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
initialValues: {
|
||||
messages: [
|
||||
{ id: "cached-1", type: "human", content: "Cached user message" },
|
||||
{ id: "cached-2", type: "ai", content: "Cached AI response" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="messages">
|
||||
{messages.map((msg, i) => (
|
||||
<div
|
||||
key={msg.id ?? i}
|
||||
data-testid={
|
||||
msg.id?.includes("cached")
|
||||
? `message-cached-${i}`
|
||||
: `message-${i}`
|
||||
}
|
||||
>
|
||||
{typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div data-testid="values">{JSON.stringify(values)}</div>
|
||||
<button
|
||||
data-testid="submit"
|
||||
onClick={() =>
|
||||
submit({ messages: [{ content: "Hello", type: "human" }] })
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestCachedComponent />);
|
||||
|
||||
// Should immediately show cached messages
|
||||
expect(screen.getByTestId("message-cached-0")).toHaveTextContent(
|
||||
"Cached user message",
|
||||
);
|
||||
expect(screen.getByTestId("message-cached-1")).toHaveTextContent(
|
||||
"Cached AI response",
|
||||
);
|
||||
|
||||
// Values should include initial values
|
||||
expect(screen.getByTestId("values")).toHaveTextContent(
|
||||
"Cached user message",
|
||||
);
|
||||
|
||||
// Submitting should clear out the cached messages
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
|
||||
// Wait for messages to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent("Hello");
|
||||
expect(screen.getByTestId("message-1")).toHaveTextContent("Hey");
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts newThreadId option without errors", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const spy = vi.fn();
|
||||
const predeterminedThreadId = randomUUID();
|
||||
|
||||
// Test that newThreadId option can be passed without causing errors
|
||||
function TestNewThreadComponent() {
|
||||
const stream = useStream<{ messages: Message[] }>({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
threadId: null, // Start with no thread
|
||||
onThreadId: spy, // Mock callback
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="loading">
|
||||
{stream.isLoading ? "Loading..." : "Not loading"}
|
||||
</div>
|
||||
<div data-testid="thread-id">
|
||||
{stream.client ? "Client ready" : "No client"}
|
||||
</div>
|
||||
<button
|
||||
data-testid="submit"
|
||||
onClick={() =>
|
||||
stream.submit({}, { threadId: predeterminedThreadId })
|
||||
}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestNewThreadComponent />);
|
||||
|
||||
// Should render without errors
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
expect(screen.getByTestId("thread-id")).toHaveTextContent("Client ready");
|
||||
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
expect(spy).toHaveBeenCalledWith(predeterminedThreadId);
|
||||
expect(await threads.get(predeterminedThreadId)).toEqual({
|
||||
thread_id: predeterminedThreadId,
|
||||
metadata: {
|
||||
graph_id: "agent",
|
||||
assistant_id: "agent",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("onStop callback is called when stop is called", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onStopCallback = vi.fn();
|
||||
|
||||
function TestComponent() {
|
||||
const { submit, stop } = useStream({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
onStop: onStopCallback,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Start a stream and stop it
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Verify onStop was called with mutate function
|
||||
expect(onStopCallback).toHaveBeenCalledTimes(1);
|
||||
expect(onStopCallback).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mutate: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("onStop mutate function updates stream values immediately", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
function TestComponent() {
|
||||
const [stopped, setStopped] = useState(false);
|
||||
const { submit, stop, messages } = useStream<{ messages: Message[] }>({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
onStop: ({ mutate }) => {
|
||||
setStopped(true);
|
||||
mutate((prev) => ({
|
||||
...prev,
|
||||
messages: [
|
||||
...(prev.messages ?? []),
|
||||
{ type: "ai", content: "Stream stopped" },
|
||||
],
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="stopped-status">
|
||||
{stopped ? "Stopped" : "Not stopped"}
|
||||
</div>
|
||||
<div data-testid="messages">
|
||||
{messages.map((msg, i) => (
|
||||
<div key={msg.id ?? i} data-testid={`message-${i}`}>
|
||||
{typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Initial state
|
||||
expect(screen.getByTestId("stopped-status")).toHaveTextContent(
|
||||
"Not stopped",
|
||||
);
|
||||
|
||||
// Start and stop stream
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Verify state was updated immediately
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("stopped-status")).toHaveTextContent("Stopped");
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent(
|
||||
"Stream stopped",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("onStop handles functional updates correctly", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
function TestComponent() {
|
||||
const { submit, stop, values } = useStream({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
initialValues: {
|
||||
counter: 5,
|
||||
items: ["item1", "item2"],
|
||||
},
|
||||
onStop: ({ mutate }) => {
|
||||
mutate((prev: any) => ({
|
||||
...prev,
|
||||
counter: (prev.counter || 0) + 10,
|
||||
items: [...(prev.items || []), "stopped"],
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="counter">{(values as any).counter}</div>
|
||||
<div data-testid="items">{(values as any).items?.join(", ")}</div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Initial state
|
||||
expect(screen.getByTestId("counter")).toHaveTextContent("5");
|
||||
expect(screen.getByTestId("items")).toHaveTextContent("item1, item2");
|
||||
|
||||
// Start and stop stream
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Verify functional update was applied correctly
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("counter")).toHaveTextContent("15");
|
||||
expect(screen.getByTestId("items")).toHaveTextContent(
|
||||
"item1, item2, stopped",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("onStop is not called when stream completes naturally", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const onStopCallback = vi.fn();
|
||||
|
||||
function TestComponent() {
|
||||
const { submit } = useStream({
|
||||
assistantId: "agent",
|
||||
apiKey: "test-api-key",
|
||||
onStop: onStopCallback,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button data-testid="submit" onClick={() => submit({})}>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render(<TestComponent />);
|
||||
|
||||
// Start a stream and let it complete naturally
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
|
||||
// Wait for stream to complete naturally
|
||||
await waitFor(() => {
|
||||
expect(onStopCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,101 +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>;
|
||||
|
||||
type BaseMessage = {
|
||||
additional_kwargs?: MessageAdditionalKwargs | undefined;
|
||||
content: MessageContent;
|
||||
id?: string | undefined;
|
||||
name?: string | undefined;
|
||||
response_metadata?: Record<string, unknown> | undefined;
|
||||
};
|
||||
|
||||
export type HumanMessage = BaseMessage & {
|
||||
type: "human";
|
||||
example?: boolean | undefined;
|
||||
};
|
||||
|
||||
export type AIMessage = BaseMessage & {
|
||||
type: "ai";
|
||||
example?: boolean | undefined;
|
||||
tool_calls?:
|
||||
| {
|
||||
name: string;
|
||||
args: { [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;
|
||||
};
|
||||
|
||||
export type ToolMessage = BaseMessage & {
|
||||
type: "tool";
|
||||
status?: "error" | "success" | undefined;
|
||||
tool_call_id: string;
|
||||
/**
|
||||
* Artifact of the Tool execution which is not meant to be sent to the model.
|
||||
*
|
||||
* Should only be specified if it is different from the message content, e.g. if only
|
||||
* a subset of the full tool output is being passed as message content but the full
|
||||
* output is needed in other parts of the code.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
artifact?: any;
|
||||
};
|
||||
|
||||
export type SystemMessage = BaseMessage & {
|
||||
type: "system";
|
||||
};
|
||||
|
||||
export type FunctionMessage = BaseMessage & {
|
||||
type: "function";
|
||||
};
|
||||
|
||||
export type RemoveMessage = BaseMessage & {
|
||||
type: "remove";
|
||||
};
|
||||
|
||||
export type Message =
|
||||
| HumanMessage
|
||||
| AIMessage
|
||||
| ToolMessage
|
||||
| SystemMessage
|
||||
| FunctionMessage
|
||||
| RemoveMessage;
|
||||
@@ -1,231 +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 { id?: string; event: string; data: unknown }> =
|
||||
{
|
||||
id?: TEvent["id"];
|
||||
event: TEvent["event"] | `${TEvent["event"]}|${string}`;
|
||||
data: TEvent["data"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream event with values after completion of each step.
|
||||
*/
|
||||
export type ValuesStreamEvent<StateType> = {
|
||||
id?: string;
|
||||
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 = {
|
||||
id?: string;
|
||||
event: "metadata";
|
||||
data: { run_id: string; thread_id: string };
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream event with error information.
|
||||
*/
|
||||
export type ErrorStreamEvent = {
|
||||
id?: string;
|
||||
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> = {
|
||||
id?: string;
|
||||
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 = {
|
||||
id?: string;
|
||||
event: "messages/metadata";
|
||||
data: { [messageId: string]: { metadata: unknown } };
|
||||
};
|
||||
type MessagesCompleteStreamEvent = {
|
||||
id?: string;
|
||||
event: "messages/complete";
|
||||
data: Message[];
|
||||
};
|
||||
type MessagesPartialStreamEvent = {
|
||||
id?: string;
|
||||
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 = { id?: string; event: "debug"; data: unknown };
|
||||
|
||||
/** @internal */
|
||||
export type SubgraphDebugStreamEvent = AsSubgraph<DebugStreamEvent>;
|
||||
|
||||
/**
|
||||
* Stream event with events occurring during execution.
|
||||
*/
|
||||
export type EventsStreamEvent = {
|
||||
id?: string;
|
||||
event: "events";
|
||||
data: {
|
||||
event:
|
||||
| `on_${"chat_model" | "llm" | "chain" | "tool" | "retriever" | "prompt"}_${"start" | "stream" | "end"}`
|
||||
| (string & {});
|
||||
name: string;
|
||||
tags: string[];
|
||||
run_id: string;
|
||||
metadata: Record<string, unknown>;
|
||||
parent_ids: string[];
|
||||
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 = {
|
||||
id?: string;
|
||||
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,209 +0,0 @@
|
||||
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
|
||||
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: 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[];
|
||||
}
|
||||
|
||||
export 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">;
|
||||
|
||||
/**
|
||||
* Whether to checkpoint during the run (or only at the end/interruption).
|
||||
*/
|
||||
checkpointDuring?: boolean;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Callback when a run is created.
|
||||
*/
|
||||
onRunCreated?: (params: { run_id: string; thread_id?: string }) => void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* For LangSmith tracing purposes only. Not part of the public API.
|
||||
*/
|
||||
_langsmithTracer?: LangChainTracer;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Whether the stream is considered resumable.
|
||||
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
|
||||
*/
|
||||
streamResumable?: boolean;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Whether the stream is considered resumable.
|
||||
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
|
||||
*/
|
||||
streamResumable?: 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,216 +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
|
||||
409, // Conflict
|
||||
422, // Unprocessable Entity
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
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,175 +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 function BytesLineDecoder() {
|
||||
let buffer: Uint8Array[] = [];
|
||||
let trailingCr = false;
|
||||
|
||||
return new TransformStream<Uint8Array, Uint8Array>({
|
||||
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 {
|
||||
id: string | undefined;
|
||||
event: string;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
export function SSEDecoder() {
|
||||
let event = "";
|
||||
let data: Uint8Array[] = [];
|
||||
let lastEventId = "";
|
||||
let retry: number | null = null;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
return new TransformStream<Uint8Array, StreamPart>({
|
||||
transform(chunk, controller) {
|
||||
// Handle empty line case
|
||||
if (!chunk.length) {
|
||||
if (!event && !data.length && !lastEventId && retry == null) return;
|
||||
|
||||
const sse = {
|
||||
id: lastEventId || undefined,
|
||||
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({
|
||||
id: lastEventId || undefined,
|
||||
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,33 +0,0 @@
|
||||
{
|
||||
"extends": "@tsconfig/recommended",
|
||||
"compilerOptions": {
|
||||
"target": "ES2021",
|
||||
"lib": ["ES2021", "ES2022.Object", "ES2022.Error", "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}/auth"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"pageTitleTemplates": {
|
||||
"index": "{projectName}/react"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
||||
},
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user