From f26c0ef7b347bba0d166b8a404f89e2053ebc9d6 Mon Sep 17 00:00:00 2001 From: Arjun Natarajan Date: Tue, 10 Jun 2025 15:35:27 -0400 Subject: [PATCH] feat: pagination and tracing --- libs/langgraph-api/src/api/assistants.mts | 15 ++++++++---- libs/langgraph-api/src/api/meta.mts | 27 +++++++++++++++++++++- libs/langgraph-api/src/http/middleware.mts | 27 +++++++++++++--------- libs/langgraph-api/src/storage/ops.mts | 13 +++++++++-- 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/libs/langgraph-api/src/api/assistants.mts b/libs/langgraph-api/src/api/assistants.mts index 62fecc4..c061be3 100644 --- a/libs/langgraph-api/src/api/assistants.mts +++ b/libs/langgraph-api/src/api/assistants.mts @@ -1,19 +1,19 @@ -import { Hono } from "hono"; import { zValidator } from "@hono/zod-validator"; +import { Hono } from "hono"; import { v4 as uuid } from "uuid"; import { z } from "zod"; import { getAssistantId, - getGraph, getCachedStaticGraphSchema, + getGraph, } from "../graph/load.mjs"; import { getRuntimeGraphSchema } from "../graph/parser/index.mjs"; -import { Assistants } from "../storage/ops.mjs"; -import * as schemas from "../schemas.mjs"; import { HTTPException } from "hono/http-exception"; +import * as schemas from "../schemas.mjs"; +import { Assistants } from "../storage/ops.mjs"; const api = new Hono(); const RunnableConfigSchema = z.object({ @@ -71,6 +71,7 @@ api.post( const payload = c.req.valid("json"); const result: unknown[] = []; + let total = 0; for await (const item of Assistants.search( { graph_id: payload.graph_id, @@ -80,9 +81,13 @@ api.post( }, c.var.auth, )) { - result.push(item); + result.push(item.assistant); + if (total === 0) { + total = item.total; + } } + c.res.headers.set("X-Pagination-Total", total.toString()); return c.json(result); }, ); diff --git a/libs/langgraph-api/src/api/meta.mts b/libs/langgraph-api/src/api/meta.mts index 7921ab1..600f93b 100644 --- a/libs/langgraph-api/src/api/meta.mts +++ b/libs/langgraph-api/src/api/meta.mts @@ -2,7 +2,32 @@ import { Hono } from "hono"; const api = new Hono(); -api.get("/info", (c) => c.json({ flags: { assistants: true, crons: false } })); +// read env variable +const env = process.env; + +api.get("/info", (c) => { + const langsmithApiKey = env["LANGSMITH_API_KEY"] || env["LANGCHAIN_API_KEY"]; + + const langsmithTracing = (() => { + if (langsmithApiKey) { + // Check if any tracing variable is explicitly set to "false" + const tracingVars = [ + env["LANGCHAIN_TRACING_V2"], + env["LANGCHAIN_TRACING"], + env["LANGSMITH_TRACING_V2"], + env["LANGSMITH_TRACING"], + ]; + + // Return true unless explicitly disabled + return !tracingVars.some((val) => val === "false" || val === "False"); + } + return undefined; + })(); + return c.json({ + flags: { assistants: true, crons: false, langsmith: langsmithTracing }, + env, + }); +}); api.get("/ok", (c) => c.json({ ok: true })); diff --git a/libs/langgraph-api/src/http/middleware.mts b/libs/langgraph-api/src/http/middleware.mts index 8d9f731..135ae98 100644 --- a/libs/langgraph-api/src/http/middleware.mts +++ b/libs/langgraph-api/src/http/middleware.mts @@ -17,7 +17,7 @@ export const cors = ( if (cors == null) { return honoCors({ origin: "*", - exposeHeaders: ["content-location"], + exposeHeaders: ["content-location", "x-pagination-total"], }); } @@ -33,16 +33,21 @@ export const cors = ( } : (cors.allow_origins ?? []); - if ( - !!cors.expose_headers?.length && - cors.expose_headers.some( - (i) => i.toLocaleLowerCase() === "content-location", - ) - ) { - console.warn( - "Adding missing `Content-Location` header in `cors.expose_headers`.", - ); - cors.expose_headers.push("content-location"); + if (cors.expose_headers?.length) { + const headersSet = new Set(cors.expose_headers.map((h) => h.toLowerCase())); + + if (!headersSet.has("content-location")) { + console.warn( + "Adding missing `Content-Location` header in `cors.expose_headers`.", + ); + cors.expose_headers.push("content-location"); + } + if (!headersSet.has("x-pagination-total")) { + console.warn( + "Adding missing `X-Pagination-Total` header in `cors.expose_headers`.", + ); + cors.expose_headers.push("x-pagination-total"); + } } // TODO: handle `cors.allow_credentials` diff --git a/libs/langgraph-api/src/storage/ops.mts b/libs/langgraph-api/src/storage/ops.mts index 817a13a..0696928 100644 --- a/libs/langgraph-api/src/storage/ops.mts +++ b/libs/langgraph-api/src/storage/ops.mts @@ -323,7 +323,7 @@ export class Assistants { offset: number; }, auth: AuthContext | undefined, - ) { + ): AsyncGenerator<{ assistant: Assistant; total: number }> { const [filters] = await handleAuthEvent(auth, "assistants:search", { graph_id: options.graph_id, metadata: options.metadata, @@ -360,11 +360,20 @@ export class Assistants { return bCreatedAt - aCreatedAt; }); + // Calculate total count before pagination + const total = filtered.length; + for (const assistant of filtered.slice( options.offset, options.offset + options.limit, )) { - yield { ...assistant, name: assistant.name ?? assistant.graph_id }; + yield { + assistant: { + ...assistant, + name: assistant.name ?? assistant.graph_id, + }, + total, + }; } }); }