@lota-sdk/core 0.1.11 → 0.1.13

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 (95) hide show
  1. package/package.json +2 -87
  2. package/src/ai/index.ts +3 -0
  3. package/src/bifrost/index.ts +1 -0
  4. package/src/config/agent-defaults.ts +30 -7
  5. package/src/config/constants.ts +0 -9
  6. package/src/config/debug-logger.ts +43 -0
  7. package/src/config/index.ts +5 -0
  8. package/src/config/model-constants.ts +0 -3
  9. package/src/config/workstream-defaults.ts +4 -0
  10. package/src/db/cursor-pagination.ts +2 -2
  11. package/src/db/index.ts +10 -0
  12. package/src/db/memory.ts +9 -15
  13. package/src/document/index.ts +2 -0
  14. package/src/document/parsing.ts +0 -25
  15. package/src/embeddings/provider.ts +17 -8
  16. package/src/index.ts +15 -505
  17. package/src/queues/index.ts +10 -0
  18. package/src/redis/connection-accessor.ts +26 -0
  19. package/src/redis/connection.ts +1 -1
  20. package/src/redis/index.ts +9 -25
  21. package/src/redis/org-memory-lock.ts +1 -1
  22. package/src/redis/redis-lease-lock.ts +1 -1
  23. package/src/redis/stream-context.ts +12 -2
  24. package/src/runtime/agent-runtime-policy.ts +9 -5
  25. package/src/runtime/agent-stream-helpers.ts +6 -3
  26. package/src/runtime/agent-types.ts +1 -5
  27. package/src/runtime/approval-continuation.ts +9 -1
  28. package/src/runtime/chat-attachments.ts +1 -1
  29. package/src/runtime/chat-request-routing.ts +1 -1
  30. package/src/runtime/context-compaction-runtime.ts +2 -2
  31. package/src/runtime/context-compaction.ts +1 -1
  32. package/src/runtime/execution-plan.ts +1 -1
  33. package/src/runtime/index.ts +26 -0
  34. package/src/runtime/indexed-repositories-policy.ts +10 -10
  35. package/src/runtime/memory-pipeline.ts +0 -2
  36. package/src/runtime/runtime-config.ts +238 -0
  37. package/src/runtime/runtime-extensions.ts +3 -2
  38. package/src/runtime/runtime-worker-registry.ts +47 -0
  39. package/src/runtime/team-consultation-orchestrator.ts +9 -6
  40. package/src/runtime/team-consultation-prompts.ts +3 -2
  41. package/src/runtime/turn-lifecycle.ts +1 -1
  42. package/src/runtime/workstream-chat-helpers.ts +0 -54
  43. package/src/runtime/workstream-routing-policy.ts +3 -7
  44. package/src/runtime.ts +387 -0
  45. package/src/services/chat-attachments.service.ts +1 -1
  46. package/src/services/context-compaction.service.ts +1 -1
  47. package/src/services/execution-plan.service.ts +14 -16
  48. package/src/services/index.ts +14 -0
  49. package/src/services/learned-skill.service.ts +80 -37
  50. package/src/services/memory.service.ts +5 -4
  51. package/src/services/mutating-approval.service.ts +1 -1
  52. package/src/services/organization-member.service.ts +1 -1
  53. package/src/services/organization.service.ts +1 -1
  54. package/src/services/plan-approval.service.ts +2 -2
  55. package/src/services/plan-artifact.service.ts +2 -3
  56. package/src/services/plan-builder.service.ts +1 -1
  57. package/src/services/plan-checkpoint.service.ts +2 -2
  58. package/src/services/plan-compiler.service.ts +2 -2
  59. package/src/services/plan-executor.service.ts +10 -9
  60. package/src/services/plan-run.service.ts +2 -2
  61. package/src/services/plan-validator.service.ts +4 -4
  62. package/src/services/recent-activity-title.service.ts +1 -1
  63. package/src/services/recent-activity.service.ts +14 -16
  64. package/src/services/user.service.ts +2 -2
  65. package/src/services/workstream-message.service.ts +2 -3
  66. package/src/services/workstream-title.service.ts +1 -1
  67. package/src/services/workstream-turn-preparation.ts +105 -50
  68. package/src/services/workstream-turn.ts +14 -1
  69. package/src/services/workstream.service.ts +9 -9
  70. package/src/storage/attachment-parser.ts +1 -1
  71. package/src/storage/attachment-storage.service.ts +11 -10
  72. package/src/storage/generated-document-storage.service.ts +7 -6
  73. package/src/storage/index.ts +10 -0
  74. package/src/system-agents/delegated-agent-factory.ts +78 -29
  75. package/src/system-agents/index.ts +4 -0
  76. package/src/system-agents/recent-activity-title-refiner.agent.ts +38 -3
  77. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  78. package/src/system-agents/skill-extractor.agent.ts +1 -1
  79. package/src/system-agents/skill-manager.agent.ts +2 -4
  80. package/src/tools/execution-plan.tool.ts +2 -2
  81. package/src/tools/firecrawl-client.ts +2 -2
  82. package/src/tools/index.ts +12 -0
  83. package/src/tools/research-topic.tool.ts +1 -1
  84. package/src/tools/team-think.tool.ts +1 -1
  85. package/src/tools/user-questions.tool.ts +2 -2
  86. package/src/utils/index.ts +6 -0
  87. package/src/workers/bootstrap.ts +8 -16
  88. package/src/workers/index.ts +7 -0
  89. package/src/workers/regular-chat-memory-digest.runner.ts +1 -1
  90. package/src/workers/skill-extraction.runner.ts +1 -1
  91. package/src/workers/utils/{repo-indexer-chunker.ts → file-section-chunker.ts} +23 -52
  92. package/src/workers/utils/repo-structure-extractor.ts +2 -5
  93. package/src/workers/utils/repomix-file-sections.ts +42 -0
  94. package/src/config/env-shapes.ts +0 -121
  95. package/src/runtime/agent-contract.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -13,91 +13,6 @@
13
13
  "bun": "./src/index.ts",
14
14
  "import": "./src/index.ts",
15
15
  "types": "./src/index.ts"
16
- },
17
- "./ai/*": {
18
- "bun": "./src/ai/*.ts",
19
- "import": "./src/ai/*.ts",
20
- "types": "./src/ai/*.ts"
21
- },
22
- "./bifrost": {
23
- "bun": "./src/bifrost/bifrost.ts",
24
- "import": "./src/bifrost/bifrost.ts",
25
- "types": "./src/bifrost/bifrost.ts"
26
- },
27
- "./bifrost/*": {
28
- "bun": "./src/bifrost/*.ts",
29
- "import": "./src/bifrost/*.ts",
30
- "types": "./src/bifrost/*.ts"
31
- },
32
- "./runtime/*": {
33
- "bun": "./src/runtime/*.ts",
34
- "import": "./src/runtime/*.ts",
35
- "types": "./src/runtime/*.ts"
36
- },
37
- "./tools/*": {
38
- "bun": "./src/tools/*.ts",
39
- "import": "./src/tools/*.ts",
40
- "types": "./src/tools/*.ts"
41
- },
42
- "./storage/*": {
43
- "bun": "./src/storage/*.ts",
44
- "import": "./src/storage/*.ts",
45
- "types": "./src/storage/*.ts"
46
- },
47
- "./document/*": {
48
- "bun": "./src/document/*.ts",
49
- "import": "./src/document/*.ts",
50
- "types": "./src/document/*.ts"
51
- },
52
- "./system-agents/*": {
53
- "bun": "./src/system-agents/*.ts",
54
- "import": "./src/system-agents/*.ts",
55
- "types": "./src/system-agents/*.ts"
56
- },
57
- "./db/*": {
58
- "bun": "./src/db/*.ts",
59
- "import": "./src/db/*.ts",
60
- "types": "./src/db/*.ts"
61
- },
62
- "./redis": {
63
- "bun": "./src/redis/index.ts",
64
- "import": "./src/redis/index.ts",
65
- "types": "./src/redis/index.ts"
66
- },
67
- "./redis/*": {
68
- "bun": "./src/redis/*.ts",
69
- "import": "./src/redis/*.ts",
70
- "types": "./src/redis/*.ts"
71
- },
72
- "./services/*": {
73
- "bun": "./src/services/*.ts",
74
- "import": "./src/services/*.ts",
75
- "types": "./src/services/*.ts"
76
- },
77
- "./config/*": {
78
- "bun": "./src/config/*.ts",
79
- "import": "./src/config/*.ts",
80
- "types": "./src/config/*.ts"
81
- },
82
- "./utils/*": {
83
- "bun": "./src/utils/*.ts",
84
- "import": "./src/utils/*.ts",
85
- "types": "./src/utils/*.ts"
86
- },
87
- "./queues/*": {
88
- "bun": "./src/queues/*.ts",
89
- "import": "./src/queues/*.ts",
90
- "types": "./src/queues/*.ts"
91
- },
92
- "./workers/*": {
93
- "bun": "./src/workers/*.ts",
94
- "import": "./src/workers/*.ts",
95
- "types": "./src/workers/*.ts"
96
- },
97
- "./embeddings": {
98
- "bun": "./src/embeddings/provider.ts",
99
- "import": "./src/embeddings/provider.ts",
100
- "types": "./src/embeddings/provider.ts"
101
16
  }
102
17
  },
103
18
  "scripts": {
@@ -115,7 +30,7 @@
115
30
  "@ai-sdk/devtools": "^0.0.15",
116
31
  "@ai-sdk/openai": "^3.0.41",
117
32
  "@logtape/logtape": "^2.0.4",
118
- "@lota-sdk/shared": "0.1.9",
33
+ "@lota-sdk/shared": "0.1.13",
119
34
  "@mendable/firecrawl-js": "^4.16.0",
120
35
  "@surrealdb/node": "^3.0.3",
121
36
  "ai": "^6.0.116",
@@ -0,0 +1,3 @@
1
+ export * from './definitions'
2
+ export * from './embedding-cache'
3
+ export * from '../embeddings/provider'
@@ -0,0 +1 @@
1
+ export * from './bifrost'
@@ -1,9 +1,18 @@
1
1
  type LotaAgentFactoryRegistry = Record<string, (...args: unknown[]) => unknown>
2
2
 
3
+ function defaultBuildAgentTools(): Record<string, never> {
4
+ return {}
5
+ }
6
+
7
+ function defaultGetAgentRuntimeConfig(): Record<string, never> {
8
+ return {}
9
+ }
10
+
3
11
  // Agent configuration — these are defaults that consumers override via createLotaRuntime config
4
12
  export let agentDisplayNames: Record<string, string> = {}
5
13
  export let agentShortDisplayNames: Record<string, string> = {}
6
14
  export let agentRoster: readonly string[] = []
15
+ export let leadAgentId = ''
7
16
  export let teamConsultParticipants: readonly string[] = []
8
17
 
9
18
  export interface CoreWorkstreamProfile {
@@ -22,12 +31,18 @@ export let getCoreWorkstreamProfile: (coreType: string) => CoreWorkstreamProfile
22
31
 
23
32
  export function configureAgents(config: {
24
33
  roster: readonly string[]
34
+ leadAgentId: string
25
35
  displayNames: Record<string, string>
26
36
  shortDisplayNames?: Record<string, string>
27
37
  teamConsultParticipants: readonly string[]
28
38
  getCoreWorkstreamProfile?: (coreType: string) => CoreWorkstreamProfile
29
39
  }): void {
40
+ if (!config.roster.includes(config.leadAgentId)) {
41
+ throw new Error(`Lead agent "${config.leadAgentId}" must be present in the configured roster.`)
42
+ }
43
+
30
44
  agentRoster = config.roster
45
+ leadAgentId = config.leadAgentId
31
46
  agentDisplayNames = config.displayNames
32
47
  agentShortDisplayNames = config.shortDisplayNames ?? {}
33
48
  teamConsultParticipants = config.teamConsultParticipants
@@ -40,6 +55,14 @@ export function isAgentName(value: unknown): boolean {
40
55
  return typeof value === 'string' && new Set(agentRoster).has(value)
41
56
  }
42
57
 
58
+ export function getLeadAgentId(): string {
59
+ return leadAgentId
60
+ }
61
+
62
+ export function getLeadAgentDisplayName(): string {
63
+ return agentDisplayNames[leadAgentId] ?? leadAgentId
64
+ }
65
+
43
66
  export function resolveAgentNameAlias(value: unknown): string | undefined {
44
67
  if (typeof value !== 'string') return undefined
45
68
  const lowered = value.trim().toLowerCase()
@@ -56,20 +79,20 @@ export function resolveAgentNameAlias(value: unknown): string | undefined {
56
79
 
57
80
  export let createAgent: LotaAgentFactoryRegistry = {}
58
81
 
59
- export let buildAgentTools: (...args: unknown[]) => unknown = () => ({})
60
- export let getAgentRuntimeConfig: (...args: unknown[]) => unknown = () => ({})
82
+ export let buildAgentTools: (...args: unknown[]) => unknown = defaultBuildAgentTools
83
+ export let getAgentRuntimeConfig: (...args: unknown[]) => unknown = defaultGetAgentRuntimeConfig
61
84
  export let pluginRuntime: unknown = undefined
62
85
 
63
86
  export function configureAgentFactory(config: {
64
- createAgent: LotaAgentFactoryRegistry
87
+ createAgent?: LotaAgentFactoryRegistry
65
88
  buildAgentTools?: (...args: unknown[]) => unknown
66
89
  getAgentRuntimeConfig?: (...args: unknown[]) => unknown
67
90
  pluginRuntime?: unknown
68
91
  }): void {
69
- createAgent = config.createAgent
70
- if (config.buildAgentTools) buildAgentTools = config.buildAgentTools
71
- if (config.getAgentRuntimeConfig) getAgentRuntimeConfig = config.getAgentRuntimeConfig
72
- if (config.pluginRuntime !== undefined) pluginRuntime = config.pluginRuntime
92
+ createAgent = config.createAgent ?? {}
93
+ buildAgentTools = config.buildAgentTools ?? defaultBuildAgentTools
94
+ getAgentRuntimeConfig = config.getAgentRuntimeConfig ?? defaultGetAgentRuntimeConfig
95
+ pluginRuntime = config.pluginRuntime
73
96
  }
74
97
 
75
98
  const AGENT_MENTION_REGEX = /(^|[^\w])@([a-z][a-z0-9_-]*)\b/gi
@@ -22,12 +22,3 @@ export const MEMORY = {
22
22
  export function validateKnnLimit(limit: unknown): number {
23
23
  return z.number().int().positive().max(MEMORY.MAX_KNN_LIMIT).parse(limit)
24
24
  }
25
-
26
- /**
27
- * Creates a KNN query string with validated limit
28
- * Example: createKnnQuery(10) returns "<|10|>"
29
- */
30
- export function createKnnQuery(limit: unknown): string {
31
- const validatedLimit = validateKnnLimit(limit)
32
- return `<|${validatedLimit}|>`
33
- }
@@ -0,0 +1,43 @@
1
+ import { chatLogger } from './logger'
2
+
3
+ const isDebug = () => process.env.LOG_LEVEL === 'debug' || process.env.LOTA_DEBUG === '1'
4
+
5
+ interface DebugTimer {
6
+ step(name: string): void
7
+ elapsed(): number
8
+ }
9
+
10
+ const NOOP_TIMER: DebugTimer = { step() {}, elapsed: () => 0 }
11
+
12
+ function createTimer(label: string): DebugTimer {
13
+ const start = performance.now()
14
+ let lastStep = start
15
+
16
+ return {
17
+ step(name: string) {
18
+ const now = performance.now()
19
+ const stepMs = now - lastStep
20
+ const totalMs = now - start
21
+ chatLogger.debug`[ttft:${label}] ${name}: ${stepMs.toFixed(1)}ms (elapsed: ${totalMs.toFixed(1)}ms)`
22
+ lastStep = now
23
+ },
24
+ elapsed() {
25
+ return performance.now() - start
26
+ },
27
+ }
28
+ }
29
+
30
+ export const lotaDebugLogger = {
31
+ get enabled() {
32
+ return isDebug()
33
+ },
34
+
35
+ timer(label: string): DebugTimer {
36
+ return isDebug() ? createTimer(label) : NOOP_TIMER
37
+ },
38
+
39
+ step(name: string) {
40
+ if (!isDebug()) return
41
+ chatLogger.debug`[ttft] ${name}`
42
+ },
43
+ }
@@ -0,0 +1,5 @@
1
+ export * from './agent-defaults'
2
+ export * from './background-processing'
3
+ export * from './logger'
4
+ export * from './model-constants'
5
+ export * from './workstream-defaults'
@@ -4,9 +4,6 @@ export const OPENROUTER_TEAM_AGENT_MODEL_ID = 'openrouter/google/gemini-3.1-pro-
4
4
  export const OPENROUTER_STRUCTURED_HELPER_MODEL_ID = 'openrouter/google/gemini-3-flash-preview:exacto' as const
5
5
  export const OPENROUTER_DELEGATED_REASONING_MODEL_ID = 'openrouter/google/gemini-3-flash-preview:exacto' as const
6
6
  export const OPENROUTER_WEB_RESEARCH_MODEL_ID = 'openrouter/stepfun/step-3.5-flash' as const
7
- export const OPENROUTER_ARTIFACT_GENERATOR_MODEL_ID = 'openrouter/qwen/qwen3.5-flash-02-23' as const
8
- export const OPENROUTER_REPO_INDEXER_MODEL_ID = 'openrouter/qwen/qwen3.5-flash-02-23:nitro' as const
9
- export const OPENROUTER_CODE_ANALYSIS_MODEL_ID = 'openrouter/xiaomi/mimo-v2-flash' as const
10
7
  export const OPENROUTER_FAST_REASONING_MODEL_ID = 'openrouter/openai/gpt-oss-120b:nitro' as const
11
8
  export const OPENROUTER_STRUCTURED_REASONING_MODEL_ID = 'openrouter/openai/gpt-oss-120b:exacto' as const
12
9
 
@@ -66,3 +66,7 @@ export function configureWorkstreams(params: { agentRoster: readonly string[]; c
66
66
  export function getWorkstreamBootstrapConfig(): ResolvedWorkstreamBootstrapConfig {
67
67
  return resolvedWorkstreamBootstrapConfig
68
68
  }
69
+
70
+ export function resolveOnboardingOwnerAgentId(defaultLeadAgentId: string): string {
71
+ return resolvedWorkstreamBootstrapConfig.onboardingWelcome?.directAgentId ?? defaultLeadAgentId
72
+ }
@@ -1,5 +1,5 @@
1
- import { toTimestamp } from '@lota-sdk/shared/runtime/chat-message-metadata'
2
- import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
1
+ import { toTimestamp } from '@lota-sdk/shared'
2
+ import type { ChatMessage } from '@lota-sdk/shared'
3
3
  import type { BoundQuery, RecordId } from 'surrealdb'
4
4
  import { z } from 'zod'
5
5
 
@@ -0,0 +1,10 @@
1
+ export * from './cursor-pagination'
2
+ export * from './memory'
3
+ export * from './memory-store'
4
+ export * from './memory-store.helpers'
5
+ export * from './memory-types'
6
+ export * from './record-id'
7
+ export * from './sdk-database'
8
+ export * from './service'
9
+ export * from './startup'
10
+ export * from './tables'
package/src/db/memory.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { env } from '../config/env-shapes'
2
1
  import { aiLogger } from '../config/logger'
3
2
  import type { CreateHelperAgentFn } from '../runtime/helper-model'
4
3
  import { createHelperModelRuntime } from '../runtime/helper-model'
@@ -12,6 +11,7 @@ import {
12
11
  import { getFactRetrievalMessages } from '../runtime/memory-prompts-fact'
13
12
  import { parseMessages } from '../runtime/memory-prompts-parse'
14
13
  import { getClassifyMemoryDeltaPrompt } from '../runtime/memory-prompts-update'
14
+ import { getRuntimeConfig } from '../runtime/runtime-config'
15
15
  import type { SurrealMemoryStore } from './memory-store'
16
16
  import { getDefaultMemoryStore } from './memory-store'
17
17
  import { hashContent, isUniqueIndexConflict } from './memory-store.helpers'
@@ -91,23 +91,15 @@ export class Memory {
91
91
  }
92
92
 
93
93
  async search(query: string, options: SearchOptions): Promise<string> {
94
- const results = await this.store.search(
95
- query,
96
- options.scopeId,
97
- options.limit ?? env.MEMORY_SEARCH_K,
98
- options.memoryType,
99
- )
94
+ const limit = options.limit ?? getRuntimeConfig().memory.searchK
95
+ const results = await this.store.search(query, options.scopeId, limit, options.memoryType)
100
96
 
101
97
  return formatResults(results)
102
98
  }
103
99
 
104
100
  async hybridSearch(query: string, options: SearchOptions): Promise<string> {
105
- const results = await this.store.hybridSearch(
106
- query,
107
- options.scopeId,
108
- options.limit ?? env.MEMORY_SEARCH_K,
109
- options.memoryType,
110
- )
101
+ const limit = options.limit ?? getRuntimeConfig().memory.searchK
102
+ const results = await this.store.hybridSearch(query, options.scopeId, limit, options.memoryType)
111
103
 
112
104
  return formatResults(results)
113
105
  }
@@ -116,9 +108,10 @@ export class Memory {
116
108
  query: string,
117
109
  options: SearchOptions & { weights?: [number, number]; normalization?: 'minmax' | 'zscore' },
118
110
  ): Promise<string> {
111
+ const limit = options.limit ?? getRuntimeConfig().memory.searchK
119
112
  const results = await this.store.hybridSearchWeighted(query, {
120
113
  scopeId: options.scopeId,
121
- limit: options.limit ?? env.MEMORY_SEARCH_K,
114
+ limit,
122
115
  memoryType: options.memoryType,
123
116
  weights: options.weights,
124
117
  normalization: options.normalization,
@@ -128,9 +121,10 @@ export class Memory {
128
121
  }
129
122
 
130
123
  async searchCandidates(query: string, options: WeightedSearchOptions): Promise<MemorySearchResult[]> {
124
+ const limit = options.limit ?? getRuntimeConfig().memory.searchK
131
125
  const results = await this.store.hybridSearchWeighted(query, {
132
126
  scopeId: options.scopeId,
133
- limit: options.limit ?? env.MEMORY_SEARCH_K,
127
+ limit,
134
128
  memoryType: options.memoryType,
135
129
  weights: options.weights,
136
130
  normalization: options.normalization,
@@ -0,0 +1,2 @@
1
+ export * from './org-document-chunking'
2
+ export * from './parsing'
@@ -13,28 +13,3 @@ export function normalizeKey(value: string): string {
13
13
  .replace(/\s+/g, '-')
14
14
  .slice(0, 120)
15
15
  }
16
-
17
- export function makeMemoryKey(kind: string, rawKey: string): string {
18
- const normalized = normalizeKey(rawKey)
19
- return normalized ? `${kind}:${normalized}` : `${kind}:item`
20
- }
21
-
22
- export function truncateForModel(value: string, maxChars: number): string {
23
- if (value.length <= maxChars) return value
24
- return `${value.slice(0, maxChars)}\n\n[...truncated due to size...]`
25
- }
26
-
27
- export function dedupeStrings(items: string[], limit: number): string[] {
28
- const out: string[] = []
29
- const seen = new Set<string>()
30
- for (const raw of items) {
31
- const value = normalizeWhitespace(raw)
32
- if (!value) continue
33
- const key = value.toLowerCase()
34
- if (seen.has(key)) continue
35
- seen.add(key)
36
- out.push(value)
37
- if (out.length >= limit) break
38
- }
39
- return out
40
- }
@@ -2,7 +2,7 @@ import { embed, embedMany } from 'ai'
2
2
 
3
3
  import { getEmbeddingCache } from '../ai/embedding-cache'
4
4
  import { bifrostEmbeddingModel } from '../bifrost/bifrost'
5
- import { env } from '../config/env-shapes'
5
+ import { getRuntimeConfig } from '../runtime/runtime-config'
6
6
 
7
7
  const SUPPORTED_EMBEDDING_PREFIXES = ['openai/', 'openrouter/'] as const
8
8
 
@@ -41,19 +41,28 @@ export class ProviderEmbeddings {
41
41
  private readonly embedFn: typeof embed
42
42
  private readonly embedManyFn: typeof embedMany
43
43
  private readonly getCache: () => SharedEmbeddingCache | null
44
- private readonly modelId: string
44
+ private readonly configuredModelId?: string
45
+ private resolvedModelId: string | null = null
45
46
  private _model: ReturnType<typeof resolveEmbeddingModel> | null = null
46
47
 
47
48
  constructor(options: ProviderEmbeddingsOptions = {}) {
48
49
  this.embedFn = options.embedFn ?? embed
49
50
  this.embedManyFn = options.embedManyFn ?? embedMany
50
51
  this.getCache = options.getCache ?? getEmbeddingCache
51
- this.modelId = options.modelId ?? env.AI_EMBEDDING_MODEL
52
+ this.configuredModelId = options.modelId
53
+ }
54
+
55
+ private getModelId(): string {
56
+ if (!this.resolvedModelId) {
57
+ this.resolvedModelId = this.configuredModelId ?? getRuntimeConfig().aiGateway.embeddingModel
58
+ }
59
+
60
+ return this.resolvedModelId
52
61
  }
53
62
 
54
63
  private getModel() {
55
64
  if (!this._model) {
56
- this._model = resolveEmbeddingModel(this.modelId)
65
+ this._model = resolveEmbeddingModel(this.getModelId())
57
66
  }
58
67
  return this._model
59
68
  }
@@ -62,7 +71,7 @@ export class ProviderEmbeddings {
62
71
  const redisCache = this.getCache()
63
72
  if (!redisCache) return null
64
73
 
65
- return await redisCache.get(this.modelId, text)
74
+ return await redisCache.get(this.getModelId(), text)
66
75
  }
67
76
 
68
77
  async embedQuery(text: string): Promise<number[]> {
@@ -77,7 +86,7 @@ export class ProviderEmbeddings {
77
86
 
78
87
  const redisCache = this.getCache()
79
88
  if (redisCache) {
80
- void redisCache.set(this.modelId, input, embedding)
89
+ void redisCache.set(this.getModelId(), input, embedding)
81
90
  }
82
91
 
83
92
  return embedding
@@ -102,7 +111,7 @@ export class ProviderEmbeddings {
102
111
  const redisCache = this.getCache()
103
112
  if (redisCache && missingTexts.length > 0) {
104
113
  const redisResults = await Promise.all(
105
- missingTexts.map(async (text) => ({ text, embedding: await redisCache.get(this.modelId, text) })),
114
+ missingTexts.map(async (text) => ({ text, embedding: await redisCache.get(this.getModelId(), text) })),
106
115
  )
107
116
 
108
117
  missingTexts = []
@@ -123,7 +132,7 @@ export class ProviderEmbeddings {
123
132
  const embedding = normalizeEmbedding(result.embeddings[index] ?? [])
124
133
  embeddingsByText.set(text, embedding)
125
134
  if (redisCache) {
126
- void redisCache.set(this.modelId, text, embedding)
135
+ void redisCache.set(this.getModelId(), text, embedding)
127
136
  }
128
137
  })
129
138
  }