saeeol 1.2.0 → 1.2.1
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 +14 -14
- package/src/session/compaction-helpers.ts +1 -169
- package/src/session/compaction.ts +1 -712
- package/src/session/core/compaction/compaction-helpers.ts +169 -0
- package/src/session/core/compaction/compaction.ts +712 -0
- package/src/session/core/compaction/overflow.ts +28 -0
- package/src/session/core/instruction.ts +234 -0
- package/src/session/core/llm.ts +504 -0
- package/src/session/core/network.ts +392 -0
- package/src/session/core/processor.ts +731 -0
- package/src/session/core/projectors.ts +139 -0
- package/src/session/core/resolve-tools.ts +241 -0
- package/src/session/core/retry.ts +149 -0
- package/src/session/core/revert.ts +173 -0
- package/src/session/core/run-state.ts +110 -0
- package/src/session/core/schema.ts +35 -0
- package/src/session/core/session-types.ts +160 -0
- package/src/session/core/session.sql.ts +124 -0
- package/src/session/core/session.ts +948 -0
- package/src/session/core/shell-exec.ts +205 -0
- package/src/session/core/status.ts +100 -0
- package/src/session/core/subtask.ts +268 -0
- package/src/session/core/summary.ts +173 -0
- package/src/session/core/system.ts +114 -0
- package/src/session/core/todo.ts +86 -0
- package/src/session/core/user-part.ts +293 -0
- package/src/session/instruction.ts +1 -234
- package/src/session/llm.ts +1 -504
- package/src/session/message/message-errors.ts +83 -0
- package/src/session/message/message-parts.ts +89 -0
- package/src/session/message/message-query.ts +107 -0
- package/src/session/message/message-transform.ts +156 -0
- package/src/session/message/message-types.ts +68 -0
- package/src/session/message/message-v2.ts +73 -0
- package/src/session/message/message.ts +192 -0
- package/src/session/message-errors.ts +1 -83
- package/src/session/message-parts.ts +1 -89
- package/src/session/message-query.ts +1 -107
- package/src/session/message-transform.ts +1 -156
- package/src/session/message-types.ts +1 -68
- package/src/session/message-v2.ts +1 -73
- package/src/session/message.ts +1 -192
- package/src/session/network.ts +1 -392
- package/src/session/overflow.ts +1 -28
- package/src/session/processor.ts +1 -731
- package/src/session/projectors.ts +2 -139
- package/src/session/prompt/prompt-command.ts +93 -0
- package/src/session/prompt/prompt-loop.ts +299 -0
- package/src/session/prompt/prompt-model.ts +44 -0
- package/src/session/prompt/prompt-reminders.ts +120 -0
- package/src/session/prompt/prompt-resolve.ts +42 -0
- package/src/session/prompt/prompt-schemas.ts +128 -0
- package/src/session/prompt/prompt-title.ts +55 -0
- package/src/session/prompt/prompt-types.ts +47 -0
- package/src/session/prompt/prompt-user-msg.ts +80 -0
- package/src/session/prompt/prompt.ts +211 -0
- package/src/session/prompt-command.ts +1 -93
- package/src/session/prompt-loop.ts +1 -299
- package/src/session/prompt-model.ts +1 -44
- package/src/session/prompt-reminders.ts +1 -120
- package/src/session/prompt-resolve.ts +1 -42
- package/src/session/prompt-schemas.ts +1 -128
- package/src/session/prompt-title.ts +1 -55
- package/src/session/prompt-types.ts +1 -47
- package/src/session/prompt-user-msg.ts +1 -80
- package/src/session/prompt.ts +1 -211
- package/src/session/resolve-tools.ts +1 -241
- package/src/session/retry.ts +1 -149
- package/src/session/revert.ts +1 -173
- package/src/session/run-state.ts +1 -110
- package/src/session/schema.ts +1 -35
- package/src/session/session-types.ts +1 -160
- package/src/session/session.sql.ts +1 -124
- package/src/session/session.ts +1 -948
- package/src/session/shell-exec.ts +1 -205
- package/src/session/status.ts +1 -100
- package/src/session/subtask.ts +1 -268
- package/src/session/summary.ts +1 -173
- package/src/session/system.ts +1 -114
- package/src/session/todo.ts +1 -86
- package/src/session/user-part.ts +1 -293
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
globalThis.AI_SDK_LOG_WARNINGS = false
|
|
3
|
+
|
|
4
|
+
import { Effect, Layer, Scope, Latch, Context } from "effect"
|
|
5
|
+
import { ChildProcessSpawner } from "effect/unstable/process"
|
|
6
|
+
import { CrossSpawnSpawner } from "@saeeol/core/cross-spawn-spawner"
|
|
7
|
+
import { SaeeolSessionPrompt } from "@/saeeol/session/prompt"
|
|
8
|
+
import { SaeeolSessionPromptQueue } from "@/saeeol/session/prompt-queue"
|
|
9
|
+
import { SaeeolSession } from "@/saeeol/session"
|
|
10
|
+
import { SaeeolSessionProcessor } from "@/saeeol/session/processor"
|
|
11
|
+
import { Suggestion } from "@/saeeol/suggestion"
|
|
12
|
+
import { Question } from "@/question"
|
|
13
|
+
import { SessionID, MessageID } from "../core/schema"
|
|
14
|
+
import { MessageV2 } from "../message/message-v2"
|
|
15
|
+
import { makeRuntime } from "@/effect/run-service"
|
|
16
|
+
import { InstanceState } from "@/effect/instance-state"
|
|
17
|
+
import { EffectBridge } from "@/effect/bridge"
|
|
18
|
+
import { SessionRunState } from "../core/run-state"
|
|
19
|
+
import { SessionStatus } from "../core/status"
|
|
20
|
+
import { SessionCompaction } from "../core/compaction/compaction"
|
|
21
|
+
import { SessionProcessor } from "../core/processor"
|
|
22
|
+
import { Command } from "../../command"
|
|
23
|
+
import { Permission } from "@/permission"
|
|
24
|
+
import { MCP } from "../../mcp"
|
|
25
|
+
import { LSP } from "@/lsp/lsp"
|
|
26
|
+
import { ToolRegistry } from "@/tool/registry"
|
|
27
|
+
import { Truncate } from "@/tool/truncate"
|
|
28
|
+
import { Provider } from "@/provider/provider"
|
|
29
|
+
import { Config } from "@/config/config"
|
|
30
|
+
import { Instruction } from "../core/instruction"
|
|
31
|
+
import { AppFileSystem } from "@saeeol/core/filesystem"
|
|
32
|
+
import { Plugin } from "../../plugin"
|
|
33
|
+
import { Session as SessionMod } from "../core/session"
|
|
34
|
+
import { SessionRevert } from "../core/revert"
|
|
35
|
+
import { SessionSummary } from "../core/summary"
|
|
36
|
+
import { Agent } from "../../agent/agent"
|
|
37
|
+
import { SystemPrompt } from "../core/system"
|
|
38
|
+
import { LLM } from "../core/llm"
|
|
39
|
+
import { Bus } from "../../bus"
|
|
40
|
+
import { Service as ResolveToolsService, defaultLayer as resolveToolsLayer } from "../core/resolve-tools"
|
|
41
|
+
import { Service as UserPartService, defaultLayer as userPartLayer } from "../core/user-part"
|
|
42
|
+
import { Service as SubtaskService, defaultLayer as subtaskLayer } from "../core/subtask"
|
|
43
|
+
import { Service as ShellService, defaultLayer as shellLayer } from "../core/shell-exec"
|
|
44
|
+
import { type PromptInput, type ShellInput, type CommandInput, LoopInput } from "./prompt-schemas"
|
|
45
|
+
import { type Interface, Service } from "./prompt-types"
|
|
46
|
+
import { resolvePromptParts } from "./prompt-resolve"
|
|
47
|
+
import { getModel, lastModel, lastAssistant } from "./prompt-model"
|
|
48
|
+
import { createUserMessage } from "./prompt-user-msg"
|
|
49
|
+
import { ensureTitle } from "./prompt-title"
|
|
50
|
+
import { insertReminders } from "./prompt-reminders"
|
|
51
|
+
import { commandHandler } from "./prompt-command"
|
|
52
|
+
import { createRunLoop } from "./prompt-loop"
|
|
53
|
+
|
|
54
|
+
export const shouldAskPlanFollowup = SaeeolSessionPrompt.shouldAskPlanFollowup
|
|
55
|
+
|
|
56
|
+
export const layer = Layer.effect(
|
|
57
|
+
Service,
|
|
58
|
+
Effect.gen(function* () {
|
|
59
|
+
const bus = yield* Bus.Service
|
|
60
|
+
const status = yield* SessionStatus.Service
|
|
61
|
+
const sessions = yield* SessionMod.Service
|
|
62
|
+
const agents = yield* Agent.Service
|
|
63
|
+
const provider = yield* Provider.Service
|
|
64
|
+
const processor = yield* SessionProcessor.Service
|
|
65
|
+
const compaction = yield* SessionCompaction.Service
|
|
66
|
+
const plugin = yield* Plugin.Service
|
|
67
|
+
const commands = yield* Command.Service
|
|
68
|
+
const config = yield* Config.Service
|
|
69
|
+
const permission = yield* Permission.Service
|
|
70
|
+
const fsys = yield* AppFileSystem.Service
|
|
71
|
+
const mcp = yield* MCP.Service
|
|
72
|
+
const lsp = yield* LSP.Service
|
|
73
|
+
const registry = yield* ToolRegistry.Service
|
|
74
|
+
const truncate = yield* Truncate.Service
|
|
75
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
76
|
+
const scope = yield* Scope.Scope
|
|
77
|
+
const instruction = yield* Instruction.Service
|
|
78
|
+
const state = yield* SessionRunState.Service
|
|
79
|
+
const revert = yield* SessionRevert.Service
|
|
80
|
+
const summary = yield* SessionSummary.Service
|
|
81
|
+
const sys = yield* SystemPrompt.Service
|
|
82
|
+
const llm = yield* LLM.Service
|
|
83
|
+
const resolveToolsSvc = yield* ResolveToolsService
|
|
84
|
+
const userPartSvc = yield* UserPartService
|
|
85
|
+
const subtaskSvc = yield* SubtaskService
|
|
86
|
+
const shellSvc = yield* ShellService
|
|
87
|
+
|
|
88
|
+
const deps = {
|
|
89
|
+
bus, status, sessions, agents, provider, processor, compaction, plugin,
|
|
90
|
+
commands, config, permission, fsys, mcp, lsp, registry, truncate,
|
|
91
|
+
spawner, scope, instruction, state, revert, summary, sys, llm,
|
|
92
|
+
resolveToolsSvc, userPartSvc, subtaskSvc, shellSvc,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const resolvePromptPartsFn = resolvePromptParts(deps as any)
|
|
96
|
+
const getModelFn = getModel(deps as any)
|
|
97
|
+
const lastModelFn = lastModel(deps as any)
|
|
98
|
+
const lastAssistantFn = lastAssistant(deps as any)
|
|
99
|
+
const createUserMessageFn = createUserMessage(deps as any)
|
|
100
|
+
const ensureTitleFn = ensureTitle(deps as any)
|
|
101
|
+
const insertRemindersFn = insertReminders(deps as any, SaeeolSessionPrompt)
|
|
102
|
+
const commandFn = commandHandler(deps as any, SaeeolSessionProcessor.markReviewTelemetry.bind(SaeeolSessionProcessor))
|
|
103
|
+
|
|
104
|
+
const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) {
|
|
105
|
+
yield* SaeeolSessionPromptQueue.cancel(sessionID)
|
|
106
|
+
SaeeolSessionPrompt.abortPlanFollowup(sessionID)
|
|
107
|
+
yield* state.cancel(sessionID)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const { runLoop, loop, shell } = createRunLoop(
|
|
111
|
+
deps as any,
|
|
112
|
+
{
|
|
113
|
+
getModel: getModelFn,
|
|
114
|
+
lastModel: lastModelFn,
|
|
115
|
+
lastAssistant: lastAssistantFn,
|
|
116
|
+
createUserMessage: createUserMessageFn,
|
|
117
|
+
ensureTitle: ensureTitleFn,
|
|
118
|
+
insertReminders: insertRemindersFn,
|
|
119
|
+
resolvePromptParts: resolvePromptPartsFn,
|
|
120
|
+
commandHandler: commandFn,
|
|
121
|
+
SaeeolSessionPrompt,
|
|
122
|
+
SaeeolSessionPromptQueue,
|
|
123
|
+
SaeeolSession,
|
|
124
|
+
SaeeolSessionProcessor,
|
|
125
|
+
} as any,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
let promptFn: any
|
|
129
|
+
|
|
130
|
+
const promptImpl = Effect.fn("SessionPrompt.prompt")(function* (input: PromptInput) {
|
|
131
|
+
return yield* promptFn(input)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
promptFn = Effect.fn("SessionPrompt.prompt")(function* (input: PromptInput) {
|
|
135
|
+
const session = yield* sessions.get(input.sessionID)
|
|
136
|
+
yield* revert.cleanup(session)
|
|
137
|
+
yield* SaeeolSessionPrompt.recoverDanglingAssistant({ sessionID: input.sessionID, status, sessions })
|
|
138
|
+
yield* SaeeolSessionPrompt.recoverProviderFinishError({ sessionID: input.sessionID, status, sessions })
|
|
139
|
+
const message = yield* createUserMessageFn(input)
|
|
140
|
+
yield* sessions.touch(input.sessionID)
|
|
141
|
+
const permissions: Permission.Ruleset = []
|
|
142
|
+
for (const [t, enabled] of Object.entries(input.tools ?? {})) {
|
|
143
|
+
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
|
|
144
|
+
}
|
|
145
|
+
if (permissions.length > 0) {
|
|
146
|
+
session.permission = permissions
|
|
147
|
+
yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
|
|
148
|
+
}
|
|
149
|
+
yield* Effect.promise(() => Suggestion.dismissAll(input.sessionID))
|
|
150
|
+
yield* Effect.promise(() => Question.dismissAll(input.sessionID))
|
|
151
|
+
if (input.noReply === true) return message
|
|
152
|
+
return yield* (SaeeolSessionPromptQueue.enqueue as any)(
|
|
153
|
+
input.sessionID, message.info.id,
|
|
154
|
+
loop({ sessionID: input.sessionID }),
|
|
155
|
+
lastAssistantFn(input.sessionID),
|
|
156
|
+
)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
return Service.of({
|
|
160
|
+
cancel,
|
|
161
|
+
prompt: promptFn,
|
|
162
|
+
loop: loop as any,
|
|
163
|
+
shell: shell as any,
|
|
164
|
+
command: commandFn as any,
|
|
165
|
+
resolvePromptParts: resolvePromptPartsFn as any,
|
|
166
|
+
})
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
export const defaultLayer = Layer.suspend(() =>
|
|
171
|
+
layer.pipe(
|
|
172
|
+
Layer.provide(
|
|
173
|
+
Layer.mergeAll(
|
|
174
|
+
resolveToolsLayer, userPartLayer, subtaskLayer, shellLayer,
|
|
175
|
+
SessionRunState.defaultLayer, SessionStatus.defaultLayer,
|
|
176
|
+
SessionCompaction.defaultLayer, SessionProcessor.defaultLayer,
|
|
177
|
+
Command.defaultLayer, Permission.defaultLayer, MCP.defaultLayer,
|
|
178
|
+
LSP.defaultLayer, ToolRegistry.defaultLayer, Truncate.defaultLayer,
|
|
179
|
+
Provider.defaultLayer, Config.defaultLayer, Instruction.defaultLayer,
|
|
180
|
+
AppFileSystem.defaultLayer, Plugin.defaultLayer, SessionMod.defaultLayer,
|
|
181
|
+
SessionRevert.defaultLayer, SessionSummary.defaultLayer, Agent.defaultLayer,
|
|
182
|
+
SystemPrompt.defaultLayer, LLM.defaultLayer, Bus.layer,
|
|
183
|
+
CrossSpawnSpawner.defaultLayer,
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
export { Service } from "./prompt-types"
|
|
190
|
+
export type { Interface } from "./prompt-types"
|
|
191
|
+
|
|
192
|
+
export {
|
|
193
|
+
PromptInput,
|
|
194
|
+
type PromptInput as PromptInputType,
|
|
195
|
+
ShellInput,
|
|
196
|
+
type ShellInput as ShellInputType,
|
|
197
|
+
CommandInput,
|
|
198
|
+
type CommandInput as CommandInputType,
|
|
199
|
+
LoopInput,
|
|
200
|
+
createStructuredOutputTool,
|
|
201
|
+
STRUCTURED_OUTPUT_DESCRIPTION, STRUCTURED_OUTPUT_SYSTEM_PROMPT,
|
|
202
|
+
REQUEST_PRUNE_BYTES,
|
|
203
|
+
bashRegex, argsRegex, placeholderRegex, quoteTrimRegex,
|
|
204
|
+
} from "./prompt-schemas"
|
|
205
|
+
|
|
206
|
+
const { runPromise } = makeRuntime(Service, defaultLayer)
|
|
207
|
+
export const prompt = (input: PromptInput) => runPromise((svc) => svc.prompt(input))
|
|
208
|
+
export const loopExport = (input: LoopInput) => runPromise((svc) => svc.loop(input))
|
|
209
|
+
export const cancel = (sessionID: SessionID) => runPromise((svc) => svc.cancel(sessionID))
|
|
210
|
+
|
|
211
|
+
export * as SessionPrompt from "./prompt"
|
|
@@ -1,93 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { NamedError } from "@saeeol/core/util/error"
|
|
3
|
-
import * as EffectLogger from "@saeeol/core/effect/logger"
|
|
4
|
-
import { ConfigMarkdown } from "@/config/markdown"
|
|
5
|
-
import { Shell } from "@/shell/shell"
|
|
6
|
-
import { Provider } from "@/provider/provider"
|
|
7
|
-
import { Process } from "@/util/process"
|
|
8
|
-
import { SessionID } from "./schema"
|
|
9
|
-
import * as Session from "./session"
|
|
10
|
-
import { resolvePromptParts } from "./prompt-resolve"
|
|
11
|
-
import { getModel, lastModel } from "./prompt-model"
|
|
12
|
-
import type { CommandInput } from "./prompt-schemas"
|
|
13
|
-
import type { PromptDeps } from "./prompt-types"
|
|
14
|
-
|
|
15
|
-
const elog = EffectLogger.create({ service: "session.prompt" })
|
|
16
|
-
|
|
17
|
-
export const commandHandler = (deps: PromptDeps, markReviewTelemetry?: (parts: any, cmd: string) => void) => {
|
|
18
|
-
const resolveParts = resolvePromptParts(deps)
|
|
19
|
-
|
|
20
|
-
return Effect.fn("SessionPrompt.command")(function* (input: CommandInput) {
|
|
21
|
-
yield* elog.info("command", { sessionID: input.sessionID, command: input.command, agent: input.agent })
|
|
22
|
-
const cmd = yield* deps.commands.get(input.command)
|
|
23
|
-
if (!cmd) {
|
|
24
|
-
const available = (yield* deps.commands.list()).map((c: any) => c.name)
|
|
25
|
-
const hint = available.length ? ` Available commands: ${available.join(", ")}` : ""
|
|
26
|
-
const error = new NamedError.Unknown({ message: `Command not found: "${input.command}".${hint}` })
|
|
27
|
-
yield* deps.bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
|
|
28
|
-
throw error
|
|
29
|
-
}
|
|
30
|
-
const argsRegex = /\S+|"[^"]*"|'[^']*'/g
|
|
31
|
-
const quoteTrimRegex = /^["']|["']$/g
|
|
32
|
-
const placeholderRegex = /\$(\d+)/g
|
|
33
|
-
const agentName = cmd.agent ?? input.agent ?? (yield* deps.agents.defaultAgent())
|
|
34
|
-
const raw = input.arguments.match(argsRegex) ?? []
|
|
35
|
-
const args = raw.map((arg: string) => arg.replace(quoteTrimRegex, ""))
|
|
36
|
-
const templateCommand = yield* Effect.promise(async () => cmd.template)
|
|
37
|
-
const placeholders = templateCommand.match(placeholderRegex) ?? []
|
|
38
|
-
let last = 0
|
|
39
|
-
for (const item of placeholders) { const value = Number(item.slice(1)); if (value > last) last = value }
|
|
40
|
-
const withArgs = templateCommand.replaceAll(placeholderRegex, (_: string, index: string) => {
|
|
41
|
-
const position = Number(index)
|
|
42
|
-
const argIndex = position - 1
|
|
43
|
-
if (argIndex >= args.length) return ""
|
|
44
|
-
if (position === last) return args.slice(argIndex).join(" ")
|
|
45
|
-
return args[argIndex]
|
|
46
|
-
})
|
|
47
|
-
const usesArgumentsPlaceholder = templateCommand.includes("$ARGUMENTS")
|
|
48
|
-
let template = withArgs.replaceAll("$ARGUMENTS", input.arguments)
|
|
49
|
-
if (placeholders.length === 0 && !usesArgumentsPlaceholder && input.arguments.trim()) {
|
|
50
|
-
template = template + "\n\n" + input.arguments
|
|
51
|
-
}
|
|
52
|
-
const bashRegex = /```bash\n([\s\S]*?)```/g
|
|
53
|
-
const shellMatches = ConfigMarkdown.shell(template)
|
|
54
|
-
if (shellMatches.length > 0) {
|
|
55
|
-
const cfg = yield* deps.config.get()
|
|
56
|
-
const sh = Shell.preferred(cfg.shell)
|
|
57
|
-
const results = yield* Effect.promise(() =>
|
|
58
|
-
Promise.all(shellMatches.map(async (match: RegExpExecArray) => (await Process.text([match[1]], { shell: sh, nothrow: true })).text)),
|
|
59
|
-
)
|
|
60
|
-
let index = 0
|
|
61
|
-
template = template.replace(bashRegex, () => results[index++])
|
|
62
|
-
}
|
|
63
|
-
template = template.trim()
|
|
64
|
-
const taskModel = yield* Effect.gen(function* () {
|
|
65
|
-
if (cmd.model) return Provider.parseModel(cmd.model)
|
|
66
|
-
if (cmd.agent) { const cmdAgent = yield* deps.agents.get(cmd.agent); if (cmdAgent?.model) return cmdAgent.model }
|
|
67
|
-
if (input.model) return Provider.parseModel(input.model)
|
|
68
|
-
return yield* lastModel(deps)(input.sessionID)
|
|
69
|
-
})
|
|
70
|
-
yield* getModel(deps)(taskModel.providerID, taskModel.modelID, input.sessionID)
|
|
71
|
-
const agent = yield* deps.agents.get(agentName)
|
|
72
|
-
if (!agent) {
|
|
73
|
-
const available = (yield* deps.agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
|
|
74
|
-
const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
|
|
75
|
-
const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
|
|
76
|
-
yield* deps.bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
|
|
77
|
-
throw error
|
|
78
|
-
}
|
|
79
|
-
const templateParts = yield* resolveParts(template)
|
|
80
|
-
markReviewTelemetry?.(templateParts, input.command)
|
|
81
|
-
const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true
|
|
82
|
-
const parts = isSubtask
|
|
83
|
-
? [{ type: "subtask" as const, agent: agent.name, description: cmd.description ?? "",
|
|
84
|
-
command: input.command, model: { providerID: taskModel.providerID, modelID: taskModel.modelID },
|
|
85
|
-
prompt: (templateParts.find((y: any) => y.type === "text") as any)?.text ?? "" }]
|
|
86
|
-
: [...templateParts, ...(input.parts ?? [])]
|
|
87
|
-
const userAgent = isSubtask ? (input.agent ?? (yield* deps.agents.defaultAgent())) : agentName
|
|
88
|
-
const userModel = isSubtask ? input.model ? Provider.parseModel(input.model) : yield* lastModel(deps)(input.sessionID) : taskModel
|
|
89
|
-
yield* deps.plugin.trigger("command.execute.before",
|
|
90
|
-
{ command: input.command, sessionID: input.sessionID, arguments: input.arguments }, { parts })
|
|
91
|
-
return { sessionID: input.sessionID, messageID: input.messageID, model: userModel, agent: userAgent, parts, variant: input.variant, command: input.command, arguments: input.arguments }
|
|
92
|
-
})
|
|
93
|
-
}
|
|
1
|
+
export * from "./prompt/prompt-command"
|
|
@@ -1,299 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import * as EffectLogger from "@saeeol/core/effect/logger"
|
|
3
|
-
import * as Log from "@saeeol/core/util/log"
|
|
4
|
-
import { SessionID, MessageID } from "./schema"
|
|
5
|
-
import { MessageV2 } from "./message-v2"
|
|
6
|
-
import * as Session from "./session"
|
|
7
|
-
import { SaeeolSession } from "@/saeeol/session"
|
|
8
|
-
import { SaeeolSessionPrompt } from "@/saeeol/session/prompt"
|
|
9
|
-
import { SaeeolSessionPromptQueue } from "@/saeeol/session/prompt-queue"
|
|
10
|
-
import { SaeeolSessionProcessor } from "@/saeeol/session/processor"
|
|
11
|
-
import { InstanceState } from "@/effect/instance-state"
|
|
12
|
-
import { EffectBridge } from "@/effect/bridge"
|
|
13
|
-
import { type TaskPromptOps } from "@/tool/task"
|
|
14
|
-
import { NamedError } from "@saeeol/core/util/error"
|
|
15
|
-
import MAX_STEPS from "./prompt/max-steps.txt"
|
|
16
|
-
import { type PromptInput, type ShellInput, LoopInput, STRUCTURED_OUTPUT_SYSTEM_PROMPT, REQUEST_PRUNE_BYTES, createStructuredOutputTool } from "./prompt-schemas"
|
|
17
|
-
import type { PromptDeps } from "./prompt-types"
|
|
18
|
-
const elog = EffectLogger.create({ service: "session.prompt" })
|
|
19
|
-
const log = Log.create({ service: "session.prompt" })
|
|
20
|
-
|
|
21
|
-
export interface LoopHelpers {
|
|
22
|
-
getModel: (...args: any[]) => any
|
|
23
|
-
lastModel: (...args: any[]) => any
|
|
24
|
-
lastAssistant: (...args: any[]) => any
|
|
25
|
-
createUserMessage: (...args: any[]) => any
|
|
26
|
-
ensureTitle: (...args: any[]) => any
|
|
27
|
-
insertReminders: (...args: any[]) => any
|
|
28
|
-
resolvePromptParts: (...args: any[]) => any
|
|
29
|
-
commandHandler: (...args: any[]) => any
|
|
30
|
-
prompt: (...args: any[]) => any
|
|
31
|
-
cancel: (...args: any[]) => any
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function createRunLoop(deps: PromptDeps, helpers: LoopHelpers) {
|
|
35
|
-
const closeReasons = new Map<string, any>()
|
|
36
|
-
const resolveTools = (input: any) => deps.resolveToolsSvc.resolve(input)
|
|
37
|
-
const runner = Effect.fn("SessionPrompt.runner")(function* () { return yield* EffectBridge.make() })
|
|
38
|
-
const ops = Effect.fn("SessionPrompt.ops")(function* () {
|
|
39
|
-
const run = yield* runner()
|
|
40
|
-
return {
|
|
41
|
-
cancel: (id: SessionID) => run.fork(helpers.cancel(id)),
|
|
42
|
-
resolvePromptParts: (t: string) => helpers.resolvePromptParts(t),
|
|
43
|
-
prompt: (input: PromptInput) => helpers.prompt(input),
|
|
44
|
-
} satisfies TaskPromptOps
|
|
45
|
-
})
|
|
46
|
-
const handleSubtask = (input: any) => Effect.gen(function* () {
|
|
47
|
-
const ops_ = yield* ops()
|
|
48
|
-
return yield* deps.subtaskSvc.handle({ ...input, getModel: helpers.getModel, promptOps: ops_ })
|
|
49
|
-
})
|
|
50
|
-
const shellImpl = (input: ShellInput, ready?: Latch.Latch) =>
|
|
51
|
-
deps.shellSvc.exec(input, helpers.getModel, helpers.lastModel as any, ready)
|
|
52
|
-
const runLoop = Effect.fn("SessionPrompt.run")(function* (sessionID: SessionID) {
|
|
53
|
-
const envCache: SaeeolSessionPrompt.EnvCache = {}
|
|
54
|
-
closeReasons.delete(sessionID)
|
|
55
|
-
let compactionAttempts = 0
|
|
56
|
-
const ctx = yield* InstanceState.context
|
|
57
|
-
const slog = elog.with({ sessionID })
|
|
58
|
-
let structured: unknown | undefined
|
|
59
|
-
let step = 0
|
|
60
|
-
const session = yield* deps.sessions.get(sessionID)
|
|
61
|
-
while (true) {
|
|
62
|
-
yield* deps.status.set(sessionID, { type: "busy" })
|
|
63
|
-
yield* slog.info("loop", { step })
|
|
64
|
-
let msgs: MessageV2.WithParts[] = yield* MessageV2.filterCompactedEffect(sessionID)
|
|
65
|
-
msgs = SaeeolSessionPromptQueue.scope(sessionID, msgs)
|
|
66
|
-
msgs = SaeeolSessionPrompt.trimBeforeLastSummary(msgs)
|
|
67
|
-
let lastUser: MessageV2.User | undefined, lastAssistant: MessageV2.Assistant | undefined, lastFinished: MessageV2.Assistant | undefined
|
|
68
|
-
let tasks: (MessageV2.CompactionPart | MessageV2.SubtaskPart)[] = []
|
|
69
|
-
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
70
|
-
const msg = msgs[i]
|
|
71
|
-
if (!lastUser && msg.info.role === "user") lastUser = msg.info
|
|
72
|
-
if (!lastAssistant && msg.info.role === "assistant") lastAssistant = msg.info
|
|
73
|
-
if (!lastFinished && msg.info.role === "assistant" && msg.info.finish) lastFinished = msg.info
|
|
74
|
-
if (lastUser && lastFinished) break
|
|
75
|
-
const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask")
|
|
76
|
-
if (task && !lastFinished) tasks.push(...task)
|
|
77
|
-
}
|
|
78
|
-
if (!lastUser) throw new Error("No user message found in stream.")
|
|
79
|
-
const telemetry = SaeeolSessionProcessor.extractReviewTelemetry(
|
|
80
|
-
msgs.findLast((m) => m.info.role === "user" && m.info.id === lastUser.id)?.parts ?? [],
|
|
81
|
-
)
|
|
82
|
-
const lastAssistantMsg = msgs.findLast(
|
|
83
|
-
(msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id,
|
|
84
|
-
)
|
|
85
|
-
const hasToolCalls = lastAssistantMsg?.parts.some((part) => part.type === "tool" && !part.metadata?.providerExecuted) ?? false
|
|
86
|
-
if (lastAssistant?.finish && hasToolCalls && lastAssistant.parentID === lastUser.id &&
|
|
87
|
-
lastUser.id < lastAssistant.id &&
|
|
88
|
-
SaeeolSessionPrompt.shouldAskPlanFollowup({ messages: msgs, abort: AbortSignal.any([]) })
|
|
89
|
-
) {
|
|
90
|
-
const action = yield* Effect.promise((signal) =>
|
|
91
|
-
SaeeolSessionPrompt.askPlanFollowup({ sessionID, messages: msgs, abort: signal }),
|
|
92
|
-
)
|
|
93
|
-
if (action === "continue") continue
|
|
94
|
-
yield* slog.info("exiting loop")
|
|
95
|
-
break
|
|
96
|
-
}
|
|
97
|
-
if (lastAssistant?.finish && !["tool-calls"].includes(lastAssistant.finish) &&
|
|
98
|
-
!hasToolCalls && lastAssistant.parentID === lastUser.id && lastUser.id < lastAssistant.id
|
|
99
|
-
) {
|
|
100
|
-
const action = yield* Effect.promise((signal) =>
|
|
101
|
-
SaeeolSessionPrompt.askPlanFollowup({ sessionID, messages: msgs, abort: signal }),
|
|
102
|
-
)
|
|
103
|
-
if (action === "continue") continue
|
|
104
|
-
yield* slog.info("exiting loop")
|
|
105
|
-
break
|
|
106
|
-
}
|
|
107
|
-
step++
|
|
108
|
-
if (step === 1)
|
|
109
|
-
yield* helpers.ensureTitle({
|
|
110
|
-
session, modelID: lastUser.model.modelID, providerID: lastUser.model.providerID, history: msgs,
|
|
111
|
-
}).pipe(Effect.ignore, Effect.forkIn(deps.scope))
|
|
112
|
-
const model = yield* helpers.getModel(lastUser.model.providerID, lastUser.model.modelID, sessionID)
|
|
113
|
-
const task = tasks.pop()
|
|
114
|
-
if (task?.type === "subtask") {
|
|
115
|
-
yield* handleSubtask({ task, model, lastUser, sessionID, session, msgs })
|
|
116
|
-
continue
|
|
117
|
-
}
|
|
118
|
-
if (task?.type === "compaction") {
|
|
119
|
-
const result = yield* deps.compaction.process({
|
|
120
|
-
messages: msgs, parentID: lastUser.id, sessionID, auto: task.auto, overflow: task.overflow,
|
|
121
|
-
})
|
|
122
|
-
if (result === "stop") { closeReasons.set(sessionID, "error"); break }
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
if (lastFinished && lastFinished.summary !== true &&
|
|
126
|
-
(yield* deps.compaction.isOverflow({ tokens: lastFinished.tokens, model }))
|
|
127
|
-
) {
|
|
128
|
-
const guard = SaeeolSessionPrompt.guardCompactionAttempt({
|
|
129
|
-
sessionID, attempts: compactionAttempts, closeReasons, message: lastFinished,
|
|
130
|
-
})
|
|
131
|
-
if (guard.exhausted) {
|
|
132
|
-
yield* deps.sessions.updateMessage(lastFinished)
|
|
133
|
-
yield* deps.bus.publish(Session.Event.Error, { sessionID, error: guard.error })
|
|
134
|
-
break
|
|
135
|
-
}
|
|
136
|
-
compactionAttempts++
|
|
137
|
-
yield* deps.compaction.create({ sessionID, agent: lastUser.agent, model: lastUser.model, auto: true })
|
|
138
|
-
continue
|
|
139
|
-
}
|
|
140
|
-
const agent = yield* deps.agents.get(lastUser.agent)
|
|
141
|
-
if (!agent) {
|
|
142
|
-
const available = (yield* deps.agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
|
|
143
|
-
const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
|
|
144
|
-
const error = new NamedError.Unknown({ message: `Agent not found: "${lastUser.agent}".${hint}` })
|
|
145
|
-
yield* deps.bus.publish(Session.Event.Error, { sessionID, error: error.toObject() })
|
|
146
|
-
throw error
|
|
147
|
-
}
|
|
148
|
-
const maxSteps = agent.steps ?? Infinity
|
|
149
|
-
const isLastStep = step >= maxSteps
|
|
150
|
-
msgs = yield* helpers.insertReminders({ messages: msgs, agent, session })
|
|
151
|
-
const msg: MessageV2.Assistant = {
|
|
152
|
-
id: MessageID.ascending(), parentID: lastUser.id, role: "assistant", mode: agent.name,
|
|
153
|
-
agent: agent.name, variant: lastUser.model.variant, path: { cwd: ctx.directory, root: ctx.worktree },
|
|
154
|
-
cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
|
155
|
-
modelID: model.id, providerID: model.providerID, time: { created: Date.now() }, sessionID,
|
|
156
|
-
}
|
|
157
|
-
yield* deps.sessions.updateMessage(msg)
|
|
158
|
-
const handle = yield* deps.processor.create({ assistantMessage: msg, sessionID, model, telemetry })
|
|
159
|
-
const outcome: "break" | "continue" = yield* Effect.gen(function* () {
|
|
160
|
-
const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
|
|
161
|
-
const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false
|
|
162
|
-
const tools = yield* resolveTools({
|
|
163
|
-
agent, session, model, tools: lastUser.tools, processor: handle,
|
|
164
|
-
bypassAgentCheck, messages: msgs,
|
|
165
|
-
})
|
|
166
|
-
if (lastUser.format?.type === "json_schema") {
|
|
167
|
-
tools["StructuredOutput"] = createStructuredOutputTool({
|
|
168
|
-
schema: lastUser.format.schema,
|
|
169
|
-
onSuccess(output: unknown) { structured = output },
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
if (step === 1)
|
|
173
|
-
yield* deps.summary.summarize({ sessionID, messageID: lastUser.id }).pipe(Effect.ignore, Effect.forkIn(deps.scope))
|
|
174
|
-
if (step > 1 && lastFinished) {
|
|
175
|
-
for (const m of msgs) {
|
|
176
|
-
if (m.info.role !== "user" || m.info.id <= lastFinished.id) continue
|
|
177
|
-
for (const p of m.parts) {
|
|
178
|
-
if (p.type !== "text" || p.ignored || p.synthetic || !p.text.trim()) continue
|
|
179
|
-
p.text = ["<system-reminder>", "The user sent the following message:", p.text, "",
|
|
180
|
-
"Please address this message and continue with your tasks.", "</system-reminder>"].join("\n")
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
yield* deps.plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
|
|
185
|
-
SaeeolSessionPrompt.injectEditorContext({ msgs, lastUser, sessionID, cache: envCache })
|
|
186
|
-
msgs = SaeeolSessionPrompt.maybeStripHistoricalMedia(msgs)
|
|
187
|
-
const allResult: any = yield* Effect.all([
|
|
188
|
-
deps.sys.skills(agent), deps.sys.environment(model, lastUser.editorContext),
|
|
189
|
-
deps.instruction.system().pipe(Effect.orDie),
|
|
190
|
-
])
|
|
191
|
-
const skills: any = allResult[0], env: any[] = allResult[1], instructions: any[] = allResult[2]
|
|
192
|
-
let modelMsgs = yield* MessageV2.toModelMessagesEffect(msgs, model)
|
|
193
|
-
const size = Buffer.byteLength(JSON.stringify(modelMsgs))
|
|
194
|
-
if (size > REQUEST_PRUNE_BYTES) {
|
|
195
|
-
yield* deps.compaction.prune({ sessionID, reason: "payload-limit" })
|
|
196
|
-
msgs = yield* MessageV2.filterCompactedEffect(sessionID)
|
|
197
|
-
msgs = SaeeolSessionPromptQueue.scope(sessionID, msgs)
|
|
198
|
-
msgs = SaeeolSessionPrompt.trimBeforeLastSummary(msgs)
|
|
199
|
-
yield* deps.plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
|
|
200
|
-
SaeeolSessionPrompt.injectEditorContext({ msgs, lastUser, sessionID, cache: envCache })
|
|
201
|
-
msgs = SaeeolSessionPrompt.maybeStripHistoricalMedia(msgs)
|
|
202
|
-
modelMsgs = yield* MessageV2.toModelMessagesEffect(msgs, model)
|
|
203
|
-
const nextSize = Buffer.byteLength(JSON.stringify(modelMsgs))
|
|
204
|
-
if (nextSize > REQUEST_PRUNE_BYTES) log.warn("payload still large after pruning", { size: nextSize })
|
|
205
|
-
}
|
|
206
|
-
const system = [...env, ...instructions, ...(skills ? [skills] : [])]
|
|
207
|
-
const format = lastUser.format ?? { type: "text" as const }
|
|
208
|
-
if (format.type === "json_schema") system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
|
|
209
|
-
const result = yield* handle.process({
|
|
210
|
-
user: lastUser, agent, permission: SaeeolSessionPrompt.guardPermissions({ agent, session }),
|
|
211
|
-
sessionID, parentSessionID: session.parentID, system,
|
|
212
|
-
messages: [...modelMsgs, ...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS }] : [])],
|
|
213
|
-
tools, model, toolChoice: format.type === "json_schema" ? "required" : undefined,
|
|
214
|
-
})
|
|
215
|
-
if (structured !== undefined) {
|
|
216
|
-
handle.message.structured = structured
|
|
217
|
-
handle.message.finish = handle.message.finish ?? "stop"
|
|
218
|
-
yield* deps.sessions.updateMessage(handle.message)
|
|
219
|
-
return "break" as const
|
|
220
|
-
}
|
|
221
|
-
const finished = handle.message.finish && !["tool-calls", "unknown"].includes(handle.message.finish)
|
|
222
|
-
if (finished && !handle.message.error) {
|
|
223
|
-
if (format.type === "json_schema") {
|
|
224
|
-
handle.message.error = new MessageV2.StructuredOutputError({
|
|
225
|
-
message: "Model did not produce structured output", retries: 0,
|
|
226
|
-
}).toObject()
|
|
227
|
-
yield* deps.sessions.updateMessage(handle.message)
|
|
228
|
-
return "break" as const
|
|
229
|
-
}
|
|
230
|
-
if (handle.message.finish === "error") {
|
|
231
|
-
SaeeolSessionProcessor.providerFinishError(handle.message)
|
|
232
|
-
yield* deps.sessions.updateMessage(handle.message)
|
|
233
|
-
closeReasons.set(sessionID, "error")
|
|
234
|
-
return "break" as const
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
if (result === "stop") {
|
|
238
|
-
if (handle.message.error) closeReasons.set(sessionID, "error")
|
|
239
|
-
return "break" as const
|
|
240
|
-
}
|
|
241
|
-
if (result === "compact") {
|
|
242
|
-
const guard = SaeeolSessionPrompt.guardCompactionAttempt({
|
|
243
|
-
sessionID, attempts: compactionAttempts, closeReasons, message: handle.message,
|
|
244
|
-
})
|
|
245
|
-
if (guard.exhausted) {
|
|
246
|
-
yield* deps.sessions.updateMessage(handle.message)
|
|
247
|
-
yield* deps.bus.publish(Session.Event.Error, { sessionID, error: guard.error })
|
|
248
|
-
return "break" as const
|
|
249
|
-
}
|
|
250
|
-
compactionAttempts++
|
|
251
|
-
yield* deps.compaction.create({
|
|
252
|
-
sessionID, agent: lastUser.agent, model: lastUser.model, auto: true,
|
|
253
|
-
overflow: !handle.message.finish,
|
|
254
|
-
})
|
|
255
|
-
}
|
|
256
|
-
if (SaeeolSessionPromptQueue.hasFollowup(sessionID)) {
|
|
257
|
-
closeReasons.set(sessionID, "interrupted")
|
|
258
|
-
return "break" as const
|
|
259
|
-
}
|
|
260
|
-
if (result !== "compact" && !handle.message.finish) {
|
|
261
|
-
handle.message.finish = "unknown"
|
|
262
|
-
yield* deps.sessions.updateMessage(handle.message)
|
|
263
|
-
}
|
|
264
|
-
return "continue" as const
|
|
265
|
-
}).pipe(Effect.ensuring(deps.instruction.clear(handle.message.id)))
|
|
266
|
-
if (outcome === "break") break
|
|
267
|
-
continue
|
|
268
|
-
}
|
|
269
|
-
yield* deps.compaction.prune({ sessionID, reason: "normal" }).pipe(Effect.ignore, Effect.forkIn(deps.scope))
|
|
270
|
-
return yield* helpers.lastAssistant(sessionID)
|
|
271
|
-
})
|
|
272
|
-
const loop = Effect.fn("SessionPrompt.loop")(function* (input: LoopInput) {
|
|
273
|
-
yield* SaeeolSessionPrompt.recoverDanglingAssistant({
|
|
274
|
-
sessionID: input.sessionID, status: deps.status, sessions: deps.sessions,
|
|
275
|
-
})
|
|
276
|
-
yield* SaeeolSessionPrompt.recoverProviderFinishError({
|
|
277
|
-
sessionID: input.sessionID, status: deps.status, sessions: deps.sessions,
|
|
278
|
-
})
|
|
279
|
-
yield* deps.bus.publish(SaeeolSession.Event.TurnOpen, { sessionID: input.sessionID })
|
|
280
|
-
return yield* Effect.onExit(
|
|
281
|
-
deps.state.ensureRunning(input.sessionID, helpers.lastAssistant(input.sessionID), runLoop(input.sessionID)),
|
|
282
|
-
Effect.fnUntraced(function* (exit) {
|
|
283
|
-
yield* deps.bus.publish(SaeeolSession.Event.TurnClose, {
|
|
284
|
-
sessionID: input.sessionID,
|
|
285
|
-
reason: SaeeolSessionPrompt.resolveCloseReason({
|
|
286
|
-
sessionID: input.sessionID, closeReasons, exit,
|
|
287
|
-
}),
|
|
288
|
-
})
|
|
289
|
-
}),
|
|
290
|
-
)
|
|
291
|
-
})
|
|
292
|
-
const shell = Effect.fn("SessionPrompt.shell")(function* (input: ShellInput) {
|
|
293
|
-
const ready = yield* Latch.make()
|
|
294
|
-
return yield* deps.state.startShell(
|
|
295
|
-
input.sessionID, helpers.lastAssistant(input.sessionID), shellImpl(input, ready), ready,
|
|
296
|
-
)
|
|
297
|
-
})
|
|
298
|
-
return { runLoop, loop, shell, closeReasons }
|
|
299
|
-
}
|
|
1
|
+
export * from "./prompt/prompt-loop"
|
|
@@ -1,44 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { SessionID, MessageID, PartID } from "./schema"
|
|
3
|
-
import { MessageV2 } from "./message-v2"
|
|
4
|
-
import { NamedError } from "@saeeol/core/util/error"
|
|
5
|
-
import * as Log from "@saeeol/core/util/log"
|
|
6
|
-
import type { PromptInput } from "./prompt-schemas"
|
|
7
|
-
import type { ModelID, ProviderID } from "../provider/schema"
|
|
8
|
-
import type { PromptDeps } from "./prompt-types"
|
|
9
|
-
|
|
10
|
-
const log = Log.create({ service: "session.prompt" })
|
|
11
|
-
|
|
12
|
-
export const getModel = (deps: PromptDeps) =>
|
|
13
|
-
Effect.fn("SessionPrompt.getModel")(function* (providerID: ProviderID, modelID: ModelID, sessionID: SessionID) {
|
|
14
|
-
const exit = yield* deps.provider.getModel(providerID, modelID).pipe(Effect.exit)
|
|
15
|
-
if (Exit.isSuccess(exit)) return exit.value
|
|
16
|
-
const err = Cause.squash(exit.cause)
|
|
17
|
-
if (deps.provider.ModelNotFoundError?.isInstance?.(err) ?? (err as any).constructor?.name === "ModelNotFoundError") {
|
|
18
|
-
const hint = (err as any).data?.suggestions?.length ? ` Did you mean: ${(err as any).data.suggestions.join(", ")}?` : ""
|
|
19
|
-
yield* deps.bus.publish(deps.sessions.constructor?.Event?.Error ?? { type: "session.error" }, {
|
|
20
|
-
sessionID,
|
|
21
|
-
error: new NamedError.Unknown({ message: `Model not found: ${providerID}/${modelID}.${hint}` }).toObject(),
|
|
22
|
-
})
|
|
23
|
-
}
|
|
24
|
-
return yield* Effect.failCause(exit.cause)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
export const lastModel = (deps: PromptDeps) =>
|
|
28
|
-
Effect.fnUntraced(function* (sessionID: SessionID) {
|
|
29
|
-
const match = yield* deps.sessions.findMessage(sessionID, (m: any) => m.info.role === "user" && !!m.info.model) as any
|
|
30
|
-
if (Option.isSome(match) && (match.value as any).info.role === "user") return (match.value as any).info.model
|
|
31
|
-
return yield* deps.provider.defaultModel()
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
export const lastAssistant = (deps: PromptDeps) =>
|
|
35
|
-
Effect.fnUntraced(function* (sessionID: SessionID) {
|
|
36
|
-
for (let attempt = 0; attempt < 10; attempt++) {
|
|
37
|
-
const match = yield* deps.sessions.findMessage(sessionID, (m: any) => m.info.role !== "user")
|
|
38
|
-
if (Option.isSome(match)) return match.value
|
|
39
|
-
const msgs = yield* deps.sessions.messages({ sessionID, limit: 1 })
|
|
40
|
-
if (msgs.length > 0) return msgs[0]
|
|
41
|
-
yield* Effect.sleep("50 millis")
|
|
42
|
-
}
|
|
43
|
-
throw new Error("Impossible")
|
|
44
|
-
})
|
|
1
|
+
export * from "./prompt/prompt-model"
|