saeeol 1.2.0 → 1.2.2
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/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
- package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
- package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
- package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
- package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
- package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
- package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
- package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
- package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
- package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
- package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
- package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
- package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
- package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
- package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
- package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
- 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
- package/src/tool/apply_patch.ts +1 -334
- package/src/tool/bash.ts +1 -656
- package/src/tool/core/external-directory.ts +55 -0
- package/src/tool/core/invalid.ts +21 -0
- package/src/tool/core/recall.ts +164 -0
- package/src/tool/core/recall.txt +12 -0
- package/src/tool/core/schema.ts +16 -0
- package/src/tool/core/tool.ts +162 -0
- package/src/tool/core/truncate.ts +160 -0
- package/src/tool/core/truncation-dir.ts +4 -0
- package/src/tool/diagnostics.ts +1 -20
- package/src/tool/edit-replacers.ts +1 -288
- package/src/tool/edit-utils.ts +1 -86
- package/src/tool/edit.ts +1 -262
- package/src/tool/external-directory.ts +1 -55
- package/src/tool/file/apply_patch.ts +334 -0
- package/src/tool/file/apply_patch.txt +33 -0
- package/src/tool/file/bash.ts +656 -0
- package/src/tool/file/bash.txt +119 -0
- package/src/tool/file/edit-replacers.ts +288 -0
- package/src/tool/file/edit-utils.ts +86 -0
- package/src/tool/file/edit.ts +262 -0
- package/src/tool/file/edit.txt +10 -0
- package/src/tool/file/read.ts +389 -0
- package/src/tool/file/read.txt +14 -0
- package/src/tool/file/write.ts +114 -0
- package/src/tool/file/write.txt +8 -0
- package/src/tool/glob.ts +1 -115
- package/src/tool/grep.ts +1 -151
- package/src/tool/integration/diagnostics.ts +20 -0
- package/src/tool/integration/lsp.ts +113 -0
- package/src/tool/integration/lsp.txt +24 -0
- package/src/tool/integration/mcp-exa.ts +73 -0
- package/src/tool/integration/package.ts +168 -0
- package/src/tool/integration/registry.ts +375 -0
- package/src/tool/invalid.ts +1 -21
- package/src/tool/lsp.ts +1 -113
- package/src/tool/mcp-exa.ts +1 -73
- package/src/tool/package.ts +1 -168
- package/src/tool/plan.ts +1 -30
- package/src/tool/question.ts +1 -52
- package/src/tool/read.ts +1 -389
- package/src/tool/recall.ts +1 -164
- package/src/tool/registry.ts +1 -375
- package/src/tool/schema.ts +1 -16
- package/src/tool/search/glob.ts +115 -0
- package/src/tool/search/glob.txt +6 -0
- package/src/tool/search/grep.ts +151 -0
- package/src/tool/search/grep.txt +8 -0
- package/src/tool/search/warpgrep.ts +107 -0
- package/src/tool/search/warpgrep.txt +10 -0
- package/src/tool/search/webfetch.ts +202 -0
- package/src/tool/search/webfetch.txt +13 -0
- package/src/tool/search/websearch.ts +71 -0
- package/src/tool/search/websearch.txt +14 -0
- package/src/tool/skill.ts +1 -91
- package/src/tool/task.ts +1 -197
- package/src/tool/todo.ts +1 -62
- package/src/tool/tool.ts +1 -162
- package/src/tool/truncate.ts +1 -160
- package/src/tool/truncation-dir.ts +1 -4
- package/src/tool/warpgrep.ts +1 -107
- package/src/tool/webfetch.ts +1 -202
- package/src/tool/websearch.ts +1 -71
- package/src/tool/workflow/plan-enter.txt +14 -0
- package/src/tool/workflow/plan-exit.txt +13 -0
- package/src/tool/workflow/plan.ts +30 -0
- package/src/tool/workflow/question.ts +52 -0
- package/src/tool/workflow/question.txt +11 -0
- package/src/tool/workflow/skill.ts +91 -0
- package/src/tool/workflow/skill.txt +5 -0
- package/src/tool/workflow/task.ts +197 -0
- package/src/tool/workflow/task.txt +57 -0
- package/src/tool/workflow/todo.ts +62 -0
- package/src/tool/workflow/todowrite.txt +167 -0
- package/src/tool/write.ts +1 -114
|
@@ -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"
|