telescope-ai 0.2.0 → 0.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"client-DDdIEKTi.cjs","names":[],"sources":["../src/core/errors.ts","../src/core/fetch-event-source.ts","../src/core/types.ts","../src/core/response-builder.ts","../src/core/stream-session.ts","../src/insights/instrument/client.ts","../src/insights/portfolio/client.ts","../src/insights/watchlist/client.ts","../src/insights/market/client.ts","../src/insights/trending/client.ts","../src/insights/client.ts","../src/atlas/client.ts","../src/news/client.ts","../src/core/client.ts"],"sourcesContent":["/**\n * Telescope Client Error Hierarchy\n *\n * Provides typed error classes for different failure scenarios\n * when interacting with the Telescope API.\n */\n\nimport type { ErrorEventData } from \"./types\";\n\n/** Base error class for all Telescope client errors */\nexport class TelescopeClientError extends Error {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\n/** Error resolving or validating client token */\nexport class TokenError extends TelescopeClientError {}\n\n/** HTTP API returned an error response */\nexport class ApiError extends TelescopeClientError {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n }\n}\n\n/** Network-level error (connection failed, DNS, etc.) */\nexport class NetworkError extends TelescopeClientError {}\n\n/** Error during SSE streaming */\nexport class StreamingError extends TelescopeClientError {\n constructor(\n message: string,\n public readonly status?: number,\n cause?: unknown,\n ) {\n super(message, cause);\n }\n}\n\n/** Error received via SSE error event from the API */\nexport class SSEError extends TelescopeClientError {\n public readonly code: string;\n public readonly httpStatus: number;\n public readonly path?: string[];\n public readonly additionalData?: Record<string, unknown>;\n\n constructor(errorData: ErrorEventData) {\n super(errorData.message);\n this.code = errorData.code;\n this.httpStatus = errorData.http_status;\n this.path = errorData.path;\n this.additionalData = errorData.additional_data;\n }\n}\n","/**\n * Minimal SSE (Server-Sent Events) client that supports POST requests.\n * Replacement for @microsoft/fetch-event-source.\n */\n\nimport type { SSESource } from \"./types\";\n\nexport interface EventSourceMessage {\n event: string;\n data: string;\n id: string;\n retry?: number;\n}\n\nexport interface FetchEventSourceOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n signal?: AbortSignal;\n openWhenHidden?: boolean;\n onopen?: (response: Response) => Promise<void>;\n onmessage?: (message: EventSourceMessage) => void;\n onclose?: () => void;\n onerror?: (error: unknown) => void;\n}\n\nexport interface ProcessSSEStreamOptions {\n onclose?: () => void;\n onerror?: (error: unknown) => void;\n signal?: AbortSignal;\n}\n\n/**\n * Parse SSE stream data into individual messages.\n * SSE format: fields separated by newlines, messages separated by blank lines.\n * Fields: event:, data:, id:, retry:\n */\nfunction parseSSEMessage(raw: string): EventSourceMessage | null {\n const lines = raw.split(\"\\n\");\n let event = \"message\";\n const dataLines: string[] = [];\n let id = \"\";\n let retry: number | undefined;\n\n for (const line of lines) {\n if (line.startsWith(\"event:\")) {\n event = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n dataLines.push(line.slice(5).trimStart());\n } else if (line.startsWith(\"id:\")) {\n id = line.slice(3).trim();\n } else if (line.startsWith(\"retry:\")) {\n const val = parseInt(line.slice(6).trim(), 10);\n if (!isNaN(val)) {\n retry = val;\n }\n }\n // Lines starting with : are comments, ignored\n // Other lines are ignored per SSE spec\n }\n\n // If no data lines, this wasn't a valid message\n if (dataLines.length === 0) {\n return null;\n }\n\n return {\n event,\n data: dataLines.join(\"\\n\"),\n id,\n retry,\n };\n}\n\n/**\n * Process an SSE stream from a pre-existing source (Response or readable stream).\n * This allows parsing SSE responses that were fetched externally (e.g., via a proxy).\n */\nexport async function processSSEStream(\n source: SSESource,\n onmessage: (message: EventSourceMessage) => void,\n options?: ProcessSSEStreamOptions,\n): Promise<void> {\n const { onclose, onerror, signal } = options ?? {};\n\n const body = source.body;\n if (!body) {\n const err = new Error(\"Source body is not readable\");\n onerror?.(err);\n throw err;\n }\n\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n // eslint-disable-next-line no-await-in-loop\n const { done, value } = await reader.read();\n\n if (done) {\n // Process any remaining buffer content\n if (buffer.trim()) {\n const message = parseSSEMessage(buffer);\n if (message) {\n onmessage(message);\n }\n }\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n // SSE messages are separated by double newlines\n const parts = buffer.split(\"\\n\\n\");\n\n // Keep the last part in buffer (may be incomplete)\n buffer = parts.pop() || \"\";\n\n // Process complete messages\n for (const part of parts) {\n if (!part.trim()) continue;\n const message = parseSSEMessage(part);\n if (message) {\n onmessage(message);\n }\n }\n }\n } catch (err) {\n // Don't report abort errors\n if (signal?.aborted) {\n return;\n }\n onerror?.(err);\n throw err;\n } finally {\n onclose?.();\n }\n}\n\n/**\n * Fetch-based SSE client that supports POST requests.\n */\nexport async function fetchEventSource(\n url: string,\n options: FetchEventSourceOptions,\n): Promise<void> {\n const {\n method = \"GET\",\n headers = {},\n body,\n signal,\n onopen,\n onmessage,\n onclose,\n onerror,\n } = options;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body,\n signal,\n });\n } catch (err) {\n onerror?.(err);\n throw err;\n }\n\n // Call onopen to let caller validate response\n // Clone response so onopen can read body without affecting our stream\n if (onopen) {\n try {\n await onopen(response.clone());\n } catch (err) {\n onerror?.(err);\n throw err;\n }\n }\n\n await processSSEStream(response, onmessage ?? (() => {}), {\n onclose,\n onerror,\n signal,\n });\n}\n","/**\n * Telescope SSE Format v2 - Type Definitions\n *\n * This module defines types for the v2 SSE streaming format which uses\n * path-based patch operations instead of flat field events.\n */\n\n/**\n * Source for SSE streaming - either a fetch Response or an object with a readable stream.\n * Used for parsing pre-made SSE responses (e.g., from customer proxy scenarios).\n */\nexport type SSESource =\n | Response\n | { body: ReadableStream<Uint8Array>; headers?: Headers };\n\n/** Path segment - string representing object key or array index */\nexport type PathSegment = string;\n\n/** Non-empty array of path segments for addressing values in the response */\nexport type Path = [PathSegment, ...PathSegment[]];\n\n/** JSON-compatible value types */\nexport type JSONValue =\n | string\n | number\n | boolean\n | null\n | JSONValue[]\n | { [key: string]: JSONValue };\n\n/** Patch operation types */\nexport type PatchOperation =\n | \"set\"\n | \"append_string\"\n | \"append_array\"\n | \"delete\";\n\n/** Patch event data - the core mutation instruction */\nexport interface PatchEventData {\n operation: PatchOperation;\n path: Path;\n value?: JSONValue;\n}\n\n/** Path complete event data */\nexport interface PathCompleteEventData {\n path: Path;\n}\n\n/** Error event data from the API */\nexport interface ErrorEventData {\n code: string;\n message: string;\n http_status: number;\n path?: Path;\n additional_data?: Record<string, unknown>;\n}\n\n/** Heartbeat event data (empty) */\nexport type HeartbeatEventData = Record<string, never>;\n\n/** Complete event data (empty) */\nexport type CompleteEventData = Record<string, never>;\n\n/** Union of all SSE events */\nexport type SSEEvent =\n | { event: \"patch\"; data: PatchEventData }\n | { event: \"path_complete\"; data: PathCompleteEventData }\n | { event: \"error\"; data: ErrorEventData }\n | { event: \"heartbeat\"; data: HeartbeatEventData }\n | { event: \"complete\"; data: CompleteEventData };\n\n/** Extract the event name from an SSEEvent */\nexport type SSEEventName = SSEEvent[\"event\"];\n\n/**\n * Check if a path segment should be interpreted as an array index.\n * Per spec: segment is array index if it consists only of digits.\n */\nexport function isArrayIndex(segment: string): boolean {\n return /^\\d+$/.test(segment);\n}\n\n/**\n * Convert a path to a string key for use in Sets/Maps.\n */\nexport function pathToKey(path: Path): string {\n return JSON.stringify(path);\n}\n\n/**\n * Parse a raw SSE message into a typed SSEEvent.\n * Returns null for unknown event types (per spec: clients MUST ignore unknown events).\n */\nexport function parseEvent(\n eventName: string,\n rawData: unknown,\n): SSEEvent | null {\n switch (eventName.toLowerCase()) {\n case \"patch\": {\n const data = rawData as Record<string, unknown>;\n return {\n event: \"patch\",\n data: {\n operation: data.operation as PatchOperation,\n path: data.path as Path,\n value: data.value as JSONValue | undefined,\n },\n };\n }\n case \"path_complete\": {\n const data = rawData as Record<string, unknown>;\n return {\n event: \"path_complete\",\n data: { path: data.path as Path },\n };\n }\n case \"error\": {\n const data = rawData as Record<string, unknown>;\n return {\n event: \"error\",\n data: {\n code: data.code as string,\n message: data.message as string,\n http_status: (data.http_status ?? data.status_code ?? 0) as number,\n path: data.path as Path | undefined,\n additional_data: data.additional_data as\n | Record<string, unknown>\n | undefined,\n },\n };\n }\n case \"heartbeat\":\n return { event: \"heartbeat\", data: {} };\n case \"complete\":\n return { event: \"complete\", data: {} };\n default:\n // Per spec: clients MUST ignore unknown event types\n return null;\n }\n}\n","/**\n * ResponseBuilder - Core V2 SSE patch application logic\n *\n * Builds a response object by applying patch operations from SSE events.\n * This is the \"meat\" of the v2 client - all patch logic lives here.\n */\n\nimport {\n type Path,\n type PatchEventData,\n type JSONValue,\n isArrayIndex,\n} from \"./types\";\n\n/**\n * ResponseBuilder accumulates patch operations into a response object.\n *\n * Design notes:\n * - Uses mutation internally for simplicity\n * - React re-renders are triggered by observer callbacks in StreamSession\n * - Path resolution follows v2 spec: all-digit segments are array indices\n */\nexport class ResponseBuilder<T = Record<string, unknown>> {\n private data: T;\n\n constructor(initialData?: T) {\n this.data = (initialData ? structuredClone(initialData) : {}) as T;\n }\n\n /**\n * Apply a patch operation to the response data.\n */\n applyPatch(patch: PatchEventData): void {\n const { operation, path, value } = patch;\n\n switch (operation) {\n case \"set\":\n this.setAtPath(path, value as JSONValue);\n break;\n case \"append_string\":\n this.appendStringAtPath(path, value as string);\n break;\n case \"append_array\":\n this.appendArrayAtPath(path, value as JSONValue);\n break;\n case \"delete\":\n this.deleteAtPath(path);\n break;\n }\n }\n\n /**\n * Get the current accumulated data.\n */\n getData(): T {\n return this.data;\n }\n\n /**\n * Set a value at the given path, creating intermediate containers as needed.\n *\n * Container creation follows the v2 spec:\n * - If next segment is array index (all digits), create array\n * - Otherwise, create object\n */\n private setAtPath(path: Path, value: JSONValue): void {\n const parent = this.ensureParentPath(path);\n const lastSegment = path[path.length - 1];\n\n if (Array.isArray(parent)) {\n parent[parseInt(lastSegment, 10)] = value;\n } else {\n (parent as Record<string, unknown>)[lastSegment] = value;\n }\n }\n\n /**\n * Append a string to the value at path.\n * If value doesn't exist, initializes to empty string first.\n */\n private appendStringAtPath(path: Path, chunk: string): void {\n const parent = this.ensureParentPath(path);\n const lastSegment = path[path.length - 1];\n\n let current: unknown;\n if (Array.isArray(parent)) {\n current = parent[parseInt(lastSegment, 10)];\n } else {\n current = (parent as Record<string, unknown>)[lastSegment];\n }\n\n // Initialize to empty string if doesn't exist\n const newValue = (typeof current === \"string\" ? current : \"\") + chunk;\n\n if (Array.isArray(parent)) {\n parent[parseInt(lastSegment, 10)] = newValue;\n } else {\n (parent as Record<string, unknown>)[lastSegment] = newValue;\n }\n }\n\n /**\n * Append an element to the array at path.\n * If array doesn't exist, initializes to empty array first.\n */\n private appendArrayAtPath(path: Path, element: JSONValue): void {\n const parent = this.ensureParentPath(path);\n const lastSegment = path[path.length - 1];\n\n let current: unknown;\n if (Array.isArray(parent)) {\n current = parent[parseInt(lastSegment, 10)];\n } else {\n current = (parent as Record<string, unknown>)[lastSegment];\n }\n\n // Initialize to empty array if doesn't exist\n const arr = Array.isArray(current) ? current : [];\n arr.push(element);\n\n if (Array.isArray(parent)) {\n parent[parseInt(lastSegment, 10)] = arr;\n } else {\n (parent as Record<string, unknown>)[lastSegment] = arr;\n }\n }\n\n /**\n * Delete/unset the value at path.\n * - For object keys: removes the property\n * - For array indices: sets to null (preserves indices per spec)\n */\n private deleteAtPath(path: Path): void {\n const parent = this.navigateToParent(path);\n if (parent === null) {\n // Path doesn't exist, nothing to delete\n return;\n }\n\n const lastSegment = path[path.length - 1];\n\n if (Array.isArray(parent)) {\n // Set to null, preserve indices (per spec)\n parent[parseInt(lastSegment, 10)] = null;\n } else {\n // Remove property from object\n delete (parent as Record<string, unknown>)[lastSegment];\n }\n }\n\n /**\n * Navigate to the parent container of the target path, creating containers as needed.\n * Returns the parent container where the final segment should be set.\n */\n private ensureParentPath(path: Path): unknown[] | Record<string, unknown> {\n let current: unknown = this.data;\n\n for (let i = 0; i < path.length - 1; i++) {\n const segment = path[i];\n const nextSegment = path[i + 1];\n const nextIsArrayIndex = isArrayIndex(nextSegment);\n\n let next: unknown;\n if (Array.isArray(current)) {\n next = current[parseInt(segment, 10)];\n } else {\n next = (current as Record<string, unknown>)[segment];\n }\n\n // Create container if doesn't exist or is wrong type\n if (next === undefined || next === null) {\n next = nextIsArrayIndex ? [] : {};\n if (Array.isArray(current)) {\n current[parseInt(segment, 10)] = next;\n } else {\n (current as Record<string, unknown>)[segment] = next;\n }\n }\n\n current = next;\n }\n\n return current as unknown[] | Record<string, unknown>;\n }\n\n /**\n * Navigate to the parent container without creating containers.\n * Returns null if the path doesn't exist.\n */\n private navigateToParent(\n path: Path,\n ): unknown[] | Record<string, unknown> | null {\n let current: unknown = this.data;\n\n for (let i = 0; i < path.length - 1; i++) {\n const segment = path[i];\n\n let next: unknown;\n if (Array.isArray(current)) {\n next = current[parseInt(segment, 10)];\n } else if (typeof current === \"object\" && current !== null) {\n next = (current as Record<string, unknown>)[segment];\n } else {\n return null;\n }\n\n if (next === undefined || next === null) {\n return null;\n }\n\n current = next;\n }\n\n return current as unknown[] | Record<string, unknown>;\n }\n}\n","/**\n * StreamSession - Manages streaming lifecycle and observers\n *\n * Composes ResponseBuilder to accumulate data while providing:\n * - Lifecycle phase tracking (idle -> pending -> streaming -> completed/error)\n * - Observer pattern for React integration\n * - Path completion tracking\n * - Promise-based completion\n */\n\nimport {\n type SSEEvent,\n type Path,\n type ErrorEventData,\n type PatchEventData,\n pathToKey,\n} from \"./types\";\nimport { ResponseBuilder } from \"./response-builder\";\nimport { SSEError, StreamingError } from \"./errors\";\n\n/** Stream lifecycle phases */\nexport type StreamPhase =\n | \"idle\"\n | \"pending\"\n | \"streaming\"\n | \"completed\"\n | \"error\";\n\n/** Stream status with timing and error information */\nexport interface StreamStatus {\n phase: StreamPhase;\n startedAt?: number;\n completedAt?: number;\n error?: SSEError | StreamingError;\n}\n\n/** Observer interface for stream events */\nexport interface StreamObserver<T> {\n /** Called on every patch event with updated data */\n onPatch?: (data: T, event: SSEEvent) => void;\n /** Called when a path is marked complete */\n onPathComplete?: (path: Path, data: T) => void;\n /** Called on SSE error events (may be fatal or non-fatal) */\n onError?: (error: ErrorEventData) => void;\n /** Called on heartbeat events */\n onHeartbeat?: () => void;\n /** Called when stream completes successfully */\n onComplete?: (data: T) => void;\n /** Called when stream fails due to transport error */\n onTransportError?: (error: StreamingError) => void;\n /** Called whenever status changes */\n onStatusChange?: (status: StreamStatus) => void;\n}\n\n/**\n * StreamSession manages a single streaming request.\n *\n * Usage:\n * ```ts\n * const session = new StreamSession<MyResponse>();\n * session.subscribe({ onPatch: (data) => console.log(data) });\n * // Transport layer calls session.processEvent() for each SSE event\n * const result = await session.complete();\n * ```\n */\nexport class StreamSession<T = Record<string, unknown>> {\n private readonly builder: ResponseBuilder<T>;\n private _status: StreamStatus = { phase: \"idle\" };\n private readonly observers = new Set<StreamObserver<T>>();\n private readonly completedPaths = new Set<string>();\n private readonly completionPromise: Promise<T>;\n private completionResolve!: (value: T) => void;\n private completionReject!: (reason: unknown) => void;\n\n constructor(initialData?: T) {\n this.builder = new ResponseBuilder<T>(initialData);\n this.completionPromise = new Promise<T>((resolve, reject) => {\n this.completionResolve = resolve;\n this.completionReject = reject;\n });\n }\n\n /**\n * Subscribe to stream events.\n * Returns an unsubscribe function.\n */\n subscribe(observer: StreamObserver<T>): () => void {\n this.observers.add(observer);\n return () => this.observers.delete(observer);\n }\n\n /** Get the current accumulated data */\n get data(): T {\n return this.builder.getData();\n }\n\n /** Get the current stream status */\n get status(): StreamStatus {\n return this._status;\n }\n\n /**\n * Returns a promise that resolves when the stream completes.\n * Rejects if the stream fails with an error.\n */\n complete(): Promise<T> {\n return this.completionPromise;\n }\n\n /**\n * Check if a specific path has been marked complete.\n */\n isPathComplete(path: Path): boolean {\n return this.completedPaths.has(pathToKey(path));\n }\n\n /**\n * Get all completed paths.\n */\n getCompletedPaths(): Path[] {\n return Array.from(this.completedPaths).map(\n (key) => JSON.parse(key) as Path,\n );\n }\n\n // --- Lifecycle methods (called by transport layer) ---\n\n /**\n * Mark the session as pending (request started but no data yet).\n */\n markPending(): void {\n if (this._status.phase === \"idle\") {\n this.setStatus({ phase: \"pending\", startedAt: Date.now() });\n }\n }\n\n /**\n * Process an incoming SSE event.\n * Called by the transport layer for each event received.\n */\n processEvent(event: SSEEvent): void {\n // Ignore events after session has ended (except heartbeats for logging purposes are still ignored)\n if (this._status.phase === \"completed\" || this._status.phase === \"error\") {\n return;\n }\n\n switch (event.event) {\n case \"patch\":\n this.handlePatch(event.data);\n break;\n case \"path_complete\":\n this.handlePathComplete(event.data.path);\n break;\n case \"error\":\n this.handleError(event.data);\n break;\n case \"heartbeat\":\n this.handleHeartbeat();\n break;\n case \"complete\":\n this.handleComplete();\n break;\n }\n }\n\n /**\n * Handle transport-level error (connection failure, timeout, etc.).\n */\n handleTransportError(error: StreamingError): void {\n if (this._status.phase === \"completed\" || this._status.phase === \"error\") {\n return;\n }\n\n this.setStatus({\n phase: \"error\",\n startedAt: this._status.startedAt,\n completedAt: Date.now(),\n error,\n });\n\n this.notifyObservers((obs) => obs.onTransportError?.(error));\n this.completionReject(error);\n }\n\n // --- Private event handlers ---\n\n private handlePatch(patch: PatchEventData): void {\n // Transition to streaming on first data event\n if (this._status.phase === \"pending\" || this._status.phase === \"idle\") {\n this.setStatus({\n phase: \"streaming\",\n startedAt: this._status.startedAt ?? Date.now(),\n });\n }\n\n this.builder.applyPatch(patch);\n const data = this.builder.getData();\n\n this.notifyObservers((obs) =>\n obs.onPatch?.(data, { event: \"patch\", data: patch }),\n );\n }\n\n private handlePathComplete(path: Path): void {\n this.completedPaths.add(pathToKey(path));\n const data = this.builder.getData();\n this.notifyObservers((obs) => obs.onPathComplete?.(path, data));\n }\n\n private handleError(errorData: ErrorEventData): void {\n // Notify observers of the error\n this.notifyObservers((obs) => obs.onError?.(errorData));\n\n // Transition to error phase so hasError becomes true.\n const sseError = new SSEError(errorData);\n this.setStatus({\n phase: \"error\",\n startedAt: this._status.startedAt,\n completedAt: Date.now(),\n error: sseError,\n });\n\n this.completionReject(sseError);\n }\n\n private handleHeartbeat(): void {\n this.notifyObservers((obs) => obs.onHeartbeat?.());\n }\n\n private handleComplete(): void {\n if (this._status.phase === \"completed\" || this._status.phase === \"error\") {\n return;\n }\n\n this.setStatus({\n phase: \"completed\",\n startedAt: this._status.startedAt,\n completedAt: Date.now(),\n });\n\n const data = this.builder.getData();\n this.notifyObservers((obs) => obs.onComplete?.(data));\n this.completionResolve(data);\n }\n\n private setStatus(status: StreamStatus): void {\n this._status = status;\n this.notifyObservers((obs) => obs.onStatusChange?.(status));\n }\n\n private notifyObservers(callback: (obs: StreamObserver<T>) => void): void {\n // Iterate over a copy to allow safe modification during iteration\n for (const observer of Array.from(this.observers)) {\n try {\n callback(observer);\n } catch (e) {\n console.error(\"Observer callback error:\", e);\n }\n }\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type { Instrument } from \"../../instruments/client\";\n\n// --- Types ---\n\nexport interface InsightGroup {\n title: string;\n content: string;\n}\n\nexport interface InsightCitation {\n id?: string;\n title?: string;\n url?: string;\n source?: string;\n published_at?: string;\n}\n\nexport interface InstrumentInsightsResponse {\n instrument: Instrument;\n groups: InsightGroup[];\n citations?: InsightCitation[];\n updated_at: string;\n}\n\nexport interface InstrumentInsightsRequest {\n instrument_id?: string;\n instrument_reference_id1?: string;\n instrument_reference_id2?: string;\n instrument_group_ids: string[];\n locale?: string;\n profile?: string;\n stream?: boolean;\n}\n\n// --- Client ---\n\nexport class InstrumentInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<InstrumentInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<InstrumentInsightsResponse> {\n return this.client.stream<InstrumentInsightsResponse>(\n \"/v2/insights/instrument\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<InstrumentInsightsRequest, \"stream\">,\n ): Promise<InstrumentInsightsResponse> {\n return this.client.post<InstrumentInsightsResponse>(\n \"/v2/insights/instrument\",\n { ...request, stream: false },\n );\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<InstrumentInsightsResponse> {\n return this.client.streamFromResponse<InstrumentInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<InstrumentInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type { InstrumentInsightsResponse } from \"../instrument/client\";\n\n// --- Types ---\n\nexport interface InsightsError {\n status_code: number;\n message: string;\n code: string;\n additional_data?: Record<string, unknown>;\n}\n\nexport interface PortfolioInsightsResponse {\n heading?: string;\n overview?: string;\n instruments?: InstrumentInsightsResponse[];\n errors?: InsightsError[];\n updated_at: string;\n}\n\nexport interface PortfolioInsightsRequest {\n instrument_ids?: string[];\n instrument_reference_id1s?: string[];\n instrument_reference_id2s?: string[];\n instrument_group_ids: string[];\n instrument_weightings?: Record<string, number>;\n omit_instruments_output?: boolean;\n locale?: string;\n profile?: string;\n stream?: boolean;\n}\n\n// --- Client ---\n\nexport class PortfolioInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<PortfolioInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<PortfolioInsightsResponse> {\n return this.client.stream<PortfolioInsightsResponse>(\n \"/v2/insights/portfolio\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<PortfolioInsightsRequest, \"stream\">,\n ): Promise<PortfolioInsightsResponse> {\n return this.client.post<PortfolioInsightsResponse>(\n \"/v2/insights/portfolio\",\n { ...request, stream: false },\n );\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<PortfolioInsightsResponse> {\n return this.client.streamFromResponse<PortfolioInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<PortfolioInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type {\n PortfolioInsightsRequest,\n PortfolioInsightsResponse,\n} from \"../portfolio/client\";\n\n// --- Types ---\n\nexport type WatchlistInsightsRequest = PortfolioInsightsRequest;\nexport type WatchlistInsightsResponse = PortfolioInsightsResponse;\n\n// --- Client ---\n\nexport class WatchlistInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<WatchlistInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<WatchlistInsightsResponse> {\n return this.client.stream<WatchlistInsightsResponse>(\n \"/v2/insights/watchlist\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<WatchlistInsightsRequest, \"stream\">,\n ): Promise<WatchlistInsightsResponse> {\n return this.client.post<WatchlistInsightsResponse>(\n \"/v2/insights/watchlist\",\n { ...request, stream: false },\n );\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<WatchlistInsightsResponse> {\n return this.client.streamFromResponse<WatchlistInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<WatchlistInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type { InsightGroup } from \"../instrument/client\";\n\n// --- Types ---\n\nexport interface MarketInsightsRequest {\n profile: string;\n locale?: string;\n stream?: boolean;\n}\n\nexport interface MarketInsightsResponse {\n groups: InsightGroup[];\n updated_at: string;\n}\n\n// --- Client ---\n\nexport class MarketInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<MarketInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<MarketInsightsResponse> {\n return this.client.stream<MarketInsightsResponse>(\n \"/v2/insights/market\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<MarketInsightsRequest, \"stream\">,\n ): Promise<MarketInsightsResponse> {\n return this.client.post<MarketInsightsResponse>(\"/v2/insights/market\", {\n ...request,\n stream: false,\n });\n }\n\n streamFromResponse(source: SSESource): StreamSession<MarketInsightsResponse> {\n return this.client.streamFromResponse<MarketInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<MarketInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\n\n// --- Types ---\n\nexport type TrendingInsightsFocus =\n | \"global\"\n | \"crypto\"\n | \"new-zealand\"\n | \"australia\"\n | \"south-africa\"\n | \"singapore\"\n | \"united-kingdom\"\n | \"europe\"\n | \"united-states\";\n\nexport interface TrendingStory {\n headline: string;\n prompt: string;\n summary: string;\n}\n\nexport interface TrendingInsightsRequest {\n focus?: TrendingInsightsFocus;\n limit?: number;\n locale?: string;\n stream?: boolean;\n}\n\nexport interface TrendingInsightsResponse {\n stories: TrendingStory[];\n updated_at: string;\n}\n\n// --- Client ---\n\nexport class TrendingInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<TrendingInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<TrendingInsightsResponse> {\n return this.client.stream<TrendingInsightsResponse>(\n \"/v2/insights/trending\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<TrendingInsightsRequest, \"stream\">,\n ): Promise<TrendingInsightsResponse> {\n return this.client.post<TrendingInsightsResponse>(\"/v2/insights/trending\", {\n ...request,\n stream: false,\n });\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<TrendingInsightsResponse> {\n return this.client.streamFromResponse<TrendingInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<TrendingInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient } from \"../core/client\";\nimport { InstrumentInsightsClient } from \"./instrument/client\";\nimport { PortfolioInsightsClient } from \"./portfolio/client\";\nimport { WatchlistInsightsClient } from \"./watchlist/client\";\nimport { MarketInsightsClient } from \"./market/client\";\nimport { TrendingInsightsClient } from \"./trending/client\";\n\nexport * from \"./instrument/client\";\nexport * from \"./portfolio/client\";\nexport * from \"./watchlist/client\";\nexport * from \"./market/client\";\nexport * from \"./trending/client\";\n\nexport class InsightsClient {\n readonly instrument: InstrumentInsightsClient;\n readonly portfolio: PortfolioInsightsClient;\n readonly watchlist: WatchlistInsightsClient;\n readonly market: MarketInsightsClient;\n readonly trending: TrendingInsightsClient;\n\n constructor(client: TelescopeClient) {\n this.instrument = new InstrumentInsightsClient(client);\n this.portfolio = new PortfolioInsightsClient(client);\n this.watchlist = new WatchlistInsightsClient(client);\n this.market = new MarketInsightsClient(client);\n this.trending = new TrendingInsightsClient(client);\n }\n}\n","/**\n * Atlas Product Client\n *\n * Provides typed access to the Atlas v2 API endpoints for\n * generating AI-powered financial advice.\n */\n\nimport { TelescopeClient, type StreamOptions } from \"../core/client\";\nimport type { StreamSession } from \"../core/stream-session\";\nimport type { SSESource } from \"../core/types\";\n\n// --- Types ---\n\n/** Request parameters for Atlas advice generation */\nexport interface AtlasAdviceRequest {\n /** Advisor environment ID (UUID) */\n advisor_environment_id: string;\n /** Client demographic and personal details */\n client_details: string;\n /** Income information */\n income_details: string;\n /** Current asset holdings */\n current_assets: string;\n /** Existing portfolio description */\n existing_portfolio: string;\n /** Financial goals and objectives */\n financial_objectives: string;\n /** Risk tolerance preferences */\n risk_preferences: string;\n /** Investment preferences and constraints */\n investment_preferences: string;\n /** Additional context or information */\n additional_information?: string;\n /** Instrument group IDs for available instruments */\n instrument_group_ids?: string[];\n /** User reference ID for tracking */\n user_reference_id?: string;\n /** Enable streaming (should be true for SSE) */\n stream?: boolean;\n}\n\n/** Trade recommendation from Atlas */\nexport interface AtlasTrade {\n action: \"BUY\" | \"SELL\" | \"HOLD\";\n title: string;\n ticker: string;\n bucket?: string;\n slug?: string;\n instrument_id?: string;\n amount?: number;\n reason?: string;\n}\n\n/** Portfolio holding from Atlas */\nexport interface AtlasHolding {\n title: string;\n ticker: string;\n slug?: string;\n description?: string;\n amount?: number;\n asset_type?: string;\n bucket?: string;\n reason?: string;\n instrument_id?: string;\n}\n\n/** Status information in Atlas response */\nexport interface AtlasStatus {\n state: string;\n reason?: string;\n}\n\n/** Response from Atlas advice endpoint */\nexport interface AtlasAdviceResponse {\n // Metadata\n id?: string;\n client_name?: string;\n advice_date?: string;\n title?: string;\n subtitle?: string;\n progress_update?: string;\n status?: AtlasStatus;\n stats?: Record<string, number>;\n\n // Text sections (streamed via append_string)\n client_summary?: string;\n existing_portfolio_opening_comment?: string;\n existing_portfolio_assessment?: string;\n strategy_overview?: string;\n target_portfolio_value?: string;\n risk_considerations?: string;\n final_report?: string;\n executive_summary?: string;\n\n // Array sections (streamed via append_array)\n existing_portfolio?: AtlasHolding[];\n investment_objectives?: Record<string, unknown>;\n allocation_segments?: Record<string, unknown>[];\n implementation_buckets?: Record<string, unknown>[];\n recommended_portfolio?: AtlasHolding[];\n recommended_trades?: AtlasTrade[];\n decision_log?: Record<string, unknown>[];\n assumption_log?: Record<string, unknown>[];\n advice?: Record<string, unknown>[];\n}\n\n/** Summary of an advisor environment returned by the list endpoint */\nexport interface AdvisorEnvironmentSummary {\n id: string;\n title: string;\n description?: string;\n currency?: string;\n is_global: boolean;\n instrument_groups?: { id: string; title: string }[];\n}\n\n// --- Client ---\n\n/**\n * AtlasClient provides access to the Atlas v2 API.\n *\n * @example\n * ```ts\n * const client = new TelescopeClient({ apiKey: \"...\" });\n *\n * const session = client.atlas.streamAdvice({\n * advisor_environment_id: \"env-uuid\",\n * client_details: \"45 year old professional...\",\n * // ... other fields\n * });\n *\n * session.subscribe({\n * onPatch: (data) => console.log(data.progress_update),\n * onComplete: (data) => console.log(\"Advice generated:\", data),\n * });\n * ```\n */\nexport class AtlasClient {\n constructor(private readonly client: TelescopeClient) {}\n\n /**\n * Stream financial advice generation.\n */\n streamAdvice(\n request: Omit<AtlasAdviceRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<AtlasAdviceResponse> {\n return this.client.stream<AtlasAdviceResponse>(\n \"/v2/atlas/advice\",\n { ...request, stream: true },\n options,\n );\n }\n\n /**\n * Create a StreamSession from a pre-existing SSE response.\n * Use this when you have a response from a proxy or custom fetch.\n */\n streamAdviceFromResponse(\n source: SSESource,\n ): StreamSession<AtlasAdviceResponse> {\n return this.client.streamFromResponse<AtlasAdviceResponse>(source);\n }\n\n /**\n * Get a previously generated advice by ID.\n */\n async getAdvice(id: string): Promise<AtlasAdviceResponse> {\n return this.client.get<AtlasAdviceResponse>(`/v2/atlas/advice?id=${id}`);\n }\n\n /**\n * Get recent advice entries.\n */\n async getLatestAdvice(limit: number = 30): Promise<AtlasAdviceResponse[]> {\n return this.client.get<AtlasAdviceResponse[]>(\n `/v2/atlas/advice/latest?limit=${limit}`,\n );\n }\n\n /**\n * Archive or unarchive advice entries.\n */\n async archiveAdvice(ids: string[], archived: boolean = true): Promise<void> {\n return this.client.post<void>(\"/v2/atlas/advice/archive\", {\n ids,\n archived,\n });\n }\n\n /**\n * List available advisor environments.\n */\n async getAdvisorEnvironments(options?: {\n region?: string;\n currency?: string;\n }): Promise<AdvisorEnvironmentSummary[]> {\n const params = new URLSearchParams();\n if (options?.region) params.set(\"region\", options.region);\n if (options?.currency) params.set(\"currency\", options.currency);\n const qs = params.toString();\n const path = `/v2/atlas/advisor-environments${qs ? `?${qs}` : \"\"}`;\n const resp = await this.client.get<{\n environments: AdvisorEnvironmentSummary[];\n }>(path);\n return resp.environments;\n }\n}\n","/**\n * News Product Client\n *\n * Provides typed access to News v2 API endpoints.\n */\n\nimport { TelescopeClient, type StreamOptions } from \"../core/client\";\nimport { ApiError } from \"../core/errors\";\nimport type { StreamSession } from \"../core/stream-session\";\nimport type { SSESource } from \"../core/types\";\n\n/** Shared story shape returned by News v2 endpoints. */\nexport interface NewsStory {\n id: string;\n headline: string;\n prompt: string;\n summary: string;\n first_clustered_at: string;\n last_revised_at: string;\n article_count: number;\n is_breaking: boolean;\n}\n\n/** Request body for POST /v2/news/latest. */\nexport interface NewsLatestRequest {\n instrument_ids?: string[];\n instrument_reference_id1s?: string[];\n instrument_reference_id2s?: string[];\n instrument_group_ids: string[];\n limit?: number;\n locale?: string;\n stream?: boolean;\n}\n\n/** Response body for POST /v2/news/latest. */\nexport interface NewsLatestResponse {\n stories: NewsStory[];\n}\n\n/** Request body for POST /v2/news/trending/topics. */\nexport interface NewsTrendingTopicsRequest {\n topics: string[];\n limit?: number;\n locale?: string;\n stream?: boolean;\n}\n\n/** Response body for POST /v2/news/trending/topics. */\nexport interface NewsTrendingTopicsResponse {\n stories: NewsStory[];\n}\n\nclass NewsLatestClient {\n constructor(private readonly client: TelescopeClient) {}\n\n /**\n * Get latest stories for a set of instruments (non-streaming).\n */\n async generate(\n request: Omit<NewsLatestRequest, \"stream\">,\n ): Promise<NewsLatestResponse> {\n return this.client.post<NewsLatestResponse>(\"/v2/news/latest\", {\n ...request,\n stream: false,\n });\n }\n}\n\nclass NewsTrendingTopicsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n /**\n * Stream ranked trending topics via SSE.\n */\n stream(\n request: Omit<NewsTrendingTopicsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<NewsTrendingTopicsResponse> {\n return this.client.stream<NewsTrendingTopicsResponse>(\n \"/v2/news/trending/topics\",\n { ...request, stream: true },\n options,\n );\n }\n\n /**\n * Get ranked trending topics (non-streaming).\n */\n async generate(\n request: Omit<NewsTrendingTopicsRequest, \"stream\">,\n ): Promise<NewsTrendingTopicsResponse> {\n return this.client.post<NewsTrendingTopicsResponse>(\n \"/v2/news/trending/topics\",\n { ...request, stream: false },\n );\n }\n\n /**\n * Create a StreamSession from a pre-existing SSE response.\n */\n streamFromResponse(\n source: SSESource,\n ): StreamSession<NewsTrendingTopicsResponse> {\n return this.client.streamFromResponse<NewsTrendingTopicsResponse>(source);\n }\n\n /**\n * Parse a pre-existing JSON response (non-streaming).\n */\n async generateFromResponse(\n response: Response,\n ): Promise<NewsTrendingTopicsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n\n/**\n * NewsClient provides access to News v2 API.\n */\nexport class NewsClient {\n readonly latest: NewsLatestClient;\n readonly trendingTopics: NewsTrendingTopicsClient;\n\n constructor(client: TelescopeClient) {\n this.latest = new NewsLatestClient(client);\n this.trendingTopics = new NewsTrendingTopicsClient(client);\n }\n}\n","/**\n * TelescopeClient - Main HTTP and SSE streaming client\n *\n * Provides:\n * - REST methods (get, post, put, patch, delete, request)\n * - SSE streaming via stream()\n * - Namespaced product access (client.insights, client.atlas, client.news)\n * - Abort signal support\n * - Configurable connect timeout and debug logging\n */\n\nimport {\n ApiError,\n NetworkError,\n StreamingError,\n TelescopeClientError,\n} from \"./errors\";\nimport {\n type EventSourceMessage,\n fetchEventSource,\n processSSEStream,\n} from \"./fetch-event-source\";\nimport { StreamSession } from \"./stream-session\";\nimport { parseEvent, type SSESource } from \"./types\";\n\n/** SDK version - matches package.json */\nexport const VERSION = \"0.2.0\";\n\nconst DEFAULT_TELESCOPE_API_ORIGIN = \"https://api.telescope.co\";\nconst DEFAULT_CONNECT_TIMEOUT = 30000; // 30 seconds\nconst DEFAULT_JSON_HEADERS = {\n \"Content-Type\": \"application/json\",\n};\n\n// --- Client Options ---\n\nexport interface TelescopeClientOptions {\n /**\n * Organization API key for authentication (starts with `tsk_`).\n * Optional in Node.js/Bun - defaults to TELESCOPE_API_KEY env var.\n * Cannot be used together with `clientToken`.\n */\n apiKey?: string;\n /**\n * Client token for frontend authentication (starts with `tsc_`).\n * Use this instead of `apiKey` in browser environments.\n * Cannot be used together with `apiKey`.\n */\n clientToken?: string;\n /**\n * Base URL for the API.\n * Defaults to TELESCOPE_BASE_URL env var, or https://api.telescope.co\n */\n baseUrl?: string;\n /** Max time in milliseconds to wait for the server to start responding (default: 30000) */\n connectTimeout?: number;\n /** Enable debug logging to console (default: false) */\n debug?: boolean;\n}\n\nexport interface RequestOptions extends RequestInit {\n /** Override the default connect timeout for this request */\n connectTimeout?: number;\n}\n\nexport interface StreamOptions {\n signal?: AbortSignal;\n /** Override the default connection timeout for this stream. Only applies to the initial connection, not the total stream duration. */\n connectTimeout?: number;\n /** Additional request headers. Merged with (and can override) the default headers the client sets. */\n headers?: Record<string, string>;\n}\n\n// --- Product Client Types (for lazy loading) ---\n\n/** InsightsClient type - imported lazily to avoid circular deps */\ntype InsightsClientType = import(\"../insights/client\").InsightsClient;\n/** AtlasClient type - imported lazily to avoid circular deps */\ntype AtlasClientType = import(\"../atlas/client\").AtlasClient;\n/** NewsClient type - imported lazily to avoid circular deps */\ntype NewsClientType = import(\"../news/client\").NewsClient;\n\n// --- Main Client ---\n\n/**\n * TelescopeClient provides HTTP and SSE streaming access to the Telescope API.\n *\n * @example\n * ```ts\n * const client = new TelescopeClient({\n * apiKey: \"your-token\",\n * connectTimeout: 30000,\n * debug: process.env.NODE_ENV === \"development\",\n * });\n *\n * // REST request\n * const data = await client.get(\"/v2/some/endpoint\");\n *\n * // SSE streaming with namespaced access\n * const session = client.insights.instrument.stream({\n * instrument_id: \"abc123\",\n * });\n * session.subscribe({ onPatch: (data) => console.log(data) });\n * const result = await session.complete();\n *\n * // Atlas advice\n * const atlasSession = client.atlas.streamAdvice({ ... });\n * ```\n */\nexport class TelescopeClient {\n /** SDK version */\n static readonly VERSION = VERSION;\n\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly connectTimeout: number;\n private readonly debugEnabled: boolean;\n\n // Lazy-loaded product clients\n private _insights?: InsightsClientType;\n private _atlas?: AtlasClientType;\n private _news?: NewsClientType;\n\n constructor(options: TelescopeClientOptions = {}) {\n if (options.apiKey && options.clientToken) {\n throw new Error(\n \"Cannot provide both apiKey and clientToken. Use one or the other.\",\n );\n }\n\n const authToken =\n options.clientToken ?? options.apiKey ?? getEnvVar(\"TELESCOPE_API_KEY\");\n if (!authToken) {\n throw new Error(\n \"apiKey or clientToken is required. Provide it in options or set TELESCOPE_API_KEY environment variable.\",\n );\n }\n\n // Validate token prefix\n if (options.clientToken && !authToken.startsWith(\"tsc_\")) {\n console.warn(\n \"[Telescope] clientToken should start with 'tsc_'. The provided value may not be a valid client token.\",\n );\n }\n if (options.apiKey && !authToken.startsWith(\"tsk_\")) {\n console.warn(\n \"[Telescope] apiKey should start with 'tsk_'. The provided value may not be a valid API key.\",\n );\n }\n\n // Warn if using apiKey in a browser environment\n if (!options.clientToken && authToken && isBrowser()) {\n console.warn(\n \"[Telescope] Using apiKey in a browser environment is not recommended. Use clientToken instead.\",\n );\n }\n\n this.apiKey = authToken;\n this.baseUrl =\n options.baseUrl ??\n getEnvVar(\"TELESCOPE_BASE_URL\") ??\n DEFAULT_TELESCOPE_API_ORIGIN;\n this.connectTimeout = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;\n this.debugEnabled = options.debug ?? false;\n\n this.debug(\"TelescopeClient initialized\", {\n baseUrl: this.baseUrl,\n connectTimeout: this.connectTimeout,\n });\n }\n\n /**\n * Namespaced access to Insights API.\n *\n * @example\n * ```ts\n * const session = client.insights.instrument.stream({ instrument_id: \"...\" });\n * const data = await client.insights.instrument.generate({ instrument_id: \"...\" });\n * ```\n */\n get insights(): InsightsClientType {\n let client = this._insights;\n if (!client) {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { InsightsClient } = require(\"../insights/client\") as {\n InsightsClient: new (c: TelescopeClient) => InsightsClientType;\n };\n client = new InsightsClient(this);\n this._insights = client;\n }\n return client;\n }\n\n /**\n * Namespaced access to Atlas API.\n *\n * @example\n * ```ts\n * const session = client.atlas.streamAdvice({ advisor_environment_id: \"...\", ... });\n * const advice = await client.atlas.getAdvice(id);\n * ```\n */\n get atlas(): AtlasClientType {\n let client = this._atlas;\n if (!client) {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { AtlasClient } = require(\"../atlas/client\") as {\n AtlasClient: new (c: TelescopeClient) => AtlasClientType;\n };\n client = new AtlasClient(this);\n this._atlas = client;\n }\n return client;\n }\n\n /**\n * Namespaced access to News API.\n *\n * @example\n * ```ts\n * const latest = await client.news.latest.generate({ ... });\n * const session = client.news.trendingTopics.stream({ topic: \"global\" });\n * ```\n */\n get news(): NewsClientType {\n let client = this._news;\n if (!client) {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { NewsClient } = require(\"../news/client\") as {\n NewsClient: new (c: TelescopeClient) => NewsClientType;\n };\n client = new NewsClient(this);\n this._news = client;\n }\n return client;\n }\n\n /**\n * Log debug messages when debug mode is enabled.\n */\n private debug(message: string, data?: unknown): void {\n if (this.debugEnabled) {\n const timestamp = new Date().toISOString();\n if (data !== undefined) {\n // eslint-disable-next-line no-console\n console.log(`[Telescope ${timestamp}] ${message}`, data);\n } else {\n // eslint-disable-next-line no-console\n console.log(`[Telescope ${timestamp}] ${message}`);\n }\n }\n }\n\n /**\n * Generate a client token for frontend clients.\n * Typically called by backends using an organization API key.\n */\n async generateClientToken(): Promise<string> {\n this.debug(\"Generating client token\");\n\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort(\n new Error(`Request timed out after ${this.connectTimeout}ms`),\n );\n }, this.connectTimeout);\n\n try {\n const response = await fetch(\n buildApiUrl(this.baseUrl, \"/v2/client-tokens\"),\n {\n method: \"POST\",\n headers: {\n ...DEFAULT_JSON_HEADERS,\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({}),\n signal: abortController.signal,\n },\n );\n\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n this.debug(\"Failed to generate client token\", {\n status: response.status,\n });\n throw new ApiError(\n `Failed to generate client token: ${response.status} ${response.statusText} ${text}`,\n response.status,\n text,\n );\n }\n\n const payload = (await response.json()) as { token?: string };\n if (!payload.token) {\n throw new Error(\"Client token response missing token field\");\n }\n\n this.debug(\"Client token generated successfully\");\n return payload.token;\n } catch (error) {\n clearTimeout(timeoutId);\n throw error;\n }\n }\n\n /**\n * Make a generic HTTP request.\n */\n async request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const connectTimeout = options.connectTimeout ?? this.connectTimeout;\n const abortController = new AbortController();\n const combinedSignal = combineSignals(\n abortController.signal,\n options.signal,\n );\n\n const timeoutId = setTimeout(() => {\n abortController.abort(\n new Error(`Request timed out after ${connectTimeout}ms`),\n );\n }, connectTimeout);\n\n this.debug(`${options.method ?? \"GET\"} ${path}`, {\n connectTimeout,\n hasBody: options.body != null,\n });\n\n try {\n const url = buildApiUrl(this.baseUrl, path);\n const headers = new Headers(options.headers || {});\n if (!headers.has(\"Content-Type\") && options.body != null) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n headers.set(\"Authorization\", `Bearer ${this.apiKey}`);\n\n const response = await fetch(url, {\n ...options,\n headers,\n signal: combinedSignal,\n });\n\n // Connection established (headers received) — clear the connect timeout.\n // Body reading proceeds with no time limit.\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => \"\");\n let parsed: unknown = undefined;\n if (bodyText) {\n try {\n parsed = JSON.parse(bodyText);\n } catch {\n parsed = bodyText;\n }\n }\n throw new ApiError(\n `API request failed (${response.status})`,\n response.status,\n parsed,\n );\n }\n\n this.debug(`Response ${response.status} ${path}`);\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (!text) {\n return undefined as T;\n }\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n } catch (error: unknown) {\n clearTimeout(timeoutId);\n this.debug(`Request failed: ${path}`, error);\n\n if (error instanceof ApiError) {\n throw error;\n }\n if (error instanceof TelescopeClientError) {\n throw error;\n }\n // Handle connect timeout errors\n const isTimeout =\n abortController.signal.aborted &&\n abortController.signal.reason instanceof Error &&\n abortController.signal.reason.message.includes(\"timed out\");\n if (isTimeout) {\n throw new NetworkError(`Request timed out after ${connectTimeout}ms`);\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new NetworkError(`Network request failed: ${message}`);\n }\n }\n\n /**\n * Make a GET request.\n */\n async get<T>(path: string, options: RequestOptions = {}): Promise<T> {\n return this.request<T>(path, { ...options, method: \"GET\" });\n }\n\n /**\n * Make a POST request.\n */\n async post<T>(\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const payload =\n body === undefined || body === null ? undefined : JSON.stringify(body);\n return this.request<T>(path, {\n ...options,\n method: \"POST\",\n body: payload,\n headers: {\n ...DEFAULT_JSON_HEADERS,\n ...options.headers,\n },\n });\n }\n\n /**\n * Start an SSE streaming request.\n *\n * Returns a StreamSession immediately. Use the session to:\n * - Subscribe to events: `session.subscribe({ onPatch, onComplete })`\n * - Get current data: `session.data`\n * - Wait for completion: `await session.complete()`\n *\n * @example\n * ```ts\n * const session = client.stream<InsightsResponse>(\"/v2/insights/instrument\", {\n * instrument_id: \"abc123\",\n * stream: true,\n * });\n *\n * session.subscribe({\n * onPatch: (data) => console.log(\"Progress:\", data),\n * onComplete: (data) => console.log(\"Done:\", data),\n * });\n *\n * const result = await session.complete();\n * ```\n */\n stream<T>(\n path: string,\n body: unknown,\n options: StreamOptions = {},\n ): StreamSession<T> {\n const session = new StreamSession<T>();\n session.markPending();\n\n // Execute streaming in background\n this.executeStream(path, body, session, options);\n\n return session;\n }\n\n /**\n * Create a StreamSession from a pre-existing SSE response.\n * Use this when you have a response from a proxy or custom fetch.\n *\n * @example\n * ```ts\n * // Customer proxy scenario\n * const response = await fetch(\"/my-proxy/insights\", { method: \"POST\", body: JSON.stringify({ portfolio_id: \"xyz\" }) });\n * const session = client.streamFromResponse<PortfolioInsightsResponse>(response);\n * session.subscribe({ onPatch: (data) => console.log(data) });\n * const result = await session.complete();\n * ```\n */\n streamFromResponse<T>(source: SSESource): StreamSession<T> {\n const session = new StreamSession<T>();\n session.markPending();\n\n // Process the SSE response in background\n this.processSSEResponse(source, session);\n\n return session;\n }\n\n /**\n * Internal: Process an SSE response and route events to the session.\n */\n private async processSSEResponse<T>(\n source: SSESource,\n session: StreamSession<T>,\n ): Promise<void> {\n try {\n await processSSEStream(\n source,\n (message: EventSourceMessage) => {\n // Parse the raw SSE data\n let rawData: unknown;\n if (typeof message.data === \"string\" && message.data !== \"\") {\n try {\n rawData = JSON.parse(message.data);\n } catch {\n rawData = message.data;\n }\n } else {\n rawData = message.data;\n }\n\n // Parse into typed SSEEvent\n const event = parseEvent(message.event || \"message\", rawData);\n if (event) {\n session.processEvent(event);\n }\n // Unknown events are ignored per spec\n },\n {\n onclose: () => {\n // Connection closed - if not already completed, mark as complete\n if (\n session.status.phase !== \"completed\" &&\n session.status.phase !== \"error\"\n ) {\n session.processEvent({ event: \"complete\", data: {} });\n }\n },\n onerror: (error) => {\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n `Streaming connection failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n },\n },\n );\n } catch (error: unknown) {\n if (\n session.status.phase === \"error\" ||\n session.status.phase === \"completed\"\n ) {\n // Already handled\n return;\n }\n\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n `Streaming failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n }\n }\n\n /**\n * Internal: Execute the SSE stream and route events to the session.\n */\n private async executeStream<T>(\n path: string,\n body: unknown,\n session: StreamSession<T>,\n options: StreamOptions,\n ): Promise<void> {\n const abortController = new AbortController();\n const unlink = linkAbortSignals(abortController, options.signal);\n\n // Connection timeout - aborts if the stream doesn't open in time.\n // Cancelled once the connection is established (onopen fires).\n const connectionTimeout = options.connectTimeout ?? this.connectTimeout;\n const timeoutId = setTimeout(() => {\n if (session.status.phase === \"pending\") {\n abortController.abort(\n new Error(`Stream connection timed out after ${connectionTimeout}ms`),\n );\n }\n }, connectionTimeout);\n\n try {\n await fetchEventSource(buildApiUrl(this.baseUrl, path), {\n method: \"POST\",\n body: JSON.stringify(body ?? {}),\n headers: {\n ...DEFAULT_JSON_HEADERS,\n Accept: \"text/event-stream\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n signal: abortController.signal,\n openWhenHidden: true,\n\n async onopen(response) {\n clearTimeout(timeoutId);\n const contentType = response.headers.get(\"content-type\") || \"\";\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new StreamingError(\n `Stream failed to open (${response.status}) ${text}`,\n response.status,\n );\n }\n if (!contentType.includes(\"text/event-stream\")) {\n throw new StreamingError(\n `Unexpected content-type for stream: ${contentType}`,\n response.status,\n );\n }\n },\n\n onmessage: (message: EventSourceMessage) => {\n // Parse the raw SSE data\n let rawData: unknown;\n if (typeof message.data === \"string\" && message.data !== \"\") {\n try {\n rawData = JSON.parse(message.data);\n } catch {\n rawData = message.data;\n }\n } else {\n rawData = message.data;\n }\n\n // Parse into typed SSEEvent\n const event = parseEvent(message.event || \"message\", rawData);\n if (event) {\n session.processEvent(event);\n }\n // Unknown events are ignored per spec\n },\n\n onclose: () => {\n // Connection closed - if not already completed, mark as complete\n if (\n session.status.phase !== \"completed\" &&\n session.status.phase !== \"error\"\n ) {\n session.processEvent({ event: \"complete\", data: {} });\n }\n },\n\n onerror: (error) => {\n if (abortController.signal.aborted) {\n return;\n }\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n `Streaming connection failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n throw wrapped;\n },\n });\n } catch (error: unknown) {\n if (\n session.status.phase === \"error\" ||\n session.status.phase === \"completed\"\n ) {\n // Already handled\n return;\n }\n\n // Surface a clear message when the connection timeout fired\n const isTimeout =\n abortController.signal.aborted &&\n abortController.signal.reason instanceof Error &&\n abortController.signal.reason.message.includes(\"timed out\");\n\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n isTimeout\n ? `Stream connection timed out after ${connectionTimeout}ms`\n : `Streaming failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n } finally {\n clearTimeout(timeoutId);\n unlink?.();\n }\n }\n}\n\n// --- Helpers ---\n\n/**\n * Link an external abort signal to a controller.\n * Returns cleanup function or null.\n */\nfunction linkAbortSignals(\n primary: AbortController,\n external?: AbortSignal | null,\n): (() => void) | null {\n if (!external) {\n return null;\n }\n const abortHandler = () => {\n primary.abort(external.reason);\n };\n if (external.aborted) {\n primary.abort(external.reason);\n return null;\n }\n external.addEventListener(\"abort\", abortHandler, { once: true });\n return () => external.removeEventListener(\"abort\", abortHandler);\n}\n\n/**\n * Combine multiple abort signals into one.\n * The combined signal aborts when any of the input signals abort.\n */\nfunction combineSignals(\n ...signals: (AbortSignal | null | undefined)[]\n): AbortSignal {\n const controller = new AbortController();\n for (const signal of signals) {\n if (signal) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\n }\n signal.addEventListener(\"abort\", () => controller.abort(signal.reason), {\n once: true,\n });\n }\n }\n return controller.signal;\n}\n\nfunction buildApiUrl(baseUrl: string, path: string): string {\n const normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n return new URL(path, normalizedBase).toString();\n}\n\n// --- Environment helpers ---\n\n/**\n * Safely get an environment variable in Node.js/Bun runtimes.\n * Returns undefined in browser environments.\n */\nfunction getEnvVar(name: string): string | undefined {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env[name];\n }\n return undefined;\n}\n\n/** Detect browser environment */\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-CQX8RsT6.mjs","names":[],"sources":["../src/core/errors.ts","../src/core/fetch-event-source.ts","../src/core/types.ts","../src/core/response-builder.ts","../src/core/stream-session.ts","../src/insights/instrument/client.ts","../src/insights/portfolio/client.ts","../src/insights/watchlist/client.ts","../src/insights/market/client.ts","../src/insights/trending/client.ts","../src/insights/client.ts","../src/atlas/client.ts","../src/news/client.ts","../src/core/client.ts"],"sourcesContent":["/**\n * Telescope Client Error Hierarchy\n *\n * Provides typed error classes for different failure scenarios\n * when interacting with the Telescope API.\n */\n\nimport type { ErrorEventData } from \"./types\";\n\n/** Base error class for all Telescope client errors */\nexport class TelescopeClientError extends Error {\n constructor(\n message: string,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = this.constructor.name;\n }\n}\n\n/** Error resolving or validating client token */\nexport class TokenError extends TelescopeClientError {}\n\n/** HTTP API returned an error response */\nexport class ApiError extends TelescopeClientError {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n }\n}\n\n/** Network-level error (connection failed, DNS, etc.) */\nexport class NetworkError extends TelescopeClientError {}\n\n/** Error during SSE streaming */\nexport class StreamingError extends TelescopeClientError {\n constructor(\n message: string,\n public readonly status?: number,\n cause?: unknown,\n ) {\n super(message, cause);\n }\n}\n\n/** Error received via SSE error event from the API */\nexport class SSEError extends TelescopeClientError {\n public readonly code: string;\n public readonly httpStatus: number;\n public readonly path?: string[];\n public readonly additionalData?: Record<string, unknown>;\n\n constructor(errorData: ErrorEventData) {\n super(errorData.message);\n this.code = errorData.code;\n this.httpStatus = errorData.http_status;\n this.path = errorData.path;\n this.additionalData = errorData.additional_data;\n }\n}\n","/**\n * Minimal SSE (Server-Sent Events) client that supports POST requests.\n * Replacement for @microsoft/fetch-event-source.\n */\n\nimport type { SSESource } from \"./types\";\n\nexport interface EventSourceMessage {\n event: string;\n data: string;\n id: string;\n retry?: number;\n}\n\nexport interface FetchEventSourceOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n signal?: AbortSignal;\n openWhenHidden?: boolean;\n onopen?: (response: Response) => Promise<void>;\n onmessage?: (message: EventSourceMessage) => void;\n onclose?: () => void;\n onerror?: (error: unknown) => void;\n}\n\nexport interface ProcessSSEStreamOptions {\n onclose?: () => void;\n onerror?: (error: unknown) => void;\n signal?: AbortSignal;\n}\n\n/**\n * Parse SSE stream data into individual messages.\n * SSE format: fields separated by newlines, messages separated by blank lines.\n * Fields: event:, data:, id:, retry:\n */\nfunction parseSSEMessage(raw: string): EventSourceMessage | null {\n const lines = raw.split(\"\\n\");\n let event = \"message\";\n const dataLines: string[] = [];\n let id = \"\";\n let retry: number | undefined;\n\n for (const line of lines) {\n if (line.startsWith(\"event:\")) {\n event = line.slice(6).trim();\n } else if (line.startsWith(\"data:\")) {\n dataLines.push(line.slice(5).trimStart());\n } else if (line.startsWith(\"id:\")) {\n id = line.slice(3).trim();\n } else if (line.startsWith(\"retry:\")) {\n const val = parseInt(line.slice(6).trim(), 10);\n if (!isNaN(val)) {\n retry = val;\n }\n }\n // Lines starting with : are comments, ignored\n // Other lines are ignored per SSE spec\n }\n\n // If no data lines, this wasn't a valid message\n if (dataLines.length === 0) {\n return null;\n }\n\n return {\n event,\n data: dataLines.join(\"\\n\"),\n id,\n retry,\n };\n}\n\n/**\n * Process an SSE stream from a pre-existing source (Response or readable stream).\n * This allows parsing SSE responses that were fetched externally (e.g., via a proxy).\n */\nexport async function processSSEStream(\n source: SSESource,\n onmessage: (message: EventSourceMessage) => void,\n options?: ProcessSSEStreamOptions,\n): Promise<void> {\n const { onclose, onerror, signal } = options ?? {};\n\n const body = source.body;\n if (!body) {\n const err = new Error(\"Source body is not readable\");\n onerror?.(err);\n throw err;\n }\n\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n // eslint-disable-next-line no-await-in-loop\n const { done, value } = await reader.read();\n\n if (done) {\n // Process any remaining buffer content\n if (buffer.trim()) {\n const message = parseSSEMessage(buffer);\n if (message) {\n onmessage(message);\n }\n }\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n\n // SSE messages are separated by double newlines\n const parts = buffer.split(\"\\n\\n\");\n\n // Keep the last part in buffer (may be incomplete)\n buffer = parts.pop() || \"\";\n\n // Process complete messages\n for (const part of parts) {\n if (!part.trim()) continue;\n const message = parseSSEMessage(part);\n if (message) {\n onmessage(message);\n }\n }\n }\n } catch (err) {\n // Don't report abort errors\n if (signal?.aborted) {\n return;\n }\n onerror?.(err);\n throw err;\n } finally {\n onclose?.();\n }\n}\n\n/**\n * Fetch-based SSE client that supports POST requests.\n */\nexport async function fetchEventSource(\n url: string,\n options: FetchEventSourceOptions,\n): Promise<void> {\n const {\n method = \"GET\",\n headers = {},\n body,\n signal,\n onopen,\n onmessage,\n onclose,\n onerror,\n } = options;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body,\n signal,\n });\n } catch (err) {\n onerror?.(err);\n throw err;\n }\n\n // Call onopen to let caller validate response\n // Clone response so onopen can read body without affecting our stream\n if (onopen) {\n try {\n await onopen(response.clone());\n } catch (err) {\n onerror?.(err);\n throw err;\n }\n }\n\n await processSSEStream(response, onmessage ?? (() => {}), {\n onclose,\n onerror,\n signal,\n });\n}\n","/**\n * Telescope SSE Format v2 - Type Definitions\n *\n * This module defines types for the v2 SSE streaming format which uses\n * path-based patch operations instead of flat field events.\n */\n\n/**\n * Source for SSE streaming - either a fetch Response or an object with a readable stream.\n * Used for parsing pre-made SSE responses (e.g., from customer proxy scenarios).\n */\nexport type SSESource =\n | Response\n | { body: ReadableStream<Uint8Array>; headers?: Headers };\n\n/** Path segment - string representing object key or array index */\nexport type PathSegment = string;\n\n/** Non-empty array of path segments for addressing values in the response */\nexport type Path = [PathSegment, ...PathSegment[]];\n\n/** JSON-compatible value types */\nexport type JSONValue =\n | string\n | number\n | boolean\n | null\n | JSONValue[]\n | { [key: string]: JSONValue };\n\n/** Patch operation types */\nexport type PatchOperation =\n | \"set\"\n | \"append_string\"\n | \"append_array\"\n | \"delete\";\n\n/** Patch event data - the core mutation instruction */\nexport interface PatchEventData {\n operation: PatchOperation;\n path: Path;\n value?: JSONValue;\n}\n\n/** Path complete event data */\nexport interface PathCompleteEventData {\n path: Path;\n}\n\n/** Error event data from the API */\nexport interface ErrorEventData {\n code: string;\n message: string;\n http_status: number;\n path?: Path;\n additional_data?: Record<string, unknown>;\n}\n\n/** Heartbeat event data (empty) */\nexport type HeartbeatEventData = Record<string, never>;\n\n/** Complete event data (empty) */\nexport type CompleteEventData = Record<string, never>;\n\n/** Union of all SSE events */\nexport type SSEEvent =\n | { event: \"patch\"; data: PatchEventData }\n | { event: \"path_complete\"; data: PathCompleteEventData }\n | { event: \"error\"; data: ErrorEventData }\n | { event: \"heartbeat\"; data: HeartbeatEventData }\n | { event: \"complete\"; data: CompleteEventData };\n\n/** Extract the event name from an SSEEvent */\nexport type SSEEventName = SSEEvent[\"event\"];\n\n/**\n * Check if a path segment should be interpreted as an array index.\n * Per spec: segment is array index if it consists only of digits.\n */\nexport function isArrayIndex(segment: string): boolean {\n return /^\\d+$/.test(segment);\n}\n\n/**\n * Convert a path to a string key for use in Sets/Maps.\n */\nexport function pathToKey(path: Path): string {\n return JSON.stringify(path);\n}\n\n/**\n * Parse a raw SSE message into a typed SSEEvent.\n * Returns null for unknown event types (per spec: clients MUST ignore unknown events).\n */\nexport function parseEvent(\n eventName: string,\n rawData: unknown,\n): SSEEvent | null {\n switch (eventName.toLowerCase()) {\n case \"patch\": {\n const data = rawData as Record<string, unknown>;\n return {\n event: \"patch\",\n data: {\n operation: data.operation as PatchOperation,\n path: data.path as Path,\n value: data.value as JSONValue | undefined,\n },\n };\n }\n case \"path_complete\": {\n const data = rawData as Record<string, unknown>;\n return {\n event: \"path_complete\",\n data: { path: data.path as Path },\n };\n }\n case \"error\": {\n const data = rawData as Record<string, unknown>;\n return {\n event: \"error\",\n data: {\n code: data.code as string,\n message: data.message as string,\n http_status: (data.http_status ?? data.status_code ?? 0) as number,\n path: data.path as Path | undefined,\n additional_data: data.additional_data as\n | Record<string, unknown>\n | undefined,\n },\n };\n }\n case \"heartbeat\":\n return { event: \"heartbeat\", data: {} };\n case \"complete\":\n return { event: \"complete\", data: {} };\n default:\n // Per spec: clients MUST ignore unknown event types\n return null;\n }\n}\n","/**\n * ResponseBuilder - Core V2 SSE patch application logic\n *\n * Builds a response object by applying patch operations from SSE events.\n * This is the \"meat\" of the v2 client - all patch logic lives here.\n */\n\nimport {\n type Path,\n type PatchEventData,\n type JSONValue,\n isArrayIndex,\n} from \"./types\";\n\n/**\n * ResponseBuilder accumulates patch operations into a response object.\n *\n * Design notes:\n * - Uses mutation internally for simplicity\n * - React re-renders are triggered by observer callbacks in StreamSession\n * - Path resolution follows v2 spec: all-digit segments are array indices\n */\nexport class ResponseBuilder<T = Record<string, unknown>> {\n private data: T;\n\n constructor(initialData?: T) {\n this.data = (initialData ? structuredClone(initialData) : {}) as T;\n }\n\n /**\n * Apply a patch operation to the response data.\n */\n applyPatch(patch: PatchEventData): void {\n const { operation, path, value } = patch;\n\n switch (operation) {\n case \"set\":\n this.setAtPath(path, value as JSONValue);\n break;\n case \"append_string\":\n this.appendStringAtPath(path, value as string);\n break;\n case \"append_array\":\n this.appendArrayAtPath(path, value as JSONValue);\n break;\n case \"delete\":\n this.deleteAtPath(path);\n break;\n }\n }\n\n /**\n * Get the current accumulated data.\n */\n getData(): T {\n return this.data;\n }\n\n /**\n * Set a value at the given path, creating intermediate containers as needed.\n *\n * Container creation follows the v2 spec:\n * - If next segment is array index (all digits), create array\n * - Otherwise, create object\n */\n private setAtPath(path: Path, value: JSONValue): void {\n const parent = this.ensureParentPath(path);\n const lastSegment = path[path.length - 1];\n\n if (Array.isArray(parent)) {\n parent[parseInt(lastSegment, 10)] = value;\n } else {\n (parent as Record<string, unknown>)[lastSegment] = value;\n }\n }\n\n /**\n * Append a string to the value at path.\n * If value doesn't exist, initializes to empty string first.\n */\n private appendStringAtPath(path: Path, chunk: string): void {\n const parent = this.ensureParentPath(path);\n const lastSegment = path[path.length - 1];\n\n let current: unknown;\n if (Array.isArray(parent)) {\n current = parent[parseInt(lastSegment, 10)];\n } else {\n current = (parent as Record<string, unknown>)[lastSegment];\n }\n\n // Initialize to empty string if doesn't exist\n const newValue = (typeof current === \"string\" ? current : \"\") + chunk;\n\n if (Array.isArray(parent)) {\n parent[parseInt(lastSegment, 10)] = newValue;\n } else {\n (parent as Record<string, unknown>)[lastSegment] = newValue;\n }\n }\n\n /**\n * Append an element to the array at path.\n * If array doesn't exist, initializes to empty array first.\n */\n private appendArrayAtPath(path: Path, element: JSONValue): void {\n const parent = this.ensureParentPath(path);\n const lastSegment = path[path.length - 1];\n\n let current: unknown;\n if (Array.isArray(parent)) {\n current = parent[parseInt(lastSegment, 10)];\n } else {\n current = (parent as Record<string, unknown>)[lastSegment];\n }\n\n // Initialize to empty array if doesn't exist\n const arr = Array.isArray(current) ? current : [];\n arr.push(element);\n\n if (Array.isArray(parent)) {\n parent[parseInt(lastSegment, 10)] = arr;\n } else {\n (parent as Record<string, unknown>)[lastSegment] = arr;\n }\n }\n\n /**\n * Delete/unset the value at path.\n * - For object keys: removes the property\n * - For array indices: sets to null (preserves indices per spec)\n */\n private deleteAtPath(path: Path): void {\n const parent = this.navigateToParent(path);\n if (parent === null) {\n // Path doesn't exist, nothing to delete\n return;\n }\n\n const lastSegment = path[path.length - 1];\n\n if (Array.isArray(parent)) {\n // Set to null, preserve indices (per spec)\n parent[parseInt(lastSegment, 10)] = null;\n } else {\n // Remove property from object\n delete (parent as Record<string, unknown>)[lastSegment];\n }\n }\n\n /**\n * Navigate to the parent container of the target path, creating containers as needed.\n * Returns the parent container where the final segment should be set.\n */\n private ensureParentPath(path: Path): unknown[] | Record<string, unknown> {\n let current: unknown = this.data;\n\n for (let i = 0; i < path.length - 1; i++) {\n const segment = path[i];\n const nextSegment = path[i + 1];\n const nextIsArrayIndex = isArrayIndex(nextSegment);\n\n let next: unknown;\n if (Array.isArray(current)) {\n next = current[parseInt(segment, 10)];\n } else {\n next = (current as Record<string, unknown>)[segment];\n }\n\n // Create container if doesn't exist or is wrong type\n if (next === undefined || next === null) {\n next = nextIsArrayIndex ? [] : {};\n if (Array.isArray(current)) {\n current[parseInt(segment, 10)] = next;\n } else {\n (current as Record<string, unknown>)[segment] = next;\n }\n }\n\n current = next;\n }\n\n return current as unknown[] | Record<string, unknown>;\n }\n\n /**\n * Navigate to the parent container without creating containers.\n * Returns null if the path doesn't exist.\n */\n private navigateToParent(\n path: Path,\n ): unknown[] | Record<string, unknown> | null {\n let current: unknown = this.data;\n\n for (let i = 0; i < path.length - 1; i++) {\n const segment = path[i];\n\n let next: unknown;\n if (Array.isArray(current)) {\n next = current[parseInt(segment, 10)];\n } else if (typeof current === \"object\" && current !== null) {\n next = (current as Record<string, unknown>)[segment];\n } else {\n return null;\n }\n\n if (next === undefined || next === null) {\n return null;\n }\n\n current = next;\n }\n\n return current as unknown[] | Record<string, unknown>;\n }\n}\n","/**\n * StreamSession - Manages streaming lifecycle and observers\n *\n * Composes ResponseBuilder to accumulate data while providing:\n * - Lifecycle phase tracking (idle -> pending -> streaming -> completed/error)\n * - Observer pattern for React integration\n * - Path completion tracking\n * - Promise-based completion\n */\n\nimport {\n type SSEEvent,\n type Path,\n type ErrorEventData,\n type PatchEventData,\n pathToKey,\n} from \"./types\";\nimport { ResponseBuilder } from \"./response-builder\";\nimport { SSEError, StreamingError } from \"./errors\";\n\n/** Stream lifecycle phases */\nexport type StreamPhase =\n | \"idle\"\n | \"pending\"\n | \"streaming\"\n | \"completed\"\n | \"error\";\n\n/** Stream status with timing and error information */\nexport interface StreamStatus {\n phase: StreamPhase;\n startedAt?: number;\n completedAt?: number;\n error?: SSEError | StreamingError;\n}\n\n/** Observer interface for stream events */\nexport interface StreamObserver<T> {\n /** Called on every patch event with updated data */\n onPatch?: (data: T, event: SSEEvent) => void;\n /** Called when a path is marked complete */\n onPathComplete?: (path: Path, data: T) => void;\n /** Called on SSE error events (may be fatal or non-fatal) */\n onError?: (error: ErrorEventData) => void;\n /** Called on heartbeat events */\n onHeartbeat?: () => void;\n /** Called when stream completes successfully */\n onComplete?: (data: T) => void;\n /** Called when stream fails due to transport error */\n onTransportError?: (error: StreamingError) => void;\n /** Called whenever status changes */\n onStatusChange?: (status: StreamStatus) => void;\n}\n\n/**\n * StreamSession manages a single streaming request.\n *\n * Usage:\n * ```ts\n * const session = new StreamSession<MyResponse>();\n * session.subscribe({ onPatch: (data) => console.log(data) });\n * // Transport layer calls session.processEvent() for each SSE event\n * const result = await session.complete();\n * ```\n */\nexport class StreamSession<T = Record<string, unknown>> {\n private readonly builder: ResponseBuilder<T>;\n private _status: StreamStatus = { phase: \"idle\" };\n private readonly observers = new Set<StreamObserver<T>>();\n private readonly completedPaths = new Set<string>();\n private readonly completionPromise: Promise<T>;\n private completionResolve!: (value: T) => void;\n private completionReject!: (reason: unknown) => void;\n\n constructor(initialData?: T) {\n this.builder = new ResponseBuilder<T>(initialData);\n this.completionPromise = new Promise<T>((resolve, reject) => {\n this.completionResolve = resolve;\n this.completionReject = reject;\n });\n }\n\n /**\n * Subscribe to stream events.\n * Returns an unsubscribe function.\n */\n subscribe(observer: StreamObserver<T>): () => void {\n this.observers.add(observer);\n return () => this.observers.delete(observer);\n }\n\n /** Get the current accumulated data */\n get data(): T {\n return this.builder.getData();\n }\n\n /** Get the current stream status */\n get status(): StreamStatus {\n return this._status;\n }\n\n /**\n * Returns a promise that resolves when the stream completes.\n * Rejects if the stream fails with an error.\n */\n complete(): Promise<T> {\n return this.completionPromise;\n }\n\n /**\n * Check if a specific path has been marked complete.\n */\n isPathComplete(path: Path): boolean {\n return this.completedPaths.has(pathToKey(path));\n }\n\n /**\n * Get all completed paths.\n */\n getCompletedPaths(): Path[] {\n return Array.from(this.completedPaths).map(\n (key) => JSON.parse(key) as Path,\n );\n }\n\n // --- Lifecycle methods (called by transport layer) ---\n\n /**\n * Mark the session as pending (request started but no data yet).\n */\n markPending(): void {\n if (this._status.phase === \"idle\") {\n this.setStatus({ phase: \"pending\", startedAt: Date.now() });\n }\n }\n\n /**\n * Process an incoming SSE event.\n * Called by the transport layer for each event received.\n */\n processEvent(event: SSEEvent): void {\n // Ignore events after session has ended (except heartbeats for logging purposes are still ignored)\n if (this._status.phase === \"completed\" || this._status.phase === \"error\") {\n return;\n }\n\n switch (event.event) {\n case \"patch\":\n this.handlePatch(event.data);\n break;\n case \"path_complete\":\n this.handlePathComplete(event.data.path);\n break;\n case \"error\":\n this.handleError(event.data);\n break;\n case \"heartbeat\":\n this.handleHeartbeat();\n break;\n case \"complete\":\n this.handleComplete();\n break;\n }\n }\n\n /**\n * Handle transport-level error (connection failure, timeout, etc.).\n */\n handleTransportError(error: StreamingError): void {\n if (this._status.phase === \"completed\" || this._status.phase === \"error\") {\n return;\n }\n\n this.setStatus({\n phase: \"error\",\n startedAt: this._status.startedAt,\n completedAt: Date.now(),\n error,\n });\n\n this.notifyObservers((obs) => obs.onTransportError?.(error));\n this.completionReject(error);\n }\n\n // --- Private event handlers ---\n\n private handlePatch(patch: PatchEventData): void {\n // Transition to streaming on first data event\n if (this._status.phase === \"pending\" || this._status.phase === \"idle\") {\n this.setStatus({\n phase: \"streaming\",\n startedAt: this._status.startedAt ?? Date.now(),\n });\n }\n\n this.builder.applyPatch(patch);\n const data = this.builder.getData();\n\n this.notifyObservers((obs) =>\n obs.onPatch?.(data, { event: \"patch\", data: patch }),\n );\n }\n\n private handlePathComplete(path: Path): void {\n this.completedPaths.add(pathToKey(path));\n const data = this.builder.getData();\n this.notifyObservers((obs) => obs.onPathComplete?.(path, data));\n }\n\n private handleError(errorData: ErrorEventData): void {\n // Notify observers of the error\n this.notifyObservers((obs) => obs.onError?.(errorData));\n\n // Transition to error phase so hasError becomes true.\n const sseError = new SSEError(errorData);\n this.setStatus({\n phase: \"error\",\n startedAt: this._status.startedAt,\n completedAt: Date.now(),\n error: sseError,\n });\n\n this.completionReject(sseError);\n }\n\n private handleHeartbeat(): void {\n this.notifyObservers((obs) => obs.onHeartbeat?.());\n }\n\n private handleComplete(): void {\n if (this._status.phase === \"completed\" || this._status.phase === \"error\") {\n return;\n }\n\n this.setStatus({\n phase: \"completed\",\n startedAt: this._status.startedAt,\n completedAt: Date.now(),\n });\n\n const data = this.builder.getData();\n this.notifyObservers((obs) => obs.onComplete?.(data));\n this.completionResolve(data);\n }\n\n private setStatus(status: StreamStatus): void {\n this._status = status;\n this.notifyObservers((obs) => obs.onStatusChange?.(status));\n }\n\n private notifyObservers(callback: (obs: StreamObserver<T>) => void): void {\n // Iterate over a copy to allow safe modification during iteration\n for (const observer of Array.from(this.observers)) {\n try {\n callback(observer);\n } catch (e) {\n console.error(\"Observer callback error:\", e);\n }\n }\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type { Instrument } from \"../../instruments/client\";\n\n// --- Types ---\n\nexport interface InsightGroup {\n title: string;\n content: string;\n}\n\nexport interface InsightCitation {\n id?: string;\n title?: string;\n url?: string;\n source?: string;\n published_at?: string;\n}\n\nexport interface InstrumentInsightsResponse {\n instrument: Instrument;\n groups: InsightGroup[];\n citations?: InsightCitation[];\n updated_at: string;\n}\n\nexport interface InstrumentInsightsRequest {\n instrument_id?: string;\n instrument_reference_id1?: string;\n instrument_reference_id2?: string;\n instrument_group_ids: string[];\n locale?: string;\n profile?: string;\n stream?: boolean;\n}\n\n// --- Client ---\n\nexport class InstrumentInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<InstrumentInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<InstrumentInsightsResponse> {\n return this.client.stream<InstrumentInsightsResponse>(\n \"/v2/insights/instrument\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<InstrumentInsightsRequest, \"stream\">,\n ): Promise<InstrumentInsightsResponse> {\n return this.client.post<InstrumentInsightsResponse>(\n \"/v2/insights/instrument\",\n { ...request, stream: false },\n );\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<InstrumentInsightsResponse> {\n return this.client.streamFromResponse<InstrumentInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<InstrumentInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type { InstrumentInsightsResponse } from \"../instrument/client\";\n\n// --- Types ---\n\nexport interface InsightsError {\n status_code: number;\n message: string;\n code: string;\n additional_data?: Record<string, unknown>;\n}\n\nexport interface PortfolioInsightsResponse {\n heading?: string;\n overview?: string;\n instruments?: InstrumentInsightsResponse[];\n errors?: InsightsError[];\n updated_at: string;\n}\n\nexport interface PortfolioInsightsRequest {\n instrument_ids?: string[];\n instrument_reference_id1s?: string[];\n instrument_reference_id2s?: string[];\n instrument_group_ids: string[];\n instrument_weightings?: Record<string, number>;\n omit_instruments_output?: boolean;\n locale?: string;\n profile?: string;\n stream?: boolean;\n}\n\n// --- Client ---\n\nexport class PortfolioInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<PortfolioInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<PortfolioInsightsResponse> {\n return this.client.stream<PortfolioInsightsResponse>(\n \"/v2/insights/portfolio\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<PortfolioInsightsRequest, \"stream\">,\n ): Promise<PortfolioInsightsResponse> {\n return this.client.post<PortfolioInsightsResponse>(\n \"/v2/insights/portfolio\",\n { ...request, stream: false },\n );\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<PortfolioInsightsResponse> {\n return this.client.streamFromResponse<PortfolioInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<PortfolioInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type {\n PortfolioInsightsRequest,\n PortfolioInsightsResponse,\n} from \"../portfolio/client\";\n\n// --- Types ---\n\nexport type WatchlistInsightsRequest = PortfolioInsightsRequest;\nexport type WatchlistInsightsResponse = PortfolioInsightsResponse;\n\n// --- Client ---\n\nexport class WatchlistInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<WatchlistInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<WatchlistInsightsResponse> {\n return this.client.stream<WatchlistInsightsResponse>(\n \"/v2/insights/watchlist\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<WatchlistInsightsRequest, \"stream\">,\n ): Promise<WatchlistInsightsResponse> {\n return this.client.post<WatchlistInsightsResponse>(\n \"/v2/insights/watchlist\",\n { ...request, stream: false },\n );\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<WatchlistInsightsResponse> {\n return this.client.streamFromResponse<WatchlistInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<WatchlistInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\nimport type { InsightGroup } from \"../instrument/client\";\n\n// --- Types ---\n\nexport interface MarketInsightsRequest {\n profile: string;\n locale?: string;\n stream?: boolean;\n}\n\nexport interface MarketInsightsResponse {\n groups: InsightGroup[];\n updated_at: string;\n}\n\n// --- Client ---\n\nexport class MarketInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<MarketInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<MarketInsightsResponse> {\n return this.client.stream<MarketInsightsResponse>(\n \"/v2/insights/market\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<MarketInsightsRequest, \"stream\">,\n ): Promise<MarketInsightsResponse> {\n return this.client.post<MarketInsightsResponse>(\"/v2/insights/market\", {\n ...request,\n stream: false,\n });\n }\n\n streamFromResponse(source: SSESource): StreamSession<MarketInsightsResponse> {\n return this.client.streamFromResponse<MarketInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<MarketInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient, type StreamOptions } from \"../../core/client\";\nimport { ApiError } from \"../../core/errors\";\nimport type { StreamSession } from \"../../core/stream-session\";\nimport type { SSESource } from \"../../core/types\";\n\n// --- Types ---\n\nexport type TrendingInsightsFocus =\n | \"global\"\n | \"crypto\"\n | \"new-zealand\"\n | \"australia\"\n | \"south-africa\"\n | \"singapore\"\n | \"united-kingdom\"\n | \"europe\"\n | \"united-states\";\n\nexport interface TrendingStory {\n headline: string;\n prompt: string;\n summary: string;\n}\n\nexport interface TrendingInsightsRequest {\n focus?: TrendingInsightsFocus;\n limit?: number;\n locale?: string;\n stream?: boolean;\n}\n\nexport interface TrendingInsightsResponse {\n stories: TrendingStory[];\n updated_at: string;\n}\n\n// --- Client ---\n\nexport class TrendingInsightsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n stream(\n request: Omit<TrendingInsightsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<TrendingInsightsResponse> {\n return this.client.stream<TrendingInsightsResponse>(\n \"/v2/insights/trending\",\n { ...request, stream: true },\n options,\n );\n }\n\n async generate(\n request: Omit<TrendingInsightsRequest, \"stream\">,\n ): Promise<TrendingInsightsResponse> {\n return this.client.post<TrendingInsightsResponse>(\"/v2/insights/trending\", {\n ...request,\n stream: false,\n });\n }\n\n streamFromResponse(\n source: SSESource,\n ): StreamSession<TrendingInsightsResponse> {\n return this.client.streamFromResponse<TrendingInsightsResponse>(source);\n }\n\n async generateFromResponse(\n response: Response,\n ): Promise<TrendingInsightsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n","import { TelescopeClient } from \"../core/client\";\nimport { InstrumentInsightsClient } from \"./instrument/client\";\nimport { PortfolioInsightsClient } from \"./portfolio/client\";\nimport { WatchlistInsightsClient } from \"./watchlist/client\";\nimport { MarketInsightsClient } from \"./market/client\";\nimport { TrendingInsightsClient } from \"./trending/client\";\n\nexport * from \"./instrument/client\";\nexport * from \"./portfolio/client\";\nexport * from \"./watchlist/client\";\nexport * from \"./market/client\";\nexport * from \"./trending/client\";\n\nexport class InsightsClient {\n readonly instrument: InstrumentInsightsClient;\n readonly portfolio: PortfolioInsightsClient;\n readonly watchlist: WatchlistInsightsClient;\n readonly market: MarketInsightsClient;\n readonly trending: TrendingInsightsClient;\n\n constructor(client: TelescopeClient) {\n this.instrument = new InstrumentInsightsClient(client);\n this.portfolio = new PortfolioInsightsClient(client);\n this.watchlist = new WatchlistInsightsClient(client);\n this.market = new MarketInsightsClient(client);\n this.trending = new TrendingInsightsClient(client);\n }\n}\n","/**\n * Atlas Product Client\n *\n * Provides typed access to the Atlas v2 API endpoints for\n * generating AI-powered financial advice.\n */\n\nimport { TelescopeClient, type StreamOptions } from \"../core/client\";\nimport type { StreamSession } from \"../core/stream-session\";\nimport type { SSESource } from \"../core/types\";\n\n// --- Types ---\n\n/** Request parameters for Atlas advice generation */\nexport interface AtlasAdviceRequest {\n /** Advisor environment ID (UUID) */\n advisor_environment_id: string;\n /** Client demographic and personal details */\n client_details: string;\n /** Income information */\n income_details: string;\n /** Current asset holdings */\n current_assets: string;\n /** Existing portfolio description */\n existing_portfolio: string;\n /** Financial goals and objectives */\n financial_objectives: string;\n /** Risk tolerance preferences */\n risk_preferences: string;\n /** Investment preferences and constraints */\n investment_preferences: string;\n /** Additional context or information */\n additional_information?: string;\n /** Instrument group IDs for available instruments */\n instrument_group_ids?: string[];\n /** User reference ID for tracking */\n user_reference_id?: string;\n /** Enable streaming (should be true for SSE) */\n stream?: boolean;\n}\n\n/** Trade recommendation from Atlas */\nexport interface AtlasTrade {\n action: \"BUY\" | \"SELL\" | \"HOLD\";\n title: string;\n ticker: string;\n bucket?: string;\n slug?: string;\n instrument_id?: string;\n amount?: number;\n reason?: string;\n}\n\n/** Portfolio holding from Atlas */\nexport interface AtlasHolding {\n title: string;\n ticker: string;\n slug?: string;\n description?: string;\n amount?: number;\n asset_type?: string;\n bucket?: string;\n reason?: string;\n instrument_id?: string;\n}\n\n/** Status information in Atlas response */\nexport interface AtlasStatus {\n state: string;\n reason?: string;\n}\n\n/** Response from Atlas advice endpoint */\nexport interface AtlasAdviceResponse {\n // Metadata\n id?: string;\n client_name?: string;\n advice_date?: string;\n title?: string;\n subtitle?: string;\n progress_update?: string;\n status?: AtlasStatus;\n stats?: Record<string, number>;\n\n // Text sections (streamed via append_string)\n client_summary?: string;\n existing_portfolio_opening_comment?: string;\n existing_portfolio_assessment?: string;\n strategy_overview?: string;\n target_portfolio_value?: string;\n risk_considerations?: string;\n final_report?: string;\n executive_summary?: string;\n\n // Array sections (streamed via append_array)\n existing_portfolio?: AtlasHolding[];\n investment_objectives?: Record<string, unknown>;\n allocation_segments?: Record<string, unknown>[];\n implementation_buckets?: Record<string, unknown>[];\n recommended_portfolio?: AtlasHolding[];\n recommended_trades?: AtlasTrade[];\n decision_log?: Record<string, unknown>[];\n assumption_log?: Record<string, unknown>[];\n advice?: Record<string, unknown>[];\n}\n\n/** Summary of an advisor environment returned by the list endpoint */\nexport interface AdvisorEnvironmentSummary {\n id: string;\n title: string;\n description?: string;\n currency?: string;\n is_global: boolean;\n instrument_groups?: { id: string; title: string }[];\n}\n\n// --- Client ---\n\n/**\n * AtlasClient provides access to the Atlas v2 API.\n *\n * @example\n * ```ts\n * const client = new TelescopeClient({ apiKey: \"...\" });\n *\n * const session = client.atlas.streamAdvice({\n * advisor_environment_id: \"env-uuid\",\n * client_details: \"45 year old professional...\",\n * // ... other fields\n * });\n *\n * session.subscribe({\n * onPatch: (data) => console.log(data.progress_update),\n * onComplete: (data) => console.log(\"Advice generated:\", data),\n * });\n * ```\n */\nexport class AtlasClient {\n constructor(private readonly client: TelescopeClient) {}\n\n /**\n * Stream financial advice generation.\n */\n streamAdvice(\n request: Omit<AtlasAdviceRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<AtlasAdviceResponse> {\n return this.client.stream<AtlasAdviceResponse>(\n \"/v2/atlas/advice\",\n { ...request, stream: true },\n options,\n );\n }\n\n /**\n * Create a StreamSession from a pre-existing SSE response.\n * Use this when you have a response from a proxy or custom fetch.\n */\n streamAdviceFromResponse(\n source: SSESource,\n ): StreamSession<AtlasAdviceResponse> {\n return this.client.streamFromResponse<AtlasAdviceResponse>(source);\n }\n\n /**\n * Get a previously generated advice by ID.\n */\n async getAdvice(id: string): Promise<AtlasAdviceResponse> {\n return this.client.get<AtlasAdviceResponse>(`/v2/atlas/advice?id=${id}`);\n }\n\n /**\n * Get recent advice entries.\n */\n async getLatestAdvice(limit: number = 30): Promise<AtlasAdviceResponse[]> {\n return this.client.get<AtlasAdviceResponse[]>(\n `/v2/atlas/advice/latest?limit=${limit}`,\n );\n }\n\n /**\n * Archive or unarchive advice entries.\n */\n async archiveAdvice(ids: string[], archived: boolean = true): Promise<void> {\n return this.client.post<void>(\"/v2/atlas/advice/archive\", {\n ids,\n archived,\n });\n }\n\n /**\n * List available advisor environments.\n */\n async getAdvisorEnvironments(options?: {\n region?: string;\n currency?: string;\n }): Promise<AdvisorEnvironmentSummary[]> {\n const params = new URLSearchParams();\n if (options?.region) params.set(\"region\", options.region);\n if (options?.currency) params.set(\"currency\", options.currency);\n const qs = params.toString();\n const path = `/v2/atlas/advisor-environments${qs ? `?${qs}` : \"\"}`;\n const resp = await this.client.get<{\n environments: AdvisorEnvironmentSummary[];\n }>(path);\n return resp.environments;\n }\n}\n","/**\n * News Product Client\n *\n * Provides typed access to News v2 API endpoints.\n */\n\nimport { TelescopeClient, type StreamOptions } from \"../core/client\";\nimport { ApiError } from \"../core/errors\";\nimport type { StreamSession } from \"../core/stream-session\";\nimport type { SSESource } from \"../core/types\";\n\n/** Shared story shape returned by News v2 endpoints. */\nexport interface NewsStory {\n id: string;\n headline: string;\n prompt: string;\n summary: string;\n first_clustered_at: string;\n last_revised_at: string;\n article_count: number;\n is_breaking: boolean;\n}\n\n/** Request body for POST /v2/news/latest. */\nexport interface NewsLatestRequest {\n instrument_ids?: string[];\n instrument_reference_id1s?: string[];\n instrument_reference_id2s?: string[];\n instrument_group_ids: string[];\n limit?: number;\n locale?: string;\n stream?: boolean;\n}\n\n/** Response body for POST /v2/news/latest. */\nexport interface NewsLatestResponse {\n stories: NewsStory[];\n}\n\n/** Request body for POST /v2/news/trending/topics. */\nexport interface NewsTrendingTopicsRequest {\n topics: string[];\n limit?: number;\n locale?: string;\n stream?: boolean;\n}\n\n/** Response body for POST /v2/news/trending/topics. */\nexport interface NewsTrendingTopicsResponse {\n stories: NewsStory[];\n}\n\nclass NewsLatestClient {\n constructor(private readonly client: TelescopeClient) {}\n\n /**\n * Get latest stories for a set of instruments (non-streaming).\n */\n async generate(\n request: Omit<NewsLatestRequest, \"stream\">,\n ): Promise<NewsLatestResponse> {\n return this.client.post<NewsLatestResponse>(\"/v2/news/latest\", {\n ...request,\n stream: false,\n });\n }\n}\n\nclass NewsTrendingTopicsClient {\n constructor(private readonly client: TelescopeClient) {}\n\n /**\n * Stream ranked trending topics via SSE.\n */\n stream(\n request: Omit<NewsTrendingTopicsRequest, \"stream\">,\n options?: StreamOptions,\n ): StreamSession<NewsTrendingTopicsResponse> {\n return this.client.stream<NewsTrendingTopicsResponse>(\n \"/v2/news/trending/topics\",\n { ...request, stream: true },\n options,\n );\n }\n\n /**\n * Get ranked trending topics (non-streaming).\n */\n async generate(\n request: Omit<NewsTrendingTopicsRequest, \"stream\">,\n ): Promise<NewsTrendingTopicsResponse> {\n return this.client.post<NewsTrendingTopicsResponse>(\n \"/v2/news/trending/topics\",\n { ...request, stream: false },\n );\n }\n\n /**\n * Create a StreamSession from a pre-existing SSE response.\n */\n streamFromResponse(\n source: SSESource,\n ): StreamSession<NewsTrendingTopicsResponse> {\n return this.client.streamFromResponse<NewsTrendingTopicsResponse>(source);\n }\n\n /**\n * Parse a pre-existing JSON response (non-streaming).\n */\n async generateFromResponse(\n response: Response,\n ): Promise<NewsTrendingTopicsResponse> {\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new ApiError(\n `Response not OK: ${response.status}`,\n response.status,\n body || undefined,\n );\n }\n return response.json();\n }\n}\n\n/**\n * NewsClient provides access to News v2 API.\n */\nexport class NewsClient {\n readonly latest: NewsLatestClient;\n readonly trendingTopics: NewsTrendingTopicsClient;\n\n constructor(client: TelescopeClient) {\n this.latest = new NewsLatestClient(client);\n this.trendingTopics = new NewsTrendingTopicsClient(client);\n }\n}\n","/**\n * TelescopeClient - Main HTTP and SSE streaming client\n *\n * Provides:\n * - REST methods (get, post, put, patch, delete, request)\n * - SSE streaming via stream()\n * - Namespaced product access (client.insights, client.atlas, client.news)\n * - Abort signal support\n * - Configurable connect timeout and debug logging\n */\n\nimport {\n ApiError,\n NetworkError,\n StreamingError,\n TelescopeClientError,\n} from \"./errors\";\nimport {\n type EventSourceMessage,\n fetchEventSource,\n processSSEStream,\n} from \"./fetch-event-source\";\nimport { StreamSession } from \"./stream-session\";\nimport { parseEvent, type SSESource } from \"./types\";\n\n/** SDK version - matches package.json */\nexport const VERSION = \"0.3.0\";\n\nconst DEFAULT_TELESCOPE_API_ORIGIN = \"https://api.telescope.co\";\nconst DEFAULT_CONNECT_TIMEOUT = 30000; // 30 seconds\nconst DEFAULT_JSON_HEADERS = {\n \"Content-Type\": \"application/json\",\n};\n\n// --- Client Options ---\n\nexport interface TelescopeClientOptions {\n /**\n * Organization API key for authentication (starts with `tsk_`).\n * Optional in Node.js/Bun - defaults to TELESCOPE_API_KEY env var.\n * Cannot be used together with `clientToken`.\n */\n apiKey?: string;\n /**\n * Client token for frontend authentication (starts with `tsc_`).\n * Use this instead of `apiKey` in browser environments.\n * Cannot be used together with `apiKey`.\n */\n clientToken?: string;\n /**\n * Base URL for the API.\n * Defaults to TELESCOPE_BASE_URL env var, or https://api.telescope.co\n */\n baseUrl?: string;\n /** Max time in milliseconds to wait for the server to start responding (default: 30000) */\n connectTimeout?: number;\n /** Enable debug logging to console (default: false) */\n debug?: boolean;\n}\n\nexport interface RequestOptions extends RequestInit {\n /** Override the default connect timeout for this request */\n connectTimeout?: number;\n}\n\nexport interface StreamOptions {\n signal?: AbortSignal;\n /** Override the default connection timeout for this stream. Only applies to the initial connection, not the total stream duration. */\n connectTimeout?: number;\n /** Additional request headers. Merged with (and can override) the default headers the client sets. */\n headers?: Record<string, string>;\n}\n\n// --- Client Token Types ---\n\nexport interface CreateClientTokenOptions {\n /** Custom identifier for the token. */\n client_id?: string;\n /** Seconds until the token expires. Default: 86400 (24 hours). */\n expires_in?: number;\n}\n\nexport interface ClientToken {\n id: string;\n token: string;\n client_id: string | null;\n expires_at: string;\n created_at: string;\n}\n\n// --- Product Client Types (for lazy loading) ---\n\n/** InsightsClient type - imported lazily to avoid circular deps */\ntype InsightsClientType = import(\"../insights/client\").InsightsClient;\n/** AtlasClient type - imported lazily to avoid circular deps */\ntype AtlasClientType = import(\"../atlas/client\").AtlasClient;\n/** NewsClient type - imported lazily to avoid circular deps */\ntype NewsClientType = import(\"../news/client\").NewsClient;\n\n// --- Main Client ---\n\n/**\n * TelescopeClient provides HTTP and SSE streaming access to the Telescope API.\n *\n * @example\n * ```ts\n * const client = new TelescopeClient({\n * apiKey: \"your-token\",\n * connectTimeout: 30000,\n * debug: process.env.NODE_ENV === \"development\",\n * });\n *\n * // REST request\n * const data = await client.get(\"/v2/some/endpoint\");\n *\n * // SSE streaming with namespaced access\n * const session = client.insights.instrument.stream({\n * instrument_id: \"abc123\",\n * });\n * session.subscribe({ onPatch: (data) => console.log(data) });\n * const result = await session.complete();\n *\n * // Atlas advice\n * const atlasSession = client.atlas.streamAdvice({ ... });\n * ```\n */\nexport class TelescopeClient {\n /** SDK version */\n static readonly VERSION = VERSION;\n\n private readonly baseUrl: string;\n private readonly apiKey: string;\n private readonly connectTimeout: number;\n private readonly debugEnabled: boolean;\n\n // Lazy-loaded product clients\n private _insights?: InsightsClientType;\n private _atlas?: AtlasClientType;\n private _news?: NewsClientType;\n\n constructor(options: TelescopeClientOptions = {}) {\n if (options.apiKey && options.clientToken) {\n throw new Error(\n \"Cannot provide both apiKey and clientToken. Use one or the other.\",\n );\n }\n\n const authToken =\n options.clientToken ?? options.apiKey ?? getEnvVar(\"TELESCOPE_API_KEY\");\n if (!authToken) {\n throw new Error(\n \"apiKey or clientToken is required. Provide it in options or set TELESCOPE_API_KEY environment variable.\",\n );\n }\n\n // Validate token prefix\n if (options.clientToken && !authToken.startsWith(\"tsc_\")) {\n console.warn(\n \"[Telescope] clientToken should start with 'tsc_'. The provided value may not be a valid client token.\",\n );\n }\n if (options.apiKey && !authToken.startsWith(\"tsk_\")) {\n console.warn(\n \"[Telescope] apiKey should start with 'tsk_'. The provided value may not be a valid API key.\",\n );\n }\n\n // Warn if using apiKey in a browser environment\n if (!options.clientToken && authToken && isBrowser()) {\n console.warn(\n \"[Telescope] Using apiKey in a browser environment is not recommended. Use clientToken instead.\",\n );\n }\n\n this.apiKey = authToken;\n this.baseUrl =\n options.baseUrl ??\n getEnvVar(\"TELESCOPE_BASE_URL\") ??\n DEFAULT_TELESCOPE_API_ORIGIN;\n this.connectTimeout = options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;\n this.debugEnabled = options.debug ?? false;\n\n this.debug(\"TelescopeClient initialized\", {\n baseUrl: this.baseUrl,\n connectTimeout: this.connectTimeout,\n });\n }\n\n /**\n * Namespaced access to Insights API.\n *\n * @example\n * ```ts\n * const session = client.insights.instrument.stream({ instrument_id: \"...\" });\n * const data = await client.insights.instrument.generate({ instrument_id: \"...\" });\n * ```\n */\n get insights(): InsightsClientType {\n let client = this._insights;\n if (!client) {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { InsightsClient } = require(\"../insights/client\") as {\n InsightsClient: new (c: TelescopeClient) => InsightsClientType;\n };\n client = new InsightsClient(this);\n this._insights = client;\n }\n return client;\n }\n\n /**\n * Namespaced access to Atlas API.\n *\n * @example\n * ```ts\n * const session = client.atlas.streamAdvice({ advisor_environment_id: \"...\", ... });\n * const advice = await client.atlas.getAdvice(id);\n * ```\n */\n get atlas(): AtlasClientType {\n let client = this._atlas;\n if (!client) {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { AtlasClient } = require(\"../atlas/client\") as {\n AtlasClient: new (c: TelescopeClient) => AtlasClientType;\n };\n client = new AtlasClient(this);\n this._atlas = client;\n }\n return client;\n }\n\n /**\n * Namespaced access to News API.\n *\n * @example\n * ```ts\n * const latest = await client.news.latest.generate({ ... });\n * const session = client.news.trendingTopics.stream({ topic: \"global\" });\n * ```\n */\n get news(): NewsClientType {\n let client = this._news;\n if (!client) {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { NewsClient } = require(\"../news/client\") as {\n NewsClient: new (c: TelescopeClient) => NewsClientType;\n };\n client = new NewsClient(this);\n this._news = client;\n }\n return client;\n }\n\n /**\n * Log debug messages when debug mode is enabled.\n */\n private debug(message: string, data?: unknown): void {\n if (this.debugEnabled) {\n const timestamp = new Date().toISOString();\n if (data !== undefined) {\n // eslint-disable-next-line no-console\n console.log(`[Telescope ${timestamp}] ${message}`, data);\n } else {\n // eslint-disable-next-line no-console\n console.log(`[Telescope ${timestamp}] ${message}`);\n }\n }\n }\n\n /**\n * Create a client token for frontend authentication.\n * Requires an API key (`tsk_`), not a client token.\n *\n * @example\n * ```ts\n * const client = new TelescopeClient({ apiKey: \"tsk_...\" });\n * const { token, expires_at } = await client.createClientToken({\n * client_id: \"user-123\",\n * expires_in: 3600,\n * });\n * ```\n */\n async createClientToken(\n options?: CreateClientTokenOptions,\n ): Promise<ClientToken> {\n this.debug(\"Creating client token\");\n if (this.apiKey.startsWith(\"tsc_\")) {\n console.warn(\n \"[Telescope] createClientToken() requires an API key (tsk_), not a client token. This request will likely fail.\",\n );\n }\n return this.post<ClientToken>(\"/v2/client-tokens\", options ?? {});\n }\n\n /**\n * Revoke a client token by ID. The token will immediately become invalid.\n * Requires an API key (`tsk_`), not a client token.\n *\n * @example\n * ```ts\n * const client = new TelescopeClient({ apiKey: \"tsk_...\" });\n * await client.revokeClientToken(\"token-uuid-here\");\n * ```\n */\n async revokeClientToken(id: string): Promise<void> {\n this.debug(\"Revoking client token\", { id });\n if (this.apiKey.startsWith(\"tsc_\")) {\n console.warn(\n \"[Telescope] revokeClientToken() requires an API key (tsk_), not a client token. This request will likely fail.\",\n );\n }\n await this.request<void>(`/v2/client-tokens/${encodeURIComponent(id)}`, {\n method: \"DELETE\",\n });\n }\n\n /**\n * Make a generic HTTP request.\n */\n async request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n const connectTimeout = options.connectTimeout ?? this.connectTimeout;\n const abortController = new AbortController();\n const combinedSignal = combineSignals(\n abortController.signal,\n options.signal,\n );\n\n const timeoutId = setTimeout(() => {\n abortController.abort(\n new Error(`Request timed out after ${connectTimeout}ms`),\n );\n }, connectTimeout);\n\n this.debug(`${options.method ?? \"GET\"} ${path}`, {\n connectTimeout,\n hasBody: options.body != null,\n });\n\n try {\n const url = buildApiUrl(this.baseUrl, path);\n const headers = new Headers(options.headers || {});\n if (!headers.has(\"Content-Type\") && options.body != null) {\n headers.set(\"Content-Type\", \"application/json\");\n }\n headers.set(\"Authorization\", `Bearer ${this.apiKey}`);\n\n const response = await fetch(url, {\n ...options,\n headers,\n signal: combinedSignal,\n });\n\n // Connection established (headers received) — clear the connect timeout.\n // Body reading proceeds with no time limit.\n clearTimeout(timeoutId);\n\n if (!response.ok) {\n const bodyText = await response.text().catch(() => \"\");\n let parsed: unknown = undefined;\n if (bodyText) {\n try {\n parsed = JSON.parse(bodyText);\n } catch {\n parsed = bodyText;\n }\n }\n throw new ApiError(\n `API request failed (${response.status})`,\n response.status,\n parsed,\n );\n }\n\n this.debug(`Response ${response.status} ${path}`);\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n const text = await response.text();\n if (!text) {\n return undefined as T;\n }\n try {\n return JSON.parse(text) as T;\n } catch {\n return text as unknown as T;\n }\n } catch (error: unknown) {\n clearTimeout(timeoutId);\n this.debug(`Request failed: ${path}`, error);\n\n if (error instanceof ApiError) {\n throw error;\n }\n if (error instanceof TelescopeClientError) {\n throw error;\n }\n // Handle connect timeout errors\n const isTimeout =\n abortController.signal.aborted &&\n abortController.signal.reason instanceof Error &&\n abortController.signal.reason.message.includes(\"timed out\");\n if (isTimeout) {\n throw new NetworkError(`Request timed out after ${connectTimeout}ms`);\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new NetworkError(`Network request failed: ${message}`);\n }\n }\n\n /**\n * Make a GET request.\n */\n async get<T>(path: string, options: RequestOptions = {}): Promise<T> {\n return this.request<T>(path, { ...options, method: \"GET\" });\n }\n\n /**\n * Make a POST request.\n */\n async post<T>(\n path: string,\n body?: unknown,\n options: RequestOptions = {},\n ): Promise<T> {\n const payload =\n body === undefined || body === null ? undefined : JSON.stringify(body);\n return this.request<T>(path, {\n ...options,\n method: \"POST\",\n body: payload,\n headers: {\n ...DEFAULT_JSON_HEADERS,\n ...options.headers,\n },\n });\n }\n\n /**\n * Start an SSE streaming request.\n *\n * Returns a StreamSession immediately. Use the session to:\n * - Subscribe to events: `session.subscribe({ onPatch, onComplete })`\n * - Get current data: `session.data`\n * - Wait for completion: `await session.complete()`\n *\n * @example\n * ```ts\n * const session = client.stream<InsightsResponse>(\"/v2/insights/instrument\", {\n * instrument_id: \"abc123\",\n * stream: true,\n * });\n *\n * session.subscribe({\n * onPatch: (data) => console.log(\"Progress:\", data),\n * onComplete: (data) => console.log(\"Done:\", data),\n * });\n *\n * const result = await session.complete();\n * ```\n */\n stream<T>(\n path: string,\n body: unknown,\n options: StreamOptions = {},\n ): StreamSession<T> {\n const session = new StreamSession<T>();\n session.markPending();\n\n // Execute streaming in background\n this.executeStream(path, body, session, options);\n\n return session;\n }\n\n /**\n * Create a StreamSession from a pre-existing SSE response.\n * Use this when you have a response from a proxy or custom fetch.\n *\n * @example\n * ```ts\n * // Customer proxy scenario\n * const response = await fetch(\"/my-proxy/insights\", { method: \"POST\", body: JSON.stringify({ portfolio_id: \"xyz\" }) });\n * const session = client.streamFromResponse<PortfolioInsightsResponse>(response);\n * session.subscribe({ onPatch: (data) => console.log(data) });\n * const result = await session.complete();\n * ```\n */\n streamFromResponse<T>(source: SSESource): StreamSession<T> {\n const session = new StreamSession<T>();\n session.markPending();\n\n // Process the SSE response in background\n this.processSSEResponse(source, session);\n\n return session;\n }\n\n /**\n * Internal: Process an SSE response and route events to the session.\n */\n private async processSSEResponse<T>(\n source: SSESource,\n session: StreamSession<T>,\n ): Promise<void> {\n try {\n await processSSEStream(\n source,\n (message: EventSourceMessage) => {\n // Parse the raw SSE data\n let rawData: unknown;\n if (typeof message.data === \"string\" && message.data !== \"\") {\n try {\n rawData = JSON.parse(message.data);\n } catch {\n rawData = message.data;\n }\n } else {\n rawData = message.data;\n }\n\n // Parse into typed SSEEvent\n const event = parseEvent(message.event || \"message\", rawData);\n if (event) {\n session.processEvent(event);\n }\n // Unknown events are ignored per spec\n },\n {\n onclose: () => {\n // Connection closed - if not already completed, mark as complete\n if (\n session.status.phase !== \"completed\" &&\n session.status.phase !== \"error\"\n ) {\n session.processEvent({ event: \"complete\", data: {} });\n }\n },\n onerror: (error) => {\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n `Streaming connection failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n },\n },\n );\n } catch (error: unknown) {\n if (\n session.status.phase === \"error\" ||\n session.status.phase === \"completed\"\n ) {\n // Already handled\n return;\n }\n\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n `Streaming failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n }\n }\n\n /**\n * Internal: Execute the SSE stream and route events to the session.\n */\n private async executeStream<T>(\n path: string,\n body: unknown,\n session: StreamSession<T>,\n options: StreamOptions,\n ): Promise<void> {\n const abortController = new AbortController();\n const unlink = linkAbortSignals(abortController, options.signal);\n\n // Connection timeout - aborts if the stream doesn't open in time.\n // Cancelled once the connection is established (onopen fires).\n const connectionTimeout = options.connectTimeout ?? this.connectTimeout;\n const timeoutId = setTimeout(() => {\n if (session.status.phase === \"pending\") {\n abortController.abort(\n new Error(`Stream connection timed out after ${connectionTimeout}ms`),\n );\n }\n }, connectionTimeout);\n\n try {\n await fetchEventSource(buildApiUrl(this.baseUrl, path), {\n method: \"POST\",\n body: JSON.stringify(body ?? {}),\n headers: {\n ...DEFAULT_JSON_HEADERS,\n Accept: \"text/event-stream\",\n Authorization: `Bearer ${this.apiKey}`,\n ...options.headers,\n },\n signal: abortController.signal,\n openWhenHidden: true,\n\n async onopen(response) {\n clearTimeout(timeoutId);\n const contentType = response.headers.get(\"content-type\") || \"\";\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new StreamingError(\n `Stream failed to open (${response.status}) ${text}`,\n response.status,\n );\n }\n if (!contentType.includes(\"text/event-stream\")) {\n throw new StreamingError(\n `Unexpected content-type for stream: ${contentType}`,\n response.status,\n );\n }\n },\n\n onmessage: (message: EventSourceMessage) => {\n // Parse the raw SSE data\n let rawData: unknown;\n if (typeof message.data === \"string\" && message.data !== \"\") {\n try {\n rawData = JSON.parse(message.data);\n } catch {\n rawData = message.data;\n }\n } else {\n rawData = message.data;\n }\n\n // Parse into typed SSEEvent\n const event = parseEvent(message.event || \"message\", rawData);\n if (event) {\n session.processEvent(event);\n }\n // Unknown events are ignored per spec\n },\n\n onclose: () => {\n // Connection closed - if not already completed, mark as complete\n if (\n session.status.phase !== \"completed\" &&\n session.status.phase !== \"error\"\n ) {\n session.processEvent({ event: \"complete\", data: {} });\n }\n },\n\n onerror: (error) => {\n if (abortController.signal.aborted) {\n return;\n }\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n `Streaming connection failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n throw wrapped;\n },\n });\n } catch (error: unknown) {\n if (\n session.status.phase === \"error\" ||\n session.status.phase === \"completed\"\n ) {\n // Already handled\n return;\n }\n\n // Surface a clear message when the connection timeout fired\n const isTimeout =\n abortController.signal.aborted &&\n abortController.signal.reason instanceof Error &&\n abortController.signal.reason.message.includes(\"timed out\");\n\n const wrapped =\n error instanceof StreamingError\n ? error\n : new StreamingError(\n isTimeout\n ? `Stream connection timed out after ${connectionTimeout}ms`\n : `Streaming failed: ${\n error instanceof Error ? error.message : String(error)\n }`,\n error instanceof Response ? error.status : undefined,\n error,\n );\n session.handleTransportError(wrapped);\n } finally {\n clearTimeout(timeoutId);\n unlink?.();\n }\n }\n}\n\n// --- Helpers ---\n\n/**\n * Link an external abort signal to a controller.\n * Returns cleanup function or null.\n */\nfunction linkAbortSignals(\n primary: AbortController,\n external?: AbortSignal | null,\n): (() => void) | null {\n if (!external) {\n return null;\n }\n const abortHandler = () => {\n primary.abort(external.reason);\n };\n if (external.aborted) {\n primary.abort(external.reason);\n return null;\n }\n external.addEventListener(\"abort\", abortHandler, { once: true });\n return () => external.removeEventListener(\"abort\", abortHandler);\n}\n\n/**\n * Combine multiple abort signals into one.\n * The combined signal aborts when any of the input signals abort.\n */\nfunction combineSignals(\n ...signals: (AbortSignal | null | undefined)[]\n): AbortSignal {\n const controller = new AbortController();\n for (const signal of signals) {\n if (signal) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\n }\n signal.addEventListener(\"abort\", () => controller.abort(signal.reason), {\n once: true,\n });\n }\n }\n return controller.signal;\n}\n\nfunction buildApiUrl(baseUrl: string, path: string): string {\n const normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n return new URL(path, normalizedBase).toString();\n}\n\n// --- Environment helpers ---\n\n/**\n * Safely get an environment variable in Node.js/Bun runtimes.\n * Returns undefined in browser environments.\n */\nfunction getEnvVar(name: string): string | undefined {\n if (typeof process !== \"undefined\" && process.env) {\n return process.env[name];\n }\n return undefined;\n}\n\n/** Detect browser environment */\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof document !== \"undefined\";\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAUa,uBAAb,cAA0C,MAAM;EAC9C,YACE,SACA,AAAgB,OAChB;AACA,SAAM,QAAQ;GAFE;AAGhB,QAAK,OAAO,KAAK,YAAY;;;CAKpB,aAAb,cAAgC,qBAAqB;CAGxC,WAAb,cAA8B,qBAAqB;EACjD,YACE,SACA,AAAgB,QAChB,AAAgB,MAChB;AACA,SAAM,QAAQ;GAHE;GACA;;;CAOP,eAAb,cAAkC,qBAAqB;CAG1C,iBAAb,cAAoC,qBAAqB;EACvD,YACE,SACA,AAAgB,QAChB,OACA;AACA,SAAM,SAAS,MAAM;GAHL;;;CAQP,WAAb,cAA8B,qBAAqB;EACjD,AAAgB;EAChB,AAAgB;EAChB,AAAgB;EAChB,AAAgB;EAEhB,YAAY,WAA2B;AACrC,SAAM,UAAU,QAAQ;AACxB,QAAK,OAAO,UAAU;AACtB,QAAK,aAAa,UAAU;AAC5B,QAAK,OAAO,UAAU;AACtB,QAAK,iBAAiB,UAAU;;;;;;;;;;;;ACvBpC,SAAS,gBAAgB,KAAwC;CAC/D,MAAM,QAAQ,IAAI,MAAM,KAAK;CAC7B,IAAI,QAAQ;CACZ,MAAM,YAAsB,EAAE;CAC9B,IAAI,KAAK;CACT,IAAI;AAEJ,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,WAAW,SAAS,CAC3B,SAAQ,KAAK,MAAM,EAAE,CAAC,MAAM;UACnB,KAAK,WAAW,QAAQ,CACjC,WAAU,KAAK,KAAK,MAAM,EAAE,CAAC,WAAW,CAAC;UAChC,KAAK,WAAW,MAAM,CAC/B,MAAK,KAAK,MAAM,EAAE,CAAC,MAAM;UAChB,KAAK,WAAW,SAAS,EAAE;EACpC,MAAM,MAAM,SAAS,KAAK,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG;AAC9C,MAAI,CAAC,MAAM,IAAI,CACb,SAAQ;;AAQd,KAAI,UAAU,WAAW,EACvB,QAAO;AAGT,QAAO;EACL;EACA,MAAM,UAAU,KAAK,KAAK;EAC1B;EACA;EACD;;;;;;AAOH,eAAsB,iBACpB,QACA,WACA,SACe;CACf,MAAM,EAAE,SAAS,SAAS,WAAW,WAAW,EAAE;CAElD,MAAM,OAAO,OAAO;AACpB,KAAI,CAAC,MAAM;EACT,MAAM,sBAAM,IAAI,MAAM,8BAA8B;AACpD,YAAU,IAAI;AACd,QAAM;;CAGR,MAAM,SAAS,KAAK,WAAW;CAC/B,MAAM,UAAU,IAAI,aAAa;CACjC,IAAI,SAAS;AAEb,KAAI;AACF,SAAO,MAAM;GAEX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAE3C,OAAI,MAAM;AAER,QAAI,OAAO,MAAM,EAAE;KACjB,MAAM,UAAU,gBAAgB,OAAO;AACvC,SAAI,QACF,WAAU,QAAQ;;AAGtB;;AAGF,aAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,MAAM,CAAC;GAGjD,MAAM,QAAQ,OAAO,MAAM,OAAO;AAGlC,YAAS,MAAM,KAAK,IAAI;AAGxB,QAAK,MAAM,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,MAAM,CAAE;IAClB,MAAM,UAAU,gBAAgB,KAAK;AACrC,QAAI,QACF,WAAU,QAAQ;;;UAIjB,KAAK;AAEZ,MAAI,QAAQ,QACV;AAEF,YAAU,IAAI;AACd,QAAM;WACE;AACR,aAAW;;;;;;AAOf,eAAsB,iBACpB,KACA,SACe;CACf,MAAM,EACJ,SAAS,OACT,UAAU,EAAE,EACZ,MACA,QACA,QACA,WACA,SACA,YACE;CAEJ,IAAI;AACJ,KAAI;AACF,aAAW,MAAM,MAAM,KAAK;GAC1B;GACA;GACA;GACA;GACD,CAAC;UACK,KAAK;AACZ,YAAU,IAAI;AACd,QAAM;;AAKR,KAAI,OACF,KAAI;AACF,QAAM,OAAO,SAAS,OAAO,CAAC;UACvB,KAAK;AACZ,YAAU,IAAI;AACd,QAAM;;AAIV,OAAM,iBAAiB,UAAU,oBAAoB,KAAK;EACxD;EACA;EACA;EACD,CAAC;;;;;;;;;AC5GJ,SAAgB,aAAa,SAA0B;AACrD,QAAO,QAAQ,KAAK,QAAQ;;;;;AAM9B,SAAgB,UAAU,MAAoB;AAC5C,QAAO,KAAK,UAAU,KAAK;;;;;;AAO7B,SAAgB,WACd,WACA,SACiB;AACjB,SAAQ,UAAU,aAAa,EAA/B;EACE,KAAK,SAAS;GACZ,MAAM,OAAO;AACb,UAAO;IACL,OAAO;IACP,MAAM;KACJ,WAAW,KAAK;KAChB,MAAM,KAAK;KACX,OAAO,KAAK;KACb;IACF;;EAEH,KAAK,gBAEH,QAAO;GACL,OAAO;GACP,MAAM,EAAE,MAHG,QAGQ,MAAc;GAClC;EAEH,KAAK,SAAS;GACZ,MAAM,OAAO;AACb,UAAO;IACL,OAAO;IACP,MAAM;KACJ,MAAM,KAAK;KACX,SAAS,KAAK;KACd,aAAc,KAAK,eAAe,KAAK,eAAe;KACtD,MAAM,KAAK;KACX,iBAAiB,KAAK;KAGvB;IACF;;EAEH,KAAK,YACH,QAAO;GAAE,OAAO;GAAa,MAAM,EAAE;GAAE;EACzC,KAAK,WACH,QAAO;GAAE,OAAO;GAAY,MAAM,EAAE;GAAE;EACxC,QAEE,QAAO;;;;;;;;;;;;;;;;;;;;ACpHb,IAAa,kBAAb,MAA0D;CACxD,AAAQ;CAER,YAAY,aAAiB;AAC3B,OAAK,OAAQ,cAAc,gBAAgB,YAAY,GAAG,EAAE;;;;;CAM9D,WAAW,OAA6B;EACtC,MAAM,EAAE,WAAW,MAAM,UAAU;AAEnC,UAAQ,WAAR;GACE,KAAK;AACH,SAAK,UAAU,MAAM,MAAmB;AACxC;GACF,KAAK;AACH,SAAK,mBAAmB,MAAM,MAAgB;AAC9C;GACF,KAAK;AACH,SAAK,kBAAkB,MAAM,MAAmB;AAChD;GACF,KAAK;AACH,SAAK,aAAa,KAAK;AACvB;;;;;;CAON,UAAa;AACX,SAAO,KAAK;;;;;;;;;CAUd,AAAQ,UAAU,MAAY,OAAwB;EACpD,MAAM,SAAS,KAAK,iBAAiB,KAAK;EAC1C,MAAM,cAAc,KAAK,KAAK,SAAS;AAEvC,MAAI,MAAM,QAAQ,OAAO,CACvB,QAAO,SAAS,aAAa,GAAG,IAAI;MAEpC,CAAC,OAAmC,eAAe;;;;;;CAQvD,AAAQ,mBAAmB,MAAY,OAAqB;EAC1D,MAAM,SAAS,KAAK,iBAAiB,KAAK;EAC1C,MAAM,cAAc,KAAK,KAAK,SAAS;EAEvC,IAAI;AACJ,MAAI,MAAM,QAAQ,OAAO,CACvB,WAAU,OAAO,SAAS,aAAa,GAAG;MAE1C,WAAW,OAAmC;EAIhD,MAAM,YAAY,OAAO,YAAY,WAAW,UAAU,MAAM;AAEhE,MAAI,MAAM,QAAQ,OAAO,CACvB,QAAO,SAAS,aAAa,GAAG,IAAI;MAEpC,CAAC,OAAmC,eAAe;;;;;;CAQvD,AAAQ,kBAAkB,MAAY,SAA0B;EAC9D,MAAM,SAAS,KAAK,iBAAiB,KAAK;EAC1C,MAAM,cAAc,KAAK,KAAK,SAAS;EAEvC,IAAI;AACJ,MAAI,MAAM,QAAQ,OAAO,CACvB,WAAU,OAAO,SAAS,aAAa,GAAG;MAE1C,WAAW,OAAmC;EAIhD,MAAM,MAAM,MAAM,QAAQ,QAAQ,GAAG,UAAU,EAAE;AACjD,MAAI,KAAK,QAAQ;AAEjB,MAAI,MAAM,QAAQ,OAAO,CACvB,QAAO,SAAS,aAAa,GAAG,IAAI;MAEpC,CAAC,OAAmC,eAAe;;;;;;;CASvD,AAAQ,aAAa,MAAkB;EACrC,MAAM,SAAS,KAAK,iBAAiB,KAAK;AAC1C,MAAI,WAAW,KAEb;EAGF,MAAM,cAAc,KAAK,KAAK,SAAS;AAEvC,MAAI,MAAM,QAAQ,OAAO,CAEvB,QAAO,SAAS,aAAa,GAAG,IAAI;MAGpC,QAAQ,OAAmC;;;;;;CAQ/C,AAAQ,iBAAiB,MAAiD;EACxE,IAAI,UAAmB,KAAK;AAE5B,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;GACxC,MAAM,UAAU,KAAK;GACrB,MAAM,cAAc,KAAK,IAAI;GAC7B,MAAM,mBAAmB,aAAa,YAAY;GAElD,IAAI;AACJ,OAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QAAQ,SAAS,SAAS,GAAG;OAEpC,QAAQ,QAAoC;AAI9C,OAAI,SAAS,UAAa,SAAS,MAAM;AACvC,WAAO,mBAAmB,EAAE,GAAG,EAAE;AACjC,QAAI,MAAM,QAAQ,QAAQ,CACxB,SAAQ,SAAS,SAAS,GAAG,IAAI;QAEjC,CAAC,QAAoC,WAAW;;AAIpD,aAAU;;AAGZ,SAAO;;;;;;CAOT,AAAQ,iBACN,MAC4C;EAC5C,IAAI,UAAmB,KAAK;AAE5B,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;GACxC,MAAM,UAAU,KAAK;GAErB,IAAI;AACJ,OAAI,MAAM,QAAQ,QAAQ,CACxB,QAAO,QAAQ,SAAS,SAAS,GAAG;YAC3B,OAAO,YAAY,YAAY,YAAY,KACpD,QAAQ,QAAoC;OAE5C,QAAO;AAGT,OAAI,SAAS,UAAa,SAAS,KACjC,QAAO;AAGT,aAAU;;AAGZ,SAAO;;;;;;;;;;;;;;;aCnMyC;;;;;;;;;;;;AA+CpD,IAAa,gBAAb,MAAwD;CACtD,AAAiB;CACjB,AAAQ,UAAwB,EAAE,OAAO,QAAQ;CACjD,AAAiB,4BAAY,IAAI,KAAwB;CACzD,AAAiB,iCAAiB,IAAI,KAAa;CACnD,AAAiB;CACjB,AAAQ;CACR,AAAQ;CAER,YAAY,aAAiB;AAC3B,OAAK,UAAU,IAAI,gBAAmB,YAAY;AAClD,OAAK,oBAAoB,IAAI,SAAY,SAAS,WAAW;AAC3D,QAAK,oBAAoB;AACzB,QAAK,mBAAmB;IACxB;;;;;;CAOJ,UAAU,UAAyC;AACjD,OAAK,UAAU,IAAI,SAAS;AAC5B,eAAa,KAAK,UAAU,OAAO,SAAS;;;CAI9C,IAAI,OAAU;AACZ,SAAO,KAAK,QAAQ,SAAS;;;CAI/B,IAAI,SAAuB;AACzB,SAAO,KAAK;;;;;;CAOd,WAAuB;AACrB,SAAO,KAAK;;;;;CAMd,eAAe,MAAqB;AAClC,SAAO,KAAK,eAAe,IAAI,UAAU,KAAK,CAAC;;;;;CAMjD,oBAA4B;AAC1B,SAAO,MAAM,KAAK,KAAK,eAAe,CAAC,KACpC,QAAQ,KAAK,MAAM,IAAI,CACzB;;;;;CAQH,cAAoB;AAClB,MAAI,KAAK,QAAQ,UAAU,OACzB,MAAK,UAAU;GAAE,OAAO;GAAW,WAAW,KAAK,KAAK;GAAE,CAAC;;;;;;CAQ/D,aAAa,OAAuB;AAElC,MAAI,KAAK,QAAQ,UAAU,eAAe,KAAK,QAAQ,UAAU,QAC/D;AAGF,UAAQ,MAAM,OAAd;GACE,KAAK;AACH,SAAK,YAAY,MAAM,KAAK;AAC5B;GACF,KAAK;AACH,SAAK,mBAAmB,MAAM,KAAK,KAAK;AACxC;GACF,KAAK;AACH,SAAK,YAAY,MAAM,KAAK;AAC5B;GACF,KAAK;AACH,SAAK,iBAAiB;AACtB;GACF,KAAK;AACH,SAAK,gBAAgB;AACrB;;;;;;CAON,qBAAqB,OAA6B;AAChD,MAAI,KAAK,QAAQ,UAAU,eAAe,KAAK,QAAQ,UAAU,QAC/D;AAGF,OAAK,UAAU;GACb,OAAO;GACP,WAAW,KAAK,QAAQ;GACxB,aAAa,KAAK,KAAK;GACvB;GACD,CAAC;AAEF,OAAK,iBAAiB,QAAQ,IAAI,mBAAmB,MAAM,CAAC;AAC5D,OAAK,iBAAiB,MAAM;;CAK9B,AAAQ,YAAY,OAA6B;AAE/C,MAAI,KAAK,QAAQ,UAAU,aAAa,KAAK,QAAQ,UAAU,OAC7D,MAAK,UAAU;GACb,OAAO;GACP,WAAW,KAAK,QAAQ,aAAa,KAAK,KAAK;GAChD,CAAC;AAGJ,OAAK,QAAQ,WAAW,MAAM;EAC9B,MAAM,OAAO,KAAK,QAAQ,SAAS;AAEnC,OAAK,iBAAiB,QACpB,IAAI,UAAU,MAAM;GAAE,OAAO;GAAS,MAAM;GAAO,CAAC,CACrD;;CAGH,AAAQ,mBAAmB,MAAkB;AAC3C,OAAK,eAAe,IAAI,UAAU,KAAK,CAAC;EACxC,MAAM,OAAO,KAAK,QAAQ,SAAS;AACnC,OAAK,iBAAiB,QAAQ,IAAI,iBAAiB,MAAM,KAAK,CAAC;;CAGjE,AAAQ,YAAY,WAAiC;AAEnD,OAAK,iBAAiB,QAAQ,IAAI,UAAU,UAAU,CAAC;EAGvD,MAAM,WAAW,IAAI,SAAS,UAAU;AACxC,OAAK,UAAU;GACb,OAAO;GACP,WAAW,KAAK,QAAQ;GACxB,aAAa,KAAK,KAAK;GACvB,OAAO;GACR,CAAC;AAEF,OAAK,iBAAiB,SAAS;;CAGjC,AAAQ,kBAAwB;AAC9B,OAAK,iBAAiB,QAAQ,IAAI,eAAe,CAAC;;CAGpD,AAAQ,iBAAuB;AAC7B,MAAI,KAAK,QAAQ,UAAU,eAAe,KAAK,QAAQ,UAAU,QAC/D;AAGF,OAAK,UAAU;GACb,OAAO;GACP,WAAW,KAAK,QAAQ;GACxB,aAAa,KAAK,KAAK;GACxB,CAAC;EAEF,MAAM,OAAO,KAAK,QAAQ,SAAS;AACnC,OAAK,iBAAiB,QAAQ,IAAI,aAAa,KAAK,CAAC;AACrD,OAAK,kBAAkB,KAAK;;CAG9B,AAAQ,UAAU,QAA4B;AAC5C,OAAK,UAAU;AACf,OAAK,iBAAiB,QAAQ,IAAI,iBAAiB,OAAO,CAAC;;CAG7D,AAAQ,gBAAgB,UAAkD;AAExE,OAAK,MAAM,YAAY,MAAM,KAAK,KAAK,UAAU,CAC/C,KAAI;AACF,YAAS,SAAS;WACX,GAAG;AACV,WAAQ,MAAM,4BAA4B,EAAE;;;;;;;;;cC/PP;CAuChC,2BAAb,MAAsC;EACpC,YAAY,AAAiB,QAAyB;GAAzB;;EAE7B,OACE,SACA,SAC2C;AAC3C,UAAO,KAAK,OAAO,OACjB,2BACA;IAAE,GAAG;IAAS,QAAQ;IAAM,EAC5B,QACD;;EAGH,MAAM,SACJ,SACqC;AACrC,UAAO,KAAK,OAAO,KACjB,2BACA;IAAE,GAAG;IAAS,QAAQ;IAAO,CAC9B;;EAGH,mBACE,QAC2C;AAC3C,UAAO,KAAK,OAAO,mBAA+C,OAAO;;EAG3E,MAAM,qBACJ,UACqC;AACrC,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,UAAM,IAAI,SACR,oBAAoB,SAAS,UAC7B,SAAS,QACT,QAAQ,OACT;;AAEH,UAAO,SAAS,MAAM;;;;;;;;;cC/EmB;CAoChC,0BAAb,MAAqC;EACnC,YAAY,AAAiB,QAAyB;GAAzB;;EAE7B,OACE,SACA,SAC0C;AAC1C,UAAO,KAAK,OAAO,OACjB,0BACA;IAAE,GAAG;IAAS,QAAQ;IAAM,EAC5B,QACD;;EAGH,MAAM,SACJ,SACoC;AACpC,UAAO,KAAK,OAAO,KACjB,0BACA;IAAE,GAAG;IAAS,QAAQ;IAAO,CAC9B;;EAGH,mBACE,QAC0C;AAC1C,UAAO,KAAK,OAAO,mBAA8C,OAAO;;EAG1E,MAAM,qBACJ,UACoC;AACpC,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,UAAM,IAAI,SACR,oBAAoB,SAAS,UAC7B,SAAS,QACT,QAAQ,OACT;;AAEH,UAAO,SAAS,MAAM;;;;;;;;;cC5EmB;CAehC,0BAAb,MAAqC;EACnC,YAAY,AAAiB,QAAyB;GAAzB;;EAE7B,OACE,SACA,SAC0C;AAC1C,UAAO,KAAK,OAAO,OACjB,0BACA;IAAE,GAAG;IAAS,QAAQ;IAAM,EAC5B,QACD;;EAGH,MAAM,SACJ,SACoC;AACpC,UAAO,KAAK,OAAO,KACjB,0BACA;IAAE,GAAG;IAAS,QAAQ;IAAO,CAC9B;;EAGH,mBACE,QAC0C;AAC1C,UAAO,KAAK,OAAO,mBAA8C,OAAO;;EAG1E,MAAM,qBACJ,UACoC;AACpC,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,UAAM,IAAI,SACR,oBAAoB,SAAS,UAC7B,SAAS,QACT,QAAQ,OACT;;AAEH,UAAO,SAAS,MAAM;;;;;;;;;cCvDmB;CAoBhC,uBAAb,MAAkC;EAChC,YAAY,AAAiB,QAAyB;GAAzB;;EAE7B,OACE,SACA,SACuC;AACvC,UAAO,KAAK,OAAO,OACjB,uBACA;IAAE,GAAG;IAAS,QAAQ;IAAM,EAC5B,QACD;;EAGH,MAAM,SACJ,SACiC;AACjC,UAAO,KAAK,OAAO,KAA6B,uBAAuB;IACrE,GAAG;IACH,QAAQ;IACT,CAAC;;EAGJ,mBAAmB,QAA0D;AAC3E,UAAO,KAAK,OAAO,mBAA2C,OAAO;;EAGvE,MAAM,qBACJ,UACiC;AACjC,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,UAAM,IAAI,SACR,oBAAoB,SAAS,UAC7B,SAAS,QACT,QAAQ,OACT;;AAEH,UAAO,SAAS,MAAM;;;;;;;;;cC1DmB;CAqChC,yBAAb,MAAoC;EAClC,YAAY,AAAiB,QAAyB;GAAzB;;EAE7B,OACE,SACA,SACyC;AACzC,UAAO,KAAK,OAAO,OACjB,yBACA;IAAE,GAAG;IAAS,QAAQ;IAAM,EAC5B,QACD;;EAGH,MAAM,SACJ,SACmC;AACnC,UAAO,KAAK,OAAO,KAA+B,yBAAyB;IACzE,GAAG;IACH,QAAQ;IACT,CAAC;;EAGJ,mBACE,QACyC;AACzC,UAAO,KAAK,OAAO,mBAA6C,OAAO;;EAGzE,MAAM,qBACJ,UACmC;AACnC,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,UAAM,IAAI,SACR,oBAAoB,SAAS,UAC7B,SAAS,QACT,QAAQ,OACT;;AAEH,UAAO,SAAS,MAAM;;;;;;;;;;;;;;;;;gBC7EqC;gBACF;gBACA;gBACN;gBACI;;;;;;CAQ9C,iBAAb,MAA4B;EAC1B,AAAS;EACT,AAAS;EACT,AAAS;EACT,AAAS;EACT,AAAS;EAET,YAAY,QAAyB;AACnC,QAAK,aAAa,IAAI,yBAAyB,OAAO;AACtD,QAAK,YAAY,IAAI,wBAAwB,OAAO;AACpD,QAAK,YAAY,IAAI,wBAAwB,OAAO;AACpD,QAAK,SAAS,IAAI,qBAAqB,OAAO;AAC9C,QAAK,WAAW,IAAI,uBAAuB,OAAO;;;;;;;;;;CCgHzC,cAAb,MAAyB;EACvB,YAAY,AAAiB,QAAyB;GAAzB;;;;;EAK7B,aACE,SACA,SACoC;AACpC,UAAO,KAAK,OAAO,OACjB,oBACA;IAAE,GAAG;IAAS,QAAQ;IAAM,EAC5B,QACD;;;;;;EAOH,yBACE,QACoC;AACpC,UAAO,KAAK,OAAO,mBAAwC,OAAO;;;;;EAMpE,MAAM,UAAU,IAA0C;AACxD,UAAO,KAAK,OAAO,IAAyB,uBAAuB,KAAK;;;;;EAM1E,MAAM,gBAAgB,QAAgB,IAAoC;AACxE,UAAO,KAAK,OAAO,IACjB,iCAAiC,QAClC;;;;;EAMH,MAAM,cAAc,KAAe,WAAoB,MAAqB;AAC1E,UAAO,KAAK,OAAO,KAAW,4BAA4B;IACxD;IACA;IACD,CAAC;;;;;EAMJ,MAAM,uBAAuB,SAGY;GACvC,MAAM,SAAS,IAAI,iBAAiB;AACpC,OAAI,SAAS,OAAQ,QAAO,IAAI,UAAU,QAAQ,OAAO;AACzD,OAAI,SAAS,SAAU,QAAO,IAAI,YAAY,QAAQ,SAAS;GAC/D,MAAM,KAAK,OAAO,UAAU;GAC5B,MAAM,OAAO,iCAAiC,KAAK,IAAI,OAAO;AAI9D,WAHa,MAAM,KAAK,OAAO,IAE5B,KAAK,EACI;;;;;;;;;;cCtM0B;CA6CpC,mBAAN,MAAuB;EACrB,YAAY,AAAiB,QAAyB;GAAzB;;;;;EAK7B,MAAM,SACJ,SAC6B;AAC7B,UAAO,KAAK,OAAO,KAAyB,mBAAmB;IAC7D,GAAG;IACH,QAAQ;IACT,CAAC;;;CAIA,2BAAN,MAA+B;EAC7B,YAAY,AAAiB,QAAyB;GAAzB;;;;;EAK7B,OACE,SACA,SAC2C;AAC3C,UAAO,KAAK,OAAO,OACjB,4BACA;IAAE,GAAG;IAAS,QAAQ;IAAM,EAC5B,QACD;;;;;EAMH,MAAM,SACJ,SACqC;AACrC,UAAO,KAAK,OAAO,KACjB,4BACA;IAAE,GAAG;IAAS,QAAQ;IAAO,CAC9B;;;;;EAMH,mBACE,QAC2C;AAC3C,UAAO,KAAK,OAAO,mBAA+C,OAAO;;;;;EAM3E,MAAM,qBACJ,UACqC;AACrC,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,UAAM,IAAI,SACR,oBAAoB,SAAS,UAC7B,SAAS,QACT,QAAQ,OACT;;AAEH,UAAO,SAAS,MAAM;;;CAOb,aAAb,MAAwB;EACtB,AAAS;EACT,AAAS;EAET,YAAY,QAAyB;AACnC,QAAK,SAAS,IAAI,iBAAiB,OAAO;AAC1C,QAAK,iBAAiB,IAAI,yBAAyB,OAAO;;;;;;;aCrH5C;;AAUlB,MAAa,UAAU;AAEvB,MAAM,+BAA+B;AACrC,MAAM,0BAA0B;AAChC,MAAM,uBAAuB,EAC3B,gBAAgB,oBACjB;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FD,IAAa,kBAAb,MAA6B;;CAE3B,OAAgB,UAAU;CAE1B,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAGjB,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,UAAkC,EAAE,EAAE;AAChD,MAAI,QAAQ,UAAU,QAAQ,YAC5B,OAAM,IAAI,MACR,oEACD;EAGH,MAAM,YACJ,QAAQ,eAAe,QAAQ,UAAU,UAAU,oBAAoB;AACzE,MAAI,CAAC,UACH,OAAM,IAAI,MACR,0GACD;AAIH,MAAI,QAAQ,eAAe,CAAC,UAAU,WAAW,OAAO,CACtD,SAAQ,KACN,wGACD;AAEH,MAAI,QAAQ,UAAU,CAAC,UAAU,WAAW,OAAO,CACjD,SAAQ,KACN,8FACD;AAIH,MAAI,CAAC,QAAQ,eAAe,aAAa,WAAW,CAClD,SAAQ,KACN,iGACD;AAGH,OAAK,SAAS;AACd,OAAK,UACH,QAAQ,WACR,UAAU,qBAAqB,IAC/B;AACF,OAAK,iBAAiB,QAAQ,kBAAkB;AAChD,OAAK,eAAe,QAAQ,SAAS;AAErC,OAAK,MAAM,+BAA+B;GACxC,SAAS,KAAK;GACd,gBAAgB,KAAK;GACtB,CAAC;;;;;;;;;;;CAYJ,IAAI,WAA+B;EACjC,IAAI,SAAS,KAAK;AAClB,MAAI,CAAC,QAAQ;GAEX,MAAM,EAAE;AAGR,YAAS,IAAI,eAAe,KAAK;AACjC,QAAK,YAAY;;AAEnB,SAAO;;;;;;;;;;;CAYT,IAAI,QAAyB;EAC3B,IAAI,SAAS,KAAK;AAClB,MAAI,CAAC,QAAQ;GAEX,MAAM,EAAE;AAGR,YAAS,IAAI,YAAY,KAAK;AAC9B,QAAK,SAAS;;AAEhB,SAAO;;;;;;;;;;;CAYT,IAAI,OAAuB;EACzB,IAAI,SAAS,KAAK;AAClB,MAAI,CAAC,QAAQ;GAEX,MAAM,EAAE;AAGR,YAAS,IAAI,WAAW,KAAK;AAC7B,QAAK,QAAQ;;AAEf,SAAO;;;;;CAMT,AAAQ,MAAM,SAAiB,MAAsB;AACnD,MAAI,KAAK,cAAc;GACrB,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,OAAI,SAAS,OAEX,SAAQ,IAAI,cAAc,UAAU,IAAI,WAAW,KAAK;OAGxD,SAAQ,IAAI,cAAc,UAAU,IAAI,UAAU;;;;;;;;;;;;;;;;CAkBxD,MAAM,kBACJ,SACsB;AACtB,OAAK,MAAM,wBAAwB;AACnC,MAAI,KAAK,OAAO,WAAW,OAAO,CAChC,SAAQ,KACN,iHACD;AAEH,SAAO,KAAK,KAAkB,qBAAqB,WAAW,EAAE,CAAC;;;;;;;;;;;;CAanE,MAAM,kBAAkB,IAA2B;AACjD,OAAK,MAAM,yBAAyB,EAAE,IAAI,CAAC;AAC3C,MAAI,KAAK,OAAO,WAAW,OAAO,CAChC,SAAQ,KACN,iHACD;AAEH,QAAM,KAAK,QAAc,qBAAqB,mBAAmB,GAAG,IAAI,EACtE,QAAQ,UACT,CAAC;;;;;CAMJ,MAAM,QAAW,MAAc,UAA0B,EAAE,EAAc;EACvE,MAAM,iBAAiB,QAAQ,kBAAkB,KAAK;EACtD,MAAM,kBAAkB,IAAI,iBAAiB;EAC7C,MAAM,iBAAiB,eACrB,gBAAgB,QAChB,QAAQ,OACT;EAED,MAAM,YAAY,iBAAiB;AACjC,mBAAgB,sBACd,IAAI,MAAM,2BAA2B,eAAe,IAAI,CACzD;KACA,eAAe;AAElB,OAAK,MAAM,GAAG,QAAQ,UAAU,MAAM,GAAG,QAAQ;GAC/C;GACA,SAAS,QAAQ,QAAQ;GAC1B,CAAC;AAEF,MAAI;GACF,MAAM,MAAM,YAAY,KAAK,SAAS,KAAK;GAC3C,MAAM,UAAU,IAAI,QAAQ,QAAQ,WAAW,EAAE,CAAC;AAClD,OAAI,CAAC,QAAQ,IAAI,eAAe,IAAI,QAAQ,QAAQ,KAClD,SAAQ,IAAI,gBAAgB,mBAAmB;AAEjD,WAAQ,IAAI,iBAAiB,UAAU,KAAK,SAAS;GAErD,MAAM,WAAW,MAAM,MAAM,KAAK;IAChC,GAAG;IACH;IACA,QAAQ;IACT,CAAC;AAIF,gBAAa,UAAU;AAEvB,OAAI,CAAC,SAAS,IAAI;IAChB,MAAM,WAAW,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;IACtD,IAAI,SAAkB;AACtB,QAAI,SACF,KAAI;AACF,cAAS,KAAK,MAAM,SAAS;YACvB;AACN,cAAS;;AAGb,UAAM,IAAI,SACR,uBAAuB,SAAS,OAAO,IACvC,SAAS,QACT,OACD;;AAGH,QAAK,MAAM,YAAY,SAAS,OAAO,GAAG,OAAO;AAEjD,OAAI,SAAS,WAAW,IACtB;GAGF,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,OAAI,CAAC,KACH;AAEF,OAAI;AACF,WAAO,KAAK,MAAM,KAAK;WACjB;AACN,WAAO;;WAEF,OAAgB;AACvB,gBAAa,UAAU;AACvB,QAAK,MAAM,mBAAmB,QAAQ,MAAM;AAE5C,OAAI,iBAAiB,SACnB,OAAM;AAER,OAAI,iBAAiB,qBACnB,OAAM;AAOR,OAHE,gBAAgB,OAAO,WACvB,gBAAgB,OAAO,kBAAkB,SACzC,gBAAgB,OAAO,OAAO,QAAQ,SAAS,YAAY,CAE3D,OAAM,IAAI,aAAa,2BAA2B,eAAe,IAAI;AAGvE,SAAM,IAAI,aAAa,2BADP,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACV;;;;;;CAOhE,MAAM,IAAO,MAAc,UAA0B,EAAE,EAAc;AACnE,SAAO,KAAK,QAAW,MAAM;GAAE,GAAG;GAAS,QAAQ;GAAO,CAAC;;;;;CAM7D,MAAM,KACJ,MACA,MACA,UAA0B,EAAE,EAChB;EACZ,MAAM,UACJ,SAAS,UAAa,SAAS,OAAO,SAAY,KAAK,UAAU,KAAK;AACxE,SAAO,KAAK,QAAW,MAAM;GAC3B,GAAG;GACH,QAAQ;GACR,MAAM;GACN,SAAS;IACP,GAAG;IACH,GAAG,QAAQ;IACZ;GACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;CA0BJ,OACE,MACA,MACA,UAAyB,EAAE,EACT;EAClB,MAAM,UAAU,IAAI,eAAkB;AACtC,UAAQ,aAAa;AAGrB,OAAK,cAAc,MAAM,MAAM,SAAS,QAAQ;AAEhD,SAAO;;;;;;;;;;;;;;;CAgBT,mBAAsB,QAAqC;EACzD,MAAM,UAAU,IAAI,eAAkB;AACtC,UAAQ,aAAa;AAGrB,OAAK,mBAAmB,QAAQ,QAAQ;AAExC,SAAO;;;;;CAMT,MAAc,mBACZ,QACA,SACe;AACf,MAAI;AACF,SAAM,iBACJ,SACC,YAAgC;IAE/B,IAAI;AACJ,QAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,SAAS,GACvD,KAAI;AACF,eAAU,KAAK,MAAM,QAAQ,KAAK;YAC5B;AACN,eAAU,QAAQ;;QAGpB,WAAU,QAAQ;IAIpB,MAAM,QAAQ,WAAW,QAAQ,SAAS,WAAW,QAAQ;AAC7D,QAAI,MACF,SAAQ,aAAa,MAAM;MAI/B;IACE,eAAe;AAEb,SACE,QAAQ,OAAO,UAAU,eACzB,QAAQ,OAAO,UAAU,QAEzB,SAAQ,aAAa;MAAE,OAAO;MAAY,MAAM,EAAE;MAAE,CAAC;;IAGzD,UAAU,UAAU;KAClB,MAAM,UACJ,iBAAiB,iBACb,QACA,IAAI,eACF,gCACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAExD,iBAAiB,WAAW,MAAM,SAAS,QAC3C,MACD;AACP,aAAQ,qBAAqB,QAAQ;;IAExC,CACF;WACM,OAAgB;AACvB,OACE,QAAQ,OAAO,UAAU,WACzB,QAAQ,OAAO,UAAU,YAGzB;GAGF,MAAM,UACJ,iBAAiB,iBACb,QACA,IAAI,eACF,qBACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAExD,iBAAiB,WAAW,MAAM,SAAS,QAC3C,MACD;AACP,WAAQ,qBAAqB,QAAQ;;;;;;CAOzC,MAAc,cACZ,MACA,MACA,SACA,SACe;EACf,MAAM,kBAAkB,IAAI,iBAAiB;EAC7C,MAAM,SAAS,iBAAiB,iBAAiB,QAAQ,OAAO;EAIhE,MAAM,oBAAoB,QAAQ,kBAAkB,KAAK;EACzD,MAAM,YAAY,iBAAiB;AACjC,OAAI,QAAQ,OAAO,UAAU,UAC3B,iBAAgB,sBACd,IAAI,MAAM,qCAAqC,kBAAkB,IAAI,CACtE;KAEF,kBAAkB;AAErB,MAAI;AACF,SAAM,iBAAiB,YAAY,KAAK,SAAS,KAAK,EAAE;IACtD,QAAQ;IACR,MAAM,KAAK,UAAU,QAAQ,EAAE,CAAC;IAChC,SAAS;KACP,GAAG;KACH,QAAQ;KACR,eAAe,UAAU,KAAK;KAC9B,GAAG,QAAQ;KACZ;IACD,QAAQ,gBAAgB;IACxB,gBAAgB;IAEhB,MAAM,OAAO,UAAU;AACrB,kBAAa,UAAU;KACvB,MAAM,cAAc,SAAS,QAAQ,IAAI,eAAe,IAAI;AAC5D,SAAI,CAAC,SAAS,IAAI;MAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAClD,YAAM,IAAI,eACR,0BAA0B,SAAS,OAAO,IAAI,QAC9C,SAAS,OACV;;AAEH,SAAI,CAAC,YAAY,SAAS,oBAAoB,CAC5C,OAAM,IAAI,eACR,uCAAuC,eACvC,SAAS,OACV;;IAIL,YAAY,YAAgC;KAE1C,IAAI;AACJ,SAAI,OAAO,QAAQ,SAAS,YAAY,QAAQ,SAAS,GACvD,KAAI;AACF,gBAAU,KAAK,MAAM,QAAQ,KAAK;aAC5B;AACN,gBAAU,QAAQ;;SAGpB,WAAU,QAAQ;KAIpB,MAAM,QAAQ,WAAW,QAAQ,SAAS,WAAW,QAAQ;AAC7D,SAAI,MACF,SAAQ,aAAa,MAAM;;IAK/B,eAAe;AAEb,SACE,QAAQ,OAAO,UAAU,eACzB,QAAQ,OAAO,UAAU,QAEzB,SAAQ,aAAa;MAAE,OAAO;MAAY,MAAM,EAAE;MAAE,CAAC;;IAIzD,UAAU,UAAU;AAClB,SAAI,gBAAgB,OAAO,QACzB;KAEF,MAAM,UACJ,iBAAiB,iBACb,QACA,IAAI,eACF,gCACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAExD,iBAAiB,WAAW,MAAM,SAAS,QAC3C,MACD;AACP,aAAQ,qBAAqB,QAAQ;AACrC,WAAM;;IAET,CAAC;WACK,OAAgB;AACvB,OACE,QAAQ,OAAO,UAAU,WACzB,QAAQ,OAAO,UAAU,YAGzB;GAIF,MAAM,YACJ,gBAAgB,OAAO,WACvB,gBAAgB,OAAO,kBAAkB,SACzC,gBAAgB,OAAO,OAAO,QAAQ,SAAS,YAAY;GAE7D,MAAM,UACJ,iBAAiB,iBACb,QACA,IAAI,eACF,YACI,qCAAqC,kBAAkB,MACvD,qBACE,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IAE5D,iBAAiB,WAAW,MAAM,SAAS,QAC3C,MACD;AACP,WAAQ,qBAAqB,QAAQ;YAC7B;AACR,gBAAa,UAAU;AACvB,aAAU;;;;;;;;AAWhB,SAAS,iBACP,SACA,UACqB;AACrB,KAAI,CAAC,SACH,QAAO;CAET,MAAM,qBAAqB;AACzB,UAAQ,MAAM,SAAS,OAAO;;AAEhC,KAAI,SAAS,SAAS;AACpB,UAAQ,MAAM,SAAS,OAAO;AAC9B,SAAO;;AAET,UAAS,iBAAiB,SAAS,cAAc,EAAE,MAAM,MAAM,CAAC;AAChE,cAAa,SAAS,oBAAoB,SAAS,aAAa;;;;;;AAOlE,SAAS,eACP,GAAG,SACU;CACb,MAAM,aAAa,IAAI,iBAAiB;AACxC,MAAK,MAAM,UAAU,QACnB,KAAI,QAAQ;AACV,MAAI,OAAO,SAAS;AAClB,cAAW,MAAM,OAAO,OAAO;AAC/B;;AAEF,SAAO,iBAAiB,eAAe,WAAW,MAAM,OAAO,OAAO,EAAE,EACtE,MAAM,MACP,CAAC;;AAGN,QAAO,WAAW;;AAGpB,SAAS,YAAY,SAAiB,MAAsB;CAC1D,MAAM,iBAAiB,QAAQ,SAAS,IAAI,GAAG,UAAU,GAAG,QAAQ;AACpE,QAAO,IAAI,IAAI,MAAM,eAAe,CAAC,UAAU;;;;;;AASjD,SAAS,UAAU,MAAkC;AACnD,KAAI,OAAO,YAAY,eAAe,QAAQ,IAC5C,QAAO,QAAQ,IAAI;;;AAMvB,SAAS,YAAqB;AAC5B,QAAO,OAAO,WAAW,eAAe,OAAO,aAAa"}
@@ -894,7 +894,7 @@ var init_client = __esmMin((() => {
894
894
  //#region src/core/client.ts
895
895
  init_errors();
896
896
  /** SDK version - matches package.json */
897
- const VERSION = "0.2.0";
897
+ const VERSION = "0.3.0";
898
898
  const DEFAULT_TELESCOPE_API_ORIGIN = "https://api.telescope.co";
899
899
  const DEFAULT_CONNECT_TIMEOUT = 3e4;
900
900
  const DEFAULT_JSON_HEADERS = { "Content-Type": "application/json" };
@@ -1014,39 +1014,37 @@ var TelescopeClient = class {
1014
1014
  }
1015
1015
  }
1016
1016
  /**
1017
- * Generate a client token for frontend clients.
1018
- * Typically called by backends using an organization API key.
1017
+ * Create a client token for frontend authentication.
1018
+ * Requires an API key (`tsk_`), not a client token.
1019
+ *
1020
+ * @example
1021
+ * ```ts
1022
+ * const client = new TelescopeClient({ apiKey: "tsk_..." });
1023
+ * const { token, expires_at } = await client.createClientToken({
1024
+ * client_id: "user-123",
1025
+ * expires_in: 3600,
1026
+ * });
1027
+ * ```
1019
1028
  */
1020
- async generateClientToken() {
1021
- this.debug("Generating client token");
1022
- const abortController = new AbortController();
1023
- const timeoutId = setTimeout(() => {
1024
- abortController.abort(/* @__PURE__ */ new Error(`Request timed out after ${this.connectTimeout}ms`));
1025
- }, this.connectTimeout);
1026
- try {
1027
- const response = await fetch(buildApiUrl(this.baseUrl, "/v2/client-tokens"), {
1028
- method: "POST",
1029
- headers: {
1030
- ...DEFAULT_JSON_HEADERS,
1031
- Authorization: `Bearer ${this.apiKey}`
1032
- },
1033
- body: JSON.stringify({}),
1034
- signal: abortController.signal
1035
- });
1036
- clearTimeout(timeoutId);
1037
- if (!response.ok) {
1038
- const text = await response.text().catch(() => "");
1039
- this.debug("Failed to generate client token", { status: response.status });
1040
- throw new ApiError(`Failed to generate client token: ${response.status} ${response.statusText} ${text}`, response.status, text);
1041
- }
1042
- const payload = await response.json();
1043
- if (!payload.token) throw new Error("Client token response missing token field");
1044
- this.debug("Client token generated successfully");
1045
- return payload.token;
1046
- } catch (error) {
1047
- clearTimeout(timeoutId);
1048
- throw error;
1049
- }
1029
+ async createClientToken(options) {
1030
+ this.debug("Creating client token");
1031
+ if (this.apiKey.startsWith("tsc_")) console.warn("[Telescope] createClientToken() requires an API key (tsk_), not a client token. This request will likely fail.");
1032
+ return this.post("/v2/client-tokens", options ?? {});
1033
+ }
1034
+ /**
1035
+ * Revoke a client token by ID. The token will immediately become invalid.
1036
+ * Requires an API key (`tsk_`), not a client token.
1037
+ *
1038
+ * @example
1039
+ * ```ts
1040
+ * const client = new TelescopeClient({ apiKey: "tsk_..." });
1041
+ * await client.revokeClientToken("token-uuid-here");
1042
+ * ```
1043
+ */
1044
+ async revokeClientToken(id) {
1045
+ this.debug("Revoking client token", { id });
1046
+ if (this.apiKey.startsWith("tsc_")) console.warn("[Telescope] revokeClientToken() requires an API key (tsk_), not a client token. This request will likely fail.");
1047
+ await this.request(`/v2/client-tokens/${encodeURIComponent(id)}`, { method: "DELETE" });
1050
1048
  }
1051
1049
  /**
1052
1050
  * Make a generic HTTP request.
@@ -1446,4 +1444,4 @@ Object.defineProperty(exports, 'processSSEStream', {
1446
1444
  return processSSEStream;
1447
1445
  }
1448
1446
  });
1449
- //# sourceMappingURL=client-DDdIEKTi.cjs.map
1447
+ //# sourceMappingURL=client-FIvbJklt.cjs.map