diff --git a/libs/sdk-js/.gitignore b/libs/sdk-js/.gitignore index 128c6dc98..db1a582d0 100644 --- a/libs/sdk-js/.gitignore +++ b/libs/sdk-js/.gitignore @@ -6,6 +6,10 @@ client.cjs client.js client.d.ts client.d.cts +auth.cjs +auth.js +auth.d.ts +auth.d.cts react.cjs react.js react.d.ts 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/package.json b/libs/sdk-js/package.json index 64ddcf047..343a33412 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -72,6 +72,15 @@ "import": "./client.js", "require": "./client.cjs" }, + "./auth": { + "types": { + "import": "./auth.d.ts", + "require": "./auth.d.cts", + "default": "./auth.d.ts" + }, + "import": "./auth.js", + "require": "./auth.cjs" + }, "./react": { "types": { "import": "./react.d.ts", @@ -111,6 +120,10 @@ "client.js", "client.d.ts", "client.d.cts", + "auth.cjs", + "auth.js", + "auth.d.ts", + "auth.d.cts", "react.cjs", "react.js", "react.d.ts", diff --git a/libs/sdk-js/src/auth/error.ts b/libs/sdk-js/src/auth/error.ts new file mode 100644 index 000000000..ace6df54c --- /dev/null +++ b/libs/sdk-js/src/auth/error.ts @@ -0,0 +1,80 @@ +const HTTP_STATUS_MAPPING: { [key: number]: string } = { + 100: "Continue", + 101: "Switching Protocols", + 102: "Processing", + 103: "Early Hints", + 200: "OK", + 201: "Created", + 202: "Accepted", + 203: "Non-Authoritative Information", + 204: "No Content", + 205: "Reset Content", + 206: "Partial Content", + 207: "Multi-Status", + 208: "Already Reported", + 226: "IM Used", + 300: "Multiple Choices", + 301: "Moved Permanently", + 302: "Found", + 303: "See Other", + 304: "Not Modified", + 305: "Use Proxy", + 307: "Temporary Redirect", + 308: "Permanent Redirect", + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Request Entity Too Large", + 414: "Request-URI Too Long", + 415: "Unsupported Media Type", + 416: "Requested Range Not Satisfiable", + 417: "Expectation Failed", + 418: "I'm a Teapot", + 421: "Misdirected Request", + 422: "Unprocessable Entity", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", +}; + +export class HTTPException extends Error { + status: number; + headers: HeadersInit; + + constructor( + status: number, + options?: { message?: string; headers?: HeadersInit; cause?: unknown }, + ) { + super(options?.message ?? HTTP_STATUS_MAPPING[status] ?? "Unknown error", { + cause: options?.cause, + }); + this.status = status; + this.headers = options?.headers ?? {}; + } +} diff --git a/libs/sdk-js/src/auth/index.ts b/libs/sdk-js/src/auth/index.ts new file mode 100644 index 000000000..c736f5cbb --- /dev/null +++ b/libs/sdk-js/src/auth/index.ts @@ -0,0 +1,36 @@ +import type { + AuthenticateCallback, + AnyCallback, + CallbackEvent, + OnCallback, + BaseAuthReturn, + ToUserLike, + BaseUser, +} from "./types.js"; + +export class Auth< + TExtra = {}, + TAuthReturn extends BaseAuthReturn = BaseAuthReturn, + TUser extends BaseUser = ToUserLike, +> { + "~handlerCache": { + authenticate?: AuthenticateCallback; + callbacks?: Record; + } = {}; + + authenticate( + cb: AuthenticateCallback, + ): Auth { + this["~handlerCache"].authenticate = cb; + return this as unknown as Auth; + } + + on(event: T, callback: OnCallback): this { + this["~handlerCache"].callbacks ??= {}; + this["~handlerCache"].callbacks[event as string] = callback as AnyCallback; + return this; + } +} + +export type { Filters, ResourceActionType } from "./types.js"; +export { HTTPException } from "./error.js"; diff --git a/libs/sdk-js/src/auth/types.ts b/libs/sdk-js/src/auth/types.ts new file mode 100644 index 000000000..d2a39e05b --- /dev/null +++ b/libs/sdk-js/src/auth/types.ts @@ -0,0 +1,345 @@ +type Maybe = T | null | undefined; +type PromiseMaybe = Promise | T; + +interface AssistantConfig { + tags?: Maybe; + recursion_limit?: Maybe; + configurable?: Maybe<{ + thread_id?: Maybe; + thread_ts?: Maybe; + [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; +} + +interface ThreadCreate { + thread_id?: Maybe; + metadata?: Maybe>; + if_exists?: Maybe<"raise" | "do_nothing">; +} + +interface ThreadRead { + thread_id?: Maybe; +} + +interface ThreadUpdate { + thread_id?: Maybe; + metadata?: Maybe>; + action?: Maybe<"interrupt" | "rollback">; +} + +interface ThreadDelete { + thread_id?: Maybe; + run_id?: Maybe; +} + +interface ThreadSearch { + thread_id?: Maybe; + status?: Maybe<"idle" | "busy" | "interrupted" | "error" | (string & {})>; + metadata?: Maybe>; + values?: Maybe>; + limit?: Maybe; + offset?: Maybe; +} + +interface CronCreate { + payload?: Maybe>; + schedule: string; + cron_id?: Maybe; + thread_id?: Maybe; + user_id?: Maybe; + end_time?: Maybe; +} + +interface CronRead { + cron_id: string; +} + +interface CronUpdate { + cron_id: string; + payload?: Maybe>; + schedule?: Maybe; +} + +interface CronDelete { + cron_id: string; +} + +interface CronSearch { + assistant_id?: Maybe; + thread_id?: Maybe; + limit?: Maybe; + offset?: Maybe; +} + +interface StorePut { + namespace: string[]; + key: string; + value: Record; +} + +interface StoreGet { + namespace: Maybe; + key: string; +} + +interface StoreSearch { + namespace?: Maybe; + filter?: Maybe>; + limit?: Maybe; + offset?: Maybe; + query?: Maybe; +} + +interface StoreListNamespaces { + namespace?: Maybe; + suffix?: Maybe; + max_depth?: Maybe; + limit?: Maybe; + offset?: Maybe; +} + +interface StoreDelete { + namespace?: Maybe; + key: string; +} + +interface RunsCreate { + thread_id?: Maybe; + assistant_id: string; + run_id: string; + status: Maybe< + "pending" | "running" | "error" | "success" | "timeout" | "interrupted" + >; + metadata?: Maybe>; + prevent_insert_if_inflight?: Maybe; + multitask_strategy?: Maybe<"interrupt" | "rollback" | "reject" | "enqueue">; + if_not_exists?: Maybe<"reject" | "create">; + after_seconds?: Maybe; + kwargs: Record; +} + +export 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"; +} + +export type BaseAuthReturn = + | { + is_authenticated?: boolean; + display_name?: string; + identity: string; + permissions: string[]; + } + | string; + +export interface BaseUser { + is_authenticated: boolean; + display_name: string; + identity: string; + permissions: string[]; +} + +export type ToUserLike = T extends string + ? { + is_authenticated: boolean; + display_name: string; + identity: string; + permissions: string[]; + } + : Omit & { + is_authenticated: boolean; + display_name: string; + }; + +type CallbackParameter< + Resource extends string = string, + Action extends string = string, + Value extends unknown = unknown, + TUser extends BaseUser = BaseUser, +> = { + resource: Resource; + action: Action; + value: Value; + user: TUser; + permissions: string[]; +}; + +type ContextMap = { + [ActionType in keyof ResourceActionType]: CallbackParameter< + ActionType extends `${infer Resource}:${string}` ? Resource : never, + ActionType, + ResourceActionType[ActionType], + BaseUser + >; +}; + +type ActionCallbackParameter< + T extends keyof ActionType, + TUser extends BaseUser = BaseUser, +> = ContextMap[ActionType[T]] & { user: TUser }; +type AuthCallbackParameter< + T extends keyof ResourceActionType, + TUser extends BaseUser = BaseUser, +> = ContextMap[T] & { user: TUser }; +type ResourceCallbackParameter< + T extends keyof ResourceType, + TUser extends BaseUser = BaseUser, +> = ContextMap[ResourceType[T]] & { user: TUser }; + +export type Filters = { + [key in TKey]: string | { [op in "$contains" | "$eq"]?: string }; +}; + +export interface AuthenticateCallback { + (request: Request): PromiseMaybe; +} + +type OnKey = keyof ResourceType | keyof ActionType | keyof ResourceActionType; + +type OnSingleParameter< + T extends OnKey, + TUser extends BaseUser = BaseUser, +> = T extends keyof ResourceType + ? ResourceCallbackParameter + : T extends keyof ActionType + ? ActionCallbackParameter + : T extends keyof ResourceActionType + ? AuthCallbackParameter + : never; + +type OnParameter< + T extends "*" | OnKey | OnKey[], + TUser extends BaseUser = BaseUser, +> = T extends OnKey[] + ? OnSingleParameter + : T extends "*" + ? AuthCallbackParameter + : T extends OnKey + ? OnSingleParameter + : never; + +export type AnyCallback = ( + request: CallbackParameter, +) => void | boolean | Filters; + +export type CallbackEvent = "*" | OnKey | OnKey[]; + +export type OnCallback< + T extends CallbackEvent, + TUser extends BaseUser = BaseUser, + TMetadata extends Record = Record, +> = ( + request: OnParameter, +) => void | boolean | Filters; 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