@lota-sdk/core 0.4.8 → 0.4.10

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 (272) hide show
  1. package/package.json +11 -12
  2. package/src/ai/embedding-cache.ts +96 -22
  3. package/src/ai-gateway/ai-gateway.ts +766 -223
  4. package/src/config/agent-defaults.ts +189 -75
  5. package/src/config/agent-types.ts +54 -4
  6. package/src/config/background-processing.ts +1 -1
  7. package/src/config/constants.ts +8 -2
  8. package/src/config/index.ts +0 -1
  9. package/src/config/logger.ts +299 -19
  10. package/src/config/thread-defaults.ts +40 -20
  11. package/src/create-runtime.ts +200 -449
  12. package/src/db/base.service.ts +52 -28
  13. package/src/db/cursor-pagination.ts +71 -30
  14. package/src/db/memory-query-builder.ts +2 -1
  15. package/src/db/memory-store.helpers.ts +4 -7
  16. package/src/db/memory-store.ts +868 -601
  17. package/src/db/memory.ts +396 -280
  18. package/src/db/record-id.ts +32 -10
  19. package/src/db/schema-fingerprint.ts +30 -12
  20. package/src/db/service-normalization.ts +288 -0
  21. package/src/db/service.ts +912 -779
  22. package/src/db/startup.ts +153 -68
  23. package/src/db/transaction-conflict.ts +15 -0
  24. package/src/effect/awaitable-effect.ts +96 -0
  25. package/src/effect/errors.ts +121 -0
  26. package/src/effect/helpers.ts +123 -0
  27. package/src/effect/index.ts +24 -0
  28. package/src/effect/layers.ts +238 -0
  29. package/src/effect/runtime-ref.ts +25 -0
  30. package/src/effect/runtime.ts +46 -0
  31. package/src/effect/services.ts +61 -0
  32. package/src/effect/zod.ts +43 -0
  33. package/src/embeddings/provider.ts +128 -83
  34. package/src/index.ts +48 -1
  35. package/src/openrouter/direct-provider.ts +11 -35
  36. package/src/queues/autonomous-job.queue.ts +117 -73
  37. package/src/queues/context-compaction.queue.ts +50 -17
  38. package/src/queues/delayed-node-promotion.queue.ts +46 -17
  39. package/src/queues/document-processor.queue.ts +52 -77
  40. package/src/queues/memory-consolidation.queue.ts +47 -32
  41. package/src/queues/organization-learning.queue.ts +26 -4
  42. package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
  43. package/src/queues/plan-scheduler.queue.ts +97 -33
  44. package/src/queues/post-chat-memory.queue.ts +56 -26
  45. package/src/queues/queue-factory.ts +227 -59
  46. package/src/queues/standalone-worker.ts +39 -0
  47. package/src/queues/title-generation.queue.ts +45 -11
  48. package/src/redis/connection.ts +182 -113
  49. package/src/redis/index.ts +6 -8
  50. package/src/redis/org-memory-lock.ts +60 -27
  51. package/src/redis/redis-lease-lock.ts +200 -121
  52. package/src/redis/runtime-connection.ts +20 -0
  53. package/src/redis/stream-context.ts +92 -46
  54. package/src/runtime/agent-identity-overrides.ts +2 -2
  55. package/src/runtime/agent-runtime-policy.ts +5 -2
  56. package/src/runtime/agent-stream-helpers.ts +24 -9
  57. package/src/runtime/chat-run-orchestration.ts +102 -19
  58. package/src/runtime/chat-run-registry.ts +36 -2
  59. package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
  60. package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
  61. package/src/runtime/domain-layer.ts +192 -0
  62. package/src/runtime/execution-plan-visibility.ts +2 -2
  63. package/src/runtime/execution-plan.ts +42 -15
  64. package/src/runtime/graph-designer.ts +16 -4
  65. package/src/runtime/helper-model.ts +139 -48
  66. package/src/runtime/index.ts +7 -8
  67. package/src/runtime/indexed-repositories-policy.ts +3 -3
  68. package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
  69. package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
  70. package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
  71. package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
  72. package/src/runtime/memory/memory-scope.ts +53 -0
  73. package/src/runtime/plugin-resolution.ts +124 -25
  74. package/src/runtime/plugin-types.ts +9 -1
  75. package/src/runtime/post-turn-side-effects.ts +177 -130
  76. package/src/runtime/retrieval-adapters.ts +40 -6
  77. package/src/runtime/runtime-accessors.ts +92 -0
  78. package/src/runtime/runtime-config.ts +150 -61
  79. package/src/runtime/runtime-extensions.ts +23 -25
  80. package/src/runtime/runtime-lifecycle.ts +124 -0
  81. package/src/runtime/runtime-services.ts +386 -0
  82. package/src/runtime/runtime-token.ts +47 -0
  83. package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
  84. package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
  85. package/src/runtime/social-chat/social-chat.ts +630 -0
  86. package/src/runtime/specialist-runner.ts +36 -10
  87. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
  88. package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
  89. package/src/runtime/thread-chat-helpers.ts +2 -2
  90. package/src/runtime/thread-plan-turn.ts +2 -1
  91. package/src/runtime/thread-turn-context.ts +183 -111
  92. package/src/runtime/turn-lifecycle.ts +93 -27
  93. package/src/services/agent-activity.service.ts +287 -203
  94. package/src/services/agent-executor.service.ts +253 -149
  95. package/src/services/artifact.service.ts +231 -149
  96. package/src/services/attachment.service.ts +171 -115
  97. package/src/services/autonomous-job.service.ts +890 -491
  98. package/src/services/background-work.service.ts +54 -0
  99. package/src/services/chat-run-registry.service.ts +13 -1
  100. package/src/services/context-compaction.service.ts +136 -86
  101. package/src/services/document-chunk.service.ts +151 -88
  102. package/src/services/execution-plan/execution-plan-approval.ts +26 -0
  103. package/src/services/execution-plan/execution-plan-context.ts +29 -0
  104. package/src/services/execution-plan/execution-plan-graph.ts +278 -0
  105. package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
  106. package/src/services/execution-plan/execution-plan-spec.ts +75 -0
  107. package/src/services/execution-plan/execution-plan.service.ts +1041 -0
  108. package/src/services/feedback-loop.service.ts +132 -76
  109. package/src/services/global-orchestrator.service.ts +101 -168
  110. package/src/services/graph-full-routing.ts +193 -0
  111. package/src/services/index.ts +19 -21
  112. package/src/services/institutional-memory.service.ts +213 -125
  113. package/src/services/learned-skill.service.ts +368 -260
  114. package/src/services/memory/memory-conversation.ts +95 -0
  115. package/src/services/memory/memory-errors.ts +27 -0
  116. package/src/services/memory/memory-org-memory.ts +50 -0
  117. package/src/services/memory/memory-preseeded.ts +86 -0
  118. package/src/services/memory/memory-rerank.ts +297 -0
  119. package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
  120. package/src/services/memory/memory.service.ts +674 -0
  121. package/src/services/memory/rerank.service.ts +201 -0
  122. package/src/services/monitoring-window.service.ts +92 -70
  123. package/src/services/mutating-approval.service.ts +62 -53
  124. package/src/services/node-workspace.service.ts +141 -98
  125. package/src/services/notification.service.ts +29 -16
  126. package/src/services/organization-member.service.ts +120 -66
  127. package/src/services/organization.service.ts +153 -77
  128. package/src/services/ownership-dispatcher.service.ts +456 -263
  129. package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
  130. package/src/services/plan/plan-agent-query.service.ts +322 -0
  131. package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
  132. package/src/services/plan/plan-artifact.service.ts +60 -0
  133. package/src/services/plan/plan-builder.service.ts +76 -0
  134. package/src/services/plan/plan-checkpoint.service.ts +103 -0
  135. package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
  136. package/src/services/plan/plan-completion-side-effects.ts +169 -0
  137. package/src/services/plan/plan-coordination.service.ts +181 -0
  138. package/src/services/plan/plan-cycle.service.ts +405 -0
  139. package/src/services/plan/plan-deadline.service.ts +533 -0
  140. package/src/services/plan/plan-event-delivery.service.ts +266 -0
  141. package/src/services/plan/plan-executor-context.ts +35 -0
  142. package/src/services/plan/plan-executor-graph.ts +522 -0
  143. package/src/services/plan/plan-executor-helpers.ts +307 -0
  144. package/src/services/plan/plan-executor-persistence.ts +209 -0
  145. package/src/services/plan/plan-executor.service.ts +1737 -0
  146. package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
  147. package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
  148. package/src/services/plan/plan-run-serialization.ts +15 -0
  149. package/src/services/plan/plan-run.service.ts +637 -0
  150. package/src/services/plan/plan-scheduler.service.ts +379 -0
  151. package/src/services/plan/plan-template.service.ts +224 -0
  152. package/src/services/plan/plan-transaction-events.ts +36 -0
  153. package/src/services/plan/plan-validator.service.ts +907 -0
  154. package/src/services/plan/plan-workspace.service.ts +131 -0
  155. package/src/services/plugin-executor.service.ts +102 -68
  156. package/src/services/quality-metrics.service.ts +112 -94
  157. package/src/services/queue-job.service.ts +288 -231
  158. package/src/services/recent-activity-title.service.ts +73 -36
  159. package/src/services/recent-activity.service.ts +274 -259
  160. package/src/services/skill-resolver.service.ts +38 -12
  161. package/src/services/social-chat-history.service.ts +190 -122
  162. package/src/services/system-executor.service.ts +96 -61
  163. package/src/services/thread/thread-active-run.ts +203 -0
  164. package/src/services/thread/thread-bootstrap.ts +385 -0
  165. package/src/services/thread/thread-listing.ts +199 -0
  166. package/src/services/thread/thread-memory-block.ts +130 -0
  167. package/src/services/thread/thread-message.service.ts +379 -0
  168. package/src/services/thread/thread-record-store.ts +155 -0
  169. package/src/services/thread/thread-title.service.ts +74 -0
  170. package/src/services/thread/thread-turn-execution.ts +280 -0
  171. package/src/services/thread/thread-turn-message-context.ts +73 -0
  172. package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
  173. package/src/services/thread/thread-turn-streaming.ts +403 -0
  174. package/src/services/thread/thread-turn-tracing.ts +35 -0
  175. package/src/services/thread/thread-turn.ts +376 -0
  176. package/src/services/thread/thread.service.ts +344 -0
  177. package/src/services/user.service.ts +82 -32
  178. package/src/services/write-intent-validator.service.ts +63 -51
  179. package/src/storage/attachment-parser.ts +69 -27
  180. package/src/storage/attachment-storage.service.ts +334 -275
  181. package/src/storage/generated-document-storage.service.ts +66 -34
  182. package/src/system-agents/agent-result.ts +3 -1
  183. package/src/system-agents/context-compaction.agent.ts +3 -3
  184. package/src/system-agents/delegated-agent-factory.ts +159 -90
  185. package/src/system-agents/helper-agent-options.ts +1 -1
  186. package/src/system-agents/memory-reranker.agent.ts +3 -3
  187. package/src/system-agents/memory.agent.ts +3 -3
  188. package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
  189. package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
  190. package/src/system-agents/skill-extractor.agent.ts +3 -3
  191. package/src/system-agents/skill-manager.agent.ts +3 -3
  192. package/src/system-agents/thread-router.agent.ts +157 -113
  193. package/src/system-agents/title-generator.agent.ts +3 -3
  194. package/src/tools/execution-plan.tool.ts +241 -171
  195. package/src/tools/fetch-webpage.tool.ts +29 -18
  196. package/src/tools/firecrawl-client.ts +26 -6
  197. package/src/tools/index.ts +1 -0
  198. package/src/tools/memory-block.tool.ts +14 -6
  199. package/src/tools/plan-approval.tool.ts +57 -47
  200. package/src/tools/read-file-parts.tool.ts +44 -33
  201. package/src/tools/remember-memory.tool.ts +65 -45
  202. package/src/tools/search-web.tool.ts +33 -22
  203. package/src/tools/search.tool.ts +41 -29
  204. package/src/tools/team-think.tool.ts +125 -84
  205. package/src/tools/user-questions.tool.ts +4 -3
  206. package/src/tools/web-tool-shared.ts +6 -0
  207. package/src/utils/async.ts +25 -22
  208. package/src/utils/crypto.ts +21 -0
  209. package/src/utils/date-time.ts +40 -1
  210. package/src/utils/errors.ts +111 -20
  211. package/src/utils/hono-error-handler.ts +24 -39
  212. package/src/utils/index.ts +2 -1
  213. package/src/utils/null-proto-record.ts +41 -0
  214. package/src/utils/sse-keepalive.ts +124 -21
  215. package/src/workers/bootstrap.ts +164 -52
  216. package/src/workers/memory-consolidation.worker.ts +325 -237
  217. package/src/workers/organization-learning.worker.ts +50 -16
  218. package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
  219. package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
  220. package/src/workers/skill-extraction.runner.ts +176 -93
  221. package/src/workers/utils/file-section-chunker.ts +8 -10
  222. package/src/workers/utils/repo-structure-extractor.ts +349 -260
  223. package/src/workers/utils/repomix-file-sections.ts +2 -2
  224. package/src/workers/utils/thread-message-query.ts +97 -38
  225. package/src/workers/worker-utils.ts +74 -31
  226. package/src/config/debug-logger.ts +0 -47
  227. package/src/config/search.ts +0 -3
  228. package/src/redis/connection-accessor.ts +0 -26
  229. package/src/runtime/agent-types.ts +0 -1
  230. package/src/runtime/context-compaction-runtime.ts +0 -87
  231. package/src/runtime/memory-scope.ts +0 -43
  232. package/src/runtime/social-chat-agent-runner.ts +0 -118
  233. package/src/runtime/social-chat.ts +0 -516
  234. package/src/runtime/team-consultation-orchestrator.ts +0 -272
  235. package/src/services/adaptive-playbook.service.ts +0 -152
  236. package/src/services/artifact-provenance.service.ts +0 -172
  237. package/src/services/chat-attachments.service.ts +0 -17
  238. package/src/services/context-compaction-runtime.singleton.ts +0 -13
  239. package/src/services/execution-plan.service.ts +0 -1118
  240. package/src/services/memory.service.ts +0 -914
  241. package/src/services/plan-agent-heartbeat.service.ts +0 -136
  242. package/src/services/plan-agent-query.service.ts +0 -267
  243. package/src/services/plan-artifact.service.ts +0 -50
  244. package/src/services/plan-builder.service.ts +0 -67
  245. package/src/services/plan-checkpoint.service.ts +0 -81
  246. package/src/services/plan-completion-side-effects.ts +0 -80
  247. package/src/services/plan-coordination.service.ts +0 -157
  248. package/src/services/plan-cycle.service.ts +0 -284
  249. package/src/services/plan-deadline.service.ts +0 -430
  250. package/src/services/plan-event-delivery.service.ts +0 -166
  251. package/src/services/plan-executor.service.ts +0 -1950
  252. package/src/services/plan-run.service.ts +0 -515
  253. package/src/services/plan-scheduler.service.ts +0 -240
  254. package/src/services/plan-template.service.ts +0 -177
  255. package/src/services/plan-validator.service.ts +0 -818
  256. package/src/services/plan-workspace.service.ts +0 -83
  257. package/src/services/rerank.service.ts +0 -156
  258. package/src/services/thread-message.service.ts +0 -275
  259. package/src/services/thread-plan-registry.service.ts +0 -22
  260. package/src/services/thread-title.service.ts +0 -39
  261. package/src/services/thread-turn-preparation.service.ts +0 -1147
  262. package/src/services/thread-turn.ts +0 -172
  263. package/src/services/thread.service.ts +0 -869
  264. package/src/utils/env.ts +0 -8
  265. /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
  266. /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
  267. /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
  268. /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
  269. /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
  270. /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
  271. /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
  272. /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
@@ -0,0 +1,403 @@
1
+ import type { ChatMessage } from '@lota-sdk/shared'
2
+ import { withMessageCreatedAt } from '@lota-sdk/shared'
3
+ import { convertToModelMessages, stepCountIs } from 'ai'
4
+ import type { PrepareStepFunction, StopCondition, ToolSet, UIMessageStreamWriter } from 'ai'
5
+ import { Effect, Ref, Schema, Stream } from 'effect'
6
+
7
+ import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../../config/agent-defaults'
8
+ import { aiLogger } from '../../config/logger'
9
+ import type { RecordIdRef } from '../../db/record-id'
10
+ import { effectTryMaybeAsync, makeEffectTryPromiseWithMessage } from '../../effect/helpers'
11
+ import { runPromise } from '../../effect/runtime'
12
+ import {
13
+ readRuntimeAgentIdentityOverrides,
14
+ resolveRuntimeAgentDisplayName,
15
+ } from '../../runtime/agent-identity-overrides'
16
+ import { createAgentMessageMetadata } from '../../runtime/agent-stream-helpers'
17
+ import { mergeInstructionSections } from '../../runtime/instruction-sections'
18
+ import type { getTurnHooks } from '../../runtime/runtime-extensions'
19
+ import {
20
+ asRecord,
21
+ collectToolOutputErrors,
22
+ extractMessageText,
23
+ readInstructionSections,
24
+ readOptionalString,
25
+ } from '../../runtime/thread-chat-helpers'
26
+ import { nowEpochMillis } from '../../utils/date-time'
27
+ import { isRecord } from '../../utils/string'
28
+ import { buildThreadTurnSpanAttributes, compactSpanAttributes } from './thread-turn-tracing'
29
+ import type { NormalizedThread } from './thread.types'
30
+
31
+ type ChatStreamChunk = Parameters<UIMessageStreamWriter<ChatMessage>['write']>[0]
32
+
33
+ interface UIMessageStreamResult {
34
+ toUIMessageStream(options: Record<string, unknown>): ReadableStream<unknown>
35
+ }
36
+
37
+ interface ToolLoopGenerateResult {
38
+ text: string
39
+ steps?: Array<{ toolResults?: Array<{ toolCallId: string; toolName: string; input?: unknown; output?: unknown }> }>
40
+ }
41
+
42
+ export class ThreadTurnStreamingError extends Schema.TaggedErrorClass<ThreadTurnStreamingError>()(
43
+ 'ThreadTurnStreamingError',
44
+ { message: Schema.String, cause: Schema.optional(Schema.Defect) },
45
+ ) {}
46
+
47
+ const effectTryPromise = makeEffectTryPromiseWithMessage(
48
+ (message, cause) => new ThreadTurnStreamingError({ message, cause }),
49
+ )
50
+
51
+ function effectFromMaybeEffect<A>(
52
+ evaluate: () => A | PromiseLike<A> | Effect.Effect<A, ThreadTurnStreamingError>,
53
+ message: string,
54
+ ): Effect.Effect<A, ThreadTurnStreamingError> {
55
+ return Effect.try({ try: evaluate, catch: (error) => new ThreadTurnStreamingError({ message, cause: error }) }).pipe(
56
+ Effect.flatMap((result) =>
57
+ Effect.isEffect(result)
58
+ ? result.pipe(Effect.mapError((error) => new ThreadTurnStreamingError({ message, cause: error })))
59
+ : effectTryMaybeAsync(
60
+ () => result,
61
+ (error) => new ThreadTurnStreamingError({ message, cause: error }),
62
+ ),
63
+ ),
64
+ )
65
+ }
66
+
67
+ function hasUIMessageStream(value: unknown): value is UIMessageStreamResult {
68
+ return (
69
+ typeof value === 'object' &&
70
+ value !== null &&
71
+ typeof (value as { toUIMessageStream?: unknown }).toUIMessageStream === 'function'
72
+ )
73
+ }
74
+
75
+ function optionalInstructionSection(value: unknown): string[] | undefined {
76
+ const section = readOptionalString(value)
77
+ return section ? [section] : undefined
78
+ }
79
+
80
+ function readStreamChunkType(value: ChatStreamChunk): string | undefined {
81
+ if (!isRecord(value)) return undefined
82
+ return typeof value.type === 'string' ? value.type : undefined
83
+ }
84
+
85
+ function isVisibleOutputChunkType(chunkType: string | undefined): boolean {
86
+ return chunkType === 'text-delta' || chunkType === 'reasoning-delta'
87
+ }
88
+
89
+ function isTextTokenChunkType(chunkType: string | undefined): boolean {
90
+ return chunkType === 'text-delta'
91
+ }
92
+
93
+ function buildFallbackResponseMessage(
94
+ result: ToolLoopGenerateResult,
95
+ ): Effect.Effect<ChatMessage, ThreadTurnStreamingError> {
96
+ const parts: ChatMessage['parts'] = []
97
+
98
+ for (const step of result.steps ?? []) {
99
+ for (const toolResult of step.toolResults ?? []) {
100
+ parts.push({
101
+ type: `tool-${toolResult.toolName}`,
102
+ toolCallId: toolResult.toolCallId,
103
+ state: 'output-available',
104
+ ...(toolResult.input !== undefined ? { input: toolResult.input } : {}),
105
+ ...(toolResult.output !== undefined ? { output: toolResult.output } : {}),
106
+ } as ChatMessage['parts'][number])
107
+ }
108
+ }
109
+
110
+ const text = result.text.trim()
111
+ if (text.length > 0) {
112
+ parts.push({ type: 'text', text })
113
+ }
114
+
115
+ if (parts.length === 0) {
116
+ return Effect.fail(
117
+ new ThreadTurnStreamingError({ message: 'Agent generate fallback did not produce any response parts.' }),
118
+ )
119
+ }
120
+
121
+ return Effect.succeed({ id: Bun.randomUUIDv7(), role: 'assistant', parts })
122
+ }
123
+
124
+ export interface StreamAgentResponseContext {
125
+ turnHooks: ReturnType<typeof getTurnHooks>
126
+ thread: NormalizedThread
127
+ threadRef: RecordIdRef
128
+ orgRef: RecordIdRef
129
+ userRef: RecordIdRef
130
+ userName?: string | null
131
+ onboardingActive: boolean
132
+ linearInstalled: boolean
133
+ githubInstalled: boolean
134
+ buildContextResult: Record<string, unknown> | null
135
+ getExecutionPlanInstructionSections: () => Effect.Effect<string[] | undefined, ThreadTurnStreamingError>
136
+ getPreSeededMemoriesSection: (agentId: string) => Effect.Effect<string | undefined, ThreadTurnStreamingError>
137
+ getLearnedSkillsSection: (
138
+ agentId: string,
139
+ queryText?: string,
140
+ ) => Effect.Effect<string | undefined, ThreadTurnStreamingError>
141
+ promptContext: { systemWorkspaceDetails?: string }
142
+ retrievedKnowledgeSection: string | undefined
143
+ memoryBlock: string
144
+ hookInstructionSections: string[]
145
+ runAbortSignal: AbortSignal
146
+ }
147
+
148
+ interface StreamAgentResponseParams {
149
+ agentId: string
150
+ mode: 'direct' | 'fixedThreadMode' | 'threadMode'
151
+ messages: ChatMessage[]
152
+ tools: ToolSet
153
+ observer: {
154
+ run: <T>(fn: () => T | Promise<T>) => Promise<T>
155
+ recordError: (error: unknown) => void
156
+ recordAbort: (error: unknown) => void
157
+ }
158
+ skills?: string[]
159
+ additionalInstructionSections?: string[]
160
+ includeExecutionPlanTools?: boolean
161
+ writer?: UIMessageStreamWriter<ChatMessage>
162
+ stopWhen?: StopCondition<ToolSet> | Array<StopCondition<ToolSet>>
163
+ prepareStep?: PrepareStepFunction<ToolSet>
164
+ abortSignal?: AbortSignal
165
+ }
166
+
167
+ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResponse')(function* (
168
+ ctx: StreamAgentResponseContext,
169
+ streamParams: StreamAgentResponseParams,
170
+ ) {
171
+ const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(ctx.buildContextResult)
172
+ const executionPlanInstructionSections =
173
+ streamParams.includeExecutionPlanTools === false
174
+ ? undefined
175
+ : yield* effectFromMaybeEffect(
176
+ () => ctx.getExecutionPlanInstructionSections(),
177
+ 'Failed to load execution plan instructions.',
178
+ ).pipe(Effect.withSpan('ThreadTurnStreaming.loadExecutionPlanInstructions'))
179
+
180
+ const agentResolution = asRecord(
181
+ yield* effectTryMaybeAsync(
182
+ () =>
183
+ ctx.turnHooks.resolveAgent?.({
184
+ agentId: streamParams.agentId,
185
+ mode: streamParams.mode,
186
+ thread: ctx.thread,
187
+ threadRef: ctx.threadRef,
188
+ orgRef: ctx.orgRef,
189
+ userRef: ctx.userRef,
190
+ userName: ctx.userName,
191
+ onboardingActive: ctx.onboardingActive,
192
+ linearInstalled: ctx.linearInstalled,
193
+ githubInstalled: ctx.githubInstalled,
194
+ skills: streamParams.skills,
195
+ additionalInstructionSections: streamParams.additionalInstructionSections,
196
+ context: ctx.buildContextResult,
197
+ }),
198
+ (error) => new ThreadTurnStreamingError({ message: 'Failed to resolve the runtime agent.', cause: error }),
199
+ ).pipe(Effect.withSpan('ThreadTurnStreaming.resolveAgent')),
200
+ )
201
+
202
+ const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? streamParams.agentId
203
+ const latestUserMessage = [...streamParams.messages].reverse().find((message) => message.role === 'user')
204
+ const latestUserMessageText = latestUserMessage ? extractMessageText(latestUserMessage).trim() : undefined
205
+ const [preSeededMemoriesSection, learnedSkillsSection] = yield* Effect.all([
206
+ effectFromMaybeEffect(
207
+ () => ctx.getPreSeededMemoriesSection(resolvedAgentId),
208
+ `Failed to load pre-seeded memories for ${resolvedAgentId}.`,
209
+ ),
210
+ effectFromMaybeEffect(
211
+ () => ctx.getLearnedSkillsSection(resolvedAgentId, latestUserMessageText),
212
+ `Failed to load learned skills for ${resolvedAgentId}.`,
213
+ ),
214
+ ]).pipe(Effect.withSpan('ThreadTurnStreaming.loadMemoriesAndSkills'))
215
+
216
+ const toolNames = new Set(Object.keys(streamParams.tools))
217
+ const hasRetrievalTools = [
218
+ 'memorySearch',
219
+ 'conversationSearch',
220
+ 'queryKnowledge',
221
+ 'researchTopic',
222
+ 'fetchWebpage',
223
+ 'inspectWebsite',
224
+ ].some((toolName) => toolNames.has(toolName))
225
+ const hasDomainRoutingSkills =
226
+ (streamParams.skills ?? []).some((skill) => skill.startsWith('cpo-') || skill.startsWith('mentor-')) ||
227
+ resolvedAgentId === 'cpo' ||
228
+ resolvedAgentId === 'mentor'
229
+ const agentFactoryConfig = getResolvedAgentFactoryConfig()
230
+ const config = getAgentRuntimeConfig({
231
+ agentId: resolvedAgentId,
232
+ threadType: ctx.thread.type,
233
+ mode: streamParams.mode,
234
+ skills: streamParams.skills,
235
+ onboardingActive: ctx.onboardingActive,
236
+ linearInstalled: ctx.linearInstalled,
237
+ systemWorkspaceDetails: ctx.promptContext.systemWorkspaceDetails,
238
+ preSeededMemoriesSection,
239
+ retrievedKnowledgeSection: ctx.retrievedKnowledgeSection,
240
+ threadMemoryBlock: ctx.memoryBlock,
241
+ learnedSkillsSection,
242
+ userMessageText: latestUserMessageText,
243
+ ruleOptions: { includeMemr3Rule: hasRetrievalTools, includeDomainReasoningFallbackRule: hasDomainRoutingSkills },
244
+ additionalInstructionSections: mergeInstructionSections(
245
+ executionPlanInstructionSections,
246
+ streamParams.additionalInstructionSections,
247
+ ctx.hookInstructionSections,
248
+ readInstructionSections(agentResolution?.additionalInstructionSections),
249
+ optionalInstructionSection(agentResolution?.extraInstructions),
250
+ ),
251
+ context: ctx.buildContextResult,
252
+ })
253
+ yield* Effect.annotateCurrentSpan({
254
+ resolvedAgentId,
255
+ inputMessageCount: streamParams.messages.length,
256
+ toolCount: toolNames.size,
257
+ skillCount: streamParams.skills?.length ?? 0,
258
+ hasRetrievalTools,
259
+ })
260
+
261
+ const modelMessages = yield* effectTryPromise(
262
+ () => convertToModelMessages(streamParams.messages, { ignoreIncompleteToolCalls: true }),
263
+ 'Failed to convert UI messages to model messages.',
264
+ ).pipe(Effect.withSpan('ThreadTurnStreaming.convertModelMessages'))
265
+
266
+ const agentFactory = agentFactoryConfig.createAgent[config.id]
267
+ if (!agentFactory) {
268
+ return yield* new ThreadTurnStreamingError({ message: `Agent factory "${config.id}" is not registered.` })
269
+ }
270
+ const agent = agentFactory({
271
+ mode: streamParams.mode,
272
+ tools: streamParams.tools,
273
+ extraInstructions: config.extraInstructions,
274
+ maxRetries: 3,
275
+ stopWhen: (agentResolution?.stopWhen as StopCondition<ToolSet> | Array<StopCondition<ToolSet>> | undefined) ??
276
+ streamParams.stopWhen ?? [stepCountIs(config.maxSteps)],
277
+ prepareStep: (agentResolution?.prepareStep as PrepareStepFunction<ToolSet> | undefined) ?? streamParams.prepareStep,
278
+ })
279
+ const agentAbortSignal = streamParams.abortSignal ?? ctx.runAbortSignal
280
+
281
+ const generateFallback = (cause: ThreadTurnStreamingError) =>
282
+ effectTryPromise(
283
+ () => streamParams.observer.run(() => agent.generate({ messages: modelMessages, abortSignal: agentAbortSignal })),
284
+ `Agent generate fallback failed for ${resolvedAgentId}.`,
285
+ ).pipe(
286
+ Effect.tap(() =>
287
+ Effect.sync(() => {
288
+ aiLogger.warn`Agent stream failed for ${resolvedAgentId}; falling back to generate: ${cause.message}`
289
+ }),
290
+ ),
291
+ Effect.flatMap((result) => buildFallbackResponseMessage(result as ToolLoopGenerateResult)),
292
+ )
293
+
294
+ const result = yield* effectTryPromise(
295
+ () => streamParams.observer.run(() => agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal })),
296
+ `Agent stream failed for ${resolvedAgentId}.`,
297
+ ).pipe(
298
+ Effect.tapError((error) =>
299
+ Effect.sync(() => {
300
+ if (agentAbortSignal.aborted) {
301
+ streamParams.observer.recordAbort(error)
302
+ return
303
+ }
304
+
305
+ streamParams.observer.recordError(error)
306
+ }),
307
+ ),
308
+ Effect.withSpan('ThreadTurnStreaming.startAgentStream'),
309
+ )
310
+
311
+ if (!hasUIMessageStream(result)) {
312
+ return yield* generateFallback(
313
+ new ThreadTurnStreamingError({ message: `Agent run for ${resolvedAgentId} did not expose a UI message stream.` }),
314
+ )
315
+ }
316
+
317
+ const { promise: finishedStream, resolve: resolveFinishedStream } = Promise.withResolvers<ChatMessage>()
318
+ const uiStream = result.toUIMessageStream({
319
+ generateMessageId: () => Bun.randomUUIDv7(),
320
+ originalMessages: streamParams.messages,
321
+ sendReasoning: true,
322
+ sendSources: true,
323
+ messageMetadata: createAgentMessageMetadata({
324
+ agentId: resolvedAgentId,
325
+ agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, resolvedAgentId),
326
+ }),
327
+ onFinish: ({ responseMessage: finishedResponseMessage }: { responseMessage: ChatMessage }) => {
328
+ resolveFinishedStream(withMessageCreatedAt(finishedResponseMessage, nowEpochMillis()))
329
+ },
330
+ }) as ReadableStream<ChatStreamChunk>
331
+ const streamStartedAt = performance.now()
332
+ const firstVisibleOutputRecorded = yield* Ref.make(false)
333
+ const firstTextTokenRecorded = yield* Ref.make(false)
334
+
335
+ const streamedResponse = yield* Stream.fromReadableStream({
336
+ evaluate: () => uiStream,
337
+ onError: (cause) => new ThreadTurnStreamingError({ message: 'Failed to read chat stream chunk.', cause }),
338
+ releaseLockOnEnd: true,
339
+ }).pipe(
340
+ Stream.runForEach((value) =>
341
+ Effect.gen(function* () {
342
+ const chunkType = readStreamChunkType(value)
343
+ const elapsedMs = Number((performance.now() - streamStartedAt).toFixed(1))
344
+ const recordFirstVisibleOutput = yield* Ref.modify(firstVisibleOutputRecorded, (recorded) => [
345
+ !recorded && isVisibleOutputChunkType(chunkType),
346
+ recorded || isVisibleOutputChunkType(chunkType),
347
+ ])
348
+ const recordFirstTextToken = yield* Ref.modify(firstTextTokenRecorded, (recorded) => [
349
+ !recorded && isTextTokenChunkType(chunkType),
350
+ recorded || isTextTokenChunkType(chunkType),
351
+ ])
352
+
353
+ if (recordFirstVisibleOutput) {
354
+ yield* Effect.annotateCurrentSpan({ firstVisibleOutputMs: elapsedMs, firstVisibleOutputChunkType: chunkType })
355
+ }
356
+
357
+ if (recordFirstTextToken) {
358
+ yield* Effect.annotateCurrentSpan({ ttftMs: elapsedMs, ttftChunkType: chunkType })
359
+ }
360
+
361
+ if (streamParams.writer) {
362
+ yield* Effect.sync(() => {
363
+ streamParams.writer?.write(value)
364
+ })
365
+ }
366
+ }),
367
+ ),
368
+ Effect.withSpan('ThreadTurnStreaming.consumeUiStream'),
369
+ Effect.andThen(
370
+ effectTryPromise(() => finishedStream, `Agent run for ${resolvedAgentId} did not produce a response message.`),
371
+ ),
372
+ Effect.catchTag('ThreadTurnStreamingError', generateFallback),
373
+ )
374
+
375
+ for (const toolError of collectToolOutputErrors({ responseMessage: streamedResponse })) {
376
+ aiLogger.error`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
377
+ }
378
+
379
+ return streamedResponse
380
+ })
381
+
382
+ export function streamAgentResponse(
383
+ ctx: StreamAgentResponseContext,
384
+ streamParams: StreamAgentResponseParams,
385
+ ): Promise<ChatMessage> {
386
+ return runPromise(
387
+ streamAgentResponseEffect(ctx, streamParams).pipe(
388
+ Effect.annotateSpans(
389
+ compactSpanAttributes({
390
+ ...buildThreadTurnSpanAttributes({
391
+ threadRef: ctx.threadRef,
392
+ orgRef: ctx.orgRef,
393
+ userRef: ctx.userRef,
394
+ agentId: streamParams.agentId,
395
+ threadType: ctx.thread.type,
396
+ mode: streamParams.mode,
397
+ }),
398
+ includeExecutionPlanTools: streamParams.includeExecutionPlanTools ?? true,
399
+ }),
400
+ ),
401
+ ),
402
+ )
403
+ }
@@ -0,0 +1,35 @@
1
+ import type { RecordIdRef } from '../../db/record-id'
2
+ import { recordIdToString } from '../../db/record-id'
3
+ import { TABLES } from '../../db/tables'
4
+
5
+ export function compactSpanAttributes(attributes: Record<string, unknown>): Record<string, unknown> {
6
+ return Object.fromEntries(
7
+ Object.entries(attributes).filter(([, value]) => value !== undefined && value !== null && value !== ''),
8
+ )
9
+ }
10
+
11
+ export function buildThreadTurnSpanAttributes(params: {
12
+ threadRef?: RecordIdRef
13
+ orgRef?: RecordIdRef
14
+ userRef?: RecordIdRef
15
+ kind?: string
16
+ streamId?: string
17
+ agentId?: string
18
+ threadType?: string
19
+ mode?: string
20
+ planRunId?: string
21
+ planNodeId?: string
22
+ }) {
23
+ return compactSpanAttributes({
24
+ threadId: params.threadRef ? recordIdToString(params.threadRef, TABLES.THREAD) : undefined,
25
+ orgId: params.orgRef ? recordIdToString(params.orgRef, TABLES.ORGANIZATION) : undefined,
26
+ userId: params.userRef ? recordIdToString(params.userRef, TABLES.USER) : undefined,
27
+ turnKind: params.kind,
28
+ streamId: params.streamId,
29
+ agentId: params.agentId,
30
+ threadType: params.threadType,
31
+ threadMode: params.mode,
32
+ planRunId: params.planRunId,
33
+ planNodeId: params.planNodeId,
34
+ })
35
+ }