Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | /**
* LLM Client Singleton for apps/web
*
* This module provides a singleton instance of the LLM client that reads
* configuration from environment variables. The client supports multiple
* providers (OpenAI, Anthropic) and provides type-safe LLM calls with
* Zod schema validation.
*
* IMPORTANT: This module is transitively imported by client components
* (via worksheet-parsing). It MUST NOT import any server-only modules
* (like @/db, node:http, etc.) — not even via dynamic import().
*
* @example
* ```typescript
* import { llm } from '@/lib/llm'
* import { z } from 'zod'
*
* const response = await llm.call({
* prompt: 'Analyze this text...',
* schema: z.object({ sentiment: z.enum(['positive', 'negative', 'neutral']) }),
* })
* ```
*
* @see packages/llm-client/README.md for full documentation
*/
import { createPersistenceMiddleware, LLMClient } from '@soroban/llm-client'
import type { StreamMiddleware } from '@soroban/llm-client'
import type { TaskHandle } from './task-manager'
import type { TaskEventBase } from './tasks/events'
// Re-export LLMClient class for use in other modules
export { LLMClient }
// Configurable snapshot interval for LLM streaming persistence
// Can be adjusted or moved to env config if needed
export const LLM_SNAPSHOT_INTERVAL_MS = 3000
// Create singleton instance
// Configuration is automatically loaded from environment variables:
// - LLM_DEFAULT_PROVIDER: Default provider (default: 'openai')
// - LLM_DEFAULT_MODEL: Default model override
// - LLM_OPENAI_API_KEY: OpenAI API key
// - LLM_OPENAI_BASE_URL: OpenAI base URL (optional)
// - LLM_ANTHROPIC_API_KEY: Anthropic API key
// - LLM_ANTHROPIC_BASE_URL: Anthropic base URL (optional)
export const llm = new LLMClient()
/**
* Create an LLM client bound to a task handle.
*
* Automatically:
* - Emits transient reasoning/output events to Socket.IO (real-time UI)
* - Persists reasoning/output snapshots every 3s (page-reload recovery)
*
* @param handle - The task handle for emitting events
* @param usageContext - Optional usage recording context. When provided, the
* caller should build a usage recording middleware via
* `createUsageRecordingMiddleware()` from `@/lib/ai-usage/llm-middleware`
* and pass it as `extraMiddleware`.
* @returns A derived LLM client with persistence middleware
*
* @example
* ```typescript
* const taskLLM = createTaskLLM(handle)
* for await (const event of taskLLM.stream({ prompt, schema })) {
* // Middleware handles reasoning, output_delta, and snapshots automatically
* if (event.type === 'complete') {
* finalResult = event.data
* } else if (event.type === 'error') {
* llmError = { message: event.message, code: event.code }
* }
* }
* ```
*/
export function createTaskLLM<
TOutput,
TEvent extends TaskEventBase & {
type: string
text?: string
isDelta?: boolean
summaryIndex?: number
outputIndex?: number
},
>(handle: TaskHandle<TOutput, TEvent>, extraMiddleware?: StreamMiddleware) {
const middlewares: StreamMiddleware[] = [
createPersistenceMiddleware({
snapshotIntervalMs: LLM_SNAPSHOT_INTERVAL_MS,
onReasoning: (text, isDelta, _accumulated) => {
handle.emitTransient({ type: 'reasoning', text, isDelta } as TEvent)
},
onOutputDelta: (text, _accumulated) => {
handle.emitTransient({ type: 'output_delta', text } as TEvent)
},
onReasoningSnapshot: (text) => {
handle.emit({ type: 'reasoning_snapshot', text } as TEvent)
},
onOutputSnapshot: (text) => {
handle.emit({ type: 'output_snapshot', text } as TEvent)
},
}),
]
if (extraMiddleware) {
middlewares.push(extraMiddleware)
}
return llm.with(...middlewares)
}
// Re-export types and utilities for convenience
export type {
LLMClientConfig,
LLMProgress,
LLMProvider,
LLMRequest,
LLMResponse,
// Streaming types
LLMStreamRequest,
LoggerFn,
LoggingConfig,
// Logging types
LogLevel,
PersistenceOptions,
ProviderConfig,
ProviderRequest,
ProviderResponse,
ReasoningConfig,
ReasoningEffort,
StreamEvent,
StreamEventComplete,
StreamEventError,
StreamEventOutputDelta,
StreamEventReasoning,
StreamEventStarted,
// Middleware types
StreamMiddleware,
ValidationFeedback,
} from '@soroban/llm-client'
export {
// Middleware
createPersistenceMiddleware,
defaultLogger,
LLMApiError,
LLMNetworkError,
LLMTimeoutError,
LLMValidationError,
// Logging
Logger,
ProviderNotConfiguredError,
} from '@soroban/llm-client'
|