@kontext-dev/js-sdk 0.3.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/ai/index.cjs +12 -2
- package/dist/adapters/ai/index.cjs.map +1 -1
- package/dist/adapters/ai/index.js +12 -2
- package/dist/adapters/ai/index.js.map +1 -1
- package/dist/adapters/cloudflare/index.cjs +13 -0
- package/dist/adapters/cloudflare/index.cjs.map +1 -1
- package/dist/adapters/cloudflare/index.js +13 -0
- package/dist/adapters/cloudflare/index.js.map +1 -1
- package/dist/adapters/cloudflare/react.cjs +12 -2
- package/dist/adapters/cloudflare/react.cjs.map +1 -1
- package/dist/adapters/cloudflare/react.js +12 -2
- package/dist/adapters/cloudflare/react.js.map +1 -1
- package/dist/adapters/react/index.cjs +12 -2
- package/dist/adapters/react/index.cjs.map +1 -1
- package/dist/adapters/react/index.js +12 -2
- package/dist/adapters/react/index.js.map +1 -1
- package/dist/client/index.cjs +108 -69
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +2 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +109 -71
- package/dist/client/index.js.map +1 -1
- package/dist/errors.cjs +78 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +7 -1
- package/dist/errors.d.ts +7 -1
- package/dist/errors.js +78 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +151 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +152 -88
- package/dist/index.js.map +1 -1
- package/dist/{kontext-CgIBANFo.d.cts → kontext-CBPuE-hq.d.cts} +3 -0
- package/dist/{kontext-CgIBANFo.d.ts → kontext-CBPuE-hq.d.ts} +3 -0
- package/dist/management/index.cjs +15 -0
- package/dist/management/index.cjs.map +1 -1
- package/dist/management/index.d.cts +2 -2
- package/dist/management/index.d.ts +2 -2
- package/dist/management/index.js +15 -1
- package/dist/management/index.js.map +1 -1
- package/dist/mcp/index.cjs +32 -3
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.d.cts +7 -1
- package/dist/mcp/index.d.ts +7 -1
- package/dist/mcp/index.js +33 -4
- package/dist/mcp/index.js.map +1 -1
- package/dist/oauth/index.cjs +12 -2
- package/dist/oauth/index.cjs.map +1 -1
- package/dist/oauth/index.d.cts +1 -1
- package/dist/oauth/index.d.ts +1 -1
- package/dist/oauth/index.js +12 -2
- package/dist/oauth/index.js.map +1 -1
- package/dist/server/index.cjs +55 -20
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +56 -21
- package/dist/server/index.js.map +1 -1
- package/dist/{types-CzhnlJHW.d.cts → types-DicGI7ix.d.cts} +23 -1
- package/dist/{types-CzhnlJHW.d.ts → types-DicGI7ix.d.ts} +23 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/errors.ts","../../../src/adapters/react/index.ts","../../../src/adapters/cloudflare/react.ts"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["/**\n * Typed error classes for the Kontext SDK.\n *\n * Every error has a `kontext_` prefixed code, an auto-generated docsUrl,\n * and a `kontextError` brand for type narrowing without instanceof.\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// Base\n// ============================================================================\n\n/**\n * Base error class for all Kontext SDK errors.\n *\n * @example\n * ```typescript\n * import { isKontextError } from '@kontext-dev/js-sdk';\n *\n * try {\n * await client.connect();\n * } catch (err) {\n * if (isKontextError(err)) {\n * console.log(err.code); // \"kontext_authorization_required\"\n * console.log(err.docsUrl); // \"https://docs.kontext.dev/errors/kontext_authorization_required\"\n * }\n * }\n * ```\n */\nexport class KontextError extends Error {\n /** Brand field for type narrowing without instanceof */\n readonly kontextError = true as const;\n\n /** Machine-readable error code, always prefixed with `kontext_` */\n readonly code: string;\n\n /** HTTP status code when applicable */\n readonly statusCode?: number;\n\n /** Auto-generated link to error documentation */\n readonly docsUrl: string;\n\n /** Server request ID for debugging / support escalation */\n readonly requestId?: string;\n\n /** Contextual metadata bag (integration IDs, param names, etc.) */\n readonly meta: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options?.cause });\n this.name = \"KontextError\";\n this.code = code;\n this.statusCode = options?.statusCode;\n this.requestId = options?.requestId;\n this.meta = options?.meta ?? {};\n this.docsUrl = `https://docs.kontext.dev/errors/${code}`;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n docsUrl: this.docsUrl,\n requestId: this.requestId,\n meta: Object.keys(this.meta).length > 0 ? this.meta : undefined,\n };\n }\n\n override toString(): string {\n const parts = [`[${this.code}] ${this.message}`];\n if (this.docsUrl) parts.push(`Docs: ${this.docsUrl}`);\n if (this.requestId) parts.push(`Request ID: ${this.requestId}`);\n return parts.join(\"\\n\");\n }\n}\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\n/**\n * Check if an error is a KontextError without instanceof.\n * Works across package versions and bundler deduplication.\n */\nexport function isKontextError(err: unknown): err is KontextError {\n return (\n typeof err === \"object\" &&\n err !== null &&\n (err as Record<string, unknown>).kontextError === true\n );\n}\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Thrown when authentication is required but no valid credentials are available.\n */\nexport class AuthorizationRequiredError extends KontextError {\n readonly authorizationUrl?: string;\n\n constructor(\n message = \"Authorization required. Complete the OAuth flow to continue.\",\n options?: {\n authorizationUrl?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, \"kontext_authorization_required\", {\n statusCode: 401,\n ...options,\n });\n this.name = \"AuthorizationRequiredError\";\n this.authorizationUrl = options?.authorizationUrl;\n }\n}\n\n// ============================================================================\n// OAuth errors\n// ============================================================================\n\n/**\n * Thrown when an OAuth flow fails — state validation, token exchange,\n * missing code verifier, or provider errors.\n */\nexport class OAuthError extends KontextError {\n readonly errorCode?: string;\n readonly errorDescription?: string;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n errorCode?: string;\n errorDescription?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode ?? 400,\n ...options,\n });\n this.name = \"OAuthError\";\n this.errorCode = options?.errorCode;\n this.errorDescription = options?.errorDescription;\n }\n}\n\n// ============================================================================\n// Integration errors\n// ============================================================================\n\n/**\n * Thrown when an integration connection is required before a tool can be used.\n */\nexport class IntegrationConnectionRequiredError extends KontextError {\n readonly integrationId: string;\n readonly integrationName?: string;\n readonly connectUrl?: string;\n\n constructor(\n integrationId: string,\n options?: {\n integrationName?: string;\n connectUrl?: string;\n message?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(\n options?.message ??\n `Connection to integration \"${integrationId}\" is required. Visit the connect URL to authorize.`,\n \"kontext_integration_connection_required\",\n { statusCode: 403, ...options },\n );\n this.name = \"IntegrationConnectionRequiredError\";\n this.integrationId = integrationId;\n this.integrationName = options?.integrationName;\n this.connectUrl = options?.connectUrl;\n }\n}\n\n// ============================================================================\n// Config errors (NEW — replaces all plain Error config throws)\n// ============================================================================\n\n/**\n * Thrown when SDK configuration is invalid or missing.\n * These are deterministic errors caught at initialization time.\n */\nexport class ConfigError extends KontextError {\n constructor(\n message: string,\n code: string,\n options?: {\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, options);\n this.name = \"ConfigError\";\n }\n}\n\n// ============================================================================\n// Network errors\n// ============================================================================\n\n/**\n * Thrown when there is a network or connection error.\n */\nexport class NetworkError extends KontextError {\n constructor(\n message = \"Network error. Check your internet connection and that the server is reachable.\",\n options?: {\n cause?: unknown;\n requestId?: string;\n meta?: Record<string, unknown>;\n },\n ) {\n super(message, \"kontext_network_error\", options);\n this.name = \"NetworkError\";\n }\n}\n\n// ============================================================================\n// HTTP response errors (differentiated by code)\n// ============================================================================\n\n/**\n * Thrown when the server returns an HTTP error.\n * Use `error.code` to distinguish between specific error types.\n */\nexport class HttpError extends KontextError {\n readonly retryAfter?: number;\n readonly validationErrors?: Array<{ field: string; message: string }>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n retryAfter?: number;\n validationErrors?: Array<{ field: string; message: string }>;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode,\n ...options,\n });\n this.name = \"HttpError\";\n this.retryAfter = options?.retryAfter;\n this.validationErrors = options?.validationErrors;\n }\n}\n\n// ============================================================================\n// Network error detection (used by translateError)\n// ============================================================================\n\n/**\n * Safely access arbitrary properties on an error object.\n * Errors in JS frequently carry extra properties (code, statusCode, etc.)\n * that aren't part of the Error interface. This avoids `as unknown as` casts.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction errorProps(err: Error): Record<string, any> {\n return err;\n}\n\nconst NETWORK_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"ECONNABORTED\",\n \"EPIPE\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Detect network errors structurally rather than by string matching.\n * Checks Node.js system error codes on the error and its cause.\n */\nexport function isNetworkError(err: Error): boolean {\n if (err.name === \"AbortError\") return true;\n\n const props = errorProps(err);\n const sysCode = props.code as string | undefined;\n if (typeof sysCode === \"string\" && NETWORK_ERROR_CODES.has(sysCode))\n return true;\n\n // fetch() throws TypeError — only classify as network error when cause\n // indicates a system-level failure\n if (err.name === \"TypeError\" && err.cause instanceof Error) {\n const causeCode = errorProps(err.cause).code;\n if (typeof causeCode === \"string\" && NETWORK_ERROR_CODES.has(causeCode))\n return true;\n }\n\n return false;\n}\n\n/**\n * Detect unauthorized errors structurally.\n * Checks status code and numeric code rather than string matching on name.\n */\nexport function isUnauthorizedError(err: Error): boolean {\n const props = errorProps(err);\n\n // Check HTTP status code (most reliable)\n if (props.statusCode === 401 || props.status === 401) return true;\n\n // Check MCP SDK UnauthorizedError by name (last resort, but needed for\n // MCP SDK errors which don't set statusCode)\n if (err.name === \"UnauthorizedError\") return true;\n if (err.message === \"Unauthorized\") return true;\n\n return false;\n}\n\n// ============================================================================\n// Elicitation types\n// ============================================================================\n\nexport interface ElicitationEntry {\n readonly url: string;\n readonly message: string;\n readonly elicitationId: string;\n readonly integrationId?: string;\n readonly integrationName?: string;\n}\n\n// ============================================================================\n// HTTP error parsing\n// ============================================================================\n\n/**\n * Parse an HTTP response into an appropriate error.\n */\nexport function parseHttpError(\n statusCode: number,\n body?: unknown,\n): KontextError {\n const message =\n typeof body === \"object\" && body !== null && \"message\" in body\n ? String((body as { message: unknown }).message)\n : `HTTP ${statusCode}`;\n\n const errorCode =\n typeof body === \"object\" && body !== null && \"code\" in body\n ? String((body as { code: unknown }).code)\n : undefined;\n\n switch (statusCode) {\n case 400:\n if (\n typeof body === \"object\" &&\n body !== null &&\n \"errors\" in body &&\n Array.isArray((body as { errors: unknown }).errors)\n ) {\n return new HttpError(message, \"kontext_validation_error\", {\n statusCode: 400,\n validationErrors: (\n body as { errors: Array<{ field: string; message: string }> }\n ).errors,\n });\n }\n return new KontextError(message, errorCode ?? \"kontext_bad_request\", {\n statusCode: 400,\n });\n\n case 401:\n return new AuthorizationRequiredError(message);\n\n case 403:\n if (errorCode === \"INTEGRATION_CONNECTION_REQUIRED\") {\n const details = body as {\n integrationId?: string;\n integrationName?: string;\n connectUrl?: string;\n };\n return new IntegrationConnectionRequiredError(\n details.integrationId ?? \"unknown\",\n {\n integrationName: details.integrationName,\n connectUrl: details.connectUrl,\n message,\n },\n );\n }\n return new HttpError(message, \"kontext_policy_denied\", {\n statusCode: 403,\n meta: { policy: (body as Record<string, unknown>)?.policy },\n });\n\n case 404:\n return new HttpError(message, \"kontext_not_found\", { statusCode: 404 });\n\n case 429: {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter)\n : undefined;\n return new HttpError(\n retryAfter\n ? `Rate limit exceeded. Retry after ${retryAfter} seconds.`\n : \"Rate limit exceeded. Wait and retry.\",\n \"kontext_rate_limited\",\n { statusCode: 429, retryAfter },\n );\n }\n\n default:\n if (statusCode >= 500) {\n return new HttpError(\n `Server error (HTTP ${statusCode}): ${message}`,\n \"kontext_server_error\",\n { statusCode },\n );\n }\n return new KontextError(message, errorCode ?? \"kontext_unknown_error\", {\n statusCode,\n });\n }\n}\n","/**\n * React bindings for Kontext\n *\n * Provides the `useKontext` hook for managing Kontext MCP authentication\n * in React applications. Platform-agnostic — works with any framework\n * that sends MCP update events (e.g. Cloudflare Agents' `useAgent`).\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n *\n * function App() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * // ...\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n createContext,\n useContext,\n useMemo,\n createElement,\n type ReactNode,\n} from \"react\";\nimport { ConfigError } from \"../../errors.js\";\n\nexport interface UseKontextOptions {\n /** Custom window features for the OAuth popup (default: \"width=600,height=700\") */\n popupFeatures?: string;\n\n /** Called when the auth popup closes */\n onAuthComplete?: () => void;\n}\n\n/** MCP server state as broadcast by the Cloudflare Agents framework */\ninterface McpServerState {\n auth_url?: string | null;\n elicitation_url?: string | null;\n state?: string;\n name?: string;\n [key: string]: unknown;\n}\n\n/** MCP state object passed to `onMcpUpdate` by `useAgent` */\ninterface McpState {\n servers?: Record<string, McpServerState>;\n tools?: unknown[];\n prompts?: unknown[];\n resources?: unknown[];\n}\n\nexport interface UseKontextReturn {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /**\n * MCP update handler — pass this to your agent framework's MCP update callback.\n * Automatically opens an OAuth popup when the server requires authentication.\n *\n * @example Cloudflare Agents\n * ```typescript\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * ```\n */\n onMcpUpdate: (data: McpState) => void;\n\n /**\n * Open an elicitation URL in a popup.\n * Useful for handling URL elicitation from the KontextMcp client's\n * `onElicitationUrl` callback in a React context.\n *\n * @param url The URL to open\n */\n handleElicitationUrl: (url: string) => void;\n}\n\n/**\n * React hook for Kontext MCP authentication.\n *\n * Handles the OAuth popup flow when an MCP server requires authentication.\n * Returns a handler to wire into your agent framework and auth state.\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * function Chat() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * // ...\n * }\n * ```\n */\nexport function useKontext(options: UseKontextOptions = {}): UseKontextReturn {\n const [authenticating, setAuthenticating] = useState(false);\n const popupRef = useRef<Window | null>(null);\n\n const openPopup = useCallback(\n (url: string, name: string) => {\n if (popupRef.current) return;\n\n setAuthenticating(true);\n const popup = window.open(\n url,\n name,\n options.popupFeatures ?? \"width=600,height=700\",\n );\n popupRef.current = popup;\n\n if (popup) {\n const check = setInterval(() => {\n if (popup.closed) {\n clearInterval(check);\n popupRef.current = null;\n setAuthenticating(false);\n options.onAuthComplete?.();\n }\n }, 500);\n } else {\n // Popup was blocked by the browser\n popupRef.current = null;\n setAuthenticating(false);\n console.warn(\n \"[kontext_popup_blocked] OAuth popup was blocked by the browser. \" +\n \"The user must allow popups for this site to authenticate. \" +\n \"Docs: https://docs.kontext.dev/errors/kontext_popup_blocked\",\n );\n }\n },\n [options.popupFeatures, options.onAuthComplete],\n );\n\n const onMcpUpdate = useCallback(\n (data: McpState) => {\n if (popupRef.current) {\n return;\n }\n\n const servers = data.servers ?? {};\n\n // Check for authentication URLs\n const authUrl = Object.values(servers).find(\n (s) => s.auth_url && s.state === \"authenticating\",\n )?.auth_url;\n\n if (authUrl) {\n openPopup(authUrl, \"kontext-auth\");\n return;\n }\n\n // Check for elicitation URLs (integration OAuth via Cloudflare path)\n const elicitationUrl = Object.values(servers).find(\n (s) => s.elicitation_url,\n )?.elicitation_url;\n\n if (elicitationUrl) {\n openPopup(elicitationUrl, \"kontext-elicitation\");\n }\n },\n [openPopup],\n );\n\n const handleElicitationUrl = useCallback(\n (url: string) => {\n openPopup(url, \"kontext-elicitation\");\n },\n [openPopup],\n );\n\n return { authenticating, onMcpUpdate, handleElicitationUrl };\n}\n\n// ============================================================================\n// Context Provider — \"wrap and forget\" alternative to useKontext\n// ============================================================================\n\n/** Values exposed by KontextProvider via React Context */\nexport interface KontextContextValue {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /** MCP update handler — injected automatically by useKontextAgent */\n onMcpUpdate: (data: McpState) => void;\n\n /** Open an elicitation URL in a popup */\n handleElicitationUrl: (url: string) => void;\n}\n\nexport interface KontextProviderProps extends UseKontextOptions {\n children: ReactNode;\n}\n\nconst KontextContext = createContext<KontextContextValue | null>(null);\n\n/**\n * React Context Provider that manages Kontext auth/elicitation popups.\n *\n * Wrap your app (or the subtree that uses Kontext) with this provider,\n * then use `useKontextAgent()` (from `@kontext-dev/js-sdk/react/cloudflare`)\n * or `useKontextContext()` to access the context directly.\n *\n * @example\n * ```tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n * ```\n */\nexport function KontextProvider({\n children,\n ...options\n}: KontextProviderProps) {\n const { authenticating, onMcpUpdate, handleElicitationUrl } =\n useKontext(options);\n\n const value = useMemo<KontextContextValue>(\n () => ({ authenticating, onMcpUpdate, handleElicitationUrl }),\n [authenticating, onMcpUpdate, handleElicitationUrl],\n );\n\n return createElement(KontextContext.Provider, { value }, children);\n}\n\n/**\n * Access the KontextProvider context directly.\n * Must be used within a `<KontextProvider>`.\n */\nexport function useKontextContext(): KontextContextValue {\n const ctx = useContext(KontextContext);\n if (!ctx) {\n throw new ConfigError(\n \"useKontextContext must be used within a <KontextProvider>. Wrap your component tree with KontextProvider.\",\n \"kontext_context_missing\",\n );\n }\n return ctx;\n}\n","/**\n * Cloudflare Agents + React bindings for Kontext\n *\n * Provides `useKontextAgent` — a drop-in replacement for `useAgent` from\n * `agents/react` that automatically wires Kontext auth popups via the\n * `<KontextProvider>` context.\n *\n * @example\n * ```tsx\n * // client.tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n *\n * // app.tsx\n * import { useKontextAgent } from \"@kontext-dev/js-sdk/react/cloudflare\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * const agent = useKontextAgent({ agent: \"chat\" });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { useAgent, type UseAgentOptions } from \"agents/react\";\nimport { useKontextContext } from \"../react/index.js\";\n\n// Re-export provider pieces so consumers can import from one place\nexport { KontextProvider, useKontextContext } from \"../react/index.js\";\nexport type {\n KontextContextValue,\n KontextProviderProps,\n} from \"../react/index.js\";\n\n/**\n * Options for `useKontextAgent`.\n *\n * Same as `UseAgentOptions` from `agents/react` but without `onMcpUpdate`\n * (injected automatically from KontextProvider).\n */\nexport type UseKontextAgentOptions<State = unknown> = Omit<\n UseAgentOptions<State>,\n \"onMcpUpdate\"\n>;\n\n/**\n * Return type of `useKontextAgent` — matches `useAgent` from `agents/react`.\n *\n * Defined structurally to avoid a hard dependency on `partysocket` types.\n * In practice the returned object is a `PartySocket` instance with extra\n * agent-specific properties.\n */\nexport interface UseKontextAgentReturn<State = unknown> {\n agent: string;\n name: string;\n identified: boolean;\n ready: Promise<void>;\n setState: (state: State) => void;\n call: <T = unknown>(method: string, args?: unknown[]) => Promise<T>;\n stub: Record<string, (...args: unknown[]) => unknown>;\n // WebSocket-compatible surface\n send: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void;\n close: (code?: number, reason?: string) => void;\n addEventListener: WebSocket[\"addEventListener\"];\n removeEventListener: WebSocket[\"removeEventListener\"];\n}\n\n/**\n * Drop-in replacement for `useAgent` that injects Kontext auth + elicitation.\n *\n * Must be used within a `<KontextProvider>`. Automatically:\n * - Injects `onMcpUpdate` for Kontext OAuth popup handling\n * - Chains through any user-provided `onMessage` handler\n */\nexport function useKontextAgent<State = unknown>(\n options: UseKontextAgentOptions<State>,\n): UseKontextAgentReturn<State> {\n const { onMcpUpdate } = useKontextContext();\n\n return useAgent<State>({\n ...options,\n onMcpUpdate,\n }) as unknown as UseKontextAgentReturn<State>;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/errors.ts","../../../src/adapters/react/index.ts","../../../src/adapters/cloudflare/react.ts"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["/**\n * Typed error classes for the Kontext SDK.\n *\n * Every error has a `kontext_` prefixed code, an auto-generated docsUrl,\n * and a `kontextError` brand for type narrowing without instanceof.\n *\n * @packageDocumentation\n */\n\nimport { ErrorCode } from \"@modelcontextprotocol/sdk/types.js\";\n\n// ============================================================================\n// Base\n// ============================================================================\n\n/**\n * Base error class for all Kontext SDK errors.\n *\n * @example\n * ```typescript\n * import { isKontextError } from '@kontext-dev/js-sdk';\n *\n * try {\n * await client.connect();\n * } catch (err) {\n * if (isKontextError(err)) {\n * console.log(err.code); // \"kontext_authorization_required\"\n * console.log(err.docsUrl); // \"https://docs.kontext.dev/errors/kontext_authorization_required\"\n * }\n * }\n * ```\n */\nexport class KontextError extends Error {\n /** Brand field for type narrowing without instanceof */\n readonly kontextError = true as const;\n\n /** Machine-readable error code, always prefixed with `kontext_` */\n readonly code: string;\n\n /** HTTP status code when applicable */\n readonly statusCode?: number;\n\n /** Auto-generated link to error documentation */\n readonly docsUrl: string;\n\n /** Server request ID for debugging / support escalation */\n readonly requestId?: string;\n\n /** Contextual metadata bag (integration IDs, param names, etc.) */\n readonly meta: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options?.cause });\n this.name = \"KontextError\";\n this.code = code;\n this.statusCode = options?.statusCode;\n this.requestId = options?.requestId;\n this.meta = options?.meta ?? {};\n this.docsUrl = `https://docs.kontext.dev/errors/${code}`;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n docsUrl: this.docsUrl,\n requestId: this.requestId,\n meta: Object.keys(this.meta).length > 0 ? this.meta : undefined,\n };\n }\n\n override toString(): string {\n const parts = [`[${this.code}] ${this.message}`];\n if (this.docsUrl) parts.push(`Docs: ${this.docsUrl}`);\n if (this.requestId) parts.push(`Request ID: ${this.requestId}`);\n return parts.join(\"\\n\");\n }\n}\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\n/**\n * Check if an error is a KontextError without instanceof.\n * Works across package versions and bundler deduplication.\n */\nexport function isKontextError(err: unknown): err is KontextError {\n return (\n typeof err === \"object\" &&\n err !== null &&\n (err as Record<string, unknown>).kontextError === true\n );\n}\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Thrown when authentication is required but no valid credentials are available.\n */\nexport class AuthorizationRequiredError extends KontextError {\n readonly authorizationUrl?: string;\n\n constructor(\n message = \"Authorization required. Complete the OAuth flow to continue.\",\n options?: {\n authorizationUrl?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, \"kontext_authorization_required\", {\n statusCode: 401,\n ...options,\n });\n this.name = \"AuthorizationRequiredError\";\n this.authorizationUrl = options?.authorizationUrl;\n }\n}\n\n// ============================================================================\n// OAuth errors\n// ============================================================================\n\n/**\n * Thrown when an OAuth flow fails — state validation, token exchange,\n * missing code verifier, or provider errors.\n */\nexport class OAuthError extends KontextError {\n readonly errorCode?: string;\n readonly errorDescription?: string;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n errorCode?: string;\n errorDescription?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode ?? 400,\n ...options,\n });\n this.name = \"OAuthError\";\n this.errorCode = options?.errorCode;\n this.errorDescription = options?.errorDescription;\n }\n}\n\n// ============================================================================\n// Integration errors\n// ============================================================================\n\n/**\n * Thrown when an integration connection is required before a tool can be used.\n */\nexport class IntegrationConnectionRequiredError extends KontextError {\n readonly integrationId: string;\n readonly integrationName?: string;\n readonly connectUrl?: string;\n\n constructor(\n integrationId: string,\n options?: {\n integrationName?: string;\n connectUrl?: string;\n message?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(\n options?.message ??\n `Connection to integration \"${integrationId}\" is required. Visit the connect URL to authorize.`,\n \"kontext_integration_connection_required\",\n { statusCode: 403, ...options },\n );\n this.name = \"IntegrationConnectionRequiredError\";\n this.integrationId = integrationId;\n this.integrationName = options?.integrationName;\n this.connectUrl = options?.connectUrl;\n }\n}\n\n// ============================================================================\n// Config errors (NEW — replaces all plain Error config throws)\n// ============================================================================\n\n/**\n * Thrown when SDK configuration is invalid or missing.\n * These are deterministic errors caught at initialization time.\n */\nexport class ConfigError extends KontextError {\n constructor(\n message: string,\n code: string,\n options?: {\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, options);\n this.name = \"ConfigError\";\n }\n}\n\n// ============================================================================\n// Network errors\n// ============================================================================\n\n/**\n * Thrown when there is a network or connection error.\n */\nexport class NetworkError extends KontextError {\n constructor(\n message = \"Network error. Check your internet connection and that the server is reachable.\",\n options?: {\n cause?: unknown;\n requestId?: string;\n meta?: Record<string, unknown>;\n },\n ) {\n super(message, \"kontext_network_error\", options);\n this.name = \"NetworkError\";\n }\n}\n\n// ============================================================================\n// HTTP response errors (differentiated by code)\n// ============================================================================\n\n/**\n * Thrown when the server returns an HTTP error.\n * Use `error.code` to distinguish between specific error types.\n */\nexport class HttpError extends KontextError {\n readonly retryAfter?: number;\n readonly validationErrors?: Array<{ field: string; message: string }>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n retryAfter?: number;\n validationErrors?: Array<{ field: string; message: string }>;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode,\n ...options,\n });\n this.name = \"HttpError\";\n this.retryAfter = options?.retryAfter;\n this.validationErrors = options?.validationErrors;\n }\n}\n\n// ============================================================================\n// Network error detection (used by translateError)\n// ============================================================================\n\n/**\n * Safely access arbitrary properties on an error object.\n * Errors in JS frequently carry extra properties (code, statusCode, etc.)\n * that aren't part of the Error interface. This avoids `as unknown as` casts.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction errorProps(err: Error): Record<string, any> {\n return err;\n}\n\nconst NETWORK_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"ECONNABORTED\",\n \"EPIPE\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Detect network errors structurally rather than by string matching.\n * Checks Node.js system error codes on the error and its cause.\n */\nexport function isNetworkError(err: Error): boolean {\n if (err.name === \"AbortError\") return true;\n\n const props = errorProps(err);\n const sysCode = props.code as string | undefined;\n if (typeof sysCode === \"string\" && NETWORK_ERROR_CODES.has(sysCode))\n return true;\n\n // fetch() throws TypeError — only classify as network error when cause\n // indicates a system-level failure\n if (err.name === \"TypeError\" && err.cause instanceof Error) {\n const causeCode = errorProps(err.cause).code;\n if (typeof causeCode === \"string\" && NETWORK_ERROR_CODES.has(causeCode))\n return true;\n }\n\n // Browser fetch() failures: TypeError with known messages, no system error code\n if (err.name === \"TypeError\") {\n const msg = err.message.toLowerCase();\n if (\n msg === \"failed to fetch\" ||\n msg === \"load failed\" ||\n msg.includes(\"networkerror\")\n ) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Detect unauthorized errors structurally.\n * Checks status code and numeric code rather than string matching on name.\n */\nexport function isUnauthorizedError(err: Error): boolean {\n const props = errorProps(err);\n\n // Check HTTP status code (most reliable)\n if (props.statusCode === 401 || props.status === 401) return true;\n\n // StreamableHTTPError stores status as `.code`\n if (props.code === 401) return true;\n\n // MCP SDK UnauthorizedError extends Error without setting `.name`,\n // so `.name` is \"Error\". Use constructor name as fallback.\n if (err.name === \"UnauthorizedError\") return true;\n if (err.constructor?.name === \"UnauthorizedError\") return true;\n if (err.message === \"Unauthorized\") return true;\n\n return false;\n}\n\n// ============================================================================\n// Elicitation types\n// ============================================================================\n\nexport interface ElicitationEntry {\n readonly url: string;\n readonly message: string;\n readonly elicitationId: string;\n readonly integrationId?: string;\n readonly integrationName?: string;\n}\n\n// ============================================================================\n// HTTP error parsing\n// ============================================================================\n\n/**\n * Parse an HTTP response into an appropriate error.\n */\nexport function parseHttpError(\n statusCode: number,\n body?: unknown,\n): KontextError {\n const message =\n typeof body === \"object\" && body !== null && \"message\" in body\n ? String((body as { message: unknown }).message)\n : `HTTP ${statusCode}`;\n\n const errorCode =\n typeof body === \"object\" && body !== null && \"code\" in body\n ? String((body as { code: unknown }).code)\n : undefined;\n\n switch (statusCode) {\n case 400:\n if (\n typeof body === \"object\" &&\n body !== null &&\n \"errors\" in body &&\n Array.isArray((body as { errors: unknown }).errors)\n ) {\n return new HttpError(message, \"kontext_validation_error\", {\n statusCode: 400,\n validationErrors: (\n body as { errors: Array<{ field: string; message: string }> }\n ).errors,\n });\n }\n return new KontextError(message, errorCode ?? \"kontext_bad_request\", {\n statusCode: 400,\n });\n\n case 401:\n return new AuthorizationRequiredError(message);\n\n case 403:\n if (errorCode === \"INTEGRATION_CONNECTION_REQUIRED\") {\n const details = body as {\n integrationId?: string;\n integrationName?: string;\n connectUrl?: string;\n };\n return new IntegrationConnectionRequiredError(\n details.integrationId ?? \"unknown\",\n {\n integrationName: details.integrationName,\n connectUrl: details.connectUrl,\n message,\n },\n );\n }\n return new HttpError(message, \"kontext_policy_denied\", {\n statusCode: 403,\n meta: { policy: (body as Record<string, unknown>)?.policy },\n });\n\n case 404:\n return new HttpError(message, \"kontext_not_found\", { statusCode: 404 });\n\n case 429: {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter)\n : undefined;\n return new HttpError(\n retryAfter\n ? `Rate limit exceeded. Retry after ${retryAfter} seconds.`\n : \"Rate limit exceeded. Wait and retry.\",\n \"kontext_rate_limited\",\n { statusCode: 429, retryAfter },\n );\n }\n\n default:\n if (statusCode >= 500) {\n return new HttpError(\n `Server error (HTTP ${statusCode}): ${message}`,\n \"kontext_server_error\",\n { statusCode },\n );\n }\n return new KontextError(message, errorCode ?? \"kontext_unknown_error\", {\n statusCode,\n });\n }\n}\n\n// ============================================================================\n// MCP error translation\n// ============================================================================\n\nconst MCP_CODE_MAP: Record<number, { code: string; statusCode?: number }> = {\n [ErrorCode.ParseError]: { code: \"kontext_mcp_parse_error\" },\n [ErrorCode.InvalidRequest]: { code: \"kontext_mcp_invalid_request\" },\n [ErrorCode.MethodNotFound]: { code: \"kontext_mcp_method_not_found\" },\n [ErrorCode.InvalidParams]: { code: \"kontext_mcp_invalid_params\" },\n [ErrorCode.InternalError]: {\n code: \"kontext_mcp_internal_error\",\n statusCode: 500,\n },\n [ErrorCode.RequestTimeout]: {\n code: \"kontext_mcp_session_expired\",\n statusCode: 401,\n },\n [ErrorCode.ConnectionClosed]: { code: \"kontext_mcp_session_error\" },\n};\n\n/**\n * Translate external errors into KontextError instances.\n * Uses structural checks (numeric codes, status codes, system error codes)\n * rather than fragile string matching.\n */\nexport function translateError(err: unknown): KontextError {\n if (isKontextError(err)) return err as KontextError;\n\n if (!(err instanceof Error)) {\n return new KontextError(String(err), \"kontext_unknown_error\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const props: Record<string, any> = err;\n\n // 1. MCP protocol -32042 (URL elicitation required) — check numeric code\n if (props.code === ErrorCode.UrlElicitationRequired) {\n const elicitations = (props.elicitations ?? props.data?.elicitations) as\n | ElicitationEntry[]\n | undefined;\n const elicitation = elicitations?.[0];\n return new IntegrationConnectionRequiredError(\n elicitation?.integrationId ?? \"unknown\",\n {\n integrationName: elicitation?.integrationName,\n connectUrl: elicitation?.url,\n message: elicitation?.message,\n cause: err,\n },\n );\n }\n\n // 2. MCP JSON-RPC errors (standard and server-defined ranges)\n if (typeof props.code === \"number\" && props.code < 0) {\n const entry = MCP_CODE_MAP[props.code];\n if (entry) {\n return new KontextError(err.message, entry.code, {\n statusCode: entry.statusCode,\n cause: err,\n });\n }\n return new KontextError(err.message, \"kontext_mcp_error\", {\n cause: err,\n meta: { mcpCode: props.code },\n });\n }\n\n // 3. HTTP status on the error (from MCP SDK or fetch wrappers)\n // StreamableHTTPError stores HTTP status as `.code` (not `.statusCode`/`.status`),\n // so check `.code` as well when it's in the HTTP error range (400–599).\n const statusCode = (props.statusCode ??\n props.status ??\n (typeof props.code === \"number\" && props.code >= 400 && props.code < 600\n ? props.code\n : undefined)) as number | undefined;\n if (typeof statusCode === \"number\" && statusCode >= 400) {\n if (statusCode === 401) {\n return new AuthorizationRequiredError(err.message, { cause: err });\n }\n return new KontextError(err.message, \"kontext_server_error\", {\n statusCode,\n cause: err,\n });\n }\n\n // 4. Auth errors — structural check\n if (isUnauthorizedError(err)) {\n return new AuthorizationRequiredError(err.message, { cause: err });\n }\n\n // 5. Network errors — structural check (system error codes, not string matching)\n if (isNetworkError(err)) {\n return new NetworkError(err.message, { cause: err });\n }\n\n // 6. Fallback\n return new KontextError(err.message, \"kontext_unknown_error\", {\n cause: err,\n });\n}\n","/**\n * React bindings for Kontext\n *\n * Provides the `useKontext` hook for managing Kontext MCP authentication\n * in React applications. Platform-agnostic — works with any framework\n * that sends MCP update events (e.g. Cloudflare Agents' `useAgent`).\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n *\n * function App() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * // ...\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n createContext,\n useContext,\n useMemo,\n createElement,\n type ReactNode,\n} from \"react\";\nimport { ConfigError } from \"../../errors.js\";\n\nexport interface UseKontextOptions {\n /** Custom window features for the OAuth popup (default: \"width=600,height=700\") */\n popupFeatures?: string;\n\n /** Called when the auth popup closes */\n onAuthComplete?: () => void;\n}\n\n/** MCP server state as broadcast by the Cloudflare Agents framework */\ninterface McpServerState {\n auth_url?: string | null;\n elicitation_url?: string | null;\n state?: string;\n name?: string;\n [key: string]: unknown;\n}\n\n/** MCP state object passed to `onMcpUpdate` by `useAgent` */\ninterface McpState {\n servers?: Record<string, McpServerState>;\n tools?: unknown[];\n prompts?: unknown[];\n resources?: unknown[];\n}\n\nexport interface UseKontextReturn {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /**\n * MCP update handler — pass this to your agent framework's MCP update callback.\n * Automatically opens an OAuth popup when the server requires authentication.\n *\n * @example Cloudflare Agents\n * ```typescript\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * ```\n */\n onMcpUpdate: (data: McpState) => void;\n\n /**\n * Open an elicitation URL in a popup.\n * Useful for handling URL elicitation from the KontextMcp client's\n * `onElicitationUrl` callback in a React context.\n *\n * @param url The URL to open\n */\n handleElicitationUrl: (url: string) => void;\n}\n\n/**\n * React hook for Kontext MCP authentication.\n *\n * Handles the OAuth popup flow when an MCP server requires authentication.\n * Returns a handler to wire into your agent framework and auth state.\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * function Chat() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * // ...\n * }\n * ```\n */\nexport function useKontext(options: UseKontextOptions = {}): UseKontextReturn {\n const [authenticating, setAuthenticating] = useState(false);\n const popupRef = useRef<Window | null>(null);\n\n const openPopup = useCallback(\n (url: string, name: string) => {\n if (popupRef.current) return;\n\n setAuthenticating(true);\n const popup = window.open(\n url,\n name,\n options.popupFeatures ?? \"width=600,height=700\",\n );\n popupRef.current = popup;\n\n if (popup) {\n const check = setInterval(() => {\n if (popup.closed) {\n clearInterval(check);\n popupRef.current = null;\n setAuthenticating(false);\n options.onAuthComplete?.();\n }\n }, 500);\n } else {\n // Popup was blocked by the browser\n popupRef.current = null;\n setAuthenticating(false);\n console.warn(\n \"[kontext_popup_blocked] OAuth popup was blocked by the browser. \" +\n \"The user must allow popups for this site to authenticate. \" +\n \"Docs: https://docs.kontext.dev/errors/kontext_popup_blocked\",\n );\n }\n },\n [options.popupFeatures, options.onAuthComplete],\n );\n\n const onMcpUpdate = useCallback(\n (data: McpState) => {\n if (popupRef.current) {\n return;\n }\n\n const servers = data.servers ?? {};\n\n // Check for authentication URLs\n const authUrl = Object.values(servers).find(\n (s) => s.auth_url && s.state === \"authenticating\",\n )?.auth_url;\n\n if (authUrl) {\n openPopup(authUrl, \"kontext-auth\");\n return;\n }\n\n // Check for elicitation URLs (integration OAuth via Cloudflare path)\n const elicitationUrl = Object.values(servers).find(\n (s) => s.elicitation_url,\n )?.elicitation_url;\n\n if (elicitationUrl) {\n openPopup(elicitationUrl, \"kontext-elicitation\");\n }\n },\n [openPopup],\n );\n\n const handleElicitationUrl = useCallback(\n (url: string) => {\n openPopup(url, \"kontext-elicitation\");\n },\n [openPopup],\n );\n\n return { authenticating, onMcpUpdate, handleElicitationUrl };\n}\n\n// ============================================================================\n// Context Provider — \"wrap and forget\" alternative to useKontext\n// ============================================================================\n\n/** Values exposed by KontextProvider via React Context */\nexport interface KontextContextValue {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /** MCP update handler — injected automatically by useKontextAgent */\n onMcpUpdate: (data: McpState) => void;\n\n /** Open an elicitation URL in a popup */\n handleElicitationUrl: (url: string) => void;\n}\n\nexport interface KontextProviderProps extends UseKontextOptions {\n children: ReactNode;\n}\n\nconst KontextContext = createContext<KontextContextValue | null>(null);\n\n/**\n * React Context Provider that manages Kontext auth/elicitation popups.\n *\n * Wrap your app (or the subtree that uses Kontext) with this provider,\n * then use `useKontextAgent()` (from `@kontext-dev/js-sdk/react/cloudflare`)\n * or `useKontextContext()` to access the context directly.\n *\n * @example\n * ```tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n * ```\n */\nexport function KontextProvider({\n children,\n ...options\n}: KontextProviderProps) {\n const { authenticating, onMcpUpdate, handleElicitationUrl } =\n useKontext(options);\n\n const value = useMemo<KontextContextValue>(\n () => ({ authenticating, onMcpUpdate, handleElicitationUrl }),\n [authenticating, onMcpUpdate, handleElicitationUrl],\n );\n\n return createElement(KontextContext.Provider, { value }, children);\n}\n\n/**\n * Access the KontextProvider context directly.\n * Must be used within a `<KontextProvider>`.\n */\nexport function useKontextContext(): KontextContextValue {\n const ctx = useContext(KontextContext);\n if (!ctx) {\n throw new ConfigError(\n \"useKontextContext must be used within a <KontextProvider>. Wrap your component tree with KontextProvider.\",\n \"kontext_context_missing\",\n );\n }\n return ctx;\n}\n","/**\n * Cloudflare Agents + React bindings for Kontext\n *\n * Provides `useKontextAgent` — a drop-in replacement for `useAgent` from\n * `agents/react` that automatically wires Kontext auth popups via the\n * `<KontextProvider>` context.\n *\n * @example\n * ```tsx\n * // client.tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n *\n * // app.tsx\n * import { useKontextAgent } from \"@kontext-dev/js-sdk/react/cloudflare\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * const agent = useKontextAgent({ agent: \"chat\" });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * ```\n *\n * @packageDocumentation\n */\n\nimport { useAgent, type UseAgentOptions } from \"agents/react\";\nimport { useKontextContext } from \"../react/index.js\";\n\n// Re-export provider pieces so consumers can import from one place\nexport { KontextProvider, useKontextContext } from \"../react/index.js\";\nexport type {\n KontextContextValue,\n KontextProviderProps,\n} from \"../react/index.js\";\n\n/**\n * Options for `useKontextAgent`.\n *\n * Same as `UseAgentOptions` from `agents/react` but without `onMcpUpdate`\n * (injected automatically from KontextProvider).\n */\nexport type UseKontextAgentOptions<State = unknown> = Omit<\n UseAgentOptions<State>,\n \"onMcpUpdate\"\n>;\n\n/**\n * Return type of `useKontextAgent` — matches `useAgent` from `agents/react`.\n *\n * Defined structurally to avoid a hard dependency on `partysocket` types.\n * In practice the returned object is a `PartySocket` instance with extra\n * agent-specific properties.\n */\nexport interface UseKontextAgentReturn<State = unknown> {\n agent: string;\n name: string;\n identified: boolean;\n ready: Promise<void>;\n setState: (state: State) => void;\n call: <T = unknown>(method: string, args?: unknown[]) => Promise<T>;\n stub: Record<string, (...args: unknown[]) => unknown>;\n // WebSocket-compatible surface\n send: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void;\n close: (code?: number, reason?: string) => void;\n addEventListener: WebSocket[\"addEventListener\"];\n removeEventListener: WebSocket[\"removeEventListener\"];\n}\n\n/**\n * Drop-in replacement for `useAgent` that injects Kontext auth + elicitation.\n *\n * Must be used within a `<KontextProvider>`. Automatically:\n * - Injects `onMcpUpdate` for Kontext OAuth popup handling\n * - Chains through any user-provided `onMessage` handler\n */\nexport function useKontextAgent<State = unknown>(\n options: UseKontextAgentOptions<State>,\n): UseKontextAgentReturn<State> {\n const { onMcpUpdate } = useKontextContext();\n\n return useAgent<State>({\n ...options,\n onMcpUpdate,\n }) as unknown as UseKontextAgentReturn<State>;\n}\n"]}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
|
+
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
4
5
|
|
|
5
6
|
// src/adapters/react/index.ts
|
|
6
|
-
|
|
7
|
-
// src/errors.ts
|
|
8
7
|
var KontextError = class extends Error {
|
|
9
8
|
/** Brand field for type narrowing without instanceof */
|
|
10
9
|
kontextError = true;
|
|
@@ -52,6 +51,17 @@ var ConfigError = class extends KontextError {
|
|
|
52
51
|
this.name = "ConfigError";
|
|
53
52
|
}
|
|
54
53
|
};
|
|
54
|
+
({
|
|
55
|
+
[types_js.ErrorCode.ParseError]: { },
|
|
56
|
+
[types_js.ErrorCode.InvalidRequest]: { },
|
|
57
|
+
[types_js.ErrorCode.MethodNotFound]: { },
|
|
58
|
+
[types_js.ErrorCode.InvalidParams]: { },
|
|
59
|
+
[types_js.ErrorCode.InternalError]: {
|
|
60
|
+
},
|
|
61
|
+
[types_js.ErrorCode.RequestTimeout]: {
|
|
62
|
+
},
|
|
63
|
+
[types_js.ErrorCode.ConnectionClosed]: { }
|
|
64
|
+
});
|
|
55
65
|
|
|
56
66
|
// src/adapters/react/index.ts
|
|
57
67
|
function useKontext(options = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/errors.ts","../../../src/adapters/react/index.ts"],"names":["useState","useRef","useCallback","createContext","useMemo","createElement","useContext"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed error classes for the Kontext SDK.\n *\n * Every error has a `kontext_` prefixed code, an auto-generated docsUrl,\n * and a `kontextError` brand for type narrowing without instanceof.\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// Base\n// ============================================================================\n\n/**\n * Base error class for all Kontext SDK errors.\n *\n * @example\n * ```typescript\n * import { isKontextError } from '@kontext-dev/js-sdk';\n *\n * try {\n * await client.connect();\n * } catch (err) {\n * if (isKontextError(err)) {\n * console.log(err.code); // \"kontext_authorization_required\"\n * console.log(err.docsUrl); // \"https://docs.kontext.dev/errors/kontext_authorization_required\"\n * }\n * }\n * ```\n */\nexport class KontextError extends Error {\n /** Brand field for type narrowing without instanceof */\n readonly kontextError = true as const;\n\n /** Machine-readable error code, always prefixed with `kontext_` */\n readonly code: string;\n\n /** HTTP status code when applicable */\n readonly statusCode?: number;\n\n /** Auto-generated link to error documentation */\n readonly docsUrl: string;\n\n /** Server request ID for debugging / support escalation */\n readonly requestId?: string;\n\n /** Contextual metadata bag (integration IDs, param names, etc.) */\n readonly meta: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options?.cause });\n this.name = \"KontextError\";\n this.code = code;\n this.statusCode = options?.statusCode;\n this.requestId = options?.requestId;\n this.meta = options?.meta ?? {};\n this.docsUrl = `https://docs.kontext.dev/errors/${code}`;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n docsUrl: this.docsUrl,\n requestId: this.requestId,\n meta: Object.keys(this.meta).length > 0 ? this.meta : undefined,\n };\n }\n\n override toString(): string {\n const parts = [`[${this.code}] ${this.message}`];\n if (this.docsUrl) parts.push(`Docs: ${this.docsUrl}`);\n if (this.requestId) parts.push(`Request ID: ${this.requestId}`);\n return parts.join(\"\\n\");\n }\n}\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\n/**\n * Check if an error is a KontextError without instanceof.\n * Works across package versions and bundler deduplication.\n */\nexport function isKontextError(err: unknown): err is KontextError {\n return (\n typeof err === \"object\" &&\n err !== null &&\n (err as Record<string, unknown>).kontextError === true\n );\n}\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Thrown when authentication is required but no valid credentials are available.\n */\nexport class AuthorizationRequiredError extends KontextError {\n readonly authorizationUrl?: string;\n\n constructor(\n message = \"Authorization required. Complete the OAuth flow to continue.\",\n options?: {\n authorizationUrl?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, \"kontext_authorization_required\", {\n statusCode: 401,\n ...options,\n });\n this.name = \"AuthorizationRequiredError\";\n this.authorizationUrl = options?.authorizationUrl;\n }\n}\n\n// ============================================================================\n// OAuth errors\n// ============================================================================\n\n/**\n * Thrown when an OAuth flow fails — state validation, token exchange,\n * missing code verifier, or provider errors.\n */\nexport class OAuthError extends KontextError {\n readonly errorCode?: string;\n readonly errorDescription?: string;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n errorCode?: string;\n errorDescription?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode ?? 400,\n ...options,\n });\n this.name = \"OAuthError\";\n this.errorCode = options?.errorCode;\n this.errorDescription = options?.errorDescription;\n }\n}\n\n// ============================================================================\n// Integration errors\n// ============================================================================\n\n/**\n * Thrown when an integration connection is required before a tool can be used.\n */\nexport class IntegrationConnectionRequiredError extends KontextError {\n readonly integrationId: string;\n readonly integrationName?: string;\n readonly connectUrl?: string;\n\n constructor(\n integrationId: string,\n options?: {\n integrationName?: string;\n connectUrl?: string;\n message?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(\n options?.message ??\n `Connection to integration \"${integrationId}\" is required. Visit the connect URL to authorize.`,\n \"kontext_integration_connection_required\",\n { statusCode: 403, ...options },\n );\n this.name = \"IntegrationConnectionRequiredError\";\n this.integrationId = integrationId;\n this.integrationName = options?.integrationName;\n this.connectUrl = options?.connectUrl;\n }\n}\n\n// ============================================================================\n// Config errors (NEW — replaces all plain Error config throws)\n// ============================================================================\n\n/**\n * Thrown when SDK configuration is invalid or missing.\n * These are deterministic errors caught at initialization time.\n */\nexport class ConfigError extends KontextError {\n constructor(\n message: string,\n code: string,\n options?: {\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, options);\n this.name = \"ConfigError\";\n }\n}\n\n// ============================================================================\n// Network errors\n// ============================================================================\n\n/**\n * Thrown when there is a network or connection error.\n */\nexport class NetworkError extends KontextError {\n constructor(\n message = \"Network error. Check your internet connection and that the server is reachable.\",\n options?: {\n cause?: unknown;\n requestId?: string;\n meta?: Record<string, unknown>;\n },\n ) {\n super(message, \"kontext_network_error\", options);\n this.name = \"NetworkError\";\n }\n}\n\n// ============================================================================\n// HTTP response errors (differentiated by code)\n// ============================================================================\n\n/**\n * Thrown when the server returns an HTTP error.\n * Use `error.code` to distinguish between specific error types.\n */\nexport class HttpError extends KontextError {\n readonly retryAfter?: number;\n readonly validationErrors?: Array<{ field: string; message: string }>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n retryAfter?: number;\n validationErrors?: Array<{ field: string; message: string }>;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode,\n ...options,\n });\n this.name = \"HttpError\";\n this.retryAfter = options?.retryAfter;\n this.validationErrors = options?.validationErrors;\n }\n}\n\n// ============================================================================\n// Network error detection (used by translateError)\n// ============================================================================\n\n/**\n * Safely access arbitrary properties on an error object.\n * Errors in JS frequently carry extra properties (code, statusCode, etc.)\n * that aren't part of the Error interface. This avoids `as unknown as` casts.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction errorProps(err: Error): Record<string, any> {\n return err;\n}\n\nconst NETWORK_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"ECONNABORTED\",\n \"EPIPE\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Detect network errors structurally rather than by string matching.\n * Checks Node.js system error codes on the error and its cause.\n */\nexport function isNetworkError(err: Error): boolean {\n if (err.name === \"AbortError\") return true;\n\n const props = errorProps(err);\n const sysCode = props.code as string | undefined;\n if (typeof sysCode === \"string\" && NETWORK_ERROR_CODES.has(sysCode))\n return true;\n\n // fetch() throws TypeError — only classify as network error when cause\n // indicates a system-level failure\n if (err.name === \"TypeError\" && err.cause instanceof Error) {\n const causeCode = errorProps(err.cause).code;\n if (typeof causeCode === \"string\" && NETWORK_ERROR_CODES.has(causeCode))\n return true;\n }\n\n return false;\n}\n\n/**\n * Detect unauthorized errors structurally.\n * Checks status code and numeric code rather than string matching on name.\n */\nexport function isUnauthorizedError(err: Error): boolean {\n const props = errorProps(err);\n\n // Check HTTP status code (most reliable)\n if (props.statusCode === 401 || props.status === 401) return true;\n\n // Check MCP SDK UnauthorizedError by name (last resort, but needed for\n // MCP SDK errors which don't set statusCode)\n if (err.name === \"UnauthorizedError\") return true;\n if (err.message === \"Unauthorized\") return true;\n\n return false;\n}\n\n// ============================================================================\n// Elicitation types\n// ============================================================================\n\nexport interface ElicitationEntry {\n readonly url: string;\n readonly message: string;\n readonly elicitationId: string;\n readonly integrationId?: string;\n readonly integrationName?: string;\n}\n\n// ============================================================================\n// HTTP error parsing\n// ============================================================================\n\n/**\n * Parse an HTTP response into an appropriate error.\n */\nexport function parseHttpError(\n statusCode: number,\n body?: unknown,\n): KontextError {\n const message =\n typeof body === \"object\" && body !== null && \"message\" in body\n ? String((body as { message: unknown }).message)\n : `HTTP ${statusCode}`;\n\n const errorCode =\n typeof body === \"object\" && body !== null && \"code\" in body\n ? String((body as { code: unknown }).code)\n : undefined;\n\n switch (statusCode) {\n case 400:\n if (\n typeof body === \"object\" &&\n body !== null &&\n \"errors\" in body &&\n Array.isArray((body as { errors: unknown }).errors)\n ) {\n return new HttpError(message, \"kontext_validation_error\", {\n statusCode: 400,\n validationErrors: (\n body as { errors: Array<{ field: string; message: string }> }\n ).errors,\n });\n }\n return new KontextError(message, errorCode ?? \"kontext_bad_request\", {\n statusCode: 400,\n });\n\n case 401:\n return new AuthorizationRequiredError(message);\n\n case 403:\n if (errorCode === \"INTEGRATION_CONNECTION_REQUIRED\") {\n const details = body as {\n integrationId?: string;\n integrationName?: string;\n connectUrl?: string;\n };\n return new IntegrationConnectionRequiredError(\n details.integrationId ?? \"unknown\",\n {\n integrationName: details.integrationName,\n connectUrl: details.connectUrl,\n message,\n },\n );\n }\n return new HttpError(message, \"kontext_policy_denied\", {\n statusCode: 403,\n meta: { policy: (body as Record<string, unknown>)?.policy },\n });\n\n case 404:\n return new HttpError(message, \"kontext_not_found\", { statusCode: 404 });\n\n case 429: {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter)\n : undefined;\n return new HttpError(\n retryAfter\n ? `Rate limit exceeded. Retry after ${retryAfter} seconds.`\n : \"Rate limit exceeded. Wait and retry.\",\n \"kontext_rate_limited\",\n { statusCode: 429, retryAfter },\n );\n }\n\n default:\n if (statusCode >= 500) {\n return new HttpError(\n `Server error (HTTP ${statusCode}): ${message}`,\n \"kontext_server_error\",\n { statusCode },\n );\n }\n return new KontextError(message, errorCode ?? \"kontext_unknown_error\", {\n statusCode,\n });\n }\n}\n","/**\n * React bindings for Kontext\n *\n * Provides the `useKontext` hook for managing Kontext MCP authentication\n * in React applications. Platform-agnostic — works with any framework\n * that sends MCP update events (e.g. Cloudflare Agents' `useAgent`).\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n *\n * function App() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * // ...\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n createContext,\n useContext,\n useMemo,\n createElement,\n type ReactNode,\n} from \"react\";\nimport { ConfigError } from \"../../errors.js\";\n\nexport interface UseKontextOptions {\n /** Custom window features for the OAuth popup (default: \"width=600,height=700\") */\n popupFeatures?: string;\n\n /** Called when the auth popup closes */\n onAuthComplete?: () => void;\n}\n\n/** MCP server state as broadcast by the Cloudflare Agents framework */\ninterface McpServerState {\n auth_url?: string | null;\n elicitation_url?: string | null;\n state?: string;\n name?: string;\n [key: string]: unknown;\n}\n\n/** MCP state object passed to `onMcpUpdate` by `useAgent` */\ninterface McpState {\n servers?: Record<string, McpServerState>;\n tools?: unknown[];\n prompts?: unknown[];\n resources?: unknown[];\n}\n\nexport interface UseKontextReturn {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /**\n * MCP update handler — pass this to your agent framework's MCP update callback.\n * Automatically opens an OAuth popup when the server requires authentication.\n *\n * @example Cloudflare Agents\n * ```typescript\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * ```\n */\n onMcpUpdate: (data: McpState) => void;\n\n /**\n * Open an elicitation URL in a popup.\n * Useful for handling URL elicitation from the KontextMcp client's\n * `onElicitationUrl` callback in a React context.\n *\n * @param url The URL to open\n */\n handleElicitationUrl: (url: string) => void;\n}\n\n/**\n * React hook for Kontext MCP authentication.\n *\n * Handles the OAuth popup flow when an MCP server requires authentication.\n * Returns a handler to wire into your agent framework and auth state.\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * function Chat() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * // ...\n * }\n * ```\n */\nexport function useKontext(options: UseKontextOptions = {}): UseKontextReturn {\n const [authenticating, setAuthenticating] = useState(false);\n const popupRef = useRef<Window | null>(null);\n\n const openPopup = useCallback(\n (url: string, name: string) => {\n if (popupRef.current) return;\n\n setAuthenticating(true);\n const popup = window.open(\n url,\n name,\n options.popupFeatures ?? \"width=600,height=700\",\n );\n popupRef.current = popup;\n\n if (popup) {\n const check = setInterval(() => {\n if (popup.closed) {\n clearInterval(check);\n popupRef.current = null;\n setAuthenticating(false);\n options.onAuthComplete?.();\n }\n }, 500);\n } else {\n // Popup was blocked by the browser\n popupRef.current = null;\n setAuthenticating(false);\n console.warn(\n \"[kontext_popup_blocked] OAuth popup was blocked by the browser. \" +\n \"The user must allow popups for this site to authenticate. \" +\n \"Docs: https://docs.kontext.dev/errors/kontext_popup_blocked\",\n );\n }\n },\n [options.popupFeatures, options.onAuthComplete],\n );\n\n const onMcpUpdate = useCallback(\n (data: McpState) => {\n if (popupRef.current) {\n return;\n }\n\n const servers = data.servers ?? {};\n\n // Check for authentication URLs\n const authUrl = Object.values(servers).find(\n (s) => s.auth_url && s.state === \"authenticating\",\n )?.auth_url;\n\n if (authUrl) {\n openPopup(authUrl, \"kontext-auth\");\n return;\n }\n\n // Check for elicitation URLs (integration OAuth via Cloudflare path)\n const elicitationUrl = Object.values(servers).find(\n (s) => s.elicitation_url,\n )?.elicitation_url;\n\n if (elicitationUrl) {\n openPopup(elicitationUrl, \"kontext-elicitation\");\n }\n },\n [openPopup],\n );\n\n const handleElicitationUrl = useCallback(\n (url: string) => {\n openPopup(url, \"kontext-elicitation\");\n },\n [openPopup],\n );\n\n return { authenticating, onMcpUpdate, handleElicitationUrl };\n}\n\n// ============================================================================\n// Context Provider — \"wrap and forget\" alternative to useKontext\n// ============================================================================\n\n/** Values exposed by KontextProvider via React Context */\nexport interface KontextContextValue {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /** MCP update handler — injected automatically by useKontextAgent */\n onMcpUpdate: (data: McpState) => void;\n\n /** Open an elicitation URL in a popup */\n handleElicitationUrl: (url: string) => void;\n}\n\nexport interface KontextProviderProps extends UseKontextOptions {\n children: ReactNode;\n}\n\nconst KontextContext = createContext<KontextContextValue | null>(null);\n\n/**\n * React Context Provider that manages Kontext auth/elicitation popups.\n *\n * Wrap your app (or the subtree that uses Kontext) with this provider,\n * then use `useKontextAgent()` (from `@kontext-dev/js-sdk/react/cloudflare`)\n * or `useKontextContext()` to access the context directly.\n *\n * @example\n * ```tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n * ```\n */\nexport function KontextProvider({\n children,\n ...options\n}: KontextProviderProps) {\n const { authenticating, onMcpUpdate, handleElicitationUrl } =\n useKontext(options);\n\n const value = useMemo<KontextContextValue>(\n () => ({ authenticating, onMcpUpdate, handleElicitationUrl }),\n [authenticating, onMcpUpdate, handleElicitationUrl],\n );\n\n return createElement(KontextContext.Provider, { value }, children);\n}\n\n/**\n * Access the KontextProvider context directly.\n * Must be used within a `<KontextProvider>`.\n */\nexport function useKontextContext(): KontextContextValue {\n const ctx = useContext(KontextContext);\n if (!ctx) {\n throw new ConfigError(\n \"useKontextContext must be used within a <KontextProvider>. Wrap your component tree with KontextProvider.\",\n \"kontext_context_missing\",\n );\n }\n return ctx;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/errors.ts","../../../src/adapters/react/index.ts"],"names":["ErrorCode","useState","useRef","useCallback","createContext","useMemo","createElement","useContext"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed error classes for the Kontext SDK.\n *\n * Every error has a `kontext_` prefixed code, an auto-generated docsUrl,\n * and a `kontextError` brand for type narrowing without instanceof.\n *\n * @packageDocumentation\n */\n\nimport { ErrorCode } from \"@modelcontextprotocol/sdk/types.js\";\n\n// ============================================================================\n// Base\n// ============================================================================\n\n/**\n * Base error class for all Kontext SDK errors.\n *\n * @example\n * ```typescript\n * import { isKontextError } from '@kontext-dev/js-sdk';\n *\n * try {\n * await client.connect();\n * } catch (err) {\n * if (isKontextError(err)) {\n * console.log(err.code); // \"kontext_authorization_required\"\n * console.log(err.docsUrl); // \"https://docs.kontext.dev/errors/kontext_authorization_required\"\n * }\n * }\n * ```\n */\nexport class KontextError extends Error {\n /** Brand field for type narrowing without instanceof */\n readonly kontextError = true as const;\n\n /** Machine-readable error code, always prefixed with `kontext_` */\n readonly code: string;\n\n /** HTTP status code when applicable */\n readonly statusCode?: number;\n\n /** Auto-generated link to error documentation */\n readonly docsUrl: string;\n\n /** Server request ID for debugging / support escalation */\n readonly requestId?: string;\n\n /** Contextual metadata bag (integration IDs, param names, etc.) */\n readonly meta: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options?.cause });\n this.name = \"KontextError\";\n this.code = code;\n this.statusCode = options?.statusCode;\n this.requestId = options?.requestId;\n this.meta = options?.meta ?? {};\n this.docsUrl = `https://docs.kontext.dev/errors/${code}`;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n docsUrl: this.docsUrl,\n requestId: this.requestId,\n meta: Object.keys(this.meta).length > 0 ? this.meta : undefined,\n };\n }\n\n override toString(): string {\n const parts = [`[${this.code}] ${this.message}`];\n if (this.docsUrl) parts.push(`Docs: ${this.docsUrl}`);\n if (this.requestId) parts.push(`Request ID: ${this.requestId}`);\n return parts.join(\"\\n\");\n }\n}\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\n/**\n * Check if an error is a KontextError without instanceof.\n * Works across package versions and bundler deduplication.\n */\nexport function isKontextError(err: unknown): err is KontextError {\n return (\n typeof err === \"object\" &&\n err !== null &&\n (err as Record<string, unknown>).kontextError === true\n );\n}\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Thrown when authentication is required but no valid credentials are available.\n */\nexport class AuthorizationRequiredError extends KontextError {\n readonly authorizationUrl?: string;\n\n constructor(\n message = \"Authorization required. Complete the OAuth flow to continue.\",\n options?: {\n authorizationUrl?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, \"kontext_authorization_required\", {\n statusCode: 401,\n ...options,\n });\n this.name = \"AuthorizationRequiredError\";\n this.authorizationUrl = options?.authorizationUrl;\n }\n}\n\n// ============================================================================\n// OAuth errors\n// ============================================================================\n\n/**\n * Thrown when an OAuth flow fails — state validation, token exchange,\n * missing code verifier, or provider errors.\n */\nexport class OAuthError extends KontextError {\n readonly errorCode?: string;\n readonly errorDescription?: string;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n errorCode?: string;\n errorDescription?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode ?? 400,\n ...options,\n });\n this.name = \"OAuthError\";\n this.errorCode = options?.errorCode;\n this.errorDescription = options?.errorDescription;\n }\n}\n\n// ============================================================================\n// Integration errors\n// ============================================================================\n\n/**\n * Thrown when an integration connection is required before a tool can be used.\n */\nexport class IntegrationConnectionRequiredError extends KontextError {\n readonly integrationId: string;\n readonly integrationName?: string;\n readonly connectUrl?: string;\n\n constructor(\n integrationId: string,\n options?: {\n integrationName?: string;\n connectUrl?: string;\n message?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(\n options?.message ??\n `Connection to integration \"${integrationId}\" is required. Visit the connect URL to authorize.`,\n \"kontext_integration_connection_required\",\n { statusCode: 403, ...options },\n );\n this.name = \"IntegrationConnectionRequiredError\";\n this.integrationId = integrationId;\n this.integrationName = options?.integrationName;\n this.connectUrl = options?.connectUrl;\n }\n}\n\n// ============================================================================\n// Config errors (NEW — replaces all plain Error config throws)\n// ============================================================================\n\n/**\n * Thrown when SDK configuration is invalid or missing.\n * These are deterministic errors caught at initialization time.\n */\nexport class ConfigError extends KontextError {\n constructor(\n message: string,\n code: string,\n options?: {\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, options);\n this.name = \"ConfigError\";\n }\n}\n\n// ============================================================================\n// Network errors\n// ============================================================================\n\n/**\n * Thrown when there is a network or connection error.\n */\nexport class NetworkError extends KontextError {\n constructor(\n message = \"Network error. Check your internet connection and that the server is reachable.\",\n options?: {\n cause?: unknown;\n requestId?: string;\n meta?: Record<string, unknown>;\n },\n ) {\n super(message, \"kontext_network_error\", options);\n this.name = \"NetworkError\";\n }\n}\n\n// ============================================================================\n// HTTP response errors (differentiated by code)\n// ============================================================================\n\n/**\n * Thrown when the server returns an HTTP error.\n * Use `error.code` to distinguish between specific error types.\n */\nexport class HttpError extends KontextError {\n readonly retryAfter?: number;\n readonly validationErrors?: Array<{ field: string; message: string }>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n retryAfter?: number;\n validationErrors?: Array<{ field: string; message: string }>;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode,\n ...options,\n });\n this.name = \"HttpError\";\n this.retryAfter = options?.retryAfter;\n this.validationErrors = options?.validationErrors;\n }\n}\n\n// ============================================================================\n// Network error detection (used by translateError)\n// ============================================================================\n\n/**\n * Safely access arbitrary properties on an error object.\n * Errors in JS frequently carry extra properties (code, statusCode, etc.)\n * that aren't part of the Error interface. This avoids `as unknown as` casts.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction errorProps(err: Error): Record<string, any> {\n return err;\n}\n\nconst NETWORK_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"ECONNABORTED\",\n \"EPIPE\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Detect network errors structurally rather than by string matching.\n * Checks Node.js system error codes on the error and its cause.\n */\nexport function isNetworkError(err: Error): boolean {\n if (err.name === \"AbortError\") return true;\n\n const props = errorProps(err);\n const sysCode = props.code as string | undefined;\n if (typeof sysCode === \"string\" && NETWORK_ERROR_CODES.has(sysCode))\n return true;\n\n // fetch() throws TypeError — only classify as network error when cause\n // indicates a system-level failure\n if (err.name === \"TypeError\" && err.cause instanceof Error) {\n const causeCode = errorProps(err.cause).code;\n if (typeof causeCode === \"string\" && NETWORK_ERROR_CODES.has(causeCode))\n return true;\n }\n\n // Browser fetch() failures: TypeError with known messages, no system error code\n if (err.name === \"TypeError\") {\n const msg = err.message.toLowerCase();\n if (\n msg === \"failed to fetch\" ||\n msg === \"load failed\" ||\n msg.includes(\"networkerror\")\n ) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Detect unauthorized errors structurally.\n * Checks status code and numeric code rather than string matching on name.\n */\nexport function isUnauthorizedError(err: Error): boolean {\n const props = errorProps(err);\n\n // Check HTTP status code (most reliable)\n if (props.statusCode === 401 || props.status === 401) return true;\n\n // StreamableHTTPError stores status as `.code`\n if (props.code === 401) return true;\n\n // MCP SDK UnauthorizedError extends Error without setting `.name`,\n // so `.name` is \"Error\". Use constructor name as fallback.\n if (err.name === \"UnauthorizedError\") return true;\n if (err.constructor?.name === \"UnauthorizedError\") return true;\n if (err.message === \"Unauthorized\") return true;\n\n return false;\n}\n\n// ============================================================================\n// Elicitation types\n// ============================================================================\n\nexport interface ElicitationEntry {\n readonly url: string;\n readonly message: string;\n readonly elicitationId: string;\n readonly integrationId?: string;\n readonly integrationName?: string;\n}\n\n// ============================================================================\n// HTTP error parsing\n// ============================================================================\n\n/**\n * Parse an HTTP response into an appropriate error.\n */\nexport function parseHttpError(\n statusCode: number,\n body?: unknown,\n): KontextError {\n const message =\n typeof body === \"object\" && body !== null && \"message\" in body\n ? String((body as { message: unknown }).message)\n : `HTTP ${statusCode}`;\n\n const errorCode =\n typeof body === \"object\" && body !== null && \"code\" in body\n ? String((body as { code: unknown }).code)\n : undefined;\n\n switch (statusCode) {\n case 400:\n if (\n typeof body === \"object\" &&\n body !== null &&\n \"errors\" in body &&\n Array.isArray((body as { errors: unknown }).errors)\n ) {\n return new HttpError(message, \"kontext_validation_error\", {\n statusCode: 400,\n validationErrors: (\n body as { errors: Array<{ field: string; message: string }> }\n ).errors,\n });\n }\n return new KontextError(message, errorCode ?? \"kontext_bad_request\", {\n statusCode: 400,\n });\n\n case 401:\n return new AuthorizationRequiredError(message);\n\n case 403:\n if (errorCode === \"INTEGRATION_CONNECTION_REQUIRED\") {\n const details = body as {\n integrationId?: string;\n integrationName?: string;\n connectUrl?: string;\n };\n return new IntegrationConnectionRequiredError(\n details.integrationId ?? \"unknown\",\n {\n integrationName: details.integrationName,\n connectUrl: details.connectUrl,\n message,\n },\n );\n }\n return new HttpError(message, \"kontext_policy_denied\", {\n statusCode: 403,\n meta: { policy: (body as Record<string, unknown>)?.policy },\n });\n\n case 404:\n return new HttpError(message, \"kontext_not_found\", { statusCode: 404 });\n\n case 429: {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter)\n : undefined;\n return new HttpError(\n retryAfter\n ? `Rate limit exceeded. Retry after ${retryAfter} seconds.`\n : \"Rate limit exceeded. Wait and retry.\",\n \"kontext_rate_limited\",\n { statusCode: 429, retryAfter },\n );\n }\n\n default:\n if (statusCode >= 500) {\n return new HttpError(\n `Server error (HTTP ${statusCode}): ${message}`,\n \"kontext_server_error\",\n { statusCode },\n );\n }\n return new KontextError(message, errorCode ?? \"kontext_unknown_error\", {\n statusCode,\n });\n }\n}\n\n// ============================================================================\n// MCP error translation\n// ============================================================================\n\nconst MCP_CODE_MAP: Record<number, { code: string; statusCode?: number }> = {\n [ErrorCode.ParseError]: { code: \"kontext_mcp_parse_error\" },\n [ErrorCode.InvalidRequest]: { code: \"kontext_mcp_invalid_request\" },\n [ErrorCode.MethodNotFound]: { code: \"kontext_mcp_method_not_found\" },\n [ErrorCode.InvalidParams]: { code: \"kontext_mcp_invalid_params\" },\n [ErrorCode.InternalError]: {\n code: \"kontext_mcp_internal_error\",\n statusCode: 500,\n },\n [ErrorCode.RequestTimeout]: {\n code: \"kontext_mcp_session_expired\",\n statusCode: 401,\n },\n [ErrorCode.ConnectionClosed]: { code: \"kontext_mcp_session_error\" },\n};\n\n/**\n * Translate external errors into KontextError instances.\n * Uses structural checks (numeric codes, status codes, system error codes)\n * rather than fragile string matching.\n */\nexport function translateError(err: unknown): KontextError {\n if (isKontextError(err)) return err as KontextError;\n\n if (!(err instanceof Error)) {\n return new KontextError(String(err), \"kontext_unknown_error\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const props: Record<string, any> = err;\n\n // 1. MCP protocol -32042 (URL elicitation required) — check numeric code\n if (props.code === ErrorCode.UrlElicitationRequired) {\n const elicitations = (props.elicitations ?? props.data?.elicitations) as\n | ElicitationEntry[]\n | undefined;\n const elicitation = elicitations?.[0];\n return new IntegrationConnectionRequiredError(\n elicitation?.integrationId ?? \"unknown\",\n {\n integrationName: elicitation?.integrationName,\n connectUrl: elicitation?.url,\n message: elicitation?.message,\n cause: err,\n },\n );\n }\n\n // 2. MCP JSON-RPC errors (standard and server-defined ranges)\n if (typeof props.code === \"number\" && props.code < 0) {\n const entry = MCP_CODE_MAP[props.code];\n if (entry) {\n return new KontextError(err.message, entry.code, {\n statusCode: entry.statusCode,\n cause: err,\n });\n }\n return new KontextError(err.message, \"kontext_mcp_error\", {\n cause: err,\n meta: { mcpCode: props.code },\n });\n }\n\n // 3. HTTP status on the error (from MCP SDK or fetch wrappers)\n // StreamableHTTPError stores HTTP status as `.code` (not `.statusCode`/`.status`),\n // so check `.code` as well when it's in the HTTP error range (400–599).\n const statusCode = (props.statusCode ??\n props.status ??\n (typeof props.code === \"number\" && props.code >= 400 && props.code < 600\n ? props.code\n : undefined)) as number | undefined;\n if (typeof statusCode === \"number\" && statusCode >= 400) {\n if (statusCode === 401) {\n return new AuthorizationRequiredError(err.message, { cause: err });\n }\n return new KontextError(err.message, \"kontext_server_error\", {\n statusCode,\n cause: err,\n });\n }\n\n // 4. Auth errors — structural check\n if (isUnauthorizedError(err)) {\n return new AuthorizationRequiredError(err.message, { cause: err });\n }\n\n // 5. Network errors — structural check (system error codes, not string matching)\n if (isNetworkError(err)) {\n return new NetworkError(err.message, { cause: err });\n }\n\n // 6. Fallback\n return new KontextError(err.message, \"kontext_unknown_error\", {\n cause: err,\n });\n}\n","/**\n * React bindings for Kontext\n *\n * Provides the `useKontext` hook for managing Kontext MCP authentication\n * in React applications. Platform-agnostic — works with any framework\n * that sends MCP update events (e.g. Cloudflare Agents' `useAgent`).\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n *\n * function App() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * // ...\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n createContext,\n useContext,\n useMemo,\n createElement,\n type ReactNode,\n} from \"react\";\nimport { ConfigError } from \"../../errors.js\";\n\nexport interface UseKontextOptions {\n /** Custom window features for the OAuth popup (default: \"width=600,height=700\") */\n popupFeatures?: string;\n\n /** Called when the auth popup closes */\n onAuthComplete?: () => void;\n}\n\n/** MCP server state as broadcast by the Cloudflare Agents framework */\ninterface McpServerState {\n auth_url?: string | null;\n elicitation_url?: string | null;\n state?: string;\n name?: string;\n [key: string]: unknown;\n}\n\n/** MCP state object passed to `onMcpUpdate` by `useAgent` */\ninterface McpState {\n servers?: Record<string, McpServerState>;\n tools?: unknown[];\n prompts?: unknown[];\n resources?: unknown[];\n}\n\nexport interface UseKontextReturn {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /**\n * MCP update handler — pass this to your agent framework's MCP update callback.\n * Automatically opens an OAuth popup when the server requires authentication.\n *\n * @example Cloudflare Agents\n * ```typescript\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * ```\n */\n onMcpUpdate: (data: McpState) => void;\n\n /**\n * Open an elicitation URL in a popup.\n * Useful for handling URL elicitation from the KontextMcp client's\n * `onElicitationUrl` callback in a React context.\n *\n * @param url The URL to open\n */\n handleElicitationUrl: (url: string) => void;\n}\n\n/**\n * React hook for Kontext MCP authentication.\n *\n * Handles the OAuth popup flow when an MCP server requires authentication.\n * Returns a handler to wire into your agent framework and auth state.\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * function Chat() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * // ...\n * }\n * ```\n */\nexport function useKontext(options: UseKontextOptions = {}): UseKontextReturn {\n const [authenticating, setAuthenticating] = useState(false);\n const popupRef = useRef<Window | null>(null);\n\n const openPopup = useCallback(\n (url: string, name: string) => {\n if (popupRef.current) return;\n\n setAuthenticating(true);\n const popup = window.open(\n url,\n name,\n options.popupFeatures ?? \"width=600,height=700\",\n );\n popupRef.current = popup;\n\n if (popup) {\n const check = setInterval(() => {\n if (popup.closed) {\n clearInterval(check);\n popupRef.current = null;\n setAuthenticating(false);\n options.onAuthComplete?.();\n }\n }, 500);\n } else {\n // Popup was blocked by the browser\n popupRef.current = null;\n setAuthenticating(false);\n console.warn(\n \"[kontext_popup_blocked] OAuth popup was blocked by the browser. \" +\n \"The user must allow popups for this site to authenticate. \" +\n \"Docs: https://docs.kontext.dev/errors/kontext_popup_blocked\",\n );\n }\n },\n [options.popupFeatures, options.onAuthComplete],\n );\n\n const onMcpUpdate = useCallback(\n (data: McpState) => {\n if (popupRef.current) {\n return;\n }\n\n const servers = data.servers ?? {};\n\n // Check for authentication URLs\n const authUrl = Object.values(servers).find(\n (s) => s.auth_url && s.state === \"authenticating\",\n )?.auth_url;\n\n if (authUrl) {\n openPopup(authUrl, \"kontext-auth\");\n return;\n }\n\n // Check for elicitation URLs (integration OAuth via Cloudflare path)\n const elicitationUrl = Object.values(servers).find(\n (s) => s.elicitation_url,\n )?.elicitation_url;\n\n if (elicitationUrl) {\n openPopup(elicitationUrl, \"kontext-elicitation\");\n }\n },\n [openPopup],\n );\n\n const handleElicitationUrl = useCallback(\n (url: string) => {\n openPopup(url, \"kontext-elicitation\");\n },\n [openPopup],\n );\n\n return { authenticating, onMcpUpdate, handleElicitationUrl };\n}\n\n// ============================================================================\n// Context Provider — \"wrap and forget\" alternative to useKontext\n// ============================================================================\n\n/** Values exposed by KontextProvider via React Context */\nexport interface KontextContextValue {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /** MCP update handler — injected automatically by useKontextAgent */\n onMcpUpdate: (data: McpState) => void;\n\n /** Open an elicitation URL in a popup */\n handleElicitationUrl: (url: string) => void;\n}\n\nexport interface KontextProviderProps extends UseKontextOptions {\n children: ReactNode;\n}\n\nconst KontextContext = createContext<KontextContextValue | null>(null);\n\n/**\n * React Context Provider that manages Kontext auth/elicitation popups.\n *\n * Wrap your app (or the subtree that uses Kontext) with this provider,\n * then use `useKontextAgent()` (from `@kontext-dev/js-sdk/react/cloudflare`)\n * or `useKontextContext()` to access the context directly.\n *\n * @example\n * ```tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n * ```\n */\nexport function KontextProvider({\n children,\n ...options\n}: KontextProviderProps) {\n const { authenticating, onMcpUpdate, handleElicitationUrl } =\n useKontext(options);\n\n const value = useMemo<KontextContextValue>(\n () => ({ authenticating, onMcpUpdate, handleElicitationUrl }),\n [authenticating, onMcpUpdate, handleElicitationUrl],\n );\n\n return createElement(KontextContext.Provider, { value }, children);\n}\n\n/**\n * Access the KontextProvider context directly.\n * Must be used within a `<KontextProvider>`.\n */\nexport function useKontextContext(): KontextContextValue {\n const ctx = useContext(KontextContext);\n if (!ctx) {\n throw new ConfigError(\n \"useKontextContext must be used within a <KontextProvider>. Wrap your component tree with KontextProvider.\",\n \"kontext_context_missing\",\n );\n }\n return ctx;\n}\n"]}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createContext, useState, useRef, useCallback, useMemo, createElement, useContext } from 'react';
|
|
2
|
+
import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
|
|
2
3
|
|
|
3
4
|
// src/adapters/react/index.ts
|
|
4
|
-
|
|
5
|
-
// src/errors.ts
|
|
6
5
|
var KontextError = class extends Error {
|
|
7
6
|
/** Brand field for type narrowing without instanceof */
|
|
8
7
|
kontextError = true;
|
|
@@ -50,6 +49,17 @@ var ConfigError = class extends KontextError {
|
|
|
50
49
|
this.name = "ConfigError";
|
|
51
50
|
}
|
|
52
51
|
};
|
|
52
|
+
({
|
|
53
|
+
[ErrorCode.ParseError]: { },
|
|
54
|
+
[ErrorCode.InvalidRequest]: { },
|
|
55
|
+
[ErrorCode.MethodNotFound]: { },
|
|
56
|
+
[ErrorCode.InvalidParams]: { },
|
|
57
|
+
[ErrorCode.InternalError]: {
|
|
58
|
+
},
|
|
59
|
+
[ErrorCode.RequestTimeout]: {
|
|
60
|
+
},
|
|
61
|
+
[ErrorCode.ConnectionClosed]: { }
|
|
62
|
+
});
|
|
53
63
|
|
|
54
64
|
// src/adapters/react/index.ts
|
|
55
65
|
function useKontext(options = {}) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/errors.ts","../../../src/adapters/react/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Typed error classes for the Kontext SDK.\n *\n * Every error has a `kontext_` prefixed code, an auto-generated docsUrl,\n * and a `kontextError` brand for type narrowing without instanceof.\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// Base\n// ============================================================================\n\n/**\n * Base error class for all Kontext SDK errors.\n *\n * @example\n * ```typescript\n * import { isKontextError } from '@kontext-dev/js-sdk';\n *\n * try {\n * await client.connect();\n * } catch (err) {\n * if (isKontextError(err)) {\n * console.log(err.code); // \"kontext_authorization_required\"\n * console.log(err.docsUrl); // \"https://docs.kontext.dev/errors/kontext_authorization_required\"\n * }\n * }\n * ```\n */\nexport class KontextError extends Error {\n /** Brand field for type narrowing without instanceof */\n readonly kontextError = true as const;\n\n /** Machine-readable error code, always prefixed with `kontext_` */\n readonly code: string;\n\n /** HTTP status code when applicable */\n readonly statusCode?: number;\n\n /** Auto-generated link to error documentation */\n readonly docsUrl: string;\n\n /** Server request ID for debugging / support escalation */\n readonly requestId?: string;\n\n /** Contextual metadata bag (integration IDs, param names, etc.) */\n readonly meta: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options?.cause });\n this.name = \"KontextError\";\n this.code = code;\n this.statusCode = options?.statusCode;\n this.requestId = options?.requestId;\n this.meta = options?.meta ?? {};\n this.docsUrl = `https://docs.kontext.dev/errors/${code}`;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n docsUrl: this.docsUrl,\n requestId: this.requestId,\n meta: Object.keys(this.meta).length > 0 ? this.meta : undefined,\n };\n }\n\n override toString(): string {\n const parts = [`[${this.code}] ${this.message}`];\n if (this.docsUrl) parts.push(`Docs: ${this.docsUrl}`);\n if (this.requestId) parts.push(`Request ID: ${this.requestId}`);\n return parts.join(\"\\n\");\n }\n}\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\n/**\n * Check if an error is a KontextError without instanceof.\n * Works across package versions and bundler deduplication.\n */\nexport function isKontextError(err: unknown): err is KontextError {\n return (\n typeof err === \"object\" &&\n err !== null &&\n (err as Record<string, unknown>).kontextError === true\n );\n}\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Thrown when authentication is required but no valid credentials are available.\n */\nexport class AuthorizationRequiredError extends KontextError {\n readonly authorizationUrl?: string;\n\n constructor(\n message = \"Authorization required. Complete the OAuth flow to continue.\",\n options?: {\n authorizationUrl?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, \"kontext_authorization_required\", {\n statusCode: 401,\n ...options,\n });\n this.name = \"AuthorizationRequiredError\";\n this.authorizationUrl = options?.authorizationUrl;\n }\n}\n\n// ============================================================================\n// OAuth errors\n// ============================================================================\n\n/**\n * Thrown when an OAuth flow fails — state validation, token exchange,\n * missing code verifier, or provider errors.\n */\nexport class OAuthError extends KontextError {\n readonly errorCode?: string;\n readonly errorDescription?: string;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n errorCode?: string;\n errorDescription?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode ?? 400,\n ...options,\n });\n this.name = \"OAuthError\";\n this.errorCode = options?.errorCode;\n this.errorDescription = options?.errorDescription;\n }\n}\n\n// ============================================================================\n// Integration errors\n// ============================================================================\n\n/**\n * Thrown when an integration connection is required before a tool can be used.\n */\nexport class IntegrationConnectionRequiredError extends KontextError {\n readonly integrationId: string;\n readonly integrationName?: string;\n readonly connectUrl?: string;\n\n constructor(\n integrationId: string,\n options?: {\n integrationName?: string;\n connectUrl?: string;\n message?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(\n options?.message ??\n `Connection to integration \"${integrationId}\" is required. Visit the connect URL to authorize.`,\n \"kontext_integration_connection_required\",\n { statusCode: 403, ...options },\n );\n this.name = \"IntegrationConnectionRequiredError\";\n this.integrationId = integrationId;\n this.integrationName = options?.integrationName;\n this.connectUrl = options?.connectUrl;\n }\n}\n\n// ============================================================================\n// Config errors (NEW — replaces all plain Error config throws)\n// ============================================================================\n\n/**\n * Thrown when SDK configuration is invalid or missing.\n * These are deterministic errors caught at initialization time.\n */\nexport class ConfigError extends KontextError {\n constructor(\n message: string,\n code: string,\n options?: {\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, options);\n this.name = \"ConfigError\";\n }\n}\n\n// ============================================================================\n// Network errors\n// ============================================================================\n\n/**\n * Thrown when there is a network or connection error.\n */\nexport class NetworkError extends KontextError {\n constructor(\n message = \"Network error. Check your internet connection and that the server is reachable.\",\n options?: {\n cause?: unknown;\n requestId?: string;\n meta?: Record<string, unknown>;\n },\n ) {\n super(message, \"kontext_network_error\", options);\n this.name = \"NetworkError\";\n }\n}\n\n// ============================================================================\n// HTTP response errors (differentiated by code)\n// ============================================================================\n\n/**\n * Thrown when the server returns an HTTP error.\n * Use `error.code` to distinguish between specific error types.\n */\nexport class HttpError extends KontextError {\n readonly retryAfter?: number;\n readonly validationErrors?: Array<{ field: string; message: string }>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n retryAfter?: number;\n validationErrors?: Array<{ field: string; message: string }>;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode,\n ...options,\n });\n this.name = \"HttpError\";\n this.retryAfter = options?.retryAfter;\n this.validationErrors = options?.validationErrors;\n }\n}\n\n// ============================================================================\n// Network error detection (used by translateError)\n// ============================================================================\n\n/**\n * Safely access arbitrary properties on an error object.\n * Errors in JS frequently carry extra properties (code, statusCode, etc.)\n * that aren't part of the Error interface. This avoids `as unknown as` casts.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction errorProps(err: Error): Record<string, any> {\n return err;\n}\n\nconst NETWORK_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"ECONNABORTED\",\n \"EPIPE\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Detect network errors structurally rather than by string matching.\n * Checks Node.js system error codes on the error and its cause.\n */\nexport function isNetworkError(err: Error): boolean {\n if (err.name === \"AbortError\") return true;\n\n const props = errorProps(err);\n const sysCode = props.code as string | undefined;\n if (typeof sysCode === \"string\" && NETWORK_ERROR_CODES.has(sysCode))\n return true;\n\n // fetch() throws TypeError — only classify as network error when cause\n // indicates a system-level failure\n if (err.name === \"TypeError\" && err.cause instanceof Error) {\n const causeCode = errorProps(err.cause).code;\n if (typeof causeCode === \"string\" && NETWORK_ERROR_CODES.has(causeCode))\n return true;\n }\n\n return false;\n}\n\n/**\n * Detect unauthorized errors structurally.\n * Checks status code and numeric code rather than string matching on name.\n */\nexport function isUnauthorizedError(err: Error): boolean {\n const props = errorProps(err);\n\n // Check HTTP status code (most reliable)\n if (props.statusCode === 401 || props.status === 401) return true;\n\n // Check MCP SDK UnauthorizedError by name (last resort, but needed for\n // MCP SDK errors which don't set statusCode)\n if (err.name === \"UnauthorizedError\") return true;\n if (err.message === \"Unauthorized\") return true;\n\n return false;\n}\n\n// ============================================================================\n// Elicitation types\n// ============================================================================\n\nexport interface ElicitationEntry {\n readonly url: string;\n readonly message: string;\n readonly elicitationId: string;\n readonly integrationId?: string;\n readonly integrationName?: string;\n}\n\n// ============================================================================\n// HTTP error parsing\n// ============================================================================\n\n/**\n * Parse an HTTP response into an appropriate error.\n */\nexport function parseHttpError(\n statusCode: number,\n body?: unknown,\n): KontextError {\n const message =\n typeof body === \"object\" && body !== null && \"message\" in body\n ? String((body as { message: unknown }).message)\n : `HTTP ${statusCode}`;\n\n const errorCode =\n typeof body === \"object\" && body !== null && \"code\" in body\n ? String((body as { code: unknown }).code)\n : undefined;\n\n switch (statusCode) {\n case 400:\n if (\n typeof body === \"object\" &&\n body !== null &&\n \"errors\" in body &&\n Array.isArray((body as { errors: unknown }).errors)\n ) {\n return new HttpError(message, \"kontext_validation_error\", {\n statusCode: 400,\n validationErrors: (\n body as { errors: Array<{ field: string; message: string }> }\n ).errors,\n });\n }\n return new KontextError(message, errorCode ?? \"kontext_bad_request\", {\n statusCode: 400,\n });\n\n case 401:\n return new AuthorizationRequiredError(message);\n\n case 403:\n if (errorCode === \"INTEGRATION_CONNECTION_REQUIRED\") {\n const details = body as {\n integrationId?: string;\n integrationName?: string;\n connectUrl?: string;\n };\n return new IntegrationConnectionRequiredError(\n details.integrationId ?? \"unknown\",\n {\n integrationName: details.integrationName,\n connectUrl: details.connectUrl,\n message,\n },\n );\n }\n return new HttpError(message, \"kontext_policy_denied\", {\n statusCode: 403,\n meta: { policy: (body as Record<string, unknown>)?.policy },\n });\n\n case 404:\n return new HttpError(message, \"kontext_not_found\", { statusCode: 404 });\n\n case 429: {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter)\n : undefined;\n return new HttpError(\n retryAfter\n ? `Rate limit exceeded. Retry after ${retryAfter} seconds.`\n : \"Rate limit exceeded. Wait and retry.\",\n \"kontext_rate_limited\",\n { statusCode: 429, retryAfter },\n );\n }\n\n default:\n if (statusCode >= 500) {\n return new HttpError(\n `Server error (HTTP ${statusCode}): ${message}`,\n \"kontext_server_error\",\n { statusCode },\n );\n }\n return new KontextError(message, errorCode ?? \"kontext_unknown_error\", {\n statusCode,\n });\n }\n}\n","/**\n * React bindings for Kontext\n *\n * Provides the `useKontext` hook for managing Kontext MCP authentication\n * in React applications. Platform-agnostic — works with any framework\n * that sends MCP update events (e.g. Cloudflare Agents' `useAgent`).\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n *\n * function App() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * // ...\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n createContext,\n useContext,\n useMemo,\n createElement,\n type ReactNode,\n} from \"react\";\nimport { ConfigError } from \"../../errors.js\";\n\nexport interface UseKontextOptions {\n /** Custom window features for the OAuth popup (default: \"width=600,height=700\") */\n popupFeatures?: string;\n\n /** Called when the auth popup closes */\n onAuthComplete?: () => void;\n}\n\n/** MCP server state as broadcast by the Cloudflare Agents framework */\ninterface McpServerState {\n auth_url?: string | null;\n elicitation_url?: string | null;\n state?: string;\n name?: string;\n [key: string]: unknown;\n}\n\n/** MCP state object passed to `onMcpUpdate` by `useAgent` */\ninterface McpState {\n servers?: Record<string, McpServerState>;\n tools?: unknown[];\n prompts?: unknown[];\n resources?: unknown[];\n}\n\nexport interface UseKontextReturn {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /**\n * MCP update handler — pass this to your agent framework's MCP update callback.\n * Automatically opens an OAuth popup when the server requires authentication.\n *\n * @example Cloudflare Agents\n * ```typescript\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * ```\n */\n onMcpUpdate: (data: McpState) => void;\n\n /**\n * Open an elicitation URL in a popup.\n * Useful for handling URL elicitation from the KontextMcp client's\n * `onElicitationUrl` callback in a React context.\n *\n * @param url The URL to open\n */\n handleElicitationUrl: (url: string) => void;\n}\n\n/**\n * React hook for Kontext MCP authentication.\n *\n * Handles the OAuth popup flow when an MCP server requires authentication.\n * Returns a handler to wire into your agent framework and auth state.\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * function Chat() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * // ...\n * }\n * ```\n */\nexport function useKontext(options: UseKontextOptions = {}): UseKontextReturn {\n const [authenticating, setAuthenticating] = useState(false);\n const popupRef = useRef<Window | null>(null);\n\n const openPopup = useCallback(\n (url: string, name: string) => {\n if (popupRef.current) return;\n\n setAuthenticating(true);\n const popup = window.open(\n url,\n name,\n options.popupFeatures ?? \"width=600,height=700\",\n );\n popupRef.current = popup;\n\n if (popup) {\n const check = setInterval(() => {\n if (popup.closed) {\n clearInterval(check);\n popupRef.current = null;\n setAuthenticating(false);\n options.onAuthComplete?.();\n }\n }, 500);\n } else {\n // Popup was blocked by the browser\n popupRef.current = null;\n setAuthenticating(false);\n console.warn(\n \"[kontext_popup_blocked] OAuth popup was blocked by the browser. \" +\n \"The user must allow popups for this site to authenticate. \" +\n \"Docs: https://docs.kontext.dev/errors/kontext_popup_blocked\",\n );\n }\n },\n [options.popupFeatures, options.onAuthComplete],\n );\n\n const onMcpUpdate = useCallback(\n (data: McpState) => {\n if (popupRef.current) {\n return;\n }\n\n const servers = data.servers ?? {};\n\n // Check for authentication URLs\n const authUrl = Object.values(servers).find(\n (s) => s.auth_url && s.state === \"authenticating\",\n )?.auth_url;\n\n if (authUrl) {\n openPopup(authUrl, \"kontext-auth\");\n return;\n }\n\n // Check for elicitation URLs (integration OAuth via Cloudflare path)\n const elicitationUrl = Object.values(servers).find(\n (s) => s.elicitation_url,\n )?.elicitation_url;\n\n if (elicitationUrl) {\n openPopup(elicitationUrl, \"kontext-elicitation\");\n }\n },\n [openPopup],\n );\n\n const handleElicitationUrl = useCallback(\n (url: string) => {\n openPopup(url, \"kontext-elicitation\");\n },\n [openPopup],\n );\n\n return { authenticating, onMcpUpdate, handleElicitationUrl };\n}\n\n// ============================================================================\n// Context Provider — \"wrap and forget\" alternative to useKontext\n// ============================================================================\n\n/** Values exposed by KontextProvider via React Context */\nexport interface KontextContextValue {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /** MCP update handler — injected automatically by useKontextAgent */\n onMcpUpdate: (data: McpState) => void;\n\n /** Open an elicitation URL in a popup */\n handleElicitationUrl: (url: string) => void;\n}\n\nexport interface KontextProviderProps extends UseKontextOptions {\n children: ReactNode;\n}\n\nconst KontextContext = createContext<KontextContextValue | null>(null);\n\n/**\n * React Context Provider that manages Kontext auth/elicitation popups.\n *\n * Wrap your app (or the subtree that uses Kontext) with this provider,\n * then use `useKontextAgent()` (from `@kontext-dev/js-sdk/react/cloudflare`)\n * or `useKontextContext()` to access the context directly.\n *\n * @example\n * ```tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n * ```\n */\nexport function KontextProvider({\n children,\n ...options\n}: KontextProviderProps) {\n const { authenticating, onMcpUpdate, handleElicitationUrl } =\n useKontext(options);\n\n const value = useMemo<KontextContextValue>(\n () => ({ authenticating, onMcpUpdate, handleElicitationUrl }),\n [authenticating, onMcpUpdate, handleElicitationUrl],\n );\n\n return createElement(KontextContext.Provider, { value }, children);\n}\n\n/**\n * Access the KontextProvider context directly.\n * Must be used within a `<KontextProvider>`.\n */\nexport function useKontextContext(): KontextContextValue {\n const ctx = useContext(KontextContext);\n if (!ctx) {\n throw new ConfigError(\n \"useKontextContext must be used within a <KontextProvider>. Wrap your component tree with KontextProvider.\",\n \"kontext_context_missing\",\n );\n }\n return ctx;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/errors.ts","../../../src/adapters/react/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Typed error classes for the Kontext SDK.\n *\n * Every error has a `kontext_` prefixed code, an auto-generated docsUrl,\n * and a `kontextError` brand for type narrowing without instanceof.\n *\n * @packageDocumentation\n */\n\nimport { ErrorCode } from \"@modelcontextprotocol/sdk/types.js\";\n\n// ============================================================================\n// Base\n// ============================================================================\n\n/**\n * Base error class for all Kontext SDK errors.\n *\n * @example\n * ```typescript\n * import { isKontextError } from '@kontext-dev/js-sdk';\n *\n * try {\n * await client.connect();\n * } catch (err) {\n * if (isKontextError(err)) {\n * console.log(err.code); // \"kontext_authorization_required\"\n * console.log(err.docsUrl); // \"https://docs.kontext.dev/errors/kontext_authorization_required\"\n * }\n * }\n * ```\n */\nexport class KontextError extends Error {\n /** Brand field for type narrowing without instanceof */\n readonly kontextError = true as const;\n\n /** Machine-readable error code, always prefixed with `kontext_` */\n readonly code: string;\n\n /** HTTP status code when applicable */\n readonly statusCode?: number;\n\n /** Auto-generated link to error documentation */\n readonly docsUrl: string;\n\n /** Server request ID for debugging / support escalation */\n readonly requestId?: string;\n\n /** Contextual metadata bag (integration IDs, param names, etc.) */\n readonly meta: Record<string, unknown>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, { cause: options?.cause });\n this.name = \"KontextError\";\n this.code = code;\n this.statusCode = options?.statusCode;\n this.requestId = options?.requestId;\n this.meta = options?.meta ?? {};\n this.docsUrl = `https://docs.kontext.dev/errors/${code}`;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n statusCode: this.statusCode,\n docsUrl: this.docsUrl,\n requestId: this.requestId,\n meta: Object.keys(this.meta).length > 0 ? this.meta : undefined,\n };\n }\n\n override toString(): string {\n const parts = [`[${this.code}] ${this.message}`];\n if (this.docsUrl) parts.push(`Docs: ${this.docsUrl}`);\n if (this.requestId) parts.push(`Request ID: ${this.requestId}`);\n return parts.join(\"\\n\");\n }\n}\n\n// ============================================================================\n// Type guard\n// ============================================================================\n\n/**\n * Check if an error is a KontextError without instanceof.\n * Works across package versions and bundler deduplication.\n */\nexport function isKontextError(err: unknown): err is KontextError {\n return (\n typeof err === \"object\" &&\n err !== null &&\n (err as Record<string, unknown>).kontextError === true\n );\n}\n\n// ============================================================================\n// Auth errors\n// ============================================================================\n\n/**\n * Thrown when authentication is required but no valid credentials are available.\n */\nexport class AuthorizationRequiredError extends KontextError {\n readonly authorizationUrl?: string;\n\n constructor(\n message = \"Authorization required. Complete the OAuth flow to continue.\",\n options?: {\n authorizationUrl?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, \"kontext_authorization_required\", {\n statusCode: 401,\n ...options,\n });\n this.name = \"AuthorizationRequiredError\";\n this.authorizationUrl = options?.authorizationUrl;\n }\n}\n\n// ============================================================================\n// OAuth errors\n// ============================================================================\n\n/**\n * Thrown when an OAuth flow fails — state validation, token exchange,\n * missing code verifier, or provider errors.\n */\nexport class OAuthError extends KontextError {\n readonly errorCode?: string;\n readonly errorDescription?: string;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n errorCode?: string;\n errorDescription?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode ?? 400,\n ...options,\n });\n this.name = \"OAuthError\";\n this.errorCode = options?.errorCode;\n this.errorDescription = options?.errorDescription;\n }\n}\n\n// ============================================================================\n// Integration errors\n// ============================================================================\n\n/**\n * Thrown when an integration connection is required before a tool can be used.\n */\nexport class IntegrationConnectionRequiredError extends KontextError {\n readonly integrationId: string;\n readonly integrationName?: string;\n readonly connectUrl?: string;\n\n constructor(\n integrationId: string,\n options?: {\n integrationName?: string;\n connectUrl?: string;\n message?: string;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(\n options?.message ??\n `Connection to integration \"${integrationId}\" is required. Visit the connect URL to authorize.`,\n \"kontext_integration_connection_required\",\n { statusCode: 403, ...options },\n );\n this.name = \"IntegrationConnectionRequiredError\";\n this.integrationId = integrationId;\n this.integrationName = options?.integrationName;\n this.connectUrl = options?.connectUrl;\n }\n}\n\n// ============================================================================\n// Config errors (NEW — replaces all plain Error config throws)\n// ============================================================================\n\n/**\n * Thrown when SDK configuration is invalid or missing.\n * These are deterministic errors caught at initialization time.\n */\nexport class ConfigError extends KontextError {\n constructor(\n message: string,\n code: string,\n options?: {\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, options);\n this.name = \"ConfigError\";\n }\n}\n\n// ============================================================================\n// Network errors\n// ============================================================================\n\n/**\n * Thrown when there is a network or connection error.\n */\nexport class NetworkError extends KontextError {\n constructor(\n message = \"Network error. Check your internet connection and that the server is reachable.\",\n options?: {\n cause?: unknown;\n requestId?: string;\n meta?: Record<string, unknown>;\n },\n ) {\n super(message, \"kontext_network_error\", options);\n this.name = \"NetworkError\";\n }\n}\n\n// ============================================================================\n// HTTP response errors (differentiated by code)\n// ============================================================================\n\n/**\n * Thrown when the server returns an HTTP error.\n * Use `error.code` to distinguish between specific error types.\n */\nexport class HttpError extends KontextError {\n readonly retryAfter?: number;\n readonly validationErrors?: Array<{ field: string; message: string }>;\n\n constructor(\n message: string,\n code: string,\n options?: {\n statusCode?: number;\n retryAfter?: number;\n validationErrors?: Array<{ field: string; message: string }>;\n requestId?: string;\n meta?: Record<string, unknown>;\n cause?: unknown;\n },\n ) {\n super(message, code, {\n statusCode: options?.statusCode,\n ...options,\n });\n this.name = \"HttpError\";\n this.retryAfter = options?.retryAfter;\n this.validationErrors = options?.validationErrors;\n }\n}\n\n// ============================================================================\n// Network error detection (used by translateError)\n// ============================================================================\n\n/**\n * Safely access arbitrary properties on an error object.\n * Errors in JS frequently carry extra properties (code, statusCode, etc.)\n * that aren't part of the Error interface. This avoids `as unknown as` casts.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction errorProps(err: Error): Record<string, any> {\n return err;\n}\n\nconst NETWORK_ERROR_CODES = new Set([\n \"ECONNREFUSED\",\n \"ENOTFOUND\",\n \"ETIMEDOUT\",\n \"ECONNRESET\",\n \"ECONNABORTED\",\n \"EPIPE\",\n \"UND_ERR_CONNECT_TIMEOUT\",\n]);\n\n/**\n * Detect network errors structurally rather than by string matching.\n * Checks Node.js system error codes on the error and its cause.\n */\nexport function isNetworkError(err: Error): boolean {\n if (err.name === \"AbortError\") return true;\n\n const props = errorProps(err);\n const sysCode = props.code as string | undefined;\n if (typeof sysCode === \"string\" && NETWORK_ERROR_CODES.has(sysCode))\n return true;\n\n // fetch() throws TypeError — only classify as network error when cause\n // indicates a system-level failure\n if (err.name === \"TypeError\" && err.cause instanceof Error) {\n const causeCode = errorProps(err.cause).code;\n if (typeof causeCode === \"string\" && NETWORK_ERROR_CODES.has(causeCode))\n return true;\n }\n\n // Browser fetch() failures: TypeError with known messages, no system error code\n if (err.name === \"TypeError\") {\n const msg = err.message.toLowerCase();\n if (\n msg === \"failed to fetch\" ||\n msg === \"load failed\" ||\n msg.includes(\"networkerror\")\n ) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Detect unauthorized errors structurally.\n * Checks status code and numeric code rather than string matching on name.\n */\nexport function isUnauthorizedError(err: Error): boolean {\n const props = errorProps(err);\n\n // Check HTTP status code (most reliable)\n if (props.statusCode === 401 || props.status === 401) return true;\n\n // StreamableHTTPError stores status as `.code`\n if (props.code === 401) return true;\n\n // MCP SDK UnauthorizedError extends Error without setting `.name`,\n // so `.name` is \"Error\". Use constructor name as fallback.\n if (err.name === \"UnauthorizedError\") return true;\n if (err.constructor?.name === \"UnauthorizedError\") return true;\n if (err.message === \"Unauthorized\") return true;\n\n return false;\n}\n\n// ============================================================================\n// Elicitation types\n// ============================================================================\n\nexport interface ElicitationEntry {\n readonly url: string;\n readonly message: string;\n readonly elicitationId: string;\n readonly integrationId?: string;\n readonly integrationName?: string;\n}\n\n// ============================================================================\n// HTTP error parsing\n// ============================================================================\n\n/**\n * Parse an HTTP response into an appropriate error.\n */\nexport function parseHttpError(\n statusCode: number,\n body?: unknown,\n): KontextError {\n const message =\n typeof body === \"object\" && body !== null && \"message\" in body\n ? String((body as { message: unknown }).message)\n : `HTTP ${statusCode}`;\n\n const errorCode =\n typeof body === \"object\" && body !== null && \"code\" in body\n ? String((body as { code: unknown }).code)\n : undefined;\n\n switch (statusCode) {\n case 400:\n if (\n typeof body === \"object\" &&\n body !== null &&\n \"errors\" in body &&\n Array.isArray((body as { errors: unknown }).errors)\n ) {\n return new HttpError(message, \"kontext_validation_error\", {\n statusCode: 400,\n validationErrors: (\n body as { errors: Array<{ field: string; message: string }> }\n ).errors,\n });\n }\n return new KontextError(message, errorCode ?? \"kontext_bad_request\", {\n statusCode: 400,\n });\n\n case 401:\n return new AuthorizationRequiredError(message);\n\n case 403:\n if (errorCode === \"INTEGRATION_CONNECTION_REQUIRED\") {\n const details = body as {\n integrationId?: string;\n integrationName?: string;\n connectUrl?: string;\n };\n return new IntegrationConnectionRequiredError(\n details.integrationId ?? \"unknown\",\n {\n integrationName: details.integrationName,\n connectUrl: details.connectUrl,\n message,\n },\n );\n }\n return new HttpError(message, \"kontext_policy_denied\", {\n statusCode: 403,\n meta: { policy: (body as Record<string, unknown>)?.policy },\n });\n\n case 404:\n return new HttpError(message, \"kontext_not_found\", { statusCode: 404 });\n\n case 429: {\n const retryAfter =\n typeof body === \"object\" && body !== null && \"retryAfter\" in body\n ? Number((body as { retryAfter: unknown }).retryAfter)\n : undefined;\n return new HttpError(\n retryAfter\n ? `Rate limit exceeded. Retry after ${retryAfter} seconds.`\n : \"Rate limit exceeded. Wait and retry.\",\n \"kontext_rate_limited\",\n { statusCode: 429, retryAfter },\n );\n }\n\n default:\n if (statusCode >= 500) {\n return new HttpError(\n `Server error (HTTP ${statusCode}): ${message}`,\n \"kontext_server_error\",\n { statusCode },\n );\n }\n return new KontextError(message, errorCode ?? \"kontext_unknown_error\", {\n statusCode,\n });\n }\n}\n\n// ============================================================================\n// MCP error translation\n// ============================================================================\n\nconst MCP_CODE_MAP: Record<number, { code: string; statusCode?: number }> = {\n [ErrorCode.ParseError]: { code: \"kontext_mcp_parse_error\" },\n [ErrorCode.InvalidRequest]: { code: \"kontext_mcp_invalid_request\" },\n [ErrorCode.MethodNotFound]: { code: \"kontext_mcp_method_not_found\" },\n [ErrorCode.InvalidParams]: { code: \"kontext_mcp_invalid_params\" },\n [ErrorCode.InternalError]: {\n code: \"kontext_mcp_internal_error\",\n statusCode: 500,\n },\n [ErrorCode.RequestTimeout]: {\n code: \"kontext_mcp_session_expired\",\n statusCode: 401,\n },\n [ErrorCode.ConnectionClosed]: { code: \"kontext_mcp_session_error\" },\n};\n\n/**\n * Translate external errors into KontextError instances.\n * Uses structural checks (numeric codes, status codes, system error codes)\n * rather than fragile string matching.\n */\nexport function translateError(err: unknown): KontextError {\n if (isKontextError(err)) return err as KontextError;\n\n if (!(err instanceof Error)) {\n return new KontextError(String(err), \"kontext_unknown_error\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const props: Record<string, any> = err;\n\n // 1. MCP protocol -32042 (URL elicitation required) — check numeric code\n if (props.code === ErrorCode.UrlElicitationRequired) {\n const elicitations = (props.elicitations ?? props.data?.elicitations) as\n | ElicitationEntry[]\n | undefined;\n const elicitation = elicitations?.[0];\n return new IntegrationConnectionRequiredError(\n elicitation?.integrationId ?? \"unknown\",\n {\n integrationName: elicitation?.integrationName,\n connectUrl: elicitation?.url,\n message: elicitation?.message,\n cause: err,\n },\n );\n }\n\n // 2. MCP JSON-RPC errors (standard and server-defined ranges)\n if (typeof props.code === \"number\" && props.code < 0) {\n const entry = MCP_CODE_MAP[props.code];\n if (entry) {\n return new KontextError(err.message, entry.code, {\n statusCode: entry.statusCode,\n cause: err,\n });\n }\n return new KontextError(err.message, \"kontext_mcp_error\", {\n cause: err,\n meta: { mcpCode: props.code },\n });\n }\n\n // 3. HTTP status on the error (from MCP SDK or fetch wrappers)\n // StreamableHTTPError stores HTTP status as `.code` (not `.statusCode`/`.status`),\n // so check `.code` as well when it's in the HTTP error range (400–599).\n const statusCode = (props.statusCode ??\n props.status ??\n (typeof props.code === \"number\" && props.code >= 400 && props.code < 600\n ? props.code\n : undefined)) as number | undefined;\n if (typeof statusCode === \"number\" && statusCode >= 400) {\n if (statusCode === 401) {\n return new AuthorizationRequiredError(err.message, { cause: err });\n }\n return new KontextError(err.message, \"kontext_server_error\", {\n statusCode,\n cause: err,\n });\n }\n\n // 4. Auth errors — structural check\n if (isUnauthorizedError(err)) {\n return new AuthorizationRequiredError(err.message, { cause: err });\n }\n\n // 5. Network errors — structural check (system error codes, not string matching)\n if (isNetworkError(err)) {\n return new NetworkError(err.message, { cause: err });\n }\n\n // 6. Fallback\n return new KontextError(err.message, \"kontext_unknown_error\", {\n cause: err,\n });\n}\n","/**\n * React bindings for Kontext\n *\n * Provides the `useKontext` hook for managing Kontext MCP authentication\n * in React applications. Platform-agnostic — works with any framework\n * that sends MCP update events (e.g. Cloudflare Agents' `useAgent`).\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n *\n * function App() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * // ...\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport {\n useState,\n useCallback,\n useRef,\n createContext,\n useContext,\n useMemo,\n createElement,\n type ReactNode,\n} from \"react\";\nimport { ConfigError } from \"../../errors.js\";\n\nexport interface UseKontextOptions {\n /** Custom window features for the OAuth popup (default: \"width=600,height=700\") */\n popupFeatures?: string;\n\n /** Called when the auth popup closes */\n onAuthComplete?: () => void;\n}\n\n/** MCP server state as broadcast by the Cloudflare Agents framework */\ninterface McpServerState {\n auth_url?: string | null;\n elicitation_url?: string | null;\n state?: string;\n name?: string;\n [key: string]: unknown;\n}\n\n/** MCP state object passed to `onMcpUpdate` by `useAgent` */\ninterface McpState {\n servers?: Record<string, McpServerState>;\n tools?: unknown[];\n prompts?: unknown[];\n resources?: unknown[];\n}\n\nexport interface UseKontextReturn {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /**\n * MCP update handler — pass this to your agent framework's MCP update callback.\n * Automatically opens an OAuth popup when the server requires authentication.\n *\n * @example Cloudflare Agents\n * ```typescript\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * ```\n */\n onMcpUpdate: (data: McpState) => void;\n\n /**\n * Open an elicitation URL in a popup.\n * Useful for handling URL elicitation from the KontextMcp client's\n * `onElicitationUrl` callback in a React context.\n *\n * @param url The URL to open\n */\n handleElicitationUrl: (url: string) => void;\n}\n\n/**\n * React hook for Kontext MCP authentication.\n *\n * Handles the OAuth popup flow when an MCP server requires authentication.\n * Returns a handler to wire into your agent framework and auth state.\n *\n * @example\n * ```typescript\n * import { useKontext } from \"@kontext-dev/js-sdk/react\";\n * import { useAgent } from \"agents/react\";\n * import { useAgentChat } from \"@cloudflare/ai-chat/react\";\n *\n * function Chat() {\n * const { onMcpUpdate, authenticating } = useKontext();\n * const agent = useAgent({ agent: \"chat\", onMcpUpdate });\n * const { messages, sendMessage } = useAgentChat({ agent });\n * // ...\n * }\n * ```\n */\nexport function useKontext(options: UseKontextOptions = {}): UseKontextReturn {\n const [authenticating, setAuthenticating] = useState(false);\n const popupRef = useRef<Window | null>(null);\n\n const openPopup = useCallback(\n (url: string, name: string) => {\n if (popupRef.current) return;\n\n setAuthenticating(true);\n const popup = window.open(\n url,\n name,\n options.popupFeatures ?? \"width=600,height=700\",\n );\n popupRef.current = popup;\n\n if (popup) {\n const check = setInterval(() => {\n if (popup.closed) {\n clearInterval(check);\n popupRef.current = null;\n setAuthenticating(false);\n options.onAuthComplete?.();\n }\n }, 500);\n } else {\n // Popup was blocked by the browser\n popupRef.current = null;\n setAuthenticating(false);\n console.warn(\n \"[kontext_popup_blocked] OAuth popup was blocked by the browser. \" +\n \"The user must allow popups for this site to authenticate. \" +\n \"Docs: https://docs.kontext.dev/errors/kontext_popup_blocked\",\n );\n }\n },\n [options.popupFeatures, options.onAuthComplete],\n );\n\n const onMcpUpdate = useCallback(\n (data: McpState) => {\n if (popupRef.current) {\n return;\n }\n\n const servers = data.servers ?? {};\n\n // Check for authentication URLs\n const authUrl = Object.values(servers).find(\n (s) => s.auth_url && s.state === \"authenticating\",\n )?.auth_url;\n\n if (authUrl) {\n openPopup(authUrl, \"kontext-auth\");\n return;\n }\n\n // Check for elicitation URLs (integration OAuth via Cloudflare path)\n const elicitationUrl = Object.values(servers).find(\n (s) => s.elicitation_url,\n )?.elicitation_url;\n\n if (elicitationUrl) {\n openPopup(elicitationUrl, \"kontext-elicitation\");\n }\n },\n [openPopup],\n );\n\n const handleElicitationUrl = useCallback(\n (url: string) => {\n openPopup(url, \"kontext-elicitation\");\n },\n [openPopup],\n );\n\n return { authenticating, onMcpUpdate, handleElicitationUrl };\n}\n\n// ============================================================================\n// Context Provider — \"wrap and forget\" alternative to useKontext\n// ============================================================================\n\n/** Values exposed by KontextProvider via React Context */\nexport interface KontextContextValue {\n /** Whether an OAuth popup is currently open */\n authenticating: boolean;\n\n /** MCP update handler — injected automatically by useKontextAgent */\n onMcpUpdate: (data: McpState) => void;\n\n /** Open an elicitation URL in a popup */\n handleElicitationUrl: (url: string) => void;\n}\n\nexport interface KontextProviderProps extends UseKontextOptions {\n children: ReactNode;\n}\n\nconst KontextContext = createContext<KontextContextValue | null>(null);\n\n/**\n * React Context Provider that manages Kontext auth/elicitation popups.\n *\n * Wrap your app (or the subtree that uses Kontext) with this provider,\n * then use `useKontextAgent()` (from `@kontext-dev/js-sdk/react/cloudflare`)\n * or `useKontextContext()` to access the context directly.\n *\n * @example\n * ```tsx\n * import { KontextProvider } from \"@kontext-dev/js-sdk/react\";\n *\n * <KontextProvider>\n * <App />\n * </KontextProvider>\n * ```\n */\nexport function KontextProvider({\n children,\n ...options\n}: KontextProviderProps) {\n const { authenticating, onMcpUpdate, handleElicitationUrl } =\n useKontext(options);\n\n const value = useMemo<KontextContextValue>(\n () => ({ authenticating, onMcpUpdate, handleElicitationUrl }),\n [authenticating, onMcpUpdate, handleElicitationUrl],\n );\n\n return createElement(KontextContext.Provider, { value }, children);\n}\n\n/**\n * Access the KontextProvider context directly.\n * Must be used within a `<KontextProvider>`.\n */\nexport function useKontextContext(): KontextContextValue {\n const ctx = useContext(KontextContext);\n if (!ctx) {\n throw new ConfigError(\n \"useKontextContext must be used within a <KontextProvider>. Wrap your component tree with KontextProvider.\",\n \"kontext_context_missing\",\n );\n }\n return ctx;\n}\n"]}
|