@lota-sdk/core 0.4.7 → 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.
- package/package.json +11 -12
- package/src/ai/embedding-cache.ts +94 -22
- package/src/ai-gateway/ai-gateway.ts +738 -223
- package/src/config/agent-defaults.ts +176 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/constants.ts +8 -2
- package/src/config/logger.ts +286 -19
- package/src/config/model-constants.ts +1 -0
- package/src/config/thread-defaults.ts +33 -21
- package/src/create-runtime.ts +725 -383
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +856 -598
- package/src/db/memory.ts +398 -275
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +255 -0
- package/src/db/service.ts +726 -761
- package/src/db/startup.ts +140 -66
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +87 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +98 -0
- package/src/effect/index.ts +22 -0
- package/src/effect/layers.ts +228 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +31 -0
- package/src/effect/services.ts +57 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +122 -71
- package/src/index.ts +46 -1
- package/src/openrouter/direct-provider.ts +29 -0
- package/src/queues/autonomous-job.queue.ts +130 -74
- package/src/queues/context-compaction.queue.ts +60 -15
- package/src/queues/delayed-node-promotion.queue.ts +52 -15
- package/src/queues/document-processor.queue.ts +52 -77
- package/src/queues/memory-consolidation.queue.ts +47 -32
- package/src/queues/organization-learning.queue.ts +13 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +65 -21
- package/src/queues/plan-scheduler.queue.ts +107 -31
- package/src/queues/post-chat-memory.queue.ts +66 -24
- package/src/queues/queue-factory.ts +142 -52
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +54 -9
- package/src/redis/connection.ts +84 -32
- package/src/redis/index.ts +6 -8
- package/src/redis/org-memory-lock.ts +60 -27
- package/src/redis/redis-lease-lock.ts +200 -121
- package/src/redis/runtime-connection.ts +10 -0
- package/src/redis/stream-context.ts +84 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +4 -1
- package/src/runtime/agent-stream-helpers.ts +20 -9
- package/src/runtime/chat-run-orchestration.ts +102 -19
- package/src/runtime/chat-run-registry.ts +36 -2
- package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
- package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +114 -91
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +11 -7
- package/src/runtime/helper-model.ts +135 -48
- package/src/runtime/index.ts +7 -7
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +40 -36
- package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
- package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +1 -1
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/{memory-scope.ts → memory/memory-scope.ts} +12 -6
- package/src/runtime/plugin-resolution.ts +144 -24
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +197 -130
- package/src/runtime/retrieval-adapters.ts +38 -4
- package/src/runtime/runtime-config.ts +165 -61
- package/src/runtime/runtime-extensions.ts +21 -34
- package/src/runtime/social-chat/social-chat-agent-runner.ts +157 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +42 -20
- package/src/runtime/social-chat/social-chat.ts +594 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +427 -0
- package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
- package/src/runtime/thread-chat-helpers.ts +2 -2
- package/src/runtime/thread-plan-turn.ts +2 -1
- package/src/runtime/thread-turn-context.ts +172 -94
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +329 -217
- package/src/services/artifact.service.ts +225 -148
- package/src/services/attachment.service.ts +137 -115
- package/src/services/autonomous-job.service.ts +888 -491
- package/src/services/chat-run-registry.service.ts +11 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +162 -90
- package/src/services/execution-plan/execution-plan-approval.ts +26 -0
- package/src/services/execution-plan/execution-plan-context.ts +29 -0
- package/src/services/execution-plan/execution-plan-graph.ts +256 -0
- package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
- package/src/services/execution-plan/execution-plan-spec.ts +75 -0
- package/src/services/execution-plan/execution-plan.service.ts +1041 -0
- package/src/services/feedback-loop.service.ts +132 -76
- package/src/services/global-orchestrator.service.ts +80 -170
- package/src/services/graph-full-routing.ts +182 -0
- package/src/services/index.ts +18 -20
- package/src/services/institutional-memory.service.ts +220 -123
- package/src/services/learned-skill.service.ts +364 -259
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-org-memory.ts +39 -0
- package/src/services/memory/memory-preseeded.ts +80 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +5 -5
- package/src/services/memory/memory.service.ts +692 -0
- package/src/services/memory/rerank.service.ts +209 -0
- package/src/services/monitoring-window.service.ts +92 -70
- package/src/services/mutating-approval.service.ts +62 -53
- package/src/services/node-workspace.service.ts +141 -98
- package/src/services/notification.service.ts +17 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +144 -51
- package/src/services/ownership-dispatcher.service.ts +415 -264
- package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
- package/src/services/plan/plan-agent-query.service.ts +322 -0
- package/src/services/plan/plan-approval.service.ts +102 -0
- package/src/services/plan/plan-artifact.service.ts +60 -0
- package/src/services/plan/plan-builder.service.ts +76 -0
- package/src/services/plan/plan-checkpoint.service.ts +103 -0
- package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
- package/src/services/plan/plan-completion-side-effects.ts +175 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +398 -0
- package/src/services/plan/plan-deadline.service.ts +547 -0
- package/src/services/plan/plan-event-delivery.service.ts +261 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +475 -0
- package/src/services/plan/plan-executor-helpers.ts +322 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1654 -0
- package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
- package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
- package/src/services/plan/plan-run-serialization.ts +15 -0
- package/src/services/plan/plan-run.service.ts +644 -0
- package/src/services/plan/plan-scheduler.service.ts +385 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +33 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +125 -0
- package/src/services/plugin-executor.service.ts +97 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +296 -230
- package/src/services/recent-activity-title.service.ts +65 -36
- package/src/services/recent-activity.service.ts +274 -259
- package/src/services/skill-resolver.service.ts +38 -12
- package/src/services/social-chat-history.service.ts +176 -125
- package/src/services/system-executor.service.ts +91 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +369 -0
- package/src/services/thread/thread-listing.ts +198 -0
- package/src/services/thread/thread-memory-block.ts +117 -0
- package/src/services/thread/thread-message.service.ts +363 -0
- package/src/services/thread/thread-record-store.ts +155 -0
- package/src/services/thread/thread-title.service.ts +74 -0
- package/src/services/thread/thread-turn-execution.ts +280 -0
- package/src/services/thread/thread-turn-message-context.ts +73 -0
- package/src/services/thread/thread-turn-preparation.service.ts +1146 -0
- package/src/services/thread/thread-turn-streaming.ts +402 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +343 -0
- package/src/services/thread/thread.service.ts +335 -0
- package/src/services/user.service.ts +82 -32
- package/src/services/write-intent-validator.service.ts +63 -51
- package/src/storage/attachment-parser.ts +69 -27
- package/src/storage/attachment-storage.service.ts +331 -275
- package/src/storage/generated-document-storage.service.ts +66 -34
- package/src/system-agents/agent-result.ts +3 -1
- package/src/system-agents/context-compaction.agent.ts +2 -2
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/memory-reranker.agent.ts +2 -2
- package/src/system-agents/memory.agent.ts +2 -2
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -2
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -2
- package/src/system-agents/skill-extractor.agent.ts +2 -2
- package/src/system-agents/skill-manager.agent.ts +2 -2
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +2 -2
- package/src/tools/execution-plan.tool.ts +220 -161
- package/src/tools/fetch-webpage.tool.ts +21 -17
- package/src/tools/firecrawl-client.ts +16 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +49 -47
- package/src/tools/read-file-parts.tool.ts +44 -33
- package/src/tools/remember-memory.tool.ts +65 -45
- package/src/tools/search-web.tool.ts +26 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +124 -83
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +17 -23
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +95 -16
- package/src/utils/hono-error-handler.ts +24 -39
- package/src/utils/index.ts +2 -1
- package/src/utils/null-proto-record.ts +41 -0
- package/src/utils/sse-keepalive.ts +124 -21
- package/src/workers/bootstrap.ts +186 -51
- package/src/workers/memory-consolidation.worker.ts +325 -237
- package/src/workers/organization-learning.worker.ts +50 -16
- package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
- package/src/workers/regular-chat-memory-digest.runner.ts +175 -114
- package/src/workers/skill-extraction.runner.ts +176 -93
- package/src/workers/utils/file-section-chunker.ts +8 -10
- package/src/workers/utils/repo-structure-extractor.ts +349 -260
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/thread-message-query.ts +97 -38
- package/src/workers/worker-utils.ts +56 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/social-chat-agent-runner.ts +0 -118
- package/src/runtime/social-chat.ts +0 -516
- package/src/runtime/team-consultation-orchestrator.ts +0 -272
- package/src/services/adaptive-playbook.service.ts +0 -152
- package/src/services/artifact-provenance.service.ts +0 -172
- package/src/services/chat-attachments.service.ts +0 -17
- package/src/services/context-compaction-runtime.singleton.ts +0 -13
- package/src/services/execution-plan.service.ts +0 -1118
- package/src/services/memory.service.ts +0 -844
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-approval.service.ts +0 -83
- package/src/services/plan-artifact.service.ts +0 -50
- package/src/services/plan-builder.service.ts +0 -67
- package/src/services/plan-checkpoint.service.ts +0 -81
- package/src/services/plan-completion-side-effects.ts +0 -80
- package/src/services/plan-coordination.service.ts +0 -157
- package/src/services/plan-cycle.service.ts +0 -284
- package/src/services/plan-deadline.service.ts +0 -430
- package/src/services/plan-event-delivery.service.ts +0 -166
- package/src/services/plan-executor.service.ts +0 -1950
- package/src/services/plan-run.service.ts +0 -515
- package/src/services/plan-scheduler.service.ts +0 -240
- package/src/services/plan-template.service.ts +0 -177
- package/src/services/plan-validator.service.ts +0 -818
- package/src/services/plan-workspace.service.ts +0 -83
- package/src/services/thread-message.service.ts +0 -275
- package/src/services/thread-plan-registry.service.ts +0 -22
- package/src/services/thread-title.service.ts +0 -39
- package/src/services/thread-turn-preparation.service.ts +0 -1147
- package/src/services/thread-turn.ts +0 -172
- package/src/services/thread.service.ts +0 -869
- package/src/utils/env.ts +0 -8
- /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
- /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
- /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
- /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
- /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
- /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
- /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
- /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
|
+
import { createUIMessageStream } from 'ai'
|
|
3
|
+
import { Context, Schema, Effect, Layer } from 'effect'
|
|
4
|
+
|
|
5
|
+
import { ensureRecordId, recordIdToString } from '../../db/record-id'
|
|
6
|
+
import { TABLES } from '../../db/tables'
|
|
7
|
+
import { BadRequestError } from '../../effect/errors'
|
|
8
|
+
import { runPromise } from '../../effect/runtime'
|
|
9
|
+
import { getCurrentRuntime } from '../../effect/runtime-ref'
|
|
10
|
+
import { hasApprovalRespondedParts, isApprovalContinuationRequest } from '../../runtime/approval-continuation'
|
|
11
|
+
import { shouldPlanNodeUseVisibleTurn } from '../../runtime/execution-plan-visibility'
|
|
12
|
+
import { wrapResponseWithKeepalive } from '../../utils/sse-keepalive'
|
|
13
|
+
import type { makePlanExecutorService } from '../plan/plan-executor.service'
|
|
14
|
+
import { PlanExecutorServiceTag } from '../plan/plan-executor.service'
|
|
15
|
+
import type { makePlanRunService } from '../plan/plan-run.service'
|
|
16
|
+
import { PlanRunServiceTag } from '../plan/plan-run.service'
|
|
17
|
+
import type { makeUserService } from '../user.service'
|
|
18
|
+
import { UserServiceTag } from '../user.service'
|
|
19
|
+
import type {
|
|
20
|
+
PreparedThreadTurnResult,
|
|
21
|
+
ThreadApprovalContinuationParams,
|
|
22
|
+
ThreadPlanTurnParams,
|
|
23
|
+
ThreadTurnParams,
|
|
24
|
+
makeThreadTurnPreparationService,
|
|
25
|
+
} from './thread-turn-preparation.service'
|
|
26
|
+
import { ThreadTurnPreparationServiceTag } from './thread-turn-preparation.service'
|
|
27
|
+
import { buildThreadTurnSpanAttributes, compactSpanAttributes } from './thread-turn-tracing'
|
|
28
|
+
import type { makeThreadService } from './thread.service'
|
|
29
|
+
import { ThreadServiceTag } from './thread.service'
|
|
30
|
+
|
|
31
|
+
export { hasApprovalRespondedParts, isApprovalContinuationRequest }
|
|
32
|
+
export { wrapResponseWithKeepalive }
|
|
33
|
+
export type { PreparedThreadTurnResult }
|
|
34
|
+
export type { ThreadPlanTurnParams }
|
|
35
|
+
|
|
36
|
+
class ThreadTurnServiceError extends Schema.TaggedErrorClass<ThreadTurnServiceError>()('ThreadTurnServiceError', {
|
|
37
|
+
message: Schema.String,
|
|
38
|
+
cause: Schema.optional(Schema.Defect),
|
|
39
|
+
}) {}
|
|
40
|
+
|
|
41
|
+
interface ThreadTurnDeps {
|
|
42
|
+
planExecutor: ReturnType<typeof makePlanExecutorService>
|
|
43
|
+
planRun: ReturnType<typeof makePlanRunService>
|
|
44
|
+
thread: ReturnType<typeof makeThreadService>
|
|
45
|
+
threadTurnPreparation: ReturnType<typeof makeThreadTurnPreparationService>
|
|
46
|
+
user: ReturnType<typeof makeUserService>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const createThreadApprovalContinuationStreamEffect = Effect.fn('ThreadTurn.createApprovalContinuationStream')(
|
|
50
|
+
function* (deps: ThreadTurnDeps, params: ThreadApprovalContinuationParams) {
|
|
51
|
+
const currentContext = yield* Effect.context()
|
|
52
|
+
const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
|
|
53
|
+
const prepared = yield* deps.threadTurnPreparation.prepareThreadRunCoreEffect({
|
|
54
|
+
...params,
|
|
55
|
+
kind: 'approvalContinuation',
|
|
56
|
+
})
|
|
57
|
+
return createUIMessageStream<ChatMessage>({
|
|
58
|
+
originalMessages: prepared.originalMessages,
|
|
59
|
+
onError: (error) => (error instanceof Error ? error.message : 'Approval continuation stream failed.'),
|
|
60
|
+
execute: ({ writer }) =>
|
|
61
|
+
runPromiseWithCurrentContext(
|
|
62
|
+
prepared.run(writer).pipe(Effect.withSpan('ThreadTurn.executeApprovalContinuationStream'), Effect.asVoid),
|
|
63
|
+
),
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
function createThreadApprovalContinuationStreamWith(deps: ThreadTurnDeps, params: ThreadApprovalContinuationParams) {
|
|
69
|
+
return createThreadApprovalContinuationStreamEffect(deps, params).pipe(
|
|
70
|
+
Effect.annotateSpans(
|
|
71
|
+
buildThreadTurnSpanAttributes({
|
|
72
|
+
threadRef: params.threadRef,
|
|
73
|
+
orgRef: params.orgRef,
|
|
74
|
+
userRef: params.userRef,
|
|
75
|
+
kind: 'approvalContinuation',
|
|
76
|
+
streamId: params.streamId,
|
|
77
|
+
threadType: params.thread.type,
|
|
78
|
+
}),
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const createThreadNativeToolApprovalStreamEffect = Effect.fn('ThreadTurn.createNativeToolApprovalStream')(function* (
|
|
84
|
+
deps: ThreadTurnDeps,
|
|
85
|
+
params: ThreadApprovalContinuationParams,
|
|
86
|
+
) {
|
|
87
|
+
const currentContext = yield* Effect.context()
|
|
88
|
+
const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
|
|
89
|
+
const prepared = yield* deps.threadTurnPreparation.prepareThreadRunCoreEffect({
|
|
90
|
+
...params,
|
|
91
|
+
kind: 'nativeToolApprovalTurn',
|
|
92
|
+
})
|
|
93
|
+
return createUIMessageStream<ChatMessage>({
|
|
94
|
+
originalMessages: prepared.originalMessages,
|
|
95
|
+
onError: (error) => (error instanceof Error ? error.message : 'Native tool approval stream failed.'),
|
|
96
|
+
execute: ({ writer }) =>
|
|
97
|
+
runPromiseWithCurrentContext(
|
|
98
|
+
prepared.run(writer).pipe(Effect.withSpan('ThreadTurn.executeNativeToolApprovalStream'), Effect.asVoid),
|
|
99
|
+
),
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
function createThreadNativeToolApprovalStreamWith(deps: ThreadTurnDeps, params: ThreadApprovalContinuationParams) {
|
|
104
|
+
return createThreadNativeToolApprovalStreamEffect(deps, params).pipe(
|
|
105
|
+
Effect.annotateSpans(
|
|
106
|
+
buildThreadTurnSpanAttributes({
|
|
107
|
+
threadRef: params.threadRef,
|
|
108
|
+
orgRef: params.orgRef,
|
|
109
|
+
userRef: params.userRef,
|
|
110
|
+
kind: 'nativeToolApprovalTurn',
|
|
111
|
+
streamId: params.streamId,
|
|
112
|
+
threadType: params.thread.type,
|
|
113
|
+
}),
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const createThreadTurnStreamEffect = Effect.fn('ThreadTurn.createThreadTurnStream')(function* (
|
|
119
|
+
deps: ThreadTurnDeps,
|
|
120
|
+
params: ThreadTurnParams,
|
|
121
|
+
) {
|
|
122
|
+
const currentContext = yield* Effect.context()
|
|
123
|
+
const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
|
|
124
|
+
const prepared = yield* deps.threadTurnPreparation.prepareThreadRunCoreEffect({ ...params, kind: 'userTurn' })
|
|
125
|
+
return createUIMessageStream<ChatMessage>({
|
|
126
|
+
originalMessages: prepared.originalMessages,
|
|
127
|
+
onError: (error) => (error instanceof Error ? error.message : 'Chat stream failed.'),
|
|
128
|
+
execute: ({ writer }) =>
|
|
129
|
+
runPromiseWithCurrentContext(
|
|
130
|
+
prepared.run(writer).pipe(Effect.withSpan('ThreadTurn.executeThreadTurnStream'), Effect.asVoid),
|
|
131
|
+
),
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
function createThreadTurnStreamWith(deps: ThreadTurnDeps, params: ThreadTurnParams) {
|
|
136
|
+
return createThreadTurnStreamEffect(deps, params).pipe(
|
|
137
|
+
Effect.annotateSpans(
|
|
138
|
+
buildThreadTurnSpanAttributes({
|
|
139
|
+
threadRef: params.threadRef,
|
|
140
|
+
orgRef: params.orgRef,
|
|
141
|
+
userRef: params.userRef,
|
|
142
|
+
kind: 'userTurn',
|
|
143
|
+
streamId: params.streamId,
|
|
144
|
+
threadType: params.thread.type,
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const runThreadTurnInBackgroundEffect = Effect.fn('ThreadTurn.runThreadTurnInBackground')(function* (
|
|
151
|
+
deps: ThreadTurnDeps,
|
|
152
|
+
params: ThreadTurnParams,
|
|
153
|
+
) {
|
|
154
|
+
const prepared = yield* deps.threadTurnPreparation.prepareThreadRunCoreEffect({ ...params, kind: 'userTurn' })
|
|
155
|
+
return yield* prepared.run().pipe(Effect.withSpan('ThreadTurn.executeBackgroundTurn'))
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
function runThreadTurnInBackgroundWith(deps: ThreadTurnDeps, params: ThreadTurnParams) {
|
|
159
|
+
return runThreadTurnInBackgroundEffect(deps, params).pipe(
|
|
160
|
+
Effect.annotateSpans(
|
|
161
|
+
buildThreadTurnSpanAttributes({
|
|
162
|
+
threadRef: params.threadRef,
|
|
163
|
+
orgRef: params.orgRef,
|
|
164
|
+
userRef: params.userRef,
|
|
165
|
+
kind: 'background',
|
|
166
|
+
streamId: params.streamId,
|
|
167
|
+
threadType: params.thread.type,
|
|
168
|
+
agentId: params.agentIdOverride,
|
|
169
|
+
}),
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const triggerPlanNodeTurnEffect = Effect.fn('ThreadTurn.triggerPlanNodeTurn')(function* (
|
|
175
|
+
deps: ThreadTurnDeps,
|
|
176
|
+
params: { runId: string; nodeId: string; abortSignal?: AbortSignal; streamId?: string },
|
|
177
|
+
) {
|
|
178
|
+
const run = yield* deps.planRun.getRunById(params.runId)
|
|
179
|
+
const spec = yield* deps.planRun.getPlanSpecById(run.planSpecId)
|
|
180
|
+
const nodeSpec = yield* deps.planRun.getNodeSpecByNodeId(spec.id, params.nodeId)
|
|
181
|
+
|
|
182
|
+
if (!shouldPlanNodeUseVisibleTurn(spec, nodeSpec) || nodeSpec.owner.executorType !== 'agent') {
|
|
183
|
+
return yield* new BadRequestError({
|
|
184
|
+
message: `Plan node "${params.nodeId}" is not eligible for a visible plan turn.`,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let activeRun = run
|
|
189
|
+
let nodeRun = yield* deps.planRun.getNodeRunByNodeId(run.id, params.nodeId)
|
|
190
|
+
if (nodeRun.status === 'ready') {
|
|
191
|
+
yield* Effect.tryPromise({
|
|
192
|
+
try: () => deps.planExecutor.transitionNodeToRunning({ runId: params.runId, nodeId: params.nodeId }),
|
|
193
|
+
catch: (cause) => new ThreadTurnServiceError({ message: 'Failed to transition plan node to running.', cause }),
|
|
194
|
+
})
|
|
195
|
+
activeRun = yield* deps.planRun.getRunById(params.runId)
|
|
196
|
+
nodeRun = yield* deps.planRun.getNodeRunByNodeId(run.id, params.nodeId)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (nodeRun.status !== 'running') {
|
|
200
|
+
return { assistantMessages: [], inputMessageId: undefined }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const thread = yield* deps.thread.getThread(activeRun.threadId)
|
|
204
|
+
const userRef = ensureRecordId(thread.userId, TABLES.USER)
|
|
205
|
+
const [artifacts, nodeRuns, nodeSpecs, userName] = yield* Effect.all([
|
|
206
|
+
deps.planRun.listArtifacts(activeRun.id),
|
|
207
|
+
deps.planRun.listNodeRuns(activeRun.id),
|
|
208
|
+
deps.planRun.listNodeSpecs(spec.id),
|
|
209
|
+
deps.user.getUser(recordIdToString(userRef, TABLES.USER)).pipe(
|
|
210
|
+
Effect.map((user) => user.name),
|
|
211
|
+
Effect.catch(() => Effect.succeed(undefined as string | undefined)),
|
|
212
|
+
),
|
|
213
|
+
]).pipe(Effect.withSpan('ThreadTurn.loadPlanTurnContext'))
|
|
214
|
+
|
|
215
|
+
const inputArtifacts = artifacts
|
|
216
|
+
.filter((artifact) => nodeSpec.upstreamNodeIds.includes(artifact.nodeId))
|
|
217
|
+
.map((artifact) => ({
|
|
218
|
+
name: artifact.name,
|
|
219
|
+
kind: artifact.kind,
|
|
220
|
+
...(artifact.description ? { description: artifact.description } : {}),
|
|
221
|
+
...(artifact.content !== undefined ? { content: artifact.content } : {}),
|
|
222
|
+
...(artifact.payload !== undefined ? { payload: artifact.payload } : {}),
|
|
223
|
+
...(artifact.publishedArtifactId
|
|
224
|
+
? { publishedArtifactId: recordIdToString(artifact.publishedArtifactId, TABLES.ARTIFACT) }
|
|
225
|
+
: {}),
|
|
226
|
+
}))
|
|
227
|
+
const upstreamNodeSpecs = new Map(nodeSpecs.map((upstreamNodeSpec) => [upstreamNodeSpec.nodeId, upstreamNodeSpec]))
|
|
228
|
+
const upstreamHandoffs = nodeRuns
|
|
229
|
+
.filter((candidate) => nodeSpec.upstreamNodeIds.includes(candidate.nodeId) && candidate.latestNotes)
|
|
230
|
+
.map((candidate) => ({
|
|
231
|
+
nodeId: candidate.nodeId,
|
|
232
|
+
label: upstreamNodeSpecs.get(candidate.nodeId)?.label ?? candidate.nodeId,
|
|
233
|
+
ownerRef: upstreamNodeSpecs.get(candidate.nodeId)?.owner.ref ?? 'system',
|
|
234
|
+
ownerType: upstreamNodeSpecs.get(candidate.nodeId)?.owner.executorType ?? 'system',
|
|
235
|
+
summary: candidate.latestNotes ?? undefined,
|
|
236
|
+
}))
|
|
237
|
+
|
|
238
|
+
const threadRef = ensureRecordId(activeRun.threadId, TABLES.THREAD)
|
|
239
|
+
const orgRef = ensureRecordId(activeRun.organizationId, TABLES.ORGANIZATION)
|
|
240
|
+
const prepared = yield* deps.threadTurnPreparation.prepareThreadRunCoreEffect({
|
|
241
|
+
kind: 'planTurn',
|
|
242
|
+
thread,
|
|
243
|
+
threadRef,
|
|
244
|
+
orgRef,
|
|
245
|
+
userRef,
|
|
246
|
+
userName,
|
|
247
|
+
abortSignal: params.abortSignal,
|
|
248
|
+
streamId: params.streamId,
|
|
249
|
+
planTurn: {
|
|
250
|
+
runId: params.runId,
|
|
251
|
+
nodeId: params.nodeId,
|
|
252
|
+
planTitle: spec.title,
|
|
253
|
+
nodeSpec,
|
|
254
|
+
nodeRun,
|
|
255
|
+
resolvedInput: nodeRun.resolvedInput ?? {},
|
|
256
|
+
inputArtifacts,
|
|
257
|
+
upstreamHandoffs,
|
|
258
|
+
},
|
|
259
|
+
})
|
|
260
|
+
return yield* prepared.run().pipe(Effect.withSpan('ThreadTurn.executePlanTurn'))
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
function triggerPlanNodeTurnWith(
|
|
264
|
+
deps: ThreadTurnDeps,
|
|
265
|
+
params: { runId: string; nodeId: string; abortSignal?: AbortSignal; streamId?: string },
|
|
266
|
+
) {
|
|
267
|
+
return triggerPlanNodeTurnEffect(deps, params).pipe(
|
|
268
|
+
Effect.annotateSpans(
|
|
269
|
+
compactSpanAttributes({
|
|
270
|
+
turnKind: 'planTurn',
|
|
271
|
+
streamId: params.streamId,
|
|
272
|
+
planRunId: params.runId,
|
|
273
|
+
planNodeId: params.nodeId,
|
|
274
|
+
}),
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function makeThreadTurnService(deps: ThreadTurnDeps) {
|
|
280
|
+
return {
|
|
281
|
+
createThreadApprovalContinuationStream(params: ThreadApprovalContinuationParams) {
|
|
282
|
+
return createThreadApprovalContinuationStreamWith(deps, params)
|
|
283
|
+
},
|
|
284
|
+
createThreadNativeToolApprovalStream(params: ThreadApprovalContinuationParams) {
|
|
285
|
+
return createThreadNativeToolApprovalStreamWith(deps, params)
|
|
286
|
+
},
|
|
287
|
+
createThreadTurnStream(params: ThreadTurnParams) {
|
|
288
|
+
return createThreadTurnStreamWith(deps, params)
|
|
289
|
+
},
|
|
290
|
+
runThreadTurnInBackground(params: ThreadTurnParams) {
|
|
291
|
+
return runThreadTurnInBackgroundWith(deps, params)
|
|
292
|
+
},
|
|
293
|
+
triggerPlanNodeTurn(params: { runId: string; nodeId: string; abortSignal?: AbortSignal; streamId?: string }) {
|
|
294
|
+
return triggerPlanNodeTurnWith(deps, params)
|
|
295
|
+
},
|
|
296
|
+
} as const
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export class ThreadTurnServiceTag extends Context.Service<
|
|
300
|
+
ThreadTurnServiceTag,
|
|
301
|
+
ReturnType<typeof makeThreadTurnService>
|
|
302
|
+
>()('ThreadTurnService') {}
|
|
303
|
+
|
|
304
|
+
export const ThreadTurnServiceLive = Layer.effect(
|
|
305
|
+
ThreadTurnServiceTag,
|
|
306
|
+
Effect.gen(function* () {
|
|
307
|
+
const planExecutor = yield* PlanExecutorServiceTag
|
|
308
|
+
const planRun = yield* PlanRunServiceTag
|
|
309
|
+
const thread = yield* ThreadServiceTag
|
|
310
|
+
const threadTurnPreparation = yield* ThreadTurnPreparationServiceTag
|
|
311
|
+
const user = yield* UserServiceTag
|
|
312
|
+
return makeThreadTurnService({ planExecutor, planRun, thread, threadTurnPreparation, user })
|
|
313
|
+
}),
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
function getThreadTurnService() {
|
|
317
|
+
return getCurrentRuntime().runSync(Effect.service(ThreadTurnServiceTag))
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function createThreadApprovalContinuationStream(params: ThreadApprovalContinuationParams) {
|
|
321
|
+
return runPromise(getThreadTurnService().createThreadApprovalContinuationStream(params))
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function createThreadNativeToolApprovalStream(params: ThreadApprovalContinuationParams) {
|
|
325
|
+
return runPromise(getThreadTurnService().createThreadNativeToolApprovalStream(params))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function createThreadTurnStream(params: ThreadTurnParams) {
|
|
329
|
+
return runPromise(getThreadTurnService().createThreadTurnStream(params))
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function runThreadTurnInBackground(params: ThreadTurnParams): Promise<PreparedThreadTurnResult> {
|
|
333
|
+
return runPromise(getThreadTurnService().runThreadTurnInBackground(params))
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function triggerPlanNodeTurn(params: {
|
|
337
|
+
runId: string
|
|
338
|
+
nodeId: string
|
|
339
|
+
abortSignal?: AbortSignal
|
|
340
|
+
streamId?: string
|
|
341
|
+
}): Promise<PreparedThreadTurnResult> {
|
|
342
|
+
return runPromise(getThreadTurnService().triggerPlanNodeTurn(params))
|
|
343
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { THREAD, sdkThreadStatusSchema } from '@lota-sdk/shared'
|
|
2
|
+
import { Context, Effect, Layer } from 'effect'
|
|
3
|
+
import { surql } from 'surrealdb'
|
|
4
|
+
|
|
5
|
+
import { getCoreThreadProfile, isAgentName } from '../../config/agent-defaults'
|
|
6
|
+
import { ensureRecordId, isRecordIdInput, recordIdToString } from '../../db/record-id'
|
|
7
|
+
import type { RecordIdRef } from '../../db/record-id'
|
|
8
|
+
import type { SurrealDBService } from '../../db/service'
|
|
9
|
+
import { TABLES } from '../../db/tables'
|
|
10
|
+
import { BadRequestError, ServiceError } from '../../effect/errors'
|
|
11
|
+
import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
|
|
12
|
+
import { DatabaseServiceTag, RedisServiceTag } from '../../effect/services'
|
|
13
|
+
import type { RedisConnectionManager } from '../../redis/connection'
|
|
14
|
+
import { CompactionCoordinationTag } from '../../runtime/chat-run-orchestration'
|
|
15
|
+
import { toIsoDateTimeString } from '../../utils/date-time'
|
|
16
|
+
import { ChatRunRegistryTag } from '../chat-run-registry.service'
|
|
17
|
+
import { ContextCompactionServiceTag } from '../context-compaction.service'
|
|
18
|
+
import type { makeContextCompactionService } from '../context-compaction.service'
|
|
19
|
+
import { createThreadActiveRunHelpers } from './thread-active-run'
|
|
20
|
+
import { createThreadBootstrapHelpers } from './thread-bootstrap'
|
|
21
|
+
import { createThreadListingHelpers } from './thread-listing'
|
|
22
|
+
import { createThreadMemoryBlockHelpers, formatMemoryBlockForPrompt } from './thread-memory-block'
|
|
23
|
+
import { ThreadMessageServiceTag } from './thread-message.service'
|
|
24
|
+
import type { makeThreadMessageService } from './thread-message.service'
|
|
25
|
+
import { createThreadRecordStore } from './thread-record-store'
|
|
26
|
+
import { NormalizedThreadSchema, PublicThreadSchema } from './thread.types'
|
|
27
|
+
import type { NormalizedThread, PublicThread, ThreadRecord } from './thread.types'
|
|
28
|
+
|
|
29
|
+
export { ActiveThreadRunConflictError } from '../../effect/errors'
|
|
30
|
+
|
|
31
|
+
function assertMutableThreadEffect(thread: ThreadRecord): Effect.Effect<void, BadRequestError> {
|
|
32
|
+
return thread.type === 'default'
|
|
33
|
+
? Effect.fail(new BadRequestError({ message: 'Default threads cannot be modified.' }))
|
|
34
|
+
: Effect.void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getDefaultTitle(thread: Pick<ThreadRecord, 'type' | 'threadType'>): string {
|
|
38
|
+
if (thread.type === 'thread' && typeof thread.threadType === 'string') {
|
|
39
|
+
return getCoreThreadProfile(thread.threadType).config.title
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return THREAD.DEFAULT_TITLE
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeRecordIdStringEffect(id: unknown, table: string): Effect.Effect<string, BadRequestError> {
|
|
46
|
+
return isRecordIdInput(id)
|
|
47
|
+
? Effect.succeed(recordIdToString(ensureRecordId(id, table), table))
|
|
48
|
+
: Effect.fail(new BadRequestError({ message: `Invalid record id for table ${table}.` }))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function toPublicThread(thread: NormalizedThread): PublicThread {
|
|
52
|
+
const { organizationId: _organizationId, userId: _userId, memoryBlock: _memoryBlock, ...publicThread } = thread
|
|
53
|
+
return PublicThreadSchema.parse(publicThread)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type ThreadServiceError = ServiceError | BadRequestError
|
|
57
|
+
|
|
58
|
+
const effectTryPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
|
|
59
|
+
|
|
60
|
+
type ChatRunRegistry = Context.Service.Shape<typeof ChatRunRegistryTag>
|
|
61
|
+
|
|
62
|
+
type CompactionCoordination = Context.Service.Shape<typeof CompactionCoordinationTag>
|
|
63
|
+
|
|
64
|
+
interface ThreadServiceDeps {
|
|
65
|
+
db: SurrealDBService
|
|
66
|
+
redis: RedisConnectionManager
|
|
67
|
+
chatRunRegistry: ChatRunRegistry
|
|
68
|
+
threadMessageService: ReturnType<typeof makeThreadMessageService>
|
|
69
|
+
contextCompactionService: ReturnType<typeof makeContextCompactionService>
|
|
70
|
+
compactionCoordination: CompactionCoordination
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function makeThreadService(deps: ThreadServiceDeps) {
|
|
74
|
+
const threadStore = createThreadRecordStore({ db: deps.db })
|
|
75
|
+
const activeRun = createThreadActiveRunHelpers({
|
|
76
|
+
db: deps.db,
|
|
77
|
+
threadStore,
|
|
78
|
+
redis: deps.redis,
|
|
79
|
+
chatRunRegistry: deps.chatRunRegistry,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
function computeIsRunning(
|
|
83
|
+
thread: Pick<ThreadRecord, 'id' | 'activeRunId'>,
|
|
84
|
+
options: { checkLease: boolean },
|
|
85
|
+
): Effect.Effect<boolean, ThreadServiceError> {
|
|
86
|
+
const activeRunId =
|
|
87
|
+
typeof thread.activeRunId === 'string' && thread.activeRunId.trim().length > 0 ? thread.activeRunId : null
|
|
88
|
+
|
|
89
|
+
if (activeRunId === null) {
|
|
90
|
+
return Effect.succeed(false)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (deps.chatRunRegistry.has(activeRunId)) {
|
|
94
|
+
return Effect.succeed(true)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!options.checkLease) {
|
|
98
|
+
return Effect.succeed(true)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return effectTryPromise(
|
|
102
|
+
() => activeRun.hasActiveRunLease(ensureRecordId(thread.id, TABLES.THREAD)),
|
|
103
|
+
'Failed to check active thread run lease.',
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toNormalizedThread(
|
|
108
|
+
thread: ThreadRecord,
|
|
109
|
+
options: { checkLease?: boolean } = {},
|
|
110
|
+
): Effect.Effect<NormalizedThread, ThreadServiceError> {
|
|
111
|
+
return Effect.gen(function* () {
|
|
112
|
+
const isRunning = yield* computeIsRunning(thread, { checkLease: options.checkLease ?? true })
|
|
113
|
+
const [id, userId, organizationId] = yield* Effect.all([
|
|
114
|
+
normalizeRecordIdStringEffect(thread.id, TABLES.THREAD),
|
|
115
|
+
normalizeRecordIdStringEffect(thread.userId, TABLES.USER),
|
|
116
|
+
normalizeRecordIdStringEffect(thread.organizationId, TABLES.ORGANIZATION),
|
|
117
|
+
])
|
|
118
|
+
return NormalizedThreadSchema.parse({
|
|
119
|
+
id,
|
|
120
|
+
userId,
|
|
121
|
+
organizationId,
|
|
122
|
+
type: thread.type,
|
|
123
|
+
...(thread.type === 'thread' && typeof thread.threadType === 'string' ? { threadType: thread.threadType } : {}),
|
|
124
|
+
nameGenerated: thread.nameGenerated,
|
|
125
|
+
isRunning,
|
|
126
|
+
isCompacting: thread.isCompacting === true,
|
|
127
|
+
...(isAgentName(thread.agentId) ? { agentId: thread.agentId } : {}),
|
|
128
|
+
title: thread.title ?? getDefaultTitle(thread),
|
|
129
|
+
status: thread.status,
|
|
130
|
+
memoryBlock: formatMemoryBlockForPrompt(thread),
|
|
131
|
+
members: thread.members,
|
|
132
|
+
createdAt: toIsoDateTimeString(thread.createdAt),
|
|
133
|
+
updatedAt: toIsoDateTimeString(thread.updatedAt),
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function toNormalizedThreads(
|
|
139
|
+
threads: ThreadRecord[],
|
|
140
|
+
options: { checkLease?: boolean } = {},
|
|
141
|
+
): Effect.Effect<NormalizedThread[], ThreadServiceError> {
|
|
142
|
+
return Effect.forEach(threads, (thread) => toNormalizedThread(thread, options))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const bootstrap = createThreadBootstrapHelpers({
|
|
146
|
+
threadStore,
|
|
147
|
+
threadMessageService: deps.threadMessageService,
|
|
148
|
+
redis: deps.redis,
|
|
149
|
+
normalizeThread: toNormalizedThread,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const listing = createThreadListingHelpers({ db: deps.db, normalizeThreads: toNormalizedThreads })
|
|
153
|
+
|
|
154
|
+
const memory = createThreadMemoryBlockHelpers({
|
|
155
|
+
threadStore,
|
|
156
|
+
contextCompactionService: { compactMemoryBlock: deps.contextCompactionService.compactMemoryBlock },
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
function getById(threadId: RecordIdRef) {
|
|
160
|
+
return effectTryPromise(() => threadStore.getById(threadId), 'Failed to load thread.')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getThread(threadId: RecordIdRef) {
|
|
164
|
+
return Effect.gen(function* () {
|
|
165
|
+
const thread = yield* getById(threadId)
|
|
166
|
+
return yield* toNormalizedThread(thread)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function updateTitle(threadId: RecordIdRef, title: string) {
|
|
171
|
+
return Effect.gen(function* () {
|
|
172
|
+
const existing = yield* getById(threadId)
|
|
173
|
+
yield* assertMutableThreadEffect(existing)
|
|
174
|
+
const thread = yield* effectTryPromise(
|
|
175
|
+
() => threadStore.update(threadId, { title, nameGenerated: true }),
|
|
176
|
+
'Failed to update thread title.',
|
|
177
|
+
)
|
|
178
|
+
return yield* toNormalizedThread(thread)
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function updateStatus(threadId: RecordIdRef, status: string) {
|
|
183
|
+
return Effect.gen(function* () {
|
|
184
|
+
const parsedStatus = sdkThreadStatusSchema.safeParse(status)
|
|
185
|
+
if (!parsedStatus.success) {
|
|
186
|
+
return yield* new BadRequestError({ message: `Invalid thread status: ${status}` })
|
|
187
|
+
}
|
|
188
|
+
const existing = yield* getById(threadId)
|
|
189
|
+
yield* assertMutableThreadEffect(existing)
|
|
190
|
+
const thread = yield* effectTryPromise(
|
|
191
|
+
() => threadStore.update(threadId, { status: parsedStatus.data }),
|
|
192
|
+
'Failed to update thread status.',
|
|
193
|
+
)
|
|
194
|
+
return yield* toNormalizedThread(thread)
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function setCompacting(threadId: RecordIdRef, value: boolean) {
|
|
199
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
200
|
+
const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
|
|
201
|
+
return Effect.asVoid(
|
|
202
|
+
effectTryPromise(
|
|
203
|
+
() => deps.db.query<unknown>(surql`UPDATE ONLY ${threadRef} SET isCompacting = ${value}`),
|
|
204
|
+
'Failed to update thread compaction flag.',
|
|
205
|
+
).pipe(Effect.tap(() => deps.compactionCoordination.signal(threadIdString, value))),
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function clearThread(threadId: RecordIdRef) {
|
|
210
|
+
return Effect.gen(function* () {
|
|
211
|
+
const existing = yield* getById(threadId)
|
|
212
|
+
yield* assertMutableThreadEffect(existing)
|
|
213
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
214
|
+
yield* effectTryPromise(
|
|
215
|
+
() => deps.db.deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef }),
|
|
216
|
+
'Failed to delete thread messages.',
|
|
217
|
+
)
|
|
218
|
+
yield* effectTryPromise(
|
|
219
|
+
() =>
|
|
220
|
+
deps.db.query<unknown>(surql`
|
|
221
|
+
UPDATE ONLY ${threadRef}
|
|
222
|
+
SET turnCount = 0,
|
|
223
|
+
compactionSummary = NONE,
|
|
224
|
+
lastCompactedMessageId = NONE,
|
|
225
|
+
activeRunId = NONE,
|
|
226
|
+
activeStreamId = NONE,
|
|
227
|
+
isCompacting = false
|
|
228
|
+
`),
|
|
229
|
+
'Failed to reset thread state.',
|
|
230
|
+
)
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function deleteThread(threadId: RecordIdRef) {
|
|
235
|
+
return Effect.gen(function* () {
|
|
236
|
+
const existing = yield* getById(threadId)
|
|
237
|
+
yield* assertMutableThreadEffect(existing)
|
|
238
|
+
yield* effectTryPromise(() => threadStore.deleteById(threadId), 'Failed to delete thread.')
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function incrementTurnCount(threadId: RecordIdRef) {
|
|
243
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
244
|
+
return Effect.gen(function* () {
|
|
245
|
+
const result = yield* effectTryPromise(
|
|
246
|
+
() =>
|
|
247
|
+
deps.db.query<{ turnCount: number }>(
|
|
248
|
+
surql`
|
|
249
|
+
UPDATE ONLY ${threadRef}
|
|
250
|
+
SET turnCount += 1
|
|
251
|
+
RETURN turnCount
|
|
252
|
+
`,
|
|
253
|
+
),
|
|
254
|
+
'Failed to increment thread turn count.',
|
|
255
|
+
)
|
|
256
|
+
return result[0].turnCount
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
findById: (...args: Parameters<typeof threadStore.findById>) =>
|
|
262
|
+
effectTryPromise(() => threadStore.findById(...args), 'Failed to find thread.'),
|
|
263
|
+
getById,
|
|
264
|
+
findAll: (...args: Parameters<typeof threadStore.findAll>) =>
|
|
265
|
+
effectTryPromise(() => threadStore.findAll(...args), 'Failed to find all threads.'),
|
|
266
|
+
create: (...args: Parameters<typeof threadStore.create>) =>
|
|
267
|
+
effectTryPromise(() => threadStore.create(...args), 'Failed to create thread.'),
|
|
268
|
+
update: (...args: Parameters<typeof threadStore.update>) =>
|
|
269
|
+
effectTryPromise(() => threadStore.update(...args), 'Failed to update thread.'),
|
|
270
|
+
delete: (...args: Parameters<typeof threadStore.deleteById>) =>
|
|
271
|
+
effectTryPromise(() => threadStore.deleteById(...args), 'Failed to delete thread.'),
|
|
272
|
+
getOrCreateDefault: (...args: Parameters<typeof bootstrap.getOrCreateDefault>) =>
|
|
273
|
+
effectTryPromise(() => bootstrap.getOrCreateDefault(...args), 'Failed to get or create default thread.'),
|
|
274
|
+
getOrCreateThread: (...args: Parameters<typeof bootstrap.getOrCreateThread>) =>
|
|
275
|
+
effectTryPromise(() => bootstrap.getOrCreateThread(...args), 'Failed to get or create thread.'),
|
|
276
|
+
createThread: (...args: Parameters<typeof bootstrap.createThread>) =>
|
|
277
|
+
effectTryPromise(() => bootstrap.createThread(...args), 'Failed to create thread.'),
|
|
278
|
+
ensureBootstrapThreads: (...args: Parameters<typeof bootstrap.ensureBootstrapThreads>) =>
|
|
279
|
+
effectTryPromise(() => bootstrap.ensureBootstrapThreads(...args), 'Failed to ensure bootstrap threads.'),
|
|
280
|
+
listThreads: (...args: Parameters<typeof listing.listThreads>) =>
|
|
281
|
+
effectTryPromise(() => listing.listThreads(...args), 'Failed to list threads.'),
|
|
282
|
+
listOrganizationThreads: (...args: Parameters<typeof listing.listOrganizationThreads>) =>
|
|
283
|
+
effectTryPromise(() => listing.listOrganizationThreads(...args), 'Failed to list organization threads.'),
|
|
284
|
+
getThread,
|
|
285
|
+
updateTitle,
|
|
286
|
+
updateStatus,
|
|
287
|
+
setActiveTurn: activeRun.setActiveTurn,
|
|
288
|
+
getActiveTurn: (...args: Parameters<typeof activeRun.getActiveTurn>) =>
|
|
289
|
+
effectTryPromise(() => activeRun.getActiveTurn(...args), 'Failed to get active turn.'),
|
|
290
|
+
getActiveRunId: activeRun.getActiveRunId,
|
|
291
|
+
hasActiveRunLease: activeRun.hasActiveRunLease,
|
|
292
|
+
withActiveRunLease: activeRun.withActiveRunLease,
|
|
293
|
+
getActiveStreamId: activeRun.getActiveStreamId,
|
|
294
|
+
clearActiveTurn: activeRun.clearActiveTurn,
|
|
295
|
+
clearStaleActiveRunIfMissingFromRegistry: activeRun.clearStaleActiveRunIfMissingFromRegistry,
|
|
296
|
+
stopActiveRun: (...args: Parameters<typeof activeRun.stopActiveRun>) =>
|
|
297
|
+
effectTryPromise(() => activeRun.stopActiveRun(...args), 'Failed to stop active run.'),
|
|
298
|
+
setCompacting,
|
|
299
|
+
appendMemoryBlock: (...args: Parameters<typeof memory.appendMemoryBlock>) =>
|
|
300
|
+
effectTryPromise(() => memory.appendMemoryBlock(...args), 'Failed to append memory block.'),
|
|
301
|
+
compactMemoryBlock: (...args: Parameters<typeof memory.compactMemoryBlock>) =>
|
|
302
|
+
effectTryPromise(() => memory.compactMemoryBlock(...args), 'Failed to compact memory block.'),
|
|
303
|
+
clearThread,
|
|
304
|
+
deleteThread,
|
|
305
|
+
listRecentThreads: (...args: Parameters<typeof listing.listRecentThreads>) =>
|
|
306
|
+
effectTryPromise(() => listing.listRecentThreads(...args), 'Failed to list recent threads.'),
|
|
307
|
+
formatMemoryBlockForPrompt,
|
|
308
|
+
toPublicThread,
|
|
309
|
+
incrementTurnCount,
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export class ThreadServiceTag extends Context.Service<ThreadServiceTag, ReturnType<typeof makeThreadService>>()(
|
|
314
|
+
'ThreadService',
|
|
315
|
+
) {}
|
|
316
|
+
|
|
317
|
+
export const ThreadServiceLive = Layer.effect(
|
|
318
|
+
ThreadServiceTag,
|
|
319
|
+
Effect.gen(function* () {
|
|
320
|
+
const db = yield* DatabaseServiceTag
|
|
321
|
+
const redis = yield* RedisServiceTag
|
|
322
|
+
const chatRunRegistry = yield* ChatRunRegistryTag
|
|
323
|
+
const threadMessageService = yield* ThreadMessageServiceTag
|
|
324
|
+
const contextCompactionService = yield* ContextCompactionServiceTag
|
|
325
|
+
const compactionCoordination = yield* CompactionCoordinationTag
|
|
326
|
+
return makeThreadService({
|
|
327
|
+
db,
|
|
328
|
+
redis,
|
|
329
|
+
chatRunRegistry,
|
|
330
|
+
threadMessageService,
|
|
331
|
+
contextCompactionService,
|
|
332
|
+
compactionCoordination,
|
|
333
|
+
})
|
|
334
|
+
}),
|
|
335
|
+
)
|