@firststep-studio/sdk 0.10.0 → 0.11.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/index.d.mts +90 -5
- package/dist/index.d.ts +90 -5
- package/dist/index.js +97 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +97 -4
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-GoTI_c14.d.mts → types-DYJ44S2E.d.mts} +17 -0
- package/dist/{types-GoTI_c14.d.ts → types-DYJ44S2E.d.ts} +17 -0
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ucp/client.ts","../src/renderMarkers.ts","../src/streamMetadata.ts","../src/platform.ts","../src/auth.ts","../src/llm.ts"],"sourcesContent":["/**\n * UCP (Universal Classification Protocol) Client\n *\n * Client for communicating with UCP-compliant classifier endpoints.\n * Supports both full configuration and simple endpoint URL.\n *\n * @example\n * ```typescript\n * // Simple usage with endpoint URL\n * const client = UCPClient.fromEndpoint('https://api.example.com/ucp/v1/classifiers/abc123');\n *\n * // With API key\n * const client = UCPClient.fromEndpoint(\n * 'https://api.example.com/ucp/v1/classifiers/abc123',\n * { apiKey: 'your-api-key' }\n * );\n *\n * // Classify messages\n * const result = await client.classify(messages);\n * ```\n */\n\nimport type {\n UCPMessage,\n UCPClassifyRequest,\n UCPClassifyResponse,\n UCPErrorResponse,\n UCPInfoResponse,\n UCPClientConfig,\n UCPEndpointConfig,\n ClassificationResult,\n} from './types';\nimport type { ChatMessage } from '../types';\n\n// Default timeout for requests\nconst DEFAULT_TIMEOUT = 30000;\n\n/**\n * UCP Client for classifier communication\n */\nexport class UCPClient {\n private baseUrl: string;\n private classifierId: string;\n private apiKey?: string;\n private timeout: number;\n private headers: Record<string, string>;\n\n constructor(config: UCPClientConfig) {\n // Normalize base URL (remove trailing slash)\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.classifierId = config.classifierId;\n this.apiKey = config.apiKey;\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.headers = config.headers ?? {};\n }\n\n /**\n * Create client from a full endpoint URL\n *\n * URL format: {baseUrl}/classifiers/{classifierId}\n * Example: https://api.example.com/ucp/v1/classifiers/abc123\n */\n static fromEndpoint(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n ): UCPClient {\n // Parse endpoint URL to extract baseUrl and classifierId\n const url = new URL(endpoint);\n const pathParts = url.pathname.split('/');\n\n // Find \"classifiers\" in path and get the ID after it\n const classifiersIndex = pathParts.indexOf('classifiers');\n if (classifiersIndex === -1 || classifiersIndex >= pathParts.length - 1) {\n throw new Error(\n `Invalid UCP endpoint URL: ${endpoint}. Expected format: {baseUrl}/classifiers/{classifierId}`\n );\n }\n\n const classifierId = pathParts[classifiersIndex + 1];\n const baseUrlPath = pathParts.slice(0, classifiersIndex).join('/');\n const baseUrl = `${url.protocol}//${url.host}${baseUrlPath}`;\n\n return new UCPClient({\n baseUrl,\n classifierId,\n apiKey: options?.apiKey,\n timeout: options?.timeout,\n });\n }\n\n /**\n * Get the classify endpoint URL\n */\n get classifyUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/classify`;\n }\n\n /**\n * Get the info endpoint URL\n */\n get infoUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/info`;\n }\n\n /**\n * Classify messages using UCP protocol\n */\n async classify(messages: UCPMessage[]): Promise<ClassificationResult> {\n const request: UCPClassifyRequest = { messages };\n\n const response = await this.fetch<UCPClassifyResponse>(\n this.classifyUrl,\n {\n method: 'POST',\n body: JSON.stringify(request),\n }\n );\n\n // Normalize to SDK format\n return {\n classifierId: this.classifierId,\n category: response.category,\n level: response.level,\n confidence: response.score / 100, // Normalize to 0-1\n reasoning: response.rationale,\n raw: response,\n };\n }\n\n /**\n * Classify ChatMessage array (convenience method)\n * Converts ChatMessage to UCPMessage format\n */\n async classifyChat(messages: ChatMessage[]): Promise<ClassificationResult> {\n const ucpMessages: UCPMessage[] = messages\n .filter(m => m.role === 'user' || m.role === 'assistant')\n .map((m, index) => ({\n id: `msg-${index}`,\n role: m.role as 'user' | 'assistant',\n content: m.content,\n timestamp: m.timestamp ? m.timestamp.getTime() : Date.now(),\n metadata: m.metadata,\n }));\n\n return this.classify(ucpMessages);\n }\n\n /**\n * Get classifier info\n */\n async getInfo(): Promise<UCPInfoResponse> {\n return this.fetch<UCPInfoResponse>(this.infoUrl, {\n method: 'GET',\n });\n }\n\n /**\n * Check if the classifier endpoint is reachable\n */\n async healthCheck(): Promise<boolean> {\n try {\n const healthUrl = `${this.baseUrl}/health`;\n const response = await fetch(healthUrl, {\n method: 'GET',\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Internal fetch helper with error handling\n */\n private async fetch<T>(url: string, options: RequestInit): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.headers,\n };\n\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const ucpError = errorData as UCPErrorResponse;\n\n throw new UCPError(\n ucpError.error?.code || `HTTP_${response.status}`,\n ucpError.error?.message || `HTTP ${response.status}: ${response.statusText}`,\n ucpError.error?.details\n );\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof UCPError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new UCPError('TIMEOUT', `Request timed out after ${this.timeout}ms`);\n }\n throw new UCPError('NETWORK_ERROR', error.message);\n }\n\n throw new UCPError('UNKNOWN_ERROR', 'An unknown error occurred');\n }\n }\n}\n\n/**\n * UCP-specific error class\n */\nexport class UCPError extends Error {\n constructor(\n public code: string,\n message: string,\n public details?: unknown\n ) {\n super(message);\n this.name = 'UCPError';\n }\n}\n\n// ============================================\n// Convenience Functions\n// ============================================\n\n/**\n * Create a UCP client from an endpoint URL\n */\nexport function createUCPClient(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n): UCPClient {\n return UCPClient.fromEndpoint(endpoint, options);\n}\n\n/**\n * Classify messages using a UCP endpoint\n * One-shot function for simple usage\n */\nexport async function classifyWithUCP(\n endpoint: string,\n messages: ChatMessage[],\n options?: { apiKey?: string; timeout?: number }\n): Promise<ClassificationResult> {\n const client = createUCPClient(endpoint, options);\n return client.classifyChat(messages);\n}\n","/**\n * Render Markers\n *\n * Utilities for building render markers that FirstStep Studio's frontend\n * can parse and display as rich UI components (cards, alerts, forms, etc.).\n *\n * This module is the single source of truth for render marker specs.\n * - Handlers (producers) use the builder functions to emit markers\n * - Studio frontend (consumer) uses the types and MARKER_TYPES constants to parse them\n *\n * @example\n * ```typescript\n * import { renderMarkers } from '@firststep-studio/sdk';\n *\n * // In your handleStream():\n * yield {\n * type: 'text',\n * content: renderMarkers.helplineCard({\n * helplines: [{ name: '988 Lifeline', phoneNumber: '988', categories: ['crisis'], status: 'open', statusLabel: 'Available' }],\n * }),\n * };\n * ```\n */\n\n// ============================================\n// Marker Type Constants\n// ============================================\n\nexport const MARKER_TYPES = {\n HELPLINE_CARD: 'RENDER_HELPLINE_CARD',\n EMERGENCY: 'RENDER_EMERGENCY',\n RESOURCE_CARD: 'RENDER_RESOURCE_CARD',\n PROVIDER_CARD: 'RENDER_PROVIDER_CARD',\n SAFETY_PLAN: 'RENDER_SAFETY_PLAN',\n REPORT_CARD: 'RENDER_REPORT_CARD',\n} as const;\n\nexport type MarkerType = typeof MARKER_TYPES[keyof typeof MARKER_TYPES];\n\n// ============================================\n// Payload Types\n// ============================================\n\n/**\n * Helpline card payload.\n * Renders a carousel of helpline cards with contact buttons (call, text, chat, WhatsApp).\n */\nexport interface HelplineCardPayload {\n helplines: HelplineCardItem[];\n type?: 'throughline' | 'throughline_fallback' | 'stage';\n fallback?: {\n message: string;\n linkText?: string;\n linkUrl?: string;\n icon?: string;\n topic?: string;\n topics?: string[];\n };\n}\n\nexport interface HelplineCardItem {\n name: string;\n description?: string;\n categories: string[];\n status: 'open' | 'closed';\n statusLabel: string;\n statusBadge?: string;\n hoursText?: string;\n supportTypes?: string;\n smsNumber?: string;\n phoneNumber?: string;\n website?: string;\n webchat?: string;\n whatsapp?: string;\n specialties?: string[];\n highlightedTag?: string;\n verified?: boolean;\n}\n\n/**\n * Emergency number payload.\n * Renders a prominent alert card with a large call button.\n */\nexport interface EmergencyPayload {\n number: string;\n countryName?: string;\n countryCode?: string;\n}\n\n/**\n * Resource card payload.\n * Renders a carousel of resource cards with video thumbnails, tags, and visit buttons.\n */\nexport interface ResourceCardPayload {\n resources: ResourceCardItem[];\n}\n\nexport interface ResourceCardItem {\n name: string;\n url?: string;\n description?: string;\n video_url?: string;\n type?: string;\n tags?: string[];\n highlightedTag?: string;\n}\n\n/**\n * Provider card payload.\n * Renders a carousel of provider directory cards with specialty/language tags and contact info.\n */\nexport interface ProviderCardPayload {\n providers: ProviderCardItem[];\n}\n\nexport interface ProviderCardItem {\n id: string;\n name: string;\n type?: string;\n specialty: string[];\n language: string[];\n description?: string;\n location?: string;\n open_hours?: string;\n contact_phone?: string;\n contact_email?: string;\n address?: string;\n}\n\n/**\n * Safety plan payload.\n * Renders a multi-section table with expandable sections and save/export actions.\n */\nexport interface SafetyPlanPayload {\n sections: SafetyPlanSection[];\n helplines?: Array<{ name: string; phone?: string }>;\n actions?: {\n savePng?: boolean;\n saveTxt?: boolean;\n copy?: boolean;\n };\n}\n\nexport interface SafetyPlanSection {\n id: string;\n title: string;\n items: Array<string | { name: string; phone?: string; description?: string }>;\n}\n\n/**\n * Report card payload.\n * Renders a summary card of collected report data with a submit button.\n */\nexport interface ReportCardPayload {\n topic: string;\n location?: string;\n description?: string;\n perpetrator_known?: boolean;\n contact_mode?: string;\n contact_value?: string;\n submitEndpoint?: string;\n}\n\n// ============================================\n// Builder Functions\n// ============================================\n\n/**\n * Wrap a payload in render marker tags.\n * Internal helper used by all builder functions.\n */\nfunction wrapMarker(type: string, payload: unknown): string {\n return `[${type}]${JSON.stringify(payload)}[/${type}]`;\n}\n\n/**\n * Render marker builder functions.\n *\n * Each function takes a typed payload and returns a render marker string\n * that can be yielded as a `text` chunk in handleStream().\n *\n * @example\n * ```typescript\n * // Emit helpline cards\n * yield { type: 'text', content: renderMarkers.helplineCard({ helplines: [...] }) };\n *\n * // Emit emergency number\n * yield { type: 'text', content: renderMarkers.emergency({ number: '911', countryName: 'United States' }) };\n * ```\n */\nexport const renderMarkers = {\n /** Build a helpline card carousel marker */\n helplineCard: (payload: HelplineCardPayload): string =>\n wrapMarker(MARKER_TYPES.HELPLINE_CARD, payload),\n\n /** Build an emergency number alert marker */\n emergency: (payload: EmergencyPayload): string =>\n wrapMarker(MARKER_TYPES.EMERGENCY, payload),\n\n /** Build a resource card carousel marker */\n resourceCard: (payload: ResourceCardPayload): string =>\n wrapMarker(MARKER_TYPES.RESOURCE_CARD, payload),\n\n /** Build a provider card carousel marker */\n providerCard: (payload: ProviderCardPayload): string =>\n wrapMarker(MARKER_TYPES.PROVIDER_CARD, payload),\n\n /** Build a safety plan artifact marker */\n safetyPlan: (payload: SafetyPlanPayload): string =>\n wrapMarker(MARKER_TYPES.SAFETY_PLAN, payload),\n\n /** Build a report draft card marker */\n reportCard: (payload: ReportCardPayload): string =>\n wrapMarker(MARKER_TYPES.REPORT_CARD, payload),\n};\n","/**\n * Stream Metadata Builders\n *\n * Utilities for building typed metadata stream chunks that FirstStep Studio's\n * proxy persists to MongoDB for Dashboard features (Form Insights, Session\n * History, Routing Logs, Agent Transitions).\n *\n * Each function returns a ready-to-yield ProtocolStreamChunk. No need to\n * construct the `{ type, content }` wrapper manually.\n *\n * @example\n * ```typescript\n * import { streamMetadata } from '@firststep-studio/sdk';\n *\n * // In your handleStream():\n *\n * // 1. Welcome path: declare your schema once\n * yield streamMetadata.declareSchema({\n * agents: [{ id: 'intake', title: 'Intake', order: 0 }],\n * questions: [{ id: 'name', agentId: 'intake', title: 'Name', type: 'text' }],\n * });\n *\n * // 2. After each turn: send collected field values\n * yield streamMetadata.formDataUpdate({ name: 'Alice', age: '28' });\n *\n * // 3. On stage change: signal an agent transition\n * yield streamMetadata.agentTransition({ id: 'support', title: 'Support' });\n *\n * // 4. On classification: send routing result\n * yield streamMetadata.routingResult({\n * decision: 'classified',\n * reason: 'User mentioned self-harm',\n * category: 'crisis',\n * level: 'high',\n * score: 92,\n * });\n * ```\n */\n\nimport type {\n ProtocolStreamChunk,\n SchemaDeclarationPayload,\n SchemaAgent,\n SchemaQuestion,\n AgentTransitionPayload,\n RoutingClassificationPayload,\n HandoffRequestPayload,\n HandoffReturnPayload,\n HandoffOfferPayload,\n} from './types';\n\n// ============================================\n// Builder Functions\n// ============================================\n\nexport const streamMetadata = {\n /**\n * Declare the form schema (agents + questions) for Dashboard Form Insights.\n *\n * Yield this once during session initialization (welcome message).\n * The Studio proxy stores it in `chatbot.externalSchema` so that\n * Dashboard Form Insights can display question-level analytics.\n *\n * @param schema - Agents and questions your handler uses\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n declareSchema(schema: SchemaDeclarationPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { schema } };\n },\n\n /**\n * Send collected form field values for Dashboard persistence.\n *\n * Call this after each turn when new fields are captured.\n * Values are incrementally merged into `ChatSession.formData`.\n *\n * @param fields - Key-value pairs of field IDs to their collected values\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n formDataUpdate(fields: Record<string, string | number>): ProtocolStreamChunk {\n return { type: 'metadata', content: { formData: fields } };\n },\n\n /**\n * Signal an agent/stage transition for routing logs.\n *\n * The Studio proxy records this in `ChatSession.routingLogs` so\n * the Dashboard can show the conversation's agent flow.\n *\n * @param agent - The agent/stage being transitioned to\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n agentTransition(agent: AgentTransitionPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { currentAgent: agent } };\n },\n\n /**\n * Send a routing/classification result for routing logs.\n *\n * The Studio proxy records this in `ChatSession.routingLogs` as a\n * classification event with category, level, and confidence score.\n *\n * @param result - Classification/routing decision details\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n routingResult(result: RoutingClassificationPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { routing: result } };\n },\n\n // ============================================\n // Handoff Protocol Builders\n // ============================================\n\n /**\n * Request a handoff to another chatbot.\n *\n * The Studio proxy pushes a handoff stack entry and routes subsequent\n * user messages to the target handler. If `options.consent` is set,\n * Studio stores the request as a pending offer instead.\n *\n * @param payload - Handoff target, reason, context, and options\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n handoffRequest(payload: HandoffRequestPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { handoffRequest: payload } };\n },\n\n /**\n * Return from a handoff back to the parent handler.\n *\n * The Studio proxy pops the handoff stack and delivers the return\n * result to the parent handler on the next user message.\n *\n * @param payload - Summary and artifacts from the completed handoff\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n handoffReturn(payload: HandoffReturnPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { handoffReturn: payload } };\n },\n\n /**\n * Offer a handoff with user consent required.\n *\n * Studio stores this as a pending offer, sends an SSE event to the\n * frontend, and waits for the user to accept or decline before\n * executing the handoff.\n *\n * @param payload - Offer display info and the pending handoff request\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n handoffOffer(payload: HandoffOfferPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { handoffOffer: payload } };\n },\n};\n\n// ============================================\n// Re-export payload types for convenience\n// ============================================\n\nexport type {\n SchemaDeclarationPayload,\n SchemaAgent,\n SchemaQuestion,\n AgentTransitionPayload,\n RoutingClassificationPayload,\n HandoffRequestPayload,\n HandoffReturnPayload,\n HandoffOfferPayload,\n};\n","/**\n * Platform Client\n *\n * SDK client for handlers to call back to FirstStep Studio.\n * Enables handlers to query platform data (chatbot list, etc.)\n * using the same API token used for request signature verification.\n *\n * @example\n * ```typescript\n * import { createPlatformClient } from '@firststep-studio/sdk';\n *\n * const client = createPlatformClient({\n * studioUrl: 'https://studio-api.example.com',\n * token: process.env.FIRSTSTEP_TOKEN,\n * });\n *\n * const chatbots = await client.listChatbots();\n * // [{ id: '...', name: 'Safety Navigator', configSlug: 'safety-nav' }, ...]\n * ```\n */\n\n// ============================================\n// Types\n// ============================================\n\nexport interface PlatformClientConfig {\n /** Studio backend API URL (e.g. https://studio-api.example.com) */\n studioUrl: string;\n /** API token (same as FIRSTSTEP_TOKEN used for signature verification) */\n token: string;\n /** Request timeout in ms (default: 10000) */\n timeout?: number;\n}\n\nexport interface PlatformOrganization {\n /** Studio organization ID */\n id: string;\n /** Display name (null if the organization record has been deleted) */\n name: string | null;\n /** URL-safe slug */\n slug: string | null;\n}\n\nexport interface PlatformTokenInfo {\n /** API token ID */\n id: string;\n /** Human-friendly token label */\n name: string;\n /** Authorized scopes */\n scopes: string[];\n /** Project IDs this token is scoped to (null = full org access) */\n projectIds: string[] | null;\n}\n\nexport interface PlatformIdentity {\n organization: PlatformOrganization;\n token: PlatformTokenInfo;\n}\n\nexport interface PlatformChatbotRegisterInput {\n /** Stable handler-side identifier used to upsert (e.g. handler config ID). */\n externalId: string;\n /** Display name shown in Studio's chatbot list. */\n name: string;\n /** Optional human description. */\n description?: string;\n /** Public URL where Studio should send protocol requests. */\n handlerUrl: string;\n /** Slug for multi-config routing on the handler side. */\n configSlug?: string;\n /** Optional capability flags advertised to Studio. */\n capabilities?: {\n streaming?: boolean;\n formQuestions?: boolean;\n knowledgeActions?: boolean;\n integrations?: boolean;\n [key: string]: unknown;\n };\n}\n\nexport interface PlatformChatbotRegistration {\n /** Studio chatbot MongoDB ID (immutable across re-registrations). */\n id: string;\n /** Echoes back the externalId used as upsert key. */\n externalId: string;\n /** Resolved name as stored in Studio. */\n name: string;\n protocolConfig: Record<string, unknown>;\n}\n\nexport interface PlatformChatbot {\n /** Studio chatbot MongoDB ID */\n id: string;\n /** Chatbot display name */\n name: string;\n /** Handler URL (if external handler configured) */\n handlerUrl?: string;\n /** Config slug for multi-config routing */\n configSlug?: string;\n /** Handler type */\n handlerType?: 'builtin' | 'external';\n}\n\nexport interface PlatformKnowledgeSummary {\n /** Studio knowledge MongoDB ID */\n id: string;\n /** Knowledge name */\n name: string;\n /** Optional description */\n description?: string;\n /** Knowledge type (e.g. 'text', 'database') */\n type?: string;\n /** Owning organization ID */\n organizationId: string;\n /** Timestamps */\n createdAt?: number | string;\n updatedAt?: number | string;\n}\n\n/**\n * Full knowledge document (shape mirrors the underlying mongoose doc).\n * Kept loose because the schema is large and varies by knowledge `type`;\n * narrow types can be added incrementally as callers need them.\n */\nexport type PlatformKnowledge = PlatformKnowledgeSummary & Record<string, unknown>;\n\nexport interface ListKnowledgeOptions {\n /**\n * Target organization to list knowledge from. If omitted, defaults to\n * the org associated with the SDK token. Use this to read from a\n * different org (e.g. a platform-operated shared library marked\n * `publicReadable` in Studio).\n */\n orgId?: string;\n}\n\nexport interface KnowledgeSearchOptions {\n /** Max records to return (server-side clamped to 1..50, default 10). */\n limit?: number;\n /**\n * Optional sheet filter. Pass a sheet id (from the knowledge document)\n * to restrict the search to that sheet. Omit to search across all\n * sheets in the knowledge.\n */\n sheetId?: string;\n}\n\n/**\n * A single hit from `client.knowledge.search`. `score` is the Atlas\n * vector similarity score (cosine in our setup, so higher is closer).\n * `data` mirrors the underlying record fields (column id -> value).\n */\nexport interface PlatformKnowledgeSearchHit {\n id: string;\n knowledgeId: string;\n sheetId: string;\n data: Record<string, unknown>;\n score: number;\n createdAt?: number | string;\n updatedAt?: number | string;\n}\n\n/**\n * Helplines query params — passed verbatim to Throughline. Loose-typed\n * so the SDK stays compatible with new Throughline query keys without\n * a release. Required by Throughline today: `country_code`. Common\n * optional keys: `topic`, `contact_method`, `limit`.\n */\nexport type ThroughlineHelplinesQuery = Record<string, string | number | boolean | undefined>;\n\nexport class PlatformError extends Error {\n status: number;\n constructor(message: string, status: number) {\n super(message);\n this.name = 'PlatformError';\n this.status = status;\n }\n}\n\n// ============================================\n// Client\n// ============================================\n\nexport class PlatformClient {\n private studioUrl: string;\n private token: string;\n private timeout: number;\n\n constructor(config: PlatformClientConfig) {\n this.studioUrl = config.studioUrl.replace(/\\/+$/, '');\n this.token = config.token;\n this.timeout = config.timeout || 10000;\n }\n\n private async request<T>(\n path: string,\n errorPrefix: string,\n init: { method?: string; body?: unknown } = {}\n ): Promise<T> {\n const res = await fetch(`${this.studioUrl}${path}`, {\n method: init.method || 'GET',\n headers: {\n 'Authorization': `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new PlatformError(`${errorPrefix}: ${res.status} ${body}`, res.status);\n }\n if (res.status === 204) return undefined as T;\n return res.json() as Promise<T>;\n }\n\n /**\n * Resolve the organization and token metadata for the configured token.\n * Useful for connection state UI (\"Connected to {orgName}\") and audit.\n */\n async getMe(): Promise<PlatformIdentity> {\n return this.request<PlatformIdentity>('/api/protocol/me', 'Failed to fetch identity');\n }\n\n /**\n * List chatbots accessible to this handler's organization.\n * Studio resolves the organization from the API token.\n */\n async listChatbots(): Promise<PlatformChatbot[]> {\n const data = await this.request<{ chatbots: PlatformChatbot[] }>(\n '/api/protocol/chatbots',\n 'Failed to list chatbots'\n );\n return data.chatbots;\n }\n\n /**\n * Register or update an external-handler chatbot under the token's\n * organization. Idempotent: identified by `externalId`, so handlers\n * can call this on every config save without creating duplicates.\n */\n async registerChatbot(input: PlatformChatbotRegisterInput): Promise<PlatformChatbotRegistration> {\n return this.request<PlatformChatbotRegistration>(\n '/api/protocol/chatbots',\n 'Failed to register chatbot',\n { method: 'POST', body: input }\n );\n }\n\n /**\n * Mark the chatbot identified by `externalId` as disconnected.\n * The Studio record is preserved (history, analytics survive).\n */\n async unregisterChatbot(externalId: string): Promise<{ ok: boolean; modified: number }> {\n return this.request<{ ok: boolean; modified: number }>(\n `/api/protocol/chatbots/${encodeURIComponent(externalId)}`,\n 'Failed to unregister chatbot',\n { method: 'DELETE' }\n );\n }\n\n /**\n * Knowledge / resource surface.\n *\n * Reads are allowed when the SDK token's org matches the target org,\n * OR when the target org has `publicReadable: true` on the Studio side.\n * Writes are not yet exposed here — those still go through the user-\n * authenticated /api/knowledge routes from the Studio UI.\n */\n knowledge = {\n /**\n * List knowledge items for an organization.\n * Defaults to the SDK token's own org if `orgId` is omitted.\n */\n list: async (options: ListKnowledgeOptions = {}): Promise<PlatformKnowledgeSummary[]> => {\n const qs = options.orgId ? `?organizationId=${encodeURIComponent(options.orgId)}` : '';\n const data = await this.request<{ knowledge: PlatformKnowledgeSummary[] }>(\n `/api/protocol/knowledge${qs}`,\n 'Failed to list knowledge'\n );\n return data.knowledge;\n },\n\n /**\n * Fetch a single knowledge item by its Studio ID. The caller must\n * have read access to the knowledge's owning org (same-org or\n * publicReadable).\n */\n get: async (id: string): Promise<PlatformKnowledge> => {\n const data = await this.request<{ knowledge: PlatformKnowledge }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}`,\n 'Failed to fetch knowledge'\n );\n return data.knowledge;\n },\n\n /**\n * Semantic search over a knowledge's records. Studio embeds the\n * query server-side (Gemini gemini-embedding-001) and runs Atlas\n * Vector Search against the `vector_index` on `knowledge_records`.\n *\n * Same read access rules as `get` — same-org or publicReadable.\n *\n * Returns an empty array (not an error) when the Atlas index is\n * absent or when nothing matches, so handler code can treat\n * \"no hits\" and \"RAG not configured yet\" identically.\n */\n search: async (\n id: string,\n query: string,\n options: KnowledgeSearchOptions = {}\n ): Promise<PlatformKnowledgeSearchHit[]> => {\n const data = await this.request<{ results: PlatformKnowledgeSearchHit[] }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/search`,\n 'Failed to search knowledge',\n {\n method: 'POST',\n body: {\n query,\n ...(options.limit !== undefined ? { limit: options.limit } : {}),\n ...(options.sheetId !== undefined ? { sheetId: options.sheetId } : {}),\n },\n }\n );\n return data.results;\n },\n\n /**\n * Turn semantic search on or off for a knowledge.\n *\n * Writes are strictly same-org — `publicReadable` does NOT confer\n * write access. Flipping `false -> true` schedules a background\n * backfill of existing records on the Studio side; this call\n * returns as soon as the flag is persisted, not when the backfill\n * finishes.\n *\n * Idempotent: setting the flag to its current value is a no-op\n * (`backfillTriggered` will be `false`).\n */\n setRagEnabled: async (\n id: string,\n enabled: boolean\n ): Promise<{ id: string; ragEnabled: boolean; backfillTriggered: boolean }> => {\n const data = await this.request<{\n knowledge: { id: string; ragEnabled: boolean; backfillTriggered: boolean };\n }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/rag`,\n 'Failed to toggle knowledge RAG',\n { method: 'POST', body: { enabled } }\n );\n return data.knowledge;\n },\n };\n\n /**\n * Throughline (find-a-helpline) surface.\n *\n * Handlers used to call api.throughlinecare.com directly, which meant\n * each handler had to hold OAuth client credentials and re-implement\n * token caching. Studio holds the per-org credentials on\n * `Organization.throughline.{clientId,clientSecret}` and proxies the\n * Throughline endpoints; the SDK token identifies the org server-side\n * so callers never see the secret.\n *\n * Responses are returned as parsed JSON. Studio does not reshape the\n * Throughline body — these surfaces stay compatible with future\n * Throughline API additions without a Studio or SDK release.\n *\n * Caching (24h on countries + topics, none on helplines) is server-\n * side; the SDK is stateless.\n */\n throughline = {\n /**\n * GET /v1/countries — Throughline's country catalog (codes,\n * names, emergency numbers). Cached 24h server-side.\n */\n getCountries: async (): Promise<unknown> => {\n return this.request<unknown>(\n '/api/protocol/throughline/countries',\n 'Failed to fetch Throughline countries'\n );\n },\n\n /**\n * GET /v1/topics — Throughline's topic taxonomy used to filter\n * helpline searches. Cached 24h server-side.\n */\n getTopics: async (): Promise<unknown> => {\n return this.request<unknown>(\n '/api/protocol/throughline/topics',\n 'Failed to fetch Throughline topics'\n );\n },\n\n /**\n * GET /v1/helplines?country_code=...&topic=...&... — live search,\n * not cached. The query object is forwarded verbatim to Throughline,\n * so any new key Throughline adds will work without an SDK change.\n * Required by Throughline today: `country_code`.\n */\n getHelplines: async (query: ThroughlineHelplinesQuery): Promise<unknown> => {\n const qs = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined || v === null) continue;\n qs.append(k, String(v));\n }\n const suffix = qs.toString();\n return this.request<unknown>(\n `/api/protocol/throughline/helplines${suffix ? `?${suffix}` : ''}`,\n 'Failed to fetch Throughline helplines'\n );\n },\n };\n}\n\n// ============================================\n// Factory\n// ============================================\n\n/**\n * Create a PlatformClient for calling back to FirstStep Studio.\n */\nexport function createPlatformClient(config: PlatformClientConfig): PlatformClient {\n return new PlatformClient(config);\n}\n","import { createHash, createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Hash a token to derive the shared HMAC key.\n * The backend only stores SHA-256(token) and uses that hash as the HMAC key.\n * The SDK must hash the plaintext token the same way to verify signatures.\n */\nfunction deriveSigningKey(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n\n/**\n * Verify an HMAC-SHA256 request signature.\n *\n * The handler server can use this to verify that incoming webhook\n * requests were signed by the FirstStep platform using the shared token.\n *\n * The HMAC key is SHA-256(token), matching the backend which only stores\n * the token hash and uses it directly as the HMAC key.\n *\n * @param token - The API token (FIRSTSTEP_TOKEN)\n * @param payload - The raw request body string\n * @param signature - The signature from the X-FirstStep-Signature header\n * @returns true if the signature is valid\n *\n * @example\n * ```typescript\n * import { verifyRequestSignature } from '@firststep-studio/sdk';\n *\n * app.post('/webhook', (req, res) => {\n * const signature = req.headers['x-firststep-signature'] as string;\n * if (!verifyRequestSignature(process.env.FIRSTSTEP_TOKEN!, req.body, signature)) {\n * return res.status(401).send('Invalid signature');\n * }\n * // Process the request...\n * });\n * ```\n */\nexport function verifyRequestSignature(\n token: string,\n payload: string,\n signature: string\n): boolean {\n try {\n const signingKey = deriveSigningKey(token);\n const expected = createHmac('sha256', signingKey)\n .update(payload)\n .digest('hex');\n const expectedBuffer = Buffer.from(expected, 'hex');\n const signatureBuffer = Buffer.from(signature, 'hex');\n\n if (expectedBuffer.length !== signatureBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(expectedBuffer, signatureBuffer);\n } catch {\n return false;\n }\n}\n\n/**\n * Create an HMAC-SHA256 signature for a payload.\n *\n * Used by the platform to sign outgoing requests to handler servers.\n *\n * @param token - The API token\n * @param payload - The request body string to sign\n * @returns The hex-encoded HMAC signature\n */\nexport function createRequestSignature(\n token: string,\n payload: string\n): string {\n const signingKey = deriveSigningKey(token);\n return createHmac('sha256', signingKey).update(payload).digest('hex');\n}\n\n/**\n * Create an Authorization header value for API requests.\n *\n * @param token - The API token (fst_xxx)\n * @returns The header value, e.g. \"Bearer fst_xxx\"\n *\n * @example\n * ```typescript\n * import { createAuthHeader } from '@firststep-studio/sdk';\n *\n * const response = await fetch('https://api.firststep.ai/api/projects', {\n * headers: {\n * 'Authorization': createAuthHeader(process.env.FIRSTSTEP_TOKEN!),\n * },\n * });\n * ```\n */\nexport function createAuthHeader(token: string): string {\n return `Bearer ${token}`;\n}\n\n/**\n * Validate that a token has the correct format (fst_ prefix + 40 hex chars).\n *\n * @param token - The token string to validate\n * @returns true if the token matches the expected format\n */\nexport function isValidToken(token: string): boolean {\n return (\n typeof token === 'string' &&\n token.startsWith(TOKEN_PREFIX) &&\n token.length === TOKEN_LENGTH\n );\n}\n","/**\n * LLM Client\n *\n * SDK client for calling Studio's `/api/llm/*` proxy routes. Studio forwards\n * the request body 1:1 to Google's Generative Language REST API, so the\n * shapes here mirror Google's REST shape (NOT the @google/genai SDK shape).\n *\n * Reference: https://ai.google.dev/api/generate-content\n *\n * @example\n * ```typescript\n * import { createLLMClient } from '@firststep-studio/sdk';\n *\n * const llm = createLLMClient({\n * studioUrl: 'https://studio-api.example.com',\n * token: process.env.FIRSTSTEP_TOKEN!,\n * });\n *\n * // Single-shot\n * const res = await llm.generate({\n * model: 'gemini-3-flash-preview',\n * contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],\n * generationConfig: { temperature: 0.7, maxOutputTokens: 500 },\n * });\n * console.log(res.candidates[0].content.parts[0].text);\n *\n * // Streaming\n * for await (const chunk of llm.generateStream({\n * model: 'gemini-3-flash-preview',\n * contents: [{ role: 'user', parts: [{ text: 'Count to 5' }] }],\n * })) {\n * process.stdout.write(chunk.candidates?.[0]?.content?.parts?.[0]?.text ?? '');\n * }\n *\n * // Cache\n * const cache = await llm.cache.create({\n * model: 'models/gemini-2.5-flash',\n * systemInstruction: { parts: [{ text: 'long system prompt...' }] },\n * ttl: '3600s',\n * });\n * await llm.cache.get(cache.name);\n * await llm.cache.delete(cache.name);\n * ```\n */\n\n// ============================================\n// Types — Google REST API shape (kept loose for forward-compat)\n// ============================================\n\nexport interface LLMClientConfig {\n /** Studio backend API URL (e.g. https://studio-api.example.com) */\n studioUrl: string;\n /** API token (Bearer fst_<...>) */\n token: string;\n /** Request timeout in ms for non-streaming calls (default: 60000) */\n timeout?: number;\n}\n\nexport interface LLMPart {\n text?: string;\n inlineData?: { mimeType: string; data: string };\n [k: string]: unknown;\n}\n\nexport interface LLMContent {\n role?: 'user' | 'model';\n parts: LLMPart[];\n}\n\nexport interface LLMGenerationConfig {\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n responseMimeType?: string;\n responseSchema?: Record<string, unknown>;\n thinkingConfig?: {\n thinkingBudget?: number;\n thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high';\n includeThoughts?: boolean;\n };\n stopSequences?: string[];\n [k: string]: unknown;\n}\n\nexport interface LLMSafetySetting {\n category: string;\n threshold: string;\n}\n\n/**\n * Request body for /api/llm/generate and /api/llm/generate/stream.\n * Mirrors Google's `:generateContent` REST request, plus `model` at the top level\n * so Studio can build the URL path.\n */\nexport interface LLMGenerateRequest {\n model: string;\n contents: LLMContent[];\n systemInstruction?: { parts: LLMPart[] };\n generationConfig?: LLMGenerationConfig;\n safetySettings?: LLMSafetySetting[];\n /** Resource name of an explicit cache, e.g. \"cachedContents/abc123\" */\n cachedContent?: string;\n /** Function-calling tools (forwarded as-is) */\n tools?: unknown[];\n [k: string]: unknown;\n}\n\nexport interface LLMUsageMetadata {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n cachedContentTokenCount?: number;\n thoughtsTokenCount?: number;\n totalTokenCount?: number;\n [k: string]: unknown;\n}\n\nexport interface LLMCandidate {\n content?: LLMContent;\n finishReason?: string;\n index?: number;\n safetyRatings?: unknown[];\n [k: string]: unknown;\n}\n\nexport interface LLMGenerateResponse {\n candidates?: LLMCandidate[];\n usageMetadata?: LLMUsageMetadata;\n promptFeedback?: { blockReason?: string; [k: string]: unknown };\n modelVersion?: string;\n responseId?: string;\n [k: string]: unknown;\n}\n\nexport interface LLMCacheCreateRequest {\n model: string;\n displayName?: string;\n systemInstruction?: { parts: LLMPart[] };\n contents?: LLMContent[];\n /** Time-to-live, e.g. \"3600s\" */\n ttl?: string;\n [k: string]: unknown;\n}\n\nexport interface LLMCachedContent {\n name: string;\n model: string;\n createTime?: string;\n updateTime?: string;\n expireTime?: string;\n displayName?: string;\n usageMetadata?: { totalTokenCount?: number };\n [k: string]: unknown;\n}\n\nexport class LLMError extends Error {\n status: number;\n body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = 'LLMError';\n this.status = status;\n this.body = body;\n }\n}\n\n// ============================================\n// Client\n// ============================================\n\nexport class LLMClient {\n private studioUrl: string;\n private token: string;\n private timeout: number;\n\n constructor(config: LLMClientConfig) {\n this.studioUrl = config.studioUrl.replace(/\\/+$/, '');\n this.token = config.token;\n this.timeout = config.timeout ?? 60000;\n }\n\n private headers(): Record<string, string> {\n return {\n 'Authorization': `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n };\n }\n\n /**\n * Single-shot generation. Body is forwarded to Google's :generateContent\n * unchanged. Response is Google's response shape unchanged.\n */\n async generate(req: LLMGenerateRequest): Promise<LLMGenerateResponse> {\n const res = await fetch(`${this.studioUrl}/api/llm/generate`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(req),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n const text = await res.text();\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n\n if (!res.ok) {\n throw new LLMError(`LLM generate failed: ${res.status}`, res.status, body);\n }\n return body as LLMGenerateResponse;\n }\n\n /**\n * Streaming generation as an AsyncGenerator of partial responses.\n * Each yielded chunk has the same shape as a non-streaming response (a\n * `candidates[]` slice with the next text fragment, plus usageMetadata\n * on the final chunk).\n *\n * Note: timeout option is not enforced for streams; consumer should\n * abort externally if needed.\n */\n async *generateStream(req: LLMGenerateRequest): AsyncGenerator<LLMGenerateResponse> {\n const res = await fetch(`${this.studioUrl}/api/llm/generate/stream`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(req),\n });\n\n if (!res.ok || !res.body) {\n const text = await res.text().catch(() => '');\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n throw new LLMError(`LLM generateStream failed: ${res.status}`, res.status, body);\n }\n\n // Consume SSE: lines like `data: {json}\\n\\n`. We pop full lines off a\n // rolling buffer and yield each parsed `data:` payload.\n const reader = (res.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n for (const line of lines) {\n const t = line.trim();\n if (!t.startsWith('data: ')) continue;\n const payload = t.slice(6);\n if (payload === '[DONE]') continue;\n try {\n yield JSON.parse(payload) as LLMGenerateResponse;\n } catch {\n // Skip unparsable chunk; SSE may emit stray comments / heartbeats.\n }\n }\n }\n\n // Flush any final buffered line that didn't end in \\n.\n const t = buffer.trim();\n if (t.startsWith('data: ')) {\n const payload = t.slice(6);\n if (payload && payload !== '[DONE]') {\n try { yield JSON.parse(payload) as LLMGenerateResponse; } catch { /* noop */ }\n }\n }\n } finally {\n try { reader.releaseLock(); } catch { /* noop */ }\n }\n }\n\n /**\n * Explicit prompt cache surface. Maps to Google's `/cachedContents` REST API.\n */\n cache = {\n /**\n * Create an explicit cache. Returns the resource (use `cache.name` as the\n * `cachedContent` field on a future generate() request).\n */\n create: async (req: LLMCacheCreateRequest): Promise<LLMCachedContent> => {\n const res = await fetch(`${this.studioUrl}/api/llm/cache`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(req),\n signal: AbortSignal.timeout(this.timeout),\n });\n const text = await res.text();\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n if (!res.ok) {\n throw new LLMError(`LLM cache.create failed: ${res.status}`, res.status, body);\n }\n return body as LLMCachedContent;\n },\n\n /**\n * Fetch a cache by resource name (e.g. \"cachedContents/abc123\").\n * Returns null when the cache is not found (404), which is the common\n * \"expired\" signal — callers can simply recreate.\n */\n get: async (name: string): Promise<LLMCachedContent | null> => {\n const res = await fetch(`${this.studioUrl}/api/llm/cache/${encodeURIComponent(name)}`, {\n method: 'GET',\n headers: this.headers(),\n signal: AbortSignal.timeout(this.timeout),\n });\n if (res.status === 404) return null;\n const text = await res.text();\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n if (!res.ok) {\n throw new LLMError(`LLM cache.get failed: ${res.status}`, res.status, body);\n }\n return body as LLMCachedContent;\n },\n\n /**\n * Delete a cache by resource name. Idempotent: 404 is treated as success.\n */\n delete: async (name: string): Promise<void> => {\n const res = await fetch(`${this.studioUrl}/api/llm/cache/${encodeURIComponent(name)}`, {\n method: 'DELETE',\n headers: this.headers(),\n signal: AbortSignal.timeout(this.timeout),\n });\n if (res.ok || res.status === 404) return;\n const text = await res.text().catch(() => '');\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n throw new LLMError(`LLM cache.delete failed: ${res.status}`, res.status, body);\n },\n };\n}\n\n// ============================================\n// Factory\n// ============================================\n\n/**\n * Create an LLMClient that talks to Studio's /api/llm/* proxy routes.\n */\nexport function createLLMClient(config: LLMClientConfig): LLMClient {\n return new LLMClient(config);\n}\n"],"mappings":"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","names":["t"]}
|
|
1
|
+
{"version":3,"sources":["../src/ucp/client.ts","../src/renderMarkers.ts","../src/streamMetadata.ts","../src/platform.ts","../src/auth.ts","../src/llm.ts"],"sourcesContent":["/**\n * UCP (Universal Classification Protocol) Client\n *\n * Client for communicating with UCP-compliant classifier endpoints.\n * Supports both full configuration and simple endpoint URL.\n *\n * @example\n * ```typescript\n * // Simple usage with endpoint URL\n * const client = UCPClient.fromEndpoint('https://api.example.com/ucp/v1/classifiers/abc123');\n *\n * // With API key\n * const client = UCPClient.fromEndpoint(\n * 'https://api.example.com/ucp/v1/classifiers/abc123',\n * { apiKey: 'your-api-key' }\n * );\n *\n * // Classify messages\n * const result = await client.classify(messages);\n * ```\n */\n\nimport type {\n UCPMessage,\n UCPClassifyRequest,\n UCPClassifyResponse,\n UCPErrorResponse,\n UCPInfoResponse,\n UCPClientConfig,\n UCPEndpointConfig,\n ClassificationResult,\n} from './types';\nimport type { ChatMessage } from '../types';\n\n// Default timeout for requests\nconst DEFAULT_TIMEOUT = 30000;\n\n/**\n * UCP Client for classifier communication\n */\nexport class UCPClient {\n private baseUrl: string;\n private classifierId: string;\n private apiKey?: string;\n private timeout: number;\n private headers: Record<string, string>;\n\n constructor(config: UCPClientConfig) {\n // Normalize base URL (remove trailing slash)\n this.baseUrl = config.baseUrl.replace(/\\/$/, '');\n this.classifierId = config.classifierId;\n this.apiKey = config.apiKey;\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n this.headers = config.headers ?? {};\n }\n\n /**\n * Create client from a full endpoint URL\n *\n * URL format: {baseUrl}/classifiers/{classifierId}\n * Example: https://api.example.com/ucp/v1/classifiers/abc123\n */\n static fromEndpoint(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n ): UCPClient {\n // Parse endpoint URL to extract baseUrl and classifierId\n const url = new URL(endpoint);\n const pathParts = url.pathname.split('/');\n\n // Find \"classifiers\" in path and get the ID after it\n const classifiersIndex = pathParts.indexOf('classifiers');\n if (classifiersIndex === -1 || classifiersIndex >= pathParts.length - 1) {\n throw new Error(\n `Invalid UCP endpoint URL: ${endpoint}. Expected format: {baseUrl}/classifiers/{classifierId}`\n );\n }\n\n const classifierId = pathParts[classifiersIndex + 1];\n const baseUrlPath = pathParts.slice(0, classifiersIndex).join('/');\n const baseUrl = `${url.protocol}//${url.host}${baseUrlPath}`;\n\n return new UCPClient({\n baseUrl,\n classifierId,\n apiKey: options?.apiKey,\n timeout: options?.timeout,\n });\n }\n\n /**\n * Get the classify endpoint URL\n */\n get classifyUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/classify`;\n }\n\n /**\n * Get the info endpoint URL\n */\n get infoUrl(): string {\n return `${this.baseUrl}/classifiers/${this.classifierId}/info`;\n }\n\n /**\n * Classify messages using UCP protocol\n */\n async classify(messages: UCPMessage[]): Promise<ClassificationResult> {\n const request: UCPClassifyRequest = { messages };\n\n const response = await this.fetch<UCPClassifyResponse>(\n this.classifyUrl,\n {\n method: 'POST',\n body: JSON.stringify(request),\n }\n );\n\n // Normalize to SDK format\n return {\n classifierId: this.classifierId,\n category: response.category,\n level: response.level,\n confidence: response.score / 100, // Normalize to 0-1\n reasoning: response.rationale,\n raw: response,\n };\n }\n\n /**\n * Classify ChatMessage array (convenience method)\n * Converts ChatMessage to UCPMessage format\n */\n async classifyChat(messages: ChatMessage[]): Promise<ClassificationResult> {\n const ucpMessages: UCPMessage[] = messages\n .filter(m => m.role === 'user' || m.role === 'assistant')\n .map((m, index) => ({\n id: `msg-${index}`,\n role: m.role as 'user' | 'assistant',\n content: m.content,\n timestamp: m.timestamp ? m.timestamp.getTime() : Date.now(),\n metadata: m.metadata,\n }));\n\n return this.classify(ucpMessages);\n }\n\n /**\n * Get classifier info\n */\n async getInfo(): Promise<UCPInfoResponse> {\n return this.fetch<UCPInfoResponse>(this.infoUrl, {\n method: 'GET',\n });\n }\n\n /**\n * Check if the classifier endpoint is reachable\n */\n async healthCheck(): Promise<boolean> {\n try {\n const healthUrl = `${this.baseUrl}/health`;\n const response = await fetch(healthUrl, {\n method: 'GET',\n signal: AbortSignal.timeout(5000),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n /**\n * Internal fetch helper with error handling\n */\n private async fetch<T>(url: string, options: RequestInit): Promise<T> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...this.headers,\n };\n\n if (this.apiKey) {\n headers['Authorization'] = `Bearer ${this.apiKey}`;\n }\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const response = await fetch(url, {\n ...options,\n headers,\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n const ucpError = errorData as UCPErrorResponse;\n\n throw new UCPError(\n ucpError.error?.code || `HTTP_${response.status}`,\n ucpError.error?.message || `HTTP ${response.status}: ${response.statusText}`,\n ucpError.error?.details\n );\n }\n\n return response.json() as Promise<T>;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof UCPError) {\n throw error;\n }\n\n if (error instanceof Error) {\n if (error.name === 'AbortError') {\n throw new UCPError('TIMEOUT', `Request timed out after ${this.timeout}ms`);\n }\n throw new UCPError('NETWORK_ERROR', error.message);\n }\n\n throw new UCPError('UNKNOWN_ERROR', 'An unknown error occurred');\n }\n }\n}\n\n/**\n * UCP-specific error class\n */\nexport class UCPError extends Error {\n constructor(\n public code: string,\n message: string,\n public details?: unknown\n ) {\n super(message);\n this.name = 'UCPError';\n }\n}\n\n// ============================================\n// Convenience Functions\n// ============================================\n\n/**\n * Create a UCP client from an endpoint URL\n */\nexport function createUCPClient(\n endpoint: string,\n options?: { apiKey?: string; timeout?: number }\n): UCPClient {\n return UCPClient.fromEndpoint(endpoint, options);\n}\n\n/**\n * Classify messages using a UCP endpoint\n * One-shot function for simple usage\n */\nexport async function classifyWithUCP(\n endpoint: string,\n messages: ChatMessage[],\n options?: { apiKey?: string; timeout?: number }\n): Promise<ClassificationResult> {\n const client = createUCPClient(endpoint, options);\n return client.classifyChat(messages);\n}\n","/**\n * Render Markers\n *\n * Utilities for building render markers that FirstStep Studio's frontend\n * can parse and display as rich UI components (cards, alerts, forms, etc.).\n *\n * This module is the single source of truth for render marker specs.\n * - Handlers (producers) use the builder functions to emit markers\n * - Studio frontend (consumer) uses the types and MARKER_TYPES constants to parse them\n *\n * @example\n * ```typescript\n * import { renderMarkers } from '@firststep-studio/sdk';\n *\n * // In your handleStream():\n * yield {\n * type: 'text',\n * content: renderMarkers.helplineCard({\n * helplines: [{ name: '988 Lifeline', phoneNumber: '988', categories: ['crisis'], status: 'open', statusLabel: 'Available' }],\n * }),\n * };\n * ```\n */\n\n// ============================================\n// Marker Type Constants\n// ============================================\n\nexport const MARKER_TYPES = {\n HELPLINE_CARD: 'RENDER_HELPLINE_CARD',\n EMERGENCY: 'RENDER_EMERGENCY',\n RESOURCE_CARD: 'RENDER_RESOURCE_CARD',\n PROVIDER_CARD: 'RENDER_PROVIDER_CARD',\n SAFETY_PLAN: 'RENDER_SAFETY_PLAN',\n REPORT_CARD: 'RENDER_REPORT_CARD',\n} as const;\n\nexport type MarkerType = typeof MARKER_TYPES[keyof typeof MARKER_TYPES];\n\n// ============================================\n// Payload Types\n// ============================================\n\n/**\n * Helpline card payload.\n * Renders a carousel of helpline cards with contact buttons (call, text, chat, WhatsApp).\n */\nexport interface HelplineCardPayload {\n helplines: HelplineCardItem[];\n type?: 'throughline' | 'throughline_fallback' | 'stage';\n fallback?: {\n message: string;\n linkText?: string;\n linkUrl?: string;\n icon?: string;\n topic?: string;\n topics?: string[];\n };\n}\n\nexport interface HelplineCardItem {\n name: string;\n description?: string;\n categories: string[];\n status: 'open' | 'closed';\n statusLabel: string;\n statusBadge?: string;\n hoursText?: string;\n supportTypes?: string;\n smsNumber?: string;\n phoneNumber?: string;\n website?: string;\n webchat?: string;\n whatsapp?: string;\n specialties?: string[];\n highlightedTag?: string;\n verified?: boolean;\n}\n\n/**\n * Emergency number payload.\n * Renders a prominent alert card with a large call button.\n */\nexport interface EmergencyPayload {\n number: string;\n countryName?: string;\n countryCode?: string;\n}\n\n/**\n * Resource card payload.\n * Renders a carousel of resource cards with video thumbnails, tags, and visit buttons.\n */\nexport interface ResourceCardPayload {\n resources: ResourceCardItem[];\n}\n\nexport interface ResourceCardItem {\n name: string;\n url?: string;\n description?: string;\n video_url?: string;\n type?: string;\n tags?: string[];\n highlightedTag?: string;\n}\n\n/**\n * Provider card payload.\n * Renders a carousel of provider directory cards with specialty/language tags and contact info.\n */\nexport interface ProviderCardPayload {\n providers: ProviderCardItem[];\n}\n\nexport interface ProviderCardItem {\n id: string;\n name: string;\n type?: string;\n specialty: string[];\n language: string[];\n description?: string;\n location?: string;\n open_hours?: string;\n contact_phone?: string;\n contact_email?: string;\n address?: string;\n}\n\n/**\n * Safety plan payload.\n * Renders a multi-section table with expandable sections and save/export actions.\n */\nexport interface SafetyPlanPayload {\n sections: SafetyPlanSection[];\n helplines?: Array<{ name: string; phone?: string }>;\n actions?: {\n savePng?: boolean;\n saveTxt?: boolean;\n copy?: boolean;\n };\n}\n\nexport interface SafetyPlanSection {\n id: string;\n title: string;\n items: Array<string | { name: string; phone?: string; description?: string }>;\n}\n\n/**\n * Report card payload.\n * Renders a summary card of collected report data with a submit button.\n */\nexport interface ReportCardPayload {\n topic: string;\n location?: string;\n description?: string;\n perpetrator_known?: boolean;\n contact_mode?: string;\n contact_value?: string;\n submitEndpoint?: string;\n}\n\n// ============================================\n// Builder Functions\n// ============================================\n\n/**\n * Wrap a payload in render marker tags.\n * Internal helper used by all builder functions.\n */\nfunction wrapMarker(type: string, payload: unknown): string {\n return `[${type}]${JSON.stringify(payload)}[/${type}]`;\n}\n\n/**\n * Render marker builder functions.\n *\n * Each function takes a typed payload and returns a render marker string\n * that can be yielded as a `text` chunk in handleStream().\n *\n * @example\n * ```typescript\n * // Emit helpline cards\n * yield { type: 'text', content: renderMarkers.helplineCard({ helplines: [...] }) };\n *\n * // Emit emergency number\n * yield { type: 'text', content: renderMarkers.emergency({ number: '911', countryName: 'United States' }) };\n * ```\n */\nexport const renderMarkers = {\n /** Build a helpline card carousel marker */\n helplineCard: (payload: HelplineCardPayload): string =>\n wrapMarker(MARKER_TYPES.HELPLINE_CARD, payload),\n\n /** Build an emergency number alert marker */\n emergency: (payload: EmergencyPayload): string =>\n wrapMarker(MARKER_TYPES.EMERGENCY, payload),\n\n /** Build a resource card carousel marker */\n resourceCard: (payload: ResourceCardPayload): string =>\n wrapMarker(MARKER_TYPES.RESOURCE_CARD, payload),\n\n /** Build a provider card carousel marker */\n providerCard: (payload: ProviderCardPayload): string =>\n wrapMarker(MARKER_TYPES.PROVIDER_CARD, payload),\n\n /** Build a safety plan artifact marker */\n safetyPlan: (payload: SafetyPlanPayload): string =>\n wrapMarker(MARKER_TYPES.SAFETY_PLAN, payload),\n\n /** Build a report draft card marker */\n reportCard: (payload: ReportCardPayload): string =>\n wrapMarker(MARKER_TYPES.REPORT_CARD, payload),\n};\n","/**\n * Stream Metadata Builders\n *\n * Utilities for building typed metadata stream chunks that FirstStep Studio's\n * proxy persists to MongoDB for Dashboard features (Form Insights, Session\n * History, Routing Logs, Agent Transitions).\n *\n * Each function returns a ready-to-yield ProtocolStreamChunk. No need to\n * construct the `{ type, content }` wrapper manually.\n *\n * @example\n * ```typescript\n * import { streamMetadata } from '@firststep-studio/sdk';\n *\n * // In your handleStream():\n *\n * // 1. Welcome path: declare your schema once\n * yield streamMetadata.declareSchema({\n * agents: [{ id: 'intake', title: 'Intake', order: 0 }],\n * questions: [{ id: 'name', agentId: 'intake', title: 'Name', type: 'text' }],\n * });\n *\n * // 2. After each turn: send collected field values\n * yield streamMetadata.formDataUpdate({ name: 'Alice', age: '28' });\n *\n * // 3. On stage change: signal an agent transition\n * yield streamMetadata.agentTransition({ id: 'support', title: 'Support' });\n *\n * // 4. On classification: send routing result\n * yield streamMetadata.routingResult({\n * decision: 'classified',\n * reason: 'User mentioned self-harm',\n * category: 'crisis',\n * level: 'high',\n * score: 92,\n * });\n * ```\n */\n\nimport type {\n ProtocolStreamChunk,\n SchemaDeclarationPayload,\n SchemaAgent,\n SchemaQuestion,\n AgentTransitionPayload,\n RoutingClassificationPayload,\n HandoffRequestPayload,\n HandoffReturnPayload,\n HandoffOfferPayload,\n} from './types';\n\n// ============================================\n// Builder Functions\n// ============================================\n\nexport const streamMetadata = {\n /**\n * Declare the form schema (agents + questions) for Dashboard Form Insights.\n *\n * Yield this once during session initialization (welcome message).\n * The Studio proxy stores it in `chatbot.externalSchema` so that\n * Dashboard Form Insights can display question-level analytics.\n *\n * @param schema - Agents and questions your handler uses\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n declareSchema(schema: SchemaDeclarationPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { schema } };\n },\n\n /**\n * Send collected form field values for Dashboard persistence.\n *\n * Call this after each turn when new fields are captured.\n * Values are incrementally merged into `ChatSession.formData`.\n *\n * @param fields - Key-value pairs of field IDs to their collected values\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n formDataUpdate(fields: Record<string, string | number>): ProtocolStreamChunk {\n return { type: 'metadata', content: { formData: fields } };\n },\n\n /**\n * Signal an agent/stage transition for routing logs.\n *\n * The Studio proxy records this in `ChatSession.routingLogs` so\n * the Dashboard can show the conversation's agent flow.\n *\n * @param agent - The agent/stage being transitioned to\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n agentTransition(agent: AgentTransitionPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { currentAgent: agent } };\n },\n\n /**\n * Send a routing/classification result for routing logs.\n *\n * The Studio proxy records this in `ChatSession.routingLogs` as a\n * classification event with category, level, and confidence score.\n *\n * @param result - Classification/routing decision details\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n routingResult(result: RoutingClassificationPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { routing: result } };\n },\n\n // ============================================\n // Handoff Protocol Builders\n // ============================================\n\n /**\n * Request a handoff to another chatbot.\n *\n * The Studio proxy pushes a handoff stack entry and routes subsequent\n * user messages to the target handler. If `options.consent` is set,\n * Studio stores the request as a pending offer instead.\n *\n * @param payload - Handoff target, reason, context, and options\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n handoffRequest(payload: HandoffRequestPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { handoffRequest: payload } };\n },\n\n /**\n * Return from a handoff back to the parent handler.\n *\n * The Studio proxy pops the handoff stack and delivers the return\n * result to the parent handler on the next user message.\n *\n * @param payload - Summary and artifacts from the completed handoff\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n handoffReturn(payload: HandoffReturnPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { handoffReturn: payload } };\n },\n\n /**\n * Offer a handoff with user consent required.\n *\n * Studio stores this as a pending offer, sends an SSE event to the\n * frontend, and waits for the user to accept or decline before\n * executing the handoff.\n *\n * @param payload - Offer display info and the pending handoff request\n * @returns A metadata stream chunk ready to yield or queue.push\n */\n handoffOffer(payload: HandoffOfferPayload): ProtocolStreamChunk {\n return { type: 'metadata', content: { handoffOffer: payload } };\n },\n};\n\n// ============================================\n// Re-export payload types for convenience\n// ============================================\n\nexport type {\n SchemaDeclarationPayload,\n SchemaAgent,\n SchemaQuestion,\n AgentTransitionPayload,\n RoutingClassificationPayload,\n HandoffRequestPayload,\n HandoffReturnPayload,\n HandoffOfferPayload,\n};\n","/**\n * Platform Client\n *\n * SDK client for handlers to call back to FirstStep Studio.\n * Enables handlers to query platform data (chatbot list, etc.)\n * using the same API token used for request signature verification.\n *\n * @example\n * ```typescript\n * import { createPlatformClient } from '@firststep-studio/sdk';\n *\n * const client = createPlatformClient({\n * studioUrl: 'https://studio-api.example.com',\n * token: process.env.FIRSTSTEP_TOKEN,\n * });\n *\n * const chatbots = await client.listChatbots();\n * // [{ id: '...', name: 'Safety Navigator', configSlug: 'safety-nav' }, ...]\n * ```\n */\n\n// ============================================\n// Types\n// ============================================\n\nexport interface PlatformClientConfig {\n /** Studio backend API URL (e.g. https://studio-api.example.com) */\n studioUrl: string;\n /** API token (same as FIRSTSTEP_TOKEN used for signature verification) */\n token: string;\n /** Request timeout in ms (default: 10000) */\n timeout?: number;\n}\n\nexport interface PlatformOrganization {\n /** Studio organization ID */\n id: string;\n /** Display name (null if the organization record has been deleted) */\n name: string | null;\n /** URL-safe slug */\n slug: string | null;\n}\n\nexport interface PlatformTokenInfo {\n /** API token ID */\n id: string;\n /** Human-friendly token label */\n name: string;\n /** Authorized scopes */\n scopes: string[];\n /** Project IDs this token is scoped to (null = full org access) */\n projectIds: string[] | null;\n}\n\nexport interface PlatformIdentity {\n organization: PlatformOrganization;\n token: PlatformTokenInfo;\n}\n\nexport interface PlatformChatbotRegisterInput {\n /** Stable handler-side identifier used to upsert (e.g. handler config ID). */\n externalId: string;\n /** Display name shown in Studio's chatbot list. */\n name: string;\n /** Optional human description. */\n description?: string;\n /** Public URL where Studio should send protocol requests. */\n handlerUrl: string;\n /** Slug for multi-config routing on the handler side. */\n configSlug?: string;\n /** Optional capability flags advertised to Studio. */\n capabilities?: {\n streaming?: boolean;\n formQuestions?: boolean;\n knowledgeActions?: boolean;\n integrations?: boolean;\n [key: string]: unknown;\n };\n}\n\nexport interface PlatformChatbotRegistration {\n /** Studio chatbot MongoDB ID (immutable across re-registrations). */\n id: string;\n /** Echoes back the externalId used as upsert key. */\n externalId: string;\n /** Resolved name as stored in Studio. */\n name: string;\n protocolConfig: Record<string, unknown>;\n}\n\nexport interface PlatformChatbot {\n /** Studio chatbot MongoDB ID */\n id: string;\n /** Chatbot display name */\n name: string;\n /** Handler URL (if external handler configured) */\n handlerUrl?: string;\n /** Config slug for multi-config routing */\n configSlug?: string;\n /** Handler type */\n handlerType?: 'builtin' | 'external';\n}\n\nexport interface PlatformKnowledgeSummary {\n /** Studio knowledge MongoDB ID */\n id: string;\n /** Knowledge name */\n name: string;\n /** Optional description */\n description?: string;\n /** Knowledge type (e.g. 'text', 'database') */\n type?: string;\n /** Owning organization ID */\n organizationId: string;\n /**\n * True for handler-managed documents created through the protocol\n * write API. System-owned knowledge is hidden from every Studio UI\n * surface and only appears in `list` when `includeSystemOwned` is set.\n */\n isSystemOwned?: boolean;\n /** Whether semantic search (record embedding) is enabled. */\n ragEnabled?: boolean;\n /** Timestamps */\n createdAt?: number | string;\n updatedAt?: number | string;\n}\n\n/**\n * Full knowledge document (shape mirrors the underlying mongoose doc).\n * Kept loose because the schema is large and varies by knowledge `type`;\n * narrow types can be added incrementally as callers need them.\n */\nexport type PlatformKnowledge = PlatformKnowledgeSummary & Record<string, unknown>;\n\nexport interface ListKnowledgeOptions {\n /**\n * Target organization to list knowledge from. If omitted, defaults to\n * the org associated with the SDK token. Use this to read from a\n * different org (e.g. a platform-operated shared library marked\n * `publicReadable` in Studio).\n */\n orgId?: string;\n /**\n * Include system-owned (handler-managed) knowledge in the listing.\n * Only honored when listing the token's own org; other orgs never\n * expose their system-owned documents.\n */\n includeSystemOwned?: boolean;\n}\n\n/** Sheet definition for `knowledge.create`. */\nexport interface CreateKnowledgeSheet {\n /** Stable sheet id, referenced later by record writes and search. */\n id: string;\n name: string;\n description?: string;\n /**\n * Column definitions ({ id, name, type, ... }). Loose-typed; the\n * Studio schema validates `type` against its column-type enum.\n */\n columns?: Array<Record<string, unknown>>;\n}\n\nexport interface CreateKnowledgeOptions {\n description?: string;\n /**\n * Enable semantic search at creation time. Defaults to false; can be\n * flipped later with `setRagEnabled` (which also backfills embeddings\n * for records inserted while the flag was off).\n */\n ragEnabled?: boolean;\n}\n\n/** A raw record as returned by the protocol record-write endpoints. */\nexport interface PlatformKnowledgeRecord {\n id: string;\n knowledgeId: string;\n sheetId: string;\n data: Record<string, unknown>;\n createdAt?: number | string;\n updatedAt?: number | string;\n}\n\nexport interface KnowledgeSearchOptions {\n /** Max records to return (server-side clamped to 1..50, default 10). */\n limit?: number;\n /**\n * Optional sheet filter. Pass a sheet id (from the knowledge document)\n * to restrict the search to that sheet. Omit to search across all\n * sheets in the knowledge.\n */\n sheetId?: string;\n}\n\n/**\n * A single hit from `client.knowledge.search`. `score` is the Atlas\n * vector similarity score (cosine in our setup, so higher is closer).\n * `data` mirrors the underlying record fields (column id -> value).\n */\nexport interface PlatformKnowledgeSearchHit {\n id: string;\n knowledgeId: string;\n sheetId: string;\n data: Record<string, unknown>;\n score: number;\n createdAt?: number | string;\n updatedAt?: number | string;\n}\n\n/**\n * Helplines query params — passed verbatim to Throughline. Loose-typed\n * so the SDK stays compatible with new Throughline query keys without\n * a release. Required by Throughline today: `country_code`. Common\n * optional keys: `topic`, `contact_method`, `limit`.\n */\nexport type ThroughlineHelplinesQuery = Record<string, string | number | boolean | undefined>;\n\nexport class PlatformError extends Error {\n status: number;\n constructor(message: string, status: number) {\n super(message);\n this.name = 'PlatformError';\n this.status = status;\n }\n}\n\n// ============================================\n// Client\n// ============================================\n\nexport class PlatformClient {\n private studioUrl: string;\n private token: string;\n private timeout: number;\n\n constructor(config: PlatformClientConfig) {\n this.studioUrl = config.studioUrl.replace(/\\/+$/, '');\n this.token = config.token;\n this.timeout = config.timeout || 10000;\n }\n\n private async request<T>(\n path: string,\n errorPrefix: string,\n init: { method?: string; body?: unknown } = {}\n ): Promise<T> {\n const res = await fetch(`${this.studioUrl}${path}`, {\n method: init.method || 'GET',\n headers: {\n 'Authorization': `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: AbortSignal.timeout(this.timeout),\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n throw new PlatformError(`${errorPrefix}: ${res.status} ${body}`, res.status);\n }\n if (res.status === 204) return undefined as T;\n return res.json() as Promise<T>;\n }\n\n /**\n * Resolve the organization and token metadata for the configured token.\n * Useful for connection state UI (\"Connected to {orgName}\") and audit.\n */\n async getMe(): Promise<PlatformIdentity> {\n return this.request<PlatformIdentity>('/api/protocol/me', 'Failed to fetch identity');\n }\n\n /**\n * List chatbots accessible to this handler's organization.\n * Studio resolves the organization from the API token.\n */\n async listChatbots(): Promise<PlatformChatbot[]> {\n const data = await this.request<{ chatbots: PlatformChatbot[] }>(\n '/api/protocol/chatbots',\n 'Failed to list chatbots'\n );\n return data.chatbots;\n }\n\n /**\n * Register or update an external-handler chatbot under the token's\n * organization. Idempotent: identified by `externalId`, so handlers\n * can call this on every config save without creating duplicates.\n */\n async registerChatbot(input: PlatformChatbotRegisterInput): Promise<PlatformChatbotRegistration> {\n return this.request<PlatformChatbotRegistration>(\n '/api/protocol/chatbots',\n 'Failed to register chatbot',\n { method: 'POST', body: input }\n );\n }\n\n /**\n * Mark the chatbot identified by `externalId` as disconnected.\n * The Studio record is preserved (history, analytics survive).\n */\n async unregisterChatbot(externalId: string): Promise<{ ok: boolean; modified: number }> {\n return this.request<{ ok: boolean; modified: number }>(\n `/api/protocol/chatbots/${encodeURIComponent(externalId)}`,\n 'Failed to unregister chatbot',\n { method: 'DELETE' }\n );\n }\n\n /**\n * Knowledge / resource surface.\n *\n * Reads are allowed when the SDK token's org matches the target org,\n * OR when the target org has `publicReadable: true` on the Studio side.\n *\n * Writes target system-owned (handler-managed) knowledge only: the\n * protocol API stamps everything created here `isSystemOwned: true`,\n * which keeps it out of every Studio UI surface — Studio acts as a\n * plain storage + RAG layer for it. UI-managed knowledge cannot be\n * modified through this surface.\n */\n knowledge = {\n /**\n * List knowledge items for an organization.\n * Defaults to the SDK token's own org if `orgId` is omitted.\n */\n list: async (options: ListKnowledgeOptions = {}): Promise<PlatformKnowledgeSummary[]> => {\n const params = new URLSearchParams();\n if (options.orgId) params.set('organizationId', options.orgId);\n if (options.includeSystemOwned) params.set('includeSystemOwned', '1');\n const qsStr = params.toString();\n const data = await this.request<{ knowledge: PlatformKnowledgeSummary[] }>(\n `/api/protocol/knowledge${qsStr ? `?${qsStr}` : ''}`,\n 'Failed to list knowledge'\n );\n return data.knowledge;\n },\n\n /**\n * Fetch a single knowledge item by its Studio ID. The caller must\n * have read access to the knowledge's owning org (same-org or\n * publicReadable).\n */\n get: async (id: string): Promise<PlatformKnowledge> => {\n const data = await this.request<{ knowledge: PlatformKnowledge }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}`,\n 'Failed to fetch knowledge'\n );\n return data.knowledge;\n },\n\n /**\n * Semantic search over a knowledge's records. Studio embeds the\n * query server-side (Gemini gemini-embedding-001) and runs Atlas\n * Vector Search against the `vector_index` on `knowledge_records`.\n *\n * Same read access rules as `get` — same-org or publicReadable.\n *\n * Returns an empty array (not an error) when the Atlas index is\n * absent or when nothing matches, so handler code can treat\n * \"no hits\" and \"RAG not configured yet\" identically.\n */\n search: async (\n id: string,\n query: string,\n options: KnowledgeSearchOptions = {}\n ): Promise<PlatformKnowledgeSearchHit[]> => {\n const data = await this.request<{ results: PlatformKnowledgeSearchHit[] }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/search`,\n 'Failed to search knowledge',\n {\n method: 'POST',\n body: {\n query,\n ...(options.limit !== undefined ? { limit: options.limit } : {}),\n ...(options.sheetId !== undefined ? { sheetId: options.sheetId } : {}),\n },\n }\n );\n return data.results;\n },\n\n /**\n * Turn semantic search on or off for a knowledge.\n *\n * Writes are strictly same-org — `publicReadable` does NOT confer\n * write access. Flipping `false -> true` schedules a background\n * backfill of existing records on the Studio side; this call\n * returns as soon as the flag is persisted, not when the backfill\n * finishes.\n *\n * Idempotent: setting the flag to its current value is a no-op\n * (`backfillTriggered` will be `false`).\n */\n setRagEnabled: async (\n id: string,\n enabled: boolean\n ): Promise<{ id: string; ragEnabled: boolean; backfillTriggered: boolean }> => {\n const data = await this.request<{\n knowledge: { id: string; ragEnabled: boolean; backfillTriggered: boolean };\n }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/rag`,\n 'Failed to toggle knowledge RAG',\n { method: 'POST', body: { enabled } }\n );\n return data.knowledge;\n },\n\n /**\n * Create a system-owned database knowledge under the token's org.\n *\n * 409 (surfaced as an error) means a knowledge of the same name\n * already exists in the org. Idempotent callers should recover by\n * listing with `includeSystemOwned: true` and matching on name.\n */\n create: async (\n name: string,\n sheets: CreateKnowledgeSheet[],\n options: CreateKnowledgeOptions = {}\n ): Promise<PlatformKnowledge> => {\n const data = await this.request<{ knowledge: PlatformKnowledge }>(\n '/api/protocol/knowledge',\n 'Failed to create knowledge',\n {\n method: 'POST',\n body: {\n name,\n sheets,\n ...(options.description !== undefined ? { description: options.description } : {}),\n ...(options.ragEnabled !== undefined ? { ragEnabled: options.ragEnabled } : {}),\n },\n }\n );\n return data.knowledge;\n },\n\n /**\n * Delete a system-owned knowledge and all of its records.\n * Refused (403) for UI-managed knowledge.\n */\n delete: async (id: string): Promise<{ deletedRecords: number }> => {\n const data = await this.request<{ ok: boolean; deletedRecords: number }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}`,\n 'Failed to delete knowledge',\n { method: 'DELETE' }\n );\n return { deletedRecords: data.deletedRecords };\n },\n\n /**\n * Append one record to a sheet. Embeds async when ragEnabled.\n */\n addRecord: async (\n id: string,\n sheetId: string,\n data: Record<string, unknown>\n ): Promise<PlatformKnowledgeRecord> => {\n const res = await this.request<{ record: PlatformKnowledgeRecord }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/records`,\n 'Failed to create record',\n { method: 'POST', body: { sheetId, data } }\n );\n return res.record;\n },\n\n /**\n * Replace a record's data payload. Re-embeds async when ragEnabled.\n */\n updateRecord: async (\n id: string,\n recordId: string,\n data: Record<string, unknown>\n ): Promise<PlatformKnowledgeRecord> => {\n const res = await this.request<{ record: PlatformKnowledgeRecord }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/records/${encodeURIComponent(recordId)}`,\n 'Failed to update record',\n { method: 'PUT', body: { data } }\n );\n return res.record;\n },\n\n /** Delete one record. */\n deleteRecord: async (id: string, recordId: string): Promise<void> => {\n await this.request<{ ok: boolean }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/records/${encodeURIComponent(recordId)}`,\n 'Failed to delete record',\n { method: 'DELETE' }\n );\n },\n\n /**\n * Bulk-insert records into one sheet (each array entry becomes one\n * record's `data`). With `replace: true` the sheet's existing\n * records are deleted first — the full-resync primitive backfills\n * use. Server caps a single call at 5000 records.\n */\n bulkRecords: async (\n id: string,\n sheetId: string,\n records: Array<Record<string, unknown>>,\n options: { replace?: boolean } = {}\n ): Promise<{ insertedCount: number; deletedCount: number }> => {\n const data = await this.request<{\n ok: boolean;\n insertedCount: number;\n deletedCount: number;\n }>(\n `/api/protocol/knowledge/${encodeURIComponent(id)}/records/bulk`,\n 'Failed to bulk-write records',\n {\n method: 'POST',\n body: {\n sheetId,\n records,\n ...(options.replace !== undefined ? { replace: options.replace } : {}),\n },\n }\n );\n return { insertedCount: data.insertedCount, deletedCount: data.deletedCount };\n },\n };\n\n /**\n * Throughline (find-a-helpline) surface.\n *\n * Handlers used to call api.throughlinecare.com directly, which meant\n * each handler had to hold OAuth client credentials and re-implement\n * token caching. Studio holds the per-org credentials on\n * `Organization.throughline.{clientId,clientSecret}` and proxies the\n * Throughline endpoints; the SDK token identifies the org server-side\n * so callers never see the secret.\n *\n * Responses are returned as parsed JSON. Studio does not reshape the\n * Throughline body — these surfaces stay compatible with future\n * Throughline API additions without a Studio or SDK release.\n *\n * Caching (24h on countries + topics, none on helplines) is server-\n * side; the SDK is stateless.\n */\n throughline = {\n /**\n * GET /v1/countries — Throughline's country catalog (codes,\n * names, emergency numbers). Cached 24h server-side.\n */\n getCountries: async (): Promise<unknown> => {\n return this.request<unknown>(\n '/api/protocol/throughline/countries',\n 'Failed to fetch Throughline countries'\n );\n },\n\n /**\n * GET /v1/topics — Throughline's topic taxonomy used to filter\n * helpline searches. Cached 24h server-side.\n */\n getTopics: async (): Promise<unknown> => {\n return this.request<unknown>(\n '/api/protocol/throughline/topics',\n 'Failed to fetch Throughline topics'\n );\n },\n\n /**\n * GET /v1/helplines?country_code=...&topic=...&... — live search,\n * not cached. The query object is forwarded verbatim to Throughline,\n * so any new key Throughline adds will work without an SDK change.\n * Required by Throughline today: `country_code`.\n */\n getHelplines: async (query: ThroughlineHelplinesQuery): Promise<unknown> => {\n const qs = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined || v === null) continue;\n qs.append(k, String(v));\n }\n const suffix = qs.toString();\n return this.request<unknown>(\n `/api/protocol/throughline/helplines${suffix ? `?${suffix}` : ''}`,\n 'Failed to fetch Throughline helplines'\n );\n },\n };\n}\n\n// ============================================\n// Factory\n// ============================================\n\n/**\n * Create a PlatformClient for calling back to FirstStep Studio.\n */\nexport function createPlatformClient(config: PlatformClientConfig): PlatformClient {\n return new PlatformClient(config);\n}\n","import { createHash, createHmac, timingSafeEqual } from 'crypto';\n\nconst TOKEN_PREFIX = 'fst_';\nconst TOKEN_LENGTH = 44; // fst_ (4) + 40 hex chars\n\n/**\n * Hash a token to derive the shared HMAC key.\n * The backend only stores SHA-256(token) and uses that hash as the HMAC key.\n * The SDK must hash the plaintext token the same way to verify signatures.\n */\nfunction deriveSigningKey(token: string): string {\n return createHash('sha256').update(token).digest('hex');\n}\n\n/**\n * Verify an HMAC-SHA256 request signature.\n *\n * The handler server can use this to verify that incoming webhook\n * requests were signed by the FirstStep platform using the shared token.\n *\n * The HMAC key is SHA-256(token), matching the backend which only stores\n * the token hash and uses it directly as the HMAC key.\n *\n * @param token - The API token (FIRSTSTEP_TOKEN)\n * @param payload - The raw request body string\n * @param signature - The signature from the X-FirstStep-Signature header\n * @returns true if the signature is valid\n *\n * @example\n * ```typescript\n * import { verifyRequestSignature } from '@firststep-studio/sdk';\n *\n * app.post('/webhook', (req, res) => {\n * const signature = req.headers['x-firststep-signature'] as string;\n * if (!verifyRequestSignature(process.env.FIRSTSTEP_TOKEN!, req.body, signature)) {\n * return res.status(401).send('Invalid signature');\n * }\n * // Process the request...\n * });\n * ```\n */\nexport function verifyRequestSignature(\n token: string,\n payload: string,\n signature: string\n): boolean {\n try {\n const signingKey = deriveSigningKey(token);\n const expected = createHmac('sha256', signingKey)\n .update(payload)\n .digest('hex');\n const expectedBuffer = Buffer.from(expected, 'hex');\n const signatureBuffer = Buffer.from(signature, 'hex');\n\n if (expectedBuffer.length !== signatureBuffer.length) {\n return false;\n }\n\n return timingSafeEqual(expectedBuffer, signatureBuffer);\n } catch {\n return false;\n }\n}\n\n/**\n * Create an HMAC-SHA256 signature for a payload.\n *\n * Used by the platform to sign outgoing requests to handler servers.\n *\n * @param token - The API token\n * @param payload - The request body string to sign\n * @returns The hex-encoded HMAC signature\n */\nexport function createRequestSignature(\n token: string,\n payload: string\n): string {\n const signingKey = deriveSigningKey(token);\n return createHmac('sha256', signingKey).update(payload).digest('hex');\n}\n\n/**\n * Create an Authorization header value for API requests.\n *\n * @param token - The API token (fst_xxx)\n * @returns The header value, e.g. \"Bearer fst_xxx\"\n *\n * @example\n * ```typescript\n * import { createAuthHeader } from '@firststep-studio/sdk';\n *\n * const response = await fetch('https://api.firststep.ai/api/projects', {\n * headers: {\n * 'Authorization': createAuthHeader(process.env.FIRSTSTEP_TOKEN!),\n * },\n * });\n * ```\n */\nexport function createAuthHeader(token: string): string {\n return `Bearer ${token}`;\n}\n\n/**\n * Validate that a token has the correct format (fst_ prefix + 40 hex chars).\n *\n * @param token - The token string to validate\n * @returns true if the token matches the expected format\n */\nexport function isValidToken(token: string): boolean {\n return (\n typeof token === 'string' &&\n token.startsWith(TOKEN_PREFIX) &&\n token.length === TOKEN_LENGTH\n );\n}\n","/**\n * LLM Client\n *\n * SDK client for calling Studio's `/api/llm/*` proxy routes. Studio forwards\n * the request body 1:1 to Google's Generative Language REST API, so the\n * shapes here mirror Google's REST shape (NOT the @google/genai SDK shape).\n *\n * Reference: https://ai.google.dev/api/generate-content\n *\n * @example\n * ```typescript\n * import { createLLMClient } from '@firststep-studio/sdk';\n *\n * const llm = createLLMClient({\n * studioUrl: 'https://studio-api.example.com',\n * token: process.env.FIRSTSTEP_TOKEN!,\n * });\n *\n * // Single-shot\n * const res = await llm.generate({\n * model: 'gemini-3-flash-preview',\n * contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],\n * generationConfig: { temperature: 0.7, maxOutputTokens: 500 },\n * });\n * console.log(res.candidates[0].content.parts[0].text);\n *\n * // Streaming\n * for await (const chunk of llm.generateStream({\n * model: 'gemini-3-flash-preview',\n * contents: [{ role: 'user', parts: [{ text: 'Count to 5' }] }],\n * })) {\n * process.stdout.write(chunk.candidates?.[0]?.content?.parts?.[0]?.text ?? '');\n * }\n *\n * // Cache\n * const cache = await llm.cache.create({\n * model: 'models/gemini-2.5-flash',\n * systemInstruction: { parts: [{ text: 'long system prompt...' }] },\n * ttl: '3600s',\n * });\n * await llm.cache.get(cache.name);\n * await llm.cache.delete(cache.name);\n * ```\n */\n\n// ============================================\n// Types — Google REST API shape (kept loose for forward-compat)\n// ============================================\n\nexport interface LLMClientConfig {\n /** Studio backend API URL (e.g. https://studio-api.example.com) */\n studioUrl: string;\n /** API token (Bearer fst_<...>) */\n token: string;\n /** Request timeout in ms for non-streaming calls (default: 60000) */\n timeout?: number;\n}\n\nexport interface LLMPart {\n text?: string;\n inlineData?: { mimeType: string; data: string };\n [k: string]: unknown;\n}\n\nexport interface LLMContent {\n role?: 'user' | 'model';\n parts: LLMPart[];\n}\n\nexport interface LLMGenerationConfig {\n temperature?: number;\n maxOutputTokens?: number;\n topP?: number;\n topK?: number;\n responseMimeType?: string;\n responseSchema?: Record<string, unknown>;\n thinkingConfig?: {\n thinkingBudget?: number;\n thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high';\n includeThoughts?: boolean;\n };\n stopSequences?: string[];\n [k: string]: unknown;\n}\n\nexport interface LLMSafetySetting {\n category: string;\n threshold: string;\n}\n\n/**\n * Request body for /api/llm/generate and /api/llm/generate/stream.\n * Mirrors Google's `:generateContent` REST request, plus `model` at the top level\n * so Studio can build the URL path.\n */\nexport interface LLMGenerateRequest {\n model: string;\n contents: LLMContent[];\n systemInstruction?: { parts: LLMPart[] };\n generationConfig?: LLMGenerationConfig;\n safetySettings?: LLMSafetySetting[];\n /** Resource name of an explicit cache, e.g. \"cachedContents/abc123\" */\n cachedContent?: string;\n /** Function-calling tools (forwarded as-is) */\n tools?: unknown[];\n [k: string]: unknown;\n}\n\nexport interface LLMUsageMetadata {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n cachedContentTokenCount?: number;\n thoughtsTokenCount?: number;\n totalTokenCount?: number;\n [k: string]: unknown;\n}\n\nexport interface LLMCandidate {\n content?: LLMContent;\n finishReason?: string;\n index?: number;\n safetyRatings?: unknown[];\n [k: string]: unknown;\n}\n\nexport interface LLMGenerateResponse {\n candidates?: LLMCandidate[];\n usageMetadata?: LLMUsageMetadata;\n promptFeedback?: { blockReason?: string; [k: string]: unknown };\n modelVersion?: string;\n responseId?: string;\n [k: string]: unknown;\n}\n\nexport interface LLMCacheCreateRequest {\n model: string;\n displayName?: string;\n systemInstruction?: { parts: LLMPart[] };\n contents?: LLMContent[];\n /** Time-to-live, e.g. \"3600s\" */\n ttl?: string;\n [k: string]: unknown;\n}\n\nexport interface LLMCachedContent {\n name: string;\n model: string;\n createTime?: string;\n updateTime?: string;\n expireTime?: string;\n displayName?: string;\n usageMetadata?: { totalTokenCount?: number };\n [k: string]: unknown;\n}\n\nexport class LLMError extends Error {\n status: number;\n body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = 'LLMError';\n this.status = status;\n this.body = body;\n }\n}\n\n// ============================================\n// Client\n// ============================================\n\nexport class LLMClient {\n private studioUrl: string;\n private token: string;\n private timeout: number;\n\n constructor(config: LLMClientConfig) {\n this.studioUrl = config.studioUrl.replace(/\\/+$/, '');\n this.token = config.token;\n this.timeout = config.timeout ?? 60000;\n }\n\n private headers(): Record<string, string> {\n return {\n 'Authorization': `Bearer ${this.token}`,\n 'Content-Type': 'application/json',\n };\n }\n\n /**\n * Single-shot generation. Body is forwarded to Google's :generateContent\n * unchanged. Response is Google's response shape unchanged.\n */\n async generate(req: LLMGenerateRequest): Promise<LLMGenerateResponse> {\n const res = await fetch(`${this.studioUrl}/api/llm/generate`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(req),\n signal: AbortSignal.timeout(this.timeout),\n });\n\n const text = await res.text();\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n\n if (!res.ok) {\n throw new LLMError(`LLM generate failed: ${res.status}`, res.status, body);\n }\n return body as LLMGenerateResponse;\n }\n\n /**\n * Streaming generation as an AsyncGenerator of partial responses.\n * Each yielded chunk has the same shape as a non-streaming response (a\n * `candidates[]` slice with the next text fragment, plus usageMetadata\n * on the final chunk).\n *\n * Note: timeout option is not enforced for streams; consumer should\n * abort externally if needed.\n */\n async *generateStream(req: LLMGenerateRequest): AsyncGenerator<LLMGenerateResponse> {\n const res = await fetch(`${this.studioUrl}/api/llm/generate/stream`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(req),\n });\n\n if (!res.ok || !res.body) {\n const text = await res.text().catch(() => '');\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n throw new LLMError(`LLM generateStream failed: ${res.status}`, res.status, body);\n }\n\n // Consume SSE: lines like `data: {json}\\n\\n`. We pop full lines off a\n // rolling buffer and yield each parsed `data:` payload.\n const reader = (res.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n for (const line of lines) {\n const t = line.trim();\n if (!t.startsWith('data: ')) continue;\n const payload = t.slice(6);\n if (payload === '[DONE]') continue;\n try {\n yield JSON.parse(payload) as LLMGenerateResponse;\n } catch {\n // Skip unparsable chunk; SSE may emit stray comments / heartbeats.\n }\n }\n }\n\n // Flush any final buffered line that didn't end in \\n.\n const t = buffer.trim();\n if (t.startsWith('data: ')) {\n const payload = t.slice(6);\n if (payload && payload !== '[DONE]') {\n try { yield JSON.parse(payload) as LLMGenerateResponse; } catch { /* noop */ }\n }\n }\n } finally {\n try { reader.releaseLock(); } catch { /* noop */ }\n }\n }\n\n /**\n * Explicit prompt cache surface. Maps to Google's `/cachedContents` REST API.\n */\n cache = {\n /**\n * Create an explicit cache. Returns the resource (use `cache.name` as the\n * `cachedContent` field on a future generate() request).\n */\n create: async (req: LLMCacheCreateRequest): Promise<LLMCachedContent> => {\n const res = await fetch(`${this.studioUrl}/api/llm/cache`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify(req),\n signal: AbortSignal.timeout(this.timeout),\n });\n const text = await res.text();\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n if (!res.ok) {\n throw new LLMError(`LLM cache.create failed: ${res.status}`, res.status, body);\n }\n return body as LLMCachedContent;\n },\n\n /**\n * Fetch a cache by resource name (e.g. \"cachedContents/abc123\").\n * Returns null when the cache is not found (404), which is the common\n * \"expired\" signal — callers can simply recreate.\n */\n get: async (name: string): Promise<LLMCachedContent | null> => {\n const res = await fetch(`${this.studioUrl}/api/llm/cache/${encodeURIComponent(name)}`, {\n method: 'GET',\n headers: this.headers(),\n signal: AbortSignal.timeout(this.timeout),\n });\n if (res.status === 404) return null;\n const text = await res.text();\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n if (!res.ok) {\n throw new LLMError(`LLM cache.get failed: ${res.status}`, res.status, body);\n }\n return body as LLMCachedContent;\n },\n\n /**\n * Delete a cache by resource name. Idempotent: 404 is treated as success.\n */\n delete: async (name: string): Promise<void> => {\n const res = await fetch(`${this.studioUrl}/api/llm/cache/${encodeURIComponent(name)}`, {\n method: 'DELETE',\n headers: this.headers(),\n signal: AbortSignal.timeout(this.timeout),\n });\n if (res.ok || res.status === 404) return;\n const text = await res.text().catch(() => '');\n let body: unknown;\n try { body = text ? JSON.parse(text) : undefined; } catch { body = { raw: text }; }\n throw new LLMError(`LLM cache.delete failed: ${res.status}`, res.status, body);\n },\n };\n}\n\n// ============================================\n// Factory\n// ============================================\n\n/**\n * Create an LLMClient that talks to Studio's /api/llm/* proxy routes.\n */\nexport function createLLMClient(config: LLMClientConfig): LLMClient {\n return new LLMClient(config);\n}\n"],"mappings":"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","names":["t"]}
|
package/dist/server.d.mts
CHANGED
package/dist/server.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ interface ProtocolRequest {
|
|
|
17
17
|
deploymentSlug: string;
|
|
18
18
|
/** Additional metadata */
|
|
19
19
|
metadata?: Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* User-selected language code (e.g. "en", "ko", "es").
|
|
22
|
+
* When present, handlers should localize user-facing strings (such as
|
|
23
|
+
* a question's formMessage) using the chatbot's i18n translations.
|
|
24
|
+
* Optional - defaults to source language when omitted.
|
|
25
|
+
*/
|
|
26
|
+
language?: string;
|
|
20
27
|
/**
|
|
21
28
|
* Handler-side config slug extracted from the URL path.
|
|
22
29
|
* When the handler URL includes a slug (e.g. /ucp/v1/safety-navigator/message),
|
|
@@ -265,6 +272,16 @@ interface SessionMetadata {
|
|
|
265
272
|
source?: string;
|
|
266
273
|
language?: string;
|
|
267
274
|
timezone?: string;
|
|
275
|
+
/**
|
|
276
|
+
* Studio-side bridge: the MongoDB ChatSession.uuid the deployed frontend
|
|
277
|
+
* created via `POST /api/chat-sessions`. The protocol sessionId
|
|
278
|
+
* (`sess_*`) the backend generates is Redis-only and not the same as the
|
|
279
|
+
* persisted ChatSession document's uuid. Studio's chat route stamps this
|
|
280
|
+
* onto the session state on first turn so the built-in guidedForm form-
|
|
281
|
+
* data mirror can resolve the right ChatSession to write into. Not used
|
|
282
|
+
* by external handlers; safe to ignore from non-Studio handlers.
|
|
283
|
+
*/
|
|
284
|
+
chatSessionUuid?: string;
|
|
268
285
|
knowledgeActionExecuted?: boolean;
|
|
269
286
|
helplineActionExecuted?: boolean;
|
|
270
287
|
executedKnowledgeIds?: string[];
|
|
@@ -17,6 +17,13 @@ interface ProtocolRequest {
|
|
|
17
17
|
deploymentSlug: string;
|
|
18
18
|
/** Additional metadata */
|
|
19
19
|
metadata?: Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* User-selected language code (e.g. "en", "ko", "es").
|
|
22
|
+
* When present, handlers should localize user-facing strings (such as
|
|
23
|
+
* a question's formMessage) using the chatbot's i18n translations.
|
|
24
|
+
* Optional - defaults to source language when omitted.
|
|
25
|
+
*/
|
|
26
|
+
language?: string;
|
|
20
27
|
/**
|
|
21
28
|
* Handler-side config slug extracted from the URL path.
|
|
22
29
|
* When the handler URL includes a slug (e.g. /ucp/v1/safety-navigator/message),
|
|
@@ -265,6 +272,16 @@ interface SessionMetadata {
|
|
|
265
272
|
source?: string;
|
|
266
273
|
language?: string;
|
|
267
274
|
timezone?: string;
|
|
275
|
+
/**
|
|
276
|
+
* Studio-side bridge: the MongoDB ChatSession.uuid the deployed frontend
|
|
277
|
+
* created via `POST /api/chat-sessions`. The protocol sessionId
|
|
278
|
+
* (`sess_*`) the backend generates is Redis-only and not the same as the
|
|
279
|
+
* persisted ChatSession document's uuid. Studio's chat route stamps this
|
|
280
|
+
* onto the session state on first turn so the built-in guidedForm form-
|
|
281
|
+
* data mirror can resolve the right ChatSession to write into. Not used
|
|
282
|
+
* by external handlers; safe to ignore from non-Studio handlers.
|
|
283
|
+
*/
|
|
284
|
+
chatSessionUuid?: string;
|
|
268
285
|
knowledgeActionExecuted?: boolean;
|
|
269
286
|
helplineActionExecuted?: boolean;
|
|
270
287
|
executedKnowledgeIds?: string[];
|