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,120 +1 @@
1
- import { Effect } from "effect"
2
- import path from "path"
3
- import { Flag } from "@saeeol/core/flag/flag"
4
- import { PartID } from "./schema"
5
- import { MessageV2 } from "./message-v2"
6
- import * as Session from "./session"
7
- import { InstanceState } from "@/effect/instance-state"
8
- import CODE_SWITCH from "./prompt/code-switch.txt"
9
- import type { PromptDeps } from "./prompt-types"
10
- import * as LTM from "@/ltm"
11
-
12
- const PLAN_REMINDER = `<system-reminder>
13
- Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supersedes any other instructions you have received.
14
-
15
- ## Plan File Info:
16
- {{PLAN_INFO}}
17
- You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit -- you are only allowed to take READ-ONLY actions.
18
-
19
- ## Plan Workflow
20
-
21
- ### Phase 1: Initial Understanding
22
- Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical to In this phase you should only use the explore subagent type.
23
-
24
- 1. Focus on understanding the user's request and the code associated with their request
25
- 2. **Launch up to 3 explore agents IN PARALLEL** to efficiently explore the codebase.
26
- 3. After exploring the code, use the question tool to clarify ambiguities in the user request up front.
27
-
28
- ### Phase 2: Design
29
- Goal: Design an implementation approach. Launch general agent(s) based on exploration results.
30
-
31
- ### Phase 3: Review
32
- Goal: Review the plan(s) and ensure alignment with the user's intentions.
33
-
34
- ### Phase 4: Final Plan
35
- Goal: Write your final plan to the plan file. Include recommended approach, critical file paths, and verification steps.
36
-
37
- ### Phase 5: Call plan_exit tool
38
- At the end, call plan_exit to indicate you are done planning. Do NOT stop unless asking a question or calling plan_exit.
39
- </system-reminder>`
40
-
41
- export const insertReminders = (deps: PromptDeps, SaeeolSessionPrompt: any) =>
42
- Effect.fn("SessionPrompt.insertReminders")(function* (input: {
43
- messages: MessageV2.WithParts[]
44
- agent: any
45
- session: Session.Info
46
- }) {
47
- const userMessage = input.messages.findLast((msg) => msg.info.role === "user")
48
- if (!userMessage) return input.messages
49
-
50
- if (!Flag.SAEEOL_EXPERIMENTAL_PLAN_MODE) {
51
- yield* Effect.promise(() =>
52
- SaeeolSessionPrompt.insertPlanReminders({ agent: input.agent, session: input.session, userMessage }),
53
- )
54
- const wasPlan = input.messages.some((msg) => msg.info.role === "assistant" && msg.info.agent === "plan")
55
- if (wasPlan && input.agent.name === "code") {
56
- userMessage.parts.push({
57
- id: PartID.ascending(), messageID: userMessage.info.id,
58
- sessionID: userMessage.info.sessionID, type: "text", text: CODE_SWITCH, synthetic: true,
59
- })
60
- }
61
-
62
- // LTM retrieval — inject relevant long-term memories into the prompt
63
- const cfg = yield* deps.config.get()
64
- if (cfg?.ltm?.enabled) {
65
- const userText = userMessage.parts
66
- .filter((p) => p.type === "text")
67
- .map((p) => (p as any).text ?? "")
68
- .join(" ")
69
- .slice(0, 500)
70
- if (userText.length > 10) {
71
- const ltmCfg = {
72
- ...LTM.Config.DEFAULT_LTM_CONFIG,
73
- ...cfg.ltm,
74
- episodic: { ...LTM.Config.DEFAULT_LTM_CONFIG.episodic, ...cfg.ltm.episodic },
75
- semantic: { ...LTM.Config.DEFAULT_LTM_CONFIG.semantic, ...cfg.ltm.semantic },
76
- procedural: { ...LTM.Config.DEFAULT_LTM_CONFIG.procedural, ...cfg.ltm.procedural },
77
- retrieval: { ...LTM.Config.DEFAULT_LTM_CONFIG.retrieval, ...cfg.ltm.retrieval },
78
- }
79
- const context = yield* Effect.promise(() => LTM.Retrieval.inject(userText, ltmCfg))
80
- if (context) {
81
- userMessage.parts.push({
82
- id: PartID.ascending(), messageID: userMessage.info.id,
83
- sessionID: userMessage.info.sessionID, type: "text", text: `\n${context}`, synthetic: true,
84
- })
85
- }
86
- }
87
- }
88
-
89
- return input.messages
90
- }
91
-
92
- const assistantMessage = input.messages.findLast((msg) => msg.info.role === "assistant")
93
- if (input.agent.name !== "plan" && assistantMessage?.info.agent === "plan") {
94
- const ctx = yield* InstanceState.context
95
- const plan = Session.plan(input.session, ctx)
96
- if (!(yield* deps.fsys.existsSafe(plan))) return input.messages
97
- const part = yield* deps.sessions.updatePart({
98
- id: PartID.ascending(), messageID: userMessage.info.id, sessionID: userMessage.info.sessionID,
99
- type: "text", text: `${CODE_SWITCH}\n\nA plan file exists at ${plan}. You should execute on the plan defined within it`, synthetic: true,
100
- })
101
- userMessage.parts.push(part)
102
- return input.messages
103
- }
104
-
105
- if (input.agent.name !== "plan" || assistantMessage?.info.agent === "plan") return input.messages
106
-
107
- const ctx = yield* InstanceState.context
108
- const plan = Session.plan(input.session, ctx)
109
- const exists = yield* deps.fsys.existsSafe(plan)
110
- if (!exists) yield* deps.fsys.ensureDir(path.dirname(plan)).pipe(Effect.catch(Effect.die))
111
- const planInfo = exists
112
- ? `A plan file already exists at ${plan}. You can read it and make incremental edits using the edit tool.`
113
- : `No plan file exists yet. You should create your plan at ${plan} using the write tool.`
114
- const part = yield* deps.sessions.updatePart({
115
- id: PartID.ascending(), messageID: userMessage.info.id, sessionID: userMessage.info.sessionID,
116
- type: "text", text: PLAN_REMINDER.replace("{{PLAN_INFO}}", planInfo), synthetic: true,
117
- })
118
- userMessage.parts.push(part)
119
- return input.messages
120
- })
1
+ export * from "./prompt/prompt-reminders"
@@ -1,42 +1 @@
1
- import { Effect, Types, Option } from "effect"
2
- import path from "path"
3
- import os from "os"
4
- import { ConfigMarkdown } from "@/config/markdown"
5
- import { pathToFileURL } from "url"
6
- import { InstanceState } from "@/effect/instance-state"
7
- import type { PromptInput } from "./prompt-schemas"
8
- import type { PromptDeps } from "./prompt-types"
9
-
10
- export const resolvePromptParts = (deps: PromptDeps) =>
11
- Effect.fn("SessionPrompt.resolvePromptParts")(function* (template: string) {
12
- const ctx = yield* InstanceState.context
13
- const parts: Types.DeepMutable<PromptInput["parts"]> = [{ type: "text", text: template }]
14
- const files = ConfigMarkdown.files(template)
15
- const seen = new Set<string>()
16
- yield* Effect.forEach(
17
- files,
18
- Effect.fnUntraced(function* (match: RegExpExecArray) {
19
- const name = match[1]
20
- if (seen.has(name)) return
21
- seen.add(name)
22
- const filepath = name.startsWith("~/")
23
- ? path.join(os.homedir(), name.slice(2))
24
- : path.resolve(ctx.worktree, name)
25
- const info = yield* deps.fsys.stat(filepath).pipe(Effect.option)
26
- if (Option.isNone(info)) {
27
- const found = yield* deps.agents.get(name)
28
- if (found) parts.push({ type: "agent", name: found.name })
29
- return
30
- }
31
- const stat = info.value
32
- parts.push({
33
- type: "file",
34
- url: pathToFileURL(filepath).href,
35
- filename: name,
36
- mime: stat.type === "Directory" ? "application/x-directory" : "text/plain",
37
- })
38
- }),
39
- { concurrency: "unbounded", discard: true },
40
- )
41
- return parts
42
- })
1
+ export * from "./prompt/prompt-resolve"
@@ -1,128 +1 @@
1
- import z from "zod"
2
- import { SessionID, MessageID, PartID } from "./schema"
3
- import { MessageV2 } from "./message-v2"
4
- import { ModelID, ProviderID } from "../provider/schema"
5
- import { type Tool as AITool, tool, jsonSchema } from "ai"
6
- import type { JSONSchema7 } from "@ai-sdk/provider"
7
- import { Schema } from "effect"
8
- import { zod } from "@/util/effect-zod"
9
- import { withStatics } from "@/util/schema"
10
-
11
- export const STRUCTURED_OUTPUT_DESCRIPTION = `Use this tool to return your final response in the requested structured format.
12
-
13
- IMPORTANT:
14
- - You MUST call this tool exactly once at the end of your response
15
- - The input must be valid JSON matching the required schema
16
- - Complete all necessary research and tool calls BEFORE calling this tool
17
- - This tool provides your final answer - no further actions are taken after calling it`
18
-
19
- export const STRUCTURED_OUTPUT_SYSTEM_PROMPT = `IMPORTANT: The user has requested structured output. You MUST use the StructuredOutput tool to provide your final response. Do NOT respond with plain text - you MUST call the StructuredOutput tool with your answer formatted according to the schema.`
20
-
21
- export const REQUEST_PRUNE_BYTES = 1_250_000
22
-
23
- const ModelRef = Schema.Struct({
24
- providerID: ProviderID,
25
- modelID: ModelID,
26
- })
27
-
28
- export const PromptInput = Schema.Struct({
29
- sessionID: SessionID,
30
- messageID: Schema.optional(MessageID),
31
- model: Schema.optional(ModelRef),
32
- agent: Schema.optional(Schema.String),
33
- noReply: Schema.optional(Schema.Boolean),
34
- tools: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)).annotate({
35
- description:
36
- "@deprecated tools and permissions have been merged, you can set permissions on the session itself now",
37
- }),
38
- format: Schema.optional(MessageV2.Format),
39
- system: Schema.optional(Schema.String),
40
- variant: Schema.optional(Schema.String),
41
- editorContext: Schema.optional(MessageV2.EditorContext),
42
- parts: Schema.Array(
43
- Schema.Union([
44
- MessageV2.TextPartInput,
45
- MessageV2.FilePartInput,
46
- MessageV2.AgentPartInput,
47
- MessageV2.SubtaskPartInput,
48
- ]).annotate({ discriminator: "type" }),
49
- ),
50
- }).pipe(withStatics((s) => ({ zod: zod(s) })))
51
-
52
- type PartInputUnion =
53
- | MessageV2.TextPartInput
54
- | MessageV2.FilePartInput
55
- | MessageV2.AgentPartInput
56
- | MessageV2.SubtaskPartInput
57
- export type PromptInput = Omit<Schema.Schema.Type<typeof PromptInput>, "parts" | "editorContext"> & {
58
- parts: PartInputUnion[]
59
- editorContext?: MessageV2.EditorContext
60
- }
61
-
62
- export class LoopInput extends Schema.Class<LoopInput>("SessionPrompt.LoopInput")({
63
- sessionID: SessionID,
64
- }) {
65
- static readonly zod = zod(this)
66
- }
67
-
68
- export const ShellInput = Schema.Struct({
69
- sessionID: SessionID,
70
- messageID: Schema.optional(MessageID),
71
- agent: Schema.String,
72
- model: Schema.optional(ModelRef),
73
- command: Schema.String,
74
- }).pipe(withStatics((s) => ({ zod: zod(s) })))
75
- export type ShellInput = Schema.Schema.Type<typeof ShellInput>
76
-
77
- export const CommandInput = Schema.Struct({
78
- messageID: Schema.optional(MessageID),
79
- sessionID: SessionID,
80
- agent: Schema.optional(Schema.String),
81
- model: Schema.optional(Schema.String),
82
- arguments: Schema.String,
83
- command: Schema.String,
84
- variant: Schema.optional(Schema.String),
85
- parts: Schema.optional(
86
- Schema.Array(
87
- Schema.Union([
88
- Schema.Struct({
89
- id: Schema.optional(PartID),
90
- type: Schema.Literal("file"),
91
- mime: Schema.String,
92
- filename: Schema.optional(Schema.String),
93
- url: Schema.String,
94
- source: Schema.optional(MessageV2.FilePartSource),
95
- }),
96
- ]).annotate({ discriminator: "type" }),
97
- ),
98
- ),
99
- }).pipe(withStatics((s) => ({ zod: zod(s) })))
100
- export type CommandInput = Schema.Schema.Type<typeof CommandInput>
101
-
102
- export const bashRegex = /!`([^`]+)`/g
103
- export const argsRegex = /(?:\[Image\s+\d+\]|"[^"]*"|'[^']*'|[^\s"']+)/gi
104
- export const placeholderRegex = /\$(\d+)/g
105
- export const quoteTrimRegex = /^["']|["']$/g
106
-
107
- /** @internal Exported for testing */
108
- export function createStructuredOutputTool(input: {
109
- schema: Record<string, any>
110
- onSuccess: (output: unknown) => void
111
- }): AITool {
112
- const { $schema: _, ...toolSchema } = input.schema
113
- return tool({
114
- description: STRUCTURED_OUTPUT_DESCRIPTION,
115
- inputSchema: jsonSchema(toolSchema as JSONSchema7),
116
- async execute(args) {
117
- input.onSuccess(args)
118
- return {
119
- output: "Structured output captured successfully.",
120
- title: "Structured Output",
121
- metadata: { valid: true },
122
- }
123
- },
124
- toModelOutput({ output }) {
125
- return { type: "text", value: output.output }
126
- },
127
- })
128
- }
1
+ export * from "./prompt/prompt-schemas"
@@ -1,55 +1 @@
1
- import { Effect, Cause, Stream } from "effect"
2
- import * as Log from "@saeeol/core/util/log"
3
- import * as EffectLogger from "@saeeol/core/effect/logger"
4
- import * as Session from "./session"
5
- import { MessageV2 } from "./message-v2"
6
- import type { PromptDeps } from "./prompt-types"
7
-
8
- const elog = EffectLogger.create({ service: "session.prompt" })
9
-
10
- export const ensureTitle = (deps: PromptDeps) =>
11
- Effect.fn("SessionPrompt.ensureTitle")(function* (input: {
12
- session: Session.Info
13
- history: MessageV2.WithParts[]
14
- providerID: any
15
- modelID: any
16
- }) {
17
- if (input.session.parentID) return
18
- if (!Session.isDefaultTitle(input.session.title)) return
19
- const real = (m: MessageV2.WithParts) =>
20
- m.info.role === "user" && !m.parts.every((p) => "synthetic" in p && p.synthetic)
21
- const idx = input.history.findIndex(real)
22
- if (idx === -1) return
23
- if (input.history.filter(real).length !== 1) return
24
- const context = input.history.slice(0, idx + 1)
25
- const firstUser = context[idx]
26
- if (!firstUser || firstUser.info.role !== "user") return
27
- const firstInfo = firstUser.info
28
- const subtasks = firstUser.parts.filter((p): p is MessageV2.SubtaskPart => p.type === "subtask")
29
- const onlySubtasks = subtasks.length > 0 && firstUser.parts.every((p) => p.type === "subtask")
30
- const ag = yield* deps.agents.get("title")
31
- if (!ag) return
32
- const mdl = ag.model
33
- ? yield* deps.provider.getModel(ag.model.providerID, ag.model.modelID)
34
- : ((yield* deps.provider.getSmallModel(input.providerID)) ??
35
- (yield* deps.provider.getModel(input.providerID, input.modelID)))
36
- const msgs = onlySubtasks
37
- ? [{ role: "user" as const, content: subtasks.map((p) => p.prompt).join("\n") }]
38
- : yield* MessageV2.toModelMessagesEffect(context, mdl)
39
- const text = yield* deps.llm.stream({
40
- agent: ag, user: firstInfo, system: [], small: true, tools: {}, model: mdl,
41
- sessionID: input.session.id, retries: 2,
42
- messages: [{ role: "user", content: "Generate a title for this conversation:\n" }, ...msgs],
43
- }).pipe(
44
- Stream.filter((e: any): e is Extract<any, { type: "text-delta" }> => e.type === "text-delta"),
45
- Stream.map((e: any) => e.text),
46
- Stream.mkString,
47
- Effect.orDie,
48
- )
49
- const cleaned = text.replace(/<think[\s\S]*?<\/think>\s*/g, "")
50
- .split("\n").map((l: string) => l.trim()).find((l: string) => l.length > 0)
51
- if (!cleaned) return
52
- const t = cleaned.length > 100 ? cleaned.substring(0, 97) + "..." : cleaned
53
- yield* deps.sessions.setTitle({ sessionID: input.session.id, title: t })
54
- .pipe(Effect.catchCause((cause) => elog.error("failed to generate title", { error: Cause.squash(cause) })))
55
- })
1
+ export * from "./prompt/prompt-title"
@@ -1,47 +1 @@
1
- import { Effect, Context, Types, Option, Scope, Exit, Cause, Latch, Layer } from "effect"
2
- import { SessionID, MessageID, PartID } from "./schema"
3
- import { MessageV2 } from "./message-v2"
4
- import type { PromptInput, ShellInput, CommandInput, LoopInput } from "./prompt-schemas"
5
- import type { ModelID, ProviderID } from "../provider/schema"
6
-
7
- export interface Interface {
8
- readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
9
- readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts>
10
- readonly loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts>
11
- readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts>
12
- readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts>
13
- readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
14
- }
15
-
16
- export class Service extends Context.Service<Service, Interface>()("@saeeol/SessionPrompt") {}
17
-
18
- export interface PromptDeps {
19
- bus: any
20
- status: any
21
- sessions: any
22
- agents: any
23
- provider: any
24
- processor: any
25
- compaction: any
26
- plugin: any
27
- commands: any
28
- config: any
29
- permission: any
30
- fsys: any
31
- mcp: any
32
- lsp: any
33
- registry: any
34
- truncate: any
35
- spawner: any
36
- scope: Scope.Scope
37
- instruction: any
38
- state: any
39
- revert: any
40
- summary: any
41
- sys: any
42
- llm: any
43
- resolveToolsSvc: any
44
- userPartSvc: any
45
- subtaskSvc: any
46
- shellSvc: any
47
- }
1
+ export * from "./prompt/prompt-types"
@@ -1,80 +1 @@
1
- import { Effect, Types } from "effect"
2
- import { 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 { PromptDeps } from "./prompt-types"
8
- import { getModel, lastModel } from "./prompt-model"
9
-
10
- const log = Log.create({ service: "session.prompt" })
11
-
12
- export const createUserMessage = (deps: PromptDeps) =>
13
- Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) {
14
- const agentName = input.agent || (yield* deps.agents.defaultAgent())
15
- const ag = yield* deps.agents.get(agentName)
16
- if (!ag) {
17
- const available = (yield* deps.agents.list()).filter((a: any) => !a.hidden).map((a: any) => a.name)
18
- const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
19
- const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
20
- yield* deps.bus.publish(deps.sessions.Event?.Error ?? { type: "session.error" }, { sessionID: input.sessionID, error: error.toObject() })
21
- throw error
22
- }
23
- const model = input.model ?? ag.model ?? (yield* lastModel(deps)(input.sessionID))
24
- const same = ag.model && model.providerID === ag.model.providerID && model.modelID === ag.model.modelID
25
- const full = !input.variant && ag.variant && same
26
- ? yield* deps.provider.getModel(model.providerID, model.modelID).pipe(Effect.catchDefect(() => Effect.void))
27
- : undefined
28
- const variant = input.variant ?? (ag.variant && full?.variants?.[ag.variant] ? ag.variant : undefined)
29
- const info: MessageV2.User = {
30
- id: input.messageID ?? MessageID.ascending(),
31
- role: "user",
32
- sessionID: input.sessionID,
33
- time: { created: Date.now() },
34
- tools: input.tools,
35
- agent: ag.name,
36
- model: { providerID: model.providerID, modelID: model.modelID, variant },
37
- system: input.system,
38
- format: input.format,
39
- editorContext: input.editorContext,
40
- }
41
- yield* Effect.addFinalizer(() => deps.instruction.clear(info.id))
42
- type Draft<T> = T extends MessageV2.Part ? Omit<T, "id"> & { id?: string } : never
43
- const assign = (part: Draft<MessageV2.Part>): MessageV2.Part => ({
44
- ...part,
45
- id: part.id ? PartID.make(part.id) : PartID.ascending(),
46
- })
47
- const resolvePart = (part: PromptInput["parts"][number]) =>
48
- deps.userPartSvc.resolve(part, {
49
- sessionID: input.sessionID,
50
- messageID: info.id,
51
- agent: input.agent!,
52
- agentPermission: ag.permission,
53
- model: info.model,
54
- })
55
- const parts = yield* Effect.forEach(input.parts, resolvePart, { concurrency: "unbounded" }).pipe(
56
- Effect.map((x: any) => x.flat().map(assign)),
57
- )
58
- yield* deps.plugin.trigger("chat.message", {
59
- sessionID: input.sessionID, agent: input.agent, model: input.model,
60
- messageID: input.messageID, variant: input.variant,
61
- }, { message: info, parts })
62
- const parsed = (MessageV2 as any).Info?.zod?.safeParse?.(info) ?? { success: true }
63
- if (!parsed.success) {
64
- log.error("invalid user message before save", {
65
- sessionID: input.sessionID, messageID: info.id, agent: info.agent,
66
- model: info.model, issues: parsed.error?.issues,
67
- })
68
- }
69
- parts.forEach((part: any, index: number) => {
70
- const p = (MessageV2 as any).Part?.zod?.safeParse?.(part) ?? { success: true }
71
- if (p.success) return
72
- log.error("invalid user part before save", {
73
- sessionID: input.sessionID, messageID: info.id, partID: part.id,
74
- partType: part.type, index, issues: p.error?.issues, part,
75
- })
76
- })
77
- yield* deps.sessions.updateMessage(info)
78
- for (const part of parts) yield* deps.sessions.updatePart(part)
79
- return { info, parts }
80
- }, Effect.scoped)
1
+ export * from "./prompt/prompt-user-msg"