basion-ai-sdk 0.9.13 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-DI-pslrx.d.ts → app-CzDArtnL.d.ts} +29 -3
- package/dist/extensions/langgraph.d.ts +1 -1
- package/dist/extensions/vercel-ai.d.ts +1 -1
- package/dist/extensions/vercel-ai.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +65 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agent.ts +55 -0
- package/src/app.ts +26 -3
|
@@ -2011,6 +2011,10 @@ declare class Agent {
|
|
|
2011
2011
|
private initialized;
|
|
2012
2012
|
private running;
|
|
2013
2013
|
private _tools?;
|
|
2014
|
+
/** AbortControllers for cancellable conversations, keyed by conversationId */
|
|
2015
|
+
private _cancelControllers;
|
|
2016
|
+
/** Tracks cancel requests that arrived before the handler created an AbortController */
|
|
2017
|
+
private _cancelledConversations;
|
|
2014
2018
|
constructor(options: {
|
|
2015
2019
|
name: string;
|
|
2016
2020
|
gatewayClient: GatewayClient;
|
|
@@ -2025,6 +2029,24 @@ declare class Agent {
|
|
|
2025
2029
|
renderClient?: RenderClient;
|
|
2026
2030
|
errorMessageTemplate?: string;
|
|
2027
2031
|
});
|
|
2032
|
+
/**
|
|
2033
|
+
* Create an AbortSignal for a conversation's current operation.
|
|
2034
|
+
* Call this before starting work (e.g. streamText) and pass the
|
|
2035
|
+
* returned signal so the operation can be cancelled externally.
|
|
2036
|
+
*
|
|
2037
|
+
* If a cancel request arrived before this call, the returned signal
|
|
2038
|
+
* will already be in the aborted state.
|
|
2039
|
+
*/
|
|
2040
|
+
createAbortSignal(conversationId: string): AbortSignal;
|
|
2041
|
+
/**
|
|
2042
|
+
* Cancel the running operation for a conversation.
|
|
2043
|
+
* Called by the message interceptor when a cancel message arrives.
|
|
2044
|
+
*/
|
|
2045
|
+
cancelConversation(conversationId: string): void;
|
|
2046
|
+
/**
|
|
2047
|
+
* Clean up the abort controller after the handler completes.
|
|
2048
|
+
*/
|
|
2049
|
+
clearAbortController(conversationId: string): void;
|
|
2028
2050
|
/**
|
|
2029
2051
|
* Access to tools (knowledge graph, etc.).
|
|
2030
2052
|
*
|
|
@@ -2374,9 +2396,13 @@ declare class BasionAgentApp {
|
|
|
2374
2396
|
*/
|
|
2375
2397
|
isConnected(): boolean;
|
|
2376
2398
|
/**
|
|
2377
|
-
*
|
|
2378
|
-
|
|
2379
|
-
|
|
2399
|
+
* Find a registered agent by its Kafka inbox topic name.
|
|
2400
|
+
*/
|
|
2401
|
+
private findAgentByTopic;
|
|
2402
|
+
/**
|
|
2403
|
+
* Set up the message interceptor for cancel signals and agent.call()
|
|
2404
|
+
* response interception. Cancel messages are checked first so they
|
|
2405
|
+
* bypass the normal handler even when the handler is busy.
|
|
2380
2406
|
*/
|
|
2381
2407
|
private setupCallInterceptor;
|
|
2382
2408
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/exceptions.ts","../../src/types.ts","../../src/agent.ts","../../src/agent-state-client.ts","../../src/extensions/vercel-ai.ts"],"names":[],"mappings":"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","file":"vercel-ai.js","sourcesContent":["/**\n * Basion Agent SDK - Custom Errors\n * Port of Python basion_agent/exceptions.py\n */\n\n/**\n * Base error class for Basion Agent SDK\n */\nexport class BasionAgentError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'BasionAgentError'\n Error.captureStackTrace?.(this, this.constructor)\n }\n}\n\n/**\n * Error during agent registration with AI Inventory\n */\nexport class RegistrationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'RegistrationError'\n }\n}\n\n/**\n * Error during Kafka operations (produce/consume)\n */\nexport class KafkaError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'KafkaError'\n }\n}\n\n/**\n * Error in SDK configuration\n */\nexport class ConfigurationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigurationError'\n }\n}\n\n/**\n * Error from API calls to backend services\n */\nexport class APIException extends BasionAgentError {\n statusCode?: number\n\n constructor(message: string, statusCode?: number) {\n super(message)\n this.name = 'APIException'\n this.statusCode = statusCode\n }\n}\n\n/**\n * Error during heartbeat operations\n */\nexport class HeartbeatError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'HeartbeatError'\n }\n}\n\n/**\n * Error during gRPC connection/communication\n */\nexport class GatewayError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'GatewayError'\n }\n}\n\n/**\n * Error during reconnection attempts\n */\nexport class ReconnectionError extends BasionAgentError {\n /** Number of attempts made before giving up */\n attempts: number\n /** The last error that occurred during reconnection */\n lastError?: Error\n\n constructor(message: string, attempts: number, lastError?: Error) {\n super(message)\n this.name = 'ReconnectionError'\n this.attempts = attempts\n this.lastError = lastError\n }\n}\n","/**\n * Basion Agent SDK - Shared Types\n * Port of Python basion_agent types\n */\n\nimport { z } from 'zod'\n\n// ============================================================================\n// Kafka Message Types\n// ============================================================================\n\nexport interface KafkaMessage {\n topic: string\n partition: number\n offset: number\n key: string\n headers: Record<string, string>\n body: Record<string, unknown>\n timestamp: number\n}\n\nexport interface KafkaHeaders {\n conversationId?: string\n userId?: string\n from_?: string\n to_?: string\n messageMetadata?: string\n messageSchema?: string\n /** @deprecated Use attachments (plural) instead. Kept for backward compatibility. */\n attachment?: string\n /** JSON-stringified array of AttachmentInfo objects */\n attachments?: string\n /** UUID of the user message in conversation-store (for cross-system mapping) */\n userMessageId?: string\n /** Pre-generated UUID for the assistant response in conversation-store (for cross-system mapping) */\n assistantMessageId?: string\n [key: string]: string | undefined\n}\n\n// ============================================================================\n// Agent Registration Types\n// ============================================================================\n\nexport interface AgentRegistrationData {\n id: string\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n}\n\nexport interface RelatedPage {\n name: string\n endpoint: string\n}\n\nexport interface Prompt {\n label: string\n prompt: string\n}\n\nexport interface RegisterOptions {\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n categoryNames?: string[]\n prompts?: Prompt[]\n detailedDescription?: string\n version?: string\n lifecycleStage?: 'dev' | 'test' | 'private-preview' | 'public-preview' | 'public'\n welcomeMessage?: string\n waitlist?: boolean\n forceUpdate?: boolean\n}\n\n// ============================================================================\n// Message Types\n// ============================================================================\n\n// Forward reference - actual Message class is imported where needed\n// Using 'any' here to avoid circular dependency, but consumers should use Message type\nexport interface MessageHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (message: any, sender: string): void | Promise<void>\n}\n\nexport interface SenderFilterOptions {\n senders?: string[]\n}\n\n// ============================================================================\n// Streamer Types\n// ============================================================================\n\nexport interface StreamOptions {\n persist?: boolean\n eventType?: string\n}\n\nexport interface StreamerOptions {\n awaiting?: boolean\n}\n\n// ============================================================================\n// Attachment Types\n// ============================================================================\n\nexport const AttachmentInfoSchema = z.object({\n url: z.string(),\n filename: z.string(),\n contentType: z.string(),\n size: z.number().optional(),\n fileType: z.string().optional(),\n objectKey: z.string().optional(),\n})\n\nexport type AttachmentInfo = z.infer<typeof AttachmentInfoSchema>\n\nexport function isImageAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType?.startsWith('image/') || false\n}\n\nexport function isPdfAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType === 'application/pdf'\n}\n\n// ============================================================================\n// Conversation Message Types\n// ============================================================================\n\nexport interface ConversationMessageData {\n id: string\n conversationId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n from?: string\n to?: string\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\n// ============================================================================\n// Gateway Client Types\n// ============================================================================\n\nexport interface ProduceAck {\n topic: string\n partition: number\n offset: number\n correlationId?: string\n}\n\n// ============================================================================\n// Reconnection Types\n// ============================================================================\n\n/**\n * Configuration options for automatic reconnection behavior.\n */\nexport interface ReconnectionOptions {\n /** Maximum number of reconnection attempts before giving up. Default: 10 */\n maxRetries?: number\n /** Initial delay in milliseconds before first retry. Default: 1000 */\n initialDelayMs?: number\n /** Maximum delay in milliseconds between retries. Default: 30000 */\n maxDelayMs?: number\n /** Multiplier for exponential backoff. Default: 2 */\n backoffMultiplier?: number\n /** Whether to automatically reconnect on disconnect. Default: true */\n autoReconnect?: boolean\n}\n\n/**\n * Connection state for the gateway client.\n */\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'\n\n/**\n * Listener callback for connection state changes.\n */\nexport interface ConnectionStateListener {\n (state: ConnectionState, error?: Error): void\n}\n\n/**\n * Default reconnection options.\n */\nexport const DEFAULT_RECONNECTION_OPTIONS: Required<ReconnectionOptions> = {\n maxRetries: 10,\n initialDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2,\n autoReconnect: true,\n}\n\n// ============================================================================\n// App Configuration Types\n// ============================================================================\n\nexport interface AppOptions {\n gatewayUrl: string\n apiKey: string\n heartbeatInterval?: number\n maxConcurrentTasks?: number\n errorMessageTemplate?: string\n secure?: boolean\n /** Reconnection options for the gateway connection */\n reconnection?: ReconnectionOptions\n /** Enable remote logging to Loki via the gateway (default: false) */\n enableRemoteLogging?: boolean\n /** Minimum log level for remote logging (default: 'info') */\n remoteLogLevel?: 'debug' | 'info' | 'warn' | 'error'\n /** Number of logs to batch before sending (default: 100) */\n remoteLogBatchSize?: number\n /** Maximum seconds between log flushes (default: 5.0) */\n remoteLogFlushInterval?: number\n /** Sentry configuration (optional — omit or leave dsn empty to disable) */\n sentry?: import('./sentry.js').SentryConfig\n /** Braintrust configuration (optional — omit or leave apiKey empty to disable) */\n braintrust?: import('./braintrust.js').BraintrustConfig\n}\n\n// ============================================================================\n// HTTP Response Types\n// ============================================================================\n\nexport interface ConversationData {\n id: string\n userId: string\n agentId?: string\n awaitingRoute?: string\n pendingResponseSchema?: Record<string, unknown>\n createdAt: string\n updatedAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface GetMessagesOptions {\n role?: string\n from?: string\n to?: string\n limit?: number\n offset?: number\n}\n\n// ============================================================================\n// Memory Types\n// ============================================================================\n\n/**\n * A message stored in ai-memory.\n * Matches the ai-memory API MessageStored schema.\n */\nexport interface MemoryMessage {\n id?: string\n role: 'user' | 'assistant' | 'system'\n content: string\n tenantId?: string\n userId?: string\n conversationId?: string\n metadata?: Record<string, unknown>\n timestamp?: string\n sequenceNumber?: number\n}\n\n/**\n * Options for memory search operations.\n */\nexport interface MemorySearchOptions {\n /** Maximum number of results (1-100, default: 10) */\n limit?: number\n /** Minimum similarity threshold (0-100, default: 70) */\n minSimilarity?: number\n /** Number of surrounding context messages to include (0-20, default: 0) */\n contextMessages?: number\n /** Tenant ID for multi-tenant filtering */\n tenantId?: string\n /** Tenant matching mode: 'exact' or 'hierarchy' */\n tenantMatch?: 'exact' | 'hierarchy'\n}\n\n/**\n * A memory search result with similarity score and optional context.\n */\nexport interface MemorySearchResult {\n message: MemoryMessage\n score: number\n context: MemoryMessage[]\n}\n\n/**\n * User conversation summary from ai-memory.\n */\nexport interface UserSummary {\n text: string\n lastUpdated: string\n messageCount: number\n version: number\n}\n","/**\n * Basion Agent SDK - Agent\n * Port of Python basion_agent/agent.py\n */\n\nimport { randomUUID } from 'crypto'\nimport { AsyncLocalStorage } from 'node:async_hooks'\n\nimport type { BasionAgentApp } from './app.js'\nimport type { AttachmentClient } from './attachment-client.js'\nimport { logBraintrustError,withBraintrustSpan } from './braintrust.js'\nimport type { ConversationClient } from './conversation-client.js'\nimport { ConfigurationError } from './exceptions.js'\nimport type { GatewayClient } from './gateway-client.js'\nimport { type HeartbeatFailureCallback,HeartbeatManager } from './heartbeat.js'\nimport type { MemoryClient } from './memory-client.js'\nimport type { MemoryV2Client } from './memory-v2-client.js'\nimport { Message } from './message.js'\nimport type { RenderClient } from './render-client.js'\nimport { captureAgentError, setConversationId,withAgentSpan } from './sentry.js'\nimport { Streamer } from './streamer.js'\nimport { Tools } from './tools/container.js'\nimport type {\n AgentRegistrationData,\n KafkaMessage,\n MessageHandler,\n SenderFilterOptions,\n StreamerOptions,\n} from './types.js'\n\n/**\n * AsyncLocalStorage context set automatically by the agent message handler.\n * VercelAIMessageStore reads from this to embed cross-system IDs without\n * requiring agent developers to pass them explicitly.\n *\n * Stored on globalThis so that the same instance is shared across all bundle\n * chunks (tsup can include this module in multiple output files; globalThis\n * ensures they all reference the same AsyncLocalStorage object).\n *\n * @internal — consumed by VercelAIMessageStore; not part of the public API.\n */\nconst _msgCtxKey = Symbol.for('basion-ai-sdk.messageContext')\nif (!(globalThis as Record<symbol, unknown>)[_msgCtxKey]) {\n (globalThis as Record<symbol, unknown>)[_msgCtxKey] = new AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n }>()\n}\nexport const _messageContext = (globalThis as Record<symbol, unknown>)[_msgCtxKey] as AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n}>\n\n/**\n * Filter for matching message senders.\n */\nexport class SenderFilter {\n private readonly _include: Set<string> = new Set()\n private readonly _exclude: Set<string> = new Set()\n readonly matchAll: boolean\n\n constructor(senders?: string[]) {\n if (!senders || senders.length === 0) {\n this.matchAll = true\n } else {\n this.matchAll = false\n for (const sender of senders) {\n if (sender.startsWith('~')) {\n this._exclude.add(sender.slice(1))\n } else {\n this._include.add(sender)\n }\n }\n }\n }\n\n /**\n * Get included senders as an array (for compatibility with Python tests).\n */\n get include(): string[] {\n return Array.from(this._include)\n }\n\n /**\n * Get excluded senders as an array (for compatibility with Python tests).\n */\n get exclude(): string[] {\n return Array.from(this._exclude)\n }\n\n /**\n * Check if sender matches this filter.\n */\n matches(sender: string): boolean {\n // If excluded, reject\n if (this._exclude.has(sender)) {\n return false\n }\n // If match all or explicitly included\n if (this.matchAll || this._include.size === 0) {\n return true\n }\n return this._include.has(sender)\n }\n}\n\n// Message handlers stored as tuples: [SenderFilter, MessageHandler]\ntype RegisteredHandler = [SenderFilter, MessageHandler]\n\n/**\n * Core agent class that handles messaging via the Agent Gateway.\n */\nexport class Agent {\n readonly name: string\n readonly agentData: AgentRegistrationData\n sendErrorResponses: boolean = true\n errorMessageTemplate: string\n\n private readonly gatewayClient: GatewayClient\n private readonly conversationClient?: ConversationClient\n private readonly memoryClient?: MemoryClient\n private readonly memoryV2Client?: MemoryV2Client\n private readonly attachmentClient?: AttachmentClient\n private readonly renderClient?: RenderClient\n private readonly heartbeatInterval: number\n private readonly heartbeatFailureThreshold: number\n private readonly onHeartbeatFailure?: HeartbeatFailureCallback\n\n /** Set by BasionAgentApp.registerMe() to enable agent.call() */\n _app?: BasionAgentApp\n\n private messageHandlers: RegisteredHandler[] = []\n private heartbeatManager?: HeartbeatManager\n private initialized: boolean = false\n private running: boolean = false\n private _tools?: Tools\n\n constructor(options: {\n name: string\n gatewayClient: GatewayClient\n agentData: AgentRegistrationData\n heartbeatInterval?: number\n heartbeatFailureThreshold?: number\n onHeartbeatFailure?: HeartbeatFailureCallback\n conversationClient?: ConversationClient\n memoryClient?: MemoryClient\n memoryV2Client?: MemoryV2Client\n attachmentClient?: AttachmentClient\n renderClient?: RenderClient\n errorMessageTemplate?: string\n }) {\n this.name = options.name\n this.gatewayClient = options.gatewayClient\n this.agentData = options.agentData\n this.heartbeatInterval = options.heartbeatInterval ?? 60\n this.heartbeatFailureThreshold = options.heartbeatFailureThreshold ?? 3\n this.onHeartbeatFailure = options.onHeartbeatFailure\n this.conversationClient = options.conversationClient\n this.memoryClient = options.memoryClient\n this.memoryV2Client = options.memoryV2Client\n this.attachmentClient = options.attachmentClient\n this.renderClient = options.renderClient\n this.errorMessageTemplate = options.errorMessageTemplate ??\n 'I encountered an error while processing your message. Please try again or contact support if the issue persists.'\n }\n\n /**\n * Access to tools (knowledge graph, etc.).\n *\n * Example:\n * const kg = agent.tools.knowledgeGraph\n * const diseases = await kg.searchDiseases({ name: \"Huntington\" })\n */\n get tools(): Tools {\n if (!this._tools) {\n this._tools = new Tools(this.gatewayClient)\n }\n return this._tools\n }\n\n /**\n * Register a message handler.\n * \n * Usage:\n * agent.onMessage(async (message, sender) => { ... })\n * agent.onMessage(handler, { senders: ['user'] })\n */\n onMessage(handler: MessageHandler): void\n onMessage(handler: MessageHandler, options: SenderFilterOptions): void\n onMessage(\n handler: MessageHandler,\n options?: SenderFilterOptions\n ): void {\n const filter = new SenderFilter(options?.senders)\n this.messageHandlers.push([filter, handler])\n }\n\n /**\n * Initialize agent after gateway connection is established.\n * Can be called multiple times (e.g., after reconnection) to re-register handlers.\n */\n initializeWithGateway(): void {\n // Register message handler with gateway\n // This is idempotent - calling it again just updates the handler\n const topic = `${this.name}.inbox`\n this.gatewayClient.registerHandler(topic, (msg) => {\n this.handleKafkaMessage(msg)\n })\n\n // Start heartbeat if not already created\n if (!this.heartbeatManager) {\n this.heartbeatManager = new HeartbeatManager(\n this.gatewayClient,\n this.name,\n this.heartbeatInterval,\n {\n failureThreshold: this.heartbeatFailureThreshold,\n onFailureThresholdExceeded: this.handleHeartbeatFailure.bind(this),\n }\n )\n }\n\n // Start or resume heartbeat (in case it was stopped during disconnect)\n if (!this.heartbeatManager.isRunning()) {\n this.heartbeatManager.start()\n } else if (this.heartbeatManager.isPaused()) {\n this.heartbeatManager.resume()\n }\n\n this.initialized = true\n }\n\n /**\n * Handle heartbeat failure threshold exceeded.\n * This can indicate a stale connection that needs reconnection.\n */\n private handleHeartbeatFailure(consecutiveFailures: number, lastError?: Error): void {\n console.warn(\n `Agent '${this.name}' heartbeat failure threshold exceeded (${consecutiveFailures} failures)`\n )\n\n // Call custom callback if provided\n if (this.onHeartbeatFailure) {\n this.onHeartbeatFailure(consecutiveFailures, lastError)\n }\n\n // If gateway is not connected, try to trigger reconnection\n if (!this.gatewayClient.isConnected() && !this.gatewayClient.isReconnectingNow()) {\n console.warn('Gateway not connected, attempting force reconnect...')\n this.gatewayClient.forceReconnect().catch((err) => {\n console.error('Force reconnect from heartbeat failure failed:', err)\n })\n }\n }\n\n /**\n * Pause heartbeat sending (e.g., during reconnection).\n */\n pauseHeartbeat(): void {\n this.heartbeatManager?.pause()\n }\n\n /**\n * Resume heartbeat sending.\n */\n resumeHeartbeat(): void {\n this.heartbeatManager?.resume()\n }\n\n /**\n * Get sender from message headers.\n */\n private getSender(headers: Record<string, string>): string {\n return headers.from_ ?? headers.from ?? 'user'\n }\n\n /**\n * Find first handler that matches the sender.\n */\n private findMatchingHandler(sender: string): MessageHandler | undefined {\n for (const [filter, handler] of this.messageHandlers) {\n if (filter.matches(sender)) {\n return handler\n }\n }\n return undefined\n }\n\n /**\n * Handle incoming Kafka message.\n */\n private handleKafkaMessage(kafkaMsg: KafkaMessage): void {\n // Run handler asynchronously\n this.handleMessageAsync(kafkaMsg).catch((error) => {\n console.error('Error in message handler:', error)\n })\n }\n\n /**\n * Async message handling with error recovery.\n */\n private async handleMessageAsync(kafkaMsg: KafkaMessage): Promise<void> {\n let message: Message | undefined\n\n try {\n // Create message from Kafka data\n message = Message.fromKafkaMessage(\n kafkaMsg.body,\n kafkaMsg.headers,\n {\n conversationClient: this.conversationClient,\n memoryClient: this.memoryClient,\n memoryV2Client: this.memoryV2Client,\n attachmentClient: this.attachmentClient,\n agentName: this.name,\n gatewayClient: this.gatewayClient,\n }\n )\n\n // Find matching handler\n const sender = this.getSender(kafkaMsg.headers)\n const handler = this.findMatchingHandler(sender)\n\n if (!handler) {\n console.warn(`No handler matched for sender: ${sender}`)\n return\n }\n\n // Extract trace ID from Kafka headers for cross-agent Braintrust correlation\n const traceId = kafkaMsg.headers.traceId\n\n // Wrap handler execution in Sentry invoke_agent span\n // setConversationId must be called INSIDE the span so the conversation ID\n // is applied within the span's scope (see Sentry AI Agents docs)\n await withAgentSpan(\n `invoke_agent ${this.name}`,\n this.name,\n 'gen_ai.invoke_agent',\n async () => {\n setConversationId(message!.conversationId)\n\n // Also wrap in Braintrust span (nested under conversation trace)\n return withBraintrustSpan(\n `invoke_agent ${this.name}`,\n () => _messageContext.run(\n {\n userMessageId: message!.userMessageId,\n responseMessageId: message!.responseMessageId,\n },\n () => handler(message!, sender),\n ),\n {\n conversationId: message!.conversationId,\n agentName: this.name,\n traceId,\n type: 'llm',\n input: [{ role: 'user', content: message!.content }],\n metadata: {\n conversationId: message!.conversationId,\n agentName: this.name,\n sender,\n },\n },\n )\n },\n {\n 'gen_ai.operation.name': 'invoke_agent',\n 'gen_ai.conversation.id': message.conversationId,\n }\n )\n\n } catch (error) {\n console.error('Error handling message:', error)\n\n // Capture error in Sentry with agent context\n captureAgentError(error, this.name, {\n conversationId: message?.conversationId ?? 'unknown',\n })\n\n // Log error in Braintrust\n logBraintrustError(error, {\n conversationId: message?.conversationId ?? 'unknown',\n agentName: this.name,\n })\n\n // Send error response if we have a message\n if (message && this.sendErrorResponses) {\n await this.sendErrorResponse(message, error as Error)\n }\n }\n }\n\n /**\n * Send error response to user when handler fails.\n */\n private async sendErrorResponse(message: Message, _error: Error): Promise<void> {\n try {\n const streamer = this.streamer(message)\n streamer.stream(this.errorMessageTemplate)\n await streamer.finish()\n } catch (err) {\n console.error('Failed to send error response:', err)\n }\n }\n\n /**\n * Report an error with agent context.\n *\n * Logs to console.error and captures in Sentry (if configured) in one call.\n * Use this for errors that don't involve streaming a response (e.g. background tasks).\n * For errors during streaming, use `streamer.streamError()` instead.\n *\n * @param error - The error to report\n * @param context - Optional additional context tags\n *\n * @example\n * ```typescript\n * try {\n * await riskyOperation()\n * } catch (e) {\n * agent.reportError(e)\n * }\n * ```\n */\n reportError(error: unknown, context?: Record<string, string>): void {\n console.error(`[${this.name}] Error:`, error)\n captureAgentError(error, this.name, context)\n }\n\n /**\n * Create a new Streamer for streaming responses.\n */\n streamer(message: Message, options: StreamerOptions = {}): Streamer {\n return new Streamer({\n agentName: this.name,\n originalMessage: message,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n }\n\n /**\n * Call another agent and wait for its response.\n *\n * Sends a message to the target agent with isCall/callId headers.\n * The response is intercepted by the app's call interception logic\n * and returned as a string.\n *\n * @param agentName - Target agent name to call\n * @param conversationId - The conversation ID for context\n * @param content - Content to send to the target agent\n * @param timeout - Timeout in milliseconds (default: 30000)\n * @returns The target agent's response content\n *\n * @example\n * ```typescript\n * const result = await agent.call('medical-agent', message.conversationId, 'Is ibuprofen safe?')\n * streamer.stream(`Medical agent says: ${result}`)\n * ```\n */\n async call(\n agentName: string,\n conversationId: string,\n content: string,\n timeout: number = 30000,\n ): Promise<string> {\n if (!this._app) {\n throw new Error(\n 'agent.call() requires a BasionAgentApp. ' +\n 'Ensure the agent was created via app.registerMe().'\n )\n }\n\n return withAgentSpan(\n `handoff from ${this.name} to ${agentName}`,\n this.name,\n 'gen_ai.handoff',\n async () => {\n const callId = randomUUID()\n\n // Create a promise that will be resolved when the response arrives\n let resolvePromise!: (value: string) => void\n let rejectPromise!: (reason: Error) => void\n const resultPromise = new Promise<string>((resolve, reject) => {\n resolvePromise = resolve\n rejectPromise = reject\n })\n\n // Register the pending call with the app\n this._app!._pendingCalls.set(callId, {\n resolve: resolvePromise,\n reject: rejectPromise,\n callerName: this.name,\n })\n\n try {\n // Produce the call message\n const headers: Record<string, string> = {\n conversationId,\n from_: this.name,\n to_: agentName,\n nextRoute: agentName,\n isCall: 'true',\n callId,\n }\n\n const body: Record<string, unknown> = {\n content,\n done: true,\n persist: false,\n }\n\n this.gatewayClient.produce('router.inbox', conversationId, headers, body)\n\n // Wait for response with timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error('TIMEOUT')), timeout)\n })\n\n const result = await Promise.race([resultPromise, timeoutPromise])\n return result\n } catch (error) {\n // Clean up on error/timeout\n this._app!._pendingCalls.delete(callId)\n this._app!._callContent.delete(callId)\n\n if (error instanceof Error && error.message === 'TIMEOUT') {\n throw new Error(`agent.call() to '${agentName}' timed out after ${timeout}ms`)\n }\n throw error\n }\n }\n ) as Promise<string>\n }\n\n /**\n * Start a new conversation and send the first message as an agent.\n *\n * Creates a conversation in the conversation store and returns a Streamer\n * for sending the initial message. The message flows through the normal\n * Kafka pipeline (router → user.inbox → provider → Centrifugo).\n *\n * The user's reply will enter the normal handler pipeline via onMessage().\n *\n * @param options - Configuration for the new conversation\n * @returns Tuple of [conversationId, streamer]\n *\n * @example\n * ```typescript\n * const [convId, streamer] = await agent.startConversation({\n * userId: 'user-uuid',\n * title: 'Weekly Check-in',\n * })\n * streamer.stream('Hi! How are you feeling today?')\n * await streamer.finish()\n * ```\n */\n async startConversation(options: {\n userId: string\n title?: string\n awaiting?: boolean\n responseSchema?: Record<string, unknown>\n messageMetadata?: Record<string, unknown>\n metadata?: Record<string, unknown>\n }): Promise<[string, Streamer]> {\n if (!this.conversationClient) {\n throw new ConfigurationError(\n 'ConversationClient is required for startConversation. ' +\n 'Ensure the agent is created via BasionAgentApp.'\n )\n }\n\n // Create conversation with current_route and locked_by set atomically\n const conversation = await this.conversationClient.createConversation({\n userId: options.userId,\n title: options.title ?? 'Agent-initiated conversation',\n agentIdentifier: this.name,\n currentRoute: this.name,\n lockedBy: this.name,\n isNew: true,\n metadata: options.metadata,\n })\n\n const conversationId = String(conversation.id)\n\n // Create streamer with direct conversationId/userId (no originalMessage)\n const streamer = new Streamer({\n agentName: this.name,\n conversationId,\n userId: options.userId,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n\n if (options.responseSchema) {\n streamer.setResponseSchema(options.responseSchema)\n }\n if (options.messageMetadata) {\n streamer.setMessageMetadata(options.messageMetadata)\n }\n\n return [conversationId, streamer]\n }\n\n /**\n * Mark agent as ready to consume messages.\n */\n startConsuming(): void {\n if (this.messageHandlers.length === 0) {\n throw new ConfigurationError('No message handlers registered')\n }\n\n if (!this.initialized) {\n throw new ConfigurationError('Agent not initialized with gateway')\n }\n\n this.running = true\n console.log(`Agent ${this.name} is now consuming messages`)\n }\n\n /**\n * Shutdown agent gracefully.\n */\n shutdown(): void {\n this.running = false\n\n if (this.heartbeatManager) {\n this.heartbeatManager.stop()\n }\n\n console.log(`Agent ${this.name} shutdown`)\n }\n\n /**\n * Check if agent is running.\n */\n isRunning(): boolean {\n return this.running\n }\n}\n","/**\n * Basion Agent SDK - Agent State Client\n * Port of Python basion_agent/agent_state_client.py\n */\n\nimport { APIException } from './exceptions.js'\n\nexport interface AgentStateResponse {\n conversationId: string\n namespace: string\n state: Record<string, unknown>\n createdAt?: string\n updatedAt?: string\n}\n\n/**\n * Async HTTP client for agent-state API.\n * Used for storing framework-specific state (e.g., Pydantic AI messages).\n */\nexport class AgentStateClient {\n private readonly baseUrl: string\n\n constructor(baseUrl: string) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n }\n\n /**\n * Store or update agent state.\n */\n async put(\n conversationId: string,\n namespace: string,\n state: Record<string, unknown>\n ): Promise<{ conversationId: string; namespace: string; created: boolean }> {\n const url = `${this.baseUrl}/agent-state/${conversationId}`\n const payload = {\n namespace,\n state,\n }\n\n try {\n const response = await fetch(url, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to store agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace: string; created: boolean }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to store agent state: ${error}`)\n }\n }\n\n /**\n * Get agent state.\n */\n async get(\n conversationId: string,\n namespace: string\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n url.searchParams.set('namespace', namespace)\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get agent state: ${error}`)\n }\n }\n\n /**\n * Delete agent state.\n */\n async delete(\n conversationId: string,\n namespace?: string\n ): Promise<{ conversationId: string; namespace?: string; deleted: number }> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n if (namespace) {\n url.searchParams.set('namespace', namespace)\n }\n\n try {\n const response = await fetch(url.toString(), { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace?: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete agent state: ${error}`)\n }\n }\n\n // ── Vercel AI Messages (row-per-message store) ──────────────────────\n\n /**\n * Append Vercel AI messages to a conversation.\n */\n async appendVercelMessages(\n conversationId: string,\n messages: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ messages }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append vercel messages: ${error}`)\n }\n }\n\n /**\n * Get Vercel AI messages for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getVercelMessages(\n conversationId: string,\n last?: number,\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/vercel-ai-messages/${conversationId}`)\n if (last !== undefined) {\n url.searchParams.set('last', String(last))\n }\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get vercel messages: ${error}`)\n }\n }\n\n /**\n * Delete all Vercel AI messages for a conversation.\n */\n async deleteVercelMessages(\n conversationId: string,\n ): Promise<{ conversation_id: string; deleted: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}`\n\n try {\n const response = await fetch(url, { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete vercel messages: ${error}`)\n }\n }\n\n // ── Filesystem Snapshots (row-per-snapshot store) ─────────────────────\n\n /**\n * Append filesystem snapshots to a conversation.\n */\n async appendFilesystemSnapshots(\n conversationId: string,\n snapshots: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ snapshots }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append filesystem snapshots: ${error}`)\n }\n }\n\n /**\n * Get filesystem snapshots for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getFilesystemSnapshots(\n conversationId: string,\n ): Promise<AgentStateResponse | null> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}`\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get filesystem snapshots: ${error}`)\n }\n }\n}\n","/**\n * Basion Agent SDK - Vercel AI SDK Extension\n *\n * VercelAIMessageStore for persistent message history with Vercel AI SDK.\n *\n * @packageDocumentation\n */\n\nimport type { BasionAgentApp } from '../app.js'\nimport { _messageContext } from '../agent.js'\nimport { AgentStateClient } from '../agent-state-client.js'\n\n// Type definitions compatible with Vercel AI SDK's CoreMessage\ninterface CoreMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content: string | Array<{ type: string;[key: string]: unknown }>\n [key: string]: unknown\n}\n\n/**\n * Persistent message store for Vercel AI SDK.\n *\n * Stores and retrieves AI SDK message history using the agent-state API.\n * Cross-system message IDs (`_messageId`) are embedded automatically — no\n * manual ID passing required.\n *\n * @example\n * ```typescript\n * import { BasionAgentApp } from 'basion-ai-sdk'\n * import { VercelAIMessageStore } from 'basion-ai-sdk/extensions/vercel-ai'\n * import { streamText } from 'ai'\n * import { anthropic } from '@ai-sdk/anthropic'\n *\n * const app = new BasionAgentApp({ gatewayUrl: 'agent-gateway:8080', apiKey: 'your-key' })\n * const store = new VercelAIMessageStore(app)\n * const agent = await app.registerMe({ ... })\n *\n * agent.onMessage(async (message) => {\n * const history = await store.load(message.conversationId)\n *\n * const messages = [...history, { role: 'user', content: message.content }]\n *\n * const result = await streamText({ model: anthropic('claude-sonnet-4-6'), messages })\n *\n * // _messageId is embedded automatically — no IDs to pass\n * await store.append(message.conversationId, [\n * { role: 'user', content: message.content },\n * ...result.response.messages,\n * ])\n * })\n *\n * app.run()\n * ```\n */\nexport class VercelAIMessageStore {\n private static readonly NAMESPACE = 'vercel_ai'\n private readonly client: AgentStateClient\n\n constructor(app: BasionAgentApp) {\n this.client = new AgentStateClient(app.gatewayClient.conversationStoreUrl)\n }\n\n /**\n * Load message history for a conversation.\n *\n * @param conversationId - The conversation ID to load history for\n * @returns Array of CoreMessage objects (empty array if no history)\n */\n async load(conversationId: string): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n /**\n * Append new messages to existing history.\n *\n * Cross-system `_messageId` fields are embedded automatically from the\n * current message handler context — no IDs need to be passed manually.\n *\n * @param conversationId - The conversation ID\n * @param newMessages - New messages to append (user turn + response messages)\n */\n async append(conversationId: string, newMessages: CoreMessage[]): Promise<void> {\n const ctx = _messageContext.getStore()\n const tagged = ctx ? this.embedMessageIds(newMessages, ctx) : newMessages\n await this.client.appendVercelMessages(\n conversationId,\n tagged.map(this.serializeMessage),\n )\n }\n\n /**\n * Clear message history for a conversation.\n *\n * @param conversationId - The conversation ID to clear history for\n */\n async clear(conversationId: string): Promise<void> {\n await this.client.deleteVercelMessages(conversationId)\n }\n\n /**\n * Get the last N messages from history.\n *\n * @param conversationId - The conversation ID\n * @param count - Number of messages to retrieve\n */\n async getLastMessages(conversationId: string, count: number): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId, count)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n private embedMessageIds(\n messages: CoreMessage[],\n ids: { userMessageId?: string; responseMessageId?: string },\n ): CoreMessage[] {\n const result = [...messages]\n\n if (ids.userMessageId) {\n const userIdx = result.findIndex((m) => m.role === 'user')\n if (userIdx !== -1) {\n result[userIdx] = { ...result[userIdx], _messageId: ids.userMessageId }\n }\n }\n\n if (ids.responseMessageId) {\n for (let i = result.length - 1; i >= 0; i--) {\n const m = result[i]\n if (\n m.role === 'assistant' &&\n (typeof m.content === 'string'\n ? m.content.length > 0\n : (m.content as Array<{ type: string }>).some((c) => c.type === 'text'))\n ) {\n result[i] = { ...result[i], _messageId: ids.responseMessageId }\n break\n }\n }\n }\n\n return result\n }\n\n private isValidMessage(msg: unknown): msg is CoreMessage {\n if (typeof msg !== 'object' || msg === null) return false\n const m = msg as Record<string, unknown>\n return (\n typeof m.role === 'string' &&\n ['system', 'user', 'assistant', 'tool'].includes(m.role) &&\n (typeof m.content === 'string' || Array.isArray(m.content))\n )\n }\n\n private serializeMessage(msg: CoreMessage): Record<string, unknown> {\n return { ...msg }\n }\n}\n\nexport type { CoreMessage }\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/exceptions.ts","../../src/types.ts","../../src/agent.ts","../../src/agent-state-client.ts","../../src/extensions/vercel-ai.ts"],"names":[],"mappings":"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","file":"vercel-ai.js","sourcesContent":["/**\n * Basion Agent SDK - Custom Errors\n * Port of Python basion_agent/exceptions.py\n */\n\n/**\n * Base error class for Basion Agent SDK\n */\nexport class BasionAgentError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'BasionAgentError'\n Error.captureStackTrace?.(this, this.constructor)\n }\n}\n\n/**\n * Error during agent registration with AI Inventory\n */\nexport class RegistrationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'RegistrationError'\n }\n}\n\n/**\n * Error during Kafka operations (produce/consume)\n */\nexport class KafkaError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'KafkaError'\n }\n}\n\n/**\n * Error in SDK configuration\n */\nexport class ConfigurationError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigurationError'\n }\n}\n\n/**\n * Error from API calls to backend services\n */\nexport class APIException extends BasionAgentError {\n statusCode?: number\n\n constructor(message: string, statusCode?: number) {\n super(message)\n this.name = 'APIException'\n this.statusCode = statusCode\n }\n}\n\n/**\n * Error during heartbeat operations\n */\nexport class HeartbeatError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'HeartbeatError'\n }\n}\n\n/**\n * Error during gRPC connection/communication\n */\nexport class GatewayError extends BasionAgentError {\n constructor(message: string) {\n super(message)\n this.name = 'GatewayError'\n }\n}\n\n/**\n * Error during reconnection attempts\n */\nexport class ReconnectionError extends BasionAgentError {\n /** Number of attempts made before giving up */\n attempts: number\n /** The last error that occurred during reconnection */\n lastError?: Error\n\n constructor(message: string, attempts: number, lastError?: Error) {\n super(message)\n this.name = 'ReconnectionError'\n this.attempts = attempts\n this.lastError = lastError\n }\n}\n","/**\n * Basion Agent SDK - Shared Types\n * Port of Python basion_agent types\n */\n\nimport { z } from 'zod'\n\n// ============================================================================\n// Kafka Message Types\n// ============================================================================\n\nexport interface KafkaMessage {\n topic: string\n partition: number\n offset: number\n key: string\n headers: Record<string, string>\n body: Record<string, unknown>\n timestamp: number\n}\n\nexport interface KafkaHeaders {\n conversationId?: string\n userId?: string\n from_?: string\n to_?: string\n messageMetadata?: string\n messageSchema?: string\n /** @deprecated Use attachments (plural) instead. Kept for backward compatibility. */\n attachment?: string\n /** JSON-stringified array of AttachmentInfo objects */\n attachments?: string\n /** UUID of the user message in conversation-store (for cross-system mapping) */\n userMessageId?: string\n /** Pre-generated UUID for the assistant response in conversation-store (for cross-system mapping) */\n assistantMessageId?: string\n [key: string]: string | undefined\n}\n\n// ============================================================================\n// Agent Registration Types\n// ============================================================================\n\nexport interface AgentRegistrationData {\n id: string\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n}\n\nexport interface RelatedPage {\n name: string\n endpoint: string\n}\n\nexport interface Prompt {\n label: string\n prompt: string\n}\n\nexport interface RegisterOptions {\n name: string\n about: string\n document: string\n representationName?: string\n baseUrl?: string\n metadata?: Record<string, unknown>\n relatedPages?: RelatedPage[]\n categoryNames?: string[]\n prompts?: Prompt[]\n detailedDescription?: string\n version?: string\n lifecycleStage?: 'dev' | 'test' | 'private-preview' | 'public-preview' | 'public'\n welcomeMessage?: string\n waitlist?: boolean\n forceUpdate?: boolean\n}\n\n// ============================================================================\n// Message Types\n// ============================================================================\n\n// Forward reference - actual Message class is imported where needed\n// Using 'any' here to avoid circular dependency, but consumers should use Message type\nexport interface MessageHandler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (message: any, sender: string): void | Promise<void>\n}\n\nexport interface SenderFilterOptions {\n senders?: string[]\n}\n\n// ============================================================================\n// Streamer Types\n// ============================================================================\n\nexport interface StreamOptions {\n persist?: boolean\n eventType?: string\n}\n\nexport interface StreamerOptions {\n awaiting?: boolean\n}\n\n// ============================================================================\n// Attachment Types\n// ============================================================================\n\nexport const AttachmentInfoSchema = z.object({\n url: z.string(),\n filename: z.string(),\n contentType: z.string(),\n size: z.number().optional(),\n fileType: z.string().optional(),\n objectKey: z.string().optional(),\n})\n\nexport type AttachmentInfo = z.infer<typeof AttachmentInfoSchema>\n\nexport function isImageAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType?.startsWith('image/') || false\n}\n\nexport function isPdfAttachment(attachment: AttachmentInfo): boolean {\n return attachment.contentType === 'application/pdf'\n}\n\n// ============================================================================\n// Conversation Message Types\n// ============================================================================\n\nexport interface ConversationMessageData {\n id: string\n conversationId: string\n role: 'user' | 'assistant' | 'system'\n content: string\n from?: string\n to?: string\n createdAt: string\n metadata?: Record<string, unknown>\n}\n\n// ============================================================================\n// Gateway Client Types\n// ============================================================================\n\nexport interface ProduceAck {\n topic: string\n partition: number\n offset: number\n correlationId?: string\n}\n\n// ============================================================================\n// Reconnection Types\n// ============================================================================\n\n/**\n * Configuration options for automatic reconnection behavior.\n */\nexport interface ReconnectionOptions {\n /** Maximum number of reconnection attempts before giving up. Default: 10 */\n maxRetries?: number\n /** Initial delay in milliseconds before first retry. Default: 1000 */\n initialDelayMs?: number\n /** Maximum delay in milliseconds between retries. Default: 30000 */\n maxDelayMs?: number\n /** Multiplier for exponential backoff. Default: 2 */\n backoffMultiplier?: number\n /** Whether to automatically reconnect on disconnect. Default: true */\n autoReconnect?: boolean\n}\n\n/**\n * Connection state for the gateway client.\n */\nexport type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting'\n\n/**\n * Listener callback for connection state changes.\n */\nexport interface ConnectionStateListener {\n (state: ConnectionState, error?: Error): void\n}\n\n/**\n * Default reconnection options.\n */\nexport const DEFAULT_RECONNECTION_OPTIONS: Required<ReconnectionOptions> = {\n maxRetries: 10,\n initialDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2,\n autoReconnect: true,\n}\n\n// ============================================================================\n// App Configuration Types\n// ============================================================================\n\nexport interface AppOptions {\n gatewayUrl: string\n apiKey: string\n heartbeatInterval?: number\n maxConcurrentTasks?: number\n errorMessageTemplate?: string\n secure?: boolean\n /** Reconnection options for the gateway connection */\n reconnection?: ReconnectionOptions\n /** Enable remote logging to Loki via the gateway (default: false) */\n enableRemoteLogging?: boolean\n /** Minimum log level for remote logging (default: 'info') */\n remoteLogLevel?: 'debug' | 'info' | 'warn' | 'error'\n /** Number of logs to batch before sending (default: 100) */\n remoteLogBatchSize?: number\n /** Maximum seconds between log flushes (default: 5.0) */\n remoteLogFlushInterval?: number\n /** Sentry configuration (optional — omit or leave dsn empty to disable) */\n sentry?: import('./sentry.js').SentryConfig\n /** Braintrust configuration (optional — omit or leave apiKey empty to disable) */\n braintrust?: import('./braintrust.js').BraintrustConfig\n}\n\n// ============================================================================\n// HTTP Response Types\n// ============================================================================\n\nexport interface ConversationData {\n id: string\n userId: string\n agentId?: string\n awaitingRoute?: string\n pendingResponseSchema?: Record<string, unknown>\n createdAt: string\n updatedAt: string\n metadata?: Record<string, unknown>\n}\n\nexport interface GetMessagesOptions {\n role?: string\n from?: string\n to?: string\n limit?: number\n offset?: number\n}\n\n// ============================================================================\n// Memory Types\n// ============================================================================\n\n/**\n * A message stored in ai-memory.\n * Matches the ai-memory API MessageStored schema.\n */\nexport interface MemoryMessage {\n id?: string\n role: 'user' | 'assistant' | 'system'\n content: string\n tenantId?: string\n userId?: string\n conversationId?: string\n metadata?: Record<string, unknown>\n timestamp?: string\n sequenceNumber?: number\n}\n\n/**\n * Options for memory search operations.\n */\nexport interface MemorySearchOptions {\n /** Maximum number of results (1-100, default: 10) */\n limit?: number\n /** Minimum similarity threshold (0-100, default: 70) */\n minSimilarity?: number\n /** Number of surrounding context messages to include (0-20, default: 0) */\n contextMessages?: number\n /** Tenant ID for multi-tenant filtering */\n tenantId?: string\n /** Tenant matching mode: 'exact' or 'hierarchy' */\n tenantMatch?: 'exact' | 'hierarchy'\n}\n\n/**\n * A memory search result with similarity score and optional context.\n */\nexport interface MemorySearchResult {\n message: MemoryMessage\n score: number\n context: MemoryMessage[]\n}\n\n/**\n * User conversation summary from ai-memory.\n */\nexport interface UserSummary {\n text: string\n lastUpdated: string\n messageCount: number\n version: number\n}\n","/**\n * Basion Agent SDK - Agent\n * Port of Python basion_agent/agent.py\n */\n\nimport { randomUUID } from 'crypto'\nimport { AsyncLocalStorage } from 'node:async_hooks'\n\nimport type { BasionAgentApp } from './app.js'\nimport type { AttachmentClient } from './attachment-client.js'\nimport { logBraintrustError,withBraintrustSpan } from './braintrust.js'\nimport type { ConversationClient } from './conversation-client.js'\nimport { ConfigurationError } from './exceptions.js'\nimport type { GatewayClient } from './gateway-client.js'\nimport { type HeartbeatFailureCallback,HeartbeatManager } from './heartbeat.js'\nimport type { MemoryClient } from './memory-client.js'\nimport type { MemoryV2Client } from './memory-v2-client.js'\nimport { Message } from './message.js'\nimport type { RenderClient } from './render-client.js'\nimport { captureAgentError, setConversationId,withAgentSpan } from './sentry.js'\nimport { Streamer } from './streamer.js'\nimport { Tools } from './tools/container.js'\nimport type {\n AgentRegistrationData,\n KafkaMessage,\n MessageHandler,\n SenderFilterOptions,\n StreamerOptions,\n} from './types.js'\n\n/**\n * AsyncLocalStorage context set automatically by the agent message handler.\n * VercelAIMessageStore reads from this to embed cross-system IDs without\n * requiring agent developers to pass them explicitly.\n *\n * Stored on globalThis so that the same instance is shared across all bundle\n * chunks (tsup can include this module in multiple output files; globalThis\n * ensures they all reference the same AsyncLocalStorage object).\n *\n * @internal — consumed by VercelAIMessageStore; not part of the public API.\n */\nconst _msgCtxKey = Symbol.for('basion-ai-sdk.messageContext')\nif (!(globalThis as Record<symbol, unknown>)[_msgCtxKey]) {\n (globalThis as Record<symbol, unknown>)[_msgCtxKey] = new AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n }>()\n}\nexport const _messageContext = (globalThis as Record<symbol, unknown>)[_msgCtxKey] as AsyncLocalStorage<{\n userMessageId?: string\n responseMessageId?: string\n}>\n\n/**\n * Filter for matching message senders.\n */\nexport class SenderFilter {\n private readonly _include: Set<string> = new Set()\n private readonly _exclude: Set<string> = new Set()\n readonly matchAll: boolean\n\n constructor(senders?: string[]) {\n if (!senders || senders.length === 0) {\n this.matchAll = true\n } else {\n this.matchAll = false\n for (const sender of senders) {\n if (sender.startsWith('~')) {\n this._exclude.add(sender.slice(1))\n } else {\n this._include.add(sender)\n }\n }\n }\n }\n\n /**\n * Get included senders as an array (for compatibility with Python tests).\n */\n get include(): string[] {\n return Array.from(this._include)\n }\n\n /**\n * Get excluded senders as an array (for compatibility with Python tests).\n */\n get exclude(): string[] {\n return Array.from(this._exclude)\n }\n\n /**\n * Check if sender matches this filter.\n */\n matches(sender: string): boolean {\n // If excluded, reject\n if (this._exclude.has(sender)) {\n return false\n }\n // If match all or explicitly included\n if (this.matchAll || this._include.size === 0) {\n return true\n }\n return this._include.has(sender)\n }\n}\n\n// Message handlers stored as tuples: [SenderFilter, MessageHandler]\ntype RegisteredHandler = [SenderFilter, MessageHandler]\n\n/**\n * Core agent class that handles messaging via the Agent Gateway.\n */\nexport class Agent {\n readonly name: string\n readonly agentData: AgentRegistrationData\n sendErrorResponses: boolean = true\n errorMessageTemplate: string\n\n private readonly gatewayClient: GatewayClient\n private readonly conversationClient?: ConversationClient\n private readonly memoryClient?: MemoryClient\n private readonly memoryV2Client?: MemoryV2Client\n private readonly attachmentClient?: AttachmentClient\n private readonly renderClient?: RenderClient\n private readonly heartbeatInterval: number\n private readonly heartbeatFailureThreshold: number\n private readonly onHeartbeatFailure?: HeartbeatFailureCallback\n\n /** Set by BasionAgentApp.registerMe() to enable agent.call() */\n _app?: BasionAgentApp\n\n private messageHandlers: RegisteredHandler[] = []\n private heartbeatManager?: HeartbeatManager\n private initialized: boolean = false\n private running: boolean = false\n private _tools?: Tools\n\n /** AbortControllers for cancellable conversations, keyed by conversationId */\n private _cancelControllers = new Map<string, AbortController>()\n /** Tracks cancel requests that arrived before the handler created an AbortController */\n private _cancelledConversations = new Set<string>()\n\n constructor(options: {\n name: string\n gatewayClient: GatewayClient\n agentData: AgentRegistrationData\n heartbeatInterval?: number\n heartbeatFailureThreshold?: number\n onHeartbeatFailure?: HeartbeatFailureCallback\n conversationClient?: ConversationClient\n memoryClient?: MemoryClient\n memoryV2Client?: MemoryV2Client\n attachmentClient?: AttachmentClient\n renderClient?: RenderClient\n errorMessageTemplate?: string\n }) {\n this.name = options.name\n this.gatewayClient = options.gatewayClient\n this.agentData = options.agentData\n this.heartbeatInterval = options.heartbeatInterval ?? 60\n this.heartbeatFailureThreshold = options.heartbeatFailureThreshold ?? 3\n this.onHeartbeatFailure = options.onHeartbeatFailure\n this.conversationClient = options.conversationClient\n this.memoryClient = options.memoryClient\n this.memoryV2Client = options.memoryV2Client\n this.attachmentClient = options.attachmentClient\n this.renderClient = options.renderClient\n this.errorMessageTemplate = options.errorMessageTemplate ??\n 'I encountered an error while processing your message. Please try again or contact support if the issue persists.'\n }\n\n /**\n * Create an AbortSignal for a conversation's current operation.\n * Call this before starting work (e.g. streamText) and pass the\n * returned signal so the operation can be cancelled externally.\n *\n * If a cancel request arrived before this call, the returned signal\n * will already be in the aborted state.\n */\n createAbortSignal(conversationId: string): AbortSignal {\n // Replace any existing controller (defensive cleanup)\n const existing = this._cancelControllers.get(conversationId)\n if (existing) {\n existing.abort()\n }\n\n const controller = new AbortController()\n this._cancelControllers.set(conversationId, controller)\n\n // Handle race: cancel arrived before handler created the controller\n if (this._cancelledConversations.has(conversationId)) {\n this._cancelledConversations.delete(conversationId)\n controller.abort()\n }\n\n return controller.signal\n }\n\n /**\n * Cancel the running operation for a conversation.\n * Called by the message interceptor when a cancel message arrives.\n */\n cancelConversation(conversationId: string): void {\n const controller = this._cancelControllers.get(conversationId)\n if (controller) {\n controller.abort()\n this._cancelControllers.delete(conversationId)\n } else {\n // Cancel arrived before handler started — mark for pre-abort\n this._cancelledConversations.add(conversationId)\n }\n }\n\n /**\n * Clean up the abort controller after the handler completes.\n */\n clearAbortController(conversationId: string): void {\n this._cancelControllers.delete(conversationId)\n this._cancelledConversations.delete(conversationId)\n }\n\n /**\n * Access to tools (knowledge graph, etc.).\n *\n * Example:\n * const kg = agent.tools.knowledgeGraph\n * const diseases = await kg.searchDiseases({ name: \"Huntington\" })\n */\n get tools(): Tools {\n if (!this._tools) {\n this._tools = new Tools(this.gatewayClient)\n }\n return this._tools\n }\n\n /**\n * Register a message handler.\n * \n * Usage:\n * agent.onMessage(async (message, sender) => { ... })\n * agent.onMessage(handler, { senders: ['user'] })\n */\n onMessage(handler: MessageHandler): void\n onMessage(handler: MessageHandler, options: SenderFilterOptions): void\n onMessage(\n handler: MessageHandler,\n options?: SenderFilterOptions\n ): void {\n const filter = new SenderFilter(options?.senders)\n this.messageHandlers.push([filter, handler])\n }\n\n /**\n * Initialize agent after gateway connection is established.\n * Can be called multiple times (e.g., after reconnection) to re-register handlers.\n */\n initializeWithGateway(): void {\n // Register message handler with gateway\n // This is idempotent - calling it again just updates the handler\n const topic = `${this.name}.inbox`\n this.gatewayClient.registerHandler(topic, (msg) => {\n this.handleKafkaMessage(msg)\n })\n\n // Start heartbeat if not already created\n if (!this.heartbeatManager) {\n this.heartbeatManager = new HeartbeatManager(\n this.gatewayClient,\n this.name,\n this.heartbeatInterval,\n {\n failureThreshold: this.heartbeatFailureThreshold,\n onFailureThresholdExceeded: this.handleHeartbeatFailure.bind(this),\n }\n )\n }\n\n // Start or resume heartbeat (in case it was stopped during disconnect)\n if (!this.heartbeatManager.isRunning()) {\n this.heartbeatManager.start()\n } else if (this.heartbeatManager.isPaused()) {\n this.heartbeatManager.resume()\n }\n\n this.initialized = true\n }\n\n /**\n * Handle heartbeat failure threshold exceeded.\n * This can indicate a stale connection that needs reconnection.\n */\n private handleHeartbeatFailure(consecutiveFailures: number, lastError?: Error): void {\n console.warn(\n `Agent '${this.name}' heartbeat failure threshold exceeded (${consecutiveFailures} failures)`\n )\n\n // Call custom callback if provided\n if (this.onHeartbeatFailure) {\n this.onHeartbeatFailure(consecutiveFailures, lastError)\n }\n\n // If gateway is not connected, try to trigger reconnection\n if (!this.gatewayClient.isConnected() && !this.gatewayClient.isReconnectingNow()) {\n console.warn('Gateway not connected, attempting force reconnect...')\n this.gatewayClient.forceReconnect().catch((err) => {\n console.error('Force reconnect from heartbeat failure failed:', err)\n })\n }\n }\n\n /**\n * Pause heartbeat sending (e.g., during reconnection).\n */\n pauseHeartbeat(): void {\n this.heartbeatManager?.pause()\n }\n\n /**\n * Resume heartbeat sending.\n */\n resumeHeartbeat(): void {\n this.heartbeatManager?.resume()\n }\n\n /**\n * Get sender from message headers.\n */\n private getSender(headers: Record<string, string>): string {\n return headers.from_ ?? headers.from ?? 'user'\n }\n\n /**\n * Find first handler that matches the sender.\n */\n private findMatchingHandler(sender: string): MessageHandler | undefined {\n for (const [filter, handler] of this.messageHandlers) {\n if (filter.matches(sender)) {\n return handler\n }\n }\n return undefined\n }\n\n /**\n * Handle incoming Kafka message.\n */\n private handleKafkaMessage(kafkaMsg: KafkaMessage): void {\n // Run handler asynchronously\n this.handleMessageAsync(kafkaMsg).catch((error) => {\n console.error('Error in message handler:', error)\n })\n }\n\n /**\n * Async message handling with error recovery.\n */\n private async handleMessageAsync(kafkaMsg: KafkaMessage): Promise<void> {\n let message: Message | undefined\n\n try {\n // Create message from Kafka data\n message = Message.fromKafkaMessage(\n kafkaMsg.body,\n kafkaMsg.headers,\n {\n conversationClient: this.conversationClient,\n memoryClient: this.memoryClient,\n memoryV2Client: this.memoryV2Client,\n attachmentClient: this.attachmentClient,\n agentName: this.name,\n gatewayClient: this.gatewayClient,\n }\n )\n\n // Find matching handler\n const sender = this.getSender(kafkaMsg.headers)\n const handler = this.findMatchingHandler(sender)\n\n if (!handler) {\n console.warn(`No handler matched for sender: ${sender}`)\n return\n }\n\n // Extract trace ID from Kafka headers for cross-agent Braintrust correlation\n const traceId = kafkaMsg.headers.traceId\n\n // Wrap handler execution in Sentry invoke_agent span\n // setConversationId must be called INSIDE the span so the conversation ID\n // is applied within the span's scope (see Sentry AI Agents docs)\n await withAgentSpan(\n `invoke_agent ${this.name}`,\n this.name,\n 'gen_ai.invoke_agent',\n async () => {\n setConversationId(message!.conversationId)\n\n // Also wrap in Braintrust span (nested under conversation trace)\n return withBraintrustSpan(\n `invoke_agent ${this.name}`,\n () => _messageContext.run(\n {\n userMessageId: message!.userMessageId,\n responseMessageId: message!.responseMessageId,\n },\n () => handler(message!, sender),\n ),\n {\n conversationId: message!.conversationId,\n agentName: this.name,\n traceId,\n type: 'llm',\n input: [{ role: 'user', content: message!.content }],\n metadata: {\n conversationId: message!.conversationId,\n agentName: this.name,\n sender,\n },\n },\n )\n },\n {\n 'gen_ai.operation.name': 'invoke_agent',\n 'gen_ai.conversation.id': message.conversationId,\n }\n )\n\n } catch (error) {\n console.error('Error handling message:', error)\n\n // Capture error in Sentry with agent context\n captureAgentError(error, this.name, {\n conversationId: message?.conversationId ?? 'unknown',\n })\n\n // Log error in Braintrust\n logBraintrustError(error, {\n conversationId: message?.conversationId ?? 'unknown',\n agentName: this.name,\n })\n\n // Send error response if we have a message\n if (message && this.sendErrorResponses) {\n await this.sendErrorResponse(message, error as Error)\n }\n }\n }\n\n /**\n * Send error response to user when handler fails.\n */\n private async sendErrorResponse(message: Message, _error: Error): Promise<void> {\n try {\n const streamer = this.streamer(message)\n streamer.stream(this.errorMessageTemplate)\n await streamer.finish()\n } catch (err) {\n console.error('Failed to send error response:', err)\n }\n }\n\n /**\n * Report an error with agent context.\n *\n * Logs to console.error and captures in Sentry (if configured) in one call.\n * Use this for errors that don't involve streaming a response (e.g. background tasks).\n * For errors during streaming, use `streamer.streamError()` instead.\n *\n * @param error - The error to report\n * @param context - Optional additional context tags\n *\n * @example\n * ```typescript\n * try {\n * await riskyOperation()\n * } catch (e) {\n * agent.reportError(e)\n * }\n * ```\n */\n reportError(error: unknown, context?: Record<string, string>): void {\n console.error(`[${this.name}] Error:`, error)\n captureAgentError(error, this.name, context)\n }\n\n /**\n * Create a new Streamer for streaming responses.\n */\n streamer(message: Message, options: StreamerOptions = {}): Streamer {\n return new Streamer({\n agentName: this.name,\n originalMessage: message,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n }\n\n /**\n * Call another agent and wait for its response.\n *\n * Sends a message to the target agent with isCall/callId headers.\n * The response is intercepted by the app's call interception logic\n * and returned as a string.\n *\n * @param agentName - Target agent name to call\n * @param conversationId - The conversation ID for context\n * @param content - Content to send to the target agent\n * @param timeout - Timeout in milliseconds (default: 30000)\n * @returns The target agent's response content\n *\n * @example\n * ```typescript\n * const result = await agent.call('medical-agent', message.conversationId, 'Is ibuprofen safe?')\n * streamer.stream(`Medical agent says: ${result}`)\n * ```\n */\n async call(\n agentName: string,\n conversationId: string,\n content: string,\n timeout: number = 30000,\n ): Promise<string> {\n if (!this._app) {\n throw new Error(\n 'agent.call() requires a BasionAgentApp. ' +\n 'Ensure the agent was created via app.registerMe().'\n )\n }\n\n return withAgentSpan(\n `handoff from ${this.name} to ${agentName}`,\n this.name,\n 'gen_ai.handoff',\n async () => {\n const callId = randomUUID()\n\n // Create a promise that will be resolved when the response arrives\n let resolvePromise!: (value: string) => void\n let rejectPromise!: (reason: Error) => void\n const resultPromise = new Promise<string>((resolve, reject) => {\n resolvePromise = resolve\n rejectPromise = reject\n })\n\n // Register the pending call with the app\n this._app!._pendingCalls.set(callId, {\n resolve: resolvePromise,\n reject: rejectPromise,\n callerName: this.name,\n })\n\n try {\n // Produce the call message\n const headers: Record<string, string> = {\n conversationId,\n from_: this.name,\n to_: agentName,\n nextRoute: agentName,\n isCall: 'true',\n callId,\n }\n\n const body: Record<string, unknown> = {\n content,\n done: true,\n persist: false,\n }\n\n this.gatewayClient.produce('router.inbox', conversationId, headers, body)\n\n // Wait for response with timeout\n const timeoutPromise = new Promise<never>((_, reject) => {\n setTimeout(() => reject(new Error('TIMEOUT')), timeout)\n })\n\n const result = await Promise.race([resultPromise, timeoutPromise])\n return result\n } catch (error) {\n // Clean up on error/timeout\n this._app!._pendingCalls.delete(callId)\n this._app!._callContent.delete(callId)\n\n if (error instanceof Error && error.message === 'TIMEOUT') {\n throw new Error(`agent.call() to '${agentName}' timed out after ${timeout}ms`)\n }\n throw error\n }\n }\n ) as Promise<string>\n }\n\n /**\n * Start a new conversation and send the first message as an agent.\n *\n * Creates a conversation in the conversation store and returns a Streamer\n * for sending the initial message. The message flows through the normal\n * Kafka pipeline (router → user.inbox → provider → Centrifugo).\n *\n * The user's reply will enter the normal handler pipeline via onMessage().\n *\n * @param options - Configuration for the new conversation\n * @returns Tuple of [conversationId, streamer]\n *\n * @example\n * ```typescript\n * const [convId, streamer] = await agent.startConversation({\n * userId: 'user-uuid',\n * title: 'Weekly Check-in',\n * })\n * streamer.stream('Hi! How are you feeling today?')\n * await streamer.finish()\n * ```\n */\n async startConversation(options: {\n userId: string\n title?: string\n awaiting?: boolean\n responseSchema?: Record<string, unknown>\n messageMetadata?: Record<string, unknown>\n metadata?: Record<string, unknown>\n }): Promise<[string, Streamer]> {\n if (!this.conversationClient) {\n throw new ConfigurationError(\n 'ConversationClient is required for startConversation. ' +\n 'Ensure the agent is created via BasionAgentApp.'\n )\n }\n\n // Create conversation with current_route and locked_by set atomically\n const conversation = await this.conversationClient.createConversation({\n userId: options.userId,\n title: options.title ?? 'Agent-initiated conversation',\n agentIdentifier: this.name,\n currentRoute: this.name,\n lockedBy: this.name,\n isNew: true,\n metadata: options.metadata,\n })\n\n const conversationId = String(conversation.id)\n\n // Create streamer with direct conversationId/userId (no originalMessage)\n const streamer = new Streamer({\n agentName: this.name,\n conversationId,\n userId: options.userId,\n gatewayClient: this.gatewayClient,\n conversationClient: this.conversationClient,\n attachmentClient: this.attachmentClient,\n renderClient: this.renderClient,\n awaiting: options.awaiting ?? false,\n })\n\n if (options.responseSchema) {\n streamer.setResponseSchema(options.responseSchema)\n }\n if (options.messageMetadata) {\n streamer.setMessageMetadata(options.messageMetadata)\n }\n\n return [conversationId, streamer]\n }\n\n /**\n * Mark agent as ready to consume messages.\n */\n startConsuming(): void {\n if (this.messageHandlers.length === 0) {\n throw new ConfigurationError('No message handlers registered')\n }\n\n if (!this.initialized) {\n throw new ConfigurationError('Agent not initialized with gateway')\n }\n\n this.running = true\n console.log(`Agent ${this.name} is now consuming messages`)\n }\n\n /**\n * Shutdown agent gracefully.\n */\n shutdown(): void {\n this.running = false\n\n if (this.heartbeatManager) {\n this.heartbeatManager.stop()\n }\n\n console.log(`Agent ${this.name} shutdown`)\n }\n\n /**\n * Check if agent is running.\n */\n isRunning(): boolean {\n return this.running\n }\n}\n","/**\n * Basion Agent SDK - Agent State Client\n * Port of Python basion_agent/agent_state_client.py\n */\n\nimport { APIException } from './exceptions.js'\n\nexport interface AgentStateResponse {\n conversationId: string\n namespace: string\n state: Record<string, unknown>\n createdAt?: string\n updatedAt?: string\n}\n\n/**\n * Async HTTP client for agent-state API.\n * Used for storing framework-specific state (e.g., Pydantic AI messages).\n */\nexport class AgentStateClient {\n private readonly baseUrl: string\n\n constructor(baseUrl: string) {\n this.baseUrl = baseUrl.replace(/\\/$/, '')\n }\n\n /**\n * Store or update agent state.\n */\n async put(\n conversationId: string,\n namespace: string,\n state: Record<string, unknown>\n ): Promise<{ conversationId: string; namespace: string; created: boolean }> {\n const url = `${this.baseUrl}/agent-state/${conversationId}`\n const payload = {\n namespace,\n state,\n }\n\n try {\n const response = await fetch(url, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to store agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace: string; created: boolean }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to store agent state: ${error}`)\n }\n }\n\n /**\n * Get agent state.\n */\n async get(\n conversationId: string,\n namespace: string\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n url.searchParams.set('namespace', namespace)\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get agent state: ${error}`)\n }\n }\n\n /**\n * Delete agent state.\n */\n async delete(\n conversationId: string,\n namespace?: string\n ): Promise<{ conversationId: string; namespace?: string; deleted: number }> {\n const url = new URL(`${this.baseUrl}/agent-state/${conversationId}`)\n if (namespace) {\n url.searchParams.set('namespace', namespace)\n }\n\n try {\n const response = await fetch(url.toString(), { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete agent state: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversationId: string; namespace?: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete agent state: ${error}`)\n }\n }\n\n // ── Vercel AI Messages (row-per-message store) ──────────────────────\n\n /**\n * Append Vercel AI messages to a conversation.\n */\n async appendVercelMessages(\n conversationId: string,\n messages: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ messages }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append vercel messages: ${error}`)\n }\n }\n\n /**\n * Get Vercel AI messages for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getVercelMessages(\n conversationId: string,\n last?: number,\n ): Promise<AgentStateResponse | null> {\n const url = new URL(`${this.baseUrl}/vercel-ai-messages/${conversationId}`)\n if (last !== undefined) {\n url.searchParams.set('last', String(last))\n }\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get vercel messages: ${error}`)\n }\n }\n\n /**\n * Delete all Vercel AI messages for a conversation.\n */\n async deleteVercelMessages(\n conversationId: string,\n ): Promise<{ conversation_id: string; deleted: number }> {\n const url = `${this.baseUrl}/vercel-ai-messages/${conversationId}`\n\n try {\n const response = await fetch(url, { method: 'DELETE' })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to delete vercel messages: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; deleted: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to delete vercel messages: ${error}`)\n }\n }\n\n // ── Filesystem Snapshots (row-per-snapshot store) ─────────────────────\n\n /**\n * Append filesystem snapshots to a conversation.\n */\n async appendFilesystemSnapshots(\n conversationId: string,\n snapshots: Record<string, unknown>[],\n ): Promise<{ conversation_id: string; appended: number; total: number }> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}/append`\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ snapshots }),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to append filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as { conversation_id: string; appended: number; total: number }\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to append filesystem snapshots: ${error}`)\n }\n }\n\n /**\n * Get filesystem snapshots for a conversation.\n * Returns AgentStateResponse-compatible shape or null if not found.\n */\n async getFilesystemSnapshots(\n conversationId: string,\n ): Promise<AgentStateResponse | null> {\n const url = `${this.baseUrl}/filesystem-snapshots/${conversationId}`\n\n try {\n const response = await fetch(url.toString())\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new APIException(`Failed to get filesystem snapshots: ${response.status} - ${errorText}`, response.status)\n }\n\n return await response.json() as AgentStateResponse\n } catch (error) {\n if (error instanceof APIException) throw error\n throw new APIException(`Failed to get filesystem snapshots: ${error}`)\n }\n }\n}\n","/**\n * Basion Agent SDK - Vercel AI SDK Extension\n *\n * VercelAIMessageStore for persistent message history with Vercel AI SDK.\n *\n * @packageDocumentation\n */\n\nimport type { BasionAgentApp } from '../app.js'\nimport { _messageContext } from '../agent.js'\nimport { AgentStateClient } from '../agent-state-client.js'\n\n// Type definitions compatible with Vercel AI SDK's CoreMessage\ninterface CoreMessage {\n role: 'system' | 'user' | 'assistant' | 'tool'\n content: string | Array<{ type: string;[key: string]: unknown }>\n [key: string]: unknown\n}\n\n/**\n * Persistent message store for Vercel AI SDK.\n *\n * Stores and retrieves AI SDK message history using the agent-state API.\n * Cross-system message IDs (`_messageId`) are embedded automatically — no\n * manual ID passing required.\n *\n * @example\n * ```typescript\n * import { BasionAgentApp } from 'basion-ai-sdk'\n * import { VercelAIMessageStore } from 'basion-ai-sdk/extensions/vercel-ai'\n * import { streamText } from 'ai'\n * import { anthropic } from '@ai-sdk/anthropic'\n *\n * const app = new BasionAgentApp({ gatewayUrl: 'agent-gateway:8080', apiKey: 'your-key' })\n * const store = new VercelAIMessageStore(app)\n * const agent = await app.registerMe({ ... })\n *\n * agent.onMessage(async (message) => {\n * const history = await store.load(message.conversationId)\n *\n * const messages = [...history, { role: 'user', content: message.content }]\n *\n * const result = await streamText({ model: anthropic('claude-sonnet-4-6'), messages })\n *\n * // _messageId is embedded automatically — no IDs to pass\n * await store.append(message.conversationId, [\n * { role: 'user', content: message.content },\n * ...result.response.messages,\n * ])\n * })\n *\n * app.run()\n * ```\n */\nexport class VercelAIMessageStore {\n private static readonly NAMESPACE = 'vercel_ai'\n private readonly client: AgentStateClient\n\n constructor(app: BasionAgentApp) {\n this.client = new AgentStateClient(app.gatewayClient.conversationStoreUrl)\n }\n\n /**\n * Load message history for a conversation.\n *\n * @param conversationId - The conversation ID to load history for\n * @returns Array of CoreMessage objects (empty array if no history)\n */\n async load(conversationId: string): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n /**\n * Append new messages to existing history.\n *\n * Cross-system `_messageId` fields are embedded automatically from the\n * current message handler context — no IDs need to be passed manually.\n *\n * @param conversationId - The conversation ID\n * @param newMessages - New messages to append (user turn + response messages)\n */\n async append(conversationId: string, newMessages: CoreMessage[]): Promise<void> {\n const ctx = _messageContext.getStore()\n const tagged = ctx ? this.embedMessageIds(newMessages, ctx) : newMessages\n await this.client.appendVercelMessages(\n conversationId,\n tagged.map(this.serializeMessage),\n )\n }\n\n /**\n * Clear message history for a conversation.\n *\n * @param conversationId - The conversation ID to clear history for\n */\n async clear(conversationId: string): Promise<void> {\n await this.client.deleteVercelMessages(conversationId)\n }\n\n /**\n * Get the last N messages from history.\n *\n * @param conversationId - The conversation ID\n * @param count - Number of messages to retrieve\n */\n async getLastMessages(conversationId: string, count: number): Promise<CoreMessage[]> {\n const result = await this.client.getVercelMessages(conversationId, count)\n\n if (!result) {\n return []\n }\n\n const state = result.state ?? {}\n const messages = state.messages as unknown[]\n\n if (!Array.isArray(messages)) {\n return []\n }\n\n return messages.filter(this.isValidMessage) as CoreMessage[]\n }\n\n private embedMessageIds(\n messages: CoreMessage[],\n ids: { userMessageId?: string; responseMessageId?: string },\n ): CoreMessage[] {\n const result = [...messages]\n\n if (ids.userMessageId) {\n const userIdx = result.findIndex((m) => m.role === 'user')\n if (userIdx !== -1) {\n result[userIdx] = { ...result[userIdx], _messageId: ids.userMessageId }\n }\n }\n\n if (ids.responseMessageId) {\n for (let i = result.length - 1; i >= 0; i--) {\n const m = result[i]\n if (\n m.role === 'assistant' &&\n (typeof m.content === 'string'\n ? m.content.length > 0\n : (m.content as Array<{ type: string }>).some((c) => c.type === 'text'))\n ) {\n result[i] = { ...result[i], _messageId: ids.responseMessageId }\n break\n }\n }\n }\n\n return result\n }\n\n private isValidMessage(msg: unknown): msg is CoreMessage {\n if (typeof msg !== 'object' || msg === null) return false\n const m = msg as Record<string, unknown>\n return (\n typeof m.role === 'string' &&\n ['system', 'user', 'assistant', 'tool'].includes(m.role) &&\n (typeof m.content === 'string' || Array.isArray(m.content))\n )\n }\n\n private serializeMessage(msg: CoreMessage): Record<string, unknown> {\n return { ...msg }\n }\n}\n\nexport type { CoreMessage }\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as StructuralStreamer, C as CheckboxConfig, F as Field, a as CheckboxGroupConfig, D as DatePickerConfig, b as FileFieldConfig, A as AttachmentInfo, H as HiddenConfig, M as MultiSelectConfig, N as NumberConfig, O as OptionDef, c as SelectConfig, d as SliderConfig, e as SwitchConfig, T as TextConfig, f as ConfirmationConfig, g as Message, h as ConfirmationResult, I as InferFields, V as ValidationResult, i as StepDef, j as ToDict, k as AccordionItemDef, l as AccordionConfig, m as AccordionComponentDict, n as CardVariant, o as CardSectionDef, p as CardButtonDef, q as CardConfig, r as CardComponentDict } from './app-
|
|
2
|
-
export { s as Agent, t as AgentInfo, u as AgentInventoryTool, v as AgentRegistrationData, w as AppOptions, x as AttachmentClient, B as BasionAgentApp, y as BraintrustConfig, z as BundleRenderOptions, E as ConnectionState, G as ConnectionStateListener, J as Conversation, K as ConversationClient, L as ConversationData, P as ConversationMessage, Q as DEFAULT_RECONNECTION_OPTIONS, R as Edge, U as Entity, W as GatewayClient, X as GenuiComponent, Y as GenuiComponentDict, Z as GetMessagesOptions, _ as HeartbeatFailureCallback, $ as HeartbeatManager, a0 as HeartbeatOptions, a1 as IngestResponse, a2 as KafkaHeaders, a3 as KafkaMessage, a4 as KnowledgeGraphTool, a5 as LogEntry, a6 as LogLevel, a7 as LokiHandlerOptions, a8 as LokiLogHandler, a9 as Memory, aa as MemoryClient, ab as MemoryIngestOptions, ac as MemoryMessage, ad as MemorySearchOptions, ae as MemorySearchResult, af as MemoryV2, ag as MemoryV2Client, ah as MemoryV2IngestPayload, ai as MemoryV2SearchPayload, aj as MessageHandler, ak as PathStep, al as PdfOptions, am as ProduceAck, an as Prompt, ao as QueryOptions, ap as ReconnectionOptions, aq as RegisterOptions, ar as RelatedPage, as as RenderBufferResult, at as RenderClient, au as RenderOptions, av as RenderResult, aw as SenderFilter, ax as SenderFilterOptions, ay as SentryConfig, az as SentryLoggerProxy, aA as SimilarDisease, aB as StreamOptions, aC as Streamer, aD as StreamerOptions, aE as Tools, aF as UserSummary, aG as createAttachmentInfo, aH as getSentryLogger, aI as isBraintrustEnabled, aJ as isSentryEnabled, aK as reportPath } from './app-
|
|
1
|
+
import { S as StructuralStreamer, C as CheckboxConfig, F as Field, a as CheckboxGroupConfig, D as DatePickerConfig, b as FileFieldConfig, A as AttachmentInfo, H as HiddenConfig, M as MultiSelectConfig, N as NumberConfig, O as OptionDef, c as SelectConfig, d as SliderConfig, e as SwitchConfig, T as TextConfig, f as ConfirmationConfig, g as Message, h as ConfirmationResult, I as InferFields, V as ValidationResult, i as StepDef, j as ToDict, k as AccordionItemDef, l as AccordionConfig, m as AccordionComponentDict, n as CardVariant, o as CardSectionDef, p as CardButtonDef, q as CardConfig, r as CardComponentDict } from './app-CzDArtnL.js';
|
|
2
|
+
export { s as Agent, t as AgentInfo, u as AgentInventoryTool, v as AgentRegistrationData, w as AppOptions, x as AttachmentClient, B as BasionAgentApp, y as BraintrustConfig, z as BundleRenderOptions, E as ConnectionState, G as ConnectionStateListener, J as Conversation, K as ConversationClient, L as ConversationData, P as ConversationMessage, Q as DEFAULT_RECONNECTION_OPTIONS, R as Edge, U as Entity, W as GatewayClient, X as GenuiComponent, Y as GenuiComponentDict, Z as GetMessagesOptions, _ as HeartbeatFailureCallback, $ as HeartbeatManager, a0 as HeartbeatOptions, a1 as IngestResponse, a2 as KafkaHeaders, a3 as KafkaMessage, a4 as KnowledgeGraphTool, a5 as LogEntry, a6 as LogLevel, a7 as LokiHandlerOptions, a8 as LokiLogHandler, a9 as Memory, aa as MemoryClient, ab as MemoryIngestOptions, ac as MemoryMessage, ad as MemorySearchOptions, ae as MemorySearchResult, af as MemoryV2, ag as MemoryV2Client, ah as MemoryV2IngestPayload, ai as MemoryV2SearchPayload, aj as MessageHandler, ak as PathStep, al as PdfOptions, am as ProduceAck, an as Prompt, ao as QueryOptions, ap as ReconnectionOptions, aq as RegisterOptions, ar as RelatedPage, as as RenderBufferResult, at as RenderClient, au as RenderOptions, av as RenderResult, aw as SenderFilter, ax as SenderFilterOptions, ay as SentryConfig, az as SentryLoggerProxy, aA as SimilarDisease, aB as StreamOptions, aC as Streamer, aD as StreamerOptions, aE as Tools, aF as UserSummary, aG as createAttachmentInfo, aH as getSentryLogger, aI as isBraintrustEnabled, aJ as isSentryEnabled, aK as reportPath } from './app-CzDArtnL.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2194,6 +2194,10 @@ var Agent = class {
|
|
|
2194
2194
|
initialized = false;
|
|
2195
2195
|
running = false;
|
|
2196
2196
|
_tools;
|
|
2197
|
+
/** AbortControllers for cancellable conversations, keyed by conversationId */
|
|
2198
|
+
_cancelControllers = /* @__PURE__ */ new Map();
|
|
2199
|
+
/** Tracks cancel requests that arrived before the handler created an AbortController */
|
|
2200
|
+
_cancelledConversations = /* @__PURE__ */ new Set();
|
|
2197
2201
|
constructor(options) {
|
|
2198
2202
|
this.name = options.name;
|
|
2199
2203
|
this.gatewayClient = options.gatewayClient;
|
|
@@ -2208,6 +2212,47 @@ var Agent = class {
|
|
|
2208
2212
|
this.renderClient = options.renderClient;
|
|
2209
2213
|
this.errorMessageTemplate = options.errorMessageTemplate ?? "I encountered an error while processing your message. Please try again or contact support if the issue persists.";
|
|
2210
2214
|
}
|
|
2215
|
+
/**
|
|
2216
|
+
* Create an AbortSignal for a conversation's current operation.
|
|
2217
|
+
* Call this before starting work (e.g. streamText) and pass the
|
|
2218
|
+
* returned signal so the operation can be cancelled externally.
|
|
2219
|
+
*
|
|
2220
|
+
* If a cancel request arrived before this call, the returned signal
|
|
2221
|
+
* will already be in the aborted state.
|
|
2222
|
+
*/
|
|
2223
|
+
createAbortSignal(conversationId) {
|
|
2224
|
+
const existing = this._cancelControllers.get(conversationId);
|
|
2225
|
+
if (existing) {
|
|
2226
|
+
existing.abort();
|
|
2227
|
+
}
|
|
2228
|
+
const controller = new AbortController();
|
|
2229
|
+
this._cancelControllers.set(conversationId, controller);
|
|
2230
|
+
if (this._cancelledConversations.has(conversationId)) {
|
|
2231
|
+
this._cancelledConversations.delete(conversationId);
|
|
2232
|
+
controller.abort();
|
|
2233
|
+
}
|
|
2234
|
+
return controller.signal;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Cancel the running operation for a conversation.
|
|
2238
|
+
* Called by the message interceptor when a cancel message arrives.
|
|
2239
|
+
*/
|
|
2240
|
+
cancelConversation(conversationId) {
|
|
2241
|
+
const controller = this._cancelControllers.get(conversationId);
|
|
2242
|
+
if (controller) {
|
|
2243
|
+
controller.abort();
|
|
2244
|
+
this._cancelControllers.delete(conversationId);
|
|
2245
|
+
} else {
|
|
2246
|
+
this._cancelledConversations.add(conversationId);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Clean up the abort controller after the handler completes.
|
|
2251
|
+
*/
|
|
2252
|
+
clearAbortController(conversationId) {
|
|
2253
|
+
this._cancelControllers.delete(conversationId);
|
|
2254
|
+
this._cancelledConversations.delete(conversationId);
|
|
2255
|
+
}
|
|
2211
2256
|
/**
|
|
2212
2257
|
* Access to tools (knowledge graph, etc.).
|
|
2213
2258
|
*
|
|
@@ -4350,12 +4395,29 @@ var BasionAgentApp = class {
|
|
|
4350
4395
|
return this.gatewayClient.isConnected();
|
|
4351
4396
|
}
|
|
4352
4397
|
/**
|
|
4353
|
-
*
|
|
4354
|
-
|
|
4355
|
-
|
|
4398
|
+
* Find a registered agent by its Kafka inbox topic name.
|
|
4399
|
+
*/
|
|
4400
|
+
findAgentByTopic(topic) {
|
|
4401
|
+
const agentName = topic.endsWith(".inbox") ? topic.slice(0, -".inbox".length) : topic;
|
|
4402
|
+
return this.agents.find((a) => a.name === agentName);
|
|
4403
|
+
}
|
|
4404
|
+
/**
|
|
4405
|
+
* Set up the message interceptor for cancel signals and agent.call()
|
|
4406
|
+
* response interception. Cancel messages are checked first so they
|
|
4407
|
+
* bypass the normal handler even when the handler is busy.
|
|
4356
4408
|
*/
|
|
4357
4409
|
setupCallInterceptor() {
|
|
4358
4410
|
this.gatewayClient.setMessageInterceptor((msg) => {
|
|
4411
|
+
if (msg.headers?.cancel === "true") {
|
|
4412
|
+
const conversationId = msg.headers?.conversationId;
|
|
4413
|
+
if (conversationId) {
|
|
4414
|
+
const agent = this.findAgentByTopic(msg.topic);
|
|
4415
|
+
if (agent) {
|
|
4416
|
+
agent.cancelConversation(conversationId);
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
return true;
|
|
4420
|
+
}
|
|
4359
4421
|
const callId = msg.headers?.callId;
|
|
4360
4422
|
if (!callId || !this._pendingCalls.has(callId)) {
|
|
4361
4423
|
return false;
|