@lota-sdk/core 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -12
- package/src/ai/embedding-cache.ts +96 -22
- package/src/ai-gateway/ai-gateway.ts +766 -223
- package/src/config/agent-defaults.ts +189 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/background-processing.ts +1 -1
- package/src/config/constants.ts +8 -2
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +299 -19
- package/src/config/thread-defaults.ts +40 -20
- package/src/create-runtime.ts +200 -449
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +868 -601
- package/src/db/memory.ts +396 -280
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +288 -0
- package/src/db/service.ts +912 -779
- package/src/db/startup.ts +153 -68
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +96 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +123 -0
- package/src/effect/index.ts +24 -0
- package/src/effect/layers.ts +238 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +46 -0
- package/src/effect/services.ts +61 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +128 -83
- package/src/index.ts +48 -1
- package/src/openrouter/direct-provider.ts +11 -35
- package/src/queues/autonomous-job.queue.ts +117 -73
- package/src/queues/context-compaction.queue.ts +50 -17
- package/src/queues/delayed-node-promotion.queue.ts +46 -17
- 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 +26 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
- package/src/queues/plan-scheduler.queue.ts +97 -33
- package/src/queues/post-chat-memory.queue.ts +56 -26
- package/src/queues/queue-factory.ts +227 -59
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +45 -11
- package/src/redis/connection.ts +182 -113
- 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 +20 -0
- package/src/redis/stream-context.ts +92 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +5 -2
- package/src/runtime/agent-stream-helpers.ts +24 -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} +161 -94
- package/src/runtime/domain-layer.ts +192 -0
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +16 -4
- package/src/runtime/helper-model.ts +139 -48
- package/src/runtime/index.ts +7 -8
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -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} +54 -67
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/memory/memory-scope.ts +53 -0
- package/src/runtime/plugin-resolution.ts +124 -25
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +177 -130
- package/src/runtime/retrieval-adapters.ts +40 -6
- package/src/runtime/runtime-accessors.ts +92 -0
- package/src/runtime/runtime-config.ts +150 -61
- package/src/runtime/runtime-extensions.ts +23 -25
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +386 -0
- package/src/runtime/runtime-token.ts +47 -0
- package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
- package/src/runtime/social-chat/social-chat.ts +630 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -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 +183 -111
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +253 -149
- package/src/services/artifact.service.ts +231 -149
- package/src/services/attachment.service.ts +171 -115
- package/src/services/autonomous-job.service.ts +890 -491
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +13 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +151 -88
- 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 +278 -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 +101 -168
- package/src/services/graph-full-routing.ts +193 -0
- package/src/services/index.ts +19 -21
- package/src/services/institutional-memory.service.ts +213 -125
- package/src/services/learned-skill.service.ts +368 -260
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +50 -0
- package/src/services/memory/memory-preseeded.ts +86 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
- package/src/services/memory/memory.service.ts +674 -0
- package/src/services/memory/rerank.service.ts +201 -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 +29 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +153 -77
- package/src/services/ownership-dispatcher.service.ts +456 -263
- 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-approval.service.ts → plan/plan-approval.service.ts} +45 -22
- 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 +169 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +405 -0
- package/src/services/plan/plan-deadline.service.ts +533 -0
- package/src/services/plan/plan-event-delivery.service.ts +266 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +522 -0
- package/src/services/plan/plan-executor-helpers.ts +307 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1737 -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 +637 -0
- package/src/services/plan/plan-scheduler.service.ts +379 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +36 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +131 -0
- package/src/services/plugin-executor.service.ts +102 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +288 -231
- package/src/services/recent-activity-title.service.ts +73 -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 +190 -122
- package/src/services/system-executor.service.ts +96 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +385 -0
- package/src/services/thread/thread-listing.ts +199 -0
- package/src/services/thread/thread-memory-block.ts +130 -0
- package/src/services/thread/thread-message.service.ts +379 -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 +1148 -0
- package/src/services/thread/thread-turn-streaming.ts +403 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +376 -0
- package/src/services/thread/thread.service.ts +344 -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 +334 -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 +3 -3
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +3 -3
- package/src/system-agents/memory.agent.ts +3 -3
- package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
- package/src/system-agents/skill-extractor.agent.ts +3 -3
- package/src/system-agents/skill-manager.agent.ts +3 -3
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +3 -3
- package/src/tools/execution-plan.tool.ts +241 -171
- package/src/tools/fetch-webpage.tool.ts +29 -18
- package/src/tools/firecrawl-client.ts +26 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +57 -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 +33 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +125 -84
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +25 -22
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +111 -20
- 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 +164 -52
- 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 +185 -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 +74 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/config/search.ts +0 -3
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/agent-types.ts +0 -1
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/memory-scope.ts +0 -43
- 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 -914
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- 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/rerank.service.ts +0 -156
- 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,1148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
THREAD,
|
|
3
|
+
dataPartsSchemas,
|
|
4
|
+
messageMetadataSchema,
|
|
5
|
+
PlanNodeResultSubmissionSchema,
|
|
6
|
+
SUBMIT_PLAN_TURN_RESULT_TOOL_NAME,
|
|
7
|
+
toTimestamp,
|
|
8
|
+
withMessageCreatedAt,
|
|
9
|
+
} from '@lota-sdk/shared'
|
|
10
|
+
import type { ChatMessage, MessageMetadata } from '@lota-sdk/shared'
|
|
11
|
+
import { tool as createTool, validateUIMessages } from 'ai'
|
|
12
|
+
import type { UIMessageStreamWriter } from 'ai'
|
|
13
|
+
import { Clock, Context, Schema, Effect, Layer } from 'effect'
|
|
14
|
+
|
|
15
|
+
import type { CoreThreadProfile } from '../../config/agent-defaults'
|
|
16
|
+
import {
|
|
17
|
+
getAgentRoster,
|
|
18
|
+
getLeadAgentId,
|
|
19
|
+
getCoreThreadProfile,
|
|
20
|
+
getResolvedAgentFactoryConfig,
|
|
21
|
+
} from '../../config/agent-defaults'
|
|
22
|
+
import { aiLogger } from '../../config/logger'
|
|
23
|
+
import type { RecordIdRef } from '../../db/record-id'
|
|
24
|
+
import { recordIdToString } from '../../db/record-id'
|
|
25
|
+
import { TABLES } from '../../db/tables'
|
|
26
|
+
import type { DatabaseError, ServiceError } from '../../effect/errors'
|
|
27
|
+
import { ThreadTurnError } from '../../effect/errors'
|
|
28
|
+
import { makeEffectTryPromiseWithMessage } from '../../effect/helpers'
|
|
29
|
+
import { enqueueContextCompaction } from '../../queues/context-compaction.queue'
|
|
30
|
+
import { enqueueThreadTitleGeneration } from '../../queues/title-generation.queue'
|
|
31
|
+
import {
|
|
32
|
+
readRuntimeAgentIdentityOverrides,
|
|
33
|
+
resolveRuntimeAgentDisplayName,
|
|
34
|
+
} from '../../runtime/agent-identity-overrides'
|
|
35
|
+
import { createServerRunAbortController } from '../../runtime/agent-stream-helpers'
|
|
36
|
+
import { hasApprovalRespondedParts } from '../../runtime/approval-continuation'
|
|
37
|
+
import { hasMessageContent } from '../../runtime/chat-message'
|
|
38
|
+
import { CompactionCoordinationTag } from '../../runtime/chat-run-orchestration'
|
|
39
|
+
import { CONTEXT_WINDOW_TOKENS } from '../../runtime/context-compaction/context-compaction-constants'
|
|
40
|
+
import { createWiredContextCompactionRuntime } from '../../runtime/context-compaction/context-compaction-runtime'
|
|
41
|
+
import { createExecutionPlanInstructionSectionCache, toExecutionPlanCacheError } from '../../runtime/execution-plan'
|
|
42
|
+
import type { HelperModelRuntime } from '../../runtime/helper-model'
|
|
43
|
+
import { HelperModelTag } from '../../runtime/helper-model'
|
|
44
|
+
import { runPostTurnSideEffects } from '../../runtime/post-turn-side-effects'
|
|
45
|
+
import { getRuntimeAdapters, getTurnHooks } from '../../runtime/runtime-extensions'
|
|
46
|
+
import { extractMessageText, toOptionalTrimmedString } from '../../runtime/thread-chat-helpers'
|
|
47
|
+
import {
|
|
48
|
+
buildPlanTurnInstructionSections,
|
|
49
|
+
buildPlanTurnPromptMessage,
|
|
50
|
+
buildPlanTurnSubmitToolDescription,
|
|
51
|
+
} from '../../runtime/thread-plan-turn'
|
|
52
|
+
import type { ThreadPlanTurnContext } from '../../runtime/thread-plan-turn'
|
|
53
|
+
import { assembleThreadTurnContext } from '../../runtime/thread-turn-context'
|
|
54
|
+
import { finalizeTurnRunEffect, TurnLifecycleError } from '../../runtime/turn-lifecycle'
|
|
55
|
+
import { triageThreadMessage, checkForNextAgent } from '../../system-agents/thread-router.agent'
|
|
56
|
+
import { safeEnqueue } from '../../utils/async'
|
|
57
|
+
import { nowEpochMillis } from '../../utils/date-time'
|
|
58
|
+
import { getErrorMessage } from '../../utils/errors'
|
|
59
|
+
import type { makeAttachmentService } from '../attachment.service'
|
|
60
|
+
import { AttachmentServiceTag } from '../attachment.service'
|
|
61
|
+
import { ChatRunRegistryTag } from '../chat-run-registry.service'
|
|
62
|
+
import type { makeExecutionPlanService } from '../execution-plan/execution-plan.service'
|
|
63
|
+
import { ExecutionPlanServiceTag } from '../execution-plan/execution-plan.service'
|
|
64
|
+
import type { makeLearnedSkillService } from '../learned-skill.service'
|
|
65
|
+
import { LearnedSkillServiceTag } from '../learned-skill.service'
|
|
66
|
+
import type { createMemoryService } from '../memory/memory.service'
|
|
67
|
+
import { MemoryServiceTag } from '../memory/memory.service'
|
|
68
|
+
import type { makePlanRunService } from '../plan/plan-run.service'
|
|
69
|
+
import { PlanRunServiceTag } from '../plan/plan-run.service'
|
|
70
|
+
import type { makeThreadMessageService } from './thread-message.service'
|
|
71
|
+
import { ThreadMessageServiceTag } from './thread-message.service'
|
|
72
|
+
import {
|
|
73
|
+
applyPlanTurnToolPolicy,
|
|
74
|
+
createThreadTurnVisibleAgentRunner,
|
|
75
|
+
writeMultiAgentEvent,
|
|
76
|
+
} from './thread-turn-execution'
|
|
77
|
+
import { createThreadTurnMessageContext, upsertChatHistoryMessage } from './thread-turn-message-context'
|
|
78
|
+
import { ThreadTurnStreamingError } from './thread-turn-streaming'
|
|
79
|
+
import type { StreamAgentResponseContext } from './thread-turn-streaming'
|
|
80
|
+
import { buildThreadTurnSpanAttributes, compactSpanAttributes } from './thread-turn-tracing'
|
|
81
|
+
import { ThreadServiceTag } from './thread.service'
|
|
82
|
+
import type { makeThreadService } from './thread.service'
|
|
83
|
+
import type { NormalizedThread } from './thread.types'
|
|
84
|
+
|
|
85
|
+
interface ThreadTurnPreparationServiceContext {
|
|
86
|
+
attachmentService: ReturnType<typeof makeAttachmentService>
|
|
87
|
+
chatRunRegistry: Context.Service.Shape<typeof ChatRunRegistryTag>
|
|
88
|
+
compactionCoordination: Context.Service.Shape<typeof CompactionCoordinationTag>
|
|
89
|
+
executionPlanService: ReturnType<typeof makeExecutionPlanService>
|
|
90
|
+
learnedSkillService: ReturnType<typeof makeLearnedSkillService>
|
|
91
|
+
memoryService: ReturnType<typeof createMemoryService>
|
|
92
|
+
planRunService: ReturnType<typeof makePlanRunService>
|
|
93
|
+
threadMessageService: ReturnType<typeof makeThreadMessageService>
|
|
94
|
+
threadService: ReturnType<typeof makeThreadService>
|
|
95
|
+
contextCompactionRuntime: ReturnType<typeof createWiredContextCompactionRuntime>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type ThreadTurnPreparationRuntimeDeps = ThreadTurnPreparationServiceContext
|
|
99
|
+
|
|
100
|
+
const PRESEEDED_MEMORY_LOOKUP_LIMIT = 3
|
|
101
|
+
|
|
102
|
+
function waitForThreadCompactionIfNeeded(deps: ThreadTurnPreparationRuntimeDeps, threadId: RecordIdRef) {
|
|
103
|
+
return deps.compactionCoordination.waitIfNeeded({
|
|
104
|
+
entityId: recordIdToString(threadId, TABLES.THREAD),
|
|
105
|
+
entityLabel: 'Thread',
|
|
106
|
+
loadEntity: () => deps.threadService.getById(threadId),
|
|
107
|
+
isCompacting: (thread) => thread.isCompacting === true,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export class ThreadTurnPreparationError extends Schema.TaggedErrorClass<ThreadTurnPreparationError>()(
|
|
112
|
+
'ThreadTurnPreparationError',
|
|
113
|
+
{ message: Schema.String, cause: Schema.optional(Schema.Defect) },
|
|
114
|
+
) {}
|
|
115
|
+
|
|
116
|
+
const effectTryPromise = makeEffectTryPromiseWithMessage(
|
|
117
|
+
(message, cause) => new ThreadTurnPreparationError({ message, cause }),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
export interface ThreadTurnParams {
|
|
121
|
+
thread: NormalizedThread
|
|
122
|
+
threadRef: RecordIdRef
|
|
123
|
+
orgRef: RecordIdRef
|
|
124
|
+
userRef: RecordIdRef
|
|
125
|
+
userName?: string | null
|
|
126
|
+
agentIdOverride?: string
|
|
127
|
+
inputMessage: ChatMessage
|
|
128
|
+
skipInputMessagePersistence?: boolean
|
|
129
|
+
abortSignal?: AbortSignal
|
|
130
|
+
streamId?: string
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ThreadApprovalContinuationParams {
|
|
134
|
+
thread: NormalizedThread
|
|
135
|
+
threadRef: RecordIdRef
|
|
136
|
+
orgRef: RecordIdRef
|
|
137
|
+
userRef: RecordIdRef
|
|
138
|
+
userName?: string | null
|
|
139
|
+
approvalMessages: ChatMessage[]
|
|
140
|
+
abortSignal?: AbortSignal
|
|
141
|
+
streamId?: string
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ThreadPlanTurnParams {
|
|
145
|
+
thread: NormalizedThread
|
|
146
|
+
threadRef: RecordIdRef
|
|
147
|
+
orgRef: RecordIdRef
|
|
148
|
+
userRef: RecordIdRef
|
|
149
|
+
userName?: string | null
|
|
150
|
+
planTurn: ThreadPlanTurnContext
|
|
151
|
+
abortSignal?: AbortSignal
|
|
152
|
+
streamId?: string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
type ThreadRunCoreParams = {
|
|
156
|
+
thread: NormalizedThread
|
|
157
|
+
threadRef: RecordIdRef
|
|
158
|
+
orgRef: RecordIdRef
|
|
159
|
+
userRef: RecordIdRef
|
|
160
|
+
userName?: string | null
|
|
161
|
+
agentIdOverride?: string
|
|
162
|
+
abortSignal?: AbortSignal
|
|
163
|
+
streamId?: string
|
|
164
|
+
} & (
|
|
165
|
+
| { kind: 'userTurn'; inputMessage: ChatMessage; skipInputMessagePersistence?: boolean }
|
|
166
|
+
| { kind: 'approvalContinuation'; approvalMessages: ChatMessage[] }
|
|
167
|
+
| { kind: 'nativeToolApprovalTurn'; approvalMessages: ChatMessage[] }
|
|
168
|
+
| { kind: 'planTurn'; planTurn: ThreadPlanTurnContext }
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
export interface PreparedThreadTurnResult {
|
|
172
|
+
inputMessageId?: string
|
|
173
|
+
assistantMessages: ChatMessage[]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
type RecentHistoryLoadError = ServiceError | ThreadTurnPreparationError
|
|
177
|
+
|
|
178
|
+
function hydrateThreadMessageFileUrls(
|
|
179
|
+
attachmentService: ReturnType<typeof makeAttachmentService>,
|
|
180
|
+
orgRef: RecordIdRef,
|
|
181
|
+
userRef: RecordIdRef,
|
|
182
|
+
message: ChatMessage,
|
|
183
|
+
): ChatMessage {
|
|
184
|
+
return {
|
|
185
|
+
...message,
|
|
186
|
+
parts: attachmentService.hydrateSignedFileUrlsInMessageParts({
|
|
187
|
+
parts: message.parts,
|
|
188
|
+
orgId: orgRef,
|
|
189
|
+
userId: userRef,
|
|
190
|
+
}),
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function validateAndHydrateThreadMessagesEffect(params: {
|
|
195
|
+
attachmentService: ReturnType<typeof makeAttachmentService>
|
|
196
|
+
orgRef: RecordIdRef
|
|
197
|
+
userRef: RecordIdRef
|
|
198
|
+
messages: ChatMessage[]
|
|
199
|
+
errorMessage: string
|
|
200
|
+
}): Effect.Effect<ChatMessage[], ThreadTurnPreparationError> {
|
|
201
|
+
const { attachmentService, orgRef, userRef, messages, errorMessage } = params
|
|
202
|
+
|
|
203
|
+
return effectTryPromise(
|
|
204
|
+
() =>
|
|
205
|
+
validateUIMessages<ChatMessage>({
|
|
206
|
+
messages,
|
|
207
|
+
metadataSchema: messageMetadataSchema,
|
|
208
|
+
dataSchemas: dataPartsSchemas,
|
|
209
|
+
}),
|
|
210
|
+
errorMessage,
|
|
211
|
+
).pipe(
|
|
212
|
+
Effect.map((validated) =>
|
|
213
|
+
validated.map((message) => hydrateThreadMessageFileUrls(attachmentService, orgRef, userRef, message)),
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function validateUserTurnInputMessageEffect(params: {
|
|
219
|
+
thread: NormalizedThread
|
|
220
|
+
inputMessage: ChatMessage
|
|
221
|
+
attachmentService: ReturnType<typeof makeAttachmentService>
|
|
222
|
+
orgRef: RecordIdRef
|
|
223
|
+
userRef: RecordIdRef
|
|
224
|
+
}): Effect.Effect<ChatMessage, ThreadTurnError> {
|
|
225
|
+
const { thread, inputMessage, attachmentService, orgRef, userRef } = params
|
|
226
|
+
|
|
227
|
+
return Effect.gen(function* () {
|
|
228
|
+
const createdAt = yield* Clock.currentTimeMillis
|
|
229
|
+
const hydratedInputMessage = hydrateThreadMessageFileUrls(
|
|
230
|
+
attachmentService,
|
|
231
|
+
orgRef,
|
|
232
|
+
userRef,
|
|
233
|
+
withMessageCreatedAt(inputMessage, createdAt),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if (hydratedInputMessage.role !== 'user') {
|
|
237
|
+
return yield* new ThreadTurnError({
|
|
238
|
+
message: 'Only user messages can be submitted to the thread runtime.',
|
|
239
|
+
reason: 'bad-request',
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
if (!hasMessageContent(hydratedInputMessage.parts)) {
|
|
243
|
+
return yield* new ThreadTurnError({
|
|
244
|
+
message: 'Thread messages must include text or attachments.',
|
|
245
|
+
reason: 'bad-request',
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
if (thread.type === 'default' && !thread.agentId) {
|
|
249
|
+
return yield* new ThreadTurnError({
|
|
250
|
+
message: 'Default threads require an assigned agent.',
|
|
251
|
+
reason: 'bad-request',
|
|
252
|
+
})
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return hydratedInputMessage
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function createRecentHistoryLoaderEffect(params: {
|
|
260
|
+
runEffect: (effect: Effect.Effect<ChatMessage[], RecentHistoryLoadError>) => Promise<ChatMessage[]>
|
|
261
|
+
threadMessageService: ReturnType<typeof makeThreadMessageService>
|
|
262
|
+
threadRef: RecordIdRef
|
|
263
|
+
attachmentService: ReturnType<typeof makeAttachmentService>
|
|
264
|
+
orgRef: RecordIdRef
|
|
265
|
+
userRef: RecordIdRef
|
|
266
|
+
}) {
|
|
267
|
+
const { runEffect, threadMessageService, threadRef, attachmentService, orgRef, userRef } = params
|
|
268
|
+
|
|
269
|
+
return Effect.gen(function* () {
|
|
270
|
+
const cachedRecentHistory = yield* Effect.cached(
|
|
271
|
+
Effect.gen(function* () {
|
|
272
|
+
const persistedRecentHistory = yield* threadMessageService.listRecentMessagesEffect(threadRef, 64)
|
|
273
|
+
if (persistedRecentHistory.length === 0) {
|
|
274
|
+
return [] as ChatMessage[]
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return yield* validateAndHydrateThreadMessagesEffect({
|
|
278
|
+
attachmentService,
|
|
279
|
+
orgRef,
|
|
280
|
+
userRef,
|
|
281
|
+
messages: persistedRecentHistory,
|
|
282
|
+
errorMessage: 'Failed to validate recent thread history.',
|
|
283
|
+
})
|
|
284
|
+
}),
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return () => runEffect(cachedRecentHistory)
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function loadPersistedLiveHistoryEffect(params: {
|
|
292
|
+
threadMessageService: ReturnType<typeof makeThreadMessageService>
|
|
293
|
+
threadRef: RecordIdRef
|
|
294
|
+
persistedCompactionCursor?: string
|
|
295
|
+
attachmentService: ReturnType<typeof makeAttachmentService>
|
|
296
|
+
orgRef: RecordIdRef
|
|
297
|
+
userRef: RecordIdRef
|
|
298
|
+
}): Effect.Effect<ChatMessage[], ThreadTurnPreparationError> {
|
|
299
|
+
const { threadMessageService, threadRef, persistedCompactionCursor, attachmentService, orgRef, userRef } = params
|
|
300
|
+
|
|
301
|
+
return Effect.gen(function* () {
|
|
302
|
+
const persistedLiveHistory = yield* threadMessageService
|
|
303
|
+
.listMessagesAfterCursorEffect(threadRef, persistedCompactionCursor)
|
|
304
|
+
.pipe(
|
|
305
|
+
Effect.withSpan('ThreadTurnPreparation.loadPersistedHistory'),
|
|
306
|
+
Effect.mapError(
|
|
307
|
+
(error) => new ThreadTurnPreparationError({ message: 'Failed to load thread history.', cause: error }),
|
|
308
|
+
),
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if (persistedLiveHistory.length === 0) {
|
|
312
|
+
return [] as ChatMessage[]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return yield* validateAndHydrateThreadMessagesEffect({
|
|
316
|
+
attachmentService,
|
|
317
|
+
orgRef,
|
|
318
|
+
userRef,
|
|
319
|
+
messages: persistedLiveHistory,
|
|
320
|
+
errorMessage: 'Failed to validate thread history.',
|
|
321
|
+
}).pipe(Effect.withSpan('ThreadTurnPreparation.validateAndHydrateHistory'))
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function deriveTurnMessageInputs(params: {
|
|
326
|
+
params: ThreadRunCoreParams
|
|
327
|
+
inputMessage?: ChatMessage
|
|
328
|
+
liveHistory: ChatMessage[]
|
|
329
|
+
shouldPersistInputMessage: boolean
|
|
330
|
+
}): {
|
|
331
|
+
userMessage?: ChatMessage
|
|
332
|
+
originalMessages: ChatMessage[]
|
|
333
|
+
referenceUserMessage?: ChatMessage
|
|
334
|
+
messageText: string
|
|
335
|
+
} {
|
|
336
|
+
const { params: turnParams, inputMessage, liveHistory } = params
|
|
337
|
+
|
|
338
|
+
const userMessage: ChatMessage | undefined = inputMessage
|
|
339
|
+
? {
|
|
340
|
+
...inputMessage,
|
|
341
|
+
id: inputMessage.id,
|
|
342
|
+
role: 'user',
|
|
343
|
+
parts: inputMessage.parts,
|
|
344
|
+
metadata: { ...inputMessage.metadata, createdAt: toTimestamp(inputMessage.metadata?.createdAt) },
|
|
345
|
+
}
|
|
346
|
+
: undefined
|
|
347
|
+
|
|
348
|
+
const originalMessages = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
|
|
349
|
+
const referenceUserMessage =
|
|
350
|
+
turnParams.kind === 'planTurn'
|
|
351
|
+
? undefined
|
|
352
|
+
: (userMessage ?? [...liveHistory].reverse().find((message) => message.role === 'user'))
|
|
353
|
+
|
|
354
|
+
const messageText =
|
|
355
|
+
turnParams.kind === 'planTurn'
|
|
356
|
+
? `${turnParams.planTurn.nodeSpec.label}\n${turnParams.planTurn.nodeSpec.objective}\n${turnParams.planTurn.nodeSpec.instructions}`
|
|
357
|
+
: referenceUserMessage
|
|
358
|
+
? extractMessageText(referenceUserMessage).trim()
|
|
359
|
+
: ''
|
|
360
|
+
|
|
361
|
+
return { userMessage, originalMessages, referenceUserMessage, messageText }
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const prepareThreadRunCoreEffect = Effect.fn('ThreadTurnPreparation.prepareThreadRunCore')(function* (
|
|
365
|
+
deps: ThreadTurnPreparationRuntimeDeps,
|
|
366
|
+
params: ThreadRunCoreParams,
|
|
367
|
+
) {
|
|
368
|
+
const runtimeAdapters = getRuntimeAdapters()
|
|
369
|
+
const turnHooks = getTurnHooks()
|
|
370
|
+
const workspaceProvider = runtimeAdapters.workspaceProvider
|
|
371
|
+
const workspacePromise =
|
|
372
|
+
params.kind !== 'approvalContinuation' && workspaceProvider
|
|
373
|
+
? workspaceProvider.getWorkspace(params.orgRef)
|
|
374
|
+
: Promise.resolve<Record<string, unknown>>({})
|
|
375
|
+
const attachmentService = deps.attachmentService
|
|
376
|
+
const executionPlanService = deps.executionPlanService
|
|
377
|
+
const learnedSkillService = deps.learnedSkillService
|
|
378
|
+
const memoryService = deps.memoryService
|
|
379
|
+
const planRunService = deps.planRunService
|
|
380
|
+
const threadMessageService = deps.threadMessageService
|
|
381
|
+
const threadService = deps.threadService
|
|
382
|
+
const contextCompactionRuntime = deps.contextCompactionRuntime
|
|
383
|
+
const chatRunRegistry = deps.chatRunRegistry
|
|
384
|
+
const { thread, threadRef, orgRef, userRef, userName } = params
|
|
385
|
+
const orgIdString = recordIdToString(orgRef, TABLES.ORGANIZATION)
|
|
386
|
+
const userIdString = recordIdToString(userRef, TABLES.USER)
|
|
387
|
+
const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
|
|
388
|
+
|
|
389
|
+
const shouldPersistInputMessage = params.kind === 'userTurn' ? params.skipInputMessagePersistence !== true : false
|
|
390
|
+
const shouldProcessPostRunSideEffects =
|
|
391
|
+
params.kind !== 'planTurn' &&
|
|
392
|
+
(params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn' || shouldPersistInputMessage)
|
|
393
|
+
const inputMessage =
|
|
394
|
+
params.kind === 'userTurn'
|
|
395
|
+
? yield* validateUserTurnInputMessageEffect({
|
|
396
|
+
thread,
|
|
397
|
+
inputMessage: params.inputMessage,
|
|
398
|
+
attachmentService,
|
|
399
|
+
orgRef,
|
|
400
|
+
userRef,
|
|
401
|
+
})
|
|
402
|
+
: undefined
|
|
403
|
+
const currentContext = yield* Effect.context()
|
|
404
|
+
const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
|
|
405
|
+
|
|
406
|
+
// Start workspace fetch early unless approval handling will short-circuit the turn.
|
|
407
|
+
const threadRecord = yield* waitForThreadCompactionIfNeeded(deps, threadRef).pipe(
|
|
408
|
+
Effect.mapError(
|
|
409
|
+
(error) => new ThreadTurnPreparationError({ message: 'Failed to wait for thread compaction.', cause: error }),
|
|
410
|
+
),
|
|
411
|
+
Effect.withSpan('ThreadTurnPreparation.waitForCompactionGate'),
|
|
412
|
+
)
|
|
413
|
+
// Plan turns run without the chat lease — they must not block or be blocked by user messages.
|
|
414
|
+
if (params.kind !== 'planTurn') {
|
|
415
|
+
const persistedActiveRunId = toOptionalTrimmedString(threadRecord.activeRunId)
|
|
416
|
+
if (persistedActiveRunId && !(yield* threadService.hasActiveRunLease(threadRef))) {
|
|
417
|
+
const clearedStaleRun = yield* threadService.clearStaleActiveRunIfMissingFromRegistry(threadRef)
|
|
418
|
+
if (!clearedStaleRun) {
|
|
419
|
+
return yield* new ThreadTurnError({ message: 'A chat run is already active.', reason: 'conflict' })
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn') {
|
|
425
|
+
const approvedAssistantMessage = [...params.approvalMessages]
|
|
426
|
+
.reverse()
|
|
427
|
+
.find((m) => m.role === 'assistant' && hasApprovalRespondedParts(m))
|
|
428
|
+
if (!approvedAssistantMessage) {
|
|
429
|
+
return yield* new ThreadTurnError({ message: 'No approval-responded message found.', reason: 'bad-request' })
|
|
430
|
+
}
|
|
431
|
+
yield* threadMessageService
|
|
432
|
+
.upsertMessagesEffect({ threadId: threadRef, messages: [approvedAssistantMessage] })
|
|
433
|
+
.pipe(Effect.withSpan('ThreadTurnPreparation.persistApprovalMessage'))
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const persistedCompactionCursor = toOptionalTrimmedString(threadRecord.lastCompactedMessageId) ?? undefined
|
|
437
|
+
const loadRecentHistory = yield* createRecentHistoryLoaderEffect({
|
|
438
|
+
runEffect: (effect) => runPromiseWithCurrentContext(effect),
|
|
439
|
+
threadMessageService,
|
|
440
|
+
threadRef,
|
|
441
|
+
attachmentService,
|
|
442
|
+
orgRef,
|
|
443
|
+
userRef,
|
|
444
|
+
})
|
|
445
|
+
const liveHistory = yield* loadPersistedLiveHistoryEffect({
|
|
446
|
+
threadMessageService,
|
|
447
|
+
threadRef,
|
|
448
|
+
persistedCompactionCursor,
|
|
449
|
+
attachmentService,
|
|
450
|
+
orgRef,
|
|
451
|
+
userRef,
|
|
452
|
+
})
|
|
453
|
+
const { userMessage, originalMessages, referenceUserMessage, messageText } = deriveTurnMessageInputs({
|
|
454
|
+
params,
|
|
455
|
+
inputMessage,
|
|
456
|
+
liveHistory,
|
|
457
|
+
shouldPersistInputMessage,
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
if (userMessage && shouldPersistInputMessage) {
|
|
461
|
+
yield* threadMessageService
|
|
462
|
+
.upsertMessagesEffect({ threadId: threadRef, messages: [userMessage] })
|
|
463
|
+
.pipe(Effect.withSpan('ThreadTurnPreparation.persistUserMessage'))
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
let allAssistantMessages: ChatMessage[] = []
|
|
467
|
+
|
|
468
|
+
const respondedBy = recordIdToString(userRef, TABLES.USER)
|
|
469
|
+
if (params.kind === 'approvalContinuation') {
|
|
470
|
+
yield* executionPlanService
|
|
471
|
+
.applyApprovalResponseFromMessages({
|
|
472
|
+
threadId: threadRef,
|
|
473
|
+
approvalMessages: params.approvalMessages,
|
|
474
|
+
respondedBy,
|
|
475
|
+
})
|
|
476
|
+
.pipe(Effect.withSpan('ThreadTurnPreparation.applyApprovalContinuation'))
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
originalMessages,
|
|
480
|
+
run: () => Effect.succeed({ inputMessageId: referenceUserMessage?.id, assistantMessages: [] }),
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (
|
|
485
|
+
params.kind === 'userTurn' &&
|
|
486
|
+
thread.type === 'group' &&
|
|
487
|
+
threadRecord.nameGenerated !== true &&
|
|
488
|
+
threadRecord.title === THREAD.DEFAULT_TITLE &&
|
|
489
|
+
messageText.length > 0
|
|
490
|
+
) {
|
|
491
|
+
void safeEnqueue(() => enqueueThreadTitleGeneration({ threadId: threadIdString, sourceText: messageText }), {
|
|
492
|
+
operationName: 'thread-title-generation',
|
|
493
|
+
})
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (thread.type === 'thread' && !thread.threadType) {
|
|
497
|
+
return yield* new ThreadTurnError({ message: 'Core threads require a thread type.', reason: 'bad-request' })
|
|
498
|
+
}
|
|
499
|
+
const coreThreadProfile: CoreThreadProfile | null =
|
|
500
|
+
thread.type === 'thread' && thread.threadType ? getCoreThreadProfile(thread.threadType) : null
|
|
501
|
+
const defaultLeadAgentId = getLeadAgentId()
|
|
502
|
+
const visibleThreadAgentId =
|
|
503
|
+
params.agentIdOverride ??
|
|
504
|
+
(thread.type === 'default' ? thread.agentId : (coreThreadProfile?.config.agentId ?? defaultLeadAgentId))
|
|
505
|
+
const coreInstructionSections = coreThreadProfile ? [coreThreadProfile.instructions] : undefined
|
|
506
|
+
const assembledContext = yield* effectTryPromise(
|
|
507
|
+
() =>
|
|
508
|
+
assembleThreadTurnContext({
|
|
509
|
+
thread,
|
|
510
|
+
threadRef,
|
|
511
|
+
orgRef,
|
|
512
|
+
userRef,
|
|
513
|
+
userName,
|
|
514
|
+
orgIdString,
|
|
515
|
+
userIdString,
|
|
516
|
+
messageText,
|
|
517
|
+
workspacePromise,
|
|
518
|
+
workspaceProvider,
|
|
519
|
+
turnHooks,
|
|
520
|
+
}),
|
|
521
|
+
'Failed to assemble thread turn context.',
|
|
522
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.assembleThreadTurnContext'))
|
|
523
|
+
const {
|
|
524
|
+
workspace,
|
|
525
|
+
onboardingActive,
|
|
526
|
+
linearInstalled,
|
|
527
|
+
githubInstalled,
|
|
528
|
+
indexedRepoContext,
|
|
529
|
+
promptContext,
|
|
530
|
+
retrievedKnowledgeSection,
|
|
531
|
+
buildContextResult,
|
|
532
|
+
hookInstructionSections,
|
|
533
|
+
} = assembledContext
|
|
534
|
+
|
|
535
|
+
let memoryBlock = threadService.formatMemoryBlockForPrompt(threadRecord)
|
|
536
|
+
const executionPlanInstructionSectionCache = createExecutionPlanInstructionSectionCache({
|
|
537
|
+
disabled: false,
|
|
538
|
+
loadPlansEffect: () =>
|
|
539
|
+
Effect.gen(function* () {
|
|
540
|
+
const runs = yield* planRunService.getActiveRunRecords(threadRef)
|
|
541
|
+
return yield* Effect.forEach(runs, (run) => planRunService.toSerializablePlan(run, { slim: true }))
|
|
542
|
+
}).pipe(Effect.mapError(toExecutionPlanCacheError)),
|
|
543
|
+
})
|
|
544
|
+
const getExecutionPlanInstructionSections = (): Effect.Effect<string[] | undefined, ThreadTurnStreamingError> =>
|
|
545
|
+
executionPlanInstructionSectionCache
|
|
546
|
+
.getSectionsEffect()
|
|
547
|
+
.pipe(
|
|
548
|
+
Effect.mapError(
|
|
549
|
+
(error) =>
|
|
550
|
+
new ThreadTurnStreamingError({ message: 'Failed to load execution plan instructions.', cause: error }),
|
|
551
|
+
),
|
|
552
|
+
)
|
|
553
|
+
const invalidateExecutionPlanInstructionSections = () => {
|
|
554
|
+
executionPlanInstructionSectionCache.invalidate()
|
|
555
|
+
}
|
|
556
|
+
if (userMessage) {
|
|
557
|
+
const appliedHumanInput = yield* executionPlanService
|
|
558
|
+
.applyHumanInputFromUserMessage({ threadId: threadRef, message: userMessage, respondedBy })
|
|
559
|
+
.pipe(Effect.withSpan('ThreadTurnPreparation.applyExecutionPlanHumanInput'))
|
|
560
|
+
if (appliedHumanInput) {
|
|
561
|
+
invalidateExecutionPlanInstructionSections()
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const preSeededMemoriesByAgent = new Map<string, string | undefined>()
|
|
566
|
+
const getPreSeededMemoriesSection = (
|
|
567
|
+
agentId: string,
|
|
568
|
+
): Effect.Effect<string | undefined, ThreadTurnStreamingError> => {
|
|
569
|
+
if (preSeededMemoriesByAgent.has(agentId)) {
|
|
570
|
+
return Effect.succeed(preSeededMemoriesByAgent.get(agentId))
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return memoryService
|
|
574
|
+
.getTopMemories({ orgId: orgIdString, agentName: agentId, limit: PRESEEDED_MEMORY_LOOKUP_LIMIT })
|
|
575
|
+
.pipe(
|
|
576
|
+
Effect.tap((preSeededMemories) =>
|
|
577
|
+
Effect.sync(() => {
|
|
578
|
+
preSeededMemoriesByAgent.set(agentId, preSeededMemories)
|
|
579
|
+
}),
|
|
580
|
+
),
|
|
581
|
+
Effect.mapError(
|
|
582
|
+
(error) =>
|
|
583
|
+
new ThreadTurnStreamingError({
|
|
584
|
+
message: `Failed to load pre-seeded memories for ${agentId}.`,
|
|
585
|
+
cause: error,
|
|
586
|
+
}),
|
|
587
|
+
),
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const learnedSkillsByAgent = new Map<string, string | undefined>()
|
|
592
|
+
const getLearnedSkillsSection = (
|
|
593
|
+
agentId: string,
|
|
594
|
+
queryText = messageText,
|
|
595
|
+
): Effect.Effect<string | undefined, ThreadTurnStreamingError> => {
|
|
596
|
+
const cacheKey = `${agentId}::${queryText}`
|
|
597
|
+
if (learnedSkillsByAgent.has(cacheKey)) {
|
|
598
|
+
return Effect.succeed(learnedSkillsByAgent.get(cacheKey))
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return Effect.gen(function* () {
|
|
602
|
+
const section = yield* learnedSkillService
|
|
603
|
+
.retrieveForTurn({ orgId: orgIdString, agentId, query: queryText, limit: 3, minConfidence: 0.6 })
|
|
604
|
+
.pipe(
|
|
605
|
+
Effect.catch((error: unknown) =>
|
|
606
|
+
Effect.sync(() => {
|
|
607
|
+
aiLogger.warn`Failed to retrieve learned skills for ${agentId}: ${error}`
|
|
608
|
+
return undefined
|
|
609
|
+
}),
|
|
610
|
+
),
|
|
611
|
+
)
|
|
612
|
+
learnedSkillsByAgent.set(cacheKey, section)
|
|
613
|
+
return section
|
|
614
|
+
}).pipe(
|
|
615
|
+
Effect.mapError(
|
|
616
|
+
(error) =>
|
|
617
|
+
new ThreadTurnStreamingError({ message: `Failed to load learned skills for ${agentId}.`, cause: error }),
|
|
618
|
+
),
|
|
619
|
+
)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const persistedCompactionSummary =
|
|
623
|
+
persistedCompactionCursor && typeof threadRecord.compactionSummary === 'string'
|
|
624
|
+
? threadRecord.compactionSummary
|
|
625
|
+
: ''
|
|
626
|
+
const messagesForContext = userMessage ? upsertChatHistoryMessage(liveHistory, userMessage) : liveHistory
|
|
627
|
+
const threadTurnMessageContext = createThreadTurnMessageContext({
|
|
628
|
+
contextCompactionRuntime,
|
|
629
|
+
persistedCompactionSummary,
|
|
630
|
+
messagesForContext,
|
|
631
|
+
orgRef: recordIdToString(orgRef, TABLES.ORGANIZATION),
|
|
632
|
+
userRef: recordIdToString(userRef, TABLES.USER),
|
|
633
|
+
latestUserMessageId: referenceUserMessage?.id ?? '',
|
|
634
|
+
listReadableUploadsFromMessages: (uploadParams) =>
|
|
635
|
+
attachmentService.listReadableUploadsFromMessages({
|
|
636
|
+
messages: uploadParams.messages.map((message) => ({ parts: message.parts })),
|
|
637
|
+
orgId: uploadParams.orgId,
|
|
638
|
+
userId: uploadParams.userId,
|
|
639
|
+
}),
|
|
640
|
+
})
|
|
641
|
+
const buildTurnToolParams = (toolParams: {
|
|
642
|
+
agentId: string
|
|
643
|
+
mode: 'direct' | 'fixedThreadMode' | 'threadMode'
|
|
644
|
+
memoryBlock: string
|
|
645
|
+
onAppendMemoryBlock: (value: string) => void
|
|
646
|
+
extraMessages?: ChatMessage[]
|
|
647
|
+
skills?: string[]
|
|
648
|
+
includeExecutionPlanTools: boolean
|
|
649
|
+
}) => ({
|
|
650
|
+
agentId: toolParams.agentId,
|
|
651
|
+
orgId: orgRef,
|
|
652
|
+
userId: userRef,
|
|
653
|
+
userName: userName ?? 'there',
|
|
654
|
+
threadId: threadRef,
|
|
655
|
+
orgIdString,
|
|
656
|
+
threadType: thread.type,
|
|
657
|
+
mode: toolParams.mode,
|
|
658
|
+
linearInstalled,
|
|
659
|
+
onboardingActive,
|
|
660
|
+
githubInstalled,
|
|
661
|
+
provideRepoTool: indexedRepoContext.provideRepoTool,
|
|
662
|
+
skills: toolParams.skills,
|
|
663
|
+
defaultRepoSections: indexedRepoContext.defaultSectionsByAgent[toolParams.agentId],
|
|
664
|
+
memoryBlock: toolParams.memoryBlock,
|
|
665
|
+
onAppendMemoryBlock: toolParams.onAppendMemoryBlock,
|
|
666
|
+
availableUploads: threadTurnMessageContext.listReadableUploads(toolParams.extraMessages),
|
|
667
|
+
includeExecutionPlanTools: toolParams.includeExecutionPlanTools,
|
|
668
|
+
onExecutionPlanChanged: invalidateExecutionPlanInstructionSections,
|
|
669
|
+
context: buildContextResult,
|
|
670
|
+
})
|
|
671
|
+
const agentIdentityOverrides = readRuntimeAgentIdentityOverrides(buildContextResult)
|
|
672
|
+
|
|
673
|
+
const executeRun = (runParams: {
|
|
674
|
+
serverRunId: string
|
|
675
|
+
runAbort: ReturnType<typeof createServerRunAbortController>
|
|
676
|
+
writer?: UIMessageStreamWriter<ChatMessage>
|
|
677
|
+
}): Effect.Effect<PreparedThreadTurnResult | void, DatabaseError | ThreadTurnError | ThreadTurnPreparationError> =>
|
|
678
|
+
Effect.ensuring(
|
|
679
|
+
Effect.gen(function* () {
|
|
680
|
+
const currentRunAbort = runParams.runAbort
|
|
681
|
+
const toAbortReason = (): unknown =>
|
|
682
|
+
currentRunAbort.signal.reason ?? new DOMException('The operation was aborted.', 'AbortError')
|
|
683
|
+
const failIfRunAborted = (): Effect.Effect<void, ThreadTurnPreparationError> =>
|
|
684
|
+
currentRunAbort.signal.aborted
|
|
685
|
+
? Effect.fail(
|
|
686
|
+
new ThreadTurnPreparationError({ message: 'Thread turn was aborted.', cause: toAbortReason() }),
|
|
687
|
+
)
|
|
688
|
+
: Effect.void
|
|
689
|
+
yield* failIfRunAborted()
|
|
690
|
+
if (params.kind !== 'planTurn') {
|
|
691
|
+
yield* threadService
|
|
692
|
+
.setActiveTurn(threadRef, runParams.serverRunId, params.streamId ?? null)
|
|
693
|
+
.pipe(Effect.withSpan('ThreadTurnPreparation.setActiveTurn'))
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const agentFactoryConfig = getResolvedAgentFactoryConfig()
|
|
697
|
+
|
|
698
|
+
const streamCtx: StreamAgentResponseContext = {
|
|
699
|
+
turnHooks,
|
|
700
|
+
thread,
|
|
701
|
+
threadRef,
|
|
702
|
+
orgRef,
|
|
703
|
+
userRef,
|
|
704
|
+
userName,
|
|
705
|
+
onboardingActive,
|
|
706
|
+
linearInstalled,
|
|
707
|
+
githubInstalled,
|
|
708
|
+
buildContextResult,
|
|
709
|
+
getExecutionPlanInstructionSections,
|
|
710
|
+
getPreSeededMemoriesSection,
|
|
711
|
+
getLearnedSkillsSection,
|
|
712
|
+
promptContext,
|
|
713
|
+
retrievedKnowledgeSection,
|
|
714
|
+
memoryBlock,
|
|
715
|
+
hookInstructionSections,
|
|
716
|
+
runAbortSignal: currentRunAbort.signal,
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const { runVisibleAgent } = createThreadTurnVisibleAgentRunner({
|
|
720
|
+
agentFactoryConfig: { buildAgentTools: agentFactoryConfig.buildAgentTools },
|
|
721
|
+
buildTurnToolParams,
|
|
722
|
+
threadTurnMessageContext,
|
|
723
|
+
threadMessageService,
|
|
724
|
+
threadRef,
|
|
725
|
+
writer: runParams.writer,
|
|
726
|
+
streamCtx,
|
|
727
|
+
agentIdentityOverrides,
|
|
728
|
+
getMemoryBlock: () => memoryBlock,
|
|
729
|
+
setMemoryBlock: (value: string) => {
|
|
730
|
+
memoryBlock = value
|
|
731
|
+
},
|
|
732
|
+
onPersistedMessage: (persistedMessage: ChatMessage) => {
|
|
733
|
+
allAssistantMessages = upsertChatHistoryMessage(allAssistantMessages, persistedMessage)
|
|
734
|
+
},
|
|
735
|
+
failIfRunAborted,
|
|
736
|
+
})
|
|
737
|
+
|
|
738
|
+
if (params.kind === 'planTurn') {
|
|
739
|
+
const planTurn = params.planTurn
|
|
740
|
+
const submitPlanTurnNodeResultTool = createTool({
|
|
741
|
+
description: buildPlanTurnSubmitToolDescription(planTurn),
|
|
742
|
+
inputSchema: PlanNodeResultSubmissionSchema,
|
|
743
|
+
execute: (result) =>
|
|
744
|
+
executionPlanService.submitPlanTurnResult({
|
|
745
|
+
threadId: threadRef,
|
|
746
|
+
runId: planTurn.runId,
|
|
747
|
+
nodeId: planTurn.nodeId,
|
|
748
|
+
emittedBy: planTurn.nodeSpec.owner.ref,
|
|
749
|
+
input: result,
|
|
750
|
+
}),
|
|
751
|
+
})
|
|
752
|
+
|
|
753
|
+
yield* effectTryPromise(
|
|
754
|
+
() =>
|
|
755
|
+
runVisibleAgent({
|
|
756
|
+
agentId: planTurn.nodeSpec.owner.ref,
|
|
757
|
+
mode: thread.type === 'default' ? 'direct' : 'threadMode',
|
|
758
|
+
additionalInstructionSections: buildPlanTurnInstructionSections(planTurn),
|
|
759
|
+
extraMessages: [buildPlanTurnPromptMessage(planTurn)],
|
|
760
|
+
includeExecutionPlanTools: false,
|
|
761
|
+
extraTools: { [SUBMIT_PLAN_TURN_RESULT_TOOL_NAME]: submitPlanTurnNodeResultTool },
|
|
762
|
+
filterTools: (tools) => applyPlanTurnToolPolicy(tools, planTurn.nodeSpec),
|
|
763
|
+
metadataPatch: { trigger: 'plan-turn', planRunId: planTurn.runId, planNodeId: planTurn.nodeId },
|
|
764
|
+
}),
|
|
765
|
+
'Failed to execute plan-turn visible agent.',
|
|
766
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.executePlanTurn'))
|
|
767
|
+
} else if (thread.type === 'default') {
|
|
768
|
+
if (!thread.agentId) {
|
|
769
|
+
return yield* new ThreadTurnError({
|
|
770
|
+
message: 'Direct threads require an assigned agent.',
|
|
771
|
+
reason: 'bad-request',
|
|
772
|
+
})
|
|
773
|
+
}
|
|
774
|
+
const directAgentId = thread.agentId
|
|
775
|
+
yield* effectTryPromise(
|
|
776
|
+
() => runVisibleAgent({ agentId: directAgentId, mode: 'direct' }),
|
|
777
|
+
'Failed to execute direct thread agent.',
|
|
778
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.executeDirectThread'))
|
|
779
|
+
} else {
|
|
780
|
+
const wsMembers = (thread as { members?: string[] }).members ?? []
|
|
781
|
+
const members = wsMembers.length > 0 ? wsMembers : [...getAgentRoster()]
|
|
782
|
+
const fallbackAgentId = coreThreadProfile?.config.agentId ?? defaultLeadAgentId
|
|
783
|
+
yield* failIfRunAborted()
|
|
784
|
+
writeMultiAgentEvent(runParams.writer, { phase: 'routing', note: 'Routing this turn to the right agent.' })
|
|
785
|
+
|
|
786
|
+
const recentContext = threadTurnMessageContext.currentMessages
|
|
787
|
+
.slice(-6)
|
|
788
|
+
.map((m) => `${m.role}: ${extractMessageText(m).slice(0, 200)}`)
|
|
789
|
+
.join('\n')
|
|
790
|
+
|
|
791
|
+
const triageResult = yield* effectTryPromise(
|
|
792
|
+
() =>
|
|
793
|
+
triageThreadMessage({
|
|
794
|
+
threadTitle: thread.title,
|
|
795
|
+
members,
|
|
796
|
+
messageText,
|
|
797
|
+
recentContext,
|
|
798
|
+
displayNamesById: agentIdentityOverrides.displayNamesById,
|
|
799
|
+
shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
|
|
800
|
+
routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
|
|
801
|
+
}),
|
|
802
|
+
'Failed to triage group thread message.',
|
|
803
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.routeGroupThread'))
|
|
804
|
+
yield* failIfRunAborted()
|
|
805
|
+
|
|
806
|
+
const runGroupAgent = (agentId: string, options?: { routingContext?: string }) => {
|
|
807
|
+
const additionalSections = [...(coreInstructionSections ?? []), ...hookInstructionSections]
|
|
808
|
+
if (options?.routingContext) {
|
|
809
|
+
additionalSections.push(`<routing-context>\n${options.routingContext}\n</routing-context>`)
|
|
810
|
+
}
|
|
811
|
+
additionalSections.push(
|
|
812
|
+
'<multi-agent-protocol>\nYou are responding as part of a multi-agent thread. Focus on your domain expertise. Be direct and concise — another agent may follow up on different aspects.\n</multi-agent-protocol>',
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
return runVisibleAgent({
|
|
816
|
+
agentId,
|
|
817
|
+
mode: 'threadMode',
|
|
818
|
+
skills: coreThreadProfile?.skills ? [...coreThreadProfile.skills] : undefined,
|
|
819
|
+
additionalInstructionSections: additionalSections,
|
|
820
|
+
})
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (!triageResult) {
|
|
824
|
+
yield* effectTryPromise(
|
|
825
|
+
() => runGroupAgent(fallbackAgentId),
|
|
826
|
+
'Failed to execute fallback group agent.',
|
|
827
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.executeFallbackGroupAgent'))
|
|
828
|
+
yield* failIfRunAborted()
|
|
829
|
+
writeMultiAgentEvent(runParams.writer, { phase: 'complete' })
|
|
830
|
+
} else {
|
|
831
|
+
const respondedAgents: string[] = []
|
|
832
|
+
let lastResponse = yield* effectTryPromise(
|
|
833
|
+
() => runGroupAgent(triageResult.agentId, { routingContext: triageResult.routingContext }),
|
|
834
|
+
'Failed to execute routed group agent.',
|
|
835
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.executeRoutedGroupAgent'))
|
|
836
|
+
respondedAgents.push(triageResult.agentId)
|
|
837
|
+
yield* failIfRunAborted()
|
|
838
|
+
|
|
839
|
+
while (respondedAgents.length < 3) {
|
|
840
|
+
const lastResponseText = extractMessageText(lastResponse).slice(0, 500)
|
|
841
|
+
const checkResult = yield* effectTryPromise(
|
|
842
|
+
() =>
|
|
843
|
+
checkForNextAgent({
|
|
844
|
+
threadTitle: thread.title,
|
|
845
|
+
members,
|
|
846
|
+
messageText,
|
|
847
|
+
respondedAgents,
|
|
848
|
+
lastResponseSummary: lastResponseText,
|
|
849
|
+
displayNamesById: agentIdentityOverrides.displayNamesById,
|
|
850
|
+
shortDisplayNamesById: agentIdentityOverrides.shortDisplayNamesById,
|
|
851
|
+
routingAliasesByAgentId: agentIdentityOverrides.routingAliasesByAgentId,
|
|
852
|
+
}),
|
|
853
|
+
'Failed to select the next group agent.',
|
|
854
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.selectNextGroupAgent'))
|
|
855
|
+
yield* failIfRunAborted()
|
|
856
|
+
|
|
857
|
+
if (checkResult.done || !checkResult.agentId) break
|
|
858
|
+
|
|
859
|
+
writeMultiAgentEvent(runParams.writer, {
|
|
860
|
+
phase: 'waiting-for-agent',
|
|
861
|
+
agentId: checkResult.agentId,
|
|
862
|
+
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
|
|
863
|
+
note: checkResult.routingContext ?? undefined,
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
const bridgeMessage: ChatMessage = {
|
|
867
|
+
id: Bun.randomUUIDv7(),
|
|
868
|
+
role: 'user',
|
|
869
|
+
parts: [
|
|
870
|
+
{
|
|
871
|
+
type: 'text',
|
|
872
|
+
text: checkResult.routingContext ?? 'Please also provide your perspective on this topic.',
|
|
873
|
+
},
|
|
874
|
+
],
|
|
875
|
+
metadata: { hidden: true, createdAt: yield* Clock.currentTimeMillis } as MessageMetadata,
|
|
876
|
+
}
|
|
877
|
+
yield* failIfRunAborted()
|
|
878
|
+
yield* threadMessageService
|
|
879
|
+
.upsertMessagesEffect({ threadId: threadRef, messages: [bridgeMessage] })
|
|
880
|
+
.pipe(Effect.withSpan('ThreadTurnPreparation.persistBridgeMessage'))
|
|
881
|
+
threadTurnMessageContext.appendMessages([bridgeMessage])
|
|
882
|
+
yield* failIfRunAborted()
|
|
883
|
+
|
|
884
|
+
lastResponse = yield* effectTryPromise(
|
|
885
|
+
() => runGroupAgent(checkResult.agentId, { routingContext: checkResult.routingContext ?? undefined }),
|
|
886
|
+
'Failed to execute follow-up group agent.',
|
|
887
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.executeFollowUpGroupAgent'))
|
|
888
|
+
respondedAgents.push(checkResult.agentId)
|
|
889
|
+
yield* failIfRunAborted()
|
|
890
|
+
writeMultiAgentEvent(runParams.writer, {
|
|
891
|
+
phase: 'agent-message-persisted',
|
|
892
|
+
agentId: checkResult.agentId,
|
|
893
|
+
agentName: resolveRuntimeAgentDisplayName(agentIdentityOverrides, checkResult.agentId),
|
|
894
|
+
messageId: lastResponse.id,
|
|
895
|
+
})
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
yield* failIfRunAborted()
|
|
899
|
+
writeMultiAgentEvent(runParams.writer, { phase: 'complete' })
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}),
|
|
903
|
+
Effect.gen(function* () {
|
|
904
|
+
const latestThreadRecord = yield* threadService.getById(threadRef)
|
|
905
|
+
|
|
906
|
+
yield* finalizeTurnRunEffect({
|
|
907
|
+
serverRunId: runParams.serverRunId,
|
|
908
|
+
getEntity: () => Effect.succeed(latestThreadRecord),
|
|
909
|
+
getUncompactedMessages: (cursor) =>
|
|
910
|
+
threadMessageService
|
|
911
|
+
.listMessagesAfterCursorEffect(threadRef, cursor)
|
|
912
|
+
.pipe(
|
|
913
|
+
Effect.mapError((error) => new TurnLifecycleError({ message: getErrorMessage(error), cause: error })),
|
|
914
|
+
),
|
|
915
|
+
assessCompaction: (summaryText, messages) =>
|
|
916
|
+
contextCompactionRuntime.shouldCompactHistory({
|
|
917
|
+
summaryText,
|
|
918
|
+
liveMessages: messages,
|
|
919
|
+
contextSize: CONTEXT_WINDOW_TOKENS,
|
|
920
|
+
}),
|
|
921
|
+
enqueueCompaction: () =>
|
|
922
|
+
enqueueContextCompaction({
|
|
923
|
+
domain: 'thread',
|
|
924
|
+
entityId: threadIdString,
|
|
925
|
+
contextSize: CONTEXT_WINDOW_TOKENS,
|
|
926
|
+
}),
|
|
927
|
+
unregisterRun: () => undefined,
|
|
928
|
+
clearActiveRunId: (runId) =>
|
|
929
|
+
Effect.gen(function* () {
|
|
930
|
+
const activeStreamId = yield* threadService.getActiveStreamId(threadRef)
|
|
931
|
+
yield* threadService.clearActiveTurn(threadRef, { runId, streamId: activeStreamId })
|
|
932
|
+
}).pipe(
|
|
933
|
+
Effect.mapError((error) => new TurnLifecycleError({ message: getErrorMessage(error), cause: error })),
|
|
934
|
+
),
|
|
935
|
+
disposeAbort: () => runParams.runAbort.dispose(),
|
|
936
|
+
activeStreamId: params.streamId,
|
|
937
|
+
clearActiveStreamId: (streamId) =>
|
|
938
|
+
Effect.gen(function* () {
|
|
939
|
+
const activeRunId = yield* threadService.getActiveRunId(threadRef)
|
|
940
|
+
if (!activeRunId) {
|
|
941
|
+
return
|
|
942
|
+
}
|
|
943
|
+
yield* threadService.clearActiveTurn(threadRef, { runId: activeRunId, streamId })
|
|
944
|
+
}).pipe(
|
|
945
|
+
Effect.mapError((error) => new TurnLifecycleError({ message: getErrorMessage(error), cause: error })),
|
|
946
|
+
),
|
|
947
|
+
}).pipe(
|
|
948
|
+
Effect.withSpan('ThreadTurnPreparation.finalizeTurnRun'),
|
|
949
|
+
Effect.mapError(
|
|
950
|
+
(error) => new ThreadTurnPreparationError({ message: 'Failed to finalize thread turn run.', cause: error }),
|
|
951
|
+
),
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
if (allAssistantMessages.length > 0 && shouldProcessPostRunSideEffects) {
|
|
955
|
+
yield* effectTryPromise(
|
|
956
|
+
() =>
|
|
957
|
+
runPostTurnSideEffects({
|
|
958
|
+
thread,
|
|
959
|
+
threadRef,
|
|
960
|
+
orgRef,
|
|
961
|
+
userRef,
|
|
962
|
+
userName,
|
|
963
|
+
orgIdString,
|
|
964
|
+
threadIdString,
|
|
965
|
+
onboardingActive,
|
|
966
|
+
workspace,
|
|
967
|
+
allAssistantMessages,
|
|
968
|
+
referenceUserMessage,
|
|
969
|
+
referenceUserMessageId: referenceUserMessage?.id ?? '',
|
|
970
|
+
loadRecentHistory,
|
|
971
|
+
listReadableUploads: () => threadTurnMessageContext.listReadableUploads(),
|
|
972
|
+
memoryBlock,
|
|
973
|
+
visibleThreadAgentId,
|
|
974
|
+
defaultLeadAgentId,
|
|
975
|
+
latestThreadRecord,
|
|
976
|
+
isUserTurn: params.kind === 'userTurn',
|
|
977
|
+
agentDisplayNamesById: agentIdentityOverrides.displayNamesById,
|
|
978
|
+
}),
|
|
979
|
+
'Failed to run post-turn side effects.',
|
|
980
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.runPostTurnSideEffects'))
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (allAssistantMessages.length > 0 && params.kind !== 'planTurn') {
|
|
984
|
+
const afterTurn = turnHooks.afterTurn
|
|
985
|
+
if (afterTurn) {
|
|
986
|
+
yield* effectTryPromise(
|
|
987
|
+
() =>
|
|
988
|
+
afterTurn({
|
|
989
|
+
thread,
|
|
990
|
+
threadRef,
|
|
991
|
+
orgRef,
|
|
992
|
+
userRef,
|
|
993
|
+
userName,
|
|
994
|
+
onboardingActive,
|
|
995
|
+
referenceUserMessage,
|
|
996
|
+
assistantMessages: allAssistantMessages,
|
|
997
|
+
latestThreadRecord,
|
|
998
|
+
context: buildContextResult,
|
|
999
|
+
}),
|
|
1000
|
+
'Failed to run afterTurn hook.',
|
|
1001
|
+
).pipe(Effect.withSpan('ThreadTurnPreparation.afterTurnHook'))
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}).pipe(
|
|
1005
|
+
Effect.catch((postRunError) =>
|
|
1006
|
+
Effect.sync(() => {
|
|
1007
|
+
aiLogger.error`Thread post-run cleanup failed: ${postRunError}`
|
|
1008
|
+
}),
|
|
1009
|
+
),
|
|
1010
|
+
),
|
|
1011
|
+
).pipe(
|
|
1012
|
+
Effect.mapError((error) =>
|
|
1013
|
+
error._tag === 'ServiceError'
|
|
1014
|
+
? new ThreadTurnPreparationError({ message: error.message, cause: error.cause })
|
|
1015
|
+
: error,
|
|
1016
|
+
),
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
const toPreparedThreadTurnResult = (): PreparedThreadTurnResult => ({
|
|
1020
|
+
inputMessageId: referenceUserMessage?.id,
|
|
1021
|
+
assistantMessages: [...allAssistantMessages],
|
|
1022
|
+
})
|
|
1023
|
+
|
|
1024
|
+
const run = (writer?: UIMessageStreamWriter<ChatMessage>) => {
|
|
1025
|
+
const serverRunId = Bun.randomUUIDv7()
|
|
1026
|
+
|
|
1027
|
+
const runEffect =
|
|
1028
|
+
params.kind === 'planTurn'
|
|
1029
|
+
? Effect.gen(function* () {
|
|
1030
|
+
const runAbort = createServerRunAbortController(
|
|
1031
|
+
[params.abortSignal].filter((signal): signal is AbortSignal => Boolean(signal)),
|
|
1032
|
+
)
|
|
1033
|
+
yield* Effect.annotateCurrentSpan('serverRunId', serverRunId)
|
|
1034
|
+
const runResult = yield* executeRun({ serverRunId, runAbort, writer })
|
|
1035
|
+
return runResult ?? toPreparedThreadTurnResult()
|
|
1036
|
+
})
|
|
1037
|
+
: threadService.withActiveRunLease(threadRef, (leaseAbortSignal) =>
|
|
1038
|
+
Effect.gen(function* () {
|
|
1039
|
+
const runAbort = createServerRunAbortController(
|
|
1040
|
+
[params.abortSignal, leaseAbortSignal].filter((signal): signal is AbortSignal => Boolean(signal)),
|
|
1041
|
+
)
|
|
1042
|
+
yield* Effect.annotateCurrentSpan('serverRunId', serverRunId)
|
|
1043
|
+
const runResult = yield* chatRunRegistry.trackRunEffect(
|
|
1044
|
+
serverRunId,
|
|
1045
|
+
runAbort.controller,
|
|
1046
|
+
executeRun({ serverRunId, runAbort, writer }),
|
|
1047
|
+
)
|
|
1048
|
+
return runResult ?? toPreparedThreadTurnResult()
|
|
1049
|
+
}),
|
|
1050
|
+
)
|
|
1051
|
+
|
|
1052
|
+
return runEffect.pipe(Effect.withSpan('ThreadTurnPreparation.executeRun'), Effect.provide(currentContext))
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
return { originalMessages, run }
|
|
1056
|
+
})
|
|
1057
|
+
|
|
1058
|
+
interface ThreadTurnPreparationDeps {
|
|
1059
|
+
attachment: ReturnType<typeof makeAttachmentService>
|
|
1060
|
+
chatRunRegistry: Context.Service.Shape<typeof ChatRunRegistryTag>
|
|
1061
|
+
compactionCoordination: Context.Service.Shape<typeof CompactionCoordinationTag>
|
|
1062
|
+
executionPlan: ReturnType<typeof makeExecutionPlanService>
|
|
1063
|
+
learnedSkill: ReturnType<typeof makeLearnedSkillService>
|
|
1064
|
+
memory: ReturnType<typeof createMemoryService>
|
|
1065
|
+
planRun: ReturnType<typeof makePlanRunService>
|
|
1066
|
+
threadMessage: ReturnType<typeof makeThreadMessageService>
|
|
1067
|
+
thread: ReturnType<typeof makeThreadService>
|
|
1068
|
+
helperModelRuntime: HelperModelRuntime
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
export function makeThreadTurnPreparationService(deps: ThreadTurnPreparationDeps) {
|
|
1072
|
+
const runtimeDeps: ThreadTurnPreparationRuntimeDeps = {
|
|
1073
|
+
attachmentService: deps.attachment,
|
|
1074
|
+
chatRunRegistry: deps.chatRunRegistry,
|
|
1075
|
+
compactionCoordination: deps.compactionCoordination,
|
|
1076
|
+
executionPlanService: deps.executionPlan,
|
|
1077
|
+
learnedSkillService: deps.learnedSkill,
|
|
1078
|
+
memoryService: deps.memory,
|
|
1079
|
+
planRunService: deps.planRun,
|
|
1080
|
+
threadMessageService: deps.threadMessage,
|
|
1081
|
+
threadService: deps.thread,
|
|
1082
|
+
contextCompactionRuntime: createWiredContextCompactionRuntime({
|
|
1083
|
+
helperModelRuntime: deps.helperModelRuntime,
|
|
1084
|
+
now: nowEpochMillis,
|
|
1085
|
+
randomId: () => Bun.randomUUIDv7(),
|
|
1086
|
+
}),
|
|
1087
|
+
}
|
|
1088
|
+
const annotateTurnSpans = <A, E, R>(params: ThreadRunCoreParams, effect: Effect.Effect<A, E, R>) =>
|
|
1089
|
+
effect.pipe(
|
|
1090
|
+
Effect.annotateSpans(
|
|
1091
|
+
compactSpanAttributes({
|
|
1092
|
+
...buildThreadTurnSpanAttributes({
|
|
1093
|
+
threadRef: params.threadRef,
|
|
1094
|
+
orgRef: params.orgRef,
|
|
1095
|
+
userRef: params.userRef,
|
|
1096
|
+
kind: params.kind,
|
|
1097
|
+
streamId: params.streamId,
|
|
1098
|
+
agentId: params.agentIdOverride,
|
|
1099
|
+
threadType: params.thread.type,
|
|
1100
|
+
planRunId: params.kind === 'planTurn' ? params.planTurn.runId : undefined,
|
|
1101
|
+
planNodeId: params.kind === 'planTurn' ? params.planTurn.nodeId : undefined,
|
|
1102
|
+
}),
|
|
1103
|
+
userName: params.userName ?? undefined,
|
|
1104
|
+
}),
|
|
1105
|
+
),
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
return {
|
|
1109
|
+
prepareThreadRunCoreEffect(params: ThreadRunCoreParams) {
|
|
1110
|
+
return annotateTurnSpans(params, prepareThreadRunCoreEffect(runtimeDeps, params))
|
|
1111
|
+
},
|
|
1112
|
+
prepareThreadRunCore(params: ThreadRunCoreParams) {
|
|
1113
|
+
return annotateTurnSpans(params, prepareThreadRunCoreEffect(runtimeDeps, params))
|
|
1114
|
+
},
|
|
1115
|
+
} as const
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
export class ThreadTurnPreparationServiceTag extends Context.Service<
|
|
1119
|
+
ThreadTurnPreparationServiceTag,
|
|
1120
|
+
ReturnType<typeof makeThreadTurnPreparationService>
|
|
1121
|
+
>()('@lota-sdk/core/ThreadTurnPreparationService') {}
|
|
1122
|
+
|
|
1123
|
+
export const ThreadTurnPreparationServiceLive = Layer.effect(
|
|
1124
|
+
ThreadTurnPreparationServiceTag,
|
|
1125
|
+
Effect.gen(function* () {
|
|
1126
|
+
const attachment = yield* AttachmentServiceTag
|
|
1127
|
+
const chatRunRegistry = yield* ChatRunRegistryTag
|
|
1128
|
+
const compactionCoordination = yield* CompactionCoordinationTag
|
|
1129
|
+
const executionPlan = yield* ExecutionPlanServiceTag
|
|
1130
|
+
const learnedSkill = yield* LearnedSkillServiceTag
|
|
1131
|
+
const memory = yield* MemoryServiceTag
|
|
1132
|
+
const planRun = yield* PlanRunServiceTag
|
|
1133
|
+
const threadMessage = yield* ThreadMessageServiceTag
|
|
1134
|
+
const thread = yield* ThreadServiceTag
|
|
1135
|
+
return makeThreadTurnPreparationService({
|
|
1136
|
+
attachment,
|
|
1137
|
+
chatRunRegistry,
|
|
1138
|
+
compactionCoordination,
|
|
1139
|
+
executionPlan,
|
|
1140
|
+
learnedSkill,
|
|
1141
|
+
memory,
|
|
1142
|
+
planRun,
|
|
1143
|
+
threadMessage,
|
|
1144
|
+
thread,
|
|
1145
|
+
helperModelRuntime: yield* HelperModelTag,
|
|
1146
|
+
})
|
|
1147
|
+
}),
|
|
1148
|
+
)
|