@lota-sdk/core 0.4.8 → 0.4.9

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