@almadar/llm 2.39.1 → 2.40.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.
@@ -2,7 +2,7 @@ import {
2
2
  RateLimiter,
3
3
  getGlobalRateLimiter,
4
4
  getGlobalTokenTracker
5
- } from "./chunk-CZX533SU.js";
5
+ } from "./chunk-RPG3SUIB.js";
6
6
 
7
7
  // src/structured-output.ts
8
8
  import OpenAI from "openai";
@@ -175,4 +175,4 @@ export {
175
175
  resetStructuredOutputClient,
176
176
  isStructuredOutputAvailable
177
177
  };
178
- //# sourceMappingURL=chunk-S6KQIMQQ.js.map
178
+ //# sourceMappingURL=chunk-5AM54OAM.js.map
@@ -5,7 +5,7 @@ import {
5
5
  RateLimiter,
6
6
  getGlobalRateLimiter,
7
7
  getGlobalTokenTracker
8
- } from "./chunk-CZX533SU.js";
8
+ } from "./chunk-RPG3SUIB.js";
9
9
 
10
10
  // src/client.ts
11
11
  import { ChatOpenAI } from "@langchain/openai";
@@ -222,6 +222,8 @@ var OPENROUTER_MODELS = {
222
222
  GEMMA_3_4B: "google/gemma-3-4b-it",
223
223
  GEMMA_3_12B: "google/gemma-3-12b-it",
224
224
  GEMMA_3_27B: "google/gemma-3-27b-it",
225
+ // Gemma 4 26B A4B (MoE, 4B active) — matrix candidate vs deepseek default (G-MODEL-1)
226
+ GEMMA_4_26B_A4B: "google/gemma-4-26b-a4b-it",
225
227
  // Mistral models - strong structured output, function calling
226
228
  MINISTRAL_8B: "mistralai/ministral-8b-2512",
227
229
  // Mistral Small 3.1: 6/6 on complex decomposition, picked std-kanban for tasks
@@ -689,8 +691,11 @@ Please output valid JSON that matches the expected schema.`;
689
691
  };
690
692
  if (options.tools.length > 0) body["tools"] = options.tools;
691
693
  if (options.maxTokens !== void 0) body["max_tokens"] = options.maxTokens;
694
+ if (this.reasoningEffort) {
695
+ body["reasoning_effort"] = this.reasoningEffort;
696
+ }
692
697
  if (this.provider === "openrouter") {
693
- body["reasoning"] = { enabled: true };
698
+ body["reasoning"] = this.reasoningEffort === "none" ? { enabled: false } : { enabled: true };
694
699
  body["usage"] = { include: true };
695
700
  }
696
701
  const startedAt = Date.now();
@@ -1132,4 +1137,4 @@ export {
1132
1137
  createOpenRouterClient,
1133
1138
  createZhipuClient
1134
1139
  };
1135
- //# sourceMappingURL=chunk-WXZXTLPF.js.map
1140
+ //# sourceMappingURL=chunk-EOZGJJ4J.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';\nimport { createLogger } from '@almadar/logger';\n\n// Structured logger so callers (e.g. the orb CLI's ink TUI) can silence this\n// chatter via `setRuntimeNamespaceFilter('')`; raw console.log can't be muted.\nconst log = createLogger('almadar:llm:client');\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 /** OpenRouter: ask for detailed usage accounting (returns cached-token breakdown). */\n usage?: { include: boolean };\n /** DeepSeek V4: controls reasoning effort (\"none\" disables thinking tokens). */\n reasoning_effort?: string;\n /** OpenRouter: `{ enabled: false }` stops the router burning tokens on CoT. */\n reasoning?: { enabled: boolean };\n}\n\n/**\n * Pull the cache-read / cache-write token split out of a LangChain\n * `usage_metadata` object. Every provider LangChain supports normalises\n * its cache-hit tokens into `input_token_details` (OpenAI/OpenRouter/DeepSeek\n * via `cached_tokens`→`cache_read`; Anthropic via `cache_read`/`cache_creation`),\n * so one extractor covers them all. Returns zeros when the provider reports\n * no cache detail — which prices identically to the old flat-rate path.\n */\nfunction cacheTokensFromUsageMetadata(usageMeta: {\n input_tokens?: number;\n output_tokens?: number;\n input_token_details?: { cache_read?: number; cache_creation?: number };\n}): { cachedPromptTokens: number; cacheWriteTokens: number } {\n const details = usageMeta.input_token_details ?? {};\n return {\n cachedPromptTokens: details.cache_read ?? 0,\n cacheWriteTokens: details.cache_creation ?? 0,\n };\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' | 'masar';\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 * Reasoning effort for thinking models (e.g. DeepSeek V4).\n * Set to \"none\" to disable reasoning tokens for faster content generation.\n */\n reasoningEffort?: string;\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 * Explicit provider credentials. When set, used instead of reading the\n * provider's env vars — lets callers (e.g. the CLI's account store) supply\n * `apiKey`/`baseUrl`/`defaultModel` directly. Absent ⇒ env fallback.\n */\n providerConfig?: ProviderConfig;\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 /** Prompt tokens served from the provider's prefix cache (OpenAI-style\n * `prompt_tokens_details.cached_tokens`, deepseek-native\n * `prompt_cache_hit_tokens`). Absent when the provider reports neither. */\n cachedPromptTokens?: 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// Vision (Anthropic image content-parts)\n// ============================================================================\n\n/** Media types the Anthropic base64 image source accepts. */\nexport type VisionImageMediaType = Anthropic.Base64ImageSource['media_type'];\n\n/** A single image sent as a base64 content-part (no `data:` URI prefix). */\nexport interface VisionImagePart {\n base64: string;\n mediaType: VisionImageMediaType;\n}\n\nexport interface VisionCallOptions<T = string> {\n systemPrompt?: string;\n userText: string;\n images: ReadonlyArray<VisionImagePart>;\n /** Zod schema for structured JSON output — reuses the package's `parseJsonResponse` mechanism. */\n schema?: z.ZodSchema<T>;\n maxTokens?: number;\n temperature?: number;\n skipSchemaValidation?: boolean;\n signal?: AbortSignal;\n}\n\n/**\n * Build the Anthropic user-message content array for a vision call: one text\n * block followed by one image block per screenshot, grounded entirely in the\n * SDK's own typed shapes. Pure and side-effect-free so it can be unit-tested\n * without any API call.\n */\nexport function buildVisionMessageContent(\n userText: string,\n images: ReadonlyArray<VisionImagePart>,\n): Anthropic.ContentBlockParam[] {\n const textBlock: Anthropic.TextBlockParam = { type: 'text', text: userText };\n const imageBlocks: Anthropic.ImageBlockParam[] = images.map((img) => ({\n type: 'image',\n source: { type: 'base64', media_type: img.mediaType, data: img.base64 },\n }));\n return [textBlock, ...imageBlocks];\n}\n\n// ============================================================================\n// Provider Configuration\n// ============================================================================\n\n/**\n * Canonical base URL per OpenAI-compatible provider — the single source of\n * truth. Used both by the env-mode factories below and by the constructor's\n * back-fill, so an explicit `providerConfig` that omits `baseUrl` (e.g. the\n * CLI's `~/.almadar` account, which only stores apiKey) is sent to the\n * PROVIDER's endpoint, not api.openai.com. openai/anthropic intentionally have\n * none (their SDK defaults are correct); orbgen/masar derive theirs from env.\n */\nconst PROVIDER_BASE_URLS: Partial<Record<LLMProvider, string>> = {\n deepseek: 'https://api.deepseek.com/v1',\n kimi: 'https://api.moonshot.ai/v1',\n openrouter: 'https://openrouter.ai/api/v1',\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: PROVIDER_BASE_URLS.deepseek,\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: PROVIDER_BASE_URLS.kimi,\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: PROVIDER_BASE_URLS.openrouter,\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 masar: () => {\n // Fine-tuned masar subagent served OpenAI-compatibly (vLLM on Cloud Run GPU).\n const baseUrl = process.env.MASAR_SUBAGENT_URL;\n if (!baseUrl) {\n throw new Error(\n 'MASAR_SUBAGENT_URL environment variable is not set. ' +\n 'Set it to the masar subagent endpoint (e.g., https://masar-subagent-xxx.run.app)',\n );\n }\n return {\n apiKey: process.env.MASAR_SUBAGENT_API_KEY ?? 'not-needed',\n baseUrl: `${baseUrl}/v1`,\n defaultModel: process.env.MASAR_SUBAGENT_MODEL ?? 'subagent',\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_HAIKU_4_5: 'claude-haiku-4-5',\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 // Gemma 4 26B A4B (MoE, 4B active) — matrix candidate vs deepseek default (G-MODEL-1)\n GEMMA_4_26B_A4B: 'google/gemma-4-26b-a4b-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 private reasoningEffort: string | undefined;\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 = options.providerConfig ?? PROVIDER_CONFIGS[this.provider]();\n // An explicit providerConfig (e.g. from the CLI's ~/.almadar account, which\n // stores only apiKey) may omit baseUrl. Back-fill the PROVIDER's default so\n // the request isn't sent to api.openai.com — the fallback that createModel()\n // and callWithTools() use when baseUrl is undefined. Env-mode already sets\n // the right baseUrl via PROVIDER_CONFIGS, and openai/anthropic correctly\n // stay undefined (no entry in PROVIDER_BASE_URLS).\n const defaultBaseUrl = PROVIDER_BASE_URLS[this.provider];\n if (this.providerConfig.baseUrl === undefined && defaultBaseUrl) {\n this.providerConfig = { ...this.providerConfig, baseUrl: defaultBaseUrl };\n }\n this.modelName = options.model || this.providerConfig.defaultModel;\n this.rawTimeoutMs = options.rawTimeoutMs ?? LLMClient.DEFAULT_RAW_TIMEOUT_MS;\n this.reasoningEffort = options.reasoningEffort;\n\n const keyPreview = this.providerConfig.apiKey.slice(-4);\n log.info(\n `[LLMClient] Provider: ${this.provider}, Model: ${this.modelName}, Key: ****${keyPreview}`,\n );\n if (this.providerConfig.baseUrl) {\n log.info(\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 streaming?: boolean;\n }): ChatModel {\n const maxTokens = options?.maxTokens;\n const temperature = options?.temperature ?? this.temperature;\n const streaming = options?.streaming ?? this.streaming;\n\n if (this.provider === 'anthropic') {\n return new ChatAnthropic({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature,\n 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 log.info(\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 log.info(\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 log.info(\n `[LLMClient:Anthropic] ${inputTokens} input, ${outputTokens} output tokens (likely cached)`,\n );\n } else {\n log.info(\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 // DeepSeek V4: disable reasoning when reasoningEffort is set\n if (this.reasoningEffort) {\n modelKwargs.reasoning_effort = this.reasoningEffort;\n }\n // OpenRouter: explicit tool_choice so the model doesn't ignore tool definitions.\n // When reasoning is disabled (reasoningEffort === 'none'), send\n // reasoning: { enabled: false } so OpenRouter doesn't burn tokens on CoT.\n if (this.provider === 'openrouter') {\n modelKwargs.tool_choice = 'auto';\n modelKwargs.usage = { include: true };\n if (this.reasoningEffort === 'none') {\n modelKwargs.reasoning = { enabled: false };\n }\n }\n\n return new ChatOpenAI({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature: useCompletionTokens ? undefined : effectiveTemp,\n 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 streaming?: boolean;\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 log.info(\n `[LLMClient:call] Starting call to ${this.provider}/${this.modelName}`,\n );\n log.info(`[LLMClient:call] Prompt length: ${userPrompt.length} chars`);\n if (maxTokens) {\n log.info(`[LLMClient:call] Max tokens: ${maxTokens}`);\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n log.info(\n `[LLMClient:call] Attempt ${attempt + 1}/${maxRetries + 1}...`,\n );\n const attemptStartTime = Date.now();\n\n const result = await this.rateLimiter.execute(async () => {\n log.info(`[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 log.info(\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 log.info(\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, ...cacheTokensFromUsageMetadata(usageMeta) },\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n if (finishReason === 'length') {\n log.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 log.info(\n `[LLMClient:call] Response length: ${content.length} chars, finish_reason: ${finishReason}`,\n );\n\n return { content, finishReason, usage };\n });\n\n log.info(\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 log.info(\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 log.error(\n `[LLMClient:call] Attempt ${attempt + 1} failed: ${lastError.message}`,\n );\n\n if (this.isRateLimitError(lastError)) {\n log.error(`[LLMClient:call] Rate limit error, not retrying`);\n throw lastError;\n }\n\n if (attempt < maxRetries && retryWithContext) {\n log.info(`[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 log.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 log.info(\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 log.info(\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 log.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 { provider: this.provider, ...cacheTokensFromUsageMetadata(usageMeta) },\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 { provider: this.provider, ...cacheTokensFromUsageMetadata(usageMeta) },\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 /** Per-call deadline (ms). Defaults to `rawTimeoutMs` (5 min unless the\n * client was constructed with a lower value). Bounds the fetch so a\n * hung connection can't run past the caller's own wall-clock cap. */\n timeoutMs?: number;\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 // This raw-fetch path bypasses createModel()'s modelKwargs, so the\n // reasoning kill-switch must be applied here as well — without it\n // DeepSeek V4 burns the whole completion cap on reasoning_content\n // before emitting a tool call (rabit's runaway-reasoning class).\n if (this.reasoningEffort) {\n body['reasoning_effort'] = this.reasoningEffort;\n }\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'] = this.reasoningEffort === 'none' ? { enabled: false } : { enabled: true };\n // Return authoritative usage accounting (real cost + cached-token split).\n body['usage'] = { include: true };\n }\n\n const startedAt = Date.now();\n log.info(\n `[LLMClient:callWithTools] Invoking ${this.provider}/${this.modelName} (tools=${options.tools.length}, messages=${options.messages.length})`,\n );\n\n // Per-call deadline. callWithTools previously honored only a\n // caller-supplied signal, so when runToolLoop passed none a hung\n // connection ran until the caller's external wall-clock cap (e.g. a\n // 300s battery race), with the subagent producing zero metrics. Drive\n // an AbortController off `timeoutMs ?? rawTimeoutMs` and merge the\n // caller's signal so a hang aborts in-process, fast.\n const effectiveTimeoutMs = options.timeoutMs ?? this.rawTimeoutMs;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(\n () => controller.abort(new Error(`LLMClient.callWithTools timed out after ${effectiveTimeoutMs}ms`)),\n effectiveTimeoutMs,\n );\n const callerSignal = options.signal;\n if (callerSignal) {\n if (callerSignal.aborted) controller.abort(callerSignal.reason);\n else callerSignal.addEventListener('abort', () => controller.abort(callerSignal.reason), { once: true });\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 signal: controller.signal,\n };\n try {\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 log.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 const rawUsage = parsed.usage as {\n prompt_tokens_details?: { cached_tokens?: number };\n prompt_cache_hit_tokens?: number;\n cost?: number;\n };\n const cachedTokens =\n rawUsage.prompt_tokens_details?.cached_tokens ?? rawUsage.prompt_cache_hit_tokens;\n usage = {\n promptTokens: parsed.usage.prompt_tokens,\n completionTokens: parsed.usage.completion_tokens,\n totalTokens: parsed.usage.total_tokens,\n ...(cachedTokens !== undefined ? { cachedPromptTokens: cachedTokens } : {}),\n };\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(usage.promptTokens, usage.completionTokens, {\n provider: this.provider,\n cachedPromptTokens: cachedTokens ?? 0,\n // OpenRouter returns the real, routing+cache-adjusted charge here.\n ...(typeof rawUsage.cost === 'number' ? { costUSD: rawUsage.cost } : {}),\n });\n }\n }\n\n log.info(\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 } finally {\n clearTimeout(timeoutHandle);\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 // streamRaw must always stream: force streaming on the model regardless of the client's\n // default `streaming` flag, or LangChain's .stream() degrades to a single whole-response chunk.\n const modelToUse = this.getModelWithOptions({ maxTokens, temperature, streaming: true });\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 log.info(\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 log.info(\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 log.info(\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 log.info(\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 log.info(\n `[LLMClient:callWithCache] Cache HIT: ${cacheRead} tokens (~${savingsPercent}% of prompt)`,\n );\n }\n if (cacheCreation === 0 && cacheRead === 0) {\n log.info(\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 // Anthropic reports input_tokens as the UNCACHED count; cache reads\n // and writes are separate. Pass the true total so the tracker prices\n // each bucket at its own rate.\n this.tokenTracker.addUsage(\n apiUsage.input_tokens + cacheRead + cacheCreation,\n usage.completionTokens,\n { provider: this.provider, cachedPromptTokens: cacheRead, cacheWriteTokens: cacheCreation },\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 log.error(\n `[LLMClient:callWithCache] Attempt ${attempt + 1} failed: ${lastError.message}`,\n );\n\n if (this.isRateLimitError(lastError)) {\n throw lastError;\n }\n }\n }\n\n throw lastError;\n }\n\n /**\n * Send one or more images (base64 content-parts) plus a text prompt to a\n * vision-capable Anthropic model and return the response. When a `schema`\n * is supplied the text output is parsed and validated through the package's\n * `parseJsonResponse` mechanism; otherwise the raw text is returned as `T`.\n *\n * Anthropic-only — image content-parts use the native messages API. Other\n * providers throw (mirrors `callWithTools`).\n */\n async callWithVision<T = string>(\n options: VisionCallOptions<T>,\n ): Promise<LLMResponse<T>> {\n if (this.provider !== 'anthropic') {\n throw new Error(\n `LLMClient.callWithVision: vision is only supported on the anthropic provider (got ${this.provider})`,\n );\n }\n\n const {\n systemPrompt,\n userText,\n images,\n schema,\n maxTokens,\n temperature,\n skipSchemaValidation = false,\n signal,\n } = options;\n\n if (images.length === 0) {\n throw new Error('LLMClient.callWithVision: at least one image is required');\n }\n\n return this.rateLimiter.execute(async () => {\n const anthropic = new Anthropic({ apiKey: this.providerConfig.apiKey });\n const content = buildVisionMessageContent(userText, images);\n const messages: Anthropic.MessageParam[] = [{ role: 'user', content }];\n\n log.info(\n `[LLMClient:callWithVision] Invoking ${this.provider}/${this.modelName} (images=${images.length})`,\n );\n\n const response = await anthropic.messages.create(\n {\n model: this.modelName,\n max_tokens: maxTokens ?? 4096,\n temperature: temperature ?? 0,\n ...(systemPrompt ? { system: systemPrompt } : {}),\n messages,\n },\n signal ? { signal } : {},\n );\n\n const textContent = response.content.find((c) => c.type === 'text');\n const rawText =\n textContent && 'text' in textContent ? textContent.text : '';\n\n const apiUsage = response.usage;\n const usage: LLMUsage = {\n promptTokens: apiUsage.input_tokens,\n completionTokens: apiUsage.output_tokens,\n totalTokens: apiUsage.input_tokens + apiUsage.output_tokens,\n };\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(usage.promptTokens, usage.completionTokens, {\n provider: this.provider,\n });\n }\n\n const finishReason: LLMFinishReason =\n response.stop_reason === 'end_turn'\n ? 'stop'\n : response.stop_reason === 'max_tokens'\n ? 'length'\n : response.stop_reason === 'tool_use'\n ? 'tool_calls'\n : null;\n\n const data: T = skipSchemaValidation\n ? parseJsonResponse<T>(rawText, undefined)\n : schema\n ? parseJsonResponse<T>(rawText, schema)\n : asGeneric<T>(rawText);\n\n return { data, raw: rawText, finishReason, usage };\n });\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 case 'masar':\n return !!process.env.MASAR_SUBAGENT_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":[]}
@@ -5,8 +5,9 @@ var RateLimiter = class {
5
5
  this.queue = [];
6
6
  this.lastMinuteReset = Date.now();
7
7
  this.lastSecondReset = Date.now();
8
- this.processing = false;
8
+ this.pumping = false;
9
9
  this.currentBackoffMs = 0;
10
+ this.wakeTimer = null;
10
11
  this.requestsPerMinute = options.requestsPerMinute ?? 60;
11
12
  this.requestsPerSecond = options.requestsPerSecond ?? 3;
12
13
  this.maxConcurrent = options.maxConcurrent ?? 5;
@@ -23,7 +24,7 @@ var RateLimiter = class {
23
24
  reject,
24
25
  retryCount: 0
25
26
  });
26
- this.processQueue();
27
+ this.pump();
27
28
  });
28
29
  }
29
30
  getStatus() {
@@ -43,59 +44,80 @@ var RateLimiter = class {
43
44
  this.currentBackoffMs = 0;
44
45
  this.lastMinuteReset = Date.now();
45
46
  this.lastSecondReset = Date.now();
47
+ if (this.wakeTimer !== null) {
48
+ clearTimeout(this.wakeTimer);
49
+ this.wakeTimer = null;
50
+ }
46
51
  }
47
- async processQueue() {
48
- if (this.processing) return;
49
- this.processing = true;
50
- while (this.queue.length > 0) {
52
+ /**
53
+ * Start queued requests up to the rate/concurrency budget, then return
54
+ * in-flight requests re-enter the pump as they settle, and a wake timer
55
+ * covers token-bucket refills and 429 backoff. Never awaits a request.
56
+ */
57
+ pump() {
58
+ if (this.pumping) return;
59
+ this.pumping = true;
60
+ try {
51
61
  this.refillTokens();
52
- if (!this.canMakeRequest()) {
53
- const waitTime = this.getWaitTime();
54
- await this.sleep(waitTime);
55
- continue;
62
+ while (this.queue.length > 0 && this.currentBackoffMs === 0 && this.canMakeRequest()) {
63
+ const request = this.queue.shift();
64
+ if (!request) break;
65
+ this.consumeTokens();
66
+ this.activeRequests++;
67
+ void this.settle(request);
56
68
  }
57
- if (this.currentBackoffMs > 0) {
58
- await this.sleep(this.currentBackoffMs);
59
- this.currentBackoffMs = 0;
60
- }
61
- const request = this.queue.shift();
62
- if (!request) continue;
63
- this.consumeTokens();
64
- this.activeRequests++;
65
- try {
66
- const result = await request.execute();
67
- request.resolve(result);
68
- this.currentBackoffMs = 0;
69
- } catch (error) {
70
- const err = error instanceof Error ? error : new Error(String(error));
71
- if (this.isRateLimitError(err)) {
72
- this.currentBackoffMs = Math.min(
73
- this.baseBackoffMs * Math.pow(2, request.retryCount),
74
- this.maxBackoffMs
75
- );
76
- console.warn(
77
- `[RateLimiter] Rate limited. Backing off for ${this.currentBackoffMs}ms (retry ${request.retryCount + 1})`
78
- );
79
- if (request.retryCount < 3) {
80
- this.queue.unshift({
81
- ...request,
82
- retryCount: request.retryCount + 1
83
- });
84
- } else {
85
- request.reject(
86
- new Error(
87
- `Rate limit exceeded after ${request.retryCount + 1} retries: ${err.message}`
88
- )
89
- );
90
- }
69
+ } finally {
70
+ this.pumping = false;
71
+ }
72
+ if (this.queue.length === 0) return;
73
+ if (this.currentBackoffMs > 0) {
74
+ this.scheduleWake(this.currentBackoffMs, true);
75
+ } else if (this.activeRequests < this.maxConcurrent) {
76
+ this.scheduleWake(this.getWaitTime(), false);
77
+ }
78
+ }
79
+ scheduleWake(ms, clearsBackoff) {
80
+ if (this.wakeTimer !== null) return;
81
+ this.wakeTimer = setTimeout(() => {
82
+ this.wakeTimer = null;
83
+ if (clearsBackoff) this.currentBackoffMs = 0;
84
+ this.pump();
85
+ }, ms);
86
+ }
87
+ async settle(request) {
88
+ try {
89
+ const result = await request.execute();
90
+ request.resolve(result);
91
+ this.currentBackoffMs = 0;
92
+ } catch (error) {
93
+ const err = error instanceof Error ? error : new Error(String(error));
94
+ if (this.isRateLimitError(err)) {
95
+ this.currentBackoffMs = Math.min(
96
+ this.baseBackoffMs * Math.pow(2, request.retryCount),
97
+ this.maxBackoffMs
98
+ );
99
+ console.warn(
100
+ `[RateLimiter] Rate limited. Backing off for ${this.currentBackoffMs}ms (retry ${request.retryCount + 1})`
101
+ );
102
+ if (request.retryCount < 3) {
103
+ this.queue.unshift({
104
+ ...request,
105
+ retryCount: request.retryCount + 1
106
+ });
91
107
  } else {
92
- request.reject(err);
108
+ request.reject(
109
+ new Error(
110
+ `Rate limit exceeded after ${request.retryCount + 1} retries: ${err.message}`
111
+ )
112
+ );
93
113
  }
94
- } finally {
95
- this.activeRequests--;
114
+ } else {
115
+ request.reject(err);
96
116
  }
117
+ } finally {
118
+ this.activeRequests--;
119
+ this.pump();
97
120
  }
98
- this.processing = false;
99
121
  }
100
122
  refillTokens() {
101
123
  const now = Date.now();
@@ -129,9 +151,6 @@ var RateLimiter = class {
129
151
  const message = error.message.toLowerCase();
130
152
  return message.includes("429") || message.includes("rate limit") || message.includes("too many requests") || message.includes("quota exceeded");
131
153
  }
132
- sleep(ms) {
133
- return new Promise((resolve) => setTimeout(resolve, ms));
134
- }
135
154
  };
136
155
  var globalRateLimiter = null;
137
156
  function getGlobalRateLimiter(options) {
@@ -389,4 +408,4 @@ export {
389
408
  getGlobalTokenTracker,
390
409
  resetGlobalTokenTracker
391
410
  };
392
- //# sourceMappingURL=chunk-CZX533SU.js.map
411
+ //# sourceMappingURL=chunk-RPG3SUIB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/rate-limiter.ts","../src/token-tracker.ts"],"sourcesContent":["/**\n * Rate Limiter for LLM API Calls\n *\n * Implements token bucket algorithm with:\n * - Configurable requests per minute/second\n * - Automatic backoff on 429 errors\n * - Queue for pending requests\n *\n * @packageDocumentation\n */\n\nexport interface RateLimiterOptions {\n /** Maximum requests per minute (default: 60) */\n requestsPerMinute?: number;\n /** Maximum requests per second (default: 3) */\n requestsPerSecond?: number;\n /** Maximum concurrent requests (default: 5) */\n maxConcurrent?: number;\n /** Base delay for exponential backoff in ms (default: 1000) */\n baseBackoffMs?: number;\n /** Maximum backoff delay in ms (default: 60000) */\n maxBackoffMs?: number;\n}\n\ninterface QueuedRequest<T> {\n execute: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n retryCount: number;\n}\n\n/**\n * Rate limiter for LLM API calls using token bucket algorithm.\n *\n * @example\n * ```typescript\n * const limiter = new RateLimiter({ requestsPerMinute: 30 });\n * const result = await limiter.execute(() => llm.invoke(messages));\n * ```\n */\nexport class RateLimiter {\n private requestsPerMinute: number;\n private requestsPerSecond: number;\n private maxConcurrent: number;\n private baseBackoffMs: number;\n private maxBackoffMs: number;\n\n private minuteTokens: number;\n private secondTokens: number;\n private activeRequests: number = 0;\n private queue: QueuedRequest<unknown>[] = [];\n private lastMinuteReset: number = Date.now();\n private lastSecondReset: number = Date.now();\n private pumping: boolean = false;\n private currentBackoffMs: number = 0;\n private wakeTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(options: RateLimiterOptions = {}) {\n this.requestsPerMinute = options.requestsPerMinute ?? 60;\n this.requestsPerSecond = options.requestsPerSecond ?? 3;\n this.maxConcurrent = options.maxConcurrent ?? 5;\n this.baseBackoffMs = options.baseBackoffMs ?? 1000;\n this.maxBackoffMs = options.maxBackoffMs ?? 60000;\n\n this.minuteTokens = this.requestsPerMinute;\n this.secondTokens = this.requestsPerSecond;\n }\n\n async execute<T>(fn: () => Promise<T>, _maxRetries: number = 3): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n this.queue.push({\n execute: fn as () => Promise<unknown>,\n resolve: resolve as (value: unknown) => void,\n reject,\n retryCount: 0,\n });\n this.pump();\n });\n }\n\n getStatus(): {\n queueLength: number;\n activeRequests: number;\n minuteTokens: number;\n secondTokens: number;\n backoffMs: number;\n } {\n return {\n queueLength: this.queue.length,\n activeRequests: this.activeRequests,\n minuteTokens: this.minuteTokens,\n secondTokens: this.secondTokens,\n backoffMs: this.currentBackoffMs,\n };\n }\n\n reset(): void {\n this.minuteTokens = this.requestsPerMinute;\n this.secondTokens = this.requestsPerSecond;\n this.activeRequests = 0;\n this.queue = [];\n this.currentBackoffMs = 0;\n this.lastMinuteReset = Date.now();\n this.lastSecondReset = Date.now();\n if (this.wakeTimer !== null) {\n clearTimeout(this.wakeTimer);\n this.wakeTimer = null;\n }\n }\n\n /**\n * Start queued requests up to the rate/concurrency budget, then return —\n * in-flight requests re-enter the pump as they settle, and a wake timer\n * covers token-bucket refills and 429 backoff. Never awaits a request.\n */\n private pump(): void {\n if (this.pumping) return;\n this.pumping = true;\n try {\n this.refillTokens();\n while (\n this.queue.length > 0 &&\n this.currentBackoffMs === 0 &&\n this.canMakeRequest()\n ) {\n const request = this.queue.shift();\n if (!request) break;\n this.consumeTokens();\n this.activeRequests++;\n void this.settle(request);\n }\n } finally {\n this.pumping = false;\n }\n\n if (this.queue.length === 0) return;\n if (this.currentBackoffMs > 0) {\n this.scheduleWake(this.currentBackoffMs, true);\n } else if (this.activeRequests < this.maxConcurrent) {\n // Blocked on token buckets alone — no in-flight settle will re-pump.\n this.scheduleWake(this.getWaitTime(), false);\n }\n // Otherwise blocked on concurrency: each settle() re-pumps in `finally`.\n }\n\n private scheduleWake(ms: number, clearsBackoff: boolean): void {\n if (this.wakeTimer !== null) return;\n this.wakeTimer = setTimeout(() => {\n this.wakeTimer = null;\n if (clearsBackoff) this.currentBackoffMs = 0;\n this.pump();\n }, ms);\n }\n\n private async settle(request: QueuedRequest<unknown>): Promise<void> {\n try {\n const result = await request.execute();\n request.resolve(result);\n this.currentBackoffMs = 0;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (this.isRateLimitError(err)) {\n this.currentBackoffMs = Math.min(\n this.baseBackoffMs * Math.pow(2, request.retryCount),\n this.maxBackoffMs,\n );\n\n console.warn(\n `[RateLimiter] Rate limited. Backing off for ${this.currentBackoffMs}ms ` +\n `(retry ${request.retryCount + 1})`,\n );\n\n if (request.retryCount < 3) {\n this.queue.unshift({\n ...request,\n retryCount: request.retryCount + 1,\n });\n } else {\n request.reject(\n new Error(\n `Rate limit exceeded after ${request.retryCount + 1} retries: ${err.message}`,\n ),\n );\n }\n } else {\n request.reject(err);\n }\n } finally {\n this.activeRequests--;\n this.pump();\n }\n }\n\n private refillTokens(): void {\n const now = Date.now();\n if (now - this.lastMinuteReset >= 60000) {\n this.minuteTokens = this.requestsPerMinute;\n this.lastMinuteReset = now;\n }\n if (now - this.lastSecondReset >= 1000) {\n this.secondTokens = this.requestsPerSecond;\n this.lastSecondReset = now;\n }\n }\n\n private canMakeRequest(): boolean {\n return (\n this.minuteTokens > 0 &&\n this.secondTokens > 0 &&\n this.activeRequests < this.maxConcurrent\n );\n }\n\n private consumeTokens(): void {\n this.minuteTokens--;\n this.secondTokens--;\n }\n\n private getWaitTime(): number {\n const now = Date.now();\n if (this.secondTokens <= 0) {\n return Math.max(0, 1000 - (now - this.lastSecondReset));\n }\n if (this.minuteTokens <= 0) {\n return Math.max(0, 60000 - (now - this.lastMinuteReset));\n }\n return 100;\n }\n\n private isRateLimitError(error: Error): boolean {\n const message = error.message.toLowerCase();\n return (\n message.includes('429') ||\n message.includes('rate limit') ||\n message.includes('too many requests') ||\n message.includes('quota exceeded')\n );\n }\n}\n\n// Singleton instance\nlet globalRateLimiter: RateLimiter | null = null;\n\nexport function getGlobalRateLimiter(\n options?: RateLimiterOptions,\n): RateLimiter {\n if (!globalRateLimiter) {\n globalRateLimiter = new RateLimiter(options);\n }\n return globalRateLimiter;\n}\n\nexport function resetGlobalRateLimiter(): void {\n globalRateLimiter?.reset();\n globalRateLimiter = null;\n}\n","/**\n * Token Tracker for LLM Usage\n *\n * Tracks token usage across multiple LLM calls for:\n * - Cost estimation (pricing fetched from OpenRouter models API)\n * - Usage monitoring\n * - Quota management\n * - Per-call JSONL logging\n *\n * @packageDocumentation\n */\n\nimport { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nexport interface TokenUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n callCount: number;\n /** Subset of `promptTokens` that were cache reads (billed at the cache-read rate). */\n cachedPromptTokens: number;\n /** Subset of `promptTokens` written to cache (Anthropic; billed at the cache-write rate). */\n cacheWriteTokens: number;\n}\n\nexport interface TokenCost {\n promptCostPer1K: number;\n completionCostPer1K: number;\n /** Per-1K rate for cache-read (cache-hit) prompt tokens. Falls back to prompt rate when absent. */\n cacheReadCostPer1K?: number;\n /** Per-1K rate for cache-write (cache-creation) prompt tokens. Falls back to prompt rate when absent. */\n cacheWriteCostPer1K?: number;\n}\n\nexport interface CallLogEntry {\n timestamp: string;\n provider: string;\n model: string;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n /** Cache-read (cache-hit) subset of promptTokens, billed at the discounted rate. */\n cachedPromptTokens?: number;\n /** Cache-write subset of promptTokens (Anthropic). */\n cacheWriteTokens?: number;\n estimatedCost: number;\n durationMs?: number;\n source: 'local-log';\n}\n\n// ---------------------------------------------------------------------------\n// Pricing: fetched from OpenRouter /api/v1/models, cached to disk for 24h\n// ---------------------------------------------------------------------------\n\nconst ALMADAR_ROOT = process.env['ALMADAR_ROOT'] ?? process.cwd();\nconst PRICING_CACHE_PATH = join(ALMADAR_ROOT, '.llm-pricing-cache.json');\nconst CALL_LOG_PATH = join(ALMADAR_ROOT, '.llm-call-log.jsonl');\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\n// Bump when the cached TokenCost shape changes so stale on-disk caches are\n// invalidated on upgrade. v2 added cacheReadCostPer1K / cacheWriteCostPer1K.\nconst PRICING_CACHE_VERSION = 2;\n\n/** Map from our local model name to OpenRouter model ID */\nconst MODEL_ID_MAP: Record<string, string> = {\n // Anthropic\n 'claude-opus-4-5-20250929': 'anthropic/claude-opus-4.5',\n 'claude-sonnet-4-5-20250929': 'anthropic/claude-sonnet-4.5',\n 'claude-sonnet-4-20250514': 'anthropic/claude-sonnet-4',\n 'claude-3-5-haiku-20241022': 'anthropic/claude-3.5-haiku',\n // DeepSeek — map to current versions on OpenRouter\n 'deepseek-chat': 'deepseek/deepseek-v4-flash',\n 'deepseek-coder': 'deepseek/deepseek-v4-flash',\n 'deepseek-reasoner': 'deepseek/deepseek-v4-flash',\n 'deepseek-v4-pro': 'deepseek/deepseek-v4-pro',\n 'deepseek-v4-flash': 'deepseek/deepseek-v4-flash',\n // Kimi\n 'kimi-k2.5': 'moonshotai/kimi-k2.5',\n};\n\n// Fallback: zero cost — forces OpenRouter fetch for real pricing\nconst FALLBACK_COSTS: Record<string, TokenCost> = {};\n\ninterface PricingCache {\n version?: number;\n fetchedAt: number;\n models: Record<string, TokenCost>;\n}\n\nlet pricingCache: PricingCache | null = null;\n\nfunction loadCachedPricing(): PricingCache | null {\n try {\n const raw = readFileSync(PRICING_CACHE_PATH, 'utf-8');\n const parsed = JSON.parse(raw) as PricingCache;\n if (parsed.version === PRICING_CACHE_VERSION && Date.now() - parsed.fetchedAt < CACHE_TTL_MS) {\n return parsed;\n }\n } catch {\n // No cache or expired\n }\n return null;\n}\n\nasync function fetchPricingFromOpenRouter(): Promise<Record<string, TokenCost>> {\n const res = await fetch('https://openrouter.ai/api/v1/models');\n if (!res.ok) throw new Error(`OpenRouter models API: HTTP ${res.status}`);\n const json = await res.json() as {\n data?: Array<{\n id: string;\n pricing?: {\n prompt?: string;\n completion?: string;\n input_cache_read?: string;\n input_cache_write?: string;\n };\n }>;\n };\n const models: Record<string, TokenCost> = {};\n for (const m of json.data ?? []) {\n const promptPerToken = parseFloat(m.pricing?.prompt ?? '0');\n const completionPerToken = parseFloat(m.pricing?.completion ?? '0');\n const cacheReadPerToken = parseFloat(m.pricing?.input_cache_read ?? '0');\n const cacheWritePerToken = parseFloat(m.pricing?.input_cache_write ?? '0');\n if (promptPerToken > 0 || completionPerToken > 0) {\n models[m.id] = {\n promptCostPer1K: promptPerToken * 1000,\n completionCostPer1K: completionPerToken * 1000,\n // 0 (field absent) → leave undefined so cost math falls back to the prompt rate.\n ...(cacheReadPerToken > 0 ? { cacheReadCostPer1K: cacheReadPerToken * 1000 } : {}),\n ...(cacheWritePerToken > 0 ? { cacheWriteCostPer1K: cacheWritePerToken * 1000 } : {}),\n };\n }\n }\n return models;\n}\n\n/**\n * Get pricing for all models. Uses 24h disk cache, fetches from OpenRouter on miss.\n * Non-blocking: returns cached/fallback immediately, refreshes in background if stale.\n */\nfunction getPricing(): Record<string, TokenCost> {\n if (pricingCache) return pricingCache.models;\n\n const diskCache = loadCachedPricing();\n if (diskCache) {\n pricingCache = diskCache;\n return diskCache.models;\n }\n\n // Trigger background fetch, return fallback for now\n refreshPricingCache();\n return FALLBACK_COSTS;\n}\n\nfunction refreshPricingCache(): void {\n fetchPricingFromOpenRouter()\n .then((models) => {\n pricingCache = { version: PRICING_CACHE_VERSION, fetchedAt: Date.now(), models };\n try {\n mkdirSync(dirname(PRICING_CACHE_PATH), { recursive: true });\n writeFileSync(PRICING_CACHE_PATH, JSON.stringify(pricingCache));\n } catch {\n // Non-critical\n }\n })\n .catch(() => {\n // Silently fail, use fallback\n });\n}\n\nfunction getCostForModel(model: string): TokenCost {\n const pricing = getPricing();\n // Try direct match on OpenRouter ID\n const orId = MODEL_ID_MAP[model];\n if (orId && pricing[orId]) return pricing[orId];\n // Try direct key match (e.g., user passed \"openai/gpt-4o\")\n if (pricing[model]) return pricing[model];\n // Fuzzy: find first key containing the model name\n for (const [key, cost] of Object.entries(pricing)) {\n if (key.includes(model) || model.includes(key.split('/')[1] ?? '')) return cost;\n }\n // No pricing available — return zero (OpenRouter fetch pending or model not listed)\n return { promptCostPer1K: 0, completionCostPer1K: 0 };\n}\n\n// ---------------------------------------------------------------------------\n// TokenTracker\n// ---------------------------------------------------------------------------\n\nexport class TokenTracker {\n private model: string;\n private usage: TokenUsage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n callCount: 0,\n cachedPromptTokens: 0,\n cacheWriteTokens: 0,\n };\n\n /** Sum of provider-reported authoritative costs (e.g. OpenRouter `usage.cost`). */\n private authoritativeCostUSD = 0;\n /** Token buckets for calls WITHOUT an authoritative cost — priced cache-aware.\n * Keyed by the model that MADE the calls: `setModel` flips the tracker\n * between models mid-process (coordinator on pro, subagents on flash), and\n * pricing the aggregate at whichever model is current when someone reads\n * the estimate mispriced the whole history by up to the models' rate ratio\n * (snapshot/diff consumers saw phantom multi-dollar deltas — or $0 after a\n * negative-clamp — per battery spec). Per-model buckets keep the\n * retroactive late-pricing property without cross-model contamination. */\n private computedByModel = new Map<\n string,\n { promptTokens: number; completionTokens: number; cachedPromptTokens: number; cacheWriteTokens: number }\n >();\n\n constructor(model: string = 'claude-sonnet-4-5-20250929') {\n this.model = model;\n }\n\n /** Cache-aware cost for one (or an aggregate of) call(s), in USD. */\n private costFor(model: string, promptTokens: number, completionTokens: number, cached: number, written: number): number {\n const costs = getCostForModel(model);\n const cacheReadRate = costs.cacheReadCostPer1K ?? costs.promptCostPer1K;\n const cacheWriteRate = costs.cacheWriteCostPer1K ?? costs.promptCostPer1K;\n const uncached = Math.max(0, promptTokens - cached - written);\n return (\n (uncached / 1000) * costs.promptCostPer1K +\n (cached / 1000) * cacheReadRate +\n (written / 1000) * cacheWriteRate +\n (completionTokens / 1000) * costs.completionCostPer1K\n );\n }\n\n /**\n * Record one LLM call's usage. `promptTokens` is the TOTAL input count\n * (cache reads + cache writes + uncached); `cachedPromptTokens` and\n * `cacheWriteTokens` are subsets of it, priced at their own (cheaper /\n * pricier) rates. Providers that don't report cache detail pass 0, which\n * reduces to the previous flat-rate behaviour.\n */\n addUsage(\n promptTokens: number,\n completionTokens: number,\n options?: {\n provider?: string;\n durationMs?: number;\n cachedPromptTokens?: number;\n cacheWriteTokens?: number;\n /** Provider-reported authoritative cost (e.g. OpenRouter `usage.cost`). When set, used verbatim. */\n costUSD?: number;\n },\n ): void {\n const cached = Math.min(promptTokens, Math.max(0, options?.cachedPromptTokens ?? 0));\n const written = Math.min(promptTokens - cached, Math.max(0, options?.cacheWriteTokens ?? 0));\n\n this.usage.promptTokens += promptTokens;\n this.usage.completionTokens += completionTokens;\n this.usage.totalTokens += promptTokens + completionTokens;\n this.usage.cachedPromptTokens += cached;\n this.usage.cacheWriteTokens += written;\n this.usage.callCount++;\n\n // Prefer the provider's authoritative cost (already cache- and routing-\n // adjusted). Otherwise bucket the tokens and price them cache-aware so\n // late-arriving pricing still applies retroactively to the estimate.\n const authoritative = options?.costUSD;\n let estimatedCost: number;\n if (authoritative != null && Number.isFinite(authoritative)) {\n this.authoritativeCostUSD += authoritative;\n estimatedCost = authoritative;\n } else {\n const bucket = this.computedByModel.get(this.model) ?? {\n promptTokens: 0,\n completionTokens: 0,\n cachedPromptTokens: 0,\n cacheWriteTokens: 0,\n };\n bucket.promptTokens += promptTokens;\n bucket.completionTokens += completionTokens;\n bucket.cachedPromptTokens += cached;\n bucket.cacheWriteTokens += written;\n this.computedByModel.set(this.model, bucket);\n estimatedCost = this.costFor(this.model, promptTokens, completionTokens, cached, written);\n }\n\n const entry: CallLogEntry = {\n timestamp: new Date().toISOString(),\n provider: options?.provider ?? 'unknown',\n model: this.model,\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n cachedPromptTokens: cached,\n cacheWriteTokens: written,\n estimatedCost,\n durationMs: options?.durationMs,\n source: 'local-log',\n };\n\n try {\n mkdirSync(dirname(CALL_LOG_PATH), { recursive: true });\n appendFileSync(CALL_LOG_PATH, JSON.stringify(entry) + '\\n');\n } catch {\n // Non-critical: don't break LLM calls if logging fails\n }\n }\n\n getSummary(): TokenUsage {\n return { ...this.usage };\n }\n\n getEstimatedCost(): number {\n let computed = 0;\n for (const [model, b] of this.computedByModel) {\n computed += this.costFor(\n model,\n b.promptTokens,\n b.completionTokens,\n b.cachedPromptTokens,\n b.cacheWriteTokens,\n );\n }\n return this.authoritativeCostUSD + computed;\n }\n\n getFormattedCost(): string {\n const cost = this.getEstimatedCost();\n return `$${cost.toFixed(4)}`;\n }\n\n getReport(): string {\n const summary = this.getSummary();\n const cost = this.getEstimatedCost();\n return [\n `Token Usage Report (${this.model})`,\n `─────────────────────────────`,\n `Calls: ${summary.callCount}`,\n `Prompt Tokens: ${summary.promptTokens.toLocaleString()}`,\n `Completion Tokens: ${summary.completionTokens.toLocaleString()}`,\n `Total Tokens: ${summary.totalTokens.toLocaleString()}`,\n `Estimated Cost: $${cost.toFixed(4)}`,\n ].join('\\n');\n }\n\n reset(): void {\n this.usage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n callCount: 0,\n cachedPromptTokens: 0,\n cacheWriteTokens: 0,\n };\n this.authoritativeCostUSD = 0;\n this.computedByModel.clear();\n }\n\n setModel(model: string): void {\n this.model = model;\n }\n}\n\n// Global tracker instance\nlet globalTracker: TokenTracker | null = null;\n\nexport function getGlobalTokenTracker(model?: string): TokenTracker {\n if (!globalTracker) {\n globalTracker = new TokenTracker(model);\n } else if (model) {\n globalTracker.setModel(model);\n }\n return globalTracker;\n}\n\nexport function resetGlobalTokenTracker(): void {\n globalTracker?.reset();\n}\n\nexport function getCallLogPath(): string {\n return CALL_LOG_PATH;\n}\n\n/** Force-refresh the pricing cache from OpenRouter. */\nexport async function refreshPricing(): Promise<void> {\n const models = await fetchPricingFromOpenRouter();\n pricingCache = { version: PRICING_CACHE_VERSION, fetchedAt: Date.now(), models };\n mkdirSync(dirname(PRICING_CACHE_PATH), { recursive: true });\n writeFileSync(PRICING_CACHE_PATH, JSON.stringify(pricingCache));\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { R as RateLimiterOptions, T as TokenUsage } from './rate-limiter-C12cctao.js';
1
+ import { R as RateLimiterOptions, T as TokenUsage } from './rate-limiter-CXaf8aAy.js';
2
2
  import { ChatOpenAI } from '@langchain/openai';
3
3
  import { ChatAnthropic } from '@langchain/anthropic';
4
4
  import Anthropic from '@anthropic-ai/sdk';
@@ -210,6 +210,7 @@ declare const OPENROUTER_MODELS: {
210
210
  readonly GEMMA_3_4B: "google/gemma-3-4b-it";
211
211
  readonly GEMMA_3_12B: "google/gemma-3-12b-it";
212
212
  readonly GEMMA_3_27B: "google/gemma-3-27b-it";
213
+ readonly GEMMA_4_26B_A4B: "google/gemma-4-26b-a4b-it";
213
214
  readonly MINISTRAL_8B: "mistralai/ministral-8b-2512";
214
215
  readonly MISTRAL_SMALL_3_1: "mistralai/mistral-small-3.1-24b-instruct";
215
216
  readonly MISTRAL_MEDIUM_3_1: "mistralai/mistral-medium-3.1";
package/dist/client.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import './rate-limiter-C12cctao.js';
1
+ import './rate-limiter-CXaf8aAy.js';
2
2
  import '@langchain/openai';
3
3
  import '@langchain/anthropic';
4
4
  import '@anthropic-ai/sdk';
5
5
  import 'zod';
6
- export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, b as CacheableBlock, D as DEEPSEEK_MODELS, K as KIMI_MODELS, j as LLMCallOptions, a as LLMClient, k as LLMClientOptions, L 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, V as VisionCallOptions, r as VisionImageMediaType, s as VisionImagePart, t as buildVisionMessageContent, u as createAnthropicClient, v as createCreativeClient, w as createDeepSeekClient, x as createFixClient, y as createKimiClient, z as createOpenAIClient, B as createOpenRouterClient, E as createRequirementsClient, F as createZhipuClient, G as getAvailableProvider, H as getSharedLLMClient, I as isProviderAvailable, M as resetSharedLLMClient } from './client-CXZebrpn.js';
6
+ export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, b as CacheableBlock, D as DEEPSEEK_MODELS, K as KIMI_MODELS, j as LLMCallOptions, a as LLMClient, k as LLMClientOptions, L 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, V as VisionCallOptions, r as VisionImageMediaType, s as VisionImagePart, t as buildVisionMessageContent, u as createAnthropicClient, v as createCreativeClient, w as createDeepSeekClient, x as createFixClient, y as createKimiClient, z as createOpenAIClient, B as createOpenRouterClient, E as createRequirementsClient, F as createZhipuClient, G as getAvailableProvider, H as getSharedLLMClient, I as isProviderAvailable, M as resetSharedLLMClient } from './client-CfCemdbr.js';
7
7
  import '@almadar/core';
package/dist/client.js CHANGED
@@ -19,9 +19,9 @@ import {
19
19
  getSharedLLMClient,
20
20
  isProviderAvailable,
21
21
  resetSharedLLMClient
22
- } from "./chunk-WXZXTLPF.js";
22
+ } from "./chunk-EOZGJJ4J.js";
23
23
  import "./chunk-P4VCT25B.js";
24
- import "./chunk-CZX533SU.js";
24
+ import "./chunk-RPG3SUIB.js";
25
25
  export {
26
26
  ANTHROPIC_MODELS,
27
27
  DEEPSEEK_MODELS,
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { L as LLMFinishReason, a as LLMClient } from './client-CXZebrpn.js';
2
- export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, b as CacheableBlock, c as ChatCompletionChoice, d as ChatCompletionMessage, e as ChatCompletionResponse, f as ChatCompletionRole, g as ChatCompletionToolCall, h as ChatCompletionToolDef, i as ChatCompletionUsage, D as DEEPSEEK_MODELS, K as KIMI_MODELS, j as LLMCallOptions, k 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, V as VisionCallOptions, r as VisionImageMediaType, s as VisionImagePart, t as buildVisionMessageContent, u as createAnthropicClient, v as createCreativeClient, w as createDeepSeekClient, x as createFixClient, y as createKimiClient, z as createOpenAIClient, B as createOpenRouterClient, E as createRequirementsClient, F as createZhipuClient, G as getAvailableProvider, H as getSharedLLMClient, I as isProviderAvailable, J as parseChatCompletionResponse, M as resetSharedLLMClient } from './client-CXZebrpn.js';
3
- export { a as RateLimiter, R as RateLimiterOptions, b as TokenTracker, T as TokenUsage, g as getGlobalRateLimiter, c as getGlobalTokenTracker, r as resetGlobalRateLimiter, d as resetGlobalTokenTracker } from './rate-limiter-C12cctao.js';
1
+ import { L as LLMFinishReason, a as LLMClient } from './client-CfCemdbr.js';
2
+ export { A as ANTHROPIC_MODELS, C as CacheAwareLLMCallOptions, b as CacheableBlock, c as ChatCompletionChoice, d as ChatCompletionMessage, e as ChatCompletionResponse, f as ChatCompletionRole, g as ChatCompletionToolCall, h as ChatCompletionToolDef, i as ChatCompletionUsage, D as DEEPSEEK_MODELS, K as KIMI_MODELS, j as LLMCallOptions, k 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, V as VisionCallOptions, r as VisionImageMediaType, s as VisionImagePart, t as buildVisionMessageContent, u as createAnthropicClient, v as createCreativeClient, w as createDeepSeekClient, x as createFixClient, y as createKimiClient, z as createOpenAIClient, B as createOpenRouterClient, E as createRequirementsClient, F as createZhipuClient, G as getAvailableProvider, H as getSharedLLMClient, I as isProviderAvailable, J as parseChatCompletionResponse, M as resetSharedLLMClient } from './client-CfCemdbr.js';
3
+ export { a as RateLimiter, R as RateLimiterOptions, b as TokenTracker, T as TokenUsage, g as getGlobalRateLimiter, c as getGlobalTokenTracker, r as resetGlobalRateLimiter, d as resetGlobalTokenTracker } from './rate-limiter-CXaf8aAy.js';
4
4
  export { autoCloseJson, extractJsonFromText, isValidJson, parseJsonResponse, safeParseJson } from './json-parser.js';
5
5
  import { z } from 'zod';
6
6
  export { JsonSchema, STRUCTURED_OUTPUT_MODELS, StructuredGenerationOptions, StructuredGenerationResult, StructuredOutputClient, StructuredOutputOptions, getStructuredOutputClient, isStructuredOutputAvailable, resetStructuredOutputClient } from './structured-output.js';
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  isProviderAvailable,
21
21
  parseChatCompletionResponse,
22
22
  resetSharedLLMClient
23
- } from "./chunk-WXZXTLPF.js";
23
+ } from "./chunk-EOZGJJ4J.js";
24
24
  import {
25
25
  autoCloseJson,
26
26
  extractJsonFromText,
@@ -34,7 +34,7 @@ import {
34
34
  getStructuredOutputClient,
35
35
  isStructuredOutputAvailable,
36
36
  resetStructuredOutputClient
37
- } from "./chunk-S6KQIMQQ.js";
37
+ } from "./chunk-5AM54OAM.js";
38
38
  import {
39
39
  RateLimiter,
40
40
  TokenTracker,
@@ -42,7 +42,7 @@ import {
42
42
  getGlobalTokenTracker,
43
43
  resetGlobalRateLimiter,
44
44
  resetGlobalTokenTracker
45
- } from "./chunk-CZX533SU.js";
45
+ } from "./chunk-RPG3SUIB.js";
46
46
  import {
47
47
  MasarError,
48
48
  MasarProvider,
@@ -104,8 +104,9 @@ declare class RateLimiter {
104
104
  private queue;
105
105
  private lastMinuteReset;
106
106
  private lastSecondReset;
107
- private processing;
107
+ private pumping;
108
108
  private currentBackoffMs;
109
+ private wakeTimer;
109
110
  constructor(options?: RateLimiterOptions);
110
111
  execute<T>(fn: () => Promise<T>, _maxRetries?: number): Promise<T>;
111
112
  getStatus(): {
@@ -116,13 +117,19 @@ declare class RateLimiter {
116
117
  backoffMs: number;
117
118
  };
118
119
  reset(): void;
119
- private processQueue;
120
+ /**
121
+ * Start queued requests up to the rate/concurrency budget, then return —
122
+ * in-flight requests re-enter the pump as they settle, and a wake timer
123
+ * covers token-bucket refills and 429 backoff. Never awaits a request.
124
+ */
125
+ private pump;
126
+ private scheduleWake;
127
+ private settle;
120
128
  private refillTokens;
121
129
  private canMakeRequest;
122
130
  private consumeTokens;
123
131
  private getWaitTime;
124
132
  private isRateLimitError;
125
- private sleep;
126
133
  }
127
134
  declare function getGlobalRateLimiter(options?: RateLimiterOptions): RateLimiter;
128
135
  declare function resetGlobalRateLimiter(): void;
@@ -1,4 +1,4 @@
1
- import { R as RateLimiterOptions, T as TokenUsage } from './rate-limiter-C12cctao.js';
1
+ import { R as RateLimiterOptions, T as TokenUsage } from './rate-limiter-CXaf8aAy.js';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
@@ -4,8 +4,8 @@ import {
4
4
  getStructuredOutputClient,
5
5
  isStructuredOutputAvailable,
6
6
  resetStructuredOutputClient
7
- } from "./chunk-S6KQIMQQ.js";
8
- import "./chunk-CZX533SU.js";
7
+ } from "./chunk-5AM54OAM.js";
8
+ import "./chunk-RPG3SUIB.js";
9
9
  export {
10
10
  STRUCTURED_OUTPUT_MODELS,
11
11
  StructuredOutputClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@almadar/llm",
3
- "version": "2.39.1",
3
+ "version": "2.40.0",
4
4
  "description": "Multi-provider LLM client with rate limiting, token tracking, structured outputs, and continuation handling",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -46,10 +46,10 @@
46
46
  "@almadar/logger": "^1.11.0"
47
47
  },
48
48
  "peerDependencies": {
49
- "@almadar/core": "^10.49.0"
49
+ "@almadar/core": "^10.56.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@almadar/core": "^10.49.0",
52
+ "@almadar/core": "^10.56.0",
53
53
  "@almadar/eslint-plugin": "^2.15.0",
54
54
  "@types/node": "^22.0.0",
55
55
  "@typescript-eslint/parser": "8.65.0",
package/src/client.ts CHANGED
@@ -428,6 +428,8 @@ export const OPENROUTER_MODELS = {
428
428
  GEMMA_3_4B: 'google/gemma-3-4b-it',
429
429
  GEMMA_3_12B: 'google/gemma-3-12b-it',
430
430
  GEMMA_3_27B: 'google/gemma-3-27b-it',
431
+ // Gemma 4 26B A4B (MoE, 4B active) — matrix candidate vs deepseek default (G-MODEL-1)
432
+ GEMMA_4_26B_A4B: 'google/gemma-4-26b-a4b-it',
431
433
 
432
434
  // Mistral models - strong structured output, function calling
433
435
  MINISTRAL_8B: 'mistralai/ministral-8b-2512',
@@ -1110,6 +1112,13 @@ export class LLMClient {
1110
1112
  };
1111
1113
  if (options.tools.length > 0) body['tools'] = options.tools;
1112
1114
  if (options.maxTokens !== undefined) body['max_tokens'] = options.maxTokens;
1115
+ // This raw-fetch path bypasses createModel()'s modelKwargs, so the
1116
+ // reasoning kill-switch must be applied here as well — without it
1117
+ // DeepSeek V4 burns the whole completion cap on reasoning_content
1118
+ // before emitting a tool call (rabit's runaway-reasoning class).
1119
+ if (this.reasoningEffort) {
1120
+ body['reasoning_effort'] = this.reasoningEffort;
1121
+ }
1113
1122
  // OpenRouter requires explicit opt-in for `reasoning` (chain-of-thought)
1114
1123
  // on models that support it (Gemma 4, etc.). DeepSeek's native API
1115
1124
  // returns `reasoning_content` without an opt-in; OpenRouter returns
@@ -1118,7 +1127,7 @@ export class LLMClient {
1118
1127
  // below maps `reasoning` → `reasoning_content` so downstream
1119
1128
  // consumers see one canonical field name.
1120
1129
  if (this.provider === 'openrouter') {
1121
- body['reasoning'] = { enabled: true };
1130
+ body['reasoning'] = this.reasoningEffort === 'none' ? { enabled: false } : { enabled: true };
1122
1131
  // Return authoritative usage accounting (real cost + cached-token split).
1123
1132
  body['usage'] = { include: true };
1124
1133
  }
@@ -51,8 +51,9 @@ export class RateLimiter {
51
51
  private queue: QueuedRequest<unknown>[] = [];
52
52
  private lastMinuteReset: number = Date.now();
53
53
  private lastSecondReset: number = Date.now();
54
- private processing: boolean = false;
54
+ private pumping: boolean = false;
55
55
  private currentBackoffMs: number = 0;
56
+ private wakeTimer: ReturnType<typeof setTimeout> | null = null;
56
57
 
57
58
  constructor(options: RateLimiterOptions = {}) {
58
59
  this.requestsPerMinute = options.requestsPerMinute ?? 60;
@@ -73,7 +74,7 @@ export class RateLimiter {
73
74
  reject,
74
75
  retryCount: 0,
75
76
  });
76
- this.processQueue();
77
+ this.pump();
77
78
  });
78
79
  }
79
80
 
@@ -101,71 +102,94 @@ export class RateLimiter {
101
102
  this.currentBackoffMs = 0;
102
103
  this.lastMinuteReset = Date.now();
103
104
  this.lastSecondReset = Date.now();
105
+ if (this.wakeTimer !== null) {
106
+ clearTimeout(this.wakeTimer);
107
+ this.wakeTimer = null;
108
+ }
104
109
  }
105
110
 
106
- private async processQueue(): Promise<void> {
107
- if (this.processing) return;
108
- this.processing = true;
109
-
110
- while (this.queue.length > 0) {
111
+ /**
112
+ * Start queued requests up to the rate/concurrency budget, then return
113
+ * in-flight requests re-enter the pump as they settle, and a wake timer
114
+ * covers token-bucket refills and 429 backoff. Never awaits a request.
115
+ */
116
+ private pump(): void {
117
+ if (this.pumping) return;
118
+ this.pumping = true;
119
+ try {
111
120
  this.refillTokens();
112
-
113
- if (!this.canMakeRequest()) {
114
- const waitTime = this.getWaitTime();
115
- await this.sleep(waitTime);
116
- continue;
117
- }
118
-
119
- if (this.currentBackoffMs > 0) {
120
- await this.sleep(this.currentBackoffMs);
121
- this.currentBackoffMs = 0;
121
+ while (
122
+ this.queue.length > 0 &&
123
+ this.currentBackoffMs === 0 &&
124
+ this.canMakeRequest()
125
+ ) {
126
+ const request = this.queue.shift();
127
+ if (!request) break;
128
+ this.consumeTokens();
129
+ this.activeRequests++;
130
+ void this.settle(request);
122
131
  }
132
+ } finally {
133
+ this.pumping = false;
134
+ }
123
135
 
124
- const request = this.queue.shift();
125
- if (!request) continue;
136
+ if (this.queue.length === 0) return;
137
+ if (this.currentBackoffMs > 0) {
138
+ this.scheduleWake(this.currentBackoffMs, true);
139
+ } else if (this.activeRequests < this.maxConcurrent) {
140
+ // Blocked on token buckets alone — no in-flight settle will re-pump.
141
+ this.scheduleWake(this.getWaitTime(), false);
142
+ }
143
+ // Otherwise blocked on concurrency: each settle() re-pumps in `finally`.
144
+ }
126
145
 
127
- this.consumeTokens();
128
- this.activeRequests++;
146
+ private scheduleWake(ms: number, clearsBackoff: boolean): void {
147
+ if (this.wakeTimer !== null) return;
148
+ this.wakeTimer = setTimeout(() => {
149
+ this.wakeTimer = null;
150
+ if (clearsBackoff) this.currentBackoffMs = 0;
151
+ this.pump();
152
+ }, ms);
153
+ }
129
154
 
130
- try {
131
- const result = await request.execute();
132
- request.resolve(result);
133
- this.currentBackoffMs = 0;
134
- } catch (error) {
135
- const err = error instanceof Error ? error : new Error(String(error));
155
+ private async settle(request: QueuedRequest<unknown>): Promise<void> {
156
+ try {
157
+ const result = await request.execute();
158
+ request.resolve(result);
159
+ this.currentBackoffMs = 0;
160
+ } catch (error) {
161
+ const err = error instanceof Error ? error : new Error(String(error));
136
162
 
137
- if (this.isRateLimitError(err)) {
138
- this.currentBackoffMs = Math.min(
139
- this.baseBackoffMs * Math.pow(2, request.retryCount),
140
- this.maxBackoffMs,
141
- );
163
+ if (this.isRateLimitError(err)) {
164
+ this.currentBackoffMs = Math.min(
165
+ this.baseBackoffMs * Math.pow(2, request.retryCount),
166
+ this.maxBackoffMs,
167
+ );
142
168
 
143
- console.warn(
144
- `[RateLimiter] Rate limited. Backing off for ${this.currentBackoffMs}ms ` +
145
- `(retry ${request.retryCount + 1})`,
146
- );
169
+ console.warn(
170
+ `[RateLimiter] Rate limited. Backing off for ${this.currentBackoffMs}ms ` +
171
+ `(retry ${request.retryCount + 1})`,
172
+ );
147
173
 
148
- if (request.retryCount < 3) {
149
- this.queue.unshift({
150
- ...request,
151
- retryCount: request.retryCount + 1,
152
- });
153
- } else {
154
- request.reject(
155
- new Error(
156
- `Rate limit exceeded after ${request.retryCount + 1} retries: ${err.message}`,
157
- ),
158
- );
159
- }
174
+ if (request.retryCount < 3) {
175
+ this.queue.unshift({
176
+ ...request,
177
+ retryCount: request.retryCount + 1,
178
+ });
160
179
  } else {
161
- request.reject(err);
180
+ request.reject(
181
+ new Error(
182
+ `Rate limit exceeded after ${request.retryCount + 1} retries: ${err.message}`,
183
+ ),
184
+ );
162
185
  }
163
- } finally {
164
- this.activeRequests--;
186
+ } else {
187
+ request.reject(err);
165
188
  }
189
+ } finally {
190
+ this.activeRequests--;
191
+ this.pump();
166
192
  }
167
-
168
- this.processing = false;
169
193
  }
170
194
 
171
195
  private refillTokens(): void {
@@ -213,10 +237,6 @@ export class RateLimiter {
213
237
  message.includes('quota exceeded')
214
238
  );
215
239
  }
216
-
217
- private sleep(ms: number): Promise<void> {
218
- return new Promise((resolve) => setTimeout(resolve, ms));
219
- }
220
240
  }
221
241
 
222
242
  // Singleton instance
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/rate-limiter.ts","../src/token-tracker.ts"],"sourcesContent":["/**\n * Rate Limiter for LLM API Calls\n *\n * Implements token bucket algorithm with:\n * - Configurable requests per minute/second\n * - Automatic backoff on 429 errors\n * - Queue for pending requests\n *\n * @packageDocumentation\n */\n\nexport interface RateLimiterOptions {\n /** Maximum requests per minute (default: 60) */\n requestsPerMinute?: number;\n /** Maximum requests per second (default: 3) */\n requestsPerSecond?: number;\n /** Maximum concurrent requests (default: 5) */\n maxConcurrent?: number;\n /** Base delay for exponential backoff in ms (default: 1000) */\n baseBackoffMs?: number;\n /** Maximum backoff delay in ms (default: 60000) */\n maxBackoffMs?: number;\n}\n\ninterface QueuedRequest<T> {\n execute: () => Promise<T>;\n resolve: (value: T) => void;\n reject: (error: Error) => void;\n retryCount: number;\n}\n\n/**\n * Rate limiter for LLM API calls using token bucket algorithm.\n *\n * @example\n * ```typescript\n * const limiter = new RateLimiter({ requestsPerMinute: 30 });\n * const result = await limiter.execute(() => llm.invoke(messages));\n * ```\n */\nexport class RateLimiter {\n private requestsPerMinute: number;\n private requestsPerSecond: number;\n private maxConcurrent: number;\n private baseBackoffMs: number;\n private maxBackoffMs: number;\n\n private minuteTokens: number;\n private secondTokens: number;\n private activeRequests: number = 0;\n private queue: QueuedRequest<unknown>[] = [];\n private lastMinuteReset: number = Date.now();\n private lastSecondReset: number = Date.now();\n private processing: boolean = false;\n private currentBackoffMs: number = 0;\n\n constructor(options: RateLimiterOptions = {}) {\n this.requestsPerMinute = options.requestsPerMinute ?? 60;\n this.requestsPerSecond = options.requestsPerSecond ?? 3;\n this.maxConcurrent = options.maxConcurrent ?? 5;\n this.baseBackoffMs = options.baseBackoffMs ?? 1000;\n this.maxBackoffMs = options.maxBackoffMs ?? 60000;\n\n this.minuteTokens = this.requestsPerMinute;\n this.secondTokens = this.requestsPerSecond;\n }\n\n async execute<T>(fn: () => Promise<T>, _maxRetries: number = 3): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n this.queue.push({\n execute: fn as () => Promise<unknown>,\n resolve: resolve as (value: unknown) => void,\n reject,\n retryCount: 0,\n });\n this.processQueue();\n });\n }\n\n getStatus(): {\n queueLength: number;\n activeRequests: number;\n minuteTokens: number;\n secondTokens: number;\n backoffMs: number;\n } {\n return {\n queueLength: this.queue.length,\n activeRequests: this.activeRequests,\n minuteTokens: this.minuteTokens,\n secondTokens: this.secondTokens,\n backoffMs: this.currentBackoffMs,\n };\n }\n\n reset(): void {\n this.minuteTokens = this.requestsPerMinute;\n this.secondTokens = this.requestsPerSecond;\n this.activeRequests = 0;\n this.queue = [];\n this.currentBackoffMs = 0;\n this.lastMinuteReset = Date.now();\n this.lastSecondReset = Date.now();\n }\n\n private async processQueue(): Promise<void> {\n if (this.processing) return;\n this.processing = true;\n\n while (this.queue.length > 0) {\n this.refillTokens();\n\n if (!this.canMakeRequest()) {\n const waitTime = this.getWaitTime();\n await this.sleep(waitTime);\n continue;\n }\n\n if (this.currentBackoffMs > 0) {\n await this.sleep(this.currentBackoffMs);\n this.currentBackoffMs = 0;\n }\n\n const request = this.queue.shift();\n if (!request) continue;\n\n this.consumeTokens();\n this.activeRequests++;\n\n try {\n const result = await request.execute();\n request.resolve(result);\n this.currentBackoffMs = 0;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (this.isRateLimitError(err)) {\n this.currentBackoffMs = Math.min(\n this.baseBackoffMs * Math.pow(2, request.retryCount),\n this.maxBackoffMs,\n );\n\n console.warn(\n `[RateLimiter] Rate limited. Backing off for ${this.currentBackoffMs}ms ` +\n `(retry ${request.retryCount + 1})`,\n );\n\n if (request.retryCount < 3) {\n this.queue.unshift({\n ...request,\n retryCount: request.retryCount + 1,\n });\n } else {\n request.reject(\n new Error(\n `Rate limit exceeded after ${request.retryCount + 1} retries: ${err.message}`,\n ),\n );\n }\n } else {\n request.reject(err);\n }\n } finally {\n this.activeRequests--;\n }\n }\n\n this.processing = false;\n }\n\n private refillTokens(): void {\n const now = Date.now();\n if (now - this.lastMinuteReset >= 60000) {\n this.minuteTokens = this.requestsPerMinute;\n this.lastMinuteReset = now;\n }\n if (now - this.lastSecondReset >= 1000) {\n this.secondTokens = this.requestsPerSecond;\n this.lastSecondReset = now;\n }\n }\n\n private canMakeRequest(): boolean {\n return (\n this.minuteTokens > 0 &&\n this.secondTokens > 0 &&\n this.activeRequests < this.maxConcurrent\n );\n }\n\n private consumeTokens(): void {\n this.minuteTokens--;\n this.secondTokens--;\n }\n\n private getWaitTime(): number {\n const now = Date.now();\n if (this.secondTokens <= 0) {\n return Math.max(0, 1000 - (now - this.lastSecondReset));\n }\n if (this.minuteTokens <= 0) {\n return Math.max(0, 60000 - (now - this.lastMinuteReset));\n }\n return 100;\n }\n\n private isRateLimitError(error: Error): boolean {\n const message = error.message.toLowerCase();\n return (\n message.includes('429') ||\n message.includes('rate limit') ||\n message.includes('too many requests') ||\n message.includes('quota exceeded')\n );\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\n// Singleton instance\nlet globalRateLimiter: RateLimiter | null = null;\n\nexport function getGlobalRateLimiter(\n options?: RateLimiterOptions,\n): RateLimiter {\n if (!globalRateLimiter) {\n globalRateLimiter = new RateLimiter(options);\n }\n return globalRateLimiter;\n}\n\nexport function resetGlobalRateLimiter(): void {\n globalRateLimiter?.reset();\n globalRateLimiter = null;\n}\n","/**\n * Token Tracker for LLM Usage\n *\n * Tracks token usage across multiple LLM calls for:\n * - Cost estimation (pricing fetched from OpenRouter models API)\n * - Usage monitoring\n * - Quota management\n * - Per-call JSONL logging\n *\n * @packageDocumentation\n */\n\nimport { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nexport interface TokenUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n callCount: number;\n /** Subset of `promptTokens` that were cache reads (billed at the cache-read rate). */\n cachedPromptTokens: number;\n /** Subset of `promptTokens` written to cache (Anthropic; billed at the cache-write rate). */\n cacheWriteTokens: number;\n}\n\nexport interface TokenCost {\n promptCostPer1K: number;\n completionCostPer1K: number;\n /** Per-1K rate for cache-read (cache-hit) prompt tokens. Falls back to prompt rate when absent. */\n cacheReadCostPer1K?: number;\n /** Per-1K rate for cache-write (cache-creation) prompt tokens. Falls back to prompt rate when absent. */\n cacheWriteCostPer1K?: number;\n}\n\nexport interface CallLogEntry {\n timestamp: string;\n provider: string;\n model: string;\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n /** Cache-read (cache-hit) subset of promptTokens, billed at the discounted rate. */\n cachedPromptTokens?: number;\n /** Cache-write subset of promptTokens (Anthropic). */\n cacheWriteTokens?: number;\n estimatedCost: number;\n durationMs?: number;\n source: 'local-log';\n}\n\n// ---------------------------------------------------------------------------\n// Pricing: fetched from OpenRouter /api/v1/models, cached to disk for 24h\n// ---------------------------------------------------------------------------\n\nconst ALMADAR_ROOT = process.env['ALMADAR_ROOT'] ?? process.cwd();\nconst PRICING_CACHE_PATH = join(ALMADAR_ROOT, '.llm-pricing-cache.json');\nconst CALL_LOG_PATH = join(ALMADAR_ROOT, '.llm-call-log.jsonl');\nconst CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours\n// Bump when the cached TokenCost shape changes so stale on-disk caches are\n// invalidated on upgrade. v2 added cacheReadCostPer1K / cacheWriteCostPer1K.\nconst PRICING_CACHE_VERSION = 2;\n\n/** Map from our local model name to OpenRouter model ID */\nconst MODEL_ID_MAP: Record<string, string> = {\n // Anthropic\n 'claude-opus-4-5-20250929': 'anthropic/claude-opus-4.5',\n 'claude-sonnet-4-5-20250929': 'anthropic/claude-sonnet-4.5',\n 'claude-sonnet-4-20250514': 'anthropic/claude-sonnet-4',\n 'claude-3-5-haiku-20241022': 'anthropic/claude-3.5-haiku',\n // DeepSeek — map to current versions on OpenRouter\n 'deepseek-chat': 'deepseek/deepseek-v4-flash',\n 'deepseek-coder': 'deepseek/deepseek-v4-flash',\n 'deepseek-reasoner': 'deepseek/deepseek-v4-flash',\n 'deepseek-v4-pro': 'deepseek/deepseek-v4-pro',\n 'deepseek-v4-flash': 'deepseek/deepseek-v4-flash',\n // Kimi\n 'kimi-k2.5': 'moonshotai/kimi-k2.5',\n};\n\n// Fallback: zero cost — forces OpenRouter fetch for real pricing\nconst FALLBACK_COSTS: Record<string, TokenCost> = {};\n\ninterface PricingCache {\n version?: number;\n fetchedAt: number;\n models: Record<string, TokenCost>;\n}\n\nlet pricingCache: PricingCache | null = null;\n\nfunction loadCachedPricing(): PricingCache | null {\n try {\n const raw = readFileSync(PRICING_CACHE_PATH, 'utf-8');\n const parsed = JSON.parse(raw) as PricingCache;\n if (parsed.version === PRICING_CACHE_VERSION && Date.now() - parsed.fetchedAt < CACHE_TTL_MS) {\n return parsed;\n }\n } catch {\n // No cache or expired\n }\n return null;\n}\n\nasync function fetchPricingFromOpenRouter(): Promise<Record<string, TokenCost>> {\n const res = await fetch('https://openrouter.ai/api/v1/models');\n if (!res.ok) throw new Error(`OpenRouter models API: HTTP ${res.status}`);\n const json = await res.json() as {\n data?: Array<{\n id: string;\n pricing?: {\n prompt?: string;\n completion?: string;\n input_cache_read?: string;\n input_cache_write?: string;\n };\n }>;\n };\n const models: Record<string, TokenCost> = {};\n for (const m of json.data ?? []) {\n const promptPerToken = parseFloat(m.pricing?.prompt ?? '0');\n const completionPerToken = parseFloat(m.pricing?.completion ?? '0');\n const cacheReadPerToken = parseFloat(m.pricing?.input_cache_read ?? '0');\n const cacheWritePerToken = parseFloat(m.pricing?.input_cache_write ?? '0');\n if (promptPerToken > 0 || completionPerToken > 0) {\n models[m.id] = {\n promptCostPer1K: promptPerToken * 1000,\n completionCostPer1K: completionPerToken * 1000,\n // 0 (field absent) → leave undefined so cost math falls back to the prompt rate.\n ...(cacheReadPerToken > 0 ? { cacheReadCostPer1K: cacheReadPerToken * 1000 } : {}),\n ...(cacheWritePerToken > 0 ? { cacheWriteCostPer1K: cacheWritePerToken * 1000 } : {}),\n };\n }\n }\n return models;\n}\n\n/**\n * Get pricing for all models. Uses 24h disk cache, fetches from OpenRouter on miss.\n * Non-blocking: returns cached/fallback immediately, refreshes in background if stale.\n */\nfunction getPricing(): Record<string, TokenCost> {\n if (pricingCache) return pricingCache.models;\n\n const diskCache = loadCachedPricing();\n if (diskCache) {\n pricingCache = diskCache;\n return diskCache.models;\n }\n\n // Trigger background fetch, return fallback for now\n refreshPricingCache();\n return FALLBACK_COSTS;\n}\n\nfunction refreshPricingCache(): void {\n fetchPricingFromOpenRouter()\n .then((models) => {\n pricingCache = { version: PRICING_CACHE_VERSION, fetchedAt: Date.now(), models };\n try {\n mkdirSync(dirname(PRICING_CACHE_PATH), { recursive: true });\n writeFileSync(PRICING_CACHE_PATH, JSON.stringify(pricingCache));\n } catch {\n // Non-critical\n }\n })\n .catch(() => {\n // Silently fail, use fallback\n });\n}\n\nfunction getCostForModel(model: string): TokenCost {\n const pricing = getPricing();\n // Try direct match on OpenRouter ID\n const orId = MODEL_ID_MAP[model];\n if (orId && pricing[orId]) return pricing[orId];\n // Try direct key match (e.g., user passed \"openai/gpt-4o\")\n if (pricing[model]) return pricing[model];\n // Fuzzy: find first key containing the model name\n for (const [key, cost] of Object.entries(pricing)) {\n if (key.includes(model) || model.includes(key.split('/')[1] ?? '')) return cost;\n }\n // No pricing available — return zero (OpenRouter fetch pending or model not listed)\n return { promptCostPer1K: 0, completionCostPer1K: 0 };\n}\n\n// ---------------------------------------------------------------------------\n// TokenTracker\n// ---------------------------------------------------------------------------\n\nexport class TokenTracker {\n private model: string;\n private usage: TokenUsage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n callCount: 0,\n cachedPromptTokens: 0,\n cacheWriteTokens: 0,\n };\n\n /** Sum of provider-reported authoritative costs (e.g. OpenRouter `usage.cost`). */\n private authoritativeCostUSD = 0;\n /** Token buckets for calls WITHOUT an authoritative cost — priced cache-aware.\n * Keyed by the model that MADE the calls: `setModel` flips the tracker\n * between models mid-process (coordinator on pro, subagents on flash), and\n * pricing the aggregate at whichever model is current when someone reads\n * the estimate mispriced the whole history by up to the models' rate ratio\n * (snapshot/diff consumers saw phantom multi-dollar deltas — or $0 after a\n * negative-clamp — per battery spec). Per-model buckets keep the\n * retroactive late-pricing property without cross-model contamination. */\n private computedByModel = new Map<\n string,\n { promptTokens: number; completionTokens: number; cachedPromptTokens: number; cacheWriteTokens: number }\n >();\n\n constructor(model: string = 'claude-sonnet-4-5-20250929') {\n this.model = model;\n }\n\n /** Cache-aware cost for one (or an aggregate of) call(s), in USD. */\n private costFor(model: string, promptTokens: number, completionTokens: number, cached: number, written: number): number {\n const costs = getCostForModel(model);\n const cacheReadRate = costs.cacheReadCostPer1K ?? costs.promptCostPer1K;\n const cacheWriteRate = costs.cacheWriteCostPer1K ?? costs.promptCostPer1K;\n const uncached = Math.max(0, promptTokens - cached - written);\n return (\n (uncached / 1000) * costs.promptCostPer1K +\n (cached / 1000) * cacheReadRate +\n (written / 1000) * cacheWriteRate +\n (completionTokens / 1000) * costs.completionCostPer1K\n );\n }\n\n /**\n * Record one LLM call's usage. `promptTokens` is the TOTAL input count\n * (cache reads + cache writes + uncached); `cachedPromptTokens` and\n * `cacheWriteTokens` are subsets of it, priced at their own (cheaper /\n * pricier) rates. Providers that don't report cache detail pass 0, which\n * reduces to the previous flat-rate behaviour.\n */\n addUsage(\n promptTokens: number,\n completionTokens: number,\n options?: {\n provider?: string;\n durationMs?: number;\n cachedPromptTokens?: number;\n cacheWriteTokens?: number;\n /** Provider-reported authoritative cost (e.g. OpenRouter `usage.cost`). When set, used verbatim. */\n costUSD?: number;\n },\n ): void {\n const cached = Math.min(promptTokens, Math.max(0, options?.cachedPromptTokens ?? 0));\n const written = Math.min(promptTokens - cached, Math.max(0, options?.cacheWriteTokens ?? 0));\n\n this.usage.promptTokens += promptTokens;\n this.usage.completionTokens += completionTokens;\n this.usage.totalTokens += promptTokens + completionTokens;\n this.usage.cachedPromptTokens += cached;\n this.usage.cacheWriteTokens += written;\n this.usage.callCount++;\n\n // Prefer the provider's authoritative cost (already cache- and routing-\n // adjusted). Otherwise bucket the tokens and price them cache-aware so\n // late-arriving pricing still applies retroactively to the estimate.\n const authoritative = options?.costUSD;\n let estimatedCost: number;\n if (authoritative != null && Number.isFinite(authoritative)) {\n this.authoritativeCostUSD += authoritative;\n estimatedCost = authoritative;\n } else {\n const bucket = this.computedByModel.get(this.model) ?? {\n promptTokens: 0,\n completionTokens: 0,\n cachedPromptTokens: 0,\n cacheWriteTokens: 0,\n };\n bucket.promptTokens += promptTokens;\n bucket.completionTokens += completionTokens;\n bucket.cachedPromptTokens += cached;\n bucket.cacheWriteTokens += written;\n this.computedByModel.set(this.model, bucket);\n estimatedCost = this.costFor(this.model, promptTokens, completionTokens, cached, written);\n }\n\n const entry: CallLogEntry = {\n timestamp: new Date().toISOString(),\n provider: options?.provider ?? 'unknown',\n model: this.model,\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n cachedPromptTokens: cached,\n cacheWriteTokens: written,\n estimatedCost,\n durationMs: options?.durationMs,\n source: 'local-log',\n };\n\n try {\n mkdirSync(dirname(CALL_LOG_PATH), { recursive: true });\n appendFileSync(CALL_LOG_PATH, JSON.stringify(entry) + '\\n');\n } catch {\n // Non-critical: don't break LLM calls if logging fails\n }\n }\n\n getSummary(): TokenUsage {\n return { ...this.usage };\n }\n\n getEstimatedCost(): number {\n let computed = 0;\n for (const [model, b] of this.computedByModel) {\n computed += this.costFor(\n model,\n b.promptTokens,\n b.completionTokens,\n b.cachedPromptTokens,\n b.cacheWriteTokens,\n );\n }\n return this.authoritativeCostUSD + computed;\n }\n\n getFormattedCost(): string {\n const cost = this.getEstimatedCost();\n return `$${cost.toFixed(4)}`;\n }\n\n getReport(): string {\n const summary = this.getSummary();\n const cost = this.getEstimatedCost();\n return [\n `Token Usage Report (${this.model})`,\n `─────────────────────────────`,\n `Calls: ${summary.callCount}`,\n `Prompt Tokens: ${summary.promptTokens.toLocaleString()}`,\n `Completion Tokens: ${summary.completionTokens.toLocaleString()}`,\n `Total Tokens: ${summary.totalTokens.toLocaleString()}`,\n `Estimated Cost: $${cost.toFixed(4)}`,\n ].join('\\n');\n }\n\n reset(): void {\n this.usage = {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n callCount: 0,\n cachedPromptTokens: 0,\n cacheWriteTokens: 0,\n };\n this.authoritativeCostUSD = 0;\n this.computedByModel.clear();\n }\n\n setModel(model: string): void {\n this.model = model;\n }\n}\n\n// Global tracker instance\nlet globalTracker: TokenTracker | null = null;\n\nexport function getGlobalTokenTracker(model?: string): TokenTracker {\n if (!globalTracker) {\n globalTracker = new TokenTracker(model);\n } else if (model) {\n globalTracker.setModel(model);\n }\n return globalTracker;\n}\n\nexport function resetGlobalTokenTracker(): void {\n globalTracker?.reset();\n}\n\nexport function getCallLogPath(): string {\n return CALL_LOG_PATH;\n}\n\n/** Force-refresh the pricing cache from OpenRouter. */\nexport async function refreshPricing(): Promise<void> {\n const models = await fetchPricingFromOpenRouter();\n pricingCache = { version: PRICING_CACHE_VERSION, fetchedAt: Date.now(), models };\n mkdirSync(dirname(PRICING_CACHE_PATH), { recursive: true });\n writeFileSync(PRICING_CACHE_PATH, JSON.stringify(pricingCache));\n}\n"],"mappings":"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","names":[]}
@@ -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';\nimport { createLogger } from '@almadar/logger';\n\n// Structured logger so callers (e.g. the orb CLI's ink TUI) can silence this\n// chatter via `setRuntimeNamespaceFilter('')`; raw console.log can't be muted.\nconst log = createLogger('almadar:llm:client');\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 /** OpenRouter: ask for detailed usage accounting (returns cached-token breakdown). */\n usage?: { include: boolean };\n /** DeepSeek V4: controls reasoning effort (\"none\" disables thinking tokens). */\n reasoning_effort?: string;\n /** OpenRouter: `{ enabled: false }` stops the router burning tokens on CoT. */\n reasoning?: { enabled: boolean };\n}\n\n/**\n * Pull the cache-read / cache-write token split out of a LangChain\n * `usage_metadata` object. Every provider LangChain supports normalises\n * its cache-hit tokens into `input_token_details` (OpenAI/OpenRouter/DeepSeek\n * via `cached_tokens`→`cache_read`; Anthropic via `cache_read`/`cache_creation`),\n * so one extractor covers them all. Returns zeros when the provider reports\n * no cache detail — which prices identically to the old flat-rate path.\n */\nfunction cacheTokensFromUsageMetadata(usageMeta: {\n input_tokens?: number;\n output_tokens?: number;\n input_token_details?: { cache_read?: number; cache_creation?: number };\n}): { cachedPromptTokens: number; cacheWriteTokens: number } {\n const details = usageMeta.input_token_details ?? {};\n return {\n cachedPromptTokens: details.cache_read ?? 0,\n cacheWriteTokens: details.cache_creation ?? 0,\n };\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' | 'masar';\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 * Reasoning effort for thinking models (e.g. DeepSeek V4).\n * Set to \"none\" to disable reasoning tokens for faster content generation.\n */\n reasoningEffort?: string;\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 * Explicit provider credentials. When set, used instead of reading the\n * provider's env vars — lets callers (e.g. the CLI's account store) supply\n * `apiKey`/`baseUrl`/`defaultModel` directly. Absent ⇒ env fallback.\n */\n providerConfig?: ProviderConfig;\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 /** Prompt tokens served from the provider's prefix cache (OpenAI-style\n * `prompt_tokens_details.cached_tokens`, deepseek-native\n * `prompt_cache_hit_tokens`). Absent when the provider reports neither. */\n cachedPromptTokens?: 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// Vision (Anthropic image content-parts)\n// ============================================================================\n\n/** Media types the Anthropic base64 image source accepts. */\nexport type VisionImageMediaType = Anthropic.Base64ImageSource['media_type'];\n\n/** A single image sent as a base64 content-part (no `data:` URI prefix). */\nexport interface VisionImagePart {\n base64: string;\n mediaType: VisionImageMediaType;\n}\n\nexport interface VisionCallOptions<T = string> {\n systemPrompt?: string;\n userText: string;\n images: ReadonlyArray<VisionImagePart>;\n /** Zod schema for structured JSON output — reuses the package's `parseJsonResponse` mechanism. */\n schema?: z.ZodSchema<T>;\n maxTokens?: number;\n temperature?: number;\n skipSchemaValidation?: boolean;\n signal?: AbortSignal;\n}\n\n/**\n * Build the Anthropic user-message content array for a vision call: one text\n * block followed by one image block per screenshot, grounded entirely in the\n * SDK's own typed shapes. Pure and side-effect-free so it can be unit-tested\n * without any API call.\n */\nexport function buildVisionMessageContent(\n userText: string,\n images: ReadonlyArray<VisionImagePart>,\n): Anthropic.ContentBlockParam[] {\n const textBlock: Anthropic.TextBlockParam = { type: 'text', text: userText };\n const imageBlocks: Anthropic.ImageBlockParam[] = images.map((img) => ({\n type: 'image',\n source: { type: 'base64', media_type: img.mediaType, data: img.base64 },\n }));\n return [textBlock, ...imageBlocks];\n}\n\n// ============================================================================\n// Provider Configuration\n// ============================================================================\n\n/**\n * Canonical base URL per OpenAI-compatible provider — the single source of\n * truth. Used both by the env-mode factories below and by the constructor's\n * back-fill, so an explicit `providerConfig` that omits `baseUrl` (e.g. the\n * CLI's `~/.almadar` account, which only stores apiKey) is sent to the\n * PROVIDER's endpoint, not api.openai.com. openai/anthropic intentionally have\n * none (their SDK defaults are correct); orbgen/masar derive theirs from env.\n */\nconst PROVIDER_BASE_URLS: Partial<Record<LLMProvider, string>> = {\n deepseek: 'https://api.deepseek.com/v1',\n kimi: 'https://api.moonshot.ai/v1',\n openrouter: 'https://openrouter.ai/api/v1',\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: PROVIDER_BASE_URLS.deepseek,\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: PROVIDER_BASE_URLS.kimi,\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: PROVIDER_BASE_URLS.openrouter,\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 masar: () => {\n // Fine-tuned masar subagent served OpenAI-compatibly (vLLM on Cloud Run GPU).\n const baseUrl = process.env.MASAR_SUBAGENT_URL;\n if (!baseUrl) {\n throw new Error(\n 'MASAR_SUBAGENT_URL environment variable is not set. ' +\n 'Set it to the masar subagent endpoint (e.g., https://masar-subagent-xxx.run.app)',\n );\n }\n return {\n apiKey: process.env.MASAR_SUBAGENT_API_KEY ?? 'not-needed',\n baseUrl: `${baseUrl}/v1`,\n defaultModel: process.env.MASAR_SUBAGENT_MODEL ?? 'subagent',\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_HAIKU_4_5: 'claude-haiku-4-5',\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 private reasoningEffort: string | undefined;\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 = options.providerConfig ?? PROVIDER_CONFIGS[this.provider]();\n // An explicit providerConfig (e.g. from the CLI's ~/.almadar account, which\n // stores only apiKey) may omit baseUrl. Back-fill the PROVIDER's default so\n // the request isn't sent to api.openai.com — the fallback that createModel()\n // and callWithTools() use when baseUrl is undefined. Env-mode already sets\n // the right baseUrl via PROVIDER_CONFIGS, and openai/anthropic correctly\n // stay undefined (no entry in PROVIDER_BASE_URLS).\n const defaultBaseUrl = PROVIDER_BASE_URLS[this.provider];\n if (this.providerConfig.baseUrl === undefined && defaultBaseUrl) {\n this.providerConfig = { ...this.providerConfig, baseUrl: defaultBaseUrl };\n }\n this.modelName = options.model || this.providerConfig.defaultModel;\n this.rawTimeoutMs = options.rawTimeoutMs ?? LLMClient.DEFAULT_RAW_TIMEOUT_MS;\n this.reasoningEffort = options.reasoningEffort;\n\n const keyPreview = this.providerConfig.apiKey.slice(-4);\n log.info(\n `[LLMClient] Provider: ${this.provider}, Model: ${this.modelName}, Key: ****${keyPreview}`,\n );\n if (this.providerConfig.baseUrl) {\n log.info(\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 streaming?: boolean;\n }): ChatModel {\n const maxTokens = options?.maxTokens;\n const temperature = options?.temperature ?? this.temperature;\n const streaming = options?.streaming ?? this.streaming;\n\n if (this.provider === 'anthropic') {\n return new ChatAnthropic({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature,\n 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 log.info(\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 log.info(\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 log.info(\n `[LLMClient:Anthropic] ${inputTokens} input, ${outputTokens} output tokens (likely cached)`,\n );\n } else {\n log.info(\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 // DeepSeek V4: disable reasoning when reasoningEffort is set\n if (this.reasoningEffort) {\n modelKwargs.reasoning_effort = this.reasoningEffort;\n }\n // OpenRouter: explicit tool_choice so the model doesn't ignore tool definitions.\n // When reasoning is disabled (reasoningEffort === 'none'), send\n // reasoning: { enabled: false } so OpenRouter doesn't burn tokens on CoT.\n if (this.provider === 'openrouter') {\n modelKwargs.tool_choice = 'auto';\n modelKwargs.usage = { include: true };\n if (this.reasoningEffort === 'none') {\n modelKwargs.reasoning = { enabled: false };\n }\n }\n\n return new ChatOpenAI({\n apiKey: this.providerConfig.apiKey,\n model: this.modelName,\n temperature: useCompletionTokens ? undefined : effectiveTemp,\n 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 streaming?: boolean;\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 log.info(\n `[LLMClient:call] Starting call to ${this.provider}/${this.modelName}`,\n );\n log.info(`[LLMClient:call] Prompt length: ${userPrompt.length} chars`);\n if (maxTokens) {\n log.info(`[LLMClient:call] Max tokens: ${maxTokens}`);\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n log.info(\n `[LLMClient:call] Attempt ${attempt + 1}/${maxRetries + 1}...`,\n );\n const attemptStartTime = Date.now();\n\n const result = await this.rateLimiter.execute(async () => {\n log.info(`[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 log.info(\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 log.info(\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, ...cacheTokensFromUsageMetadata(usageMeta) },\n );\n }\n }\n\n const finishReason = this.extractFinishReason(response);\n if (finishReason === 'length') {\n log.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 log.info(\n `[LLMClient:call] Response length: ${content.length} chars, finish_reason: ${finishReason}`,\n );\n\n return { content, finishReason, usage };\n });\n\n log.info(\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 log.info(\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 log.error(\n `[LLMClient:call] Attempt ${attempt + 1} failed: ${lastError.message}`,\n );\n\n if (this.isRateLimitError(lastError)) {\n log.error(`[LLMClient:call] Rate limit error, not retrying`);\n throw lastError;\n }\n\n if (attempt < maxRetries && retryWithContext) {\n log.info(`[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 log.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 log.info(\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 log.info(\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 log.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 { provider: this.provider, ...cacheTokensFromUsageMetadata(usageMeta) },\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 { provider: this.provider, ...cacheTokensFromUsageMetadata(usageMeta) },\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 /** Per-call deadline (ms). Defaults to `rawTimeoutMs` (5 min unless the\n * client was constructed with a lower value). Bounds the fetch so a\n * hung connection can't run past the caller's own wall-clock cap. */\n timeoutMs?: number;\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 // Return authoritative usage accounting (real cost + cached-token split).\n body['usage'] = { include: true };\n }\n\n const startedAt = Date.now();\n log.info(\n `[LLMClient:callWithTools] Invoking ${this.provider}/${this.modelName} (tools=${options.tools.length}, messages=${options.messages.length})`,\n );\n\n // Per-call deadline. callWithTools previously honored only a\n // caller-supplied signal, so when runToolLoop passed none a hung\n // connection ran until the caller's external wall-clock cap (e.g. a\n // 300s battery race), with the subagent producing zero metrics. Drive\n // an AbortController off `timeoutMs ?? rawTimeoutMs` and merge the\n // caller's signal so a hang aborts in-process, fast.\n const effectiveTimeoutMs = options.timeoutMs ?? this.rawTimeoutMs;\n const controller = new AbortController();\n const timeoutHandle = setTimeout(\n () => controller.abort(new Error(`LLMClient.callWithTools timed out after ${effectiveTimeoutMs}ms`)),\n effectiveTimeoutMs,\n );\n const callerSignal = options.signal;\n if (callerSignal) {\n if (callerSignal.aborted) controller.abort(callerSignal.reason);\n else callerSignal.addEventListener('abort', () => controller.abort(callerSignal.reason), { once: true });\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 signal: controller.signal,\n };\n try {\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 log.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 const rawUsage = parsed.usage as {\n prompt_tokens_details?: { cached_tokens?: number };\n prompt_cache_hit_tokens?: number;\n cost?: number;\n };\n const cachedTokens =\n rawUsage.prompt_tokens_details?.cached_tokens ?? rawUsage.prompt_cache_hit_tokens;\n usage = {\n promptTokens: parsed.usage.prompt_tokens,\n completionTokens: parsed.usage.completion_tokens,\n totalTokens: parsed.usage.total_tokens,\n ...(cachedTokens !== undefined ? { cachedPromptTokens: cachedTokens } : {}),\n };\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(usage.promptTokens, usage.completionTokens, {\n provider: this.provider,\n cachedPromptTokens: cachedTokens ?? 0,\n // OpenRouter returns the real, routing+cache-adjusted charge here.\n ...(typeof rawUsage.cost === 'number' ? { costUSD: rawUsage.cost } : {}),\n });\n }\n }\n\n log.info(\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 } finally {\n clearTimeout(timeoutHandle);\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 // streamRaw must always stream: force streaming on the model regardless of the client's\n // default `streaming` flag, or LangChain's .stream() degrades to a single whole-response chunk.\n const modelToUse = this.getModelWithOptions({ maxTokens, temperature, streaming: true });\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 log.info(\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 log.info(\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 log.info(\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 log.info(\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 log.info(\n `[LLMClient:callWithCache] Cache HIT: ${cacheRead} tokens (~${savingsPercent}% of prompt)`,\n );\n }\n if (cacheCreation === 0 && cacheRead === 0) {\n log.info(\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 // Anthropic reports input_tokens as the UNCACHED count; cache reads\n // and writes are separate. Pass the true total so the tracker prices\n // each bucket at its own rate.\n this.tokenTracker.addUsage(\n apiUsage.input_tokens + cacheRead + cacheCreation,\n usage.completionTokens,\n { provider: this.provider, cachedPromptTokens: cacheRead, cacheWriteTokens: cacheCreation },\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 log.error(\n `[LLMClient:callWithCache] Attempt ${attempt + 1} failed: ${lastError.message}`,\n );\n\n if (this.isRateLimitError(lastError)) {\n throw lastError;\n }\n }\n }\n\n throw lastError;\n }\n\n /**\n * Send one or more images (base64 content-parts) plus a text prompt to a\n * vision-capable Anthropic model and return the response. When a `schema`\n * is supplied the text output is parsed and validated through the package's\n * `parseJsonResponse` mechanism; otherwise the raw text is returned as `T`.\n *\n * Anthropic-only — image content-parts use the native messages API. Other\n * providers throw (mirrors `callWithTools`).\n */\n async callWithVision<T = string>(\n options: VisionCallOptions<T>,\n ): Promise<LLMResponse<T>> {\n if (this.provider !== 'anthropic') {\n throw new Error(\n `LLMClient.callWithVision: vision is only supported on the anthropic provider (got ${this.provider})`,\n );\n }\n\n const {\n systemPrompt,\n userText,\n images,\n schema,\n maxTokens,\n temperature,\n skipSchemaValidation = false,\n signal,\n } = options;\n\n if (images.length === 0) {\n throw new Error('LLMClient.callWithVision: at least one image is required');\n }\n\n return this.rateLimiter.execute(async () => {\n const anthropic = new Anthropic({ apiKey: this.providerConfig.apiKey });\n const content = buildVisionMessageContent(userText, images);\n const messages: Anthropic.MessageParam[] = [{ role: 'user', content }];\n\n log.info(\n `[LLMClient:callWithVision] Invoking ${this.provider}/${this.modelName} (images=${images.length})`,\n );\n\n const response = await anthropic.messages.create(\n {\n model: this.modelName,\n max_tokens: maxTokens ?? 4096,\n temperature: temperature ?? 0,\n ...(systemPrompt ? { system: systemPrompt } : {}),\n messages,\n },\n signal ? { signal } : {},\n );\n\n const textContent = response.content.find((c) => c.type === 'text');\n const rawText =\n textContent && 'text' in textContent ? textContent.text : '';\n\n const apiUsage = response.usage;\n const usage: LLMUsage = {\n promptTokens: apiUsage.input_tokens,\n completionTokens: apiUsage.output_tokens,\n totalTokens: apiUsage.input_tokens + apiUsage.output_tokens,\n };\n if (this.tokenTracker) {\n this.tokenTracker.addUsage(usage.promptTokens, usage.completionTokens, {\n provider: this.provider,\n });\n }\n\n const finishReason: LLMFinishReason =\n response.stop_reason === 'end_turn'\n ? 'stop'\n : response.stop_reason === 'max_tokens'\n ? 'length'\n : response.stop_reason === 'tool_use'\n ? 'tool_calls'\n : null;\n\n const data: T = skipSchemaValidation\n ? parseJsonResponse<T>(rawText, undefined)\n : schema\n ? parseJsonResponse<T>(rawText, schema)\n : asGeneric<T>(rawText);\n\n return { data, raw: rawText, finishReason, usage };\n });\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 case 'masar':\n return !!process.env.MASAR_SUBAGENT_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":[]}