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.
Files changed (193) hide show
  1. package/package.json +14 -14
  2. package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
  3. package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
  4. package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
  5. package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
  6. package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
  7. package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
  8. package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
  9. package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
  10. package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
  11. package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
  12. package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
  13. package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
  14. package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
  15. package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
  16. package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
  17. package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
  18. package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
  19. package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
  20. package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
  21. package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
  22. package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
  23. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
  24. package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
  25. package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
  26. package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
  27. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
  28. package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
  29. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
  30. package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
  31. package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
  32. package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
  33. package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
  34. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
  35. package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
  36. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
  37. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
  38. package/src/session/compaction-helpers.ts +1 -169
  39. package/src/session/compaction.ts +1 -712
  40. package/src/session/core/compaction/compaction-helpers.ts +169 -0
  41. package/src/session/core/compaction/compaction.ts +712 -0
  42. package/src/session/core/compaction/overflow.ts +28 -0
  43. package/src/session/core/instruction.ts +234 -0
  44. package/src/session/core/llm.ts +504 -0
  45. package/src/session/core/network.ts +392 -0
  46. package/src/session/core/processor.ts +731 -0
  47. package/src/session/core/projectors.ts +139 -0
  48. package/src/session/core/resolve-tools.ts +241 -0
  49. package/src/session/core/retry.ts +149 -0
  50. package/src/session/core/revert.ts +173 -0
  51. package/src/session/core/run-state.ts +110 -0
  52. package/src/session/core/schema.ts +35 -0
  53. package/src/session/core/session-types.ts +160 -0
  54. package/src/session/core/session.sql.ts +124 -0
  55. package/src/session/core/session.ts +948 -0
  56. package/src/session/core/shell-exec.ts +205 -0
  57. package/src/session/core/status.ts +100 -0
  58. package/src/session/core/subtask.ts +268 -0
  59. package/src/session/core/summary.ts +173 -0
  60. package/src/session/core/system.ts +114 -0
  61. package/src/session/core/todo.ts +86 -0
  62. package/src/session/core/user-part.ts +293 -0
  63. package/src/session/instruction.ts +1 -234
  64. package/src/session/llm.ts +1 -504
  65. package/src/session/message/message-errors.ts +83 -0
  66. package/src/session/message/message-parts.ts +89 -0
  67. package/src/session/message/message-query.ts +107 -0
  68. package/src/session/message/message-transform.ts +156 -0
  69. package/src/session/message/message-types.ts +68 -0
  70. package/src/session/message/message-v2.ts +73 -0
  71. package/src/session/message/message.ts +192 -0
  72. package/src/session/message-errors.ts +1 -83
  73. package/src/session/message-parts.ts +1 -89
  74. package/src/session/message-query.ts +1 -107
  75. package/src/session/message-transform.ts +1 -156
  76. package/src/session/message-types.ts +1 -68
  77. package/src/session/message-v2.ts +1 -73
  78. package/src/session/message.ts +1 -192
  79. package/src/session/network.ts +1 -392
  80. package/src/session/overflow.ts +1 -28
  81. package/src/session/processor.ts +1 -731
  82. package/src/session/projectors.ts +2 -139
  83. package/src/session/prompt/prompt-command.ts +93 -0
  84. package/src/session/prompt/prompt-loop.ts +299 -0
  85. package/src/session/prompt/prompt-model.ts +44 -0
  86. package/src/session/prompt/prompt-reminders.ts +120 -0
  87. package/src/session/prompt/prompt-resolve.ts +42 -0
  88. package/src/session/prompt/prompt-schemas.ts +128 -0
  89. package/src/session/prompt/prompt-title.ts +55 -0
  90. package/src/session/prompt/prompt-types.ts +47 -0
  91. package/src/session/prompt/prompt-user-msg.ts +80 -0
  92. package/src/session/prompt/prompt.ts +211 -0
  93. package/src/session/prompt-command.ts +1 -93
  94. package/src/session/prompt-loop.ts +1 -299
  95. package/src/session/prompt-model.ts +1 -44
  96. package/src/session/prompt-reminders.ts +1 -120
  97. package/src/session/prompt-resolve.ts +1 -42
  98. package/src/session/prompt-schemas.ts +1 -128
  99. package/src/session/prompt-title.ts +1 -55
  100. package/src/session/prompt-types.ts +1 -47
  101. package/src/session/prompt-user-msg.ts +1 -80
  102. package/src/session/prompt.ts +1 -211
  103. package/src/session/resolve-tools.ts +1 -241
  104. package/src/session/retry.ts +1 -149
  105. package/src/session/revert.ts +1 -173
  106. package/src/session/run-state.ts +1 -110
  107. package/src/session/schema.ts +1 -35
  108. package/src/session/session-types.ts +1 -160
  109. package/src/session/session.sql.ts +1 -124
  110. package/src/session/session.ts +1 -948
  111. package/src/session/shell-exec.ts +1 -205
  112. package/src/session/status.ts +1 -100
  113. package/src/session/subtask.ts +1 -268
  114. package/src/session/summary.ts +1 -173
  115. package/src/session/system.ts +1 -114
  116. package/src/session/todo.ts +1 -86
  117. package/src/session/user-part.ts +1 -293
  118. package/src/tool/apply_patch.ts +1 -334
  119. package/src/tool/bash.ts +1 -656
  120. package/src/tool/core/external-directory.ts +55 -0
  121. package/src/tool/core/invalid.ts +21 -0
  122. package/src/tool/core/recall.ts +164 -0
  123. package/src/tool/core/recall.txt +12 -0
  124. package/src/tool/core/schema.ts +16 -0
  125. package/src/tool/core/tool.ts +162 -0
  126. package/src/tool/core/truncate.ts +160 -0
  127. package/src/tool/core/truncation-dir.ts +4 -0
  128. package/src/tool/diagnostics.ts +1 -20
  129. package/src/tool/edit-replacers.ts +1 -288
  130. package/src/tool/edit-utils.ts +1 -86
  131. package/src/tool/edit.ts +1 -262
  132. package/src/tool/external-directory.ts +1 -55
  133. package/src/tool/file/apply_patch.ts +334 -0
  134. package/src/tool/file/apply_patch.txt +33 -0
  135. package/src/tool/file/bash.ts +656 -0
  136. package/src/tool/file/bash.txt +119 -0
  137. package/src/tool/file/edit-replacers.ts +288 -0
  138. package/src/tool/file/edit-utils.ts +86 -0
  139. package/src/tool/file/edit.ts +262 -0
  140. package/src/tool/file/edit.txt +10 -0
  141. package/src/tool/file/read.ts +389 -0
  142. package/src/tool/file/read.txt +14 -0
  143. package/src/tool/file/write.ts +114 -0
  144. package/src/tool/file/write.txt +8 -0
  145. package/src/tool/glob.ts +1 -115
  146. package/src/tool/grep.ts +1 -151
  147. package/src/tool/integration/diagnostics.ts +20 -0
  148. package/src/tool/integration/lsp.ts +113 -0
  149. package/src/tool/integration/lsp.txt +24 -0
  150. package/src/tool/integration/mcp-exa.ts +73 -0
  151. package/src/tool/integration/package.ts +168 -0
  152. package/src/tool/integration/registry.ts +375 -0
  153. package/src/tool/invalid.ts +1 -21
  154. package/src/tool/lsp.ts +1 -113
  155. package/src/tool/mcp-exa.ts +1 -73
  156. package/src/tool/package.ts +1 -168
  157. package/src/tool/plan.ts +1 -30
  158. package/src/tool/question.ts +1 -52
  159. package/src/tool/read.ts +1 -389
  160. package/src/tool/recall.ts +1 -164
  161. package/src/tool/registry.ts +1 -375
  162. package/src/tool/schema.ts +1 -16
  163. package/src/tool/search/glob.ts +115 -0
  164. package/src/tool/search/glob.txt +6 -0
  165. package/src/tool/search/grep.ts +151 -0
  166. package/src/tool/search/grep.txt +8 -0
  167. package/src/tool/search/warpgrep.ts +107 -0
  168. package/src/tool/search/warpgrep.txt +10 -0
  169. package/src/tool/search/webfetch.ts +202 -0
  170. package/src/tool/search/webfetch.txt +13 -0
  171. package/src/tool/search/websearch.ts +71 -0
  172. package/src/tool/search/websearch.txt +14 -0
  173. package/src/tool/skill.ts +1 -91
  174. package/src/tool/task.ts +1 -197
  175. package/src/tool/todo.ts +1 -62
  176. package/src/tool/tool.ts +1 -162
  177. package/src/tool/truncate.ts +1 -160
  178. package/src/tool/truncation-dir.ts +1 -4
  179. package/src/tool/warpgrep.ts +1 -107
  180. package/src/tool/webfetch.ts +1 -202
  181. package/src/tool/websearch.ts +1 -71
  182. package/src/tool/workflow/plan-enter.txt +14 -0
  183. package/src/tool/workflow/plan-exit.txt +13 -0
  184. package/src/tool/workflow/plan.ts +30 -0
  185. package/src/tool/workflow/question.ts +52 -0
  186. package/src/tool/workflow/question.txt +11 -0
  187. package/src/tool/workflow/skill.ts +91 -0
  188. package/src/tool/workflow/skill.txt +5 -0
  189. package/src/tool/workflow/task.ts +197 -0
  190. package/src/tool/workflow/task.txt +57 -0
  191. package/src/tool/workflow/todo.ts +62 -0
  192. package/src/tool/workflow/todowrite.txt +167 -0
  193. package/src/tool/write.ts +1 -114
@@ -1,211 +1 @@
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 "./schema"
14
- import { MessageV2 } from "./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 "./run-state"
19
- import { SessionStatus } from "./status"
20
- import { SessionCompaction } from "./compaction"
21
- import { SessionProcessor } from "./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 "./instruction"
31
- import { AppFileSystem } from "@saeeol/core/filesystem"
32
- import { Plugin } from "../plugin"
33
- import { Session as SessionMod } from "./session"
34
- import { SessionRevert } from "./revert"
35
- import { SessionSummary } from "./summary"
36
- import { Agent } from "../agent/agent"
37
- import { SystemPrompt } from "./system"
38
- import { LLM } from "./llm"
39
- import { Bus } from "../bus"
40
- import { Service as ResolveToolsService, defaultLayer as resolveToolsLayer } from "./resolve-tools"
41
- import { Service as UserPartService, defaultLayer as userPartLayer } from "./user-part"
42
- import { Service as SubtaskService, defaultLayer as subtaskLayer } from "./subtask"
43
- import { Service as ShellService, defaultLayer as shellLayer } from "./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
+ export * from "./prompt/prompt"
@@ -1,241 +1 @@
1
- import { type Tool as AITool, tool, jsonSchema, type ToolExecutionOptions, asSchema } from "ai"
2
- import { Effect, Context, Layer } from "effect"
3
- import * as EffectZod from "@/util/effect-zod"
4
- import { ModelID, ProviderID } from "../provider/schema"
5
- import { SessionID, PartID } from "./schema"
6
- import { MessageV2 } from "./message-v2"
7
- import * as Session from "./session"
8
- import { Agent } from "../agent/agent"
9
- import { Provider } from "@/provider/provider"
10
- import { ProviderTransform } from "@/provider/transform"
11
- import { ToolRegistry } from "@/tool/registry"
12
- import { MCP } from "../mcp"
13
- import { Plugin } from "../plugin"
14
- import { Permission } from "@/permission"
15
- import { Truncate } from "@/tool/truncate"
16
- import { SessionProcessor } from "./processor"
17
- import { SaeeolSessionPrompt } from "@/saeeol/session/prompt"
18
- import { EffectBridge } from "@/effect/bridge"
19
- import { Tool } from "@/tool/tool"
20
- import { type TaskPromptOps } from "@/tool/task"
21
- import * as Log from "@saeeol/core/util/log"
22
-
23
- const log = Log.create({ service: "session.resolve-tools" })
24
-
25
- export interface ResolveToolsInput {
26
- agent: Agent.Info
27
- model: Provider.Model
28
- session: Session.Info
29
- tools?: Record<string, boolean>
30
- processor: Pick<SessionProcessor.Handle, "message" | "updateToolCall" | "completeToolCall">
31
- bypassAgentCheck: boolean
32
- messages: MessageV2.WithParts[]
33
- }
34
-
35
- export interface Interface {
36
- readonly resolve: (input: ResolveToolsInput) => Effect.Effect<Record<string, AITool>>
37
- }
38
-
39
- export class Service extends Context.Service<Service, Interface>()("@saeeol/SessionResolveTools") {}
40
-
41
- export const layer = Layer.effect(
42
- Service,
43
- Effect.gen(function* () {
44
- const registry = yield* ToolRegistry.Service
45
- const plugin = yield* Plugin.Service
46
- const permission = yield* Permission.Service
47
- const mcp = yield* MCP.Service
48
- const truncate = yield* Truncate.Service
49
-
50
- const resolve = Effect.fn("SessionResolveTools.resolve")(function* (input: ResolveToolsInput) {
51
- using _ = log.time("resolveTools")
52
- const tools: Record<string, AITool> = {}
53
- const bridge = yield* EffectBridge.make()
54
- const promptOps: TaskPromptOps = {
55
- cancel: (sessionID: SessionID) => Effect.void,
56
- resolvePromptParts: (template: string) => Effect.succeed([{ type: "text" as const, text: template }]) as any,
57
- prompt: (_input: any) => Effect.fail(new Error("not available")) as any,
58
- }
59
-
60
- const context = (args: any, options: ToolExecutionOptions): Tool.Context => ({
61
- sessionID: input.session.id,
62
- abort: options.abortSignal!,
63
- messageID: input.processor.message.id,
64
- callID: options.toolCallId,
65
- extra: { model: input.model, bypassAgentCheck: input.bypassAgentCheck, promptOps },
66
- agent: input.agent.name,
67
- messages: input.messages,
68
- metadata: (val: any) =>
69
- input.processor.updateToolCall(options.toolCallId, (match) => {
70
- if (!["running", "pending"].includes(match.state.status)) return match
71
- return {
72
- ...match,
73
- state: {
74
- title: val.title,
75
- metadata: val.metadata,
76
- status: "running",
77
- input: args,
78
- time: { start: Date.now() },
79
- },
80
- }
81
- }),
82
- ask: (req: any) =>
83
- permission
84
- .ask({
85
- ...req,
86
- sessionID: input.session.id,
87
- tool: { messageID: input.processor.message.id, callID: options.toolCallId },
88
- ruleset: Permission.merge(
89
- input.agent.permission,
90
- SaeeolSessionPrompt.guardPermissions({ agent: input.agent, session: input.session }),
91
- ),
92
- hardRuleset: SaeeolSessionPrompt.hardPermissions({ agent: input.agent }),
93
- })
94
- .pipe(Effect.orDie),
95
- })
96
-
97
- for (const item of yield* registry.tools({
98
- modelID: ModelID.make(input.model.api.id),
99
- providerID: input.model.providerID,
100
- agent: input.agent,
101
- })) {
102
- const schema = ProviderTransform.schema(input.model, EffectZod.toJsonSchema(item.parameters))
103
- tools[item.id] = tool({
104
- description: item.description,
105
- inputSchema: jsonSchema(schema),
106
- execute(args, options) {
107
- return bridge.promise(
108
- Effect.gen(function* () {
109
- const ctx = context(args, options)
110
- yield* plugin.trigger(
111
- "tool.execute.before",
112
- { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID },
113
- { args },
114
- )
115
- const result = yield* item.execute(args, ctx)
116
- const output = {
117
- ...result,
118
- attachments: result.attachments?.map((attachment) => ({
119
- ...attachment,
120
- id: PartID.ascending(),
121
- sessionID: ctx.sessionID,
122
- messageID: input.processor.message.id,
123
- })),
124
- }
125
- yield* plugin.trigger(
126
- "tool.execute.after",
127
- { tool: item.id, sessionID: ctx.sessionID, callID: ctx.callID, args },
128
- output,
129
- )
130
- if (options.abortSignal?.aborted) {
131
- yield* input.processor.completeToolCall(options.toolCallId, output)
132
- }
133
- return output
134
- }),
135
- )
136
- },
137
- })
138
- }
139
-
140
- for (const [key, item] of Object.entries(yield* mcp.tools())) {
141
- const execute = item.execute
142
- if (!execute) continue
143
- const schema = yield* Effect.promise(() => Promise.resolve(asSchema(item.inputSchema).jsonSchema))
144
- const transformed = ProviderTransform.schema(input.model, schema)
145
- item.inputSchema = jsonSchema(transformed)
146
- item.execute = (args, opts) =>
147
- bridge.promise(
148
- Effect.gen(function* () {
149
- const ctx = context(args, opts)
150
- yield* plugin.trigger(
151
- "tool.execute.before",
152
- { tool: key, sessionID: ctx.sessionID, callID: opts.toolCallId },
153
- { args },
154
- )
155
- const result: Awaited<ReturnType<NonNullable<typeof execute>>> = yield* Effect.gen(function* () {
156
- yield* ctx.ask({ permission: key, metadata: {}, patterns: ["*"], always: ["*"] })
157
- return yield* Effect.promise(() => execute(args, opts))
158
- }).pipe(
159
- Effect.withSpan("Tool.execute", {
160
- attributes: {
161
- "tool.name": key,
162
- "tool.call_id": opts.toolCallId,
163
- "session.id": ctx.sessionID,
164
- "message.id": input.processor.message.id,
165
- },
166
- }),
167
- )
168
- yield* plugin.trigger(
169
- "tool.execute.after",
170
- { tool: key, sessionID: ctx.sessionID, callID: opts.toolCallId, args },
171
- result,
172
- )
173
-
174
- const textParts: string[] = []
175
- const attachments: Omit<MessageV2.FilePart, "id" | "sessionID" | "messageID">[] = []
176
- for (const contentItem of result.content) {
177
- if (contentItem.type === "text") textParts.push(contentItem.text)
178
- else if (contentItem.type === "image") {
179
- attachments.push({
180
- type: "file",
181
- mime: contentItem.mimeType,
182
- url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
183
- })
184
- } else if (contentItem.type === "resource") {
185
- const { resource } = contentItem
186
- if (resource.text) textParts.push(resource.text)
187
- if (resource.blob) {
188
- attachments.push({
189
- type: "file",
190
- mime: resource.mimeType ?? "application/octet-stream",
191
- url: `data:${resource.mimeType ?? "application/octet-stream"};base64,${resource.blob}`,
192
- filename: resource.uri,
193
- })
194
- }
195
- }
196
- }
197
-
198
- const truncated = yield* truncate.output(textParts.join("\n\n"), {}, input.agent)
199
- const metadata = {
200
- ...result.metadata,
201
- truncated: truncated.truncated,
202
- ...(truncated.truncated && { outputPath: truncated.outputPath }),
203
- }
204
- const output = {
205
- title: "",
206
- metadata,
207
- output: truncated.content,
208
- attachments: attachments.map((attachment) => ({
209
- ...attachment,
210
- id: PartID.ascending(),
211
- sessionID: ctx.sessionID,
212
- messageID: input.processor.message.id,
213
- })),
214
- content: result.content,
215
- }
216
- if (opts.abortSignal?.aborted) {
217
- yield* input.processor.completeToolCall(opts.toolCallId, output)
218
- }
219
- return output
220
- }),
221
- )
222
- tools[key] = item
223
- }
224
-
225
- return tools
226
- })
227
-
228
- return { resolve }
229
- }),
230
- )
231
-
232
- export const defaultLayer = Layer.suspend(() =>
233
- layer.pipe(
234
- Layer.provide(ToolRegistry.defaultLayer),
235
- Layer.provide(MCP.defaultLayer),
236
- Layer.provide(Plugin.defaultLayer),
237
- Layer.provide(Permission.defaultLayer),
238
- Layer.provide(Truncate.defaultLayer),
239
- Layer.provide(Provider.defaultLayer),
240
- ),
241
- )
1
+ export * from "./core/resolve-tools"
@@ -1,149 +1 @@
1
- import type { NamedError } from "@saeeol/core/util/error"
2
- import { Cause, Clock, Duration, Effect, Schedule } from "effect"
3
- import { MessageV2 } from "./message-v2"
4
- import { isSaeeolError } from "@/saeeol/errors"
5
- import { SessionNetwork } from "./network"
6
- import { iife } from "@/util/iife"
7
-
8
- export type Err = ReturnType<NamedError["toObject"]>
9
-
10
- // This exported message is shared with the TUI upsell detector. Matching on a
11
- // literal error string kind of sucks, but it is the simplest for now.
12
- export const GO_UPSELL_MESSAGE = "Free usage exceeded, subscribe to Go https://saeeol.ai/go"
13
-
14
- export const RETRY_INITIAL_DELAY = 2000
15
- export const RETRY_BACKOFF_FACTOR = 2
16
- export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
17
- export const RETRY_MAX_DELAY = 2_147_483_647 // max 32-bit signed integer for setTimeout
18
-
19
- function cap(ms: number) {
20
- return Math.min(ms, RETRY_MAX_DELAY)
21
- }
22
-
23
- export function delay(attempt: number, error?: MessageV2.APIError) {
24
- if (error) {
25
- const headers = error.data.responseHeaders
26
- if (headers) {
27
- const retryAfterMs = headers["retry-after-ms"]
28
- if (retryAfterMs) {
29
- const parsedMs = Number.parseFloat(retryAfterMs)
30
- if (!Number.isNaN(parsedMs)) {
31
- return cap(parsedMs)
32
- }
33
- }
34
-
35
- const retryAfter = headers["retry-after"]
36
- if (retryAfter) {
37
- const parsedSeconds = Number.parseFloat(retryAfter)
38
- if (!Number.isNaN(parsedSeconds)) {
39
- // convert seconds to milliseconds
40
- return cap(Math.ceil(parsedSeconds * 1000))
41
- }
42
- // Try parsing as HTTP date format
43
- const parsed = Date.parse(retryAfter) - Date.now()
44
- if (!Number.isNaN(parsed) && parsed > 0) {
45
- return cap(Math.ceil(parsed))
46
- }
47
- }
48
-
49
- return cap(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1))
50
- }
51
- }
52
-
53
- return cap(Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS))
54
- }
55
-
56
- export function retryable(error: Err) {
57
- // context overflow errors should not be retried
58
- if (MessageV2.ContextOverflowError.isInstance(error)) return undefined
59
- if (MessageV2.APIError.isInstance(error)) {
60
- const status = error.data.statusCode
61
- if (isSaeeolError(error)) return undefined
62
-
63
- // 5xx errors are transient server failures and should always be retried,
64
- // even when the provider SDK doesn't explicitly mark them as retryable.
65
- if (!error.data.isRetryable && !(status !== undefined && status >= 500)) return undefined
66
- // capped model is futile and the backoff loop cannot be broken by switching
67
- // models in the chat selector (the retry loop holds a stale model ref).
68
- if (error.data.responseBody?.includes("FreeUsageLimitError")) return undefined
69
- return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
70
- }
71
-
72
- // Check for rate limit patterns in plain text error messages
73
- const msg = error.data?.message
74
- if (typeof msg === "string") {
75
- const lower = msg.toLowerCase()
76
- if (
77
- lower.includes("rate increased too quickly") ||
78
- lower.includes("rate limit") ||
79
- lower.includes("too many requests")
80
- ) {
81
- return msg
82
- }
83
- }
84
-
85
- const json = iife(() => {
86
- try {
87
- if (typeof error.data?.message === "string") {
88
- const parsed = JSON.parse(error.data.message)
89
- return parsed
90
- }
91
-
92
- return JSON.parse(error.data.message)
93
- } catch {
94
- return undefined
95
- }
96
- })
97
- if (!json || typeof json !== "object") return undefined
98
- const code = typeof json.code === "string" ? json.code : ""
99
-
100
- if (json.type === "error" && json.error?.type === "too_many_requests") {
101
- return "Too Many Requests"
102
- }
103
- if (code.includes("exhausted") || code.includes("unavailable")) {
104
- return "Provider is overloaded"
105
- }
106
- if (json.type === "error" && typeof json.error?.code === "string" && json.error.code.includes("rate_limit")) {
107
- return "Rate Limited"
108
- }
109
- return undefined
110
- }
111
-
112
- export function policy(opts: {
113
- parse: (error: unknown) => Err
114
- set: (input: { attempt: number; message: string; next: number }) => Effect.Effect<void>
115
- limit?: number
116
- offline?: (input: { error: unknown; message: string }) => Effect.Effect<"retry" | "blocked" | "aborted">
117
- }) {
118
- return Schedule.fromStepWithMetadata(
119
- Effect.succeed((meta: Schedule.InputMetadata<unknown>) => {
120
- if (opts.limit !== undefined && meta.attempt > opts.limit) {
121
- return Cause.done(meta.attempt)
122
- }
123
-
124
- const error = opts.parse(meta.input)
125
- const message = retryable(error)
126
- if (!message) return Cause.done(meta.attempt)
127
- return Effect.gen(function* () {
128
- if (opts.offline && SessionNetwork.disconnected(meta.input)) {
129
- const result = yield* opts.offline({
130
- error: meta.input,
131
- message: SessionNetwork.message(meta.input),
132
- })
133
- if (result !== "retry") {
134
- return yield* Cause.done(meta.attempt)
135
- }
136
- yield* opts.set({ attempt: 0, message: "Reconnected", next: Date.now() })
137
- return [0, Duration.zero] as [number, Duration.Duration]
138
- }
139
-
140
- const wait = delay(meta.attempt, MessageV2.APIError.isInstance(error) ? error : undefined)
141
- const now = yield* Clock.currentTimeMillis
142
- yield* opts.set({ attempt: meta.attempt, message, next: now + wait })
143
- return [meta.attempt, Duration.millis(wait)] as [number, Duration.Duration]
144
- })
145
- }),
146
- )
147
- }
148
-
149
- export * as SessionRetry from "./retry"
1
+ export * from "./core/retry"