@almadar/llm 2.37.0 → 2.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EELADMF4.js → chunk-KVRUGEMY.js} +5 -1
- package/dist/chunk-KVRUGEMY.js.map +1 -0
- package/dist/{client-9G5ND9Uk.d.ts → client-CXZebrpn.d.ts} +6 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/vector/index.js +6 -1
- package/dist/vector/index.js.map +1 -1
- package/package.json +5 -5
- package/src/client.ts +13 -0
- package/src/vector/store.ts +8 -1
- package/dist/chunk-EELADMF4.js.map +0 -1
|
@@ -251,6 +251,7 @@ var _LLMClient = class _LLMClient {
|
|
|
251
251
|
}
|
|
252
252
|
this.modelName = options.model || this.providerConfig.defaultModel;
|
|
253
253
|
this.rawTimeoutMs = options.rawTimeoutMs ?? _LLMClient.DEFAULT_RAW_TIMEOUT_MS;
|
|
254
|
+
this.reasoningEffort = options.reasoningEffort;
|
|
254
255
|
const keyPreview = this.providerConfig.apiKey.slice(-4);
|
|
255
256
|
log.info(
|
|
256
257
|
`[LLMClient] Provider: ${this.provider}, Model: ${this.modelName}, Key: ****${keyPreview}`
|
|
@@ -332,6 +333,9 @@ var _LLMClient = class _LLMClient {
|
|
|
332
333
|
if (isKimi) {
|
|
333
334
|
modelKwargs.thinking = { type: "disabled" };
|
|
334
335
|
}
|
|
336
|
+
if (this.reasoningEffort) {
|
|
337
|
+
modelKwargs.reasoning_effort = this.reasoningEffort;
|
|
338
|
+
}
|
|
335
339
|
if (this.provider === "openrouter") {
|
|
336
340
|
modelKwargs.tool_choice = "auto";
|
|
337
341
|
modelKwargs.usage = { include: true };
|
|
@@ -1125,4 +1129,4 @@ export {
|
|
|
1125
1129
|
createOpenRouterClient,
|
|
1126
1130
|
createZhipuClient
|
|
1127
1131
|
};
|
|
1128
|
-
//# sourceMappingURL=chunk-
|
|
1132
|
+
//# sourceMappingURL=chunk-KVRUGEMY.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}\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 (Qwen): explicit tool_choice so the model doesn't ignore tool definitions\n if (this.provider === 'openrouter') {\n modelKwargs.tool_choice = 'auto';\n // Return the cached-token breakdown so cost is priced cache-aware.\n modelKwargs.usage = { include: true };\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":[]}
|
|
@@ -82,6 +82,11 @@ interface LLMClientOptions {
|
|
|
82
82
|
rateLimiter?: RateLimiterOptions;
|
|
83
83
|
useGlobalRateLimiter?: boolean;
|
|
84
84
|
trackTokens?: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Reasoning effort for thinking models (e.g. DeepSeek V4).
|
|
87
|
+
* Set to "none" to disable reasoning tokens for faster content generation.
|
|
88
|
+
*/
|
|
89
|
+
reasoningEffort?: string;
|
|
85
90
|
/**
|
|
86
91
|
* Hard per-call deadline (ms) for the raw invoke paths (`callRaw`,
|
|
87
92
|
* `callRawWithMetadata`, `callWithMessages`). Defaults to 5 min. Set this
|
|
@@ -225,6 +230,7 @@ declare class LLMClient {
|
|
|
225
230
|
private temperature;
|
|
226
231
|
private streaming;
|
|
227
232
|
private rawTimeoutMs;
|
|
233
|
+
private reasoningEffort;
|
|
228
234
|
constructor(options?: LLMClientOptions);
|
|
229
235
|
private usesMaxCompletionTokens;
|
|
230
236
|
private createModel;
|
package/dist/client.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ 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-
|
|
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';
|
|
7
7
|
import '@almadar/core';
|
package/dist/client.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LLMFinishReason, a as LLMClient } from './client-
|
|
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-
|
|
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
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';
|
|
4
4
|
export { autoCloseJson, extractJsonFromText, isValidJson, parseJsonResponse, safeParseJson } from './json-parser.js';
|
|
5
5
|
import { z } from 'zod';
|
package/dist/index.js
CHANGED
package/dist/vector/index.js
CHANGED
|
@@ -258,12 +258,17 @@ function buildVectorStoreHandle(client, target) {
|
|
|
258
258
|
};
|
|
259
259
|
}
|
|
260
260
|
var vectorStoreCache = /* @__PURE__ */ new Map();
|
|
261
|
+
var emptyConfigSkipLogged = false;
|
|
261
262
|
function resetVectorStoreCache() {
|
|
262
263
|
vectorStoreCache.clear();
|
|
264
|
+
emptyConfigSkipLogged = false;
|
|
263
265
|
}
|
|
264
266
|
async function connectVectorStore(config) {
|
|
265
267
|
if (isEmptyConfig(config)) {
|
|
266
|
-
|
|
268
|
+
if (!emptyConfigSkipLogged) {
|
|
269
|
+
emptyConfigSkipLogged = true;
|
|
270
|
+
vectorLog.debug("store:skip", () => ({ reason: "config-empty" }));
|
|
271
|
+
}
|
|
267
272
|
return null;
|
|
268
273
|
}
|
|
269
274
|
const key = configCacheKey(config);
|
package/dist/vector/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vector/env.ts","../../src/vector/store.ts","../../src/vector/shape.ts","../../src/vector/collection.ts"],"sourcesContent":["/**\n * Vector-store env resolution + host parsing — pure seams, no chromadb import.\n */\n\nexport interface VectorStoreConfig {\n host?: string;\n apiKey?: string;\n tenant?: string;\n database?: string;\n}\n\nfunction cleanEnvValue(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nexport function resolveVectorStoreEnv(): VectorStoreConfig {\n const env = process.env;\n return {\n host: cleanEnvValue(env.ALMADAR_CHROMA_HOST),\n apiKey: cleanEnvValue(env.ALMADAR_CHROMA_API_KEY),\n tenant: cleanEnvValue(env.ALMADAR_CHROMA_TENANT),\n database: cleanEnvValue(env.ALMADAR_CHROMA_DATABASE),\n };\n}\n\nexport interface VectorHostInit {\n host?: string;\n port?: number;\n ssl?: boolean;\n}\n\n/** Parse a bare host or full URL into chromadb client args: bare host ⇒ port 8000, https default 443. */\nexport function parseVectorHost(host: string): VectorHostInit {\n const init: VectorHostInit = {};\n try {\n const url = new URL(host.includes('://') ? host : `http://${host}`);\n init.host = url.hostname;\n init.port = url.port ? parseInt(url.port, 10) : url.protocol === 'https:' ? 443 : 8000;\n init.ssl = url.protocol === 'https:';\n } catch {\n init.host = host;\n }\n return init;\n}\n","/**\n * Vector-store connection + collection resolution: env → chromadb client\n * (cached per process, per distinct config), plus the space-mismatch policy\n * that guards a `collection()` call from silently ranking/writing under the\n * wrong index space.\n */\nimport type * as Chroma from 'chromadb';\nimport { createLogger } from '@almadar/logger';\nimport type { VectorStoreConfig } from './env.js';\nimport { parseVectorHost } from './env.js';\nimport { wrapVectorCollection, type VectorCollection } from './collection.js';\n\nconst vectorLog = createLogger('almadar:llm:vector');\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport type VectorSpace = 'cosine' | 'l2' | 'ip';\n\nexport interface VectorCollectionSpec {\n name: string;\n space?: VectorSpace;\n metadata?: Chroma.CollectionMetadata;\n /** 'recreate' drops + rebuilds a wrong-space collection (derived/rebuildable data only). Default 'reject' never auto-drops a consumer's data. */\n onSpaceMismatch?: 'recreate' | 'reject';\n}\n\nexport interface VectorStoreHandle {\n /** Human-readable connection target, for logs. */\n readonly target: string;\n collection(spec: VectorCollectionSpec): Promise<VectorCollection | null>;\n getCollection(name: string): Promise<VectorCollection | null>;\n deleteCollection(name: string): Promise<boolean>;\n heartbeat(): Promise<boolean>;\n}\n\nconst stubEmbeddingFunction: Chroma.EmbeddingFunction = {\n // Callers always supply pre-computed vectors; the stub stops chromadb from\n // instantiating its own DefaultEmbeddingFunction.\n generate: async () => [],\n};\n\nconst DEFAULT_ON_SPACE_MISMATCH = 'reject';\n\nfunction isEmptyConfig(config: VectorStoreConfig): boolean {\n return !config.host && !config.apiKey;\n}\n\nfunction configCacheKey(config: VectorStoreConfig): string {\n return JSON.stringify([config.host ?? null, config.apiKey ?? null, config.tenant ?? null, config.database ?? null]);\n}\n\nasync function loadChromaModule(): Promise<typeof import('chromadb') | null> {\n try {\n return await import('chromadb');\n } catch (err) {\n vectorLog.warn('store:module-missing', () => ({\n error: errorMessage(err),\n hint: 'add chromadb to dependencies and run pnpm install',\n }));\n return null;\n }\n}\n\nasync function resolveCollection(\n client: Chroma.ChromaClient,\n spec: VectorCollectionSpec,\n): Promise<VectorCollection | null> {\n const create = (): Promise<Chroma.Collection> =>\n client.getOrCreateCollection({\n name: spec.name,\n embeddingFunction: stubEmbeddingFunction,\n ...(spec.space ? { configuration: { hnsw: { space: spec.space } } } : {}),\n ...(spec.metadata ? { metadata: spec.metadata } : {}),\n });\n\n let raw: Chroma.Collection;\n try {\n raw = await create();\n } catch (err) {\n vectorLog.warn('collection:create-failed', () => ({ name: spec.name, error: errorMessage(err) }));\n return null;\n }\n\n // spec.space omitted ⇒ no verification, server default on create.\n if (!spec.space) return wrapVectorCollection(raw);\n\n const foundSpace = raw.configuration.hnsw?.space ?? null;\n if (foundSpace === spec.space) return wrapVectorCollection(raw);\n\n const policy = spec.onSpaceMismatch ?? DEFAULT_ON_SPACE_MISMATCH;\n if (policy === 'reject') {\n vectorLog.warn('collection:space-mismatch', () => ({\n name: spec.name,\n declaredSpace: spec.space,\n foundSpace,\n policy,\n }));\n return null;\n }\n\n vectorLog.warn('collection:space-heal', () => ({ name: spec.name, declaredSpace: spec.space, foundSpace }));\n await client.deleteCollection({ name: spec.name });\n try {\n raw = await create();\n } catch (err) {\n vectorLog.warn('collection:create-failed', () => ({ name: spec.name, error: errorMessage(err) }));\n return null;\n }\n return wrapVectorCollection(raw);\n}\n\nfunction buildVectorStoreHandle(client: Chroma.ChromaClient, target: string): VectorStoreHandle {\n return {\n target,\n collection(spec) {\n return resolveCollection(client, spec);\n },\n async getCollection(name) {\n try {\n const raw = await client.getCollection({ name, embeddingFunction: stubEmbeddingFunction });\n return wrapVectorCollection(raw);\n } catch (err) {\n vectorLog.debug('collection:get-missing', () => ({ name, error: errorMessage(err) }));\n return null;\n }\n },\n async deleteCollection(name) {\n try {\n await client.deleteCollection({ name });\n return true;\n } catch (err) {\n vectorLog.warn('collection:delete-failed', () => ({ name, error: errorMessage(err) }));\n return false;\n }\n },\n async heartbeat() {\n try {\n await client.heartbeat();\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n\nconst vectorStoreCache = new Map<string, VectorStoreHandle>();\n\n/** Test-only reset — clears the per-process handle cache between cases. */\nexport function resetVectorStoreCache(): void {\n vectorStoreCache.clear();\n}\n\nexport async function connectVectorStore(config: VectorStoreConfig): Promise<VectorStoreHandle | null> {\n if (isEmptyConfig(config)) {\n vectorLog.debug('store:skip', () => ({ reason: 'config-empty' }));\n return null;\n }\n\n const key = configCacheKey(config);\n const cached = vectorStoreCache.get(key);\n if (cached) return cached;\n\n const mod = await loadChromaModule();\n if (!mod) return null;\n\n let client: Chroma.ChromaClient;\n let target: string;\n if (config.apiKey) {\n client = new mod.CloudClient({ apiKey: config.apiKey, tenant: config.tenant, database: config.database });\n target = `cloud(tenant=${config.tenant ?? 'default'}, db=${config.database ?? 'default'})`;\n } else if (config.host) {\n client = new mod.ChromaClient(parseVectorHost(config.host));\n target = config.host;\n } else {\n // unreachable: isEmptyConfig already guards host/apiKey both absent.\n return null;\n }\n\n try {\n await client.heartbeat();\n } catch (err) {\n vectorLog.warn('store:heartbeat-failed', () => ({ target, error: errorMessage(err) }));\n return null;\n }\n\n const handle = buildVectorStoreHandle(client, target);\n vectorStoreCache.set(key, handle);\n vectorLog.info('store:ready', () => ({ target }));\n return handle;\n}\n","/**\n * Query-result shaping — pure seams, no chromadb import (types only, erased).\n */\nimport type * as Chroma from 'chromadb';\n\nexport interface VectorHit {\n id: string;\n distance: number;\n score: number;\n metadata?: Chroma.Metadata;\n document?: string;\n}\n\nconst DEFAULT_QUERY_TOP_K = 10;\n\nexport function normalizeVectorTopK(topK: number | undefined): number {\n if (typeof topK !== 'number' || !Number.isFinite(topK) || topK <= 0) return DEFAULT_QUERY_TOP_K;\n return Math.floor(topK);\n}\n\nfunction clamp01(value: number): number {\n if (value < 0) return 0;\n if (value > 1) return 1;\n return value;\n}\n\n/** Structural subset of chromadb's `QueryResult` this module reads (pure-shaping seam). */\nexport interface VectorQueryResultLike {\n readonly ids: ReadonlyArray<ReadonlyArray<string>>;\n readonly distances: ReadonlyArray<ReadonlyArray<number | null>>;\n readonly metadatas?: ReadonlyArray<ReadonlyArray<Chroma.Metadata | null>>;\n readonly documents?: ReadonlyArray<ReadonlyArray<string | null>>;\n}\n\nexport function shapeVectorHits(result: VectorQueryResultLike): VectorHit[] {\n const ids = result.ids[0] ?? [];\n const distances = result.distances[0] ?? [];\n const metadatas = result.metadatas?.[0] ?? [];\n const documents = result.documents?.[0] ?? [];\n const hits: VectorHit[] = [];\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n if (typeof id !== 'string') continue;\n const rawDistance = distances[i];\n // a missing/null distance is the worst case (no similarity signal) — treat as 1, same as rabit's shapeChromaMatches.\n const distance = typeof rawDistance === 'number' ? rawDistance : 1;\n const score = clamp01(1 - distance);\n const metadata = metadatas[i];\n const document = documents[i];\n hits.push({\n id,\n distance,\n score,\n ...(metadata ? { metadata } : {}),\n ...(typeof document === 'string' ? { document } : {}),\n });\n }\n hits.sort((a, b) => b.score - a.score);\n return hits;\n}\n","/**\n * VectorCollection — thin wrapper over a live chromadb `Collection`: batched\n * writes, query shaping, and space/metadata read-through. Mechanics only —\n * callers own retry/best-effort policy, so failures propagate as rejections\n * here (unlike the connect/collection-resolution seams, which never throw).\n */\nimport type * as Chroma from 'chromadb';\nimport { normalizeVectorTopK, shapeVectorHits, type VectorHit } from './shape.js';\n\nexport interface VectorRecords {\n ids: readonly string[];\n embeddings: readonly (readonly number[])[];\n documents?: readonly string[];\n metadatas?: readonly Chroma.Metadata[];\n}\n\nexport interface VectorQueryOptions {\n embedding: readonly number[];\n topK?: number;\n where?: Chroma.Where;\n includeDocuments?: boolean;\n includeMetadatas?: boolean;\n}\n\nexport interface VectorDeleteTarget {\n ids?: readonly string[];\n where?: Chroma.Where;\n}\n\nexport interface VectorGetOptions {\n where?: Chroma.Where;\n /** Default true. */\n includeMetadatas?: boolean;\n /** Default false — privacy default: raw node text is never returned unless explicitly asked for. */\n includeDocuments?: boolean;\n limit?: number;\n}\n\nexport interface VectorRecord {\n id: string;\n metadata?: Chroma.Metadata | undefined;\n document?: string | undefined;\n}\n\nexport interface VectorCollection {\n upsert(records: VectorRecords): Promise<number>;\n add(records: VectorRecords): Promise<number>;\n query(opts: VectorQueryOptions): Promise<VectorHit[]>;\n /** Fetch records by metadata filter (no embedding / no similarity). Defaults to metadatas-only. */\n get(opts?: VectorGetOptions): Promise<VectorRecord[]>;\n delete(target: VectorDeleteTarget): Promise<void>;\n count(): Promise<number>;\n /** Every id in the collection (paged get, vectors not fetched) — backs mirror-sync reconciliation. */\n listIds(): Promise<string[]>;\n readonly metadata: Chroma.CollectionMetadata | undefined;\n readonly space: string | null;\n}\n\nconst WRITE_BATCH_SIZE = 500;\n\ninterface WriteBatch {\n ids: string[];\n embeddings: number[][];\n documents?: string[];\n metadatas?: Chroma.Metadata[];\n}\n\nasync function writeInBatches(\n write: (batch: WriteBatch) => Promise<void>,\n records: VectorRecords,\n): Promise<number> {\n const { ids, embeddings, documents, metadatas } = records;\n let written = 0;\n for (let start = 0; start < ids.length; start += WRITE_BATCH_SIZE) {\n const end = Math.min(start + WRITE_BATCH_SIZE, ids.length);\n await write({\n ids: ids.slice(start, end),\n embeddings: embeddings.slice(start, end).map((vec) => [...vec]),\n ...(documents ? { documents: documents.slice(start, end) } : {}),\n ...(metadatas ? { metadatas: metadatas.slice(start, end) } : {}),\n });\n written += end - start;\n }\n return written;\n}\n\nexport function wrapVectorCollection(raw: Chroma.Collection): VectorCollection {\n return {\n upsert(records) {\n return writeInBatches((batch) => raw.upsert(batch), records);\n },\n add(records) {\n return writeInBatches((batch) => raw.add(batch), records);\n },\n async query(opts) {\n const include: Array<'distances' | 'metadatas' | 'documents'> = ['distances'];\n if (opts.includeMetadatas) include.push('metadatas');\n if (opts.includeDocuments) include.push('documents');\n const result = await raw.query({\n queryEmbeddings: [[...opts.embedding]],\n nResults: normalizeVectorTopK(opts.topK),\n where: opts.where,\n include,\n });\n return shapeVectorHits(result);\n },\n async delete(target) {\n await raw.delete({\n ...(target.ids ? { ids: [...target.ids] } : {}),\n ...(target.where ? { where: target.where } : {}),\n });\n },\n async get(opts = {}) {\n const include: Array<'metadatas' | 'documents'> = [];\n if (opts.includeMetadatas !== false) include.push('metadatas');\n if (opts.includeDocuments) include.push('documents');\n const records: VectorRecord[] = [];\n const PAGE = 10_000;\n const hardLimit = opts.limit ?? Number.MAX_SAFE_INTEGER;\n for (let offset = 0; offset < hardLimit; offset += PAGE) {\n const pageLimit = Math.min(PAGE, hardLimit - offset);\n const page = await raw.get({\n ...(opts.where ? { where: opts.where } : {}),\n include,\n limit: pageLimit,\n offset,\n });\n const wantMeta = include.includes('metadatas');\n const wantDoc = include.includes('documents');\n for (let i = 0; i < page.ids.length; i++) {\n records.push({\n id: page.ids[i],\n ...(wantMeta ? { metadata: page.metadatas?.[i] ?? undefined } : {}),\n ...(wantDoc ? { document: page.documents?.[i] ?? undefined } : {}),\n });\n }\n if (page.ids.length < pageLimit) break;\n }\n return records;\n },\n count() {\n return raw.count();\n },\n async listIds() {\n const ids: string[] = [];\n const PAGE = 10_000;\n for (let offset = 0; ; offset += PAGE) {\n const page = await raw.get({ include: [], limit: PAGE, offset });\n ids.push(...page.ids);\n if (page.ids.length < PAGE) break;\n }\n return ids;\n },\n get metadata() {\n return raw.metadata;\n },\n get space() {\n return raw.configuration.hnsw?.space ?? null;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/vector/env.ts","../../src/vector/store.ts","../../src/vector/shape.ts","../../src/vector/collection.ts"],"sourcesContent":["/**\n * Vector-store env resolution + host parsing — pure seams, no chromadb import.\n */\n\nexport interface VectorStoreConfig {\n host?: string;\n apiKey?: string;\n tenant?: string;\n database?: string;\n}\n\nfunction cleanEnvValue(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nexport function resolveVectorStoreEnv(): VectorStoreConfig {\n const env = process.env;\n return {\n host: cleanEnvValue(env.ALMADAR_CHROMA_HOST),\n apiKey: cleanEnvValue(env.ALMADAR_CHROMA_API_KEY),\n tenant: cleanEnvValue(env.ALMADAR_CHROMA_TENANT),\n database: cleanEnvValue(env.ALMADAR_CHROMA_DATABASE),\n };\n}\n\nexport interface VectorHostInit {\n host?: string;\n port?: number;\n ssl?: boolean;\n}\n\n/** Parse a bare host or full URL into chromadb client args: bare host ⇒ port 8000, https default 443. */\nexport function parseVectorHost(host: string): VectorHostInit {\n const init: VectorHostInit = {};\n try {\n const url = new URL(host.includes('://') ? host : `http://${host}`);\n init.host = url.hostname;\n init.port = url.port ? parseInt(url.port, 10) : url.protocol === 'https:' ? 443 : 8000;\n init.ssl = url.protocol === 'https:';\n } catch {\n init.host = host;\n }\n return init;\n}\n","/**\n * Vector-store connection + collection resolution: env → chromadb client\n * (cached per process, per distinct config), plus the space-mismatch policy\n * that guards a `collection()` call from silently ranking/writing under the\n * wrong index space.\n */\nimport type * as Chroma from 'chromadb';\nimport { createLogger } from '@almadar/logger';\nimport type { VectorStoreConfig } from './env.js';\nimport { parseVectorHost } from './env.js';\nimport { wrapVectorCollection, type VectorCollection } from './collection.js';\n\nconst vectorLog = createLogger('almadar:llm:vector');\n\nfunction errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport type VectorSpace = 'cosine' | 'l2' | 'ip';\n\nexport interface VectorCollectionSpec {\n name: string;\n space?: VectorSpace;\n metadata?: Chroma.CollectionMetadata;\n /** 'recreate' drops + rebuilds a wrong-space collection (derived/rebuildable data only). Default 'reject' never auto-drops a consumer's data. */\n onSpaceMismatch?: 'recreate' | 'reject';\n}\n\nexport interface VectorStoreHandle {\n /** Human-readable connection target, for logs. */\n readonly target: string;\n collection(spec: VectorCollectionSpec): Promise<VectorCollection | null>;\n getCollection(name: string): Promise<VectorCollection | null>;\n deleteCollection(name: string): Promise<boolean>;\n heartbeat(): Promise<boolean>;\n}\n\nconst stubEmbeddingFunction: Chroma.EmbeddingFunction = {\n // Callers always supply pre-computed vectors; the stub stops chromadb from\n // instantiating its own DefaultEmbeddingFunction.\n generate: async () => [],\n};\n\nconst DEFAULT_ON_SPACE_MISMATCH = 'reject';\n\nfunction isEmptyConfig(config: VectorStoreConfig): boolean {\n return !config.host && !config.apiKey;\n}\n\nfunction configCacheKey(config: VectorStoreConfig): string {\n return JSON.stringify([config.host ?? null, config.apiKey ?? null, config.tenant ?? null, config.database ?? null]);\n}\n\nasync function loadChromaModule(): Promise<typeof import('chromadb') | null> {\n try {\n return await import('chromadb');\n } catch (err) {\n vectorLog.warn('store:module-missing', () => ({\n error: errorMessage(err),\n hint: 'add chromadb to dependencies and run pnpm install',\n }));\n return null;\n }\n}\n\nasync function resolveCollection(\n client: Chroma.ChromaClient,\n spec: VectorCollectionSpec,\n): Promise<VectorCollection | null> {\n const create = (): Promise<Chroma.Collection> =>\n client.getOrCreateCollection({\n name: spec.name,\n embeddingFunction: stubEmbeddingFunction,\n ...(spec.space ? { configuration: { hnsw: { space: spec.space } } } : {}),\n ...(spec.metadata ? { metadata: spec.metadata } : {}),\n });\n\n let raw: Chroma.Collection;\n try {\n raw = await create();\n } catch (err) {\n vectorLog.warn('collection:create-failed', () => ({ name: spec.name, error: errorMessage(err) }));\n return null;\n }\n\n // spec.space omitted ⇒ no verification, server default on create.\n if (!spec.space) return wrapVectorCollection(raw);\n\n const foundSpace = raw.configuration.hnsw?.space ?? null;\n if (foundSpace === spec.space) return wrapVectorCollection(raw);\n\n const policy = spec.onSpaceMismatch ?? DEFAULT_ON_SPACE_MISMATCH;\n if (policy === 'reject') {\n vectorLog.warn('collection:space-mismatch', () => ({\n name: spec.name,\n declaredSpace: spec.space,\n foundSpace,\n policy,\n }));\n return null;\n }\n\n vectorLog.warn('collection:space-heal', () => ({ name: spec.name, declaredSpace: spec.space, foundSpace }));\n await client.deleteCollection({ name: spec.name });\n try {\n raw = await create();\n } catch (err) {\n vectorLog.warn('collection:create-failed', () => ({ name: spec.name, error: errorMessage(err) }));\n return null;\n }\n return wrapVectorCollection(raw);\n}\n\nfunction buildVectorStoreHandle(client: Chroma.ChromaClient, target: string): VectorStoreHandle {\n return {\n target,\n collection(spec) {\n return resolveCollection(client, spec);\n },\n async getCollection(name) {\n try {\n const raw = await client.getCollection({ name, embeddingFunction: stubEmbeddingFunction });\n return wrapVectorCollection(raw);\n } catch (err) {\n vectorLog.debug('collection:get-missing', () => ({ name, error: errorMessage(err) }));\n return null;\n }\n },\n async deleteCollection(name) {\n try {\n await client.deleteCollection({ name });\n return true;\n } catch (err) {\n vectorLog.warn('collection:delete-failed', () => ({ name, error: errorMessage(err) }));\n return false;\n }\n },\n async heartbeat() {\n try {\n await client.heartbeat();\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n\nconst vectorStoreCache = new Map<string, VectorStoreHandle>();\n\n/** The empty-config skip is a per-process fact — log it once, not per retrieval. */\nlet emptyConfigSkipLogged = false;\n\n/** Test-only reset — clears the per-process handle cache between cases. */\nexport function resetVectorStoreCache(): void {\n vectorStoreCache.clear();\n emptyConfigSkipLogged = false;\n}\n\nexport async function connectVectorStore(config: VectorStoreConfig): Promise<VectorStoreHandle | null> {\n if (isEmptyConfig(config)) {\n if (!emptyConfigSkipLogged) {\n emptyConfigSkipLogged = true;\n vectorLog.debug('store:skip', () => ({ reason: 'config-empty' }));\n }\n return null;\n }\n\n const key = configCacheKey(config);\n const cached = vectorStoreCache.get(key);\n if (cached) return cached;\n\n const mod = await loadChromaModule();\n if (!mod) return null;\n\n let client: Chroma.ChromaClient;\n let target: string;\n if (config.apiKey) {\n client = new mod.CloudClient({ apiKey: config.apiKey, tenant: config.tenant, database: config.database });\n target = `cloud(tenant=${config.tenant ?? 'default'}, db=${config.database ?? 'default'})`;\n } else if (config.host) {\n client = new mod.ChromaClient(parseVectorHost(config.host));\n target = config.host;\n } else {\n // unreachable: isEmptyConfig already guards host/apiKey both absent.\n return null;\n }\n\n try {\n await client.heartbeat();\n } catch (err) {\n vectorLog.warn('store:heartbeat-failed', () => ({ target, error: errorMessage(err) }));\n return null;\n }\n\n const handle = buildVectorStoreHandle(client, target);\n vectorStoreCache.set(key, handle);\n vectorLog.info('store:ready', () => ({ target }));\n return handle;\n}\n","/**\n * Query-result shaping — pure seams, no chromadb import (types only, erased).\n */\nimport type * as Chroma from 'chromadb';\n\nexport interface VectorHit {\n id: string;\n distance: number;\n score: number;\n metadata?: Chroma.Metadata;\n document?: string;\n}\n\nconst DEFAULT_QUERY_TOP_K = 10;\n\nexport function normalizeVectorTopK(topK: number | undefined): number {\n if (typeof topK !== 'number' || !Number.isFinite(topK) || topK <= 0) return DEFAULT_QUERY_TOP_K;\n return Math.floor(topK);\n}\n\nfunction clamp01(value: number): number {\n if (value < 0) return 0;\n if (value > 1) return 1;\n return value;\n}\n\n/** Structural subset of chromadb's `QueryResult` this module reads (pure-shaping seam). */\nexport interface VectorQueryResultLike {\n readonly ids: ReadonlyArray<ReadonlyArray<string>>;\n readonly distances: ReadonlyArray<ReadonlyArray<number | null>>;\n readonly metadatas?: ReadonlyArray<ReadonlyArray<Chroma.Metadata | null>>;\n readonly documents?: ReadonlyArray<ReadonlyArray<string | null>>;\n}\n\nexport function shapeVectorHits(result: VectorQueryResultLike): VectorHit[] {\n const ids = result.ids[0] ?? [];\n const distances = result.distances[0] ?? [];\n const metadatas = result.metadatas?.[0] ?? [];\n const documents = result.documents?.[0] ?? [];\n const hits: VectorHit[] = [];\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n if (typeof id !== 'string') continue;\n const rawDistance = distances[i];\n // a missing/null distance is the worst case (no similarity signal) — treat as 1, same as rabit's shapeChromaMatches.\n const distance = typeof rawDistance === 'number' ? rawDistance : 1;\n const score = clamp01(1 - distance);\n const metadata = metadatas[i];\n const document = documents[i];\n hits.push({\n id,\n distance,\n score,\n ...(metadata ? { metadata } : {}),\n ...(typeof document === 'string' ? { document } : {}),\n });\n }\n hits.sort((a, b) => b.score - a.score);\n return hits;\n}\n","/**\n * VectorCollection — thin wrapper over a live chromadb `Collection`: batched\n * writes, query shaping, and space/metadata read-through. Mechanics only —\n * callers own retry/best-effort policy, so failures propagate as rejections\n * here (unlike the connect/collection-resolution seams, which never throw).\n */\nimport type * as Chroma from 'chromadb';\nimport { normalizeVectorTopK, shapeVectorHits, type VectorHit } from './shape.js';\n\nexport interface VectorRecords {\n ids: readonly string[];\n embeddings: readonly (readonly number[])[];\n documents?: readonly string[];\n metadatas?: readonly Chroma.Metadata[];\n}\n\nexport interface VectorQueryOptions {\n embedding: readonly number[];\n topK?: number;\n where?: Chroma.Where;\n includeDocuments?: boolean;\n includeMetadatas?: boolean;\n}\n\nexport interface VectorDeleteTarget {\n ids?: readonly string[];\n where?: Chroma.Where;\n}\n\nexport interface VectorGetOptions {\n where?: Chroma.Where;\n /** Default true. */\n includeMetadatas?: boolean;\n /** Default false — privacy default: raw node text is never returned unless explicitly asked for. */\n includeDocuments?: boolean;\n limit?: number;\n}\n\nexport interface VectorRecord {\n id: string;\n metadata?: Chroma.Metadata | undefined;\n document?: string | undefined;\n}\n\nexport interface VectorCollection {\n upsert(records: VectorRecords): Promise<number>;\n add(records: VectorRecords): Promise<number>;\n query(opts: VectorQueryOptions): Promise<VectorHit[]>;\n /** Fetch records by metadata filter (no embedding / no similarity). Defaults to metadatas-only. */\n get(opts?: VectorGetOptions): Promise<VectorRecord[]>;\n delete(target: VectorDeleteTarget): Promise<void>;\n count(): Promise<number>;\n /** Every id in the collection (paged get, vectors not fetched) — backs mirror-sync reconciliation. */\n listIds(): Promise<string[]>;\n readonly metadata: Chroma.CollectionMetadata | undefined;\n readonly space: string | null;\n}\n\nconst WRITE_BATCH_SIZE = 500;\n\ninterface WriteBatch {\n ids: string[];\n embeddings: number[][];\n documents?: string[];\n metadatas?: Chroma.Metadata[];\n}\n\nasync function writeInBatches(\n write: (batch: WriteBatch) => Promise<void>,\n records: VectorRecords,\n): Promise<number> {\n const { ids, embeddings, documents, metadatas } = records;\n let written = 0;\n for (let start = 0; start < ids.length; start += WRITE_BATCH_SIZE) {\n const end = Math.min(start + WRITE_BATCH_SIZE, ids.length);\n await write({\n ids: ids.slice(start, end),\n embeddings: embeddings.slice(start, end).map((vec) => [...vec]),\n ...(documents ? { documents: documents.slice(start, end) } : {}),\n ...(metadatas ? { metadatas: metadatas.slice(start, end) } : {}),\n });\n written += end - start;\n }\n return written;\n}\n\nexport function wrapVectorCollection(raw: Chroma.Collection): VectorCollection {\n return {\n upsert(records) {\n return writeInBatches((batch) => raw.upsert(batch), records);\n },\n add(records) {\n return writeInBatches((batch) => raw.add(batch), records);\n },\n async query(opts) {\n const include: Array<'distances' | 'metadatas' | 'documents'> = ['distances'];\n if (opts.includeMetadatas) include.push('metadatas');\n if (opts.includeDocuments) include.push('documents');\n const result = await raw.query({\n queryEmbeddings: [[...opts.embedding]],\n nResults: normalizeVectorTopK(opts.topK),\n where: opts.where,\n include,\n });\n return shapeVectorHits(result);\n },\n async delete(target) {\n await raw.delete({\n ...(target.ids ? { ids: [...target.ids] } : {}),\n ...(target.where ? { where: target.where } : {}),\n });\n },\n async get(opts = {}) {\n const include: Array<'metadatas' | 'documents'> = [];\n if (opts.includeMetadatas !== false) include.push('metadatas');\n if (opts.includeDocuments) include.push('documents');\n const records: VectorRecord[] = [];\n const PAGE = 10_000;\n const hardLimit = opts.limit ?? Number.MAX_SAFE_INTEGER;\n for (let offset = 0; offset < hardLimit; offset += PAGE) {\n const pageLimit = Math.min(PAGE, hardLimit - offset);\n const page = await raw.get({\n ...(opts.where ? { where: opts.where } : {}),\n include,\n limit: pageLimit,\n offset,\n });\n const wantMeta = include.includes('metadatas');\n const wantDoc = include.includes('documents');\n for (let i = 0; i < page.ids.length; i++) {\n records.push({\n id: page.ids[i],\n ...(wantMeta ? { metadata: page.metadatas?.[i] ?? undefined } : {}),\n ...(wantDoc ? { document: page.documents?.[i] ?? undefined } : {}),\n });\n }\n if (page.ids.length < pageLimit) break;\n }\n return records;\n },\n count() {\n return raw.count();\n },\n async listIds() {\n const ids: string[] = [];\n const PAGE = 10_000;\n for (let offset = 0; ; offset += PAGE) {\n const page = await raw.get({ include: [], limit: PAGE, offset });\n ids.push(...page.ids);\n if (page.ids.length < PAGE) break;\n }\n return ids;\n },\n get metadata() {\n return raw.metadata;\n },\n get space() {\n return raw.configuration.hnsw?.space ?? null;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almadar/llm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.39.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",
|
|
@@ -43,16 +43,16 @@
|
|
|
43
43
|
"chromadb": "^3.1.0",
|
|
44
44
|
"openai": "^6.18.0",
|
|
45
45
|
"zod": "^3.22.0",
|
|
46
|
-
"@almadar/logger": "^1.
|
|
46
|
+
"@almadar/logger": "^1.11.0"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@almadar/core": "^10.
|
|
49
|
+
"@almadar/core": "^10.49.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@almadar/core": "^10.
|
|
52
|
+
"@almadar/core": "^10.49.0",
|
|
53
53
|
"@almadar/eslint-plugin": "^2.15.0",
|
|
54
54
|
"@types/node": "^22.0.0",
|
|
55
|
-
"@typescript-eslint/parser": "8.
|
|
55
|
+
"@typescript-eslint/parser": "8.65.0",
|
|
56
56
|
"eslint": "10.0.0",
|
|
57
57
|
"tsup": "^8.0.0",
|
|
58
58
|
"turbo": "^2.8.17",
|
package/src/client.ts
CHANGED
|
@@ -62,6 +62,8 @@ interface ModelKwargs {
|
|
|
62
62
|
tool_choice?: string;
|
|
63
63
|
/** OpenRouter: ask for detailed usage accounting (returns cached-token breakdown). */
|
|
64
64
|
usage?: { include: boolean };
|
|
65
|
+
/** DeepSeek V4: controls reasoning effort ("none" disables thinking tokens). */
|
|
66
|
+
reasoning_effort?: string;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
/**
|
|
@@ -144,6 +146,11 @@ export interface LLMClientOptions {
|
|
|
144
146
|
rateLimiter?: RateLimiterOptions;
|
|
145
147
|
useGlobalRateLimiter?: boolean;
|
|
146
148
|
trackTokens?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* Reasoning effort for thinking models (e.g. DeepSeek V4).
|
|
151
|
+
* Set to "none" to disable reasoning tokens for faster content generation.
|
|
152
|
+
*/
|
|
153
|
+
reasoningEffort?: string;
|
|
147
154
|
/**
|
|
148
155
|
* Hard per-call deadline (ms) for the raw invoke paths (`callRaw`,
|
|
149
156
|
* `callRawWithMetadata`, `callWithMessages`). Defaults to 5 min. Set this
|
|
@@ -456,6 +463,7 @@ export class LLMClient {
|
|
|
456
463
|
private temperature: number;
|
|
457
464
|
private streaming: boolean;
|
|
458
465
|
private rawTimeoutMs: number;
|
|
466
|
+
private reasoningEffort: string | undefined;
|
|
459
467
|
|
|
460
468
|
constructor(options: LLMClientOptions = {}) {
|
|
461
469
|
this.provider = options.provider || 'openai';
|
|
@@ -477,6 +485,7 @@ export class LLMClient {
|
|
|
477
485
|
}
|
|
478
486
|
this.modelName = options.model || this.providerConfig.defaultModel;
|
|
479
487
|
this.rawTimeoutMs = options.rawTimeoutMs ?? LLMClient.DEFAULT_RAW_TIMEOUT_MS;
|
|
488
|
+
this.reasoningEffort = options.reasoningEffort;
|
|
480
489
|
|
|
481
490
|
const keyPreview = this.providerConfig.apiKey.slice(-4);
|
|
482
491
|
log.info(
|
|
@@ -596,6 +605,10 @@ export class LLMClient {
|
|
|
596
605
|
if (isKimi) {
|
|
597
606
|
modelKwargs.thinking = { type: 'disabled' };
|
|
598
607
|
}
|
|
608
|
+
// DeepSeek V4: disable reasoning when reasoningEffort is set
|
|
609
|
+
if (this.reasoningEffort) {
|
|
610
|
+
modelKwargs.reasoning_effort = this.reasoningEffort;
|
|
611
|
+
}
|
|
599
612
|
// OpenRouter (Qwen): explicit tool_choice so the model doesn't ignore tool definitions
|
|
600
613
|
if (this.provider === 'openrouter') {
|
|
601
614
|
modelKwargs.tool_choice = 'auto';
|
package/src/vector/store.ts
CHANGED
|
@@ -148,14 +148,21 @@ function buildVectorStoreHandle(client: Chroma.ChromaClient, target: string): Ve
|
|
|
148
148
|
|
|
149
149
|
const vectorStoreCache = new Map<string, VectorStoreHandle>();
|
|
150
150
|
|
|
151
|
+
/** The empty-config skip is a per-process fact — log it once, not per retrieval. */
|
|
152
|
+
let emptyConfigSkipLogged = false;
|
|
153
|
+
|
|
151
154
|
/** Test-only reset — clears the per-process handle cache between cases. */
|
|
152
155
|
export function resetVectorStoreCache(): void {
|
|
153
156
|
vectorStoreCache.clear();
|
|
157
|
+
emptyConfigSkipLogged = false;
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
export async function connectVectorStore(config: VectorStoreConfig): Promise<VectorStoreHandle | null> {
|
|
157
161
|
if (isEmptyConfig(config)) {
|
|
158
|
-
|
|
162
|
+
if (!emptyConfigSkipLogged) {
|
|
163
|
+
emptyConfigSkipLogged = true;
|
|
164
|
+
vectorLog.debug('store:skip', () => ({ reason: 'config-empty' }));
|
|
165
|
+
}
|
|
159
166
|
return null;
|
|
160
167
|
}
|
|
161
168
|
|
|
@@ -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}\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 * 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\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\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 // OpenRouter (Qwen): explicit tool_choice so the model doesn't ignore tool definitions\n if (this.provider === 'openrouter') {\n modelKwargs.tool_choice = 'auto';\n // Return the cached-token breakdown so cost is priced cache-aware.\n modelKwargs.usage = { include: true };\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":[]}
|