@lota-sdk/core 0.4.10 → 0.4.11

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.
Files changed (108) hide show
  1. package/package.json +2 -2
  2. package/src/ai-gateway/ai-gateway.ts +149 -95
  3. package/src/ai-gateway/index.ts +16 -1
  4. package/src/config/agent-defaults.ts +4 -120
  5. package/src/config/logger.ts +18 -34
  6. package/src/config/thread-defaults.ts +1 -18
  7. package/src/create-runtime.ts +90 -28
  8. package/src/db/base.service.ts +30 -38
  9. package/src/db/service.ts +489 -545
  10. package/src/effect/index.ts +0 -2
  11. package/src/effect/layers.ts +6 -13
  12. package/src/embeddings/provider.ts +2 -7
  13. package/src/index.ts +4 -5
  14. package/src/queues/autonomous-job.queue.ts +159 -113
  15. package/src/queues/context-compaction.queue.ts +39 -25
  16. package/src/queues/delayed-node-promotion.queue.ts +56 -29
  17. package/src/queues/document-processor.queue.ts +5 -3
  18. package/src/queues/index.ts +1 -0
  19. package/src/queues/memory-consolidation.queue.ts +79 -53
  20. package/src/queues/organization-learning.queue.ts +63 -39
  21. package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
  22. package/src/queues/plan-scheduler.queue.ts +100 -84
  23. package/src/queues/post-chat-memory.queue.ts +55 -33
  24. package/src/queues/queue-factory.ts +40 -41
  25. package/src/queues/queues.service.ts +61 -0
  26. package/src/queues/title-generation.queue.ts +42 -31
  27. package/src/redis/org-memory-lock.ts +24 -9
  28. package/src/redis/redis-lease-lock.ts +8 -1
  29. package/src/runtime/agent-identity-overrides.ts +7 -3
  30. package/src/runtime/agent-runtime-policy.ts +9 -4
  31. package/src/runtime/agent-stream-helpers.ts +9 -4
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  33. package/src/runtime/context-compaction/context-compaction.ts +9 -7
  34. package/src/runtime/domain-layer.ts +15 -4
  35. package/src/runtime/execution-plan-visibility.ts +5 -2
  36. package/src/runtime/graph-designer.ts +0 -22
  37. package/src/runtime/index.ts +1 -0
  38. package/src/runtime/indexed-repositories-policy.ts +2 -6
  39. package/src/runtime/plugin-resolution.ts +29 -12
  40. package/src/runtime/post-turn-side-effects.ts +139 -141
  41. package/src/runtime/runtime-config.ts +0 -6
  42. package/src/runtime/runtime-extensions.ts +0 -54
  43. package/src/runtime/runtime-lifecycle.ts +4 -4
  44. package/src/runtime/runtime-services.ts +122 -53
  45. package/src/runtime/runtime-worker-registry.ts +113 -30
  46. package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
  47. package/src/runtime/social-chat/social-chat-history.ts +3 -1
  48. package/src/runtime/social-chat/social-chat.ts +35 -20
  49. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
  50. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  51. package/src/runtime/thread-chat-helpers.ts +18 -9
  52. package/src/runtime/thread-turn-context.ts +7 -47
  53. package/src/runtime/turn-lifecycle.ts +6 -14
  54. package/src/services/agent-activity.service.ts +168 -175
  55. package/src/services/agent-executor.service.ts +35 -16
  56. package/src/services/attachment.service.ts +4 -70
  57. package/src/services/autonomous-job.service.ts +53 -61
  58. package/src/services/context-compaction.service.ts +7 -9
  59. package/src/services/execution-plan/execution-plan-graph.ts +106 -115
  60. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  61. package/src/services/execution-plan/execution-plan.service.ts +67 -50
  62. package/src/services/global-orchestrator.service.ts +18 -7
  63. package/src/services/graph-full-routing.ts +7 -6
  64. package/src/services/memory/memory-conversation.ts +10 -5
  65. package/src/services/memory/memory.service.ts +11 -8
  66. package/src/services/ownership-dispatcher.service.ts +16 -5
  67. package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
  68. package/src/services/plan/plan-agent-query.service.ts +12 -8
  69. package/src/services/plan/plan-completion-side-effects.ts +93 -101
  70. package/src/services/plan/plan-cycle.service.ts +7 -45
  71. package/src/services/plan/plan-deadline.service.ts +28 -17
  72. package/src/services/plan/plan-event-delivery.service.ts +47 -40
  73. package/src/services/plan/plan-executor-context.ts +2 -0
  74. package/src/services/plan/plan-executor-graph.ts +366 -391
  75. package/src/services/plan/plan-executor.service.ts +13 -91
  76. package/src/services/plan/plan-scheduler.service.ts +62 -49
  77. package/src/services/plan/plan-transaction-events.ts +1 -1
  78. package/src/services/recent-activity-title.service.ts +6 -2
  79. package/src/services/thread/thread-bootstrap.ts +11 -9
  80. package/src/services/thread/thread-message.service.ts +6 -5
  81. package/src/services/thread/thread-turn-execution.ts +86 -82
  82. package/src/services/thread/thread-turn-preparation.service.ts +47 -24
  83. package/src/services/thread/thread-turn-streaming.ts +20 -25
  84. package/src/services/thread/thread-turn.ts +25 -44
  85. package/src/services/thread/thread.service.ts +21 -6
  86. package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
  87. package/src/system-agents/thread-router.agent.ts +23 -20
  88. package/src/tools/execution-plan.tool.ts +8 -3
  89. package/src/tools/fetch-webpage.tool.ts +10 -9
  90. package/src/tools/firecrawl-client.ts +0 -15
  91. package/src/tools/remember-memory.tool.ts +3 -6
  92. package/src/tools/research-topic.tool.ts +12 -3
  93. package/src/tools/search-web.tool.ts +10 -9
  94. package/src/tools/search.tool.ts +4 -5
  95. package/src/tools/team-think.tool.ts +139 -121
  96. package/src/workers/bootstrap.ts +9 -10
  97. package/src/workers/memory-consolidation.worker.ts +4 -1
  98. package/src/workers/organization-learning.worker.ts +15 -2
  99. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  100. package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
  101. package/src/workers/skill-extraction.runner.ts +13 -15
  102. package/src/workers/worker-utils.ts +6 -18
  103. package/src/effect/awaitable-effect.ts +0 -96
  104. package/src/effect/runtime-ref.ts +0 -25
  105. package/src/effect/runtime.ts +0 -46
  106. package/src/redis/runtime-connection.ts +0 -20
  107. package/src/runtime/runtime-accessors.ts +0 -92
  108. package/src/runtime/runtime-token.ts +0 -47
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lota-sdk/core",
3
- "version": "0.4.10",
3
+ "version": "0.4.11",
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.10",
34
+ "@lota-sdk/shared": "0.4.11",
35
35
  "@mendable/firecrawl-js": "^4.18.3",
36
36
  "@surrealdb/node": "^3.0.3",
37
37
  "ai": "^6.0.167",
@@ -6,7 +6,6 @@ import { Cause, Clock, Context, Duration, Effect, ExecutionPlan, Fiber, Layer, S
6
6
 
7
7
  import { DEFAULT_AI_GATEWAY_URL } from '../config/constants'
8
8
  import { AiGenerationError, ConfigurationError } from '../effect/errors'
9
- import { resolveLotaService } from '../effect/runtime'
10
9
  import { RuntimeConfigServiceTag } from '../effect/services'
11
10
  import { getDirectOpenRouterProvider, normalizeDirectOpenRouterModelId } from '../openrouter/direct-provider'
12
11
  import { isRecord, readString } from '../utils/string'
@@ -24,6 +23,8 @@ type AiGatewayGeneratedContent = AiGatewayGenerateResult['content'][number]
24
23
  type AiGatewayStreamPart = AiGatewayStreamResult['stream'] extends ReadableStream<infer T> ? T : never
25
24
  type AiGatewayProviderOptions = NonNullable<AiGatewayCallOptions['providerOptions']>
26
25
  type AiGatewayAttemptResult<A> = { source: string; result: A }
26
+ // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
27
+ type AiGatewayRunFork = <A, E>(effect: Effect.Effect<A, E, never>) => Fiber.Fiber<A, E | unknown>
27
28
 
28
29
  class AiGatewayGenerateAttempt extends Context.Service<
29
30
  AiGatewayGenerateAttempt,
@@ -271,12 +272,13 @@ function withAiGatewayResilience<A>(source: string, effect: Effect.Effect<A, AiG
271
272
  function withAiGatewayStreamIdleTimeout(
272
273
  stream: ReadableStream<AiGatewayStreamPart>,
273
274
  source: string,
275
+ runFork: AiGatewayRunFork,
274
276
  onFinalize?: () => void,
275
277
  ): ReadableStream<AiGatewayStreamPart> {
276
278
  let closed = false
277
279
  let reader: ReadableStreamDefaultReader<AiGatewayStreamPart> | null = null
278
- let idleTimeoutFiber: ReturnType<typeof Effect.runFork> | null = null
279
- let bodyPumpFiber: ReturnType<typeof Effect.runFork> | null = null
280
+ let idleTimeoutFiber: Fiber.Fiber<unknown, unknown> | null = null
281
+ let bodyPumpFiber: Fiber.Fiber<unknown, unknown> | null = null
280
282
  let finalized = false
281
283
 
282
284
  const finalize = () => {
@@ -285,9 +287,9 @@ function withAiGatewayStreamIdleTimeout(
285
287
  onFinalize?.()
286
288
  }
287
289
 
288
- const interruptFiber = (fiber: ReturnType<typeof Effect.runFork> | null) => {
290
+ const interruptFiber = (fiber: Fiber.Fiber<unknown, unknown> | null) => {
289
291
  if (!fiber) return
290
- void Effect.runFork(Fiber.interrupt(fiber))
292
+ void runFork(Fiber.interrupt(fiber))
291
293
  }
292
294
 
293
295
  const stopIdleTimeout = () => {
@@ -351,7 +353,7 @@ function withAiGatewayStreamIdleTimeout(
351
353
 
352
354
  const resetIdleTimeout = (controller: ReadableStreamDefaultController<AiGatewayStreamPart>) => {
353
355
  stopIdleTimeout()
354
- idleTimeoutFiber = Effect.runFork(
356
+ idleTimeoutFiber = runFork(
355
357
  Effect.sleep(Duration.millis(AI_GATEWAY_STREAM_IDLE_TIMEOUT_MS)).pipe(
356
358
  Effect.flatMap(() =>
357
359
  Effect.gen(function* () {
@@ -417,7 +419,7 @@ function withAiGatewayStreamIdleTimeout(
417
419
  start(controller) {
418
420
  const streamReader = stream.getReader()
419
421
  reader = streamReader
420
- bodyPumpFiber = Effect.runFork(pumpStreamEffect(streamReader, controller))
422
+ bodyPumpFiber = runFork(pumpStreamEffect(streamReader, controller))
421
423
  },
422
424
  cancel(reason) {
423
425
  closed = true
@@ -493,40 +495,22 @@ export const AiGatewayLive = Layer.effect(
493
495
 
494
496
  type AiGatewayRuntimeConfig = Context.Service.Shape<typeof RuntimeConfigServiceTag>
495
497
 
496
- let currentAiGateway: AiGatewayTag['Service'] | null = null
497
- let currentAiGatewayRuntimeConfig: AiGatewayRuntimeConfig | null = null
498
-
499
- export function configureAiGatewayRuntimeAccessors(params: {
500
- aiGateway: AiGatewayTag['Service']
501
- runtimeConfig: AiGatewayRuntimeConfig
502
- }): void {
503
- currentAiGateway = params.aiGateway
504
- currentAiGatewayRuntimeConfig = params.runtimeConfig
505
- }
506
-
507
- export function clearAiGatewayRuntimeAccessors(): void {
508
- currentAiGateway = null
509
- currentAiGatewayRuntimeConfig = null
510
- }
511
-
512
- function getAiGateway(): AiGatewayTag['Service'] {
513
- return currentAiGateway ?? resolveLotaService(AiGatewayTag)
514
- }
515
-
516
- function getAiGatewayRuntimeConfig(): AiGatewayRuntimeConfig {
517
- return currentAiGatewayRuntimeConfig ?? resolveLotaService(RuntimeConfigServiceTag)
518
- }
519
-
520
- function withAiGatewayConcurrency<A>(effect: Effect.Effect<A, AiGenerationError>): Effect.Effect<A, AiGenerationError> {
521
- return getAiGateway().semaphore.withPermit(effect)
498
+ function withAiGatewayConcurrency<A>(
499
+ effect: Effect.Effect<A, AiGenerationError>,
500
+ ): Effect.Effect<A, AiGenerationError, AiGatewayTag> {
501
+ return Effect.gen(function* () {
502
+ const gateway = yield* AiGatewayTag
503
+ return yield* gateway.semaphore.withPermit(effect)
504
+ })
522
505
  }
523
506
 
524
507
  function withAiGatewayStreamConcurrency(
525
508
  effect: Effect.Effect<AiGatewayAttemptResult<AiGatewayStreamResult>, AiGenerationError>,
526
- ): Effect.Effect<AiGatewayAttemptResult<AiGatewayStreamResult>, AiGenerationError> {
509
+ runFork: AiGatewayRunFork,
510
+ ): Effect.Effect<AiGatewayAttemptResult<AiGatewayStreamResult>, AiGenerationError, AiGatewayTag> {
527
511
  return Effect.uninterruptibleMask((restore) =>
528
512
  Effect.gen(function* () {
529
- const { semaphore } = getAiGateway()
513
+ const { semaphore } = yield* AiGatewayTag
530
514
  const currentContext = yield* Effect.context<never>()
531
515
  yield* semaphore.take(1)
532
516
 
@@ -551,7 +535,7 @@ function withAiGatewayStreamConcurrency(
551
535
  ...attempt,
552
536
  result: {
553
537
  ...attempt.result,
554
- stream: withAiGatewayStreamIdleTimeout(attempt.result.stream, attempt.source, release),
538
+ stream: withAiGatewayStreamIdleTimeout(attempt.result.stream, attempt.source, runFork, release),
555
539
  },
556
540
  }
557
541
  }),
@@ -638,18 +622,20 @@ function isOpenRouterModel(modelId: string): boolean {
638
622
  return modelId.trim().toLowerCase().startsWith('openrouter/')
639
623
  }
640
624
 
641
- function hasDirectOpenRouterFallback(modelId: string): boolean {
642
- const config = getAiGatewayRuntimeConfig()
625
+ function hasDirectOpenRouterFallback(config: AiGatewayRuntimeConfig, modelId: string): boolean {
643
626
  return isOpenRouterModel(modelId) && Boolean(config.aiGateway.openRouterApiKey?.trim())
644
627
  }
645
628
 
646
- function getDirectOpenRouterChatModel(modelId: string): AiGatewayLanguageModel {
647
- const config = getAiGatewayRuntimeConfig()
629
+ function getDirectOpenRouterChatModel(config: AiGatewayRuntimeConfig, modelId: string): AiGatewayLanguageModel {
648
630
  return getDirectOpenRouterProvider(config.aiGateway.openRouterApiKey).chat(normalizeDirectOpenRouterModelId(modelId))
649
631
  }
650
632
 
651
- function shouldFallbackToDirectOpenRouter(modelId: string, error: AiGenerationError): boolean {
652
- return hasDirectOpenRouterFallback(modelId) && isRetryableAiGatewayError(error)
633
+ function shouldFallbackToDirectOpenRouter(
634
+ config: AiGatewayRuntimeConfig,
635
+ modelId: string,
636
+ error: AiGenerationError,
637
+ ): boolean {
638
+ return hasDirectOpenRouterFallback(config, modelId) && isRetryableAiGatewayError(error)
653
639
  }
654
640
 
655
641
  function attemptAiGatewayGenerate(
@@ -681,22 +667,25 @@ function attemptAiGatewayStream(
681
667
  }
682
668
 
683
669
  function attemptDirectOpenRouterGenerate(
670
+ config: AiGatewayRuntimeConfig,
684
671
  modelId: string,
685
672
  params: AiGatewayCallOptions,
686
673
  ): Effect.Effect<AiGatewayAttemptResult<AiGatewayGenerateResult>, AiGenerationError> {
687
- const model = getDirectOpenRouterChatModel(modelId)
674
+ const model = getDirectOpenRouterChatModel(config, modelId)
688
675
  return attemptAiGatewayGenerate('openrouter.generate', () => model.doGenerate(params))
689
676
  }
690
677
 
691
678
  function attemptDirectOpenRouterStream(
679
+ config: AiGatewayRuntimeConfig,
692
680
  modelId: string,
693
681
  params: AiGatewayCallOptions,
694
682
  ): Effect.Effect<AiGatewayAttemptResult<AiGatewayStreamResult>, AiGenerationError> {
695
- const model = getDirectOpenRouterChatModel(modelId)
683
+ const model = getDirectOpenRouterChatModel(config, modelId)
696
684
  return attemptAiGatewayStream('openrouter.stream', () => model.doStream(params))
697
685
  }
698
686
 
699
687
  function executeGenerateAttemptPlan(
688
+ config: AiGatewayRuntimeConfig,
700
689
  modelId: string,
701
690
  params: AiGatewayCallOptions,
702
691
  doGenerate: () => PromiseLike<AiGatewayGenerateResult>,
@@ -709,7 +698,7 @@ function executeGenerateAttemptPlan(
709
698
  return yield* attempt.execute
710
699
  })
711
700
 
712
- if (!hasDirectOpenRouterFallback(modelId)) {
701
+ if (!hasDirectOpenRouterFallback(config, modelId)) {
713
702
  return effect.pipe(
714
703
  Effect.provide(primary),
715
704
  Effect.withSpan('AiGateway.executeGeneratePlan'),
@@ -723,9 +712,9 @@ function executeGenerateAttemptPlan(
723
712
  { provide: primary },
724
713
  {
725
714
  provide: Layer.succeed(AiGatewayGenerateAttempt, {
726
- execute: attemptDirectOpenRouterGenerate(modelId, params),
715
+ execute: attemptDirectOpenRouterGenerate(config, modelId, params),
727
716
  }),
728
- while: (error: AiGenerationError) => shouldFallbackToDirectOpenRouter(modelId, error),
717
+ while: (error: AiGenerationError) => shouldFallbackToDirectOpenRouter(config, modelId, error),
729
718
  },
730
719
  ),
731
720
  ),
@@ -735,6 +724,7 @@ function executeGenerateAttemptPlan(
735
724
  }
736
725
 
737
726
  function executeStreamAttemptPlan(
727
+ config: AiGatewayRuntimeConfig,
738
728
  modelId: string,
739
729
  params: AiGatewayCallOptions,
740
730
  doStream: () => PromiseLike<AiGatewayStreamResult>,
@@ -747,7 +737,7 @@ function executeStreamAttemptPlan(
747
737
  return yield* attempt.execute
748
738
  })
749
739
 
750
- if (!hasDirectOpenRouterFallback(modelId)) {
740
+ if (!hasDirectOpenRouterFallback(config, modelId)) {
751
741
  return effect.pipe(
752
742
  Effect.provide(primary),
753
743
  Effect.withSpan('AiGateway.executeStreamPlan'),
@@ -760,8 +750,10 @@ function executeStreamAttemptPlan(
760
750
  ExecutionPlan.make(
761
751
  { provide: primary },
762
752
  {
763
- provide: Layer.succeed(AiGatewayStreamAttempt, { execute: attemptDirectOpenRouterStream(modelId, params) }),
764
- while: (error: AiGenerationError) => shouldFallbackToDirectOpenRouter(modelId, error),
753
+ provide: Layer.succeed(AiGatewayStreamAttempt, {
754
+ execute: attemptDirectOpenRouterStream(config, modelId, params),
755
+ }),
756
+ while: (error: AiGenerationError) => shouldFallbackToDirectOpenRouter(config, modelId, error),
765
757
  },
766
758
  ),
767
759
  ),
@@ -849,7 +841,56 @@ function addAiGatewayReasoningRawChunks(
849
841
  return { ...params, includeRawChunks: true }
850
842
  }
851
843
 
852
- function createAiGatewayLanguageModelMiddleware(modelId: string): LanguageModelMiddleware {
844
+ function resolveProviderModel(
845
+ provider: ReturnType<typeof createOpenAI>,
846
+ modelId: string,
847
+ providerId: string,
848
+ ): AiGatewayLanguageModel {
849
+ return providerId === OPENAI_CHAT_PROVIDER_ID ? provider.chat(modelId) : provider(modelId)
850
+ }
851
+
852
+ // Module-level Promise slot that `createLotaRuntime` populates during boot.
853
+ // This is a legitimate per-process singleton (mirrors the worker bootstrap
854
+ // pattern in `workers/bootstrap.ts`): the AI gateway middleware is dispatched
855
+ // by AI SDK callers that live outside Effect context, so the middleware needs
856
+ // a way to run gateway Effects without capturing a `ManagedRuntime` through
857
+ // every `aiGatewayModel(modelId)` call site.
858
+ //
859
+ // Only `createLotaRuntime` writes to the slot; resetting on disconnect is a
860
+ // Phase 3b concern — for now it stays alive for the process lifetime.
861
+ let aiGatewayRuntimeReady: Promise<{
862
+ gateway: Context.Service.Shape<typeof AiGatewayTag>
863
+ runtimeConfig: Context.Service.Shape<typeof RuntimeConfigServiceTag>
864
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
865
+ runFork: AiGatewayRunFork
866
+ }> | null = null
867
+
868
+ export function bindAiGatewayRuntime(params: {
869
+ gateway: Context.Service.Shape<typeof AiGatewayTag>
870
+ runtimeConfig: Context.Service.Shape<typeof RuntimeConfigServiceTag>
871
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
872
+ runFork: AiGatewayRunFork
873
+ }): void {
874
+ aiGatewayRuntimeReady = Promise.resolve(params)
875
+ }
876
+
877
+ export function clearAiGatewayRuntime(): void {
878
+ aiGatewayRuntimeReady = null
879
+ }
880
+
881
+ async function getAiGatewayRuntime(): Promise<{
882
+ gateway: Context.Service.Shape<typeof AiGatewayTag>
883
+ runtimeConfig: Context.Service.Shape<typeof RuntimeConfigServiceTag>
884
+ runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
885
+ runFork: AiGatewayRunFork
886
+ }> {
887
+ if (!aiGatewayRuntimeReady) {
888
+ throw new Error('AI gateway runtime has not been initialized. Call createLotaRuntime() first.')
889
+ }
890
+ return aiGatewayRuntimeReady
891
+ }
892
+
893
+ function createAiGatewayLanguageModelMiddleware(modelId: string, providerId: string): LanguageModelMiddleware {
853
894
  return {
854
895
  specificationVersion: 'v3',
855
896
  transformParams: ({ params, type }) =>
@@ -858,10 +899,12 @@ function createAiGatewayLanguageModelMiddleware(modelId: string): LanguageModelM
858
899
  addAiGatewayReasoningRawChunks(normalizeAiGatewayChatProviderOptions(params, modelId), type),
859
900
  ),
860
901
  ),
861
- wrapGenerate: ({ doGenerate, params }) =>
862
- Effect.runPromise(
902
+ wrapGenerate: async ({ params }) => {
903
+ const { gateway, runtimeConfig, runPromise } = await getAiGatewayRuntime()
904
+ const model = resolveProviderModel(gateway.provider, modelId, providerId)
905
+ return runPromise(
863
906
  withAiGatewayConcurrency(
864
- executeGenerateAttemptPlan(modelId, params, doGenerate).pipe(
907
+ executeGenerateAttemptPlan(runtimeConfig, modelId, params, () => model.doGenerate(params)).pipe(
865
908
  Effect.map(({ result }) => ({
866
909
  ...result,
867
910
  content: injectAiGatewayChatReasoningContent(
@@ -870,12 +913,15 @@ function createAiGatewayLanguageModelMiddleware(modelId: string): LanguageModelM
870
913
  ),
871
914
  })),
872
915
  ),
873
- ),
874
- ),
875
- wrapStream: ({ doStream, params }) =>
876
- Effect.runPromise(
916
+ ).pipe(Effect.provideService(AiGatewayTag, gateway)),
917
+ )
918
+ },
919
+ wrapStream: async ({ params }) => {
920
+ const { gateway, runtimeConfig, runPromise, runFork } = await getAiGatewayRuntime()
921
+ const model = resolveProviderModel(gateway.provider, modelId, providerId)
922
+ return runPromise(
877
923
  withAiGatewayStreamConcurrency(
878
- executeStreamAttemptPlan(modelId, params, doStream).pipe(
924
+ executeStreamAttemptPlan(runtimeConfig, modelId, params, () => model.doStream(params)).pipe(
879
925
  Effect.map((attempt) => ({
880
926
  ...attempt,
881
927
  result: isReasoningEnabled(params)
@@ -883,8 +929,12 @@ function createAiGatewayLanguageModelMiddleware(modelId: string): LanguageModelM
883
929
  : attempt.result,
884
930
  })),
885
931
  ),
886
- ).pipe(Effect.map(({ result }) => result)),
887
- ),
932
+ runFork,
933
+ )
934
+ .pipe(Effect.map(({ result }) => result))
935
+ .pipe(Effect.provideService(AiGatewayTag, gateway)),
936
+ )
937
+ },
888
938
  }
889
939
  }
890
940
 
@@ -921,36 +971,42 @@ function withAiGatewayDevTools<TModel extends AiGatewayLanguageModel>(model: TMo
921
971
  return wrapLanguageModel({ model, middleware: devToolsMiddleware() }) as TModel
922
972
  }
923
973
 
924
- function createLazyAiGatewayLanguageModel(params: {
925
- modelId: string
926
- providerId: string
927
- resolve: () => AiGatewayLanguageModel
928
- }): AiGatewayLanguageModel {
974
+ function createAiGatewayLanguageModelPlaceholder(modelId: string, providerId: string): AiGatewayLanguageModel {
975
+ const unreachable = (method: string) =>
976
+ Promise.reject(
977
+ new Error(
978
+ `[ai-gateway] AiGateway language model ${modelId}.${method} was invoked without the gateway middleware; ` +
979
+ 'this call path should be fully handled by createAiGatewayLanguageModelMiddleware.',
980
+ ),
981
+ )
982
+
929
983
  return {
930
984
  specificationVersion: 'v3',
931
- provider: params.providerId,
932
- modelId: params.modelId,
985
+ provider: providerId,
986
+ modelId,
933
987
  supportedUrls: {},
934
- doGenerate: (options) => params.resolve().doGenerate(options),
935
- doStream: (options) => params.resolve().doStream(options),
988
+ doGenerate: () => unreachable('doGenerate'),
989
+ doStream: () => unreachable('doStream'),
936
990
  }
937
991
  }
938
992
 
939
- function createLazyAiGatewayEmbeddingModel(modelId: string): AiGatewayEmbeddingModel {
993
+ function createAiGatewayEmbeddingModelPlaceholder(modelId: string): AiGatewayEmbeddingModel {
940
994
  return {
941
995
  specificationVersion: 'v3',
942
996
  provider: OPENAI_EMBEDDING_PROVIDER_ID,
943
997
  modelId,
944
998
  maxEmbeddingsPerCall: OPENAI_EMBEDDING_MAX_PER_CALL,
945
999
  supportsParallelCalls: true,
946
- doEmbed: (options) => getAiGatewayProvider().embeddingModel(modelId).doEmbed(options),
1000
+ doEmbed: () =>
1001
+ Promise.reject(
1002
+ new Error(
1003
+ `[ai-gateway] AiGateway embedding model ${modelId}.doEmbed was invoked without the gateway middleware; ` +
1004
+ 'this call path should be fully handled by aiGatewayEmbeddingModel middleware.',
1005
+ ),
1006
+ ),
947
1007
  }
948
1008
  }
949
1009
 
950
- export function getAiGatewayProvider() {
951
- return getAiGateway().provider
952
- }
953
-
954
1010
  export function aiGatewayModel(modelId: string) {
955
1011
  if (isOpenRouterModel(modelId)) {
956
1012
  return aiGatewayChatModel(modelId)
@@ -958,12 +1014,8 @@ export function aiGatewayModel(modelId: string) {
958
1014
 
959
1015
  return withAiGatewayDevTools(
960
1016
  wrapLanguageModel({
961
- model: createLazyAiGatewayLanguageModel({
962
- modelId,
963
- providerId: OPENAI_RESPONSES_PROVIDER_ID,
964
- resolve: () => getAiGatewayProvider()(modelId),
965
- }),
966
- middleware: createAiGatewayLanguageModelMiddleware(modelId),
1017
+ model: createAiGatewayLanguageModelPlaceholder(modelId, OPENAI_RESPONSES_PROVIDER_ID),
1018
+ middleware: createAiGatewayLanguageModelMiddleware(modelId, OPENAI_RESPONSES_PROVIDER_ID),
967
1019
  }),
968
1020
  )
969
1021
  }
@@ -975,30 +1027,32 @@ export function aiGatewayOpenRouterResponseHealingModel(modelId: string) {
975
1027
  export function aiGatewayChatModel(modelId: string) {
976
1028
  return withAiGatewayDevTools(
977
1029
  wrapLanguageModel({
978
- model: createLazyAiGatewayLanguageModel({
979
- modelId,
980
- providerId: OPENAI_CHAT_PROVIDER_ID,
981
- resolve: () => getAiGatewayProvider().chat(modelId),
982
- }),
983
- middleware: createAiGatewayLanguageModelMiddleware(modelId),
1030
+ model: createAiGatewayLanguageModelPlaceholder(modelId, OPENAI_CHAT_PROVIDER_ID),
1031
+ middleware: createAiGatewayLanguageModelMiddleware(modelId, OPENAI_CHAT_PROVIDER_ID),
984
1032
  }),
985
1033
  )
986
1034
  }
987
1035
 
988
1036
  export function aiGatewayEmbeddingModel(modelId: string) {
989
1037
  return wrapEmbeddingModel({
990
- model: createLazyAiGatewayEmbeddingModel(modelId),
1038
+ model: createAiGatewayEmbeddingModelPlaceholder(modelId),
991
1039
  middleware: {
992
1040
  specificationVersion: 'v3',
993
- wrapEmbed: ({ doEmbed }) =>
994
- Effect.runPromise(
1041
+ wrapEmbed: async ({ params }) => {
1042
+ const { gateway, runPromise } = await getAiGatewayRuntime()
1043
+ const embeddingModel = gateway.provider.embeddingModel(modelId)
1044
+ return runPromise(
995
1045
  withAiGatewayConcurrency(
996
1046
  withAiGatewayResilience(
997
1047
  'ai-gateway.embed',
998
- Effect.tryPromise({ try: doEmbed, catch: (cause) => classifyAiGatewayError('ai-gateway.embed', cause) }),
999
- ),
1000
- ).pipe(Effect.withSpan('AiGateway.embed'), Effect.annotateSpans({ modelId })),
1001
- ),
1048
+ Effect.tryPromise({
1049
+ try: () => embeddingModel.doEmbed(params),
1050
+ catch: (cause) => classifyAiGatewayError('ai-gateway.embed', cause),
1051
+ }),
1052
+ ).pipe(Effect.withSpan('AiGateway.embed'), Effect.annotateSpans({ modelId })),
1053
+ ).pipe(Effect.provideService(AiGatewayTag, gateway)),
1054
+ )
1055
+ },
1002
1056
  },
1003
1057
  })
1004
1058
  }
@@ -1,2 +1,17 @@
1
- export * from './ai-gateway'
1
+ export {
2
+ AiGatewayLive,
3
+ AiGatewayTag,
4
+ DEFAULT_AI_GATEWAY_URL,
5
+ aiGatewayChatModel,
6
+ aiGatewayEmbeddingModel,
7
+ aiGatewayModel,
8
+ aiGatewayOpenRouterResponseHealingModel,
9
+ bindAiGatewayRuntime,
10
+ extractAiGatewayChatReasoningDeltaText,
11
+ extractAiGatewayChatReasoningText,
12
+ injectAiGatewayChatReasoningContent,
13
+ injectAiGatewayChatReasoningStream,
14
+ normalizeAiGatewayChatProviderOptions,
15
+ normalizeAiGatewayUrl,
16
+ } from './ai-gateway'
2
17
  export * from './cache-headers'
@@ -1,8 +1,6 @@
1
1
  import type { ToolSet } from 'ai'
2
2
 
3
3
  import { ConfigurationError } from '../effect/errors'
4
- import { resolveLotaService } from '../effect/runtime'
5
- import { AgentConfigServiceTag, AgentFactoryServiceTag } from '../effect/services'
6
4
  import type {
7
5
  AgentFactory,
8
6
  AgentRuntimeConfigParams,
@@ -130,125 +128,11 @@ export interface CoreThreadProfile {
130
128
  instructions: string
131
129
  }
132
130
 
133
- let currentResolvedAgentConfig: ResolvedAgentConfig | null = null
134
- let currentResolvedAgentFactoryConfig: ResolvedAgentFactoryConfig | null = null
135
-
136
- export function configureAgentRuntimeDefaults(params: {
137
- agentConfig: ResolvedAgentConfig
138
- agentFactoryConfig: ResolvedAgentFactoryConfig
139
- }): void {
140
- currentResolvedAgentConfig = params.agentConfig
141
- currentResolvedAgentFactoryConfig = params.agentFactoryConfig
142
- }
143
-
144
- export function clearAgentRuntimeDefaults(): void {
145
- currentResolvedAgentConfig = null
146
- currentResolvedAgentFactoryConfig = null
147
- }
148
-
149
- function resolveAgentConfigFromRuntime(): ResolvedAgentConfig {
150
- return currentResolvedAgentConfig ?? resolveLotaService(AgentConfigServiceTag)
151
- }
152
-
153
- function resolveAgentFactoryConfigFromRuntime(): ResolvedAgentFactoryConfig {
154
- return currentResolvedAgentFactoryConfig ?? resolveLotaService(AgentFactoryServiceTag)
155
- }
156
-
157
- export function getResolvedAgentConfig(): ResolvedAgentConfig {
158
- return resolveAgentConfigFromRuntime()
159
- }
160
-
161
- export function getResolvedAgentFactoryConfig(): ResolvedAgentFactoryConfig {
162
- return resolveAgentFactoryConfigFromRuntime()
163
- }
164
-
165
- export function isAgentName(value: unknown): value is string {
166
- return typeof value === 'string' && resolveAgentConfigFromRuntime().rosterSet.has(value)
167
- }
168
-
169
- export function getAgentRoster(): readonly string[] {
170
- return resolveAgentConfigFromRuntime().roster
171
- }
172
-
173
- export function getAgentDisplayNames(): Record<string, string> {
174
- return resolveAgentConfigFromRuntime().displayNames
175
- }
176
-
177
- export function getAgentShortDisplayNames(): Record<string, string> {
178
- return resolveAgentConfigFromRuntime().shortDisplayNames
179
- }
180
-
181
- export function getAgentDescriptions(): Record<string, string> {
182
- return resolveAgentConfigFromRuntime().descriptions
183
- }
184
-
185
- export function getLeadAgentId(): string {
186
- return resolveAgentConfigFromRuntime().leadAgentId
187
- }
188
-
189
- export function getLeadAgentDisplayName(): string {
190
- const resolved = resolveAgentConfigFromRuntime()
191
- return resolved.displayNames[resolved.leadAgentId] ?? resolved.leadAgentId
192
- }
193
-
194
- export function getRouterModelId(): string | undefined {
195
- return resolveAgentConfigFromRuntime().routerModelId
196
- }
197
-
198
- export function getTeamConsultParticipants(): readonly string[] {
199
- return resolveAgentConfigFromRuntime().teamConsultParticipants
200
- }
201
-
202
- export function getCoreThreadProfile(coreType: string): CoreThreadProfile {
203
- return resolveAgentConfigFromRuntime().getCoreThreadProfile(coreType)
131
+ export function isAgentName(agentConfig: ResolvedAgentConfig, value: unknown): value is string {
132
+ return typeof value === 'string' && agentConfig.rosterSet.has(value)
204
133
  }
205
134
 
206
- export function resolveAgentNameAlias(value: unknown): string | undefined {
135
+ export function resolveAgentNameAlias(agentConfig: ResolvedAgentConfig, value: unknown): string | undefined {
207
136
  if (typeof value !== 'string') return undefined
208
- return resolveAgentConfigFromRuntime().aliasMap.get(normalizeAgentLookupKey(value))
209
- }
210
-
211
- export function getCreateAgentRegistry(): AgentFactory {
212
- return resolveAgentFactoryConfigFromRuntime().createAgent
213
- }
214
-
215
- export function buildAgentTools(...args: Parameters<AgentToolBuilder>): ReturnType<AgentToolBuilder> {
216
- return resolveAgentFactoryConfigFromRuntime().buildAgentTools(...args)
217
- }
218
-
219
- export function getAgentRuntimeConfig(
220
- ...args: Parameters<AgentRuntimeConfigProvider>
221
- ): ReturnType<AgentRuntimeConfigProvider> {
222
- return resolveAgentFactoryConfigFromRuntime().getAgentRuntimeConfig(...args)
223
- }
224
-
225
- export function getPluginRuntime(): Record<string, unknown> | undefined {
226
- return resolveAgentFactoryConfigFromRuntime().pluginRuntime
227
- }
228
-
229
- const AGENT_MENTION_REGEX = /(^|[^\w])@([a-z][a-z0-9_-]*)\b/gi
230
-
231
- export interface AgentMentionMatch {
232
- agent: string
233
- mention: string
234
- index: number
235
- length: number
236
- }
237
-
238
- export function extractAgentMentions(message: string, agentConfig?: ResolvedAgentConfig): AgentMentionMatch[] {
239
- const matches: AgentMentionMatch[] = []
240
- if (!message.trim()) return matches
241
-
242
- const resolvedConfig = agentConfig ?? resolveAgentConfigFromRuntime()
243
- const regex = new RegExp(AGENT_MENTION_REGEX)
244
- for (const rawMatch of message.matchAll(regex)) {
245
- const prefix = rawMatch[1]
246
- const rawAgent = rawMatch[2].toLowerCase()
247
- if (!resolvedConfig.rosterSet.has(rawAgent)) continue
248
-
249
- const index = rawMatch.index + prefix.length
250
- matches.push({ agent: rawAgent, mention: `@${rawAgent}`, index, length: rawAgent.length + 1 })
251
- }
252
-
253
- return matches
137
+ return agentConfig.aliasMap.get(normalizeAgentLookupKey(value))
254
138
  }