@lota-sdk/core 0.4.14 → 0.4.16
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 +2 -2
- package/src/ai-gateway/ai-gateway.ts +36 -17
- package/src/config/constants.ts +1 -1
- package/src/create-runtime.ts +8 -5
- package/src/queues/plan-agent-heartbeat.queue.ts +40 -24
- package/src/runtime/domain-layer.ts +6 -6
- package/src/services/plan/plan-agent-heartbeat.service.ts +7 -12
- package/src/services/thread/thread-turn-streaming.ts +1 -7
- package/src/services/thread/thread-turn.ts +10 -8
- package/src/system-agents/thread-router.agent.ts +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lota-sdk/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@ai-sdk/openai": "^3.0.53",
|
|
32
32
|
"@chat-adapter/slack": "^4.26.0",
|
|
33
33
|
"@chat-adapter/state-ioredis": "^4.26.0",
|
|
34
|
-
"@lota-sdk/shared": "0.4.
|
|
34
|
+
"@lota-sdk/shared": "0.4.16",
|
|
35
35
|
"@mendable/firecrawl-js": "^4.18.3",
|
|
36
36
|
"@surrealdb/node": "^3.0.3",
|
|
37
37
|
"ai": "^6.0.168",
|
|
@@ -977,10 +977,26 @@ export type AiGatewayDeps = {
|
|
|
977
977
|
runFork: AiGatewayRunFork
|
|
978
978
|
}
|
|
979
979
|
|
|
980
|
+
let defaultAiGatewayDeps: AiGatewayDeps | undefined
|
|
981
|
+
|
|
982
|
+
export function setDefaultAiGatewayDeps(deps: AiGatewayDeps): void {
|
|
983
|
+
defaultAiGatewayDeps = deps
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function resolveAiGatewayDeps(deps: AiGatewayDeps | undefined): AiGatewayDeps {
|
|
987
|
+
if (deps) return deps
|
|
988
|
+
if (defaultAiGatewayDeps) return defaultAiGatewayDeps
|
|
989
|
+
|
|
990
|
+
throw new Error(
|
|
991
|
+
'[ai-gateway] aiGatewayModel(...) was used before createLotaRuntime initialized the AI gateway. ' +
|
|
992
|
+
'Create a LotaRuntime first, or use runtime.ai.model/chatModel/embeddingModel.',
|
|
993
|
+
)
|
|
994
|
+
}
|
|
995
|
+
|
|
980
996
|
function createAiGatewayLanguageModelMiddleware(
|
|
981
997
|
modelId: string,
|
|
982
998
|
providerId: string,
|
|
983
|
-
deps
|
|
999
|
+
deps?: AiGatewayDeps,
|
|
984
1000
|
): LanguageModelMiddleware {
|
|
985
1001
|
return {
|
|
986
1002
|
specificationVersion: 'v3',
|
|
@@ -991,10 +1007,11 @@ function createAiGatewayLanguageModelMiddleware(
|
|
|
991
1007
|
),
|
|
992
1008
|
),
|
|
993
1009
|
wrapGenerate: ({ params }) => {
|
|
994
|
-
const
|
|
995
|
-
|
|
1010
|
+
const resolvedDeps = resolveAiGatewayDeps(deps)
|
|
1011
|
+
const model = resolveProviderModel(resolvedDeps.gateway.provider, modelId, providerId)
|
|
1012
|
+
return resolvedDeps.runPromise(
|
|
996
1013
|
withAiGatewayConcurrency(
|
|
997
|
-
executeGenerateAttemptPlan(
|
|
1014
|
+
executeGenerateAttemptPlan(resolvedDeps.runtimeConfig, modelId, params, () => model.doGenerate(params)).pipe(
|
|
998
1015
|
Effect.map(({ result }) => ({
|
|
999
1016
|
...result,
|
|
1000
1017
|
content: injectAiGatewayChatReasoningContent(
|
|
@@ -1003,14 +1020,15 @@ function createAiGatewayLanguageModelMiddleware(
|
|
|
1003
1020
|
),
|
|
1004
1021
|
})),
|
|
1005
1022
|
),
|
|
1006
|
-
).pipe(Effect.provideService(AiGatewayTag,
|
|
1023
|
+
).pipe(Effect.provideService(AiGatewayTag, resolvedDeps.gateway)),
|
|
1007
1024
|
)
|
|
1008
1025
|
},
|
|
1009
1026
|
wrapStream: ({ params }) => {
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1027
|
+
const resolvedDeps = resolveAiGatewayDeps(deps)
|
|
1028
|
+
const model = resolveProviderModel(resolvedDeps.gateway.provider, modelId, providerId)
|
|
1029
|
+
return resolvedDeps.runPromise(
|
|
1012
1030
|
withAiGatewayStreamConcurrency(
|
|
1013
|
-
executeStreamAttemptPlan(
|
|
1031
|
+
executeStreamAttemptPlan(resolvedDeps.runtimeConfig, modelId, params, () => model.doStream(params)).pipe(
|
|
1014
1032
|
Effect.map((attempt) => ({
|
|
1015
1033
|
...attempt,
|
|
1016
1034
|
result: isReasoningEnabled(params)
|
|
@@ -1018,10 +1036,10 @@ function createAiGatewayLanguageModelMiddleware(
|
|
|
1018
1036
|
: attempt.result,
|
|
1019
1037
|
})),
|
|
1020
1038
|
),
|
|
1021
|
-
|
|
1039
|
+
resolvedDeps.runFork,
|
|
1022
1040
|
).pipe(
|
|
1023
1041
|
Effect.map(({ result }) => result),
|
|
1024
|
-
Effect.provideService(AiGatewayTag,
|
|
1042
|
+
Effect.provideService(AiGatewayTag, resolvedDeps.gateway),
|
|
1025
1043
|
),
|
|
1026
1044
|
)
|
|
1027
1045
|
},
|
|
@@ -1097,7 +1115,7 @@ function createAiGatewayEmbeddingModelPlaceholder(modelId: string): AiGatewayEmb
|
|
|
1097
1115
|
}
|
|
1098
1116
|
}
|
|
1099
1117
|
|
|
1100
|
-
export function aiGatewayModel(modelId: string, deps
|
|
1118
|
+
export function aiGatewayModel(modelId: string, deps?: AiGatewayDeps) {
|
|
1101
1119
|
if (isOpenRouterModel(modelId)) {
|
|
1102
1120
|
return aiGatewayChatModel(modelId, deps)
|
|
1103
1121
|
}
|
|
@@ -1110,11 +1128,11 @@ export function aiGatewayModel(modelId: string, deps: AiGatewayDeps) {
|
|
|
1110
1128
|
)
|
|
1111
1129
|
}
|
|
1112
1130
|
|
|
1113
|
-
export function aiGatewayOpenRouterResponseHealingModel(modelId: string, deps
|
|
1131
|
+
export function aiGatewayOpenRouterResponseHealingModel(modelId: string, deps?: AiGatewayDeps) {
|
|
1114
1132
|
return aiGatewayChatModel(modelId, deps)
|
|
1115
1133
|
}
|
|
1116
1134
|
|
|
1117
|
-
export function aiGatewayChatModel(modelId: string, deps
|
|
1135
|
+
export function aiGatewayChatModel(modelId: string, deps?: AiGatewayDeps) {
|
|
1118
1136
|
return withAiGatewayDevTools(
|
|
1119
1137
|
wrapLanguageModel({
|
|
1120
1138
|
model: createAiGatewayLanguageModelPlaceholder(modelId, OPENAI_CHAT_PROVIDER_ID),
|
|
@@ -1123,14 +1141,15 @@ export function aiGatewayChatModel(modelId: string, deps: AiGatewayDeps) {
|
|
|
1123
1141
|
)
|
|
1124
1142
|
}
|
|
1125
1143
|
|
|
1126
|
-
export function aiGatewayEmbeddingModel(modelId: string, deps
|
|
1144
|
+
export function aiGatewayEmbeddingModel(modelId: string, deps?: AiGatewayDeps) {
|
|
1127
1145
|
return wrapEmbeddingModel({
|
|
1128
1146
|
model: createAiGatewayEmbeddingModelPlaceholder(modelId),
|
|
1129
1147
|
middleware: {
|
|
1130
1148
|
specificationVersion: 'v3',
|
|
1131
1149
|
wrapEmbed: ({ params }) => {
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1150
|
+
const resolvedDeps = resolveAiGatewayDeps(deps)
|
|
1151
|
+
const embeddingModel = resolvedDeps.gateway.provider.embeddingModel(modelId)
|
|
1152
|
+
return resolvedDeps.runPromise(
|
|
1134
1153
|
withAiGatewayConcurrency(
|
|
1135
1154
|
withAiGatewayResilience(
|
|
1136
1155
|
'ai-gateway.embed',
|
|
@@ -1139,7 +1158,7 @@ export function aiGatewayEmbeddingModel(modelId: string, deps: AiGatewayDeps) {
|
|
|
1139
1158
|
catch: (cause) => classifyAiGatewayError('ai-gateway.embed', cause),
|
|
1140
1159
|
}),
|
|
1141
1160
|
).pipe(Effect.withSpan('AiGateway.embed'), Effect.annotateSpans({ modelId })),
|
|
1142
|
-
).pipe(Effect.provideService(AiGatewayTag,
|
|
1161
|
+
).pipe(Effect.provideService(AiGatewayTag, resolvedDeps.gateway)),
|
|
1143
1162
|
)
|
|
1144
1163
|
},
|
|
1145
1164
|
},
|
package/src/config/constants.ts
CHANGED
|
@@ -6,7 +6,7 @@ export const MEMORY = {
|
|
|
6
6
|
MAX_KNN_LIMIT: 100,
|
|
7
7
|
} as const
|
|
8
8
|
|
|
9
|
-
export const DEFAULT_AI_GATEWAY_URL = 'https://
|
|
9
|
+
export const DEFAULT_AI_GATEWAY_URL = 'https://aigateway.dev.ventur-os.com' as const
|
|
10
10
|
|
|
11
11
|
/** Validates that a value is a safe integer for KNN queries. Throws if validation fails. */
|
|
12
12
|
export function validateKnnLimit(limit: unknown): number {
|
package/src/create-runtime.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
RuntimeBridgeTag,
|
|
9
9
|
createAiGatewayModels,
|
|
10
10
|
makeAiGatewayService,
|
|
11
|
+
setDefaultAiGatewayDeps,
|
|
11
12
|
} from './ai-gateway/ai-gateway'
|
|
12
13
|
import type { AiGatewayModels, RuntimeBridge } from './ai-gateway/ai-gateway'
|
|
13
14
|
import { computeSchemaFingerprint } from './db/schema-fingerprint'
|
|
@@ -79,9 +80,9 @@ export interface LotaRuntime {
|
|
|
79
80
|
config: ResolvedLotaRuntimeConfig
|
|
80
81
|
plugins: Record<string, LotaPlugin>
|
|
81
82
|
systemExecutors: Record<string, SystemNodeExecutor>
|
|
82
|
-
/** Pre-bound AI gateway model factories.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
83
|
+
/** Pre-bound AI gateway model factories. Prefer these inside runtime-bound
|
|
84
|
+
* services; the module-level helpers are bound during runtime creation for
|
|
85
|
+
* module-scope agent definitions. */
|
|
85
86
|
ai: AiGatewayModels
|
|
86
87
|
connectPluginDatabases(): Promise<void>
|
|
87
88
|
connect(): Promise<void>
|
|
@@ -130,12 +131,14 @@ export async function createLotaRuntime(config: LotaRuntimeConfig): Promise<Lota
|
|
|
130
131
|
runPromise: (effect, options) => managedRuntime.runPromise(effect, options),
|
|
131
132
|
runFork: (effect) => managedRuntime.runFork(effect),
|
|
132
133
|
}
|
|
133
|
-
const
|
|
134
|
+
const aiGatewayDeps = {
|
|
134
135
|
gateway: aiGateway,
|
|
135
136
|
runtimeConfig,
|
|
136
137
|
runPromise: runtimeBridge.runPromise,
|
|
137
138
|
runFork: runtimeBridge.runFork,
|
|
138
|
-
}
|
|
139
|
+
}
|
|
140
|
+
const aiGatewayModels = createAiGatewayModels(aiGatewayDeps)
|
|
141
|
+
setDefaultAiGatewayDeps(aiGatewayDeps)
|
|
139
142
|
|
|
140
143
|
if (!Effect.runSync(Deferred.succeed(runtimeBridgeDeferred, runtimeBridge))) {
|
|
141
144
|
throw new ServiceError({ message: 'Failed to initialize the runtime bridge.' })
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Job } from 'bullmq'
|
|
1
|
+
import type { Job, JobsOptions } from 'bullmq'
|
|
2
2
|
import { Effect, Schema } from 'effect'
|
|
3
3
|
import type { Context } from 'effect'
|
|
4
4
|
import type IORedis from 'ioredis'
|
|
@@ -44,6 +44,7 @@ export type PlanAgentHeartbeatJob = PlanAgentHeartbeatWakeJob | PlanAgentHeartbe
|
|
|
44
44
|
export const PLAN_AGENT_HEARTBEAT_QUEUE = 'plan-agent-heartbeat'
|
|
45
45
|
const PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS = 30_000
|
|
46
46
|
const PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID = 'plan-agent-heartbeat-sweep'
|
|
47
|
+
const REUSABLE_HEARTBEAT_JOB_STATES = new Set(['completed', 'failed'])
|
|
47
48
|
|
|
48
49
|
export interface PlanAgentHeartbeatWorkerDeps {
|
|
49
50
|
databaseService: Context.Service.Shape<typeof DatabaseServiceTag>
|
|
@@ -84,35 +85,50 @@ export function makePlanAgentHeartbeatQueueRuntime(
|
|
|
84
85
|
): PlanAgentHeartbeatQueueRuntime {
|
|
85
86
|
const { connectionProvider, queueJobService } = params
|
|
86
87
|
|
|
88
|
+
const enqueueReusableHeartbeatJob = async (job: PlanAgentHeartbeatJob, options: JobsOptions): Promise<void> => {
|
|
89
|
+
const jobId = typeof options.jobId === 'string' ? options.jobId : null
|
|
90
|
+
if (jobId) {
|
|
91
|
+
const existing = await queue.getQueue().getJob(jobId)
|
|
92
|
+
if (existing) {
|
|
93
|
+
const state = await existing.getState()
|
|
94
|
+
if (REUSABLE_HEARTBEAT_JOB_STATES.has(state)) {
|
|
95
|
+
await existing.remove()
|
|
96
|
+
serverLogger.info`Removed terminal Plan agent heartbeat job before re-enqueue (${jobId}, state=${state})`
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await queue.enqueue(job, options)
|
|
102
|
+
}
|
|
103
|
+
|
|
87
104
|
const enqueueDelayedSweep = (delayMs = PLAN_AGENT_HEARTBEAT_SWEEP_INTERVAL_MS): Promise<void> =>
|
|
88
|
-
|
|
105
|
+
enqueueReusableHeartbeatJob({ type: 'sweep' }, { delay: delayMs, jobId: PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID })
|
|
89
106
|
|
|
90
107
|
const processPlanAgentHeartbeatJob = (
|
|
91
108
|
deps: PlanAgentHeartbeatWorkerDeps,
|
|
92
109
|
job: Job<PlanAgentHeartbeatJob>,
|
|
93
110
|
): Promise<void> => {
|
|
94
111
|
const { planAgentHeartbeatService: service } = deps
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
112
|
+
const program: Effect.Effect<void, unknown, never> = Effect.gen(function* () {
|
|
113
|
+
if (job.data.type === 'wake-node') {
|
|
114
|
+
const wakeJob: PlanAgentHeartbeatWakeJob = job.data
|
|
115
|
+
yield* service.wakeNode(wakeJob)
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
yield* service.sweep({ organizationId: job.data.organizationId })
|
|
120
|
+
if (!job.data.organizationId) {
|
|
121
|
+
yield* Effect.tryPromise({
|
|
122
|
+
try: () => enqueueDelayedSweep(),
|
|
123
|
+
catch: (cause) =>
|
|
124
|
+
new PlanAgentHeartbeatQueueError({
|
|
125
|
+
message: 'Failed to enqueue delayed plan-agent heartbeat sweep.',
|
|
126
|
+
cause,
|
|
127
|
+
}),
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
return Effect.runPromise(program)
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
const queue = createQueueFactoryWithDeps<PlanAgentHeartbeatJob, PlanAgentHeartbeatWorkerDeps>({
|
|
@@ -130,7 +146,7 @@ export function makePlanAgentHeartbeatQueueRuntime(
|
|
|
130
146
|
|
|
131
147
|
return {
|
|
132
148
|
enqueuePlanAgentHeartbeatWake: (wakeParams) =>
|
|
133
|
-
|
|
149
|
+
enqueueReusableHeartbeatJob({ type: 'wake-node', ...wakeParams }, { jobId: buildWakeJobId(wakeParams) }),
|
|
134
150
|
startWorker: (options) => {
|
|
135
151
|
const handle = queue.startWorker({
|
|
136
152
|
deps: options.deps,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builds the domain-service Layer tree for `createLotaRuntime`.
|
|
3
3
|
*
|
|
4
|
-
* The services form a
|
|
4
|
+
* The services form a tiered dependency graph on top of the infrastructure
|
|
5
5
|
* layer (config, logging, database, redis, agents, threads, extensions).
|
|
6
6
|
* Each tier is provided with the accumulated context of earlier tiers so
|
|
7
7
|
* every service resolves cleanly when the ManagedRuntime eagerly loads them.
|
|
@@ -184,10 +184,7 @@ export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer
|
|
|
184
184
|
const tier5 = provide(PlanExecutorServiceLive, ctx4)
|
|
185
185
|
const ctx5 = Layer.mergeAll(ctx4, tier5)
|
|
186
186
|
|
|
187
|
-
const tier6 = provide(
|
|
188
|
-
Layer.mergeAll(OwnershipDispatcherServiceLive, PlanAgentHeartbeatServiceLive, PlanDeadlineServiceLive),
|
|
189
|
-
ctx5,
|
|
190
|
-
)
|
|
187
|
+
const tier6 = provide(Layer.mergeAll(OwnershipDispatcherServiceLive, PlanDeadlineServiceLive), ctx5)
|
|
191
188
|
const ctx6 = Layer.mergeAll(ctx5, tier6)
|
|
192
189
|
|
|
193
190
|
const tier7 = provide(Layer.mergeAll(ExecutionPlanServiceLive, GlobalOrchestratorServiceLive), ctx6)
|
|
@@ -205,5 +202,8 @@ export function buildDomainServiceLayer(infrastructureLayer: InfrastructureLayer
|
|
|
205
202
|
const ctx8 = Layer.mergeAll(ctx7, tier8)
|
|
206
203
|
|
|
207
204
|
const tier9 = provide(Layer.mergeAll(PlanCycleServiceLive, ThreadTurnServiceLive), ctx8)
|
|
208
|
-
|
|
205
|
+
const ctx9 = Layer.mergeAll(ctx8, tier9)
|
|
206
|
+
|
|
207
|
+
const tier10 = provide(PlanAgentHeartbeatServiceLive, ctx9)
|
|
208
|
+
return Layer.mergeAll(ctx9, tier10)
|
|
209
209
|
}
|
|
@@ -11,6 +11,8 @@ import { LotaQueuesServiceTag } from '../../queues/queues.service'
|
|
|
11
11
|
import type { RedisConnectionManager } from '../../redis/connection'
|
|
12
12
|
import { withLeaseLock } from '../../redis/redis-lease-lock'
|
|
13
13
|
import { resolvePlanNodeExecutionVisibility } from '../../runtime/execution-plan-visibility'
|
|
14
|
+
import type { makeThreadTurnService } from '../thread/thread-turn'
|
|
15
|
+
import { ThreadTurnServiceTag } from '../thread/thread-turn'
|
|
14
16
|
import type { makeThreadService } from '../thread/thread.service'
|
|
15
17
|
import { ThreadServiceTag } from '../thread/thread.service'
|
|
16
18
|
import type { makePlanAgentQueryService } from './plan-agent-query.service'
|
|
@@ -51,24 +53,24 @@ function heartbeatServiceEffect<A, E, R = never>(
|
|
|
51
53
|
|
|
52
54
|
interface PlanAgentHeartbeatDeps {
|
|
53
55
|
agentConfig: ResolvedAgentConfig
|
|
54
|
-
provideCurrentContext: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, never>
|
|
55
56
|
redis: RedisConnectionManager
|
|
56
57
|
planAgentQueryService: ReturnType<typeof makePlanAgentQueryService>
|
|
57
58
|
planExecutorService: ReturnType<typeof makePlanExecutorService>
|
|
58
59
|
planRunService: ReturnType<typeof makePlanRunService>
|
|
59
60
|
threadService: ReturnType<typeof makeThreadService>
|
|
61
|
+
threadTurnService: ReturnType<typeof makeThreadTurnService>
|
|
60
62
|
planAgentHeartbeatQueue: PlanAgentHeartbeatQueueRuntime
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
|
|
64
66
|
const {
|
|
65
67
|
agentConfig,
|
|
66
|
-
provideCurrentContext,
|
|
67
68
|
planExecutorService,
|
|
68
69
|
planRunService,
|
|
69
70
|
redis,
|
|
70
71
|
planAgentQueryService,
|
|
71
72
|
threadService,
|
|
73
|
+
threadTurnService,
|
|
72
74
|
planAgentHeartbeatQueue,
|
|
73
75
|
} = deps
|
|
74
76
|
|
|
@@ -149,14 +151,9 @@ export function makePlanAgentHeartbeatService(deps: PlanAgentHeartbeatDeps) {
|
|
|
149
151
|
)
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
const { triggerPlanNodeTurn } = yield* Effect.tryPromise({
|
|
153
|
-
try: () => import('../thread/thread-turn'),
|
|
154
|
-
catch: (cause) => new PlanAgentHeartbeatError({ operation: 'import-thread-turn', cause }),
|
|
155
|
-
})
|
|
156
|
-
|
|
157
154
|
yield* heartbeatServiceEffect(
|
|
158
155
|
'trigger-plan-node-turn',
|
|
159
|
-
|
|
156
|
+
threadTurnService.triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId }),
|
|
160
157
|
)
|
|
161
158
|
return true
|
|
162
159
|
}),
|
|
@@ -227,24 +224,22 @@ export class PlanAgentHeartbeatServiceTag extends Context.Service<
|
|
|
227
224
|
export const PlanAgentHeartbeatServiceLive = Layer.effect(
|
|
228
225
|
PlanAgentHeartbeatServiceTag,
|
|
229
226
|
Effect.gen(function* () {
|
|
230
|
-
const currentContext = yield* Effect.context()
|
|
231
|
-
const provideCurrentContext = <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, never> =>
|
|
232
|
-
effect.pipe(Effect.provide(currentContext)) as Effect.Effect<A, E, never>
|
|
233
227
|
const agentConfig = yield* AgentConfigServiceTag
|
|
234
228
|
const redis = yield* RedisServiceTag
|
|
235
229
|
const planAgentQueryService = yield* PlanAgentQueryServiceTag
|
|
236
230
|
const planRunService = yield* PlanRunServiceTag
|
|
237
231
|
const planExecutor = yield* PlanExecutorServiceTag
|
|
238
232
|
const threadSvc = yield* ThreadServiceTag
|
|
233
|
+
const threadTurnSvc = yield* ThreadTurnServiceTag
|
|
239
234
|
const queues = yield* LotaQueuesServiceTag
|
|
240
235
|
return makePlanAgentHeartbeatService({
|
|
241
236
|
agentConfig,
|
|
242
|
-
provideCurrentContext,
|
|
243
237
|
redis,
|
|
244
238
|
planAgentQueryService,
|
|
245
239
|
planExecutorService: planExecutor,
|
|
246
240
|
planRunService,
|
|
247
241
|
threadService: threadSvc,
|
|
242
|
+
threadTurnService: threadTurnSvc,
|
|
248
243
|
planAgentHeartbeatQueue: queues.planAgentHeartbeat,
|
|
249
244
|
})
|
|
250
245
|
}),
|
|
@@ -74,7 +74,7 @@ function isTextTokenChunkType(chunkType: string | undefined): boolean {
|
|
|
74
74
|
return chunkType === 'text-delta'
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function buildFallbackResponseMessage(
|
|
77
|
+
export function buildFallbackResponseMessage(
|
|
78
78
|
result: ToolLoopGenerateResult,
|
|
79
79
|
): Effect.Effect<ChatMessage, ThreadTurnStreamingError> {
|
|
80
80
|
const parts: ChatMessage['parts'] = []
|
|
@@ -96,12 +96,6 @@ function buildFallbackResponseMessage(
|
|
|
96
96
|
parts.push({ type: 'text', text })
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
if (parts.length === 0) {
|
|
100
|
-
return Effect.fail(
|
|
101
|
-
new ThreadTurnStreamingError({ message: 'Agent generate fallback did not produce any response parts.' }),
|
|
102
|
-
)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
99
|
return Effect.succeed({ id: Bun.randomUUIDv7(), role: 'assistant', parts })
|
|
106
100
|
}
|
|
107
101
|
|
|
@@ -428,14 +428,16 @@ function triggerPlanNodeTurnWith(
|
|
|
428
428
|
deps: ThreadTurnDeps,
|
|
429
429
|
params: { runId: string; nodeId: string; abortSignal?: AbortSignal; streamId?: string },
|
|
430
430
|
) {
|
|
431
|
-
return
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
431
|
+
return deps.provideCurrentContext(
|
|
432
|
+
triggerPlanNodeTurnEffect(deps, params).pipe(
|
|
433
|
+
Effect.annotateSpans(
|
|
434
|
+
compactSpanAttributes({
|
|
435
|
+
turnKind: 'planTurn',
|
|
436
|
+
streamId: params.streamId,
|
|
437
|
+
planRunId: params.runId,
|
|
438
|
+
planNodeId: params.nodeId,
|
|
439
|
+
}),
|
|
440
|
+
),
|
|
439
441
|
),
|
|
440
442
|
)
|
|
441
443
|
}
|
|
@@ -176,14 +176,13 @@ function generateRouterObjectEffect<TSchema extends z.ZodTypeAny>(params: {
|
|
|
176
176
|
prompt: string
|
|
177
177
|
label: 'triage' | 'check'
|
|
178
178
|
}): Effect.Effect<z.infer<TSchema> | null, never> {
|
|
179
|
-
const modelId = params.agentConfig.routerModelId ?? '
|
|
179
|
+
const modelId = params.agentConfig.routerModelId ?? 'gpt-5.4-nano'
|
|
180
180
|
|
|
181
181
|
return Effect.tryPromise({
|
|
182
182
|
try: () =>
|
|
183
183
|
generateObject({
|
|
184
184
|
model: params.aiGatewayModels.chatModel(modelId),
|
|
185
185
|
headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
|
|
186
|
-
providerOptions: { openai: { reasoningEffort: 'low' } },
|
|
187
186
|
schema: params.schema,
|
|
188
187
|
system: params.system,
|
|
189
188
|
prompt: params.prompt,
|