@lota-sdk/core 0.4.13 → 0.4.14
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.
- package/package.json +4 -4
- package/src/ai/embedding-cache.ts +17 -11
- package/src/ai-gateway/ai-gateway.ts +164 -94
- package/src/ai-gateway/index.ts +4 -1
- package/src/config/agent-defaults.ts +2 -2
- package/src/config/agent-types.ts +1 -1
- package/src/create-runtime.ts +259 -200
- package/src/db/cursor-pagination.ts +2 -9
- package/src/db/memory-store.ts +194 -175
- package/src/db/memory.ts +125 -71
- package/src/db/schema-fingerprint.ts +5 -4
- package/src/db/service-normalization.ts +4 -3
- package/src/db/service.ts +3 -2
- package/src/db/startup.ts +15 -16
- package/src/effect/errors.ts +161 -21
- package/src/effect/index.ts +0 -1
- package/src/embeddings/provider.ts +15 -7
- package/src/queues/autonomous-job.queue.ts +10 -22
- package/src/queues/delayed-node-promotion.queue.ts +8 -14
- package/src/queues/document-processor.queue.ts +13 -4
- package/src/queues/memory-consolidation.queue.ts +26 -14
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
- package/src/queues/plan-scheduler.queue.ts +37 -15
- package/src/queues/queue-factory.ts +59 -35
- package/src/queues/standalone-worker.ts +3 -2
- package/src/redis/connection.ts +10 -3
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +5 -5
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/chat-message.ts +64 -1
- package/src/runtime/chat-run-orchestration.ts +33 -20
- package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
- package/src/runtime/context-compaction/context-compaction.ts +78 -66
- package/src/runtime/domain-layer.ts +13 -7
- package/src/runtime/execution-plan.ts +7 -3
- package/src/runtime/memory/memory-block.ts +3 -9
- package/src/runtime/memory/memory-scope.ts +3 -1
- package/src/runtime/plugin-resolution.ts +2 -1
- package/src/runtime/post-turn-side-effects.ts +6 -5
- package/src/runtime/retrieval-adapters.ts +8 -20
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +2 -4
- package/src/runtime/runtime-lifecycle.ts +56 -16
- package/src/runtime/runtime-services.ts +180 -102
- package/src/runtime/runtime-worker-registry.ts +3 -1
- package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
- package/src/runtime/social-chat/social-chat-history.ts +21 -18
- package/src/runtime/social-chat/social-chat.ts +356 -223
- package/src/runtime/specialist-runner.ts +3 -1
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
- package/src/runtime/thread-turn-context.ts +142 -102
- package/src/runtime/turn-lifecycle.ts +15 -46
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +107 -77
- package/src/services/autonomous-job.service.ts +354 -293
- package/src/services/background-work.service.ts +3 -3
- package/src/services/context-compaction.service.ts +7 -2
- package/src/services/document-chunk.service.ts +50 -32
- package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
- package/src/services/execution-plan/execution-plan.service.ts +162 -179
- package/src/services/feedback-loop.service.ts +5 -4
- package/src/services/graph-full-routing.ts +37 -36
- package/src/services/institutional-memory.service.ts +28 -30
- package/src/services/learned-skill.service.ts +107 -72
- package/src/services/memory/memory-errors.ts +4 -23
- package/src/services/memory/memory-org-memory.ts +10 -5
- package/src/services/memory/memory-rerank.ts +18 -6
- package/src/services/memory/memory.service.ts +170 -111
- package/src/services/memory/rerank.service.ts +29 -20
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +69 -75
- package/src/services/ownership-dispatcher.service.ts +40 -39
- package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
- package/src/services/plan/plan-agent-query.service.ts +39 -31
- package/src/services/plan/plan-completion-side-effects.ts +13 -17
- package/src/services/plan/plan-coordination.service.ts +2 -1
- package/src/services/plan/plan-cycle.service.ts +6 -5
- package/src/services/plan/plan-deadline.service.ts +57 -54
- package/src/services/plan/plan-event-delivery.service.ts +5 -4
- package/src/services/plan/plan-executor-graph.ts +18 -15
- package/src/services/plan/plan-executor.service.ts +235 -262
- package/src/services/plan/plan-run.service.ts +169 -93
- package/src/services/plan/plan-scheduler.service.ts +192 -202
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +23 -14
- package/src/services/plugin-executor.service.ts +5 -9
- package/src/services/queue-job.service.ts +117 -59
- package/src/services/recent-activity-title.service.ts +13 -12
- package/src/services/recent-activity.service.ts +6 -1
- package/src/services/social-chat-history.service.ts +29 -25
- package/src/services/system-executor.service.ts +5 -9
- package/src/services/thread/thread-active-run.ts +2 -2
- package/src/services/thread/thread-listing.ts +61 -57
- package/src/services/thread/thread-memory-block.ts +73 -48
- package/src/services/thread/thread-message.service.ts +76 -65
- package/src/services/thread/thread-record-store.ts +8 -8
- package/src/services/thread/thread-title.service.ts +10 -4
- package/src/services/thread/thread-turn-execution.ts +43 -45
- package/src/services/thread/thread-turn-preparation.service.ts +257 -135
- package/src/services/thread/thread-turn-streaming.ts +82 -85
- package/src/services/thread/thread-turn.ts +8 -8
- package/src/services/thread/thread.service.ts +135 -100
- package/src/services/user.service.ts +45 -48
- package/src/storage/attachment-parser.ts +6 -2
- package/src/storage/attachment-storage.service.ts +5 -6
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +10 -9
- package/src/system-agents/delegated-agent-factory.ts +30 -6
- package/src/system-agents/memory-reranker.agent.ts +10 -9
- package/src/system-agents/memory.agent.ts +10 -9
- package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
- package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
- package/src/system-agents/skill-extractor.agent.ts +13 -12
- package/src/system-agents/skill-manager.agent.ts +13 -12
- package/src/system-agents/thread-router.agent.ts +10 -5
- package/src/system-agents/title-generator.agent.ts +13 -12
- package/src/tools/fetch-webpage.tool.ts +13 -13
- package/src/tools/memory-block.tool.ts +3 -1
- package/src/tools/plan-approval.tool.ts +4 -2
- package/src/tools/read-file-parts.tool.ts +10 -4
- package/src/tools/remember-memory.tool.ts +3 -1
- package/src/tools/research-topic.tool.ts +9 -5
- package/src/tools/search-web.tool.ts +16 -16
- package/src/tools/search.tool.ts +20 -5
- package/src/tools/team-think.tool.ts +61 -38
- package/src/utils/async.ts +5 -5
- package/src/utils/errors.ts +19 -18
- package/src/utils/sse-keepalive.ts +28 -25
- package/src/workers/bootstrap.ts +75 -11
- package/src/workers/memory-consolidation.worker.ts +82 -91
- package/src/workers/organization-learning.worker.ts +14 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
- package/src/workers/skill-extraction.runner.ts +97 -61
- package/src/workers/utils/repo-structure-extractor.ts +13 -8
- package/src/workers/utils/thread-message-query.ts +24 -24
- package/src/workers/worker-utils.ts +23 -4
- package/src/effect/helpers.ts +0 -123
|
@@ -6,7 +6,7 @@ import { Effect, Ref, Schema, Stream } from 'effect'
|
|
|
6
6
|
|
|
7
7
|
import { aiLogger } from '../../config/logger'
|
|
8
8
|
import type { RecordIdRef } from '../../db/record-id'
|
|
9
|
-
import {
|
|
9
|
+
import { ERROR_TAGS } from '../../effect/errors'
|
|
10
10
|
import { AgentConfigServiceTag, AgentFactoryServiceTag } from '../../effect/services'
|
|
11
11
|
import {
|
|
12
12
|
readRuntimeAgentIdentityOverrides,
|
|
@@ -40,28 +40,12 @@ interface ToolLoopGenerateResult {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export class ThreadTurnStreamingError extends Schema.TaggedErrorClass<ThreadTurnStreamingError>()(
|
|
43
|
-
'ThreadTurnStreamingError',
|
|
43
|
+
'@lota-sdk/core/ThreadTurnStreamingError',
|
|
44
44
|
{ message: Schema.String, cause: Schema.optional(Schema.Defect) },
|
|
45
45
|
) {}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
)
|
|
47
|
+
function toThreadTurnStreamingError(message: string, cause: unknown): ThreadTurnStreamingError {
|
|
48
|
+
return new ThreadTurnStreamingError({ message, cause })
|
|
65
49
|
}
|
|
66
50
|
|
|
67
51
|
function hasUIMessageStream(value: unknown): value is UIMessageStreamResult {
|
|
@@ -151,7 +135,7 @@ interface StreamAgentResponseParams {
|
|
|
151
135
|
messages: ChatMessage[]
|
|
152
136
|
tools: ToolSet
|
|
153
137
|
observer: {
|
|
154
|
-
run: <T>(fn: () =>
|
|
138
|
+
run: <T>(fn: () => Promise<T>) => Promise<T>
|
|
155
139
|
recordError: (error: unknown) => void
|
|
156
140
|
recordAbort: (error: unknown) => void
|
|
157
141
|
}
|
|
@@ -172,45 +156,54 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
|
|
|
172
156
|
const executionPlanInstructionSections =
|
|
173
157
|
streamParams.includeExecutionPlanTools === false
|
|
174
158
|
? undefined
|
|
175
|
-
: yield*
|
|
176
|
-
() =>
|
|
177
|
-
'
|
|
178
|
-
)
|
|
159
|
+
: yield* ctx.getExecutionPlanInstructionSections().pipe(
|
|
160
|
+
Effect.mapError((cause) => toThreadTurnStreamingError('Failed to load execution plan instructions.', cause)),
|
|
161
|
+
Effect.withSpan('ThreadTurnStreaming.loadExecutionPlanInstructions'),
|
|
162
|
+
)
|
|
179
163
|
|
|
164
|
+
const resolveAgent = ctx.turnHooks.resolveAgent
|
|
180
165
|
const agentResolution = asRecord(
|
|
181
|
-
|
|
182
|
-
(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
166
|
+
resolveAgent
|
|
167
|
+
? yield* Effect.tryPromise({
|
|
168
|
+
try: () =>
|
|
169
|
+
resolveAgent({
|
|
170
|
+
agentId: streamParams.agentId,
|
|
171
|
+
mode: streamParams.mode,
|
|
172
|
+
thread: ctx.thread,
|
|
173
|
+
threadRef: ctx.threadRef,
|
|
174
|
+
orgRef: ctx.orgRef,
|
|
175
|
+
userRef: ctx.userRef,
|
|
176
|
+
userName: ctx.userName,
|
|
177
|
+
onboardingActive: ctx.onboardingActive,
|
|
178
|
+
linearInstalled: ctx.linearInstalled,
|
|
179
|
+
githubInstalled: ctx.githubInstalled,
|
|
180
|
+
skills: streamParams.skills,
|
|
181
|
+
additionalInstructionSections: streamParams.additionalInstructionSections,
|
|
182
|
+
context: ctx.buildContextResult,
|
|
183
|
+
}),
|
|
184
|
+
catch: (cause) => toThreadTurnStreamingError('Failed to resolve the runtime agent.', cause),
|
|
185
|
+
}).pipe(Effect.withSpan('ThreadTurnStreaming.resolveAgent'))
|
|
186
|
+
: undefined,
|
|
200
187
|
)
|
|
201
188
|
|
|
202
189
|
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? streamParams.agentId
|
|
203
190
|
const latestUserMessage = [...streamParams.messages].reverse().find((message) => message.role === 'user')
|
|
204
191
|
const latestUserMessageText = latestUserMessage ? extractMessageText(latestUserMessage).trim() : undefined
|
|
205
192
|
const [preSeededMemoriesSection, learnedSkillsSection] = yield* Effect.all([
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
193
|
+
ctx
|
|
194
|
+
.getPreSeededMemoriesSection(resolvedAgentId)
|
|
195
|
+
.pipe(
|
|
196
|
+
Effect.mapError((cause) =>
|
|
197
|
+
toThreadTurnStreamingError(`Failed to load pre-seeded memories for ${resolvedAgentId}.`, cause),
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
ctx
|
|
201
|
+
.getLearnedSkillsSection(resolvedAgentId, latestUserMessageText)
|
|
202
|
+
.pipe(
|
|
203
|
+
Effect.mapError((cause) =>
|
|
204
|
+
toThreadTurnStreamingError(`Failed to load learned skills for ${resolvedAgentId}.`, cause),
|
|
205
|
+
),
|
|
206
|
+
),
|
|
214
207
|
]).pipe(Effect.withSpan('ThreadTurnStreaming.loadMemoriesAndSkills'))
|
|
215
208
|
|
|
216
209
|
const toolNames = new Set(Object.keys(streamParams.tools))
|
|
@@ -259,10 +252,10 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
|
|
|
259
252
|
hasRetrievalTools,
|
|
260
253
|
})
|
|
261
254
|
|
|
262
|
-
const modelMessages = yield*
|
|
263
|
-
() => convertToModelMessages(streamParams.messages, { ignoreIncompleteToolCalls: true }),
|
|
264
|
-
'Failed to convert UI messages to model messages.',
|
|
265
|
-
).pipe(Effect.withSpan('ThreadTurnStreaming.convertModelMessages'))
|
|
255
|
+
const modelMessages = yield* Effect.tryPromise({
|
|
256
|
+
try: () => convertToModelMessages(streamParams.messages, { ignoreIncompleteToolCalls: true }),
|
|
257
|
+
catch: (cause) => toThreadTurnStreamingError('Failed to convert UI messages to model messages.', cause),
|
|
258
|
+
}).pipe(Effect.withSpan('ThreadTurnStreaming.convertModelMessages'))
|
|
266
259
|
|
|
267
260
|
const agentFactory = agentFactoryConfig.createAgent[config.id]
|
|
268
261
|
if (!agentFactory) {
|
|
@@ -281,10 +274,11 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
|
|
|
281
274
|
const resolvedAgentName = resolveRuntimeAgentDisplayName(agentConfig, agentIdentityOverrides, resolvedAgentId)
|
|
282
275
|
|
|
283
276
|
const generateFallback = (cause: ThreadTurnStreamingError) =>
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
277
|
+
Effect.tryPromise({
|
|
278
|
+
try: () =>
|
|
279
|
+
streamParams.observer.run(() => agent.generate({ messages: modelMessages, abortSignal: agentAbortSignal })),
|
|
280
|
+
catch: (error) => toThreadTurnStreamingError(`Agent generate fallback failed for ${resolvedAgentId}.`, error),
|
|
281
|
+
}).pipe(
|
|
288
282
|
Effect.tap(() =>
|
|
289
283
|
Effect.sync(() => {
|
|
290
284
|
aiLogger.warn`Agent stream failed for ${resolvedAgentId}; falling back to generate: ${cause.message}`
|
|
@@ -293,10 +287,11 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
|
|
|
293
287
|
Effect.flatMap((result) => buildFallbackResponseMessage(result as ToolLoopGenerateResult)),
|
|
294
288
|
)
|
|
295
289
|
|
|
296
|
-
const generateWithoutUiStream =
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
290
|
+
const generateWithoutUiStream = Effect.tryPromise({
|
|
291
|
+
try: () =>
|
|
292
|
+
streamParams.observer.run(() => agent.generate({ messages: modelMessages, abortSignal: agentAbortSignal })),
|
|
293
|
+
catch: (cause) => toThreadTurnStreamingError(`Agent generate failed for ${resolvedAgentId}.`, cause),
|
|
294
|
+
}).pipe(
|
|
300
295
|
Effect.tapError((error) =>
|
|
301
296
|
Effect.sync(() => {
|
|
302
297
|
if (agentAbortSignal.aborted) {
|
|
@@ -321,10 +316,12 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
|
|
|
321
316
|
return generatedResponse
|
|
322
317
|
}
|
|
323
318
|
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
319
|
+
const writer = streamParams.writer
|
|
320
|
+
const result = yield* Effect.tryPromise({
|
|
321
|
+
try: () =>
|
|
322
|
+
streamParams.observer.run(() => agent.stream({ messages: modelMessages, abortSignal: agentAbortSignal })),
|
|
323
|
+
catch: (cause) => toThreadTurnStreamingError(`Agent stream failed for ${resolvedAgentId}.`, cause),
|
|
324
|
+
}).pipe(
|
|
328
325
|
Effect.tapError((error) =>
|
|
329
326
|
Effect.sync(() => {
|
|
330
327
|
if (agentAbortSignal.aborted) {
|
|
@@ -355,14 +352,12 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
|
|
|
355
352
|
resolveFinishedStream(withMessageCreatedAt(finishedResponseMessage, nowEpochMillis()))
|
|
356
353
|
},
|
|
357
354
|
}) as ReadableStream<ChatStreamChunk>
|
|
358
|
-
const liveTurnTrace =
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
})
|
|
365
|
-
: null
|
|
355
|
+
const liveTurnTrace = createLiveTurnTraceStreamObserver({
|
|
356
|
+
traceId: `trace:${Bun.randomUUIDv7()}`,
|
|
357
|
+
writer,
|
|
358
|
+
agentId: resolvedAgentId,
|
|
359
|
+
agentName: resolvedAgentName,
|
|
360
|
+
})
|
|
366
361
|
const streamStartedAt = performance.now()
|
|
367
362
|
const firstVisibleOutputRecorded = yield* Ref.make(false)
|
|
368
363
|
const firstTextTokenRecorded = yield* Ref.make(false)
|
|
@@ -393,21 +388,23 @@ const streamAgentResponseEffect = Effect.fn('ThreadTurnStreaming.streamAgentResp
|
|
|
393
388
|
yield* Effect.annotateCurrentSpan({ ttftMs: elapsedMs, ttftChunkType: chunkType })
|
|
394
389
|
}
|
|
395
390
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
})
|
|
401
|
-
}
|
|
391
|
+
yield* Effect.sync(() => {
|
|
392
|
+
writer.write(value)
|
|
393
|
+
liveTurnTrace.observeChunk(value)
|
|
394
|
+
})
|
|
402
395
|
}),
|
|
403
396
|
),
|
|
404
397
|
Effect.withSpan('ThreadTurnStreaming.consumeUiStream'),
|
|
405
398
|
Effect.andThen(
|
|
406
|
-
|
|
399
|
+
Effect.tryPromise({
|
|
400
|
+
try: () => finishedStream,
|
|
401
|
+
catch: (cause) =>
|
|
402
|
+
toThreadTurnStreamingError(`Agent run for ${resolvedAgentId} did not produce a response message.`, cause),
|
|
403
|
+
}),
|
|
407
404
|
),
|
|
408
|
-
Effect.catchTag(
|
|
405
|
+
Effect.catchTag(ERROR_TAGS.ThreadTurnStreamingError, generateFallback),
|
|
409
406
|
)
|
|
410
|
-
liveTurnTrace
|
|
407
|
+
liveTurnTrace.finish()
|
|
411
408
|
|
|
412
409
|
for (const toolError of collectToolOutputErrors({ responseMessage: streamedResponse })) {
|
|
413
410
|
aiLogger.error`Tool execution failed (agent=${resolvedAgentId}, tool=${toolError.toolName}, toolCallId=${toolError.toolCallId}): ${toolError.errorText}`
|
|
@@ -5,12 +5,12 @@ import { Context, Schema, Effect, Layer } from 'effect'
|
|
|
5
5
|
import type { ResolvedAgentConfig } from '../../config/agent-defaults'
|
|
6
6
|
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
7
7
|
import { TABLES } from '../../db/tables'
|
|
8
|
-
import { BadRequestError, ForbiddenError } from '../../effect/errors'
|
|
8
|
+
import { ERROR_TAGS, BadRequestError, ForbiddenError } from '../../effect/errors'
|
|
9
9
|
import { AgentConfigServiceTag } from '../../effect/services'
|
|
10
10
|
import { hasApprovalRespondedParts, isApprovalContinuationRequest } from '../../runtime/approval-continuation'
|
|
11
11
|
import { shouldPlanNodeUseVisibleTurn } from '../../runtime/execution-plan-visibility'
|
|
12
12
|
import { wrapResponseWithKeepalive } from '../../utils/sse-keepalive'
|
|
13
|
-
import {
|
|
13
|
+
import { BackgroundWorkServiceTag } from '../background-work.service'
|
|
14
14
|
import type { makePlanExecutorService } from '../plan/plan-executor.service'
|
|
15
15
|
import { PlanExecutorServiceTag } from '../plan/plan-executor.service'
|
|
16
16
|
import type { makePlanRunService } from '../plan/plan-run.service'
|
|
@@ -67,14 +67,14 @@ export interface LaunchBackgroundThreadWorkResult {
|
|
|
67
67
|
message: string
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
class ThreadTurnServiceError extends Schema.TaggedErrorClass<ThreadTurnServiceError>()(
|
|
71
|
-
|
|
72
|
-
cause: Schema.optional(Schema.Defect),
|
|
73
|
-
|
|
70
|
+
class ThreadTurnServiceError extends Schema.TaggedErrorClass<ThreadTurnServiceError>()(
|
|
71
|
+
ERROR_TAGS.ThreadTurnServiceError,
|
|
72
|
+
{ message: Schema.String, cause: Schema.optional(Schema.Defect) },
|
|
73
|
+
) {}
|
|
74
74
|
|
|
75
75
|
interface ThreadTurnDeps {
|
|
76
76
|
agentConfig: ResolvedAgentConfig
|
|
77
|
-
background: Context.Service.Shape<typeof
|
|
77
|
+
background: Context.Service.Shape<typeof BackgroundWorkServiceTag>
|
|
78
78
|
planExecutor: ReturnType<typeof makePlanExecutorService>
|
|
79
79
|
planRun: ReturnType<typeof makePlanRunService>
|
|
80
80
|
provideCurrentContext: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, never>
|
|
@@ -475,7 +475,7 @@ export const ThreadTurnServiceLive = Layer.effect(
|
|
|
475
475
|
const provideCurrentContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, never> =>
|
|
476
476
|
effect.pipe(Effect.provide(currentContext)) as Effect.Effect<A, E, never>
|
|
477
477
|
const agentConfig = yield* AgentConfigServiceTag
|
|
478
|
-
const background = yield*
|
|
478
|
+
const background = yield* BackgroundWorkServiceTag
|
|
479
479
|
const planExecutor = yield* PlanExecutorServiceTag
|
|
480
480
|
const planRun = yield* PlanRunServiceTag
|
|
481
481
|
const thread = yield* ThreadServiceTag
|
|
@@ -10,7 +10,6 @@ import type { RecordIdRef } from '../../db/record-id'
|
|
|
10
10
|
import type { SurrealDBService } from '../../db/service'
|
|
11
11
|
import { TABLES } from '../../db/tables'
|
|
12
12
|
import { BadRequestError, ServiceError } from '../../effect/errors'
|
|
13
|
-
import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
|
|
14
13
|
import {
|
|
15
14
|
AgentConfigServiceTag,
|
|
16
15
|
DatabaseServiceTag,
|
|
@@ -20,7 +19,7 @@ import {
|
|
|
20
19
|
import type { RedisConnectionManager } from '../../redis/connection'
|
|
21
20
|
import { CompactionCoordinationTag } from '../../runtime/chat-run-orchestration'
|
|
22
21
|
import { toIsoDateTimeString } from '../../utils/date-time'
|
|
23
|
-
import {
|
|
22
|
+
import { BackgroundWorkServiceTag } from '../background-work.service'
|
|
24
23
|
import { ChatRunRegistryTag } from '../chat-run-registry.service'
|
|
25
24
|
import { ContextCompactionServiceTag } from '../context-compaction.service'
|
|
26
25
|
import type { makeContextCompactionService } from '../context-compaction.service'
|
|
@@ -63,8 +62,6 @@ function toPublicThread(thread: NormalizedThread): PublicThread {
|
|
|
63
62
|
|
|
64
63
|
type ThreadServiceError = ServiceError | BadRequestError
|
|
65
64
|
|
|
66
|
-
const effectTryPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
|
|
67
|
-
|
|
68
65
|
type ChatRunRegistry = Context.Service.Shape<typeof ChatRunRegistryTag>
|
|
69
66
|
|
|
70
67
|
type CompactionCoordination = Context.Service.Shape<typeof CompactionCoordinationTag>
|
|
@@ -78,7 +75,7 @@ interface ThreadServiceDeps {
|
|
|
78
75
|
threadMessageService: ReturnType<typeof makeThreadMessageService>
|
|
79
76
|
contextCompactionService: ReturnType<typeof makeContextCompactionService>
|
|
80
77
|
compactionCoordination: CompactionCoordination
|
|
81
|
-
background: Context.Service.Shape<typeof
|
|
78
|
+
background: Context.Service.Shape<typeof BackgroundWorkServiceTag>
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
export function makeThreadService(deps: ThreadServiceDeps) {
|
|
@@ -109,10 +106,11 @@ export function makeThreadService(deps: ThreadServiceDeps) {
|
|
|
109
106
|
return Effect.succeed(true)
|
|
110
107
|
}
|
|
111
108
|
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
return activeRun
|
|
110
|
+
.hasActiveRunLease(ensureRecordId(thread.id, TABLES.THREAD))
|
|
111
|
+
.pipe(
|
|
112
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to check active thread run lease.', cause })),
|
|
113
|
+
)
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
function toNormalizedThread(
|
|
@@ -174,68 +172,59 @@ export function makeThreadService(deps: ThreadServiceDeps) {
|
|
|
174
172
|
background: deps.background,
|
|
175
173
|
})
|
|
176
174
|
|
|
177
|
-
|
|
178
|
-
return
|
|
179
|
-
|
|
175
|
+
const getById = Effect.fn('ThreadService.getById')(function* (threadId: RecordIdRef) {
|
|
176
|
+
return yield* threadStore
|
|
177
|
+
.getById(threadId)
|
|
178
|
+
.pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to load thread.', cause })))
|
|
179
|
+
})
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
})
|
|
186
|
-
}
|
|
181
|
+
const getThread = Effect.fn('ThreadService.getThread')(function* (threadId: RecordIdRef) {
|
|
182
|
+
const thread = yield* getById(threadId)
|
|
183
|
+
return yield* toNormalizedThread(thread)
|
|
184
|
+
})
|
|
187
185
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return yield* toNormalizedThread(thread)
|
|
197
|
-
})
|
|
198
|
-
}
|
|
186
|
+
const updateTitle = Effect.fn('ThreadService.updateTitle')(function* (threadId: RecordIdRef, title: string) {
|
|
187
|
+
const existing = yield* getById(threadId)
|
|
188
|
+
yield* assertMutableThreadEffect(existing)
|
|
189
|
+
const thread = yield* threadStore
|
|
190
|
+
.update(threadId, { title, nameGenerated: true })
|
|
191
|
+
.pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread title.', cause })))
|
|
192
|
+
return yield* toNormalizedThread(thread)
|
|
193
|
+
})
|
|
199
194
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return yield* toNormalizedThread(thread)
|
|
213
|
-
})
|
|
214
|
-
}
|
|
195
|
+
const updateStatus = Effect.fn('ThreadService.updateStatus')(function* (threadId: RecordIdRef, status: string) {
|
|
196
|
+
const parsedStatus = sdkThreadStatusSchema.safeParse(status)
|
|
197
|
+
if (!parsedStatus.success) {
|
|
198
|
+
return yield* new BadRequestError({ message: `Invalid thread status: ${status}` })
|
|
199
|
+
}
|
|
200
|
+
const existing = yield* getById(threadId)
|
|
201
|
+
yield* assertMutableThreadEffect(existing)
|
|
202
|
+
const thread = yield* threadStore
|
|
203
|
+
.update(threadId, { status: parsedStatus.data })
|
|
204
|
+
.pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread status.', cause })))
|
|
205
|
+
return yield* toNormalizedThread(thread)
|
|
206
|
+
})
|
|
215
207
|
|
|
216
|
-
|
|
208
|
+
const setCompacting = Effect.fn('ThreadService.setCompacting')(function* (threadId: RecordIdRef, value: boolean) {
|
|
217
209
|
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
218
210
|
const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
'Failed to update thread compaction flag.',
|
|
223
|
-
).pipe(Effect.tap(() => deps.compactionCoordination.signal(threadIdString, value))),
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function clearThread(threadId: RecordIdRef) {
|
|
228
|
-
return Effect.gen(function* () {
|
|
229
|
-
const existing = yield* getById(threadId)
|
|
230
|
-
yield* assertMutableThreadEffect(existing)
|
|
231
|
-
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
232
|
-
yield* effectTryPromise(
|
|
233
|
-
() => deps.db.deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef }),
|
|
234
|
-
'Failed to delete thread messages.',
|
|
211
|
+
yield* deps.db
|
|
212
|
+
.query<unknown>(surql`UPDATE ONLY ${threadRef} SET isCompacting = ${value}`)
|
|
213
|
+
.pipe(
|
|
214
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread compaction flag.', cause })),
|
|
235
215
|
)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
216
|
+
yield* deps.compactionCoordination.signal(threadIdString, value)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
const clearThread = Effect.fn('ThreadService.clearThread')(function* (threadId: RecordIdRef) {
|
|
220
|
+
const existing = yield* getById(threadId)
|
|
221
|
+
yield* assertMutableThreadEffect(existing)
|
|
222
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
223
|
+
yield* deps.db
|
|
224
|
+
.deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef })
|
|
225
|
+
.pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to delete thread messages.', cause })))
|
|
226
|
+
yield* deps.db
|
|
227
|
+
.query<unknown>(surql`
|
|
239
228
|
UPDATE ONLY ${threadRef}
|
|
240
229
|
SET turnCount = 0,
|
|
241
230
|
compactionSummary = NONE,
|
|
@@ -243,68 +232,102 @@ export function makeThreadService(deps: ThreadServiceDeps) {
|
|
|
243
232
|
activeRunId = NONE,
|
|
244
233
|
activeStreamId = NONE,
|
|
245
234
|
isCompacting = false
|
|
246
|
-
`)
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
})
|
|
250
|
-
}
|
|
235
|
+
`)
|
|
236
|
+
.pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to reset thread state.', cause })))
|
|
237
|
+
})
|
|
251
238
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
239
|
+
const deleteThread = Effect.fn('ThreadService.deleteThread')(function* (threadId: RecordIdRef) {
|
|
240
|
+
const existing = yield* getById(threadId)
|
|
241
|
+
yield* assertMutableThreadEffect(existing)
|
|
242
|
+
yield* threadStore
|
|
243
|
+
.deleteById(threadId)
|
|
244
|
+
.pipe(Effect.mapError((cause) => new ServiceError({ message: 'Failed to delete thread.', cause })))
|
|
245
|
+
})
|
|
259
246
|
|
|
260
247
|
function incrementTurnCount(threadId: RecordIdRef) {
|
|
261
248
|
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
262
249
|
return Effect.gen(function* () {
|
|
263
|
-
const result = yield*
|
|
264
|
-
(
|
|
265
|
-
|
|
266
|
-
surql`
|
|
250
|
+
const result = yield* deps.db
|
|
251
|
+
.query<{ turnCount: number }>(
|
|
252
|
+
surql`
|
|
267
253
|
UPDATE ONLY ${threadRef}
|
|
268
254
|
SET turnCount += 1
|
|
269
255
|
RETURN turnCount
|
|
270
256
|
`,
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
257
|
+
)
|
|
258
|
+
.pipe(
|
|
259
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to increment thread turn count.', cause })),
|
|
260
|
+
)
|
|
274
261
|
return result[0].turnCount
|
|
275
262
|
})
|
|
276
263
|
}
|
|
277
264
|
|
|
278
265
|
return {
|
|
279
266
|
findById: (...args: Parameters<typeof threadStore.findById>) =>
|
|
280
|
-
|
|
267
|
+
threadStore.findById(...args).pipe(
|
|
268
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to find thread.', cause })),
|
|
269
|
+
Effect.withSpan('ThreadService.findById'),
|
|
270
|
+
),
|
|
281
271
|
getById,
|
|
282
272
|
findAll: (...args: Parameters<typeof threadStore.findAll>) =>
|
|
283
|
-
|
|
273
|
+
threadStore.findAll(...args).pipe(
|
|
274
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to find all threads.', cause })),
|
|
275
|
+
Effect.withSpan('ThreadService.findAll'),
|
|
276
|
+
),
|
|
284
277
|
create: (...args: Parameters<typeof threadStore.create>) =>
|
|
285
|
-
|
|
278
|
+
threadStore.create(...args).pipe(
|
|
279
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to create thread.', cause })),
|
|
280
|
+
Effect.withSpan('ThreadService.create'),
|
|
281
|
+
),
|
|
286
282
|
update: (...args: Parameters<typeof threadStore.update>) =>
|
|
287
|
-
|
|
283
|
+
threadStore.update(...args).pipe(
|
|
284
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to update thread.', cause })),
|
|
285
|
+
Effect.withSpan('ThreadService.update'),
|
|
286
|
+
),
|
|
288
287
|
delete: (...args: Parameters<typeof threadStore.deleteById>) =>
|
|
289
|
-
|
|
288
|
+
threadStore.deleteById(...args).pipe(
|
|
289
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to delete thread.', cause })),
|
|
290
|
+
Effect.withSpan('ThreadService.delete'),
|
|
291
|
+
),
|
|
290
292
|
getOrCreateDefault: (...args: Parameters<typeof bootstrap.getOrCreateDefault>) =>
|
|
291
|
-
|
|
293
|
+
bootstrap.getOrCreateDefault(...args).pipe(
|
|
294
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to get or create default thread.', cause })),
|
|
295
|
+
Effect.withSpan('ThreadService.getOrCreateDefault'),
|
|
296
|
+
),
|
|
292
297
|
getOrCreateThread: (...args: Parameters<typeof bootstrap.getOrCreateThread>) =>
|
|
293
|
-
|
|
298
|
+
bootstrap.getOrCreateThread(...args).pipe(
|
|
299
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to get or create thread.', cause })),
|
|
300
|
+
Effect.withSpan('ThreadService.getOrCreateThread'),
|
|
301
|
+
),
|
|
294
302
|
createThread: (...args: Parameters<typeof bootstrap.createThread>) =>
|
|
295
|
-
|
|
303
|
+
bootstrap.createThread(...args).pipe(
|
|
304
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to create thread.', cause })),
|
|
305
|
+
Effect.withSpan('ThreadService.createThread'),
|
|
306
|
+
),
|
|
296
307
|
ensureBootstrapThreads: (...args: Parameters<typeof bootstrap.ensureBootstrapThreads>) =>
|
|
297
|
-
|
|
308
|
+
bootstrap.ensureBootstrapThreads(...args).pipe(
|
|
309
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to ensure bootstrap threads.', cause })),
|
|
310
|
+
Effect.withSpan('ThreadService.ensureBootstrapThreads'),
|
|
311
|
+
),
|
|
298
312
|
listThreads: (...args: Parameters<typeof listing.listThreads>) =>
|
|
299
|
-
|
|
313
|
+
listing.listThreads(...args).pipe(
|
|
314
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to list threads.', cause })),
|
|
315
|
+
Effect.withSpan('ThreadService.listThreads'),
|
|
316
|
+
),
|
|
300
317
|
listOrganizationThreads: (...args: Parameters<typeof listing.listOrganizationThreads>) =>
|
|
301
|
-
|
|
318
|
+
listing.listOrganizationThreads(...args).pipe(
|
|
319
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to list organization threads.', cause })),
|
|
320
|
+
Effect.withSpan('ThreadService.listOrganizationThreads'),
|
|
321
|
+
),
|
|
302
322
|
getThread,
|
|
303
323
|
updateTitle,
|
|
304
324
|
updateStatus,
|
|
305
325
|
setActiveTurn: activeRun.setActiveTurn,
|
|
306
326
|
getActiveTurn: (...args: Parameters<typeof activeRun.getActiveTurn>) =>
|
|
307
|
-
|
|
327
|
+
activeRun.getActiveTurn(...args).pipe(
|
|
328
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to get active turn.', cause })),
|
|
329
|
+
Effect.withSpan('ThreadService.getActiveTurn'),
|
|
330
|
+
),
|
|
308
331
|
getActiveRunId: activeRun.getActiveRunId,
|
|
309
332
|
hasActiveRunLease: activeRun.hasActiveRunLease,
|
|
310
333
|
withActiveRunLease: activeRun.withActiveRunLease,
|
|
@@ -312,16 +335,28 @@ export function makeThreadService(deps: ThreadServiceDeps) {
|
|
|
312
335
|
clearActiveTurn: activeRun.clearActiveTurn,
|
|
313
336
|
clearStaleActiveRunIfMissingFromRegistry: activeRun.clearStaleActiveRunIfMissingFromRegistry,
|
|
314
337
|
stopActiveRun: (...args: Parameters<typeof activeRun.stopActiveRun>) =>
|
|
315
|
-
|
|
338
|
+
activeRun.stopActiveRun(...args).pipe(
|
|
339
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to stop active run.', cause })),
|
|
340
|
+
Effect.withSpan('ThreadService.stopActiveRun'),
|
|
341
|
+
),
|
|
316
342
|
setCompacting,
|
|
317
343
|
appendMemoryBlock: (...args: Parameters<typeof memory.appendMemoryBlock>) =>
|
|
318
|
-
|
|
344
|
+
memory.appendMemoryBlock(...args).pipe(
|
|
345
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to append memory block.', cause })),
|
|
346
|
+
Effect.withSpan('ThreadService.appendMemoryBlock'),
|
|
347
|
+
),
|
|
319
348
|
compactMemoryBlock: (...args: Parameters<typeof memory.compactMemoryBlock>) =>
|
|
320
|
-
|
|
349
|
+
memory.compactMemoryBlock(...args).pipe(
|
|
350
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to compact memory block.', cause })),
|
|
351
|
+
Effect.withSpan('ThreadService.compactMemoryBlock'),
|
|
352
|
+
),
|
|
321
353
|
clearThread,
|
|
322
354
|
deleteThread,
|
|
323
355
|
listRecentThreads: (...args: Parameters<typeof listing.listRecentThreads>) =>
|
|
324
|
-
|
|
356
|
+
listing.listRecentThreads(...args).pipe(
|
|
357
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to list recent threads.', cause })),
|
|
358
|
+
Effect.withSpan('ThreadService.listRecentThreads'),
|
|
359
|
+
),
|
|
325
360
|
formatMemoryBlockForPrompt,
|
|
326
361
|
toPublicThread,
|
|
327
362
|
incrementTurnCount,
|
|
@@ -343,7 +378,7 @@ export const ThreadServiceLive = Layer.effect(
|
|
|
343
378
|
const threadMessageService = yield* ThreadMessageServiceTag
|
|
344
379
|
const contextCompactionService = yield* ContextCompactionServiceTag
|
|
345
380
|
const compactionCoordination = yield* CompactionCoordinationTag
|
|
346
|
-
const background = yield*
|
|
381
|
+
const background = yield* BackgroundWorkServiceTag
|
|
347
382
|
return makeThreadService({
|
|
348
383
|
agentConfig,
|
|
349
384
|
threadBootstrapConfig,
|