@lota-sdk/core 0.4.17 → 0.4.19

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.17",
3
+ "version": "0.4.19",
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.17",
34
+ "@lota-sdk/shared": "0.4.19",
35
35
  "@mendable/firecrawl-js": "^4.18.3",
36
36
  "@surrealdb/node": "^3.0.3",
37
37
  "ai": "^6.0.168",
@@ -21,7 +21,6 @@ type AiGatewayGenerateResult = Awaited<ReturnType<WrapStreamOptions['doGenerate'
21
21
  type AiGatewayStreamResult = Awaited<ReturnType<WrapStreamOptions['doStream']>>
22
22
  type AiGatewayGeneratedContent = AiGatewayGenerateResult['content'][number]
23
23
  type AiGatewayStreamPart = AiGatewayStreamResult['stream'] extends ReadableStream<infer T> ? T : never
24
- type AiGatewayProviderOptions = NonNullable<AiGatewayCallOptions['providerOptions']>
25
24
  type AiGatewayAttemptResult<A> = { source: string; result: A }
26
25
  // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
27
26
  type AiGatewayRunFork = <A, E>(effect: Effect.Effect<A, E, never>) => Fiber.Fiber<A, E | unknown>
@@ -867,10 +866,6 @@ function executeStreamAttemptPlan(
867
866
  )
868
867
  }
869
868
 
870
- function isOpenRouterOpenAIReasoningModel(modelId: string): boolean {
871
- return modelId.trim().toLowerCase().startsWith('openrouter/openai/gpt-5')
872
- }
873
-
874
869
  function shouldCloseInjectedReasoning(chunk: AiGatewayStreamPart): boolean {
875
870
  switch (chunk.type) {
876
871
  case 'stream-start':
@@ -983,6 +978,10 @@ export function setDefaultAiGatewayDeps(deps: AiGatewayDeps): void {
983
978
  defaultAiGatewayDeps = deps
984
979
  }
985
980
 
981
+ export function getDefaultAiGatewayRunPromise(): AiGatewayDeps['runPromise'] {
982
+ return resolveAiGatewayDeps(undefined).runPromise
983
+ }
984
+
986
985
  function resolveAiGatewayDeps(deps: AiGatewayDeps | undefined): AiGatewayDeps {
987
986
  if (deps) return deps
988
987
  if (defaultAiGatewayDeps) return defaultAiGatewayDeps
@@ -1048,27 +1047,9 @@ function createAiGatewayLanguageModelMiddleware(
1048
1047
 
1049
1048
  export function normalizeAiGatewayChatProviderOptions(
1050
1049
  params: AiGatewayCallOptions,
1051
- modelId?: string,
1050
+ _modelId?: string,
1052
1051
  ): AiGatewayCallOptions {
1053
- const providerOptions = isRecord(params.providerOptions)
1054
- ? ({ ...params.providerOptions } as AiGatewayProviderOptions)
1055
- : ({} as AiGatewayProviderOptions)
1056
- const openaiOptions = isRecord(providerOptions.openai)
1057
- ? { ...providerOptions.openai }
1058
- : ({} as Record<string, unknown>)
1059
-
1060
- if (modelId && isOpenRouterOpenAIReasoningModel(modelId) && openaiOptions.forceReasoning === undefined) {
1061
- openaiOptions.forceReasoning = true
1062
- }
1063
-
1064
- if (providerOptions.openai === openaiOptions || Object.keys(openaiOptions).length === 0) {
1065
- return params
1066
- }
1067
-
1068
- return {
1069
- ...params,
1070
- providerOptions: { ...providerOptions, openai: openaiOptions as AiGatewayProviderOptions['openai'] },
1071
- }
1052
+ return params
1072
1053
  }
1073
1054
 
1074
1055
  function withAiGatewayDevTools<TModel extends AiGatewayLanguageModel>(model: TModel): TModel {
@@ -3,6 +3,7 @@ import type { ModelMessage, LanguageModel, ToolLoopAgentSettings, ToolSet } from
3
3
  import { Schema, Effect } from 'effect'
4
4
  import { z } from 'zod'
5
5
 
6
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
6
7
  import type { ToolDefinition } from '../ai/definitions'
7
8
  import { aiLogger } from '../config/logger'
8
9
  import { ERROR_TAGS } from '../effect/errors'
@@ -26,7 +27,7 @@ interface DelegatedAgentDefinition {
26
27
  maxSteps?: number
27
28
  maxOutputTokens?: number
28
29
  temperature?: number
29
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
30
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
30
31
  }
31
32
 
32
33
  type DelegatedAgentRunPromise = <A, E>(
@@ -42,7 +43,7 @@ interface DelegatedAgentDefinitionWithContext<TContext> extends Omit<
42
43
  createTools: (context: TContext) => ToolSet
43
44
  /** Pull runPromise off the caller's context so each tool invocation carries
44
45
  * its own runtime binding rather than closing over a boot-time one. */
45
- getRunPromise: (context: TContext) => DelegatedAgentRunPromise
46
+ getRunPromise?: (context: TContext) => DelegatedAgentRunPromise
46
47
  }
47
48
 
48
49
  class DelegatedAgentError extends Schema.TaggedErrorClass<DelegatedAgentError>()(ERROR_TAGS.DelegatedAgentError, {
@@ -251,7 +252,7 @@ export function createDelegatedAgentTool(definition: DelegatedAgentDefinition):
251
252
  description: definition.description,
252
253
  inputSchema: z.object({ task: z.string().min(1) }),
253
254
  execute: ({ task }: { task: string }, { abortSignal }) =>
254
- definition.runPromise(
255
+ (definition.runPromise ?? getDefaultAiGatewayRunPromise())(
255
256
  Effect.gen(function* () {
256
257
  const agentTools = definition.tools
257
258
  const createAgent = () =>
@@ -309,7 +310,7 @@ export function createDelegatedAgentToolWithContext<TContext>(
309
310
  description: definition.description,
310
311
  inputSchema: z.object({ task: z.string().min(1) }),
311
312
  execute: ({ task }: { task: string }, { abortSignal }) =>
312
- definition.getRunPromise(context)(
313
+ (definition.getRunPromise?.(context) ?? getDefaultAiGatewayRunPromise())(
313
314
  Effect.gen(function* () {
314
315
  const agentTools = definition.createTools(context)
315
316
  const createAgent = () =>
@@ -15,6 +15,7 @@ import type { CreateProjectWithPlanResultData, ExecutionPlanArgs } from '@lota-s
15
15
  import { tool } from 'ai'
16
16
  import { Effect } from 'effect'
17
17
 
18
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
18
19
  import { serverLogger } from '../config/logger'
19
20
  import type { RecordIdRef } from '../db/record-id'
20
21
  import { recordIdToString } from '../db/record-id'
@@ -58,7 +59,7 @@ export function createExecutionPlanTool(params: {
58
59
  agentId: string
59
60
  executionPlanService: ExecutionPlanExecutionPlanService
60
61
  threadService: ExecutionPlanThreadService
61
- runPromise: ExecutionPlanToolRunPromise
62
+ runPromise?: ExecutionPlanToolRunPromise
62
63
  onPlanChanged?: () => void
63
64
  validateInlinePlan?: (draft: ReturnType<typeof expandAgentPlanDraft>) => void
64
65
  }) {
@@ -67,7 +68,7 @@ export function createExecutionPlanTool(params: {
67
68
  'Manage execution plans. Actions: create (inline, 1-2 nodes), create-project (dedicated project thread, 3+ nodes), replace (swap active plan), resume (resume interrupted plan), update-node (submit result for a specific node).',
68
69
  inputSchema: ExecutionPlanArgsSchema,
69
70
  execute: (input) =>
70
- params.runPromise(
71
+ (params.runPromise ?? getDefaultAiGatewayRunPromise())(
71
72
  Effect.gen(function* () {
72
73
  const parsed = yield* parseExecutionPlanArgsEffect(input)
73
74
 
@@ -265,14 +266,14 @@ function extractAgentPlanDraftEffect(
265
266
  export function createExecutionPlanQueryTool(params: {
266
267
  threadId: RecordIdRef
267
268
  executionPlanService: Pick<ExecutionPlanExecutionPlanService, 'listActivePlanSummaries' | 'getActivePlanToolResult'>
268
- runPromise: ExecutionPlanToolRunPromise
269
+ runPromise?: ExecutionPlanToolRunPromise
269
270
  }) {
270
271
  return tool({
271
272
  description:
272
273
  'Query execution plans. Omit runId to list all active plans. Provide runId to load a specific plan run.',
273
274
  inputSchema: ExecutionPlanQueryArgsSchema,
274
275
  execute: (input) =>
275
- params.runPromise(
276
+ (params.runPromise ?? getDefaultAiGatewayRunPromise())(
276
277
  Effect.gen(function* () {
277
278
  if (!input.runId) {
278
279
  return yield* params.executionPlanService.listActivePlanSummaries(params.threadId)
@@ -295,7 +296,7 @@ export function createSubmitExecutionNodeResultTool(params: {
295
296
  threadId: RecordIdRef
296
297
  agentId: string
297
298
  executionPlanService: Pick<ExecutionPlanExecutionPlanService, 'submitNodeResult'>
298
- runPromise: ExecutionPlanToolRunPromise
299
+ runPromise?: ExecutionPlanToolRunPromise
299
300
  onPlanChanged?: () => void
300
301
  }) {
301
302
  return tool({
@@ -303,7 +304,7 @@ export function createSubmitExecutionNodeResultTool(params: {
303
304
  'Submit the result for the currently running execution node. The executor validates outputs, artifacts, and completion checks before advancing the run.',
304
305
  inputSchema: SubmitExecutionNodeResultArgsSchema,
305
306
  execute: (input) =>
306
- params.runPromise(
307
+ (params.runPromise ?? getDefaultAiGatewayRunPromise())(
307
308
  Effect.gen(function* () {
308
309
  const result = yield* params.executionPlanService.submitNodeResult({
309
310
  threadId: params.threadId,
@@ -3,6 +3,7 @@ import { tool } from 'ai'
3
3
  import { Effect, Schema } from 'effect'
4
4
  import { z } from 'zod'
5
5
 
6
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
6
7
  import type { ToolDefinition } from '../ai/definitions'
7
8
  import { withTimeout } from '../utils/async'
8
9
  import { nowIsoDateTimeString } from '../utils/date-time'
@@ -12,7 +13,7 @@ import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
12
13
 
13
14
  export interface FetchWebpageToolContext {
14
15
  firecrawl: Firecrawl
15
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
16
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
16
17
  }
17
18
 
18
19
  class FetchWebpageToolError extends Schema.TaggedErrorClass<FetchWebpageToolError>()(
@@ -103,8 +104,9 @@ function buildFetchCitations(url: string, document: unknown): WebCitation[] {
103
104
 
104
105
  export const fetchWebpageTool = {
105
106
  name: 'fetchWebpage',
106
- create: ({ firecrawl, runPromise }: FetchWebpageToolContext) =>
107
- tool({
107
+ create: ({ firecrawl, runPromise }: FetchWebpageToolContext) => {
108
+ const runEffect = runPromise ?? getDefaultAiGatewayRunPromise()
109
+ return tool({
108
110
  description: 'Retrieve and parse a single webpage.',
109
111
  inputSchema: z
110
112
  .object({
@@ -123,7 +125,7 @@ export const fetchWebpageTool = {
123
125
  }: { url: string; formats?: z.infer<typeof FormatSchema>[]; onlyMainContent?: boolean; maxAge?: number },
124
126
  { abortSignal }: { abortSignal?: AbortSignal } = {},
125
127
  ) =>
126
- runPromise(
128
+ runEffect(
127
129
  Effect.gen(function* () {
128
130
  const result = yield* Effect.tryPromise({
129
131
  try: () =>
@@ -142,5 +144,6 @@ export const fetchWebpageTool = {
142
144
  }).pipe(Effect.withSpan('tool.fetchWebpage.execute')),
143
145
  abortSignal ? { signal: abortSignal } : undefined,
144
146
  ),
145
- }),
147
+ })
148
+ },
146
149
  } as const satisfies ToolDefinition<FetchWebpageToolContext>
@@ -2,6 +2,7 @@ import { tool } from 'ai'
2
2
  import { Effect } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
5
6
  import type { RecordIdRef } from '../db/record-id'
6
7
  import {
7
8
  hasMemoryBlockEntry,
@@ -25,10 +26,11 @@ export function createMemoryBlockTool({
25
26
  threadId: RecordIdRef
26
27
  agentLabel: string
27
28
  threadService: MemoryBlockThreadService
28
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
29
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
29
30
  getCurrentBlock?: () => string
30
31
  onAppend?: (value: string) => void
31
32
  }) {
33
+ const runEffect = runPromise ?? getDefaultAiGatewayRunPromise()
32
34
  return tool({
33
35
  description:
34
36
  'Append a critical 2-3 sentence note to the conversation memory block. Include only essential information.',
@@ -50,7 +52,7 @@ export function createMemoryBlockTool({
50
52
 
51
53
  void safeEnqueue(
52
54
  () =>
53
- runPromise(
55
+ runEffect(
54
56
  Effect.gen(function* () {
55
57
  const updated = yield* threadService.appendMemoryBlock(threadId, prepared.formatted)
56
58
  onAppend?.(updated)
@@ -2,6 +2,7 @@ import { PlanApprovalArgsSchema, PlanApprovalToolResultDataSchema } from '@lota-
2
2
  import { tool } from 'ai'
3
3
  import { Effect } from 'effect'
4
4
 
5
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
5
6
  import type { RecordIdRef } from '../db/record-id'
6
7
  import type { makeExecutionPlanService } from '../services/execution-plan/execution-plan.service'
7
8
  import { toToolPlanSummary } from '../services/plan/plan-run-data'
@@ -21,15 +22,16 @@ export function createPlanApprovalTool(params: {
21
22
  threadId: RecordIdRef
22
23
  actorId: string
23
24
  executionPlanService: PlanApprovalExecutionPlanService
24
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
25
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
25
26
  }) {
27
+ const runEffect = params.runPromise ?? getDefaultAiGatewayRunPromise()
26
28
  return tool({
27
29
  description:
28
30
  '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.',
29
31
  inputSchema: PlanApprovalArgsSchema,
30
32
  outputSchema: PlanApprovalToolResultDataSchema,
31
33
  execute: (input, { abortSignal }: { abortSignal?: AbortSignal } = {}) =>
32
- params.runPromise(
34
+ runEffect(
33
35
  Effect.gen(function* () {
34
36
  switch (input.action) {
35
37
  case 'approve': {
@@ -2,6 +2,7 @@ import { tool } from 'ai'
2
2
  import { Schema, Effect } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
5
6
  import type { ToolDefinition } from '../ai/definitions'
6
7
  import { ERROR_TAGS } from '../effect/errors'
7
8
  import type { makeAttachmentService } from '../services/attachment.service'
@@ -16,7 +17,7 @@ export interface ReadFilePartsToolContext {
16
17
  userId: string
17
18
  uploads: ReadableUploadMetadata[]
18
19
  attachmentService: ReadFilePartsAttachmentService
19
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
20
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
20
21
  }
21
22
 
22
23
  function toUploadSummary(upload: ReadableUploadMetadata) {
@@ -49,8 +50,9 @@ function resolveUploadTargetEffect(params: {
49
50
 
50
51
  export const readFilePartsTool = {
51
52
  name: 'readFileParts',
52
- create: ({ orgId, userId, uploads, attachmentService, runPromise }: ReadFilePartsToolContext) =>
53
- tool({
53
+ create: ({ orgId, userId, uploads, attachmentService, runPromise }: ReadFilePartsToolContext) => {
54
+ const runEffect = runPromise ?? getDefaultAiGatewayRunPromise()
55
+ return tool({
54
56
  description:
55
57
  'Read uploaded file content by part. Call with no args to list uploads metadata, then call with storageKey and part (25 pages per part).',
56
58
  inputSchema: z
@@ -60,7 +62,7 @@ export const readFilePartsTool = {
60
62
  { storageKey, part }: { storageKey?: string; part?: number },
61
63
  { abortSignal }: { abortSignal?: AbortSignal } = {},
62
64
  ) =>
63
- runPromise(
65
+ runEffect(
64
66
  Effect.gen(function* () {
65
67
  const availableUploads = uploads.map(toUploadSummary)
66
68
  const selected = yield* resolveUploadTargetEffect({ uploads, storageKey })
@@ -93,5 +95,6 @@ export const readFilePartsTool = {
93
95
  }),
94
96
  abortSignal ? { signal: abortSignal } : undefined,
95
97
  ),
96
- }),
98
+ })
99
+ },
97
100
  } as const satisfies ToolDefinition<ReadFilePartsToolContext>
@@ -2,6 +2,7 @@ import { tool } from 'ai'
2
2
  import { Effect } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
5
6
  import type { RecordIdRef } from '../db/record-id'
6
7
  import { recordIdToString } from '../db/record-id'
7
8
  import { TABLES } from '../db/tables'
@@ -29,8 +30,9 @@ export function createRememberMemoryTool({
29
30
  orgId: RecordIdRef
30
31
  agentName: string
31
32
  memoryService: RememberMemoryService
32
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
33
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>) => Promise<A>
33
34
  }) {
35
+ const runEffect = runPromise ?? getDefaultAiGatewayRunPromise()
34
36
  return tool({
35
37
  description:
36
38
  'Save a durable memory. Importance is scored automatically from memory significance. Use targetScope="agent" for role-specific memory, or targetScope="global" for organization-wide memory.',
@@ -44,7 +46,7 @@ export function createRememberMemoryTool({
44
46
  const orgIdString = recordIdToString(orgId, TABLES.ORGANIZATION)
45
47
  void safeEnqueue(
46
48
  () =>
47
- runPromise(
49
+ runEffect(
48
50
  Effect.gen(function* () {
49
51
  const assessment = yield* memoryService.assessMemoryCandidate({ orgId: orgIdString, content: trimmed })
50
52
  if (!assessment) {
@@ -2,6 +2,7 @@ import type Firecrawl from '@mendable/firecrawl-js'
2
2
  import type { Effect } from 'effect'
3
3
 
4
4
  import type { AiGatewayModels } from '../ai-gateway/ai-gateway'
5
+ import { aiGatewayChatModel, getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
5
6
  import { buildAiGatewayStrictSemanticCacheHeaders } from '../ai-gateway/cache-headers'
6
7
  import {
7
8
  OPENROUTER_FAST_REASONING_MODEL_ID,
@@ -14,15 +15,18 @@ import { searchWebTool } from './search-web.tool'
14
15
 
15
16
  export interface ResearchTopicToolContext {
16
17
  firecrawl: Firecrawl
17
- aiGatewayModels: AiGatewayModels
18
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
18
+ aiGatewayModels?: AiGatewayModels
19
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
19
20
  }
20
21
 
21
22
  export const researchTopicTool = createDelegatedAgentToolWithContext<ResearchTopicToolContext>({
22
23
  id: 'researchTopic',
23
24
  description:
24
25
  'Delegate a research task to a dedicated research agent that searches the web, fetches pages, and returns a synthesized markdown report.',
25
- model: ({ aiGatewayModels }) => aiGatewayModels.chatModel(OPENROUTER_FAST_REASONING_MODEL_ID),
26
+ model: ({ aiGatewayModels }) =>
27
+ aiGatewayModels
28
+ ? aiGatewayModels.chatModel(OPENROUTER_FAST_REASONING_MODEL_ID)
29
+ : aiGatewayChatModel(OPENROUTER_FAST_REASONING_MODEL_ID),
26
30
  providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
27
31
  headers: buildAiGatewayStrictSemanticCacheHeaders('researchTopic'),
28
32
  instructions: RESEARCHER_PROMPT,
@@ -31,5 +35,5 @@ export const researchTopicTool = createDelegatedAgentToolWithContext<ResearchTop
31
35
  fetchWebpage: fetchWebpageTool.create({ firecrawl, runPromise }),
32
36
  }),
33
37
  maxSteps: 6,
34
- getRunPromise: (context) => context.runPromise,
38
+ getRunPromise: (context) => context.runPromise ?? getDefaultAiGatewayRunPromise(),
35
39
  })
@@ -3,6 +3,7 @@ import { tool } from 'ai'
3
3
  import { Effect, Schema } from 'effect'
4
4
  import { z } from 'zod'
5
5
 
6
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
6
7
  import type { ToolDefinition } from '../ai/definitions'
7
8
  import { ERROR_TAGS } from '../effect/errors'
8
9
  import { withTimeout } from '../utils/async'
@@ -13,7 +14,7 @@ import { toRecord, WEB_TOOL_TIMEOUT_MS } from './web-tool-shared'
13
14
 
14
15
  export interface SearchWebToolContext {
15
16
  firecrawl: Firecrawl
16
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
17
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
17
18
  }
18
19
 
19
20
  class SearchWebToolError extends Schema.TaggedErrorClass<SearchWebToolError>()(ERROR_TAGS.SearchWebToolError, {
@@ -141,8 +142,9 @@ function buildWebCitations(results: { web?: unknown[]; news?: unknown[]; images?
141
142
 
142
143
  export const searchWebTool = {
143
144
  name: 'searchWeb',
144
- create: ({ firecrawl, runPromise }: SearchWebToolContext) =>
145
- tool({
145
+ create: ({ firecrawl, runPromise }: SearchWebToolContext) => {
146
+ const runEffect = runPromise ?? getDefaultAiGatewayRunPromise()
147
+ return tool({
146
148
  description: 'Search the web for real-time information.',
147
149
  inputSchema: z
148
150
  .object({
@@ -163,7 +165,7 @@ export const searchWebTool = {
163
165
  }: { query: string; limit?: number; sources?: ('web' | 'news' | 'images')[]; location?: string; tbs?: string },
164
166
  { abortSignal }: { abortSignal?: AbortSignal } = {},
165
167
  ) =>
166
- runPromise(
168
+ runEffect(
167
169
  Effect.gen(function* () {
168
170
  const results = yield* Effect.tryPromise({
169
171
  try: () =>
@@ -186,5 +188,6 @@ export const searchWebTool = {
186
188
  }).pipe(Effect.withSpan('tool.searchWeb.execute')),
187
189
  abortSignal ? { signal: abortSignal } : undefined,
188
190
  ),
189
- }),
191
+ })
192
+ },
190
193
  } as const satisfies ToolDefinition<SearchWebToolContext>
@@ -2,6 +2,7 @@ import { tool } from 'ai'
2
2
  import { Effect } from 'effect'
3
3
  import { z } from 'zod'
4
4
 
5
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
5
6
  import type { ResolvedAgentConfig } from '../config/agent-defaults'
6
7
  import { isAgentName } from '../config/agent-defaults'
7
8
  import type { RecordIdRef } from '../db/record-id'
@@ -16,15 +17,33 @@ type MemorySearchService = Pick<ReturnType<typeof createMemoryService>, 'searchA
16
17
  type ConversationSearchService = Pick<ReturnType<typeof makeThreadMessageService>, 'searchMessagesEffect'>
17
18
 
18
19
  type SearchToolRunPromise = <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
20
+ type MemorySearchOptions = { fastMode?: boolean; allowMultiScopeRerank?: boolean }
19
21
 
20
22
  export function createMemorySearchTool(
21
23
  agentConfig: ResolvedAgentConfig,
22
24
  orgIdString: string,
23
25
  memoryService: MemorySearchService,
24
- runPromise: SearchToolRunPromise,
25
- agentName?: string,
26
- options?: { fastMode?: boolean; allowMultiScopeRerank?: boolean },
26
+ runPromiseOrAgentName?: SearchToolRunPromise | string,
27
+ agentNameOrOptions?: string | MemorySearchOptions,
28
+ optionsArg?: MemorySearchOptions,
27
29
  ) {
30
+ const runPromise =
31
+ typeof runPromiseOrAgentName === 'function' ? runPromiseOrAgentName : getDefaultAiGatewayRunPromise()
32
+ const agentName =
33
+ typeof runPromiseOrAgentName === 'string'
34
+ ? runPromiseOrAgentName
35
+ : typeof agentNameOrOptions === 'string'
36
+ ? agentNameOrOptions
37
+ : undefined
38
+ const options =
39
+ typeof runPromiseOrAgentName === 'function'
40
+ ? typeof agentNameOrOptions === 'object'
41
+ ? agentNameOrOptions
42
+ : optionsArg
43
+ : typeof agentNameOrOptions === 'object'
44
+ ? agentNameOrOptions
45
+ : optionsArg
46
+
28
47
  return tool({
29
48
  description: 'Search organization and agent memories relevant to a query.',
30
49
  inputSchema: MemorySearchInputSchema,
@@ -55,7 +74,7 @@ export function createMemorySearchTool(
55
74
  export function createConversationSearchTool(
56
75
  threadId: RecordIdRef,
57
76
  threadMessageService: ConversationSearchService,
58
- runPromise: SearchToolRunPromise,
77
+ runPromise: SearchToolRunPromise = getDefaultAiGatewayRunPromise(),
59
78
  ) {
60
79
  return tool({
61
80
  description: 'Search prior chat messages by role and query text.',
@@ -3,6 +3,7 @@ import { stepCountIs } from 'ai'
3
3
  import type { ToolSet } from 'ai'
4
4
  import { Schema, Effect } from 'effect'
5
5
 
6
+ import { getDefaultAiGatewayRunPromise } from '../ai-gateway/ai-gateway'
6
7
  import { aiLogger } from '../config/logger'
7
8
  import type { RecordIdRef } from '../db/record-id'
8
9
  import { recordIdToString } from '../db/record-id'
@@ -72,8 +73,9 @@ export function createTeamThinkTool(params: {
72
73
  context?: unknown
73
74
  toolProviders?: ToolSet
74
75
  abortSignal: AbortSignal
75
- runPromise: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
76
+ runPromise?: <A, E>(effect: Effect.Effect<A, E, never>, options?: { signal?: AbortSignal }) => Promise<A>
76
77
  }) {
78
+ const runEffect = params.runPromise ?? getDefaultAiGatewayRunPromise()
77
79
  return Effect.gen(function* () {
78
80
  const turnHooks = yield* TurnHooksServiceTag
79
81
  const runtimeAdapters = yield* RuntimeAdaptersServiceTag
@@ -84,7 +86,7 @@ export function createTeamThinkTool(params: {
84
86
  )
85
87
  const participantRunner: TeamConsultationParticipantRunner = {
86
88
  buildParticipantAgent(agentId, runParams) {
87
- return params.runPromise(
89
+ return runEffect(
88
90
  Effect.gen(function* () {
89
91
  // Capture the fiber context of the managed runtime and replay it
90
92
  // for observer callbacks that cross the Effect → Promise boundary