@lota-sdk/core 0.1.47 → 0.1.48
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.1.
|
|
3
|
+
"version": "0.1.48",
|
|
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.1.
|
|
35
|
+
"@lota-sdk/shared": "0.1.48",
|
|
36
36
|
"@mendable/firecrawl-js": "^4.18.0",
|
|
37
37
|
"@surrealdb/node": "^3.0.3",
|
|
38
38
|
"ai": "^6.0.141",
|
|
@@ -50,22 +50,38 @@ export function createAgentMessageMetadata(params: {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
function toExternalAbortSignals(externalAbortSignal?: AbortSignal | readonly AbortSignal[]): AbortSignal[] {
|
|
54
|
+
if (!externalAbortSignal) return []
|
|
55
|
+
|
|
56
|
+
const signals = Array.isArray(externalAbortSignal) ? externalAbortSignal : [externalAbortSignal]
|
|
57
|
+
return [...new Set<AbortSignal>(signals)]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createServerRunAbortController(externalAbortSignal?: AbortSignal | readonly AbortSignal[]) {
|
|
54
61
|
const controller = new AbortController()
|
|
55
62
|
const abort = (reason?: unknown) => {
|
|
56
63
|
if (controller.signal.aborted) return
|
|
57
64
|
controller.abort(reason ?? new DOMException('Run stopped by user.', 'AbortError'))
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
const externalSignals = toExternalAbortSignals(externalAbortSignal)
|
|
68
|
+
const listeners = externalSignals.map((signal) => {
|
|
69
|
+
const abortFromExternal = () => {
|
|
70
|
+
abort((signal as AbortSignal & { reason?: unknown }).reason)
|
|
71
|
+
}
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
if (externalAbortSignal.aborted) {
|
|
73
|
+
if (signal.aborted) {
|
|
66
74
|
abortFromExternal()
|
|
67
75
|
} else {
|
|
68
|
-
|
|
76
|
+
signal.addEventListener('abort', abortFromExternal, { once: true })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { signal, abortFromExternal }
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (controller.signal.aborted) {
|
|
83
|
+
for (const { signal, abortFromExternal } of listeners) {
|
|
84
|
+
signal.removeEventListener('abort', abortFromExternal)
|
|
69
85
|
}
|
|
70
86
|
}
|
|
71
87
|
|
|
@@ -74,8 +90,9 @@ export function createServerRunAbortController(externalAbortSignal?: AbortSignal
|
|
|
74
90
|
signal: controller.signal,
|
|
75
91
|
abort,
|
|
76
92
|
dispose: () => {
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
for (const { signal, abortFromExternal } of listeners) {
|
|
94
|
+
signal.removeEventListener('abort', abortFromExternal)
|
|
95
|
+
}
|
|
79
96
|
},
|
|
80
97
|
}
|
|
81
98
|
}
|
|
@@ -734,10 +734,13 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
734
734
|
return {
|
|
735
735
|
originalMessages,
|
|
736
736
|
run: async (writer?: UIMessageStreamWriter<ChatMessage>) => {
|
|
737
|
-
const executeRun = async (): Promise<PreparedWorkstreamTurnResult | void> => {
|
|
737
|
+
const executeRun = async (leaseAbortSignal?: AbortSignal): Promise<PreparedWorkstreamTurnResult | void> => {
|
|
738
738
|
const runTimer = lotaDebugLogger.timer('run')
|
|
739
739
|
const serverRunId = Bun.randomUUIDv7()
|
|
740
|
-
const
|
|
740
|
+
const runAbortSignals = leaseAbortSignal ? [params.abortSignal, leaseAbortSignal] : [params.abortSignal]
|
|
741
|
+
const runAbort = createServerRunAbortController(
|
|
742
|
+
runAbortSignals.filter((signal): signal is AbortSignal => Boolean(signal)),
|
|
743
|
+
)
|
|
741
744
|
// Plan turns run without the chat lease — don't claim the active run slot.
|
|
742
745
|
if (params.kind !== 'planTurn') {
|
|
743
746
|
await workstreamService.setActiveTurn(workstreamRef, serverRunId, params.streamId ?? null)
|
|
@@ -1093,8 +1096,8 @@ export async function prepareWorkstreamRunCore(params: WorkstreamRunCoreParams):
|
|
|
1093
1096
|
}
|
|
1094
1097
|
|
|
1095
1098
|
try {
|
|
1096
|
-
return await workstreamService.withActiveRunLease(workstreamRef, async () => {
|
|
1097
|
-
const runResult = await executeRun()
|
|
1099
|
+
return await workstreamService.withActiveRunLease(workstreamRef, async (leaseAbortSignal) => {
|
|
1100
|
+
const runResult = await executeRun(leaseAbortSignal)
|
|
1098
1101
|
if (runResult) {
|
|
1099
1102
|
return runResult
|
|
1100
1103
|
}
|
|
@@ -3,7 +3,13 @@ import { z } from 'zod'
|
|
|
3
3
|
|
|
4
4
|
import { aiGatewayChatModel } from '../ai-gateway/ai-gateway'
|
|
5
5
|
import { buildAiGatewayDirectCacheHeaders } from '../ai-gateway/cache-headers'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
agentDescriptions,
|
|
8
|
+
agentDisplayNames,
|
|
9
|
+
agentShortDisplayNames,
|
|
10
|
+
resolveAgentNameAlias,
|
|
11
|
+
routerModelId,
|
|
12
|
+
} from '../config/agent-defaults'
|
|
7
13
|
|
|
8
14
|
// ---------------------------------------------------------------------------
|
|
9
15
|
// Schemas
|
|
@@ -34,6 +40,60 @@ function buildMembersDescription(members: readonly string[]): string {
|
|
|
34
40
|
.join('\n')
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
function escapeRegex(value: string): string {
|
|
44
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildExplicitAgentRoutingContext(agentId: string): string {
|
|
48
|
+
const displayName = agentDisplayNames[agentId] ?? agentId
|
|
49
|
+
return `Respond directly to the part of the user's request explicitly addressed to ${displayName}.`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function extractExplicitAgentTargets(messageText: string, members: readonly string[]): string[] {
|
|
53
|
+
const normalizedMessage = messageText.trim()
|
|
54
|
+
if (!normalizedMessage) return []
|
|
55
|
+
|
|
56
|
+
const memberSet = new Set(members)
|
|
57
|
+
const aliases = new Map<string, string>()
|
|
58
|
+
|
|
59
|
+
for (const member of members) {
|
|
60
|
+
for (const rawAlias of [member, agentDisplayNames[member], agentShortDisplayNames[member]]) {
|
|
61
|
+
if (typeof rawAlias !== 'string') continue
|
|
62
|
+
const alias = rawAlias.trim().toLowerCase()
|
|
63
|
+
if (!alias) continue
|
|
64
|
+
aliases.set(alias, member)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (aliases.size === 0) return []
|
|
69
|
+
|
|
70
|
+
const orderedAliases = [...aliases.keys()].sort((left, right) => right.length - left.length)
|
|
71
|
+
const matches: Array<{ agentId: string; index: number }> = []
|
|
72
|
+
const directAddressRegex = new RegExp(
|
|
73
|
+
`(^|[^\\w@])(?<alias>${orderedAliases.map(escapeRegex).join('|')})(?=\\s*(?::|\\-|—))`,
|
|
74
|
+
'gi',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
for (const match of normalizedMessage.matchAll(directAddressRegex)) {
|
|
78
|
+
const alias = match.groups?.alias
|
|
79
|
+
const agentId = alias ? resolveAgentNameAlias(alias) : undefined
|
|
80
|
+
if (!agentId || !memberSet.has(agentId)) continue
|
|
81
|
+
|
|
82
|
+
const prefix = match[1]
|
|
83
|
+
const index = match.index + prefix.length
|
|
84
|
+
matches.push({ agentId, index })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const seenAgents = new Set<string>()
|
|
88
|
+
return matches
|
|
89
|
+
.sort((left, right) => left.index - right.index)
|
|
90
|
+
.flatMap(({ agentId }) => {
|
|
91
|
+
if (seenAgents.has(agentId)) return []
|
|
92
|
+
seenAgents.add(agentId)
|
|
93
|
+
return [agentId]
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
37
97
|
function extractJson(text: string): unknown {
|
|
38
98
|
const match = text.match(/\{[\s\S]*\}/)
|
|
39
99
|
if (!match) return null
|
|
@@ -53,7 +113,13 @@ function extractResultText(result: { text?: string; reasoning?: unknown }): stri
|
|
|
53
113
|
if (typeof reasoning === 'string') return reasoning
|
|
54
114
|
if (Array.isArray(reasoning)) {
|
|
55
115
|
return reasoning
|
|
56
|
-
.map((r) =>
|
|
116
|
+
.map((r) => {
|
|
117
|
+
if (typeof r === 'string') return r
|
|
118
|
+
if (typeof r !== 'object' || r === null || !('text' in r)) return ''
|
|
119
|
+
|
|
120
|
+
const text = (r as { text?: unknown }).text
|
|
121
|
+
return typeof text === 'string' ? text : ''
|
|
122
|
+
})
|
|
57
123
|
.join('')
|
|
58
124
|
}
|
|
59
125
|
return ''
|
|
@@ -112,6 +178,12 @@ export async function triageWorkstreamMessage(params: {
|
|
|
112
178
|
messageText: string
|
|
113
179
|
recentContext?: string
|
|
114
180
|
}): Promise<RouterTriageResult | null> {
|
|
181
|
+
const explicitTargets = extractExplicitAgentTargets(params.messageText, params.members)
|
|
182
|
+
const firstExplicitTarget = explicitTargets[0]
|
|
183
|
+
if (firstExplicitTarget) {
|
|
184
|
+
return { agentId: firstExplicitTarget, routingContext: buildExplicitAgentRoutingContext(firstExplicitTarget) }
|
|
185
|
+
}
|
|
186
|
+
|
|
115
187
|
const membersDesc = buildMembersDescription(params.members)
|
|
116
188
|
const prompt = [
|
|
117
189
|
`Workstream: "${params.workstreamTitle}"`,
|
|
@@ -159,6 +231,16 @@ export async function checkForNextAgent(params: {
|
|
|
159
231
|
respondedAgents: string[]
|
|
160
232
|
lastResponseSummary: string
|
|
161
233
|
}): Promise<RouterCheckResult> {
|
|
234
|
+
const explicitTargets = extractExplicitAgentTargets(params.messageText, params.members)
|
|
235
|
+
const nextExplicitTarget = explicitTargets.find((agentId) => !params.respondedAgents.includes(agentId))
|
|
236
|
+
if (nextExplicitTarget) {
|
|
237
|
+
return {
|
|
238
|
+
done: false,
|
|
239
|
+
agentId: nextExplicitTarget,
|
|
240
|
+
routingContext: buildExplicitAgentRoutingContext(nextExplicitTarget),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
162
244
|
const remainingMembers = params.members.filter((id) => !params.respondedAgents.includes(id))
|
|
163
245
|
if (remainingMembers.length === 0) return { done: true }
|
|
164
246
|
|