mirror of
https://github.com/langchain-ai/langgraphjs-api.git
synced 2026-07-01 13:58:12 -04:00
Add turbo, fix formatting
This commit is contained in:
+2
-1
@@ -5,4 +5,5 @@ dist
|
||||
.venv
|
||||
__pycache__
|
||||
.langgraph_api
|
||||
.uv
|
||||
.uv
|
||||
.turbo
|
||||
@@ -0,0 +1,36 @@
|
||||
# Contributing to Agent Protocol
|
||||
|
||||
👋 Hi there! Thank you for being interested in contributing to LangGraph.
|
||||
As an open source project in a rapidly developing field, we are extremely open
|
||||
to contributions, whether it be in the form of a new feature, improved infra, or better documentation.
|
||||
|
||||
To contribute to this project, please follow a ["fork and pull request"](https://docs.github.com/en/get-started/quickstart/contributing-to-projects) workflow. Please do not try to push directly to this repo unless you are a maintainer.
|
||||
|
||||
## 🗺️ Contributing Guidelines
|
||||
|
||||
Our [issues](https://github.com/langchain-ai/langgraphjs/issues) page contains
|
||||
with bugs, improvements, and feature requests.
|
||||
|
||||
If you start working on an issue, please comment and a maintainer can assign it to you.
|
||||
|
||||
If you are adding an issue, please try to keep it focused on a single modular bug/improvement/feature.
|
||||
If the two issues are related, or blocking, please link them rather than keep them as one single one.
|
||||
|
||||
We will try to keep these issues as up to date as possible, though
|
||||
with the rapid rate of development in this field some may get out of date.
|
||||
If you notice this happening, please just let us know.
|
||||
|
||||
## 🚀 Setup
|
||||
|
||||
To get started, you will need to install the dependencies for the project. To do so, run:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
To build the project, run:
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License Copyright (c) 2025 LangChain
|
||||
|
||||
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
|
||||
(including the next paragraph) 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,3 +1,3 @@
|
||||
# LangGraph.js API and CLI
|
||||
|
||||
This repository contains the source code for the in-memory implementation of the LangGraph.js API and the LangGraph.js CLI tools.
|
||||
This repository contains the source code for the in-memory implementation of the LangGraph.js API and the LangGraph.js CLI tools.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License Copyright (c) 2025 LangChain
|
||||
|
||||
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
|
||||
(including the next paragraph) 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.
|
||||
@@ -11,10 +11,13 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "npx -y bun scripts/clean.mjs",
|
||||
"cli": "tsx src/cli.mts",
|
||||
"cli:watch": "tsx watch src/cli.mts",
|
||||
"build": "rm -rf dist && tsc --outDir dist",
|
||||
"prepack": "pnpm run build"
|
||||
"build": "npx -y bun scripts/build.mjs",
|
||||
"prepack": "pnpm run build",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.9.1",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bun
|
||||
function $(strings, ...rest) {
|
||||
console.log("$", ...strings.raw);
|
||||
return Bun.$(strings, ...rest);
|
||||
}
|
||||
|
||||
await $`rm -rf dist`;
|
||||
await $`pnpm tsc --outDir dist`;
|
||||
@@ -1,8 +0,0 @@
|
||||
Set-PSDebug -Trace 1
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Remove-Item -Path "dist" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
tsc --outDir dist
|
||||
|
||||
Move-Item -Path "dist/src/*" -Destination "dist"
|
||||
Remove-Item -Path "dist/src","dist/tests" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on error
|
||||
set -x # Print commands before execution
|
||||
|
||||
rm -rf dist
|
||||
tsc --outDir dist
|
||||
|
||||
mv dist/src/* dist
|
||||
rm -rf dist/src dist/tests
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bun
|
||||
function $(strings, ...rest) {
|
||||
console.log("$", ...strings.raw);
|
||||
return Bun.$(strings, ...rest);
|
||||
}
|
||||
|
||||
await $`rm -rf dist`;
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawn } from "node:child_process";
|
||||
import * as process from "node:process";
|
||||
|
||||
const file = process.argv.at(-1);
|
||||
|
||||
const proc =
|
||||
process.platform === "win32"
|
||||
? spawn(
|
||||
"powershell.exe",
|
||||
["-ExecutionPolicy", "Bypass", "-File", `scripts/${file}.ps1`],
|
||||
{ stdio: "inherit" }
|
||||
)
|
||||
: spawn("bash", ["-c", `scripts/${file}.sh`], { stdio: "inherit" });
|
||||
|
||||
proc.on("exit", (code) => process.exit(code ?? 0));
|
||||
@@ -56,7 +56,7 @@ const TEMPLATE_ID_TO_CONFIG = Object.entries(TEMPLATES).reduce(
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, [string, string, string]>
|
||||
{} as Record<string, [string, string, string]>,
|
||||
);
|
||||
|
||||
const TEMPLATE_IDS = Object.keys(TEMPLATE_ID_TO_CONFIG);
|
||||
@@ -84,7 +84,7 @@ async function downloadAndExtract(url: string, targetPath: string) {
|
||||
|
||||
// Move files from the extracted directory to target path
|
||||
const extractedDir = (await fs.readdir(targetPath)).find((f) =>
|
||||
f.endsWith("-main")
|
||||
f.endsWith("-main"),
|
||||
);
|
||||
if (extractedDir) {
|
||||
const fullExtractedPath = path.join(targetPath, extractedDir);
|
||||
@@ -93,16 +93,16 @@ async function downloadAndExtract(url: string, targetPath: string) {
|
||||
files.map((file) =>
|
||||
fs.rename(
|
||||
path.join(fullExtractedPath, file),
|
||||
path.join(targetPath, file)
|
||||
)
|
||||
)
|
||||
path.join(targetPath, file),
|
||||
),
|
||||
),
|
||||
);
|
||||
await fs.rmdir(fullExtractedPath);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(
|
||||
`Failed to download and extract template: ${error.message}`
|
||||
`Failed to download and extract template: ${error.message}`,
|
||||
);
|
||||
}
|
||||
throw new Error("Failed to download and extract template");
|
||||
@@ -181,14 +181,14 @@ async function createNew(projectPath?: string, templateId?: string) {
|
||||
- cd ${path.relative(process.cwd(), absolutePath)}
|
||||
- yarn install
|
||||
- npx @langchain/langgraph-cli@latest dev
|
||||
`
|
||||
`,
|
||||
)
|
||||
: null;
|
||||
|
||||
outro(
|
||||
[`Project created successfully at ${color.green(absolutePath)}`, guide]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
.join("\n\n"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ const program = new Command()
|
||||
if (!config) {
|
||||
console.error(`Invalid template ID "${options.template}"`);
|
||||
console.error(
|
||||
`Available options:\n${TEMPLATE_IDS.map((id) => `- ${id}`).join("\n")}`
|
||||
`Available options:\n${TEMPLATE_IDS.map((id) => `- ${id}`).join("\n")}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ let analyticsPromise = Promise.resolve();
|
||||
|
||||
export function withAnalytics<TCommand extends Command<any, any, any>>(
|
||||
fn?: (command: TCommand) => Record<string, boolean>,
|
||||
options?: { name?: string }
|
||||
options?: { name?: string },
|
||||
) {
|
||||
if (process.env.LANGGRAPH_CLI_NO_ANALYTICS === "1") {
|
||||
return () => void 0;
|
||||
@@ -50,7 +50,7 @@ export function withAnalytics<TCommand extends Command<any, any, any>>(
|
||||
cli_version: version,
|
||||
cli_command: options?.name ?? actionCommand.name(),
|
||||
params: fn?.(actionCommand) ?? {},
|
||||
}).catch(() => {})
|
||||
}).catch(() => {}),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as url from "node:url";
|
||||
async function getVersion() {
|
||||
try {
|
||||
const packageJson = url.fileURLToPath(
|
||||
new URL("../../package.json", import.meta.url)
|
||||
new URL("../../package.json", import.meta.url),
|
||||
);
|
||||
const { version } = JSON.parse(await fs.readFile(packageJson, "utf-8"));
|
||||
return version;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,3 +1,3 @@
|
||||
# LangGraph.js API
|
||||
|
||||
In-memory implementation of the LangGraph.js API.
|
||||
In-memory implementation of the LangGraph.js API.
|
||||
|
||||
@@ -11,13 +11,16 @@
|
||||
"dist/"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "npx -y bun scripts/clean.mjs",
|
||||
"build": "npx -y bun scripts/build.mjs",
|
||||
"dev": "tsx ./tests/utils.server.mts",
|
||||
"prepack": "pnpm run build",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "vitest",
|
||||
"test:parser": "vitest run ./tests/parser.test.mts --testTimeout 15000",
|
||||
"test:api": "npx -y bun scripts/test.mjs"
|
||||
"test:api": "npx -y bun scripts/test.mjs",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bun
|
||||
function $(strings, ...rest) {
|
||||
console.log("$", ...strings.raw);
|
||||
return Bun.$(strings, ...rest);
|
||||
}
|
||||
|
||||
await $`rm -rf dist`;
|
||||
@@ -12,6 +12,6 @@ await Promise.race([
|
||||
(async () => {
|
||||
await $`bun x wait-port -t 12000 localhost:2024`;
|
||||
await $`pnpm vitest run --exclude ./tests/parser.test.mts"`;
|
||||
process.exit(0)
|
||||
process.exit(0);
|
||||
})(),
|
||||
]);
|
||||
|
||||
@@ -27,7 +27,7 @@ api.post(
|
||||
});
|
||||
|
||||
return c.json(assistant);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -48,7 +48,7 @@ api.post(
|
||||
}
|
||||
|
||||
return c.json(result);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get("/assistants/:assistant_id", async (c) => {
|
||||
@@ -72,7 +72,7 @@ api.patch(
|
||||
const payload = c.req.valid("json");
|
||||
|
||||
return c.json(await Assistants.patch(assistantId, payload));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const RunnableConfigSchema = z.object({
|
||||
@@ -86,7 +86,7 @@ const RunnableConfigSchema = z.object({
|
||||
});
|
||||
|
||||
const getRunnableConfig = (
|
||||
userConfig: z.infer<typeof RunnableConfigSchema> | null | undefined
|
||||
userConfig: z.infer<typeof RunnableConfigSchema> | null | undefined,
|
||||
) => {
|
||||
if (!userConfig) return {};
|
||||
return {
|
||||
@@ -111,9 +111,9 @@ api.get(
|
||||
|
||||
const graph = getGraph(assistant.graph_id);
|
||||
return c.json(
|
||||
graph.getGraph({ ...getRunnableConfig(assistant.config), xray }).toJSON()
|
||||
graph.getGraph({ ...getRunnableConfig(assistant.config), xray }).toJSON(),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get("/assistants/:assistant_id/schemas", async (c) => {
|
||||
@@ -140,7 +140,7 @@ api.get(
|
||||
"/assistants/:assistant_id/subgraphs/:namespace?",
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({ assistant_id: z.string(), namespace: z.string().optional() })
|
||||
z.object({ assistant_id: z.string(), namespace: z.string().optional() }),
|
||||
),
|
||||
zValidator("query", z.object({ recurse: schemas.coercedBoolean.optional() })),
|
||||
async (c) => {
|
||||
@@ -173,7 +173,7 @@ api.get(
|
||||
}
|
||||
|
||||
return c.json(Object.fromEntries(result));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -184,7 +184,7 @@ api.post(
|
||||
const assistantId = getAssistantId(c.req.param("assistant_id"));
|
||||
const { version } = c.req.valid("json");
|
||||
return c.json(await Assistants.setLatest(assistantId, version));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -195,7 +195,7 @@ api.post(
|
||||
limit: z.number().min(1).max(1000).optional().default(10),
|
||||
offset: z.number().min(0).optional().default(0),
|
||||
metadata: z.record(z.unknown()).optional(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
// Get Assistant Versions
|
||||
@@ -206,9 +206,9 @@ api.post(
|
||||
limit,
|
||||
offset,
|
||||
metadata,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -19,7 +19,7 @@ const api = new Hono();
|
||||
|
||||
const createValidRun = async (
|
||||
threadId: string | undefined,
|
||||
payload: z.infer<typeof schemas.RunCreate>
|
||||
payload: z.infer<typeof schemas.RunCreate>,
|
||||
): Promise<Run> => {
|
||||
const { assistant_id: assistantId, ...run } = payload;
|
||||
const runId = uuid4();
|
||||
@@ -78,7 +78,7 @@ const createValidRun = async (
|
||||
preventInsertInInflight,
|
||||
afterSeconds: payload.after_seconds,
|
||||
ifNotExists: payload.if_not_exists,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (first?.run_id === runId) {
|
||||
@@ -91,7 +91,7 @@ const createValidRun = async (
|
||||
await Runs.cancel(
|
||||
threadId,
|
||||
inflight.map((run) => run.run_id),
|
||||
{ action: multitaskStrategy }
|
||||
{ action: multitaskStrategy },
|
||||
);
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
@@ -100,7 +100,7 @@ const createValidRun = async (
|
||||
error,
|
||||
run_ids: inflight.map((run) => run.run_id),
|
||||
thread_id: threadId,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ api.post(
|
||||
async () => {
|
||||
// Search Crons
|
||||
throw new HTTPException(500, { message: "Not implemented" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.delete(
|
||||
@@ -138,7 +138,7 @@ api.delete(
|
||||
async () => {
|
||||
// Delete Cron
|
||||
throw new HTTPException(500, { message: "Not implemented" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -148,7 +148,7 @@ api.post(
|
||||
async () => {
|
||||
// Create Thread Cron
|
||||
throw new HTTPException(500, { message: "Not implemented" });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
|
||||
@@ -166,7 +166,7 @@ api.post("/runs/stream", zValidator("json", schemas.RunCreate), async (c) => {
|
||||
for await (const { event, data } of Runs.Stream.join(
|
||||
run.run_id,
|
||||
undefined,
|
||||
{ cancelOnDisconnect, ignore404: true }
|
||||
{ cancelOnDisconnect, ignore404: true },
|
||||
)) {
|
||||
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
||||
}
|
||||
@@ -197,10 +197,10 @@ api.post(
|
||||
// Batch Runs
|
||||
const payload = c.req.valid("json");
|
||||
const runs = await Promise.all(
|
||||
payload.map((run) => createValidRun(undefined, run))
|
||||
payload.map((run) => createValidRun(undefined, run)),
|
||||
);
|
||||
return jsonExtra(c, runs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
@@ -213,7 +213,7 @@ api.get(
|
||||
offset: z.coerce.number().nullish(),
|
||||
status: z.string().nullish(),
|
||||
metadata: z.record(z.string(), z.unknown()).nullish(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
// List runs
|
||||
@@ -231,7 +231,7 @@ api.get(
|
||||
]);
|
||||
|
||||
return jsonExtra(c, runs);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -245,7 +245,7 @@ api.post(
|
||||
|
||||
const run = await createValidRun(thread_id, payload);
|
||||
return jsonExtra(c, run);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -268,7 +268,7 @@ api.post(
|
||||
for await (const { event, data } of Runs.Stream.join(
|
||||
run.run_id,
|
||||
thread_id,
|
||||
{ cancelOnDisconnect }
|
||||
{ cancelOnDisconnect },
|
||||
)) {
|
||||
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
||||
}
|
||||
@@ -276,7 +276,7 @@ api.post(
|
||||
logError(error, { prefix: "Error streaming run" });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -290,14 +290,14 @@ api.post(
|
||||
|
||||
const run = await createValidRun(thread_id, payload);
|
||||
return waitKeepAlive(c, Runs.join(run.run_id, thread_id));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
"/threads/:thread_id/runs/:run_id",
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() }),
|
||||
),
|
||||
async (c) => {
|
||||
const { thread_id, run_id } = c.req.valid("param");
|
||||
@@ -307,45 +307,45 @@ api.get(
|
||||
]);
|
||||
|
||||
return jsonExtra(c, run);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.delete(
|
||||
"/threads/:thread_id/runs/:run_id",
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() }),
|
||||
),
|
||||
async (c) => {
|
||||
// Delete Run
|
||||
const { thread_id, run_id } = c.req.valid("param");
|
||||
await Runs.delete(run_id, thread_id);
|
||||
return c.body(null, 204);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
"/threads/:thread_id/runs/:run_id/join",
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() }),
|
||||
),
|
||||
async (c) => {
|
||||
// Join Run Http
|
||||
const { thread_id, run_id } = c.req.valid("param");
|
||||
return jsonExtra(c, await Runs.join(run_id, thread_id));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
"/threads/:thread_id/runs/:run_id/stream",
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() }),
|
||||
),
|
||||
zValidator(
|
||||
"query",
|
||||
z.object({ cancel_on_disconnect: schemas.coercedBoolean.optional() })
|
||||
z.object({ cancel_on_disconnect: schemas.coercedBoolean.optional() }),
|
||||
),
|
||||
async (c) => {
|
||||
// Stream Run Http
|
||||
@@ -362,21 +362,21 @@ api.get(
|
||||
await stream.writeSSE({ data: serialiseAsDict(data), event });
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
"/threads/:thread_id/runs/:run_id/cancel",
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() })
|
||||
z.object({ thread_id: z.string().uuid(), run_id: z.string().uuid() }),
|
||||
),
|
||||
zValidator(
|
||||
"query",
|
||||
z.object({
|
||||
wait: z.coerce.boolean().optional().default(false),
|
||||
action: z.enum(["interrupt", "rollback"]).optional().default("interrupt"),
|
||||
})
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
// Cancel Run Http
|
||||
@@ -386,7 +386,7 @@ api.post(
|
||||
await Runs.cancel(thread_id, [run_id], { action });
|
||||
if (wait) await Runs.join(run_id, thread_id);
|
||||
return c.body(null, wait ? 204 : 202);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -54,7 +54,7 @@ api.post(
|
||||
maxDepth: payload.max_depth,
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -73,7 +73,7 @@ api.post(
|
||||
});
|
||||
|
||||
return c.json({ items: items.map(mapItemsToApi) });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.put("/store/items", zValidator("json", schemas.StorePutItem), async (c) => {
|
||||
@@ -93,7 +93,7 @@ api.delete(
|
||||
if (payload.namespace) validateNamespace(payload.namespace);
|
||||
await storageStore.delete(payload.namespace ?? [], payload.key);
|
||||
return c.body(null, 204);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
@@ -105,7 +105,7 @@ api.get(
|
||||
const key = payload.key;
|
||||
const namespace = payload.namespace;
|
||||
return c.json(mapItemsToApi(await storageStore.get(namespace, key)));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -46,7 +46,7 @@ api.post(
|
||||
}
|
||||
|
||||
return jsonExtra(c, result);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
@@ -54,7 +54,7 @@ api.get(
|
||||
zValidator("param", z.object({ thread_id: z.string().uuid() })),
|
||||
zValidator(
|
||||
"query",
|
||||
z.object({ subgraphs: schemas.coercedBoolean.optional() })
|
||||
z.object({ subgraphs: schemas.coercedBoolean.optional() }),
|
||||
),
|
||||
async (c) => {
|
||||
// Get Latest Thread State
|
||||
@@ -62,11 +62,11 @@ api.get(
|
||||
const { subgraphs } = c.req.valid("query");
|
||||
|
||||
const state = stateSnapshotToThreadState(
|
||||
await Threads.State.get({ configurable: { thread_id } }, { subgraphs })
|
||||
await Threads.State.get({ configurable: { thread_id } }, { subgraphs }),
|
||||
);
|
||||
|
||||
return jsonExtra(c, state);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -84,7 +84,7 @@ api.post(
|
||||
as_node: z.string().optional(),
|
||||
checkpoint_id: z.string().optional(),
|
||||
checkpoint: schemas.CheckpointSchema.nullish(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
// Update Thread State
|
||||
@@ -104,22 +104,25 @@ api.post(
|
||||
const inserted = await Threads.State.post(
|
||||
config,
|
||||
payload.values,
|
||||
payload.as_node
|
||||
payload.as_node,
|
||||
);
|
||||
|
||||
return jsonExtra(c, inserted);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
"/threads/:thread_id/state/:checkpoint_id",
|
||||
zValidator(
|
||||
"param",
|
||||
z.object({ thread_id: z.string().uuid(), checkpoint_id: z.string().uuid() })
|
||||
z.object({
|
||||
thread_id: z.string().uuid(),
|
||||
checkpoint_id: z.string().uuid(),
|
||||
}),
|
||||
),
|
||||
zValidator(
|
||||
"query",
|
||||
z.object({ subgraphs: schemas.coercedBoolean.optional() })
|
||||
z.object({ subgraphs: schemas.coercedBoolean.optional() }),
|
||||
),
|
||||
async (c) => {
|
||||
// Get Thread State At Checkpoint
|
||||
@@ -128,12 +131,12 @@ api.get(
|
||||
const state = stateSnapshotToThreadState(
|
||||
await Threads.State.get(
|
||||
{ configurable: { thread_id, checkpoint_id } },
|
||||
{ subgraphs }
|
||||
)
|
||||
{ subgraphs },
|
||||
),
|
||||
);
|
||||
|
||||
return jsonExtra(c, state);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -144,7 +147,7 @@ api.post(
|
||||
z.object({
|
||||
subgraphs: schemas.coercedBoolean.optional(),
|
||||
checkpoint: schemas.CheckpointSchema.nullish(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
// Get Thread State At Checkpoint Post
|
||||
@@ -154,12 +157,12 @@ api.post(
|
||||
const state = stateSnapshotToThreadState(
|
||||
await Threads.State.get(
|
||||
{ configurable: { thread_id, ...checkpoint } },
|
||||
{ subgraphs }
|
||||
)
|
||||
{ subgraphs },
|
||||
),
|
||||
);
|
||||
|
||||
return jsonExtra(c, state);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
@@ -174,7 +177,7 @@ api.get(
|
||||
.default("10")
|
||||
.transform((value) => parseInt(value, 10)),
|
||||
before: z.string().optional(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
// Get Thread History
|
||||
@@ -183,10 +186,10 @@ api.get(
|
||||
|
||||
const states = await Threads.State.list(
|
||||
{ configurable: { thread_id, checkpoint_ns: "" } },
|
||||
{ limit, before }
|
||||
{ limit, before },
|
||||
);
|
||||
return jsonExtra(c, states.map(stateSnapshotToThreadState));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -205,7 +208,7 @@ api.post(
|
||||
checkpoint_map: z.record(z.string(), z.unknown()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
// Get Thread History Post
|
||||
@@ -214,11 +217,11 @@ api.post(
|
||||
|
||||
const states = await Threads.State.list(
|
||||
{ configurable: { thread_id, checkpoint_ns: "", ...checkpoint } },
|
||||
{ limit, before, metadata }
|
||||
{ limit, before, metadata },
|
||||
);
|
||||
|
||||
return jsonExtra(c, states.map(stateSnapshotToThreadState));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.get(
|
||||
@@ -228,7 +231,7 @@ api.get(
|
||||
// Get Thread
|
||||
const { thread_id } = c.req.valid("param");
|
||||
return jsonExtra(c, await Threads.get(thread_id));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.delete(
|
||||
@@ -239,7 +242,7 @@ api.delete(
|
||||
const { thread_id } = c.req.valid("param");
|
||||
await Threads.delete(thread_id);
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.patch(
|
||||
@@ -251,7 +254,7 @@ api.patch(
|
||||
const { thread_id } = c.req.valid("param");
|
||||
const { metadata } = c.req.valid("json");
|
||||
return jsonExtra(c, await Threads.patch(thread_id, { metadata }));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
api.post(
|
||||
@@ -261,7 +264,7 @@ api.post(
|
||||
// Copy Thread
|
||||
const { thread_id } = c.req.valid("param");
|
||||
return jsonExtra(c, await Threads.copy(thread_id));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export default api;
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function spawnServer(
|
||||
options: {
|
||||
pid: number;
|
||||
projectCwd: string;
|
||||
}
|
||||
},
|
||||
) {
|
||||
const localUrl = `http://${args.host}:${args.port}`;
|
||||
const studioUrl = `${context.hostUrl}/studio?baseUrl=${localUrl}`;
|
||||
@@ -39,7 +39,7 @@ For production use, please use LangGraph Cloud.
|
||||
process.execPath,
|
||||
[
|
||||
fileURLToPath(
|
||||
new URL("../../cli.mjs", import.meta.resolve("tsx/esm/api"))
|
||||
new URL("../../cli.mjs", import.meta.resolve("tsx/esm/api")),
|
||||
),
|
||||
"watch",
|
||||
"--clear-screen=false",
|
||||
@@ -53,6 +53,6 @@ For production use, please use LangGraph Cloud.
|
||||
cwd: options.projectCwd,
|
||||
}),
|
||||
],
|
||||
{ stdio: ["inherit", "inherit", "inherit", "ipc"], env: context.env }
|
||||
{ stdio: ["inherit", "inherit", "inherit", "ipc"], env: context.env },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// MIT License
|
||||
//
|
||||
//
|
||||
// Copyright (c) Hiroki Osame <hiroki.osame@gmail.com>
|
||||
//
|
||||
//
|
||||
// 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
|
||||
@@ -19,7 +19,7 @@
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
// https://github.com/privatenumber/tsx/tree/28a3e7d2b8fd72b683aab8a98dd1fcee4624e4cb
|
||||
import net from "node:net";
|
||||
import { getPipePath } from "./utils/get-pipe-path.mjs";
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// MIT License
|
||||
//
|
||||
//
|
||||
// Copyright (c) Hiroki Osame <hiroki.osame@gmail.com>
|
||||
//
|
||||
//
|
||||
// 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
|
||||
@@ -19,7 +19,7 @@
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
// https://github.com/privatenumber/tsx/tree/28a3e7d2b8fd72b683aab8a98dd1fcee4624e4cb
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
|
||||
@@ -23,7 +23,7 @@ export const GRAPH_SPEC: Record<string, GraphSpec> = {};
|
||||
export const GRAPH_SCHEMA: Record<string, Record<string, GraphSchema>> = {};
|
||||
|
||||
export const NAMESPACE_GRAPH = uuid.parse(
|
||||
"6ba7b821-9dad-11d1-80b4-00c04fd430c8"
|
||||
"6ba7b821-9dad-11d1-80b4-00c04fd430c8",
|
||||
);
|
||||
|
||||
const ConfigSchema = z.record(z.unknown());
|
||||
@@ -35,7 +35,7 @@ export const getAssistantId = (graphId: string) => {
|
||||
|
||||
export async function registerFromEnv(
|
||||
specs: Record<string, string>,
|
||||
options: { cwd: string }
|
||||
options: { cwd: string },
|
||||
) {
|
||||
const envConfig = process.env.LANGGRAPH_CONFIG
|
||||
? ConfigSchema.parse(JSON.parse(process.env.LANGGRAPH_CONFIG))
|
||||
@@ -65,7 +65,7 @@ export async function registerFromEnv(
|
||||
});
|
||||
|
||||
return resolved;
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export function getGraph(
|
||||
options?: {
|
||||
checkpointer?: BaseCheckpointSaver | null;
|
||||
store?: BaseStore;
|
||||
}
|
||||
},
|
||||
) {
|
||||
if (!GRAPHS[graphId])
|
||||
throw new HTTPException(404, { message: `Graph "${graphId}" not found` });
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { JSONSchema7 } from "json-schema";
|
||||
|
||||
export const GRAPHS: Record<string, CompiledGraph<string>> = {};
|
||||
export const NAMESPACE_GRAPH = uuid.parse(
|
||||
"6ba7b821-9dad-11d1-80b4-00c04fd430c8"
|
||||
"6ba7b821-9dad-11d1-80b4-00c04fd430c8",
|
||||
);
|
||||
|
||||
export interface GraphSchema {
|
||||
@@ -25,7 +25,7 @@ export interface GraphSpec {
|
||||
|
||||
export async function resolveGraph(
|
||||
spec: string,
|
||||
options: { cwd: string; onlyFilePresence?: false }
|
||||
options: { cwd: string; onlyFilePresence?: false },
|
||||
): Promise<{
|
||||
sourceFile: string;
|
||||
exportSymbol: string;
|
||||
@@ -34,12 +34,12 @@ export async function resolveGraph(
|
||||
|
||||
export async function resolveGraph(
|
||||
spec: string,
|
||||
options: { cwd: string; onlyFilePresence: true }
|
||||
options: { cwd: string; onlyFilePresence: true },
|
||||
): Promise<{ sourceFile: string; exportSymbol: string; resolved: undefined }>;
|
||||
|
||||
export async function resolveGraph(
|
||||
spec: string,
|
||||
options: { cwd: string; onlyFilePresence?: boolean }
|
||||
options: { cwd: string; onlyFilePresence?: boolean },
|
||||
) {
|
||||
const [userFile, exportSymbol] = spec.split(":", 2);
|
||||
const sourceFile = path.resolve(options.cwd, userFile);
|
||||
@@ -84,7 +84,7 @@ export async function runGraphSchemaWorker(spec: GraphSpec) {
|
||||
|
||||
return await new Promise<Record<string, GraphSchema>>((resolve, reject) => {
|
||||
const worker = new Worker(
|
||||
fileURLToPath(new URL("./parser/parser.worker.mjs", import.meta.url))
|
||||
fileURLToPath(new URL("./parser/parser.worker.mjs", import.meta.url)),
|
||||
);
|
||||
|
||||
// Set a timeout to reject if the worker takes too long
|
||||
|
||||
@@ -28,7 +28,7 @@ export class SubgraphExtractor {
|
||||
program: ts.Program,
|
||||
sourceFile: ts.SourceFile,
|
||||
inferFile: ts.SourceFile,
|
||||
options?: { strict?: boolean }
|
||||
options?: { strict?: boolean },
|
||||
) {
|
||||
this.program = program;
|
||||
this.sourceFile = sourceFile;
|
||||
@@ -65,7 +65,7 @@ export class SubgraphExtractor {
|
||||
|
||||
private find = (
|
||||
root: ts.Node,
|
||||
predicate: (node: ts.Node) => boolean
|
||||
predicate: (node: ts.Node) => boolean,
|
||||
): ts.Node | undefined => {
|
||||
let result: ts.Node | undefined = undefined;
|
||||
|
||||
@@ -84,7 +84,7 @@ export class SubgraphExtractor {
|
||||
|
||||
protected findSubgraphs = (
|
||||
node: ts.Node,
|
||||
namespace: string[] = []
|
||||
namespace: string[] = [],
|
||||
): {
|
||||
node: string;
|
||||
namespace: string[];
|
||||
@@ -96,7 +96,7 @@ export class SubgraphExtractor {
|
||||
namespace: string[];
|
||||
subgraph: { name: string; node: ts.Node };
|
||||
}[],
|
||||
node: ts.Node
|
||||
node: ts.Node,
|
||||
) => {
|
||||
if (ts.isCallExpression(node)) {
|
||||
const firstChild = node.getChildAt(0);
|
||||
@@ -128,7 +128,7 @@ export class SubgraphExtractor {
|
||||
variables = this.reduceChildren(
|
||||
callArg,
|
||||
this.findSubgraphIdentifiers,
|
||||
[]
|
||||
[],
|
||||
);
|
||||
} else if (ts.isIdentifier(callArg)) {
|
||||
variables = this.findSubgraphIdentifiers([], callArg);
|
||||
@@ -166,13 +166,13 @@ export class SubgraphExtractor {
|
||||
type InternalFlowNode = ts.Node & { flowNode?: { node: ts.Node } };
|
||||
const candidate = this.find(
|
||||
node,
|
||||
(node: any) => node && "flowNode" in node && node.flowNode
|
||||
(node: any) => node && "flowNode" in node && node.flowNode,
|
||||
) as InternalFlowNode | undefined;
|
||||
|
||||
if (
|
||||
candidate?.flowNode &&
|
||||
this.isGraphOrPregelType(
|
||||
this.checker.getTypeAtLocation(candidate.flowNode.node)
|
||||
this.checker.getTypeAtLocation(candidate.flowNode.node),
|
||||
)
|
||||
) {
|
||||
subgraphs = this.findSubgraphs(candidate.flowNode.node, namespace);
|
||||
@@ -184,7 +184,7 @@ export class SubgraphExtractor {
|
||||
return [
|
||||
...subgraphs,
|
||||
...subgraphs.map(({ subgraph, node }) =>
|
||||
this.findSubgraphs(subgraph.node, [...namespace, node])
|
||||
this.findSubgraphs(subgraph.node, [...namespace, node]),
|
||||
),
|
||||
].flat();
|
||||
}
|
||||
@@ -199,7 +199,7 @@ export class SubgraphExtractor {
|
||||
const targetExport = exports.find((item) => item.name === name);
|
||||
if (!targetExport) throw new Error(`Failed to find export "${name}"`);
|
||||
const varDecls = (targetExport.declarations ?? []).filter(
|
||||
ts.isVariableDeclaration
|
||||
ts.isVariableDeclaration,
|
||||
);
|
||||
|
||||
return varDecls.flatMap((varDecl) => {
|
||||
@@ -209,7 +209,7 @@ export class SubgraphExtractor {
|
||||
};
|
||||
|
||||
public getAugmentedSourceFile = (
|
||||
name: string
|
||||
name: string,
|
||||
): {
|
||||
files: [filePath: string, contents: string][];
|
||||
exports: { typeName: string; valueName: string; graphName: string }[];
|
||||
@@ -238,7 +238,7 @@ export class SubgraphExtractor {
|
||||
this.getText(this.sourceFile),
|
||||
...typeExports.map(
|
||||
({ typeName, valueName }) =>
|
||||
`export type ${typeName} = typeof ${valueName}`
|
||||
`export type ${typeName} = typeof ${valueName}`,
|
||||
),
|
||||
].join("\n\n");
|
||||
|
||||
@@ -246,7 +246,7 @@ export class SubgraphExtractor {
|
||||
const inferContents = [
|
||||
...typeExports.map(
|
||||
({ typeName }) =>
|
||||
`import type { ${typeName}} from "./__langgraph__source.mts"`
|
||||
`import type { ${typeName}} from "./__langgraph__source.mts"`,
|
||||
),
|
||||
this.inferFile.getText(this.inferFile),
|
||||
|
||||
@@ -277,7 +277,7 @@ export class SubgraphExtractor {
|
||||
|
||||
protected findSubgraphIdentifiers = (
|
||||
acc: { node: ts.Node; name: string }[],
|
||||
node: ts.Node
|
||||
node: ts.Node,
|
||||
) => {
|
||||
if (ts.isIdentifier(node)) {
|
||||
const smb = this.checker.getSymbolAtLocation(node);
|
||||
@@ -322,7 +322,7 @@ export class SubgraphExtractor {
|
||||
protected reduceChildren<Acc>(
|
||||
node: ts.Node,
|
||||
fn: (acc: Acc, node: ts.Node) => Acc,
|
||||
initial: Acc
|
||||
initial: Acc,
|
||||
): Acc {
|
||||
let acc = initial;
|
||||
function it(node: ts.Node) {
|
||||
@@ -343,7 +343,7 @@ export class SubgraphExtractor {
|
||||
files?: [fileName: string, contents: string][];
|
||||
},
|
||||
name: string,
|
||||
options?: { strict?: boolean }
|
||||
options?: { strict?: boolean },
|
||||
) {
|
||||
const dirname =
|
||||
typeof target === "string" ? path.dirname(target) : __dirname;
|
||||
@@ -365,7 +365,7 @@ export class SubgraphExtractor {
|
||||
|
||||
const inferTemplatePath = path.resolve(
|
||||
__dirname,
|
||||
"./schema/types.template.mts"
|
||||
"./schema/types.template.mts",
|
||||
);
|
||||
|
||||
if (typeof target !== "string") {
|
||||
@@ -417,7 +417,7 @@ export class SubgraphExtractor {
|
||||
research,
|
||||
research.getSourceFile(targetPath)!,
|
||||
research.getSourceFile(inferTemplatePath)!,
|
||||
options
|
||||
options,
|
||||
);
|
||||
|
||||
const { files, exports } = extractor.getAugmentedSourceFile(name);
|
||||
@@ -438,7 +438,7 @@ export class SubgraphExtractor {
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Failed to obtain symbol "${symbol}":`,
|
||||
(e as Error)?.message
|
||||
(e as Error)?.message,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
@@ -453,7 +453,7 @@ export class SubgraphExtractor {
|
||||
state: trySymbol(schemaGenerator, `${typeName}__update`),
|
||||
config: trySymbol(schemaGenerator, `${typeName}__config`),
|
||||
},
|
||||
])
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ parentPort?.on("message", async (payload) => {
|
||||
const result = SubgraphExtractor.extractSchemas(
|
||||
payload.sourceFile,
|
||||
payload.exportSymbol,
|
||||
{ strict: false }
|
||||
{ strict: false },
|
||||
);
|
||||
parentPort?.postMessage(result);
|
||||
});
|
||||
|
||||
@@ -240,7 +240,7 @@ function resolveRequiredFile(
|
||||
symbol: ts.Symbol,
|
||||
key: string,
|
||||
fileName: string,
|
||||
objectName: string
|
||||
objectName: string,
|
||||
): any {
|
||||
const sourceFile = getSourceFile(symbol);
|
||||
const requiredFilePath = /^[.\/]+/.test(fileName)
|
||||
@@ -404,7 +404,7 @@ function getCanonicalDeclaration(sym: ts.Symbol): ts.Declaration {
|
||||
|
||||
const declarationCount = sym.declarations?.length ?? 0;
|
||||
throw new Error(
|
||||
`Symbol "${sym.name}" has no valueDeclaration and ${declarationCount} declarations.`
|
||||
`Symbol "${sym.name}" has no valueDeclaration and ${declarationCount} declarations.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -418,7 +418,7 @@ function getSourceFile(sym: ts.Symbol): ts.SourceFile {
|
||||
while (currentDecl.kind !== ts.SyntaxKind.SourceFile) {
|
||||
if (currentDecl.parent === undefined) {
|
||||
throw new Error(
|
||||
`Unable to locate source file for declaration "${sym.name}".`
|
||||
`Unable to locate source file for declaration "${sym.name}".`,
|
||||
);
|
||||
}
|
||||
currentDecl = currentDecl.parent;
|
||||
@@ -561,7 +561,7 @@ class JsonSchemaGenerator {
|
||||
userSymbols: { [name: string]: ts.Symbol },
|
||||
inheritingTypes: { [baseName: string]: string[] },
|
||||
tc: ts.TypeChecker,
|
||||
private args = getDefaultArgs()
|
||||
private args = getDefaultArgs(),
|
||||
) {
|
||||
this.symbols = symbols;
|
||||
this.allSymbols = allSymbols;
|
||||
@@ -570,7 +570,7 @@ class JsonSchemaGenerator {
|
||||
this.tc = tc;
|
||||
this.userValidationKeywords = args.validationKeywords.reduce(
|
||||
(acc, word) => ({ ...acc, [word]: true }),
|
||||
{}
|
||||
{},
|
||||
);
|
||||
this.constAsEnum = args.constAsEnum;
|
||||
}
|
||||
@@ -606,7 +606,7 @@ class JsonSchemaGenerator {
|
||||
private parseCommentsIntoDefinition(
|
||||
symbol: ts.Symbol,
|
||||
definition: Definition,
|
||||
otherAnnotations: Record<string, true>
|
||||
otherAnnotations: Record<string, true>,
|
||||
): void {
|
||||
if (!symbol) {
|
||||
return;
|
||||
@@ -621,7 +621,7 @@ class JsonSchemaGenerator {
|
||||
.map((comment) => {
|
||||
const newlineNormalizedComment = comment.text.replace(
|
||||
/\r\n/g,
|
||||
"\n"
|
||||
"\n",
|
||||
);
|
||||
|
||||
// If a comment contains a "{@link XYZ}" inline tag that could not be
|
||||
@@ -657,7 +657,7 @@ class JsonSchemaGenerator {
|
||||
}
|
||||
} else if (name === "TJS" && text.startsWith("-")) {
|
||||
let match: string[] | RegExpExecArray | null = new RegExp(
|
||||
REGEX_TJS_JSDOC
|
||||
REGEX_TJS_JSDOC,
|
||||
).exec(originalText);
|
||||
if (match) {
|
||||
name = match[1];
|
||||
@@ -673,7 +673,7 @@ class JsonSchemaGenerator {
|
||||
// to process the "." and beyond from the value
|
||||
if (subDefinitions[name]) {
|
||||
const match: string[] | RegExpExecArray | null = new RegExp(
|
||||
REGEX_GROUP_JSDOC
|
||||
REGEX_GROUP_JSDOC,
|
||||
).exec(text);
|
||||
if (match) {
|
||||
const k = match[1];
|
||||
@@ -716,7 +716,7 @@ class JsonSchemaGenerator {
|
||||
reffedType: ts.Symbol,
|
||||
definition: Definition,
|
||||
defaultNumberType = this.args.defaultNumberType,
|
||||
ignoreUndefined = false
|
||||
ignoreUndefined = false,
|
||||
): Definition {
|
||||
const tupleType = resolveTupleType(propertyType);
|
||||
|
||||
@@ -725,7 +725,7 @@ class JsonSchemaGenerator {
|
||||
const elemTypes: ts.NodeArray<ts.TypeNode> = (propertyType as any)
|
||||
.typeArguments;
|
||||
const fixedTypes = elemTypes.map((elType) =>
|
||||
this.getTypeDefinition(elType as any)
|
||||
this.getTypeDefinition(elType as any),
|
||||
);
|
||||
definition.type = "array";
|
||||
if (fixedTypes.length > 0) {
|
||||
@@ -743,12 +743,12 @@ class JsonSchemaGenerator {
|
||||
const propertyTypeString = this.tc.typeToString(
|
||||
propertyType,
|
||||
undefined,
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType,
|
||||
);
|
||||
const flags = propertyType.flags;
|
||||
const arrayType = this.tc.getIndexTypeOfType(
|
||||
propertyType,
|
||||
ts.IndexKind.Number
|
||||
ts.IndexKind.Number,
|
||||
);
|
||||
|
||||
if (flags & ts.TypeFlags.String) {
|
||||
@@ -826,7 +826,7 @@ class JsonSchemaGenerator {
|
||||
};
|
||||
if (
|
||||
!!Array.from((propertyType as any).members as any[])?.find(
|
||||
(member: [string]) => member[0] !== "__index"
|
||||
(member: [string]) => member[0] !== "__index",
|
||||
)
|
||||
) {
|
||||
this.getClassDefinition(propertyType, definition);
|
||||
@@ -879,7 +879,7 @@ class JsonSchemaGenerator {
|
||||
} else {
|
||||
// Report that type could not be processed
|
||||
const error = new TypeError(
|
||||
"Unsupported type: " + propertyTypeString
|
||||
"Unsupported type: " + propertyTypeString,
|
||||
);
|
||||
(error as any).type = propertyType;
|
||||
throw error;
|
||||
@@ -908,7 +908,7 @@ class JsonSchemaGenerator {
|
||||
|
||||
private getDefinitionForProperty(
|
||||
prop: ts.Symbol,
|
||||
node: ts.Node
|
||||
node: ts.Node,
|
||||
): Definition | null {
|
||||
if (prop.flags & ts.SymbolFlags.Method) {
|
||||
return null;
|
||||
@@ -923,7 +923,7 @@ class JsonSchemaGenerator {
|
||||
undefined,
|
||||
undefined,
|
||||
prop,
|
||||
reffedType
|
||||
reffedType,
|
||||
);
|
||||
|
||||
if (this.args.titles) {
|
||||
@@ -967,12 +967,12 @@ class JsonSchemaGenerator {
|
||||
definition.default = val;
|
||||
} else if (val) {
|
||||
console.warn(
|
||||
"unknown initializer for property " + propertyName + ": " + val
|
||||
"unknown initializer for property " + propertyName + ": " + val,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"exception evaluating initializer for property " + propertyName
|
||||
"exception evaluating initializer for property " + propertyName,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -983,13 +983,13 @@ class JsonSchemaGenerator {
|
||||
|
||||
private getEnumDefinition(
|
||||
clazzType: ts.Type,
|
||||
definition: Definition
|
||||
definition: Definition,
|
||||
): Definition {
|
||||
const node = clazzType.getSymbol()!.getDeclarations()![0];
|
||||
const fullName = this.tc.typeToString(
|
||||
clazzType,
|
||||
undefined,
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType,
|
||||
);
|
||||
const members: ts.NodeArray<ts.EnumMember> =
|
||||
node.kind === ts.SyntaxKind.EnumDeclaration
|
||||
@@ -1034,7 +1034,7 @@ class JsonSchemaGenerator {
|
||||
"initializer is expression for enum: " +
|
||||
fullName +
|
||||
"." +
|
||||
caseLabel
|
||||
caseLabel,
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
@@ -1068,7 +1068,7 @@ class JsonSchemaGenerator {
|
||||
private getUnionDefinition(
|
||||
unionType: ts.UnionType,
|
||||
unionModifier: keyof Definition,
|
||||
definition: Definition
|
||||
definition: Definition,
|
||||
): Definition {
|
||||
const enumValues: PrimitiveType[] = [];
|
||||
const simpleTypes: JSONSchema7TypeName[] = [];
|
||||
@@ -1100,7 +1100,7 @@ class JsonSchemaGenerator {
|
||||
symbol,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
true,
|
||||
);
|
||||
if (def.type === ("undefined" as any)) {
|
||||
continue;
|
||||
@@ -1184,7 +1184,7 @@ class JsonSchemaGenerator {
|
||||
|
||||
private getIntersectionDefinition(
|
||||
intersectionType: ts.IntersectionType,
|
||||
definition: Definition
|
||||
definition: Definition,
|
||||
): Definition {
|
||||
const simpleTypes: JSONSchema7TypeName[] = [];
|
||||
const schemas: Definition[] = [];
|
||||
@@ -1230,7 +1230,7 @@ class JsonSchemaGenerator {
|
||||
|
||||
private getClassDefinition(
|
||||
clazzType: ts.Type,
|
||||
definition: Definition
|
||||
definition: Definition,
|
||||
): Definition {
|
||||
const node = clazzType.getSymbol()!.getDeclarations()![0];
|
||||
|
||||
@@ -1277,7 +1277,7 @@ class JsonSchemaGenerator {
|
||||
const fullName = this.tc.typeToString(
|
||||
clazzType,
|
||||
undefined,
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType,
|
||||
);
|
||||
|
||||
const modifierFlags = ts.getCombinedModifierFlags(node);
|
||||
@@ -1297,7 +1297,7 @@ class JsonSchemaGenerator {
|
||||
clazz.members == null
|
||||
? []
|
||||
: clazz.members.filter(
|
||||
(x) => x.kind === ts.SyntaxKind.IndexSignature
|
||||
(x) => x.kind === ts.SyntaxKind.IndexSignature,
|
||||
);
|
||||
if (indexSignatures.length === 1) {
|
||||
// for case "array-types"
|
||||
@@ -1305,21 +1305,21 @@ class JsonSchemaGenerator {
|
||||
indexSignatures[0] as ts.IndexSignatureDeclaration;
|
||||
if (indexSignature.parameters.length !== 1) {
|
||||
throw new Error(
|
||||
"Not supported: IndexSignatureDeclaration parameters.length != 1"
|
||||
"Not supported: IndexSignatureDeclaration parameters.length != 1",
|
||||
);
|
||||
}
|
||||
const indexSymbol: ts.Symbol = (indexSignature.parameters[0] as any)
|
||||
.symbol;
|
||||
const indexType = this.tc.getTypeOfSymbolAtLocation(
|
||||
indexSymbol,
|
||||
node
|
||||
node,
|
||||
);
|
||||
const isIndexedObject =
|
||||
indexType.flags === ts.TypeFlags.String ||
|
||||
indexType.flags === ts.TypeFlags.Number;
|
||||
if (indexType.flags !== ts.TypeFlags.Number && !isIndexedObject) {
|
||||
throw new Error(
|
||||
"Not supported: IndexSignatureDeclaration with index symbol other than a number or a string"
|
||||
"Not supported: IndexSignatureDeclaration with index symbol other than a number or a string",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1327,7 +1327,7 @@ class JsonSchemaGenerator {
|
||||
let def: Definition | undefined;
|
||||
if (typ.flags & ts.TypeFlags.IndexedAccess) {
|
||||
const targetName = ts.escapeLeadingUnderscores(
|
||||
(clazzType as any).mapper?.target?.value
|
||||
(clazzType as any).mapper?.target?.value,
|
||||
);
|
||||
const indexedAccessType = typ as ts.IndexedAccessType;
|
||||
const symbols: Map<ts.__String, ts.Symbol> = (
|
||||
@@ -1339,7 +1339,7 @@ class JsonSchemaGenerator {
|
||||
const targetNode = targetSymbol.getDeclarations()![0];
|
||||
const targetDef = this.getDefinitionForProperty(
|
||||
targetSymbol,
|
||||
targetNode
|
||||
targetNode,
|
||||
);
|
||||
if (targetDef) {
|
||||
def = targetDef;
|
||||
@@ -1372,7 +1372,7 @@ class JsonSchemaGenerator {
|
||||
}
|
||||
return all;
|
||||
},
|
||||
{}
|
||||
{},
|
||||
);
|
||||
|
||||
if (definition.type === undefined) {
|
||||
@@ -1403,7 +1403,7 @@ class JsonSchemaGenerator {
|
||||
order.push(prop.getName());
|
||||
return order;
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
definition.propertyOrder = propertyOrder;
|
||||
@@ -1427,7 +1427,7 @@ class JsonSchemaGenerator {
|
||||
}
|
||||
return required;
|
||||
},
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
if (requiredProps.length > 0) {
|
||||
@@ -1454,9 +1454,9 @@ class JsonSchemaGenerator {
|
||||
typ,
|
||||
undefined,
|
||||
ts.TypeFormatFlags.NoTruncation |
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType,
|
||||
)
|
||||
.replace(REGEX_FILE_NAME_OR_SPACE, "")
|
||||
.replace(REGEX_FILE_NAME_OR_SPACE, ""),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1489,7 +1489,7 @@ class JsonSchemaGenerator {
|
||||
reffedType?: ts.Symbol,
|
||||
pairedSymbol?: ts.Symbol,
|
||||
forceNotRef: boolean = false,
|
||||
ignoreUndefined = false
|
||||
ignoreUndefined = false,
|
||||
): Definition {
|
||||
const definition: Definition = {}; // real definition
|
||||
|
||||
@@ -1574,7 +1574,7 @@ class JsonSchemaGenerator {
|
||||
.getFullyQualifiedName(
|
||||
reffedType!.getFlags() & ts.SymbolFlags.Alias
|
||||
? this.tc.getAliasedSymbol(reffedType!)
|
||||
: reffedType!
|
||||
: reffedType!,
|
||||
)
|
||||
.replace(REGEX_FILE_NAME_OR_SPACE, "");
|
||||
if (this.args.uniqueNames && reffedType) {
|
||||
@@ -1582,7 +1582,7 @@ class JsonSchemaGenerator {
|
||||
const relativePath = path.relative(process.cwd(), sourceFile.fileName);
|
||||
fullTypeName = `${typeName}.${generateHashOfNode(
|
||||
getCanonicalDeclaration(reffedType!),
|
||||
relativePath
|
||||
relativePath,
|
||||
)}`;
|
||||
} else {
|
||||
fullTypeName = this.makeTypeNameUnique(typ, typeName);
|
||||
@@ -1595,7 +1595,7 @@ class JsonSchemaGenerator {
|
||||
const relativePath = path.relative(process.cwd(), sourceFile.fileName);
|
||||
fullTypeName = `${this.getTypeName(typ)}.${generateHashOfNode(
|
||||
getCanonicalDeclaration(sym),
|
||||
relativePath
|
||||
relativePath,
|
||||
)}`;
|
||||
} else if (
|
||||
reffedType &&
|
||||
@@ -1631,20 +1631,20 @@ class JsonSchemaGenerator {
|
||||
this.parseCommentsIntoDefinition(
|
||||
typ.aliasSymbol!,
|
||||
definition,
|
||||
otherAnnotations
|
||||
otherAnnotations,
|
||||
);
|
||||
if (prop) {
|
||||
this.parseCommentsIntoDefinition(
|
||||
prop,
|
||||
returnedDefinition,
|
||||
otherAnnotations
|
||||
otherAnnotations,
|
||||
);
|
||||
}
|
||||
if (pairedSymbol && symbol && this.isFromDefaultLib(symbol)) {
|
||||
this.parseCommentsIntoDefinition(
|
||||
pairedSymbol,
|
||||
definition,
|
||||
otherAnnotations
|
||||
otherAnnotations,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1669,7 +1669,7 @@ class JsonSchemaGenerator {
|
||||
true,
|
||||
undefined,
|
||||
symbol,
|
||||
symbol
|
||||
symbol,
|
||||
);
|
||||
} else {
|
||||
reffedDefinition = definition;
|
||||
@@ -1693,7 +1693,7 @@ class JsonSchemaGenerator {
|
||||
this.getUnionDefinition(
|
||||
typ as ts.UnionType,
|
||||
unionModifier,
|
||||
definition
|
||||
definition,
|
||||
);
|
||||
} else if (typ.flags & ts.TypeFlags.Intersection) {
|
||||
if (this.args.noExtraProps) {
|
||||
@@ -1711,7 +1711,7 @@ class JsonSchemaGenerator {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
true,
|
||||
);
|
||||
definition.type = other.type; // should always be object
|
||||
definition.properties = {
|
||||
@@ -1722,19 +1722,19 @@ class JsonSchemaGenerator {
|
||||
if (Object.keys(other.default || {}).length > 0) {
|
||||
definition.default = extend(
|
||||
definition.default || {},
|
||||
other.default
|
||||
other.default,
|
||||
);
|
||||
}
|
||||
if (other.required) {
|
||||
definition.required = unique(
|
||||
(definition.required || []).concat(other.required)
|
||||
(definition.required || []).concat(other.required),
|
||||
).sort();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.getIntersectionDefinition(
|
||||
typ as ts.IntersectionType,
|
||||
definition
|
||||
definition,
|
||||
);
|
||||
}
|
||||
} else if (isRawType) {
|
||||
@@ -1746,7 +1746,7 @@ class JsonSchemaGenerator {
|
||||
reffedType!,
|
||||
definition,
|
||||
undefined,
|
||||
ignoreUndefined
|
||||
ignoreUndefined,
|
||||
);
|
||||
} else if (
|
||||
node &&
|
||||
@@ -1806,7 +1806,7 @@ class JsonSchemaGenerator {
|
||||
public getSchemaForSymbol(
|
||||
symbolName: string,
|
||||
includeReffedDefinitions: boolean = true,
|
||||
includeAllOverrides: boolean = false
|
||||
includeAllOverrides: boolean = false,
|
||||
): Definition {
|
||||
const overrideDefinition = this.schemaOverrides.get(symbolName);
|
||||
if (!this.allSymbols[symbolName] && !overrideDefinition) {
|
||||
@@ -1827,7 +1827,7 @@ class JsonSchemaGenerator {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
this.userSymbols[symbolName] || undefined
|
||||
this.userSymbols[symbolName] || undefined,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1849,7 +1849,7 @@ class JsonSchemaGenerator {
|
||||
public getSchemaForSymbols(
|
||||
symbolNames: string[],
|
||||
includeReffedDefinitions: boolean = true,
|
||||
includeAllOverrides: boolean = false
|
||||
includeAllOverrides: boolean = false,
|
||||
): Definition {
|
||||
const root: {
|
||||
$id?: string;
|
||||
@@ -1875,7 +1875,7 @@ class JsonSchemaGenerator {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
this.userSymbols[symbolName]
|
||||
this.userSymbols[symbolName],
|
||||
);
|
||||
}
|
||||
if (
|
||||
@@ -1902,7 +1902,7 @@ class JsonSchemaGenerator {
|
||||
|
||||
public getMainFileSymbols(
|
||||
program: ts.Program,
|
||||
onlyIncludeFiles?: string[]
|
||||
onlyIncludeFiles?: string[],
|
||||
): string[] {
|
||||
function includeFile(file: ts.SourceFile): boolean {
|
||||
if (onlyIncludeFiles === undefined) {
|
||||
@@ -1940,7 +1940,7 @@ function generateHashOfNode(node: ts.Node, relativePath: string): string {
|
||||
|
||||
export function buildGenerator(
|
||||
program: ts.Program,
|
||||
args: PartialArgs = {}
|
||||
args: PartialArgs = {},
|
||||
): JsonSchemaGenerator | null {
|
||||
// Use defaults unless otherwise specified
|
||||
const settings = getDefaultArgs();
|
||||
@@ -1990,7 +1990,7 @@ export function buildGenerator(
|
||||
var baseName = tc.typeToString(
|
||||
baseType,
|
||||
undefined,
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType
|
||||
ts.TypeFormatFlags.UseFullyQualifiedType,
|
||||
);
|
||||
if (!inheritingTypes[baseName]) {
|
||||
inheritingTypes[baseName] = [];
|
||||
@@ -2011,14 +2011,14 @@ export function buildGenerator(
|
||||
userSymbols,
|
||||
inheritingTypes,
|
||||
typeChecker,
|
||||
settings
|
||||
settings,
|
||||
);
|
||||
}
|
||||
|
||||
export async function extractGraphSchema(
|
||||
id: string,
|
||||
userPath: string,
|
||||
exportName: string
|
||||
exportName: string,
|
||||
) {
|
||||
const filePath = path.resolve(process.cwd(), userPath);
|
||||
const parentPath = path.dirname(filePath);
|
||||
@@ -2104,7 +2104,7 @@ export async function extractGraphSchema(
|
||||
export type __input = Inspect<FilterAny<__builder["input"]>>;
|
||||
export type __output = Inspect<FilterAny<__builder["output"]>>;
|
||||
export type __config = Inspect<FilterAny<__builder["config"]>>;
|
||||
`
|
||||
`,
|
||||
);
|
||||
const program = ts.createProgram([typePath], {
|
||||
noEmit: true,
|
||||
@@ -2120,7 +2120,7 @@ export async function extractGraphSchema(
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Failed to obtain symbol "${symbol}":`,
|
||||
(e as Error)?.message
|
||||
(e as Error)?.message,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -50,7 +50,7 @@ export const logger = createLogger({
|
||||
|
||||
return JSON.stringify({ timestamp, level, event, ...rest });
|
||||
}),
|
||||
])
|
||||
]),
|
||||
),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
@@ -61,7 +61,7 @@ const formatStack = (stack: string | undefined | null) => {
|
||||
const [firstFile] = stacktraceParser(stack).filter(
|
||||
(item) =>
|
||||
!item.file?.split(path.sep).includes("node_modules") &&
|
||||
!item.file?.startsWith("node:")
|
||||
!item.file?.startsWith("node:"),
|
||||
);
|
||||
|
||||
if (firstFile?.file && firstFile?.lineNumber) {
|
||||
@@ -74,7 +74,7 @@ const formatStack = (stack: string | undefined | null) => {
|
||||
const spliceIndex = messageLines.findIndex((i) => i.includes(filePath));
|
||||
|
||||
const padding = " ".repeat(
|
||||
Math.max(0, messageLines[spliceIndex].indexOf("at"))
|
||||
Math.max(0, messageLines[spliceIndex].indexOf("at")),
|
||||
);
|
||||
|
||||
const highlightCode = process.stdout.isTTY;
|
||||
@@ -82,7 +82,7 @@ const formatStack = (stack: string | undefined | null) => {
|
||||
let codeFrame = codeFrameColumns(
|
||||
readFileSync(filePath, "utf-8"),
|
||||
{ start: { line, column } },
|
||||
{ highlightCode }
|
||||
{ highlightCode },
|
||||
);
|
||||
|
||||
codeFrame = codeFrame
|
||||
@@ -113,7 +113,7 @@ export const logError = (
|
||||
options?: {
|
||||
context?: Record<string, unknown>;
|
||||
prefix?: string;
|
||||
}
|
||||
},
|
||||
) => {
|
||||
let message;
|
||||
let context = options?.context;
|
||||
|
||||
@@ -171,7 +171,7 @@ export const RunCreate = z
|
||||
z.union([
|
||||
z.string(),
|
||||
z.object({ node: z.string(), input: z.unknown().optional() }),
|
||||
])
|
||||
]),
|
||||
),
|
||||
])
|
||||
.optional(),
|
||||
@@ -210,7 +210,7 @@ export const RunCreate = z
|
||||
"updates",
|
||||
"events",
|
||||
"debug",
|
||||
])
|
||||
]),
|
||||
),
|
||||
z.enum([
|
||||
"values",
|
||||
|
||||
@@ -52,7 +52,7 @@ app.post(
|
||||
assistants: z.boolean().optional(),
|
||||
checkpointer: z.boolean().optional(),
|
||||
store: z.boolean().optional(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
(c) => {
|
||||
const { runs, threads, assistants, checkpointer, store } =
|
||||
@@ -60,7 +60,7 @@ app.post(
|
||||
|
||||
truncate({ runs, threads, assistants, checkpointer, store });
|
||||
return c.json({ ok: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const StartServerSchema = z.object({
|
||||
@@ -96,8 +96,8 @@ export async function startServer(options: z.infer<typeof StartServerSchema>) {
|
||||
{ fetch: app.fetch, port: options.port, hostname: options.host },
|
||||
(c) => {
|
||||
resolve({ host: `${c.address}:${c.port}`, cleanup });
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import { runnableConfigToCheckpoint } from "./utils/runnableConfig.mjs";
|
||||
import { serializeError } from "./utils/serde.mjs";
|
||||
|
||||
const isStateSnapshot = (
|
||||
state: StateSnapshot | LangGraphRunnableConfig
|
||||
state: StateSnapshot | LangGraphRunnableConfig,
|
||||
): state is StateSnapshot => {
|
||||
return "values" in state && "next" in state;
|
||||
};
|
||||
|
||||
export const stateSnapshotToThreadState = (
|
||||
state: StateSnapshot
|
||||
state: StateSnapshot,
|
||||
): ThreadState => {
|
||||
return {
|
||||
values: state.values,
|
||||
|
||||
@@ -77,18 +77,18 @@ class InMemorySaver extends MemorySaver {
|
||||
async put(
|
||||
config: RunnableConfig,
|
||||
checkpoint: Checkpoint,
|
||||
metadata: CheckpointMetadata
|
||||
metadata: CheckpointMetadata,
|
||||
): Promise<RunnableConfig> {
|
||||
return await conn.with(() =>
|
||||
super.put(config, checkpoint, {
|
||||
...Object.fromEntries(
|
||||
Object.entries(config.configurable ?? {}).filter(
|
||||
([key]) => !key.startsWith("__") && !EXCLUDED_KEYS.includes(key)
|
||||
)
|
||||
([key]) => !key.startsWith("__") && !EXCLUDED_KEYS.includes(key),
|
||||
),
|
||||
),
|
||||
...config.metadata,
|
||||
...metadata,
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ export const conn = new FileSystemPersistence<Store>(
|
||||
assistants: {},
|
||||
assistant_versions: [],
|
||||
retry_counter: {},
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
class TimeoutError extends Error {}
|
||||
@@ -255,8 +255,8 @@ export const truncate = (flags: {
|
||||
Object.entries(STORE.assistants).filter(
|
||||
([key, assistant]) =>
|
||||
assistant.metadata?.created_by === "system" &&
|
||||
uuid5(assistant.graph_id, NAMESPACE_GRAPH) === key
|
||||
)
|
||||
uuid5(assistant.graph_id, NAMESPACE_GRAPH) === key,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ const isObject = (value: unknown): value is Record<string, unknown> => {
|
||||
|
||||
const isJsonbContained = (
|
||||
superset: Record<string, unknown> | undefined,
|
||||
subset: Record<string, unknown> | undefined
|
||||
subset: Record<string, unknown> | undefined,
|
||||
): boolean => {
|
||||
if (superset == null || subset == null) return true;
|
||||
for (const [key, value] of Object.entries(subset)) {
|
||||
@@ -321,7 +321,7 @@ export class Assistants {
|
||||
|
||||
for (const assistant of filtered.slice(
|
||||
options.offset,
|
||||
options.offset + options.limit
|
||||
options.offset + options.limit,
|
||||
)) {
|
||||
yield { ...assistant, name: assistant.name ?? assistant.graph_id };
|
||||
}
|
||||
@@ -345,7 +345,7 @@ export class Assistants {
|
||||
metadata?: Metadata;
|
||||
if_exists: OnConflictBehavior;
|
||||
name?: string;
|
||||
}
|
||||
},
|
||||
): Promise<Assistant> {
|
||||
return conn.with((STORE) => {
|
||||
if (STORE.assistants[assistantId] != null) {
|
||||
@@ -389,7 +389,7 @@ export class Assistants {
|
||||
graph_id?: string;
|
||||
metadata?: Metadata;
|
||||
name?: string;
|
||||
}
|
||||
},
|
||||
): Promise<Assistant> {
|
||||
return conn.with((STORE) => {
|
||||
const assistant = STORE.assistants[assistantId];
|
||||
@@ -428,7 +428,7 @@ export class Assistants {
|
||||
Math.max(
|
||||
...STORE.assistant_versions
|
||||
.filter((v) => v["assistant_id"] === assistantId)
|
||||
.map((v) => v["version"])
|
||||
.map((v) => v["version"]),
|
||||
) + 1;
|
||||
|
||||
assistant.version = newVersion;
|
||||
@@ -458,7 +458,7 @@ export class Assistants {
|
||||
|
||||
// Cascade delete for assistant versions and crons
|
||||
STORE.assistant_versions = STORE.assistant_versions.filter(
|
||||
(v) => v["assistant_id"] !== assistantId
|
||||
(v) => v["assistant_id"] !== assistantId,
|
||||
);
|
||||
|
||||
for (const run of Object.values(STORE.runs)) {
|
||||
@@ -473,7 +473,7 @@ export class Assistants {
|
||||
|
||||
static async setLatest(
|
||||
assistantId: string,
|
||||
version: number
|
||||
version: number,
|
||||
): Promise<Assistant> {
|
||||
return conn.with((STORE) => {
|
||||
const assistant = STORE.assistants[assistantId];
|
||||
@@ -481,7 +481,7 @@ export class Assistants {
|
||||
throw new HTTPException(404, { message: "Assistant not found" });
|
||||
|
||||
const assistantVersion = STORE.assistant_versions.find(
|
||||
(v) => v["assistant_id"] === assistantId && v["version"] === version
|
||||
(v) => v["assistant_id"] === assistantId && v["version"] === version,
|
||||
);
|
||||
|
||||
if (!assistantVersion)
|
||||
@@ -509,7 +509,7 @@ export class Assistants {
|
||||
limit: number;
|
||||
offset: number;
|
||||
metadata?: Metadata;
|
||||
}
|
||||
},
|
||||
) {
|
||||
return conn.with((STORE) => {
|
||||
const versions = STORE.assistant_versions
|
||||
@@ -620,7 +620,7 @@ export class Threads {
|
||||
|
||||
for (const thread of filtered.slice(
|
||||
options.offset,
|
||||
options.offset + options.limit
|
||||
options.offset + options.limit,
|
||||
)) {
|
||||
yield thread;
|
||||
}
|
||||
@@ -644,7 +644,7 @@ export class Threads {
|
||||
options?: {
|
||||
metadata?: Metadata;
|
||||
if_exists: OnConflictBehavior;
|
||||
}
|
||||
},
|
||||
): Promise<Thread> {
|
||||
return conn.with((STORE) => {
|
||||
const now = new Date();
|
||||
@@ -674,7 +674,7 @@ export class Threads {
|
||||
threadId: string,
|
||||
options?: {
|
||||
metadata?: Metadata;
|
||||
}
|
||||
},
|
||||
): Promise<Thread> {
|
||||
return conn.with((STORE) => {
|
||||
const thread = STORE.threads[threadId];
|
||||
@@ -699,7 +699,7 @@ export class Threads {
|
||||
options: {
|
||||
checkpoint?: CheckpointPayload;
|
||||
exception?: Error;
|
||||
}
|
||||
},
|
||||
) {
|
||||
return conn.with((STORE) => {
|
||||
const thread = STORE.threads[threadId];
|
||||
@@ -712,7 +712,7 @@ export class Threads {
|
||||
}
|
||||
|
||||
const hasPendingRuns = Object.values(STORE.runs).some(
|
||||
(run) => run["thread_id"] === threadId && run["status"] === "pending"
|
||||
(run) => run["thread_id"] === threadId && run["status"] === "pending",
|
||||
);
|
||||
|
||||
let status: ThreadStatus = "idle";
|
||||
@@ -737,7 +737,7 @@ export class Threads {
|
||||
if (task.interrupts) acc[task.id] = task.interrupts;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
{},
|
||||
)
|
||||
: undefined;
|
||||
});
|
||||
@@ -790,7 +790,7 @@ export class Threads {
|
||||
config: RunnableConfig,
|
||||
options: {
|
||||
subgraphs?: boolean;
|
||||
}
|
||||
},
|
||||
): Promise<LangGraphStateSnapshot> {
|
||||
const subgraphs = options.subgraphs ?? false;
|
||||
const threadId = config.configurable?.thread_id;
|
||||
@@ -831,7 +831,7 @@ export class Threads {
|
||||
| Record<string, unknown>
|
||||
| null
|
||||
| undefined,
|
||||
asNode?: string | undefined
|
||||
asNode?: string | undefined,
|
||||
) {
|
||||
const threadId = config.configurable?.thread_id;
|
||||
const thread = threadId ? await Threads.get(threadId) : undefined;
|
||||
@@ -880,7 +880,7 @@ export class Threads {
|
||||
limit?: number;
|
||||
before?: string | RunnableConfig;
|
||||
metadata?: Metadata;
|
||||
}
|
||||
},
|
||||
) {
|
||||
const threadId = config.configurable?.thread_id;
|
||||
if (!threadId) return [];
|
||||
@@ -932,7 +932,7 @@ export class Runs {
|
||||
|
||||
if (!thread) {
|
||||
await console.warn(
|
||||
`Unexpected missing thread in Runs.next: ${threadId}`
|
||||
`Unexpected missing thread in Runs.next: ${threadId}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -965,7 +965,7 @@ export class Runs {
|
||||
multitaskStrategy?: MultitaskStrategy;
|
||||
ifNotExists?: IfNotExists;
|
||||
afterSeconds?: number;
|
||||
}
|
||||
},
|
||||
): Promise<Run[]> {
|
||||
return conn.with(async (STORE) => {
|
||||
const assistant = STORE.assistants[assistantId];
|
||||
@@ -985,7 +985,7 @@ export class Runs {
|
||||
const config: RunnableConfig = kwargs.config ?? {};
|
||||
|
||||
const existingThread = Object.values(STORE.threads).find(
|
||||
(thread) => thread.thread_id === threadId
|
||||
(thread) => thread.thread_id === threadId,
|
||||
);
|
||||
|
||||
const now = new Date();
|
||||
@@ -1000,7 +1000,7 @@ export class Runs {
|
||||
configurable: Object.assign(
|
||||
{},
|
||||
assistant.config?.configurable,
|
||||
config?.configurable
|
||||
config?.configurable,
|
||||
),
|
||||
}),
|
||||
created_at: now,
|
||||
@@ -1025,9 +1025,9 @@ export class Runs {
|
||||
{},
|
||||
assistant.config?.configurable,
|
||||
existingThread?.config?.configurable,
|
||||
config?.configurable
|
||||
config?.configurable,
|
||||
),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
existingThread.updated_at = now;
|
||||
@@ -1039,7 +1039,7 @@ export class Runs {
|
||||
// if multitask_mode = reject, check for inflight runs
|
||||
// and if there are any, return them to reject putting a new run
|
||||
const inflightRuns = Object.values(STORE.runs).filter(
|
||||
(run) => run.thread_id === threadId && run.status === "pending"
|
||||
(run) => run.thread_id === threadId && run.status === "pending",
|
||||
);
|
||||
|
||||
if (options?.preventInsertInInflight) {
|
||||
@@ -1062,14 +1062,14 @@ export class Runs {
|
||||
existingThread?.config?.configurable?.user_id ??
|
||||
assistant.config?.configurable?.user_id ??
|
||||
options?.userId,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const mergedMetadata = Object.assign(
|
||||
{},
|
||||
assistant.metadata,
|
||||
existingThread?.metadata,
|
||||
metadata
|
||||
metadata,
|
||||
);
|
||||
|
||||
const newRun: Run = {
|
||||
@@ -1084,7 +1084,7 @@ export class Runs {
|
||||
assistant.config,
|
||||
config,
|
||||
{ configurable },
|
||||
{ metadata: mergedMetadata }
|
||||
{ metadata: mergedMetadata },
|
||||
),
|
||||
}),
|
||||
multitask_strategy: multitaskStrategy,
|
||||
@@ -1099,7 +1099,7 @@ export class Runs {
|
||||
|
||||
static async get(
|
||||
runId: string,
|
||||
threadId: string | undefined
|
||||
threadId: string | undefined,
|
||||
): Promise<Run | null> {
|
||||
return conn.with(async (STORE) => {
|
||||
const run = STORE.runs[runId];
|
||||
@@ -1115,7 +1115,7 @@ export class Runs {
|
||||
|
||||
static async delete(
|
||||
runId: string,
|
||||
threadId: string | undefined
|
||||
threadId: string | undefined,
|
||||
): Promise<string | null> {
|
||||
return conn.with(async (STORE) => {
|
||||
const run = STORE.runs[runId];
|
||||
@@ -1167,7 +1167,7 @@ export class Runs {
|
||||
runIds: string[],
|
||||
options: {
|
||||
action?: "interrupt" | "rollback";
|
||||
}
|
||||
},
|
||||
) {
|
||||
return conn.with(async (STORE) => {
|
||||
const action = options.action ?? "interrupt";
|
||||
@@ -1194,7 +1194,7 @@ export class Runs {
|
||||
{
|
||||
run_id: runId,
|
||||
thread_id: threadId,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
promises.push(Runs.delete(runId, threadId));
|
||||
@@ -1228,7 +1228,7 @@ export class Runs {
|
||||
offset?: number | null;
|
||||
status?: string | null;
|
||||
metadata?: Metadata | null;
|
||||
}
|
||||
},
|
||||
) {
|
||||
return conn.with(async (STORE) => {
|
||||
const runs = Object.values(STORE.runs).filter((run) => {
|
||||
@@ -1263,7 +1263,7 @@ export class Runs {
|
||||
options?: {
|
||||
ignore404?: boolean;
|
||||
cancelOnDisconnect?: AbortSignal;
|
||||
}
|
||||
},
|
||||
): AsyncGenerator<{ event: string; data: unknown }> {
|
||||
// TODO: what if we're joining an already completed run? Should we check before?
|
||||
const signal = options?.cancelOnDisconnect;
|
||||
@@ -1276,7 +1276,7 @@ export class Runs {
|
||||
if (message.data === "done") break;
|
||||
} else {
|
||||
const streamTopic = message.topic.substring(
|
||||
`run:${runId}:stream:`.length
|
||||
`run:${runId}:stream:`.length,
|
||||
);
|
||||
|
||||
yield { event: streamTopic, data: message.data };
|
||||
|
||||
@@ -11,7 +11,7 @@ superjson.registerCustom<Uint8Array, string>(
|
||||
serialize: (v) => Buffer.from(v).toString("base64"),
|
||||
deserialize: (v) => new Uint8Array(Buffer.from(v, "base64")),
|
||||
},
|
||||
"Uint8Array"
|
||||
"Uint8Array",
|
||||
);
|
||||
|
||||
export function serialize(data: unknown) {
|
||||
@@ -81,7 +81,7 @@ export class FileSystemPersistence<Schema> {
|
||||
}
|
||||
|
||||
async *withGenerator<T extends AsyncGenerator<any>>(
|
||||
fn: ((data: Schema) => T) | T
|
||||
fn: ((data: Schema) => T) | T,
|
||||
) {
|
||||
if (this.filepath == null || this.data == null) {
|
||||
throw new Error(`${this.name} not initialized`);
|
||||
|
||||
@@ -30,7 +30,7 @@ class InMemoryStore extends BaseMemoryStore {
|
||||
}
|
||||
|
||||
async batch<Op extends readonly Operation[]>(
|
||||
operations: Op
|
||||
operations: Op,
|
||||
): Promise<OperationResults<Op>> {
|
||||
return await conn.with(() => super.batch(operations));
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ function preprocessDebugCheckpointTask(task: DebugTask): StreamTaskResult {
|
||||
}
|
||||
|
||||
const isConfigurablePresent = (
|
||||
config: unknown
|
||||
config: unknown,
|
||||
): config is {
|
||||
[key: string]: unknown;
|
||||
callbacks?: unknown;
|
||||
@@ -130,8 +130,8 @@ const deleteInternalConfigurableFields = (config: unknown) => {
|
||||
...config,
|
||||
configurable: Object.fromEntries(
|
||||
Object.entries(config.configurable).filter(
|
||||
([key]) => !key.startsWith("__")
|
||||
)
|
||||
([key]) => !key.startsWith("__"),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -168,7 +168,7 @@ export async function* streamState(
|
||||
onCheckpoint?: (checkpoint: StreamCheckpoint) => void;
|
||||
onTaskResult?: (taskResult: StreamTaskResult) => void;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
},
|
||||
): AsyncGenerator<{ event: string; data: unknown }> {
|
||||
const kwargs = run.kwargs;
|
||||
const graphId = kwargs.config?.configurable?.graph_id;
|
||||
@@ -185,8 +185,8 @@ export async function* streamState(
|
||||
|
||||
const libStreamMode: Set<LangGraphStreamMode> = new Set(
|
||||
userStreamMode.filter(
|
||||
(mode) => mode !== "events" && mode !== "messages-tuple"
|
||||
) ?? []
|
||||
(mode) => mode !== "events" && mode !== "messages-tuple",
|
||||
) ?? [],
|
||||
);
|
||||
|
||||
if (userStreamMode.includes("messages-tuple")) {
|
||||
@@ -232,7 +232,7 @@ export async function* streamState(
|
||||
runId: run.run_id,
|
||||
streamMode: [...libStreamMode],
|
||||
signal: options?.signal,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const messages: Record<string, BaseMessageChunk> = {};
|
||||
@@ -344,11 +344,11 @@ export async function* streamState(
|
||||
kwargs.feedback_keys.map(async (feedback) => {
|
||||
const { url } = await client.createPresignedFeedbackToken(
|
||||
run.run_id,
|
||||
feedback
|
||||
feedback,
|
||||
);
|
||||
return [feedback, url];
|
||||
})
|
||||
)
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
yield { event: "feedback", data };
|
||||
|
||||
@@ -7,7 +7,7 @@ export const combineAbortSignals = (
|
||||
|
||||
const abortController = new AbortController();
|
||||
signals.forEach((signal) =>
|
||||
signal.addEventListener("abort", () => abortController.abort())
|
||||
signal.addEventListener("abort", () => abortController.abort()),
|
||||
);
|
||||
return abortController.signal;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ const ConfigSchema = z.object({
|
||||
});
|
||||
|
||||
export const runnableConfigToCheckpoint = (
|
||||
config: RunnableConfig | null | undefined
|
||||
config: RunnableConfig | null | undefined,
|
||||
): Checkpoint | null => {
|
||||
if (!config || !config.configurable || !config.configurable.thread_id) {
|
||||
return null;
|
||||
@@ -38,7 +38,7 @@ const TaskConfigSchema = z.object({
|
||||
});
|
||||
|
||||
export const taskRunnableConfigToCheckpoint = (
|
||||
config: RunnableConfig | null | undefined
|
||||
config: RunnableConfig | null | undefined,
|
||||
): Partial<Checkpoint> | null => {
|
||||
if (!config || !config.configurable || !config.configurable.thread_id) {
|
||||
return null;
|
||||
|
||||
@@ -16,7 +16,7 @@ export const serialiseAsDict = (obj: unknown) => {
|
||||
|
||||
return value;
|
||||
},
|
||||
2
|
||||
2,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -26,5 +26,3 @@ export const serializeError = (error: unknown) => {
|
||||
}
|
||||
return { error: "Error", message: JSON.stringify(error) };
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ const truncate = async (
|
||||
store?: boolean;
|
||||
checkpoint?: boolean;
|
||||
}
|
||||
| "all"
|
||||
| "all",
|
||||
) => {
|
||||
const flags =
|
||||
options === "all"
|
||||
@@ -81,7 +81,7 @@ describe("assistants", () => {
|
||||
|
||||
await client.assistants.delete(res.assistant_id);
|
||||
await expect(() => client.assistants.get(res.assistant_id)).rejects.toThrow(
|
||||
"HTTP 404: Assistant not found"
|
||||
"HTTP 404: Assistant not found",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -159,7 +159,7 @@ describe("assistants", () => {
|
||||
|
||||
await client.assistants.delete(res.assistant_id);
|
||||
await expect(() => client.assistants.get(res.assistant_id)).rejects.toThrow(
|
||||
"HTTP 404: Assistant not found"
|
||||
"HTTP 404: Assistant not found",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("assistants", () => {
|
||||
});
|
||||
expect(search.length).toBeGreaterThanOrEqual(1);
|
||||
expect(search.every((i) => i.assistant_id !== create.assistant_id)).toBe(
|
||||
true
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -193,7 +193,7 @@ describe("assistants", () => {
|
||||
|
||||
// (1) initial version
|
||||
expect(
|
||||
await client.assistants.getVersions(assistant.assistant_id)
|
||||
await client.assistants.getVersions(assistant.assistant_id),
|
||||
).toMatchObject([{ version: 1 }]);
|
||||
|
||||
// (2) update and create a new version
|
||||
@@ -201,7 +201,7 @@ describe("assistants", () => {
|
||||
config: { configurable: { foo: "bar" } },
|
||||
});
|
||||
expect(
|
||||
await client.assistants.getVersions(assistant.assistant_id)
|
||||
await client.assistants.getVersions(assistant.assistant_id),
|
||||
).toMatchObject([
|
||||
{ version: 2, config: { configurable: { foo: "bar" } } },
|
||||
{ version: 1 },
|
||||
@@ -211,14 +211,14 @@ describe("assistants", () => {
|
||||
expect(
|
||||
await client.assistants.getVersions(assistant.assistant_id, {
|
||||
limit: 1,
|
||||
})
|
||||
}),
|
||||
).toMatchObject([{ version: 2 }]);
|
||||
|
||||
// descending order
|
||||
expect(
|
||||
await client.assistants.getVersions(assistant.assistant_id, {
|
||||
offset: 1,
|
||||
})
|
||||
}),
|
||||
).toMatchObject([{ version: 1 }]);
|
||||
|
||||
// (3) create a version with metadata
|
||||
@@ -229,14 +229,14 @@ describe("assistants", () => {
|
||||
expect(
|
||||
await client.assistants.getVersions(assistant.assistant_id, {
|
||||
metadata: { foo: "baz" },
|
||||
})
|
||||
}),
|
||||
).toMatchObject([{ version: 3 }]);
|
||||
|
||||
// (4) noop update
|
||||
await client.assistants.update(assistant.assistant_id, {});
|
||||
|
||||
expect(
|
||||
await client.assistants.getVersions(assistant.assistant_id)
|
||||
await client.assistants.getVersions(assistant.assistant_id),
|
||||
).toMatchObject([
|
||||
{ version: 4 },
|
||||
{ version: 3 },
|
||||
@@ -246,7 +246,7 @@ describe("assistants", () => {
|
||||
|
||||
await client.assistants.delete(assistant.assistant_id);
|
||||
expect(
|
||||
await client.assistants.getVersions(assistant.assistant_id)
|
||||
await client.assistants.getVersions(assistant.assistant_id),
|
||||
).toMatchObject([]);
|
||||
});
|
||||
|
||||
@@ -260,12 +260,12 @@ describe("assistants", () => {
|
||||
|
||||
const updatedAgain = await client.assistants.update(
|
||||
created.assistant_id,
|
||||
{}
|
||||
{},
|
||||
);
|
||||
|
||||
expect(updatedAgain.version).toBe(3);
|
||||
await expect(
|
||||
client.assistants.setLatest(created.assistant_id, 4)
|
||||
client.assistants.setLatest(created.assistant_id, 4),
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
@@ -388,7 +388,7 @@ describe("threads copy", () => {
|
||||
|
||||
const copiedThread = await client.threads.copy(thread.thread_id);
|
||||
const copiedThreadState = await client.threads.getState(
|
||||
copiedThread.thread_id
|
||||
copiedThread.thread_id,
|
||||
);
|
||||
|
||||
// check copied thread state matches expected output
|
||||
@@ -414,7 +414,7 @@ describe("threads copy", () => {
|
||||
// For in-memory connections, check the thread history
|
||||
const originalHistory = await client.threads.getHistory(thread.thread_id);
|
||||
const copiedHistory = await client.threads.getHistory(
|
||||
copiedThread.thread_id
|
||||
copiedThread.thread_id,
|
||||
);
|
||||
|
||||
expect(originalHistory.length).toBe(copiedHistory.length);
|
||||
@@ -443,7 +443,7 @@ describe("threads copy", () => {
|
||||
} else {
|
||||
const sql = postgres(
|
||||
process.env.POSTGRES_URI ??
|
||||
"postgres://postgres:postgres@127.0.0.1:5433/postgres?sslmode=disable"
|
||||
"postgres://postgres:postgres@127.0.0.1:5433/postgres?sslmode=disable",
|
||||
);
|
||||
|
||||
// check checkpoints in DB
|
||||
@@ -504,11 +504,11 @@ describe("threads copy", () => {
|
||||
|
||||
// test that copied thread has original as well as new values
|
||||
const copiedThreadState = await client.threads.getState<AgentState>(
|
||||
copiedThread.thread_id
|
||||
copiedThread.thread_id,
|
||||
);
|
||||
|
||||
const copiedThreadStateMessages = copiedThreadState.values.messages.map(
|
||||
(m) => m.content
|
||||
(m) => m.content,
|
||||
);
|
||||
expect(copiedThreadStateMessages).toEqual([
|
||||
// original messages
|
||||
@@ -525,7 +525,7 @@ describe("threads copy", () => {
|
||||
|
||||
// test that the new run on the copied thread doesn't affect the original one
|
||||
const currentOriginalThreadState = await client.threads.getState(
|
||||
thread.thread_id
|
||||
thread.thread_id,
|
||||
);
|
||||
expect(currentOriginalThreadState).toEqual(originalThreadState);
|
||||
});
|
||||
@@ -544,7 +544,7 @@ describe("threads copy", () => {
|
||||
});
|
||||
|
||||
const history = await client.threads.getHistory<AgentState>(
|
||||
thread.thread_id
|
||||
thread.thread_id,
|
||||
);
|
||||
expect(history.length).toBe(5);
|
||||
expect(history[0].values.messages.length).toBe(4);
|
||||
@@ -560,11 +560,11 @@ describe("threads copy", () => {
|
||||
});
|
||||
|
||||
const fullHistory = await client.threads.getHistory<AgentState>(
|
||||
thread.thread_id
|
||||
thread.thread_id,
|
||||
);
|
||||
const filteredHistory = await client.threads.getHistory<AgentState>(
|
||||
thread.thread_id,
|
||||
{ metadata: runMetadata }
|
||||
{ metadata: runMetadata },
|
||||
);
|
||||
|
||||
expect(fullHistory.length).toBe(10);
|
||||
@@ -595,13 +595,13 @@ describe("threads copy", () => {
|
||||
});
|
||||
|
||||
const copiedThreadState = await client.threads.getState<AgentState>(
|
||||
copyThread.thread_id
|
||||
copyThread.thread_id,
|
||||
);
|
||||
expect(copiedThreadState.values.messages[0].content).toBe("bar");
|
||||
|
||||
// test that updating the copied thread doesn't affect the original one
|
||||
const currentOriginalThreadState = await client.threads.getState(
|
||||
thread.thread_id
|
||||
thread.thread_id,
|
||||
);
|
||||
expect(currentOriginalThreadState).toEqual(originalState);
|
||||
});
|
||||
@@ -625,7 +625,7 @@ describe("runs", () => {
|
||||
input: { messages: [{ type: "human", content: "bar" }] },
|
||||
config: globalConfig,
|
||||
afterSeconds: 10,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let runs = await client.runs.list(thread.thread_id);
|
||||
@@ -649,7 +649,7 @@ describe("runs", () => {
|
||||
const stream = client.runs.stream(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, streamMode: "values", config: globalConfig }
|
||||
{ input, streamMode: "values", config: globalConfig },
|
||||
);
|
||||
|
||||
let runId: string | null = null;
|
||||
@@ -666,7 +666,7 @@ describe("runs", () => {
|
||||
|
||||
if (chunk.event === "values") {
|
||||
const messageIds = chunk.data.messages.map(
|
||||
(message: { id: string }) => message.id
|
||||
(message: { id: string }) => message.id,
|
||||
);
|
||||
expect(messageIds.slice(0, -1)).toEqual(previousMessageIds);
|
||||
previousMessageIds = messageIds;
|
||||
@@ -686,7 +686,7 @@ describe("runs", () => {
|
||||
} else {
|
||||
const sql = postgres(
|
||||
process.env.POSTGRES_URI ??
|
||||
"postgres://postgres:postgres@127.0.0.1:5433/postgres?sslmode=disable"
|
||||
"postgres://postgres:postgres@127.0.0.1:5433/postgres?sslmode=disable",
|
||||
);
|
||||
|
||||
let cur = await sql`SELECT * FROM checkpoints WHERE run_id is null`;
|
||||
@@ -709,7 +709,7 @@ describe("runs", () => {
|
||||
client.runs.wait(thread.thread_id, assistant.assistant_id, {
|
||||
input,
|
||||
config: { ...globalConfig, recursion_limit: 1 },
|
||||
})
|
||||
}),
|
||||
).rejects.toThrowError(/GraphRecursionError/);
|
||||
const threadUpdated = await client.threads.get(thread.thread_id);
|
||||
expect(threadUpdated.status).toBe("error");
|
||||
@@ -724,7 +724,7 @@ describe("runs", () => {
|
||||
const values = await client.runs.wait(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config: globalConfig }
|
||||
{ input, config: globalConfig },
|
||||
);
|
||||
|
||||
expect(Array.isArray((values as any).messages)).toBe(true);
|
||||
@@ -741,7 +741,7 @@ describe("runs", () => {
|
||||
const stream = client.runs.stream(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, streamMode: "updates", config: globalConfig }
|
||||
{ input, streamMode: "updates", config: globalConfig },
|
||||
);
|
||||
|
||||
let runId: string | null = null;
|
||||
@@ -781,18 +781,18 @@ describe("runs", () => {
|
||||
const stream = client.runs.stream(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, streamMode: "events", config: globalConfig }
|
||||
{ input, streamMode: "events", config: globalConfig },
|
||||
);
|
||||
|
||||
const events = await gatherIterator(stream);
|
||||
expect(new Set(events.map((i) => i.event))).toEqual(
|
||||
new Set(["metadata", "events"])
|
||||
new Set(["metadata", "events"]),
|
||||
);
|
||||
|
||||
expect(
|
||||
new Set(
|
||||
events.filter((i) => i.event === "events").map((i) => i.data.event)
|
||||
)
|
||||
events.filter((i) => i.event === "events").map((i) => i.data.event),
|
||||
),
|
||||
).toEqual(
|
||||
new Set([
|
||||
"on_chain_start",
|
||||
@@ -800,7 +800,7 @@ describe("runs", () => {
|
||||
"on_chat_model_end",
|
||||
"on_chat_model_start",
|
||||
"on_chat_model_stream",
|
||||
])
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -813,7 +813,7 @@ describe("runs", () => {
|
||||
const stream = client.runs.stream(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, streamMode: "messages", config: globalConfig }
|
||||
{ input, streamMode: "messages", config: globalConfig },
|
||||
);
|
||||
|
||||
let runId: string | null = null;
|
||||
@@ -854,7 +854,7 @@ describe("runs", () => {
|
||||
"messages/metadata",
|
||||
"messages/partial",
|
||||
"messages/complete",
|
||||
])
|
||||
]),
|
||||
);
|
||||
|
||||
expect(runId).not.toBeNull();
|
||||
@@ -871,7 +871,7 @@ describe("runs", () => {
|
||||
const stream = await client.runs.stream(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, streamMode: "messages-tuple", config: globalConfig }
|
||||
{ input, streamMode: "messages-tuple", config: globalConfig },
|
||||
);
|
||||
|
||||
const chunks = await gatherIterator(stream);
|
||||
@@ -905,7 +905,7 @@ describe("runs", () => {
|
||||
const stream = await client.runs.stream(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, streamMode: ["messages", "values"], config: globalConfig }
|
||||
{ input, streamMode: ["messages", "values"], config: globalConfig },
|
||||
);
|
||||
|
||||
const chunks = await gatherIterator(stream);
|
||||
@@ -914,7 +914,7 @@ describe("runs", () => {
|
||||
|
||||
const messages: BaseMessage[] = findLast(
|
||||
chunks,
|
||||
(i) => i.event === "values"
|
||||
(i) => i.event === "values",
|
||||
)?.data.messages;
|
||||
|
||||
expect(messages.length).toBe(4);
|
||||
@@ -931,7 +931,7 @@ describe("runs", () => {
|
||||
"messages/partial",
|
||||
"messages/complete",
|
||||
"values",
|
||||
])
|
||||
]),
|
||||
);
|
||||
|
||||
const run = await client.runs.get(thread.thread_id, runId);
|
||||
@@ -956,7 +956,7 @@ describe("runs", () => {
|
||||
input,
|
||||
interruptBefore: ["tool"],
|
||||
config: globalConfig,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(chunks.filter((i) => i.event === "error").length).toBe(0);
|
||||
@@ -976,7 +976,7 @@ describe("runs", () => {
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
||||
input: null,
|
||||
config: globalConfig,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(chunks.filter((i) => i.event === "error").length).toBe(0);
|
||||
@@ -988,7 +988,7 @@ describe("runs", () => {
|
||||
|
||||
const threadAfterContinue = await client.threads.get(thread.thread_id);
|
||||
expect(threadAfterContinue.status).toBe("idle");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.concurrent("human in the loop - modification", async () => {
|
||||
@@ -1006,7 +1006,7 @@ describe("runs", () => {
|
||||
input,
|
||||
interruptBefore: ["tool"],
|
||||
config: globalConfig,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(chunks.filter((i) => i.event === "error").length).toBe(0);
|
||||
@@ -1014,7 +1014,7 @@ describe("runs", () => {
|
||||
// edit the last message
|
||||
const lastMessage = findLast(
|
||||
chunks,
|
||||
(i) => i.event === "values"
|
||||
(i) => i.event === "values",
|
||||
)?.data.messages.at(-1);
|
||||
lastMessage.content = "modified";
|
||||
|
||||
@@ -1031,7 +1031,7 @@ describe("runs", () => {
|
||||
expect(modifiedThread.metadata?.modified).toBe(true);
|
||||
|
||||
const stateAfterModify = await client.threads.getState<AgentState>(
|
||||
thread.thread_id
|
||||
thread.thread_id,
|
||||
);
|
||||
expect(stateAfterModify.values.messages.at(-1)?.content).toBe("modified");
|
||||
expect(stateAfterModify.next).toEqual(["tool"]);
|
||||
@@ -1044,7 +1044,7 @@ describe("runs", () => {
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
||||
input: null,
|
||||
config: globalConfig,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const threadAfterContinue = await client.threads.get(thread.thread_id);
|
||||
@@ -1059,7 +1059,7 @@ describe("runs", () => {
|
||||
|
||||
// get the history
|
||||
const history = await client.threads.getHistory<AgentState>(
|
||||
thread.thread_id
|
||||
thread.thread_id,
|
||||
);
|
||||
expect(history.length).toBe(6);
|
||||
expect(history[0].next.length).toBe(0);
|
||||
@@ -1091,13 +1091,13 @@ describe("runs", () => {
|
||||
};
|
||||
|
||||
await expect(
|
||||
client.runs.wait(thread.thread_id, "non-existent", { input })
|
||||
client.runs.wait(thread.thread_id, "non-existent", { input }),
|
||||
).rejects.toThrow(/No assistant found for/);
|
||||
|
||||
await expect(
|
||||
gatherIterator(
|
||||
client.runs.stream(thread.thread_id, "non-existent", { input })
|
||||
)
|
||||
client.runs.stream(thread.thread_id, "non-existent", { input }),
|
||||
),
|
||||
).rejects.toThrow(/No assistant found for/);
|
||||
});
|
||||
});
|
||||
@@ -1120,7 +1120,7 @@ describe("shared state", () => {
|
||||
const res1 = (await client.runs.wait(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config }
|
||||
{ input, config },
|
||||
)) as Awaited<Record<string, any>>;
|
||||
expect(res1.sharedStateValue).toBe(null);
|
||||
|
||||
@@ -1128,7 +1128,7 @@ describe("shared state", () => {
|
||||
const res2 = (await client.runs.wait(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config }
|
||||
{ input, config },
|
||||
)) as Awaited<Record<string, any>>;
|
||||
expect(res2.sharedStateValue).toBe(config.configurable.user_id);
|
||||
});
|
||||
@@ -1146,7 +1146,7 @@ describe("shared state", () => {
|
||||
const res1 = (await client.runs.wait(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config: config1 }
|
||||
{ input, config: config1 },
|
||||
)) as Awaited<Record<string, any>>;
|
||||
|
||||
// Run with the same thread id but a new config
|
||||
@@ -1154,7 +1154,7 @@ describe("shared state", () => {
|
||||
const res2 = (await client.runs.wait(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config: config2 }
|
||||
{ input, config: config2 },
|
||||
)) as Awaited<Record<string, any>>;
|
||||
|
||||
expect(res1.sharedStateValue).toBe(config1.configurable.user_id);
|
||||
@@ -1180,12 +1180,12 @@ describe("shared state", () => {
|
||||
const res1 = (await client.runs.wait(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config }
|
||||
{ input, config },
|
||||
)) as Awaited<Record<string, any>>;
|
||||
expect(res1.sharedStateFromStoreConfig).toBeDefined();
|
||||
expect(res1.sharedStateFromStoreConfig.id).toBeDefined();
|
||||
expect(res1.sharedStateFromStoreConfig.id).toBe(
|
||||
config.configurable.user_id
|
||||
config.configurable.user_id,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1210,12 +1210,12 @@ describe("shared state", () => {
|
||||
const res1 = (await client.runs.wait(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config }
|
||||
{ input, config },
|
||||
)) as Awaited<Record<string, any>>;
|
||||
expect(res1.sharedStateFromStoreConfig).toBeDefined();
|
||||
expect(res1.sharedStateFromStoreConfig.id).toBeDefined();
|
||||
expect(res1.sharedStateFromStoreConfig.id).toBe(
|
||||
config.configurable.user_id
|
||||
config.configurable.user_id,
|
||||
);
|
||||
|
||||
// Fetch data from store client
|
||||
@@ -1305,10 +1305,10 @@ describe("StoreClient", () => {
|
||||
expect(searchResAfterPut.items[0].createdAt).toBeDefined();
|
||||
expect(searchResAfterPut.items[0].updatedAt).toBeDefined();
|
||||
expect(
|
||||
new Date(searchResAfterPut.items[0].createdAt).getTime()
|
||||
new Date(searchResAfterPut.items[0].createdAt).getTime(),
|
||||
).toBeLessThanOrEqual(Date.now());
|
||||
expect(
|
||||
new Date(searchResAfterPut.items[0].updatedAt).getTime()
|
||||
new Date(searchResAfterPut.items[0].updatedAt).getTime(),
|
||||
).toBeLessThanOrEqual(Date.now());
|
||||
|
||||
const updatedValue = { foo: "baz" };
|
||||
@@ -1325,7 +1325,7 @@ describe("StoreClient", () => {
|
||||
expect(searchResAfterUpdate.items[0].value).toEqual(updatedValue);
|
||||
|
||||
expect(
|
||||
new Date(searchResAfterUpdate.items[0].updatedAt).getTime()
|
||||
new Date(searchResAfterUpdate.items[0].updatedAt).getTime(),
|
||||
).toBeGreaterThan(new Date(searchResAfterPut.items[0].updatedAt).getTime());
|
||||
|
||||
const listResAfterPut = await client.store.listNamespaces();
|
||||
@@ -1349,12 +1349,12 @@ describe("subgraphs", () => {
|
||||
const assistant = await client.assistants.create({ graphId: "nested" });
|
||||
|
||||
expect(
|
||||
Object.keys(await client.assistants.getSubgraphs(assistant.assistant_id))
|
||||
Object.keys(await client.assistants.getSubgraphs(assistant.assistant_id)),
|
||||
).toEqual(["gp_two"]);
|
||||
|
||||
const subgraphs = await client.assistants.getSubgraphs(
|
||||
assistant.assistant_id,
|
||||
{ recurse: true }
|
||||
{ recurse: true },
|
||||
);
|
||||
|
||||
expect(Object.keys(subgraphs)).toEqual(["gp_two", "gp_two|p_two"]);
|
||||
@@ -1399,7 +1399,7 @@ describe("subgraphs", () => {
|
||||
messages: [{ role: "human", content: "SF", id: "initial-message" }],
|
||||
},
|
||||
interruptBefore: ["tool"],
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
@@ -1468,7 +1468,7 @@ describe("subgraphs", () => {
|
||||
const stateRecursive = await client.threads.getState(
|
||||
thread.thread_id,
|
||||
undefined,
|
||||
{ subgraphs: true }
|
||||
{ subgraphs: true },
|
||||
);
|
||||
|
||||
expect(stateRecursive.next).toEqual(["weather_graph"]);
|
||||
@@ -1527,7 +1527,7 @@ describe("subgraphs", () => {
|
||||
input: null,
|
||||
streamMode: ["values", "updates"],
|
||||
streamSubgraphs: true,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(chunks.filter((i) => i.event === "error")).toEqual([]);
|
||||
@@ -1681,7 +1681,7 @@ describe("subgraphs", () => {
|
||||
|
||||
// run until the interrupt (same as before)
|
||||
let chunks = await gatherIterator(
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, { input })
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, { input }),
|
||||
);
|
||||
expect(chunks.filter((i) => i.event === "error")).toEqual([]);
|
||||
|
||||
@@ -1713,7 +1713,7 @@ describe("subgraphs", () => {
|
||||
// get inner state after update
|
||||
const innerState = await client.threads.getState<{ city: string }>(
|
||||
thread.thread_id,
|
||||
state.tasks[0].checkpoint ?? undefined
|
||||
state.tasks[0].checkpoint ?? undefined,
|
||||
);
|
||||
|
||||
expect(innerState.values.city).toBe("LA");
|
||||
@@ -1735,7 +1735,7 @@ describe("subgraphs", () => {
|
||||
chunks = await gatherIterator(
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
||||
input: null,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(chunks.filter((i) => i.event === "error")).toEqual([]);
|
||||
@@ -1820,7 +1820,7 @@ describe("subgraphs", () => {
|
||||
const stream = await gatherIterator(
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
||||
command: { resume: "i want to resume" },
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(stream.at(-1)?.event).toBe("values");
|
||||
@@ -1837,7 +1837,7 @@ describe("errors", () => {
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
||||
input: { messages: [] },
|
||||
streamMode: ["debug", "events"],
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
expect(stream.at(-1)).toMatchObject({
|
||||
@@ -1856,7 +1856,7 @@ describe("errors", () => {
|
||||
const run = await client.runs.create(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input: { messages: [] } }
|
||||
{ input: { messages: [] } },
|
||||
);
|
||||
|
||||
await client.runs.join(thread.thread_id, run.run_id);
|
||||
@@ -1871,11 +1871,11 @@ describe("errors", () => {
|
||||
const run = await client.runs.create(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input: { messages: [] } }
|
||||
{ input: { messages: [] } },
|
||||
);
|
||||
|
||||
const stream = await gatherIterator(
|
||||
client.runs.joinStream(thread.thread_id, run.run_id)
|
||||
client.runs.joinStream(thread.thread_id, run.run_id),
|
||||
);
|
||||
|
||||
expect(stream.at(-1)).toMatchObject({
|
||||
@@ -1905,7 +1905,7 @@ describe("long running tasks", () => {
|
||||
{
|
||||
input: { messages: [], delay },
|
||||
config: globalConfig,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await client.runs.join(thread.thread_id, run.run_id);
|
||||
@@ -1921,7 +1921,7 @@ describe("long running tasks", () => {
|
||||
expect(runResult.values.messages).toMatchObject([
|
||||
{ content: `finished after ${delay}ms` },
|
||||
]);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1947,7 +1947,7 @@ describe("command update state", () => {
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
||||
command: { update: { keyOne: "value3", keyTwo: "value4" } },
|
||||
config: globalConfig,
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(stream.filter((chunk) => chunk.event === "error")).toEqual([]);
|
||||
|
||||
@@ -1982,7 +1982,7 @@ describe("command update state", () => {
|
||||
],
|
||||
},
|
||||
config: globalConfig,
|
||||
})
|
||||
}),
|
||||
);
|
||||
expect(stream.filter((chunk) => chunk.event === "error")).toEqual([]);
|
||||
|
||||
@@ -2005,7 +2005,7 @@ it("stream debug checkpoint", async () => {
|
||||
{
|
||||
input,
|
||||
streamMode: "debug",
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const stream = [];
|
||||
@@ -2024,13 +2024,13 @@ it("stream debug checkpoint", async () => {
|
||||
step: i.metadata?.step,
|
||||
checkpoint: i.checkpoint,
|
||||
parent_checkpoint: i.parent_checkpoint,
|
||||
}))
|
||||
})),
|
||||
).toEqual(
|
||||
history.map((i) => ({
|
||||
step: i.metadata?.step,
|
||||
checkpoint: i.checkpoint,
|
||||
parent_checkpoint: i.parent_checkpoint,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2047,7 +2047,7 @@ it("continue after interrupt must have checkpoint present", async () => {
|
||||
input,
|
||||
streamMode: "debug",
|
||||
interruptBefore: ["router_node"],
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const initialStream = stream
|
||||
@@ -2062,7 +2062,7 @@ it("continue after interrupt must have checkpoint present", async () => {
|
||||
client.runs.stream(thread.thread_id, assistant.assistant_id, {
|
||||
streamMode: "debug",
|
||||
checkpoint,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const continueHistory = (
|
||||
@@ -2078,13 +2078,13 @@ it("continue after interrupt must have checkpoint present", async () => {
|
||||
step: i.metadata?.step,
|
||||
checkpoint: i.checkpoint,
|
||||
parent_checkpoint: i.parent_checkpoint,
|
||||
}))
|
||||
})),
|
||||
).toEqual(
|
||||
continueHistory.map((i) => ({
|
||||
step: i.metadata?.step,
|
||||
checkpoint: i.checkpoint,
|
||||
parent_checkpoint: i.parent_checkpoint,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2093,7 +2093,7 @@ describe("multitasking", () => {
|
||||
const pollRun = async (
|
||||
threadId: string,
|
||||
runId: string,
|
||||
maxIter: number = 600
|
||||
maxIter: number = 600,
|
||||
) => {
|
||||
let lastStatus:
|
||||
| Awaited<ReturnType<typeof client.runs.get>>["status"]
|
||||
@@ -2128,7 +2128,7 @@ describe("multitasking", () => {
|
||||
const run = await client.runs.create(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input, config: globalConfig }
|
||||
{ input, config: globalConfig },
|
||||
);
|
||||
|
||||
// Attempt another run that should be rejected
|
||||
@@ -2137,7 +2137,7 @@ describe("multitasking", () => {
|
||||
input,
|
||||
multitaskStrategy: "reject",
|
||||
config: globalConfig,
|
||||
})
|
||||
}),
|
||||
).rejects.toThrow();
|
||||
|
||||
const runStatus = await pollRun(thread.thread_id, run.run_id);
|
||||
@@ -2156,7 +2156,7 @@ describe("multitasking", () => {
|
||||
const run1 = await client.runs.create(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input: input1, config: globalConfig }
|
||||
{ input: input1, config: globalConfig },
|
||||
);
|
||||
|
||||
// Start second run that should interrupt first
|
||||
@@ -2171,7 +2171,7 @@ describe("multitasking", () => {
|
||||
input: input2,
|
||||
multitaskStrategy: "interrupt",
|
||||
config: globalConfig,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const run1Status = await pollRun(thread.thread_id, run1.run_id);
|
||||
@@ -2204,7 +2204,7 @@ describe("multitasking", () => {
|
||||
const run1 = await client.runs.create(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input: input1, config: globalConfig }
|
||||
{ input: input1, config: globalConfig },
|
||||
);
|
||||
|
||||
// Start second run that should rollback first
|
||||
@@ -2214,12 +2214,12 @@ describe("multitasking", () => {
|
||||
const run2 = await client.runs.create(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input: input2, multitaskStrategy: "rollback", config: globalConfig }
|
||||
{ input: input2, multitaskStrategy: "rollback", config: globalConfig },
|
||||
);
|
||||
|
||||
// First run should be deleted
|
||||
await expect(() =>
|
||||
pollRun(thread.thread_id, run1.run_id)
|
||||
pollRun(thread.thread_id, run1.run_id),
|
||||
).rejects.toThrow();
|
||||
|
||||
const run2Status = await pollRun(thread.thread_id, run2.run_id);
|
||||
@@ -2242,7 +2242,7 @@ describe("multitasking", () => {
|
||||
const run1 = await client.runs.create(
|
||||
thread.thread_id,
|
||||
assistant.assistant_id,
|
||||
{ input: input1, config: globalConfig }
|
||||
{ input: input1, config: globalConfig },
|
||||
);
|
||||
|
||||
// Start second run that should be enqueued
|
||||
@@ -2257,7 +2257,7 @@ describe("multitasking", () => {
|
||||
input: input2,
|
||||
multitaskStrategy: "enqueue",
|
||||
config: globalConfig,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const run1Status = await pollRun(thread.thread_id, run1.run_id);
|
||||
@@ -2283,7 +2283,7 @@ describe("RemoteGraph", () => {
|
||||
});
|
||||
const stream = await graph.stream(
|
||||
{ messages: [{ type: "human", content: "foo", id: "initial-message" }] },
|
||||
{ streamMode: "values", ...globalConfig }
|
||||
{ streamMode: "values", ...globalConfig },
|
||||
);
|
||||
|
||||
const chunks = await gatherIterator(stream);
|
||||
|
||||
@@ -37,7 +37,7 @@ class StableFakeListChatModel extends FakeListChatModel {
|
||||
async *_streamResponseChunks(
|
||||
_messages: BaseMessage[],
|
||||
options: this["ParsedCallOptions"],
|
||||
runManager?: CallbackManagerForLLMRun
|
||||
runManager?: CallbackManagerForLLMRun,
|
||||
): AsyncGenerator<ChatGenerationChunk> {
|
||||
const response = this._currentResponse();
|
||||
this._incrementResponse();
|
||||
@@ -68,7 +68,7 @@ class StableFakeListChatModel extends FakeListChatModel {
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{ chunk }
|
||||
{ chunk },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@ const getModel = (threadId: string) => {
|
||||
|
||||
const agentNode = async (
|
||||
state: typeof GraphAnnotationInput.State,
|
||||
config: LangGraphRunnableConfig
|
||||
config: LangGraphRunnableConfig,
|
||||
) => {
|
||||
if (state.interrupt) interrupt("i want to interrupt");
|
||||
|
||||
@@ -120,7 +120,7 @@ const agentNode = async (
|
||||
|
||||
const toolNode = async (
|
||||
state: typeof GraphAnnotationInput.State,
|
||||
config: LangGraphRunnableConfig
|
||||
config: LangGraphRunnableConfig,
|
||||
) => {
|
||||
const store = config.store;
|
||||
let sharedStateFromStoreConfig: Record<string, any> | null = null;
|
||||
@@ -144,7 +144,7 @@ const toolNode = async (
|
||||
|
||||
const checkSharedStateNode = async (
|
||||
_: typeof GraphAnnotationInput.State,
|
||||
config: LangGraphRunnableConfig
|
||||
config: LangGraphRunnableConfig,
|
||||
): Promise<Partial<typeof GraphAnnotationInput.State>> => {
|
||||
const store = config.store;
|
||||
const namespace = ["inputtedState", "data"];
|
||||
@@ -174,7 +174,7 @@ const workflow = new StateGraph(
|
||||
input: GraphAnnotationInput,
|
||||
output: GraphAnnotationOutput,
|
||||
},
|
||||
Annotation.Root({ model_name: Annotation<string> })
|
||||
Annotation.Root({ model_name: Annotation<string> }),
|
||||
)
|
||||
.addNode("agent", agentNode)
|
||||
.addNode("tool", toolNode)
|
||||
|
||||
@@ -12,7 +12,7 @@ const StateSchema = Annotation.Root({
|
||||
});
|
||||
|
||||
const longRunning = async (
|
||||
state: typeof StateSchema.State
|
||||
state: typeof StateSchema.State,
|
||||
): Promise<typeof StateSchema.Update> => {
|
||||
await new Promise((resolve) => setTimeout(resolve, state.delay));
|
||||
return { messages: [`finished after ${state.delay}ms`] };
|
||||
|
||||
@@ -6,7 +6,7 @@ const child = new StateGraph(
|
||||
reducer: (a, b) => a.concat(b),
|
||||
}),
|
||||
child: Annotation<"child_one" | "child_two">,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.addNode("c_one", () => ({ messages: ["Entered c_one node"] }))
|
||||
.addNode("c_two", () => ({ messages: ["Entered c_two node"] }))
|
||||
@@ -20,7 +20,7 @@ const parent = new StateGraph(
|
||||
reducer: (a, b) => a.concat(b),
|
||||
}),
|
||||
parent: Annotation<"parent_one" | "parent_two">,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.addNode("p_one", () => ({ messages: ["Entered p_one node"] }))
|
||||
.addNode("p_two", child.compile())
|
||||
@@ -33,7 +33,7 @@ const grandParent = new StateGraph(
|
||||
messages: Annotation<string[]>({
|
||||
reducer: (a, b) => a.concat(b),
|
||||
}),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.addNode("gp_one", () => ({ messages: ["Entered gp_one node"] }))
|
||||
.addNode("gp_two", parent.compile())
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
"@langchain/core": "^0.3.22",
|
||||
"@langchain/langgraph": "^0.2.31"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ const router = new StateGraph(routerState)
|
||||
if (route === "weather") return "weather_graph";
|
||||
return "normal_llm_node";
|
||||
},
|
||||
["weather_graph", "normal_llm_node"]
|
||||
["weather_graph", "normal_llm_node"],
|
||||
)
|
||||
.addEdge("weather_graph", END)
|
||||
.addEdge("normal_llm_node", END);
|
||||
|
||||
@@ -56,7 +56,7 @@ describe.concurrent("graph factories", () => {
|
||||
])("%s", ([prop]) => {
|
||||
const schemas = SubgraphExtractor.extractSchemas(
|
||||
{ contents: `${common}\n\nexport const graph = ${prop};` },
|
||||
"graph"
|
||||
"graph",
|
||||
);
|
||||
|
||||
expect(schemas.graph.input).toMatchObject(MessagesSchema);
|
||||
@@ -107,7 +107,7 @@ describe.concurrent("subgraphs", () => {
|
||||
.compile();
|
||||
`,
|
||||
},
|
||||
"graph"
|
||||
"graph",
|
||||
);
|
||||
expect(schemas["graph|child"].input).toMatchObject({
|
||||
type: "object",
|
||||
@@ -226,7 +226,7 @@ describe.concurrent("subgraphs", () => {
|
||||
.compile();
|
||||
`,
|
||||
},
|
||||
"parent"
|
||||
"parent",
|
||||
);
|
||||
|
||||
expect(Object.keys(schemas)).toEqual(
|
||||
@@ -234,7 +234,7 @@ describe.concurrent("subgraphs", () => {
|
||||
"parent",
|
||||
"parent|parent_two",
|
||||
"parent|parent_two|child_two",
|
||||
])
|
||||
]),
|
||||
);
|
||||
|
||||
expect(schemas.parent.state).toMatchObject({
|
||||
@@ -352,10 +352,10 @@ describe.concurrent("subgraphs", () => {
|
||||
`,
|
||||
},
|
||||
"parent",
|
||||
{ strict: true }
|
||||
{ strict: true },
|
||||
);
|
||||
}).toThrowError(
|
||||
`Multiple unique subgraph invocations found for "parent|parent_one"`
|
||||
`Multiple unique subgraph invocations found for "parent|parent_one"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -414,11 +414,11 @@ describe.concurrent("subgraphs", () => {
|
||||
],
|
||||
],
|
||||
},
|
||||
"graph"
|
||||
"graph",
|
||||
);
|
||||
|
||||
expect(Object.keys(schemas)).toEqual(
|
||||
expect.arrayContaining(["graph", "graph|child"])
|
||||
expect.arrayContaining(["graph", "graph|child"]),
|
||||
);
|
||||
|
||||
expect(schemas["graph|child"].input).toMatchObject({
|
||||
@@ -547,11 +547,11 @@ describe.concurrent("subgraphs", () => {
|
||||
],
|
||||
],
|
||||
},
|
||||
"graph"
|
||||
"graph",
|
||||
);
|
||||
|
||||
expect(Object.keys(schemas)).toEqual(
|
||||
expect.arrayContaining(["graph", "graph|child"])
|
||||
expect.arrayContaining(["graph", "graph|child"]),
|
||||
);
|
||||
|
||||
expect(schemas["graph|child"].input).toMatchObject({
|
||||
@@ -665,7 +665,7 @@ describe.concurrent("subgraphs", () => {
|
||||
export const graph = parent.compile()
|
||||
`,
|
||||
},
|
||||
"graph"
|
||||
"graph",
|
||||
);
|
||||
expect(schemas["graph|child"].input).toMatchObject({
|
||||
type: "object",
|
||||
@@ -803,11 +803,11 @@ test.concurrent("weather", () => {
|
||||
export const graph = router.compile();
|
||||
`,
|
||||
},
|
||||
"graph"
|
||||
"graph",
|
||||
);
|
||||
|
||||
expect(Object.keys(schemas)).toEqual(
|
||||
expect.arrayContaining(["graph", "graph|weather_graph"])
|
||||
expect.arrayContaining(["graph", "graph|weather_graph"]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -861,10 +861,10 @@ test.concurrent("nested", () => {
|
||||
export const graph = grandParent.compile();
|
||||
`,
|
||||
},
|
||||
"graph"
|
||||
"graph",
|
||||
);
|
||||
|
||||
expect(Object.keys(schemas)).toEqual(
|
||||
expect.arrayContaining(["graph", "graph|gp_two", "graph|gp_two|p_two"])
|
||||
expect.arrayContaining(["graph", "graph|gp_two", "graph|gp_two|p_two"]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { readFile } from "node:fs/promises";
|
||||
import { dirname } from "node:path";
|
||||
|
||||
const configPath = fileURLToPath(
|
||||
new URL("./graphs/langgraph.json", import.meta.url)
|
||||
new URL("./graphs/langgraph.json", import.meta.url),
|
||||
);
|
||||
const config = JSON.parse(await readFile(configPath, "utf-8"));
|
||||
|
||||
@@ -20,5 +20,5 @@ await spawnServer(
|
||||
env: config.env,
|
||||
hostUrl: "https://smith.langchain.com",
|
||||
},
|
||||
{ pid: process.pid, projectCwd: dirname(configPath) }
|
||||
{ pid: process.pid, projectCwd: dirname(configPath) },
|
||||
);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -51,18 +51,18 @@ The CLI uses a `langgraph.json` configuration file with these key settings:
|
||||
```json5
|
||||
{
|
||||
// Required: Graph definitions
|
||||
"graphs": {
|
||||
"graph": "./src/graph.ts:graph"
|
||||
graphs: {
|
||||
graph: "./src/graph.ts:graph",
|
||||
},
|
||||
|
||||
// Optional: Node version (20 only at the moment)
|
||||
"node_version": "20",
|
||||
node_version: "20",
|
||||
|
||||
// Optional: Environment variables
|
||||
"env": ".env",
|
||||
|
||||
env: ".env",
|
||||
|
||||
// Optional: Additional Dockerfile commands
|
||||
"dockerfile_lines": []
|
||||
dockerfile_lines: [],
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
"dist/"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "npx -y bun scripts/clean.mjs",
|
||||
"build": "npx -y bun scripts/build.mjs",
|
||||
"prepack": "pnpm run build",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"cli": "tsx src/cli/cli.mts",
|
||||
"cli:watch": "tsx watch src/cli/cli.mts",
|
||||
"test": "vitest"
|
||||
"test": "vitest",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@commander-js/extra-typings": "^13.0.0",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bun
|
||||
function $(strings, ...rest) {
|
||||
console.log("$", ...strings.raw);
|
||||
return Bun.$(strings, ...rest);
|
||||
}
|
||||
|
||||
await $`rm -rf dist`;
|
||||
@@ -26,7 +26,7 @@ builder
|
||||
.option("-c, --config <path>", "Path to configuration file", process.cwd())
|
||||
.option(
|
||||
"--no-pull",
|
||||
"Running the server with locally-built images. By default LangGraph will pull the latest images from the registry"
|
||||
"Running the server with locally-built images. By default LangGraph will pull the latest images from the registry",
|
||||
)
|
||||
.argument("[args...]")
|
||||
.passThroughOptions()
|
||||
@@ -35,7 +35,7 @@ builder
|
||||
withAnalytics((command) => ({
|
||||
config: command.opts().config !== process.cwd(),
|
||||
pull: command.opts().pull,
|
||||
}))
|
||||
})),
|
||||
)
|
||||
.action(async (pass, params) => {
|
||||
const configPath = await getProjectPath(params.config);
|
||||
@@ -63,6 +63,6 @@ builder
|
||||
|
||||
exec = $({ ...opts, input });
|
||||
await stream(
|
||||
exec`docker build -f - -t ${params.tag} ${projectDir} ${pass}`
|
||||
exec`docker build -f - -t ${params.tag} ${projectDir} ${pass}`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import { withAnalytics } from "./utils/analytics.mjs";
|
||||
builder
|
||||
.command("dev")
|
||||
.description(
|
||||
"Run LangGraph API server in development mode with hot reloading."
|
||||
"Run LangGraph API server in development mode with hot reloading.",
|
||||
)
|
||||
.option("-p, --port <number>", "port to run the server on", "2024")
|
||||
.option("-h, --host <string>", "host to bind to", "localhost")
|
||||
@@ -33,7 +33,7 @@ builder
|
||||
port: command.opts().port !== "2024",
|
||||
host: command.opts().host !== "localhost",
|
||||
n_jobs_per_worker: command.opts().nJobsPerWorker !== "10",
|
||||
}))
|
||||
})),
|
||||
)
|
||||
.action(async (options, { args }) => {
|
||||
try {
|
||||
@@ -65,11 +65,11 @@ builder
|
||||
|
||||
if (!gitignoreContent.includes(".langgraph_api")) {
|
||||
logger.info(
|
||||
"Updating .gitignore to prevent `.langgraph_api` from being committed."
|
||||
"Updating .gitignore to prevent `.langgraph_api` from being committed.",
|
||||
);
|
||||
await fs.appendFile(
|
||||
gitignorePath,
|
||||
"\n# LangGraph API\n.langgraph_api\n"
|
||||
"\n# LangGraph API\n.langgraph_api\n",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,15 +96,15 @@ builder
|
||||
|
||||
const oldWatch = Object.entries(watcher.getWatched()).flatMap(
|
||||
([dir, files]) =>
|
||||
files.map((file) => path.resolve(projectCwd, dir, file))
|
||||
files.map((file) => path.resolve(projectCwd, dir, file)),
|
||||
);
|
||||
|
||||
const addedTarget = newWatch.filter(
|
||||
(target) => !oldWatch.includes(target)
|
||||
(target) => !oldWatch.includes(target),
|
||||
);
|
||||
|
||||
const removedTarget = oldWatch.filter(
|
||||
(target) => !newWatch.includes(target)
|
||||
(target) => !newWatch.includes(target),
|
||||
);
|
||||
|
||||
watcher.unwatch(removedTarget).add(addedTarget);
|
||||
@@ -130,21 +130,21 @@ builder
|
||||
|
||||
if ("python_version" in config) {
|
||||
logger.warn(
|
||||
"Launching Python server from @langchain/langgraph-cli is experimental. Please use the `langgraph-cli` package from PyPi instead."
|
||||
"Launching Python server from @langchain/langgraph-cli is experimental. Please use the `langgraph-cli` package from PyPi instead.",
|
||||
);
|
||||
|
||||
const { spawnPythonServer } = await import("./dev.python.mjs");
|
||||
child = await spawnPythonServer(
|
||||
{ ...options, rest: args },
|
||||
{ configPath, config, env, hostUrl },
|
||||
{ pid, projectCwd }
|
||||
{ pid, projectCwd },
|
||||
);
|
||||
} else {
|
||||
const { spawnServer } = await import("@langchain/langgraph-api");
|
||||
child = await spawnServer(
|
||||
options,
|
||||
{ config, env, hostUrl },
|
||||
{ pid, projectCwd }
|
||||
{ pid, projectCwd },
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@ function getDownloadUrl(info: UvBinaryInfo): string {
|
||||
async function downloadAndExtract(
|
||||
url: string,
|
||||
destPath: string,
|
||||
info: UvBinaryInfo
|
||||
info: UvBinaryInfo,
|
||||
): Promise<string> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok)
|
||||
@@ -100,7 +100,7 @@ async function downloadAndExtract(
|
||||
await tarExtract({ file: tempFilePath, cwd: tempDirPath });
|
||||
sourceBinaryPath = path.resolve(
|
||||
sourceBinaryPath,
|
||||
path.basename(tempFilePath).slice(0, ".tar.gz".length * -1)
|
||||
path.basename(tempFilePath).slice(0, ".tar.gz".length * -1),
|
||||
);
|
||||
}
|
||||
sourceBinaryPath = path.resolve(sourceBinaryPath, info.binaryName);
|
||||
@@ -151,7 +151,7 @@ export async function spawnPythonServer(
|
||||
options: {
|
||||
pid: number;
|
||||
projectCwd: string;
|
||||
}
|
||||
},
|
||||
) {
|
||||
const deps = await assembleLocalDeps(context.configPath, context.config);
|
||||
const requirements = deps.rebuildFiles.filter((i) => i.endsWith(".txt"));
|
||||
@@ -180,6 +180,6 @@ export async function spawnPythonServer(
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
env: context.env,
|
||||
cwd: options.projectCwd,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,12 +25,12 @@ const fileExists = async (path: string) => {
|
||||
builder
|
||||
.command("dockerfile")
|
||||
.description(
|
||||
"Generate a Dockerfile for the LangGraph API server, with Docker Compose options."
|
||||
"Generate a Dockerfile for the LangGraph API server, with Docker Compose options.",
|
||||
)
|
||||
.argument("<save-path>", "Path to save the Dockerfile")
|
||||
.option(
|
||||
"--add-docker-compose",
|
||||
"Add additional files for running the LangGraph API server with docker-compose. These files include a docker-compose.yml, .env file, and a .dockerignore file."
|
||||
"Add additional files for running the LangGraph API server with docker-compose. These files include a docker-compose.yml, .env file, and a .dockerignore file.",
|
||||
)
|
||||
.option("-c, --config <path>", "Path to configuration file", process.cwd())
|
||||
.hook(
|
||||
@@ -38,7 +38,7 @@ builder
|
||||
withAnalytics((command) => ({
|
||||
config: command.opts().config !== process.cwd(),
|
||||
add_docker_compose: !!command.opts().addDockerCompose,
|
||||
}))
|
||||
})),
|
||||
)
|
||||
.action(async (savePath, options) => {
|
||||
const configPath = await getProjectPath(options.config);
|
||||
@@ -67,7 +67,7 @@ builder
|
||||
|
||||
const composePath = path.resolve(
|
||||
path.dirname(targetPath),
|
||||
"docker-compose.yml"
|
||||
"docker-compose.yml",
|
||||
);
|
||||
|
||||
await fs.writeFile(composePath, compose);
|
||||
@@ -75,7 +75,7 @@ builder
|
||||
|
||||
const dockerignorePath = path.resolve(
|
||||
path.dirname(targetPath),
|
||||
".dockerignore"
|
||||
".dockerignore",
|
||||
);
|
||||
|
||||
if (!fileExists(dockerignorePath)) {
|
||||
@@ -124,7 +124,7 @@ builder
|
||||
*.test.js
|
||||
*.spec.js
|
||||
tests
|
||||
`
|
||||
`,
|
||||
);
|
||||
logger.info(`✅ Created: ${path.basename(dockerignorePath)}`);
|
||||
}
|
||||
@@ -139,7 +139,7 @@ builder
|
||||
# Or if you have a LangGraph Cloud license key, then uncomment the following line:
|
||||
# LANGGRAPH_CLOUD_LICENSE_KEY=your-license-key
|
||||
# Add any other environment variables go below...
|
||||
`
|
||||
`,
|
||||
);
|
||||
logger.info(`✅ Created: ${path.basename(envPath)}`);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ const waitForHealthcheck = async (port: number) => {
|
||||
while (Date.now() - now < 10_000) {
|
||||
const ok = await fetch(`http://localhost:${port}/ok`).then(
|
||||
(res) => res.ok,
|
||||
() => false
|
||||
() => false,
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
@@ -46,23 +46,23 @@ builder
|
||||
.option("-c, --config <path>", "Path to configuration file", process.cwd())
|
||||
.option(
|
||||
"-d, --docker-compose <path>",
|
||||
"Advanced: Path to docker-compose.yml file with additional services to launch"
|
||||
"Advanced: Path to docker-compose.yml file with additional services to launch",
|
||||
)
|
||||
.option("-p, --port <port>", "Port to run the server on", "8123")
|
||||
.option("--recreate", "Force recreate containers and volumes", false)
|
||||
.option(
|
||||
"--no-pull",
|
||||
"Running the server with locally-built images. By default LangGraph will pull the latest images from the registry"
|
||||
"Running the server with locally-built images. By default LangGraph will pull the latest images from the registry",
|
||||
)
|
||||
.option("--watch", "Restart on file changes", false)
|
||||
.option(
|
||||
"--wait",
|
||||
"Wait for services to start before returning. Implies --detach",
|
||||
false
|
||||
false,
|
||||
)
|
||||
.option(
|
||||
"--postgres-uri <uri>",
|
||||
"Postgres URI to use for the database. Defaults to launching a local database"
|
||||
"Postgres URI to use for the database. Defaults to launching a local database",
|
||||
)
|
||||
.hook(
|
||||
"preAction",
|
||||
@@ -75,7 +75,7 @@ builder
|
||||
pull: command.opts().pull,
|
||||
watch: command.opts().watch,
|
||||
wait: command.opts().wait,
|
||||
}))
|
||||
})),
|
||||
)
|
||||
.action(async (params) => {
|
||||
logger.info("Starting LangGraph API server...");
|
||||
@@ -83,7 +83,7 @@ builder
|
||||
dedent`
|
||||
For local dev, requires env var LANGSMITH_API_KEY with access to LangGraph Cloud closed beta.
|
||||
For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KEY.
|
||||
`
|
||||
`,
|
||||
);
|
||||
|
||||
const configPath = await getProjectPath(params.config);
|
||||
@@ -118,13 +118,13 @@ builder
|
||||
// remove dangling images
|
||||
logger.info(`Pruning dangling images...`);
|
||||
await stream(
|
||||
exec`docker image prune -f --filter ${`label=com.docker.compose.project=${name}`}`
|
||||
exec`docker image prune -f --filter ${`label=com.docker.compose.project=${name}`}`,
|
||||
);
|
||||
|
||||
// remove stale containers
|
||||
logger.info(`Pruning stale containers...`);
|
||||
await stream(
|
||||
exec`docker container prune -f --filter ${`label=com.docker.compose.project=${name}`}`
|
||||
exec`docker container prune -f --filter ${`label=com.docker.compose.project=${name}`}`,
|
||||
);
|
||||
|
||||
const input = createCompose(capabilities, {
|
||||
@@ -148,7 +148,7 @@ builder
|
||||
args.push("--watch");
|
||||
} else {
|
||||
logger.warn(
|
||||
"Watch mode is not available. Please upgrade your Docker Engine."
|
||||
"Watch mode is not available. Please upgrade your Docker Engine.",
|
||||
);
|
||||
}
|
||||
} else if (params.wait) {
|
||||
@@ -178,7 +178,7 @@ builder
|
||||
- LangGraph Studio: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:${params.port}
|
||||
`);
|
||||
},
|
||||
() => void 0
|
||||
() => void 0,
|
||||
);
|
||||
|
||||
await up.catch(() => void 0);
|
||||
|
||||
@@ -34,7 +34,7 @@ async function logData(data: LogData): Promise<void> {
|
||||
let analyticsPromise = Promise.resolve();
|
||||
|
||||
export function withAnalytics<TCommand extends Command<any, any, any>>(
|
||||
fn?: (command: TCommand) => Record<string, boolean>
|
||||
fn?: (command: TCommand) => Record<string, boolean>,
|
||||
) {
|
||||
if (process.env.LANGGRAPH_CLI_NO_ANALYTICS === "1") {
|
||||
return () => void 0;
|
||||
@@ -49,7 +49,7 @@ export function withAnalytics<TCommand extends Command<any, any, any>>(
|
||||
cli_version: version,
|
||||
cli_command: actionCommand.name(),
|
||||
params: fn?.(actionCommand) ?? {},
|
||||
}).catch(() => {})
|
||||
}).catch(() => {}),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export const createIpcServer = async () => {
|
||||
bufferData((message: Buffer) => {
|
||||
const data = JSON.parse(message.toString());
|
||||
server.emit("data", data);
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// MIT License
|
||||
//
|
||||
//
|
||||
// Copyright (c) Hiroki Osame <hiroki.osame@gmail.com>
|
||||
//
|
||||
//
|
||||
// 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
|
||||
@@ -19,7 +19,7 @@
|
||||
// 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.
|
||||
//
|
||||
//
|
||||
// https://github.com/privatenumber/tsx/tree/28a3e7d2b8fd72b683aab8a98dd1fcee4624e4cb
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as url from "node:url";
|
||||
async function getVersion() {
|
||||
try {
|
||||
const packageJson = url.fileURLToPath(
|
||||
new URL("../../../package.json", import.meta.url)
|
||||
new URL("../../../package.json", import.meta.url),
|
||||
);
|
||||
const { version } = JSON.parse(await fs.readFile(packageJson, "utf-8"));
|
||||
return version + "+js";
|
||||
|
||||
@@ -59,7 +59,7 @@ function parseVersion(input: string): Version {
|
||||
const patchStr = parts[2] ?? "0";
|
||||
|
||||
const major = Number.parseInt(
|
||||
majorStr.startsWith("v") ? majorStr.slice(1) : majorStr
|
||||
majorStr.startsWith("v") ? majorStr.slice(1) : majorStr,
|
||||
);
|
||||
const minor = Number.parseInt(minorStr);
|
||||
const patch = Number.parseInt(patchStr.split("-").at(0) ?? "0");
|
||||
@@ -96,7 +96,7 @@ export async function getDockerCapabilities(): Promise<DockerCapabilities> {
|
||||
z.object({
|
||||
Name: z.string(),
|
||||
Version: z.string().optional(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
}),
|
||||
})
|
||||
@@ -108,11 +108,11 @@ export async function getDockerCapabilities(): Promise<DockerCapabilities> {
|
||||
|
||||
const composePlugin = info.data.ClientInfo.Plugins.find(
|
||||
(i): i is { Name: string; Version: string } =>
|
||||
i.Name === "compose" && i.Version != null
|
||||
i.Name === "compose" && i.Version != null,
|
||||
);
|
||||
const buildxPlugin = info.data.ClientInfo.Plugins.find(
|
||||
(i): i is { Name: string; Version: string } =>
|
||||
i.Name === "buildx" && i.Version != null
|
||||
i.Name === "buildx" && i.Version != null,
|
||||
);
|
||||
|
||||
let composeRes: Pick<DockerCapabilities, "versionCompose" | "composeType">;
|
||||
@@ -124,7 +124,7 @@ export async function getDockerCapabilities(): Promise<DockerCapabilities> {
|
||||
} else {
|
||||
try {
|
||||
const standalone = await $(
|
||||
await getExecaOptions()
|
||||
await getExecaOptions(),
|
||||
)`docker-compose --version --short`;
|
||||
composeRes = {
|
||||
composeType: "standalone",
|
||||
@@ -159,7 +159,11 @@ function isPlainObject(value: unknown): value is Record<string, any> {
|
||||
|
||||
export function createCompose(
|
||||
capabilities: DockerCapabilities,
|
||||
options: { port?: number; postgresUri?: string; apiDef?: Record<string, any> }
|
||||
options: {
|
||||
port?: number;
|
||||
postgresUri?: string;
|
||||
apiDef?: Record<string, any>;
|
||||
},
|
||||
) {
|
||||
let includeDb = false;
|
||||
let postgresUri = options.postgresUri;
|
||||
|
||||
@@ -25,7 +25,7 @@ async function exists(path: string) {
|
||||
|
||||
export async function assembleLocalDeps(
|
||||
configPath: string,
|
||||
config: Config
|
||||
config: Config,
|
||||
): Promise<LocalDeps> {
|
||||
const reserved = new Set([
|
||||
"src",
|
||||
@@ -46,7 +46,7 @@ export async function assembleLocalDeps(
|
||||
function checkReserved(name: string, ref: string) {
|
||||
if (reserved.has(name)) {
|
||||
throw new Error(
|
||||
`Package name '${name}' used in local dep '${ref}' is reserved. Rename the directory.`
|
||||
`Package name '${name}' used in local dep '${ref}' is reserved. Rename the directory.`,
|
||||
);
|
||||
}
|
||||
reserved.add(name);
|
||||
@@ -71,7 +71,7 @@ export async function assembleLocalDeps(
|
||||
throw new Error(`Local dependency must be a directory: ${resolved}`);
|
||||
} else if (!resolved.startsWith(path.dirname(configPath))) {
|
||||
throw new Error(
|
||||
`Local dependency must be a subdirectory of the config file: ${resolved}`
|
||||
`Local dependency must be a subdirectory of the config file: ${resolved}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export async function assembleLocalDeps(
|
||||
// flat layout
|
||||
if (path.basename(resolved).includes("-")) {
|
||||
throw new Error(
|
||||
`Package name '${path.basename(resolved)}' contains a hyphen. Rename the directory to use it as flat-layout package.`
|
||||
`Package name '${path.basename(resolved)}' contains a hyphen. Rename the directory to use it as flat-layout package.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,13 +167,13 @@ export async function assembleLocalDeps(
|
||||
async function updateGraphPaths(
|
||||
configPath: string,
|
||||
config: Config,
|
||||
localDeps: LocalDeps
|
||||
localDeps: LocalDeps,
|
||||
) {
|
||||
for (const [graphId, importStr] of Object.entries(config.graphs)) {
|
||||
let [moduleStr, attrStr] = importStr.split(":", 2);
|
||||
if (!moduleStr || !attrStr) {
|
||||
throw new Error(
|
||||
`Import string "${importStr}" must be in format "<module>:<attribute>".`
|
||||
`Import string "${importStr}" must be in format "<module>:<attribute>".`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ async function updateGraphPaths(
|
||||
}
|
||||
|
||||
for (const [fauxPkg, [_, destPath]] of Object.entries(
|
||||
localDeps.fauxPkgs
|
||||
localDeps.fauxPkgs,
|
||||
)) {
|
||||
if (resolved.startsWith(fauxPkg)) {
|
||||
moduleStr = `${destPath}/${path.relative(fauxPkg, resolved)}`;
|
||||
@@ -201,7 +201,7 @@ async function updateGraphPaths(
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Module '${importStr}' not found in 'dependencies' list. Add its containing package to 'dependencies' list.`
|
||||
`Module '${importStr}' not found in 'dependencies' list. Add its containing package to 'dependencies' list.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ export async function configToDocker(
|
||||
watch: boolean;
|
||||
dockerCommand?: string;
|
||||
onWorkingDir?: (workingDir: string | undefined) => void;
|
||||
}
|
||||
},
|
||||
) {
|
||||
// figure out the package manager used here
|
||||
const testFile = async (file: string) =>
|
||||
@@ -264,16 +264,16 @@ export async function configToDocker(
|
||||
: undefined;
|
||||
|
||||
const pipReqs = localDeps.pipReqs.map(
|
||||
([reqpath, destpath]) => `ADD ${reqpath} ${destpath}`
|
||||
([reqpath, destpath]) => `ADD ${reqpath} ${destpath}`,
|
||||
);
|
||||
if (pipReqs.length) {
|
||||
pipReqs.push(
|
||||
`RUN ${pipInstall} ${localDeps.pipReqs.map(([, r]) => `-r ${r}`).join(" ")}`
|
||||
`RUN ${pipInstall} ${localDeps.pipReqs.map(([, r]) => `-r ${r}`).join(" ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const localPkg = Object.entries(localDeps.realPkgs).map(
|
||||
([fullpath, relpath]) => `ADD ${relpath} /deps/${path.basename(fullpath)}`
|
||||
([fullpath, relpath]) => `ADD ${relpath} /deps/${path.basename(fullpath)}`,
|
||||
);
|
||||
|
||||
const fauxPkgs = Object.entries(localDeps.fauxPkgs).flatMap(
|
||||
@@ -289,7 +289,7 @@ export async function configToDocker(
|
||||
echo "${options?.dockerCommand === "build" ? "$line" : "$$line"}" >> /deps/__outer_${path.basename(fullpath)}/pyproject.toml; \
|
||||
done
|
||||
`,
|
||||
]
|
||||
],
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -344,7 +344,7 @@ export async function configToDocker(
|
||||
if (options?.watch && (localDeps.workingDir || localDeps.reloadDir)) {
|
||||
// TODO: hacky, should add as entrypoint to the langgraph-api base image
|
||||
lines.push(
|
||||
`CMD exec uvicorn langgraph_api.server:app --log-config /api/logging.json --no-access-log --host 0.0.0.0 --port 8000 --reload --reload-dir ${localDeps.workingDir || localDeps.reloadDir}`
|
||||
`CMD exec uvicorn langgraph_api.server:app --log-config /api/logging.json --no-access-log --host 0.0.0.0 --port 8000 --reload --reload-dir ${localDeps.workingDir || localDeps.reloadDir}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ export async function configToDocker(
|
||||
export async function configToWatch(
|
||||
configPath: string,
|
||||
config: Config,
|
||||
localDeps: LocalDeps
|
||||
localDeps: LocalDeps,
|
||||
) {
|
||||
const projectDir = path.dirname(configPath);
|
||||
const watch: Array<{
|
||||
@@ -405,7 +405,7 @@ export async function configToCompose(
|
||||
options?: {
|
||||
watch: boolean;
|
||||
extendEnv?: Record<string, string>;
|
||||
}
|
||||
},
|
||||
): Promise<{
|
||||
apiDef: Record<string, unknown>;
|
||||
rewrite: { source: string; target: string } | undefined;
|
||||
|
||||
@@ -53,8 +53,8 @@ async function getLoginPath() {
|
||||
|
||||
const [fromShell, fromBackup] = await Promise.allSettled(
|
||||
[extractPathFromShell(), guessUserPath()].map((promise) =>
|
||||
promise.then(verifyDockerPath)
|
||||
)
|
||||
promise.then(verifyDockerPath),
|
||||
),
|
||||
);
|
||||
|
||||
if (fromShell.status === "fulfilled") {
|
||||
@@ -65,7 +65,7 @@ async function getLoginPath() {
|
||||
console.error(
|
||||
"Failed to get PATH from shell or backup",
|
||||
fromShell.reason,
|
||||
fromBackup.reason
|
||||
fromBackup.reason,
|
||||
);
|
||||
throw fromShell.reason || fromBackup.reason;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ async function getLoginPath() {
|
||||
type CommonOptions = Exclude<Parameters<ExecaScriptMethod>[1], undefined>;
|
||||
|
||||
export async function getExecaOptions<T extends CommonOptions = {}>(
|
||||
options?: T
|
||||
options?: T,
|
||||
) {
|
||||
const env = await getLoginPath();
|
||||
return { ...options, env } as T & { env: typeof env };
|
||||
|
||||
@@ -7,7 +7,7 @@ const BaseConfigSchema = z.object({
|
||||
graphs: z.record(
|
||||
z.string().refine((i) => i.includes(":"), {
|
||||
message: "Import string must be in format '<file>:<export>'",
|
||||
})
|
||||
}),
|
||||
),
|
||||
_INTERNAL_docker_tag: z.string().optional(),
|
||||
env: z
|
||||
@@ -45,16 +45,16 @@ const PythonConfigSchema = BaseConfigSchema.merge(
|
||||
dependencies: z
|
||||
.array(z.string())
|
||||
.nonempty("You need to specify at least one dependency"),
|
||||
})
|
||||
}),
|
||||
).merge(
|
||||
z.object({
|
||||
python_version: PythonVersionSchema.default(DEFAULT_PYTHON_VERSION),
|
||||
node_version: NodeVersionSchema.optional(),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const NodeConfigSchema = BaseConfigSchema.merge(
|
||||
z.object({ node_version: NodeVersionSchema.default(DEFAULT_NODE_VERSION) })
|
||||
z.object({ node_version: NodeVersionSchema.default(DEFAULT_NODE_VERSION) }),
|
||||
);
|
||||
|
||||
const ConfigSchema = z.union([NodeConfigSchema, PythonConfigSchema]);
|
||||
@@ -66,7 +66,7 @@ export const getConfig = (config: z.input<typeof ConfigSchema> | string) => {
|
||||
const { graphs } = BaseConfigSchema.parse(input);
|
||||
|
||||
const isPython = Object.values(graphs).map((i) =>
|
||||
PYTHON_EXTENSIONS.includes(extname(i.split(":")[0]))
|
||||
PYTHON_EXTENSIONS.includes(extname(i.split(":")[0])),
|
||||
);
|
||||
const somePython = isPython.some((i) => i);
|
||||
const someNode = !isPython.every((i) => i);
|
||||
|
||||
@@ -48,7 +48,7 @@ export const logger = createLogger({
|
||||
|
||||
return JSON.stringify({ timestamp, level, event, ...rest });
|
||||
}),
|
||||
])
|
||||
]),
|
||||
),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
@@ -59,7 +59,7 @@ const formatStack = (stack: string | undefined | null) => {
|
||||
const [firstFile] = stacktraceParser(stack).filter(
|
||||
(item) =>
|
||||
!item.file?.split(path.sep).includes("node_modules") &&
|
||||
!item.file?.startsWith("node:")
|
||||
!item.file?.startsWith("node:"),
|
||||
);
|
||||
|
||||
if (firstFile?.file && firstFile?.lineNumber) {
|
||||
@@ -72,7 +72,7 @@ const formatStack = (stack: string | undefined | null) => {
|
||||
const spliceIndex = messageLines.findIndex((i) => i.includes(filePath));
|
||||
|
||||
const padding = " ".repeat(
|
||||
Math.max(0, messageLines[spliceIndex].indexOf("at"))
|
||||
Math.max(0, messageLines[spliceIndex].indexOf("at")),
|
||||
);
|
||||
|
||||
const highlightCode = process.stdout.isTTY;
|
||||
@@ -80,7 +80,7 @@ const formatStack = (stack: string | undefined | null) => {
|
||||
let codeFrame = codeFrameColumns(
|
||||
readFileSync(filePath, "utf-8"),
|
||||
{ start: { line, column } },
|
||||
{ highlightCode }
|
||||
{ highlightCode },
|
||||
);
|
||||
|
||||
codeFrame = codeFrame
|
||||
@@ -111,7 +111,7 @@ export const logError = (
|
||||
options?: {
|
||||
context?: Record<string, unknown>;
|
||||
prefix?: string;
|
||||
}
|
||||
},
|
||||
) => {
|
||||
let message;
|
||||
let context = options?.context;
|
||||
|
||||
@@ -35,13 +35,13 @@ describe("config to watch", () => {
|
||||
|
||||
const localDeps = await assembleLocalDeps(
|
||||
path.resolve(__dirname, "./unit_tests/langgraph.json"),
|
||||
config
|
||||
config,
|
||||
);
|
||||
|
||||
const watch = await configToWatch(
|
||||
path.resolve(__dirname, "./unit_tests/langgraph.json"),
|
||||
config,
|
||||
localDeps
|
||||
localDeps,
|
||||
);
|
||||
|
||||
expect(watch).toEqual([
|
||||
@@ -65,13 +65,13 @@ describe("config to watch", () => {
|
||||
|
||||
const localDeps = await assembleLocalDeps(
|
||||
path.resolve(__dirname, "./unit_tests/langgraph.json"),
|
||||
config
|
||||
config,
|
||||
);
|
||||
|
||||
const watch = await configToWatch(
|
||||
path.resolve(__dirname, "./unit_tests/langgraph.json"),
|
||||
config,
|
||||
localDeps
|
||||
localDeps,
|
||||
);
|
||||
|
||||
expect(watch).toEqual([
|
||||
@@ -113,7 +113,7 @@ describe("config to docker", () => {
|
||||
const actual = await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
|
||||
expect(actual).toEqual(dedenter`
|
||||
@@ -144,7 +144,7 @@ describe("config to docker", () => {
|
||||
const actual = await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
|
||||
expect(actual).toEqual(dedenter`
|
||||
@@ -178,7 +178,7 @@ describe("config to docker", () => {
|
||||
await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
}).rejects.toThrowError(/Could not find local dependency/);
|
||||
|
||||
@@ -194,7 +194,7 @@ describe("config to docker", () => {
|
||||
await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
}).rejects.toThrowError(/Could not find local module/);
|
||||
});
|
||||
@@ -210,7 +210,7 @@ describe("config to docker", () => {
|
||||
const actual = await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
|
||||
expect(actual).toEqual(dedenter`
|
||||
@@ -239,7 +239,7 @@ describe("config to docker", () => {
|
||||
version = "0.1"
|
||||
dependencies = ["langchain"]
|
||||
`,
|
||||
{ encoding: "utf-8" }
|
||||
{ encoding: "utf-8" },
|
||||
);
|
||||
|
||||
const graphs = { agent: "./graphs/agent.py:graph" };
|
||||
@@ -252,7 +252,7 @@ describe("config to docker", () => {
|
||||
const actual = await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
|
||||
await fs.rm(pyproject);
|
||||
@@ -279,7 +279,7 @@ describe("config to docker", () => {
|
||||
const actual = await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
|
||||
expect(actual).toEqual(dedenter`
|
||||
@@ -314,7 +314,7 @@ describe("config to docker", () => {
|
||||
const actual = await configToDocker(
|
||||
PATH_TO_CONFIG,
|
||||
config,
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config)
|
||||
await assembleLocalDeps(PATH_TO_CONFIG, config),
|
||||
);
|
||||
|
||||
// TODO: add support for any packager
|
||||
@@ -484,7 +484,7 @@ describe("config to compose", () => {
|
||||
dependencies: ["."],
|
||||
graphs: graph,
|
||||
},
|
||||
{ watch: true }
|
||||
{ watch: true },
|
||||
);
|
||||
|
||||
expect(yaml.stringify(actual, { blockQuote: "literal" })).toEqual(expected);
|
||||
@@ -497,7 +497,7 @@ describe("config to compose", () => {
|
||||
it("env", async () => {
|
||||
const PATH_TO_CONFIG = path.resolve(
|
||||
__dirname,
|
||||
"./env_tests/langgraph.json"
|
||||
"./env_tests/langgraph.json",
|
||||
);
|
||||
|
||||
const graph = { agent: "./agent.py:graph" };
|
||||
@@ -545,7 +545,7 @@ describe("config to compose", () => {
|
||||
{
|
||||
watch: true,
|
||||
extendEnv: { ANTHROPIC_API_KEY: "key", OPENAI_API_KEY: "key" },
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
expect(yaml.stringify(actual, { blockQuote: "literal" })).toEqual(expected);
|
||||
@@ -566,10 +566,10 @@ describe("config to compose", () => {
|
||||
env: ["OPENAI_API_KEY", "ANTHROPIC_API_KEY"],
|
||||
dockerfile_lines: [],
|
||||
},
|
||||
{ watch: true }
|
||||
)
|
||||
{ watch: true },
|
||||
),
|
||||
).rejects.toThrowError(
|
||||
/Missing environment variables: OPENAI_API_KEY, ANTHROPIC_API_KEY/
|
||||
/Missing environment variables: OPENAI_API_KEY, ANTHROPIC_API_KEY/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -659,7 +659,7 @@ describe("config to compose", () => {
|
||||
env: ".env",
|
||||
dockerfile_lines: [],
|
||||
},
|
||||
{ watch: true }
|
||||
{ watch: true },
|
||||
);
|
||||
|
||||
expect(yaml.stringify(actual, { blockQuote: "literal" })).toEqual(expected);
|
||||
@@ -672,11 +672,11 @@ describe("config to compose", () => {
|
||||
|
||||
describe("packaging", () => {
|
||||
async function loadConfig(
|
||||
rel: string
|
||||
rel: string,
|
||||
): Promise<[path: string, config: Config]> {
|
||||
const res = path.resolve(__dirname, rel);
|
||||
const config = getConfig(
|
||||
JSON.parse(await fs.readFile(res, { encoding: "utf-8" }))
|
||||
JSON.parse(await fs.readFile(res, { encoding: "utf-8" })),
|
||||
);
|
||||
return [res, config];
|
||||
}
|
||||
@@ -684,7 +684,7 @@ describe("packaging", () => {
|
||||
it("faux", async () => {
|
||||
const { apiDef: actual } = await configToCompose(
|
||||
...(await loadConfig("./packaging_tests/faux/langgraph.json")),
|
||||
{ watch: true }
|
||||
{ watch: true },
|
||||
);
|
||||
|
||||
const expected =
|
||||
@@ -728,7 +728,7 @@ describe("packaging", () => {
|
||||
it("js", async () => {
|
||||
const { apiDef: actual, rewrite } = await configToCompose(
|
||||
...(await loadConfig("./packaging_tests/js/langgraph.json")),
|
||||
{ watch: true }
|
||||
{ watch: true },
|
||||
);
|
||||
|
||||
const expected =
|
||||
@@ -787,7 +787,7 @@ it("node config and python config", () => {
|
||||
dockerfile_lines: [],
|
||||
dependencies: ["."],
|
||||
graphs: { agent: "./route.ts:agent" },
|
||||
})
|
||||
}),
|
||||
).toEqual({
|
||||
node_version: "20",
|
||||
dockerfile_lines: [],
|
||||
@@ -804,7 +804,7 @@ it("node config and python config", () => {
|
||||
pip_config_file: undefined,
|
||||
dependencies: ["."],
|
||||
graphs: { agent: "./agent.py:graph" },
|
||||
})
|
||||
}),
|
||||
).toEqual({
|
||||
python_version: "3.11",
|
||||
pip_config_file: undefined,
|
||||
@@ -819,7 +819,7 @@ it("node config and python config", () => {
|
||||
getConfig({
|
||||
dependencies: ["."],
|
||||
graphs: { agent: "./agent.py:graph" },
|
||||
})
|
||||
}),
|
||||
).toEqual({
|
||||
python_version: "3.11",
|
||||
pip_config_file: undefined,
|
||||
@@ -842,7 +842,7 @@ it("node config and python config", () => {
|
||||
getConfig({
|
||||
dependencies: ["."],
|
||||
graphs: { js: "./agent.js:graph", py: "./agent.py:graph" },
|
||||
})
|
||||
}),
|
||||
).toEqual({
|
||||
python_version: "3.12",
|
||||
node_version: "20",
|
||||
@@ -860,7 +860,7 @@ it("node config and python config", () => {
|
||||
python_version: "3.11",
|
||||
graphs: { agent: "./agent.py:graph", js: "./agent.js:graph" },
|
||||
dependencies: ["."],
|
||||
})
|
||||
}),
|
||||
)
|
||||
.toThrow("Only Python 3.12 is supported with Node.js");
|
||||
|
||||
@@ -870,7 +870,7 @@ it("node config and python config", () => {
|
||||
getConfig({
|
||||
graphs: { agent: "agent.py" },
|
||||
dependencies: ["."],
|
||||
})
|
||||
}),
|
||||
)
|
||||
.toThrow(`Import string must be in format '<file>:<export>'`);
|
||||
|
||||
@@ -881,7 +881,7 @@ it("node config and python config", () => {
|
||||
graphs: { agent: "./agent.py:graph" },
|
||||
// @ts-expect-error
|
||||
dependencies: [], // Empty array
|
||||
})
|
||||
}),
|
||||
)
|
||||
.toThrow("You need to specify at least one dependency");
|
||||
|
||||
@@ -893,7 +893,7 @@ it("node config and python config", () => {
|
||||
python_version: "3.10", // Unsupported version
|
||||
graphs: { agent: "./agent.py:graph" },
|
||||
dependencies: ["."],
|
||||
})
|
||||
}),
|
||||
)
|
||||
.toThrow();
|
||||
|
||||
@@ -904,7 +904,7 @@ it("node config and python config", () => {
|
||||
// @ts-expect-error
|
||||
node_version: "18", // Unsupported version
|
||||
graphs: { agent: "./agent.js:graph" },
|
||||
})
|
||||
}),
|
||||
)
|
||||
.toThrow();
|
||||
});
|
||||
|
||||
@@ -48,8 +48,8 @@ it("compose with custom db and healthcheck", () => {
|
||||
expect(
|
||||
createCompose(
|
||||
{ ...DEFAULT_DOCKER_CAPABILITIES, healthcheckStartInterval: true },
|
||||
{ port, postgresUri }
|
||||
)
|
||||
{ port, postgresUri },
|
||||
),
|
||||
).toEqual(dedent`
|
||||
services:
|
||||
langgraph-redis:
|
||||
|
||||
+11
-2
@@ -1,6 +1,15 @@
|
||||
{
|
||||
"name": "langgraphjs-api",
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"keywords": []
|
||||
"packageManager": "pnpm@9.1.1",
|
||||
"scripts": {
|
||||
"clean": "turbo run clean",
|
||||
"build": "turbo run build",
|
||||
"format": "turbo run format",
|
||||
"format:check": "turbo run format:check"
|
||||
},
|
||||
"keywords": [],
|
||||
"devDependencies": {
|
||||
"turbo": "^2.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+66
-1
@@ -6,7 +6,11 @@ settings:
|
||||
|
||||
importers:
|
||||
|
||||
.: {}
|
||||
.:
|
||||
devDependencies:
|
||||
turbo:
|
||||
specifier: ^2.4.0
|
||||
version: 2.4.0
|
||||
|
||||
libs/create-langgraph:
|
||||
dependencies:
|
||||
@@ -1307,6 +1311,40 @@ packages:
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
turbo-darwin-64@2.4.0:
|
||||
resolution: {integrity: sha512-kVMScnPUa3R4n7woNmkR15kOY0aUwCLJcUyH5UC59ggKqr5HIHwweKYK8N1pwBQso0LQF4I9i93hIzfJguCcwQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
turbo-darwin-arm64@2.4.0:
|
||||
resolution: {integrity: sha512-8JObIpfun1guA7UlFR5jC/SOVm49lRscxMxfg5jZ5ABft79rhFC+ygN9AwAhGKv6W2DUhIh2xENkSgu4EDmUyg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
turbo-linux-64@2.4.0:
|
||||
resolution: {integrity: sha512-xWDGGcRlBuGV7HXWAVuTY6vsQi4aZxGMAnuiuNDg8Ij1aHGohOM0RUsWMXjxz4vuJmjk9+/D6NQqHH3AJEXezg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
turbo-linux-arm64@2.4.0:
|
||||
resolution: {integrity: sha512-c3En99xMguc/Pdtk/rZP53LnDdw0W6lgUc04he8r8F+UHYSNvgzHh0WGXXmCC6lGbBH72kPhhGx4bAwyvi7dug==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
turbo-windows-64@2.4.0:
|
||||
resolution: {integrity: sha512-/gOORuOlyA8JDPzyA16CD3wvyRcuBFePa1URAnFUof9hXQmKxK0VvSDO79cYZFsJSchCKNJpckUS0gYxGsWwoA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
turbo-windows-arm64@2.4.0:
|
||||
resolution: {integrity: sha512-/DJIdTFijEMM5LSiEpSfarDOMOlYqJV+EzmppqWtHqDsOLF4hbbIBH9sJR6OOp5dURAu5eURBYdmvBRz9Lo6TA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
turbo@2.4.0:
|
||||
resolution: {integrity: sha512-ah/yQp2oMif1X0u7fBJ4MLMygnkbKnW5O8SG6pJvloPCpHfFoZctkSVQiJ3VnvNTq71V2JJIdwmOeu1i34OQyg==}
|
||||
hasBin: true
|
||||
|
||||
type-fest@0.7.1:
|
||||
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2385,6 +2423,33 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
turbo-darwin-64@2.4.0:
|
||||
optional: true
|
||||
|
||||
turbo-darwin-arm64@2.4.0:
|
||||
optional: true
|
||||
|
||||
turbo-linux-64@2.4.0:
|
||||
optional: true
|
||||
|
||||
turbo-linux-arm64@2.4.0:
|
||||
optional: true
|
||||
|
||||
turbo-windows-64@2.4.0:
|
||||
optional: true
|
||||
|
||||
turbo-windows-arm64@2.4.0:
|
||||
optional: true
|
||||
|
||||
turbo@2.4.0:
|
||||
optionalDependencies:
|
||||
turbo-darwin-64: 2.4.0
|
||||
turbo-darwin-arm64: 2.4.0
|
||||
turbo-linux-64: 2.4.0
|
||||
turbo-linux-arm64: 2.4.0
|
||||
turbo-windows-64: 2.4.0
|
||||
turbo-windows-arm64: 2.4.0
|
||||
|
||||
type-fest@0.7.1: {}
|
||||
|
||||
typescript@5.5.4: {}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**"]
|
||||
},
|
||||
"format": {
|
||||
"dependsOn": ["^format"]
|
||||
},
|
||||
"format:check": {
|
||||
"dependsOn": ["^format:check"]
|
||||
},
|
||||
"test": {
|
||||
"cache": false,
|
||||
"dependsOn": ["^build", "build"]
|
||||
},
|
||||
"clean": {
|
||||
"dependsOn": ["^clean"]
|
||||
},
|
||||
"precommit": {},
|
||||
"start": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user