@lota-sdk/core 0.3.2 → 0.4.0

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.4.0",
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.4.0",
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,14 +55,8 @@ 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 {
59
- return {
60
- ...params,
61
- headers: mergeAiGatewayHeaders(
62
- params.headers,
63
- buildAiGatewayCacheHeaders(`model:${toAiGatewayCacheKeyPart(modelId)}`),
64
- ),
65
- }
58
+ function withDefaultAiGatewayCacheHeaders(params: AiGatewayCallOptions): AiGatewayCallOptions {
59
+ return { ...params, headers: mergeAiGatewayHeaders(params.headers, buildAiGatewayCacheHeaders('lota-sdk')) }
66
60
  }
67
61
 
68
62
  function normalizeAiGatewayUrl(value: string): string {
@@ -417,7 +411,7 @@ export function aiGatewayModel(modelId: string) {
417
411
  middleware: {
418
412
  specificationVersion: 'v3',
419
413
  transformParams: async ({ params, type }) =>
420
- withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type), modelId),
414
+ withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type)),
421
415
  wrapStream: async ({ doStream, params }) => {
422
416
  const result = await doStream()
423
417
  if (!isReasoningEnabled(params)) return result
@@ -435,7 +429,7 @@ export function aiGatewayOpenRouterResponseHealingModel(modelId: string) {
435
429
  model: getAiGatewayOpenRouterResponseHealingProvider()(modelId),
436
430
  middleware: {
437
431
  specificationVersion: 'v3',
438
- transformParams: async ({ params }) => withDefaultAiGatewayCacheHeaders(params, modelId),
432
+ transformParams: async ({ params }) => withDefaultAiGatewayCacheHeaders(params),
439
433
  },
440
434
  }),
441
435
  )
@@ -449,7 +443,7 @@ export function aiGatewayChatModel(modelId: string) {
449
443
  specificationVersion: 'v3',
450
444
  transformParams: async ({ params, type }) =>
451
445
  normalizeAiGatewayChatProviderOptions(
452
- withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type), modelId),
446
+ withDefaultAiGatewayCacheHeaders(addAiGatewayReasoningRawChunks(params, type)),
453
447
  ),
454
448
  wrapGenerate: async ({ doGenerate }) => {
455
449
  const result = await doGenerate()
@@ -1 +1 @@
1
- export const LOTA_SDK_DATABASE_NAME = 'lotasdk'
1
+ export const LOTA_SDK_DATABASE_NAME = 'lotasdk_thread'
@@ -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
@@ -368,7 +368,7 @@ class PlanRunService {
368
368
 
369
369
  // Slim mode: non-active/ready nodes get summary only (used for prompt injection via JSON.stringify).
370
370
  // The cast is safe — this data is only consumed by formatExecutionPlansForPrompt, not by Zod validation.
371
- // Plan introspection tools (getExecutionPlanDetails) call toSerializablePlan without slim=true.
371
+ // executionPlanQuery calls toSerializablePlan without slim=true for full inspection payloads.
372
372
  if (slim && !isActiveOrReady) {
373
373
  return {
374
374
  id: nodeSpec.nodeId,
@@ -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
  },