@lota-sdk/core 0.3.2 → 0.3.3

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.3.2",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -32,7 +32,7 @@
32
32
  "@chat-adapter/slack": "^4.23.0",
33
33
  "@chat-adapter/state-ioredis": "^4.23.0",
34
34
  "@logtape/logtape": "^2.0.5",
35
- "@lota-sdk/shared": "0.3.2",
35
+ "@lota-sdk/shared": "0.3.3",
36
36
  "@mendable/firecrawl-js": "^4.18.1",
37
37
  "@surrealdb/node": "^3.0.3",
38
38
  "ai": "^6.0.145",
@@ -5,7 +5,7 @@ import type { LanguageModelMiddleware } from 'ai'
5
5
 
6
6
  import { getRuntimeConfig } from '../runtime/runtime-config'
7
7
  import { isRecord, readString } from '../utils/string'
8
- import { buildAiGatewayCacheHeaders, toAiGatewayCacheKeyPart } from './cache-headers'
8
+ import { buildAiGatewayCacheHeaders } from './cache-headers'
9
9
 
10
10
  type AiGatewayLanguageModel = Parameters<typeof wrapLanguageModel>[0]['model']
11
11
  type AiGatewayExtraParams = Record<string, unknown>
@@ -55,13 +55,10 @@ function parseAiGatewayJsonRequestBody(body: BodyInit | null | undefined): Recor
55
55
  return isRecord(parsed) ? parsed : null
56
56
  }
57
57
 
58
- function withDefaultAiGatewayCacheHeaders(params: AiGatewayCallOptions, modelId: string): AiGatewayCallOptions {
58
+ function withDefaultAiGatewayCacheHeaders(params: AiGatewayCallOptions): AiGatewayCallOptions {
59
59
  return {
60
60
  ...params,
61
- headers: mergeAiGatewayHeaders(
62
- params.headers,
63
- buildAiGatewayCacheHeaders(`model:${toAiGatewayCacheKeyPart(modelId)}`),
64
- ),
61
+ headers: mergeAiGatewayHeaders(params.headers, buildAiGatewayCacheHeaders('lota-sdk')),
65
62
  }
66
63
  }
67
64
 
@@ -417,7 +414,7 @@ export function aiGatewayModel(modelId: string) {
417
414
  middleware: {
418
415
  specificationVersion: 'v3',
419
416
  transformParams: async ({ params, type }) =>
420
- withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type), modelId),
417
+ withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type)),
421
418
  wrapStream: async ({ doStream, params }) => {
422
419
  const result = await doStream()
423
420
  if (!isReasoningEnabled(params)) return result
@@ -435,7 +432,7 @@ export function aiGatewayOpenRouterResponseHealingModel(modelId: string) {
435
432
  model: getAiGatewayOpenRouterResponseHealingProvider()(modelId),
436
433
  middleware: {
437
434
  specificationVersion: 'v3',
438
- transformParams: async ({ params }) => withDefaultAiGatewayCacheHeaders(params, modelId),
435
+ transformParams: async ({ params }) => withDefaultAiGatewayCacheHeaders(params),
439
436
  },
440
437
  }),
441
438
  )
@@ -449,7 +446,7 @@ export function aiGatewayChatModel(modelId: string) {
449
446
  specificationVersion: 'v3',
450
447
  transformParams: async ({ params, type }) =>
451
448
  normalizeAiGatewayChatProviderOptions(
452
- withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type), modelId),
449
+ withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type)),
453
450
  ),
454
451
  wrapGenerate: async ({ doGenerate }) => {
455
452
  const result = await doGenerate()
@@ -38,7 +38,10 @@ let sharedSubscriber: { client: Redis; subscriber: Subscriber } | undefined
38
38
 
39
39
  function getSharedSubscriber(): Subscriber {
40
40
  if (!sharedSubscriber) {
41
- const client = getRedisConnection().duplicate()
41
+ // Disable enableReadyCheck — the ready check sends INFO which is rejected
42
+ // on connections in subscribe mode, causing unhandled ioredis error events.
43
+ const client = getRedisConnection().duplicate({ enableReadyCheck: false })
44
+ client.on('error', () => {}) // prevent [ioredis] Unhandled error event logs
42
45
  sharedSubscriber = { client, subscriber: toSubscriber(client) }
43
46
  }
44
47
  return sharedSubscriber.subscriber
@@ -712,6 +712,19 @@ class ThreadService extends BaseService<typeof ThreadSchema> {
712
712
  return true
713
713
  }
714
714
 
715
+ async clearThread(threadId: RecordIdRef): Promise<void> {
716
+ const threadRef = ensureRecordId(threadId, TABLES.THREAD)
717
+ await databaseService.deleteWhere(TABLES.THREAD_MESSAGE, { threadId: threadRef })
718
+ await databaseService.query<unknown>(surql`
719
+ UPDATE ONLY ${threadRef}
720
+ SET turnCount = 0,
721
+ compactionSummary = NONE,
722
+ lastCompactedMessageId = NONE,
723
+ activeRunId = NONE,
724
+ activeStreamId = NONE
725
+ `)
726
+ }
727
+
715
728
  async deleteThread(threadId: RecordIdRef): Promise<void> {
716
729
  const existing = await this.getById(threadId)
717
730
  assertMutableThread(existing)
@@ -35,7 +35,7 @@ export function createContextCompactionAgent(options: CreateHelperToolLoopAgentO
35
35
  return new ToolLoopAgent({
36
36
  id: 'context-compaction',
37
37
  model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
38
- headers: buildAiGatewayDirectCacheHeaders('context-compaction'),
38
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
39
39
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
40
40
  ...resolveHelperAgentOptions(options, { instructions: CONTEXT_COMPACTION_PROMPT }),
41
41
  })
@@ -33,7 +33,7 @@ export function createMemoryRerankerAgent(options: CreateHelperToolLoopAgentOpti
33
33
  return new ToolLoopAgent({
34
34
  id: 'memory-reranker',
35
35
  model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
36
- headers: buildAiGatewayDirectCacheHeaders('memory-reranker'),
36
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
37
37
  providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
38
38
  ...resolveHelperAgentOptions(options),
39
39
  })
@@ -53,7 +53,7 @@ export function createOrgMemoryAgent(options: CreateHelperToolLoopAgentOptions)
53
53
  return new ToolLoopAgent({
54
54
  id: 'org-memory',
55
55
  model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
56
- headers: buildAiGatewayDirectCacheHeaders('org-memory'),
56
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
57
57
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
58
58
  ...resolveHelperAgentOptions(options),
59
59
  })
@@ -80,7 +80,7 @@ export function createRecentActivityTitleRefinerAgent(options: CreateHelperToolL
80
80
  return new ToolLoopAgent({
81
81
  id: 'recent-activity-title-refiner',
82
82
  model: aiGatewayModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
83
- headers: buildAiGatewayDirectCacheHeaders('recent-activity-title-refiner'),
83
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
84
84
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
85
85
  ...resolveHelperAgentOptions(options, {
86
86
  instructions: buildRecentActivityTitleRefinerPrompt(),
@@ -28,7 +28,7 @@ export function createRegularChatMemoryDigestAgent(options: CreateHelperToolLoop
28
28
  return new ToolLoopAgent({
29
29
  id: 'regular-chat-memory-digest',
30
30
  model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
31
- headers: buildAiGatewayDirectCacheHeaders('regular-chat-memory-digest'),
31
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
32
32
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
33
33
  ...resolveHelperAgentOptions(options, {
34
34
  instructions: regularChatMemoryDigestPrompt,
@@ -46,7 +46,7 @@ export function createSkillExtractorAgent(options: CreateHelperToolLoopAgentOpti
46
46
  return new ToolLoopAgent({
47
47
  id: 'skill-extractor',
48
48
  model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
49
- headers: buildAiGatewayDirectCacheHeaders('skill-extractor'),
49
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
50
50
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
51
51
  ...resolveHelperAgentOptions(options, {
52
52
  instructions: skillExtractorPrompt,
@@ -70,7 +70,7 @@ export function createSkillManagerAgent(options: CreateHelperToolLoopAgentOption
70
70
  return new ToolLoopAgent({
71
71
  id: 'skill-manager',
72
72
  model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
73
- headers: buildAiGatewayDirectCacheHeaders('skill-manager'),
73
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
74
74
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
75
75
  ...resolveHelperAgentOptions(options, {
76
76
  instructions: skillManagerPrompt,
@@ -165,7 +165,7 @@ async function generateRouterObject<TSchema extends z.ZodTypeAny>(params: {
165
165
  const { object } = await withTimeout(
166
166
  generateObject({
167
167
  model: aiGatewayChatModel(modelId),
168
- headers: buildAiGatewayDirectCacheHeaders('thread-router'),
168
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
169
169
  providerOptions: { openai: { reasoningEffort: 'low' } },
170
170
  schema: params.schema,
171
171
  system: params.system,
@@ -34,7 +34,7 @@ export function createThreadTitleGeneratorAgent(options: CreateHelperToolLoopAge
34
34
  return new ToolLoopAgent({
35
35
  id: 'thread-title-generator',
36
36
  model: aiGatewayModel(OPENROUTER_FAST_REASONING_MODEL_ID),
37
- headers: buildAiGatewayDirectCacheHeaders('thread-title-generator'),
37
+ headers: buildAiGatewayDirectCacheHeaders('lota-sdk'),
38
38
  providerOptions: OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
39
39
  ...resolveHelperAgentOptions(options, {
40
40
  instructions: THREAD_TITLE_GENERATOR_PROMPT,
@@ -5,7 +5,7 @@ import {
5
5
  expandAgentPlanDraft,
6
6
  getLatestExecutionPlanResult,
7
7
  } from '@lota-sdk/shared'
8
- import type { CreateProjectWithPlanResultData, ExecutionPlanAction, ExecutionPlanArgs } from '@lota-sdk/shared'
8
+ import type { CreateProjectWithPlanResultData } from '@lota-sdk/shared'
9
9
  import { tool } from 'ai'
10
10
 
11
11
  import type { RecordIdRef } from '../db/record-id'
@@ -26,11 +26,6 @@ type ExecutionPlanExecutionPlanService = Pick<
26
26
  | 'getActivePlansForThread'
27
27
  >
28
28
 
29
- function extractDraft(input: ExecutionPlanArgs) {
30
- const { action, projectTitle, targetThreadId, runId, reason, ...draftInput } = input
31
- return { action, projectTitle, targetThreadId, runId, reason, draft: expandAgentPlanDraft(draftInput) }
32
- }
33
-
34
29
  export function createExecutionPlanTool(params: {
35
30
  orgId: RecordIdRef
36
31
  userId: RecordIdRef
@@ -49,20 +44,26 @@ export function createExecutionPlanTool(params: {
49
44
  'Manage execution plans. Actions: create (inline, 1-2 nodes), create-project (dedicated project thread, 3+ nodes), replace (swap active plan), resume (resume interrupted plan).',
50
45
  inputSchema: ExecutionPlanArgsSchema,
51
46
  execute: async (input) => {
52
- const { action, projectTitle, targetThreadId, runId, reason, draft } = extractDraft(input)
47
+ const parsed = ExecutionPlanArgsSchema.parse(input)
48
+ let result: unknown
53
49
 
54
- const handler: Record<ExecutionPlanAction, () => Promise<unknown>> = {
55
- create: async () => {
50
+ switch (parsed.action) {
51
+ case 'create': {
52
+ const { action: _action, targetThreadId, ...draftInput } = parsed
53
+ const draft = expandAgentPlanDraft(draftInput)
56
54
  params.validateInlinePlan?.(draft)
57
- return await resolvedEpService.createPlan({
55
+ result = await resolvedEpService.createPlan({
58
56
  organizationId: params.orgId,
59
57
  threadId: targetThreadId ?? params.threadId,
60
58
  leadAgentId: params.agentId,
61
59
  input: draft,
62
60
  })
63
- },
61
+ break
62
+ }
64
63
 
65
- 'create-project': async () => {
64
+ case 'create-project': {
65
+ const { action: _action, projectTitle, targetThreadId, ...draftInput } = parsed
66
+ const draft = expandAgentPlanDraft(draftInput)
66
67
  const targetThread = targetThreadId
67
68
  ? await resolvedWsService.getThread(targetThreadId)
68
69
  : await (() => {
@@ -94,14 +95,14 @@ export function createExecutionPlanTool(params: {
94
95
 
95
96
  const createdThread = !targetThreadId
96
97
  try {
97
- const result = await resolvedEpService.createPlan({
98
+ const created = await resolvedEpService.createPlan({
98
99
  organizationId: params.orgId,
99
100
  threadId: targetThread.id,
100
101
  leadAgentId: params.agentId,
101
102
  input: draft,
102
103
  })
103
- return {
104
- ...result,
104
+ result = {
105
+ ...created,
105
106
  threadId: targetThread.id,
106
107
  threadTitle: targetThread.title,
107
108
  createdThread,
@@ -112,35 +113,30 @@ export function createExecutionPlanTool(params: {
112
113
  }
113
114
  throw error
114
115
  }
115
- },
116
-
117
- replace: async () => {
118
- if (!runId || !reason) {
119
- throw new Error('runId and reason are required when action is "replace".')
120
- }
116
+ break
117
+ }
121
118
 
122
- return await resolvedEpService.replacePlan({
119
+ case 'replace': {
120
+ const { action: _action, runId, reason, ...draftInput } = parsed
121
+ const draft = expandAgentPlanDraft(draftInput)
122
+ result = await resolvedEpService.replacePlan({
123
123
  organizationId: params.orgId,
124
124
  threadId: params.threadId,
125
125
  leadAgentId: params.agentId,
126
126
  input: { runId, reason, ...draft },
127
127
  })
128
- },
129
-
130
- resume: async () => {
131
- if (!runId) {
132
- throw new Error('runId is required when action is "resume".')
133
- }
128
+ break
129
+ }
134
130
 
135
- return await resolvedEpService.resumeRun({
131
+ case 'resume':
132
+ result = await resolvedEpService.resumeRun({
136
133
  threadId: params.threadId,
137
134
  emittedBy: params.agentId,
138
- input: { runId },
135
+ input: { runId: parsed.runId },
139
136
  })
140
- },
137
+ break
141
138
  }
142
139
 
143
- const result = await handler[action]()
144
140
  params.onPlanChanged?.()
145
141
  return result
146
142
  },