@lota-sdk/core 0.1.5
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/infrastructure/schema/00_workstream.surql +55 -0
- package/infrastructure/schema/01_memory.surql +47 -0
- package/infrastructure/schema/02_execution_plan.surql +62 -0
- package/infrastructure/schema/03_learned_skill.surql +32 -0
- package/infrastructure/schema/04_runtime_bootstrap.surql +8 -0
- package/package.json +128 -0
- package/src/ai/definitions.ts +308 -0
- package/src/bifrost/bifrost.ts +256 -0
- package/src/config/agent-defaults.ts +99 -0
- package/src/config/constants.ts +33 -0
- package/src/config/env-shapes.ts +122 -0
- package/src/config/logger.ts +29 -0
- package/src/config/model-constants.ts +31 -0
- package/src/config/search.ts +17 -0
- package/src/config/workstream-defaults.ts +68 -0
- package/src/db/base.service.ts +55 -0
- package/src/db/cursor-pagination.ts +73 -0
- package/src/db/memory-query-builder.ts +207 -0
- package/src/db/memory-store.helpers.ts +118 -0
- package/src/db/memory-store.rows.ts +29 -0
- package/src/db/memory-store.ts +974 -0
- package/src/db/memory-types.ts +193 -0
- package/src/db/memory.ts +505 -0
- package/src/db/record-id.ts +78 -0
- package/src/db/service.ts +932 -0
- package/src/db/startup.ts +152 -0
- package/src/db/tables.ts +20 -0
- package/src/document/org-document-chunking.ts +224 -0
- package/src/document/parsing.ts +40 -0
- package/src/embeddings/provider.ts +76 -0
- package/src/index.ts +302 -0
- package/src/queues/context-compaction.queue.ts +82 -0
- package/src/queues/document-processor.queue.ts +118 -0
- package/src/queues/memory-consolidation.queue.ts +65 -0
- package/src/queues/post-chat-memory.queue.ts +128 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +69 -0
- package/src/queues/regular-chat-memory-digest.config.ts +12 -0
- package/src/queues/regular-chat-memory-digest.queue.ts +73 -0
- package/src/queues/skill-extraction.config.ts +9 -0
- package/src/queues/skill-extraction.queue.ts +62 -0
- package/src/redis/connection.ts +176 -0
- package/src/redis/index.ts +30 -0
- package/src/redis/org-memory-lock.ts +43 -0
- package/src/redis/redis-lease-lock.ts +158 -0
- package/src/runtime/agent-contract.ts +1 -0
- package/src/runtime/agent-prompt-context.ts +119 -0
- package/src/runtime/agent-runtime-policy.ts +192 -0
- package/src/runtime/agent-stream-helpers.ts +117 -0
- package/src/runtime/agent-types.ts +22 -0
- package/src/runtime/approval-continuation.ts +16 -0
- package/src/runtime/chat-attachments.ts +46 -0
- package/src/runtime/chat-message.ts +10 -0
- package/src/runtime/chat-request-routing.ts +21 -0
- package/src/runtime/chat-run-orchestration.ts +25 -0
- package/src/runtime/chat-run-registry.ts +20 -0
- package/src/runtime/chat-types.ts +18 -0
- package/src/runtime/context-compaction-constants.ts +11 -0
- package/src/runtime/context-compaction-runtime.ts +86 -0
- package/src/runtime/context-compaction.ts +909 -0
- package/src/runtime/execution-plan.ts +59 -0
- package/src/runtime/helper-model.ts +405 -0
- package/src/runtime/indexed-repositories-policy.ts +28 -0
- package/src/runtime/instruction-sections.ts +8 -0
- package/src/runtime/llm-content.ts +71 -0
- package/src/runtime/memory-block.ts +264 -0
- package/src/runtime/memory-digest-policy.ts +14 -0
- package/src/runtime/memory-format.ts +8 -0
- package/src/runtime/memory-pipeline.ts +570 -0
- package/src/runtime/memory-prompts-fact.ts +47 -0
- package/src/runtime/memory-prompts-parse.ts +3 -0
- package/src/runtime/memory-prompts-update.ts +37 -0
- package/src/runtime/memory-scope.ts +43 -0
- package/src/runtime/plugin-types.ts +10 -0
- package/src/runtime/retrieval-adapters.ts +25 -0
- package/src/runtime/retrieval-pipeline.ts +3 -0
- package/src/runtime/runtime-extensions.ts +154 -0
- package/src/runtime/skill-extraction-policy.ts +3 -0
- package/src/runtime/team-consultation-orchestrator.ts +245 -0
- package/src/runtime/team-consultation-prompts.ts +32 -0
- package/src/runtime/title-helpers.ts +12 -0
- package/src/runtime/turn-lifecycle.ts +28 -0
- package/src/runtime/workstream-chat-helpers.ts +187 -0
- package/src/runtime/workstream-routing-policy.ts +301 -0
- package/src/runtime/workstream-state.ts +261 -0
- package/src/services/attachment.service.ts +159 -0
- package/src/services/chat-attachments.service.ts +17 -0
- package/src/services/chat-run-registry.service.ts +3 -0
- package/src/services/context-compaction-runtime.ts +13 -0
- package/src/services/context-compaction.service.ts +115 -0
- package/src/services/document-chunk.service.ts +141 -0
- package/src/services/execution-plan.service.ts +890 -0
- package/src/services/learned-skill.service.ts +328 -0
- package/src/services/memory-assessment.service.ts +43 -0
- package/src/services/memory.service.ts +807 -0
- package/src/services/memory.utils.ts +84 -0
- package/src/services/mutating-approval.service.ts +110 -0
- package/src/services/recent-activity-title.service.ts +74 -0
- package/src/services/recent-activity.service.ts +397 -0
- package/src/services/workstream-change-tracker.service.ts +313 -0
- package/src/services/workstream-message.service.ts +283 -0
- package/src/services/workstream-title.service.ts +58 -0
- package/src/services/workstream-turn-preparation.ts +1340 -0
- package/src/services/workstream-turn.ts +37 -0
- package/src/services/workstream.service.ts +854 -0
- package/src/services/workstream.types.ts +118 -0
- package/src/storage/attachment-parser.ts +101 -0
- package/src/storage/attachment-storage.service.ts +391 -0
- package/src/storage/attachments.types.ts +11 -0
- package/src/storage/attachments.utils.ts +58 -0
- package/src/storage/generated-document-storage.service.ts +55 -0
- package/src/system-agents/agent-result.ts +27 -0
- package/src/system-agents/context-compacter.agent.ts +46 -0
- package/src/system-agents/delegated-agent-factory.ts +177 -0
- package/src/system-agents/helper-agent-options.ts +20 -0
- package/src/system-agents/memory-reranker.agent.ts +38 -0
- package/src/system-agents/memory.agent.ts +58 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +53 -0
- package/src/system-agents/regular-chat-memory-digest.agent.ts +75 -0
- package/src/system-agents/researcher.agent.ts +34 -0
- package/src/system-agents/skill-extractor.agent.ts +88 -0
- package/src/system-agents/skill-manager.agent.ts +80 -0
- package/src/system-agents/title-generator.agent.ts +42 -0
- package/src/system-agents/workstream-tracker.agent.ts +58 -0
- package/src/tools/execution-plan.tool.ts +163 -0
- package/src/tools/fetch-webpage.tool.ts +132 -0
- package/src/tools/firecrawl-client.ts +12 -0
- package/src/tools/memory-block.tool.ts +55 -0
- package/src/tools/read-file-parts.tool.ts +80 -0
- package/src/tools/remember-memory.tool.ts +85 -0
- package/src/tools/research-topic.tool.ts +15 -0
- package/src/tools/search-tools.ts +55 -0
- package/src/tools/search-web.tool.ts +175 -0
- package/src/tools/team-think.tool.ts +125 -0
- package/src/tools/tool-contract.ts +21 -0
- package/src/tools/user-questions.tool.ts +18 -0
- package/src/utils/async.ts +50 -0
- package/src/utils/date-time.ts +34 -0
- package/src/utils/error.ts +10 -0
- package/src/utils/errors.ts +28 -0
- package/src/utils/hono-error-handler.ts +71 -0
- package/src/utils/string.ts +51 -0
- package/src/workers/bootstrap.ts +44 -0
- package/src/workers/memory-consolidation.worker.ts +318 -0
- package/src/workers/regular-chat-memory-digest.helpers.ts +100 -0
- package/src/workers/regular-chat-memory-digest.runner.ts +363 -0
- package/src/workers/regular-chat-memory-digest.worker.ts +22 -0
- package/src/workers/skill-extraction.runner.ts +331 -0
- package/src/workers/skill-extraction.worker.ts +22 -0
- package/src/workers/utils/repo-indexer-chunker.ts +331 -0
- package/src/workers/utils/repo-structure-extractor.ts +645 -0
- package/src/workers/utils/repomix-process-concurrency.ts +65 -0
- package/src/workers/utils/sandbox-error.ts +5 -0
- package/src/workers/worker-utils.ts +182 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { SerializableExecutionPlan } from '@lota-sdk/shared/schemas/execution-plan'
|
|
2
|
+
|
|
3
|
+
export const EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT = `<execution-plan-protocol>
|
|
4
|
+
- Before doing multi-step work, create an execution plan instead of tracking steps only in prose.
|
|
5
|
+
- Keep plans short and operational. Prefer 2 to 7 tasks unless the work truly needs more.
|
|
6
|
+
- Use execution-plan tools to create, replace, update, inspect, and restart the plan.
|
|
7
|
+
- Only one execution-plan task may be active at a time.
|
|
8
|
+
- Every task should have a concrete rationale and a concise output summary when work happens.
|
|
9
|
+
- Treat prior task output summaries and carried tasks in <execution-plan-state> as observed facts.
|
|
10
|
+
- If the ordered steps materially change, replace the plan instead of silently rewriting it in prose.
|
|
11
|
+
- If the active plan is blocked, do not continue blindly. Replan, restart a task, ask for user input, or abort.
|
|
12
|
+
</execution-plan-protocol>`
|
|
13
|
+
|
|
14
|
+
export function formatExecutionPlanForPrompt(plan: SerializableExecutionPlan | null | undefined): string | undefined {
|
|
15
|
+
if (!plan) return undefined
|
|
16
|
+
|
|
17
|
+
const payload = {
|
|
18
|
+
policy: {
|
|
19
|
+
activePlanIsAuthoritative: true,
|
|
20
|
+
priorOutputSummariesAreObservedFacts: true,
|
|
21
|
+
singleActiveTask: true,
|
|
22
|
+
explicitReplanRequired: true,
|
|
23
|
+
failureBudget: 2,
|
|
24
|
+
},
|
|
25
|
+
plan,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return ['<execution-plan-state>', JSON.stringify(payload, null, 2), '</execution-plan-state>'].join('\n')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildExecutionPlanInstructionSections(
|
|
32
|
+
plan: SerializableExecutionPlan | null | undefined,
|
|
33
|
+
): string[] | undefined {
|
|
34
|
+
const sections = [EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT]
|
|
35
|
+
const executionPlanStateSection = formatExecutionPlanForPrompt(plan)
|
|
36
|
+
if (executionPlanStateSection) {
|
|
37
|
+
sections.push(executionPlanStateSection)
|
|
38
|
+
}
|
|
39
|
+
return sections
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createExecutionPlanInstructionSectionCache(params: {
|
|
43
|
+
disabled?: boolean
|
|
44
|
+
loadPlan: () => Promise<SerializableExecutionPlan | null | undefined>
|
|
45
|
+
}) {
|
|
46
|
+
let sectionsPromise: Promise<string[] | undefined> | null = null
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
invalidate() {
|
|
50
|
+
sectionsPromise = null
|
|
51
|
+
},
|
|
52
|
+
async getSections(): Promise<string[] | undefined> {
|
|
53
|
+
if (params.disabled) return undefined
|
|
54
|
+
|
|
55
|
+
sectionsPromise ??= params.loadPlan().then((plan) => buildExecutionPlanInstructionSections(plan))
|
|
56
|
+
return await sectionsPromise
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { Output } from 'ai'
|
|
2
|
+
import type {
|
|
3
|
+
TimeoutConfiguration,
|
|
4
|
+
ToolLoopAgentOnFinishCallback,
|
|
5
|
+
ToolLoopAgentOnStepFinishCallback,
|
|
6
|
+
ToolSet,
|
|
7
|
+
} from 'ai'
|
|
8
|
+
import type { ZodSchema } from 'zod'
|
|
9
|
+
|
|
10
|
+
export interface HelperToolLoopAgentOptions {
|
|
11
|
+
instructions?: string
|
|
12
|
+
maxOutputTokens?: number
|
|
13
|
+
temperature?: number
|
|
14
|
+
output?: Output.Output
|
|
15
|
+
maxRetries?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HelperMessage {
|
|
19
|
+
role: 'system' | 'user' | 'assistant'
|
|
20
|
+
content: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface HelperAgentGenerateParams {
|
|
24
|
+
messages: Array<{ role: 'user' | 'assistant'; content: string }>
|
|
25
|
+
timeout?: TimeoutConfiguration
|
|
26
|
+
onStepFinish?: ToolLoopAgentOnStepFinishCallback<ToolSet>
|
|
27
|
+
onFinish?: ToolLoopAgentOnFinishCallback<ToolSet>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface HelperAgent {
|
|
31
|
+
generate(params: HelperAgentGenerateParams): Promise<{ text?: unknown; output?: unknown }>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type CreateHelperAgentFn = (options: HelperToolLoopAgentOptions) => HelperAgent
|
|
35
|
+
|
|
36
|
+
export interface GenerateHelperTextParams {
|
|
37
|
+
tag: string
|
|
38
|
+
createAgent: CreateHelperAgentFn
|
|
39
|
+
messages: HelperMessage[]
|
|
40
|
+
systemPrompt?: string
|
|
41
|
+
defaultSystemPrompt?: string
|
|
42
|
+
temperature?: number
|
|
43
|
+
maxOutputTokens?: number
|
|
44
|
+
timeoutMs?: number
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GenerateHelperStructuredParams<T> extends Omit<GenerateHelperTextParams, 'tag'> {
|
|
48
|
+
tag: string
|
|
49
|
+
schema: ZodSchema<T>
|
|
50
|
+
textFallbackParser?: (text: string) => T | null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
54
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getNumericField(value: Record<string, unknown>, key: string): number | null {
|
|
58
|
+
const field = value[key]
|
|
59
|
+
if (typeof field === 'number' && Number.isFinite(field)) return field
|
|
60
|
+
if (typeof field === 'string') {
|
|
61
|
+
const parsed = Number(field)
|
|
62
|
+
if (Number.isFinite(parsed)) return parsed
|
|
63
|
+
}
|
|
64
|
+
return null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getErrorStatus(error: unknown): number | null {
|
|
68
|
+
if (!isObject(error)) return null
|
|
69
|
+
return getNumericField(error, 'status') ?? getNumericField(error, 'statusCode')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isRateLimitError(error: unknown): boolean {
|
|
73
|
+
return getErrorStatus(error) === 429
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function stringifyUnknown(value: unknown, maxChars: number): string | null {
|
|
77
|
+
if (value === null || value === undefined) return null
|
|
78
|
+
const raw = (() => {
|
|
79
|
+
if (typeof value === 'string') return value
|
|
80
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
81
|
+
return String(value)
|
|
82
|
+
}
|
|
83
|
+
if (typeof value === 'symbol') return value.description ? `Symbol(${value.description})` : 'Symbol()'
|
|
84
|
+
if (typeof value === 'function') return value.name ? `[function ${value.name}]` : '[function anonymous]'
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.stringify(value)
|
|
88
|
+
} catch {
|
|
89
|
+
return `[array(${value.length})]`
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
return JSON.stringify(value)
|
|
95
|
+
} catch {
|
|
96
|
+
const maybeName =
|
|
97
|
+
isObject(value) && typeof (value as { constructor?: { name?: unknown } }).constructor?.name === 'string'
|
|
98
|
+
? (value as { constructor?: { name?: string } }).constructor?.name
|
|
99
|
+
: 'Object'
|
|
100
|
+
return `[object ${maybeName}]`
|
|
101
|
+
}
|
|
102
|
+
})()
|
|
103
|
+
|
|
104
|
+
const normalized = raw.replace(/\s+/g, ' ').trim()
|
|
105
|
+
if (!normalized) return null
|
|
106
|
+
return normalized.length > maxChars ? `${normalized.slice(0, maxChars)}...` : normalized
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function formatError(tag: string, error: unknown): Error {
|
|
110
|
+
const status = getErrorStatus(error)
|
|
111
|
+
const rateLimited = isRateLimitError(error)
|
|
112
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
113
|
+
const errorRecord = isObject(error) ? error : null
|
|
114
|
+
const responseBody = errorRecord ? stringifyUnknown(errorRecord.responseBody, 600) : null
|
|
115
|
+
const responseData = errorRecord ? stringifyUnknown(errorRecord.data, 600) : null
|
|
116
|
+
const requestUrl = errorRecord ? stringifyUnknown(errorRecord.url, 200) : null
|
|
117
|
+
|
|
118
|
+
const parts = [`[${tag}]`]
|
|
119
|
+
if (status !== null) parts.push(`status=${status}`)
|
|
120
|
+
if (rateLimited) parts.push('rate_limited')
|
|
121
|
+
parts.push(message)
|
|
122
|
+
if (responseData) parts.push(`provider_data=${responseData}`)
|
|
123
|
+
if (responseBody) parts.push(`response_body=${responseBody}`)
|
|
124
|
+
if (requestUrl) parts.push(`url=${requestUrl}`)
|
|
125
|
+
|
|
126
|
+
return new Error(parts.join(' '))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function toModelMessages(messages: HelperMessage[]): Array<{ role: 'user' | 'assistant'; content: string }> {
|
|
130
|
+
return messages
|
|
131
|
+
.filter((message): message is HelperMessage & { role: 'user' | 'assistant' } => message.role !== 'system')
|
|
132
|
+
.map((message) => ({ role: message.role, content: message.content }))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveSystemPrompt(params: {
|
|
136
|
+
explicitSystemPrompt?: string
|
|
137
|
+
defaultSystemPrompt?: string
|
|
138
|
+
}): string | undefined {
|
|
139
|
+
if (typeof params.explicitSystemPrompt === 'string') {
|
|
140
|
+
const explicit = params.explicitSystemPrompt.trim()
|
|
141
|
+
return explicit.length > 0 ? explicit : undefined
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const fallback = params.defaultSystemPrompt?.trim()
|
|
145
|
+
return fallback && fallback.length > 0 ? fallback : undefined
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolveStructuredSystemPrompt(systemPrompt?: string): string {
|
|
149
|
+
const parts = [
|
|
150
|
+
systemPrompt?.trim(),
|
|
151
|
+
'Return only a valid JSON object that matches the required schema. Do not wrap it in markdown or code fences.',
|
|
152
|
+
].filter((part): part is string => Boolean(part && part.length > 0))
|
|
153
|
+
|
|
154
|
+
return parts.join('\n\n')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function formatSchemaIssueSummary(issues: Array<{ path: PropertyKey[]; message: string }>): string {
|
|
158
|
+
return issues
|
|
159
|
+
.slice(0, 5)
|
|
160
|
+
.map((issue) => `${issue.path.map((segment) => String(segment)).join('.') || 'root'}: ${issue.message}`)
|
|
161
|
+
.join('; ')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function extractJsonObjectCandidates(text: string): string[] {
|
|
165
|
+
const trimmed = text.trim()
|
|
166
|
+
if (!trimmed) return []
|
|
167
|
+
|
|
168
|
+
const candidates: string[] = [trimmed]
|
|
169
|
+
const fencedMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/i)
|
|
170
|
+
if (fencedMatch?.[1]) {
|
|
171
|
+
candidates.push(fencedMatch[1].trim())
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let start = -1
|
|
175
|
+
let depth = 0
|
|
176
|
+
let inString = false
|
|
177
|
+
let escaping = false
|
|
178
|
+
|
|
179
|
+
for (let index = 0; index < trimmed.length; index += 1) {
|
|
180
|
+
const character = trimmed[index]
|
|
181
|
+
|
|
182
|
+
if (escaping) {
|
|
183
|
+
escaping = false
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (character === '\\') {
|
|
188
|
+
escaping = true
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (character === '"') {
|
|
193
|
+
inString = !inString
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (inString) continue
|
|
198
|
+
|
|
199
|
+
if (character === '{') {
|
|
200
|
+
if (depth === 0) start = index
|
|
201
|
+
depth += 1
|
|
202
|
+
continue
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (character === '}') {
|
|
206
|
+
depth -= 1
|
|
207
|
+
if (depth === 0 && start >= 0) {
|
|
208
|
+
candidates.push(trimmed.slice(start, index + 1))
|
|
209
|
+
start = -1
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return [...new Set(candidates.filter((candidate) => candidate.length > 0))]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseStructuredCandidate<T>(params: {
|
|
218
|
+
schema: ZodSchema<T>
|
|
219
|
+
candidate: unknown
|
|
220
|
+
}): { data: T; source: string } | null {
|
|
221
|
+
const direct = params.schema.safeParse(params.candidate)
|
|
222
|
+
if (direct.success) {
|
|
223
|
+
return { data: direct.data, source: 'root' }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (isObject(params.candidate)) {
|
|
227
|
+
for (const key of ['output', 'result', 'data'] as const) {
|
|
228
|
+
const nested = params.candidate[key]
|
|
229
|
+
const nestedParsed = params.schema.safeParse(nested)
|
|
230
|
+
if (nestedParsed.success) {
|
|
231
|
+
return { data: nestedParsed.data, source: key }
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return null
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function parseStructuredTextFallback<T>(params: { schema: ZodSchema<T>; text: string }): T {
|
|
240
|
+
const candidates = extractJsonObjectCandidates(params.text)
|
|
241
|
+
if (candidates.length === 0) {
|
|
242
|
+
throw new Error('Structured fallback did not contain a JSON object candidate.')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let lastError = 'Structured fallback could not be parsed.'
|
|
246
|
+
|
|
247
|
+
for (const candidateText of candidates) {
|
|
248
|
+
let parsedJson: unknown
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
parsedJson = JSON.parse(candidateText) as unknown
|
|
252
|
+
} catch {
|
|
253
|
+
lastError = 'Structured fallback JSON parsing failed.'
|
|
254
|
+
continue
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const parsed = parseStructuredCandidate({ schema: params.schema, candidate: parsedJson })
|
|
258
|
+
if (parsed) {
|
|
259
|
+
return parsed.data
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const issues = params.schema.safeParse(parsedJson)
|
|
263
|
+
if (!issues.success) {
|
|
264
|
+
lastError = `Structured fallback failed schema validation: ${formatSchemaIssueSummary(issues.error.issues)}`
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
throw new Error(lastError)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseStructuredTextWithFallbacks<T>(params: {
|
|
272
|
+
schema: ZodSchema<T>
|
|
273
|
+
text: string
|
|
274
|
+
textFallbackParser?: (text: string) => T | null
|
|
275
|
+
}): T {
|
|
276
|
+
try {
|
|
277
|
+
return parseStructuredTextFallback({ schema: params.schema, text: params.text })
|
|
278
|
+
} catch (error) {
|
|
279
|
+
const parseError = error instanceof Error ? error : new Error(String(error))
|
|
280
|
+
|
|
281
|
+
if (params.textFallbackParser) {
|
|
282
|
+
const parsedCandidate = params.textFallbackParser(params.text)
|
|
283
|
+
if (parsedCandidate !== null) {
|
|
284
|
+
const validated = params.schema.safeParse(parsedCandidate)
|
|
285
|
+
if (validated.success) {
|
|
286
|
+
return validated.data
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
throw new Error(
|
|
290
|
+
`Custom text fallback failed schema validation: ${formatSchemaIssueSummary(validated.error.issues)}`,
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
throw parseError
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function createHelperModelRuntime() {
|
|
300
|
+
async function generateHelperText(params: GenerateHelperTextParams): Promise<string> {
|
|
301
|
+
const systemPrompt = resolveSystemPrompt({
|
|
302
|
+
explicitSystemPrompt: params.systemPrompt,
|
|
303
|
+
defaultSystemPrompt: params.defaultSystemPrompt,
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
if (!systemPrompt && params.messages.length === 0) {
|
|
307
|
+
throw new Error(`[${params.tag}] Empty helper messages`)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const agent = params.createAgent({
|
|
312
|
+
...(systemPrompt ? { instructions: systemPrompt } : {}),
|
|
313
|
+
...(typeof params.temperature === 'number' ? { temperature: params.temperature } : {}),
|
|
314
|
+
...(typeof params.maxOutputTokens === 'number' ? { maxOutputTokens: params.maxOutputTokens } : {}),
|
|
315
|
+
maxRetries: 2,
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
const result = await agent.generate({
|
|
319
|
+
messages: toModelMessages(params.messages),
|
|
320
|
+
...(typeof params.timeoutMs === 'number' ? { timeout: params.timeoutMs } : {}),
|
|
321
|
+
})
|
|
322
|
+
const text = typeof result.text === 'string' ? result.text.trim() : ''
|
|
323
|
+
if (!text) {
|
|
324
|
+
throw new Error('Empty helper model output')
|
|
325
|
+
}
|
|
326
|
+
return text
|
|
327
|
+
} catch (error) {
|
|
328
|
+
throw formatError(params.tag, error)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function generateHelperStructured<T>(params: GenerateHelperStructuredParams<T>): Promise<T> {
|
|
333
|
+
const baseSystemPrompt = resolveSystemPrompt({
|
|
334
|
+
explicitSystemPrompt: params.systemPrompt,
|
|
335
|
+
defaultSystemPrompt: params.defaultSystemPrompt,
|
|
336
|
+
})
|
|
337
|
+
const systemPrompt = resolveStructuredSystemPrompt(baseSystemPrompt)
|
|
338
|
+
|
|
339
|
+
if (!baseSystemPrompt && params.messages.length === 0) {
|
|
340
|
+
throw new Error(`[${params.tag}] Empty helper messages`)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const agent = params.createAgent({
|
|
345
|
+
output: Output.object({ schema: params.schema }),
|
|
346
|
+
...(systemPrompt ? { instructions: systemPrompt } : {}),
|
|
347
|
+
...(typeof params.temperature === 'number' ? { temperature: params.temperature } : {}),
|
|
348
|
+
...(typeof params.maxOutputTokens === 'number' ? { maxOutputTokens: params.maxOutputTokens } : {}),
|
|
349
|
+
maxRetries: 2,
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
const result = await agent.generate({
|
|
353
|
+
messages: toModelMessages(params.messages),
|
|
354
|
+
...(typeof params.timeoutMs === 'number' ? { timeout: params.timeoutMs } : {}),
|
|
355
|
+
})
|
|
356
|
+
const parsed = parseStructuredCandidate({ schema: params.schema, candidate: result.output })
|
|
357
|
+
if (parsed) {
|
|
358
|
+
return parsed.data
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (typeof result.text === 'string' && result.text.trim().length > 0) {
|
|
362
|
+
return parseStructuredTextWithFallbacks({
|
|
363
|
+
schema: params.schema,
|
|
364
|
+
text: result.text,
|
|
365
|
+
textFallbackParser: params.textFallbackParser,
|
|
366
|
+
})
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const fallbackParsed = params.schema.safeParse(result.output)
|
|
370
|
+
if (!fallbackParsed.success) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
`Structured output failed schema validation: ${formatSchemaIssueSummary(fallbackParsed.error.issues)}`,
|
|
373
|
+
)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return fallbackParsed.data
|
|
377
|
+
} catch (error) {
|
|
378
|
+
const structuredError = formatError(params.tag, error)
|
|
379
|
+
const fallbackMessages: string[] = []
|
|
380
|
+
const fallbackPrompts = [
|
|
381
|
+
systemPrompt,
|
|
382
|
+
...(baseSystemPrompt && baseSystemPrompt !== systemPrompt ? [baseSystemPrompt] : []),
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
for (const fallbackPrompt of fallbackPrompts) {
|
|
386
|
+
try {
|
|
387
|
+
const fallbackText = await generateHelperText({ ...params, systemPrompt: fallbackPrompt })
|
|
388
|
+
return parseStructuredTextWithFallbacks({
|
|
389
|
+
schema: params.schema,
|
|
390
|
+
text: fallbackText,
|
|
391
|
+
textFallbackParser: params.textFallbackParser,
|
|
392
|
+
})
|
|
393
|
+
} catch (fallbackError) {
|
|
394
|
+
fallbackMessages.push(fallbackError instanceof Error ? fallbackError.message : String(fallbackError))
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
throw new Error(`${structuredError.message}; structured_fallback=${fallbackMessages.join(' | ')}`)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return { generateHelperText, generateHelperStructured }
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export const llmHelperService = createHelperModelRuntime()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type IndexedRepoAgentName = string
|
|
2
|
+
|
|
3
|
+
export const REPO_SECTION_NAMES = [
|
|
4
|
+
'metadata',
|
|
5
|
+
'vision',
|
|
6
|
+
'prd',
|
|
7
|
+
'technicalMap',
|
|
8
|
+
'goToMarket',
|
|
9
|
+
'structure',
|
|
10
|
+
'summary',
|
|
11
|
+
] as const
|
|
12
|
+
export type RepoSectionName = (typeof REPO_SECTION_NAMES)[number]
|
|
13
|
+
|
|
14
|
+
const ALL_REPO_SECTIONS: RepoSectionName[] = [...REPO_SECTION_NAMES]
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_REPO_SECTIONS_BY_AGENT: Record<IndexedRepoAgentName, RepoSectionName[]> = {
|
|
17
|
+
chief: [...ALL_REPO_SECTIONS],
|
|
18
|
+
ceo: [...ALL_REPO_SECTIONS],
|
|
19
|
+
cto: [...ALL_REPO_SECTIONS],
|
|
20
|
+
cpo: [...ALL_REPO_SECTIONS],
|
|
21
|
+
cmo: [...ALL_REPO_SECTIONS],
|
|
22
|
+
cfo: [...ALL_REPO_SECTIONS],
|
|
23
|
+
mentor: [...ALL_REPO_SECTIONS],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function emptyAgentContextMap(): Record<IndexedRepoAgentName, string> {
|
|
27
|
+
return { chief: '', ceo: '', cto: '', cpo: '', cmo: '', cfo: '', mentor: '' }
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function mergeInstructionSections(...groups: Array<readonly string[] | undefined>): string[] | undefined {
|
|
2
|
+
const sections = groups
|
|
3
|
+
.flatMap((group) => group ?? [])
|
|
4
|
+
.map((section) => section.trim())
|
|
5
|
+
.filter((section) => section.length > 0)
|
|
6
|
+
|
|
7
|
+
return sections.length > 0 ? sections : undefined
|
|
8
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export function stripMemr3Sections(text: string): string {
|
|
2
|
+
const normalized = text.replace(/\r\n/g, '\n')
|
|
3
|
+
const answerMatch = normalized.match(/\[ANSWER\]\s*\n([\s\S]*)/i)
|
|
4
|
+
|
|
5
|
+
if (answerMatch) {
|
|
6
|
+
return answerMatch[1]?.trim() ?? ''
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return normalized
|
|
10
|
+
.replace(/```evidence\s*\n[\s\S]*?```/gi, '')
|
|
11
|
+
.replace(/```gaps\s*\n[\s\S]*?```/gi, '')
|
|
12
|
+
.replace(/\[EVIDENCE\][\s\S]*?(?=\n\[GAPS\]|\n\[ANSWER\]|\n*$)/i, '')
|
|
13
|
+
.replace(/\[GAPS\][\s\S]*?(?=\n\[ANSWER\]|\n*$)/i, '')
|
|
14
|
+
.replace(/\[ANSWER\]/i, '')
|
|
15
|
+
.trim()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const userQuestionTagsRegex = /<\/?user_question>/gi
|
|
19
|
+
|
|
20
|
+
export function stripUserQuestionTags(text: string): string {
|
|
21
|
+
return text.replace(userQuestionTagsRegex, '').trim()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const assistantMessageTagsRegex = /<\/?assistant-message\b[^>]*>/gi
|
|
25
|
+
|
|
26
|
+
export function stripAssistantMessageTags(text: string): string {
|
|
27
|
+
return text.replace(assistantMessageTagsRegex, '').trim()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const memorySectionsTokenRegex = /"sections"\s*:\s*\[/i
|
|
31
|
+
const memoryIdTokenRegex = /"id"\s*:\s*"memory:[^"]+"/i
|
|
32
|
+
const decisionHeadingRegex = /decision\s*:/i
|
|
33
|
+
|
|
34
|
+
function looksLikeMemoryJsonPreamble(text: string): boolean {
|
|
35
|
+
return memorySectionsTokenRegex.test(text) && memoryIdTokenRegex.test(text)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getDecisionHeadingStart(text: string): number | null {
|
|
39
|
+
const match = decisionHeadingRegex.exec(text)
|
|
40
|
+
if (!match) return null
|
|
41
|
+
return match.index
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function stripLeadingMemorySectionJsonBlocks(text: string): string {
|
|
45
|
+
const trimmedStart = text.trimStart()
|
|
46
|
+
if (!trimmedStart) return trimmedStart
|
|
47
|
+
|
|
48
|
+
const decisionStart = getDecisionHeadingStart(trimmedStart)
|
|
49
|
+
if (decisionStart === null) {
|
|
50
|
+
return looksLikeMemoryJsonPreamble(trimmedStart) ? '' : trimmedStart
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (decisionStart === 0) {
|
|
54
|
+
return trimmedStart
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const preamble = trimmedStart.slice(0, decisionStart).trim()
|
|
58
|
+
if (!preamble || !looksLikeMemoryJsonPreamble(preamble)) {
|
|
59
|
+
return trimmedStart
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return trimmedStart.slice(decisionStart).trimStart()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function sanitizeAgentOutputForMemory(text: string): string {
|
|
66
|
+
const withoutAssistantTags = stripAssistantMessageTags(text)
|
|
67
|
+
const withoutMemr3 = stripMemr3Sections(withoutAssistantTags)
|
|
68
|
+
const withoutUserQuestion = stripUserQuestionTags(withoutMemr3)
|
|
69
|
+
const withoutMemoryJson = stripLeadingMemorySectionJsonBlocks(withoutUserQuestion)
|
|
70
|
+
return withoutMemoryJson.trim()
|
|
71
|
+
}
|