@almadar/llm 2.33.0 → 2.35.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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/embedding-client.ts","../src/truncation-detector.ts","../src/continuation.ts"],"sourcesContent":["/**\n * Embedding Client\n *\n * Single-purpose client for text embeddings. Used by `@almadar-io/agent`'s\n * cosine-similarity catalog retrieval (rank organisms + atoms against the\n * user's request before rendering Stage A's prompt) and by\n * `@almadar/std`'s publish-time embedding bake step.\n *\n * Providers:\n * - `openai` (default model `text-embedding-3-small`, 1536-d) — requires\n * `OPENAI_API_KEY`.\n * - `openrouter` (default model `baai/bge-base-en-v1.5`, 768-d) — requires\n * `OPEN_ROUTER_API_KEY`. Same OpenAI-compatible request shape, just a\n * different base URL.\n *\n * Both providers return the same response envelope (`{data:[{embedding,index}]}`),\n * so the request/response code is shared.\n *\n * @packageDocumentation\n */\n\nexport type EmbeddingProvider = 'openai' | 'openrouter';\n\nexport interface EmbeddingClientOptions {\n provider: EmbeddingProvider;\n /** Defaults: openai → text-embedding-3-small, openrouter → baai/bge-base-en-v1.5. */\n model?: string;\n /** Override API key. Defaults to provider-specific env var. */\n apiKey?: string;\n /** Override base URL. Defaults to provider canonical endpoint. */\n baseUrl?: string;\n}\n\nexport interface EmbeddingUsage {\n promptTokens: number;\n totalTokens: number;\n}\n\nexport interface EmbeddingResult {\n embedding: readonly number[];\n usage: EmbeddingUsage;\n}\n\nexport interface EmbeddingBatchResult {\n embeddings: readonly (readonly number[])[];\n usage: EmbeddingUsage;\n}\n\ninterface OpenAIEmbeddingApiResponse {\n data: Array<{ embedding: number[]; index: number }>;\n usage?: { prompt_tokens?: number; total_tokens?: number };\n /** OpenRouter reports provider hiccups as HTTP 200 with an error envelope and no `data`. */\n error?: { message?: string; code?: number | string };\n}\n\n/** Transient-failure retry policy: attempts and backoff (ms) between them. */\nconst EMBED_MAX_ATTEMPTS = 3;\nconst EMBED_RETRY_BACKOFF_MS = [500, 2000];\n\n/** Provider rejects an input that exceeds the model's context window (bge: 512 tokens). */\nconst TOKEN_OVERFLOW_RE =\n /context length|maximum input length|reduce the length of the input|input_tokens/i;\n\nconst DEFAULT_MODELS: Record<EmbeddingProvider, string> = {\n openai: 'text-embedding-3-small',\n openrouter: 'baai/bge-base-en-v1.5',\n};\n\nconst DEFAULT_BASE_URLS: Record<EmbeddingProvider, string> = {\n openai: 'https://api.openai.com/v1',\n openrouter: 'https://openrouter.ai/api/v1',\n};\n\nconst API_KEY_ENV_VARS: Record<EmbeddingProvider, string> = {\n openai: 'OPENAI_API_KEY',\n openrouter: 'OPEN_ROUTER_API_KEY',\n};\n\nexport class EmbeddingClient {\n private readonly provider: EmbeddingProvider;\n private readonly model: string;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(opts: EmbeddingClientOptions) {\n this.provider = opts.provider;\n this.model = opts.model ?? DEFAULT_MODELS[opts.provider];\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URLS[opts.provider]).replace(/\\/$/, '');\n\n const apiKey = opts.apiKey ?? this.resolveApiKey();\n if (!apiKey) {\n throw new Error(\n `EmbeddingClient: API key for provider '${this.provider}' not found. ` +\n `Set ${API_KEY_ENV_VARS[this.provider]} in your environment or pass options.apiKey.`,\n );\n }\n this.apiKey = apiKey;\n }\n\n /** Embed a single text. */\n async embed(text: string): Promise<EmbeddingResult> {\n const batch = await this.embedBatch([text]);\n const embedding = batch.embeddings[0];\n if (!embedding) {\n throw new Error('EmbeddingClient.embed: provider returned no embedding');\n }\n return { embedding, usage: batch.usage };\n }\n\n /**\n * Embed a batch of texts. Used by `@almadar/std`'s publish-time\n * `build-embeddings.ts` script — one batch call for the full catalog\n * (≈190 entries) rather than per-entry requests.\n */\n async embedBatch(texts: ReadonlyArray<string>): Promise<EmbeddingBatchResult> {\n if (texts.length === 0) {\n return { embeddings: [], usage: { promptTokens: 0, totalTokens: 0 } };\n }\n // bge-base-en-v1.5 has a 512-token context limit. Chars-per-token varies\n // with density (~4 for typical English, ~2.5-3 for dense technical/code\n // text), so no fixed char cap can guarantee a fit. Start at 2000 chars and,\n // when the provider rejects an input for exceeding the context window,\n // halve the cap and retry — the provider's own token count is the source\n // of truth, not a chars-per-token guess.\n let charCap = 2000;\n let lastError: Error = new Error('EmbeddingClient: unreachable');\n for (let attempt = 1; attempt <= EMBED_MAX_ATTEMPTS; attempt++) {\n const trimmed = texts.map((t) => (t.length > charCap ? t.slice(0, charCap) : t));\n try {\n return await this.embedBatchOnce(texts.length, trimmed);\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n if (TOKEN_OVERFLOW_RE.test(lastError.message) && charCap > 500) {\n charCap = Math.floor(charCap / 2);\n continue;\n }\n const backoff = EMBED_RETRY_BACKOFF_MS[attempt - 1];\n if (backoff === undefined) break;\n await new Promise((r) => setTimeout(r, backoff));\n }\n }\n throw lastError;\n }\n\n private async embedBatchOnce(\n inputCount: number,\n trimmed: ReadonlyArray<string>,\n ): Promise<EmbeddingBatchResult> {\n const response = await fetch(`${this.baseUrl}/embeddings`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ model: this.model, input: trimmed }),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n throw new Error(\n `EmbeddingClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n );\n }\n const rawBody = await response.text();\n let body: OpenAIEmbeddingApiResponse;\n try {\n body = JSON.parse(rawBody) as OpenAIEmbeddingApiResponse;\n } catch {\n throw new Error(\n `EmbeddingClient: ${this.provider} returned unparseable body: ${rawBody}`,\n );\n }\n if (!Array.isArray(body.data)) {\n // OpenRouter reports provider hiccups as 200 + `{error}` with no data —\n // surface the real body instead of a blind \"missing data\" (G9: this\n // error was undiagnosable for weeks because the body was discarded).\n const detail = body.error?.message ?? rawBody;\n throw new Error(\n `EmbeddingClient: ${this.provider} response has no \\`data\\` array: ${detail}`,\n );\n }\n const ordered = [...body.data].sort((a, b) => a.index - b.index);\n if (ordered.length !== inputCount) {\n throw new Error(\n `EmbeddingClient: provider returned ${ordered.length} embeddings for ${inputCount} inputs`,\n );\n }\n return {\n embeddings: ordered.map((d) => d.embedding),\n usage: {\n promptTokens: body.usage?.prompt_tokens ?? 0,\n totalTokens: body.usage?.total_tokens ?? 0,\n },\n };\n }\n\n private resolveApiKey(): string | undefined {\n return process.env[API_KEY_ENV_VARS[this.provider]];\n }\n}\n\n/**\n * Cosine similarity between two equal-length vectors. Returns a value in\n * [-1, 1]; higher means more similar. Used by `@almadar-io/agent`'s\n * catalog retrieval to rank organism/atom descriptions against the user\n * request embedding.\n *\n * Defensive: returns 0 when either vector is the zero vector (would\n * otherwise divide by zero).\n */\nexport function cosineSimilarity(a: ReadonlyArray<number>, b: ReadonlyArray<number>): number {\n if (a.length !== b.length) {\n throw new Error(`cosineSimilarity: vector length mismatch (${a.length} vs ${b.length})`);\n }\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const av = a[i];\n const bv = b[i];\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n if (normA === 0 || normB === 0) return 0;\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n","/**\n * Truncation Detector\n *\n * Utilities for detecting when LLM output has been truncated and\n * extracting usable content from partial responses.\n *\n * @packageDocumentation\n */\n\nimport type { LLMFinishReason } from './client.js';\nimport { autoCloseJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type TruncationReason =\n | 'finish_reason'\n | 'json_incomplete'\n | 'bracket_mismatch'\n | 'none';\n\nexport interface TruncationResult {\n isTruncated: boolean;\n reason: TruncationReason;\n partialContent?: string;\n lastCompleteElement?: unknown;\n missingCloseBrackets?: number;\n missingCloseBraces?: number;\n}\n\n// ============================================================================\n// Main Detection Function\n// ============================================================================\n\n/**\n * Detect if an LLM response has been truncated.\n *\n * Analyzes the response content and finish reason to determine if\n * the output was cut off due to token limits or other issues.\n *\n * @param {string} response - The LLM response text\n * @param {LLMFinishReason} finishReason - The finish reason from the LLM\n * @returns {TruncationResult} Detection result with truncation details\n */\nexport function detectTruncation(\n response: string,\n finishReason: LLMFinishReason,\n): TruncationResult {\n if (finishReason === 'length') {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'finish_reason',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n JSON.parse(response);\n return { isTruncated: false, reason: 'none' };\n } catch {\n // JSON is invalid, check if due to truncation\n }\n\n if (finishReason === 'stop' || finishReason === null) {\n const trimmed = response.trim();\n\n const isMidContent =\n trimmed.endsWith(',') ||\n trimmed.endsWith(':') ||\n trimmed.endsWith('\": ') ||\n /:\\s*$/.test(trimmed) ||\n /,\\s*$/.test(trimmed);\n\n if (isMidContent) {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'json_incomplete',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n const closed = autoCloseJson(trimmed);\n JSON.parse(closed);\n return { isTruncated: false, reason: 'none' };\n } catch {\n return { isTruncated: false, reason: 'none' };\n }\n }\n\n const bracketInfo = countBrackets(response);\n if (\n bracketInfo.missingCloseBrackets > 0 ||\n bracketInfo.missingCloseBraces > 0\n ) {\n return {\n isTruncated: true,\n reason: 'bracket_mismatch',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n return { isTruncated: false, reason: 'none' };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction countBrackets(json: string): {\n openBrackets: number;\n closeBrackets: number;\n openBraces: number;\n closeBraces: number;\n missingCloseBrackets: number;\n missingCloseBraces: number;\n} {\n let inString = false;\n let escaped = false;\n let openBrackets = 0;\n let closeBrackets = 0;\n let openBraces = 0;\n let closeBraces = 0;\n\n for (const char of json) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n switch (char) {\n case '[':\n openBrackets++;\n break;\n case ']':\n closeBrackets++;\n break;\n case '{':\n openBraces++;\n break;\n case '}':\n closeBraces++;\n break;\n }\n }\n\n return {\n openBrackets,\n closeBrackets,\n openBraces,\n closeBraces,\n missingCloseBrackets: Math.max(0, openBrackets - closeBrackets),\n missingCloseBraces: Math.max(0, openBraces - closeBraces),\n };\n}\n\nexport function findLastCompleteElement(json: string): unknown | null {\n const autoClosed = autoCloseJson(json);\n try {\n return JSON.parse(autoClosed);\n } catch {\n // Auto-close didn't work\n }\n\n const trimmed = json.trim();\n\n if (trimmed.startsWith('[')) {\n const lastCompleteIndex = findLastCompleteArrayElement(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + ']';\n try {\n return JSON.parse(subset);\n } catch {\n // Continue\n }\n }\n }\n\n if (trimmed.startsWith('{')) {\n const closed = autoCloseJson(trimmed);\n try {\n return JSON.parse(closed);\n } catch {\n const lastCompleteIndex = findLastCompleteObjectProperty(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + '}';\n try {\n return JSON.parse(subset);\n } catch {\n // Give up\n }\n }\n }\n }\n\n return null;\n}\n\nfunction findLastCompleteArrayElement(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCompleteElementEnd = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n if (depth === 1) {\n lastCompleteElementEnd = i + 1;\n }\n } else if (char === ',' && depth === 1) {\n lastCompleteElementEnd = i;\n }\n }\n\n return lastCompleteElementEnd > 0 ? lastCompleteElementEnd : -1;\n}\n\nfunction findLastCompleteObjectProperty(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCommaIndex = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n } else if (char === ',' && depth === 1) {\n lastCommaIndex = i;\n }\n }\n\n return lastCommaIndex > 0 ? lastCommaIndex : -1;\n}\n\nexport function isLikelyTruncated(content: string): boolean {\n const trimmed = content.trim();\n if (!trimmed) return false;\n\n const brackets = countBrackets(trimmed);\n if (\n brackets.missingCloseBrackets > 0 ||\n brackets.missingCloseBraces > 0\n ) {\n return true;\n }\n\n const abruptEndings = [\n /,\\s*$/,\n /:\\s*$/,\n /\"\\s*:\\s*$/,\n /\\[\\s*$/,\n /{\\s*$/,\n ];\n\n for (const pattern of abruptEndings) {\n if (pattern.test(trimmed)) return true;\n }\n\n return false;\n}\n","/**\n * LLM Continuation Utility\n *\n * Handles truncated LLM responses with automatic continuation.\n * - Detects truncation via finish_reason and JSON structure\n * - Automatically continues with full context\n * - Merges partial and continuation responses\n * - Salvages partial data if max continuations reached\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod';\nimport { LLMClient, type LLMFinishReason } from './client.js';\nimport { detectTruncation } from './truncation-detector.js';\nimport { extractJsonFromText, autoCloseJson, isValidJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ContinuationOptions<T> {\n client: LLMClient;\n systemPrompt: string;\n userPrompt: string;\n schema?: z.ZodSchema<T>;\n maxTokens?: number;\n maxContinuations?: number;\n maxRetries?: number;\n buildContinuationPrompt: (\n partialResponse: string,\n attempt: number,\n ) => string;\n continuationSystemPrompt?: string;\n}\n\nexport interface ContinuationResult<T> {\n data: T;\n raw: string;\n continuationCount: number;\n warnings: string[];\n wasSalvaged: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MAX_TOKENS = 8192;\nconst DEFAULT_MAX_CONTINUATIONS = 3;\n\n/**\n * Default continuation system prompt.\n * Used when no custom continuationSystemPrompt is provided.\n */\nconst DEFAULT_CONTINUATION_SYSTEM_PROMPT = `You are a JSON continuation assistant. Your ONLY job is to continue generating JSON from where the previous response was truncated.\n\nRules:\n1. Continue from EXACTLY where the previous output stopped\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly with all closing brackets\n4. Do NOT wrap in markdown code blocks\n5. Output ONLY the continuation JSON, nothing else`;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Merge a previous partial response with a continuation.\n *\n * Handles JSON structure merging, removing markdown code blocks,\n * and ensuring proper comma separation between merged parts.\n *\n * @param {string} previous - The previous partial response\n * @param {string} continuation - The continuation to merge\n * @returns {string} The merged response\n */\nexport function mergeResponses(\n previous: string,\n continuation: string,\n): string {\n const trimmedPrev = previous.trimEnd();\n const trimmedCont = continuation.trimStart();\n\n let cleanedCont = trimmedCont\n .replace(/^```json?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\n if (cleanedCont.startsWith('{')) {\n try {\n const contParsed = JSON.parse(autoCloseJson(cleanedCont));\n const keys = Object.keys(contParsed);\n if (keys.length === 1 && Array.isArray(contParsed[keys[0]])) {\n cleanedCont = contParsed[keys[0]]\n .map((item: unknown) => JSON.stringify(item))\n .join(',\\n');\n }\n } catch {\n // Continue with original cleaning\n }\n }\n\n if (cleanedCont.startsWith('}') || cleanedCont.startsWith(']')) {\n return trimmedPrev + cleanedCont;\n }\n\n const prevEndsWithValue = /[\\}\\]\\\"\\d]$/.test(trimmedPrev);\n const contStartsWithValue = /^[\\{\\[\\\"]/.test(cleanedCont);\n\n if (prevEndsWithValue && contStartsWithValue) {\n return trimmedPrev + ',\\n' + cleanedCont;\n }\n\n return trimmedPrev + cleanedCont;\n}\n\nexport function salvagePartialResponse<T>(rawResponse: string): T | null {\n console.warn('[Continuation] Attempting to salvage partial response');\n\n try {\n const cleanedResponse = extractJsonFromText(rawResponse) || rawResponse;\n const closed = autoCloseJson(cleanedResponse);\n const parsed = JSON.parse(closed) as T;\n console.log('[Continuation] Successfully salvaged partial response');\n return parsed;\n } catch (error) {\n console.error('[Continuation] Could not salvage response:', error);\n }\n\n return null;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Call an LLM with automatic continuation handling.\n *\n * Manages token limits by detecting truncation and making continuation\n * calls until the complete response is received or max continuations reached.\n *\n * @template T - Expected response type\n * @param {ContinuationOptions<T>} options - Continuation call options\n * @returns {Promise<ContinuationResult<T>>} Result with data and continuation metadata\n */\nexport async function callWithContinuation<T>(\n options: ContinuationOptions<T>,\n): Promise<ContinuationResult<T>> {\n const {\n client,\n systemPrompt,\n userPrompt,\n schema,\n maxTokens = DEFAULT_MAX_TOKENS,\n maxContinuations = DEFAULT_MAX_CONTINUATIONS,\n buildContinuationPrompt,\n continuationSystemPrompt = DEFAULT_CONTINUATION_SYSTEM_PROMPT,\n } = options;\n\n let rawResponse = '';\n let continuationCount = 0;\n const warnings: string[] = [];\n let wasSalvaged = false;\n\n console.log('[Continuation] Starting LLM call with continuation support');\n console.log(\n `[Continuation] Max tokens: ${maxTokens}, Max continuations: ${maxContinuations}`,\n );\n\n try {\n const response = await client.callRawWithMetadata({\n systemPrompt,\n userPrompt,\n maxTokens,\n });\n\n rawResponse = extractJsonFromText(response.raw) || response.raw;\n\n console.log(\n `[Continuation] Initial response: ${rawResponse.length} chars, finish_reason: ${response.finishReason}`,\n );\n\n let truncation = detectTruncation(rawResponse, response.finishReason);\n\n while (truncation.isTruncated && continuationCount < maxContinuations) {\n continuationCount++;\n const warningMsg = `Response truncated (${truncation.reason}), continuing (attempt ${continuationCount}/${maxContinuations})`;\n console.log(`[Continuation] ${warningMsg}`);\n warnings.push(warningMsg);\n\n const contPrompt = buildContinuationPrompt(\n rawResponse,\n continuationCount,\n );\n\n const contResponse = await client.callRawWithMetadata({\n systemPrompt: continuationSystemPrompt,\n userPrompt: contPrompt,\n maxTokens,\n });\n\n console.log(\n `[Continuation] Continuation response: ${contResponse.raw.length} chars, finish_reason: ${contResponse.finishReason}`,\n );\n\n const cleanedContResponse =\n extractJsonFromText(contResponse.raw) || contResponse.raw;\n rawResponse = mergeResponses(rawResponse, cleanedContResponse);\n\n truncation = detectTruncation(rawResponse, contResponse.finishReason);\n }\n\n if (\n continuationCount >= maxContinuations &&\n truncation.isTruncated\n ) {\n console.warn(\n `[Continuation] Reached max continuations (${maxContinuations}), attempting to salvage...`,\n );\n warnings.push(\n `Reached max continuations - some content may be incomplete`,\n );\n wasSalvaged = true;\n }\n\n const cleanedResponse =\n extractJsonFromText(rawResponse) || rawResponse;\n let data: T;\n\n try {\n if (isValidJson(cleanedResponse)) {\n data = JSON.parse(cleanedResponse) as T;\n } else {\n const closed = autoCloseJson(cleanedResponse);\n data = JSON.parse(closed) as T;\n if (!wasSalvaged) {\n warnings.push('Response required auto-closing of JSON brackets');\n }\n }\n } catch (parseError) {\n const salvaged = salvagePartialResponse<T>(cleanedResponse);\n if (salvaged) {\n data = salvaged;\n wasSalvaged = true;\n warnings.push('Response was salvaged from partial data');\n } else {\n throw new Error(\n `Failed to parse response after ${continuationCount} continuations: ${parseError}`,\n );\n }\n }\n\n if (schema) {\n try {\n data = schema.parse(data);\n } catch (validationError) {\n console.warn(\n '[Continuation] Schema validation failed:',\n validationError,\n );\n warnings.push(`Schema validation issue: ${validationError}`);\n }\n }\n\n console.log(\n `[Continuation] Complete. Continuations: ${continuationCount}, Warnings: ${warnings.length}`,\n );\n\n return {\n data,\n raw: rawResponse,\n continuationCount,\n warnings,\n wasSalvaged,\n };\n } catch (error) {\n console.error('[Continuation] Error during LLM call:', error);\n throw error;\n }\n}\n\n/**\n * Build a generic continuation prompt.\n *\n * Creates a prompt that instructs the LLM to continue from where\n * a previous truncated response left off.\n *\n * @param {string} context - Original context/prompt\n * @param {string} partialResponse - The partial response generated so far\n * @param {number} attempt - Current continuation attempt number\n * @param {number} [maxAttempts] - Maximum allowed continuation attempts\n * @returns {string} The formatted continuation prompt\n */\nexport function buildGenericContinuationPrompt(\n context: string,\n partialResponse: string,\n attempt: number,\n maxAttempts: number = DEFAULT_MAX_CONTINUATIONS,\n): string {\n return `## CONTINUATION REQUEST (Attempt ${attempt}/${maxAttempts})\n\nYour previous response was truncated. Continue generating from where you left off.\n\n### ORIGINAL CONTEXT\n${context}\n\n### WHAT YOU GENERATED SO FAR\n\\`\\`\\`json\n${partialResponse}\n\\`\\`\\`\n\n### INSTRUCTIONS\n1. Continue from EXACTLY where the response was cut off\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly\n4. Do NOT wrap your response in markdown code blocks\n\nContinue generating now:`;\n}\n"],"mappings":"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","names":["bracketInfo"]}
1
+ {"version":3,"sources":["../src/embedding-client.ts","../src/truncation-detector.ts","../src/continuation.ts"],"sourcesContent":["/**\n * Embedding Client\n *\n * Single-purpose client for text embeddings. Used by `@almadar-io/agent`'s\n * cosine-similarity catalog retrieval (rank organisms + atoms against the\n * user's request before rendering Stage A's prompt) and by\n * `@almadar/std`'s publish-time embedding bake step.\n *\n * Providers:\n * - `openai` (default model `text-embedding-3-small`, 1536-d) — requires\n * `OPENAI_API_KEY`.\n * - `openrouter` (default model `baai/bge-base-en-v1.5`, 768-d) — requires\n * `OPEN_ROUTER_API_KEY`. Same OpenAI-compatible request shape, just a\n * different base URL.\n *\n * Both providers return the same response envelope (`{data:[{embedding,index}]}`),\n * so the request/response code is shared.\n *\n * @packageDocumentation\n */\n\nexport type EmbeddingProvider = 'openai' | 'openrouter';\n\nexport interface EmbeddingClientOptions {\n provider: EmbeddingProvider;\n /** Defaults: openai → text-embedding-3-small, openrouter → baai/bge-base-en-v1.5. */\n model?: string;\n /** Override API key. Defaults to provider-specific env var. */\n apiKey?: string;\n /** Override base URL. Defaults to provider canonical endpoint. */\n baseUrl?: string;\n}\n\nexport interface EmbeddingUsage {\n promptTokens: number;\n totalTokens: number;\n}\n\nexport interface EmbeddingResult {\n embedding: readonly number[];\n usage: EmbeddingUsage;\n}\n\nexport interface EmbeddingBatchResult {\n embeddings: readonly (readonly number[])[];\n usage: EmbeddingUsage;\n}\n\ninterface OpenAIEmbeddingApiResponse {\n data: Array<{ embedding: number[]; index: number }>;\n usage?: { prompt_tokens?: number; total_tokens?: number };\n /** OpenRouter reports provider hiccups as HTTP 200 with an error envelope and no `data`. */\n error?: { message?: string; code?: number | string };\n}\n\n/** Transient-failure retry policy: attempts and backoff (ms) between them. */\nconst EMBED_MAX_ATTEMPTS = 3;\nconst EMBED_RETRY_BACKOFF_MS = [500, 2000];\n\n/** Provider rejects an input that exceeds the model's context window (bge: 512 tokens). */\nconst TOKEN_OVERFLOW_RE =\n /context length|maximum input length|reduce the length of the input|input_tokens/i;\n\nconst DEFAULT_MODELS: Record<EmbeddingProvider, string> = {\n openai: 'text-embedding-3-small',\n openrouter: 'baai/bge-base-en-v1.5',\n};\n\nconst DEFAULT_BASE_URLS: Record<EmbeddingProvider, string> = {\n openai: 'https://api.openai.com/v1',\n openrouter: 'https://openrouter.ai/api/v1',\n};\n\nconst API_KEY_ENV_VARS: Record<EmbeddingProvider, string> = {\n openai: 'OPENAI_API_KEY',\n openrouter: 'OPEN_ROUTER_API_KEY',\n};\n\nexport class EmbeddingClient {\n private readonly provider: EmbeddingProvider;\n private readonly model: string;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n\n constructor(opts: EmbeddingClientOptions) {\n this.provider = opts.provider;\n this.model = opts.model ?? DEFAULT_MODELS[opts.provider];\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URLS[opts.provider]).replace(/\\/$/, '');\n\n const apiKey = opts.apiKey ?? this.resolveApiKey();\n if (!apiKey) {\n throw new Error(\n `EmbeddingClient: API key for provider '${this.provider}' not found. ` +\n `Set ${API_KEY_ENV_VARS[this.provider]} in your environment or pass options.apiKey.`,\n );\n }\n this.apiKey = apiKey;\n }\n\n /** Embed a single text. */\n async embed(text: string): Promise<EmbeddingResult> {\n const batch = await this.embedBatch([text]);\n const embedding = batch.embeddings[0];\n if (!embedding) {\n throw new Error('EmbeddingClient.embed: provider returned no embedding');\n }\n return { embedding, usage: batch.usage };\n }\n\n /**\n * Embed a batch of texts. Used by `@almadar/std`'s publish-time\n * `build-embeddings.ts` script — one batch call for the full catalog\n * (≈190 entries) rather than per-entry requests.\n */\n async embedBatch(texts: ReadonlyArray<string>): Promise<EmbeddingBatchResult> {\n if (texts.length === 0) {\n return { embeddings: [], usage: { promptTokens: 0, totalTokens: 0 } };\n }\n // bge-base-en-v1.5 has a 512-token context limit. Chars-per-token varies\n // with density (~4 for typical English, ~2.5-3 for dense technical/code\n // text), so no fixed char cap can guarantee a fit. Start at 2000 chars and,\n // when the provider rejects an input for exceeding the context window,\n // halve the cap and retry — the provider's own token count is the source\n // of truth, not a chars-per-token guess.\n let charCap = 2000;\n let lastError: Error = new Error('EmbeddingClient: unreachable');\n for (let attempt = 1; attempt <= EMBED_MAX_ATTEMPTS; attempt++) {\n const trimmed = texts.map((t) => (t.length > charCap ? t.slice(0, charCap) : t));\n try {\n return await this.embedBatchOnce(texts.length, trimmed);\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n if (TOKEN_OVERFLOW_RE.test(lastError.message) && charCap > 500) {\n charCap = Math.floor(charCap / 2);\n continue;\n }\n const backoff = EMBED_RETRY_BACKOFF_MS[attempt - 1];\n if (backoff === undefined) break;\n await new Promise((r) => setTimeout(r, backoff));\n }\n }\n throw lastError;\n }\n\n private async embedBatchOnce(\n inputCount: number,\n trimmed: ReadonlyArray<string>,\n ): Promise<EmbeddingBatchResult> {\n const response = await fetch(`${this.baseUrl}/embeddings`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ model: this.model, input: trimmed }),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n throw new Error(\n `EmbeddingClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n );\n }\n const rawBody = await response.text();\n let body: OpenAIEmbeddingApiResponse;\n try {\n body = JSON.parse(rawBody) as OpenAIEmbeddingApiResponse;\n } catch {\n throw new Error(\n `EmbeddingClient: ${this.provider} returned unparseable body: ${rawBody}`,\n );\n }\n if (!Array.isArray(body.data)) {\n // OpenRouter reports provider hiccups as 200 + `{error}` with no data —\n // surface the real body instead of a blind \"missing data\" (G9: this\n // error was undiagnosable for weeks because the body was discarded).\n const detail = body.error?.message ?? rawBody;\n throw new Error(\n `EmbeddingClient: ${this.provider} response has no \\`data\\` array: ${detail}`,\n );\n }\n const ordered = [...body.data].sort((a, b) => a.index - b.index);\n if (ordered.length !== inputCount) {\n throw new Error(\n `EmbeddingClient: provider returned ${ordered.length} embeddings for ${inputCount} inputs`,\n );\n }\n return {\n embeddings: ordered.map((d) => d.embedding),\n usage: {\n promptTokens: body.usage?.prompt_tokens ?? 0,\n totalTokens: body.usage?.total_tokens ?? 0,\n },\n };\n }\n\n private resolveApiKey(): string | undefined {\n return process.env[API_KEY_ENV_VARS[this.provider]];\n }\n}\n\n/**\n * Cosine similarity between two equal-length vectors. Returns a value in\n * [-1, 1]; higher means more similar. Used by `@almadar-io/agent`'s\n * catalog retrieval to rank organism/atom descriptions against the user\n * request embedding.\n *\n * Defensive: returns 0 when either vector is the zero vector (would\n * otherwise divide by zero).\n */\nexport function cosineSimilarity(a: ReadonlyArray<number>, b: ReadonlyArray<number>): number {\n if (a.length !== b.length) {\n throw new Error(`cosineSimilarity: vector length mismatch (${a.length} vs ${b.length})`);\n }\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const av = a[i];\n const bv = b[i];\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n if (normA === 0 || normB === 0) return 0;\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n","/**\n * Truncation Detector\n *\n * Utilities for detecting when LLM output has been truncated and\n * extracting usable content from partial responses.\n *\n * @packageDocumentation\n */\n\nimport type { LLMFinishReason } from './client.js';\nimport { autoCloseJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type TruncationReason =\n | 'finish_reason'\n | 'json_incomplete'\n | 'bracket_mismatch'\n | 'none';\n\nexport interface TruncationResult {\n isTruncated: boolean;\n reason: TruncationReason;\n partialContent?: string;\n lastCompleteElement?: unknown;\n missingCloseBrackets?: number;\n missingCloseBraces?: number;\n}\n\n// ============================================================================\n// Main Detection Function\n// ============================================================================\n\n/**\n * Detect if an LLM response has been truncated.\n *\n * Analyzes the response content and finish reason to determine if\n * the output was cut off due to token limits or other issues.\n *\n * @param {string} response - The LLM response text\n * @param {LLMFinishReason} finishReason - The finish reason from the LLM\n * @returns {TruncationResult} Detection result with truncation details\n */\nexport function detectTruncation(\n response: string,\n finishReason: LLMFinishReason,\n): TruncationResult {\n if (finishReason === 'length') {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'finish_reason',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n JSON.parse(response);\n return { isTruncated: false, reason: 'none' };\n } catch {\n // JSON is invalid, check if due to truncation\n }\n\n if (finishReason === 'stop' || finishReason === null) {\n const trimmed = response.trim();\n\n const isMidContent =\n trimmed.endsWith(',') ||\n trimmed.endsWith(':') ||\n trimmed.endsWith('\": ') ||\n /:\\s*$/.test(trimmed) ||\n /,\\s*$/.test(trimmed);\n\n if (isMidContent) {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'json_incomplete',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n const closed = autoCloseJson(trimmed);\n JSON.parse(closed);\n return { isTruncated: false, reason: 'none' };\n } catch {\n return { isTruncated: false, reason: 'none' };\n }\n }\n\n const bracketInfo = countBrackets(response);\n if (\n bracketInfo.missingCloseBrackets > 0 ||\n bracketInfo.missingCloseBraces > 0\n ) {\n return {\n isTruncated: true,\n reason: 'bracket_mismatch',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n return { isTruncated: false, reason: 'none' };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction countBrackets(json: string): {\n openBrackets: number;\n closeBrackets: number;\n openBraces: number;\n closeBraces: number;\n missingCloseBrackets: number;\n missingCloseBraces: number;\n} {\n let inString = false;\n let escaped = false;\n let openBrackets = 0;\n let closeBrackets = 0;\n let openBraces = 0;\n let closeBraces = 0;\n\n for (const char of json) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n switch (char) {\n case '[':\n openBrackets++;\n break;\n case ']':\n closeBrackets++;\n break;\n case '{':\n openBraces++;\n break;\n case '}':\n closeBraces++;\n break;\n }\n }\n\n return {\n openBrackets,\n closeBrackets,\n openBraces,\n closeBraces,\n missingCloseBrackets: Math.max(0, openBrackets - closeBrackets),\n missingCloseBraces: Math.max(0, openBraces - closeBraces),\n };\n}\n\nexport function findLastCompleteElement(json: string): unknown | null {\n const autoClosed = autoCloseJson(json);\n try {\n return JSON.parse(autoClosed);\n } catch {\n // Auto-close didn't work\n }\n\n const trimmed = json.trim();\n\n if (trimmed.startsWith('[')) {\n const lastCompleteIndex = findLastCompleteArrayElement(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + ']';\n try {\n return JSON.parse(subset);\n } catch {\n // Continue\n }\n }\n }\n\n if (trimmed.startsWith('{')) {\n const closed = autoCloseJson(trimmed);\n try {\n return JSON.parse(closed);\n } catch {\n const lastCompleteIndex = findLastCompleteObjectProperty(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + '}';\n try {\n return JSON.parse(subset);\n } catch {\n // Give up\n }\n }\n }\n }\n\n return null;\n}\n\nfunction findLastCompleteArrayElement(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCompleteElementEnd = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n if (depth === 1) {\n lastCompleteElementEnd = i + 1;\n }\n } else if (char === ',' && depth === 1) {\n lastCompleteElementEnd = i;\n }\n }\n\n return lastCompleteElementEnd > 0 ? lastCompleteElementEnd : -1;\n}\n\nfunction findLastCompleteObjectProperty(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCommaIndex = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n } else if (char === ',' && depth === 1) {\n lastCommaIndex = i;\n }\n }\n\n return lastCommaIndex > 0 ? lastCommaIndex : -1;\n}\n\nexport function isLikelyTruncated(content: string): boolean {\n const trimmed = content.trim();\n if (!trimmed) return false;\n\n const brackets = countBrackets(trimmed);\n if (\n brackets.missingCloseBrackets > 0 ||\n brackets.missingCloseBraces > 0\n ) {\n return true;\n }\n\n const abruptEndings = [\n /,\\s*$/,\n /:\\s*$/,\n /\"\\s*:\\s*$/,\n /\\[\\s*$/,\n /{\\s*$/,\n ];\n\n for (const pattern of abruptEndings) {\n if (pattern.test(trimmed)) return true;\n }\n\n return false;\n}\n","/**\n * LLM Continuation Utility\n *\n * Handles truncated LLM responses with automatic continuation.\n * - Detects truncation via finish_reason and JSON structure\n * - Automatically continues with full context\n * - Merges partial and continuation responses\n * - Salvages partial data if max continuations reached\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod';\nimport { LLMClient, type LLMFinishReason } from './client.js';\nimport { detectTruncation } from './truncation-detector.js';\nimport { extractJsonFromText, autoCloseJson, isValidJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ContinuationOptions<T> {\n client: LLMClient;\n systemPrompt: string;\n userPrompt: string;\n schema?: z.ZodSchema<T>;\n maxTokens?: number;\n maxContinuations?: number;\n maxRetries?: number;\n buildContinuationPrompt: (\n partialResponse: string,\n attempt: number,\n ) => string;\n continuationSystemPrompt?: string;\n}\n\nexport interface ContinuationResult<T> {\n data: T;\n raw: string;\n continuationCount: number;\n warnings: string[];\n wasSalvaged: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MAX_TOKENS = 8192;\nconst DEFAULT_MAX_CONTINUATIONS = 3;\n\n/**\n * Default continuation system prompt.\n * Used when no custom continuationSystemPrompt is provided.\n */\nconst DEFAULT_CONTINUATION_SYSTEM_PROMPT = `You are a JSON continuation assistant. Your ONLY job is to continue generating JSON from where the previous response was truncated.\n\nRules:\n1. Continue from EXACTLY where the previous output stopped\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly with all closing brackets\n4. Do NOT wrap in markdown code blocks\n5. Output ONLY the continuation JSON, nothing else`;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Merge a previous partial response with a continuation.\n *\n * Handles JSON structure merging, removing markdown code blocks,\n * and ensuring proper comma separation between merged parts.\n *\n * @param {string} previous - The previous partial response\n * @param {string} continuation - The continuation to merge\n * @returns {string} The merged response\n */\nexport function mergeResponses(\n previous: string,\n continuation: string,\n): string {\n const trimmedPrev = previous.trimEnd();\n const trimmedCont = continuation.trimStart();\n\n let cleanedCont = trimmedCont\n .replace(/^```json?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\n if (cleanedCont.startsWith('{')) {\n try {\n const contParsed = JSON.parse(autoCloseJson(cleanedCont));\n const keys = Object.keys(contParsed);\n if (keys.length === 1 && Array.isArray(contParsed[keys[0]])) {\n cleanedCont = contParsed[keys[0]]\n .map((item: unknown) => JSON.stringify(item))\n .join(',\\n');\n }\n } catch {\n // Continue with original cleaning\n }\n }\n\n if (cleanedCont.startsWith('}') || cleanedCont.startsWith(']')) {\n return trimmedPrev + cleanedCont;\n }\n\n const prevEndsWithValue = /[\\}\\]\\\"\\d]$/.test(trimmedPrev);\n const contStartsWithValue = /^[\\{\\[\\\"]/.test(cleanedCont);\n\n if (prevEndsWithValue && contStartsWithValue) {\n return trimmedPrev + ',\\n' + cleanedCont;\n }\n\n return trimmedPrev + cleanedCont;\n}\n\nexport function salvagePartialResponse<T>(rawResponse: string): T | null {\n console.warn('[Continuation] Attempting to salvage partial response');\n\n try {\n const cleanedResponse = extractJsonFromText(rawResponse) || rawResponse;\n const closed = autoCloseJson(cleanedResponse);\n const parsed = JSON.parse(closed) as T;\n console.log('[Continuation] Successfully salvaged partial response');\n return parsed;\n } catch (error) {\n console.error('[Continuation] Could not salvage response:', error);\n }\n\n return null;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Call an LLM with automatic continuation handling.\n *\n * Manages token limits by detecting truncation and making continuation\n * calls until the complete response is received or max continuations reached.\n *\n * @template T - Expected response type\n * @param {ContinuationOptions<T>} options - Continuation call options\n * @returns {Promise<ContinuationResult<T>>} Result with data and continuation metadata\n */\nexport async function callWithContinuation<T>(\n options: ContinuationOptions<T>,\n): Promise<ContinuationResult<T>> {\n const {\n client,\n systemPrompt,\n userPrompt,\n schema,\n maxTokens = DEFAULT_MAX_TOKENS,\n maxContinuations = DEFAULT_MAX_CONTINUATIONS,\n buildContinuationPrompt,\n continuationSystemPrompt = DEFAULT_CONTINUATION_SYSTEM_PROMPT,\n } = options;\n\n let rawResponse = '';\n let continuationCount = 0;\n const warnings: string[] = [];\n let wasSalvaged = false;\n\n console.log('[Continuation] Starting LLM call with continuation support');\n console.log(\n `[Continuation] Max tokens: ${maxTokens}, Max continuations: ${maxContinuations}`,\n );\n\n try {\n const response = await client.callRawWithMetadata({\n systemPrompt,\n userPrompt,\n maxTokens,\n });\n\n rawResponse = extractJsonFromText(response.raw) || response.raw;\n\n console.log(\n `[Continuation] Initial response: ${rawResponse.length} chars, finish_reason: ${response.finishReason}`,\n );\n\n let truncation = detectTruncation(rawResponse, response.finishReason);\n\n while (truncation.isTruncated && continuationCount < maxContinuations) {\n continuationCount++;\n const warningMsg = `Response truncated (${truncation.reason}), continuing (attempt ${continuationCount}/${maxContinuations})`;\n console.log(`[Continuation] ${warningMsg}`);\n warnings.push(warningMsg);\n\n const contPrompt = buildContinuationPrompt(\n rawResponse,\n continuationCount,\n );\n\n const contResponse = await client.callRawWithMetadata({\n systemPrompt: continuationSystemPrompt,\n userPrompt: contPrompt,\n maxTokens,\n });\n\n console.log(\n `[Continuation] Continuation response: ${contResponse.raw.length} chars, finish_reason: ${contResponse.finishReason}`,\n );\n\n const cleanedContResponse =\n extractJsonFromText(contResponse.raw) || contResponse.raw;\n rawResponse = mergeResponses(rawResponse, cleanedContResponse);\n\n truncation = detectTruncation(rawResponse, contResponse.finishReason);\n }\n\n if (\n continuationCount >= maxContinuations &&\n truncation.isTruncated\n ) {\n console.warn(\n `[Continuation] Reached max continuations (${maxContinuations}), attempting to salvage...`,\n );\n warnings.push(\n `Reached max continuations - some content may be incomplete`,\n );\n wasSalvaged = true;\n }\n\n const cleanedResponse =\n extractJsonFromText(rawResponse) || rawResponse;\n let data: T;\n\n try {\n if (isValidJson(cleanedResponse)) {\n data = JSON.parse(cleanedResponse) as T;\n } else {\n const closed = autoCloseJson(cleanedResponse);\n data = JSON.parse(closed) as T;\n if (!wasSalvaged) {\n warnings.push('Response required auto-closing of JSON brackets');\n }\n }\n } catch (parseError) {\n const salvaged = salvagePartialResponse<T>(cleanedResponse);\n if (salvaged) {\n data = salvaged;\n wasSalvaged = true;\n warnings.push('Response was salvaged from partial data');\n } else {\n throw new Error(\n `Failed to parse response after ${continuationCount} continuations: ${parseError}`,\n );\n }\n }\n\n if (schema) {\n try {\n data = schema.parse(data);\n } catch (validationError) {\n console.warn(\n '[Continuation] Schema validation failed:',\n validationError,\n );\n warnings.push(`Schema validation issue: ${validationError}`);\n }\n }\n\n console.log(\n `[Continuation] Complete. Continuations: ${continuationCount}, Warnings: ${warnings.length}`,\n );\n\n return {\n data,\n raw: rawResponse,\n continuationCount,\n warnings,\n wasSalvaged,\n };\n } catch (error) {\n console.error('[Continuation] Error during LLM call:', error);\n throw error;\n }\n}\n\n/**\n * Build a generic continuation prompt.\n *\n * Creates a prompt that instructs the LLM to continue from where\n * a previous truncated response left off.\n *\n * @param {string} context - Original context/prompt\n * @param {string} partialResponse - The partial response generated so far\n * @param {number} attempt - Current continuation attempt number\n * @param {number} [maxAttempts] - Maximum allowed continuation attempts\n * @returns {string} The formatted continuation prompt\n */\nexport function buildGenericContinuationPrompt(\n context: string,\n partialResponse: string,\n attempt: number,\n maxAttempts: number = DEFAULT_MAX_CONTINUATIONS,\n): string {\n return `## CONTINUATION REQUEST (Attempt ${attempt}/${maxAttempts})\n\nYour previous response was truncated. Continue generating from where you left off.\n\n### ORIGINAL CONTEXT\n${context}\n\n### WHAT YOU GENERATED SO FAR\n\\`\\`\\`json\n${partialResponse}\n\\`\\`\\`\n\n### INSTRUCTIONS\n1. Continue from EXACTLY where the response was cut off\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly\n4. Do NOT wrap your response in markdown code blocks\n\nContinue generating now:`;\n}\n"],"mappings":"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","names":["bracketInfo"]}
@@ -0,0 +1,112 @@
1
+ import * as Chroma from 'chromadb';
2
+ export { Metadata, Where } from 'chromadb';
3
+
4
+ /**
5
+ * Vector-store env resolution + host parsing — pure seams, no chromadb import.
6
+ */
7
+ interface VectorStoreConfig {
8
+ host?: string;
9
+ apiKey?: string;
10
+ tenant?: string;
11
+ database?: string;
12
+ }
13
+ declare function resolveVectorStoreEnv(): VectorStoreConfig;
14
+ interface VectorHostInit {
15
+ host?: string;
16
+ port?: number;
17
+ ssl?: boolean;
18
+ }
19
+ /** Parse a bare host or full URL into chromadb client args: bare host ⇒ port 8000, https default 443. */
20
+ declare function parseVectorHost(host: string): VectorHostInit;
21
+
22
+ /**
23
+ * Query-result shaping — pure seams, no chromadb import (types only, erased).
24
+ */
25
+
26
+ interface VectorHit {
27
+ id: string;
28
+ distance: number;
29
+ score: number;
30
+ metadata?: Chroma.Metadata;
31
+ document?: string;
32
+ }
33
+
34
+ /**
35
+ * VectorCollection — thin wrapper over a live chromadb `Collection`: batched
36
+ * writes, query shaping, and space/metadata read-through. Mechanics only —
37
+ * callers own retry/best-effort policy, so failures propagate as rejections
38
+ * here (unlike the connect/collection-resolution seams, which never throw).
39
+ */
40
+
41
+ interface VectorRecords {
42
+ ids: readonly string[];
43
+ embeddings: readonly (readonly number[])[];
44
+ documents?: readonly string[];
45
+ metadatas?: readonly Chroma.Metadata[];
46
+ }
47
+ interface VectorQueryOptions {
48
+ embedding: readonly number[];
49
+ topK?: number;
50
+ where?: Chroma.Where;
51
+ includeDocuments?: boolean;
52
+ includeMetadatas?: boolean;
53
+ }
54
+ interface VectorDeleteTarget {
55
+ ids?: readonly string[];
56
+ where?: Chroma.Where;
57
+ }
58
+ interface VectorGetOptions {
59
+ where?: Chroma.Where;
60
+ /** Default true. */
61
+ includeMetadatas?: boolean;
62
+ /** Default false — privacy default: raw node text is never returned unless explicitly asked for. */
63
+ includeDocuments?: boolean;
64
+ limit?: number;
65
+ }
66
+ interface VectorRecord {
67
+ id: string;
68
+ metadata?: Chroma.Metadata | undefined;
69
+ document?: string | undefined;
70
+ }
71
+ interface VectorCollection {
72
+ upsert(records: VectorRecords): Promise<number>;
73
+ add(records: VectorRecords): Promise<number>;
74
+ query(opts: VectorQueryOptions): Promise<VectorHit[]>;
75
+ /** Fetch records by metadata filter (no embedding / no similarity). Defaults to metadatas-only. */
76
+ get(opts?: VectorGetOptions): Promise<VectorRecord[]>;
77
+ delete(target: VectorDeleteTarget): Promise<void>;
78
+ count(): Promise<number>;
79
+ /** Every id in the collection (paged get, vectors not fetched) — backs mirror-sync reconciliation. */
80
+ listIds(): Promise<string[]>;
81
+ readonly metadata: Chroma.CollectionMetadata | undefined;
82
+ readonly space: string | null;
83
+ }
84
+
85
+ /**
86
+ * Vector-store connection + collection resolution: env → chromadb client
87
+ * (cached per process, per distinct config), plus the space-mismatch policy
88
+ * that guards a `collection()` call from silently ranking/writing under the
89
+ * wrong index space.
90
+ */
91
+
92
+ type VectorSpace = 'cosine' | 'l2' | 'ip';
93
+ interface VectorCollectionSpec {
94
+ name: string;
95
+ space?: VectorSpace;
96
+ metadata?: Chroma.CollectionMetadata;
97
+ /** 'recreate' drops + rebuilds a wrong-space collection (derived/rebuildable data only). Default 'reject' never auto-drops a consumer's data. */
98
+ onSpaceMismatch?: 'recreate' | 'reject';
99
+ }
100
+ interface VectorStoreHandle {
101
+ /** Human-readable connection target, for logs. */
102
+ readonly target: string;
103
+ collection(spec: VectorCollectionSpec): Promise<VectorCollection | null>;
104
+ getCollection(name: string): Promise<VectorCollection | null>;
105
+ deleteCollection(name: string): Promise<boolean>;
106
+ heartbeat(): Promise<boolean>;
107
+ }
108
+ /** Test-only reset — clears the per-process handle cache between cases. */
109
+ declare function resetVectorStoreCache(): void;
110
+ declare function connectVectorStore(config: VectorStoreConfig): Promise<VectorStoreHandle | null>;
111
+
112
+ export { type VectorCollection, type VectorCollectionSpec, type VectorGetOptions, type VectorHit, type VectorQueryOptions, type VectorRecord, type VectorRecords, type VectorStoreConfig, type VectorStoreHandle, connectVectorStore, parseVectorHost, resetVectorStoreCache, resolveVectorStoreEnv };
@@ -0,0 +1,302 @@
1
+ // src/vector/env.ts
2
+ function cleanEnvValue(value) {
3
+ const trimmed = value?.trim();
4
+ return trimmed ? trimmed : void 0;
5
+ }
6
+ function resolveVectorStoreEnv() {
7
+ const env = process.env;
8
+ return {
9
+ host: cleanEnvValue(env.ALMADAR_CHROMA_HOST),
10
+ apiKey: cleanEnvValue(env.ALMADAR_CHROMA_API_KEY),
11
+ tenant: cleanEnvValue(env.ALMADAR_CHROMA_TENANT),
12
+ database: cleanEnvValue(env.ALMADAR_CHROMA_DATABASE)
13
+ };
14
+ }
15
+ function parseVectorHost(host) {
16
+ const init = {};
17
+ try {
18
+ const url = new URL(host.includes("://") ? host : `http://${host}`);
19
+ init.host = url.hostname;
20
+ init.port = url.port ? parseInt(url.port, 10) : url.protocol === "https:" ? 443 : 8e3;
21
+ init.ssl = url.protocol === "https:";
22
+ } catch {
23
+ init.host = host;
24
+ }
25
+ return init;
26
+ }
27
+
28
+ // src/vector/store.ts
29
+ import { createLogger } from "@almadar/logger";
30
+
31
+ // src/vector/shape.ts
32
+ var DEFAULT_QUERY_TOP_K = 10;
33
+ function normalizeVectorTopK(topK) {
34
+ if (typeof topK !== "number" || !Number.isFinite(topK) || topK <= 0) return DEFAULT_QUERY_TOP_K;
35
+ return Math.floor(topK);
36
+ }
37
+ function clamp01(value) {
38
+ if (value < 0) return 0;
39
+ if (value > 1) return 1;
40
+ return value;
41
+ }
42
+ function shapeVectorHits(result) {
43
+ const ids = result.ids[0] ?? [];
44
+ const distances = result.distances[0] ?? [];
45
+ const metadatas = result.metadatas?.[0] ?? [];
46
+ const documents = result.documents?.[0] ?? [];
47
+ const hits = [];
48
+ for (let i = 0; i < ids.length; i++) {
49
+ const id = ids[i];
50
+ if (typeof id !== "string") continue;
51
+ const rawDistance = distances[i];
52
+ const distance = typeof rawDistance === "number" ? rawDistance : 1;
53
+ const score = clamp01(1 - distance);
54
+ const metadata = metadatas[i];
55
+ const document = documents[i];
56
+ hits.push({
57
+ id,
58
+ distance,
59
+ score,
60
+ ...metadata ? { metadata } : {},
61
+ ...typeof document === "string" ? { document } : {}
62
+ });
63
+ }
64
+ hits.sort((a, b) => b.score - a.score);
65
+ return hits;
66
+ }
67
+
68
+ // src/vector/collection.ts
69
+ var WRITE_BATCH_SIZE = 500;
70
+ async function writeInBatches(write, records) {
71
+ const { ids, embeddings, documents, metadatas } = records;
72
+ let written = 0;
73
+ for (let start = 0; start < ids.length; start += WRITE_BATCH_SIZE) {
74
+ const end = Math.min(start + WRITE_BATCH_SIZE, ids.length);
75
+ await write({
76
+ ids: ids.slice(start, end),
77
+ embeddings: embeddings.slice(start, end).map((vec) => [...vec]),
78
+ ...documents ? { documents: documents.slice(start, end) } : {},
79
+ ...metadatas ? { metadatas: metadatas.slice(start, end) } : {}
80
+ });
81
+ written += end - start;
82
+ }
83
+ return written;
84
+ }
85
+ function wrapVectorCollection(raw) {
86
+ return {
87
+ upsert(records) {
88
+ return writeInBatches((batch) => raw.upsert(batch), records);
89
+ },
90
+ add(records) {
91
+ return writeInBatches((batch) => raw.add(batch), records);
92
+ },
93
+ async query(opts) {
94
+ const include = ["distances"];
95
+ if (opts.includeMetadatas) include.push("metadatas");
96
+ if (opts.includeDocuments) include.push("documents");
97
+ const result = await raw.query({
98
+ queryEmbeddings: [[...opts.embedding]],
99
+ nResults: normalizeVectorTopK(opts.topK),
100
+ where: opts.where,
101
+ include
102
+ });
103
+ return shapeVectorHits(result);
104
+ },
105
+ async delete(target) {
106
+ await raw.delete({
107
+ ...target.ids ? { ids: [...target.ids] } : {},
108
+ ...target.where ? { where: target.where } : {}
109
+ });
110
+ },
111
+ async get(opts = {}) {
112
+ const include = [];
113
+ if (opts.includeMetadatas !== false) include.push("metadatas");
114
+ if (opts.includeDocuments) include.push("documents");
115
+ const records = [];
116
+ const PAGE = 1e4;
117
+ const hardLimit = opts.limit ?? Number.MAX_SAFE_INTEGER;
118
+ for (let offset = 0; offset < hardLimit; offset += PAGE) {
119
+ const pageLimit = Math.min(PAGE, hardLimit - offset);
120
+ const page = await raw.get({
121
+ ...opts.where ? { where: opts.where } : {},
122
+ include,
123
+ limit: pageLimit,
124
+ offset
125
+ });
126
+ const wantMeta = include.includes("metadatas");
127
+ const wantDoc = include.includes("documents");
128
+ for (let i = 0; i < page.ids.length; i++) {
129
+ records.push({
130
+ id: page.ids[i],
131
+ ...wantMeta ? { metadata: page.metadatas?.[i] ?? void 0 } : {},
132
+ ...wantDoc ? { document: page.documents?.[i] ?? void 0 } : {}
133
+ });
134
+ }
135
+ if (page.ids.length < pageLimit) break;
136
+ }
137
+ return records;
138
+ },
139
+ count() {
140
+ return raw.count();
141
+ },
142
+ async listIds() {
143
+ const ids = [];
144
+ const PAGE = 1e4;
145
+ for (let offset = 0; ; offset += PAGE) {
146
+ const page = await raw.get({ include: [], limit: PAGE, offset });
147
+ ids.push(...page.ids);
148
+ if (page.ids.length < PAGE) break;
149
+ }
150
+ return ids;
151
+ },
152
+ get metadata() {
153
+ return raw.metadata;
154
+ },
155
+ get space() {
156
+ return raw.configuration.hnsw?.space ?? null;
157
+ }
158
+ };
159
+ }
160
+
161
+ // src/vector/store.ts
162
+ var vectorLog = createLogger("almadar:llm:vector");
163
+ function errorMessage(err) {
164
+ return err instanceof Error ? err.message : String(err);
165
+ }
166
+ var stubEmbeddingFunction = {
167
+ // Callers always supply pre-computed vectors; the stub stops chromadb from
168
+ // instantiating its own DefaultEmbeddingFunction.
169
+ generate: async () => []
170
+ };
171
+ var DEFAULT_ON_SPACE_MISMATCH = "reject";
172
+ function isEmptyConfig(config) {
173
+ return !config.host && !config.apiKey;
174
+ }
175
+ function configCacheKey(config) {
176
+ return JSON.stringify([config.host ?? null, config.apiKey ?? null, config.tenant ?? null, config.database ?? null]);
177
+ }
178
+ async function loadChromaModule() {
179
+ try {
180
+ return await import("chromadb");
181
+ } catch (err) {
182
+ vectorLog.warn("store:module-missing", () => ({
183
+ error: errorMessage(err),
184
+ hint: "add chromadb to dependencies and run pnpm install"
185
+ }));
186
+ return null;
187
+ }
188
+ }
189
+ async function resolveCollection(client, spec) {
190
+ const create = () => client.getOrCreateCollection({
191
+ name: spec.name,
192
+ embeddingFunction: stubEmbeddingFunction,
193
+ ...spec.space ? { configuration: { hnsw: { space: spec.space } } } : {},
194
+ ...spec.metadata ? { metadata: spec.metadata } : {}
195
+ });
196
+ let raw;
197
+ try {
198
+ raw = await create();
199
+ } catch (err) {
200
+ vectorLog.warn("collection:create-failed", () => ({ name: spec.name, error: errorMessage(err) }));
201
+ return null;
202
+ }
203
+ if (!spec.space) return wrapVectorCollection(raw);
204
+ const foundSpace = raw.configuration.hnsw?.space ?? null;
205
+ if (foundSpace === spec.space) return wrapVectorCollection(raw);
206
+ const policy = spec.onSpaceMismatch ?? DEFAULT_ON_SPACE_MISMATCH;
207
+ if (policy === "reject") {
208
+ vectorLog.warn("collection:space-mismatch", () => ({
209
+ name: spec.name,
210
+ declaredSpace: spec.space,
211
+ foundSpace,
212
+ policy
213
+ }));
214
+ return null;
215
+ }
216
+ vectorLog.warn("collection:space-heal", () => ({ name: spec.name, declaredSpace: spec.space, foundSpace }));
217
+ await client.deleteCollection({ name: spec.name });
218
+ try {
219
+ raw = await create();
220
+ } catch (err) {
221
+ vectorLog.warn("collection:create-failed", () => ({ name: spec.name, error: errorMessage(err) }));
222
+ return null;
223
+ }
224
+ return wrapVectorCollection(raw);
225
+ }
226
+ function buildVectorStoreHandle(client, target) {
227
+ return {
228
+ target,
229
+ collection(spec) {
230
+ return resolveCollection(client, spec);
231
+ },
232
+ async getCollection(name) {
233
+ try {
234
+ const raw = await client.getCollection({ name, embeddingFunction: stubEmbeddingFunction });
235
+ return wrapVectorCollection(raw);
236
+ } catch (err) {
237
+ vectorLog.debug("collection:get-missing", () => ({ name, error: errorMessage(err) }));
238
+ return null;
239
+ }
240
+ },
241
+ async deleteCollection(name) {
242
+ try {
243
+ await client.deleteCollection({ name });
244
+ return true;
245
+ } catch (err) {
246
+ vectorLog.warn("collection:delete-failed", () => ({ name, error: errorMessage(err) }));
247
+ return false;
248
+ }
249
+ },
250
+ async heartbeat() {
251
+ try {
252
+ await client.heartbeat();
253
+ return true;
254
+ } catch {
255
+ return false;
256
+ }
257
+ }
258
+ };
259
+ }
260
+ var vectorStoreCache = /* @__PURE__ */ new Map();
261
+ function resetVectorStoreCache() {
262
+ vectorStoreCache.clear();
263
+ }
264
+ async function connectVectorStore(config) {
265
+ if (isEmptyConfig(config)) {
266
+ vectorLog.debug("store:skip", () => ({ reason: "config-empty" }));
267
+ return null;
268
+ }
269
+ const key = configCacheKey(config);
270
+ const cached = vectorStoreCache.get(key);
271
+ if (cached) return cached;
272
+ const mod = await loadChromaModule();
273
+ if (!mod) return null;
274
+ let client;
275
+ let target;
276
+ if (config.apiKey) {
277
+ client = new mod.CloudClient({ apiKey: config.apiKey, tenant: config.tenant, database: config.database });
278
+ target = `cloud(tenant=${config.tenant ?? "default"}, db=${config.database ?? "default"})`;
279
+ } else if (config.host) {
280
+ client = new mod.ChromaClient(parseVectorHost(config.host));
281
+ target = config.host;
282
+ } else {
283
+ return null;
284
+ }
285
+ try {
286
+ await client.heartbeat();
287
+ } catch (err) {
288
+ vectorLog.warn("store:heartbeat-failed", () => ({ target, error: errorMessage(err) }));
289
+ return null;
290
+ }
291
+ const handle = buildVectorStoreHandle(client, target);
292
+ vectorStoreCache.set(key, handle);
293
+ vectorLog.info("store:ready", () => ({ target }));
294
+ return handle;
295
+ }
296
+ export {
297
+ connectVectorStore,
298
+ parseVectorHost,
299
+ resetVectorStoreCache,
300
+ resolveVectorStoreEnv
301
+ };
302
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}