@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,344 @@
|
|
|
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 { BackgroundWorkService } from '../background-work.service'
|
|
17
|
+
import { ChatRunRegistryTag } from '../chat-run-registry.service'
|
|
18
|
+
import { ContextCompactionServiceTag } from '../context-compaction.service'
|
|
19
|
+
import type { makeContextCompactionService } from '../context-compaction.service'
|
|
20
|
+
import { createThreadActiveRunHelpers } from './thread-active-run'
|
|
21
|
+
import { createThreadBootstrapHelpers } from './thread-bootstrap'
|
|
22
|
+
import { createThreadListingHelpers } from './thread-listing'
|
|
23
|
+
import { createThreadMemoryBlockHelpers, formatMemoryBlockForPrompt } from './thread-memory-block'
|
|
24
|
+
import { ThreadMessageServiceTag } from './thread-message.service'
|
|
25
|
+
import type { makeThreadMessageService } from './thread-message.service'
|
|
26
|
+
import { createThreadRecordStore } from './thread-record-store'
|
|
27
|
+
import { NormalizedThreadSchema, PublicThreadSchema } from './thread.types'
|
|
28
|
+
import type { NormalizedThread, PublicThread, ThreadRecord } from './thread.types'
|
|
29
|
+
|
|
30
|
+
export { ActiveThreadRunConflictError } from '../../effect/errors'
|
|
31
|
+
|
|
32
|
+
function assertMutableThreadEffect(thread: ThreadRecord): Effect.Effect<void, BadRequestError> {
|
|
33
|
+
return thread.type === 'default'
|
|
34
|
+
? Effect.fail(new BadRequestError({ message: 'Default threads cannot be modified.' }))
|
|
35
|
+
: Effect.void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getDefaultTitle(thread: Pick<ThreadRecord, 'type' | 'threadType'>): string {
|
|
39
|
+
if (thread.type === 'thread' && typeof thread.threadType === 'string') {
|
|
40
|
+
return getCoreThreadProfile(thread.threadType).config.title
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return THREAD.DEFAULT_TITLE
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeRecordIdStringEffect(id: unknown, table: string): Effect.Effect<string, BadRequestError> {
|
|
47
|
+
return isRecordIdInput(id)
|
|
48
|
+
? Effect.succeed(recordIdToString(ensureRecordId(id, table), table))
|
|
49
|
+
: Effect.fail(new BadRequestError({ message: `Invalid record id for table ${table}.` }))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function toPublicThread(thread: NormalizedThread): PublicThread {
|
|
53
|
+
const { organizationId: _organizationId, userId: _userId, memoryBlock: _memoryBlock, ...publicThread } = thread
|
|
54
|
+
return PublicThreadSchema.parse(publicThread)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type ThreadServiceError = ServiceError | BadRequestError
|
|
58
|
+
|
|
59
|
+
const effectTryPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
|
|
60
|
+
|
|
61
|
+
type ChatRunRegistry = Context.Service.Shape<typeof ChatRunRegistryTag>
|
|
62
|
+
|
|
63
|
+
type CompactionCoordination = Context.Service.Shape<typeof CompactionCoordinationTag>
|
|
64
|
+
|
|
65
|
+
interface ThreadServiceDeps {
|
|
66
|
+
db: SurrealDBService
|
|
67
|
+
redis: RedisConnectionManager
|
|
68
|
+
chatRunRegistry: ChatRunRegistry
|
|
69
|
+
threadMessageService: ReturnType<typeof makeThreadMessageService>
|
|
70
|
+
contextCompactionService: ReturnType<typeof makeContextCompactionService>
|
|
71
|
+
compactionCoordination: CompactionCoordination
|
|
72
|
+
background: Context.Service.Shape<typeof BackgroundWorkService>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function makeThreadService(deps: ThreadServiceDeps) {
|
|
76
|
+
const threadStore = createThreadRecordStore({ db: deps.db })
|
|
77
|
+
const activeRun = createThreadActiveRunHelpers({
|
|
78
|
+
db: deps.db,
|
|
79
|
+
threadStore,
|
|
80
|
+
redis: deps.redis,
|
|
81
|
+
chatRunRegistry: deps.chatRunRegistry,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
function computeIsRunning(
|
|
85
|
+
thread: Pick<ThreadRecord, 'id' | 'activeRunId'>,
|
|
86
|
+
options: { checkLease: boolean },
|
|
87
|
+
): Effect.Effect<boolean, ThreadServiceError> {
|
|
88
|
+
const activeRunId =
|
|
89
|
+
typeof thread.activeRunId === 'string' && thread.activeRunId.trim().length > 0 ? thread.activeRunId : null
|
|
90
|
+
|
|
91
|
+
if (activeRunId === null) {
|
|
92
|
+
return Effect.succeed(false)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (deps.chatRunRegistry.has(activeRunId)) {
|
|
96
|
+
return Effect.succeed(true)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!options.checkLease) {
|
|
100
|
+
return Effect.succeed(true)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return effectTryPromise(
|
|
104
|
+
() => activeRun.hasActiveRunLease(ensureRecordId(thread.id, TABLES.THREAD)),
|
|
105
|
+
'Failed to check active thread run lease.',
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function toNormalizedThread(
|
|
110
|
+
thread: ThreadRecord,
|
|
111
|
+
options: { checkLease?: boolean } = {},
|
|
112
|
+
): Effect.Effect<NormalizedThread, ThreadServiceError> {
|
|
113
|
+
return Effect.gen(function* () {
|
|
114
|
+
const isRunning = yield* computeIsRunning(thread, { checkLease: options.checkLease ?? true })
|
|
115
|
+
const [id, userId, organizationId] = yield* Effect.all([
|
|
116
|
+
normalizeRecordIdStringEffect(thread.id, TABLES.THREAD),
|
|
117
|
+
normalizeRecordIdStringEffect(thread.userId, TABLES.USER),
|
|
118
|
+
normalizeRecordIdStringEffect(thread.organizationId, TABLES.ORGANIZATION),
|
|
119
|
+
])
|
|
120
|
+
const candidate = {
|
|
121
|
+
id,
|
|
122
|
+
userId,
|
|
123
|
+
organizationId,
|
|
124
|
+
type: thread.type,
|
|
125
|
+
...(thread.type === 'thread' && typeof thread.threadType === 'string' ? { threadType: thread.threadType } : {}),
|
|
126
|
+
nameGenerated: thread.nameGenerated,
|
|
127
|
+
isRunning,
|
|
128
|
+
isCompacting: thread.isCompacting === true,
|
|
129
|
+
...(isAgentName(thread.agentId) ? { agentId: thread.agentId } : {}),
|
|
130
|
+
title: thread.title ?? getDefaultTitle(thread),
|
|
131
|
+
status: thread.status,
|
|
132
|
+
memoryBlock: formatMemoryBlockForPrompt(thread),
|
|
133
|
+
members: thread.members,
|
|
134
|
+
createdAt: toIsoDateTimeString(thread.createdAt),
|
|
135
|
+
updatedAt: toIsoDateTimeString(thread.updatedAt),
|
|
136
|
+
}
|
|
137
|
+
return yield* Effect.try({
|
|
138
|
+
try: () => NormalizedThreadSchema.parse(candidate),
|
|
139
|
+
catch: (cause) => new ServiceError({ message: 'Failed to parse normalized thread.', cause }),
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function toNormalizedThreads(
|
|
145
|
+
threads: ThreadRecord[],
|
|
146
|
+
options: { checkLease?: boolean } = {},
|
|
147
|
+
): Effect.Effect<NormalizedThread[], ThreadServiceError> {
|
|
148
|
+
return Effect.forEach(threads, (thread) => toNormalizedThread(thread, options))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const bootstrap = createThreadBootstrapHelpers({
|
|
152
|
+
threadStore,
|
|
153
|
+
threadMessageService: deps.threadMessageService,
|
|
154
|
+
redis: deps.redis,
|
|
155
|
+
normalizeThread: toNormalizedThread,
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
const listing = createThreadListingHelpers({ db: deps.db, normalizeThreads: toNormalizedThreads })
|
|
159
|
+
|
|
160
|
+
const memory = createThreadMemoryBlockHelpers({
|
|
161
|
+
threadStore,
|
|
162
|
+
contextCompactionService: { compactMemoryBlock: deps.contextCompactionService.compactMemoryBlock },
|
|
163
|
+
background: deps.background,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
function getById(threadId: RecordIdRef) {
|
|
167
|
+
return effectTryPromise(() => threadStore.getById(threadId), 'Failed to load thread.')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function getThread(threadId: RecordIdRef) {
|
|
171
|
+
return Effect.gen(function* () {
|
|
172
|
+
const thread = yield* getById(threadId)
|
|
173
|
+
return yield* toNormalizedThread(thread)
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function updateTitle(threadId: RecordIdRef, title: string) {
|
|
178
|
+
return Effect.gen(function* () {
|
|
179
|
+
const existing = yield* getById(threadId)
|
|
180
|
+
yield* assertMutableThreadEffect(existing)
|
|
181
|
+
const thread = yield* effectTryPromise(
|
|
182
|
+
() => threadStore.update(threadId, { title, nameGenerated: true }),
|
|
183
|
+
'Failed to update thread title.',
|
|
184
|
+
)
|
|
185
|
+
return yield* toNormalizedThread(thread)
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function updateStatus(threadId: RecordIdRef, status: string) {
|
|
190
|
+
return Effect.gen(function* () {
|
|
191
|
+
const parsedStatus = sdkThreadStatusSchema.safeParse(status)
|
|
192
|
+
if (!parsedStatus.success) {
|
|
193
|
+
return yield* new BadRequestError({ message: `Invalid thread status: ${status}` })
|
|
194
|
+
}
|
|
195
|
+
const existing = yield* getById(threadId)
|
|
196
|
+
yield* assertMutableThreadEffect(existing)
|
|
197
|
+
const thread = yield* effectTryPromise(
|
|
198
|
+
() => threadStore.update(threadId, { status: parsedStatus.data }),
|
|
199
|
+
'Failed to update thread status.',
|
|
200
|
+
)
|
|
201
|
+
return yield* toNormalizedThread(thread)
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function setCompacting(threadId: RecordIdRef, value: boolean) {
|
|
206
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
207
|
+
const threadIdString = recordIdToString(threadRef, TABLES.THREAD)
|
|
208
|
+
return Effect.asVoid(
|
|
209
|
+
effectTryPromise(
|
|
210
|
+
() => deps.db.query<unknown>(surql`UPDATE ONLY ${threadRef} SET isCompacting = ${value}`),
|
|
211
|
+
'Failed to update thread compaction flag.',
|
|
212
|
+
).pipe(Effect.tap(() => deps.compactionCoordination.signal(threadIdString, value))),
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function clearThread(threadId: RecordIdRef) {
|
|
217
|
+
return Effect.gen(function* () {
|
|
218
|
+
const existing = yield* getById(threadId)
|
|
219
|
+
yield* assertMutableThreadEffect(existing)
|
|
220
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
221
|
+
yield* effectTryPromise(
|
|
222
|
+
() => deps.db.deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef }),
|
|
223
|
+
'Failed to delete thread messages.',
|
|
224
|
+
)
|
|
225
|
+
yield* effectTryPromise(
|
|
226
|
+
() =>
|
|
227
|
+
deps.db.query<unknown>(surql`
|
|
228
|
+
UPDATE ONLY ${threadRef}
|
|
229
|
+
SET turnCount = 0,
|
|
230
|
+
compactionSummary = NONE,
|
|
231
|
+
lastCompactedMessageId = NONE,
|
|
232
|
+
activeRunId = NONE,
|
|
233
|
+
activeStreamId = NONE,
|
|
234
|
+
isCompacting = false
|
|
235
|
+
`),
|
|
236
|
+
'Failed to reset thread state.',
|
|
237
|
+
)
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function deleteThread(threadId: RecordIdRef) {
|
|
242
|
+
return Effect.gen(function* () {
|
|
243
|
+
const existing = yield* getById(threadId)
|
|
244
|
+
yield* assertMutableThreadEffect(existing)
|
|
245
|
+
yield* effectTryPromise(() => threadStore.deleteById(threadId), 'Failed to delete thread.')
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function incrementTurnCount(threadId: RecordIdRef) {
|
|
250
|
+
const threadRef = ensureRecordId(threadId, TABLES.THREAD)
|
|
251
|
+
return Effect.gen(function* () {
|
|
252
|
+
const result = yield* effectTryPromise(
|
|
253
|
+
() =>
|
|
254
|
+
deps.db.query<{ turnCount: number }>(
|
|
255
|
+
surql`
|
|
256
|
+
UPDATE ONLY ${threadRef}
|
|
257
|
+
SET turnCount += 1
|
|
258
|
+
RETURN turnCount
|
|
259
|
+
`,
|
|
260
|
+
),
|
|
261
|
+
'Failed to increment thread turn count.',
|
|
262
|
+
)
|
|
263
|
+
return result[0].turnCount
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
findById: (...args: Parameters<typeof threadStore.findById>) =>
|
|
269
|
+
effectTryPromise(() => threadStore.findById(...args), 'Failed to find thread.'),
|
|
270
|
+
getById,
|
|
271
|
+
findAll: (...args: Parameters<typeof threadStore.findAll>) =>
|
|
272
|
+
effectTryPromise(() => threadStore.findAll(...args), 'Failed to find all threads.'),
|
|
273
|
+
create: (...args: Parameters<typeof threadStore.create>) =>
|
|
274
|
+
effectTryPromise(() => threadStore.create(...args), 'Failed to create thread.'),
|
|
275
|
+
update: (...args: Parameters<typeof threadStore.update>) =>
|
|
276
|
+
effectTryPromise(() => threadStore.update(...args), 'Failed to update thread.'),
|
|
277
|
+
delete: (...args: Parameters<typeof threadStore.deleteById>) =>
|
|
278
|
+
effectTryPromise(() => threadStore.deleteById(...args), 'Failed to delete thread.'),
|
|
279
|
+
getOrCreateDefault: (...args: Parameters<typeof bootstrap.getOrCreateDefault>) =>
|
|
280
|
+
effectTryPromise(() => bootstrap.getOrCreateDefault(...args), 'Failed to get or create default thread.'),
|
|
281
|
+
getOrCreateThread: (...args: Parameters<typeof bootstrap.getOrCreateThread>) =>
|
|
282
|
+
effectTryPromise(() => bootstrap.getOrCreateThread(...args), 'Failed to get or create thread.'),
|
|
283
|
+
createThread: (...args: Parameters<typeof bootstrap.createThread>) =>
|
|
284
|
+
effectTryPromise(() => bootstrap.createThread(...args), 'Failed to create thread.'),
|
|
285
|
+
ensureBootstrapThreads: (...args: Parameters<typeof bootstrap.ensureBootstrapThreads>) =>
|
|
286
|
+
effectTryPromise(() => bootstrap.ensureBootstrapThreads(...args), 'Failed to ensure bootstrap threads.'),
|
|
287
|
+
listThreads: (...args: Parameters<typeof listing.listThreads>) =>
|
|
288
|
+
effectTryPromise(() => listing.listThreads(...args), 'Failed to list threads.'),
|
|
289
|
+
listOrganizationThreads: (...args: Parameters<typeof listing.listOrganizationThreads>) =>
|
|
290
|
+
effectTryPromise(() => listing.listOrganizationThreads(...args), 'Failed to list organization threads.'),
|
|
291
|
+
getThread,
|
|
292
|
+
updateTitle,
|
|
293
|
+
updateStatus,
|
|
294
|
+
setActiveTurn: activeRun.setActiveTurn,
|
|
295
|
+
getActiveTurn: (...args: Parameters<typeof activeRun.getActiveTurn>) =>
|
|
296
|
+
effectTryPromise(() => activeRun.getActiveTurn(...args), 'Failed to get active turn.'),
|
|
297
|
+
getActiveRunId: activeRun.getActiveRunId,
|
|
298
|
+
hasActiveRunLease: activeRun.hasActiveRunLease,
|
|
299
|
+
withActiveRunLease: activeRun.withActiveRunLease,
|
|
300
|
+
getActiveStreamId: activeRun.getActiveStreamId,
|
|
301
|
+
clearActiveTurn: activeRun.clearActiveTurn,
|
|
302
|
+
clearStaleActiveRunIfMissingFromRegistry: activeRun.clearStaleActiveRunIfMissingFromRegistry,
|
|
303
|
+
stopActiveRun: (...args: Parameters<typeof activeRun.stopActiveRun>) =>
|
|
304
|
+
effectTryPromise(() => activeRun.stopActiveRun(...args), 'Failed to stop active run.'),
|
|
305
|
+
setCompacting,
|
|
306
|
+
appendMemoryBlock: (...args: Parameters<typeof memory.appendMemoryBlock>) =>
|
|
307
|
+
effectTryPromise(() => memory.appendMemoryBlock(...args), 'Failed to append memory block.'),
|
|
308
|
+
compactMemoryBlock: (...args: Parameters<typeof memory.compactMemoryBlock>) =>
|
|
309
|
+
effectTryPromise(() => memory.compactMemoryBlock(...args), 'Failed to compact memory block.'),
|
|
310
|
+
clearThread,
|
|
311
|
+
deleteThread,
|
|
312
|
+
listRecentThreads: (...args: Parameters<typeof listing.listRecentThreads>) =>
|
|
313
|
+
effectTryPromise(() => listing.listRecentThreads(...args), 'Failed to list recent threads.'),
|
|
314
|
+
formatMemoryBlockForPrompt,
|
|
315
|
+
toPublicThread,
|
|
316
|
+
incrementTurnCount,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export class ThreadServiceTag extends Context.Service<ThreadServiceTag, ReturnType<typeof makeThreadService>>()(
|
|
321
|
+
'@lota-sdk/core/ThreadService',
|
|
322
|
+
) {}
|
|
323
|
+
|
|
324
|
+
export const ThreadServiceLive = Layer.effect(
|
|
325
|
+
ThreadServiceTag,
|
|
326
|
+
Effect.gen(function* () {
|
|
327
|
+
const db = yield* DatabaseServiceTag
|
|
328
|
+
const redis = yield* RedisServiceTag
|
|
329
|
+
const chatRunRegistry = yield* ChatRunRegistryTag
|
|
330
|
+
const threadMessageService = yield* ThreadMessageServiceTag
|
|
331
|
+
const contextCompactionService = yield* ContextCompactionServiceTag
|
|
332
|
+
const compactionCoordination = yield* CompactionCoordinationTag
|
|
333
|
+
const background = yield* BackgroundWorkService
|
|
334
|
+
return makeThreadService({
|
|
335
|
+
db,
|
|
336
|
+
redis,
|
|
337
|
+
chatRunRegistry,
|
|
338
|
+
threadMessageService,
|
|
339
|
+
contextCompactionService,
|
|
340
|
+
compactionCoordination,
|
|
341
|
+
background,
|
|
342
|
+
})
|
|
343
|
+
}),
|
|
344
|
+
)
|
|
@@ -1,56 +1,106 @@
|
|
|
1
1
|
import type { SdkUser, SdkUserRecord } from '@lota-sdk/shared'
|
|
2
2
|
import { sdkUserRecordSchema, sdkUserSchema } from '@lota-sdk/shared'
|
|
3
|
+
import { Context, Schema, Effect, Layer } from 'effect'
|
|
3
4
|
|
|
4
|
-
import { BaseService } from '../db/base.service'
|
|
5
5
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
6
6
|
import type { RecordIdInput } from '../db/record-id'
|
|
7
|
-
import {
|
|
7
|
+
import type { SurrealDBService } from '../db/service'
|
|
8
8
|
import { TABLES } from '../db/tables'
|
|
9
|
+
import { NotFoundError } from '../effect/errors'
|
|
10
|
+
import { DatabaseServiceTag } from '../effect/services'
|
|
9
11
|
import { toIsoDateTimeString } from '../utils/date-time'
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
function toPublic(record: SdkUserRecord): SdkUser {
|
|
14
|
+
return sdkUserSchema.parse({
|
|
15
|
+
id: recordIdToString(ensureRecordId(record.id as RecordIdInput, TABLES.USER), TABLES.USER),
|
|
16
|
+
name: record.name,
|
|
17
|
+
email: record.email,
|
|
18
|
+
createdAt: toIsoDateTimeString(record.createdAt),
|
|
19
|
+
updatedAt: toIsoDateTimeString(record.updatedAt),
|
|
20
|
+
})
|
|
21
|
+
}
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
function userNotFoundError(userId: RecordIdInput): NotFoundError {
|
|
24
|
+
return new NotFoundError({
|
|
25
|
+
resource: TABLES.USER,
|
|
26
|
+
id: recordIdToString(userId, TABLES.USER),
|
|
27
|
+
message: `Record not found in ${TABLES.USER}: ${recordIdToString(userId, TABLES.USER)}`,
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
class UserServiceError extends Schema.TaggedErrorClass<UserServiceError>()('UserServiceError', {
|
|
32
|
+
operation: Schema.String,
|
|
33
|
+
cause: Schema.Defect,
|
|
34
|
+
}) {}
|
|
35
|
+
|
|
36
|
+
function toUserServiceError(operation: string, cause: unknown): UserServiceError {
|
|
37
|
+
return new UserServiceError({ operation, cause })
|
|
38
|
+
}
|
|
25
39
|
|
|
26
|
-
|
|
40
|
+
export function makeUserService(db: SurrealDBService) {
|
|
41
|
+
function upsertUserEffect(params: { id: RecordIdInput; name: string; email: string }) {
|
|
27
42
|
const userRef = ensureRecordId(params.id, TABLES.USER)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{ name: params.name, email: params.email },
|
|
32
|
-
sdkUserRecordSchema,
|
|
43
|
+
return db.upsert(TABLES.USER, userRef, { name: params.name, email: params.email }, sdkUserRecordSchema).pipe(
|
|
44
|
+
Effect.mapError((cause) => toUserServiceError('upsertUser', cause)),
|
|
45
|
+
Effect.map(toPublic),
|
|
33
46
|
)
|
|
34
|
-
return this.toPublic(record)
|
|
35
47
|
}
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
return
|
|
49
|
+
function getUserEffect(userId: RecordIdInput) {
|
|
50
|
+
return db.findOne(TABLES.USER, { id: ensureRecordId(userId, TABLES.USER) }, sdkUserRecordSchema).pipe(
|
|
51
|
+
Effect.mapError((cause) => toUserServiceError('getUser', cause)),
|
|
52
|
+
Effect.flatMap((record) => (record ? Effect.succeed(record) : Effect.fail(userNotFoundError(userId)))),
|
|
53
|
+
Effect.map(toPublic),
|
|
54
|
+
)
|
|
39
55
|
}
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
return (
|
|
57
|
+
function listUsersEffect() {
|
|
58
|
+
return db.findMany(TABLES.USER, {}, sdkUserRecordSchema, { orderBy: 'createdAt', orderDir: 'ASC' }).pipe(
|
|
59
|
+
Effect.mapError((cause) => toUserServiceError('listUsers', cause)),
|
|
60
|
+
Effect.map((records) => records.map(toPublic)),
|
|
61
|
+
)
|
|
43
62
|
}
|
|
44
63
|
|
|
45
|
-
|
|
46
|
-
return
|
|
64
|
+
function updateUserEffect(userId: RecordIdInput, params: { name?: string; email?: string }) {
|
|
65
|
+
return db.update(TABLES.USER, ensureRecordId(userId, TABLES.USER), params, sdkUserRecordSchema).pipe(
|
|
66
|
+
Effect.mapError((cause) => toUserServiceError('updateUser', cause)),
|
|
67
|
+
Effect.flatMap((updated) => (updated ? Effect.succeed(updated) : Effect.fail(userNotFoundError(userId)))),
|
|
68
|
+
Effect.map(toPublic),
|
|
69
|
+
)
|
|
47
70
|
}
|
|
48
71
|
|
|
49
|
-
|
|
72
|
+
function deleteUserEffect(userId: RecordIdInput) {
|
|
50
73
|
const userRef = ensureRecordId(userId, TABLES.USER)
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
return Effect.gen(function* () {
|
|
75
|
+
yield* db
|
|
76
|
+
.deleteWhere(TABLES.ORGANIZATION_MEMBER, { in: userRef })
|
|
77
|
+
.pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteMemberships', cause)))
|
|
78
|
+
const deleted = yield* db
|
|
79
|
+
.deleteById(TABLES.USER, userRef)
|
|
80
|
+
.pipe(Effect.mapError((cause) => toUserServiceError('deleteUser.deleteUser', cause)))
|
|
81
|
+
if (!deleted) {
|
|
82
|
+
return yield* userNotFoundError(userId)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
upsertUser: upsertUserEffect,
|
|
89
|
+
getUser: getUserEffect,
|
|
90
|
+
listUsers: listUsersEffect,
|
|
91
|
+
updateUser: updateUserEffect,
|
|
92
|
+
deleteUser: deleteUserEffect,
|
|
53
93
|
}
|
|
54
94
|
}
|
|
55
95
|
|
|
56
|
-
export
|
|
96
|
+
export class UserServiceTag extends Context.Service<UserServiceTag, ReturnType<typeof makeUserService>>()(
|
|
97
|
+
'@lota-sdk/core/UserService',
|
|
98
|
+
) {}
|
|
99
|
+
|
|
100
|
+
export const UserServiceLive = Layer.effect(
|
|
101
|
+
UserServiceTag,
|
|
102
|
+
Effect.gen(function* () {
|
|
103
|
+
const db = yield* DatabaseServiceTag
|
|
104
|
+
return makeUserService(db)
|
|
105
|
+
}),
|
|
106
|
+
)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { PlanDataSchema, PlanNodeSpec, PlanSchemaRegistry, WriteIntent } from '@lota-sdk/shared'
|
|
2
|
+
import { Context, Layer } from 'effect'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { nowIsoDateTimeString } from '../utils/date-time'
|
|
5
|
+
import { validateSchemaValue } from './plan/plan-validator.service'
|
|
4
6
|
|
|
5
7
|
export interface WriteValidationIssue {
|
|
6
8
|
code: string
|
|
@@ -15,19 +17,51 @@ export interface WriteValidationResult {
|
|
|
15
17
|
validatedAt: string
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
function buildWriteValidationResult(issues: WriteValidationIssue[]): WriteValidationResult {
|
|
21
|
+
const hasFailed = issues.length > 0
|
|
22
|
+
return {
|
|
23
|
+
status: hasFailed ? 'fail' : 'pass',
|
|
24
|
+
issues,
|
|
25
|
+
...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
|
|
26
|
+
validatedAt: nowIsoDateTimeString(),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function makeWriteIntentValidatorService() {
|
|
31
|
+
return {
|
|
32
|
+
validate(params: {
|
|
33
|
+
intent: WriteIntent
|
|
34
|
+
nodeSpec: PlanNodeSpec
|
|
35
|
+
schemaRegistry: PlanSchemaRegistry
|
|
36
|
+
existingDeliverables: Map<string, unknown>
|
|
37
|
+
}): WriteValidationResult {
|
|
38
|
+
const issues: WriteValidationIssue[] = []
|
|
39
|
+
const { intent, nodeSpec, schemaRegistry, existingDeliverables } = params
|
|
40
|
+
|
|
41
|
+
if (intent.targetPath.startsWith('structuredOutput')) {
|
|
42
|
+
if (nodeSpec.outputSchemaRef) {
|
|
43
|
+
const schema = schemaRegistry[nodeSpec.outputSchemaRef] as PlanDataSchema | undefined
|
|
44
|
+
if (schema) {
|
|
45
|
+
const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
|
|
46
|
+
for (const message of schemaIssues) {
|
|
47
|
+
issues.push({ code: 'schema_validation_failed', message })
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return buildWriteValidationResult(issues)
|
|
52
|
+
}
|
|
27
53
|
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
54
|
+
const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
|
|
55
|
+
if (!deliverable) {
|
|
56
|
+
issues.push({
|
|
57
|
+
code: 'unknown_deliverable',
|
|
58
|
+
message: `"${intent.targetPath}" does not match any declared deliverable.`,
|
|
59
|
+
})
|
|
60
|
+
return buildWriteValidationResult(issues)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (deliverable.schemaRef) {
|
|
64
|
+
const schema = schemaRegistry[deliverable.schemaRef] as PlanDataSchema | undefined
|
|
31
65
|
if (schema) {
|
|
32
66
|
const schemaIssues = validateSchemaValue({ schema, value: intent.payload, path: intent.targetPath })
|
|
33
67
|
for (const message of schemaIssues) {
|
|
@@ -35,47 +69,25 @@ class WriteIntentValidatorService {
|
|
|
35
69
|
}
|
|
36
70
|
}
|
|
37
71
|
}
|
|
38
|
-
return this.buildResult(issues)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const deliverable = nodeSpec.deliverables.find((d) => d.name === intent.targetPath)
|
|
42
|
-
if (!deliverable) {
|
|
43
|
-
issues.push({
|
|
44
|
-
code: 'unknown_deliverable',
|
|
45
|
-
message: `"${intent.targetPath}" does not match any declared deliverable.`,
|
|
46
|
-
})
|
|
47
|
-
return this.buildResult(issues)
|
|
48
|
-
}
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
issues.push({ code: 'schema_validation_failed', message })
|
|
56
|
-
}
|
|
73
|
+
if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
|
|
74
|
+
issues.push({
|
|
75
|
+
code: 'update_target_not_found',
|
|
76
|
+
message: `Cannot update "${intent.targetPath}" — no prior write exists.`,
|
|
77
|
+
})
|
|
57
78
|
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (intent.action === 'update' && !existingDeliverables.has(intent.targetPath)) {
|
|
61
|
-
issues.push({
|
|
62
|
-
code: 'update_target_not_found',
|
|
63
|
-
message: `Cannot update "${intent.targetPath}" — no prior write exists.`,
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
private buildResult(issues: WriteValidationIssue[]): WriteValidationResult {
|
|
71
|
-
const hasFailed = issues.length > 0
|
|
72
|
-
return {
|
|
73
|
-
status: hasFailed ? 'fail' : 'pass',
|
|
74
|
-
issues,
|
|
75
|
-
...(hasFailed ? { suggestion: issues.map((i) => i.message).join('; ') } : {}),
|
|
76
|
-
validatedAt: new Date().toISOString(),
|
|
77
|
-
}
|
|
80
|
+
return buildWriteValidationResult(issues)
|
|
81
|
+
},
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
export
|
|
85
|
+
export class WriteIntentValidatorServiceTag extends Context.Service<
|
|
86
|
+
WriteIntentValidatorServiceTag,
|
|
87
|
+
ReturnType<typeof makeWriteIntentValidatorService>
|
|
88
|
+
>()('@lota-sdk/core/WriteIntentValidatorService') {}
|
|
89
|
+
|
|
90
|
+
export const WriteIntentValidatorServiceLive = Layer.succeed(
|
|
91
|
+
WriteIntentValidatorServiceTag,
|
|
92
|
+
makeWriteIntentValidatorService(),
|
|
93
|
+
)
|