@lota-sdk/core 0.4.13 → 0.4.15

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 (139) hide show
  1. package/package.json +4 -4
  2. package/src/ai/embedding-cache.ts +17 -11
  3. package/src/ai-gateway/ai-gateway.ts +164 -94
  4. package/src/ai-gateway/index.ts +4 -1
  5. package/src/config/agent-defaults.ts +2 -2
  6. package/src/config/agent-types.ts +1 -1
  7. package/src/config/constants.ts +1 -1
  8. package/src/create-runtime.ts +259 -200
  9. package/src/db/cursor-pagination.ts +2 -9
  10. package/src/db/memory-store.ts +194 -175
  11. package/src/db/memory.ts +125 -71
  12. package/src/db/schema-fingerprint.ts +5 -4
  13. package/src/db/service-normalization.ts +4 -3
  14. package/src/db/service.ts +3 -2
  15. package/src/db/startup.ts +15 -16
  16. package/src/effect/errors.ts +161 -21
  17. package/src/effect/index.ts +0 -1
  18. package/src/embeddings/provider.ts +15 -7
  19. package/src/queues/autonomous-job.queue.ts +10 -22
  20. package/src/queues/delayed-node-promotion.queue.ts +8 -14
  21. package/src/queues/document-processor.queue.ts +13 -4
  22. package/src/queues/memory-consolidation.queue.ts +26 -14
  23. package/src/queues/plan-agent-heartbeat.queue.ts +48 -31
  24. package/src/queues/plan-scheduler.queue.ts +37 -15
  25. package/src/queues/queue-factory.ts +59 -35
  26. package/src/queues/standalone-worker.ts +3 -2
  27. package/src/redis/connection.ts +10 -3
  28. package/src/redis/org-memory-lock.ts +1 -1
  29. package/src/redis/redis-lease-lock.ts +5 -5
  30. package/src/redis/stream-context.ts +1 -1
  31. package/src/runtime/chat-message.ts +64 -1
  32. package/src/runtime/chat-run-orchestration.ts +33 -20
  33. package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
  34. package/src/runtime/context-compaction/context-compaction.ts +78 -66
  35. package/src/runtime/domain-layer.ts +19 -13
  36. package/src/runtime/execution-plan.ts +7 -3
  37. package/src/runtime/memory/memory-block.ts +3 -9
  38. package/src/runtime/memory/memory-scope.ts +3 -1
  39. package/src/runtime/plugin-resolution.ts +2 -1
  40. package/src/runtime/post-turn-side-effects.ts +6 -5
  41. package/src/runtime/retrieval-adapters.ts +8 -20
  42. package/src/runtime/runtime-config.ts +3 -9
  43. package/src/runtime/runtime-extensions.ts +2 -4
  44. package/src/runtime/runtime-lifecycle.ts +56 -16
  45. package/src/runtime/runtime-services.ts +180 -102
  46. package/src/runtime/runtime-worker-registry.ts +3 -1
  47. package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
  48. package/src/runtime/social-chat/social-chat-history.ts +21 -18
  49. package/src/runtime/social-chat/social-chat.ts +356 -223
  50. package/src/runtime/specialist-runner.ts +3 -1
  51. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
  52. package/src/runtime/thread-turn-context.ts +142 -102
  53. package/src/runtime/turn-lifecycle.ts +15 -46
  54. package/src/services/agent-activity.service.ts +1 -1
  55. package/src/services/agent-executor.service.ts +107 -77
  56. package/src/services/autonomous-job.service.ts +354 -293
  57. package/src/services/background-work.service.ts +3 -3
  58. package/src/services/context-compaction.service.ts +7 -2
  59. package/src/services/document-chunk.service.ts +50 -32
  60. package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
  61. package/src/services/execution-plan/execution-plan.service.ts +162 -179
  62. package/src/services/feedback-loop.service.ts +5 -4
  63. package/src/services/graph-full-routing.ts +37 -36
  64. package/src/services/institutional-memory.service.ts +28 -30
  65. package/src/services/learned-skill.service.ts +107 -72
  66. package/src/services/memory/memory-errors.ts +4 -23
  67. package/src/services/memory/memory-org-memory.ts +10 -5
  68. package/src/services/memory/memory-rerank.ts +18 -6
  69. package/src/services/memory/memory.service.ts +170 -111
  70. package/src/services/memory/rerank.service.ts +29 -20
  71. package/src/services/organization-member.service.ts +1 -1
  72. package/src/services/organization.service.ts +69 -75
  73. package/src/services/ownership-dispatcher.service.ts +40 -39
  74. package/src/services/plan/plan-agent-heartbeat.service.ts +22 -24
  75. package/src/services/plan/plan-agent-query.service.ts +39 -31
  76. package/src/services/plan/plan-completion-side-effects.ts +13 -17
  77. package/src/services/plan/plan-coordination.service.ts +2 -1
  78. package/src/services/plan/plan-cycle.service.ts +6 -5
  79. package/src/services/plan/plan-deadline.service.ts +57 -54
  80. package/src/services/plan/plan-event-delivery.service.ts +5 -4
  81. package/src/services/plan/plan-executor-graph.ts +18 -15
  82. package/src/services/plan/plan-executor.service.ts +235 -262
  83. package/src/services/plan/plan-run.service.ts +169 -93
  84. package/src/services/plan/plan-scheduler.service.ts +192 -202
  85. package/src/services/plan/plan-template.service.ts +1 -1
  86. package/src/services/plan/plan-transaction-events.ts +1 -1
  87. package/src/services/plan/plan-workspace.service.ts +23 -14
  88. package/src/services/plugin-executor.service.ts +5 -9
  89. package/src/services/queue-job.service.ts +117 -59
  90. package/src/services/recent-activity-title.service.ts +13 -12
  91. package/src/services/recent-activity.service.ts +6 -1
  92. package/src/services/social-chat-history.service.ts +29 -25
  93. package/src/services/system-executor.service.ts +5 -9
  94. package/src/services/thread/thread-active-run.ts +2 -2
  95. package/src/services/thread/thread-listing.ts +61 -57
  96. package/src/services/thread/thread-memory-block.ts +73 -48
  97. package/src/services/thread/thread-message.service.ts +76 -65
  98. package/src/services/thread/thread-record-store.ts +8 -8
  99. package/src/services/thread/thread-title.service.ts +10 -4
  100. package/src/services/thread/thread-turn-execution.ts +43 -45
  101. package/src/services/thread/thread-turn-preparation.service.ts +257 -135
  102. package/src/services/thread/thread-turn-streaming.ts +83 -92
  103. package/src/services/thread/thread-turn.ts +18 -16
  104. package/src/services/thread/thread.service.ts +135 -100
  105. package/src/services/user.service.ts +45 -48
  106. package/src/storage/attachment-parser.ts +6 -2
  107. package/src/storage/attachment-storage.service.ts +5 -6
  108. package/src/storage/generated-document-storage.service.ts +1 -1
  109. package/src/system-agents/context-compaction.agent.ts +10 -9
  110. package/src/system-agents/delegated-agent-factory.ts +30 -6
  111. package/src/system-agents/memory-reranker.agent.ts +10 -9
  112. package/src/system-agents/memory.agent.ts +10 -9
  113. package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
  114. package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
  115. package/src/system-agents/skill-extractor.agent.ts +13 -12
  116. package/src/system-agents/skill-manager.agent.ts +13 -12
  117. package/src/system-agents/thread-router.agent.ts +11 -7
  118. package/src/system-agents/title-generator.agent.ts +13 -12
  119. package/src/tools/fetch-webpage.tool.ts +13 -13
  120. package/src/tools/memory-block.tool.ts +3 -1
  121. package/src/tools/plan-approval.tool.ts +4 -2
  122. package/src/tools/read-file-parts.tool.ts +10 -4
  123. package/src/tools/remember-memory.tool.ts +3 -1
  124. package/src/tools/research-topic.tool.ts +9 -5
  125. package/src/tools/search-web.tool.ts +16 -16
  126. package/src/tools/search.tool.ts +20 -5
  127. package/src/tools/team-think.tool.ts +61 -38
  128. package/src/utils/async.ts +5 -5
  129. package/src/utils/errors.ts +19 -18
  130. package/src/utils/sse-keepalive.ts +28 -25
  131. package/src/workers/bootstrap.ts +75 -11
  132. package/src/workers/memory-consolidation.worker.ts +82 -91
  133. package/src/workers/organization-learning.worker.ts +14 -4
  134. package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
  135. package/src/workers/skill-extraction.runner.ts +97 -61
  136. package/src/workers/utils/repo-structure-extractor.ts +13 -8
  137. package/src/workers/utils/thread-message-query.ts +24 -24
  138. package/src/workers/worker-utils.ts +23 -4
  139. package/src/effect/helpers.ts +0 -123
@@ -5,6 +5,7 @@ import { z } from 'zod'
5
5
 
6
6
  import type { ToolDefinition } from '../ai/definitions'
7
7
  import { aiLogger } from '../config/logger'
8
+ import { ERROR_TAGS } from '../effect/errors'
8
9
  import { formatUtcPromptDate, nowDate } from '../utils/date-time'
9
10
  import { getErrorMessage } from '../utils/errors'
10
11
  import { isRecord } from '../utils/string'
@@ -12,6 +13,7 @@ import { assertSubstantiveAgentResult } from './agent-result'
12
13
 
13
14
  type AgentProviderOptions = ToolLoopAgentSettings['providerOptions']
14
15
  type AgentModel = LanguageModel | (() => LanguageModel)
16
+ type AgentModelWithContext<TContext> = LanguageModel | ((context: TContext) => LanguageModel)
15
17
 
16
18
  interface DelegatedAgentDefinition {
17
19
  id: string
@@ -24,13 +26,26 @@ interface DelegatedAgentDefinition {
24
26
  maxSteps?: number
25
27
  maxOutputTokens?: number
26
28
  temperature?: number
29
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
27
30
  }
28
31
 
29
- interface DelegatedAgentDefinitionWithContext<TContext> extends Omit<DelegatedAgentDefinition, 'tools'> {
32
+ type DelegatedAgentRunPromise = <A, E>(
33
+ effect: Effect.Effect<A, E, never>,
34
+ options?: { signal?: AbortSignal },
35
+ ) => Promise<A>
36
+
37
+ interface DelegatedAgentDefinitionWithContext<TContext> extends Omit<
38
+ DelegatedAgentDefinition,
39
+ 'tools' | 'model' | 'runPromise'
40
+ > {
41
+ model: AgentModelWithContext<TContext>
30
42
  createTools: (context: TContext) => ToolSet
43
+ /** Pull runPromise off the caller's context so each tool invocation carries
44
+ * its own runtime binding rather than closing over a boot-time one. */
45
+ getRunPromise: (context: TContext) => DelegatedAgentRunPromise
31
46
  }
32
47
 
33
- class DelegatedAgentError extends Schema.TaggedErrorClass<DelegatedAgentError>()('DelegatedAgentError', {
48
+ class DelegatedAgentError extends Schema.TaggedErrorClass<DelegatedAgentError>()(ERROR_TAGS.DelegatedAgentError, {
34
49
  stage: Schema.Literals(['generate', 'validate', 'follow-up-generate', 'follow-up-validate']),
35
50
  label: Schema.String,
36
51
  message: Schema.String,
@@ -49,6 +64,13 @@ function resolveAgentModel(model: AgentModel): LanguageModel {
49
64
  return typeof model === 'function' ? model() : model
50
65
  }
51
66
 
67
+ function resolveAgentModelWithContext<TContext>(
68
+ model: AgentModelWithContext<TContext>,
69
+ context: TContext,
70
+ ): LanguageModel {
71
+ return typeof model === 'function' ? (model as (ctx: TContext) => LanguageModel)(context) : model
72
+ }
73
+
52
74
  function buildCurrentDateContext(now = nowDate()): string {
53
75
  return [`Today is ${formatUtcPromptDate(now)}.`, 'Use this exact date for any recency reasoning.'].join(' ')
54
76
  }
@@ -229,7 +251,7 @@ export function createDelegatedAgentTool(definition: DelegatedAgentDefinition):
229
251
  description: definition.description,
230
252
  inputSchema: z.object({ task: z.string().min(1) }),
231
253
  execute: ({ task }: { task: string }, { abortSignal }) =>
232
- Effect.runPromise(
254
+ definition.runPromise(
233
255
  Effect.gen(function* () {
234
256
  const agentTools = definition.tools
235
257
  const createAgent = () =>
@@ -268,6 +290,7 @@ export function createDelegatedAgentTool(definition: DelegatedAgentDefinition):
268
290
  }),
269
291
  }
270
292
  }),
293
+ abortSignal ? { signal: abortSignal } : undefined,
271
294
  ),
272
295
  }),
273
296
  } as const satisfies ToolDefinition<void>
@@ -286,13 +309,13 @@ export function createDelegatedAgentToolWithContext<TContext>(
286
309
  description: definition.description,
287
310
  inputSchema: z.object({ task: z.string().min(1) }),
288
311
  execute: ({ task }: { task: string }, { abortSignal }) =>
289
- Effect.runPromise(
312
+ definition.getRunPromise(context)(
290
313
  Effect.gen(function* () {
291
314
  const agentTools = definition.createTools(context)
292
315
  const createAgent = () =>
293
316
  new ToolLoopAgent({
294
317
  id: definition.id,
295
- model: resolveAgentModel(definition.model),
318
+ model: resolveAgentModelWithContext(definition.model, context),
296
319
  ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
297
320
  ...(definition.headers ? { headers: definition.headers } : {}),
298
321
  instructions: buildDelegatedAgentInstructions(definition.instructions, agentTools),
@@ -304,7 +327,7 @@ export function createDelegatedAgentToolWithContext<TContext>(
304
327
  })
305
328
  const synthesize = (prompt: string, abortSignal?: AbortSignal) =>
306
329
  generateText({
307
- model: resolveAgentModel(definition.model),
330
+ model: resolveAgentModelWithContext(definition.model, context),
308
331
  ...(definition.providerOptions ? { providerOptions: definition.providerOptions } : {}),
309
332
  ...(definition.headers ? { headers: definition.headers } : {}),
310
333
  system: `${definition.instructions.trim()}\n\nReturn a complete final markdown answer using only the provided research data. Do not call tools.`,
@@ -325,6 +348,7 @@ export function createDelegatedAgentToolWithContext<TContext>(
325
348
  }),
326
349
  }
327
350
  }),
351
+ abortSignal ? { signal: abortSignal } : undefined,
328
352
  ),
329
353
  }),
330
354
  } as const satisfies ToolDefinition<TContext>
@@ -1,7 +1,7 @@
1
1
  import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
 
4
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
4
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import {
7
7
  OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
@@ -29,12 +29,13 @@ Set every item.relevance as a string; use empty string when no reason is needed.
29
29
  </output>
30
30
  </agent-instructions>`
31
31
 
32
- export function createMemoryRerankerAgent(options: CreateHelperToolLoopAgentOptions) {
33
- return new ToolLoopAgent({
34
- id: 'memory-reranker',
35
- model: aiGatewayChatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
36
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
37
- providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
38
- ...resolveHelperAgentOptions(options),
39
- })
32
+ export function makeMemoryRerankerAgentFactory(models: AiGatewayModels) {
33
+ return (options: CreateHelperToolLoopAgentOptions) =>
34
+ new ToolLoopAgent({
35
+ id: 'memory-reranker',
36
+ model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
37
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
38
+ providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
39
+ ...resolveHelperAgentOptions(options),
40
+ })
40
41
  }
@@ -1,7 +1,7 @@
1
1
  import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
 
4
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
4
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import {
7
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
@@ -49,12 +49,13 @@ Return only the schema fields with no extra formatting.
49
49
  </format>
50
50
  </agent-instructions>`
51
51
 
52
- export function createOrgMemoryAgent(options: CreateHelperToolLoopAgentOptions) {
53
- return new ToolLoopAgent({
54
- id: 'org-memory',
55
- model: aiGatewayChatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
56
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
57
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
58
- ...resolveHelperAgentOptions(options),
59
- })
52
+ export function makeOrgMemoryAgentFactory(models: AiGatewayModels) {
53
+ return (options: CreateHelperToolLoopAgentOptions) =>
54
+ new ToolLoopAgent({
55
+ id: 'org-memory',
56
+ model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
57
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
58
+ providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
59
+ ...resolveHelperAgentOptions(options),
60
+ })
60
61
  }
@@ -1,7 +1,7 @@
1
1
  import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
 
4
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
4
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
7
7
  import {
@@ -76,18 +76,16 @@ Return only the title text. No quotes, labels, JSON, markdown, or explanation.
76
76
  </output>
77
77
  </agent-instructions>`
78
78
 
79
- export function createRecentActivityTitleRefinerAgent(
80
- agentConfig: ResolvedAgentConfig,
81
- options: CreateHelperToolLoopAgentOptions,
82
- ) {
83
- return new ToolLoopAgent({
84
- id: 'recent-activity-title-refiner',
85
- model: aiGatewayChatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
86
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
87
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
88
- ...resolveHelperAgentOptions(options, {
89
- instructions: buildRecentActivityTitleRefinerPrompt(agentConfig),
90
- maxOutputTokens: RECENT_ACTIVITY_TITLE_MAX_TOKENS,
91
- }),
92
- })
79
+ export function makeRecentActivityTitleRefinerAgentFactory(models: AiGatewayModels, agentConfig: ResolvedAgentConfig) {
80
+ return (options: CreateHelperToolLoopAgentOptions) =>
81
+ new ToolLoopAgent({
82
+ id: 'recent-activity-title-refiner',
83
+ model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
84
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
85
+ providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
86
+ ...resolveHelperAgentOptions(options, {
87
+ instructions: buildRecentActivityTitleRefinerPrompt(agentConfig),
88
+ maxOutputTokens: RECENT_ACTIVITY_TITLE_MAX_TOKENS,
89
+ }),
90
+ })
93
91
  }
@@ -1,7 +1,7 @@
1
1
  import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
 
4
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
4
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import {
7
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
@@ -24,15 +24,16 @@ Synthesize an updated workspace profile summary and durable memory facts from co
24
24
  </rules>
25
25
  </agent-instructions>`
26
26
 
27
- export function createRegularChatMemoryDigestAgent(options: CreateHelperToolLoopAgentOptions) {
28
- return new ToolLoopAgent({
29
- id: 'regular-chat-memory-digest',
30
- model: aiGatewayChatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
31
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
32
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
33
- ...resolveHelperAgentOptions(options, {
34
- instructions: regularChatMemoryDigestPrompt,
35
- maxOutputTokens: REGULAR_CHAT_MEMORY_DIGEST_MAX_TOKENS,
36
- }),
37
- })
27
+ export function makeRegularChatMemoryDigestAgentFactory(models: AiGatewayModels) {
28
+ return (options: CreateHelperToolLoopAgentOptions) =>
29
+ new ToolLoopAgent({
30
+ id: 'regular-chat-memory-digest',
31
+ model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
32
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
33
+ providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
34
+ ...resolveHelperAgentOptions(options, {
35
+ instructions: regularChatMemoryDigestPrompt,
36
+ maxOutputTokens: REGULAR_CHAT_MEMORY_DIGEST_MAX_TOKENS,
37
+ }),
38
+ })
38
39
  }
@@ -2,7 +2,7 @@ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
  import { z } from 'zod'
4
4
 
5
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
5
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
6
6
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
7
7
  import {
8
8
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
@@ -42,15 +42,16 @@ export const SkillExtractionOutputSchema = z.object({ candidates: z.array(SkillC
42
42
 
43
43
  export type SkillCandidate = z.infer<typeof SkillCandidateSchema>
44
44
 
45
- export function createSkillExtractorAgent(options: CreateHelperToolLoopAgentOptions) {
46
- return new ToolLoopAgent({
47
- id: 'skill-extractor',
48
- model: aiGatewayChatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
49
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
50
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
51
- ...resolveHelperAgentOptions(options, {
52
- instructions: skillExtractorPrompt,
53
- maxOutputTokens: SKILL_EXTRACTOR_MAX_TOKENS,
54
- }),
55
- })
45
+ export function makeSkillExtractorAgentFactory(models: AiGatewayModels) {
46
+ return (options: CreateHelperToolLoopAgentOptions) =>
47
+ new ToolLoopAgent({
48
+ id: 'skill-extractor',
49
+ model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
50
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
51
+ providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
52
+ ...resolveHelperAgentOptions(options, {
53
+ instructions: skillExtractorPrompt,
54
+ maxOutputTokens: SKILL_EXTRACTOR_MAX_TOKENS,
55
+ }),
56
+ })
56
57
  }
@@ -2,7 +2,7 @@ import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
  import { z } from 'zod'
4
4
 
5
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
5
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
6
6
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
7
7
  import {
8
8
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
@@ -66,15 +66,16 @@ export const SkillManagerOutputSchema = z.object({
66
66
  mergedSkill: MergedSkillSchema.optional(),
67
67
  })
68
68
 
69
- export function createSkillManagerAgent(options: CreateHelperToolLoopAgentOptions) {
70
- return new ToolLoopAgent({
71
- id: 'skill-manager',
72
- model: aiGatewayChatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
73
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
74
- providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
75
- ...resolveHelperAgentOptions(options, {
76
- instructions: skillManagerPrompt,
77
- maxOutputTokens: SKILL_MANAGER_MAX_TOKENS,
78
- }),
79
- })
69
+ export function makeSkillManagerAgentFactory(models: AiGatewayModels) {
70
+ return (options: CreateHelperToolLoopAgentOptions) =>
71
+ new ToolLoopAgent({
72
+ id: 'skill-manager',
73
+ model: models.chatModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
74
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
75
+ providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
76
+ ...resolveHelperAgentOptions(options, {
77
+ instructions: skillManagerPrompt,
78
+ maxOutputTokens: SKILL_MANAGER_MAX_TOKENS,
79
+ }),
80
+ })
80
81
  }
@@ -2,12 +2,12 @@ import { generateObject } from 'ai'
2
2
  import { Effect } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
5
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
6
6
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
7
7
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
8
8
  import { chatLogger } from '../config/logger'
9
9
  import type { ValidationError } from '../effect/errors'
10
- import { AiGenerationError } from '../effect/errors'
10
+ import { ERROR_TAGS, AiGenerationError } from '../effect/errors'
11
11
  import { zodParse } from '../effect/zod'
12
12
 
13
13
  const TriageResultSchema = z.object({ agentId: z.string(), routingContext: z.string() })
@@ -170,19 +170,19 @@ function toRouterGenerationError(label: 'triage' | 'check', error: unknown): AiG
170
170
 
171
171
  function generateRouterObjectEffect<TSchema extends z.ZodTypeAny>(params: {
172
172
  agentConfig: ResolvedAgentConfig
173
+ aiGatewayModels: AiGatewayModels
173
174
  schema: TSchema
174
175
  system: string
175
176
  prompt: string
176
177
  label: 'triage' | 'check'
177
178
  }): Effect.Effect<z.infer<TSchema> | null, never> {
178
- const modelId = params.agentConfig.routerModelId ?? 'openrouter/openai/gpt-5.4-nano'
179
+ const modelId = params.agentConfig.routerModelId ?? 'gpt-5.4-nano'
179
180
 
180
181
  return Effect.tryPromise({
181
182
  try: () =>
182
183
  generateObject({
183
- model: aiGatewayChatModel(modelId),
184
+ model: params.aiGatewayModels.chatModel(modelId),
184
185
  headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
185
- providerOptions: { openai: { reasoningEffort: 'low' } },
186
186
  schema: params.schema,
187
187
  system: params.system,
188
188
  prompt: params.prompt,
@@ -201,12 +201,12 @@ function generateRouterObjectEffect<TSchema extends z.ZodTypeAny>(params: {
201
201
  ),
202
202
  Effect.flatMap(({ object }) => zodParse(params.schema, object)),
203
203
  Effect.catchTags({
204
- AiGenerationError: (error: AiGenerationError) =>
204
+ [ERROR_TAGS.AiGenerationError]: (error: AiGenerationError) =>
205
205
  Effect.sync(() => {
206
206
  chatLogger.error`[thread-router] ${params.label} failed: ${error.message}`
207
207
  return null
208
208
  }),
209
- ValidationError: (error: ValidationError) =>
209
+ [ERROR_TAGS.ValidationError]: (error: ValidationError) =>
210
210
  Effect.sync(() => {
211
211
  chatLogger.error`[thread-router] ${params.label} failed: ${error.message}`
212
212
  return null
@@ -217,6 +217,7 @@ function generateRouterObjectEffect<TSchema extends z.ZodTypeAny>(params: {
217
217
 
218
218
  export function triageThreadMessage(params: {
219
219
  agentConfig: ResolvedAgentConfig
220
+ aiGatewayModels: AiGatewayModels
220
221
  threadTitle: string
221
222
  members: readonly string[]
222
223
  messageText: string
@@ -254,6 +255,7 @@ export function triageThreadMessage(params: {
254
255
 
255
256
  const parsed = yield* generateRouterObjectEffect({
256
257
  agentConfig: params.agentConfig,
258
+ aiGatewayModels: params.aiGatewayModels,
257
259
  schema: TriageResultSchema,
258
260
  system: TRIAGE_SYSTEM_PROMPT,
259
261
  prompt,
@@ -280,6 +282,7 @@ export function triageThreadMessage(params: {
280
282
 
281
283
  export function checkForNextAgent(params: {
282
284
  agentConfig: ResolvedAgentConfig
285
+ aiGatewayModels: AiGatewayModels
283
286
  threadTitle: string
284
287
  members: readonly string[]
285
288
  messageText: string
@@ -323,6 +326,7 @@ export function checkForNextAgent(params: {
323
326
 
324
327
  const parsed = yield* generateRouterObjectEffect({
325
328
  agentConfig: params.agentConfig,
329
+ aiGatewayModels: params.aiGatewayModels,
326
330
  schema: CheckResultObjectSchema,
327
331
  system: CHECK_SYSTEM_PROMPT,
328
332
  prompt,
@@ -1,7 +1,7 @@
1
1
  import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
2
2
  import { ToolLoopAgent } from 'ai'
3
3
 
4
- import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
4
+ import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
5
  import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import {
7
7
  OPENROUTER_FAST_REASONING_MODEL_ID,
@@ -30,15 +30,16 @@ Return only the title text. No quotes, no labels, no explanation.
30
30
  </output-format>
31
31
  </agent-instructions>`
32
32
 
33
- export function createThreadTitleGeneratorAgent(options: CreateHelperToolLoopAgentOptions) {
34
- return new ToolLoopAgent({
35
- id: 'thread-title-generator',
36
- model: aiGatewayChatModel(OPENROUTER_FAST_REASONING_MODEL_ID),
37
- headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
38
- providerOptions: OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
39
- ...resolveHelperAgentOptions(options, {
40
- instructions: THREAD_TITLE_GENERATOR_PROMPT,
41
- maxOutputTokens: THREAD_TITLE_MAX_TOKENS,
42
- }),
43
- })
33
+ export function makeThreadTitleGeneratorAgentFactory(models: AiGatewayModels) {
34
+ return (options: CreateHelperToolLoopAgentOptions) =>
35
+ new ToolLoopAgent({
36
+ id: 'thread-title-generator',
37
+ model: models.chatModel(OPENROUTER_FAST_REASONING_MODEL_ID),
38
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
39
+ providerOptions: OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
40
+ ...resolveHelperAgentOptions(options, {
41
+ instructions: THREAD_TITLE_GENERATOR_PROMPT,
42
+ maxOutputTokens: THREAD_TITLE_MAX_TOKENS,
43
+ }),
44
+ })
44
45
  }
@@ -12,6 +12,7 @@ import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
12
12
 
13
13
  export interface FetchWebpageToolContext {
14
14
  firecrawl: Firecrawl
15
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
15
16
  }
16
17
 
17
18
  class FetchWebpageToolError extends Schema.TaggedErrorClass<FetchWebpageToolError>()(
@@ -102,7 +103,7 @@ function buildFetchCitations(url: string, document: unknown): WebCitation[] {
102
103
 
103
104
  export const fetchWebpageTool = {
104
105
  name: 'fetchWebpage',
105
- create: ({ firecrawl }: FetchWebpageToolContext) =>
106
+ create: ({ firecrawl, runPromise }: FetchWebpageToolContext) =>
106
107
  tool({
107
108
  description: 'Retrieve and parse a single webpage.',
108
109
  inputSchema: z
@@ -113,18 +114,16 @@ export const fetchWebpageTool = {
113
114
  maxAge: z.number().int().min(1).optional(),
114
115
  })
115
116
  .strict(),
116
- execute: ({
117
- url,
118
- formats,
119
- onlyMainContent,
120
- maxAge,
121
- }: {
122
- url: string
123
- formats?: z.infer<typeof FormatSchema>[]
124
- onlyMainContent?: boolean
125
- maxAge?: number
126
- }) =>
127
- Effect.runPromise(
117
+ execute: (
118
+ {
119
+ url,
120
+ formats,
121
+ onlyMainContent,
122
+ maxAge,
123
+ }: { url: string; formats?: z.infer<typeof FormatSchema>[]; onlyMainContent?: boolean; maxAge?: number },
124
+ { abortSignal }: { abortSignal?: AbortSignal } = {},
125
+ ) =>
126
+ runPromise(
128
127
  Effect.gen(function* () {
129
128
  const result = yield* Effect.tryPromise({
130
129
  try: () =>
@@ -141,6 +140,7 @@ export const fetchWebpageTool = {
141
140
  })
142
141
  return { url, document: summarizeDocument(url, result), citations: buildFetchCitations(url, result) }
143
142
  }).pipe(Effect.withSpan('tool.fetchWebpage.execute')),
143
+ abortSignal ? { signal: abortSignal } : undefined,
144
144
  ),
145
145
  }),
146
146
  } as const satisfies ToolDefinition<FetchWebpageToolContext>
@@ -18,12 +18,14 @@ export function createMemoryBlockTool({
18
18
  threadId,
19
19
  agentLabel,
20
20
  threadService,
21
+ runPromise,
21
22
  getCurrentBlock,
22
23
  onAppend,
23
24
  }: {
24
25
  threadId: RecordIdRef
25
26
  agentLabel: string
26
27
  threadService: MemoryBlockThreadService
28
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
27
29
  getCurrentBlock?: () => string
28
30
  onAppend?: (value: string) => void
29
31
  }) {
@@ -48,7 +50,7 @@ export function createMemoryBlockTool({
48
50
 
49
51
  void safeEnqueue(
50
52
  () =>
51
- Effect.runPromise(
53
+ runPromise(
52
54
  Effect.gen(function* () {
53
55
  const updated = yield* threadService.appendMemoryBlock(threadId, prepared.formatted)
54
56
  onAppend?.(updated)
@@ -21,14 +21,15 @@ export function createPlanApprovalTool(params: {
21
21
  threadId: RecordIdRef
22
22
  actorId: string
23
23
  executionPlanService: PlanApprovalExecutionPlanService
24
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
24
25
  }) {
25
26
  return tool({
26
27
  description:
27
28
  'Approve, reject, or request changes to a plan that is pending approval. Use approve to start execution, reject to abort it, or modify to request a revised plan.',
28
29
  inputSchema: PlanApprovalArgsSchema,
29
30
  outputSchema: PlanApprovalToolResultDataSchema,
30
- execute: (input) =>
31
- Effect.runPromise(
31
+ execute: (input, { abortSignal }: { abortSignal?: AbortSignal } = {}) =>
32
+ params.runPromise(
32
33
  Effect.gen(function* () {
33
34
  switch (input.action) {
34
35
  case 'approve': {
@@ -71,6 +72,7 @@ export function createPlanApprovalTool(params: {
71
72
  }
72
73
  }
73
74
  }),
75
+ abortSignal ? { signal: abortSignal } : undefined,
74
76
  ),
75
77
  })
76
78
  }
@@ -3,6 +3,7 @@ import { Schema, Effect } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
5
  import type { ToolDefinition } from '../ai/definitions'
6
+ import { ERROR_TAGS } from '../effect/errors'
6
7
  import type { makeAttachmentService } from '../services/attachment.service'
7
8
  import type { ReadableUploadMetadata } from '../storage/attachment-types'
8
9
 
@@ -15,6 +16,7 @@ export interface ReadFilePartsToolContext {
15
16
  userId: string
16
17
  uploads: ReadableUploadMetadata[]
17
18
  attachmentService: ReadFilePartsAttachmentService
19
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
18
20
  }
19
21
 
20
22
  function toUploadSummary(upload: ReadableUploadMetadata) {
@@ -26,7 +28,7 @@ function toUploadSummary(upload: ReadableUploadMetadata) {
26
28
  }
27
29
  }
28
30
 
29
- class ReadFilePartsError extends Schema.TaggedErrorClass<ReadFilePartsError>()('ReadFilePartsError', {
31
+ class ReadFilePartsError extends Schema.TaggedErrorClass<ReadFilePartsError>()(ERROR_TAGS.ReadFilePartsError, {
30
32
  message: Schema.String,
31
33
  }) {}
32
34
 
@@ -47,15 +49,18 @@ function resolveUploadTargetEffect(params: {
47
49
 
48
50
  export const readFilePartsTool = {
49
51
  name: 'readFileParts',
50
- create: ({ orgId, userId, uploads, attachmentService }: ReadFilePartsToolContext) =>
52
+ create: ({ orgId, userId, uploads, attachmentService, runPromise }: ReadFilePartsToolContext) =>
51
53
  tool({
52
54
  description:
53
55
  'Read uploaded file content by part. Call with no args to list uploads metadata, then call with storageKey and part (25 pages per part).',
54
56
  inputSchema: z
55
57
  .object({ storageKey: z.string().trim().min(1).optional(), part: z.number().int().positive().default(1) })
56
58
  .strict(),
57
- execute: ({ storageKey, part }: { storageKey?: string; part?: number }) =>
58
- Effect.runPromise(
59
+ execute: (
60
+ { storageKey, part }: { storageKey?: string; part?: number },
61
+ { abortSignal }: { abortSignal?: AbortSignal } = {},
62
+ ) =>
63
+ runPromise(
59
64
  Effect.gen(function* () {
60
65
  const availableUploads = uploads.map(toUploadSummary)
61
66
  const selected = yield* resolveUploadTargetEffect({ uploads, storageKey })
@@ -86,6 +91,7 @@ export const readFilePartsTool = {
86
91
 
87
92
  return { availableUploads, selectedUpload: toUploadSummary(selected), ...parts }
88
93
  }),
94
+ abortSignal ? { signal: abortSignal } : undefined,
89
95
  ),
90
96
  }),
91
97
  } as const satisfies ToolDefinition<ReadFilePartsToolContext>
@@ -24,10 +24,12 @@ export function createRememberMemoryTool({
24
24
  orgId,
25
25
  agentName,
26
26
  memoryService,
27
+ runPromise,
27
28
  }: {
28
29
  orgId: RecordIdRef
29
30
  agentName: string
30
31
  memoryService: RememberMemoryService
32
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
31
33
  }) {
32
34
  return tool({
33
35
  description:
@@ -42,7 +44,7 @@ export function createRememberMemoryTool({
42
44
  const orgIdString = recordIdToString(orgId, TABLES.ORGANIZATION)
43
45
  void safeEnqueue(
44
46
  () =>
45
- Effect.runPromise(
47
+ runPromise(
46
48
  Effect.gen(function* () {
47
49
  const assessment = yield* memoryService.assessMemoryCandidate({ orgId: orgIdString, content: trimmed })
48
50
  if (!assessment) {