@lota-sdk/core 0.1.9 → 0.1.12

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 (105) hide show
  1. package/infrastructure/schema/00_workstream.surql +1 -0
  2. package/infrastructure/schema/02_execution_plan.surql +202 -52
  3. package/package.json +4 -87
  4. package/src/ai/index.ts +3 -0
  5. package/src/bifrost/bifrost.ts +94 -25
  6. package/src/bifrost/index.ts +1 -0
  7. package/src/config/agent-defaults.ts +30 -7
  8. package/src/config/constants.ts +0 -9
  9. package/src/config/debug-logger.ts +43 -0
  10. package/src/config/index.ts +5 -0
  11. package/src/config/model-constants.ts +8 -9
  12. package/src/config/workstream-defaults.ts +4 -0
  13. package/src/db/cursor-pagination.ts +2 -2
  14. package/src/db/index.ts +10 -0
  15. package/src/db/memory-store.ts +3 -71
  16. package/src/db/memory.ts +9 -15
  17. package/src/db/service.ts +42 -2
  18. package/src/db/tables.ts +9 -2
  19. package/src/document/index.ts +2 -0
  20. package/src/document/parsing.ts +0 -25
  21. package/src/embeddings/provider.ts +102 -22
  22. package/src/index.ts +15 -499
  23. package/src/queues/index.ts +10 -0
  24. package/src/redis/connection-accessor.ts +26 -0
  25. package/src/redis/connection.ts +1 -1
  26. package/src/redis/index.ts +9 -25
  27. package/src/redis/org-memory-lock.ts +1 -1
  28. package/src/redis/redis-lease-lock.ts +1 -1
  29. package/src/redis/stream-context.ts +54 -0
  30. package/src/runtime/agent-runtime-policy.ts +9 -5
  31. package/src/runtime/agent-stream-helpers.ts +6 -3
  32. package/src/runtime/agent-types.ts +1 -5
  33. package/src/runtime/approval-continuation.ts +68 -1
  34. package/src/runtime/chat-attachments.ts +1 -1
  35. package/src/runtime/chat-request-routing.ts +6 -2
  36. package/src/runtime/context-compaction-runtime.ts +2 -2
  37. package/src/runtime/context-compaction.ts +1 -1
  38. package/src/runtime/execution-plan.ts +22 -15
  39. package/src/runtime/index.ts +26 -0
  40. package/src/runtime/indexed-repositories-policy.ts +10 -10
  41. package/src/runtime/memory-pipeline.ts +0 -2
  42. package/src/runtime/runtime-config.ts +238 -0
  43. package/src/runtime/runtime-extensions.ts +3 -2
  44. package/src/runtime/runtime-worker-registry.ts +47 -0
  45. package/src/runtime/team-consultation-orchestrator.ts +9 -6
  46. package/src/runtime/team-consultation-prompts.ts +3 -2
  47. package/src/runtime/turn-lifecycle.ts +13 -5
  48. package/src/runtime/workstream-chat-helpers.ts +0 -54
  49. package/src/runtime/workstream-routing-policy.ts +3 -7
  50. package/src/runtime.ts +387 -0
  51. package/src/services/chat-attachments.service.ts +1 -1
  52. package/src/services/context-compaction.service.ts +1 -1
  53. package/src/services/document-chunk.service.ts +2 -2
  54. package/src/services/execution-plan.service.ts +584 -793
  55. package/src/services/index.ts +14 -0
  56. package/src/services/learned-skill.service.ts +82 -39
  57. package/src/services/memory.service.ts +5 -4
  58. package/src/services/mutating-approval.service.ts +1 -1
  59. package/src/services/organization-member.service.ts +1 -1
  60. package/src/services/organization.service.ts +1 -1
  61. package/src/services/plan-approval.service.ts +83 -0
  62. package/src/services/plan-artifact.service.ts +44 -0
  63. package/src/services/plan-builder.service.ts +61 -0
  64. package/src/services/plan-checkpoint.service.ts +53 -0
  65. package/src/services/plan-compiler.service.ts +81 -0
  66. package/src/services/plan-executor.service.ts +1624 -0
  67. package/src/services/plan-run.service.ts +422 -0
  68. package/src/services/plan-validator.service.ts +760 -0
  69. package/src/services/recent-activity-title.service.ts +1 -1
  70. package/src/services/recent-activity.service.ts +14 -16
  71. package/src/services/user.service.ts +2 -2
  72. package/src/services/workstream-message.service.ts +2 -3
  73. package/src/services/workstream-title.service.ts +1 -1
  74. package/src/services/workstream-turn-preparation.ts +156 -59
  75. package/src/services/workstream-turn.ts +26 -1
  76. package/src/services/workstream.service.ts +35 -9
  77. package/src/services/workstream.types.ts +1 -0
  78. package/src/storage/attachment-parser.ts +1 -1
  79. package/src/storage/attachment-storage.service.ts +11 -10
  80. package/src/storage/generated-document-storage.service.ts +7 -6
  81. package/src/storage/index.ts +10 -0
  82. package/src/system-agents/delegated-agent-factory.ts +78 -29
  83. package/src/system-agents/index.ts +4 -0
  84. package/src/system-agents/recent-activity-title-refiner.agent.ts +38 -3
  85. package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
  86. package/src/system-agents/skill-extractor.agent.ts +1 -1
  87. package/src/system-agents/skill-manager.agent.ts +2 -4
  88. package/src/system-agents/title-generator.agent.ts +2 -2
  89. package/src/tools/execution-plan.tool.ts +22 -48
  90. package/src/tools/firecrawl-client.ts +2 -2
  91. package/src/tools/index.ts +12 -0
  92. package/src/tools/log-hello-world.tool.ts +17 -0
  93. package/src/tools/research-topic.tool.ts +1 -1
  94. package/src/tools/team-think.tool.ts +1 -1
  95. package/src/tools/user-questions.tool.ts +2 -2
  96. package/src/utils/index.ts +6 -0
  97. package/src/workers/bootstrap.ts +8 -16
  98. package/src/workers/index.ts +7 -0
  99. package/src/workers/regular-chat-memory-digest.runner.ts +1 -1
  100. package/src/workers/skill-extraction.runner.ts +3 -3
  101. package/src/workers/utils/{repo-indexer-chunker.ts → file-section-chunker.ts} +23 -52
  102. package/src/workers/utils/repo-structure-extractor.ts +2 -5
  103. package/src/workers/utils/repomix-file-sections.ts +42 -0
  104. package/src/config/env-shapes.ts +0 -121
  105. package/src/runtime/agent-contract.ts +0 -1
@@ -12,7 +12,7 @@ export const researchTopicTool = createDelegatedAgentTool({
12
12
  id: 'researchTopic',
13
13
  description:
14
14
  'Delegate a research task to a dedicated research agent that searches the web, fetches pages, and returns a synthesized markdown report. Call multiple instances in parallel for broad research across different topics.',
15
- model: bifrostChatModel(OPENROUTER_WEB_RESEARCH_MODEL_ID),
15
+ model: () => bifrostChatModel(OPENROUTER_WEB_RESEARCH_MODEL_ID),
16
16
  providerOptions: OPENROUTER_MEDIUM_REASONING_PROVIDER_OPTIONS,
17
17
  instructions: RESEARCHER_PROMPT,
18
18
  tools: { searchWeb: searchWebTool.create(), fetchWebpage: fetchWebpageTool.create() },
@@ -1,4 +1,4 @@
1
- import type { ChatMessage } from '@lota-sdk/shared/schemas/chat-message'
1
+ import type { ChatMessage } from '@lota-sdk/shared'
2
2
  import { stepCountIs } from 'ai'
3
3
  import type { ToolSet } from 'ai'
4
4
 
@@ -1,5 +1,5 @@
1
- import { USER_QUESTIONS_TOOL_NAME, UserQuestionsArgsSchema } from '@lota-sdk/shared/schemas/tools'
2
- import type { UserQuestionsArgs } from '@lota-sdk/shared/schemas/tools'
1
+ import { USER_QUESTIONS_TOOL_NAME, UserQuestionsArgsSchema } from '@lota-sdk/shared'
2
+ import type { UserQuestionsArgs } from '@lota-sdk/shared'
3
3
  import { tool } from 'ai'
4
4
 
5
5
  import type { ToolDefinition } from '../ai/definitions'
@@ -0,0 +1,6 @@
1
+ export * from './async'
2
+ export * from './date-time'
3
+ export * from './error'
4
+ export * from './errors'
5
+ export * from './hono-error-handler'
6
+ export * from './string'
@@ -2,26 +2,17 @@ import { configureLogger, serverLogger } from '../config/logger'
2
2
  import { LOTA_SDK_DATABASE_NAME } from '../db/sdk-database'
3
3
  import { SurrealDBService, databaseService, setDatabaseService } from '../db/service'
4
4
  import { connectWithStartupRetry, waitForDatabaseBootstrap } from '../db/startup'
5
+ import { parseWorkerBootstrapEnv } from '../runtime/runtime-config'
5
6
  import { getConfiguredPluginDatabaseConnector } from '../runtime/runtime-extensions'
6
7
 
7
- /**
8
- * Sandboxed BullMQ workers run in separate child processes where createLotaRuntime
9
- * was never called, so the global databaseService proxy has no backing instance.
10
- * Create one from env vars so the proxy resolves.
11
- */
12
- function getRequiredEnv(key: string): string {
13
- const value = process.env[key]
14
- if (!value) throw new Error(`Missing required env var: ${key}`)
15
- return value
16
- }
17
-
18
8
  function ensureDatabaseServiceConfigured(): void {
9
+ const env = parseWorkerBootstrapEnv(process.env)
19
10
  const db = new SurrealDBService({
20
- url: getRequiredEnv('SURREALDB_URL'),
21
- namespace: getRequiredEnv('SURREALDB_NAMESPACE'),
11
+ url: env.SURREALDB_URL,
12
+ namespace: env.SURREALDB_NAMESPACE,
22
13
  database: LOTA_SDK_DATABASE_NAME,
23
- username: process.env.SURREALDB_USER,
24
- password: process.env.SURREALDB_PASSWORD,
14
+ username: env.SURREALDB_USER,
15
+ password: env.SURREALDB_PASSWORD,
25
16
  })
26
17
  setDatabaseService(db)
27
18
  }
@@ -35,6 +26,7 @@ export async function initializeSandboxedWorkerRuntime(): Promise<void> {
35
26
  }
36
27
 
37
28
  sandboxedWorkerRuntimePromise = (async () => {
29
+ const env = parseWorkerBootstrapEnv(process.env)
38
30
  await configureLogger()
39
31
 
40
32
  ensureDatabaseServiceConfigured()
@@ -58,7 +50,7 @@ export async function initializeSandboxedWorkerRuntime(): Promise<void> {
58
50
 
59
51
  await waitForDatabaseBootstrap({
60
52
  databaseService,
61
- expectedFingerprint: process.env.DB_SCHEMA_FINGERPRINT,
53
+ expectedFingerprint: env.DB_SCHEMA_FINGERPRINT,
62
54
  label: 'sandboxed worker runtime',
63
55
  logger: serverLogger,
64
56
  connect: () => databaseService.connect(),
@@ -0,0 +1,7 @@
1
+ export * from './bootstrap'
2
+ export * from './regular-chat-memory-digest.helpers'
3
+ export * from './worker-utils'
4
+ export * from './utils/file-section-chunker'
5
+ export * from './utils/repomix-file-sections'
6
+ export * from './utils/repo-structure-extractor'
7
+ export * from './utils/repomix-process-concurrency'
@@ -1,4 +1,4 @@
1
- import { toTimestamp } from '@lota-sdk/shared/runtime/chat-message-metadata'
1
+ import { toTimestamp } from '@lota-sdk/shared'
2
2
  import { BoundQuery } from 'surrealdb'
3
3
  import { z } from 'zod'
4
4
 
@@ -1,4 +1,4 @@
1
- import { toTimestamp } from '@lota-sdk/shared/runtime/chat-message-metadata'
1
+ import { toTimestamp } from '@lota-sdk/shared'
2
2
  import { BoundQuery } from 'surrealdb'
3
3
  import { z } from 'zod'
4
4
 
@@ -7,7 +7,7 @@ import { ensureRecordId, recordIdToString } from '../db/record-id'
7
7
  import type { RecordIdRef } from '../db/record-id'
8
8
  import { databaseService } from '../db/service'
9
9
  import { TABLES } from '../db/tables'
10
- import { createDefaultEmbeddings } from '../embeddings/provider'
10
+ import { getDefaultEmbeddings } from '../embeddings/provider'
11
11
  import type { SkillExtractionJob } from '../queues/skill-extraction.queue'
12
12
  import { createHelperModelRuntime } from '../runtime/helper-model'
13
13
  import { getRuntimeAdapters, withConfiguredWorkspaceMemoryLock } from '../runtime/runtime-extensions'
@@ -54,7 +54,7 @@ interface SkillExtractionRunResult {
54
54
  extractedSkills: number
55
55
  }
56
56
 
57
- const embeddings = createDefaultEmbeddings()
57
+ const embeddings = getDefaultEmbeddings()
58
58
 
59
59
  const helperModelRuntime = createHelperModelRuntime()
60
60
 
@@ -1,16 +1,15 @@
1
- export const DEFAULT_REPOMIX_CHUNK_MAX_CHARS = 250_000
2
- const MIN_REPOMIX_CHUNK_MAX_CHARS = 4_000
3
- export const DEFAULT_REPOMIX_CHUNK_MIN_CHARS = 10_000
1
+ export const DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS = 250_000
2
+ const MIN_FILE_SECTION_CHUNK_MAX_CHARS = 4_000
3
+ export const DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS = 10_000
4
4
  const SECTION_SEPARATOR_LENGTH = 2
5
- const FILE_SECTION_HEADER_SOURCE = '^## File:\\s+(.+)$'
6
5
 
7
- interface RepomixSection {
6
+ export interface FileSection {
8
7
  kind: 'preamble' | 'file'
9
8
  content: string
10
9
  filePath?: string
11
10
  }
12
11
 
13
- export interface RepomixContextChunk {
12
+ export interface FileSectionChunk {
14
13
  index: number
15
14
  totalChunks: number
16
15
  content: string
@@ -22,7 +21,7 @@ export interface RepomixContextChunk {
22
21
  lastFilePath: string | null
23
22
  }
24
23
 
25
- interface RepomixChunkOptions {
24
+ export interface FileSectionChunkOptions {
26
25
  maxChars?: number
27
26
  minChunkChars?: number
28
27
  preserveCodeFenceIntegrity?: boolean
@@ -35,14 +34,14 @@ function estimateTokenCountFromChars(text: string): number {
35
34
 
36
35
  function normalizeMaxChars(value?: number): number {
37
36
  if (typeof value !== 'number' || !Number.isFinite(value)) {
38
- return DEFAULT_REPOMIX_CHUNK_MAX_CHARS
37
+ return DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS
39
38
  }
40
- return Math.max(MIN_REPOMIX_CHUNK_MAX_CHARS, Math.floor(value))
39
+ return Math.max(MIN_FILE_SECTION_CHUNK_MAX_CHARS, Math.floor(value))
41
40
  }
42
41
 
43
42
  function normalizeMinChunkChars(value: number | undefined, maxChars: number): number {
44
43
  if (typeof value !== 'number' || !Number.isFinite(value)) {
45
- return Math.min(DEFAULT_REPOMIX_CHUNK_MIN_CHARS, Math.floor(maxChars * 0.35))
44
+ return Math.min(DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS, Math.floor(maxChars * 0.35))
46
45
  }
47
46
  const normalized = Math.max(512, Math.floor(value))
48
47
  return Math.min(normalized, Math.floor(maxChars * 0.6))
@@ -156,9 +155,9 @@ function mergeTinyTailParts(parts: string[], options: { minChunkChars: number; m
156
155
  }
157
156
 
158
157
  async function splitOversizedSection(
159
- section: RepomixSection,
158
+ section: FileSection,
160
159
  options: { maxChars: number; minChunkChars: number; preserveCodeFenceIntegrity: boolean },
161
- ): Promise<RepomixSection[]> {
160
+ ): Promise<FileSection[]> {
162
161
  if (section.content.length <= options.maxChars) {
163
162
  return [section]
164
163
  }
@@ -188,43 +187,12 @@ async function splitOversizedSection(
188
187
  }))
189
188
  }
190
189
 
191
- function parseRepomixSections(repomixOutput: string): RepomixSection[] {
192
- const source = repomixOutput.trim()
193
- if (!source) return []
194
-
195
- const matches = Array.from(source.matchAll(new RegExp(FILE_SECTION_HEADER_SOURCE, 'gm')))
196
- if (matches.length === 0) {
197
- return [{ kind: 'preamble', content: source }]
198
- }
199
-
200
- const sections: RepomixSection[] = []
201
- const firstMatch = matches.at(0)
202
- const firstIndex = firstMatch ? firstMatch.index : 0
203
- if (firstIndex > 0) {
204
- const preamble = source.slice(0, firstIndex).trim()
205
- if (preamble) {
206
- sections.push({ kind: 'preamble', content: preamble })
207
- }
208
- }
209
-
210
- for (const [index, match] of matches.entries()) {
211
- const start = match.index
212
- const nextStart = matches[index + 1]?.index ?? source.length
213
- const content = source.slice(start, nextStart).trim()
214
- if (!content) continue
215
- const filePath = (match[1] ?? '').trim()
216
- sections.push({ kind: 'file', content, filePath: filePath || undefined })
217
- }
218
-
219
- return sections
220
- }
221
-
222
190
  function mergeTinyChunks(
223
- chunks: Omit<RepomixContextChunk, 'index' | 'totalChunks'>[],
191
+ chunks: Omit<FileSectionChunk, 'index' | 'totalChunks'>[],
224
192
  options: { minChunkChars: number; maxChars: number },
225
- ): Omit<RepomixContextChunk, 'index' | 'totalChunks'>[] {
193
+ ): Omit<FileSectionChunk, 'index' | 'totalChunks'>[] {
226
194
  if (chunks.length <= 1) return chunks
227
- const merged: Omit<RepomixContextChunk, 'index' | 'totalChunks'>[] = []
195
+ const merged: Omit<FileSectionChunk, 'index' | 'totalChunks'>[] = []
228
196
 
229
197
  for (const chunk of chunks) {
230
198
  const previous = merged.at(-1)
@@ -252,15 +220,18 @@ function mergeTinyChunks(
252
220
  return merged
253
221
  }
254
222
 
255
- export async function chunkRepomixOutput(
256
- repomixOutput: string,
257
- options: RepomixChunkOptions = {},
258
- ): Promise<RepomixContextChunk[]> {
223
+ export async function chunkFileSections(
224
+ fileSections: readonly FileSection[],
225
+ options: FileSectionChunkOptions = {},
226
+ ): Promise<FileSectionChunk[]> {
259
227
  const maxChars = normalizeMaxChars(options.maxChars)
260
228
  const minChunkChars = normalizeMinChunkChars(options.minChunkChars, maxChars)
261
229
  const preserveCodeFenceIntegrity = options.preserveCodeFenceIntegrity ?? true
262
230
 
263
- const rawSections = parseRepomixSections(repomixOutput)
231
+ const rawSections = fileSections
232
+ .map((section) => ({ kind: section.kind, content: section.content.trim(), filePath: section.filePath }))
233
+ .filter((section) => section.content.length > 0)
234
+
264
235
  const splitSections = await Promise.all(
265
236
  rawSections.map(
266
237
  async (section) => await splitOversizedSection(section, { maxChars, minChunkChars, preserveCodeFenceIntegrity }),
@@ -270,7 +241,7 @@ export async function chunkRepomixOutput(
270
241
 
271
242
  if (sections.length === 0) return []
272
243
 
273
- const chunks: Omit<RepomixContextChunk, 'index' | 'totalChunks'>[] = []
244
+ const chunks: Omit<FileSectionChunk, 'index' | 'totalChunks'>[] = []
274
245
  let currentParts: string[] = []
275
246
  let currentCharLength = 0
276
247
  let currentSectionCount = 0
@@ -1,17 +1,14 @@
1
1
  import { readdir, readFile } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
 
4
- import {
5
- RepositoryStructureArtifactSchema,
6
- RepositoryStructureSummarySchema,
7
- } from '@lota-sdk/shared/schemas/repository-structure'
4
+ import { RepositoryStructureArtifactSchema, RepositoryStructureSummarySchema } from '@lota-sdk/shared'
8
5
  import type {
9
6
  RepositoryStructureArtifact,
10
7
  RepositoryStructureComponent,
11
8
  RepositoryStructureShape,
12
9
  RepositoryStructureSignal,
13
10
  RepositoryStructureSummary,
14
- } from '@lota-sdk/shared/schemas/repository-structure'
11
+ } from '@lota-sdk/shared'
15
12
 
16
13
  const EXTRACTOR_VERSION = 'repository-structure-extractor.v1'
17
14
  const IGNORED_DIR_NAMES = new Set([
@@ -0,0 +1,42 @@
1
+ import type { FileSection, FileSectionChunk, FileSectionChunkOptions } from './file-section-chunker'
2
+ import { chunkFileSections } from './file-section-chunker'
3
+
4
+ const FILE_SECTION_HEADER_SOURCE = '^## File:\\s+(.+)$'
5
+
6
+ export function parseRepomixFileSections(repomixOutput: string): FileSection[] {
7
+ const source = repomixOutput.trim()
8
+ if (!source) return []
9
+
10
+ const matches = Array.from(source.matchAll(new RegExp(FILE_SECTION_HEADER_SOURCE, 'gm')))
11
+ if (matches.length === 0) {
12
+ return [{ kind: 'preamble', content: source }]
13
+ }
14
+
15
+ const sections: FileSection[] = []
16
+ const firstMatch = matches.at(0)
17
+ const firstIndex = firstMatch ? firstMatch.index : 0
18
+ if (firstIndex > 0) {
19
+ const preamble = source.slice(0, firstIndex).trim()
20
+ if (preamble) {
21
+ sections.push({ kind: 'preamble', content: preamble })
22
+ }
23
+ }
24
+
25
+ for (const [index, match] of matches.entries()) {
26
+ const start = match.index
27
+ const nextStart = matches[index + 1]?.index ?? source.length
28
+ const content = source.slice(start, nextStart).trim()
29
+ if (!content) continue
30
+ const filePath = (match[1] ?? '').trim()
31
+ sections.push({ kind: 'file', content, filePath: filePath || undefined })
32
+ }
33
+
34
+ return sections
35
+ }
36
+
37
+ export async function chunkRepomixFileSections(
38
+ repomixOutput: string,
39
+ options: FileSectionChunkOptions = {},
40
+ ): Promise<FileSectionChunk[]> {
41
+ return await chunkFileSections(parseRepomixFileSections(repomixOutput), options)
42
+ }
@@ -1,121 +0,0 @@
1
- import type { ZodTypeAny } from 'zod'
2
- import { z } from 'zod'
3
-
4
- const logLevelValues = ['trace', 'debug', 'info', 'warning', 'error', 'fatal'] as const
5
-
6
- export const nodeServerEnvShape = {
7
- NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
8
- PORT: z.coerce.number().default(3000),
9
- MAIN_CLIENT_URL: z.string().min(1).default('http://localhost:5173'),
10
- } as const satisfies Record<string, ZodTypeAny>
11
-
12
- export const surrealDbEnvShape = {
13
- SURREALDB_URL: z.string().min(1, 'SurrealDB URL is required'),
14
- SURREALDB_USER: z.string().min(1, 'SurrealDB user is required'),
15
- SURREALDB_PASSWORD: z.string().min(1, 'SurrealDB password is required'),
16
- SURREALDB_NAMESPACE: z.string().min(1, 'SurrealDB namespace is required'),
17
- } as const satisfies Record<string, ZodTypeAny>
18
-
19
- export const betterAuthEnvShape = {
20
- BETTER_AUTH_SECRET: z.string().min(32, 'Better Auth secret must be at least 32 characters long'),
21
- } as const satisfies Record<string, ZodTypeAny>
22
-
23
- export const serverUrlEnvShape = { SERVER_URL: z.string().min(1, 'Server URL is required') } as const satisfies Record<
24
- string,
25
- ZodTypeAny
26
- >
27
-
28
- export const aiGatewayEnvShape = {
29
- AI_GATEWAY_URL: z.string().url('AI gateway URL is required'),
30
- AI_GATEWAY_KEY: z.string().min(1, 'AI gateway key is required'),
31
- AI_GATEWAY_ADMIN: z.string().min(1).optional(),
32
- AI_GATEWAY_PASS: z.string().min(1).optional(),
33
- AI_EMBEDDING_MODEL: z.string().default('openai/text-embedding-3-small'),
34
- } as const satisfies Record<string, ZodTypeAny>
35
-
36
- export const firecrawlEnvShape = {
37
- FIRECRAWL_API_KEY: z
38
- .string()
39
- .min(1, 'Firecrawl API key is required')
40
- .refine((value) => value.startsWith('fc-'), 'Firecrawl API key must start with fc-')
41
- .refine((value) => value !== 'dev-fire-key', 'Firecrawl API key placeholder is not allowed'),
42
- FIRECRAWL_API_BASE_URL: z.string().url().optional(),
43
- } as const satisfies Record<string, ZodTypeAny>
44
-
45
- export const memorySearchEnvShape = {
46
- MEMORY_SEARCH_K: z.coerce.number().int().positive().default(6),
47
- } as const satisfies Record<string, ZodTypeAny>
48
-
49
- export const redisEnvShape = { REDIS_URL: z.string().min(1, 'Redis URL is required') } as const satisfies Record<
50
- string,
51
- ZodTypeAny
52
- >
53
-
54
- export const s3StorageEnvShape = {
55
- S3_ENDPOINT: z.string().min(1, 'S3 endpoint is required'),
56
- S3_BUCKET: z.string().min(1, 'S3 bucket is required'),
57
- S3_REGION: z.string().default('garage'),
58
- S3_ACCESS_KEY_ID: z.string().min(1, 'S3 access key is required'),
59
- S3_SECRET_ACCESS_KEY: z.string().min(1, 'S3 secret access key is required'),
60
- ATTACHMENT_URL_EXPIRES_IN: z.coerce.number().positive('Attachment URL expiry must be positive').default(1800),
61
- } as const satisfies Record<string, ZodTypeAny>
62
-
63
- export const loggingEnvShape = { LOG_LEVEL: z.enum(logLevelValues).default('info') } as const satisfies Record<
64
- string,
65
- ZodTypeAny
66
- >
67
-
68
- export const githubAppEnvShape = {
69
- GITHUB_APP_ID: z.string().optional(),
70
- GITHUB_PRIVATE_KEY: z.string().optional(),
71
- GITHUB_APP_SLUG: z.string().optional(),
72
- } as const satisfies Record<string, ZodTypeAny>
73
-
74
- export const linearOauthEnvShape = {
75
- LINEAR_CLIENT_ID: z.string().optional(),
76
- LINEAR_CLIENT_SECRET: z.string().optional(),
77
- LINEAR_REDIRECT_URI: z.string().optional(),
78
- } as const satisfies Record<string, ZodTypeAny>
79
-
80
- export const marketingStudioEnvShape = {
81
- MARKETING_STUDIO_BASE_URL: z.string().url().optional(),
82
- MARKETING_STUDIO_ADMIN_API_KEY: z.string().min(1).optional(),
83
- MARKETING_STUDIO_WEBHOOK_SIGNING_SECRET: z.string().min(1).optional(),
84
- } as const satisfies Record<string, ZodTypeAny>
85
-
86
- export function envKeys<TShape extends Record<string, ZodTypeAny>>(shape: TShape) {
87
- return Object.freeze(Object.keys(shape)) as readonly Extract<keyof TShape, string>[]
88
- }
89
-
90
- const lotaSdkEnvSchema = z.object({
91
- ...aiGatewayEnvShape,
92
- ...memorySearchEnvShape,
93
- ...redisEnvShape,
94
- ...loggingEnvShape,
95
- ...s3StorageEnvShape,
96
- ...firecrawlEnvShape,
97
- })
98
-
99
- export const lotaSdkEnvKeys = Object.freeze([
100
- ...envKeys(aiGatewayEnvShape),
101
- ...envKeys(memorySearchEnvShape),
102
- ...envKeys(redisEnvShape),
103
- ...envKeys(loggingEnvShape),
104
- ...envKeys(s3StorageEnvShape),
105
- ...envKeys(firecrawlEnvShape),
106
- ]) as readonly string[]
107
-
108
- type LotaSdkEnv = z.infer<typeof lotaSdkEnvSchema>
109
-
110
- let _env: LotaSdkEnv | undefined
111
-
112
- export function setEnv(value: LotaSdkEnv): void {
113
- _env = value
114
- }
115
-
116
- export const env: LotaSdkEnv = new Proxy({} as LotaSdkEnv, {
117
- get(_target, prop: string) {
118
- if (!_env) throw new Error(`lota-sdk env not configured. Call setEnv() before accessing env.${prop}`)
119
- return (_env as Record<string, unknown>)[prop]
120
- },
121
- })
@@ -1 +0,0 @@
1
- export type AgentSkill = string