@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.4.14",
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.14",
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: AiGatewayDeps,
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 model = resolveProviderModel(deps.gateway.provider, modelId, providerId)
995
- return deps.runPromise(
1010
+ const resolvedDeps = resolveAiGatewayDeps(deps)
1011
+ const model = resolveProviderModel(resolvedDeps.gateway.provider, modelId, providerId)
1012
+ return resolvedDeps.runPromise(
996
1013
  withAiGatewayConcurrency(
997
- executeGenerateAttemptPlan(deps.runtimeConfig, modelId, params, () => model.doGenerate(params)).pipe(
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, deps.gateway)),
1023
+ ).pipe(Effect.provideService(AiGatewayTag, resolvedDeps.gateway)),
1007
1024
  )
1008
1025
  },
1009
1026
  wrapStream: ({ params }) => {
1010
- const model = resolveProviderModel(deps.gateway.provider, modelId, providerId)
1011
- return deps.runPromise(
1027
+ const resolvedDeps = resolveAiGatewayDeps(deps)
1028
+ const model = resolveProviderModel(resolvedDeps.gateway.provider, modelId, providerId)
1029
+ return resolvedDeps.runPromise(
1012
1030
  withAiGatewayStreamConcurrency(
1013
- executeStreamAttemptPlan(deps.runtimeConfig, modelId, params, () => model.doStream(params)).pipe(
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
- deps.runFork,
1039
+ resolvedDeps.runFork,
1022
1040
  ).pipe(
1023
1041
  Effect.map(({ result }) => result),
1024
- Effect.provideService(AiGatewayTag, deps.gateway),
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: AiGatewayDeps) {
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: AiGatewayDeps) {
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: AiGatewayDeps) {
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: AiGatewayDeps) {
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 embeddingModel = deps.gateway.provider.embeddingModel(modelId)
1133
- return deps.runPromise(
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, deps.gateway)),
1161
+ ).pipe(Effect.provideService(AiGatewayTag, resolvedDeps.gateway)),
1143
1162
  )
1144
1163
  },
1145
1164
  },
@@ -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://ai-gateway.gobrainy.ai' as const
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 {
@@ -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. Use instead of the legacy
83
- * `aiGatewayChatModel(modelId)` / `aiGatewayModel(modelId)` module-level
84
- * helpers those now require a `deps` argument. */
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 aiGatewayModels = createAiGatewayModels({
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
- queue.enqueue({ type: 'sweep' }, { delay: delayMs, jobId: PLAN_AGENT_HEARTBEAT_SWEEP_JOB_ID })
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
- return Effect.runPromise(
96
- Effect.gen(function* () {
97
- if (job.data.type === 'wake-node') {
98
- const wakeJob: PlanAgentHeartbeatWakeJob = job.data
99
- yield* service.wakeNode(wakeJob)
100
- return
101
- }
102
-
103
- yield* service.sweep({ organizationId: job.data.organizationId })
104
- if (!job.data.organizationId) {
105
- yield* Effect.tryPromise({
106
- try: () => enqueueDelayedSweep(),
107
- catch: (cause) =>
108
- new PlanAgentHeartbeatQueueError({
109
- message: 'Failed to enqueue delayed plan-agent heartbeat sweep.',
110
- cause,
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
- queue.enqueue({ type: 'wake-node', ...wakeParams }, { jobId: buildWakeJobId(wakeParams) }),
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 9-tier dependency graph on top of the infrastructure
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
- return Layer.mergeAll(ctx8, tier9)
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
- provideCurrentContext(triggerPlanNodeTurn({ runId: params.runId, nodeId: params.nodeId })),
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 triggerPlanNodeTurnEffect(deps, params).pipe(
432
- Effect.annotateSpans(
433
- compactSpanAttributes({
434
- turnKind: 'planTurn',
435
- streamId: params.streamId,
436
- planRunId: params.runId,
437
- planNodeId: params.nodeId,
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 ?? 'openrouter/openai/gpt-5.4-nano'
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,