From c757247858938894e73fc2272edc0fb7f865585e Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 7 Apr 2025 20:29:14 +0200 Subject: [PATCH] feat(sdk-js): add auth types --- libs/sdk-js/langchain.config.js | 1 + libs/sdk-js/src/auth/error.ts | 74 +++++++++ libs/sdk-js/src/auth/index.ts | 267 ++++++++++++++++++++++++++++++++ libs/sdk-js/tsconfig.json | 24 +-- 4 files changed, 347 insertions(+), 19 deletions(-) create mode 100644 libs/sdk-js/src/auth/error.ts create mode 100644 libs/sdk-js/src/auth/index.ts diff --git a/libs/sdk-js/langchain.config.js b/libs/sdk-js/langchain.config.js index 21d796c87..0c8a691cd 100644 --- a/libs/sdk-js/langchain.config.js +++ b/libs/sdk-js/langchain.config.js @@ -14,6 +14,7 @@ export const config = { entrypoints: { index: "index", client: "client", + auth: "auth/index", react: "react/index", "react-ui": "react-ui/index", "react-ui/server": "react-ui/server/index", diff --git a/libs/sdk-js/src/auth/error.ts b/libs/sdk-js/src/auth/error.ts new file mode 100644 index 000000000..bd26c8912 --- /dev/null +++ b/libs/sdk-js/src/auth/error.ts @@ -0,0 +1,74 @@ +const HTTP_STATUS_MAPPING: { [key: number]: string } = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 103: "Early Hints", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non-Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi-Status", + 208: "Already Reported", + 226: "IM Used", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a Teapot", + 421: "Misdirected Request", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", +}; + +export class HTTPException extends Error { + status: number; + constructor(status: number, options?: { message?: string; cause?: Error }) { + super(options?.message ?? HTTP_STATUS_MAPPING[status] ?? "Unknown error", { + cause: options?.cause, + }); + this.status = status; + } +} diff --git a/libs/sdk-js/src/auth/index.ts b/libs/sdk-js/src/auth/index.ts new file mode 100644 index 000000000..900a3adbb --- /dev/null +++ b/libs/sdk-js/src/auth/index.ts @@ -0,0 +1,267 @@ +type Maybe = T | null | undefined; + +interface AssistantConfig { + tags?: Maybe; + recursion_limit?: Maybe; + configurable?: Maybe<{ + thread_id?: Maybe; + thread_ts?: Maybe; + [key: string]: unknown; + }>; + [key: string]: unknown; +} + +interface AssistantCreate { + assistant_id?: Maybe; + metadata?: Maybe>; + config?: Maybe; + if_exists?: Maybe<"raise" | "do_nothing">; + name?: Maybe; + graph_id: string; +} + +interface AssistantRead { + assistant_id: string; + metadata?: Maybe>; +} + +interface AssistantUpdate { + assistant_id: string; + metadata?: Maybe>; + config?: Maybe; + graph_id?: Maybe; + name?: Maybe; + version?: Maybe; +} + +interface AssistantDelete { + assistant_id: string; +} + +interface AssistantSearch { + graph_id?: Maybe; + metadata?: Maybe>; + limit?: Maybe; + offset?: Maybe; +} + +// TODO: add missing types +interface ThreadCreate {} +interface ThreadRead {} +interface ThreadUpdate {} +interface ThreadDelete {} +interface ThreadSearch {} + +interface CronCreate {} +interface CronRead {} +interface CronUpdate {} +interface CronDelete {} +interface CronSearch {} + +interface StorePut {} +interface StoreGet {} +interface StoreSearch {} +interface StoreListNamespaces {} +interface StoreDelete {} + +interface RunsCreate {} + +interface ResourceActionType { + ["threads:create"]: ThreadCreate; + ["threads:read"]: ThreadRead; + ["threads:update"]: ThreadUpdate; + ["threads:delete"]: ThreadDelete; + ["threads:search"]: ThreadSearch; + ["threads:create_run"]: RunsCreate; + + ["assistants:create"]: AssistantCreate; + ["assistants:read"]: AssistantRead; + ["assistants:update"]: AssistantUpdate; + ["assistants:delete"]: AssistantDelete; + ["assistants:search"]: AssistantSearch; + + ["crons:create"]: CronCreate; + ["crons:read"]: CronRead; + ["crons:update"]: CronUpdate; + ["crons:delete"]: CronDelete; + ["crons:search"]: CronSearch; + + ["store:put"]: StorePut; + ["store:get"]: StoreGet; + ["store:search"]: StoreSearch; + ["store:list_namespaces"]: StoreListNamespaces; + ["store:delete"]: StoreDelete; +} + +interface ResourceType { + threads: + | "threads:create" + | "threads:read" + | "threads:update" + | "threads:delete" + | "threads:search" + | "threads:create_run"; + + assistants: + | "assistants:create" + | "assistants:read" + | "assistants:update" + | "assistants:delete" + | "assistants:search"; + crons: + | "crons:create" + | "crons:read" + | "crons:update" + | "crons:delete" + | "crons:search"; + + store: + | "store:put" + | "store:get" + | "store:search" + | "store:list_namespaces" + | "store:delete"; +} + +interface ActionType { + "*:create": "threads:create" | "assistants:create" | "crons:create"; + + "*:read": "threads:read" | "assistants:read" | "crons:read"; + + "*:update": "threads:update" | "assistants:update" | "crons:update"; + + "*:delete": + | "threads:delete" + | "assistants:delete" + | "crons:delete" + | "store:delete"; + + "*:search": + | "threads:search" + | "assistants:search" + | "crons:search" + | "store:search"; + + "*:create_run": "threads:create_run"; + + "*:put": "store:put"; + + "*:get": "store:get"; + + "*:list_namespaces": "store:list_namespaces"; +} + +interface BaseAuthContext { + permissions?: string[]; + user?: { + is_authenticated: boolean; + display_name: string; + identity: string; + permissions: string[]; + }; +} + +type ContextMap = { + [ActionType in keyof ResourceActionType]: { + resource: ActionType extends `${infer Resource}:${string}` + ? Resource + : never; + action: ActionType; + data: ResourceActionType[ActionType]; + context: BaseAuthContext; + }; +}; + +type ActionCallbackParameter< + T extends keyof ActionType, + AuthContext = {}, +> = ContextMap[ActionType[T]] & { context: AuthContext }; + +type AuthCallbackParameter< + T extends keyof ResourceActionType, + AuthContext = {}, +> = ContextMap[T] & { context: AuthContext }; + +type ResourceCallbackParameter< + T extends keyof ResourceType, + AuthContext = {}, +> = ContextMap[ResourceType[T]] & { context: AuthContext }; + +type Filters = { + [key in TKey]: string | { [op in "$contains" | "$eq"]?: string }; +}; + +interface AuthenticateCallback> { + (request: Request): AuthContext; +} + +export class Auth< + Metadata extends Record = {}, + AuthContext extends Record = {}, +> { + protected __lg_type = Symbol.for("lg:auth"); + + "~handlerCache": { + authenticate?: AuthenticateCallback; + callbacks?: Record< + string, + (request: any) => void | boolean | Filters + >; + } = {}; + + authenticate(cb: AuthenticateCallback): this { + this["~handlerCache"].authenticate = cb; + return this; + } + + /** + * Global handler for all requests + */ + on( + event: "*", + callback: ( + data: AuthCallbackParameter, + ) => void | boolean | Filters, + ): this; + + /** + * Resource-specific handler + */ + on( + event: T, + callback: ( + data: ResourceCallbackParameter, + ) => void | boolean | Filters, + ): this; + + /** + * Action-specific handler + */ + on( + event: T, + callback: ( + data: ActionCallbackParameter, + ) => void | boolean | Filters, + ): this; + + /** + * Resource-action specific handler + */ + on( + event: T, + callback: ( + data: AuthCallbackParameter, + ) => void | boolean | Filters, + ): this; + + on( + event: string, + callback: ( + data: AuthCallbackParameter, + ) => void | boolean | Filters, + ): this { + this["~handlerCache"].callbacks ??= {}; + this["~handlerCache"].callbacks[event] = callback; + return this; + } +} diff --git a/libs/sdk-js/tsconfig.json b/libs/sdk-js/tsconfig.json index 9c6561a09..d5c1626ab 100644 --- a/libs/sdk-js/tsconfig.json +++ b/libs/sdk-js/tsconfig.json @@ -2,11 +2,7 @@ "extends": "@tsconfig/recommended", "compilerOptions": { "target": "ES2021", - "lib": [ - "ES2021", - "ES2022.Object", - "DOM" - ], + "lib": ["ES2021", "ES2022.Object", "ES2022.Error", "DOM"], "module": "NodeNext", "moduleResolution": "nodenext", "esModuleInterop": true, @@ -22,24 +18,14 @@ "jsx": "react-jsx", "outDir": "dist" }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "dist", - "coverage" - ], + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "coverage"], "includeVersion": true, "typedocOptions": { - "entryPoints": [ - "src/client.ts" - ], + "entryPoints": ["src/client.ts"], "readme": "none", "out": "docs", - "plugin": [ - "typedoc-plugin-markdown" - ], + "plugin": ["typedoc-plugin-markdown"], "excludePrivate": true, "excludeProtected": true, "excludeExternals": false