hazo_llm_api 1.2.13 → 2.0.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.
Files changed (247) hide show
  1. package/README.md +321 -8
  2. package/config/hazo_llm_api_config.ini +38 -0
  3. package/dist/components/index.d.ts +2 -0
  4. package/dist/components/index.d.ts.map +1 -1
  5. package/dist/components/index.js +2 -0
  6. package/dist/components/index.js.map +1 -1
  7. package/dist/components/llm_call_inspector/index.d.ts +6 -0
  8. package/dist/components/llm_call_inspector/index.d.ts.map +1 -0
  9. package/dist/components/llm_call_inspector/index.js +5 -0
  10. package/dist/components/llm_call_inspector/index.js.map +1 -0
  11. package/dist/components/llm_call_inspector/llm_call_inspector.d.ts +18 -0
  12. package/dist/components/llm_call_inspector/llm_call_inspector.d.ts.map +1 -0
  13. package/dist/components/llm_call_inspector/llm_call_inspector.js +103 -0
  14. package/dist/components/llm_call_inspector/llm_call_inspector.js.map +1 -0
  15. package/dist/components/llm_cost_dashboard/index.d.ts +6 -0
  16. package/dist/components/llm_cost_dashboard/index.d.ts.map +1 -0
  17. package/dist/components/llm_cost_dashboard/index.js +5 -0
  18. package/dist/components/llm_cost_dashboard/index.js.map +1 -0
  19. package/dist/components/llm_cost_dashboard/llm_cost_dashboard.d.ts +16 -0
  20. package/dist/components/llm_cost_dashboard/llm_cost_dashboard.d.ts.map +1 -0
  21. package/dist/components/llm_cost_dashboard/llm_cost_dashboard.js +154 -0
  22. package/dist/components/llm_cost_dashboard/llm_cost_dashboard.js.map +1 -0
  23. package/dist/components/prompt_editor/prompt_editor.d.ts.map +1 -1
  24. package/dist/components/prompt_editor/prompt_editor.js +3 -2
  25. package/dist/components/prompt_editor/prompt_editor.js.map +1 -1
  26. package/dist/components/prompt_editor/ui/alert_dialog.js +1 -1
  27. package/dist/components/prompt_editor/ui/alert_dialog.js.map +1 -1
  28. package/dist/components/prompt_editor/ui/button.js +1 -1
  29. package/dist/components/prompt_editor/ui/button.js.map +1 -1
  30. package/dist/components/prompt_editor/ui/checkbox.js +1 -1
  31. package/dist/components/prompt_editor/ui/checkbox.js.map +1 -1
  32. package/dist/components/prompt_editor/ui/dialog.js +1 -1
  33. package/dist/components/prompt_editor/ui/dialog.js.map +1 -1
  34. package/dist/components/prompt_editor/ui/index.d.ts +1 -1
  35. package/dist/components/prompt_editor/ui/index.d.ts.map +1 -1
  36. package/dist/components/prompt_editor/ui/index.js +1 -1
  37. package/dist/components/prompt_editor/ui/index.js.map +1 -1
  38. package/dist/components/prompt_editor/ui/input.js +1 -1
  39. package/dist/components/prompt_editor/ui/input.js.map +1 -1
  40. package/dist/components/prompt_editor/ui/textarea.js +1 -1
  41. package/dist/components/prompt_editor/ui/textarea.js.map +1 -1
  42. package/dist/components/prompt_editor/ui/tooltip.js +1 -1
  43. package/dist/components/prompt_editor/ui/tooltip.js.map +1 -1
  44. package/dist/index.d.ts +2 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/lib/cascade/cascade_runner.d.ts +50 -0
  47. package/dist/lib/cascade/cascade_runner.d.ts.map +1 -0
  48. package/dist/lib/cascade/cascade_runner.js +115 -0
  49. package/dist/lib/cascade/cascade_runner.js.map +1 -0
  50. package/dist/lib/cascade/index.d.ts +5 -0
  51. package/dist/lib/cascade/index.d.ts.map +1 -0
  52. package/dist/lib/cascade/index.js +4 -0
  53. package/dist/lib/cascade/index.js.map +1 -0
  54. package/dist/lib/cascade/types.d.ts +35 -0
  55. package/dist/lib/cascade/types.d.ts.map +1 -0
  56. package/dist/lib/cascade/types.js +14 -0
  57. package/dist/lib/cascade/types.js.map +1 -0
  58. package/dist/lib/config/index.d.ts +1 -0
  59. package/dist/lib/config/index.d.ts.map +1 -1
  60. package/dist/lib/config/index.js +3 -0
  61. package/dist/lib/config/index.js.map +1 -1
  62. package/dist/lib/config/llm_api_config.d.ts +42 -0
  63. package/dist/lib/config/llm_api_config.d.ts.map +1 -0
  64. package/dist/lib/config/llm_api_config.js +49 -0
  65. package/dist/lib/config/llm_api_config.js.map +1 -0
  66. package/dist/lib/cost_cap/cost_cap.d.ts +40 -0
  67. package/dist/lib/cost_cap/cost_cap.d.ts.map +1 -0
  68. package/dist/lib/cost_cap/cost_cap.js +150 -0
  69. package/dist/lib/cost_cap/cost_cap.js.map +1 -0
  70. package/dist/lib/cost_cap/index.d.ts +3 -0
  71. package/dist/lib/cost_cap/index.d.ts.map +1 -0
  72. package/dist/lib/cost_cap/index.js +2 -0
  73. package/dist/lib/cost_cap/index.js.map +1 -0
  74. package/dist/lib/database/init_api_log.d.ts +10 -0
  75. package/dist/lib/database/init_api_log.d.ts.map +1 -0
  76. package/dist/lib/database/init_api_log.js +91 -0
  77. package/dist/lib/database/init_api_log.js.map +1 -0
  78. package/dist/lib/database/init_database.d.ts.map +1 -1
  79. package/dist/lib/database/init_database.js +2 -1
  80. package/dist/lib/database/init_database.js.map +1 -1
  81. package/dist/lib/hazo_connect/direct_db_connect.d.ts +11 -7
  82. package/dist/lib/hazo_connect/direct_db_connect.d.ts.map +1 -1
  83. package/dist/lib/hazo_connect/direct_db_connect.js +61 -7
  84. package/dist/lib/hazo_connect/direct_db_connect.js.map +1 -1
  85. package/dist/lib/hazo_connect/types.d.ts +35 -3
  86. package/dist/lib/hazo_connect/types.d.ts.map +1 -1
  87. package/dist/lib/llm_api/embed_cache.d.ts +15 -0
  88. package/dist/lib/llm_api/embed_cache.d.ts.map +1 -0
  89. package/dist/lib/llm_api/embed_cache.js +53 -0
  90. package/dist/lib/llm_api/embed_cache.js.map +1 -0
  91. package/dist/lib/llm_api/hazo_llm_document_text.d.ts.map +1 -1
  92. package/dist/lib/llm_api/hazo_llm_document_text.js +56 -14
  93. package/dist/lib/llm_api/hazo_llm_document_text.js.map +1 -1
  94. package/dist/lib/llm_api/hazo_llm_dynamic_data_extract.d.ts.map +1 -1
  95. package/dist/lib/llm_api/hazo_llm_dynamic_data_extract.js +19 -1
  96. package/dist/lib/llm_api/hazo_llm_dynamic_data_extract.js.map +1 -1
  97. package/dist/lib/llm_api/hazo_llm_embed.d.ts +10 -0
  98. package/dist/lib/llm_api/hazo_llm_embed.d.ts.map +1 -0
  99. package/dist/lib/llm_api/hazo_llm_embed.js +80 -0
  100. package/dist/lib/llm_api/hazo_llm_embed.js.map +1 -0
  101. package/dist/lib/llm_api/hazo_llm_image_image.d.ts.map +1 -1
  102. package/dist/lib/llm_api/hazo_llm_image_image.js +56 -14
  103. package/dist/lib/llm_api/hazo_llm_image_image.js.map +1 -1
  104. package/dist/lib/llm_api/hazo_llm_image_text.d.ts.map +1 -1
  105. package/dist/lib/llm_api/hazo_llm_image_text.js +56 -14
  106. package/dist/lib/llm_api/hazo_llm_image_text.js.map +1 -1
  107. package/dist/lib/llm_api/hazo_llm_prompt_chain.d.ts.map +1 -1
  108. package/dist/lib/llm_api/hazo_llm_prompt_chain.js +19 -2
  109. package/dist/lib/llm_api/hazo_llm_prompt_chain.js.map +1 -1
  110. package/dist/lib/llm_api/hazo_llm_text_image.d.ts.map +1 -1
  111. package/dist/lib/llm_api/hazo_llm_text_image.js +56 -14
  112. package/dist/lib/llm_api/hazo_llm_text_image.js.map +1 -1
  113. package/dist/lib/llm_api/hazo_llm_text_text.d.ts.map +1 -1
  114. package/dist/lib/llm_api/hazo_llm_text_text.js +90 -15
  115. package/dist/lib/llm_api/hazo_llm_text_text.js.map +1 -1
  116. package/dist/lib/llm_api/index.d.ts +31 -3
  117. package/dist/lib/llm_api/index.d.ts.map +1 -1
  118. package/dist/lib/llm_api/index.js +427 -35
  119. package/dist/lib/llm_api/index.js.map +1 -1
  120. package/dist/lib/llm_api/prompt_parts_helper.d.ts +15 -0
  121. package/dist/lib/llm_api/prompt_parts_helper.d.ts.map +1 -0
  122. package/dist/lib/llm_api/prompt_parts_helper.js +9 -0
  123. package/dist/lib/llm_api/prompt_parts_helper.js.map +1 -0
  124. package/dist/lib/llm_api/provider_helper.d.ts.map +1 -1
  125. package/dist/lib/llm_api/provider_helper.js +3 -38
  126. package/dist/lib/llm_api/provider_helper.js.map +1 -1
  127. package/dist/lib/llm_api/types.d.ts +187 -2
  128. package/dist/lib/llm_api/types.d.ts.map +1 -1
  129. package/dist/lib/llm_api/types.js +4 -0
  130. package/dist/lib/llm_api/types.js.map +1 -1
  131. package/dist/lib/maintenance/purge_log_job.d.ts +23 -0
  132. package/dist/lib/maintenance/purge_log_job.d.ts.map +1 -0
  133. package/dist/lib/maintenance/purge_log_job.js +49 -0
  134. package/dist/lib/maintenance/purge_log_job.js.map +1 -0
  135. package/dist/lib/observability/log_context.d.ts +15 -0
  136. package/dist/lib/observability/log_context.d.ts.map +1 -0
  137. package/dist/lib/observability/log_context.js +32 -0
  138. package/dist/lib/observability/log_context.js.map +1 -0
  139. package/dist/lib/observability/log_writer.d.ts +35 -0
  140. package/dist/lib/observability/log_writer.d.ts.map +1 -0
  141. package/dist/lib/observability/log_writer.js +106 -0
  142. package/dist/lib/observability/log_writer.js.map +1 -0
  143. package/dist/lib/observability/queries.d.ts +15 -0
  144. package/dist/lib/observability/queries.d.ts.map +1 -0
  145. package/dist/lib/observability/queries.js +78 -0
  146. package/dist/lib/observability/queries.js.map +1 -0
  147. package/dist/lib/observability/types.d.ts +77 -0
  148. package/dist/lib/observability/types.d.ts.map +1 -0
  149. package/dist/lib/observability/types.js +8 -0
  150. package/dist/lib/observability/types.js.map +1 -0
  151. package/dist/lib/pricing/pricing.d.ts +49 -0
  152. package/dist/lib/pricing/pricing.d.ts.map +1 -0
  153. package/dist/lib/pricing/pricing.js +153 -0
  154. package/dist/lib/pricing/pricing.js.map +1 -0
  155. package/dist/lib/pricing/pricing.json +75 -0
  156. package/dist/lib/pricing/types.d.ts +58 -0
  157. package/dist/lib/pricing/types.d.ts.map +1 -0
  158. package/dist/lib/pricing/types.js +8 -0
  159. package/dist/lib/pricing/types.js.map +1 -0
  160. package/dist/lib/providers/anthropic/anthropic_client.d.ts +71 -0
  161. package/dist/lib/providers/anthropic/anthropic_client.d.ts.map +1 -0
  162. package/dist/lib/providers/anthropic/anthropic_client.js +135 -0
  163. package/dist/lib/providers/anthropic/anthropic_client.js.map +1 -0
  164. package/dist/lib/providers/anthropic/anthropic_provider.d.ts +60 -0
  165. package/dist/lib/providers/anthropic/anthropic_provider.d.ts.map +1 -0
  166. package/dist/lib/providers/anthropic/anthropic_provider.js +273 -0
  167. package/dist/lib/providers/anthropic/anthropic_provider.js.map +1 -0
  168. package/dist/lib/providers/anthropic/anthropic_response_to_usage.d.ts +21 -0
  169. package/dist/lib/providers/anthropic/anthropic_response_to_usage.d.ts.map +1 -0
  170. package/dist/lib/providers/anthropic/anthropic_response_to_usage.js +46 -0
  171. package/dist/lib/providers/anthropic/anthropic_response_to_usage.js.map +1 -0
  172. package/dist/lib/providers/anthropic/index.d.ts +3 -0
  173. package/dist/lib/providers/anthropic/index.d.ts.map +1 -0
  174. package/dist/lib/providers/anthropic/index.js +2 -0
  175. package/dist/lib/providers/anthropic/index.js.map +1 -0
  176. package/dist/lib/providers/deepseek/deepseek_client.d.ts +55 -0
  177. package/dist/lib/providers/deepseek/deepseek_client.d.ts.map +1 -0
  178. package/dist/lib/providers/deepseek/deepseek_client.js +130 -0
  179. package/dist/lib/providers/deepseek/deepseek_client.js.map +1 -0
  180. package/dist/lib/providers/deepseek/deepseek_provider.d.ts +50 -0
  181. package/dist/lib/providers/deepseek/deepseek_provider.d.ts.map +1 -0
  182. package/dist/lib/providers/deepseek/deepseek_provider.js +147 -0
  183. package/dist/lib/providers/deepseek/deepseek_provider.js.map +1 -0
  184. package/dist/lib/providers/deepseek/deepseek_response_to_usage.d.ts +21 -0
  185. package/dist/lib/providers/deepseek/deepseek_response_to_usage.d.ts.map +1 -0
  186. package/dist/lib/providers/deepseek/deepseek_response_to_usage.js +40 -0
  187. package/dist/lib/providers/deepseek/deepseek_response_to_usage.js.map +1 -0
  188. package/dist/lib/providers/deepseek/index.d.ts +3 -0
  189. package/dist/lib/providers/deepseek/index.d.ts.map +1 -0
  190. package/dist/lib/providers/deepseek/index.js +2 -0
  191. package/dist/lib/providers/deepseek/index.js.map +1 -0
  192. package/dist/lib/providers/gemini/gemini_client.d.ts.map +1 -1
  193. package/dist/lib/providers/gemini/gemini_client.js +2 -1
  194. package/dist/lib/providers/gemini/gemini_client.js.map +1 -1
  195. package/dist/lib/providers/gemini/gemini_provider.d.ts.map +1 -1
  196. package/dist/lib/providers/gemini/gemini_provider.js +43 -6
  197. package/dist/lib/providers/gemini/gemini_provider.js.map +1 -1
  198. package/dist/lib/providers/gemini/gemini_response_to_usage.d.ts +37 -0
  199. package/dist/lib/providers/gemini/gemini_response_to_usage.d.ts.map +1 -0
  200. package/dist/lib/providers/gemini/gemini_response_to_usage.js +49 -0
  201. package/dist/lib/providers/gemini/gemini_response_to_usage.js.map +1 -0
  202. package/dist/lib/providers/index.d.ts +3 -0
  203. package/dist/lib/providers/index.d.ts.map +1 -1
  204. package/dist/lib/providers/index.js +3 -0
  205. package/dist/lib/providers/index.js.map +1 -1
  206. package/dist/lib/providers/openai/index.d.ts +3 -0
  207. package/dist/lib/providers/openai/index.d.ts.map +1 -0
  208. package/dist/lib/providers/openai/index.js +2 -0
  209. package/dist/lib/providers/openai/index.js.map +1 -0
  210. package/dist/lib/providers/openai/openai_client.d.ts +99 -0
  211. package/dist/lib/providers/openai/openai_client.d.ts.map +1 -0
  212. package/dist/lib/providers/openai/openai_client.js +188 -0
  213. package/dist/lib/providers/openai/openai_client.js.map +1 -0
  214. package/dist/lib/providers/openai/openai_provider.d.ts +66 -0
  215. package/dist/lib/providers/openai/openai_provider.d.ts.map +1 -0
  216. package/dist/lib/providers/openai/openai_provider.js +297 -0
  217. package/dist/lib/providers/openai/openai_provider.js.map +1 -0
  218. package/dist/lib/providers/openai/openai_response_to_usage.d.ts +21 -0
  219. package/dist/lib/providers/openai/openai_response_to_usage.d.ts.map +1 -0
  220. package/dist/lib/providers/openai/openai_response_to_usage.js +50 -0
  221. package/dist/lib/providers/openai/openai_response_to_usage.js.map +1 -0
  222. package/dist/lib/providers/qwen/qwen_client.d.ts.map +1 -1
  223. package/dist/lib/providers/qwen/qwen_client.js +10 -9
  224. package/dist/lib/providers/qwen/qwen_client.js.map +1 -1
  225. package/dist/lib/providers/qwen/qwen_provider.d.ts.map +1 -1
  226. package/dist/lib/providers/qwen/qwen_provider.js +58 -8
  227. package/dist/lib/providers/qwen/qwen_provider.js.map +1 -1
  228. package/dist/lib/providers/qwen/qwen_response_to_usage.d.ts +36 -0
  229. package/dist/lib/providers/qwen/qwen_response_to_usage.d.ts.map +1 -0
  230. package/dist/lib/providers/qwen/qwen_response_to_usage.js +50 -0
  231. package/dist/lib/providers/qwen/qwen_response_to_usage.js.map +1 -0
  232. package/dist/lib/providers/types.d.ts +16 -6
  233. package/dist/lib/providers/types.d.ts.map +1 -1
  234. package/dist/lib/providers/types.js +1 -0
  235. package/dist/lib/providers/types.js.map +1 -1
  236. package/dist/lib/utils.d.ts +13 -0
  237. package/dist/lib/utils.d.ts.map +1 -0
  238. package/dist/lib/utils.js +16 -0
  239. package/dist/lib/utils.js.map +1 -0
  240. package/dist/server.d.ts +16 -2
  241. package/dist/server.d.ts.map +1 -1
  242. package/dist/server.js +29 -2
  243. package/dist/server.js.map +1 -1
  244. package/migrations/db_setup_postgres.sql +68 -0
  245. package/migrations/db_setup_sqlite.sql +66 -0
  246. package/migrations/hazo_llm_api_log.sql +72 -0
  247. package/package.json +24 -6
@@ -25,11 +25,20 @@ import { hazo_llm_image_image_text as hazo_llm_image_image_text_internal } from
25
25
  import { hazo_llm_prompt_chain as hazo_llm_prompt_chain_internal } from './hazo_llm_prompt_chain.js';
26
26
  import { hazo_llm_document_text as hazo_llm_document_text_internal } from './hazo_llm_document_text.js';
27
27
  import { hazo_llm_dynamic_data_extract as hazo_llm_dynamic_data_extract_internal } from './hazo_llm_dynamic_data_extract.js';
28
+ import { hazo_llm_embed_internal } from './hazo_llm_embed.js';
29
+ import { configure_embed_cache } from './embed_cache.js';
28
30
  import { register_provider, set_enabled_llms, set_primary_llm, get_primary_llm, get_provider, } from '../providers/registry.js';
29
31
  import { GeminiProvider } from '../providers/gemini/index.js';
30
32
  import { QwenProvider } from '../providers/qwen/index.js';
33
+ import { AnthropicProvider } from '../providers/anthropic/index.js';
34
+ import { OpenAIProvider } from '../providers/openai/index.js';
35
+ import { DeepSeekProvider } from '../providers/deepseek/index.js';
31
36
  import { SERVICE_TYPES } from '../providers/types.js';
32
37
  import { create_request_context, call_before_request_hook, call_after_response_hook, call_on_error_hook, detect_error_code, } from './provider_helper.js';
38
+ import { ensure_api_log_table } from '../database/init_api_log.js';
39
+ import { create_default_after_response_hook } from '../observability/log_writer.js';
40
+ import { load_pricing, set_pricing_overrides } from '../pricing/pricing.js';
41
+ import { createLogger, HazoConfigError, HazoInternalError } from 'hazo_core';
33
42
  import * as fs from 'fs';
34
43
  import * as path from 'path';
35
44
  import * as ini from 'ini';
@@ -40,27 +49,16 @@ let initialized = false;
40
49
  let db_auto_initialized = false;
41
50
  let current_config = null;
42
51
  let stored_hazo_connect = null;
52
+ let stored_cascade_config = null;
53
+ let stored_cost_cap_config = null;
43
54
  // =============================================================================
44
55
  // Default Logger
45
56
  // =============================================================================
46
57
  /**
47
- * Default console logger used when no custom logger is provided
48
- * Can be used directly or as a fallback in functions
58
+ * Default structured logger used when no custom logger is provided.
59
+ * Delegates to hazo_logs when available, auto-injects correlationId and env.
49
60
  */
50
- export const default_logger = {
51
- error: (message, meta) => {
52
- console.error(`[HAZO_LLM_API ERROR] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
53
- },
54
- info: (message, meta) => {
55
- console.log(`[HAZO_LLM_API INFO] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
56
- },
57
- warn: (message, meta) => {
58
- console.warn(`[HAZO_LLM_API WARN] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
59
- },
60
- debug: (message, meta) => {
61
- console.debug(`[HAZO_LLM_API DEBUG] ${message}`, meta ? JSON.stringify(meta, null, 2) : '');
62
- },
63
- };
61
+ export const default_logger = createLogger('hazo_llm_api');
64
62
  /**
65
63
  * Stored logger instance - set during initialization
66
64
  */
@@ -403,7 +401,7 @@ function load_api_key_from_env(provider_name) {
403
401
  }
404
402
  /**
405
403
  * Read LLM global config from [llm] section
406
- * @returns Object with enabled_llms and primary_llm
404
+ * @returns Object with enabled_llms, primary_llm, sqlite_path, and optional cascade config
407
405
  */
408
406
  function get_llm_global_config() {
409
407
  const config_path = find_config_file();
@@ -424,10 +422,37 @@ function get_llm_global_config() {
424
422
  const enabled_llms = parse_enabled_llms(llm_section.enabled_llms);
425
423
  const primary_llm = llm_section.primary_llm || default_primary;
426
424
  const sqlite_path = llm_section.sqlite_path || default_sqlite;
425
+ const cascade_section = config.llm_cascade;
426
+ let parsed_cascade;
427
+ if (cascade_section) {
428
+ parsed_cascade = {};
429
+ if (cascade_section.providers) {
430
+ parsed_cascade.providers = String(cascade_section.providers)
431
+ .split(',')
432
+ .map((s) => s.trim())
433
+ .filter(Boolean);
434
+ }
435
+ if (cascade_section.timeout_ms_per_attempt) {
436
+ const ms = parseInt(String(cascade_section.timeout_ms_per_attempt), 10);
437
+ if (!isNaN(ms) && ms > 0) {
438
+ parsed_cascade.timeout_ms_per_attempt = ms;
439
+ }
440
+ }
441
+ if (cascade_section.cascade_on_codes) {
442
+ const codes = String(cascade_section.cascade_on_codes)
443
+ .split(',')
444
+ .map((s) => s.trim())
445
+ .filter(Boolean);
446
+ if (codes.length > 0) {
447
+ parsed_cascade.cascade_on_codes = codes;
448
+ }
449
+ }
450
+ }
427
451
  return {
428
452
  enabled_llms: enabled_llms.length > 0 ? enabled_llms : default_enabled,
429
453
  primary_llm,
430
454
  sqlite_path,
455
+ cascade: parsed_cascade,
431
456
  };
432
457
  }
433
458
  catch {
@@ -435,6 +460,7 @@ function get_llm_global_config() {
435
460
  enabled_llms: default_enabled,
436
461
  primary_llm: default_primary,
437
462
  sqlite_path: default_sqlite,
463
+ cascade: undefined,
438
464
  };
439
465
  }
440
466
  }
@@ -448,7 +474,6 @@ function load_gemini_provider_from_config(logger) {
448
474
  if (!config_path) {
449
475
  logger.warn('Config file not found, cannot load Gemini provider', {
450
476
  file: 'index.ts',
451
- line: 340,
452
477
  });
453
478
  return null;
454
479
  }
@@ -462,7 +487,6 @@ function load_gemini_provider_from_config(logger) {
462
487
  if (!api_key) {
463
488
  logger.error(`${env_var_name} not found in environment variables`, {
464
489
  file: 'index.ts',
465
- line: 352,
466
490
  data: { config_path, env_var_name },
467
491
  });
468
492
  return null;
@@ -492,7 +516,6 @@ function load_gemini_provider_from_config(logger) {
492
516
  const error_message = error instanceof Error ? error.message : String(error);
493
517
  logger.error('Failed to load Gemini provider from config', {
494
518
  file: 'index.ts',
495
- line: 378,
496
519
  data: { error: error_message, config_path },
497
520
  });
498
521
  return null;
@@ -607,7 +630,6 @@ function load_qwen_provider_from_config(logger) {
607
630
  if (!config_path) {
608
631
  logger.warn('Config file not found, cannot load Qwen provider', {
609
632
  file: 'index.ts',
610
- line: 500,
611
633
  });
612
634
  return null;
613
635
  }
@@ -621,7 +643,6 @@ function load_qwen_provider_from_config(logger) {
621
643
  if (!api_key) {
622
644
  logger.error(`${env_var_name} not found in environment variables`, {
623
645
  file: 'index.ts',
624
- line: 512,
625
646
  data: { config_path, env_var_name },
626
647
  });
627
648
  return null;
@@ -654,7 +675,211 @@ function load_qwen_provider_from_config(logger) {
654
675
  const error_message = error instanceof Error ? error.message : String(error);
655
676
  logger.error('Failed to load Qwen provider from config', {
656
677
  file: 'index.ts',
657
- line: 543,
678
+ data: { error: error_message, config_path },
679
+ });
680
+ return null;
681
+ }
682
+ }
683
+ /**
684
+ * Load Anthropic provider from config file
685
+ * Reads [llm_anthropic] section + ANTHROPIC_API_KEY env var
686
+ */
687
+ function load_anthropic_provider_from_config(logger) {
688
+ const config_path = find_config_file();
689
+ if (!config_path) {
690
+ logger.warn('Config file not found, cannot load Anthropic provider', {
691
+ file: 'index.ts',
692
+ });
693
+ return null;
694
+ }
695
+ try {
696
+ const config_content = fs.readFileSync(config_path, 'utf-8');
697
+ const config = ini.parse(config_content);
698
+ const section = config.llm_anthropic || {};
699
+ const env_var_name = section.api_key_env || 'ANTHROPIC_API_KEY';
700
+ const api_key = process.env[env_var_name];
701
+ if (!api_key) {
702
+ logger.error(`${env_var_name} not found in environment variables`, {
703
+ file: 'index.ts',
704
+ data: { config_path, env_var_name },
705
+ });
706
+ return null;
707
+ }
708
+ const capabilities = parse_capabilities(section.capabilities);
709
+ const generation_config = {};
710
+ let has_config = false;
711
+ if (section.text_max_tokens !== undefined) {
712
+ const v = parseInt(section.text_max_tokens, 10);
713
+ if (!isNaN(v)) {
714
+ generation_config.max_tokens = v;
715
+ has_config = true;
716
+ }
717
+ }
718
+ if (section.text_temperature !== undefined) {
719
+ const v = parseFloat(section.text_temperature);
720
+ if (!isNaN(v)) {
721
+ generation_config.temperature = v;
722
+ has_config = true;
723
+ }
724
+ }
725
+ const provider_config = {
726
+ api_key,
727
+ api_url: section.api_url,
728
+ api_version: section.api_version,
729
+ model_text_text: section.model_text_text,
730
+ model_image_text: section.model_image_text,
731
+ model_document_text: section.model_document_text,
732
+ generation_config: has_config ? generation_config : undefined,
733
+ capabilities: capabilities.length > 0 ? capabilities : undefined,
734
+ logger,
735
+ };
736
+ return new AnthropicProvider(provider_config);
737
+ }
738
+ catch (error) {
739
+ const error_message = error instanceof Error ? error.message : String(error);
740
+ logger.error('Failed to load Anthropic provider from config', {
741
+ file: 'index.ts',
742
+ data: { error: error_message, config_path },
743
+ });
744
+ return null;
745
+ }
746
+ }
747
+ /**
748
+ * Load OpenAI provider from config file
749
+ * Reads [llm_openai] section + OPENAI_API_KEY env var
750
+ */
751
+ function load_openai_provider_from_config(logger) {
752
+ const config_path = find_config_file();
753
+ if (!config_path) {
754
+ logger.warn('Config file not found, cannot load OpenAI provider', {
755
+ file: 'index.ts',
756
+ });
757
+ return null;
758
+ }
759
+ try {
760
+ const config_content = fs.readFileSync(config_path, 'utf-8');
761
+ const config = ini.parse(config_content);
762
+ const section = config.llm_openai || {};
763
+ const env_var_name = section.api_key_env || 'OPENAI_API_KEY';
764
+ const api_key = process.env[env_var_name];
765
+ if (!api_key) {
766
+ logger.error(`${env_var_name} not found in environment variables`, {
767
+ file: 'index.ts',
768
+ data: { config_path, env_var_name },
769
+ });
770
+ return null;
771
+ }
772
+ const capabilities = parse_capabilities(section.capabilities);
773
+ const generation_config = {};
774
+ let has_config = false;
775
+ if (section.text_temperature !== undefined) {
776
+ const v = parseFloat(section.text_temperature);
777
+ if (!isNaN(v)) {
778
+ generation_config.temperature = v;
779
+ has_config = true;
780
+ }
781
+ }
782
+ if (section.text_top_p !== undefined) {
783
+ const v = parseFloat(section.text_top_p);
784
+ if (!isNaN(v)) {
785
+ generation_config.top_p = v;
786
+ has_config = true;
787
+ }
788
+ }
789
+ if (section.text_max_tokens !== undefined) {
790
+ const v = parseInt(section.text_max_tokens, 10);
791
+ if (!isNaN(v)) {
792
+ generation_config.max_tokens = v;
793
+ has_config = true;
794
+ }
795
+ }
796
+ const provider_config = {
797
+ api_key,
798
+ api_url: section.api_url,
799
+ api_url_image: section.api_url_image,
800
+ api_url_embed: section.api_url_embed,
801
+ model_text_text: section.model_text_text,
802
+ model_image_text: section.model_image_text,
803
+ model_text_image: section.model_text_image,
804
+ model_embed: section.model_embed,
805
+ generation_config: has_config ? generation_config : undefined,
806
+ capabilities: capabilities.length > 0 ? capabilities : undefined,
807
+ logger,
808
+ };
809
+ return new OpenAIProvider(provider_config);
810
+ }
811
+ catch (error) {
812
+ const error_message = error instanceof Error ? error.message : String(error);
813
+ logger.error('Failed to load OpenAI provider from config', {
814
+ file: 'index.ts',
815
+ data: { error: error_message, config_path },
816
+ });
817
+ return null;
818
+ }
819
+ }
820
+ /**
821
+ * Load DeepSeek provider from config file
822
+ * Reads [llm_deepseek] section + DEEPSEEK_API_KEY env var
823
+ */
824
+ function load_deepseek_provider_from_config(logger) {
825
+ const config_path = find_config_file();
826
+ if (!config_path) {
827
+ logger.warn('Config file not found, cannot load DeepSeek provider', {
828
+ file: 'index.ts',
829
+ });
830
+ return null;
831
+ }
832
+ try {
833
+ const config_content = fs.readFileSync(config_path, 'utf-8');
834
+ const config = ini.parse(config_content);
835
+ const section = config.llm_deepseek || {};
836
+ const env_var_name = section.api_key_env || 'DEEPSEEK_API_KEY';
837
+ const api_key = process.env[env_var_name];
838
+ if (!api_key) {
839
+ logger.error(`${env_var_name} not found in environment variables`, {
840
+ file: 'index.ts',
841
+ data: { config_path, env_var_name },
842
+ });
843
+ return null;
844
+ }
845
+ const capabilities = parse_capabilities(section.capabilities);
846
+ const generation_config = {};
847
+ let has_config = false;
848
+ if (section.text_temperature !== undefined) {
849
+ const v = parseFloat(section.text_temperature);
850
+ if (!isNaN(v)) {
851
+ generation_config.temperature = v;
852
+ has_config = true;
853
+ }
854
+ }
855
+ if (section.text_top_p !== undefined) {
856
+ const v = parseFloat(section.text_top_p);
857
+ if (!isNaN(v)) {
858
+ generation_config.top_p = v;
859
+ has_config = true;
860
+ }
861
+ }
862
+ if (section.text_max_tokens !== undefined) {
863
+ const v = parseInt(section.text_max_tokens, 10);
864
+ if (!isNaN(v)) {
865
+ generation_config.max_tokens = v;
866
+ has_config = true;
867
+ }
868
+ }
869
+ const provider_config = {
870
+ api_key,
871
+ api_url: section.api_url,
872
+ model_text_text: section.model_text_text,
873
+ generation_config: has_config ? generation_config : undefined,
874
+ capabilities: capabilities.length > 0 ? capabilities : undefined,
875
+ logger,
876
+ };
877
+ return new DeepSeekProvider(provider_config);
878
+ }
879
+ catch (error) {
880
+ const error_message = error instanceof Error ? error.message : String(error);
881
+ logger.error('Failed to load DeepSeek provider from config', {
882
+ file: 'index.ts',
658
883
  data: { error: error_message, config_path },
659
884
  });
660
885
  return null;
@@ -679,7 +904,6 @@ function load_and_register_providers(logger) {
679
904
  else {
680
905
  logger.warn('Gemini provider is enabled in config but failed to load. Check GEMINI_API_KEY in environment variables.', {
681
906
  file: 'index.ts',
682
- line: 716,
683
907
  data: { llm_name: llm_name.toLowerCase() },
684
908
  });
685
909
  }
@@ -692,12 +916,47 @@ function load_and_register_providers(logger) {
692
916
  else {
693
917
  logger.warn('Qwen provider is enabled in config but failed to load. Check QWEN_API_KEY in environment variables.', {
694
918
  file: 'index.ts',
695
- line: 728,
696
919
  data: { llm_name: llm_name.toLowerCase() },
697
920
  });
698
921
  }
699
922
  }
700
- // Future: Add other providers here (OpenAI, Anthropic, etc.)
923
+ else if (llm_name.toLowerCase() === 'anthropic') {
924
+ const provider = load_anthropic_provider_from_config(logger);
925
+ if (provider) {
926
+ register_provider(provider);
927
+ }
928
+ else {
929
+ logger.warn('Anthropic provider is enabled in config but failed to load. Check ANTHROPIC_API_KEY in environment variables.', {
930
+ file: 'index.ts',
931
+ data: { llm_name: llm_name.toLowerCase() },
932
+ });
933
+ }
934
+ }
935
+ else if (llm_name.toLowerCase() === 'openai') {
936
+ const provider = load_openai_provider_from_config(logger);
937
+ if (provider) {
938
+ register_provider(provider);
939
+ }
940
+ else {
941
+ logger.warn('OpenAI provider is enabled in config but failed to load. Check OPENAI_API_KEY in environment variables.', {
942
+ file: 'index.ts',
943
+ data: { llm_name: llm_name.toLowerCase() },
944
+ });
945
+ }
946
+ }
947
+ else if (llm_name.toLowerCase() === 'deepseek') {
948
+ const provider = load_deepseek_provider_from_config(logger);
949
+ if (provider) {
950
+ register_provider(provider);
951
+ }
952
+ else {
953
+ logger.warn('DeepSeek provider is enabled in config but failed to load. Check DEEPSEEK_API_KEY in environment variables.', {
954
+ file: 'index.ts',
955
+ data: { llm_name: llm_name.toLowerCase() },
956
+ });
957
+ }
958
+ }
959
+ // Future: Add other providers here
701
960
  }
702
961
  }
703
962
  // =============================================================================
@@ -721,7 +980,6 @@ async function auto_initialize_database() {
721
980
  const error_message = error instanceof Error ? error.message : String(error);
722
981
  default_logger.error('Failed to auto-initialize database', {
723
982
  file: file_name,
724
- line: 124,
725
983
  data: { error: error_message },
726
984
  });
727
985
  // Don't throw - allow manual initialization later
@@ -758,10 +1016,12 @@ export async function initialize_llm_api(config = {}) {
758
1016
  const logger = config.logger || default_logger;
759
1017
  // Store the logger for use by other functions
760
1018
  set_logger(logger);
761
- // Store hooks if provided
762
- if (config.hooks) {
763
- set_hooks(config.hooks);
764
- }
1019
+ // Reset hooks will be set correctly after connect is established below
1020
+ set_hooks({});
1021
+ // Reset cascade config — will be set correctly below
1022
+ stored_cascade_config = null;
1023
+ // Reset cost cap config — will be set correctly below
1024
+ stored_cost_cap_config = null;
765
1025
  // Get global config from file
766
1026
  const global_config = get_llm_global_config();
767
1027
  // Use provided sqlite_path or fall back to config file value
@@ -774,9 +1034,8 @@ export async function initialize_llm_api(config = {}) {
774
1034
  const error_msg = 'No primary LLM configured. Set primary_llm in [llm] section of config file.';
775
1035
  logger.error(error_msg, {
776
1036
  file: file_name,
777
- line: 615,
778
1037
  });
779
- throw new Error(error_msg);
1038
+ throw new HazoConfigError({ code: 'HAZO_LLM_API_NO_PRIMARY_LLM', pkg: 'hazo_llm_api', message: error_msg });
780
1039
  }
781
1040
  // Set final config
782
1041
  const final_config = {
@@ -803,13 +1062,72 @@ export async function initialize_llm_api(config = {}) {
803
1062
  }
804
1063
  initialized = true;
805
1064
  current_config = final_config;
1065
+ // Apply pricing table and any consumer overrides
1066
+ load_pricing();
1067
+ if (config.pricing_overrides) {
1068
+ set_pricing_overrides(config.pricing_overrides);
1069
+ }
1070
+ // Store resolved cascade config: init-time config takes precedence over INI
1071
+ stored_cascade_config = config.cascade ?? global_config.cascade ?? null;
1072
+ // Store cost cap config from consumer
1073
+ stored_cost_cap_config = config.cost_cap ?? null;
1074
+ // Configure embed cache from config
1075
+ if (config.embed_cache) {
1076
+ configure_embed_cache({
1077
+ max_size: config.embed_cache.max_size,
1078
+ keyv: config.embed_cache.keyv,
1079
+ });
1080
+ }
1081
+ // Set up the log table + default afterResponse hook unless explicitly disabled
1082
+ const resolved_connect = stored_hazo_connect;
1083
+ const api_log_enabled = config.api_log?.enabled !== false;
1084
+ const consumer_after = config.hooks?.afterResponse;
1085
+ if (api_log_enabled && resolved_connect) {
1086
+ try {
1087
+ await ensure_api_log_table(resolved_connect, logger);
1088
+ // Ensure cost cap index exists for fast session-based SUM queries
1089
+ const { ensure_cost_cap_index } = await import('../cost_cap/cost_cap.js');
1090
+ await ensure_cost_cap_index(resolved_connect, logger);
1091
+ const default_hook = create_default_after_response_hook({
1092
+ connect: resolved_connect,
1093
+ logger,
1094
+ extract_context: config.api_log?.extract_context,
1095
+ });
1096
+ // Chain: default hook first, then consumer hook (if provided)
1097
+ const chained = consumer_after
1098
+ ? async (ctx) => {
1099
+ await default_hook(ctx);
1100
+ await consumer_after(ctx);
1101
+ }
1102
+ : default_hook;
1103
+ set_hooks({
1104
+ ...get_hooks(), // preserve any other hooks (beforeRequest, onError)
1105
+ ...config.hooks, // merge consumer hooks
1106
+ afterResponse: chained,
1107
+ });
1108
+ }
1109
+ catch (err) {
1110
+ logger.warn('api_log setup failed; calls will not be logged', {
1111
+ error: err instanceof Error ? err.message : String(err),
1112
+ });
1113
+ // Fall through — install consumer hooks only, don't override with failing default
1114
+ if (config.hooks) {
1115
+ set_hooks(config.hooks);
1116
+ }
1117
+ }
1118
+ }
1119
+ else {
1120
+ // api_log disabled or no connect: install consumer hooks as-is (or leave empty)
1121
+ if (config.hooks) {
1122
+ set_hooks(config.hooks);
1123
+ }
1124
+ }
806
1125
  // LLM API initialized successfully
807
1126
  }
808
1127
  catch (error) {
809
1128
  const error_message = error instanceof Error ? error.message : String(error);
810
1129
  logger.error('Failed to initialize LLM API', {
811
1130
  file: file_name,
812
- line: 653,
813
1131
  data: { error: error_message },
814
1132
  });
815
1133
  throw error;
@@ -857,7 +1175,7 @@ export async function initialize_llm_api(config = {}) {
857
1175
  */
858
1176
  function check_initialized() {
859
1177
  if (!initialized || !current_config || !stored_hazo_connect) {
860
- throw new Error('LLM API not initialized. Call initialize_llm_api first.');
1178
+ throw new HazoInternalError({ code: 'HAZO_LLM_API_NOT_INITIALIZED', pkg: 'hazo_llm_api', message: 'LLM API not initialized. Call initialize_llm_api first.' });
861
1179
  }
862
1180
  // Ensure logger and hazo_connect are always present
863
1181
  return {
@@ -1116,6 +1434,36 @@ export async function hazo_llm_dynamic_data_extract(params, llm) {
1116
1434
  }
1117
1435
  }
1118
1436
  // =============================================================================
1437
+ // Embed Function
1438
+ // =============================================================================
1439
+ /**
1440
+ * Generate vector embeddings for one or more text inputs.
1441
+ * Caches results in-memory (or via BYO Keyv) and deduplicates repeated texts in batches.
1442
+ *
1443
+ * @param params - Text string or array of strings to embed, plus optional model override
1444
+ * @returns EmbedResponse with vectors, dimensions, model, and cache flags
1445
+ *
1446
+ * @example
1447
+ * ```typescript
1448
+ * import { hazo_llm_embed } from 'hazo_llm_api/server';
1449
+ *
1450
+ * const result = await hazo_llm_embed({ text: 'Hello world' });
1451
+ * if (result.success) {
1452
+ * console.log(result.vectors?.[0]); // number[]
1453
+ * }
1454
+ * ```
1455
+ */
1456
+ export async function hazo_llm_embed(params, _llm) {
1457
+ try {
1458
+ const config = check_initialized();
1459
+ const logger = config.logger || default_logger;
1460
+ return hazo_llm_embed_internal(params, logger);
1461
+ }
1462
+ catch (error) {
1463
+ return { success: false, error: error instanceof Error ? error.message : String(error) };
1464
+ }
1465
+ }
1466
+ // =============================================================================
1119
1467
  // Streaming Functions
1120
1468
  // =============================================================================
1121
1469
  /**
@@ -1173,6 +1521,21 @@ export async function* hazo_llm_text_text_stream(params, llm) {
1173
1521
  };
1174
1522
  return;
1175
1523
  }
1524
+ // Cost cap pre-call gate
1525
+ const cost_cap_config = get_cost_cap_config();
1526
+ if (cost_cap_config) {
1527
+ const { check_cost_cap_gate } = await import('../cost_cap/cost_cap.js');
1528
+ const cap_block = await check_cost_cap_gate(stored_hazo_connect, cost_cap_config, logger);
1529
+ if (cap_block !== null) {
1530
+ yield {
1531
+ text: '',
1532
+ done: true,
1533
+ error: cap_block.error ?? cap_block.error_info?.message,
1534
+ error_info: cap_block.error_info,
1535
+ };
1536
+ return;
1537
+ }
1538
+ }
1176
1539
  // Call hooks and streaming method
1177
1540
  const request_context = create_request_context(SERVICE_TYPES.TEXT_TEXT, provider.get_name(), {
1178
1541
  prompt: params.prompt,
@@ -1267,6 +1630,21 @@ export async function* hazo_llm_image_text_stream(params, llm) {
1267
1630
  };
1268
1631
  return;
1269
1632
  }
1633
+ // Cost cap pre-call gate
1634
+ const cost_cap_config = get_cost_cap_config();
1635
+ if (cost_cap_config) {
1636
+ const { check_cost_cap_gate } = await import('../cost_cap/cost_cap.js');
1637
+ const cap_block = await check_cost_cap_gate(stored_hazo_connect, cost_cap_config, logger);
1638
+ if (cap_block !== null) {
1639
+ yield {
1640
+ text: '',
1641
+ done: true,
1642
+ error: cap_block.error ?? cap_block.error_info?.message,
1643
+ error_info: cap_block.error_info,
1644
+ };
1645
+ return;
1646
+ }
1647
+ }
1270
1648
  // Call hooks and streaming method
1271
1649
  const request_context = create_request_context(SERVICE_TYPES.IMAGE_TEXT, provider.get_name(), {
1272
1650
  prompt: params.prompt,
@@ -1329,6 +1707,20 @@ export async function* hazo_llm_image_text_stream(params, llm) {
1329
1707
  export function is_initialized() {
1330
1708
  return initialized;
1331
1709
  }
1710
+ /**
1711
+ * Get the current cascade configuration (from init or INI).
1712
+ * Returns null if no cascade config has been set.
1713
+ */
1714
+ export function get_cascade_config() {
1715
+ return stored_cascade_config;
1716
+ }
1717
+ /**
1718
+ * Get the current cost cap configuration (set during initialize_llm_api).
1719
+ * Returns null if no cost cap config has been set.
1720
+ */
1721
+ export function get_cost_cap_config() {
1722
+ return stored_cost_cap_config;
1723
+ }
1332
1724
  /**
1333
1725
  * Get the current configuration (without sensitive logger)
1334
1726
  * @returns Current configuration or null if not initialized