agents 0.7.5 → 0.7.7

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.
@@ -602,18 +602,31 @@ var MCPClientConnection = class {
602
602
  const defaultClientOptions = { jsonSchemaValidator: new CfWorkerJsonSchemaValidator() };
603
603
  /**
604
604
  * Blocked hostname patterns for SSRF protection.
605
- * Prevents MCP client from connecting to internal/private network addresses.
605
+ * Prevents MCP client from connecting to internal/private network addresses
606
+ * while allowing loopback hosts for local development.
606
607
  */
607
608
  const BLOCKED_HOSTNAMES = new Set([
608
- "localhost",
609
609
  "0.0.0.0",
610
- "[::1]",
611
610
  "[::]",
612
611
  "metadata.google.internal"
613
612
  ]);
614
613
  /**
614
+ * Check whether four IPv4 octets belong to a private/reserved range.
615
+ * Blocks RFC 1918, link-local, cloud metadata, and unspecified addresses.
616
+ */
617
+ function isPrivateIPv4(octets) {
618
+ const [a, b] = octets;
619
+ if (a === 10) return true;
620
+ if (a === 172 && b >= 16 && b <= 31) return true;
621
+ if (a === 192 && b === 168) return true;
622
+ if (a === 169 && b === 254) return true;
623
+ if (a === 0) return true;
624
+ return false;
625
+ }
626
+ /**
615
627
  * Check whether a hostname looks like a private/internal IP address.
616
- * Blocks RFC 1918, link-local, loopback, and cloud metadata endpoints.
628
+ * Blocks RFC 1918, link-local, unique-local, unspecified,
629
+ * and cloud metadata endpoints. Also detects IPv4-mapped IPv6 addresses.
617
630
  */
618
631
  function isBlockedUrl(url) {
619
632
  let parsed;
@@ -626,18 +639,31 @@ function isBlockedUrl(url) {
626
639
  if (BLOCKED_HOSTNAMES.has(hostname)) return true;
627
640
  const ipv4Parts = hostname.split(".");
628
641
  if (ipv4Parts.length === 4 && ipv4Parts.every((p) => /^\d{1,3}$/.test(p))) {
629
- const [a, b] = ipv4Parts.map(Number);
630
- if (a === 10) return true;
631
- if (a === 172 && b >= 16 && b <= 31) return true;
632
- if (a === 192 && b === 168) return true;
633
- if (a === 127) return true;
634
- if (a === 169 && b === 254) return true;
635
- if (a === 0) return true;
642
+ if (isPrivateIPv4(ipv4Parts.map(Number))) return true;
636
643
  }
637
644
  if (hostname.startsWith("[") && hostname.endsWith("]")) {
638
645
  const addr = hostname.slice(1, -1).toLowerCase();
639
646
  if (addr.startsWith("fc") || addr.startsWith("fd")) return true;
640
647
  if (addr.startsWith("fe80")) return true;
648
+ if (addr.startsWith("::ffff:")) {
649
+ const mapped = addr.slice(7);
650
+ const dotParts = mapped.split(".");
651
+ if (dotParts.length === 4 && dotParts.every((p) => /^\d{1,3}$/.test(p))) {
652
+ if (isPrivateIPv4(dotParts.map(Number))) return true;
653
+ } else {
654
+ const hexParts = mapped.split(":");
655
+ if (hexParts.length === 2) {
656
+ const hi = parseInt(hexParts[0], 16);
657
+ const lo = parseInt(hexParts[1], 16);
658
+ if (isPrivateIPv4([
659
+ hi >> 8 & 255,
660
+ hi & 255,
661
+ lo >> 8 & 255,
662
+ lo & 255
663
+ ])) return true;
664
+ }
665
+ }
666
+ }
641
667
  }
642
668
  return false;
643
669
  }
@@ -1439,4 +1465,4 @@ function getNamespacedData(mcpClients, type) {
1439
1465
  //#endregion
1440
1466
  export { RPCServerTransport as a, RPCClientTransport as i, getNamespacedData as n, RPC_DO_PREFIX as o, MCPConnectionState as r, DisposableStore as s, MCPClientManager as t };
1441
1467
 
1442
- //# sourceMappingURL=client-CgXIwdcc.js.map
1468
+ //# sourceMappingURL=client-K8Z-u76l.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-K8Z-u76l.js","names":[],"sources":["../src/core/events.ts","../src/mcp/errors.ts","../src/mcp/rpc.ts","../src/mcp/client-connection.ts","../src/mcp/client.ts"],"sourcesContent":["export interface Disposable {\n dispose(): void;\n}\n\nexport function toDisposable(fn: () => void): Disposable {\n return { dispose: fn };\n}\n\nexport class DisposableStore implements Disposable {\n private readonly _items: Disposable[] = [];\n\n add<T extends Disposable>(d: T): T {\n this._items.push(d);\n return d;\n }\n\n dispose(): void {\n while (this._items.length) {\n try {\n this._items.pop()!.dispose();\n } catch {\n // best-effort cleanup\n }\n }\n }\n}\n\nexport type Event<T> = (listener: (e: T) => void) => Disposable;\n\nexport class Emitter<T> implements Disposable {\n private _listeners: Set<(e: T) => void> = new Set();\n\n readonly event: Event<T> = (listener) => {\n this._listeners.add(listener);\n return toDisposable(() => this._listeners.delete(listener));\n };\n\n fire(data: T): void {\n for (const listener of [...this._listeners]) {\n try {\n listener(data);\n } catch (err) {\n // do not let one bad listener break others\n console.error(\"Emitter listener error:\", err);\n }\n }\n }\n\n dispose(): void {\n this._listeners.clear();\n }\n}\n","export function toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction getErrorCode(error: unknown): number | undefined {\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n typeof (error as { code: unknown }).code === \"number\"\n ) {\n return (error as { code: number }).code;\n }\n return undefined;\n}\n\nexport function isUnauthorized(error: unknown): boolean {\n const code = getErrorCode(error);\n if (code === 401) return true;\n\n const msg = toErrorMessage(error);\n return msg.includes(\"Unauthorized\") || msg.includes(\"401\");\n}\n\n// MCP SDK change (v1.24.0, commit 6b90e1a):\n// - Old: Error POSTing to endpoint (HTTP 404): Not Found\n// - New: StreamableHTTPError with code: 404 and message Error POSTing to endpoint: Not Found\nexport function isTransportNotImplemented(error: unknown): boolean {\n const code = getErrorCode(error);\n if (code === 404 || code === 405) return true;\n\n const msg = toErrorMessage(error);\n return (\n msg.includes(\"404\") ||\n msg.includes(\"405\") ||\n msg.includes(\"Not Implemented\") ||\n msg.includes(\"not implemented\")\n );\n}\n","import type {\n Transport,\n TransportSendOptions\n} from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type {\n JSONRPCMessage,\n MessageExtraInfo\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { JSONRPCMessageSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport { getServerByName } from \"partyserver\";\nimport type { McpAgent } from \".\";\n\nexport const RPC_DO_PREFIX = \"rpc:\";\n\nfunction makeInvalidRequestError(id: unknown): JSONRPCMessage {\n return {\n jsonrpc: \"2.0\",\n id: id ?? null,\n error: {\n code: -32600,\n message: \"Invalid Request\"\n }\n } as JSONRPCMessage;\n}\n\nfunction validateBatch(batch: JSONRPCMessage[]): void {\n if (batch.length === 0) {\n throw new Error(\"Invalid JSON-RPC batch: array must not be empty\");\n }\n}\n\nexport interface RPCClientTransportOptions<T extends McpAgent = McpAgent> {\n namespace: DurableObjectNamespace<T>;\n name: string;\n props?: Record<string, unknown>;\n}\n\nexport class RPCClientTransport implements Transport {\n private _namespace: DurableObjectNamespace<McpAgent>;\n private _name: string;\n private _props?: Record<string, unknown>;\n private _stub?: DurableObjectStub<McpAgent>;\n private _started = false;\n private _protocolVersion?: string;\n\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;\n\n constructor(options: RPCClientTransportOptions<McpAgent>) {\n this._namespace = options.namespace;\n this._name = options.name;\n this._props = options.props;\n }\n\n setProtocolVersion(version: string): void {\n this._protocolVersion = version;\n }\n\n getProtocolVersion(): string | undefined {\n return this._protocolVersion;\n }\n\n async start(): Promise<void> {\n if (this._started) {\n throw new Error(\"Transport already started\");\n }\n\n const doName = `${RPC_DO_PREFIX}${this._name}`;\n this._stub = await getServerByName(this._namespace, doName, {\n props: this._props\n });\n\n this._started = true;\n }\n\n async close(): Promise<void> {\n this._started = false;\n this._stub = undefined;\n this.onclose?.();\n }\n\n async send(\n message: JSONRPCMessage | JSONRPCMessage[],\n options?: TransportSendOptions\n ): Promise<void> {\n if (!this._started || !this._stub) {\n throw new Error(\"Transport not started\");\n }\n\n try {\n const result: JSONRPCMessage | JSONRPCMessage[] | undefined =\n await this._stub.handleMcpMessage(message);\n\n if (!result) {\n return;\n }\n\n const extra: MessageExtraInfo | undefined = options?.relatedRequestId\n ? { requestInfo: { headers: {} } }\n : undefined;\n\n const messages = Array.isArray(result) ? result : [result];\n for (const msg of messages) {\n this.onmessage?.(msg, extra);\n }\n } catch (error) {\n this.onerror?.(error as Error);\n throw error;\n }\n }\n}\n\nexport interface RPCServerTransportOptions {\n timeout?: number;\n}\n\nexport class RPCServerTransport implements Transport {\n private _started = false;\n private _pendingResponse: JSONRPCMessage | JSONRPCMessage[] | null = null;\n private _responseResolver: (() => void) | null = null;\n private _protocolVersion?: string;\n private _timeout: number;\n\n sessionId?: string;\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;\n\n constructor(options?: RPCServerTransportOptions) {\n this._timeout = options?.timeout ?? 60000;\n }\n\n setProtocolVersion(version: string): void {\n this._protocolVersion = version;\n }\n\n getProtocolVersion(): string | undefined {\n return this._protocolVersion;\n }\n\n async start(): Promise<void> {\n if (this._started) {\n throw new Error(\"Transport already started\");\n }\n this._started = true;\n }\n\n async close(): Promise<void> {\n this._started = false;\n this.onclose?.();\n if (this._responseResolver) {\n this._responseResolver();\n this._responseResolver = null;\n }\n }\n\n async send(\n message: JSONRPCMessage,\n _options?: TransportSendOptions\n ): Promise<void> {\n if (!this._started) {\n throw new Error(\"Transport not started\");\n }\n\n if (!this._pendingResponse) {\n this._pendingResponse = message;\n } else if (Array.isArray(this._pendingResponse)) {\n this._pendingResponse.push(message);\n } else {\n this._pendingResponse = [this._pendingResponse, message];\n }\n\n if (this._responseResolver) {\n const resolver = this._responseResolver;\n queueMicrotask(() => resolver());\n }\n }\n\n async handle(\n message: JSONRPCMessage | JSONRPCMessage[]\n ): Promise<JSONRPCMessage | JSONRPCMessage[] | undefined> {\n if (!this._started) {\n throw new Error(\"Transport not started\");\n }\n\n if (Array.isArray(message)) {\n validateBatch(message);\n\n const responses: JSONRPCMessage[] = [];\n for (const msg of message) {\n const response = await this.handle(msg);\n if (response !== undefined) {\n if (Array.isArray(response)) {\n responses.push(...response);\n } else {\n responses.push(response);\n }\n }\n }\n\n return responses.length === 0 ? undefined : responses;\n }\n\n try {\n JSONRPCMessageSchema.parse(message);\n } catch {\n const id =\n typeof message === \"object\" && message !== null && \"id\" in message\n ? (message as { id: unknown }).id\n : null;\n return makeInvalidRequestError(id);\n }\n\n this._pendingResponse = null;\n\n const isNotification = !(\"id\" in message);\n if (isNotification) {\n this.onmessage?.(message);\n return undefined;\n }\n\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n const responsePromise = new Promise<void>((resolve, reject) => {\n timeoutId = setTimeout(() => {\n this._responseResolver = null;\n reject(\n new Error(\n `Request timeout: No response received within ${this._timeout}ms`\n )\n );\n }, this._timeout);\n\n this._responseResolver = () => {\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n this._responseResolver = null;\n resolve();\n };\n });\n\n this.onmessage?.(message);\n\n try {\n await responsePromise;\n } catch (error) {\n this._pendingResponse = null;\n this._responseResolver = null;\n throw error;\n }\n\n const response = this._pendingResponse;\n this._pendingResponse = null;\n\n return response ?? undefined;\n }\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport {\n SSEClientTransport,\n type SSEClientTransportOptions\n} from \"@modelcontextprotocol/sdk/client/sse.js\";\nimport {\n StreamableHTTPClientTransport,\n type StreamableHTTPClientTransportOptions\n} from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\n// Import types directly from MCP SDK\nimport type {\n Prompt,\n Resource,\n Tool\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n type ClientCapabilities,\n type ElicitRequest,\n ElicitRequestSchema,\n type ElicitResult,\n type ListPromptsResult,\n type ListResourceTemplatesResult,\n type ListResourcesResult,\n type ListToolsResult,\n PromptListChangedNotificationSchema,\n ResourceListChangedNotificationSchema,\n type ResourceTemplate,\n type ServerCapabilities,\n ToolListChangedNotificationSchema\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { Emitter, type Event } from \"../core/events\";\nimport type { MCPObservabilityEvent } from \"../observability/mcp\";\nimport type { AgentMcpOAuthProvider } from \"./do-oauth-client-provider\";\nimport {\n isTransportNotImplemented,\n isUnauthorized,\n toErrorMessage\n} from \"./errors\";\nimport { RPCClientTransport, type RPCClientTransportOptions } from \"./rpc\";\nimport type {\n BaseTransportType,\n HttpTransportType,\n TransportType,\n McpClientOptions\n} from \"./types\";\n\n/**\n * Connection state machine for MCP client connections.\n *\n * State transitions:\n * - Non-OAuth: init() → CONNECTING → DISCOVERING → READY\n * - OAuth: init() → AUTHENTICATING → (callback) → CONNECTING → DISCOVERING → READY\n * - Any state can transition to FAILED on error\n */\nexport const MCPConnectionState = {\n /** Waiting for OAuth authorization to complete */\n AUTHENTICATING: \"authenticating\",\n /** Establishing transport connection to MCP server */\n CONNECTING: \"connecting\",\n /** Transport connection established */\n CONNECTED: \"connected\",\n /** Discovering server capabilities (tools, resources, prompts) */\n DISCOVERING: \"discovering\",\n /** Fully connected and ready to use */\n READY: \"ready\",\n /** Connection failed at some point */\n FAILED: \"failed\"\n} as const;\n\n/**\n * Connection state type for MCP client connections.\n */\nexport type MCPConnectionState =\n (typeof MCPConnectionState)[keyof typeof MCPConnectionState];\n\n/**\n * Transport options for MCP client connections.\n * Combines transport-specific options with auth provider and type selection.\n */\nexport type MCPTransportOptions = (\n | SSEClientTransportOptions\n | StreamableHTTPClientTransportOptions\n | RPCClientTransportOptions\n) & {\n authProvider?: AgentMcpOAuthProvider;\n type?: TransportType;\n};\n\nexport type MCPClientConnectionResult = {\n state: MCPConnectionState;\n error?: Error;\n transport?: BaseTransportType;\n};\n\n/**\n * Result of a discovery operation.\n * success indicates whether discovery completed successfully.\n * error is present when success is false.\n */\nexport type MCPDiscoveryResult = {\n success: boolean;\n error?: string;\n};\n\nexport class MCPClientConnection {\n client: Client;\n connectionState: MCPConnectionState = MCPConnectionState.CONNECTING;\n connectionError: string | null = null;\n lastConnectedTransport: BaseTransportType | undefined;\n instructions?: string;\n tools: Tool[] = [];\n prompts: Prompt[] = [];\n resources: Resource[] = [];\n resourceTemplates: ResourceTemplate[] = [];\n serverCapabilities: ServerCapabilities | undefined;\n\n /** Tracks in-flight discovery to allow cancellation */\n private _discoveryAbortController: AbortController | undefined;\n\n private readonly _onObservabilityEvent = new Emitter<MCPObservabilityEvent>();\n public readonly onObservabilityEvent: Event<MCPObservabilityEvent> =\n this._onObservabilityEvent.event;\n\n constructor(\n public url: URL,\n info: ConstructorParameters<typeof Client>[0],\n public options: {\n transport: MCPTransportOptions;\n client: McpClientOptions;\n } = { client: {}, transport: {} }\n ) {\n const clientOptions = {\n ...options.client,\n capabilities: {\n ...options.client?.capabilities,\n elicitation: {}\n } as ClientCapabilities\n };\n\n this.client = new Client(info, clientOptions);\n }\n\n /**\n * Initialize a client connection, if authentication is required, the connection will be in the AUTHENTICATING state\n * Sets connection state based on the result and emits observability events\n *\n * @returns Error message if connection failed, undefined otherwise\n */\n async init(): Promise<string | undefined> {\n const transportType = this.options.transport.type;\n if (!transportType) {\n throw new Error(\"Transport type must be specified\");\n }\n\n const res = await this.tryConnect(transportType);\n\n // Set the connection state\n this.connectionState = res.state;\n\n // Handle the result and emit appropriate events\n if (res.state === MCPConnectionState.CONNECTED && res.transport) {\n // Set up elicitation request handler after successful connection\n this.client.setRequestHandler(\n ElicitRequestSchema,\n async (request: ElicitRequest) => {\n return await this.handleElicitationRequest(request);\n }\n );\n\n this.lastConnectedTransport = res.transport;\n\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: this.url.toString(),\n transport: res.transport,\n state: this.connectionState\n },\n timestamp: Date.now()\n });\n return undefined;\n } else if (res.state === MCPConnectionState.FAILED && res.error) {\n const errorMessage = toErrorMessage(res.error);\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: this.url.toString(),\n transport: transportType,\n state: this.connectionState,\n error: errorMessage\n },\n timestamp: Date.now()\n });\n return errorMessage;\n }\n return undefined;\n }\n\n /**\n * Finish OAuth by probing transports based on configured type.\n * - Explicit: finish on that transport\n * - Auto: try streamable-http, then sse on 404/405/Not Implemented\n */\n private async finishAuthProbe(code: string): Promise<void> {\n if (!this.options.transport.authProvider) {\n throw new Error(\"No auth provider configured\");\n }\n\n const configuredType = this.options.transport.type;\n if (!configuredType) {\n throw new Error(\"Transport type must be specified\");\n }\n\n const finishAuth = async (base: HttpTransportType) => {\n const transport = this.getTransport(base);\n if (\n \"finishAuth\" in transport &&\n typeof transport.finishAuth === \"function\"\n ) {\n await transport.finishAuth(code);\n }\n };\n\n if (configuredType === \"rpc\") {\n throw new Error(\"RPC transport does not support authentication\");\n }\n\n if (configuredType === \"sse\" || configuredType === \"streamable-http\") {\n await finishAuth(configuredType);\n return;\n }\n\n // For \"auto\" mode, try streamable-http first, then fall back to SSE\n try {\n await finishAuth(\"streamable-http\");\n } catch (e) {\n if (isTransportNotImplemented(e)) {\n await finishAuth(\"sse\");\n return;\n }\n throw e;\n }\n }\n\n /**\n * Complete OAuth authorization\n */\n async completeAuthorization(code: string): Promise<void> {\n if (this.connectionState !== MCPConnectionState.AUTHENTICATING) {\n throw new Error(\n \"Connection must be in authenticating state to complete authorization\"\n );\n }\n\n try {\n // Finish OAuth by probing transports per configuration\n await this.finishAuthProbe(code);\n\n // Mark as connecting\n this.connectionState = MCPConnectionState.CONNECTING;\n } catch (error) {\n this.connectionState = MCPConnectionState.FAILED;\n throw error;\n }\n }\n\n /**\n * Discover server capabilities and register tools, resources, prompts, and templates.\n * This method does the work but does not manage connection state - that's handled by discover().\n */\n async discoverAndRegister(): Promise<void> {\n this.serverCapabilities = this.client.getServerCapabilities();\n if (!this.serverCapabilities) {\n throw new Error(\"The MCP Server failed to return server capabilities\");\n }\n\n // Build list of operations to perform based on server capabilities\n type DiscoveryResult =\n | string\n | undefined\n | Tool[]\n | Resource[]\n | Prompt[]\n | ResourceTemplate[];\n const operations: Promise<DiscoveryResult>[] = [];\n const operationNames: string[] = [];\n\n // Instructions (always try to fetch if available)\n operations.push(Promise.resolve(this.client.getInstructions()));\n operationNames.push(\"instructions\");\n\n // Only register capabilities that the server advertises\n if (this.serverCapabilities.tools) {\n operations.push(this.registerTools());\n operationNames.push(\"tools\");\n }\n\n if (this.serverCapabilities.resources) {\n operations.push(this.registerResources());\n operationNames.push(\"resources\");\n }\n\n if (this.serverCapabilities.prompts) {\n operations.push(this.registerPrompts());\n operationNames.push(\"prompts\");\n }\n\n if (this.serverCapabilities.resources) {\n operations.push(this.registerResourceTemplates());\n operationNames.push(\"resource templates\");\n }\n\n try {\n const results = await Promise.all(operations);\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const name = operationNames[i];\n\n switch (name) {\n case \"instructions\":\n this.instructions = result as string | undefined;\n break;\n case \"tools\":\n this.tools = result as Tool[];\n break;\n case \"resources\":\n this.resources = result as Resource[];\n break;\n case \"prompts\":\n this.prompts = result as Prompt[];\n break;\n case \"resource templates\":\n this.resourceTemplates = result as ResourceTemplate[];\n break;\n }\n }\n } catch (error) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:discover\",\n payload: {\n url: this.url.toString(),\n error: toErrorMessage(error)\n },\n timestamp: Date.now()\n });\n\n throw error;\n }\n }\n\n /**\n * Discover server capabilities with timeout and cancellation support.\n * If called while a previous discovery is in-flight, the previous discovery will be aborted.\n *\n * @param options Optional configuration\n * @param options.timeoutMs Timeout in milliseconds (default: 15000)\n * @returns Result indicating success/failure with optional error message\n */\n async discover(\n options: { timeoutMs?: number } = {}\n ): Promise<MCPDiscoveryResult> {\n const { timeoutMs = 15000 } = options;\n\n // Check if state allows discovery\n if (\n this.connectionState !== MCPConnectionState.CONNECTED &&\n this.connectionState !== MCPConnectionState.READY\n ) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:discover\",\n payload: {\n url: this.url.toString(),\n state: this.connectionState\n },\n timestamp: Date.now()\n });\n return {\n success: false,\n error: `Discovery skipped - connection in ${this.connectionState} state`\n };\n }\n\n // Cancel any previous in-flight discovery\n if (this._discoveryAbortController) {\n this._discoveryAbortController.abort();\n this._discoveryAbortController = undefined;\n }\n\n // Create a new AbortController for this discovery\n const abortController = new AbortController();\n this._discoveryAbortController = abortController;\n\n this.connectionState = MCPConnectionState.DISCOVERING;\n\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n try {\n // Create timeout promise\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(`Discovery timed out after ${timeoutMs}ms`)),\n timeoutMs\n );\n });\n\n // Check if aborted before starting\n if (abortController.signal.aborted) {\n throw new Error(\"Discovery was cancelled\");\n }\n\n // Create an abort promise that rejects when signal fires\n const abortPromise = new Promise<never>((_, reject) => {\n abortController.signal.addEventListener(\"abort\", () => {\n reject(new Error(\"Discovery was cancelled\"));\n });\n });\n\n await Promise.race([\n this.discoverAndRegister(),\n timeoutPromise,\n abortPromise\n ]);\n\n // Clear timeout on success\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n\n // Discovery succeeded - transition to ready\n this.connectionState = MCPConnectionState.READY;\n\n this._onObservabilityEvent.fire({\n type: \"mcp:client:discover\",\n payload: {\n url: this.url.toString()\n },\n timestamp: Date.now()\n });\n\n return { success: true };\n } catch (e) {\n // Always clear the timeout\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n\n // Return to CONNECTED state so user can retry discovery\n this.connectionState = MCPConnectionState.CONNECTED;\n\n const error = e instanceof Error ? e.message : String(e);\n return { success: false, error };\n } finally {\n // Clean up the abort controller\n this._discoveryAbortController = undefined;\n }\n }\n\n /**\n * Cancel any in-flight discovery operation.\n * Called when closing the connection.\n */\n cancelDiscovery(): void {\n if (this._discoveryAbortController) {\n this._discoveryAbortController.abort();\n this._discoveryAbortController = undefined;\n }\n }\n\n /**\n * Notification handler registration for tools\n * Should only be called if serverCapabilities.tools exists\n */\n async registerTools(): Promise<Tool[]> {\n if (this.serverCapabilities?.tools?.listChanged) {\n this.client.setNotificationHandler(\n ToolListChangedNotificationSchema,\n async (_notification) => {\n this.tools = await this.fetchTools();\n }\n );\n }\n\n return this.fetchTools();\n }\n\n /**\n * Notification handler registration for resources\n * Should only be called if serverCapabilities.resources exists\n */\n async registerResources(): Promise<Resource[]> {\n if (this.serverCapabilities?.resources?.listChanged) {\n this.client.setNotificationHandler(\n ResourceListChangedNotificationSchema,\n async (_notification) => {\n this.resources = await this.fetchResources();\n }\n );\n }\n\n return this.fetchResources();\n }\n\n /**\n * Notification handler registration for prompts\n * Should only be called if serverCapabilities.prompts exists\n */\n async registerPrompts(): Promise<Prompt[]> {\n if (this.serverCapabilities?.prompts?.listChanged) {\n this.client.setNotificationHandler(\n PromptListChangedNotificationSchema,\n async (_notification) => {\n this.prompts = await this.fetchPrompts();\n }\n );\n }\n\n return this.fetchPrompts();\n }\n\n async registerResourceTemplates(): Promise<ResourceTemplate[]> {\n return this.fetchResourceTemplates();\n }\n\n async fetchTools() {\n let toolsAgg: Tool[] = [];\n let toolsResult: ListToolsResult = { tools: [] };\n do {\n toolsResult = await this.client\n .listTools({\n cursor: toolsResult.nextCursor\n })\n .catch(this._capabilityErrorHandler({ tools: [] }, \"tools/list\"));\n toolsAgg = toolsAgg.concat(toolsResult.tools);\n } while (toolsResult.nextCursor);\n return toolsAgg;\n }\n\n async fetchResources() {\n let resourcesAgg: Resource[] = [];\n let resourcesResult: ListResourcesResult = { resources: [] };\n do {\n resourcesResult = await this.client\n .listResources({\n cursor: resourcesResult.nextCursor\n })\n .catch(\n this._capabilityErrorHandler({ resources: [] }, \"resources/list\")\n );\n resourcesAgg = resourcesAgg.concat(resourcesResult.resources);\n } while (resourcesResult.nextCursor);\n return resourcesAgg;\n }\n\n async fetchPrompts() {\n let promptsAgg: Prompt[] = [];\n let promptsResult: ListPromptsResult = { prompts: [] };\n do {\n promptsResult = await this.client\n .listPrompts({\n cursor: promptsResult.nextCursor\n })\n .catch(this._capabilityErrorHandler({ prompts: [] }, \"prompts/list\"));\n promptsAgg = promptsAgg.concat(promptsResult.prompts);\n } while (promptsResult.nextCursor);\n return promptsAgg;\n }\n\n async fetchResourceTemplates() {\n let templatesAgg: ResourceTemplate[] = [];\n let templatesResult: ListResourceTemplatesResult = {\n resourceTemplates: []\n };\n do {\n templatesResult = await this.client\n .listResourceTemplates({\n cursor: templatesResult.nextCursor\n })\n .catch(\n this._capabilityErrorHandler(\n { resourceTemplates: [] },\n \"resources/templates/list\"\n )\n );\n templatesAgg = templatesAgg.concat(templatesResult.resourceTemplates);\n } while (templatesResult.nextCursor);\n return templatesAgg;\n }\n\n /**\n * Handle elicitation request from server\n * Automatically uses the Agent's built-in elicitation handling if available\n */\n async handleElicitationRequest(\n _request: ElicitRequest\n ): Promise<ElicitResult> {\n // Elicitation handling must be implemented by the platform\n // For MCP servers, this should be handled by McpAgent.elicitInput()\n throw new Error(\n \"Elicitation handler must be implemented for your platform. Override handleElicitationRequest method.\"\n );\n }\n /**\n * Get the transport for the client\n * @param transportType - The transport type to get\n * @returns The transport for the client\n */\n getTransport(transportType: BaseTransportType) {\n switch (transportType) {\n case \"streamable-http\":\n return new StreamableHTTPClientTransport(\n this.url,\n this.options.transport as StreamableHTTPClientTransportOptions\n );\n case \"sse\":\n return new SSEClientTransport(\n this.url,\n this.options.transport as SSEClientTransportOptions\n );\n case \"rpc\":\n return new RPCClientTransport(\n this.options.transport as RPCClientTransportOptions\n );\n default:\n throw new Error(`Unsupported transport type: ${transportType}`);\n }\n }\n\n private async tryConnect(\n transportType: TransportType\n ): Promise<MCPClientConnectionResult> {\n const transports: BaseTransportType[] =\n transportType === \"auto\" ? [\"streamable-http\", \"sse\"] : [transportType];\n\n for (const currentTransportType of transports) {\n const isLastTransport =\n currentTransportType === transports[transports.length - 1];\n const hasFallback =\n transportType === \"auto\" &&\n currentTransportType === \"streamable-http\" &&\n !isLastTransport;\n\n const transport = this.getTransport(currentTransportType);\n\n try {\n await this.client.connect(transport);\n\n return {\n state: MCPConnectionState.CONNECTED,\n transport: currentTransportType\n };\n } catch (e) {\n const error = e instanceof Error ? e : new Error(String(e));\n\n if (isUnauthorized(error)) {\n return {\n state: MCPConnectionState.AUTHENTICATING\n };\n }\n\n if (isTransportNotImplemented(error) && hasFallback) {\n // Try the next transport\n continue;\n }\n\n return {\n state: MCPConnectionState.FAILED,\n error\n };\n }\n }\n\n // Should never reach here\n return {\n state: MCPConnectionState.FAILED,\n error: new Error(\"No transports available\")\n };\n }\n\n private _capabilityErrorHandler<T>(empty: T, method: string) {\n return (e: { code: number }) => {\n // server is badly behaved and returning invalid capabilities. This commonly occurs for resource templates\n if (e.code === -32601) {\n const url = this.url.toString();\n this._onObservabilityEvent.fire({\n type: \"mcp:client:discover\",\n payload: {\n url,\n capability: method.split(\"/\")[0],\n error: toErrorMessage(e)\n },\n timestamp: Date.now()\n });\n return empty;\n }\n throw e;\n };\n }\n}\n","import type { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport type { RequestOptions } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type {\n CallToolRequest,\n CallToolResultSchema,\n CompatibilityCallToolResultSchema,\n GetPromptRequest,\n Prompt,\n ReadResourceRequest,\n Resource,\n ResourceTemplate,\n Tool\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { CfWorkerJsonSchemaValidator } from \"@modelcontextprotocol/sdk/validation/cfworker-provider.js\";\nimport { type RetryOptions, tryN } from \"../retries\";\nimport type { ToolSet } from \"ai\";\nimport type { JSONSchema7 } from \"json-schema\";\nimport { nanoid } from \"nanoid\";\nimport { Emitter, type Event, DisposableStore } from \"../core/events\";\nimport type { MCPObservabilityEvent } from \"../observability/mcp\";\nimport {\n MCPClientConnection,\n MCPConnectionState,\n type MCPTransportOptions\n} from \"./client-connection\";\nimport { toErrorMessage } from \"./errors\";\nimport { RPC_DO_PREFIX } from \"./rpc\";\nimport type { TransportType } from \"./types\";\nimport type { MCPServerRow } from \"./client-storage\";\nimport type { AgentMcpOAuthProvider } from \"./do-oauth-client-provider\";\nimport { DurableObjectOAuthClientProvider } from \"./do-oauth-client-provider\";\n\nconst defaultClientOptions: ConstructorParameters<typeof Client>[1] = {\n jsonSchemaValidator: new CfWorkerJsonSchemaValidator()\n};\n\n/**\n * Blocked hostname patterns for SSRF protection.\n * Prevents MCP client from connecting to internal/private network addresses\n * while allowing loopback hosts for local development.\n */\nconst BLOCKED_HOSTNAMES = new Set([\n \"0.0.0.0\",\n \"[::]\",\n \"metadata.google.internal\"\n]);\n\n/**\n * Check whether four IPv4 octets belong to a private/reserved range.\n * Blocks RFC 1918, link-local, cloud metadata, and unspecified addresses.\n */\nfunction isPrivateIPv4(octets: number[]): boolean {\n const [a, b] = octets;\n // 10.0.0.0/8\n if (a === 10) return true;\n // 172.16.0.0/12\n if (a === 172 && b >= 16 && b <= 31) return true;\n // 192.168.0.0/16\n if (a === 192 && b === 168) return true;\n // 169.254.0.0/16 (link-local / cloud metadata)\n if (a === 169 && b === 254) return true;\n // 0.0.0.0/8\n if (a === 0) return true;\n return false;\n}\n\n/**\n * Check whether a hostname looks like a private/internal IP address.\n * Blocks RFC 1918, link-local, unique-local, unspecified,\n * and cloud metadata endpoints. Also detects IPv4-mapped IPv6 addresses.\n */\nfunction isBlockedUrl(url: string): boolean {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return true; // Malformed URLs are blocked\n }\n\n const hostname = parsed.hostname;\n\n if (BLOCKED_HOSTNAMES.has(hostname)) return true;\n\n // IPv4 checks\n const ipv4Parts = hostname.split(\".\");\n if (ipv4Parts.length === 4 && ipv4Parts.every((p) => /^\\d{1,3}$/.test(p))) {\n if (isPrivateIPv4(ipv4Parts.map(Number))) return true;\n }\n\n // IPv6 private range checks\n // URL parser keeps brackets: hostname for [fc00::1] is \"[fc00::1]\"\n if (hostname.startsWith(\"[\") && hostname.endsWith(\"]\")) {\n const addr = hostname.slice(1, -1).toLowerCase();\n // fc00::/7 — unique local addresses (fc00:: through fdff::)\n if (addr.startsWith(\"fc\") || addr.startsWith(\"fd\")) return true;\n // fe80::/10 — link-local addresses\n if (addr.startsWith(\"fe80\")) return true;\n // IPv4-mapped IPv6 (::ffff:x.x.x.x or ::ffff:XXYY:ZZWW)\n if (addr.startsWith(\"::ffff:\")) {\n const mapped = addr.slice(7);\n const dotParts = mapped.split(\".\");\n if (dotParts.length === 4 && dotParts.every((p) => /^\\d{1,3}$/.test(p))) {\n if (isPrivateIPv4(dotParts.map(Number))) return true;\n } else {\n const hexParts = mapped.split(\":\");\n if (hexParts.length === 2) {\n const hi = parseInt(hexParts[0], 16);\n const lo = parseInt(hexParts[1], 16);\n if (\n isPrivateIPv4([\n (hi >> 8) & 0xff,\n hi & 0xff,\n (lo >> 8) & 0xff,\n lo & 0xff\n ])\n )\n return true;\n }\n }\n }\n }\n\n return false;\n}\n\n/**\n * Options that can be stored in the server_options column\n * This is what gets JSON.stringify'd and stored in the database\n */\nexport type MCPServerOptions = {\n client?: ConstructorParameters<typeof Client>[1];\n transport?: {\n headers?: HeadersInit;\n type?: TransportType;\n };\n /** Retry options for connection and reconnection attempts */\n retry?: RetryOptions;\n};\n\n/**\n * Result of an OAuth callback request\n */\nexport type MCPOAuthCallbackResult =\n | { serverId: string; authSuccess: true; authError?: undefined }\n | { serverId?: string; authSuccess: false; authError: string };\n\n/**\n * Options for registering an MCP server\n */\nexport type RegisterServerOptions = {\n url: string;\n name: string;\n callbackUrl?: string;\n client?: ConstructorParameters<typeof Client>[1];\n transport?: MCPTransportOptions;\n authUrl?: string;\n clientId?: string;\n /** Retry options for connection and reconnection attempts */\n retry?: RetryOptions;\n};\n\n/**\n * Result of attempting to connect to an MCP server.\n * Discriminated union ensures error is present only on failure.\n */\nexport type MCPConnectionResult =\n | {\n state: typeof MCPConnectionState.FAILED;\n error: string;\n }\n | {\n state: typeof MCPConnectionState.AUTHENTICATING;\n authUrl: string;\n clientId?: string;\n }\n | {\n state: typeof MCPConnectionState.CONNECTED;\n };\n\n/**\n * Result of discovering server capabilities.\n * success indicates whether discovery completed successfully.\n * state is the current connection state at time of return.\n * error is present when success is false.\n */\nexport type MCPDiscoverResult = {\n success: boolean;\n state: MCPConnectionState;\n error?: string;\n};\n\nexport type MCPClientOAuthCallbackConfig = {\n successRedirect?: string;\n errorRedirect?: string;\n customHandler?: (result: MCPClientOAuthResult) => Response;\n};\n\nexport type MCPClientOAuthResult =\n | { serverId: string; authSuccess: true; authError?: undefined }\n | {\n serverId?: string;\n authSuccess: false;\n /** May contain untrusted content from external OAuth providers. Escape appropriately for your output context. */\n authError: string;\n };\n\nexport type MCPClientManagerOptions = {\n storage: DurableObjectStorage;\n createAuthProvider?: (callbackUrl: string) => AgentMcpOAuthProvider;\n};\n\n/**\n * Utility class that aggregates multiple MCP clients into one\n */\nexport class MCPClientManager {\n public mcpConnections: Record<string, MCPClientConnection> = {};\n private _didWarnAboutUnstableGetAITools = false;\n private _oauthCallbackConfig?: MCPClientOAuthCallbackConfig;\n private _connectionDisposables = new Map<string, DisposableStore>();\n private _storage: DurableObjectStorage;\n private _createAuthProviderFn?: (\n callbackUrl: string\n ) => AgentMcpOAuthProvider;\n private _isRestored = false;\n private _pendingConnections = new Map<string, Promise<void>>();\n\n /** @internal Protected for testing purposes. */\n protected readonly _onObservabilityEvent =\n new Emitter<MCPObservabilityEvent>();\n public readonly onObservabilityEvent: Event<MCPObservabilityEvent> =\n this._onObservabilityEvent.event;\n\n private readonly _onServerStateChanged = new Emitter<void>();\n /**\n * Event that fires whenever any MCP server state changes (registered, connected, removed, etc.)\n * This is useful for broadcasting server state to clients.\n */\n public readonly onServerStateChanged: Event<void> =\n this._onServerStateChanged.event;\n\n /**\n * @param _name Name of the MCP client\n * @param _version Version of the MCP Client\n * @param options Storage adapter for persisting MCP server state\n */\n constructor(\n private _name: string,\n private _version: string,\n options: MCPClientManagerOptions\n ) {\n if (!options.storage) {\n throw new Error(\n \"MCPClientManager requires a valid DurableObjectStorage instance\"\n );\n }\n this._storage = options.storage;\n this._createAuthProviderFn = options.createAuthProvider;\n }\n\n // SQL helper - runs a query and returns results as array\n private sql<T extends Record<string, SqlStorageValue>>(\n query: string,\n ...bindings: SqlStorageValue[]\n ): T[] {\n return [...this._storage.sql.exec<T>(query, ...bindings)];\n }\n\n // Storage operations\n private saveServerToStorage(server: MCPServerRow): void {\n this.sql(\n `INSERT OR REPLACE INTO cf_agents_mcp_servers (\n id, name, server_url, client_id, auth_url, callback_url, server_options\n ) VALUES (?, ?, ?, ?, ?, ?, ?)`,\n server.id,\n server.name,\n server.server_url,\n server.client_id ?? null,\n server.auth_url ?? null,\n server.callback_url,\n server.server_options ?? null\n );\n }\n\n private removeServerFromStorage(serverId: string): void {\n this.sql(\"DELETE FROM cf_agents_mcp_servers WHERE id = ?\", serverId);\n }\n\n private getServersFromStorage(): MCPServerRow[] {\n return this.sql<MCPServerRow>(\n \"SELECT id, name, server_url, client_id, auth_url, callback_url, server_options FROM cf_agents_mcp_servers\"\n );\n }\n\n /**\n * Get the retry options for a server from stored server_options\n */\n private getServerRetryOptions(serverId: string): RetryOptions | undefined {\n const rows = this.sql<MCPServerRow>(\n \"SELECT server_options FROM cf_agents_mcp_servers WHERE id = ?\",\n serverId\n );\n if (!rows.length || !rows[0].server_options) return undefined;\n const parsed: MCPServerOptions = JSON.parse(rows[0].server_options);\n return parsed.retry;\n }\n\n private clearServerAuthUrl(serverId: string): void {\n this.sql(\n \"UPDATE cf_agents_mcp_servers SET auth_url = NULL WHERE id = ?\",\n serverId\n );\n }\n\n private failConnection(\n serverId: string,\n error: string\n ): MCPOAuthCallbackResult {\n this.clearServerAuthUrl(serverId);\n if (this.mcpConnections[serverId]) {\n this.mcpConnections[serverId].connectionState = MCPConnectionState.FAILED;\n this.mcpConnections[serverId].connectionError = error;\n }\n this._onServerStateChanged.fire();\n return { serverId, authSuccess: false, authError: error };\n }\n\n jsonSchema: typeof import(\"ai\").jsonSchema | undefined;\n\n /**\n * Create an auth provider for a server\n * @internal\n */\n private createAuthProvider(\n serverId: string,\n callbackUrl: string,\n clientName: string,\n clientId?: string\n ): AgentMcpOAuthProvider {\n if (!this._storage) {\n throw new Error(\n \"Cannot create auth provider: storage is not initialized\"\n );\n }\n const authProvider = new DurableObjectOAuthClientProvider(\n this._storage,\n clientName,\n callbackUrl\n );\n authProvider.serverId = serverId;\n if (clientId) {\n authProvider.clientId = clientId;\n }\n return authProvider;\n }\n\n /**\n * Get saved RPC servers from storage (servers with rpc:// URLs).\n * These are restored separately by the Agent class since they need env bindings.\n */\n getRpcServersFromStorage(): MCPServerRow[] {\n return this.getServersFromStorage().filter((s) =>\n s.server_url.startsWith(RPC_DO_PREFIX)\n );\n }\n\n /**\n * Save an RPC server to storage for hibernation recovery.\n * The bindingName is stored in server_options so the Agent can look up\n * the namespace from env during restore.\n */\n saveRpcServerToStorage(\n id: string,\n name: string,\n normalizedName: string,\n bindingName: string,\n props?: Record<string, unknown>\n ): void {\n this.saveServerToStorage({\n id,\n name,\n server_url: `${RPC_DO_PREFIX}${normalizedName}`,\n client_id: null,\n auth_url: null,\n callback_url: \"\",\n server_options: JSON.stringify({ bindingName, props })\n });\n }\n\n /**\n * Restore MCP server connections from storage\n * This method is called on Agent initialization to restore previously connected servers.\n * RPC servers (rpc:// URLs) are skipped here -- they are restored by the Agent class\n * which has access to env bindings.\n *\n * @param clientName Name to use for OAuth client (typically the agent instance name)\n */\n async restoreConnectionsFromStorage(clientName: string): Promise<void> {\n if (this._isRestored) {\n return;\n }\n\n const servers = this.getServersFromStorage();\n\n if (!servers || servers.length === 0) {\n this._isRestored = true;\n return;\n }\n\n for (const server of servers) {\n if (server.server_url.startsWith(RPC_DO_PREFIX)) {\n continue;\n }\n\n const existingConn = this.mcpConnections[server.id];\n\n // Skip if connection already exists and is in a good state\n if (existingConn) {\n if (existingConn.connectionState === MCPConnectionState.READY) {\n console.warn(\n `[MCPClientManager] Server ${server.id} already has a ready connection. Skipping recreation.`\n );\n continue;\n }\n\n // Don't interrupt in-flight OAuth or connections\n if (\n existingConn.connectionState === MCPConnectionState.AUTHENTICATING ||\n existingConn.connectionState === MCPConnectionState.CONNECTING ||\n existingConn.connectionState === MCPConnectionState.DISCOVERING\n ) {\n // Let the existing flow complete\n continue;\n }\n\n // If failed, clean up the old connection before recreating\n if (existingConn.connectionState === MCPConnectionState.FAILED) {\n try {\n await existingConn.client.close();\n } catch (error) {\n console.warn(\n `[MCPClientManager] Error closing failed connection ${server.id}:`,\n error\n );\n }\n delete this.mcpConnections[server.id];\n this._connectionDisposables.get(server.id)?.dispose();\n this._connectionDisposables.delete(server.id);\n }\n }\n\n const parsedOptions: MCPServerOptions | null = server.server_options\n ? JSON.parse(server.server_options)\n : null;\n\n let authProvider: AgentMcpOAuthProvider | undefined;\n if (server.callback_url) {\n authProvider = this._createAuthProviderFn\n ? this._createAuthProviderFn(server.callback_url)\n : this.createAuthProvider(\n server.id,\n server.callback_url,\n clientName,\n server.client_id ?? undefined\n );\n authProvider.serverId = server.id;\n if (server.client_id) {\n authProvider.clientId = server.client_id;\n }\n }\n\n // Create the in-memory connection object (no need to save to storage - we just read from it!)\n const conn = this.createConnection(server.id, server.server_url, {\n client: parsedOptions?.client ?? {},\n transport: {\n ...(parsedOptions?.transport ?? {}),\n type: parsedOptions?.transport?.type ?? (\"auto\" as TransportType),\n authProvider\n }\n });\n\n // If auth_url exists, OAuth flow is in progress - set state and wait for callback\n if (server.auth_url) {\n conn.connectionState = MCPConnectionState.AUTHENTICATING;\n continue;\n }\n\n // Start connection in background (don't await) to avoid blocking the DO\n this._trackConnection(\n server.id,\n this._restoreServer(server.id, parsedOptions?.retry)\n );\n }\n\n this._isRestored = true;\n }\n\n /**\n * Track a pending connection promise for a server.\n * The promise is removed from the map when it settles.\n */\n private _trackConnection(serverId: string, promise: Promise<void>): void {\n const tracked = promise.finally(() => {\n // Only delete if it's still the same promise (not replaced by a newer one)\n if (this._pendingConnections.get(serverId) === tracked) {\n this._pendingConnections.delete(serverId);\n }\n });\n this._pendingConnections.set(serverId, tracked);\n }\n\n /**\n * Wait for all in-flight connection and discovery operations to settle.\n * This is useful when you need MCP tools to be available before proceeding,\n * e.g. before calling getAITools() after the agent wakes from hibernation.\n *\n * Returns once every pending connection has either connected and discovered,\n * failed, or timed out. Never rejects.\n *\n * @param options.timeout - Maximum time in milliseconds to wait.\n * `0` returns immediately without waiting.\n * `undefined` (default) waits indefinitely.\n */\n async waitForConnections(options?: { timeout?: number }): Promise<void> {\n if (this._pendingConnections.size === 0) {\n return;\n }\n if (options?.timeout != null && options.timeout <= 0) {\n return;\n }\n const settled = Promise.allSettled(this._pendingConnections.values());\n if (options?.timeout != null && options.timeout > 0) {\n let timerId: ReturnType<typeof setTimeout>;\n const timer = new Promise<void>((resolve) => {\n timerId = setTimeout(resolve, options.timeout);\n });\n await Promise.race([settled, timer]);\n clearTimeout(timerId!);\n } else {\n await settled;\n }\n }\n\n /**\n * Internal method to restore a single server connection and discovery\n */\n private async _restoreServer(\n serverId: string,\n retry?: RetryOptions\n ): Promise<void> {\n // Always try to connect - the connection logic will determine if OAuth is needed\n // If stored OAuth tokens are valid, connection will succeed automatically\n // If tokens are missing/invalid, connection will fail with Unauthorized\n // and state will be set to \"authenticating\"\n const maxAttempts = retry?.maxAttempts ?? 3;\n const baseDelayMs = retry?.baseDelayMs ?? 500;\n const maxDelayMs = retry?.maxDelayMs ?? 5000;\n\n const connectResult = await tryN(\n maxAttempts,\n async () => this.connectToServer(serverId),\n { baseDelayMs, maxDelayMs }\n ).catch((error) => {\n console.error(\n `Error connecting to ${serverId} after ${maxAttempts} attempts:`,\n error\n );\n return null;\n });\n\n if (connectResult?.state === MCPConnectionState.CONNECTED) {\n const discoverResult = await this.discoverIfConnected(serverId);\n if (discoverResult && !discoverResult.success) {\n console.error(`Error discovering ${serverId}:`, discoverResult.error);\n }\n }\n }\n\n /**\n * Connect to and register an MCP server\n *\n * @deprecated This method is maintained for backward compatibility.\n * For new code, use registerServer() and connectToServer() separately.\n *\n * @param url Server URL\n * @param options Connection options\n * @returns Object with server ID, auth URL (if OAuth), and client ID (if OAuth)\n */\n async connect(\n url: string,\n options: {\n // Allows you to reconnect to a server (in the case of an auth reconnect)\n reconnect?: {\n // server id\n id: string;\n oauthClientId?: string;\n oauthCode?: string;\n };\n // we're overriding authProvider here because we want to be able to access the auth URL\n transport?: MCPTransportOptions;\n client?: ConstructorParameters<typeof Client>[1];\n } = {}\n ): Promise<{\n id: string;\n authUrl?: string;\n clientId?: string;\n }> {\n /* Late initialization of jsonSchemaFn */\n /**\n * We need to delay loading ai sdk, because putting it in module scope is\n * causing issues with startup time.\n * The only place it's used is in getAITools, which only matters after\n * .connect() is called on at least one server.\n * So it's safe to delay loading it until .connect() is called.\n */\n await this.ensureJsonSchema();\n\n const id = options.reconnect?.id ?? nanoid(8);\n\n if (options.transport?.authProvider) {\n options.transport.authProvider.serverId = id;\n // reconnect with auth\n if (options.reconnect?.oauthClientId) {\n options.transport.authProvider.clientId =\n options.reconnect?.oauthClientId;\n }\n }\n\n if (isBlockedUrl(url)) {\n throw new Error(\n `Blocked URL: ${url} — MCP client connections to private/internal addresses are not allowed`\n );\n }\n\n // During OAuth reconnect, reuse existing connection to preserve state\n if (!options.reconnect?.oauthCode || !this.mcpConnections[id]) {\n const normalizedTransport = {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n };\n\n this.mcpConnections[id] = new MCPClientConnection(\n new URL(url),\n {\n name: this._name,\n version: this._version\n },\n {\n client: options.client ?? {},\n transport: normalizedTransport\n }\n );\n\n // Pipe connection-level observability events to the manager-level emitter\n // and track the subscription for cleanup.\n const store = new DisposableStore();\n // If we somehow already had disposables for this id, clear them first\n const existing = this._connectionDisposables.get(id);\n if (existing) existing.dispose();\n this._connectionDisposables.set(id, store);\n store.add(\n this.mcpConnections[id].onObservabilityEvent((event) => {\n this._onObservabilityEvent.fire(event);\n })\n );\n }\n\n // Initialize connection first. this will try connect\n await this.mcpConnections[id].init();\n\n // Handle OAuth completion if we have a reconnect code\n if (options.reconnect?.oauthCode) {\n try {\n await this.mcpConnections[id].completeAuthorization(\n options.reconnect.oauthCode\n );\n\n // Reinitialize connection\n await this.mcpConnections[id].init();\n } catch (error) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: url,\n transport: options.transport?.type ?? \"auto\",\n state: this.mcpConnections[id].connectionState,\n error: toErrorMessage(error)\n },\n timestamp: Date.now()\n });\n // Re-throw to signal failure to the caller\n throw error;\n }\n }\n\n // If connection is in authenticating state, return auth URL for OAuth flow\n const authUrl = options.transport?.authProvider?.authUrl;\n if (\n this.mcpConnections[id].connectionState ===\n MCPConnectionState.AUTHENTICATING &&\n authUrl &&\n options.transport?.authProvider?.redirectUrl\n ) {\n return {\n authUrl,\n clientId: options.transport?.authProvider?.clientId,\n id\n };\n }\n\n // If connection is connected, discover capabilities\n const discoverResult = await this.discoverIfConnected(id);\n if (discoverResult && !discoverResult.success) {\n throw new Error(\n `Failed to discover server capabilities: ${discoverResult.error}`\n );\n }\n\n return {\n id\n };\n }\n\n /**\n * Create an in-memory connection object and set up observability\n * Does NOT save to storage - use registerServer() for that\n * @returns The connection object (existing or newly created)\n */\n private createConnection(\n id: string,\n url: string,\n options: {\n client?: ConstructorParameters<typeof Client>[1];\n transport: MCPTransportOptions;\n }\n ): MCPClientConnection {\n // Return existing connection if already exists\n if (this.mcpConnections[id]) {\n return this.mcpConnections[id];\n }\n\n const normalizedTransport = {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n };\n\n this.mcpConnections[id] = new MCPClientConnection(\n new URL(url),\n {\n name: this._name,\n version: this._version\n },\n {\n client: { ...defaultClientOptions, ...options.client },\n transport: normalizedTransport\n }\n );\n\n // Pipe connection-level observability events to the manager-level emitter\n const store = new DisposableStore();\n const existing = this._connectionDisposables.get(id);\n if (existing) existing.dispose();\n this._connectionDisposables.set(id, store);\n store.add(\n this.mcpConnections[id].onObservabilityEvent((event) => {\n this._onObservabilityEvent.fire(event);\n })\n );\n\n return this.mcpConnections[id];\n }\n\n /**\n * Register an MCP server connection without connecting\n * Creates the connection object, sets up observability, and saves to storage\n *\n * @param id Server ID\n * @param options Registration options including URL, name, callback URL, and connection config\n * @returns Server ID\n */\n async registerServer(\n id: string,\n options: RegisterServerOptions\n ): Promise<string> {\n if (isBlockedUrl(options.url)) {\n throw new Error(\n `Blocked URL: ${options.url} — MCP client connections to private/internal addresses are not allowed`\n );\n }\n\n // Create the in-memory connection\n this.createConnection(id, options.url, {\n client: options.client,\n transport: {\n ...options.transport,\n type: options.transport?.type ?? (\"auto\" as TransportType)\n }\n });\n\n // Save to storage (exclude authProvider since it's recreated during restore)\n const { authProvider: _, ...transportWithoutAuth } =\n options.transport ?? {};\n this.saveServerToStorage({\n id,\n name: options.name,\n server_url: options.url,\n callback_url: options.callbackUrl ?? \"\",\n client_id: options.clientId ?? null,\n auth_url: options.authUrl ?? null,\n server_options: JSON.stringify({\n client: options.client,\n transport: transportWithoutAuth,\n retry: options.retry\n })\n });\n\n this._onServerStateChanged.fire();\n\n return id;\n }\n\n /**\n * Connect to an already registered MCP server and initialize the connection.\n *\n * For OAuth servers, returns `{ state: \"authenticating\", authUrl, clientId? }`.\n * The user must complete the OAuth flow via the authUrl, which triggers a\n * callback handled by `handleCallbackRequest()`.\n *\n * For non-OAuth servers, establishes the transport connection and returns\n * `{ state: \"connected\" }`. Call `discoverIfConnected()` afterwards to\n * discover capabilities and transition to \"ready\" state.\n *\n * @param id Server ID (must be registered first via registerServer())\n * @returns Connection result with current state and OAuth info (if applicable)\n */\n async connectToServer(id: string): Promise<MCPConnectionResult> {\n const conn = this.mcpConnections[id];\n if (!conn) {\n throw new Error(\n `Server ${id} is not registered. Call registerServer() first.`\n );\n }\n\n const error = await conn.init();\n this._onServerStateChanged.fire();\n\n switch (conn.connectionState) {\n case MCPConnectionState.FAILED:\n return {\n state: conn.connectionState,\n error: error ?? \"Unknown connection error\"\n };\n\n case MCPConnectionState.AUTHENTICATING: {\n const authUrl = conn.options.transport.authProvider?.authUrl;\n const redirectUrl = conn.options.transport.authProvider?.redirectUrl;\n\n if (!authUrl || !redirectUrl) {\n return {\n state: MCPConnectionState.FAILED,\n error: `OAuth configuration incomplete: missing ${!authUrl ? \"authUrl\" : \"redirectUrl\"}`\n };\n }\n\n const clientId = conn.options.transport.authProvider?.clientId;\n\n // Update storage with auth URL and client ID\n const servers = this.getServersFromStorage();\n const serverRow = servers.find((s) => s.id === id);\n if (serverRow) {\n this.saveServerToStorage({\n ...serverRow,\n auth_url: authUrl,\n client_id: clientId ?? null\n });\n // Broadcast again so clients receive the auth_url\n this._onServerStateChanged.fire();\n }\n\n this._onObservabilityEvent.fire({\n type: \"mcp:client:authorize\",\n payload: { serverId: id, authUrl, clientId },\n timestamp: Date.now()\n });\n\n return {\n state: conn.connectionState,\n authUrl,\n clientId\n };\n }\n\n case MCPConnectionState.CONNECTED:\n return { state: conn.connectionState };\n\n default:\n return {\n state: MCPConnectionState.FAILED,\n error: `Unexpected connection state after init: ${conn.connectionState}`\n };\n }\n }\n\n private extractServerIdFromState(state: string | null): string | null {\n if (!state) return null;\n const parts = state.split(\".\");\n return parts.length === 2 ? parts[1] : null;\n }\n\n isCallbackRequest(req: Request): boolean {\n if (req.method !== \"GET\") {\n return false;\n }\n\n const url = new URL(req.url);\n const state = url.searchParams.get(\"state\");\n const serverId = this.extractServerIdFromState(state);\n if (!serverId) {\n return false;\n }\n\n // Match by server ID AND verify the request origin + pathname matches the registered callback URL.\n // This prevents unrelated GET requests with a `state` param from being intercepted.\n const servers = this.getServersFromStorage();\n return servers.some((server) => {\n if (server.id !== serverId) return false;\n try {\n const storedUrl = new URL(server.callback_url);\n return (\n storedUrl.origin === url.origin && storedUrl.pathname === url.pathname\n );\n } catch {\n return false;\n }\n });\n }\n\n private validateCallbackRequest(\n req: Request\n ):\n | { valid: true; serverId: string; code: string; state: string }\n | { valid: false; serverId?: string; error: string } {\n const url = new URL(req.url);\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n const error = url.searchParams.get(\"error\");\n const errorDescription = url.searchParams.get(\"error_description\");\n\n // Early validation - return errors because we can't identify the connection\n if (!state) {\n return {\n valid: false,\n error: \"Unauthorized: no state provided\"\n };\n }\n\n const serverId = this.extractServerIdFromState(state);\n if (!serverId) {\n return {\n valid: false,\n error:\n \"No serverId found in state parameter. Expected format: {nonce}.{serverId}\"\n };\n }\n\n if (error) {\n return {\n serverId: serverId,\n valid: false,\n error: errorDescription || error\n };\n }\n\n if (!code) {\n return {\n serverId: serverId,\n valid: false,\n error: \"Unauthorized: no code provided\"\n };\n }\n\n const servers = this.getServersFromStorage();\n const serverExists = servers.some((server) => server.id === serverId);\n if (!serverExists) {\n return {\n serverId: serverId,\n valid: false,\n error: `No server found with id \"${serverId}\". Was the request matched with \\`isCallbackRequest()\\`?`\n };\n }\n\n if (this.mcpConnections[serverId] === undefined) {\n return {\n serverId: serverId,\n valid: false,\n error: `No connection found for serverId \"${serverId}\".`\n };\n }\n\n return {\n valid: true,\n serverId,\n code: code,\n state: state\n };\n }\n\n async handleCallbackRequest(req: Request): Promise<MCPOAuthCallbackResult> {\n const validation = this.validateCallbackRequest(req);\n\n if (!validation.valid) {\n if (validation.serverId && this.mcpConnections[validation.serverId]) {\n return this.failConnection(validation.serverId, validation.error);\n }\n\n return {\n serverId: validation.serverId,\n authSuccess: false,\n authError: validation.error\n };\n }\n\n const { serverId, code, state } = validation;\n const conn = this.mcpConnections[serverId]; // We have a valid connection - all errors from here should fail the connection\n\n try {\n if (!conn.options.transport.authProvider) {\n throw new Error(\n \"Trying to finalize authentication for a server connection without an authProvider\"\n );\n }\n\n const authProvider = conn.options.transport.authProvider;\n authProvider.serverId = serverId;\n\n // Two-phase state validation: check first (non-destructive), consume later\n // This prevents DoS attacks where attacker consumes valid state before legitimate callback\n const stateValidation = await authProvider.checkState(state);\n if (!stateValidation.valid) {\n throw new Error(stateValidation.error || \"Invalid state\");\n }\n\n // Already authenticated - just return success\n if (\n conn.connectionState === MCPConnectionState.READY ||\n conn.connectionState === MCPConnectionState.CONNECTED\n ) {\n this.clearServerAuthUrl(serverId);\n return { serverId, authSuccess: true };\n }\n\n if (conn.connectionState !== MCPConnectionState.AUTHENTICATING) {\n throw new Error(\n `Failed to authenticate: the client is in \"${conn.connectionState}\" state, expected \"authenticating\"`\n );\n }\n\n await authProvider.consumeState(state);\n await conn.completeAuthorization(code);\n await authProvider.deleteCodeVerifier();\n this.clearServerAuthUrl(serverId);\n conn.connectionError = null;\n this._onServerStateChanged.fire();\n\n return { serverId, authSuccess: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return this.failConnection(serverId, message);\n }\n }\n\n /**\n * Discover server capabilities if connection is in CONNECTED or READY state.\n * Transitions to DISCOVERING then READY (or CONNECTED on error).\n * Can be called to refresh server capabilities (e.g., from a UI refresh button).\n *\n * If called while a previous discovery is in-flight for the same server,\n * the previous discovery will be aborted.\n *\n * @param serverId The server ID to discover\n * @param options Optional configuration\n * @param options.timeoutMs Timeout in milliseconds (default: 30000)\n * @returns Result with current state and optional error, or undefined if connection not found\n */\n async discoverIfConnected(\n serverId: string,\n options: { timeoutMs?: number } = {}\n ): Promise<MCPDiscoverResult | undefined> {\n const conn = this.mcpConnections[serverId];\n if (!conn) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:discover\",\n payload: {},\n timestamp: Date.now()\n });\n return undefined;\n }\n\n // Delegate to connection's discover method which handles cancellation and timeout\n const result = await conn.discover(options);\n this._onServerStateChanged.fire();\n\n return {\n ...result,\n state: conn.connectionState\n };\n }\n\n /**\n * Establish connection in the background after OAuth completion.\n * This method connects to the server and discovers its capabilities.\n * The connection is automatically tracked so that `waitForConnections()`\n * will include it.\n * @param serverId The server ID to establish connection for\n */\n async establishConnection(serverId: string): Promise<void> {\n const promise = this._doEstablishConnection(serverId);\n this._trackConnection(serverId, promise);\n return promise;\n }\n\n private async _doEstablishConnection(serverId: string): Promise<void> {\n const conn = this.mcpConnections[serverId];\n if (!conn) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:preconnect\",\n payload: { serverId },\n timestamp: Date.now()\n });\n return;\n }\n\n // Skip if already discovering or ready - prevents duplicate work\n if (\n conn.connectionState === MCPConnectionState.DISCOVERING ||\n conn.connectionState === MCPConnectionState.READY\n ) {\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: conn.url.toString(),\n transport: conn.options.transport.type || \"unknown\",\n state: conn.connectionState\n },\n timestamp: Date.now()\n });\n return;\n }\n\n const retry = this.getServerRetryOptions(serverId);\n const maxAttempts = retry?.maxAttempts ?? 3;\n const baseDelayMs = retry?.baseDelayMs ?? 500;\n const maxDelayMs = retry?.maxDelayMs ?? 5000;\n\n const connectResult = await tryN(\n maxAttempts,\n async () => this.connectToServer(serverId),\n { baseDelayMs, maxDelayMs }\n );\n this._onServerStateChanged.fire();\n\n if (connectResult.state === MCPConnectionState.CONNECTED) {\n await this.discoverIfConnected(serverId);\n }\n\n this._onObservabilityEvent.fire({\n type: \"mcp:client:connect\",\n payload: {\n url: conn.url.toString(),\n transport: conn.options.transport.type || \"unknown\",\n state: conn.connectionState\n },\n timestamp: Date.now()\n });\n }\n\n /**\n * Configure OAuth callback handling\n * @param config OAuth callback configuration\n */\n configureOAuthCallback(config: MCPClientOAuthCallbackConfig): void {\n this._oauthCallbackConfig = config;\n }\n\n /**\n * Get the current OAuth callback configuration\n * @returns The current OAuth callback configuration\n */\n getOAuthCallbackConfig(): MCPClientOAuthCallbackConfig | undefined {\n return this._oauthCallbackConfig;\n }\n\n /**\n * @returns namespaced list of tools\n */\n listTools(): NamespacedData[\"tools\"] {\n return getNamespacedData(this.mcpConnections, \"tools\");\n }\n\n /**\n * Lazy-loads the jsonSchema function from the AI SDK.\n *\n * This defers importing the \"ai\" package until it's actually needed, which helps reduce\n * initial bundle size and startup time. The jsonSchema function is required for converting\n * MCP tools into AI SDK tool definitions via getAITools().\n *\n * @internal This method is for internal use only. It's automatically called before operations\n * that need jsonSchema (like getAITools() or OAuth flows). External consumers should not need\n * to call this directly.\n */\n async ensureJsonSchema() {\n if (!this.jsonSchema) {\n const { jsonSchema } = await import(\"ai\");\n this.jsonSchema = jsonSchema;\n }\n }\n\n /**\n * @returns a set of tools that you can use with the AI SDK\n */\n getAITools(): ToolSet {\n if (!this.jsonSchema) {\n throw new Error(\"jsonSchema not initialized.\");\n }\n\n // Warn if tools are being read from non-ready connections\n for (const [id, conn] of Object.entries(this.mcpConnections)) {\n if (\n conn.connectionState !== MCPConnectionState.READY &&\n conn.connectionState !== MCPConnectionState.AUTHENTICATING\n ) {\n console.warn(\n `[getAITools] WARNING: Reading tools from connection ${id} in state \"${conn.connectionState}\". Tools may not be loaded yet.`\n );\n }\n }\n\n const entries: [string, ToolSet[string]][] = [];\n for (const tool of getNamespacedData(this.mcpConnections, \"tools\")) {\n try {\n const toolKey = `tool_${tool.serverId.replace(/-/g, \"\")}_${tool.name}`;\n entries.push([\n toolKey,\n {\n description: tool.description,\n execute: async (args) => {\n const result = await this.callTool({\n arguments: args,\n name: tool.name,\n serverId: tool.serverId\n });\n if (result.isError) {\n const content = result.content as\n | Array<{ type: string; text?: string }>\n | undefined;\n const textContent = content?.[0];\n const message =\n textContent?.type === \"text\" && textContent.text\n ? textContent.text\n : \"Tool call failed\";\n throw new Error(message);\n }\n return result;\n },\n inputSchema: tool.inputSchema\n ? this.jsonSchema!(tool.inputSchema as JSONSchema7)\n : this.jsonSchema!({ type: \"object\" } as JSONSchema7),\n outputSchema: tool.outputSchema\n ? this.jsonSchema!(tool.outputSchema as JSONSchema7)\n : undefined\n }\n ]);\n } catch (e) {\n console.warn(\n `[getAITools] Skipping tool \"${tool.name}\" from \"${tool.serverId}\": ${e}`\n );\n }\n }\n return Object.fromEntries(entries);\n }\n\n /**\n * @deprecated this has been renamed to getAITools(), and unstable_getAITools will be removed in the next major version\n * @returns a set of tools that you can use with the AI SDK\n */\n unstable_getAITools(): ToolSet {\n if (!this._didWarnAboutUnstableGetAITools) {\n this._didWarnAboutUnstableGetAITools = true;\n console.warn(\n \"unstable_getAITools is deprecated, use getAITools instead. unstable_getAITools will be removed in the next major version.\"\n );\n }\n return this.getAITools();\n }\n\n /**\n * Closes all active in-memory connections to MCP servers.\n *\n * Note: This only closes the transport connections - it does NOT remove\n * servers from storage. Servers will still be listed and their callback\n * URLs will still match incoming OAuth requests.\n *\n * Use removeServer() instead if you want to fully clean up a server\n * (closes connection AND removes from storage).\n */\n async closeAllConnections() {\n const ids = Object.keys(this.mcpConnections);\n\n // Clear all pending connection tracking\n this._pendingConnections.clear();\n\n // Cancel all in-flight discoveries\n for (const id of ids) {\n this.mcpConnections[id].cancelDiscovery();\n }\n\n await Promise.all(\n ids.map(async (id) => {\n await this.mcpConnections[id].client.close();\n })\n );\n // Dispose all per-connection subscriptions\n for (const id of ids) {\n const store = this._connectionDisposables.get(id);\n if (store) store.dispose();\n this._connectionDisposables.delete(id);\n delete this.mcpConnections[id];\n }\n }\n\n /**\n * Closes a connection to an MCP server\n * @param id The id of the connection to close\n */\n async closeConnection(id: string) {\n if (!this.mcpConnections[id]) {\n throw new Error(`Connection with id \"${id}\" does not exist.`);\n }\n\n // Cancel any in-flight discovery\n this.mcpConnections[id].cancelDiscovery();\n\n // Remove from pending so waitForConnections() doesn't block on a closed server\n this._pendingConnections.delete(id);\n\n await this.mcpConnections[id].client.close();\n delete this.mcpConnections[id];\n\n const store = this._connectionDisposables.get(id);\n if (store) store.dispose();\n this._connectionDisposables.delete(id);\n }\n\n /**\n * Remove an MCP server - closes connection if active and removes from storage.\n */\n async removeServer(serverId: string): Promise<void> {\n if (this.mcpConnections[serverId]) {\n try {\n await this.closeConnection(serverId);\n } catch (_e) {\n // Ignore errors when closing\n }\n }\n this.removeServerFromStorage(serverId);\n this._onServerStateChanged.fire();\n }\n\n /**\n * List all MCP servers from storage\n */\n listServers(): MCPServerRow[] {\n return this.getServersFromStorage();\n }\n\n /**\n * Dispose the manager and all resources.\n */\n async dispose(): Promise<void> {\n try {\n await this.closeAllConnections();\n } finally {\n // Dispose manager-level emitters\n this._onServerStateChanged.dispose();\n this._onObservabilityEvent.dispose();\n }\n }\n\n /**\n * @returns namespaced list of prompts\n */\n listPrompts(): NamespacedData[\"prompts\"] {\n return getNamespacedData(this.mcpConnections, \"prompts\");\n }\n\n /**\n * @returns namespaced list of tools\n */\n listResources(): NamespacedData[\"resources\"] {\n return getNamespacedData(this.mcpConnections, \"resources\");\n }\n\n /**\n * @returns namespaced list of resource templates\n */\n listResourceTemplates(): NamespacedData[\"resourceTemplates\"] {\n return getNamespacedData(this.mcpConnections, \"resourceTemplates\");\n }\n\n /**\n * Namespaced version of callTool\n */\n async callTool(\n params: CallToolRequest[\"params\"] & { serverId: string },\n resultSchema?:\n | typeof CallToolResultSchema\n | typeof CompatibilityCallToolResultSchema,\n options?: RequestOptions\n ) {\n const { serverId, ...mcpParams } = params;\n const unqualifiedName = mcpParams.name.replace(`${serverId}.`, \"\");\n return this.mcpConnections[serverId].client.callTool(\n {\n ...mcpParams,\n name: unqualifiedName\n },\n resultSchema,\n options\n );\n }\n\n /**\n * Namespaced version of readResource\n */\n readResource(\n params: ReadResourceRequest[\"params\"] & { serverId: string },\n options: RequestOptions\n ) {\n return this.mcpConnections[params.serverId].client.readResource(\n params,\n options\n );\n }\n\n /**\n * Namespaced version of getPrompt\n */\n getPrompt(\n params: GetPromptRequest[\"params\"] & { serverId: string },\n options: RequestOptions\n ) {\n return this.mcpConnections[params.serverId].client.getPrompt(\n params,\n options\n );\n }\n}\n\ntype NamespacedData = {\n tools: (Tool & { serverId: string })[];\n prompts: (Prompt & { serverId: string })[];\n resources: (Resource & { serverId: string })[];\n resourceTemplates: (ResourceTemplate & { serverId: string })[];\n};\n\nexport function getNamespacedData<T extends keyof NamespacedData>(\n mcpClients: Record<string, MCPClientConnection>,\n type: T\n): NamespacedData[T] {\n const sets = Object.entries(mcpClients).map(([name, conn]) => {\n return { data: conn[type], name };\n });\n\n const namespacedData = sets.flatMap(({ name: serverId, data }) => {\n return data.map((item) => {\n return {\n ...item,\n // we add a serverId so we can easily pull it out and send the tool call to the right server\n serverId\n };\n });\n });\n\n return namespacedData as NamespacedData[T]; // Type assertion needed due to TS limitations with conditional return types\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n type PartyFetchOptions,\n PartySocket,\n type PartySocketOptions\n} from \"partysocket\";\nimport type { RPCRequest, RPCResponse } from \"./\";\nimport type {\n SerializableReturnValue,\n SerializableValue\n} from \"./serializable\";\nimport { MessageType } from \"./types\";\n\n/**\n * Options for creating an AgentClient\n */\nexport type AgentClientOptions<State = unknown> = Omit<\n PartySocketOptions,\n \"party\" | \"room\"\n> & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the client connects to this path directly.\n * Server must handle routing manually (e.g., with getAgentByName + fetch).\n * @example\n * // Client connects to /user, server routes based on session\n * useAgent({ agent: \"UserAgent\", basePath: \"user\" })\n */\n basePath?: string;\n /** Called when the Agent's state is updated */\n onStateUpdate?: (state: State, source: \"server\" | \"client\") => void;\n /** Called when a state update fails (e.g., connection is readonly) */\n onStateUpdateError?: (error: string) => void;\n /**\n * Called when the server sends the agent's identity on connect.\n * Useful when using basePath, as the actual instance name is determined server-side.\n * @param name The actual agent instance name\n * @param agent The agent class name (kebab-case)\n */\n onIdentity?: (name: string, agent: string) => void;\n /**\n * Called when identity changes on reconnect (different instance than before).\n * If not provided and identity changes, a warning will be logged.\n * @param oldName Previous instance name\n * @param newName New instance name\n * @param oldAgent Previous agent class name\n * @param newAgent New agent class name\n */\n onIdentityChange?: (\n oldName: string,\n newName: string,\n oldAgent: string,\n newAgent: string\n ) => void;\n /**\n * Additional path to append to the URL.\n * Works with both standard routing and basePath.\n * @example\n * // With basePath: /user/settings\n * { basePath: \"user\", path: \"settings\" }\n * // Standard: /agents/my-agent/room/settings\n * { agent: \"MyAgent\", name: \"room\", path: \"settings\" }\n */\n path?: string;\n};\n\n/**\n * Options for streaming RPC calls\n */\nexport type StreamOptions = {\n /** Called when a chunk of data is received */\n onChunk?: (chunk: unknown) => void;\n /** Called when the stream ends */\n onDone?: (finalChunk: unknown) => void;\n /** Called when an error occurs */\n onError?: (error: string) => void;\n};\n\n/**\n * Options for RPC calls\n */\nexport type CallOptions = {\n /** Timeout in milliseconds. If the call doesn't complete within this time, it will be rejected. */\n timeout?: number;\n /** Streaming options for handling streaming responses */\n stream?: StreamOptions;\n};\n\n/**\n * Options for the agentFetch function\n */\nexport type AgentClientFetchOptions = Omit<\n PartyFetchOptions,\n \"party\" | \"room\"\n> & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the request is made to this path directly.\n */\n basePath?: string;\n};\n\nimport { camelCaseToKebabCase } from \"./utils\";\n\n/**\n * WebSocket client for connecting to an Agent\n */\nexport class AgentClient<State = unknown> extends PartySocket {\n /**\n * @deprecated Use agentFetch instead\n */\n static fetch(_opts: PartyFetchOptions): Promise<Response> {\n throw new Error(\n \"AgentClient.fetch is not implemented, use agentFetch instead\"\n );\n }\n agent: string;\n name: string;\n\n /**\n * Whether the client has received identity from the server.\n * Becomes true after the first identity message is received.\n * Resets to false on connection close.\n */\n identified = false;\n\n /**\n * Promise that resolves when identity has been received from the server.\n * Useful for waiting before making calls that depend on knowing the instance.\n * Resets on connection close so it can be awaited again after reconnect.\n */\n get ready(): Promise<void> {\n return this._readyPromise;\n }\n\n private options: AgentClientOptions<State>;\n private _pendingCalls = new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n stream?: StreamOptions;\n type?: unknown;\n }\n >();\n private _readyPromise!: Promise<void>;\n private _resolveReady!: () => void;\n private _previousName: string | null = null;\n private _previousAgent: string | null = null;\n\n private _resetReady() {\n this._readyPromise = new Promise((resolve) => {\n this._resolveReady = resolve;\n });\n }\n\n constructor(options: AgentClientOptions<State>) {\n const agentNamespace = camelCaseToKebabCase(options.agent);\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n const socketOptions = options.basePath\n ? { basePath: options.basePath, path: options.path, ...options }\n : {\n party: agentNamespace,\n prefix: \"agents\",\n room: options.name || \"default\",\n path: options.path,\n ...options\n };\n\n super(socketOptions);\n this.agent = agentNamespace;\n this.name = options.name || \"default\";\n this.options = options;\n\n // Initialize ready promise\n this._resetReady();\n\n this.addEventListener(\"message\", (event) => {\n if (typeof event.data === \"string\") {\n let parsedMessage: Record<string, unknown>;\n try {\n parsedMessage = JSON.parse(event.data);\n } catch (_error) {\n // silently ignore invalid messages for now\n // TODO: log errors with log levels\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_IDENTITY) {\n const oldName = this._previousName;\n const oldAgent = this._previousAgent;\n const newName = parsedMessage.name as string;\n const newAgent = parsedMessage.agent as string;\n\n // Resolve ready/identified\n this.identified = true;\n this._resolveReady();\n\n // Detect identity change on reconnect\n if (\n oldName !== null &&\n oldAgent !== null &&\n (oldName !== newName || oldAgent !== newAgent)\n ) {\n if (this.options.onIdentityChange) {\n this.options.onIdentityChange(\n oldName,\n newName,\n oldAgent,\n newAgent\n );\n } else {\n const agentChanged = oldAgent !== newAgent;\n const nameChanged = oldName !== newName;\n let changeDescription = \"\";\n if (agentChanged && nameChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\", instance \"${oldName}\" → \"${newName}\"`;\n } else if (agentChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\"`;\n } else {\n changeDescription = `instance \"${oldName}\" → \"${newName}\"`;\n }\n console.warn(\n `[agents] Identity changed on reconnect: ${changeDescription}. ` +\n \"This can happen with server-side routing (e.g., basePath with getAgentByName) \" +\n \"where the instance is determined by auth/session. \" +\n \"Provide onIdentityChange callback to handle this explicitly, \" +\n \"or ignore if this is expected for your routing pattern.\"\n );\n }\n }\n\n // Always update from server identity (server is authoritative)\n this._previousName = newName;\n this._previousAgent = newAgent;\n this.name = newName;\n this.agent = newAgent;\n\n // Call onIdentity callback\n this.options.onIdentity?.(newName, newAgent);\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE) {\n this.options.onStateUpdate?.(parsedMessage.state as State, \"server\");\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE_ERROR) {\n this.options.onStateUpdateError?.(parsedMessage.error as string);\n return;\n }\n if (parsedMessage.type === MessageType.RPC) {\n const response = parsedMessage as RPCResponse;\n const pending = this._pendingCalls.get(response.id);\n if (!pending) return;\n\n if (!response.success) {\n pending.reject(new Error(response.error));\n this._pendingCalls.delete(response.id);\n pending.stream?.onError?.(response.error);\n return;\n }\n\n // Handle streaming responses\n if (\"done\" in response) {\n if (response.done) {\n pending.resolve(response.result);\n this._pendingCalls.delete(response.id);\n pending.stream?.onDone?.(response.result);\n } else {\n pending.stream?.onChunk?.(response.result);\n }\n } else {\n // Non-streaming response\n pending.resolve(response.result);\n this._pendingCalls.delete(response.id);\n }\n }\n }\n });\n\n // Clean up pending calls and reset ready state when connection closes\n this.addEventListener(\"close\", () => {\n // Reset ready state for next connection\n this.identified = false;\n this._resetReady();\n\n // Reject any remaining pending calls (e.g., from unexpected disconnect)\n this._rejectPendingCalls(\"Connection closed\");\n });\n }\n\n /**\n * Reject all pending RPC calls with the given reason.\n */\n private _rejectPendingCalls(reason: string) {\n const error = new Error(reason);\n for (const pending of this._pendingCalls.values()) {\n pending.reject(error);\n pending.stream?.onError?.(reason);\n }\n this._pendingCalls.clear();\n }\n\n setState(state: State) {\n this.send(JSON.stringify({ state, type: MessageType.CF_AGENT_STATE }));\n this.options.onStateUpdate?.(state, \"client\");\n }\n\n /**\n * Close the connection and immediately reject all pending RPC calls.\n * This provides immediate feedback on intentional close rather than\n * waiting for the WebSocket close handshake to complete.\n *\n * Note: Any calls made after `close()` will be rejected when the\n * underlying WebSocket close event fires.\n */\n close(code?: number, reason?: string) {\n // Immediately reject all pending calls on intentional close\n this._rejectPendingCalls(\"Connection closed\");\n\n // Then close the underlying socket\n super.close(code, reason);\n }\n\n /**\n * Call a method on the Agent\n * @param method Name of the method to call\n * @param args Arguments to pass to the method\n * @param options Options for the call (timeout, streaming) or legacy StreamOptions\n * @returns Promise that resolves with the method's return value\n */\n call<T extends SerializableReturnValue>(\n method: string,\n args?: SerializableValue[],\n options?: CallOptions | StreamOptions\n ): Promise<T>;\n call<T = unknown>(\n method: string,\n args?: unknown[],\n options?: CallOptions | StreamOptions\n ): Promise<T>;\n async call<T>(\n method: string,\n args: unknown[] = [],\n options?: CallOptions | StreamOptions\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const id = crypto.randomUUID();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Detect legacy format: { onChunk?, onDone?, onError? } vs new format: { timeout?, stream? }\n const isLegacyFormat =\n options &&\n (\"onChunk\" in options || \"onDone\" in options || \"onError\" in options);\n const streamOptions = isLegacyFormat\n ? (options as StreamOptions)\n : (options as CallOptions | undefined)?.stream;\n const timeout = isLegacyFormat\n ? undefined\n : (options as CallOptions | undefined)?.timeout;\n\n // Set up timeout if specified\n if (timeout) {\n timeoutId = setTimeout(() => {\n const pending = this._pendingCalls.get(id);\n this._pendingCalls.delete(id);\n const errorMessage = `RPC call to ${method} timed out after ${timeout}ms`;\n // Call stream onError callback if present (for streaming calls)\n pending?.stream?.onError?.(errorMessage);\n reject(new Error(errorMessage));\n }, timeout);\n }\n\n this._pendingCalls.set(id, {\n reject: (e: Error) => {\n if (timeoutId) clearTimeout(timeoutId);\n reject(e);\n },\n resolve: (value: unknown) => {\n if (timeoutId) clearTimeout(timeoutId);\n resolve(value as T);\n },\n stream: streamOptions,\n type: null as T\n });\n\n const request: RPCRequest = {\n args,\n id,\n method,\n type: MessageType.RPC\n };\n\n this.send(JSON.stringify(request));\n });\n }\n}\n\n/**\n * Make an HTTP request to an Agent\n * @param opts Connection options\n * @param init Request initialization options\n * @returns Promise resolving to a Response\n */\nexport function agentFetch(opts: AgentClientFetchOptions, init?: RequestInit) {\n const agentNamespace = camelCaseToKebabCase(opts.agent);\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n // When basePath is set, room/party aren't used by PartySocket (basePath replaces the URL)\n if (opts.basePath) {\n return PartySocket.fetch(\n { basePath: opts.basePath, ...opts } as unknown as PartyFetchOptions,\n init\n );\n }\n\n return PartySocket.fetch(\n {\n party: agentNamespace,\n prefix: \"agents\",\n room: opts.name || \"default\",\n ...opts\n },\n init\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n type PartyFetchOptions,\n PartySocket,\n type PartySocketOptions\n} from \"partysocket\";\nimport type { RPCRequest, RPCResponse } from \"./\";\nimport type {\n SerializableReturnValue,\n SerializableValue\n} from \"./serializable\";\nimport { MessageType } from \"./types\";\n\n/**\n * Options for creating an AgentClient\n */\nexport type AgentClientOptions<State = unknown> = Omit<\n PartySocketOptions,\n \"party\" | \"room\"\n> & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the client connects to this path directly.\n * Server must handle routing manually (e.g., with getAgentByName + fetch).\n * @example\n * // Client connects to /user, server routes based on session\n * useAgent({ agent: \"UserAgent\", basePath: \"user\" })\n */\n basePath?: string;\n /** Called when the Agent's state is updated */\n onStateUpdate?: (state: State, source: \"server\" | \"client\") => void;\n /** Called when a state update fails (e.g., connection is readonly) */\n onStateUpdateError?: (error: string) => void;\n /**\n * Called when the server sends the agent's identity on connect.\n * Useful when using basePath, as the actual instance name is determined server-side.\n * @param name The actual agent instance name\n * @param agent The agent class name (kebab-case)\n */\n onIdentity?: (name: string, agent: string) => void;\n /**\n * Called when identity changes on reconnect (different instance than before).\n * If not provided and identity changes, a warning will be logged.\n * @param oldName Previous instance name\n * @param newName New instance name\n * @param oldAgent Previous agent class name\n * @param newAgent New agent class name\n */\n onIdentityChange?: (\n oldName: string,\n newName: string,\n oldAgent: string,\n newAgent: string\n ) => void;\n /**\n * Additional path to append to the URL.\n * Works with both standard routing and basePath.\n * @example\n * // With basePath: /user/settings\n * { basePath: \"user\", path: \"settings\" }\n * // Standard: /agents/my-agent/room/settings\n * { agent: \"MyAgent\", name: \"room\", path: \"settings\" }\n */\n path?: string;\n};\n\n/**\n * Options for streaming RPC calls\n */\nexport type StreamOptions = {\n /** Called when a chunk of data is received */\n onChunk?: (chunk: unknown) => void;\n /** Called when the stream ends */\n onDone?: (finalChunk: unknown) => void;\n /** Called when an error occurs */\n onError?: (error: string) => void;\n};\n\n/**\n * Options for RPC calls\n */\nexport type CallOptions = {\n /** Timeout in milliseconds. If the call doesn't complete within this time, it will be rejected. */\n timeout?: number;\n /** Streaming options for handling streaming responses */\n stream?: StreamOptions;\n};\n\n/**\n * Options for the agentFetch function\n */\nexport type AgentClientFetchOptions = Omit<\n PartyFetchOptions,\n \"party\" | \"room\"\n> & {\n /** Name of the agent to connect to (ignored if basePath is set) */\n agent: string;\n /** Name of the specific Agent instance (ignored if basePath is set) */\n name?: string;\n /**\n * Full URL path - bypasses agent/name URL construction.\n * When set, the request is made to this path directly.\n */\n basePath?: string;\n};\n\nimport { camelCaseToKebabCase } from \"./utils\";\n\n/**\n * WebSocket client for connecting to an Agent\n */\nexport class AgentClient<State = unknown> extends PartySocket {\n /**\n * @deprecated Use agentFetch instead\n */\n static fetch(_opts: PartyFetchOptions): Promise<Response> {\n throw new Error(\n \"AgentClient.fetch is not implemented, use agentFetch instead\"\n );\n }\n agent: string;\n name: string;\n\n /**\n * Whether the client has received identity from the server.\n * Becomes true after the first identity message is received.\n * Resets to false on connection close.\n */\n identified = false;\n\n /**\n * Promise that resolves when identity has been received from the server.\n * Useful for waiting before making calls that depend on knowing the instance.\n * Resets on connection close so it can be awaited again after reconnect.\n */\n get ready(): Promise<void> {\n return this._readyPromise;\n }\n\n private options: AgentClientOptions<State>;\n private _pendingCalls = new Map<\n string,\n {\n resolve: (value: unknown) => void;\n reject: (error: Error) => void;\n stream?: StreamOptions;\n type?: unknown;\n }\n >();\n private _readyPromise!: Promise<void>;\n private _resolveReady!: () => void;\n private _previousName: string | null = null;\n private _previousAgent: string | null = null;\n\n private _resetReady() {\n this._readyPromise = new Promise((resolve) => {\n this._resolveReady = resolve;\n });\n }\n\n constructor(options: AgentClientOptions<State>) {\n const agentNamespace = camelCaseToKebabCase(options.agent);\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n const socketOptions = options.basePath\n ? { basePath: options.basePath, path: options.path, ...options }\n : {\n party: agentNamespace,\n prefix: \"agents\",\n room: options.name || \"default\",\n path: options.path,\n ...options\n };\n\n super(socketOptions);\n this.agent = agentNamespace;\n this.name = options.name || \"default\";\n this.options = options;\n\n // Initialize ready promise\n this._resetReady();\n\n this.addEventListener(\"message\", (event) => {\n if (typeof event.data === \"string\") {\n let parsedMessage: Record<string, unknown>;\n try {\n parsedMessage = JSON.parse(event.data);\n } catch (_error) {\n // silently ignore invalid messages for now\n // TODO: log errors with log levels\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_IDENTITY) {\n const oldName = this._previousName;\n const oldAgent = this._previousAgent;\n const newName = parsedMessage.name as string;\n const newAgent = parsedMessage.agent as string;\n\n // Resolve ready/identified\n this.identified = true;\n this._resolveReady();\n\n // Detect identity change on reconnect\n if (\n oldName !== null &&\n oldAgent !== null &&\n (oldName !== newName || oldAgent !== newAgent)\n ) {\n if (this.options.onIdentityChange) {\n this.options.onIdentityChange(\n oldName,\n newName,\n oldAgent,\n newAgent\n );\n } else {\n const agentChanged = oldAgent !== newAgent;\n const nameChanged = oldName !== newName;\n let changeDescription = \"\";\n if (agentChanged && nameChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\", instance \"${oldName}\" → \"${newName}\"`;\n } else if (agentChanged) {\n changeDescription = `agent \"${oldAgent}\" → \"${newAgent}\"`;\n } else {\n changeDescription = `instance \"${oldName}\" → \"${newName}\"`;\n }\n console.warn(\n `[agents] Identity changed on reconnect: ${changeDescription}. ` +\n \"This can happen with server-side routing (e.g., basePath with getAgentByName) \" +\n \"where the instance is determined by auth/session. \" +\n \"Provide onIdentityChange callback to handle this explicitly, \" +\n \"or ignore if this is expected for your routing pattern.\"\n );\n }\n }\n\n // Always update from server identity (server is authoritative)\n this._previousName = newName;\n this._previousAgent = newAgent;\n this.name = newName;\n this.agent = newAgent;\n\n // Call onIdentity callback\n this.options.onIdentity?.(newName, newAgent);\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE) {\n this.options.onStateUpdate?.(parsedMessage.state as State, \"server\");\n return;\n }\n if (parsedMessage.type === MessageType.CF_AGENT_STATE_ERROR) {\n this.options.onStateUpdateError?.(parsedMessage.error as string);\n return;\n }\n if (parsedMessage.type === MessageType.RPC) {\n const response = parsedMessage as RPCResponse;\n const pending = this._pendingCalls.get(response.id);\n if (!pending) return;\n\n if (!response.success) {\n pending.reject(new Error(response.error));\n this._pendingCalls.delete(response.id);\n pending.stream?.onError?.(response.error);\n return;\n }\n\n // Handle streaming responses\n if (\"done\" in response) {\n if (response.done) {\n pending.resolve(response.result);\n this._pendingCalls.delete(response.id);\n pending.stream?.onDone?.(response.result);\n } else {\n pending.stream?.onChunk?.(response.result);\n }\n } else {\n // Non-streaming response\n pending.resolve(response.result);\n this._pendingCalls.delete(response.id);\n }\n }\n }\n });\n\n // Clean up pending calls and reset ready state when connection closes\n this.addEventListener(\"close\", () => {\n // Reset ready state for next connection\n this.identified = false;\n this._resetReady();\n\n // Reject any remaining pending calls (e.g., from unexpected disconnect)\n this._rejectPendingCalls(\"Connection closed\");\n });\n }\n\n /**\n * Reject all pending RPC calls with the given reason.\n */\n private _rejectPendingCalls(reason: string) {\n const error = new Error(reason);\n for (const pending of this._pendingCalls.values()) {\n pending.reject(error);\n pending.stream?.onError?.(reason);\n }\n this._pendingCalls.clear();\n }\n\n setState(state: State) {\n this.send(JSON.stringify({ state, type: MessageType.CF_AGENT_STATE }));\n this.options.onStateUpdate?.(state, \"client\");\n }\n\n /**\n * Close the connection and immediately reject all pending RPC calls.\n * This provides immediate feedback on intentional close rather than\n * waiting for the WebSocket close handshake to complete.\n *\n * Note: Any calls made after `close()` will be rejected when the\n * underlying WebSocket close event fires.\n */\n close(code?: number, reason?: string) {\n // Immediately reject all pending calls on intentional close\n this._rejectPendingCalls(\"Connection closed\");\n\n // Then close the underlying socket\n super.close(code, reason);\n }\n\n /**\n * Call a method on the Agent\n * @param method Name of the method to call\n * @param args Arguments to pass to the method\n * @param options Options for the call (timeout, streaming) or legacy StreamOptions\n * @returns Promise that resolves with the method's return value\n */\n call<T extends SerializableReturnValue>(\n method: string,\n args?: SerializableValue[],\n options?: CallOptions | StreamOptions\n ): Promise<T>;\n call<T = unknown>(\n method: string,\n args?: unknown[],\n options?: CallOptions | StreamOptions\n ): Promise<T>;\n async call<T>(\n method: string,\n args: unknown[] = [],\n options?: CallOptions | StreamOptions\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const id = crypto.randomUUID();\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n // Detect legacy format: { onChunk?, onDone?, onError? } vs new format: { timeout?, stream? }\n const isLegacyFormat =\n options &&\n (\"onChunk\" in options || \"onDone\" in options || \"onError\" in options);\n const streamOptions = isLegacyFormat\n ? (options as StreamOptions)\n : (options as CallOptions | undefined)?.stream;\n const timeout = isLegacyFormat\n ? undefined\n : (options as CallOptions | undefined)?.timeout;\n\n // Set up timeout if specified\n if (timeout) {\n timeoutId = setTimeout(() => {\n const pending = this._pendingCalls.get(id);\n this._pendingCalls.delete(id);\n const errorMessage = `RPC call to ${method} timed out after ${timeout}ms`;\n // Call stream onError callback if present (for streaming calls)\n pending?.stream?.onError?.(errorMessage);\n reject(new Error(errorMessage));\n }, timeout);\n }\n\n this._pendingCalls.set(id, {\n reject: (e: Error) => {\n if (timeoutId) clearTimeout(timeoutId);\n reject(e);\n },\n resolve: (value: unknown) => {\n if (timeoutId) clearTimeout(timeoutId);\n resolve(value as T);\n },\n stream: streamOptions,\n type: null as T\n });\n\n const request: RPCRequest = {\n args,\n id,\n method,\n type: MessageType.RPC\n };\n\n this.send(JSON.stringify(request));\n });\n }\n}\n\n/**\n * Make an HTTP request to an Agent\n * @param opts Connection options\n * @param init Request initialization options\n * @returns Promise resolving to a Response\n */\nexport function agentFetch(opts: AgentClientFetchOptions, init?: RequestInit) {\n const agentNamespace = camelCaseToKebabCase(opts.agent);\n\n // If basePath is provided, use it directly; otherwise construct from agent/name\n // When basePath is set, room/party aren't used by PartySocket (basePath replaces the URL)\n if (opts.basePath) {\n return PartySocket.fetch(\n { basePath: opts.basePath, ...opts } as unknown as PartyFetchOptions,\n init\n );\n }\n\n return PartySocket.fetch(\n {\n party: agentNamespace,\n prefix: \"agents\",\n room: opts.name || \"default\",\n ...opts\n },\n init\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { r as Agent } from "../index-p1XLNvwQ.js";
1
+ import { r as Agent } from "../index-DBW341gU.js";
2
2
  //#region src/experimental/forever.d.ts
3
3
  type FiberState = {
4
4
  id: string;
@@ -44,7 +44,7 @@ function withFibers(Base, options) {
44
44
  this._fiberActiveFibers = /* @__PURE__ */ new Set();
45
45
  this._fiberRecoveryInProgress = false;
46
46
  this._fiberLastCleanupTime = 0;
47
- console.warn("[agents/experimental/forever] WARNING: You are using an experimental API that WILL break between releases. Do not use in production.");
47
+ console.warn("[agents/experimental/forever] WARNING: You are using an experimental API that WILL break between releases. Use with caution.");
48
48
  this.sql`
49
49
  CREATE TABLE IF NOT EXISTS cf_agents_fibers (
50
50
  id TEXT PRIMARY KEY NOT NULL,
@@ -1 +1 @@
1
- {"version":3,"file":"forever.js","names":[],"sources":["../../src/experimental/forever.ts"],"sourcesContent":["/**\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n * !! WARNING: EXPERIMENTAL — DO NOT USE IN PRODUCTION !!\n * !! !!\n * !! This API is under active development and WILL break between !!\n * !! releases. Method names, types, behavior, and the mixin signature !!\n * !! are all subject to change without notice. !!\n * !! !!\n * !! If you use this, pin your agents version and expect to rewrite !!\n * !! your code when upgrading. !!\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n *\n * Experimental fiber mixin for durable long-running execution.\n *\n * Usage:\n * import { Agent } from \"agents\";\n * import { withFibers } from \"agents/experimental/forever\";\n *\n * class MyAgent extends withFibers(Agent)<Env, State> {\n * async doWork(payload, fiberCtx) { ... }\n * }\n *\n * This mixin adds:\n * - keepAlive() — keep the DO alive via scheduled heartbeats\n * - spawnFiber() — fire-and-forget durable execution\n * - stashFiber() — checkpoint progress that survives eviction\n * - cancelFiber() / getFiber() — manage running fibers\n * - onFiberComplete / onFiberRecovered / onFibersRecovered — lifecycle hooks\n *\n * @experimental This API is not yet stable and may change.\n */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { nanoid } from \"nanoid\";\nimport type { Agent } from \"../index\";\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport type FiberState = {\n id: string;\n callback: string;\n payload: unknown;\n snapshot: unknown | null;\n status: \"running\" | \"completed\" | \"failed\" | \"interrupted\" | \"cancelled\";\n retryCount: number;\n maxRetries: number;\n result: unknown | null;\n error: string | null;\n startedAt: number | null;\n updatedAt: number | null;\n completedAt: number | null;\n createdAt: number;\n};\n\nexport type FiberRecoveryContext = {\n id: string;\n methodName: string;\n payload: unknown;\n snapshot: unknown | null;\n retryCount: number;\n};\n\nexport type FiberContext = {\n id: string;\n snapshot: unknown | null;\n retryCount: number;\n};\n\nexport type FiberCompleteContext = {\n id: string;\n methodName: string;\n payload: unknown;\n result: unknown;\n};\n\n// ── Internal types ────────────────────────────────────────────────────\n\ntype RawFiberRow = {\n id: string;\n callback: string;\n payload: string | null;\n snapshot: string | null;\n status: string;\n retry_count: number;\n max_retries: number;\n result: string | null;\n error: string | null;\n started_at: number | null;\n updated_at: number | null;\n completed_at: number | null;\n created_at: number;\n};\n\n// ── Constants ─────────────────────────────────────────────────────────\n\nconst FIBER_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;\nconst FIBER_CLEANUP_COMPLETED_MS = 24 * 60 * 60 * 1000;\nconst FIBER_CLEANUP_FAILED_MS = 7 * 24 * 60 * 60 * 1000;\n\nconst fiberContext = new AsyncLocalStorage<{ fiberId: string }>();\n\n// ── Mixin ─────────────────────────────────────────────────────────────\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- mixin constructor pattern\ntype AgentConstructor = new (...args: any[]) => Agent;\n\nexport function withFibers<TBase extends typeof Agent>(\n Base: TBase,\n options?: { debugFibers?: boolean }\n) {\n const debugEnabled = options?.debugFibers ?? false;\n\n class FiberAgent extends (Base as AgentConstructor) {\n // ── Fiber state ───────────────────────────────────────────────\n\n /** @internal */ _fiberActiveFibers = new Set<string>();\n /** @internal */ _fiberRecoveryInProgress = false;\n /** @internal */ _fiberLastCleanupTime = 0;\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- mixin constructor\n constructor(...args: any[]) {\n super(...args);\n\n console.warn(\n \"[agents/experimental/forever] WARNING: You are using an experimental API that WILL break between releases. Do not use in production.\"\n );\n\n // Create the fibers table\n this.sql`\n CREATE TABLE IF NOT EXISTS cf_agents_fibers (\n id TEXT PRIMARY KEY NOT NULL,\n callback TEXT NOT NULL,\n payload TEXT,\n snapshot TEXT,\n status TEXT NOT NULL DEFAULT 'running'\n CHECK(status IN ('running', 'completed', 'failed', 'interrupted', 'cancelled')),\n retry_count INTEGER NOT NULL DEFAULT 0,\n max_retries INTEGER NOT NULL DEFAULT 3,\n result TEXT,\n error TEXT,\n started_at INTEGER,\n updated_at INTEGER,\n completed_at INTEGER,\n created_at INTEGER NOT NULL\n )\n `;\n }\n\n // ── Debug logging ─────────────────────────────────────────────\n\n /** @internal */ _fiberDebug(msg: string, ...args: unknown[]) {\n if (debugEnabled) {\n console.debug(`[fiber] ${msg}`, ...args);\n }\n }\n\n // ── Heartbeat callback override ───────────────────────────────\n\n // Override the base Agent's no-op heartbeat to add fiber recovery.\n // The scheduler dispatches by string name, so this override runs\n // when the keepAlive schedule fires.\n /** @internal */ async _cf_keepAliveHeartbeat() {\n await this._checkInterruptedFibers();\n }\n\n // ── Public API ────────────────────────────────────────────────\n\n spawnFiber(\n methodName: keyof this,\n payload?: unknown,\n options?: { maxRetries?: number }\n ): string {\n this._maybeCleanupFibers();\n\n const name = methodName as string;\n if (typeof this[methodName] !== \"function\") {\n throw new Error(`this.${name} is not a function`);\n }\n\n const id = nanoid();\n const now = Date.now();\n const maxRetries = options?.maxRetries ?? 3;\n\n this.sql`\n INSERT INTO cf_agents_fibers (id, callback, payload, status, max_retries, retry_count, started_at, updated_at, created_at)\n VALUES (${id}, ${name}, ${JSON.stringify(payload ?? null)}, 'running', ${maxRetries}, 0, ${now}, ${now}, ${now})\n `;\n\n this._fiberActiveFibers.add(id);\n this._fiberDebug(\n \"spawned fiber=%s method=%s maxRetries=%d\",\n id,\n name,\n maxRetries\n );\n\n void this._startFiber(id, name, payload, maxRetries).catch((e) => {\n console.error(`Unhandled error in fiber ${id}:`, e);\n });\n\n return id;\n }\n\n stashFiber(data: unknown): void {\n const ctx = fiberContext.getStore();\n if (!ctx) {\n throw new Error(\n \"stashFiber() can only be called within a fiber execution context\"\n );\n }\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET snapshot = ${JSON.stringify(data)}, updated_at = ${now}\n WHERE id = ${ctx.fiberId}\n `;\n this._fiberDebug(\"stash fiber=%s\", ctx.fiberId);\n }\n\n /**\n * Note: cancellation is cooperative. The status is set to 'cancelled'\n * in SQLite, and the _runFiber retry loop checks for this status at\n * the top of each iteration.\n */\n cancelFiber(fiberId: string): boolean {\n const fiber = this._getRawFiber(fiberId);\n if (!fiber) return false;\n if (\n fiber.status === \"completed\" ||\n fiber.status === \"failed\" ||\n fiber.status === \"cancelled\"\n ) {\n return false;\n }\n\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'cancelled', updated_at = ${now}\n WHERE id = ${fiberId}\n `;\n this._fiberActiveFibers.delete(fiberId);\n this._fiberDebug(\"cancelled fiber=%s\", fiberId);\n return true;\n }\n\n getFiber(fiberId: string): FiberState | null {\n const raw = this._getRawFiber(fiberId);\n if (!raw) return null;\n return this._toFiberState(raw);\n }\n\n restartFiber(fiberId: string): void {\n const fiber = this._getRawFiber(fiberId);\n if (!fiber) {\n throw new Error(`Fiber ${fiberId} not found`);\n }\n\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'running', started_at = ${now}, updated_at = ${now}\n WHERE id = ${fiberId}\n `;\n\n this._fiberActiveFibers.add(fiberId);\n this._fiberDebug(\n \"restarting fiber=%s method=%s retryCount=%d\",\n fiberId,\n fiber.callback,\n fiber.retry_count\n );\n\n const parsedPayload = fiber.payload\n ? JSON.parse(fiber.payload)\n : undefined;\n\n void this._startFiber(\n fiberId,\n fiber.callback,\n parsedPayload,\n fiber.max_retries\n ).catch((e) => {\n console.error(`Error restarting fiber ${fiberId}:`, e);\n });\n }\n\n // ── Lifecycle hooks (override in subclass) ────────────────────\n\n /**\n * Manually trigger fiber recovery check.\n * In production, this runs automatically via the heartbeat schedule.\n * Useful for testing or when you need immediate recovery after\n * detecting an eviction.\n */\n async checkFibers(): Promise<void> {\n await this._checkInterruptedFibers();\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-unused-vars -- overridable hook\n onFiberComplete(_ctx: FiberCompleteContext): void | Promise<void> {}\n\n onFiberRecovered(ctx: FiberRecoveryContext): void | Promise<void> {\n this.restartFiber(ctx.id);\n }\n\n async onFibersRecovered(fibers: FiberRecoveryContext[]): Promise<void> {\n for (const fiber of fibers) {\n await this.onFiberRecovered(fiber);\n }\n }\n\n // ── Private implementation ────────────────────────────────────\n\n /** @internal */ _getRawFiber(fiberId: string): RawFiberRow | null {\n const result = this.sql<RawFiberRow>`\n SELECT * FROM cf_agents_fibers WHERE id = ${fiberId}\n `;\n return result && result.length > 0 ? result[0] : null;\n }\n\n /** @internal */ _safeJsonParse(value: string | null): unknown {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n }\n\n /** @internal */ _toFiberState(raw: RawFiberRow): FiberState {\n return {\n id: raw.id,\n callback: raw.callback,\n payload: this._safeJsonParse(raw.payload),\n snapshot: this._safeJsonParse(raw.snapshot),\n status: raw.status as FiberState[\"status\"],\n retryCount: raw.retry_count,\n maxRetries: raw.max_retries,\n result: this._safeJsonParse(raw.result),\n error: raw.error,\n startedAt: raw.started_at,\n updatedAt: raw.updated_at,\n completedAt: raw.completed_at,\n createdAt: raw.created_at\n };\n }\n\n /** @internal */ async _startFiber(\n id: string,\n methodName: string,\n payload: unknown,\n maxRetries: number\n ): Promise<void> {\n const disposeKeepAlive = await this.keepAlive();\n await this._runFiber(\n id,\n methodName,\n payload,\n maxRetries,\n disposeKeepAlive\n );\n }\n\n /** @internal */ async _runFiber(\n id: string,\n methodName: string,\n payload: unknown,\n maxRetries: number,\n disposeKeepAlive: () => void\n ): Promise<void> {\n try {\n while (true) {\n const fiber = this._getRawFiber(id);\n if (!fiber || fiber.status === \"cancelled\") {\n this._fiberDebug(\n \"fiber=%s exiting: %s\",\n id,\n !fiber ? \"not found\" : \"cancelled\"\n );\n return;\n }\n\n try {\n await fiberContext.run({ fiberId: id }, async () => {\n const snapshot = this._safeJsonParse(fiber.snapshot);\n const retryCount = fiber.retry_count;\n\n const callback = this[methodName as keyof this];\n if (typeof callback !== \"function\") {\n throw new Error(`Fiber method ${methodName} not found`);\n }\n\n const result = await (\n callback as (p: unknown, ctx: FiberContext) => Promise<unknown>\n ).call(this, payload, { id, snapshot, retryCount });\n\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'completed',\n result = ${JSON.stringify(result ?? null)},\n completed_at = ${now},\n updated_at = ${now}\n WHERE id = ${id}\n `;\n\n this._fiberDebug(\"fiber=%s completed method=%s\", id, methodName);\n\n try {\n await this.onFiberComplete({\n id,\n methodName,\n payload,\n result\n });\n } catch (e) {\n console.error(\"Error in onFiberComplete:\", e);\n }\n });\n\n return;\n } catch (e) {\n const now = Date.now();\n const currentFiber = this._getRawFiber(id);\n const newRetryCount = (currentFiber?.retry_count ?? 0) + 1;\n\n if (newRetryCount > maxRetries) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'failed',\n error = ${errorMsg},\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${id}\n `;\n this._fiberDebug(\n \"fiber=%s failed after %d retries: %s\",\n id,\n newRetryCount,\n errorMsg\n );\n return;\n }\n\n this.sql`\n UPDATE cf_agents_fibers\n SET retry_count = ${newRetryCount}, updated_at = ${now}\n WHERE id = ${id}\n `;\n this._fiberDebug(\n \"fiber=%s retrying (%d/%d)\",\n id,\n newRetryCount,\n maxRetries\n );\n continue;\n }\n }\n } finally {\n this._fiberActiveFibers.delete(id);\n disposeKeepAlive();\n }\n }\n\n /** @internal */ async _checkInterruptedFibers(): Promise<void> {\n if (this._fiberRecoveryInProgress) return;\n this._fiberRecoveryInProgress = true;\n\n try {\n const runningFibers = this.sql<RawFiberRow>`\n SELECT * FROM cf_agents_fibers\n WHERE status = 'running'\n ORDER BY created_at ASC\n `;\n\n if (!runningFibers || runningFibers.length === 0) return;\n\n const interrupted: FiberRecoveryContext[] = [];\n\n for (const fiber of runningFibers) {\n if (this._fiberActiveFibers.has(fiber.id)) continue;\n\n const newRetryCount = fiber.retry_count + 1;\n const now = Date.now();\n\n if (newRetryCount > fiber.max_retries) {\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'failed',\n error = 'max retries exceeded (eviction recovery)',\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${fiber.id}\n `;\n this._fiberDebug(\n \"fiber=%s max retries exceeded on recovery\",\n fiber.id\n );\n } else {\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'interrupted',\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${fiber.id}\n `;\n\n interrupted.push({\n id: fiber.id,\n methodName: fiber.callback,\n payload: this._safeJsonParse(fiber.payload),\n snapshot: this._safeJsonParse(fiber.snapshot),\n retryCount: newRetryCount\n });\n }\n }\n\n if (interrupted.length > 0) {\n this._fiberDebug(\n \"recovering %d interrupted fibers\",\n interrupted.length\n );\n\n this._cleanupOrphanedHeartbeats();\n\n try {\n await this.onFibersRecovered(interrupted);\n } catch (e) {\n console.error(\"Error in onFibersRecovered:\", e);\n }\n }\n } finally {\n this._fiberRecoveryInProgress = false;\n }\n }\n\n /** @internal */ _cleanupOrphanedHeartbeats() {\n this.sql`\n DELETE FROM cf_agents_schedules\n WHERE callback = '_cf_keepAliveHeartbeat'\n `;\n this._fiberDebug(\"cleaned up orphaned heartbeat schedules\");\n }\n\n /** @internal */ _maybeCleanupFibers() {\n const now = Date.now();\n if (now - this._fiberLastCleanupTime < FIBER_CLEANUP_INTERVAL_MS) {\n return;\n }\n this._fiberLastCleanupTime = now;\n\n const completedCutoff = now - FIBER_CLEANUP_COMPLETED_MS;\n const failedCutoff = now - FIBER_CLEANUP_FAILED_MS;\n\n this.sql`\n DELETE FROM cf_agents_fibers\n WHERE (status = 'completed' AND completed_at < ${completedCutoff})\n OR (status = 'failed' AND updated_at < ${failedCutoff})\n OR (status = 'cancelled' AND updated_at < ${completedCutoff})\n `;\n\n this._fiberDebug(\n \"cleanup: checked for old completed/failed/cancelled fibers\"\n );\n }\n }\n\n return FiberAgent as unknown as FiberAgentClass;\n}\n\n// ── Return type ──────────────────────────────────────────────────────\n//\n// Explicit generic constructor so consumers can write:\n// const FA = withFibers(Agent);\n// class MyAgent extends FA<Env> { ... }\n// and still see all FiberMethods on `this`.\n//\n// We define this as a standalone constructor (not intersected with\n// typeof Agent) to avoid overload-resolution issues where TypeScript\n// would pick Agent's constructor and lose FiberMethods.\n\ntype FiberAgentClass = {\n new <\n Env extends Cloudflare.Env = Cloudflare.Env,\n State = unknown,\n Props extends Record<string, unknown> = Record<string, unknown>\n >(\n ctx: DurableObjectState,\n env: Env\n ): Agent<Env, State, Props> & FiberMethods;\n};\n\n// ── FiberMethods interface ───────────────────────────────────────────\n//\n// Describes the methods added by the mixin. Exported so users can\n// reference the shape in their own type annotations if needed.\n\nexport interface FiberMethods {\n spawnFiber(\n methodName: string,\n payload?: unknown,\n options?: { maxRetries?: number }\n ): string;\n stashFiber(data: unknown): void;\n cancelFiber(fiberId: string): boolean;\n getFiber(fiberId: string): FiberState | null;\n restartFiber(fiberId: string): void;\n checkFibers(): Promise<void>;\n onFiberComplete(ctx: FiberCompleteContext): void | Promise<void>;\n onFiberRecovered(ctx: FiberRecoveryContext): void | Promise<void>;\n onFibersRecovered(fibers: FiberRecoveryContext[]): Promise<void>;\n\n /** @internal */ _fiberActiveFibers: Set<string>;\n /** @internal */ _fiberRecoveryInProgress: boolean;\n /** @internal */ _fiberLastCleanupTime: number;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FA,MAAM,4BAA4B,MAAU;AAC5C,MAAM,6BAA6B,OAAU,KAAK;AAClD,MAAM,0BAA0B,QAAc,KAAK;AAEnD,MAAM,eAAe,IAAI,mBAAwC;AAOjE,SAAgB,WACd,MACA,SACA;CACA,MAAM,eAAe,SAAS,eAAe;CAE7C,MAAM,mBAAoB,KAA0B;EAQlD,YAAY,GAAG,MAAa;AAC1B,SAAM,GAAG,KAAK;6CANsB,IAAI,KAAa;mCACX;gCACH;AAMvC,WAAQ,KACN,uIACD;AAGD,QAAK,GAAG;;;;;;;;;;;;;;;;;;;mBAsBO,YAAY,KAAa,GAAG,MAAiB;AAC5D,OAAI,aACF,SAAQ,MAAM,WAAW,OAAO,GAAG,KAAK;;mBAS3B,MAAM,yBAAyB;AAC9C,SAAM,KAAK,yBAAyB;;EAKtC,WACE,YACA,SACA,SACQ;AACR,QAAK,qBAAqB;GAE1B,MAAM,OAAO;AACb,OAAI,OAAO,KAAK,gBAAgB,WAC9B,OAAM,IAAI,MAAM,QAAQ,KAAK,oBAAoB;GAGnD,MAAM,KAAK,QAAQ;GACnB,MAAM,MAAM,KAAK,KAAK;GACtB,MAAM,aAAa,SAAS,cAAc;AAE1C,QAAK,GAAG;;kBAEI,GAAG,IAAI,KAAK,IAAI,KAAK,UAAU,WAAW,KAAK,CAAC,eAAe,WAAW,OAAO,IAAI,IAAI,IAAI,IAAI,IAAI;;AAGjH,QAAK,mBAAmB,IAAI,GAAG;AAC/B,QAAK,YACH,4CACA,IACA,MACA,WACD;AAEI,QAAK,YAAY,IAAI,MAAM,SAAS,WAAW,CAAC,OAAO,MAAM;AAChE,YAAQ,MAAM,4BAA4B,GAAG,IAAI,EAAE;KACnD;AAEF,UAAO;;EAGT,WAAW,MAAqB;GAC9B,MAAM,MAAM,aAAa,UAAU;AACnC,OAAI,CAAC,IACH,OAAM,IAAI,MACR,mEACD;GAEH,MAAM,MAAM,KAAK,KAAK;AACtB,QAAK,GAAG;;yBAEW,KAAK,UAAU,KAAK,CAAC,iBAAiB,IAAI;qBAC9C,IAAI,QAAQ;;AAE3B,QAAK,YAAY,kBAAkB,IAAI,QAAQ;;;;;;;EAQjD,YAAY,SAA0B;GACpC,MAAM,QAAQ,KAAK,aAAa,QAAQ;AACxC,OAAI,CAAC,MAAO,QAAO;AACnB,OACE,MAAM,WAAW,eACjB,MAAM,WAAW,YACjB,MAAM,WAAW,YAEjB,QAAO;GAGT,MAAM,MAAM,KAAK,KAAK;AACtB,QAAK,GAAG;;iDAEmC,IAAI;qBAChC,QAAQ;;AAEvB,QAAK,mBAAmB,OAAO,QAAQ;AACvC,QAAK,YAAY,sBAAsB,QAAQ;AAC/C,UAAO;;EAGT,SAAS,SAAoC;GAC3C,MAAM,MAAM,KAAK,aAAa,QAAQ;AACtC,OAAI,CAAC,IAAK,QAAO;AACjB,UAAO,KAAK,cAAc,IAAI;;EAGhC,aAAa,SAAuB;GAClC,MAAM,QAAQ,KAAK,aAAa,QAAQ;AACxC,OAAI,CAAC,MACH,OAAM,IAAI,MAAM,SAAS,QAAQ,YAAY;GAG/C,MAAM,MAAM,KAAK,KAAK;AACtB,QAAK,GAAG;;+CAEiC,IAAI,iBAAiB,IAAI;qBACnD,QAAQ;;AAGvB,QAAK,mBAAmB,IAAI,QAAQ;AACpC,QAAK,YACH,+CACA,SACA,MAAM,UACN,MAAM,YACP;GAED,MAAM,gBAAgB,MAAM,UACxB,KAAK,MAAM,MAAM,QAAQ,GACzB,KAAA;AAEC,QAAK,YACR,SACA,MAAM,UACN,eACA,MAAM,YACP,CAAC,OAAO,MAAM;AACb,YAAQ,MAAM,0BAA0B,QAAQ,IAAI,EAAE;KACtD;;;;;;;;EAWJ,MAAM,cAA6B;AACjC,SAAM,KAAK,yBAAyB;;EAItC,gBAAgB,MAAkD;EAElE,iBAAiB,KAAiD;AAChE,QAAK,aAAa,IAAI,GAAG;;EAG3B,MAAM,kBAAkB,QAA+C;AACrE,QAAK,MAAM,SAAS,OAClB,OAAM,KAAK,iBAAiB,MAAM;;mBAMrB,aAAa,SAAqC;GACjE,MAAM,SAAS,KAAK,GAAgB;oDACU,QAAQ;;AAEtD,UAAO,UAAU,OAAO,SAAS,IAAI,OAAO,KAAK;;mBAGlC,eAAe,OAA+B;AAC7D,OAAI,UAAU,KAAM,QAAO;AAC3B,OAAI;AACF,WAAO,KAAK,MAAM,MAAM;WAClB;AACN,WAAO;;;mBAIM,cAAc,KAA8B;AAC3D,UAAO;IACL,IAAI,IAAI;IACR,UAAU,IAAI;IACd,SAAS,KAAK,eAAe,IAAI,QAAQ;IACzC,UAAU,KAAK,eAAe,IAAI,SAAS;IAC3C,QAAQ,IAAI;IACZ,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB,QAAQ,KAAK,eAAe,IAAI,OAAO;IACvC,OAAO,IAAI;IACX,WAAW,IAAI;IACf,WAAW,IAAI;IACf,aAAa,IAAI;IACjB,WAAW,IAAI;IAChB;;mBAGc,MAAM,YACrB,IACA,YACA,SACA,YACe;GACf,MAAM,mBAAmB,MAAM,KAAK,WAAW;AAC/C,SAAM,KAAK,UACT,IACA,YACA,SACA,YACA,iBACD;;mBAGc,MAAM,UACrB,IACA,YACA,SACA,YACA,kBACe;AACf,OAAI;AACF,WAAO,MAAM;KACX,MAAM,QAAQ,KAAK,aAAa,GAAG;AACnC,SAAI,CAAC,SAAS,MAAM,WAAW,aAAa;AAC1C,WAAK,YACH,wBACA,IACA,CAAC,QAAQ,cAAc,YACxB;AACD;;AAGF,SAAI;AACF,YAAM,aAAa,IAAI,EAAE,SAAS,IAAI,EAAE,YAAY;OAClD,MAAM,WAAW,KAAK,eAAe,MAAM,SAAS;OACpD,MAAM,aAAa,MAAM;OAEzB,MAAM,WAAW,KAAK;AACtB,WAAI,OAAO,aAAa,WACtB,OAAM,IAAI,MAAM,gBAAgB,WAAW,YAAY;OAGzD,MAAM,SAAS,MACb,SACA,KAAK,MAAM,SAAS;QAAE;QAAI;QAAU;QAAY,CAAC;OAEnD,MAAM,MAAM,KAAK,KAAK;AACtB,YAAK,GAAG;;;+BAGS,KAAK,UAAU,UAAU,KAAK,CAAC;qCACzB,IAAI;mCACN,IAAI;6BACV,GAAG;;AAGlB,YAAK,YAAY,gCAAgC,IAAI,WAAW;AAEhE,WAAI;AACF,cAAM,KAAK,gBAAgB;SACzB;SACA;SACA;SACA;SACD,CAAC;gBACK,GAAG;AACV,gBAAQ,MAAM,6BAA6B,EAAE;;QAE/C;AAEF;cACO,GAAG;MACV,MAAM,MAAM,KAAK,KAAK;MAEtB,MAAM,iBADe,KAAK,aAAa,GAAG,EACL,eAAe,KAAK;AAEzD,UAAI,gBAAgB,YAAY;OAC9B,MAAM,WAAW,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AAC3D,YAAK,GAAG;;;8BAGQ,SAAS;oCACH,cAAc;mCACf,IAAI;6BACV,GAAG;;AAElB,YAAK,YACH,wCACA,IACA,eACA,SACD;AACD;;AAGF,WAAK,GAAG;;kCAEc,cAAc,iBAAiB,IAAI;2BAC1C,GAAG;;AAElB,WAAK,YACH,6BACA,IACA,eACA,WACD;AACD;;;aAGI;AACR,SAAK,mBAAmB,OAAO,GAAG;AAClC,sBAAkB;;;mBAIL,MAAM,0BAAyC;AAC9D,OAAI,KAAK,yBAA0B;AACnC,QAAK,2BAA2B;AAEhC,OAAI;IACF,MAAM,gBAAgB,KAAK,GAAgB;;;;;AAM3C,QAAI,CAAC,iBAAiB,cAAc,WAAW,EAAG;IAElD,MAAM,cAAsC,EAAE;AAE9C,SAAK,MAAM,SAAS,eAAe;AACjC,SAAI,KAAK,mBAAmB,IAAI,MAAM,GAAG,CAAE;KAE3C,MAAM,gBAAgB,MAAM,cAAc;KAC1C,MAAM,MAAM,KAAK,KAAK;AAEtB,SAAI,gBAAgB,MAAM,aAAa;AACrC,WAAK,GAAG;;;;kCAIc,cAAc;iCACf,IAAI;2BACV,MAAM,GAAG;;AAExB,WAAK,YACH,6CACA,MAAM,GACP;YACI;AACL,WAAK,GAAG;;;kCAGc,cAAc;iCACf,IAAI;2BACV,MAAM,GAAG;;AAGxB,kBAAY,KAAK;OACf,IAAI,MAAM;OACV,YAAY,MAAM;OAClB,SAAS,KAAK,eAAe,MAAM,QAAQ;OAC3C,UAAU,KAAK,eAAe,MAAM,SAAS;OAC7C,YAAY;OACb,CAAC;;;AAIN,QAAI,YAAY,SAAS,GAAG;AAC1B,UAAK,YACH,oCACA,YAAY,OACb;AAED,UAAK,4BAA4B;AAEjC,SAAI;AACF,YAAM,KAAK,kBAAkB,YAAY;cAClC,GAAG;AACV,cAAQ,MAAM,+BAA+B,EAAE;;;aAG3C;AACR,SAAK,2BAA2B;;;mBAInB,6BAA6B;AAC5C,QAAK,GAAG;;;;AAIR,QAAK,YAAY,0CAA0C;;mBAG5C,sBAAsB;GACrC,MAAM,MAAM,KAAK,KAAK;AACtB,OAAI,MAAM,KAAK,wBAAwB,0BACrC;AAEF,QAAK,wBAAwB;GAE7B,MAAM,kBAAkB,MAAM;GAC9B,MAAM,eAAe,MAAM;AAE3B,QAAK,GAAG;;yDAE2C,gBAAgB;oDACrB,aAAa;uDACV,gBAAgB;;AAGjE,QAAK,YACH,6DACD;;;AAIL,QAAO"}
1
+ {"version":3,"file":"forever.js","names":[],"sources":["../../src/experimental/forever.ts"],"sourcesContent":["/**\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n * !! WARNING: EXPERIMENTAL — DO NOT USE IN PRODUCTION !!\n * !! !!\n * !! This API is under active development and WILL break between !!\n * !! releases. Method names, types, behavior, and the mixin signature !!\n * !! are all subject to change without notice. !!\n * !! !!\n * !! If you use this, pin your agents version and expect to rewrite !!\n * !! your code when upgrading. !!\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n *\n * Experimental fiber mixin for durable long-running execution.\n *\n * Usage:\n * import { Agent } from \"agents\";\n * import { withFibers } from \"agents/experimental/forever\";\n *\n * class MyAgent extends withFibers(Agent)<Env, State> {\n * async doWork(payload, fiberCtx) { ... }\n * }\n *\n * This mixin adds:\n * - keepAlive() — keep the DO alive via scheduled heartbeats\n * - spawnFiber() — fire-and-forget durable execution\n * - stashFiber() — checkpoint progress that survives eviction\n * - cancelFiber() / getFiber() — manage running fibers\n * - onFiberComplete / onFiberRecovered / onFibersRecovered — lifecycle hooks\n *\n * @experimental This API is not yet stable and may change.\n */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { nanoid } from \"nanoid\";\nimport type { Agent } from \"../index\";\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport type FiberState = {\n id: string;\n callback: string;\n payload: unknown;\n snapshot: unknown | null;\n status: \"running\" | \"completed\" | \"failed\" | \"interrupted\" | \"cancelled\";\n retryCount: number;\n maxRetries: number;\n result: unknown | null;\n error: string | null;\n startedAt: number | null;\n updatedAt: number | null;\n completedAt: number | null;\n createdAt: number;\n};\n\nexport type FiberRecoveryContext = {\n id: string;\n methodName: string;\n payload: unknown;\n snapshot: unknown | null;\n retryCount: number;\n};\n\nexport type FiberContext = {\n id: string;\n snapshot: unknown | null;\n retryCount: number;\n};\n\nexport type FiberCompleteContext = {\n id: string;\n methodName: string;\n payload: unknown;\n result: unknown;\n};\n\n// ── Internal types ────────────────────────────────────────────────────\n\ntype RawFiberRow = {\n id: string;\n callback: string;\n payload: string | null;\n snapshot: string | null;\n status: string;\n retry_count: number;\n max_retries: number;\n result: string | null;\n error: string | null;\n started_at: number | null;\n updated_at: number | null;\n completed_at: number | null;\n created_at: number;\n};\n\n// ── Constants ─────────────────────────────────────────────────────────\n\nconst FIBER_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;\nconst FIBER_CLEANUP_COMPLETED_MS = 24 * 60 * 60 * 1000;\nconst FIBER_CLEANUP_FAILED_MS = 7 * 24 * 60 * 60 * 1000;\n\nconst fiberContext = new AsyncLocalStorage<{ fiberId: string }>();\n\n// ── Mixin ─────────────────────────────────────────────────────────────\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- mixin constructor pattern\ntype AgentConstructor = new (...args: any[]) => Agent;\n\nexport function withFibers<TBase extends typeof Agent>(\n Base: TBase,\n options?: { debugFibers?: boolean }\n) {\n const debugEnabled = options?.debugFibers ?? false;\n\n class FiberAgent extends (Base as AgentConstructor) {\n // ── Fiber state ───────────────────────────────────────────────\n\n /** @internal */ _fiberActiveFibers = new Set<string>();\n /** @internal */ _fiberRecoveryInProgress = false;\n /** @internal */ _fiberLastCleanupTime = 0;\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- mixin constructor\n constructor(...args: any[]) {\n super(...args);\n\n console.warn(\n \"[agents/experimental/forever] WARNING: You are using an experimental API that WILL break between releases. Use with caution.\"\n );\n\n // Create the fibers table\n this.sql`\n CREATE TABLE IF NOT EXISTS cf_agents_fibers (\n id TEXT PRIMARY KEY NOT NULL,\n callback TEXT NOT NULL,\n payload TEXT,\n snapshot TEXT,\n status TEXT NOT NULL DEFAULT 'running'\n CHECK(status IN ('running', 'completed', 'failed', 'interrupted', 'cancelled')),\n retry_count INTEGER NOT NULL DEFAULT 0,\n max_retries INTEGER NOT NULL DEFAULT 3,\n result TEXT,\n error TEXT,\n started_at INTEGER,\n updated_at INTEGER,\n completed_at INTEGER,\n created_at INTEGER NOT NULL\n )\n `;\n }\n\n // ── Debug logging ─────────────────────────────────────────────\n\n /** @internal */ _fiberDebug(msg: string, ...args: unknown[]) {\n if (debugEnabled) {\n console.debug(`[fiber] ${msg}`, ...args);\n }\n }\n\n // ── Heartbeat callback override ───────────────────────────────\n\n // Override the base Agent's no-op heartbeat to add fiber recovery.\n // The scheduler dispatches by string name, so this override runs\n // when the keepAlive schedule fires.\n /** @internal */ async _cf_keepAliveHeartbeat() {\n await this._checkInterruptedFibers();\n }\n\n // ── Public API ────────────────────────────────────────────────\n\n spawnFiber(\n methodName: keyof this,\n payload?: unknown,\n options?: { maxRetries?: number }\n ): string {\n this._maybeCleanupFibers();\n\n const name = methodName as string;\n if (typeof this[methodName] !== \"function\") {\n throw new Error(`this.${name} is not a function`);\n }\n\n const id = nanoid();\n const now = Date.now();\n const maxRetries = options?.maxRetries ?? 3;\n\n this.sql`\n INSERT INTO cf_agents_fibers (id, callback, payload, status, max_retries, retry_count, started_at, updated_at, created_at)\n VALUES (${id}, ${name}, ${JSON.stringify(payload ?? null)}, 'running', ${maxRetries}, 0, ${now}, ${now}, ${now})\n `;\n\n this._fiberActiveFibers.add(id);\n this._fiberDebug(\n \"spawned fiber=%s method=%s maxRetries=%d\",\n id,\n name,\n maxRetries\n );\n\n void this._startFiber(id, name, payload, maxRetries).catch((e) => {\n console.error(`Unhandled error in fiber ${id}:`, e);\n });\n\n return id;\n }\n\n stashFiber(data: unknown): void {\n const ctx = fiberContext.getStore();\n if (!ctx) {\n throw new Error(\n \"stashFiber() can only be called within a fiber execution context\"\n );\n }\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET snapshot = ${JSON.stringify(data)}, updated_at = ${now}\n WHERE id = ${ctx.fiberId}\n `;\n this._fiberDebug(\"stash fiber=%s\", ctx.fiberId);\n }\n\n /**\n * Note: cancellation is cooperative. The status is set to 'cancelled'\n * in SQLite, and the _runFiber retry loop checks for this status at\n * the top of each iteration.\n */\n cancelFiber(fiberId: string): boolean {\n const fiber = this._getRawFiber(fiberId);\n if (!fiber) return false;\n if (\n fiber.status === \"completed\" ||\n fiber.status === \"failed\" ||\n fiber.status === \"cancelled\"\n ) {\n return false;\n }\n\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'cancelled', updated_at = ${now}\n WHERE id = ${fiberId}\n `;\n this._fiberActiveFibers.delete(fiberId);\n this._fiberDebug(\"cancelled fiber=%s\", fiberId);\n return true;\n }\n\n getFiber(fiberId: string): FiberState | null {\n const raw = this._getRawFiber(fiberId);\n if (!raw) return null;\n return this._toFiberState(raw);\n }\n\n restartFiber(fiberId: string): void {\n const fiber = this._getRawFiber(fiberId);\n if (!fiber) {\n throw new Error(`Fiber ${fiberId} not found`);\n }\n\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'running', started_at = ${now}, updated_at = ${now}\n WHERE id = ${fiberId}\n `;\n\n this._fiberActiveFibers.add(fiberId);\n this._fiberDebug(\n \"restarting fiber=%s method=%s retryCount=%d\",\n fiberId,\n fiber.callback,\n fiber.retry_count\n );\n\n const parsedPayload = fiber.payload\n ? JSON.parse(fiber.payload)\n : undefined;\n\n void this._startFiber(\n fiberId,\n fiber.callback,\n parsedPayload,\n fiber.max_retries\n ).catch((e) => {\n console.error(`Error restarting fiber ${fiberId}:`, e);\n });\n }\n\n // ── Lifecycle hooks (override in subclass) ────────────────────\n\n /**\n * Manually trigger fiber recovery check.\n * In production, this runs automatically via the heartbeat schedule.\n * Useful for testing or when you need immediate recovery after\n * detecting an eviction.\n */\n async checkFibers(): Promise<void> {\n await this._checkInterruptedFibers();\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-unused-vars -- overridable hook\n onFiberComplete(_ctx: FiberCompleteContext): void | Promise<void> {}\n\n onFiberRecovered(ctx: FiberRecoveryContext): void | Promise<void> {\n this.restartFiber(ctx.id);\n }\n\n async onFibersRecovered(fibers: FiberRecoveryContext[]): Promise<void> {\n for (const fiber of fibers) {\n await this.onFiberRecovered(fiber);\n }\n }\n\n // ── Private implementation ────────────────────────────────────\n\n /** @internal */ _getRawFiber(fiberId: string): RawFiberRow | null {\n const result = this.sql<RawFiberRow>`\n SELECT * FROM cf_agents_fibers WHERE id = ${fiberId}\n `;\n return result && result.length > 0 ? result[0] : null;\n }\n\n /** @internal */ _safeJsonParse(value: string | null): unknown {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n }\n\n /** @internal */ _toFiberState(raw: RawFiberRow): FiberState {\n return {\n id: raw.id,\n callback: raw.callback,\n payload: this._safeJsonParse(raw.payload),\n snapshot: this._safeJsonParse(raw.snapshot),\n status: raw.status as FiberState[\"status\"],\n retryCount: raw.retry_count,\n maxRetries: raw.max_retries,\n result: this._safeJsonParse(raw.result),\n error: raw.error,\n startedAt: raw.started_at,\n updatedAt: raw.updated_at,\n completedAt: raw.completed_at,\n createdAt: raw.created_at\n };\n }\n\n /** @internal */ async _startFiber(\n id: string,\n methodName: string,\n payload: unknown,\n maxRetries: number\n ): Promise<void> {\n const disposeKeepAlive = await this.keepAlive();\n await this._runFiber(\n id,\n methodName,\n payload,\n maxRetries,\n disposeKeepAlive\n );\n }\n\n /** @internal */ async _runFiber(\n id: string,\n methodName: string,\n payload: unknown,\n maxRetries: number,\n disposeKeepAlive: () => void\n ): Promise<void> {\n try {\n while (true) {\n const fiber = this._getRawFiber(id);\n if (!fiber || fiber.status === \"cancelled\") {\n this._fiberDebug(\n \"fiber=%s exiting: %s\",\n id,\n !fiber ? \"not found\" : \"cancelled\"\n );\n return;\n }\n\n try {\n await fiberContext.run({ fiberId: id }, async () => {\n const snapshot = this._safeJsonParse(fiber.snapshot);\n const retryCount = fiber.retry_count;\n\n const callback = this[methodName as keyof this];\n if (typeof callback !== \"function\") {\n throw new Error(`Fiber method ${methodName} not found`);\n }\n\n const result = await (\n callback as (p: unknown, ctx: FiberContext) => Promise<unknown>\n ).call(this, payload, { id, snapshot, retryCount });\n\n const now = Date.now();\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'completed',\n result = ${JSON.stringify(result ?? null)},\n completed_at = ${now},\n updated_at = ${now}\n WHERE id = ${id}\n `;\n\n this._fiberDebug(\"fiber=%s completed method=%s\", id, methodName);\n\n try {\n await this.onFiberComplete({\n id,\n methodName,\n payload,\n result\n });\n } catch (e) {\n console.error(\"Error in onFiberComplete:\", e);\n }\n });\n\n return;\n } catch (e) {\n const now = Date.now();\n const currentFiber = this._getRawFiber(id);\n const newRetryCount = (currentFiber?.retry_count ?? 0) + 1;\n\n if (newRetryCount > maxRetries) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'failed',\n error = ${errorMsg},\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${id}\n `;\n this._fiberDebug(\n \"fiber=%s failed after %d retries: %s\",\n id,\n newRetryCount,\n errorMsg\n );\n return;\n }\n\n this.sql`\n UPDATE cf_agents_fibers\n SET retry_count = ${newRetryCount}, updated_at = ${now}\n WHERE id = ${id}\n `;\n this._fiberDebug(\n \"fiber=%s retrying (%d/%d)\",\n id,\n newRetryCount,\n maxRetries\n );\n continue;\n }\n }\n } finally {\n this._fiberActiveFibers.delete(id);\n disposeKeepAlive();\n }\n }\n\n /** @internal */ async _checkInterruptedFibers(): Promise<void> {\n if (this._fiberRecoveryInProgress) return;\n this._fiberRecoveryInProgress = true;\n\n try {\n const runningFibers = this.sql<RawFiberRow>`\n SELECT * FROM cf_agents_fibers\n WHERE status = 'running'\n ORDER BY created_at ASC\n `;\n\n if (!runningFibers || runningFibers.length === 0) return;\n\n const interrupted: FiberRecoveryContext[] = [];\n\n for (const fiber of runningFibers) {\n if (this._fiberActiveFibers.has(fiber.id)) continue;\n\n const newRetryCount = fiber.retry_count + 1;\n const now = Date.now();\n\n if (newRetryCount > fiber.max_retries) {\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'failed',\n error = 'max retries exceeded (eviction recovery)',\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${fiber.id}\n `;\n this._fiberDebug(\n \"fiber=%s max retries exceeded on recovery\",\n fiber.id\n );\n } else {\n this.sql`\n UPDATE cf_agents_fibers\n SET status = 'interrupted',\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${fiber.id}\n `;\n\n interrupted.push({\n id: fiber.id,\n methodName: fiber.callback,\n payload: this._safeJsonParse(fiber.payload),\n snapshot: this._safeJsonParse(fiber.snapshot),\n retryCount: newRetryCount\n });\n }\n }\n\n if (interrupted.length > 0) {\n this._fiberDebug(\n \"recovering %d interrupted fibers\",\n interrupted.length\n );\n\n this._cleanupOrphanedHeartbeats();\n\n try {\n await this.onFibersRecovered(interrupted);\n } catch (e) {\n console.error(\"Error in onFibersRecovered:\", e);\n }\n }\n } finally {\n this._fiberRecoveryInProgress = false;\n }\n }\n\n /** @internal */ _cleanupOrphanedHeartbeats() {\n this.sql`\n DELETE FROM cf_agents_schedules\n WHERE callback = '_cf_keepAliveHeartbeat'\n `;\n this._fiberDebug(\"cleaned up orphaned heartbeat schedules\");\n }\n\n /** @internal */ _maybeCleanupFibers() {\n const now = Date.now();\n if (now - this._fiberLastCleanupTime < FIBER_CLEANUP_INTERVAL_MS) {\n return;\n }\n this._fiberLastCleanupTime = now;\n\n const completedCutoff = now - FIBER_CLEANUP_COMPLETED_MS;\n const failedCutoff = now - FIBER_CLEANUP_FAILED_MS;\n\n this.sql`\n DELETE FROM cf_agents_fibers\n WHERE (status = 'completed' AND completed_at < ${completedCutoff})\n OR (status = 'failed' AND updated_at < ${failedCutoff})\n OR (status = 'cancelled' AND updated_at < ${completedCutoff})\n `;\n\n this._fiberDebug(\n \"cleanup: checked for old completed/failed/cancelled fibers\"\n );\n }\n }\n\n return FiberAgent as unknown as FiberAgentClass;\n}\n\n// ── Return type ──────────────────────────────────────────────────────\n//\n// Explicit generic constructor so consumers can write:\n// const FA = withFibers(Agent);\n// class MyAgent extends FA<Env> { ... }\n// and still see all FiberMethods on `this`.\n//\n// We define this as a standalone constructor (not intersected with\n// typeof Agent) to avoid overload-resolution issues where TypeScript\n// would pick Agent's constructor and lose FiberMethods.\n\ntype FiberAgentClass = {\n new <\n Env extends Cloudflare.Env = Cloudflare.Env,\n State = unknown,\n Props extends Record<string, unknown> = Record<string, unknown>\n >(\n ctx: DurableObjectState,\n env: Env\n ): Agent<Env, State, Props> & FiberMethods;\n};\n\n// ── FiberMethods interface ───────────────────────────────────────────\n//\n// Describes the methods added by the mixin. Exported so users can\n// reference the shape in their own type annotations if needed.\n\nexport interface FiberMethods {\n spawnFiber(\n methodName: string,\n payload?: unknown,\n options?: { maxRetries?: number }\n ): string;\n stashFiber(data: unknown): void;\n cancelFiber(fiberId: string): boolean;\n getFiber(fiberId: string): FiberState | null;\n restartFiber(fiberId: string): void;\n checkFibers(): Promise<void>;\n onFiberComplete(ctx: FiberCompleteContext): void | Promise<void>;\n onFiberRecovered(ctx: FiberRecoveryContext): void | Promise<void>;\n onFibersRecovered(fibers: FiberRecoveryContext[]): Promise<void>;\n\n /** @internal */ _fiberActiveFibers: Set<string>;\n /** @internal */ _fiberRecoveryInProgress: boolean;\n /** @internal */ _fiberLastCleanupTime: number;\n}\n"],"mappings":"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"}