@lota-sdk/core 0.1.17 → 0.1.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.
@@ -101,6 +101,90 @@ function sanitizeStateText(value: string): string | null {
101
101
  return normalized
102
102
  }
103
103
 
104
+ function buildExistingWorkstreamStateForCompactionPrompt(state: WorkstreamState): Record<string, unknown> {
105
+ const currentPlanText = state.currentPlan ? sanitizeStateText(state.currentPlan.text) : null
106
+
107
+ const activeConstraints = state.activeConstraints
108
+ .map((constraint) => ({
109
+ id: constraint.id,
110
+ text: sanitizeStateText(constraint.text),
111
+ source: constraint.source,
112
+ approved: constraint.approved,
113
+ sourceMessageIds: constraint.sourceMessageIds,
114
+ }))
115
+ .filter((constraint): constraint is typeof constraint & { text: string } => Boolean(constraint.text))
116
+
117
+ const keyDecisions = state.keyDecisions
118
+ .map((decision) => ({
119
+ id: decision.id,
120
+ decision: sanitizeStateText(decision.decision),
121
+ rationale: sanitizeStateText(decision.rationale),
122
+ agent: decision.agent,
123
+ sourceMessageIds: decision.sourceMessageIds,
124
+ confidence: decision.confidence,
125
+ }))
126
+ .filter((decision): decision is typeof decision & { decision: string; rationale: string } =>
127
+ Boolean(decision.decision && decision.rationale),
128
+ )
129
+
130
+ const tasks = state.tasks
131
+ .map((task) => ({
132
+ id: task.id,
133
+ title: sanitizeStateText(task.title),
134
+ status: task.status,
135
+ owner: sanitizeStateText(task.owner),
136
+ externalId: task.externalId,
137
+ source: task.source,
138
+ sourceMessageIds: task.sourceMessageIds,
139
+ }))
140
+ .filter((task): task is typeof task & { title: string; owner: string } => Boolean(task.title && task.owner))
141
+
142
+ const openQuestions = state.openQuestions
143
+ .map((question) => ({
144
+ id: question.id,
145
+ text: sanitizeStateText(question.text),
146
+ source: question.source,
147
+ sourceMessageIds: question.sourceMessageIds,
148
+ }))
149
+ .filter((question): question is typeof question & { text: string } => Boolean(question.text))
150
+
151
+ const artifacts = state.artifacts
152
+ .map((artifact) => ({
153
+ id: artifact.id,
154
+ name: sanitizeStateText(artifact.name),
155
+ type: artifact.type,
156
+ pointer: sanitizeStateText(artifact.pointer),
157
+ }))
158
+ .filter((artifact): artifact is typeof artifact & { name: string; pointer: string } =>
159
+ Boolean(artifact.name && artifact.pointer),
160
+ )
161
+
162
+ const agentContributions = state.agentContributions
163
+ .map((note) => ({ id: note.id, agent: note.agent, summary: sanitizeStateText(note.summary) }))
164
+ .filter((note): note is typeof note & { summary: string } => Boolean(note.summary))
165
+
166
+ return {
167
+ currentPlan: currentPlanText
168
+ ? {
169
+ id: state.currentPlan?.id,
170
+ text: currentPlanText,
171
+ source: state.currentPlan?.source,
172
+ approved: state.currentPlan?.approved,
173
+ sourceMessageIds: state.currentPlan?.sourceMessageIds ?? [],
174
+ }
175
+ : null,
176
+ activeConstraints,
177
+ keyDecisions,
178
+ tasks,
179
+ openQuestions,
180
+ risks: state.risks.map((risk) => sanitizeStateText(risk)).filter((risk): risk is string => Boolean(risk)),
181
+ artifacts,
182
+ agentContributions,
183
+ approvedBy: state.approvedBy ? sanitizeStateText(state.approvedBy) : undefined,
184
+ approvalNote: state.approvalNote ? sanitizeStateText(state.approvalNote) : undefined,
185
+ }
186
+ }
187
+
104
188
  function createStableId(prefix: string, ...parts: Array<string | number | undefined>): string {
105
189
  const payload = parts
106
190
  .map((part) => (part === undefined ? '' : String(part)))
@@ -488,7 +572,7 @@ export function buildContextCompactionPrompt(params: ContextCompactionPromptPara
488
572
  params.previousSummary.trim() || 'None',
489
573
  '</previous-summary>',
490
574
  '<existing-workstream-state>',
491
- JSON.stringify(params.existingState),
575
+ JSON.stringify(buildExistingWorkstreamStateForCompactionPrompt(params.existingState)),
492
576
  '</existing-workstream-state>',
493
577
  '<new-messages>',
494
578
  params.transcript || 'None',
@@ -605,27 +689,34 @@ export function createContextCompactionRuntime(
605
689
  .filter((constraint): constraint is typeof constraint & { text: string } => Boolean(constraint.text))
606
690
 
607
691
  const openQuestions = state.openQuestions
608
- .map((question) => ({ ...question, text: sanitizeStateText(question.text) }))
609
- .filter((question): question is typeof question & { text: string } => Boolean(question.text))
692
+ .map((question) => sanitizeStateText(question.text))
693
+ .filter((question): question is string => Boolean(question))
610
694
 
611
695
  const decisions = state.keyDecisions
612
696
  .map((decision) => ({
613
- ...decision,
697
+ agent: decision.agent,
614
698
  decision: sanitizeStateText(decision.decision),
615
699
  rationale: sanitizeStateText(decision.rationale),
700
+ confidence: decision.confidence,
616
701
  }))
617
702
  .filter((decision): decision is typeof decision & { decision: string; rationale: string } =>
618
703
  Boolean(decision.decision && decision.rationale),
619
704
  )
620
705
 
621
706
  const tasks = state.tasks
622
- .map((task) => ({ ...task, title: sanitizeStateText(task.title), owner: sanitizeStateText(task.owner) }))
707
+ .map((task) => ({
708
+ title: sanitizeStateText(task.title),
709
+ status: task.status,
710
+ owner: sanitizeStateText(task.owner),
711
+ externalId: task.externalId,
712
+ source: task.source,
713
+ }))
623
714
  .filter((task): task is typeof task & { title: string; owner: string } => Boolean(task.title && task.owner))
624
715
 
625
716
  const artifacts = state.artifacts
626
717
  .map((artifact) => ({
627
- ...artifact,
628
718
  name: sanitizeStateText(artifact.name),
719
+ type: artifact.type,
629
720
  pointer: sanitizeStateText(artifact.pointer),
630
721
  }))
631
722
  .filter((artifact): artifact is typeof artifact & { name: string; pointer: string } =>
@@ -633,7 +724,7 @@ export function createContextCompactionRuntime(
633
724
  )
634
725
 
635
726
  const agentContributions = state.agentContributions
636
- .map((note) => ({ ...note, summary: sanitizeStateText(note.summary) }))
727
+ .map((note) => ({ agent: note.agent, summary: sanitizeStateText(note.summary) }))
637
728
  .filter((note): note is typeof note & { summary: string } => Boolean(note.summary))
638
729
 
639
730
  const payload = {
@@ -661,12 +752,9 @@ export function createContextCompactionRuntime(
661
752
  artifacts,
662
753
  agentContributions,
663
754
  advisory: {
664
- approvedBy: state.approvedBy ?? null,
665
- approvedAt: state.approvedAt ?? null,
666
- approvalMessageId: state.approvalMessageId ?? null,
667
- approvalNote: state.approvalNote ?? null,
755
+ approvedBy: state.approvedBy ? sanitizeStateText(state.approvedBy) : null,
756
+ approvalNote: state.approvalNote ? sanitizeStateText(state.approvalNote) : null,
668
757
  },
669
- lastUpdated: state.lastUpdated,
670
758
  }
671
759
 
672
760
  return ['<workstream-state>', JSON.stringify(payload, null, 2), '</workstream-state>'].join('\n')
@@ -1,3 +1,5 @@
1
+ import { formatUtcPromptDate } from '../utils/date-time'
2
+
1
3
  export function getFactRetrievalMessages(
2
4
  parsedMessages: string,
3
5
  customPrompt?: string,
@@ -38,7 +40,7 @@ Hard rules:
38
40
  - Prefer returning fewer items. If uncertain, return an empty list.
39
41
  - Max ${maxFacts} facts.
40
42
 
41
- Today's date is ${new Date().toISOString().split('T')[0]}.
43
+ Today's date is ${formatUtcPromptDate(new Date())}.
42
44
  ${baseInstructions}`
43
45
 
44
46
  const userPrompt = `Conversation:\n${parsedMessages}`
@@ -220,8 +220,6 @@ export const LotaRuntimeConfigSchema = z.object({
220
220
  aiGateway: z.object({
221
221
  url: z.string().trim().min(1),
222
222
  key: z.string().trim().min(1),
223
- admin: z.string().trim().min(1).optional(),
224
- pass: z.string().trim().min(1).optional(),
225
223
  embeddingModel: z.string().trim().min(1).default('openai/text-embedding-3-small'),
226
224
  }),
227
225
  s3: z.object({
@@ -295,8 +293,6 @@ export const LOTA_RUNTIME_ENV_KEYS = Object.freeze([
295
293
  'REDIS_URL',
296
294
  'AI_GATEWAY_URL',
297
295
  'AI_GATEWAY_KEY',
298
- 'AI_GATEWAY_ADMIN',
299
- 'AI_GATEWAY_PASS',
300
296
  'AI_EMBEDDING_MODEL',
301
297
  'S3_ENDPOINT',
302
298
  'S3_BUCKET',
@@ -10,7 +10,7 @@ import {
10
10
  } from '../system-agents/title-generator.agent'
11
11
  import { workstreamService } from './workstream.service'
12
12
 
13
- const WORKSTREAM_TITLE_TIMEOUT_MS = 5_000
13
+ const WORKSTREAM_TITLE_TIMEOUT_MS = 30_000
14
14
 
15
15
  class WorkstreamTitleService {
16
16
  helperRuntime = createHelperModelRuntime()
@@ -632,7 +632,10 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
632
632
  const workstreamRecord = await waitForWorkstreamCompactionIfNeeded(workstreamRef)
633
633
  timer.step('compaction-gate')
634
634
  if (toOptionalTrimmedString(workstreamRecord.activeRunId)) {
635
- throw new WorkstreamTurnError('A chat run is already active.', 409)
635
+ const clearedStaleRun = await workstreamService.clearStaleActiveRunIfMissingFromRegistry(workstreamRef)
636
+ if (!clearedStaleRun) {
637
+ throw new WorkstreamTurnError('A chat run is already active.', 409)
638
+ }
636
639
  }
637
640
 
638
641
  if (params.kind === 'approvalContinuation' || params.kind === 'nativeToolApprovalTurn') {
@@ -525,6 +525,22 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
525
525
  )
526
526
  }
527
527
 
528
+ async clearStaleActiveRunIfMissingFromRegistry(workstreamId: RecordIdRef): Promise<boolean> {
529
+ const activeRunId = await this.getActiveRunId(workstreamId)
530
+ if (!activeRunId || chatRunRegistry.has(activeRunId)) {
531
+ return false
532
+ }
533
+
534
+ const activeStreamId = await this.getActiveStreamId(workstreamId)
535
+ await Promise.all([
536
+ this.clearActiveRunIdIfMatches(workstreamId, activeRunId),
537
+ activeStreamId ? this.clearActiveStreamIdIfMatches(workstreamId, activeStreamId) : Promise.resolve(),
538
+ ])
539
+
540
+ serverLogger.warn`Cleared stale workstream run after process restart: workstream=${recordIdToString(ensureRecordId(workstreamId, TABLES.WORKSTREAM), TABLES.WORKSTREAM)} run=${activeRunId}`
541
+ return true
542
+ }
543
+
528
544
  async stopActiveRun(workstreamId: RecordIdRef): Promise<boolean> {
529
545
  const activeRunId = await this.getActiveRunId(workstreamId)
530
546
  if (!activeRunId) return false
@@ -534,7 +550,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
534
550
  return true
535
551
  }
536
552
 
537
- await this.clearActiveRunIdIfMatches(workstreamId, activeRunId)
553
+ await this.clearStaleActiveRunIfMissingFromRegistry(workstreamId)
538
554
  return false
539
555
  }
540
556
 
@@ -675,6 +691,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
675
691
  typeof workstream.activeRunId === 'string' && workstream.activeRunId.trim().length > 0
676
692
  ? workstream.activeRunId
677
693
  : null
694
+ const isRunning = activeRunId !== null && chatRunRegistry.has(activeRunId)
678
695
  const isCompacting = workstream.isCompacting === true
679
696
  const mode = workstream.mode
680
697
  const core = workstream.core
@@ -688,7 +705,7 @@ class WorkstreamService extends BaseService<typeof WorkstreamSchema> {
688
705
  core,
689
706
  ...(coreType ? { coreType } : {}),
690
707
  nameGenerated: workstream.nameGenerated,
691
- isRunning: activeRunId !== null,
708
+ isRunning,
692
709
  isCompacting,
693
710
  ...(isAgentName(workstream.agentId) ? { agentId: workstream.agentId } : {}),
694
711
  title: workstream.title ?? this.getDefaultTitle(workstream),
@@ -1,7 +1,7 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
 
3
- import { bifrostOpenRouterResponseHealingModel } from '../bifrost/bifrost'
4
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
3
+ import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
4
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
5
5
  import {
6
6
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
7
7
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
@@ -40,8 +40,8 @@ Return valid data for:
40
40
  export function createContextCompactionAgent(options: CreateHelperToolLoopAgentOptions) {
41
41
  return new ToolLoopAgent({
42
42
  id: 'context-compaction',
43
- model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
44
- headers: buildBifrostCacheHeaders('context-compaction'),
43
+ model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
44
+ headers: buildAiGatewayDirectCacheHeaders('context-compaction'),
45
45
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
46
46
  ...resolveHelperAgentOptions(options, { instructions: CONTEXT_COMPACTION_PROMPT }),
47
47
  })
@@ -4,6 +4,7 @@ import { z } from 'zod'
4
4
 
5
5
  import type { ToolDefinition } from '../ai/definitions'
6
6
  import { aiLogger } from '../config/logger'
7
+ import { formatUtcPromptDate } from '../utils/date-time'
7
8
  import { isRecord } from '../utils/string'
8
9
  import { assertSubstantiveAgentResult } from './agent-result'
9
10
 
@@ -32,15 +33,7 @@ function resolveAgentModel(model: AgentModel): LanguageModel {
32
33
  }
33
34
 
34
35
  function buildCurrentDateContext(now = new Date()): string {
35
- const isoDate = now.toISOString().slice(0, 10)
36
- const humanDate = new Intl.DateTimeFormat('en-US', {
37
- timeZone: 'UTC',
38
- year: 'numeric',
39
- month: 'long',
40
- day: 'numeric',
41
- }).format(now)
42
-
43
- return [`Today is ${isoDate} (${humanDate}, UTC).`, 'Use this exact date for any recency reasoning.'].join(' ')
36
+ return [`Today is ${formatUtcPromptDate(now)}.`, 'Use this exact date for any recency reasoning.'].join(' ')
44
37
  }
45
38
 
46
39
  export function buildRecencyInstructions(tools?: ToolSet): string {
@@ -1,7 +1,7 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
 
3
- import { bifrostOpenRouterResponseHealingModel } from '../bifrost/bifrost'
4
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
3
+ import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
4
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
5
5
  import {
6
6
  OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
7
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
@@ -32,8 +32,8 @@ Set every item.relevance as a string; use empty string when no reason is needed.
32
32
  export function createMemoryRerankerAgent(options: CreateHelperToolLoopAgentOptions) {
33
33
  return new ToolLoopAgent({
34
34
  id: 'memory-reranker',
35
- model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
36
- headers: buildBifrostCacheHeaders('memory-reranker'),
35
+ model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
36
+ headers: buildAiGatewayDirectCacheHeaders('memory-reranker'),
37
37
  providerOptions: OPENROUTER_LOW_REASONING_PROVIDER_OPTIONS,
38
38
  ...resolveHelperAgentOptions(options),
39
39
  })
@@ -1,7 +1,7 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
 
3
- import { bifrostOpenRouterResponseHealingModel } from '../bifrost/bifrost'
4
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
3
+ import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
4
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
5
5
  import {
6
6
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
7
7
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
@@ -52,8 +52,8 @@ Return only the schema fields with no extra formatting.
52
52
  export function createOrgMemoryAgent(options: CreateHelperToolLoopAgentOptions) {
53
53
  return new ToolLoopAgent({
54
54
  id: 'org-memory',
55
- model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
56
- headers: buildBifrostCacheHeaders('org-memory'),
55
+ model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
56
+ headers: buildAiGatewayDirectCacheHeaders('org-memory'),
57
57
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
58
58
  ...resolveHelperAgentOptions(options),
59
59
  })
@@ -1,7 +1,7 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
 
3
- import { bifrostModel } from '../bifrost/bifrost'
4
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
3
+ import { aiGatewayModel } from '../ai-gateway/ai-gateway'
4
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
5
5
  import { getLeadAgentDisplayName } from '../config/agent-defaults'
6
6
  import {
7
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
@@ -79,8 +79,8 @@ Return only the title text. No quotes, labels, JSON, markdown, or explanation.
79
79
  export function createRecentActivityTitleRefinerAgent(options: CreateHelperToolLoopAgentOptions) {
80
80
  return new ToolLoopAgent({
81
81
  id: 'recent-activity-title-refiner',
82
- model: bifrostModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
83
- headers: buildBifrostCacheHeaders('recent-activity-title-refiner'),
82
+ model: aiGatewayModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
83
+ headers: buildAiGatewayDirectCacheHeaders('recent-activity-title-refiner'),
84
84
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
85
85
  ...resolveHelperAgentOptions(options, {
86
86
  instructions: buildRecentActivityTitleRefinerPrompt(),
@@ -1,7 +1,7 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
 
3
- import { bifrostOpenRouterResponseHealingModel } from '../bifrost/bifrost'
4
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
3
+ import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
4
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
5
5
  import {
6
6
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
7
7
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
@@ -66,8 +66,8 @@ Return only schema fields.
66
66
  export function createRegularChatMemoryDigestAgent(options: CreateHelperToolLoopAgentOptions) {
67
67
  return new ToolLoopAgent({
68
68
  id: 'regular-chat-memory-digest',
69
- model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
70
- headers: buildBifrostCacheHeaders('regular-chat-memory-digest'),
69
+ model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
70
+ headers: buildAiGatewayDirectCacheHeaders('regular-chat-memory-digest'),
71
71
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
72
72
  ...resolveHelperAgentOptions(options, {
73
73
  instructions: regularChatMemoryDigestPrompt,
@@ -1,8 +1,8 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
  import { z } from 'zod'
3
3
 
4
- import { bifrostOpenRouterResponseHealingModel } from '../bifrost/bifrost'
5
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
4
+ import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
5
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import {
7
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
8
8
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
@@ -79,8 +79,8 @@ export type SkillCandidate = z.infer<typeof SkillCandidateSchema>
79
79
  export function createSkillExtractorAgent(options: CreateHelperToolLoopAgentOptions) {
80
80
  return new ToolLoopAgent({
81
81
  id: 'skill-extractor',
82
- model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
83
- headers: buildBifrostCacheHeaders('skill-extractor'),
82
+ model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
83
+ headers: buildAiGatewayDirectCacheHeaders('skill-extractor'),
84
84
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
85
85
  ...resolveHelperAgentOptions(options, {
86
86
  instructions: skillExtractorPrompt,
@@ -1,8 +1,8 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
  import { z } from 'zod'
3
3
 
4
- import { bifrostOpenRouterResponseHealingModel } from '../bifrost/bifrost'
5
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
4
+ import { aiGatewayOpenRouterResponseHealingModel } from '../ai-gateway/ai-gateway'
5
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
6
6
  import {
7
7
  OPENROUTER_STRUCTURED_HELPER_MODEL_ID,
8
8
  OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
@@ -69,8 +69,8 @@ export const SkillManagerOutputSchema = z.object({
69
69
  export function createSkillManagerAgent(options: CreateHelperToolLoopAgentOptions) {
70
70
  return new ToolLoopAgent({
71
71
  id: 'skill-manager',
72
- model: bifrostOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
73
- headers: buildBifrostCacheHeaders('skill-manager'),
72
+ model: aiGatewayOpenRouterResponseHealingModel(OPENROUTER_STRUCTURED_HELPER_MODEL_ID),
73
+ headers: buildAiGatewayDirectCacheHeaders('skill-manager'),
74
74
  providerOptions: OPENROUTER_HIGH_REASONING_PROVIDER_OPTIONS,
75
75
  ...resolveHelperAgentOptions(options, {
76
76
  instructions: skillManagerPrompt,
@@ -1,7 +1,7 @@
1
1
  import { ToolLoopAgent } from 'ai'
2
2
 
3
- import { bifrostModel } from '../bifrost/bifrost'
4
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
3
+ import { aiGatewayModel } from '../ai-gateway/ai-gateway'
4
+ import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
5
5
  import {
6
6
  OPENROUTER_FAST_REASONING_MODEL_ID,
7
7
  OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
@@ -33,8 +33,8 @@ Return only the title text. No quotes, no labels, no explanation.
33
33
  export function createWorkstreamTitleGeneratorAgent(options: CreateHelperToolLoopAgentOptions) {
34
34
  return new ToolLoopAgent({
35
35
  id: 'workstream-title-generator',
36
- model: bifrostModel(OPENROUTER_FAST_REASONING_MODEL_ID),
37
- headers: buildBifrostCacheHeaders('workstream-title-generator'),
36
+ model: aiGatewayModel(OPENROUTER_FAST_REASONING_MODEL_ID),
37
+ headers: buildAiGatewayDirectCacheHeaders('workstream-title-generator'),
38
38
  providerOptions: OPENROUTER_MINIMAL_REASONING_PROVIDER_OPTIONS,
39
39
  ...resolveHelperAgentOptions(options, {
40
40
  instructions: WORKSTREAM_TITLE_GENERATOR_PROMPT,
@@ -1,5 +1,5 @@
1
- import { bifrostChatModel } from '../bifrost/bifrost'
2
- import { buildBifrostCacheHeaders } from '../bifrost/cache-headers'
1
+ import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
2
+ import { buildAiGatewayStrictSemanticCacheHeaders } from '../ai-gateway/cache-headers'
3
3
  import {
4
4
  OPENROUTER_MEDIUM_REASONING_PROVIDER_OPTIONS,
5
5
  OPENROUTER_WEB_RESEARCH_MODEL_ID,
@@ -13,9 +13,9 @@ export const researchTopicTool = createDelegatedAgentTool({
13
13
  id: 'researchTopic',
14
14
  description:
15
15
  'Delegate a research task to a dedicated research agent that searches the web, fetches pages, and returns a synthesized markdown report. Call multiple instances in parallel for broad research across different topics.',
16
- model: () => bifrostChatModel(OPENROUTER_WEB_RESEARCH_MODEL_ID),
16
+ model: () => aiGatewayChatModel(OPENROUTER_WEB_RESEARCH_MODEL_ID),
17
17
  providerOptions: OPENROUTER_MEDIUM_REASONING_PROVIDER_OPTIONS,
18
- headers: buildBifrostCacheHeaders('researchTopic'),
18
+ headers: buildAiGatewayStrictSemanticCacheHeaders('researchTopic'),
19
19
  instructions: RESEARCHER_PROMPT,
20
20
  tools: { searchWeb: searchWebTool.create(), fetchWebpage: fetchWebpageTool.create() },
21
21
  })
@@ -1,6 +1,17 @@
1
1
  export { toIsoDateTimeString, toOptionalIsoDateTimeString } from '@lota-sdk/shared'
2
2
 
3
+ const PROMPT_DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
4
+ timeZone: 'UTC',
5
+ year: 'numeric',
6
+ month: 'long',
7
+ day: 'numeric',
8
+ })
9
+
3
10
  export function toDatabaseDateTime(value: string | Date | null | undefined): Date | undefined {
4
11
  if (value === null || value === undefined) return undefined
5
12
  return value instanceof Date ? value : new Date(value)
6
13
  }
14
+
15
+ export function formatUtcPromptDate(value: Date): string {
16
+ return PROMPT_DATE_FORMATTER.format(value)
17
+ }
@@ -1,6 +1,6 @@
1
1
  import { CHARS_PER_TOKEN_ESTIMATE } from '../../utils/string'
2
2
 
3
- export const DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS = 250_000
3
+ export const DEFAULT_FILE_SECTION_CHUNK_MAX_CHARS = 1_500_000
4
4
  export const MIN_FILE_SECTION_CHUNK_MAX_CHARS = 4_000
5
5
  export const DEFAULT_FILE_SECTION_CHUNK_MIN_CHARS = 10_000
6
6
  const SECTION_SEPARATOR_LENGTH = 2