@lota-sdk/core 0.4.13 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/ai/embedding-cache.ts +17 -11
- package/src/ai-gateway/ai-gateway.ts +164 -94
- package/src/ai-gateway/index.ts +4 -1
- package/src/config/agent-defaults.ts +2 -2
- package/src/config/agent-types.ts +1 -1
- package/src/create-runtime.ts +259 -200
- package/src/db/cursor-pagination.ts +2 -9
- package/src/db/memory-store.ts +194 -175
- package/src/db/memory.ts +125 -71
- package/src/db/schema-fingerprint.ts +5 -4
- package/src/db/service-normalization.ts +4 -3
- package/src/db/service.ts +3 -2
- package/src/db/startup.ts +15 -16
- package/src/effect/errors.ts +161 -21
- package/src/effect/index.ts +0 -1
- package/src/embeddings/provider.ts +15 -7
- package/src/queues/autonomous-job.queue.ts +10 -22
- package/src/queues/delayed-node-promotion.queue.ts +8 -14
- package/src/queues/document-processor.queue.ts +13 -4
- package/src/queues/memory-consolidation.queue.ts +26 -14
- package/src/queues/plan-agent-heartbeat.queue.ts +10 -9
- package/src/queues/plan-scheduler.queue.ts +37 -15
- package/src/queues/queue-factory.ts +59 -35
- package/src/queues/standalone-worker.ts +3 -2
- package/src/redis/connection.ts +10 -3
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +5 -5
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/chat-message.ts +64 -1
- package/src/runtime/chat-run-orchestration.ts +33 -20
- package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
- package/src/runtime/context-compaction/context-compaction.ts +78 -66
- package/src/runtime/domain-layer.ts +13 -7
- package/src/runtime/execution-plan.ts +7 -3
- package/src/runtime/memory/memory-block.ts +3 -9
- package/src/runtime/memory/memory-scope.ts +3 -1
- package/src/runtime/plugin-resolution.ts +2 -1
- package/src/runtime/post-turn-side-effects.ts +6 -5
- package/src/runtime/retrieval-adapters.ts +8 -20
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +2 -4
- package/src/runtime/runtime-lifecycle.ts +56 -16
- package/src/runtime/runtime-services.ts +180 -102
- package/src/runtime/runtime-worker-registry.ts +3 -1
- package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
- package/src/runtime/social-chat/social-chat-history.ts +21 -18
- package/src/runtime/social-chat/social-chat.ts +356 -223
- package/src/runtime/specialist-runner.ts +3 -1
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
- package/src/runtime/thread-turn-context.ts +142 -102
- package/src/runtime/turn-lifecycle.ts +15 -46
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +107 -77
- package/src/services/autonomous-job.service.ts +354 -293
- package/src/services/background-work.service.ts +3 -3
- package/src/services/context-compaction.service.ts +7 -2
- package/src/services/document-chunk.service.ts +50 -32
- package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
- package/src/services/execution-plan/execution-plan.service.ts +162 -179
- package/src/services/feedback-loop.service.ts +5 -4
- package/src/services/graph-full-routing.ts +37 -36
- package/src/services/institutional-memory.service.ts +28 -30
- package/src/services/learned-skill.service.ts +107 -72
- package/src/services/memory/memory-errors.ts +4 -23
- package/src/services/memory/memory-org-memory.ts +10 -5
- package/src/services/memory/memory-rerank.ts +18 -6
- package/src/services/memory/memory.service.ts +170 -111
- package/src/services/memory/rerank.service.ts +29 -20
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +69 -75
- package/src/services/ownership-dispatcher.service.ts +40 -39
- package/src/services/plan/plan-agent-heartbeat.service.ts +26 -23
- package/src/services/plan/plan-agent-query.service.ts +39 -31
- package/src/services/plan/plan-completion-side-effects.ts +13 -17
- package/src/services/plan/plan-coordination.service.ts +2 -1
- package/src/services/plan/plan-cycle.service.ts +6 -5
- package/src/services/plan/plan-deadline.service.ts +57 -54
- package/src/services/plan/plan-event-delivery.service.ts +5 -4
- package/src/services/plan/plan-executor-graph.ts +18 -15
- package/src/services/plan/plan-executor.service.ts +235 -262
- package/src/services/plan/plan-run.service.ts +169 -93
- package/src/services/plan/plan-scheduler.service.ts +192 -202
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +23 -14
- package/src/services/plugin-executor.service.ts +5 -9
- package/src/services/queue-job.service.ts +117 -59
- package/src/services/recent-activity-title.service.ts +13 -12
- package/src/services/recent-activity.service.ts +6 -1
- package/src/services/social-chat-history.service.ts +29 -25
- package/src/services/system-executor.service.ts +5 -9
- package/src/services/thread/thread-active-run.ts +2 -2
- package/src/services/thread/thread-listing.ts +61 -57
- package/src/services/thread/thread-memory-block.ts +73 -48
- package/src/services/thread/thread-message.service.ts +76 -65
- package/src/services/thread/thread-record-store.ts +8 -8
- package/src/services/thread/thread-title.service.ts +10 -4
- package/src/services/thread/thread-turn-execution.ts +43 -45
- package/src/services/thread/thread-turn-preparation.service.ts +257 -135
- package/src/services/thread/thread-turn-streaming.ts +82 -85
- package/src/services/thread/thread-turn.ts +8 -8
- package/src/services/thread/thread.service.ts +135 -100
- package/src/services/user.service.ts +45 -48
- package/src/storage/attachment-parser.ts +6 -2
- package/src/storage/attachment-storage.service.ts +5 -6
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +10 -9
- package/src/system-agents/delegated-agent-factory.ts +30 -6
- package/src/system-agents/memory-reranker.agent.ts +10 -9
- package/src/system-agents/memory.agent.ts +10 -9
- package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
- package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
- package/src/system-agents/skill-extractor.agent.ts +13 -12
- package/src/system-agents/skill-manager.agent.ts +13 -12
- package/src/system-agents/thread-router.agent.ts +10 -5
- package/src/system-agents/title-generator.agent.ts +13 -12
- package/src/tools/fetch-webpage.tool.ts +13 -13
- package/src/tools/memory-block.tool.ts +3 -1
- package/src/tools/plan-approval.tool.ts +4 -2
- package/src/tools/read-file-parts.tool.ts +10 -4
- package/src/tools/remember-memory.tool.ts +3 -1
- package/src/tools/research-topic.tool.ts +9 -5
- package/src/tools/search-web.tool.ts +16 -16
- package/src/tools/search.tool.ts +20 -5
- package/src/tools/team-think.tool.ts +61 -38
- package/src/utils/async.ts +5 -5
- package/src/utils/errors.ts +19 -18
- package/src/utils/sse-keepalive.ts +28 -25
- package/src/workers/bootstrap.ts +75 -11
- package/src/workers/memory-consolidation.worker.ts +82 -91
- package/src/workers/organization-learning.worker.ts +14 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
- package/src/workers/skill-extraction.runner.ts +97 -61
- package/src/workers/utils/repo-structure-extractor.ts +13 -8
- package/src/workers/utils/thread-message-query.ts +24 -24
- package/src/workers/worker-utils.ts +23 -4
- package/src/effect/helpers.ts +0 -123
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CreateHelperToolLoopAgentOptions } from '@lota-sdk/shared'
|
|
2
2
|
import { ToolLoopAgent } from 'ai'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
|
|
5
5
|
import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
|
|
6
6
|
import {
|
|
7
7
|
OPENROUTER_FAST_REASONING_MODEL_ID,
|
|
@@ -30,15 +30,16 @@ Return only the title text. No quotes, no labels, no explanation.
|
|
|
30
30
|
</output-format>
|
|
31
31
|
</agent-instructions>`
|
|
32
32
|
|
|
33
|
-
export function
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
export function makeThreadTitleGeneratorAgentFactory(models: AiGatewayModels) {
|
|
34
|
+
return (options: CreateHelperToolLoopAgentOptions) =>
|
|
35
|
+
new ToolLoopAgent({
|
|
36
|
+
id: 'thread-title-generator',
|
|
37
|
+
model: models.chatModel(OPENROUTER_FAST_REASONING_MODEL_ID),
|
|
38
|
+
headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
|
|
39
|
+
providerOptions: OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
|
|
40
|
+
...resolveHelperAgentOptions(options, {
|
|
41
|
+
instructions: THREAD_TITLE_GENERATOR_PROMPT,
|
|
42
|
+
maxOutputTokens: THREAD_TITLE_MAX_TOKENS,
|
|
43
|
+
}),
|
|
44
|
+
})
|
|
44
45
|
}
|
|
@@ -12,6 +12,7 @@ import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
|
|
|
12
12
|
|
|
13
13
|
export interface FetchWebpageToolContext {
|
|
14
14
|
firecrawl: Firecrawl
|
|
15
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
class FetchWebpageToolError extends Schema.TaggedErrorClass<FetchWebpageToolError>()(
|
|
@@ -102,7 +103,7 @@ function buildFetchCitations(url: string, document: unknown): WebCitation[] {
|
|
|
102
103
|
|
|
103
104
|
export const fetchWebpageTool = {
|
|
104
105
|
name: 'fetchWebpage',
|
|
105
|
-
create: ({ firecrawl }: FetchWebpageToolContext) =>
|
|
106
|
+
create: ({ firecrawl, runPromise }: FetchWebpageToolContext) =>
|
|
106
107
|
tool({
|
|
107
108
|
description: 'Retrieve and parse a single webpage.',
|
|
108
109
|
inputSchema: z
|
|
@@ -113,18 +114,16 @@ export const fetchWebpageTool = {
|
|
|
113
114
|
maxAge: z.number().int().min(1).optional(),
|
|
114
115
|
})
|
|
115
116
|
.strict(),
|
|
116
|
-
execute: (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
url: string
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}) =>
|
|
127
|
-
Effect.runPromise(
|
|
117
|
+
execute: (
|
|
118
|
+
{
|
|
119
|
+
url,
|
|
120
|
+
formats,
|
|
121
|
+
onlyMainContent,
|
|
122
|
+
maxAge,
|
|
123
|
+
}: { url: string; formats?: z.infer<typeof FormatSchema>[]; onlyMainContent?: boolean; maxAge?: number },
|
|
124
|
+
{ abortSignal }: { abortSignal?: AbortSignal } = {},
|
|
125
|
+
) =>
|
|
126
|
+
runPromise(
|
|
128
127
|
Effect.gen(function* () {
|
|
129
128
|
const result = yield* Effect.tryPromise({
|
|
130
129
|
try: () =>
|
|
@@ -141,6 +140,7 @@ export const fetchWebpageTool = {
|
|
|
141
140
|
})
|
|
142
141
|
return { url, document: summarizeDocument(url, result), citations: buildFetchCitations(url, result) }
|
|
143
142
|
}).pipe(Effect.withSpan('tool.fetchWebpage.execute')),
|
|
143
|
+
abortSignal ? { signal: abortSignal } : undefined,
|
|
144
144
|
),
|
|
145
145
|
}),
|
|
146
146
|
} as const satisfies ToolDefinition<FetchWebpageToolContext>
|
|
@@ -18,12 +18,14 @@ export function createMemoryBlockTool({
|
|
|
18
18
|
threadId,
|
|
19
19
|
agentLabel,
|
|
20
20
|
threadService,
|
|
21
|
+
runPromise,
|
|
21
22
|
getCurrentBlock,
|
|
22
23
|
onAppend,
|
|
23
24
|
}: {
|
|
24
25
|
threadId: RecordIdRef
|
|
25
26
|
agentLabel: string
|
|
26
27
|
threadService: MemoryBlockThreadService
|
|
28
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
|
|
27
29
|
getCurrentBlock?: () => string
|
|
28
30
|
onAppend?: (value: string) => void
|
|
29
31
|
}) {
|
|
@@ -48,7 +50,7 @@ export function createMemoryBlockTool({
|
|
|
48
50
|
|
|
49
51
|
void safeEnqueue(
|
|
50
52
|
() =>
|
|
51
|
-
|
|
53
|
+
runPromise(
|
|
52
54
|
Effect.gen(function* () {
|
|
53
55
|
const updated = yield* threadService.appendMemoryBlock(threadId, prepared.formatted)
|
|
54
56
|
onAppend?.(updated)
|
|
@@ -21,14 +21,15 @@ export function createPlanApprovalTool(params: {
|
|
|
21
21
|
threadId: RecordIdRef
|
|
22
22
|
actorId: string
|
|
23
23
|
executionPlanService: PlanApprovalExecutionPlanService
|
|
24
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
|
|
24
25
|
}) {
|
|
25
26
|
return tool({
|
|
26
27
|
description:
|
|
27
28
|
'Approve, reject, or request changes to a plan that is pending approval. Use approve to start execution, reject to abort it, or modify to request a revised plan.',
|
|
28
29
|
inputSchema: PlanApprovalArgsSchema,
|
|
29
30
|
outputSchema: PlanApprovalToolResultDataSchema,
|
|
30
|
-
execute: (input) =>
|
|
31
|
-
|
|
31
|
+
execute: (input, { abortSignal }: { abortSignal?: AbortSignal } = {}) =>
|
|
32
|
+
params.runPromise(
|
|
32
33
|
Effect.gen(function* () {
|
|
33
34
|
switch (input.action) {
|
|
34
35
|
case 'approve': {
|
|
@@ -71,6 +72,7 @@ export function createPlanApprovalTool(params: {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
}),
|
|
75
|
+
abortSignal ? { signal: abortSignal } : undefined,
|
|
74
76
|
),
|
|
75
77
|
})
|
|
76
78
|
}
|
|
@@ -3,6 +3,7 @@ import { Schema, Effect } from 'effect'
|
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
|
|
5
5
|
import type { ToolDefinition } from '../ai/definitions'
|
|
6
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
6
7
|
import type { makeAttachmentService } from '../services/attachment.service'
|
|
7
8
|
import type { ReadableUploadMetadata } from '../storage/attachment-types'
|
|
8
9
|
|
|
@@ -15,6 +16,7 @@ export interface ReadFilePartsToolContext {
|
|
|
15
16
|
userId: string
|
|
16
17
|
uploads: ReadableUploadMetadata[]
|
|
17
18
|
attachmentService: ReadFilePartsAttachmentService
|
|
19
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function toUploadSummary(upload: ReadableUploadMetadata) {
|
|
@@ -26,7 +28,7 @@ function toUploadSummary(upload: ReadableUploadMetadata) {
|
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
class ReadFilePartsError extends Schema.TaggedErrorClass<ReadFilePartsError>()(
|
|
31
|
+
class ReadFilePartsError extends Schema.TaggedErrorClass<ReadFilePartsError>()(ERROR_TAGS.ReadFilePartsError, {
|
|
30
32
|
message: Schema.String,
|
|
31
33
|
}) {}
|
|
32
34
|
|
|
@@ -47,15 +49,18 @@ function resolveUploadTargetEffect(params: {
|
|
|
47
49
|
|
|
48
50
|
export const readFilePartsTool = {
|
|
49
51
|
name: 'readFileParts',
|
|
50
|
-
create: ({ orgId, userId, uploads, attachmentService }: ReadFilePartsToolContext) =>
|
|
52
|
+
create: ({ orgId, userId, uploads, attachmentService, runPromise }: ReadFilePartsToolContext) =>
|
|
51
53
|
tool({
|
|
52
54
|
description:
|
|
53
55
|
'Read uploaded file content by part. Call with no args to list uploads metadata, then call with storageKey and part (25 pages per part).',
|
|
54
56
|
inputSchema: z
|
|
55
57
|
.object({ storageKey: z.string().trim().min(1).optional(), part: z.number().int().positive().default(1) })
|
|
56
58
|
.strict(),
|
|
57
|
-
execute: (
|
|
58
|
-
|
|
59
|
+
execute: (
|
|
60
|
+
{ storageKey, part }: { storageKey?: string; part?: number },
|
|
61
|
+
{ abortSignal }: { abortSignal?: AbortSignal } = {},
|
|
62
|
+
) =>
|
|
63
|
+
runPromise(
|
|
59
64
|
Effect.gen(function* () {
|
|
60
65
|
const availableUploads = uploads.map(toUploadSummary)
|
|
61
66
|
const selected = yield* resolveUploadTargetEffect({ uploads, storageKey })
|
|
@@ -86,6 +91,7 @@ export const readFilePartsTool = {
|
|
|
86
91
|
|
|
87
92
|
return { availableUploads, selectedUpload: toUploadSummary(selected), ...parts }
|
|
88
93
|
}),
|
|
94
|
+
abortSignal ? { signal: abortSignal } : undefined,
|
|
89
95
|
),
|
|
90
96
|
}),
|
|
91
97
|
} as const satisfies ToolDefinition<ReadFilePartsToolContext>
|
|
@@ -24,10 +24,12 @@ export function createRememberMemoryTool({
|
|
|
24
24
|
orgId,
|
|
25
25
|
agentName,
|
|
26
26
|
memoryService,
|
|
27
|
+
runPromise,
|
|
27
28
|
}: {
|
|
28
29
|
orgId: RecordIdRef
|
|
29
30
|
agentName: string
|
|
30
31
|
memoryService: RememberMemoryService
|
|
32
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
|
|
31
33
|
}) {
|
|
32
34
|
return tool({
|
|
33
35
|
description:
|
|
@@ -42,7 +44,7 @@ export function createRememberMemoryTool({
|
|
|
42
44
|
const orgIdString = recordIdToString(orgId, TABLES.ORGANIZATION)
|
|
43
45
|
void safeEnqueue(
|
|
44
46
|
() =>
|
|
45
|
-
|
|
47
|
+
runPromise(
|
|
46
48
|
Effect.gen(function* () {
|
|
47
49
|
const assessment = yield* memoryService.assessMemoryCandidate({ orgId: orgIdString, content: trimmed })
|
|
48
50
|
if (!assessment) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type Firecrawl from '@mendable/firecrawl-js'
|
|
2
|
+
import type { Effect } from 'effect'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
|
|
4
5
|
import { buildAiGatewayStrictSemanticCacheHeaders } from '../ai-gateway/cache-headers'
|
|
5
6
|
import {
|
|
6
7
|
OPENROUTER_FAST_REASONING_MODEL_ID,
|
|
@@ -13,19 +14,22 @@ import { searchWebTool } from './search-web.tool'
|
|
|
13
14
|
|
|
14
15
|
export interface ResearchTopicToolContext {
|
|
15
16
|
firecrawl: Firecrawl
|
|
17
|
+
aiGatewayModels: AiGatewayModels
|
|
18
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export const researchTopicTool = createDelegatedAgentToolWithContext<ResearchTopicToolContext>({
|
|
19
22
|
id: 'researchTopic',
|
|
20
23
|
description:
|
|
21
24
|
'Delegate a research task to a dedicated research agent that searches the web, fetches pages, and returns a synthesized markdown report.',
|
|
22
|
-
model: () =>
|
|
25
|
+
model: ({ aiGatewayModels }) => aiGatewayModels.chatModel(OPENROUTER_FAST_REASONING_MODEL_ID),
|
|
23
26
|
providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
|
|
24
27
|
headers: buildAiGatewayStrictSemanticCacheHeaders('researchTopic'),
|
|
25
28
|
instructions: RESEARCHER_PROMPT,
|
|
26
|
-
createTools: ({ firecrawl }) => ({
|
|
27
|
-
searchWeb: searchWebTool.create({ firecrawl }),
|
|
28
|
-
fetchWebpage: fetchWebpageTool.create({ firecrawl }),
|
|
29
|
+
createTools: ({ firecrawl, runPromise }) => ({
|
|
30
|
+
searchWeb: searchWebTool.create({ firecrawl, runPromise }),
|
|
31
|
+
fetchWebpage: fetchWebpageTool.create({ firecrawl, runPromise }),
|
|
29
32
|
}),
|
|
30
33
|
maxSteps: 6,
|
|
34
|
+
getRunPromise: (context) => context.runPromise,
|
|
31
35
|
})
|
|
@@ -4,6 +4,7 @@ import { Effect, Schema } from 'effect'
|
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
|
|
6
6
|
import type { ToolDefinition } from '../ai/definitions'
|
|
7
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
7
8
|
import { withTimeout } from '../utils/async'
|
|
8
9
|
import { nowIsoDateTimeString } from '../utils/date-time'
|
|
9
10
|
import { readStringField, truncateOptionalText } from '../utils/string'
|
|
@@ -12,9 +13,10 @@ import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
|
|
|
12
13
|
|
|
13
14
|
export interface SearchWebToolContext {
|
|
14
15
|
firecrawl: Firecrawl
|
|
16
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
class SearchWebToolError extends Schema.TaggedErrorClass<SearchWebToolError>()(
|
|
19
|
+
class SearchWebToolError extends Schema.TaggedErrorClass<SearchWebToolError>()(ERROR_TAGS.SearchWebToolError, {
|
|
18
20
|
message: Schema.String,
|
|
19
21
|
cause: Schema.optional(Schema.Defect),
|
|
20
22
|
}) {}
|
|
@@ -139,7 +141,7 @@ function buildWebCitations(results: { web?: unknown[]; news?: unknown[]; images?
|
|
|
139
141
|
|
|
140
142
|
export const searchWebTool = {
|
|
141
143
|
name: 'searchWeb',
|
|
142
|
-
create: ({ firecrawl }: SearchWebToolContext) =>
|
|
144
|
+
create: ({ firecrawl, runPromise }: SearchWebToolContext) =>
|
|
143
145
|
tool({
|
|
144
146
|
description: 'Search the web for real-time information.',
|
|
145
147
|
inputSchema: z
|
|
@@ -151,20 +153,17 @@ export const searchWebTool = {
|
|
|
151
153
|
tbs: z.string().optional(),
|
|
152
154
|
})
|
|
153
155
|
.strict(),
|
|
154
|
-
execute: (
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
query: string
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
tbs?: string
|
|
166
|
-
}) =>
|
|
167
|
-
Effect.runPromise(
|
|
156
|
+
execute: (
|
|
157
|
+
{
|
|
158
|
+
query,
|
|
159
|
+
limit,
|
|
160
|
+
sources,
|
|
161
|
+
location,
|
|
162
|
+
tbs,
|
|
163
|
+
}: { query: string; limit?: number; sources?: ('web' | 'news' | 'images')[]; location?: string; tbs?: string },
|
|
164
|
+
{ abortSignal }: { abortSignal?: AbortSignal } = {},
|
|
165
|
+
) =>
|
|
166
|
+
runPromise(
|
|
168
167
|
Effect.gen(function* () {
|
|
169
168
|
const results = yield* Effect.tryPromise({
|
|
170
169
|
try: () =>
|
|
@@ -185,6 +184,7 @@ export const searchWebTool = {
|
|
|
185
184
|
citations: buildWebCitations(results),
|
|
186
185
|
}
|
|
187
186
|
}).pipe(Effect.withSpan('tool.searchWeb.execute')),
|
|
187
|
+
abortSignal ? { signal: abortSignal } : undefined,
|
|
188
188
|
),
|
|
189
189
|
}),
|
|
190
190
|
} as const satisfies ToolDefinition<SearchWebToolContext>
|
package/src/tools/search.tool.ts
CHANGED
|
@@ -15,18 +15,24 @@ const ConversationSearchInputSchema = z.object({ query: z.string().min(1), type:
|
|
|
15
15
|
type MemorySearchService = Pick<ReturnType<typeof createMemoryService>, 'searchAllMemoriesBatched'>
|
|
16
16
|
type ConversationSearchService = Pick<ReturnType<typeof makeThreadMessageService>, 'searchMessagesEffect'>
|
|
17
17
|
|
|
18
|
+
type SearchToolRunPromise = <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
|
|
19
|
+
|
|
18
20
|
export function createMemorySearchTool(
|
|
19
21
|
agentConfig: ResolvedAgentConfig,
|
|
20
22
|
orgIdString: string,
|
|
21
23
|
memoryService: MemorySearchService,
|
|
24
|
+
runPromise: SearchToolRunPromise,
|
|
22
25
|
agentName?: string,
|
|
23
26
|
options?: { fastMode?: boolean; allowMultiScopeRerank?: boolean },
|
|
24
27
|
) {
|
|
25
28
|
return tool({
|
|
26
29
|
description: 'Search organization and agent memories relevant to a query.',
|
|
27
30
|
inputSchema: MemorySearchInputSchema,
|
|
28
|
-
execute: (
|
|
29
|
-
|
|
31
|
+
execute: (
|
|
32
|
+
{ query }: z.infer<typeof MemorySearchInputSchema>,
|
|
33
|
+
{ abortSignal }: { abortSignal?: AbortSignal } = {},
|
|
34
|
+
) =>
|
|
35
|
+
runPromise(
|
|
30
36
|
Effect.gen(function* () {
|
|
31
37
|
const normalizedQuery = query.trim()
|
|
32
38
|
const retrieval = yield* memoryService.searchAllMemoriesBatched({
|
|
@@ -41,16 +47,24 @@ export function createMemorySearchTool(
|
|
|
41
47
|
const reminder = `Remember: you searched for "${normalizedQuery}". Use the highest-relevance retrieved lines above to answer.`
|
|
42
48
|
return { query: normalizedQuery, retrieval, reminder }
|
|
43
49
|
}),
|
|
50
|
+
abortSignal ? { signal: abortSignal } : undefined,
|
|
44
51
|
),
|
|
45
52
|
})
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
export function createConversationSearchTool(
|
|
55
|
+
export function createConversationSearchTool(
|
|
56
|
+
threadId: RecordIdRef,
|
|
57
|
+
threadMessageService: ConversationSearchService,
|
|
58
|
+
runPromise: SearchToolRunPromise,
|
|
59
|
+
) {
|
|
49
60
|
return tool({
|
|
50
61
|
description: 'Search prior chat messages by role and query text.',
|
|
51
62
|
inputSchema: ConversationSearchInputSchema,
|
|
52
|
-
execute: (
|
|
53
|
-
|
|
63
|
+
execute: (
|
|
64
|
+
{ query, type }: z.infer<typeof ConversationSearchInputSchema>,
|
|
65
|
+
{ abortSignal }: { abortSignal?: AbortSignal } = {},
|
|
66
|
+
) =>
|
|
67
|
+
runPromise(
|
|
54
68
|
Effect.gen(function* () {
|
|
55
69
|
const normalizedQuery = query.trim()
|
|
56
70
|
const results = yield* threadMessageService.searchMessagesEffect({
|
|
@@ -61,6 +75,7 @@ export function createConversationSearchTool(threadId: RecordIdRef, threadMessag
|
|
|
61
75
|
})
|
|
62
76
|
return { query: normalizedQuery, type, count: results.length, results }
|
|
63
77
|
}),
|
|
78
|
+
abortSignal ? { signal: abortSignal } : undefined,
|
|
64
79
|
),
|
|
65
80
|
})
|
|
66
81
|
}
|
|
@@ -7,7 +7,7 @@ import { aiLogger } from '../config/logger'
|
|
|
7
7
|
import type { RecordIdRef } from '../db/record-id'
|
|
8
8
|
import { recordIdToString } from '../db/record-id'
|
|
9
9
|
import { TABLES } from '../db/tables'
|
|
10
|
-
import {
|
|
10
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
11
11
|
import {
|
|
12
12
|
AgentConfigServiceTag,
|
|
13
13
|
AgentFactoryServiceTag,
|
|
@@ -35,29 +35,25 @@ function buildTeamThinkAgentToolsEffect(
|
|
|
35
35
|
return Effect.succeed({ tools: {} })
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
return
|
|
38
|
+
return Effect.tryPromise({
|
|
39
|
+
try: () => builder(params),
|
|
40
|
+
catch: (error) => new TeamThinkRuntimeError({ message: 'Failed to build team-think agent tools.', cause: error }),
|
|
41
|
+
})
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
const TEAM_THINK_AGENT_MAX_RETRIES = 1
|
|
42
45
|
const TEAM_THINK_AGENT_MAX_STEPS = 3
|
|
43
46
|
|
|
44
47
|
class TeamThinkAgentFactoryNotConfiguredError extends Schema.TaggedErrorClass<TeamThinkAgentFactoryNotConfiguredError>()(
|
|
45
|
-
'TeamThinkAgentFactoryNotConfiguredError',
|
|
48
|
+
'@lota-sdk/core/TeamThinkAgentFactoryNotConfiguredError',
|
|
46
49
|
{ agentId: Schema.String },
|
|
47
50
|
) {}
|
|
48
51
|
|
|
49
|
-
class TeamThinkRuntimeError extends Schema.TaggedErrorClass<TeamThinkRuntimeError>()(
|
|
52
|
+
class TeamThinkRuntimeError extends Schema.TaggedErrorClass<TeamThinkRuntimeError>()(ERROR_TAGS.TeamThinkRuntimeError, {
|
|
50
53
|
message: Schema.String,
|
|
51
54
|
cause: Schema.optional(Schema.Defect),
|
|
52
55
|
}) {}
|
|
53
56
|
|
|
54
|
-
function effectTryMaybeAsync<A>(
|
|
55
|
-
evaluate: () => A | PromiseLike<A>,
|
|
56
|
-
message: string,
|
|
57
|
-
): Effect.Effect<A, TeamThinkRuntimeError> {
|
|
58
|
-
return effectTryMaybeAsyncShared(evaluate, (error) => new TeamThinkRuntimeError({ message, cause: error }))
|
|
59
|
-
}
|
|
60
|
-
|
|
61
57
|
export function createTeamThinkTool(params: {
|
|
62
58
|
historyMessages: ChatMessage[]
|
|
63
59
|
latestUserMessageId: string
|
|
@@ -76,6 +72,7 @@ export function createTeamThinkTool(params: {
|
|
|
76
72
|
context?: unknown
|
|
77
73
|
toolProviders?: ToolSet
|
|
78
74
|
abortSignal: AbortSignal
|
|
75
|
+
runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
|
|
79
76
|
}) {
|
|
80
77
|
return Effect.gen(function* () {
|
|
81
78
|
const turnHooks = yield* TurnHooksServiceTag
|
|
@@ -87,35 +84,54 @@ export function createTeamThinkTool(params: {
|
|
|
87
84
|
)
|
|
88
85
|
const participantRunner: TeamConsultationParticipantRunner = {
|
|
89
86
|
buildParticipantAgent(agentId, runParams) {
|
|
90
|
-
return
|
|
87
|
+
return params.runPromise(
|
|
91
88
|
Effect.gen(function* () {
|
|
89
|
+
// Capture the fiber context of the managed runtime and replay it
|
|
90
|
+
// for observer callbacks that cross the Effect → Promise boundary
|
|
91
|
+
// (AI SDK agent observers). This preserves spans/loggers across
|
|
92
|
+
// the callback edge — it is NOT an ambient-runtime slot; the
|
|
93
|
+
// captured context lives only for this participant's fiber.
|
|
92
94
|
const currentContext = yield* Effect.context()
|
|
93
95
|
const runPromiseWithCurrentContext = Effect.runPromiseWith(currentContext)
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
const getAdditionalInstructionSections = params.getAdditionalInstructionSections
|
|
97
|
+
const dynamicInstructionSections = getAdditionalInstructionSections
|
|
98
|
+
? yield* Effect.tryPromise({
|
|
99
|
+
try: () => getAdditionalInstructionSections(),
|
|
100
|
+
catch: (error) =>
|
|
101
|
+
new TeamThinkRuntimeError({
|
|
102
|
+
message: 'Failed to load dynamic team-think instruction sections.',
|
|
103
|
+
cause: error,
|
|
104
|
+
}),
|
|
105
|
+
})
|
|
106
|
+
: undefined
|
|
107
|
+
const resolveAgent = turnHooks.resolveAgent
|
|
98
108
|
const agentResolution = asRecord(
|
|
99
|
-
|
|
100
|
-
(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
109
|
+
resolveAgent
|
|
110
|
+
? yield* Effect.tryPromise({
|
|
111
|
+
try: () =>
|
|
112
|
+
resolveAgent({
|
|
113
|
+
agentId,
|
|
114
|
+
mode: 'fixedThreadMode',
|
|
115
|
+
thread: null,
|
|
116
|
+
threadRef: params.threadId,
|
|
117
|
+
orgRef: params.orgId,
|
|
118
|
+
userRef: params.userId,
|
|
119
|
+
onboardingActive: false,
|
|
120
|
+
linearInstalled: false,
|
|
121
|
+
githubInstalled: params.githubInstalled,
|
|
122
|
+
additionalInstructionSections: mergeInstructionSections(
|
|
123
|
+
dynamicInstructionSections,
|
|
124
|
+
params.additionalInstructionSections,
|
|
125
|
+
),
|
|
126
|
+
context: (params.context as Record<string, unknown> | null | undefined) ?? null,
|
|
127
|
+
}),
|
|
128
|
+
catch: (error) =>
|
|
129
|
+
new TeamThinkRuntimeError({
|
|
130
|
+
message: 'Failed to resolve team-think participant agent.',
|
|
131
|
+
cause: error,
|
|
132
|
+
}),
|
|
133
|
+
})
|
|
134
|
+
: undefined,
|
|
119
135
|
)
|
|
120
136
|
const resolvedAgentId = readOptionalString(agentResolution?.agentId) ?? agentId
|
|
121
137
|
const config = agentFactoryConfig.getAgentRuntimeConfig({
|
|
@@ -169,9 +185,16 @@ export function createTeamThinkTool(params: {
|
|
|
169
185
|
stopWhen: [stepCountIs(maxSteps)],
|
|
170
186
|
})
|
|
171
187
|
const observer = {
|
|
172
|
-
run: <T>(fn: () =>
|
|
188
|
+
run: <T>(fn: () => Promise<T>): Promise<T> =>
|
|
173
189
|
runPromiseWithCurrentContext(
|
|
174
|
-
|
|
190
|
+
Effect.tryPromise({
|
|
191
|
+
try: () => fn(),
|
|
192
|
+
catch: (error) =>
|
|
193
|
+
new TeamThinkRuntimeError({
|
|
194
|
+
message: `Team-think participant run failed (${agentId}).`,
|
|
195
|
+
cause: error,
|
|
196
|
+
}),
|
|
197
|
+
}),
|
|
175
198
|
),
|
|
176
199
|
recordError: (error: unknown) => {
|
|
177
200
|
aiLogger.error`Team-think participant failed (${agentId}): ${error}`
|
package/src/utils/async.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { Cause, Duration, Effect, Exit, Schema } from 'effect'
|
|
2
2
|
|
|
3
3
|
import { serverLogger } from '../config/logger'
|
|
4
|
-
import { TimeoutError } from '../effect/errors'
|
|
4
|
+
import { ERROR_TAGS, TimeoutError } from '../effect/errors'
|
|
5
5
|
import { getErrorMessage, toError } from './errors'
|
|
6
6
|
|
|
7
|
-
class TimedOperationError extends Schema.TaggedErrorClass<TimedOperationError>()(
|
|
7
|
+
class TimedOperationError extends Schema.TaggedErrorClass<TimedOperationError>()(ERROR_TAGS.TimedOperationError, {
|
|
8
8
|
operation: Schema.String,
|
|
9
9
|
cause: Schema.Defect,
|
|
10
10
|
}) {}
|
|
11
11
|
|
|
12
12
|
function isTimedOperationError(error: unknown): error is TimedOperationError {
|
|
13
|
-
return typeof error === 'object' && error !== null && '_tag' in error && error._tag ===
|
|
13
|
+
return typeof error === 'object' && error !== null && '_tag' in error && error._tag === ERROR_TAGS.TimedOperationError
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function withTimeout<T>(promise: Promise<T>, ms: number, operation: string): Promise<T> {
|
|
@@ -46,8 +46,8 @@ export function createSafeEnqueue(logger: { warn: (message: string) => void }) {
|
|
|
46
46
|
|
|
47
47
|
const _defaultSafeEnqueue = createSafeEnqueue({ warn: (message: string) => serverLogger.warn`${message}` })
|
|
48
48
|
export function safeEnqueue<T>(
|
|
49
|
-
operation: () =>
|
|
49
|
+
operation: () => Promise<T>,
|
|
50
50
|
options: { operationName: string; onError?: (error: unknown) => void; logPrefix?: string },
|
|
51
51
|
): Promise<T | void> {
|
|
52
|
-
return _defaultSafeEnqueue(
|
|
52
|
+
return _defaultSafeEnqueue(operation, options)
|
|
53
53
|
}
|
package/src/utils/errors.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { getErrorMessage } from '@lota-sdk/shared'
|
|
2
2
|
import { Data, Match } from 'effect'
|
|
3
3
|
|
|
4
|
-
import type { EffectError, ValidationIssue } from '../effect/errors'
|
|
5
|
-
import {
|
|
4
|
+
import type { BaseServicePersistenceError, EffectError, ValidationIssue } from '../effect/errors'
|
|
5
|
+
import { ERROR_TAGS, isEffectError } from '../effect/errors'
|
|
6
6
|
|
|
7
7
|
export function toError(value: unknown): Error {
|
|
8
8
|
return value instanceof Error ? value : new Error(String(value))
|
|
@@ -129,29 +129,30 @@ export function toAppErrorResponse(error: AppErrorLike): AppErrorResponse {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
function isBaseServicePersistenceError(error: EffectError): error is BaseServicePersistenceError {
|
|
132
|
-
return error._tag === BaseServicePersistenceError
|
|
132
|
+
return error._tag === ERROR_TAGS.BaseServicePersistenceError
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
const toHttpErrorMatch = Match.type<Exclude<EffectError, BaseServicePersistenceError>>().pipe(
|
|
136
|
-
Match.tag(
|
|
137
|
-
Match.tag(
|
|
138
|
-
Match.tag(
|
|
139
|
-
Match.tag(
|
|
140
|
-
Match.tag(
|
|
141
|
-
Match.tag(
|
|
136
|
+
Match.tag(ERROR_TAGS.NotFoundError, (e) => httpError(e.message, 'NOT_FOUND', 404)),
|
|
137
|
+
Match.tag(ERROR_TAGS.BadRequestError, (e) => httpError(e.message, 'BAD_REQUEST', 400)),
|
|
138
|
+
Match.tag(ERROR_TAGS.ValidationError, (e) => httpError(e.message, 'VALIDATION_ERROR', 400)),
|
|
139
|
+
Match.tag(ERROR_TAGS.ConflictError, (e) => httpError(e.message, 'CONFLICT', 409)),
|
|
140
|
+
Match.tag(ERROR_TAGS.ForbiddenError, (e) => httpError(e.message, 'FORBIDDEN', 403)),
|
|
141
|
+
Match.tag(ERROR_TAGS.ThreadTurnError, (e) =>
|
|
142
142
|
httpError(e.message, e.reason === 'conflict' ? 'CONFLICT' : 'BAD_REQUEST', e.reason === 'conflict' ? 409 : 400),
|
|
143
143
|
),
|
|
144
|
-
Match.tag(
|
|
145
|
-
Match.tag(
|
|
146
|
-
Match.tag(
|
|
147
|
-
Match.tag(
|
|
148
|
-
Match.tag(
|
|
144
|
+
Match.tag(ERROR_TAGS.ActiveThreadRunConflictError, (e) => httpError(e.message, 'CONFLICT', 409)),
|
|
145
|
+
Match.tag(ERROR_TAGS.ConfigurationError, (e) => httpError(e.message, 'INTERNAL_SERVER_ERROR', 500)),
|
|
146
|
+
Match.tag(ERROR_TAGS.DatabaseError, () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
147
|
+
Match.tag(ERROR_TAGS.RedisError, () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
148
|
+
Match.tag(ERROR_TAGS.RuntimeLifecycleError, () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
149
|
+
Match.tag(ERROR_TAGS.TimeoutError, (e) =>
|
|
149
150
|
httpError(`Operation "${e.operation}" timed out after ${e.ms}ms`, 'INTERNAL_SERVER_ERROR', 500),
|
|
150
151
|
),
|
|
151
|
-
Match.tag(
|
|
152
|
-
Match.tag(
|
|
153
|
-
Match.tag(
|
|
154
|
-
Match.tag(
|
|
152
|
+
Match.tag(ERROR_TAGS.LockAcquisitionError, () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
153
|
+
Match.tag(ERROR_TAGS.LockLostError, () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
154
|
+
Match.tag(ERROR_TAGS.AiGenerationError, (e) => httpError(e.message, 'INTERNAL_SERVER_ERROR', 500)),
|
|
155
|
+
Match.tag(ERROR_TAGS.ServiceError, () => httpError('Internal server error', 'INTERNAL_SERVER_ERROR', 500)),
|
|
155
156
|
Match.exhaustive,
|
|
156
157
|
)
|
|
157
158
|
|