@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
|
@@ -1,37 +1,58 @@
|
|
|
1
1
|
import type { ChatMessage } from '@lota-sdk/shared'
|
|
2
2
|
import { stepCountIs } from 'ai'
|
|
3
3
|
import type { ToolSet } from 'ai'
|
|
4
|
+
import { Schema, Effect } from 'effect'
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
+
import { getAgentRuntimeConfig, getResolvedAgentFactoryConfig } from '../config/agent-defaults'
|
|
6
7
|
import { aiLogger } from '../config/logger'
|
|
7
8
|
import type { RecordIdRef } from '../db/record-id'
|
|
8
9
|
import { recordIdToString } from '../db/record-id'
|
|
9
10
|
import { TABLES } from '../db/tables'
|
|
11
|
+
import { effectTryMaybeAsync as effectTryMaybeAsyncShared } from '../effect/helpers'
|
|
10
12
|
import { readRuntimeAgentIdentityOverrides } from '../runtime/agent-identity-overrides'
|
|
11
13
|
import { mergeInstructionSections } from '../runtime/instruction-sections'
|
|
12
14
|
import { getRuntimeAdapters, getTurnHooks } from '../runtime/runtime-extensions'
|
|
13
15
|
import type { LotaRuntimeTeamThinkToolsParams } from '../runtime/runtime-extensions'
|
|
14
|
-
import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation-orchestrator'
|
|
15
|
-
import type {
|
|
16
|
-
|
|
16
|
+
import { createConsultTeamTool as createConsultTeamToolSdk } from '../runtime/team-consultation/team-consultation-orchestrator'
|
|
17
|
+
import type {
|
|
18
|
+
DefaultRepoSections,
|
|
19
|
+
TeamConsultationParticipantRunner,
|
|
20
|
+
} from '../runtime/team-consultation/team-consultation-orchestrator'
|
|
21
|
+
import { buildTeamConsultationResponseGuard } from '../runtime/team-consultation/team-consultation-prompts'
|
|
17
22
|
import { asRecord, readInstructionSections, readOptionalString } from '../runtime/thread-chat-helpers'
|
|
18
23
|
import type { ReadableUploadMetadata } from '../services/attachment.service'
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
function buildTeamThinkAgentToolsEffect(
|
|
21
26
|
params: LotaRuntimeTeamThinkToolsParams,
|
|
22
|
-
):
|
|
27
|
+
): Effect.Effect<{ tools: ToolSet }, TeamThinkRuntimeError> {
|
|
23
28
|
const builder = getRuntimeAdapters().buildTeamThinkAgentTools
|
|
24
29
|
if (!builder) {
|
|
25
|
-
return { tools: {} }
|
|
30
|
+
return Effect.succeed({ tools: {} })
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
return { tools: result.tools as Record<string, unknown> }
|
|
33
|
+
return effectTryMaybeAsync(() => builder(params), 'Failed to build team-think agent tools.')
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
const TEAM_THINK_AGENT_MAX_RETRIES = 1
|
|
33
37
|
const TEAM_THINK_AGENT_MAX_STEPS = 3
|
|
34
38
|
|
|
39
|
+
class TeamThinkAgentFactoryNotConfiguredError extends Schema.TaggedErrorClass<TeamThinkAgentFactoryNotConfiguredError>()(
|
|
40
|
+
'TeamThinkAgentFactoryNotConfiguredError',
|
|
41
|
+
{ agentId: Schema.String },
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
class TeamThinkRuntimeError extends Schema.TaggedErrorClass<TeamThinkRuntimeError>()('TeamThinkRuntimeError', {
|
|
45
|
+
message: Schema.String,
|
|
46
|
+
cause: Schema.optional(Schema.Defect),
|
|
47
|
+
}) {}
|
|
48
|
+
|
|
49
|
+
function effectTryMaybeAsync<A>(
|
|
50
|
+
evaluate: () => A | PromiseLike<A>,
|
|
51
|
+
message: string,
|
|
52
|
+
): Effect.Effect<A, TeamThinkRuntimeError> {
|
|
53
|
+
return effectTryMaybeAsyncShared(evaluate, (error) => new TeamThinkRuntimeError({ message, cause: error }))
|
|
54
|
+
}
|
|
55
|
+
|
|
35
56
|
export function createTeamThinkTool(params: {
|
|
36
57
|
historyMessages: ChatMessage[]
|
|
37
58
|
latestUserMessageId: string
|
|
@@ -55,82 +76,102 @@ export function createTeamThinkTool(params: {
|
|
|
55
76
|
(params.context as Record<string, unknown> | null | undefined) ?? null,
|
|
56
77
|
)
|
|
57
78
|
const participantRunner: TeamConsultationParticipantRunner = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
buildParticipantAgent(agentId, runParams) {
|
|
80
|
+
return Effect.runPromise(
|
|
81
|
+
Effect.gen(function* () {
|
|
82
|
+
const currentContext = yield* Effect.context()
|
|
83
|
+
const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
|
|
84
|
+
const dynamicInstructionSections = yield* effectTryMaybeAsync(
|
|
85
|
+
() => params.getAdditionalInstructionSections?.(),
|
|
86
|
+
'Failed to load dynamic team-think instruction sections.',
|
|
87
|
+
)
|
|
88
|
+
const agentResolution = asRecord(
|
|
89
|
+
yield* effectTryMaybeAsync(
|
|
90
|
+
() =>
|
|
91
|
+
getTurnHooks().resolveAgent?.({
|
|
92
|
+
agentId,
|
|
93
|
+
mode: 'fixedThreadMode',
|
|
94
|
+
thread: null,
|
|
95
|
+
threadRef: params.threadId,
|
|
96
|
+
orgRef: params.orgId,
|
|
97
|
+
userRef: params.userId,
|
|
98
|
+
onboardingActive: false,
|
|
99
|
+
linearInstalled: false,
|
|
100
|
+
githubInstalled: params.githubInstalled,
|
|
101
|
+
additionalInstructionSections: mergeInstructionSections(
|
|
102
|
+
dynamicInstructionSections,
|
|
103
|
+
params.additionalInstructionSections,
|
|
104
|
+
),
|
|
105
|
+
context: (params.context as Record<string, unknown> | null | undefined) ?? null,
|
|
106
|
+
}),
|
|
107
|
+
'Failed to resolve team-think participant agent.',
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
|
|
111
|
+
const config = getAgentRuntimeConfig({
|
|
112
|
+
agentId: resolvedAgentId,
|
|
113
|
+
threadType: 'group' as const,
|
|
114
|
+
mode: 'fixedThreadMode',
|
|
115
|
+
onboardingActive: false,
|
|
116
|
+
linearInstalled: false,
|
|
117
|
+
systemWorkspaceDetails: runParams.systemWorkspaceDetails,
|
|
118
|
+
preSeededMemoriesSection: runParams.preSeededMemoriesSection,
|
|
119
|
+
retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
|
|
120
|
+
additionalInstructionSections: mergeInstructionSections(
|
|
121
|
+
dynamicInstructionSections,
|
|
122
|
+
params.additionalInstructionSections,
|
|
123
|
+
readInstructionSections(agentResolution?.additionalInstructionSections),
|
|
124
|
+
),
|
|
125
|
+
responseGuardSection: buildTeamConsultationResponseGuard({
|
|
126
|
+
agentId: resolvedAgentId,
|
|
127
|
+
task: runParams.task,
|
|
128
|
+
}),
|
|
129
|
+
})
|
|
130
|
+
const { tools } = yield* buildTeamThinkAgentToolsEffect({
|
|
131
|
+
agentId: resolvedAgentId,
|
|
132
|
+
workspaceId: params.orgId,
|
|
133
|
+
userId: params.userId,
|
|
134
|
+
workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
|
|
135
|
+
threadId: params.threadId,
|
|
136
|
+
githubInstalled: params.githubInstalled,
|
|
137
|
+
provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
|
|
138
|
+
availableUploads: params.availableUploads,
|
|
139
|
+
defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
|
|
140
|
+
context: params.context,
|
|
141
|
+
toolProviders: params.toolProviders,
|
|
142
|
+
})
|
|
143
|
+
const agentId_ = config.id || resolvedAgentId
|
|
144
|
+
const configuredMaxSteps = config.maxSteps
|
|
145
|
+
const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
|
|
146
|
+
const agentFactory = getResolvedAgentFactoryConfig().createAgent[agentId_]
|
|
147
|
+
if (!agentFactory) {
|
|
148
|
+
return yield* new TeamThinkAgentFactoryNotConfiguredError({ agentId: agentId_ })
|
|
149
|
+
}
|
|
150
|
+
const agent = agentFactory({
|
|
151
|
+
mode: 'fixedThreadMode',
|
|
152
|
+
tools,
|
|
153
|
+
extraInstructions: config.extraInstructions,
|
|
154
|
+
maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
|
|
155
|
+
stopWhen: [stepCountIs(maxSteps)],
|
|
156
|
+
})
|
|
157
|
+
const observer = {
|
|
158
|
+
run: <T>(fn: () => T | Promise<T>): Promise<T> =>
|
|
159
|
+
runPromiseWithCurrentContext(effectTryMaybeAsync(fn, `Team-think participant run failed (${agentId}).`)),
|
|
160
|
+
recordError: (error: unknown) => {
|
|
161
|
+
aiLogger.error`Team-think participant failed (${agentId}): ${error}`
|
|
162
|
+
},
|
|
163
|
+
recordAbort: (error: unknown) => {
|
|
164
|
+
aiLogger.debug`Team-think participant aborted (${agentId}): ${
|
|
165
|
+
error instanceof Error ? error.message : String(error)
|
|
166
|
+
}`
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
|
|
171
|
+
observer,
|
|
172
|
+
}
|
|
173
|
+
}).pipe(Effect.withSpan('tool.teamThink.buildParticipantAgent')),
|
|
77
174
|
)
|
|
78
|
-
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
|
|
79
|
-
const config = getAgentRuntimeConfig({
|
|
80
|
-
agentId: resolvedAgentId,
|
|
81
|
-
threadType: 'group' as const,
|
|
82
|
-
mode: 'fixedThreadMode',
|
|
83
|
-
onboardingActive: false,
|
|
84
|
-
linearInstalled: false,
|
|
85
|
-
systemWorkspaceDetails: runParams.systemWorkspaceDetails,
|
|
86
|
-
preSeededMemoriesSection: runParams.preSeededMemoriesSection,
|
|
87
|
-
retrievedKnowledgeSection: runParams.retrievedKnowledgeSection,
|
|
88
|
-
additionalInstructionSections: mergeInstructionSections(
|
|
89
|
-
dynamicInstructionSections,
|
|
90
|
-
params.additionalInstructionSections,
|
|
91
|
-
readInstructionSections(agentResolution?.additionalInstructionSections),
|
|
92
|
-
),
|
|
93
|
-
responseGuardSection: buildTeamConsultationResponseGuard({ agentId: resolvedAgentId, task: runParams.task }),
|
|
94
|
-
})
|
|
95
|
-
const { tools } = await buildTeamThinkAgentTools({
|
|
96
|
-
agentId: resolvedAgentId,
|
|
97
|
-
workspaceId: params.orgId,
|
|
98
|
-
userId: params.userId,
|
|
99
|
-
workspaceIdString: recordIdToString(params.orgId, TABLES.ORGANIZATION),
|
|
100
|
-
threadId: params.threadId,
|
|
101
|
-
githubInstalled: params.githubInstalled,
|
|
102
|
-
provideRepoTool: resolvedAgentId !== 'mentor' && params.provideRepoTool,
|
|
103
|
-
availableUploads: params.availableUploads,
|
|
104
|
-
defaultRepoSections: params.defaultRepoSectionsByAgent[resolvedAgentId],
|
|
105
|
-
context: params.context,
|
|
106
|
-
toolProviders: params.toolProviders,
|
|
107
|
-
})
|
|
108
|
-
const agentConfig = config as Record<string, unknown>
|
|
109
|
-
const agentId_ = typeof agentConfig.id === 'string' ? agentConfig.id : resolvedAgentId
|
|
110
|
-
const configuredMaxSteps = typeof agentConfig.maxSteps === 'number' ? agentConfig.maxSteps : 10
|
|
111
|
-
const maxSteps = Math.min(configuredMaxSteps, TEAM_THINK_AGENT_MAX_STEPS)
|
|
112
|
-
const agent = createAgent[agentId_]({
|
|
113
|
-
mode: 'fixedThreadMode',
|
|
114
|
-
tools,
|
|
115
|
-
extraInstructions: agentConfig.extraInstructions,
|
|
116
|
-
maxRetries: TEAM_THINK_AGENT_MAX_RETRIES,
|
|
117
|
-
stopWhen: [stepCountIs(maxSteps)],
|
|
118
|
-
})
|
|
119
|
-
const observer = {
|
|
120
|
-
run: async <T>(fn: () => T | Promise<T>): Promise<T> => fn(),
|
|
121
|
-
recordError: (error: unknown) => {
|
|
122
|
-
aiLogger.error`Team-think participant failed (${agentId}): ${error}`
|
|
123
|
-
},
|
|
124
|
-
recordAbort: (error: unknown) => {
|
|
125
|
-
aiLogger.info`Team-think participant aborted (${agentId}): ${
|
|
126
|
-
error instanceof Error ? error.message : String(error)
|
|
127
|
-
}`
|
|
128
|
-
},
|
|
129
|
-
}
|
|
130
|
-
return {
|
|
131
|
-
agent: agent as Awaited<ReturnType<TeamConsultationParticipantRunner['buildParticipantAgent']>>['agent'],
|
|
132
|
-
observer,
|
|
133
|
-
}
|
|
134
175
|
},
|
|
135
176
|
}
|
|
136
177
|
|
|
@@ -11,8 +11,9 @@ export const userQuestionsTool = {
|
|
|
11
11
|
description:
|
|
12
12
|
'Ask the user structured questions. UI renders them; do not repeat them in text. Turn ends after this call.',
|
|
13
13
|
inputSchema: UserQuestionsArgsSchema,
|
|
14
|
-
execute:
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
execute: (_args: UserQuestionsArgs) =>
|
|
15
|
+
Promise.resolve(
|
|
16
|
+
'Questions have been presented to the user in the chat UI. Do NOT restate them in text, and do NOT generate further text or tool calls. Your turn is complete.',
|
|
17
|
+
),
|
|
17
18
|
}),
|
|
18
19
|
} as const satisfies ToolDefinition<void>
|
package/src/utils/async.ts
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
|
+
import { Cause, Duration, Effect, Exit, Schema } from 'effect'
|
|
2
|
+
|
|
1
3
|
import { serverLogger } from '../config/logger'
|
|
2
|
-
import {
|
|
4
|
+
import { TimeoutError } from '../effect/errors'
|
|
5
|
+
import { getErrorMessage, toError } from './errors'
|
|
3
6
|
|
|
4
|
-
class
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
class TimedOperationError extends Schema.TaggedErrorClass<TimedOperationError>()('TimedOperationError', {
|
|
8
|
+
operation: Schema.String,
|
|
9
|
+
cause: Schema.Defect,
|
|
10
|
+
}) {}
|
|
11
|
+
|
|
12
|
+
function isTimedOperationError(error: unknown): error is TimedOperationError {
|
|
13
|
+
return typeof error === 'object' && error !== null && '_tag' in error && error._tag === 'TimedOperationError'
|
|
9
14
|
}
|
|
10
15
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}),
|
|
22
|
-
])
|
|
23
|
-
} finally {
|
|
24
|
-
if (timeoutId) {
|
|
25
|
-
clearTimeout(timeoutId)
|
|
16
|
+
export function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {
|
|
17
|
+
return Effect.runPromise(
|
|
18
|
+
Effect.tryPromise({ try: () => promise, catch: (cause) => new TimedOperationError({ operation, cause }) }).pipe(
|
|
19
|
+
Effect.timeout(Duration.millis(ms)),
|
|
20
|
+
Effect.catchTag('TimeoutError', () => Effect.fail(new TimeoutError({ operation, ms }))),
|
|
21
|
+
Effect.exit,
|
|
22
|
+
),
|
|
23
|
+
).then((exit) => {
|
|
24
|
+
if (Exit.isSuccess(exit)) {
|
|
25
|
+
return exit.value
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
const error = Cause.squash(exit.cause)
|
|
29
|
+
throw isTimedOperationError(error) ? toError(error.cause) : error
|
|
30
|
+
})
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export function createSafeEnqueue(logger: { warn: (message: string) => void }) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { RecordId } from 'surrealdb'
|
|
2
|
+
|
|
3
|
+
export function createSha256Hasher(): Bun.CryptoHasher {
|
|
4
|
+
return new Bun.CryptoHasher('sha256')
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function sha256Hex(value: string): string {
|
|
8
|
+
return createSha256Hasher().update(value).digest('hex')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function sha256HexFromParts(parts: Iterable<string>): string {
|
|
12
|
+
const hasher = createSha256Hasher()
|
|
13
|
+
for (const part of parts) {
|
|
14
|
+
hasher.update(part)
|
|
15
|
+
}
|
|
16
|
+
return hasher.digest('hex')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createDeterministicRecordId(table: string, key: string): RecordId {
|
|
20
|
+
return new RecordId(table, sha256Hex(key))
|
|
21
|
+
}
|
package/src/utils/date-time.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DateTime } from 'effect'
|
|
2
|
+
|
|
1
3
|
export { toIsoDateTimeString, toOptionalIsoDateTimeString } from '@lota-sdk/shared'
|
|
2
4
|
|
|
3
5
|
const PROMPT_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
|
|
@@ -9,9 +11,46 @@ const PROMPT_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
|
|
|
9
11
|
|
|
10
12
|
export function toDatabaseDateTime(value: string | Date | null | undefined): Date | undefined {
|
|
11
13
|
if (value === null || value === undefined) return undefined
|
|
12
|
-
return value instanceof Date ? value :
|
|
14
|
+
return value instanceof Date ? value : DateTime.toDateUtc(DateTime.makeUnsafe(value))
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export function formatUtcPromptDate(value: Date): string {
|
|
16
18
|
return PROMPT_DATE_FORMATTER.format(value)
|
|
17
19
|
}
|
|
20
|
+
|
|
21
|
+
export function nowDateTime(): DateTime.Utc {
|
|
22
|
+
return DateTime.nowUnsafe()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function nowEpochMillis(): number {
|
|
26
|
+
return nowDateTime().epochMilliseconds
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function nowDate(): Date {
|
|
30
|
+
return DateTime.toDateUtc(nowDateTime())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function nowIsoDateTimeString(): string {
|
|
34
|
+
return DateTime.formatIso(nowDateTime())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function unsafeDateFrom(value: string | number | Date): Date {
|
|
38
|
+
if (value instanceof Date) {
|
|
39
|
+
return value
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const normalized = typeof value === 'number' && value < 1_000_000_000_000 ? value * 1000 : value
|
|
43
|
+
return DateTime.toDateUtc(DateTime.makeUnsafe(normalized))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function unsafeDateFromUnknown(value: unknown): Date {
|
|
47
|
+
if (value instanceof Date) {
|
|
48
|
+
return value
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof value === 'number') {
|
|
52
|
+
return unsafeDateFrom(value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return DateTime.toDateUtc(DateTime.makeUnsafe(String(value)))
|
|
56
|
+
}
|
package/src/utils/errors.ts
CHANGED
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
export { getErrorMessage } from '@lota-sdk/shared'
|
|
2
|
+
import { Data, Match } from 'effect'
|
|
3
|
+
|
|
4
|
+
import type { EffectError, ValidationIssue } from '../effect/errors'
|
|
5
|
+
import { BaseServicePersistenceError, isEffectError } from '../effect/errors'
|
|
2
6
|
|
|
3
7
|
export function toError(value: unknown): Error {
|
|
4
8
|
return value instanceof Error ? value : new Error(String(value))
|
|
5
9
|
}
|
|
6
10
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
export interface HttpError {
|
|
12
|
+
statusCode: number
|
|
13
|
+
code: string
|
|
14
|
+
message: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AppErrorResponse {
|
|
18
|
+
status: number
|
|
19
|
+
body: { error: { code: string; message: string } }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type AppErrorLike = Error & { code: string; statusCode: number; toResponse?: () => unknown }
|
|
10
23
|
|
|
24
|
+
export class AppError extends Data.Error<{
|
|
25
|
+
readonly message: string
|
|
26
|
+
readonly code: string
|
|
27
|
+
readonly statusCode: number
|
|
28
|
+
}> {
|
|
11
29
|
constructor(message: string, code: string, statusCode: number) {
|
|
12
|
-
super(message)
|
|
13
|
-
this.name =
|
|
14
|
-
this.code = code
|
|
15
|
-
this.statusCode = statusCode
|
|
16
|
-
Error.captureStackTrace(this, this.constructor)
|
|
30
|
+
super({ message, code, statusCode })
|
|
31
|
+
this.name = new.target.name
|
|
17
32
|
}
|
|
18
33
|
|
|
19
|
-
|
|
34
|
+
toResponse(): AppErrorResponse {
|
|
20
35
|
return { status: this.statusCode, body: { error: { code: this.code, message: this.message } } }
|
|
21
36
|
}
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
export class
|
|
25
|
-
constructor(message
|
|
26
|
-
super(message, '
|
|
39
|
+
export class BadRequestError extends AppError {
|
|
40
|
+
constructor(message: string) {
|
|
41
|
+
super(message, 'BAD_REQUEST', 400)
|
|
27
42
|
}
|
|
28
43
|
}
|
|
29
44
|
|
|
30
|
-
export class
|
|
31
|
-
constructor(message
|
|
32
|
-
super(message, '
|
|
45
|
+
export class NotFoundError extends AppError {
|
|
46
|
+
constructor(message: string) {
|
|
47
|
+
super(message, 'NOT_FOUND', 404)
|
|
33
48
|
}
|
|
34
49
|
}
|
|
35
50
|
|
|
@@ -44,11 +59,6 @@ type ErrorCode =
|
|
|
44
59
|
| 'HTTP_ERROR'
|
|
45
60
|
| 'TOO_MANY_REQUESTS'
|
|
46
61
|
|
|
47
|
-
interface ValidationIssue {
|
|
48
|
-
path: string
|
|
49
|
-
message: string
|
|
50
|
-
}
|
|
51
|
-
|
|
52
62
|
export interface ErrorBody {
|
|
53
63
|
error: { code: ErrorCode; message: string; issues?: ValidationIssue[] }
|
|
54
64
|
}
|
|
@@ -73,3 +83,84 @@ export const errorResponses = {
|
|
|
73
83
|
createValidationErrorResponse(message, issues),
|
|
74
84
|
httpError: (message: string) => createErrorResponse('HTTP_ERROR', message),
|
|
75
85
|
} as const
|
|
86
|
+
|
|
87
|
+
function httpError(message: string, code: string, statusCode: number): HttpError {
|
|
88
|
+
return { message, code, statusCode }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function isAppErrorLike(error: unknown): error is AppErrorLike {
|
|
92
|
+
if (!(error instanceof Error)) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const candidate = error as Partial<AppErrorLike>
|
|
97
|
+
return typeof candidate.code === 'string' && typeof candidate.statusCode === 'number'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isAppErrorResponse(value: unknown): value is AppErrorResponse {
|
|
101
|
+
if (typeof value !== 'object' || value === null) {
|
|
102
|
+
return false
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const candidate = value as {
|
|
106
|
+
status?: unknown
|
|
107
|
+
body?: { error?: { code?: unknown; message?: unknown } | null } | null
|
|
108
|
+
}
|
|
109
|
+
return (
|
|
110
|
+
typeof candidate.status === 'number' &&
|
|
111
|
+
typeof candidate.body === 'object' &&
|
|
112
|
+
candidate.body !== null &&
|
|
113
|
+
typeof candidate.body.error === 'object' &&
|
|
114
|
+
candidate.body.error !== null &&
|
|
115
|
+
typeof candidate.body.error.code === 'string' &&
|
|
116
|
+
typeof candidate.body.error.message === 'string'
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function toAppErrorResponse(error: AppErrorLike): AppErrorResponse {
|
|
121
|
+
if (typeof error.toResponse === 'function') {
|
|
122
|
+
const response = error.toResponse()
|
|
123
|
+
if (isAppErrorResponse(response)) {
|
|
124
|
+
return response
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { status: error.statusCode, body: { error: { code: error.code, message: error.message } } }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isBaseServicePersistenceError(error: EffectError): error is BaseServicePersistenceError {
|
|
132
|
+
return error._tag === BaseServicePersistenceError.name
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const toHttpErrorMatch = Match.type<Exclude<EffectError, BaseServicePersistenceError>>().pipe(
|
|
136
|
+
Match.tag('NotFoundError', (e) => httpError(e.message, 'NOT_FOUND', 404)),
|
|
137
|
+
Match.tag('BadRequestError', (e) => httpError(e.message, 'BAD_REQUEST', 400)),
|
|
138
|
+
Match.tag('ValidationError', (e) => httpError(e.message, 'VALIDATION_ERROR', 400)),
|
|
139
|
+
Match.tag('ConflictError', (e) => httpError(e.message, 'CONFLICT', 409)),
|
|
140
|
+
Match.tag('ForbiddenError', (e) => httpError(e.message, 'FORBIDDEN', 403)),
|
|
141
|
+
Match.tag('ThreadTurnError', (e) =>
|
|
142
|
+
httpError(e.message, e.reason === 'conflict' ? 'CONFLICT' : 'BAD_REQUEST', e.reason === 'conflict' ? 409 : 400),
|
|
143
|
+
),
|
|
144
|
+
Match.tag('ActiveThreadRunConflictError', (e) => httpError(e.message, 'CONFLICT', 409)),
|
|
145
|
+
Match.tag('ConfigurationError', (e) => httpError(e.message, 'INTERNAL_SERVER_ERROR', 500)),
|
|
146
|
+
Match.tag('DatabaseError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
147
|
+
Match.tag('RedisError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
148
|
+
Match.tag('TimeoutError', (e) =>
|
|
149
|
+
httpError(`Operation "${e.operation}" timed out after ${e.ms}ms`, 'INTERNAL_SERVER_ERROR', 500),
|
|
150
|
+
),
|
|
151
|
+
Match.tag('LockAcquisitionError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
152
|
+
Match.tag('LockLostError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
153
|
+
Match.tag('AiGenerationError', (e) => httpError(e.message, 'INTERNAL_SERVER_ERROR', 500)),
|
|
154
|
+
Match.tag('ServiceError', () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
155
|
+
Match.exhaustive,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
export function toHttpError(error: EffectError): HttpError {
|
|
159
|
+
if (isBaseServicePersistenceError(error)) {
|
|
160
|
+
return httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return toHttpErrorMatch(error)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export { isEffectError }
|
|
@@ -2,69 +2,54 @@ import type { ErrorHandler } from 'hono'
|
|
|
2
2
|
import { HTTPException } from 'hono/http-exception'
|
|
3
3
|
import { ZodError } from 'zod'
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
type AppErrorLike = Pick<AppError, 'code' | 'message' | 'statusCode' | 'toResponse'> & { name?: string }
|
|
5
|
+
import { toValidationIssues } from '../effect/zod'
|
|
6
|
+
import { getErrorMessage, isAppErrorLike, isEffectError, toAppErrorResponse, toHttpError } from './errors'
|
|
8
7
|
|
|
9
8
|
type HonoErrorLogger = Pick<Console, 'warn' | 'error'>
|
|
10
9
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
return false
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const candidate = error as Partial<AppErrorLike>
|
|
17
|
-
return (
|
|
18
|
-
typeof candidate.code === 'string' &&
|
|
19
|
-
typeof candidate.message === 'string' &&
|
|
20
|
-
typeof candidate.statusCode === 'number' &&
|
|
21
|
-
typeof candidate.toResponse === 'function'
|
|
22
|
-
)
|
|
10
|
+
function createErrorResponse(code: string, message: string) {
|
|
11
|
+
return { error: { code, message } }
|
|
23
12
|
}
|
|
24
13
|
|
|
25
14
|
function createValidationErrorResponse(issues: Array<{ path: string; message: string }>) {
|
|
26
15
|
return { error: { code: 'VALIDATION_ERROR', message: 'Validation failed', issues } }
|
|
27
16
|
}
|
|
28
17
|
|
|
29
|
-
function createHttpErrorResponse(message: string) {
|
|
30
|
-
return { error: { code: 'HTTP_ERROR', message } }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function createServerErrorResponse(message: string) {
|
|
34
|
-
return { error: { code: 'INTERNAL_SERVER_ERROR', message } }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
18
|
export function createHonoErrorHandler(logger: HonoErrorLogger): ErrorHandler {
|
|
38
19
|
return (error, c) => {
|
|
39
|
-
const appError = error instanceof AppError || isAppErrorLike(error) ? error : null
|
|
40
|
-
|
|
41
|
-
if (appError) {
|
|
42
|
-
const log = appError.statusCode >= 500 ? logger.error : logger.warn
|
|
43
|
-
const errorName = typeof appError.name === 'string' && appError.name.length > 0 ? appError.name : 'AppError'
|
|
44
|
-
log(`Request failed: ${errorName} (${appError.code}) ${appError.message}`)
|
|
45
|
-
const { status, body } = appError.toResponse()
|
|
46
|
-
const typedStatus = status as Parameters<typeof c.json>[1]
|
|
47
|
-
return c.json(body, typedStatus)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
20
|
if (error instanceof HTTPException) {
|
|
51
21
|
const log = error.status >= 500 ? logger.error : logger.warn
|
|
52
22
|
log(`Request failed: HTTPException ${error.status} ${error.message}`)
|
|
53
|
-
return error.res ?? c.json(
|
|
23
|
+
return error.res ?? c.json(createErrorResponse('HTTP_ERROR', error.message), error.status)
|
|
54
24
|
}
|
|
55
25
|
|
|
56
26
|
if (error instanceof ZodError) {
|
|
57
27
|
logger.warn(`Request failed: ZodError ${error.message}`)
|
|
58
|
-
|
|
59
|
-
|
|
28
|
+
return c.json(createValidationErrorResponse([...toValidationIssues(error)]), 400)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isAppErrorLike(error)) {
|
|
32
|
+
const response = toAppErrorResponse(error)
|
|
33
|
+
const log = response.status >= 500 ? logger.error : logger.warn
|
|
34
|
+
log(`Request failed: ${error.name} ${error.message}`)
|
|
35
|
+
const typedStatus = response.status as Parameters<typeof c.json>[1]
|
|
36
|
+
return c.json(response.body, typedStatus)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isEffectError(error)) {
|
|
40
|
+
const httpError = toHttpError(error)
|
|
41
|
+
const log = httpError.statusCode >= 500 ? logger.error : logger.warn
|
|
42
|
+
log(`Request failed: ${error._tag} ${error.message}`)
|
|
43
|
+
const typedStatus = httpError.statusCode as Parameters<typeof c.json>[1]
|
|
44
|
+
return c.json(createErrorResponse(httpError.code, httpError.message), typedStatus)
|
|
60
45
|
}
|
|
61
46
|
|
|
62
47
|
if (error instanceof Error) {
|
|
63
48
|
logger.error(`Server error: ${error.name} ${error.message}\n${error.stack ?? ''}`)
|
|
64
|
-
return c.json(
|
|
49
|
+
return c.json(createErrorResponse('INTERNAL_SERVER_ERROR', 'Internal Server Error'), 500)
|
|
65
50
|
}
|
|
66
51
|
|
|
67
52
|
logger.error(`Server error: ${getErrorMessage(error)}`)
|
|
68
|
-
return c.json(
|
|
53
|
+
return c.json(createErrorResponse('INTERNAL_SERVER_ERROR', 'Internal Server Error'), 500)
|
|
69
54
|
}
|
|
70
55
|
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export * from './async'
|
|
2
|
+
export * from './crypto'
|
|
2
3
|
export * from './date-time'
|
|
3
|
-
export * from './env'
|
|
4
4
|
export * from './errors'
|
|
5
5
|
export * from './hono-error-handler'
|
|
6
|
+
export * from './null-proto-record'
|
|
6
7
|
export * from './sse-keepalive'
|
|
7
8
|
export {
|
|
8
9
|
CHARS_PER_TOKEN_ESTIMATE,
|