@almadar/llm 2.16.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZZO446DL.js → chunk-4NG4445W.js} +11 -2
- package/dist/chunk-4NG4445W.js.map +1 -0
- package/dist/{client-DMCU9rVo.d.ts → client-DK8e0wEb.d.ts} +10 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/client.ts +36 -1
- package/dist/chunk-ZZO446DL.js.map +0 -1
|
@@ -210,6 +210,7 @@ var _LLMClient = class _LLMClient {
|
|
|
210
210
|
this.streaming = options.streaming ?? false;
|
|
211
211
|
this.providerConfig = PROVIDER_CONFIGS[this.provider]();
|
|
212
212
|
this.modelName = options.model || this.providerConfig.defaultModel;
|
|
213
|
+
this.rawTimeoutMs = options.rawTimeoutMs ?? _LLMClient.DEFAULT_RAW_TIMEOUT_MS;
|
|
213
214
|
const keyPreview = this.providerConfig.apiKey.slice(-4);
|
|
214
215
|
console.log(
|
|
215
216
|
`[LLMClient] Provider: ${this.provider}, Model: ${this.modelName}, Key: ****${keyPreview}`
|
|
@@ -474,7 +475,8 @@ Please output valid JSON that matches the expected schema.`;
|
|
|
474
475
|
* indefinitely with no feedback. This wrapper makes hangs visible (start
|
|
475
476
|
* + duration logs) and bounded (timeout fires with a clear error).
|
|
476
477
|
*/
|
|
477
|
-
async invokeWithObservability(label, modelToUse, langchainMessages, callerSignal,
|
|
478
|
+
async invokeWithObservability(label, modelToUse, langchainMessages, callerSignal, timeoutMsArg) {
|
|
479
|
+
const timeoutMs = timeoutMsArg ?? this.rawTimeoutMs;
|
|
478
480
|
const startedAt = Date.now();
|
|
479
481
|
const controller = new AbortController();
|
|
480
482
|
const timeoutHandle = setTimeout(
|
|
@@ -637,6 +639,9 @@ Please output valid JSON that matches the expected schema.`;
|
|
|
637
639
|
};
|
|
638
640
|
if (options.tools.length > 0) body["tools"] = options.tools;
|
|
639
641
|
if (options.maxTokens !== void 0) body["max_tokens"] = options.maxTokens;
|
|
642
|
+
if (this.provider === "openrouter") {
|
|
643
|
+
body["reasoning"] = { enabled: true };
|
|
644
|
+
}
|
|
640
645
|
const startedAt = Date.now();
|
|
641
646
|
console.log(
|
|
642
647
|
`[LLMClient:callWithTools] Invoking ${this.provider}/${this.modelName} (tools=${options.tools.length}, messages=${options.messages.length})`
|
|
@@ -667,6 +672,10 @@ Please output valid JSON that matches the expected schema.`;
|
|
|
667
672
|
if (!choice) {
|
|
668
673
|
throw new Error("LLMClient.callWithTools: no choices in response");
|
|
669
674
|
}
|
|
675
|
+
const msgWithReasoning = choice.message;
|
|
676
|
+
if (msgWithReasoning.reasoning !== void 0 && choice.message.reasoning_content === void 0) {
|
|
677
|
+
choice.message.reasoning_content = msgWithReasoning.reasoning;
|
|
678
|
+
}
|
|
670
679
|
let usage = null;
|
|
671
680
|
if (parsed.usage) {
|
|
672
681
|
usage = {
|
|
@@ -983,4 +992,4 @@ export {
|
|
|
983
992
|
createOpenRouterClient,
|
|
984
993
|
createZhipuClient
|
|
985
994
|
};
|
|
986
|
-
//# sourceMappingURL=chunk-
|
|
995
|
+
//# sourceMappingURL=chunk-4NG4445W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/tool-call-types.ts"],"sourcesContent":["/**\n * Shared LLM Client\n *\n * Multi-provider LLM client with:\n * - OpenAI, DeepSeek, Anthropic, and Kimi support\n * - Anthropic prompt caching (CachingChatAnthropic)\n * - Rate limiting and retry logic\n * - Token tracking\n * - Structured output parsing with Zod\n *\n * @packageDocumentation\n */\n\nimport { ChatOpenAI } from '@langchain/openai';\nimport { ChatAnthropic } from '@langchain/anthropic';\nimport type { BaseMessageLike } from '@langchain/core/messages';\nimport Anthropic from '@anthropic-ai/sdk';\nimport { z } from 'zod';\nimport {\n RateLimiter,\n getGlobalRateLimiter,\n type RateLimiterOptions,\n} from './rate-limiter.js';\nimport { TokenTracker, getGlobalTokenTracker } from './token-tracker.js';\nimport { parseJsonResponse } from './json-parser.js';\nimport {\n parseChatCompletionResponse,\n type ChatCompletionMessage,\n type ChatCompletionToolDef,\n} from './tool-call-types.js';\n\n// ============================================================================\n// Local type helpers (avoid Record<string, unknown> and unsafe casts)\n// ============================================================================\n\n/** Anthropic generation output with usage metadata (not in Langchain's base types). */\ninterface AnthropicGenerationWithUsage {\n message?: {\n usage_metadata?: {\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n input_tokens?: number;\n output_tokens?: number;\n };\n };\n}\n\n/** Response metadata from OpenAI-compatible providers. */\ninterface OpenAIResponseMetadata {\n finish_reason?: string;\n}\n\n/** Model-specific kwargs passed to ChatOpenAI constructor. */\ninterface ModelKwargs {\n max_completion_tokens?: number;\n thinking?: { type: string };\n tool_choice?: string;\n}\n\n/**\n * Identity cast for generic return types.\n * Used when a string value must satisfy a generic T parameter\n * (e.g., rawText mode where caller declares T = string).\n *\n * Safety: callers only reach this path when rawText=true, which\n * constrains T to string by convention. TypeScript cannot verify\n * this constraint statically because T is caller-supplied.\n */\nfunction asGeneric<T>(value: string): T {\n return value as T;\n}\n\n// ============================================================================\n// Anthropic Cache Control Helper\n// ============================================================================\n\nfunction addCacheControlToSystemMessages(\n messages: Array<{ role: string; content: string }>,\n): BaseMessageLike[] {\n return messages.map((msg) => {\n if (msg.role !== 'system') {\n return msg as BaseMessageLike;\n }\n\n return {\n role: msg.role,\n content: [\n {\n type: 'text' as const,\n text: msg.content,\n cache_control: { type: 'ephemeral' },\n },\n ],\n } as BaseMessageLike;\n });\n}\n\ntype ChatModel = ChatOpenAI | ChatAnthropic;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type LLMProvider = 'openai' | 'deepseek' | 'anthropic' | 'kimi' | 'openrouter' | 'orbgen';\n\nexport interface ProviderConfig {\n apiKey: string;\n baseUrl?: string;\n defaultModel: string;\n}\n\nexport interface LLMClientOptions {\n provider?: LLMProvider;\n model?: string;\n temperature?: number;\n streaming?: boolean;\n rateLimiter?: RateLimiterOptions;\n useGlobalRateLimiter?: boolean;\n trackTokens?: boolean;\n /**\n * Hard per-call deadline (ms) for the raw invoke paths (`callRaw`,\n * `callRawWithMetadata`, `callWithMessages`). Defaults to 5 min. Set this\n * BELOW the caller's own wall-clock cap so a hung connection aborts in\n * time for the caller to recover (e.g. a battery spec capped at 300s sets\n * 120s here so a hang fails fast and leaves budget for a retry, instead of\n * the call consuming the entire 300s).\n */\n rawTimeoutMs?: number;\n}\n\nexport interface LLMCallOptions<T = unknown> {\n systemPrompt: string;\n userPrompt: string;\n schema?: z.ZodSchema<T>;\n maxRetries?: number;\n retryWithContext?: boolean;\n maxTokens?: number;\n skipSchemaValidation?: boolean;\n temperature?: number;\n}\n\nexport interface CacheableBlock {\n type: 'text';\n text: string;\n cache_control?: { type: 'ephemeral' };\n}\n\nexport interface CacheAwareLLMCallOptions<T = unknown>\n extends LLMCallOptions<T> {\n systemBlocks?: CacheableBlock[];\n userBlocks?: CacheableBlock[];\n rawText?: boolean;\n}\n\nexport interface LLMUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\nexport type LLMFinishReason =\n | 'stop'\n | 'length'\n | 'content_filter'\n | 'tool_calls'\n | null;\n\nexport interface LLMResponse<T> {\n data: T;\n raw: string;\n finishReason: LLMFinishReason;\n usage: LLMUsage | null;\n}\n\nexport interface LLMStreamOptions {\n systemPrompt: string;\n messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>;\n maxTokens?: number;\n temperature?: number;\n}\n\nexport interface LLMStreamChunk {\n content: string;\n done: boolean;\n}\n\n// ============================================================================\n// Provider Configuration\n// ============================================================================\n\nconst PROVIDER_CONFIGS: Record<LLMProvider, () => ProviderConfig> = {\n openai: () => {\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'OPENAI_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return { apiKey, baseUrl: undefined, defaultModel: 'gpt-4o' };\n },\n deepseek: () => {\n const apiKey = process.env.DEEPSEEK_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'DEEPSEEK_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: 'https://api.deepseek.com/v1',\n defaultModel: 'deepseek-v4-flash',\n };\n },\n anthropic: () => {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ANTHROPIC_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: undefined,\n defaultModel: 'claude-sonnet-4-5-20250929',\n };\n },\n kimi: () => {\n const apiKey = process.env.KIMI_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'KIMI_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: 'https://api.moonshot.ai/v1',\n defaultModel: 'kimi-k2.5',\n };\n },\n openrouter: () => {\n const apiKey = process.env.OPEN_ROUTER_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'OPEN_ROUTER_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModel: 'qwen/qwen-2.5-72b-instruct', // Default to Qwen 2.5\n };\n },\n orbgen: () => {\n const baseUrl = process.env.ORBGEN_URL;\n if (!baseUrl) {\n throw new Error(\n 'ORBGEN_URL environment variable is not set. ' +\n 'Set it to the OrbGen Cloud Run URL (e.g., https://orbgen-v2-xxx.run.app)',\n );\n }\n return {\n apiKey: 'not-needed',\n baseUrl: `${baseUrl}/v1`,\n defaultModel: 'orbgen-v2',\n };\n },\n};\n\nexport const DEEPSEEK_MODELS = {\n CHAT: 'deepseek-chat',\n CODER: 'deepseek-coder',\n REASONER: 'deepseek-reasoner',\n V4_PRO: 'deepseek-v4-pro',\n V4_FLASH: 'deepseek-v4-flash',\n} as const;\n\nexport const OPENAI_MODELS = {\n GPT4O: 'gpt-4o',\n GPT4O_MINI: 'gpt-4o-mini',\n GPT4_TURBO: 'gpt-4-turbo',\n GPT35_TURBO: 'gpt-3.5-turbo',\n GPT_5_1: 'gpt-5.1',\n} as const;\n\nexport const ANTHROPIC_MODELS = {\n CLAUDE_SONNET_4_5: 'claude-sonnet-4-5-20250929',\n CLAUDE_SONNET_4: 'claude-sonnet-4-20250514',\n CLAUDE_OPUS_4_5: 'claude-opus-4-5-20250929',\n CLAUDE_3_5_HAIKU: 'claude-3-5-haiku-20241022',\n} as const;\n\nexport const KIMI_MODELS = {\n K2_5: 'kimi-k2.5',\n} as const;\n\nexport const OPENROUTER_MODELS = {\n // Qwen models - JSON/structured data specialists\n QWEN_2_5_72B: 'qwen/qwen-2.5-72b-instruct',\n QWEN_2_5_CODER_32B: 'qwen/qwen-2.5-coder-32b-instruct',\n QWEN_3_235B: 'qwen/qwen3-235b-a22b',\n\n // Gemma models - best small models for structured JSON output\n // Gemma 3 4B: 6/6 on complex decomposition, 100% behavior matching, free, fastest\n GEMMA_3_4B: 'google/gemma-3-4b-it',\n GEMMA_3_12B: 'google/gemma-3-12b-it',\n GEMMA_3_27B: 'google/gemma-3-27b-it',\n\n // Mistral models - strong structured output, function calling\n MINISTRAL_8B: 'mistralai/ministral-8b-2512',\n // Mistral Small 3.1: 6/6 on complex decomposition, picked std-kanban for tasks\n MISTRAL_SMALL_3_1: 'mistralai/mistral-small-3.1-24b-instruct',\n // Mistral Medium 3.1: next tier up from Small, stronger reasoning, tool calling\n MISTRAL_MEDIUM_3_1: 'mistralai/mistral-medium-3.1',\n\n // Llama models - agentic workhorses\n LLAMA_3_3_70B: 'meta-llama/llama-3.3-70b-instruct',\n LLAMA_3_1_405B: 'meta-llama/llama-3.1-405b-instruct',\n LLAMA_4_MAVERICK: 'meta-llama/llama-4-maverick',\n LLAMA_4_SCOUT: 'meta-llama/llama-4-scout',\n\n // Kimi models - strong reasoning\n KIMI_K2: 'moonshotai/kimi-k2',\n\n // Zhipu GLM models - via OpenRouter\n GLM_4_7: 'z-ai/glm-4.7',\n} as const;\n\nconst DEFAULT_TEMPERATURE = 0.3;\n\n// ============================================================================\n// LLM Client\n// ============================================================================\n\nexport class LLMClient {\n private model: ChatModel;\n private rateLimiter: RateLimiter;\n private tokenTracker: TokenTracker | null;\n private modelName: string;\n private provider: LLMProvider;\n private providerConfig: ProviderConfig;\n private temperature: number;\n private streaming: boolean;\n private rawTimeoutMs: number;\n\n constructor(options: LLMClientOptions = {}) {\n this.provider = options.provider || 'openai';\n // Kimi: 0.6 when thinking disabled (our default), 1.0 when thinking enabled\n this.temperature = options.temperature ?? \n (this.provider === 'kimi' ? 0.6 : DEFAULT_TEMPERATURE);\n this.streaming = options.streaming ?? false;\n\n this.providerConfig = PROVIDER_CONFIGS[this.provider]();\n this.modelName = options.model || this.providerConfig.defaultModel;\n this.rawTimeoutMs = options.rawTimeoutMs ?? LLMClient.DEFAULT_RAW_TIMEOUT_MS;\n\n const keyPreview = this.providerConfig.apiKey.slice(-4);\n console.log(\n `[LLMClient] Provider: ${this.provider}, Model: ${this.modelName}, Key: ****${keyPreview}`,\n );\n if (this.providerConfig.baseUrl) {\n console.log(\n `[LLMClient] Using custom base URL: ${this.providerConfig.baseUrl}`,\n );\n }\n\n this.model = this.createModel();\n\n this.rateLimiter =\n options.useGlobalRateLimiter !== false\n ? getGlobalRateLimiter(options.rateLimiter)\n : new RateLimiter(options.rateLimiter);\n\n this.tokenTracker =\n options.trackTokens !== false\n ? getGlobalTokenTracker(this.modelName)\n : null;\n }\n\n private usesMaxCompletionTokens(): boolean {\n const model = this.modelName.toLowerCase();\n return (\n model.startsWith('o1') ||\n model.startsWith('gpt-5') ||\n model.includes('o1-') ||\n model.includes('o3')\n );\n }\n\n private createModel(options?: {\n maxTokens?: number;\n temperature?: number;\n }): ChatModel {\n const maxTokens = options?.maxTokens;\n const temperature = options?.temperature ?? this.temperature;\n\n if (this.provider === 'anthropic') {\n return new ChatAnthropic({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature,\n streaming: this.streaming,\n maxTokens: maxTokens || 8192,\n callbacks: [\n {\n handleLLMEnd: (output) => {\n const generation = output.generations?.[0]?.[0];\n const generationWithUsage = generation as\n | (typeof generation & AnthropicGenerationWithUsage)\n | undefined;\n const usage = generationWithUsage?.message?.usage_metadata;\n\n if (usage) {\n const cacheCreated = usage.cache_creation_input_tokens ?? 0;\n const cacheRead = usage.cache_read_input_tokens ?? 0;\n const inputTokens = usage.input_tokens ?? 0;\n const outputTokens = usage.output_tokens ?? 0;\n\n if (cacheCreated > 0) {\n console.log(\n `[LLMClient:Anthropic] Cache WRITE: ${cacheCreated} tokens cached`,\n );\n }\n if (cacheRead > 0) {\n const savingsPercent = Math.round(\n (cacheRead / (cacheRead + inputTokens)) * 100,\n );\n console.log(\n `[LLMClient:Anthropic] Cache HIT: ${cacheRead} tokens (~${savingsPercent}% of prompt)`,\n );\n }\n if (cacheCreated === 0 && cacheRead === 0 && inputTokens > 0) {\n if (inputTokens < 500) {\n console.log(\n `[LLMClient:Anthropic] ${inputTokens} input, ${outputTokens} output tokens (likely cached)`,\n );\n } else {\n console.log(\n `[LLMClient:Anthropic] ${inputTokens} input, ${outputTokens} output tokens`,\n );\n }\n }\n }\n },\n },\n ],\n });\n }\n\n const useCompletionTokens = this.usesMaxCompletionTokens();\n\n const tokenConfig = maxTokens\n ? useCompletionTokens\n ? { modelKwargs: { max_completion_tokens: maxTokens } }\n : { maxTokens }\n : {};\n\n const timeout = this.provider === 'deepseek' ? 600000 : undefined;\n\n // Kimi-k2.5: disable thinking to avoid reasoning_content issues with tool calls\n // When thinking is disabled, temperature must be 0.6 (not 1.0)\n const isKimi = this.provider === 'kimi';\n const effectiveTemp = isKimi ? 0.6 : temperature;\n\n // Build modelKwargs incrementally to avoid spread conflicts\n const modelKwargs: ModelKwargs = {};\n if (useCompletionTokens && maxTokens) {\n modelKwargs.max_completion_tokens = maxTokens;\n }\n if (isKimi) {\n modelKwargs.thinking = { type: 'disabled' };\n }\n // OpenRouter (Qwen): explicit tool_choice so the model doesn't ignore tool definitions\n if (this.provider === 'openrouter') {\n modelKwargs.tool_choice = 'auto';\n }\n\n return new ChatOpenAI({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature: useCompletionTokens ? undefined : effectiveTemp,\n streaming: this.streaming,\n timeout,\n ...(Object.keys(modelKwargs).length > 0 ? { modelKwargs } : {}),\n ...(useCompletionTokens ? {} : maxTokens ? { maxTokens } : {}),\n configuration: {\n apiKey: this.providerConfig.apiKey,\n ...(this.providerConfig.baseUrl\n ? { baseURL: this.providerConfig.baseUrl }\n : {}),\n },\n });\n }\n\n private getModelWithOptions(options: {\n maxTokens?: number;\n temperature?: number;\n }): ChatModel {\n return this.createModel(options);\n }\n\n /**\n * Check if this model is a Qwen3.5 thinking model.\n * These models burn all output tokens on internal reasoning\n * unless thinking is explicitly disabled via /no_think prefix.\n */\n private isQwenThinkingModel(): boolean {\n return this.modelName.includes('qwen3.5');\n }\n\n /**\n * Prepare user prompt with provider-specific adjustments.\n * Qwen3.5 models require /no_think to disable reasoning mode.\n */\n private prepareUserPrompt(prompt: string): string {\n if (this.isQwenThinkingModel()) {\n return `/no_think\\n${prompt}`;\n }\n return prompt;\n }\n\n getProvider(): LLMProvider {\n return this.provider;\n }\n\n getModelName(): string {\n return this.modelName;\n }\n\n getModel(): ChatModel {\n return this.model;\n }\n\n getRateLimiterStatus() {\n return this.rateLimiter.getStatus();\n }\n\n getTokenUsage() {\n return this.tokenTracker?.getSummary() ?? null;\n }\n\n async call<T>(options: LLMCallOptions<T>): Promise<T> {\n const response = await this.callWithMetadata(options);\n return response.data;\n }\n\n async callWithMetadata<T>(options: LLMCallOptions<T>): Promise<LLMResponse<T>> {\n const {\n systemPrompt,\n userPrompt,\n schema,\n maxRetries = 2,\n retryWithContext = true,\n maxTokens,\n skipSchemaValidation = false,\n temperature,\n } = options;\n\n let currentPrompt = userPrompt;\n let lastError: Error | null = null;\n\n console.log(\n `[LLMClient:call] Starting call to ${this.provider}/${this.modelName}`,\n );\n console.log(`[LLMClient:call] Prompt length: ${userPrompt.length} chars`);\n if (maxTokens) {\n console.log(`[LLMClient:call] Max tokens: ${maxTokens}`);\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n console.log(\n `[LLMClient:call] Attempt ${attempt + 1}/${maxRetries + 1}...`,\n );\n const attemptStartTime = Date.now();\n\n const result = await this.rateLimiter.execute(async () => {\n console.log(`[LLMClient:call] Invoking model...`);\n const invokeStartTime = Date.now();\n\n const modelToUse =\n maxTokens || temperature !== undefined\n ? this.getModelWithOptions({ maxTokens, temperature })\n : this.model;\n\n const messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: this.prepareUserPrompt(currentPrompt) },\n ];\n const response = await modelToUse.invoke(\n this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : messages,\n );\n\n console.log(\n `[LLMClient:call] Model responded in ${Date.now() - invokeStartTime}ms`,\n );\n\n let usage: LLMUsage | null = null;\n if (response.usage_metadata) {\n const usageMeta = response.usage_metadata as {\n input_tokens?: number;\n output_tokens?: number;\n };\n usage = {\n promptTokens: usageMeta.input_tokens || 0,\n completionTokens: usageMeta.output_tokens || 0,\n totalTokens:\n (usageMeta.input_tokens || 0) +\n (usageMeta.output_tokens || 0),\n };\n console.log(\n `[LLMClient:call] Tokens used: ${usage.promptTokens} in, ${usage.completionTokens} out`,\n );\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n { provider: this.provider },\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n if (finishReason === 'length') {\n console.warn(\n `[LLMClient:call] Response truncated (finish_reason=length)`,\n );\n }\n\n const content =\n typeof response.content === 'string'\n ? response.content\n : JSON.stringify(response.content);\n\n console.log(\n `[LLMClient:call] Response length: ${content.length} chars, finish_reason: ${finishReason}`,\n );\n\n return { content, finishReason, usage };\n });\n\n console.log(\n `[LLMClient:call] Attempt ${attempt + 1} completed in ${Date.now() - attemptStartTime}ms, parsing response...`,\n );\n\n const parsed = skipSchemaValidation\n ? (parseJsonResponse(result.content, undefined) as T)\n : parseJsonResponse(result.content, schema);\n console.log(\n `[LLMClient:call] Response parsed successfully${skipSchemaValidation ? ' (schema validation skipped)' : ''}`,\n );\n\n return {\n data: parsed,\n raw: result.content,\n finishReason: result.finishReason,\n usage: result.usage,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n console.error(\n `[LLMClient:call] Attempt ${attempt + 1} failed:`,\n lastError.message,\n );\n\n if (this.isRateLimitError(lastError)) {\n console.error(`[LLMClient:call] Rate limit error, not retrying`);\n throw lastError;\n }\n\n if (attempt < maxRetries && retryWithContext) {\n console.log(`[LLMClient:call] Will retry with error context`);\n currentPrompt =\n `${userPrompt}\\n\\n` +\n `[Previous attempt failed with: ${lastError.message}]\\n` +\n `Please output valid JSON that matches the expected schema.`;\n }\n }\n }\n\n console.error(`[LLMClient:call] All attempts exhausted, throwing error`);\n throw lastError;\n }\n\n private extractFinishReason(\n response: Awaited<ReturnType<ChatOpenAI['invoke']>>,\n ): LLMFinishReason {\n const metadata = response.response_metadata as\n | OpenAIResponseMetadata\n | undefined;\n if (metadata?.finish_reason) {\n const reason = metadata.finish_reason as string;\n if (\n reason === 'stop' ||\n reason === 'length' ||\n reason === 'content_filter' ||\n reason === 'tool_calls'\n ) {\n return reason;\n }\n }\n return null;\n }\n\n /**\n * Default hard timeout for raw LLM invocations. The langchain ChatOpenAI\n * `timeout` option doesn't reliably fire when an HTTP connection\n * half-opens (the response stream can sit open indefinitely), so every\n * raw call gets wrapped in an AbortController-driven deadline. Callers\n * can override by passing their own `signal`.\n */\n private static readonly DEFAULT_RAW_TIMEOUT_MS = 5 * 60 * 1000; // 5 min\n\n /**\n * Wrap a `model.invoke(...)` with start/finish/error logging and a hard\n * abort-driven timeout. Returns whatever the underlying `invoke` returns.\n *\n * The raw paths (`callRaw`, `callRawWithMetadata`, `callWithMessages`)\n * used to be silent — when a connection half-opened, callers sat\n * indefinitely with no feedback. This wrapper makes hangs visible (start\n * + duration logs) and bounded (timeout fires with a clear error).\n */\n private async invokeWithObservability(\n label: string,\n modelToUse: ChatModel,\n langchainMessages: BaseMessageLike[],\n callerSignal?: AbortSignal,\n timeoutMsArg?: number,\n ): Promise<Awaited<ReturnType<ChatModel['invoke']>>> {\n const timeoutMs = timeoutMsArg ?? this.rawTimeoutMs;\n const startedAt = Date.now();\n const controller = new AbortController();\n const timeoutHandle = setTimeout(\n () => controller.abort(new Error(`LLM call timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n if (callerSignal) {\n if (callerSignal.aborted) {\n controller.abort(callerSignal.reason);\n } else {\n callerSignal.addEventListener(\n 'abort',\n () => controller.abort(callerSignal.reason),\n { once: true },\n );\n }\n }\n console.log(\n `[LLMClient:${label}] Invoking ${this.provider}/${this.modelName} (timeout=${timeoutMs}ms)`,\n );\n try {\n const response = await modelToUse.invoke(langchainMessages, {\n signal: controller.signal,\n });\n console.log(\n `[LLMClient:${label}] Responded in ${Date.now() - startedAt}ms`,\n );\n return response;\n } catch (err) {\n const elapsed = Date.now() - startedAt;\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `[LLMClient:${label}] FAILED after ${elapsed}ms: ${message}`,\n );\n throw err;\n } finally {\n clearTimeout(timeoutHandle);\n }\n }\n\n async callRaw(options: {\n systemPrompt: string;\n userPrompt: string;\n maxTokens?: number;\n signal?: AbortSignal;\n }): Promise<string> {\n const response = await this.callRawWithMetadata(options);\n return response.raw;\n }\n\n async callRawWithMetadata(options: {\n systemPrompt: string;\n userPrompt: string;\n maxTokens?: number;\n signal?: AbortSignal;\n }): Promise<Omit<LLMResponse<string>, 'data'> & { raw: string }> {\n const { systemPrompt, userPrompt, maxTokens, signal } = options;\n\n return this.rateLimiter.execute(async () => {\n const modelToUse = maxTokens\n ? this.getModelWithOptions({ maxTokens })\n : this.model;\n\n const messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: this.prepareUserPrompt(userPrompt) },\n ];\n const response = await this.invokeWithObservability(\n 'callRawWithMetadata',\n modelToUse,\n this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : (messages as BaseMessageLike[]),\n signal,\n );\n\n let usage: LLMUsage | null = null;\n if (response.usage_metadata) {\n const usageMeta = response.usage_metadata as {\n input_tokens?: number;\n output_tokens?: number;\n };\n usage = {\n promptTokens: usageMeta.input_tokens || 0,\n completionTokens: usageMeta.output_tokens || 0,\n totalTokens:\n (usageMeta.input_tokens || 0) + (usageMeta.output_tokens || 0),\n };\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n const content =\n typeof response.content === 'string'\n ? response.content\n : JSON.stringify(response.content);\n\n return { raw: content, finishReason, usage };\n });\n }\n\n /**\n * Call the LLM with a structured messages array.\n *\n * Unlike callRawWithMetadata (which takes systemPrompt + userPrompt strings),\n * this accepts a full conversation history with proper role separation.\n * This enables:\n * - Anthropic prompt caching on message boundaries (not just system prompt)\n * - Proper tool_use/tool_result role handling across providers\n * - Reduced token waste from string concatenation\n *\n * All providers support the messages format:\n * - Anthropic: native messages API with cache_control\n * - DeepSeek: OpenAI-compatible messages via ChatOpenAI\n * - OpenRouter: OpenAI-compatible messages via ChatOpenAI\n */\n async callWithMessages(options: {\n messages: Array<{ role: string; content: string }>;\n maxTokens?: number;\n signal?: AbortSignal;\n }): Promise<Omit<LLMResponse<string>, 'data'> & { raw: string }> {\n const { messages, maxTokens, signal } = options;\n\n return this.rateLimiter.execute(async () => {\n const modelToUse = maxTokens\n ? this.getModelWithOptions({ maxTokens })\n : this.model;\n\n const langchainMessages = this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : (messages as BaseMessageLike[]);\n\n const response = await this.invokeWithObservability(\n 'callWithMessages',\n modelToUse,\n langchainMessages,\n signal,\n );\n\n let usage: LLMUsage | null = null;\n if (response.usage_metadata) {\n const usageMeta = response.usage_metadata as {\n input_tokens?: number;\n output_tokens?: number;\n };\n usage = {\n promptTokens: usageMeta.input_tokens || 0,\n completionTokens: usageMeta.output_tokens || 0,\n totalTokens:\n (usageMeta.input_tokens || 0) + (usageMeta.output_tokens || 0),\n };\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n const content =\n typeof response.content === 'string'\n ? response.content\n : JSON.stringify(response.content);\n\n return { raw: content, finishReason, usage };\n });\n }\n\n /**\n * Tool-calling chat-completion call that speaks the OpenAI wire format\n * directly via `fetch`, bypassing LangChain's `ChatOpenAI` converter.\n *\n * MOTIVATION: LangChain's `convertMessagesToCompletionsMessageParams`\n * silently drops every `additional_kwargs` field except `function_call`\n * and `tool_calls`. DeepSeek V4 thinking-mode requires\n * `reasoning_content` to be echoed back on assistant turns that\n * triggered tool_calls; LangChain's converter strips it, the next\n * round-trip fails with \"400 The reasoning_content in the thinking\n * mode must be passed back to the API.\" This method preserves every\n * assistant field verbatim across round-trips.\n *\n * Supported providers: any OpenAI-compatible endpoint (openai,\n * deepseek, openrouter, kimi, orbgen). Anthropic uses a different\n * wire format and is intentionally not supported here — use\n * `callWithMessages` for Anthropic.\n *\n * Defaults `parallel_tool_calls: false` — sequential tool dispatch is\n * the protocol-safe baseline. Multi-tool-call assistant messages\n * trigger DeepSeek's \"insufficient tool messages\" 400 error.\n */\n async callWithTools(options: {\n messages: ReadonlyArray<ChatCompletionMessage>;\n tools: ReadonlyArray<ChatCompletionToolDef>;\n maxTokens?: number;\n parallelToolCalls?: boolean;\n signal?: AbortSignal;\n }): Promise<{\n message: ChatCompletionMessage;\n finishReason: string;\n usage: LLMUsage | null;\n }> {\n if (this.provider === 'anthropic') {\n throw new Error(\n 'LLMClient.callWithTools: anthropic provider is not supported; use callWithMessages instead',\n );\n }\n return this.rateLimiter.execute(async () => {\n const baseUrl = (this.providerConfig.baseUrl ?? 'https://api.openai.com/v1').replace(/\\/$/, '');\n const url = `${baseUrl}/chat/completions`;\n\n const body: { [key: string]: unknown } = {\n model: this.modelName,\n messages: options.messages,\n parallel_tool_calls: options.parallelToolCalls ?? false,\n temperature: this.temperature,\n };\n if (options.tools.length > 0) body['tools'] = options.tools;\n if (options.maxTokens !== undefined) body['max_tokens'] = options.maxTokens;\n // OpenRouter requires explicit opt-in for `reasoning` (chain-of-thought)\n // on models that support it (Gemma 4, etc.). DeepSeek's native API\n // returns `reasoning_content` without an opt-in; OpenRouter returns\n // the field as `reasoning`. We send the flag always for openrouter —\n // models that don't support it ignore it. The response-side normaliser\n // below maps `reasoning` → `reasoning_content` so downstream\n // consumers see one canonical field name.\n if (this.provider === 'openrouter') {\n body['reasoning'] = { enabled: true };\n }\n\n const startedAt = Date.now();\n console.log(\n `[LLMClient:callWithTools] Invoking ${this.provider}/${this.modelName} (tools=${options.tools.length}, messages=${options.messages.length})`,\n );\n\n const fetchInit: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.providerConfig.apiKey}`,\n },\n body: JSON.stringify(body),\n };\n if (options.signal) fetchInit.signal = options.signal;\n const response = await fetch(url, fetchInit);\n\n if (!response.ok) {\n const errText = await response.text().catch(() => '<no body>');\n const elapsed = Date.now() - startedAt;\n console.error(\n `[LLMClient:callWithTools] FAILED after ${elapsed}ms (${response.status} ${response.statusText}): ${errText}`,\n );\n throw new Error(\n `LLMClient.callWithTools: ${this.provider} returned ${response.status} ${response.statusText}: ${errText}`,\n );\n }\n\n const raw = await response.text();\n const parsed = parseChatCompletionResponse(raw);\n const choice = parsed.choices[0];\n if (!choice) {\n throw new Error('LLMClient.callWithTools: no choices in response');\n }\n // Normalise OpenRouter's `reasoning` field to DeepSeek's\n // `reasoning_content` so downstream code (rabit's `fromWireMessage`\n // at runtime/services/tool-loop.ts:103) reads one canonical field\n // for chain-of-thought regardless of provider. Cast through\n // `as { reasoning?: string }` because `ChatCompletionMessage`'s\n // type doesn't declare the off-spec `reasoning` field.\n const msgWithReasoning = choice.message as ChatCompletionMessage & { reasoning?: string };\n if (\n msgWithReasoning.reasoning !== undefined &&\n choice.message.reasoning_content === undefined\n ) {\n choice.message.reasoning_content = msgWithReasoning.reasoning;\n }\n\n let usage: LLMUsage | null = null;\n if (parsed.usage) {\n usage = {\n promptTokens: parsed.usage.prompt_tokens,\n completionTokens: parsed.usage.completion_tokens,\n totalTokens: parsed.usage.total_tokens,\n };\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(usage.promptTokens, usage.completionTokens);\n }\n }\n\n console.log(\n `[LLMClient:callWithTools] Responded in ${Date.now() - startedAt}ms (prompt=${usage?.promptTokens ?? 0}, completion=${usage?.completionTokens ?? 0}, tool_calls=${choice.message.tool_calls?.length ?? 0})`,\n );\n\n return {\n message: choice.message,\n finishReason: choice.finish_reason,\n usage,\n };\n });\n }\n\n /**\n * Stream a raw text response as an async iterator of content chunks.\n * Uses the underlying LangChain model's .stream() method.\n *\n * @param options - System prompt plus full message history\n * @yields LLMStreamChunk with content deltas and a done flag\n */\n async *streamRaw(options: LLMStreamOptions): AsyncGenerator<LLMStreamChunk> {\n const { messages, maxTokens, temperature } = options;\n\n const modelToUse = (maxTokens || temperature !== undefined)\n ? this.getModelWithOptions({ maxTokens, temperature })\n : this.model;\n\n const langchainMessages = this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : messages;\n\n const stream = await modelToUse.stream(langchainMessages);\n\n for await (const chunk of stream) {\n const content = typeof chunk.content === 'string'\n ? chunk.content\n : Array.isArray(chunk.content)\n ? chunk.content\n .filter((c): c is { type: 'text'; text: string } => typeof c === 'object' && c !== null && 'text' in c)\n .map((c) => c.text)\n .join('')\n : '';\n\n if (content) {\n yield { content, done: false };\n }\n }\n\n yield { content: '', done: true };\n }\n\n private isRateLimitError(error: Error): boolean {\n const message = error.message.toLowerCase();\n return (\n message.includes('rate limit') ||\n message.includes('429') ||\n message.includes('quota exceeded')\n );\n }\n\n // ==========================================================================\n // Anthropic Cache Control Support\n // ==========================================================================\n\n async callWithCache<T>(\n options: CacheAwareLLMCallOptions<T>,\n ): Promise<LLMResponse<T>> {\n const {\n systemPrompt,\n userPrompt,\n systemBlocks,\n userBlocks,\n schema,\n maxRetries = 2,\n maxTokens,\n skipSchemaValidation = false,\n temperature,\n rawText = false,\n } = options;\n\n if (this.provider !== 'anthropic') {\n console.log(\n `[LLMClient:callWithCache] Provider ${this.provider} doesn't support caching, using regular call`,\n );\n return this.callWithMetadata(options);\n }\n\n const cacheableCount =\n (systemBlocks || []).filter((b) => b.cache_control).length +\n (userBlocks || []).filter((b) => b.cache_control).length;\n console.log(\n `[LLMClient:callWithCache] ${cacheableCount} cacheable block(s)`,\n );\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n console.log(\n `[LLMClient:callWithCache] Attempt ${attempt + 1}/${maxRetries + 1}...`,\n );\n\n const result = await this.rateLimiter.execute(async () => {\n const anthropic = new Anthropic();\n\n const systemContent =\n systemBlocks && systemBlocks.length > 0\n ? systemBlocks.map((b) => ({\n type: 'text' as const,\n text: b.text,\n ...(b.cache_control\n ? { cache_control: b.cache_control }\n : {}),\n }))\n : systemPrompt\n ? [{ type: 'text' as const, text: systemPrompt }]\n : [];\n\n const userContent =\n userBlocks && userBlocks.length > 0\n ? userBlocks.map((b) => ({\n type: 'text' as const,\n text: b.text,\n ...(b.cache_control\n ? { cache_control: b.cache_control }\n : {}),\n }))\n : userPrompt\n ? [{ type: 'text' as const, text: userPrompt }]\n : [];\n\n const response = await anthropic.messages.create({\n model: this.modelName,\n max_tokens: maxTokens || 8192,\n temperature: temperature ?? 0,\n system: systemContent,\n messages: [{ role: 'user', content: userContent }],\n });\n\n const textContent = response.content.find((c) => c.type === 'text');\n const content =\n textContent && 'text' in textContent ? textContent.text : '';\n\n const apiUsage = response.usage as {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n\n const cacheRead = apiUsage.cache_read_input_tokens || 0;\n const cacheCreation = apiUsage.cache_creation_input_tokens || 0;\n\n if (cacheCreation > 0) {\n console.log(\n `[LLMClient:callWithCache] Cache WRITE: ${cacheCreation} tokens`,\n );\n }\n if (cacheRead > 0) {\n const savingsPercent = Math.round(\n (cacheRead / (cacheRead + apiUsage.input_tokens)) * 100,\n );\n console.log(\n `[LLMClient:callWithCache] Cache HIT: ${cacheRead} tokens (~${savingsPercent}% of prompt)`,\n );\n }\n if (cacheCreation === 0 && cacheRead === 0) {\n console.log(\n `[LLMClient:callWithCache] No caching: ${apiUsage.input_tokens} input tokens`,\n );\n }\n\n const usage: LLMUsage = {\n promptTokens: apiUsage.input_tokens,\n completionTokens: apiUsage.output_tokens,\n totalTokens: apiUsage.input_tokens + apiUsage.output_tokens,\n };\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n );\n }\n\n const finishReason =\n response.stop_reason === 'end_turn'\n ? 'stop'\n : response.stop_reason;\n\n return {\n content,\n finishReason: finishReason as LLMFinishReason,\n usage,\n };\n });\n\n let parsed: T;\n if (rawText) {\n // rawText mode: caller expects T = string; content is already a string\n parsed = asGeneric<T>(result.content);\n } else if (skipSchemaValidation) {\n parsed = parseJsonResponse(result.content, undefined) as T;\n } else {\n parsed = parseJsonResponse(result.content, schema);\n }\n\n return {\n data: parsed,\n raw: result.content,\n finishReason: result.finishReason,\n usage: result.usage,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n console.error(\n `[LLMClient:callWithCache] Attempt ${attempt + 1} failed:`,\n lastError.message,\n );\n\n if (this.isRateLimitError(lastError)) {\n throw lastError;\n }\n }\n }\n\n throw lastError;\n }\n\n static cacheableBlock(text: string, cache = true): CacheableBlock {\n return cache\n ? { type: 'text', text, cache_control: { type: 'ephemeral' } }\n : { type: 'text', text };\n }\n}\n\n// ============================================================================\n// Singleton Instances\n// ============================================================================\n\nconst sharedClients: Partial<Record<LLMProvider, LLMClient>> = {};\n\nexport function getSharedLLMClient(options?: LLMClientOptions): LLMClient {\n const provider = options?.provider || 'openai';\n if (!sharedClients[provider]) {\n sharedClients[provider] = new LLMClient(options);\n }\n return sharedClients[provider]!;\n}\n\nexport function resetSharedLLMClient(provider?: LLMProvider): void {\n if (provider) {\n delete sharedClients[provider];\n } else {\n for (const key of Object.keys(sharedClients) as LLMProvider[]) {\n delete sharedClients[key];\n }\n }\n}\n\n// ============================================================================\n// Provider Detection\n// ============================================================================\n\nexport function getAvailableProvider(): LLMProvider {\n if (process.env.ANTHROPIC_API_KEY) return 'anthropic';\n if (process.env.DEEPSEEK_API_KEY) return 'deepseek';\n if (process.env.KIMI_API_KEY) return 'kimi';\n if (process.env.OPENAI_API_KEY) return 'openai';\n throw new Error(\n 'No LLM API key found. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or KIMI_API_KEY.',\n );\n}\n\nexport function isProviderAvailable(provider: LLMProvider): boolean {\n switch (provider) {\n case 'openai':\n return !!process.env.OPENAI_API_KEY;\n case 'deepseek':\n return !!process.env.DEEPSEEK_API_KEY;\n case 'anthropic':\n return !!process.env.ANTHROPIC_API_KEY;\n case 'kimi':\n return !!process.env.KIMI_API_KEY;\n case 'openrouter':\n return !!process.env.OPEN_ROUTER_API_KEY;\n case 'orbgen':\n return !!process.env.ORBGEN_URL;\n default:\n return false;\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Create an LLM client optimized for requirements analysis.\n *\n * Uses lower temperature (0.3) for more deterministic output.\n * Defaults to GPT-5.1 for OpenAI or DeepSeek Chat.\n *\n * @param {Partial<LLMClientOptions>} [options] - Optional client configuration\n * @returns {LLMClient} Configured LLM client\n */\nexport function createRequirementsClient(\n options?: Partial<LLMClientOptions>,\n): LLMClient {\n const provider = options?.provider || getAvailableProvider();\n const defaultModel =\n provider === 'deepseek' ? DEEPSEEK_MODELS.CHAT : OPENAI_MODELS.GPT_5_1;\n return new LLMClient({\n provider,\n model: defaultModel,\n temperature: 0.3,\n ...options,\n });\n}\n\n/**\n * Create an LLM client optimized for creative tasks.\n *\n * Uses higher temperature (0.7) for more varied output.\n * Defaults to GPT-4o or DeepSeek Reasoner.\n *\n * @param {Partial<LLMClientOptions>} [options] - Optional client configuration\n * @returns {LLMClient} Configured LLM client\n */\nexport function createCreativeClient(\n options?: Partial<LLMClientOptions>,\n): LLMClient {\n const provider = options?.provider || getAvailableProvider();\n const defaultModel =\n provider === 'deepseek' ? DEEPSEEK_MODELS.REASONER : OPENAI_MODELS.GPT4O;\n return new LLMClient({\n provider,\n model: defaultModel,\n temperature: 0.7,\n ...options,\n });\n}\n\n/**\n * Create an LLM client optimized for code fixing.\n *\n * Uses low temperature (0.2) for precise, deterministic fixes.\n * Defaults to GPT-4o Mini or DeepSeek Chat for cost efficiency.\n *\n * @param {Partial<LLMClientOptions>} [options] - Optional client configuration\n * @returns {LLMClient} Configured LLM client\n */\nexport function createFixClient(\n options?: Partial<LLMClientOptions>,\n): LLMClient {\n const provider = options?.provider || getAvailableProvider();\n const defaultModel =\n provider === 'deepseek'\n ? DEEPSEEK_MODELS.CHAT\n : OPENAI_MODELS.GPT4O_MINI;\n return new LLMClient({\n provider,\n model: defaultModel,\n temperature: 0.2,\n ...options,\n });\n}\n\n/**\n * Create a DeepSeek LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured DeepSeek client\n */\nexport function createDeepSeekClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'deepseek',\n model: DEEPSEEK_MODELS.CHAT,\n ...options,\n });\n}\n\n/**\n * Create an OpenAI LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured OpenAI client\n */\nexport function createOpenAIClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'openai',\n model: OPENAI_MODELS.GPT4O,\n ...options,\n });\n}\n\n/**\n * Create an Anthropic LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured Anthropic client\n */\nexport function createAnthropicClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'anthropic',\n model: ANTHROPIC_MODELS.CLAUDE_SONNET_4_5,\n ...options,\n });\n}\n\n/**\n * Create a Kimi LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured Kimi client\n */\nexport function createKimiClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'kimi',\n model: KIMI_MODELS.K2_5,\n ...options,\n });\n}\n\n/**\n * Create an OpenRouter LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured OpenRouter client\n */\nexport function createOpenRouterClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'openrouter',\n model: OPENROUTER_MODELS.QWEN_2_5_72B,\n ...options,\n });\n}\n\n/**\n * Create a Zhipu (GLM) LLM client via OpenRouter.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured Zhipu client\n */\nexport function createZhipuClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'openrouter',\n model: OPENROUTER_MODELS.GLM_4_7,\n ...options,\n });\n}\n","/**\n * OpenAI Chat Completions wire-format types used by `LLMClient.callWithTools`.\n *\n * These mirror the public OpenAI Chat Completions API spec, which is also\n * the protocol every OpenAI-compatible provider (DeepSeek, OpenRouter,\n * Kimi, OrbGen, etc.) implements. The types are intentionally faithful\n * to the wire format — when the LLM emits a `reasoning_content` field\n * (DeepSeek V4 thinking mode), it's preserved verbatim and echoed back\n * on the next round-trip.\n */\n\nimport type { JsonSchema } from '@almadar/core';\n\n// ============================================================================\n// Tool definitions sent to the API\n// ============================================================================\n\nexport interface ChatCompletionToolDef {\n type: 'function';\n function: {\n name: string;\n description: string;\n /** JSON Schema describing the tool's parameters. Sent verbatim to the\n * provider as the tool-call `function.parameters` field. */\n parameters: JsonSchema;\n };\n}\n\n// ============================================================================\n// Message shape (used in both request and response)\n// ============================================================================\n\nexport type ChatCompletionRole = 'system' | 'user' | 'assistant' | 'tool';\n\nexport interface ChatCompletionToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n}\n\nexport interface ChatCompletionMessage {\n role: ChatCompletionRole;\n /** Null is valid (assistant-only) when the message exists purely to carry `tool_calls`. */\n content: string | null;\n /** Present on assistant turns that called one or more tools. */\n tool_calls?: ChatCompletionToolCall[];\n /** Present on tool-role messages — matches `tool_calls[*].id` of the preceding assistant turn. */\n tool_call_id?: string;\n /**\n * DeepSeek V4 thinking-mode chain-of-thought string. Must be echoed\n * back on the next round-trip when the assistant turn triggered\n * tool_calls — that's the protocol contract that LangChain's\n * ChatOpenAI converter breaks.\n */\n reasoning_content?: string;\n}\n\n// ============================================================================\n// Response shape\n// ============================================================================\n\nexport interface ChatCompletionChoice {\n index: number;\n message: ChatCompletionMessage;\n finish_reason: string;\n}\n\nexport interface ChatCompletionUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\nexport interface ChatCompletionResponse {\n choices: ChatCompletionChoice[];\n usage?: ChatCompletionUsage;\n}\n\n// ============================================================================\n// Response parsing — strict type narrowing without `as unknown as`\n// ============================================================================\n\nexport function parseChatCompletionResponse(raw: string): ChatCompletionResponse {\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(\n `parseChatCompletionResponse: not valid JSON (${reason}): ${raw.slice(0, 400)}`,\n );\n }\n if (!isChatCompletionResponse(json)) {\n throw new Error(\n `parseChatCompletionResponse: response did not match expected shape: ${raw.slice(0, 400)}`,\n );\n }\n return json;\n}\n\nfunction isChatCompletionResponse(value: unknown): value is ChatCompletionResponse {\n if (value === null || typeof value !== 'object') return false;\n const obj = value as { choices?: unknown };\n if (!Array.isArray(obj.choices)) return false;\n for (const c of obj.choices) {\n if (!isChatCompletionChoice(c)) return false;\n }\n return true;\n}\n\nfunction isChatCompletionChoice(value: unknown): value is ChatCompletionChoice {\n if (value === null || typeof value !== 'object') return false;\n const c = value as { message?: unknown; finish_reason?: unknown; index?: unknown };\n if (typeof c.finish_reason !== 'string') return false;\n if (typeof c.index !== 'number') return false;\n if (!isChatCompletionMessage(c.message)) return false;\n return true;\n}\n\nfunction isChatCompletionMessage(value: unknown): value is ChatCompletionMessage {\n if (value === null || typeof value !== 'object') return false;\n const m = value as { role?: unknown; content?: unknown };\n if (typeof m.role !== 'string') return false;\n if (m.content !== null && typeof m.content !== 'string') return false;\n return true;\n}\n"],"mappings":";;;;;;;;;;AAaA,SAAS,kBAAkB;AAC3B,SAAS,qBAAqB;AAE9B,OAAO,eAAe;;;ACkEf,SAAS,4BAA4B,KAAqC;AAC/E,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,SAAS,KAAK;AACZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI;AAAA,MACR,gDAAgD,MAAM,MAAM,IAAI,MAAM,GAAG,GAAG,CAAC;AAAA,IAC/E;AAAA,EACF;AACA,MAAI,CAAC,yBAAyB,IAAI,GAAG;AACnC,UAAM,IAAI;AAAA,MACR,uEAAuE,IAAI,MAAM,GAAG,GAAG,CAAC;AAAA,IAC1F;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,yBAAyB,OAAiD;AACjF,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,QAAM,MAAM;AACZ,MAAI,CAAC,MAAM,QAAQ,IAAI,OAAO,EAAG,QAAO;AACxC,aAAW,KAAK,IAAI,SAAS;AAC3B,QAAI,CAAC,uBAAuB,CAAC,EAAG,QAAO;AAAA,EACzC;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,OAA+C;AAC7E,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,kBAAkB,SAAU,QAAO;AAChD,MAAI,OAAO,EAAE,UAAU,SAAU,QAAO;AACxC,MAAI,CAAC,wBAAwB,EAAE,OAAO,EAAG,QAAO;AAChD,SAAO;AACT;AAEA,SAAS,wBAAwB,OAAgD;AAC/E,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,SAAS,SAAU,QAAO;AACvC,MAAI,EAAE,YAAY,QAAQ,OAAO,EAAE,YAAY,SAAU,QAAO;AAChE,SAAO;AACT;;;ADzDA,SAAS,UAAa,OAAkB;AACtC,SAAO;AACT;AAMA,SAAS,gCACP,UACmB;AACnB,SAAO,SAAS,IAAI,CAAC,QAAQ;AAC3B,QAAI,IAAI,SAAS,UAAU;AACzB,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,SAAS;AAAA,QACP;AAAA,UACE,MAAM;AAAA,UACN,MAAM,IAAI;AAAA,UACV,eAAe,EAAE,MAAM,YAAY;AAAA,QACrC;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH;AA+FA,IAAM,mBAA8D;AAAA,EAClE,QAAQ,MAAM;AACZ,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,WAAO,EAAE,QAAQ,SAAS,QAAW,cAAc,SAAS;AAAA,EAC9D;AAAA,EACA,UAAU,MAAM;AACd,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA,IAChB;AAAA,EACF;AAAA,EACA,WAAW,MAAM;AACf,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA,IAChB;AAAA,EACF;AAAA,EACA,MAAM,MAAM;AACV,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA,IAChB;AAAA,EACF;AAAA,EACA,YAAY,MAAM;AAChB,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,cAAc;AAAA;AAAA,IAChB;AAAA,EACF;AAAA,EACA,QAAQ,MAAM;AACZ,UAAM,UAAU,QAAQ,IAAI;AAC5B,QAAI,CAAC,SAAS;AACZ,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AACA,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,SAAS,GAAG,OAAO;AAAA,MACnB,cAAc;AAAA,IAChB;AAAA,EACF;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B,MAAM;AAAA,EACN,OAAO;AAAA,EACP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,UAAU;AACZ;AAEO,IAAM,gBAAgB;AAAA,EAC3B,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,SAAS;AACX;AAEO,IAAM,mBAAmB;AAAA,EAC9B,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,kBAAkB;AACpB;AAEO,IAAM,cAAc;AAAA,EACzB,MAAM;AACR;AAEO,IAAM,oBAAoB;AAAA;AAAA,EAE/B,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,aAAa;AAAA;AAAA;AAAA,EAIb,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,aAAa;AAAA;AAAA,EAGb,cAAc;AAAA;AAAA,EAEd,mBAAmB;AAAA;AAAA,EAEnB,oBAAoB;AAAA;AAAA,EAGpB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,eAAe;AAAA;AAAA,EAGf,SAAS;AAAA;AAAA,EAGT,SAAS;AACX;AAEA,IAAM,sBAAsB;AAMrB,IAAM,aAAN,MAAM,WAAU;AAAA,EAWrB,YAAY,UAA4B,CAAC,GAAG;AAC1C,SAAK,WAAW,QAAQ,YAAY;AAEpC,SAAK,cAAc,QAAQ,gBACxB,KAAK,aAAa,SAAS,MAAM;AACpC,SAAK,YAAY,QAAQ,aAAa;AAEtC,SAAK,iBAAiB,iBAAiB,KAAK,QAAQ,EAAE;AACtD,SAAK,YAAY,QAAQ,SAAS,KAAK,eAAe;AACtD,SAAK,eAAe,QAAQ,gBAAgB,WAAU;AAEtD,UAAM,aAAa,KAAK,eAAe,OAAO,MAAM,EAAE;AACtD,YAAQ;AAAA,MACN,yBAAyB,KAAK,QAAQ,YAAY,KAAK,SAAS,cAAc,UAAU;AAAA,IAC1F;AACA,QAAI,KAAK,eAAe,SAAS;AAC/B,cAAQ;AAAA,QACN,sCAAsC,KAAK,eAAe,OAAO;AAAA,MACnE;AAAA,IACF;AAEA,SAAK,QAAQ,KAAK,YAAY;AAE9B,SAAK,cACH,QAAQ,yBAAyB,QAC7B,qBAAqB,QAAQ,WAAW,IACxC,IAAI,YAAY,QAAQ,WAAW;AAEzC,SAAK,eACH,QAAQ,gBAAgB,QACpB,sBAAsB,KAAK,SAAS,IACpC;AAAA,EACR;AAAA,EAEQ,0BAAmC;AACzC,UAAM,QAAQ,KAAK,UAAU,YAAY;AACzC,WACE,MAAM,WAAW,IAAI,KACrB,MAAM,WAAW,OAAO,KACxB,MAAM,SAAS,KAAK,KACpB,MAAM,SAAS,IAAI;AAAA,EAEvB;AAAA,EAEQ,YAAY,SAGN;AACZ,UAAM,YAAY,SAAS;AAC3B,UAAM,cAAc,SAAS,eAAe,KAAK;AAEjD,QAAI,KAAK,aAAa,aAAa;AACjC,aAAO,IAAI,cAAc;AAAA,QACvB,QAAQ,KAAK,eAAe;AAAA,QAC5B,OAAO,KAAK;AAAA,QACZ;AAAA,QACA,WAAW,KAAK;AAAA,QAChB,WAAW,aAAa;AAAA,QACxB,WAAW;AAAA,UACT;AAAA,YACE,cAAc,CAAC,WAAW;AACxB,oBAAM,aAAa,OAAO,cAAc,CAAC,IAAI,CAAC;AAC9C,oBAAM,sBAAsB;AAG5B,oBAAM,QAAQ,qBAAqB,SAAS;AAE5C,kBAAI,OAAO;AACT,sBAAM,eAAe,MAAM,+BAA+B;AAC1D,sBAAM,YAAY,MAAM,2BAA2B;AACnD,sBAAM,cAAc,MAAM,gBAAgB;AAC1C,sBAAM,eAAe,MAAM,iBAAiB;AAE5C,oBAAI,eAAe,GAAG;AACpB,0BAAQ;AAAA,oBACN,sCAAsC,YAAY;AAAA,kBACpD;AAAA,gBACF;AACA,oBAAI,YAAY,GAAG;AACjB,wBAAM,iBAAiB,KAAK;AAAA,oBACzB,aAAa,YAAY,eAAgB;AAAA,kBAC5C;AACA,0BAAQ;AAAA,oBACN,oCAAoC,SAAS,aAAa,cAAc;AAAA,kBAC1E;AAAA,gBACF;AACA,oBAAI,iBAAiB,KAAK,cAAc,KAAK,cAAc,GAAG;AAC5D,sBAAI,cAAc,KAAK;AACrB,4BAAQ;AAAA,sBACN,yBAAyB,WAAW,WAAW,YAAY;AAAA,oBAC7D;AAAA,kBACF,OAAO;AACL,4BAAQ;AAAA,sBACN,yBAAyB,WAAW,WAAW,YAAY;AAAA,oBAC7D;AAAA,kBACF;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAEA,UAAM,sBAAsB,KAAK,wBAAwB;AAEzD,UAAM,cAAc,YAChB,sBACE,EAAE,aAAa,EAAE,uBAAuB,UAAU,EAAE,IACpD,EAAE,UAAU,IACd,CAAC;AAEL,UAAM,UAAU,KAAK,aAAa,aAAa,MAAS;AAIxD,UAAM,SAAS,KAAK,aAAa;AACjC,UAAM,gBAAgB,SAAS,MAAM;AAGrC,UAAM,cAA2B,CAAC;AAClC,QAAI,uBAAuB,WAAW;AACpC,kBAAY,wBAAwB;AAAA,IACtC;AACA,QAAI,QAAQ;AACV,kBAAY,WAAW,EAAE,MAAM,WAAW;AAAA,IAC5C;AAEA,QAAI,KAAK,aAAa,cAAc;AAClC,kBAAY,cAAc;AAAA,IAC5B;AAEA,WAAO,IAAI,WAAW;AAAA,MACpB,QAAQ,KAAK,eAAe;AAAA,MAC5B,OAAO,KAAK;AAAA,MACZ,aAAa,sBAAsB,SAAY;AAAA,MAC/C,WAAW,KAAK;AAAA,MAChB;AAAA,MACA,GAAI,OAAO,KAAK,WAAW,EAAE,SAAS,IAAI,EAAE,YAAY,IAAI,CAAC;AAAA,MAC7D,GAAI,sBAAsB,CAAC,IAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,MAC5D,eAAe;AAAA,QACb,QAAQ,KAAK,eAAe;AAAA,QAC5B,GAAI,KAAK,eAAe,UACpB,EAAE,SAAS,KAAK,eAAe,QAAQ,IACvC,CAAC;AAAA,MACP;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,oBAAoB,SAGd;AACZ,WAAO,KAAK,YAAY,OAAO;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,sBAA+B;AACrC,WAAO,KAAK,UAAU,SAAS,SAAS;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,kBAAkB,QAAwB;AAChD,QAAI,KAAK,oBAAoB,GAAG;AAC9B,aAAO;AAAA,EAAc,MAAM;AAAA,IAC7B;AACA,WAAO;AAAA,EACT;AAAA,EAEA,cAA2B;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,WAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,uBAAuB;AACrB,WAAO,KAAK,YAAY,UAAU;AAAA,EACpC;AAAA,EAEA,gBAAgB;AACd,WAAO,KAAK,cAAc,WAAW,KAAK;AAAA,EAC5C;AAAA,EAEA,MAAM,KAAQ,SAAwC;AACpD,UAAM,WAAW,MAAM,KAAK,iBAAiB,OAAO;AACpD,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,MAAM,iBAAoB,SAAqD;AAC7E,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,mBAAmB;AAAA,MACnB;AAAA,MACA,uBAAuB;AAAA,MACvB;AAAA,IACF,IAAI;AAEJ,QAAI,gBAAgB;AACpB,QAAI,YAA0B;AAE9B,YAAQ;AAAA,MACN,qCAAqC,KAAK,QAAQ,IAAI,KAAK,SAAS;AAAA,IACtE;AACA,YAAQ,IAAI,mCAAmC,WAAW,MAAM,QAAQ;AACxE,QAAI,WAAW;AACb,cAAQ,IAAI,gCAAgC,SAAS,EAAE;AAAA,IACzD;AAEA,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,UAAI;AACF,gBAAQ;AAAA,UACN,4BAA4B,UAAU,CAAC,IAAI,aAAa,CAAC;AAAA,QAC3D;AACA,cAAM,mBAAmB,KAAK,IAAI;AAElC,cAAM,SAAS,MAAM,KAAK,YAAY,QAAQ,YAAY;AACxD,kBAAQ,IAAI,oCAAoC;AAChD,gBAAM,kBAAkB,KAAK,IAAI;AAEjC,gBAAM,aACJ,aAAa,gBAAgB,SACzB,KAAK,oBAAoB,EAAE,WAAW,YAAY,CAAC,IACnD,KAAK;AAEX,gBAAM,WAAW;AAAA,YACf,EAAE,MAAM,UAAU,SAAS,aAAa;AAAA,YACxC,EAAE,MAAM,QAAQ,SAAS,KAAK,kBAAkB,aAAa,EAAE;AAAA,UACjE;AACA,gBAAM,WAAW,MAAM,WAAW;AAAA,YAChC,KAAK,aAAa,cACd,gCAAgC,QAAQ,IACxC;AAAA,UACN;AAEA,kBAAQ;AAAA,YACN,uCAAuC,KAAK,IAAI,IAAI,eAAe;AAAA,UACrE;AAEA,cAAI,QAAyB;AAC7B,cAAI,SAAS,gBAAgB;AAC3B,kBAAM,YAAY,SAAS;AAI3B,oBAAQ;AAAA,cACN,cAAc,UAAU,gBAAgB;AAAA,cACxC,kBAAkB,UAAU,iBAAiB;AAAA,cAC7C,cACG,UAAU,gBAAgB,MAC1B,UAAU,iBAAiB;AAAA,YAChC;AACA,oBAAQ;AAAA,cACN,iCAAiC,MAAM,YAAY,QAAQ,MAAM,gBAAgB;AAAA,YACnF;AAEA,gBAAI,KAAK,cAAc;AACrB,mBAAK,aAAa;AAAA,gBAChB,MAAM;AAAA,gBACN,MAAM;AAAA,gBACN,EAAE,UAAU,KAAK,SAAS;AAAA,cAC5B;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,eAAe,KAAK,oBAAoB,QAAQ;AACtD,cAAI,iBAAiB,UAAU;AAC7B,oBAAQ;AAAA,cACN;AAAA,YACF;AAAA,UACF;AAEA,gBAAM,UACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,OAAO;AAErC,kBAAQ;AAAA,YACN,qCAAqC,QAAQ,MAAM,0BAA0B,YAAY;AAAA,UAC3F;AAEA,iBAAO,EAAE,SAAS,cAAc,MAAM;AAAA,QACxC,CAAC;AAED,gBAAQ;AAAA,UACN,4BAA4B,UAAU,CAAC,iBAAiB,KAAK,IAAI,IAAI,gBAAgB;AAAA,QACvF;AAEA,cAAM,SAAS,uBACV,kBAAkB,OAAO,SAAS,MAAS,IAC5C,kBAAkB,OAAO,SAAS,MAAM;AAC5C,gBAAQ;AAAA,UACN,gDAAgD,uBAAuB,iCAAiC,EAAE;AAAA,QAC5G;AAEA,eAAO;AAAA,UACL,MAAM;AAAA,UACN,KAAK,OAAO;AAAA,UACZ,cAAc,OAAO;AAAA,UACrB,OAAO,OAAO;AAAA,QAChB;AAAA,MACF,SAAS,OAAO;AACd,oBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,gBAAQ;AAAA,UACN,4BAA4B,UAAU,CAAC;AAAA,UACvC,UAAU;AAAA,QACZ;AAEA,YAAI,KAAK,iBAAiB,SAAS,GAAG;AACpC,kBAAQ,MAAM,iDAAiD;AAC/D,gBAAM;AAAA,QACR;AAEA,YAAI,UAAU,cAAc,kBAAkB;AAC5C,kBAAQ,IAAI,gDAAgD;AAC5D,0BACE,GAAG,UAAU;AAAA;AAAA,iCACqB,UAAU,OAAO;AAAA;AAAA,QAEvD;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,MAAM,yDAAyD;AACvE,UAAM;AAAA,EACR;AAAA,EAEQ,oBACN,UACiB;AACjB,UAAM,WAAW,SAAS;AAG1B,QAAI,UAAU,eAAe;AAC3B,YAAM,SAAS,SAAS;AACxB,UACE,WAAW,UACX,WAAW,YACX,WAAW,oBACX,WAAW,cACX;AACA,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,MAAc,wBACZ,OACA,YACA,mBACA,cACA,cACmD;AACnD,UAAM,YAAY,gBAAgB,KAAK;AACvC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,gBAAgB;AAAA,MACpB,MAAM,WAAW,MAAM,IAAI,MAAM,4BAA4B,SAAS,IAAI,CAAC;AAAA,MAC3E;AAAA,IACF;AACA,QAAI,cAAc;AAChB,UAAI,aAAa,SAAS;AACxB,mBAAW,MAAM,aAAa,MAAM;AAAA,MACtC,OAAO;AACL,qBAAa;AAAA,UACX;AAAA,UACA,MAAM,WAAW,MAAM,aAAa,MAAM;AAAA,UAC1C,EAAE,MAAM,KAAK;AAAA,QACf;AAAA,MACF;AAAA,IACF;AACA,YAAQ;AAAA,MACN,cAAc,KAAK,cAAc,KAAK,QAAQ,IAAI,KAAK,SAAS,aAAa,SAAS;AAAA,IACxF;AACA,QAAI;AACF,YAAM,WAAW,MAAM,WAAW,OAAO,mBAAmB;AAAA,QAC1D,QAAQ,WAAW;AAAA,MACrB,CAAC;AACD,cAAQ;AAAA,QACN,cAAc,KAAK,kBAAkB,KAAK,IAAI,IAAI,SAAS;AAAA,MAC7D;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,cAAQ;AAAA,QACN,cAAc,KAAK,kBAAkB,OAAO,OAAO,OAAO;AAAA,MAC5D;AACA,YAAM;AAAA,IACR,UAAE;AACA,mBAAa,aAAa;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,SAKM;AAClB,UAAM,WAAW,MAAM,KAAK,oBAAoB,OAAO;AACvD,WAAO,SAAS;AAAA,EAClB;AAAA,EAEA,MAAM,oBAAoB,SAKuC;AAC/D,UAAM,EAAE,cAAc,YAAY,WAAW,OAAO,IAAI;AAExD,WAAO,KAAK,YAAY,QAAQ,YAAY;AAC1C,YAAM,aAAa,YACf,KAAK,oBAAoB,EAAE,UAAU,CAAC,IACtC,KAAK;AAET,YAAM,WAAW;AAAA,QACf,EAAE,MAAM,UAAU,SAAS,aAAa;AAAA,QACxC,EAAE,MAAM,QAAQ,SAAS,KAAK,kBAAkB,UAAU,EAAE;AAAA,MAC9D;AACA,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,KAAK,aAAa,cACd,gCAAgC,QAAQ,IACvC;AAAA,QACL;AAAA,MACF;AAEA,UAAI,QAAyB;AAC7B,UAAI,SAAS,gBAAgB;AAC3B,cAAM,YAAY,SAAS;AAI3B,gBAAQ;AAAA,UACN,cAAc,UAAU,gBAAgB;AAAA,UACxC,kBAAkB,UAAU,iBAAiB;AAAA,UAC7C,cACG,UAAU,gBAAgB,MAAM,UAAU,iBAAiB;AAAA,QAChE;AAEA,YAAI,KAAK,cAAc;AACrB,eAAK,aAAa;AAAA,YAChB,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,eAAe,KAAK,oBAAoB,QAAQ;AACtD,YAAM,UACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,OAAO;AAErC,aAAO,EAAE,KAAK,SAAS,cAAc,MAAM;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAM,iBAAiB,SAI0C;AAC/D,UAAM,EAAE,UAAU,WAAW,OAAO,IAAI;AAExC,WAAO,KAAK,YAAY,QAAQ,YAAY;AAC1C,YAAM,aAAa,YACf,KAAK,oBAAoB,EAAE,UAAU,CAAC,IACtC,KAAK;AAET,YAAM,oBAAoB,KAAK,aAAa,cACxC,gCAAgC,QAAQ,IACvC;AAEL,YAAM,WAAW,MAAM,KAAK;AAAA,QAC1B;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAEA,UAAI,QAAyB;AAC7B,UAAI,SAAS,gBAAgB;AAC3B,cAAM,YAAY,SAAS;AAI3B,gBAAQ;AAAA,UACN,cAAc,UAAU,gBAAgB;AAAA,UACxC,kBAAkB,UAAU,iBAAiB;AAAA,UAC7C,cACG,UAAU,gBAAgB,MAAM,UAAU,iBAAiB;AAAA,QAChE;AAEA,YAAI,KAAK,cAAc;AACrB,eAAK,aAAa;AAAA,YAChB,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,eAAe,KAAK,oBAAoB,QAAQ;AACtD,YAAM,UACJ,OAAO,SAAS,YAAY,WACxB,SAAS,UACT,KAAK,UAAU,SAAS,OAAO;AAErC,aAAO,EAAE,KAAK,SAAS,cAAc,MAAM;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAM,cAAc,SAUjB;AACD,QAAI,KAAK,aAAa,aAAa;AACjC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,YAAY,QAAQ,YAAY;AAC1C,YAAM,WAAW,KAAK,eAAe,WAAW,6BAA6B,QAAQ,OAAO,EAAE;AAC9F,YAAM,MAAM,GAAG,OAAO;AAEtB,YAAM,OAAmC;AAAA,QACvC,OAAO,KAAK;AAAA,QACZ,UAAU,QAAQ;AAAA,QAClB,qBAAqB,QAAQ,qBAAqB;AAAA,QAClD,aAAa,KAAK;AAAA,MACpB;AACA,UAAI,QAAQ,MAAM,SAAS,EAAG,MAAK,OAAO,IAAI,QAAQ;AACtD,UAAI,QAAQ,cAAc,OAAW,MAAK,YAAY,IAAI,QAAQ;AAQlE,UAAI,KAAK,aAAa,cAAc;AAClC,aAAK,WAAW,IAAI,EAAE,SAAS,KAAK;AAAA,MACtC;AAEA,YAAM,YAAY,KAAK,IAAI;AAC3B,cAAQ;AAAA,QACN,sCAAsC,KAAK,QAAQ,IAAI,KAAK,SAAS,WAAW,QAAQ,MAAM,MAAM,cAAc,QAAQ,SAAS,MAAM;AAAA,MAC3I;AAEA,YAAM,YAAyB;AAAA,QAC7B,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,eAAe,UAAU,KAAK,eAAe,MAAM;AAAA,QACrD;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B;AACA,UAAI,QAAQ,OAAQ,WAAU,SAAS,QAAQ;AAC/C,YAAM,WAAW,MAAM,MAAM,KAAK,SAAS;AAE3C,UAAI,CAAC,SAAS,IAAI;AAChB,cAAM,UAAU,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,WAAW;AAC7D,cAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,gBAAQ;AAAA,UACN,0CAA0C,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS,UAAU,MAAM,OAAO;AAAA,QAC7G;AACA,cAAM,IAAI;AAAA,UACR,4BAA4B,KAAK,QAAQ,aAAa,SAAS,MAAM,IAAI,SAAS,UAAU,KAAK,OAAO;AAAA,QAC1G;AAAA,MACF;AAEA,YAAM,MAAM,MAAM,SAAS,KAAK;AAChC,YAAM,SAAS,4BAA4B,GAAG;AAC9C,YAAM,SAAS,OAAO,QAAQ,CAAC;AAC/B,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,iDAAiD;AAAA,MACnE;AAOA,YAAM,mBAAmB,OAAO;AAChC,UACE,iBAAiB,cAAc,UAC/B,OAAO,QAAQ,sBAAsB,QACrC;AACA,eAAO,QAAQ,oBAAoB,iBAAiB;AAAA,MACtD;AAEA,UAAI,QAAyB;AAC7B,UAAI,OAAO,OAAO;AAChB,gBAAQ;AAAA,UACN,cAAc,OAAO,MAAM;AAAA,UAC3B,kBAAkB,OAAO,MAAM;AAAA,UAC/B,aAAa,OAAO,MAAM;AAAA,QAC5B;AACA,YAAI,KAAK,cAAc;AACrB,eAAK,aAAa,SAAS,MAAM,cAAc,MAAM,gBAAgB;AAAA,QACvE;AAAA,MACF;AAEA,cAAQ;AAAA,QACN,0CAA0C,KAAK,IAAI,IAAI,SAAS,cAAc,OAAO,gBAAgB,CAAC,gBAAgB,OAAO,oBAAoB,CAAC,gBAAgB,OAAO,QAAQ,YAAY,UAAU,CAAC;AAAA,MAC1M;AAEA,aAAO;AAAA,QACL,SAAS,OAAO;AAAA,QAChB,cAAc,OAAO;AAAA,QACrB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,UAAU,SAA2D;AAC1E,UAAM,EAAE,UAAU,WAAW,YAAY,IAAI;AAE7C,UAAM,aAAc,aAAa,gBAAgB,SAC7C,KAAK,oBAAoB,EAAE,WAAW,YAAY,CAAC,IACnD,KAAK;AAET,UAAM,oBAAoB,KAAK,aAAa,cACxC,gCAAgC,QAAQ,IACxC;AAEJ,UAAM,SAAS,MAAM,WAAW,OAAO,iBAAiB;AAExD,qBAAiB,SAAS,QAAQ;AAChC,YAAM,UAAU,OAAO,MAAM,YAAY,WACrC,MAAM,UACN,MAAM,QAAQ,MAAM,OAAO,IACzB,MAAM,QACH,OAAO,CAAC,MAA2C,OAAO,MAAM,YAAY,MAAM,QAAQ,UAAU,CAAC,EACrG,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,EAAE,IACV;AAEN,UAAI,SAAS;AACX,cAAM,EAAE,SAAS,MAAM,MAAM;AAAA,MAC/B;AAAA,IACF;AAEA,UAAM,EAAE,SAAS,IAAI,MAAM,KAAK;AAAA,EAClC;AAAA,EAEQ,iBAAiB,OAAuB;AAC9C,UAAM,UAAU,MAAM,QAAQ,YAAY;AAC1C,WACE,QAAQ,SAAS,YAAY,KAC7B,QAAQ,SAAS,KAAK,KACtB,QAAQ,SAAS,gBAAgB;AAAA,EAErC;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cACJ,SACyB;AACzB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,MACA,uBAAuB;AAAA,MACvB;AAAA,MACA,UAAU;AAAA,IACZ,IAAI;AAEJ,QAAI,KAAK,aAAa,aAAa;AACjC,cAAQ;AAAA,QACN,sCAAsC,KAAK,QAAQ;AAAA,MACrD;AACA,aAAO,KAAK,iBAAiB,OAAO;AAAA,IACtC;AAEA,UAAM,kBACH,gBAAgB,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,UACnD,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE;AACpD,YAAQ;AAAA,MACN,6BAA6B,cAAc;AAAA,IAC7C;AAEA,QAAI,YAA0B;AAE9B,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,UAAI;AACF,gBAAQ;AAAA,UACN,qCAAqC,UAAU,CAAC,IAAI,aAAa,CAAC;AAAA,QACpE;AAEA,cAAM,SAAS,MAAM,KAAK,YAAY,QAAQ,YAAY;AACxD,gBAAM,YAAY,IAAI,UAAU;AAEhC,gBAAM,gBACJ,gBAAgB,aAAa,SAAS,IAClC,aAAa,IAAI,CAAC,OAAO;AAAA,YACvB,MAAM;AAAA,YACN,MAAM,EAAE;AAAA,YACR,GAAI,EAAE,gBACF,EAAE,eAAe,EAAE,cAAc,IACjC,CAAC;AAAA,UACP,EAAE,IACF,eACE,CAAC,EAAE,MAAM,QAAiB,MAAM,aAAa,CAAC,IAC9C,CAAC;AAET,gBAAM,cACJ,cAAc,WAAW,SAAS,IAC9B,WAAW,IAAI,CAAC,OAAO;AAAA,YACrB,MAAM;AAAA,YACN,MAAM,EAAE;AAAA,YACR,GAAI,EAAE,gBACF,EAAE,eAAe,EAAE,cAAc,IACjC,CAAC;AAAA,UACP,EAAE,IACF,aACE,CAAC,EAAE,MAAM,QAAiB,MAAM,WAAW,CAAC,IAC5C,CAAC;AAET,gBAAM,WAAW,MAAM,UAAU,SAAS,OAAO;AAAA,YAC/C,OAAO,KAAK;AAAA,YACZ,YAAY,aAAa;AAAA,YACzB,aAAa,eAAe;AAAA,YAC5B,QAAQ;AAAA,YACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,UACnD,CAAC;AAED,gBAAM,cAAc,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAClE,gBAAM,UACJ,eAAe,UAAU,cAAc,YAAY,OAAO;AAE5D,gBAAM,WAAW,SAAS;AAO1B,gBAAM,YAAY,SAAS,2BAA2B;AACtD,gBAAM,gBAAgB,SAAS,+BAA+B;AAE9D,cAAI,gBAAgB,GAAG;AACrB,oBAAQ;AAAA,cACN,0CAA0C,aAAa;AAAA,YACzD;AAAA,UACF;AACA,cAAI,YAAY,GAAG;AACjB,kBAAM,iBAAiB,KAAK;AAAA,cACzB,aAAa,YAAY,SAAS,gBAAiB;AAAA,YACtD;AACA,oBAAQ;AAAA,cACN,wCAAwC,SAAS,aAAa,cAAc;AAAA,YAC9E;AAAA,UACF;AACA,cAAI,kBAAkB,KAAK,cAAc,GAAG;AAC1C,oBAAQ;AAAA,cACN,yCAAyC,SAAS,YAAY;AAAA,YAChE;AAAA,UACF;AAEA,gBAAM,QAAkB;AAAA,YACtB,cAAc,SAAS;AAAA,YACvB,kBAAkB,SAAS;AAAA,YAC3B,aAAa,SAAS,eAAe,SAAS;AAAA,UAChD;AAEA,cAAI,KAAK,cAAc;AACrB,iBAAK,aAAa;AAAA,cAChB,MAAM;AAAA,cACN,MAAM;AAAA,YACR;AAAA,UACF;AAEA,gBAAM,eACJ,SAAS,gBAAgB,aACrB,SACA,SAAS;AAEf,iBAAO;AAAA,YACL;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF,CAAC;AAED,YAAI;AACJ,YAAI,SAAS;AAEX,mBAAS,UAAa,OAAO,OAAO;AAAA,QACtC,WAAW,sBAAsB;AAC/B,mBAAS,kBAAkB,OAAO,SAAS,MAAS;AAAA,QACtD,OAAO;AACL,mBAAS,kBAAkB,OAAO,SAAS,MAAM;AAAA,QACnD;AAEA,eAAO;AAAA,UACL,MAAM;AAAA,UACN,KAAK,OAAO;AAAA,UACZ,cAAc,OAAO;AAAA,UACrB,OAAO,OAAO;AAAA,QAChB;AAAA,MACF,SAAS,OAAO;AACd,oBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,gBAAQ;AAAA,UACN,qCAAqC,UAAU,CAAC;AAAA,UAChD,UAAU;AAAA,QACZ;AAEA,YAAI,KAAK,iBAAiB,SAAS,GAAG;AACpC,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AAAA,EAEA,OAAO,eAAe,MAAc,QAAQ,MAAsB;AAChE,WAAO,QACH,EAAE,MAAM,QAAQ,MAAM,eAAe,EAAE,MAAM,YAAY,EAAE,IAC3D,EAAE,MAAM,QAAQ,KAAK;AAAA,EAC3B;AACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAh6Ba,WA4Xa,yBAAyB,IAAI,KAAK;AA5XrD,IAAM,YAAN;AAs6BP,IAAM,gBAAyD,CAAC;AAEzD,SAAS,mBAAmB,SAAuC;AACxE,QAAM,WAAW,SAAS,YAAY;AACtC,MAAI,CAAC,cAAc,QAAQ,GAAG;AAC5B,kBAAc,QAAQ,IAAI,IAAI,UAAU,OAAO;AAAA,EACjD;AACA,SAAO,cAAc,QAAQ;AAC/B;AAEO,SAAS,qBAAqB,UAA8B;AACjE,MAAI,UAAU;AACZ,WAAO,cAAc,QAAQ;AAAA,EAC/B,OAAO;AACL,eAAW,OAAO,OAAO,KAAK,aAAa,GAAoB;AAC7D,aAAO,cAAc,GAAG;AAAA,IAC1B;AAAA,EACF;AACF;AAMO,SAAS,uBAAoC;AAClD,MAAI,QAAQ,IAAI,kBAAmB,QAAO;AAC1C,MAAI,QAAQ,IAAI,iBAAkB,QAAO;AACzC,MAAI,QAAQ,IAAI,aAAc,QAAO;AACrC,MAAI,QAAQ,IAAI,eAAgB,QAAO;AACvC,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,oBAAoB,UAAgC;AAClE,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,CAAC,CAAC,QAAQ,IAAI;AAAA,IACvB,KAAK;AACH,aAAO,CAAC,CAAC,QAAQ,IAAI;AAAA,IACvB,KAAK;AACH,aAAO,CAAC,CAAC,QAAQ,IAAI;AAAA,IACvB,KAAK;AACH,aAAO,CAAC,CAAC,QAAQ,IAAI;AAAA,IACvB,KAAK;AACH,aAAO,CAAC,CAAC,QAAQ,IAAI;AAAA,IACvB,KAAK;AACH,aAAO,CAAC,CAAC,QAAQ,IAAI;AAAA,IACvB;AACE,aAAO;AAAA,EACX;AACF;AAeO,SAAS,yBACd,SACW;AACX,QAAM,WAAW,SAAS,YAAY,qBAAqB;AAC3D,QAAM,eACJ,aAAa,aAAa,gBAAgB,OAAO,cAAc;AACjE,SAAO,IAAI,UAAU;AAAA,IACnB;AAAA,IACA,OAAO;AAAA,IACP,aAAa;AAAA,IACb,GAAG;AAAA,EACL,CAAC;AACH;AAWO,SAAS,qBACd,SACW;AACX,QAAM,WAAW,SAAS,YAAY,qBAAqB;AAC3D,QAAM,eACJ,aAAa,aAAa,gBAAgB,WAAW,cAAc;AACrE,SAAO,IAAI,UAAU;AAAA,IACnB;AAAA,IACA,OAAO;AAAA,IACP,aAAa;AAAA,IACb,GAAG;AAAA,EACL,CAAC;AACH;AAWO,SAAS,gBACd,SACW;AACX,QAAM,WAAW,SAAS,YAAY,qBAAqB;AAC3D,QAAM,eACJ,aAAa,aACT,gBAAgB,OAChB,cAAc;AACpB,SAAO,IAAI,UAAU;AAAA,IACnB;AAAA,IACA,OAAO;AAAA,IACP,aAAa;AAAA,IACb,GAAG;AAAA,EACL,CAAC;AACH;AAQO,SAAS,qBACd,SACW;AACX,SAAO,IAAI,UAAU;AAAA,IACnB,UAAU;AAAA,IACV,OAAO,gBAAgB;AAAA,IACvB,GAAG;AAAA,EACL,CAAC;AACH;AAQO,SAAS,mBACd,SACW;AACX,SAAO,IAAI,UAAU;AAAA,IACnB,UAAU;AAAA,IACV,OAAO,cAAc;AAAA,IACrB,GAAG;AAAA,EACL,CAAC;AACH;AAQO,SAAS,sBACd,SACW;AACX,SAAO,IAAI,UAAU;AAAA,IACnB,UAAU;AAAA,IACV,OAAO,iBAAiB;AAAA,IACxB,GAAG;AAAA,EACL,CAAC;AACH;AAQO,SAAS,iBACd,SACW;AACX,SAAO,IAAI,UAAU;AAAA,IACnB,UAAU;AAAA,IACV,OAAO,YAAY;AAAA,IACnB,GAAG;AAAA,EACL,CAAC;AACH;AAQO,SAAS,uBACd,SACW;AACX,SAAO,IAAI,UAAU;AAAA,IACnB,UAAU;AAAA,IACV,OAAO,kBAAkB;AAAA,IACzB,GAAG;AAAA,EACL,CAAC;AACH;AAQO,SAAS,kBACd,SACW;AACX,SAAO,IAAI,UAAU;AAAA,IACnB,UAAU;AAAA,IACV,OAAO,kBAAkB;AAAA,IACzB,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
|
|
@@ -81,6 +81,15 @@ interface LLMClientOptions {
|
|
|
81
81
|
rateLimiter?: RateLimiterOptions;
|
|
82
82
|
useGlobalRateLimiter?: boolean;
|
|
83
83
|
trackTokens?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Hard per-call deadline (ms) for the raw invoke paths (`callRaw`,
|
|
86
|
+
* `callRawWithMetadata`, `callWithMessages`). Defaults to 5 min. Set this
|
|
87
|
+
* BELOW the caller's own wall-clock cap so a hung connection aborts in
|
|
88
|
+
* time for the caller to recover (e.g. a battery spec capped at 300s sets
|
|
89
|
+
* 120s here so a hang fails fast and leaves budget for a retry, instead of
|
|
90
|
+
* the call consuming the entire 300s).
|
|
91
|
+
*/
|
|
92
|
+
rawTimeoutMs?: number;
|
|
84
93
|
}
|
|
85
94
|
interface LLMCallOptions<T = unknown> {
|
|
86
95
|
systemPrompt: string;
|
|
@@ -178,6 +187,7 @@ declare class LLMClient {
|
|
|
178
187
|
private providerConfig;
|
|
179
188
|
private temperature;
|
|
180
189
|
private streaming;
|
|
190
|
+
private rawTimeoutMs;
|
|
181
191
|
constructor(options?: LLMClientOptions);
|
|
182
192
|
private usesMaxCompletionTokens;
|
|
183
193
|
private createModel;
|
package/dist/client.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import './rate-limiter-BqWOhaXY.js';
|
|
|
2
2
|
import '@langchain/openai';
|
|
3
3
|
import '@langchain/anthropic';
|
|
4
4
|
import 'zod';
|
|
5
|
-
export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, a as CacheableBlock, D as DEEPSEEK_MODELS, K as KIMI_MODELS, L as LLMCallOptions, i as LLMClient, j as LLMClientOptions, k as LLMFinishReason, l as LLMProvider, m as LLMResponse, n as LLMStreamChunk, o as LLMStreamOptions, p as LLMUsage, O as OPENAI_MODELS, q as OPENROUTER_MODELS, P as ProviderConfig, r as createAnthropicClient, s as createCreativeClient, t as createDeepSeekClient, u as createFixClient, v as createKimiClient, w as createOpenAIClient, x as createOpenRouterClient, y as createRequirementsClient, z as createZhipuClient, B as getAvailableProvider, E as getSharedLLMClient, F as isProviderAvailable, H as resetSharedLLMClient } from './client-
|
|
5
|
+
export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, a as CacheableBlock, D as DEEPSEEK_MODELS, K as KIMI_MODELS, L as LLMCallOptions, i as LLMClient, j as LLMClientOptions, k as LLMFinishReason, l as LLMProvider, m as LLMResponse, n as LLMStreamChunk, o as LLMStreamOptions, p as LLMUsage, O as OPENAI_MODELS, q as OPENROUTER_MODELS, P as ProviderConfig, r as createAnthropicClient, s as createCreativeClient, t as createDeepSeekClient, u as createFixClient, v as createKimiClient, w as createOpenAIClient, x as createOpenRouterClient, y as createRequirementsClient, z as createZhipuClient, B as getAvailableProvider, E as getSharedLLMClient, F as isProviderAvailable, H as resetSharedLLMClient } from './client-DK8e0wEb.js';
|
|
6
6
|
import '@almadar/core';
|
package/dist/client.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { k as LLMFinishReason, i as LLMClient } from './client-
|
|
2
|
-
export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, a as CacheableBlock, b as ChatCompletionChoice, c as ChatCompletionMessage, d as ChatCompletionResponse, e as ChatCompletionRole, f as ChatCompletionToolCall, g as ChatCompletionToolDef, h as ChatCompletionUsage, D as DEEPSEEK_MODELS, K as KIMI_MODELS, L as LLMCallOptions, j as LLMClientOptions, l as LLMProvider, m as LLMResponse, n as LLMStreamChunk, o as LLMStreamOptions, p as LLMUsage, O as OPENAI_MODELS, q as OPENROUTER_MODELS, P as ProviderConfig, r as createAnthropicClient, s as createCreativeClient, t as createDeepSeekClient, u as createFixClient, v as createKimiClient, w as createOpenAIClient, x as createOpenRouterClient, y as createRequirementsClient, z as createZhipuClient, B as getAvailableProvider, E as getSharedLLMClient, F as isProviderAvailable, G as parseChatCompletionResponse, H as resetSharedLLMClient } from './client-
|
|
1
|
+
import { k as LLMFinishReason, i as LLMClient } from './client-DK8e0wEb.js';
|
|
2
|
+
export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, a as CacheableBlock, b as ChatCompletionChoice, c as ChatCompletionMessage, d as ChatCompletionResponse, e as ChatCompletionRole, f as ChatCompletionToolCall, g as ChatCompletionToolDef, h as ChatCompletionUsage, D as DEEPSEEK_MODELS, K as KIMI_MODELS, L as LLMCallOptions, j as LLMClientOptions, l as LLMProvider, m as LLMResponse, n as LLMStreamChunk, o as LLMStreamOptions, p as LLMUsage, O as OPENAI_MODELS, q as OPENROUTER_MODELS, P as ProviderConfig, r as createAnthropicClient, s as createCreativeClient, t as createDeepSeekClient, u as createFixClient, v as createKimiClient, w as createOpenAIClient, x as createOpenRouterClient, y as createRequirementsClient, z as createZhipuClient, B as getAvailableProvider, E as getSharedLLMClient, F as isProviderAvailable, G as parseChatCompletionResponse, H as resetSharedLLMClient } from './client-DK8e0wEb.js';
|
|
3
3
|
export { R as RateLimiter, a as RateLimiterOptions, T as TokenTracker, b as TokenUsage, g as getGlobalRateLimiter, c as getGlobalTokenTracker, r as resetGlobalRateLimiter, d as resetGlobalTokenTracker } from './rate-limiter-BqWOhaXY.js';
|
|
4
4
|
export { autoCloseJson, extractJsonFromText, isValidJson, parseJsonResponse, safeParseJson } from './json-parser.js';
|
|
5
5
|
import { z } from 'zod';
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -117,6 +117,15 @@ export interface LLMClientOptions {
|
|
|
117
117
|
rateLimiter?: RateLimiterOptions;
|
|
118
118
|
useGlobalRateLimiter?: boolean;
|
|
119
119
|
trackTokens?: boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Hard per-call deadline (ms) for the raw invoke paths (`callRaw`,
|
|
122
|
+
* `callRawWithMetadata`, `callWithMessages`). Defaults to 5 min. Set this
|
|
123
|
+
* BELOW the caller's own wall-clock cap so a hung connection aborts in
|
|
124
|
+
* time for the caller to recover (e.g. a battery spec capped at 300s sets
|
|
125
|
+
* 120s here so a hang fails fast and leaves budget for a retry, instead of
|
|
126
|
+
* the call consuming the entire 300s).
|
|
127
|
+
*/
|
|
128
|
+
rawTimeoutMs?: number;
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
export interface LLMCallOptions<T = unknown> {
|
|
@@ -336,6 +345,7 @@ export class LLMClient {
|
|
|
336
345
|
private providerConfig: ProviderConfig;
|
|
337
346
|
private temperature: number;
|
|
338
347
|
private streaming: boolean;
|
|
348
|
+
private rawTimeoutMs: number;
|
|
339
349
|
|
|
340
350
|
constructor(options: LLMClientOptions = {}) {
|
|
341
351
|
this.provider = options.provider || 'openai';
|
|
@@ -346,6 +356,7 @@ export class LLMClient {
|
|
|
346
356
|
|
|
347
357
|
this.providerConfig = PROVIDER_CONFIGS[this.provider]();
|
|
348
358
|
this.modelName = options.model || this.providerConfig.defaultModel;
|
|
359
|
+
this.rawTimeoutMs = options.rawTimeoutMs ?? LLMClient.DEFAULT_RAW_TIMEOUT_MS;
|
|
349
360
|
|
|
350
361
|
const keyPreview = this.providerConfig.apiKey.slice(-4);
|
|
351
362
|
console.log(
|
|
@@ -721,8 +732,9 @@ export class LLMClient {
|
|
|
721
732
|
modelToUse: ChatModel,
|
|
722
733
|
langchainMessages: BaseMessageLike[],
|
|
723
734
|
callerSignal?: AbortSignal,
|
|
724
|
-
|
|
735
|
+
timeoutMsArg?: number,
|
|
725
736
|
): Promise<Awaited<ReturnType<ChatModel['invoke']>>> {
|
|
737
|
+
const timeoutMs = timeoutMsArg ?? this.rawTimeoutMs;
|
|
726
738
|
const startedAt = Date.now();
|
|
727
739
|
const controller = new AbortController();
|
|
728
740
|
const timeoutHandle = setTimeout(
|
|
@@ -949,6 +961,16 @@ export class LLMClient {
|
|
|
949
961
|
};
|
|
950
962
|
if (options.tools.length > 0) body['tools'] = options.tools;
|
|
951
963
|
if (options.maxTokens !== undefined) body['max_tokens'] = options.maxTokens;
|
|
964
|
+
// OpenRouter requires explicit opt-in for `reasoning` (chain-of-thought)
|
|
965
|
+
// on models that support it (Gemma 4, etc.). DeepSeek's native API
|
|
966
|
+
// returns `reasoning_content` without an opt-in; OpenRouter returns
|
|
967
|
+
// the field as `reasoning`. We send the flag always for openrouter —
|
|
968
|
+
// models that don't support it ignore it. The response-side normaliser
|
|
969
|
+
// below maps `reasoning` → `reasoning_content` so downstream
|
|
970
|
+
// consumers see one canonical field name.
|
|
971
|
+
if (this.provider === 'openrouter') {
|
|
972
|
+
body['reasoning'] = { enabled: true };
|
|
973
|
+
}
|
|
952
974
|
|
|
953
975
|
const startedAt = Date.now();
|
|
954
976
|
console.log(
|
|
@@ -983,6 +1005,19 @@ export class LLMClient {
|
|
|
983
1005
|
if (!choice) {
|
|
984
1006
|
throw new Error('LLMClient.callWithTools: no choices in response');
|
|
985
1007
|
}
|
|
1008
|
+
// Normalise OpenRouter's `reasoning` field to DeepSeek's
|
|
1009
|
+
// `reasoning_content` so downstream code (rabit's `fromWireMessage`
|
|
1010
|
+
// at runtime/services/tool-loop.ts:103) reads one canonical field
|
|
1011
|
+
// for chain-of-thought regardless of provider. Cast through
|
|
1012
|
+
// `as { reasoning?: string }` because `ChatCompletionMessage`'s
|
|
1013
|
+
// type doesn't declare the off-spec `reasoning` field.
|
|
1014
|
+
const msgWithReasoning = choice.message as ChatCompletionMessage & { reasoning?: string };
|
|
1015
|
+
if (
|
|
1016
|
+
msgWithReasoning.reasoning !== undefined &&
|
|
1017
|
+
choice.message.reasoning_content === undefined
|
|
1018
|
+
) {
|
|
1019
|
+
choice.message.reasoning_content = msgWithReasoning.reasoning;
|
|
1020
|
+
}
|
|
986
1021
|
|
|
987
1022
|
let usage: LLMUsage | null = null;
|
|
988
1023
|
if (parsed.usage) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/tool-call-types.ts"],"sourcesContent":["/**\n * Shared LLM Client\n *\n * Multi-provider LLM client with:\n * - OpenAI, DeepSeek, Anthropic, and Kimi support\n * - Anthropic prompt caching (CachingChatAnthropic)\n * - Rate limiting and retry logic\n * - Token tracking\n * - Structured output parsing with Zod\n *\n * @packageDocumentation\n */\n\nimport { ChatOpenAI } from '@langchain/openai';\nimport { ChatAnthropic } from '@langchain/anthropic';\nimport type { BaseMessageLike } from '@langchain/core/messages';\nimport Anthropic from '@anthropic-ai/sdk';\nimport { z } from 'zod';\nimport {\n RateLimiter,\n getGlobalRateLimiter,\n type RateLimiterOptions,\n} from './rate-limiter.js';\nimport { TokenTracker, getGlobalTokenTracker } from './token-tracker.js';\nimport { parseJsonResponse } from './json-parser.js';\nimport {\n parseChatCompletionResponse,\n type ChatCompletionMessage,\n type ChatCompletionToolDef,\n} from './tool-call-types.js';\n\n// ============================================================================\n// Local type helpers (avoid Record<string, unknown> and unsafe casts)\n// ============================================================================\n\n/** Anthropic generation output with usage metadata (not in Langchain's base types). */\ninterface AnthropicGenerationWithUsage {\n message?: {\n usage_metadata?: {\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n input_tokens?: number;\n output_tokens?: number;\n };\n };\n}\n\n/** Response metadata from OpenAI-compatible providers. */\ninterface OpenAIResponseMetadata {\n finish_reason?: string;\n}\n\n/** Model-specific kwargs passed to ChatOpenAI constructor. */\ninterface ModelKwargs {\n max_completion_tokens?: number;\n thinking?: { type: string };\n tool_choice?: string;\n}\n\n/**\n * Identity cast for generic return types.\n * Used when a string value must satisfy a generic T parameter\n * (e.g., rawText mode where caller declares T = string).\n *\n * Safety: callers only reach this path when rawText=true, which\n * constrains T to string by convention. TypeScript cannot verify\n * this constraint statically because T is caller-supplied.\n */\nfunction asGeneric<T>(value: string): T {\n return value as T;\n}\n\n// ============================================================================\n// Anthropic Cache Control Helper\n// ============================================================================\n\nfunction addCacheControlToSystemMessages(\n messages: Array<{ role: string; content: string }>,\n): BaseMessageLike[] {\n return messages.map((msg) => {\n if (msg.role !== 'system') {\n return msg as BaseMessageLike;\n }\n\n return {\n role: msg.role,\n content: [\n {\n type: 'text' as const,\n text: msg.content,\n cache_control: { type: 'ephemeral' },\n },\n ],\n } as BaseMessageLike;\n });\n}\n\ntype ChatModel = ChatOpenAI | ChatAnthropic;\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type LLMProvider = 'openai' | 'deepseek' | 'anthropic' | 'kimi' | 'openrouter' | 'orbgen';\n\nexport interface ProviderConfig {\n apiKey: string;\n baseUrl?: string;\n defaultModel: string;\n}\n\nexport interface LLMClientOptions {\n provider?: LLMProvider;\n model?: string;\n temperature?: number;\n streaming?: boolean;\n rateLimiter?: RateLimiterOptions;\n useGlobalRateLimiter?: boolean;\n trackTokens?: boolean;\n}\n\nexport interface LLMCallOptions<T = unknown> {\n systemPrompt: string;\n userPrompt: string;\n schema?: z.ZodSchema<T>;\n maxRetries?: number;\n retryWithContext?: boolean;\n maxTokens?: number;\n skipSchemaValidation?: boolean;\n temperature?: number;\n}\n\nexport interface CacheableBlock {\n type: 'text';\n text: string;\n cache_control?: { type: 'ephemeral' };\n}\n\nexport interface CacheAwareLLMCallOptions<T = unknown>\n extends LLMCallOptions<T> {\n systemBlocks?: CacheableBlock[];\n userBlocks?: CacheableBlock[];\n rawText?: boolean;\n}\n\nexport interface LLMUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\nexport type LLMFinishReason =\n | 'stop'\n | 'length'\n | 'content_filter'\n | 'tool_calls'\n | null;\n\nexport interface LLMResponse<T> {\n data: T;\n raw: string;\n finishReason: LLMFinishReason;\n usage: LLMUsage | null;\n}\n\nexport interface LLMStreamOptions {\n systemPrompt: string;\n messages: Array<{ role: 'system' | 'user' | 'assistant'; content: string }>;\n maxTokens?: number;\n temperature?: number;\n}\n\nexport interface LLMStreamChunk {\n content: string;\n done: boolean;\n}\n\n// ============================================================================\n// Provider Configuration\n// ============================================================================\n\nconst PROVIDER_CONFIGS: Record<LLMProvider, () => ProviderConfig> = {\n openai: () => {\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'OPENAI_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return { apiKey, baseUrl: undefined, defaultModel: 'gpt-4o' };\n },\n deepseek: () => {\n const apiKey = process.env.DEEPSEEK_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'DEEPSEEK_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: 'https://api.deepseek.com/v1',\n defaultModel: 'deepseek-v4-flash',\n };\n },\n anthropic: () => {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'ANTHROPIC_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: undefined,\n defaultModel: 'claude-sonnet-4-5-20250929',\n };\n },\n kimi: () => {\n const apiKey = process.env.KIMI_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'KIMI_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: 'https://api.moonshot.ai/v1',\n defaultModel: 'kimi-k2.5',\n };\n },\n openrouter: () => {\n const apiKey = process.env.OPEN_ROUTER_API_KEY;\n if (!apiKey) {\n throw new Error(\n 'OPEN_ROUTER_API_KEY environment variable is not set. ' +\n 'Please set it in your .env file or environment.',\n );\n }\n return {\n apiKey,\n baseUrl: 'https://openrouter.ai/api/v1',\n defaultModel: 'qwen/qwen-2.5-72b-instruct', // Default to Qwen 2.5\n };\n },\n orbgen: () => {\n const baseUrl = process.env.ORBGEN_URL;\n if (!baseUrl) {\n throw new Error(\n 'ORBGEN_URL environment variable is not set. ' +\n 'Set it to the OrbGen Cloud Run URL (e.g., https://orbgen-v2-xxx.run.app)',\n );\n }\n return {\n apiKey: 'not-needed',\n baseUrl: `${baseUrl}/v1`,\n defaultModel: 'orbgen-v2',\n };\n },\n};\n\nexport const DEEPSEEK_MODELS = {\n CHAT: 'deepseek-chat',\n CODER: 'deepseek-coder',\n REASONER: 'deepseek-reasoner',\n V4_PRO: 'deepseek-v4-pro',\n V4_FLASH: 'deepseek-v4-flash',\n} as const;\n\nexport const OPENAI_MODELS = {\n GPT4O: 'gpt-4o',\n GPT4O_MINI: 'gpt-4o-mini',\n GPT4_TURBO: 'gpt-4-turbo',\n GPT35_TURBO: 'gpt-3.5-turbo',\n GPT_5_1: 'gpt-5.1',\n} as const;\n\nexport const ANTHROPIC_MODELS = {\n CLAUDE_SONNET_4_5: 'claude-sonnet-4-5-20250929',\n CLAUDE_SONNET_4: 'claude-sonnet-4-20250514',\n CLAUDE_OPUS_4_5: 'claude-opus-4-5-20250929',\n CLAUDE_3_5_HAIKU: 'claude-3-5-haiku-20241022',\n} as const;\n\nexport const KIMI_MODELS = {\n K2_5: 'kimi-k2.5',\n} as const;\n\nexport const OPENROUTER_MODELS = {\n // Qwen models - JSON/structured data specialists\n QWEN_2_5_72B: 'qwen/qwen-2.5-72b-instruct',\n QWEN_2_5_CODER_32B: 'qwen/qwen-2.5-coder-32b-instruct',\n QWEN_3_235B: 'qwen/qwen3-235b-a22b',\n\n // Gemma models - best small models for structured JSON output\n // Gemma 3 4B: 6/6 on complex decomposition, 100% behavior matching, free, fastest\n GEMMA_3_4B: 'google/gemma-3-4b-it',\n GEMMA_3_12B: 'google/gemma-3-12b-it',\n GEMMA_3_27B: 'google/gemma-3-27b-it',\n\n // Mistral models - strong structured output, function calling\n MINISTRAL_8B: 'mistralai/ministral-8b-2512',\n // Mistral Small 3.1: 6/6 on complex decomposition, picked std-kanban for tasks\n MISTRAL_SMALL_3_1: 'mistralai/mistral-small-3.1-24b-instruct',\n // Mistral Medium 3.1: next tier up from Small, stronger reasoning, tool calling\n MISTRAL_MEDIUM_3_1: 'mistralai/mistral-medium-3.1',\n\n // Llama models - agentic workhorses\n LLAMA_3_3_70B: 'meta-llama/llama-3.3-70b-instruct',\n LLAMA_3_1_405B: 'meta-llama/llama-3.1-405b-instruct',\n LLAMA_4_MAVERICK: 'meta-llama/llama-4-maverick',\n LLAMA_4_SCOUT: 'meta-llama/llama-4-scout',\n\n // Kimi models - strong reasoning\n KIMI_K2: 'moonshotai/kimi-k2',\n\n // Zhipu GLM models - via OpenRouter\n GLM_4_7: 'z-ai/glm-4.7',\n} as const;\n\nconst DEFAULT_TEMPERATURE = 0.3;\n\n// ============================================================================\n// LLM Client\n// ============================================================================\n\nexport class LLMClient {\n private model: ChatModel;\n private rateLimiter: RateLimiter;\n private tokenTracker: TokenTracker | null;\n private modelName: string;\n private provider: LLMProvider;\n private providerConfig: ProviderConfig;\n private temperature: number;\n private streaming: boolean;\n\n constructor(options: LLMClientOptions = {}) {\n this.provider = options.provider || 'openai';\n // Kimi: 0.6 when thinking disabled (our default), 1.0 when thinking enabled\n this.temperature = options.temperature ?? \n (this.provider === 'kimi' ? 0.6 : DEFAULT_TEMPERATURE);\n this.streaming = options.streaming ?? false;\n\n this.providerConfig = PROVIDER_CONFIGS[this.provider]();\n this.modelName = options.model || this.providerConfig.defaultModel;\n\n const keyPreview = this.providerConfig.apiKey.slice(-4);\n console.log(\n `[LLMClient] Provider: ${this.provider}, Model: ${this.modelName}, Key: ****${keyPreview}`,\n );\n if (this.providerConfig.baseUrl) {\n console.log(\n `[LLMClient] Using custom base URL: ${this.providerConfig.baseUrl}`,\n );\n }\n\n this.model = this.createModel();\n\n this.rateLimiter =\n options.useGlobalRateLimiter !== false\n ? getGlobalRateLimiter(options.rateLimiter)\n : new RateLimiter(options.rateLimiter);\n\n this.tokenTracker =\n options.trackTokens !== false\n ? getGlobalTokenTracker(this.modelName)\n : null;\n }\n\n private usesMaxCompletionTokens(): boolean {\n const model = this.modelName.toLowerCase();\n return (\n model.startsWith('o1') ||\n model.startsWith('gpt-5') ||\n model.includes('o1-') ||\n model.includes('o3')\n );\n }\n\n private createModel(options?: {\n maxTokens?: number;\n temperature?: number;\n }): ChatModel {\n const maxTokens = options?.maxTokens;\n const temperature = options?.temperature ?? this.temperature;\n\n if (this.provider === 'anthropic') {\n return new ChatAnthropic({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature,\n streaming: this.streaming,\n maxTokens: maxTokens || 8192,\n callbacks: [\n {\n handleLLMEnd: (output) => {\n const generation = output.generations?.[0]?.[0];\n const generationWithUsage = generation as\n | (typeof generation & AnthropicGenerationWithUsage)\n | undefined;\n const usage = generationWithUsage?.message?.usage_metadata;\n\n if (usage) {\n const cacheCreated = usage.cache_creation_input_tokens ?? 0;\n const cacheRead = usage.cache_read_input_tokens ?? 0;\n const inputTokens = usage.input_tokens ?? 0;\n const outputTokens = usage.output_tokens ?? 0;\n\n if (cacheCreated > 0) {\n console.log(\n `[LLMClient:Anthropic] Cache WRITE: ${cacheCreated} tokens cached`,\n );\n }\n if (cacheRead > 0) {\n const savingsPercent = Math.round(\n (cacheRead / (cacheRead + inputTokens)) * 100,\n );\n console.log(\n `[LLMClient:Anthropic] Cache HIT: ${cacheRead} tokens (~${savingsPercent}% of prompt)`,\n );\n }\n if (cacheCreated === 0 && cacheRead === 0 && inputTokens > 0) {\n if (inputTokens < 500) {\n console.log(\n `[LLMClient:Anthropic] ${inputTokens} input, ${outputTokens} output tokens (likely cached)`,\n );\n } else {\n console.log(\n `[LLMClient:Anthropic] ${inputTokens} input, ${outputTokens} output tokens`,\n );\n }\n }\n }\n },\n },\n ],\n });\n }\n\n const useCompletionTokens = this.usesMaxCompletionTokens();\n\n const tokenConfig = maxTokens\n ? useCompletionTokens\n ? { modelKwargs: { max_completion_tokens: maxTokens } }\n : { maxTokens }\n : {};\n\n const timeout = this.provider === 'deepseek' ? 600000 : undefined;\n\n // Kimi-k2.5: disable thinking to avoid reasoning_content issues with tool calls\n // When thinking is disabled, temperature must be 0.6 (not 1.0)\n const isKimi = this.provider === 'kimi';\n const effectiveTemp = isKimi ? 0.6 : temperature;\n\n // Build modelKwargs incrementally to avoid spread conflicts\n const modelKwargs: ModelKwargs = {};\n if (useCompletionTokens && maxTokens) {\n modelKwargs.max_completion_tokens = maxTokens;\n }\n if (isKimi) {\n modelKwargs.thinking = { type: 'disabled' };\n }\n // OpenRouter (Qwen): explicit tool_choice so the model doesn't ignore tool definitions\n if (this.provider === 'openrouter') {\n modelKwargs.tool_choice = 'auto';\n }\n\n return new ChatOpenAI({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature: useCompletionTokens ? undefined : effectiveTemp,\n streaming: this.streaming,\n timeout,\n ...(Object.keys(modelKwargs).length > 0 ? { modelKwargs } : {}),\n ...(useCompletionTokens ? {} : maxTokens ? { maxTokens } : {}),\n configuration: {\n apiKey: this.providerConfig.apiKey,\n ...(this.providerConfig.baseUrl\n ? { baseURL: this.providerConfig.baseUrl }\n : {}),\n },\n });\n }\n\n private getModelWithOptions(options: {\n maxTokens?: number;\n temperature?: number;\n }): ChatModel {\n return this.createModel(options);\n }\n\n /**\n * Check if this model is a Qwen3.5 thinking model.\n * These models burn all output tokens on internal reasoning\n * unless thinking is explicitly disabled via /no_think prefix.\n */\n private isQwenThinkingModel(): boolean {\n return this.modelName.includes('qwen3.5');\n }\n\n /**\n * Prepare user prompt with provider-specific adjustments.\n * Qwen3.5 models require /no_think to disable reasoning mode.\n */\n private prepareUserPrompt(prompt: string): string {\n if (this.isQwenThinkingModel()) {\n return `/no_think\\n${prompt}`;\n }\n return prompt;\n }\n\n getProvider(): LLMProvider {\n return this.provider;\n }\n\n getModelName(): string {\n return this.modelName;\n }\n\n getModel(): ChatModel {\n return this.model;\n }\n\n getRateLimiterStatus() {\n return this.rateLimiter.getStatus();\n }\n\n getTokenUsage() {\n return this.tokenTracker?.getSummary() ?? null;\n }\n\n async call<T>(options: LLMCallOptions<T>): Promise<T> {\n const response = await this.callWithMetadata(options);\n return response.data;\n }\n\n async callWithMetadata<T>(options: LLMCallOptions<T>): Promise<LLMResponse<T>> {\n const {\n systemPrompt,\n userPrompt,\n schema,\n maxRetries = 2,\n retryWithContext = true,\n maxTokens,\n skipSchemaValidation = false,\n temperature,\n } = options;\n\n let currentPrompt = userPrompt;\n let lastError: Error | null = null;\n\n console.log(\n `[LLMClient:call] Starting call to ${this.provider}/${this.modelName}`,\n );\n console.log(`[LLMClient:call] Prompt length: ${userPrompt.length} chars`);\n if (maxTokens) {\n console.log(`[LLMClient:call] Max tokens: ${maxTokens}`);\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n console.log(\n `[LLMClient:call] Attempt ${attempt + 1}/${maxRetries + 1}...`,\n );\n const attemptStartTime = Date.now();\n\n const result = await this.rateLimiter.execute(async () => {\n console.log(`[LLMClient:call] Invoking model...`);\n const invokeStartTime = Date.now();\n\n const modelToUse =\n maxTokens || temperature !== undefined\n ? this.getModelWithOptions({ maxTokens, temperature })\n : this.model;\n\n const messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: this.prepareUserPrompt(currentPrompt) },\n ];\n const response = await modelToUse.invoke(\n this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : messages,\n );\n\n console.log(\n `[LLMClient:call] Model responded in ${Date.now() - invokeStartTime}ms`,\n );\n\n let usage: LLMUsage | null = null;\n if (response.usage_metadata) {\n const usageMeta = response.usage_metadata as {\n input_tokens?: number;\n output_tokens?: number;\n };\n usage = {\n promptTokens: usageMeta.input_tokens || 0,\n completionTokens: usageMeta.output_tokens || 0,\n totalTokens:\n (usageMeta.input_tokens || 0) +\n (usageMeta.output_tokens || 0),\n };\n console.log(\n `[LLMClient:call] Tokens used: ${usage.promptTokens} in, ${usage.completionTokens} out`,\n );\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n { provider: this.provider },\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n if (finishReason === 'length') {\n console.warn(\n `[LLMClient:call] Response truncated (finish_reason=length)`,\n );\n }\n\n const content =\n typeof response.content === 'string'\n ? response.content\n : JSON.stringify(response.content);\n\n console.log(\n `[LLMClient:call] Response length: ${content.length} chars, finish_reason: ${finishReason}`,\n );\n\n return { content, finishReason, usage };\n });\n\n console.log(\n `[LLMClient:call] Attempt ${attempt + 1} completed in ${Date.now() - attemptStartTime}ms, parsing response...`,\n );\n\n const parsed = skipSchemaValidation\n ? (parseJsonResponse(result.content, undefined) as T)\n : parseJsonResponse(result.content, schema);\n console.log(\n `[LLMClient:call] Response parsed successfully${skipSchemaValidation ? ' (schema validation skipped)' : ''}`,\n );\n\n return {\n data: parsed,\n raw: result.content,\n finishReason: result.finishReason,\n usage: result.usage,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n console.error(\n `[LLMClient:call] Attempt ${attempt + 1} failed:`,\n lastError.message,\n );\n\n if (this.isRateLimitError(lastError)) {\n console.error(`[LLMClient:call] Rate limit error, not retrying`);\n throw lastError;\n }\n\n if (attempt < maxRetries && retryWithContext) {\n console.log(`[LLMClient:call] Will retry with error context`);\n currentPrompt =\n `${userPrompt}\\n\\n` +\n `[Previous attempt failed with: ${lastError.message}]\\n` +\n `Please output valid JSON that matches the expected schema.`;\n }\n }\n }\n\n console.error(`[LLMClient:call] All attempts exhausted, throwing error`);\n throw lastError;\n }\n\n private extractFinishReason(\n response: Awaited<ReturnType<ChatOpenAI['invoke']>>,\n ): LLMFinishReason {\n const metadata = response.response_metadata as\n | OpenAIResponseMetadata\n | undefined;\n if (metadata?.finish_reason) {\n const reason = metadata.finish_reason as string;\n if (\n reason === 'stop' ||\n reason === 'length' ||\n reason === 'content_filter' ||\n reason === 'tool_calls'\n ) {\n return reason;\n }\n }\n return null;\n }\n\n /**\n * Default hard timeout for raw LLM invocations. The langchain ChatOpenAI\n * `timeout` option doesn't reliably fire when an HTTP connection\n * half-opens (the response stream can sit open indefinitely), so every\n * raw call gets wrapped in an AbortController-driven deadline. Callers\n * can override by passing their own `signal`.\n */\n private static readonly DEFAULT_RAW_TIMEOUT_MS = 5 * 60 * 1000; // 5 min\n\n /**\n * Wrap a `model.invoke(...)` with start/finish/error logging and a hard\n * abort-driven timeout. Returns whatever the underlying `invoke` returns.\n *\n * The raw paths (`callRaw`, `callRawWithMetadata`, `callWithMessages`)\n * used to be silent — when a connection half-opened, callers sat\n * indefinitely with no feedback. This wrapper makes hangs visible (start\n * + duration logs) and bounded (timeout fires with a clear error).\n */\n private async invokeWithObservability(\n label: string,\n modelToUse: ChatModel,\n langchainMessages: BaseMessageLike[],\n callerSignal?: AbortSignal,\n timeoutMs: number = LLMClient.DEFAULT_RAW_TIMEOUT_MS,\n ): Promise<Awaited<ReturnType<ChatModel['invoke']>>> {\n const startedAt = Date.now();\n const controller = new AbortController();\n const timeoutHandle = setTimeout(\n () => controller.abort(new Error(`LLM call timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n if (callerSignal) {\n if (callerSignal.aborted) {\n controller.abort(callerSignal.reason);\n } else {\n callerSignal.addEventListener(\n 'abort',\n () => controller.abort(callerSignal.reason),\n { once: true },\n );\n }\n }\n console.log(\n `[LLMClient:${label}] Invoking ${this.provider}/${this.modelName} (timeout=${timeoutMs}ms)`,\n );\n try {\n const response = await modelToUse.invoke(langchainMessages, {\n signal: controller.signal,\n });\n console.log(\n `[LLMClient:${label}] Responded in ${Date.now() - startedAt}ms`,\n );\n return response;\n } catch (err) {\n const elapsed = Date.now() - startedAt;\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `[LLMClient:${label}] FAILED after ${elapsed}ms: ${message}`,\n );\n throw err;\n } finally {\n clearTimeout(timeoutHandle);\n }\n }\n\n async callRaw(options: {\n systemPrompt: string;\n userPrompt: string;\n maxTokens?: number;\n signal?: AbortSignal;\n }): Promise<string> {\n const response = await this.callRawWithMetadata(options);\n return response.raw;\n }\n\n async callRawWithMetadata(options: {\n systemPrompt: string;\n userPrompt: string;\n maxTokens?: number;\n signal?: AbortSignal;\n }): Promise<Omit<LLMResponse<string>, 'data'> & { raw: string }> {\n const { systemPrompt, userPrompt, maxTokens, signal } = options;\n\n return this.rateLimiter.execute(async () => {\n const modelToUse = maxTokens\n ? this.getModelWithOptions({ maxTokens })\n : this.model;\n\n const messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: this.prepareUserPrompt(userPrompt) },\n ];\n const response = await this.invokeWithObservability(\n 'callRawWithMetadata',\n modelToUse,\n this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : (messages as BaseMessageLike[]),\n signal,\n );\n\n let usage: LLMUsage | null = null;\n if (response.usage_metadata) {\n const usageMeta = response.usage_metadata as {\n input_tokens?: number;\n output_tokens?: number;\n };\n usage = {\n promptTokens: usageMeta.input_tokens || 0,\n completionTokens: usageMeta.output_tokens || 0,\n totalTokens:\n (usageMeta.input_tokens || 0) + (usageMeta.output_tokens || 0),\n };\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n const content =\n typeof response.content === 'string'\n ? response.content\n : JSON.stringify(response.content);\n\n return { raw: content, finishReason, usage };\n });\n }\n\n /**\n * Call the LLM with a structured messages array.\n *\n * Unlike callRawWithMetadata (which takes systemPrompt + userPrompt strings),\n * this accepts a full conversation history with proper role separation.\n * This enables:\n * - Anthropic prompt caching on message boundaries (not just system prompt)\n * - Proper tool_use/tool_result role handling across providers\n * - Reduced token waste from string concatenation\n *\n * All providers support the messages format:\n * - Anthropic: native messages API with cache_control\n * - DeepSeek: OpenAI-compatible messages via ChatOpenAI\n * - OpenRouter: OpenAI-compatible messages via ChatOpenAI\n */\n async callWithMessages(options: {\n messages: Array<{ role: string; content: string }>;\n maxTokens?: number;\n signal?: AbortSignal;\n }): Promise<Omit<LLMResponse<string>, 'data'> & { raw: string }> {\n const { messages, maxTokens, signal } = options;\n\n return this.rateLimiter.execute(async () => {\n const modelToUse = maxTokens\n ? this.getModelWithOptions({ maxTokens })\n : this.model;\n\n const langchainMessages = this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : (messages as BaseMessageLike[]);\n\n const response = await this.invokeWithObservability(\n 'callWithMessages',\n modelToUse,\n langchainMessages,\n signal,\n );\n\n let usage: LLMUsage | null = null;\n if (response.usage_metadata) {\n const usageMeta = response.usage_metadata as {\n input_tokens?: number;\n output_tokens?: number;\n };\n usage = {\n promptTokens: usageMeta.input_tokens || 0,\n completionTokens: usageMeta.output_tokens || 0,\n totalTokens:\n (usageMeta.input_tokens || 0) + (usageMeta.output_tokens || 0),\n };\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n const content =\n typeof response.content === 'string'\n ? response.content\n : JSON.stringify(response.content);\n\n return { raw: content, finishReason, usage };\n });\n }\n\n /**\n * Tool-calling chat-completion call that speaks the OpenAI wire format\n * directly via `fetch`, bypassing LangChain's `ChatOpenAI` converter.\n *\n * MOTIVATION: LangChain's `convertMessagesToCompletionsMessageParams`\n * silently drops every `additional_kwargs` field except `function_call`\n * and `tool_calls`. DeepSeek V4 thinking-mode requires\n * `reasoning_content` to be echoed back on assistant turns that\n * triggered tool_calls; LangChain's converter strips it, the next\n * round-trip fails with \"400 The reasoning_content in the thinking\n * mode must be passed back to the API.\" This method preserves every\n * assistant field verbatim across round-trips.\n *\n * Supported providers: any OpenAI-compatible endpoint (openai,\n * deepseek, openrouter, kimi, orbgen). Anthropic uses a different\n * wire format and is intentionally not supported here — use\n * `callWithMessages` for Anthropic.\n *\n * Defaults `parallel_tool_calls: false` — sequential tool dispatch is\n * the protocol-safe baseline. Multi-tool-call assistant messages\n * trigger DeepSeek's \"insufficient tool messages\" 400 error.\n */\n async callWithTools(options: {\n messages: ReadonlyArray<ChatCompletionMessage>;\n tools: ReadonlyArray<ChatCompletionToolDef>;\n maxTokens?: number;\n parallelToolCalls?: boolean;\n signal?: AbortSignal;\n }): Promise<{\n message: ChatCompletionMessage;\n finishReason: string;\n usage: LLMUsage | null;\n }> {\n if (this.provider === 'anthropic') {\n throw new Error(\n 'LLMClient.callWithTools: anthropic provider is not supported; use callWithMessages instead',\n );\n }\n return this.rateLimiter.execute(async () => {\n const baseUrl = (this.providerConfig.baseUrl ?? 'https://api.openai.com/v1').replace(/\\/$/, '');\n const url = `${baseUrl}/chat/completions`;\n\n const body: { [key: string]: unknown } = {\n model: this.modelName,\n messages: options.messages,\n parallel_tool_calls: options.parallelToolCalls ?? false,\n temperature: this.temperature,\n };\n if (options.tools.length > 0) body['tools'] = options.tools;\n if (options.maxTokens !== undefined) body['max_tokens'] = options.maxTokens;\n\n const startedAt = Date.now();\n console.log(\n `[LLMClient:callWithTools] Invoking ${this.provider}/${this.modelName} (tools=${options.tools.length}, messages=${options.messages.length})`,\n );\n\n const fetchInit: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.providerConfig.apiKey}`,\n },\n body: JSON.stringify(body),\n };\n if (options.signal) fetchInit.signal = options.signal;\n const response = await fetch(url, fetchInit);\n\n if (!response.ok) {\n const errText = await response.text().catch(() => '<no body>');\n const elapsed = Date.now() - startedAt;\n console.error(\n `[LLMClient:callWithTools] FAILED after ${elapsed}ms (${response.status} ${response.statusText}): ${errText}`,\n );\n throw new Error(\n `LLMClient.callWithTools: ${this.provider} returned ${response.status} ${response.statusText}: ${errText}`,\n );\n }\n\n const raw = await response.text();\n const parsed = parseChatCompletionResponse(raw);\n const choice = parsed.choices[0];\n if (!choice) {\n throw new Error('LLMClient.callWithTools: no choices in response');\n }\n\n let usage: LLMUsage | null = null;\n if (parsed.usage) {\n usage = {\n promptTokens: parsed.usage.prompt_tokens,\n completionTokens: parsed.usage.completion_tokens,\n totalTokens: parsed.usage.total_tokens,\n };\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(usage.promptTokens, usage.completionTokens);\n }\n }\n\n console.log(\n `[LLMClient:callWithTools] Responded in ${Date.now() - startedAt}ms (prompt=${usage?.promptTokens ?? 0}, completion=${usage?.completionTokens ?? 0}, tool_calls=${choice.message.tool_calls?.length ?? 0})`,\n );\n\n return {\n message: choice.message,\n finishReason: choice.finish_reason,\n usage,\n };\n });\n }\n\n /**\n * Stream a raw text response as an async iterator of content chunks.\n * Uses the underlying LangChain model's .stream() method.\n *\n * @param options - System prompt plus full message history\n * @yields LLMStreamChunk with content deltas and a done flag\n */\n async *streamRaw(options: LLMStreamOptions): AsyncGenerator<LLMStreamChunk> {\n const { messages, maxTokens, temperature } = options;\n\n const modelToUse = (maxTokens || temperature !== undefined)\n ? this.getModelWithOptions({ maxTokens, temperature })\n : this.model;\n\n const langchainMessages = this.provider === 'anthropic'\n ? addCacheControlToSystemMessages(messages)\n : messages;\n\n const stream = await modelToUse.stream(langchainMessages);\n\n for await (const chunk of stream) {\n const content = typeof chunk.content === 'string'\n ? chunk.content\n : Array.isArray(chunk.content)\n ? chunk.content\n .filter((c): c is { type: 'text'; text: string } => typeof c === 'object' && c !== null && 'text' in c)\n .map((c) => c.text)\n .join('')\n : '';\n\n if (content) {\n yield { content, done: false };\n }\n }\n\n yield { content: '', done: true };\n }\n\n private isRateLimitError(error: Error): boolean {\n const message = error.message.toLowerCase();\n return (\n message.includes('rate limit') ||\n message.includes('429') ||\n message.includes('quota exceeded')\n );\n }\n\n // ==========================================================================\n // Anthropic Cache Control Support\n // ==========================================================================\n\n async callWithCache<T>(\n options: CacheAwareLLMCallOptions<T>,\n ): Promise<LLMResponse<T>> {\n const {\n systemPrompt,\n userPrompt,\n systemBlocks,\n userBlocks,\n schema,\n maxRetries = 2,\n maxTokens,\n skipSchemaValidation = false,\n temperature,\n rawText = false,\n } = options;\n\n if (this.provider !== 'anthropic') {\n console.log(\n `[LLMClient:callWithCache] Provider ${this.provider} doesn't support caching, using regular call`,\n );\n return this.callWithMetadata(options);\n }\n\n const cacheableCount =\n (systemBlocks || []).filter((b) => b.cache_control).length +\n (userBlocks || []).filter((b) => b.cache_control).length;\n console.log(\n `[LLMClient:callWithCache] ${cacheableCount} cacheable block(s)`,\n );\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n console.log(\n `[LLMClient:callWithCache] Attempt ${attempt + 1}/${maxRetries + 1}...`,\n );\n\n const result = await this.rateLimiter.execute(async () => {\n const anthropic = new Anthropic();\n\n const systemContent =\n systemBlocks && systemBlocks.length > 0\n ? systemBlocks.map((b) => ({\n type: 'text' as const,\n text: b.text,\n ...(b.cache_control\n ? { cache_control: b.cache_control }\n : {}),\n }))\n : systemPrompt\n ? [{ type: 'text' as const, text: systemPrompt }]\n : [];\n\n const userContent =\n userBlocks && userBlocks.length > 0\n ? userBlocks.map((b) => ({\n type: 'text' as const,\n text: b.text,\n ...(b.cache_control\n ? { cache_control: b.cache_control }\n : {}),\n }))\n : userPrompt\n ? [{ type: 'text' as const, text: userPrompt }]\n : [];\n\n const response = await anthropic.messages.create({\n model: this.modelName,\n max_tokens: maxTokens || 8192,\n temperature: temperature ?? 0,\n system: systemContent,\n messages: [{ role: 'user', content: userContent }],\n });\n\n const textContent = response.content.find((c) => c.type === 'text');\n const content =\n textContent && 'text' in textContent ? textContent.text : '';\n\n const apiUsage = response.usage as {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens?: number;\n cache_read_input_tokens?: number;\n };\n\n const cacheRead = apiUsage.cache_read_input_tokens || 0;\n const cacheCreation = apiUsage.cache_creation_input_tokens || 0;\n\n if (cacheCreation > 0) {\n console.log(\n `[LLMClient:callWithCache] Cache WRITE: ${cacheCreation} tokens`,\n );\n }\n if (cacheRead > 0) {\n const savingsPercent = Math.round(\n (cacheRead / (cacheRead + apiUsage.input_tokens)) * 100,\n );\n console.log(\n `[LLMClient:callWithCache] Cache HIT: ${cacheRead} tokens (~${savingsPercent}% of prompt)`,\n );\n }\n if (cacheCreation === 0 && cacheRead === 0) {\n console.log(\n `[LLMClient:callWithCache] No caching: ${apiUsage.input_tokens} input tokens`,\n );\n }\n\n const usage: LLMUsage = {\n promptTokens: apiUsage.input_tokens,\n completionTokens: apiUsage.output_tokens,\n totalTokens: apiUsage.input_tokens + apiUsage.output_tokens,\n };\n\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(\n usage.promptTokens,\n usage.completionTokens,\n );\n }\n\n const finishReason =\n response.stop_reason === 'end_turn'\n ? 'stop'\n : response.stop_reason;\n\n return {\n content,\n finishReason: finishReason as LLMFinishReason,\n usage,\n };\n });\n\n let parsed: T;\n if (rawText) {\n // rawText mode: caller expects T = string; content is already a string\n parsed = asGeneric<T>(result.content);\n } else if (skipSchemaValidation) {\n parsed = parseJsonResponse(result.content, undefined) as T;\n } else {\n parsed = parseJsonResponse(result.content, schema);\n }\n\n return {\n data: parsed,\n raw: result.content,\n finishReason: result.finishReason,\n usage: result.usage,\n };\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n console.error(\n `[LLMClient:callWithCache] Attempt ${attempt + 1} failed:`,\n lastError.message,\n );\n\n if (this.isRateLimitError(lastError)) {\n throw lastError;\n }\n }\n }\n\n throw lastError;\n }\n\n static cacheableBlock(text: string, cache = true): CacheableBlock {\n return cache\n ? { type: 'text', text, cache_control: { type: 'ephemeral' } }\n : { type: 'text', text };\n }\n}\n\n// ============================================================================\n// Singleton Instances\n// ============================================================================\n\nconst sharedClients: Partial<Record<LLMProvider, LLMClient>> = {};\n\nexport function getSharedLLMClient(options?: LLMClientOptions): LLMClient {\n const provider = options?.provider || 'openai';\n if (!sharedClients[provider]) {\n sharedClients[provider] = new LLMClient(options);\n }\n return sharedClients[provider]!;\n}\n\nexport function resetSharedLLMClient(provider?: LLMProvider): void {\n if (provider) {\n delete sharedClients[provider];\n } else {\n for (const key of Object.keys(sharedClients) as LLMProvider[]) {\n delete sharedClients[key];\n }\n }\n}\n\n// ============================================================================\n// Provider Detection\n// ============================================================================\n\nexport function getAvailableProvider(): LLMProvider {\n if (process.env.ANTHROPIC_API_KEY) return 'anthropic';\n if (process.env.DEEPSEEK_API_KEY) return 'deepseek';\n if (process.env.KIMI_API_KEY) return 'kimi';\n if (process.env.OPENAI_API_KEY) return 'openai';\n throw new Error(\n 'No LLM API key found. Please set ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, or KIMI_API_KEY.',\n );\n}\n\nexport function isProviderAvailable(provider: LLMProvider): boolean {\n switch (provider) {\n case 'openai':\n return !!process.env.OPENAI_API_KEY;\n case 'deepseek':\n return !!process.env.DEEPSEEK_API_KEY;\n case 'anthropic':\n return !!process.env.ANTHROPIC_API_KEY;\n case 'kimi':\n return !!process.env.KIMI_API_KEY;\n case 'openrouter':\n return !!process.env.OPEN_ROUTER_API_KEY;\n case 'orbgen':\n return !!process.env.ORBGEN_URL;\n default:\n return false;\n }\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Create an LLM client optimized for requirements analysis.\n *\n * Uses lower temperature (0.3) for more deterministic output.\n * Defaults to GPT-5.1 for OpenAI or DeepSeek Chat.\n *\n * @param {Partial<LLMClientOptions>} [options] - Optional client configuration\n * @returns {LLMClient} Configured LLM client\n */\nexport function createRequirementsClient(\n options?: Partial<LLMClientOptions>,\n): LLMClient {\n const provider = options?.provider || getAvailableProvider();\n const defaultModel =\n provider === 'deepseek' ? DEEPSEEK_MODELS.CHAT : OPENAI_MODELS.GPT_5_1;\n return new LLMClient({\n provider,\n model: defaultModel,\n temperature: 0.3,\n ...options,\n });\n}\n\n/**\n * Create an LLM client optimized for creative tasks.\n *\n * Uses higher temperature (0.7) for more varied output.\n * Defaults to GPT-4o or DeepSeek Reasoner.\n *\n * @param {Partial<LLMClientOptions>} [options] - Optional client configuration\n * @returns {LLMClient} Configured LLM client\n */\nexport function createCreativeClient(\n options?: Partial<LLMClientOptions>,\n): LLMClient {\n const provider = options?.provider || getAvailableProvider();\n const defaultModel =\n provider === 'deepseek' ? DEEPSEEK_MODELS.REASONER : OPENAI_MODELS.GPT4O;\n return new LLMClient({\n provider,\n model: defaultModel,\n temperature: 0.7,\n ...options,\n });\n}\n\n/**\n * Create an LLM client optimized for code fixing.\n *\n * Uses low temperature (0.2) for precise, deterministic fixes.\n * Defaults to GPT-4o Mini or DeepSeek Chat for cost efficiency.\n *\n * @param {Partial<LLMClientOptions>} [options] - Optional client configuration\n * @returns {LLMClient} Configured LLM client\n */\nexport function createFixClient(\n options?: Partial<LLMClientOptions>,\n): LLMClient {\n const provider = options?.provider || getAvailableProvider();\n const defaultModel =\n provider === 'deepseek'\n ? DEEPSEEK_MODELS.CHAT\n : OPENAI_MODELS.GPT4O_MINI;\n return new LLMClient({\n provider,\n model: defaultModel,\n temperature: 0.2,\n ...options,\n });\n}\n\n/**\n * Create a DeepSeek LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured DeepSeek client\n */\nexport function createDeepSeekClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'deepseek',\n model: DEEPSEEK_MODELS.CHAT,\n ...options,\n });\n}\n\n/**\n * Create an OpenAI LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured OpenAI client\n */\nexport function createOpenAIClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'openai',\n model: OPENAI_MODELS.GPT4O,\n ...options,\n });\n}\n\n/**\n * Create an Anthropic LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured Anthropic client\n */\nexport function createAnthropicClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'anthropic',\n model: ANTHROPIC_MODELS.CLAUDE_SONNET_4_5,\n ...options,\n });\n}\n\n/**\n * Create a Kimi LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured Kimi client\n */\nexport function createKimiClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'kimi',\n model: KIMI_MODELS.K2_5,\n ...options,\n });\n}\n\n/**\n * Create an OpenRouter LLM client.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured OpenRouter client\n */\nexport function createOpenRouterClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'openrouter',\n model: OPENROUTER_MODELS.QWEN_2_5_72B,\n ...options,\n });\n}\n\n/**\n * Create a Zhipu (GLM) LLM client via OpenRouter.\n *\n * @param {Partial<Omit<LLMClientOptions, 'provider'>>} [options] - Optional client configuration\n * @returns {LLMClient} Configured Zhipu client\n */\nexport function createZhipuClient(\n options?: Partial<Omit<LLMClientOptions, 'provider'>>,\n): LLMClient {\n return new LLMClient({\n provider: 'openrouter',\n model: OPENROUTER_MODELS.GLM_4_7,\n ...options,\n });\n}\n","/**\n * OpenAI Chat Completions wire-format types used by `LLMClient.callWithTools`.\n *\n * These mirror the public OpenAI Chat Completions API spec, which is also\n * the protocol every OpenAI-compatible provider (DeepSeek, OpenRouter,\n * Kimi, OrbGen, etc.) implements. The types are intentionally faithful\n * to the wire format — when the LLM emits a `reasoning_content` field\n * (DeepSeek V4 thinking mode), it's preserved verbatim and echoed back\n * on the next round-trip.\n */\n\nimport type { JsonSchema } from '@almadar/core';\n\n// ============================================================================\n// Tool definitions sent to the API\n// ============================================================================\n\nexport interface ChatCompletionToolDef {\n type: 'function';\n function: {\n name: string;\n description: string;\n /** JSON Schema describing the tool's parameters. Sent verbatim to the\n * provider as the tool-call `function.parameters` field. */\n parameters: JsonSchema;\n };\n}\n\n// ============================================================================\n// Message shape (used in both request and response)\n// ============================================================================\n\nexport type ChatCompletionRole = 'system' | 'user' | 'assistant' | 'tool';\n\nexport interface ChatCompletionToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n}\n\nexport interface ChatCompletionMessage {\n role: ChatCompletionRole;\n /** Null is valid (assistant-only) when the message exists purely to carry `tool_calls`. */\n content: string | null;\n /** Present on assistant turns that called one or more tools. */\n tool_calls?: ChatCompletionToolCall[];\n /** Present on tool-role messages — matches `tool_calls[*].id` of the preceding assistant turn. */\n tool_call_id?: string;\n /**\n * DeepSeek V4 thinking-mode chain-of-thought string. Must be echoed\n * back on the next round-trip when the assistant turn triggered\n * tool_calls — that's the protocol contract that LangChain's\n * ChatOpenAI converter breaks.\n */\n reasoning_content?: string;\n}\n\n// ============================================================================\n// Response shape\n// ============================================================================\n\nexport interface ChatCompletionChoice {\n index: number;\n message: ChatCompletionMessage;\n finish_reason: string;\n}\n\nexport interface ChatCompletionUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\nexport interface ChatCompletionResponse {\n choices: ChatCompletionChoice[];\n usage?: ChatCompletionUsage;\n}\n\n// ============================================================================\n// Response parsing — strict type narrowing without `as unknown as`\n// ============================================================================\n\nexport function parseChatCompletionResponse(raw: string): ChatCompletionResponse {\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n throw new Error(\n `parseChatCompletionResponse: not valid JSON (${reason}): ${raw.slice(0, 400)}`,\n );\n }\n if (!isChatCompletionResponse(json)) {\n throw new Error(\n `parseChatCompletionResponse: response did not match expected shape: ${raw.slice(0, 400)}`,\n );\n }\n return json;\n}\n\nfunction isChatCompletionResponse(value: unknown): value is ChatCompletionResponse {\n if (value === null || typeof value !== 'object') return false;\n const obj = value as { choices?: unknown };\n if (!Array.isArray(obj.choices)) return false;\n for (const c of obj.choices) {\n if (!isChatCompletionChoice(c)) return false;\n }\n return true;\n}\n\nfunction isChatCompletionChoice(value: unknown): value is ChatCompletionChoice {\n if (value === null || typeof value !== 'object') return false;\n const c = value as { message?: unknown; finish_reason?: unknown; index?: unknown };\n if (typeof c.finish_reason !== 'string') return false;\n if (typeof c.index !== 'number') return false;\n if (!isChatCompletionMessage(c.message)) return false;\n return true;\n}\n\nfunction isChatCompletionMessage(value: unknown): value is ChatCompletionMessage {\n if (value === null || typeof value !== 'object') return false;\n const m = value as { role?: unknown; content?: unknown };\n if (typeof m.role !== 'string') return false;\n if (m.content !== null && typeof m.content !== 'string') return false;\n return true;\n}\n"],"mappings":"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","names":[]}
|