saeeol 1.2.7 → 1.2.9
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/bin/saeeol +187 -0
- package/package.json +15 -13
- package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +2 -3
- package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +0 -2
- package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +0 -1
- package/src/cli/cmd/tui/context/app/helper.tsx +2 -3
- package/src/cli/cmd/tui/context/app/sdk.tsx +2 -4
- package/src/cli/cmd/tui/context/app/sync.tsx +0 -1
- package/src/cli/cmd/tui/context/app/theme.tsx +0 -1
- package/src/cli/cmd/tui/context/runtime/local.tsx +0 -3
- package/src/ltm/config.ts +2 -12
- package/src/ltm/memory/procedural.ts +2 -12
- package/src/ltm/pipeline.ts +3 -19
- package/src/ltm/scheduler.ts +2 -14
- package/src/ltm/store.ts +2 -11
- package/src/ltm/types.ts +2 -8
- package/src/provider/local/embedder.ts +2 -18
- package/src/session/core/llm.ts +2 -6
- package/src/session/core/retry.ts +2 -6
- package/src/session/core/session-events.ts +101 -0
- package/src/session/core/session-types.ts +5 -7
- package/src/session/core/session.ts +5 -43
- package/src/session/message/message-errors.ts +2 -6
- package/src/session/message/message-parts.ts +2 -4
- package/src/session/prompt/prompt.ts +2 -3
- package/src/tool/file/apply_patch.ts +0 -21
- package/src/tool/file/edit-replacers.ts +2 -3
- package/src/tool/integration/package.ts +2 -4
- package/src/tool/search/warpgrep.ts +0 -8
- package/src/tool/search/webfetch.ts +2 -10
- package/tsconfig.json +19 -0
package/src/ltm/types.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
/** LTM — long-term memory type definitions */
|
|
1
|
+
/** LTM — long-term memory type definitions */
|
|
2
2
|
|
|
3
3
|
import { Schema } from "effect"
|
|
4
4
|
import { optionalOmitUndefined } from "@/util/schema"
|
|
5
5
|
|
|
6
|
-
// ── Memory types ──
|
|
7
6
|
|
|
8
7
|
export const MemoryType = Schema.Literals(["episodic", "semantic", "procedural"])
|
|
9
8
|
export type MemoryType = Schema.Schema.Type<typeof MemoryType>
|
|
10
9
|
|
|
11
|
-
// ── Memory entry ──
|
|
12
10
|
|
|
13
11
|
export const MemoryMetadata = Schema.Struct({
|
|
14
12
|
source: Schema.String,
|
|
@@ -30,7 +28,6 @@ export const Memory = Schema.Struct({
|
|
|
30
28
|
})
|
|
31
29
|
export type Memory = Schema.Schema.Type<typeof Memory>
|
|
32
30
|
|
|
33
|
-
// ── Embedding server ──
|
|
34
31
|
|
|
35
32
|
export const EmbedderStatus = Schema.Literals(["stopped", "starting", "running", "error"])
|
|
36
33
|
export type EmbedderStatus = Schema.Schema.Type<typeof EmbedderStatus>
|
|
@@ -45,7 +42,6 @@ export const EmbeddingServer = Schema.Struct({
|
|
|
45
42
|
})
|
|
46
43
|
export type EmbeddingServer = Schema.Schema.Type<typeof EmbeddingServer>
|
|
47
44
|
|
|
48
|
-
// ── Hardware profile ──
|
|
49
45
|
|
|
50
46
|
export const HardwareProfile = Schema.Struct({
|
|
51
47
|
gpuCount: Schema.Number,
|
|
@@ -57,7 +53,6 @@ export const HardwareProfile = Schema.Struct({
|
|
|
57
53
|
})
|
|
58
54
|
export type HardwareProfile = Schema.Schema.Type<typeof HardwareProfile>
|
|
59
55
|
|
|
60
|
-
// ── LLM parameters (deterministic) ──
|
|
61
56
|
|
|
62
57
|
export const LLMBakeParams = Schema.Struct({
|
|
63
58
|
/** Embedding model ID */
|
|
@@ -79,7 +74,6 @@ export const LLMBakeParams = Schema.Struct({
|
|
|
79
74
|
})
|
|
80
75
|
export type LLMBakeParams = Schema.Schema.Type<typeof LLMBakeParams>
|
|
81
76
|
|
|
82
|
-
// ── LTM configuration ──
|
|
83
77
|
|
|
84
78
|
export const LTMConfig = Schema.Struct({
|
|
85
79
|
enabled: Schema.Boolean,
|
|
@@ -105,4 +99,4 @@ export const LTMConfig = Schema.Struct({
|
|
|
105
99
|
maxTokens: Schema.Number,
|
|
106
100
|
}),
|
|
107
101
|
})
|
|
108
|
-
export type LTMConfig = Schema.Schema.Type<typeof LTMConfig>
|
|
102
|
+
export type LTMConfig = Schema.Schema.Type<typeof LTMConfig>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Local embedding server — Ollama-based embedding model lifecycle management */
|
|
1
|
+
/** Local embedding server — Ollama-based embedding model lifecycle management */
|
|
2
2
|
|
|
3
3
|
import { Effect } from "effect"
|
|
4
4
|
import * as Log from "@saeeol/core/util/log"
|
|
@@ -17,8 +17,6 @@ const log = Log.create({ service: "local/embedder" })
|
|
|
17
17
|
|
|
18
18
|
let server: EmbeddingServer | undefined
|
|
19
19
|
|
|
20
|
-
// ── Ollama status checks ──
|
|
21
|
-
|
|
22
20
|
async function isOllamaRunning(endpoint: string): Promise<boolean> {
|
|
23
21
|
try {
|
|
24
22
|
const res = await fetch(`${endpoint}/api/tags`, { signal: AbortSignal.timeout(3000) })
|
|
@@ -53,8 +51,6 @@ async function isModelInstalled(endpoint: string, model: string): Promise<boolea
|
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
// ── Pull model from Ollama ──
|
|
57
|
-
|
|
58
54
|
async function pullModel(endpoint: string, model: string): Promise<void> {
|
|
59
55
|
log.info("pulling embedding model", { model })
|
|
60
56
|
const ollamaModel = getOllamaModelName(model)
|
|
@@ -91,8 +87,6 @@ function getOllamaModelName(modelId: string): string {
|
|
|
91
87
|
return map[modelId] ?? modelId
|
|
92
88
|
}
|
|
93
89
|
|
|
94
|
-
// ── Embedding API call ──
|
|
95
|
-
|
|
96
90
|
async function embedViaOllama(endpoint: string, model: string, texts: string[]): Promise<number[][]> {
|
|
97
91
|
const ollamaModel = getOllamaModelName(model)
|
|
98
92
|
const res = await fetch(`${endpoint}/api/embed`, {
|
|
@@ -114,16 +108,12 @@ async function embedViaOllama(endpoint: string, model: string, texts: string[]):
|
|
|
114
108
|
return data.embeddings
|
|
115
109
|
}
|
|
116
110
|
|
|
117
|
-
// ── VRAM estimation ──
|
|
118
|
-
|
|
119
111
|
function estimateEmbeddingVRAM(modelId: string): number {
|
|
120
112
|
const model = RAG.EMBEDDING_MODELS.find((m) => m.id === modelId)
|
|
121
113
|
if (!model) return 200
|
|
122
114
|
return Math.ceil(model.sizeBytes * 1.2 / (1024 * 1024))
|
|
123
115
|
}
|
|
124
116
|
|
|
125
|
-
// ── Public API ──
|
|
126
|
-
|
|
127
117
|
/** Start the embedding server */
|
|
128
118
|
export async function start(bake: LLMBakeParams): Promise<EmbeddingServer> {
|
|
129
119
|
if (server && server.status === "running") {
|
|
@@ -146,8 +136,6 @@ export async function start(bake: LLMBakeParams): Promise<EmbeddingServer> {
|
|
|
146
136
|
}
|
|
147
137
|
|
|
148
138
|
void Bus.publish(LTMEvent.EmbedderStatusChanged, { status: "starting", model: bake.embeddingModel })
|
|
149
|
-
|
|
150
|
-
// Check if Ollama is running
|
|
151
139
|
const running = await isOllamaRunning(endpoint)
|
|
152
140
|
if (!running) {
|
|
153
141
|
log.warn("Ollama not running, embedding server unavailable", { endpoint })
|
|
@@ -155,16 +143,12 @@ export async function start(bake: LLMBakeParams): Promise<EmbeddingServer> {
|
|
|
155
143
|
void Bus.publish(LTMEvent.EmbedderStatusChanged, { status: "error", model: bake.embeddingModel })
|
|
156
144
|
return server
|
|
157
145
|
}
|
|
158
|
-
|
|
159
|
-
// Check if model is installed
|
|
160
146
|
const ollamaModel = getOllamaModelName(bake.embeddingModel)
|
|
161
147
|
const installed = await isModelInstalled(endpoint, ollamaModel)
|
|
162
148
|
if (!installed) {
|
|
163
149
|
log.info("model not installed, pulling", { model: ollamaModel })
|
|
164
150
|
await pullModel(endpoint, ollamaModel)
|
|
165
151
|
}
|
|
166
|
-
|
|
167
|
-
// Check if model is loaded (auto-loaded on first call)
|
|
168
152
|
const loaded = await isModelLoaded(endpoint, ollamaModel)
|
|
169
153
|
if (!loaded) {
|
|
170
154
|
// Warmup call — load model into memory
|
|
@@ -217,4 +201,4 @@ export async function embedOne(text: string): Promise<number[]> {
|
|
|
217
201
|
/** VRAM usage in MB */
|
|
218
202
|
export function vramUsage(): number {
|
|
219
203
|
return server?.vramMB ?? 0
|
|
220
|
-
}
|
|
204
|
+
}
|
package/src/session/core/llm.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Provider } from "@/provider/provider"
|
|
1
|
+
import { Provider } from "@/provider/provider"
|
|
2
2
|
import * as Log from "@saeeol/core/util/log"
|
|
3
3
|
import { Context, Effect, Layer, Record } from "effect"
|
|
4
4
|
import * as Stream from "effect/Stream"
|
|
@@ -119,11 +119,8 @@ const live: Layer.Layer<
|
|
|
119
119
|
system.push(
|
|
120
120
|
[
|
|
121
121
|
...(isOpenaiOauth ? [] : [SystemPrompt.soul()]),
|
|
122
|
-
// use agent prompt otherwise provider prompt
|
|
123
122
|
...(input.agent.prompt ? [input.agent.prompt] : SystemPrompt.provider(input.model)),
|
|
124
|
-
// any custom prompt passed into this call
|
|
125
123
|
...input.system,
|
|
126
|
-
// any custom prompt from last user message
|
|
127
124
|
...(input.user.system ? [input.user.system] : []),
|
|
128
125
|
]
|
|
129
126
|
.filter((x) => x)
|
|
@@ -435,7 +432,6 @@ const live: Layer.Layer<
|
|
|
435
432
|
specificationVersion: "v3" as const,
|
|
436
433
|
async transformParams(args) {
|
|
437
434
|
if (args.type === "stream") {
|
|
438
|
-
// @ts-expect-error
|
|
439
435
|
args.params.prompt = ProviderTransform.message(args.params.prompt, input.model, options)
|
|
440
436
|
}
|
|
441
437
|
return args.params
|
|
@@ -501,4 +497,4 @@ export function hasToolCalls(messages: ModelMessage[]): boolean {
|
|
|
501
497
|
return false
|
|
502
498
|
}
|
|
503
499
|
|
|
504
|
-
export * as LLM from "./llm"
|
|
500
|
+
export * as LLM from "./llm"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NamedError } from "@saeeol/core/util/error"
|
|
1
|
+
import type { NamedError } from "@saeeol/core/util/error"
|
|
2
2
|
import { Cause, Clock, Duration, Effect, Schedule } from "effect"
|
|
3
3
|
import { MessageV2 } from "../message/message-v2"
|
|
4
4
|
import { isSaeeolError } from "@/saeeol/errors"
|
|
@@ -36,10 +36,8 @@ export function delay(attempt: number, error?: MessageV2.APIError) {
|
|
|
36
36
|
if (retryAfter) {
|
|
37
37
|
const parsedSeconds = Number.parseFloat(retryAfter)
|
|
38
38
|
if (!Number.isNaN(parsedSeconds)) {
|
|
39
|
-
// convert seconds to milliseconds
|
|
40
39
|
return cap(Math.ceil(parsedSeconds * 1000))
|
|
41
40
|
}
|
|
42
|
-
// Try parsing as HTTP date format
|
|
43
41
|
const parsed = Date.parse(retryAfter) - Date.now()
|
|
44
42
|
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
45
43
|
return cap(Math.ceil(parsed))
|
|
@@ -68,8 +66,6 @@ export function retryable(error: Err) {
|
|
|
68
66
|
if (error.data.responseBody?.includes("FreeUsageLimitError")) return undefined
|
|
69
67
|
return error.data.message.includes("Overloaded") ? "Provider is overloaded" : error.data.message
|
|
70
68
|
}
|
|
71
|
-
|
|
72
|
-
// Check for rate limit patterns in plain text error messages
|
|
73
69
|
const msg = error.data?.message
|
|
74
70
|
if (typeof msg === "string") {
|
|
75
71
|
const lower = msg.toLowerCase()
|
|
@@ -146,4 +142,4 @@ export function policy(opts: {
|
|
|
146
142
|
)
|
|
147
143
|
}
|
|
148
144
|
|
|
149
|
-
export * as SessionRetry from "./retry"
|
|
145
|
+
export * as SessionRetry from "./retry"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-events.ts — SyncEvent/BusEvent 정의 단일 소스
|
|
3
|
+
*
|
|
4
|
+
* core/session.ts와 core/session-types.ts가 공유.
|
|
5
|
+
* SaeeolSession overlay를 import하지 않아 순환 의존 없음.
|
|
6
|
+
* TurnOpen/TurnClose는 overlay 의존이 필요하므로 각 소비자에서 직접 추가.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { BusEvent } from "@/bus/bus-event"
|
|
10
|
+
import { SyncEvent } from "../../sync"
|
|
11
|
+
import { SessionID } from "./schema"
|
|
12
|
+
import { Snapshot } from "@/snapshot"
|
|
13
|
+
import { MessageV2 } from "../message/message-v2"
|
|
14
|
+
import { Info, Summary, Revert } from "./session-types"
|
|
15
|
+
import { ArchivedTimestamp } from "./session-types"
|
|
16
|
+
import { Schema } from "effect"
|
|
17
|
+
import { Permission } from "@/permission"
|
|
18
|
+
import { ProjectID } from "../../project/schema"
|
|
19
|
+
import { WorkspaceID } from "../../control-plane/schema"
|
|
20
|
+
import { NonNegativeInt } from "@/util/schema"
|
|
21
|
+
|
|
22
|
+
// ── Event schemas ──
|
|
23
|
+
|
|
24
|
+
const UpdatedShare = Schema.Struct({
|
|
25
|
+
url: Schema.optional(Schema.NullOr(Schema.String)),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const UpdatedTime = Schema.Struct({
|
|
29
|
+
created: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
|
30
|
+
updated: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
|
31
|
+
compacting: Schema.optional(Schema.NullOr(NonNegativeInt)),
|
|
32
|
+
archived: Schema.optional(Schema.NullOr(ArchivedTimestamp)),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const UpdatedInfo = Schema.Struct({
|
|
36
|
+
id: Schema.optional(Schema.NullOr(SessionID)),
|
|
37
|
+
slug: Schema.optional(Schema.NullOr(Schema.String)),
|
|
38
|
+
projectID: Schema.optional(Schema.NullOr(ProjectID)),
|
|
39
|
+
workspaceID: Schema.optional(Schema.NullOr(WorkspaceID)),
|
|
40
|
+
directory: Schema.optional(Schema.NullOr(Schema.String)),
|
|
41
|
+
path: Schema.optional(Schema.NullOr(Schema.String)),
|
|
42
|
+
parentID: Schema.optional(Schema.NullOr(SessionID)),
|
|
43
|
+
summary: Schema.optional(Schema.NullOr(Summary)),
|
|
44
|
+
share: Schema.optional(UpdatedShare),
|
|
45
|
+
title: Schema.optional(Schema.NullOr(Schema.String)),
|
|
46
|
+
version: Schema.optional(Schema.NullOr(Schema.String)),
|
|
47
|
+
time: Schema.optional(UpdatedTime),
|
|
48
|
+
permission: Schema.optional(Schema.NullOr(Permission.Ruleset)),
|
|
49
|
+
revert: Schema.optional(Schema.NullOr(Revert)),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export const CreatedEventSchema = Schema.Struct({
|
|
53
|
+
sessionID: SessionID,
|
|
54
|
+
info: Info,
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const UpdatedEventSchema = Schema.Struct({
|
|
58
|
+
sessionID: SessionID,
|
|
59
|
+
info: UpdatedInfo,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// ── Event definitions (singleton registration) ──
|
|
63
|
+
|
|
64
|
+
export const SyncEvents = {
|
|
65
|
+
Created: SyncEvent.define({
|
|
66
|
+
type: "session.created",
|
|
67
|
+
version: 1,
|
|
68
|
+
aggregate: "sessionID",
|
|
69
|
+
schema: CreatedEventSchema,
|
|
70
|
+
}),
|
|
71
|
+
Updated: SyncEvent.define({
|
|
72
|
+
type: "session.updated",
|
|
73
|
+
version: 1,
|
|
74
|
+
aggregate: "sessionID",
|
|
75
|
+
schema: UpdatedEventSchema,
|
|
76
|
+
busSchema: CreatedEventSchema,
|
|
77
|
+
}),
|
|
78
|
+
Deleted: SyncEvent.define({
|
|
79
|
+
type: "session.deleted",
|
|
80
|
+
version: 1,
|
|
81
|
+
aggregate: "sessionID",
|
|
82
|
+
schema: CreatedEventSchema,
|
|
83
|
+
}),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const BusEvents = {
|
|
87
|
+
Diff: BusEvent.define(
|
|
88
|
+
"session.diff",
|
|
89
|
+
Schema.Struct({
|
|
90
|
+
sessionID: SessionID,
|
|
91
|
+
diff: Schema.Array(Snapshot.FileDiff),
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
Error: BusEvent.define(
|
|
95
|
+
"session.error",
|
|
96
|
+
Schema.Struct({
|
|
97
|
+
sessionID: Schema.optional(SessionID),
|
|
98
|
+
error: MessageV2.Assistant.fields.error,
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
}
|
|
@@ -19,6 +19,7 @@ import { SaeeolSession } from "@/saeeol/session"
|
|
|
19
19
|
import { Effect, Schema, Types } from "effect"
|
|
20
20
|
import { zod } from "@/util/effect-zod"
|
|
21
21
|
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@/util/schema"
|
|
22
|
+
import { SyncEvents, BusEvents } from "./session-events"
|
|
22
23
|
|
|
23
24
|
const log = Log.create({ service: "session" })
|
|
24
25
|
|
|
@@ -70,11 +71,11 @@ export function sessionPath(worktree: string, cwd: string) {
|
|
|
70
71
|
return path.relative(path.resolve(worktree), cwd).replaceAll("\\", "/")
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
const Summary = Schema.Struct({ additions: NonNegativeInt, deletions: NonNegativeInt, files: NonNegativeInt, diffs: optionalOmitUndefined(Schema.Array(Snapshot.SummaryFileDiff)) })
|
|
74
|
+
export const Summary = Schema.Struct({ additions: NonNegativeInt, deletions: NonNegativeInt, files: NonNegativeInt, diffs: optionalOmitUndefined(Schema.Array(Snapshot.SummaryFileDiff)) })
|
|
74
75
|
const Share = Schema.Struct({ url: Schema.String })
|
|
75
76
|
export const ArchivedTimestamp = Schema.Finite
|
|
76
77
|
const Time = Schema.Struct({ created: NonNegativeInt, updated: NonNegativeInt, compacting: optionalOmitUndefined(NonNegativeInt), archived: optionalOmitUndefined(ArchivedTimestamp) })
|
|
77
|
-
const Revert = Schema.Struct({ messageID: MessageID, partID: optionalOmitUndefined(PartID), snapshot: optionalOmitUndefined(Schema.String), diff: optionalOmitUndefined(Schema.String) })
|
|
78
|
+
export const Revert = Schema.Struct({ messageID: MessageID, partID: optionalOmitUndefined(PartID), snapshot: optionalOmitUndefined(Schema.String), diff: optionalOmitUndefined(Schema.String) })
|
|
78
79
|
|
|
79
80
|
export const Info = Schema.Struct({
|
|
80
81
|
id: SessionID, slug: Schema.String, projectID: ProjectID, workspaceID: optionalOmitUndefined(WorkspaceID),
|
|
@@ -125,11 +126,8 @@ const UpdatedInfo = Schema.Struct({
|
|
|
125
126
|
const UpdatedEventSchema = Schema.Struct({ sessionID: SessionID, info: UpdatedInfo })
|
|
126
127
|
|
|
127
128
|
export const Event = {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Deleted: SyncEvent.define({ type: "session.deleted", version: 1, aggregate: "sessionID", schema: CreatedEventSchema }),
|
|
131
|
-
Diff: BusEvent.define("session.diff", Schema.Struct({ sessionID: SessionID, diff: Schema.Array(Snapshot.FileDiff) })),
|
|
132
|
-
Error: BusEvent.define("session.error", Schema.Struct({ sessionID: Schema.optional(SessionID), error: MessageV2.Assistant.fields.error })),
|
|
129
|
+
...SyncEvents,
|
|
130
|
+
...BusEvents,
|
|
133
131
|
TurnOpen: SaeeolSession.Event.TurnOpen,
|
|
134
132
|
TurnClose: SaeeolSession.Event.TurnClose,
|
|
135
133
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Slug } from "@saeeol/core/util/slug"
|
|
1
|
+
import { Slug } from "@saeeol/core/util/slug"
|
|
2
2
|
import path from "path"
|
|
3
3
|
import { BusEvent } from "@/bus/bus-event"
|
|
4
4
|
import { Bus } from "@/bus"
|
|
@@ -33,6 +33,7 @@ import { fn } from "@/util/fn"
|
|
|
33
33
|
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
|
|
34
34
|
import { zod } from "@/util/effect-zod"
|
|
35
35
|
import { NonNegativeInt, optionalOmitUndefined, withStatics } from "@/util/schema"
|
|
36
|
+
import { SyncEvents, BusEvents } from "./session-events"
|
|
36
37
|
|
|
37
38
|
const log = Log.create({ service: "session" })
|
|
38
39
|
|
|
@@ -280,41 +281,8 @@ const UpdatedEventSchema = Schema.Struct({
|
|
|
280
281
|
})
|
|
281
282
|
|
|
282
283
|
export const Event = {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
version: 1,
|
|
286
|
-
aggregate: "sessionID",
|
|
287
|
-
schema: CreatedEventSchema,
|
|
288
|
-
}),
|
|
289
|
-
Updated: SyncEvent.define({
|
|
290
|
-
type: "session.updated",
|
|
291
|
-
version: 1,
|
|
292
|
-
aggregate: "sessionID",
|
|
293
|
-
schema: UpdatedEventSchema,
|
|
294
|
-
busSchema: CreatedEventSchema,
|
|
295
|
-
}),
|
|
296
|
-
Deleted: SyncEvent.define({
|
|
297
|
-
type: "session.deleted",
|
|
298
|
-
version: 1,
|
|
299
|
-
aggregate: "sessionID",
|
|
300
|
-
schema: CreatedEventSchema,
|
|
301
|
-
}),
|
|
302
|
-
Diff: BusEvent.define(
|
|
303
|
-
"session.diff",
|
|
304
|
-
Schema.Struct({
|
|
305
|
-
sessionID: SessionID,
|
|
306
|
-
diff: Schema.Array(Snapshot.FileDiff),
|
|
307
|
-
}),
|
|
308
|
-
),
|
|
309
|
-
Error: BusEvent.define(
|
|
310
|
-
"session.error",
|
|
311
|
-
Schema.Struct({
|
|
312
|
-
sessionID: Schema.optional(SessionID),
|
|
313
|
-
// Reuses MessageV2.Assistant.fields.error (already Schema.optional) so
|
|
314
|
-
// the derived zod keeps the same discriminated-union shape on the bus.
|
|
315
|
-
error: MessageV2.Assistant.fields.error,
|
|
316
|
-
}),
|
|
317
|
-
),
|
|
284
|
+
...SyncEvents,
|
|
285
|
+
...BusEvents,
|
|
318
286
|
TurnOpen: SaeeolSession.Event.TurnOpen,
|
|
319
287
|
TurnClose: SaeeolSession.Event.TurnClose,
|
|
320
288
|
}
|
|
@@ -348,11 +316,8 @@ export const getUsage = (input: {
|
|
|
348
316
|
input.usage.inputTokenDetails?.cacheWriteTokens ??
|
|
349
317
|
input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ??
|
|
350
318
|
// google-vertex-anthropic returns metadata under "vertex" key
|
|
351
|
-
// (AnthropicMessagesLanguageModel custom provider key from 'vertex.anthropic.messages')
|
|
352
319
|
input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ??
|
|
353
|
-
// @ts-expect-error
|
|
354
320
|
input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ??
|
|
355
|
-
// @ts-expect-error
|
|
356
321
|
input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ??
|
|
357
322
|
0,
|
|
358
323
|
),
|
|
@@ -840,9 +805,6 @@ function* listByProject(
|
|
|
840
805
|
)
|
|
841
806
|
}
|
|
842
807
|
} else if (input.scope !== "project" && !Flag.SAEEOL_EXPERIMENTAL_WORKSPACES) {
|
|
843
|
-
// if (input.directory) {
|
|
844
|
-
// conditions.push(eq(SessionTable.directory, input.directory))
|
|
845
|
-
// }
|
|
846
808
|
}
|
|
847
809
|
if (input.roots) {
|
|
848
810
|
conditions.push(isNull(SessionTable.parent_id))
|
|
@@ -945,4 +907,4 @@ export const updatePartDelta = fn(
|
|
|
945
907
|
(input) => runPromise((svc) => svc.updatePartDelta(input)),
|
|
946
908
|
)
|
|
947
909
|
|
|
948
|
-
export * as Session from "./session"
|
|
910
|
+
export * as Session from "./session"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Error types + fromError + OutputFormat — extracted from message-v2.ts */
|
|
1
|
+
/** Error types + fromError + OutputFormat — extracted from message-v2.ts */
|
|
2
2
|
|
|
3
3
|
import { APICallError, LoadAPIKeyError } from "ai"
|
|
4
4
|
import { NamedError } from "@saeeol/core/util/error"
|
|
@@ -37,8 +37,6 @@ const _Format = Schema.Union([OutputFormatText, OutputFormatJsonSchema]).annotat
|
|
|
37
37
|
export { _Format }
|
|
38
38
|
export const Format = Object.assign(_Format, { zod: zod(_Format) })
|
|
39
39
|
export type OutputFormat = Schema.Schema.Type<typeof _Format>
|
|
40
|
-
|
|
41
|
-
// Assistant error union (Zod)
|
|
42
40
|
import z from "zod"
|
|
43
41
|
const AssistantErrorZod = z.discriminatedUnion("name", [
|
|
44
42
|
AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema, AbortedError.Schema,
|
|
@@ -46,8 +44,6 @@ const AssistantErrorZod = z.discriminatedUnion("name", [
|
|
|
46
44
|
])
|
|
47
45
|
export type AssistantError = z.infer<typeof AssistantErrorZod>
|
|
48
46
|
export { AssistantErrorZod }
|
|
49
|
-
|
|
50
|
-
// Assistant error union (Effect Schema)
|
|
51
47
|
export const AssistantErrorSchema = Schema.Union([
|
|
52
48
|
AuthError.EffectSchema,
|
|
53
49
|
Schema.Struct({ name: Schema.Literal("UnknownError"), data: Schema.Struct({ message: Schema.String }) }).annotate({ identifier: "UnknownError" }),
|
|
@@ -80,4 +76,4 @@ export function fromError(e: unknown, ctx: { providerID: ProviderID; aborted?: b
|
|
|
80
76
|
} catch {}
|
|
81
77
|
return new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject()
|
|
82
78
|
}
|
|
83
|
-
}
|
|
79
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Part schemas + ToolState + Input schemas — extracted from message-v2.ts */
|
|
1
|
+
/** Part schemas + ToolState + Input schemas — extracted from message-v2.ts */
|
|
2
2
|
|
|
3
3
|
import { SessionID, MessageID, PartID } from "../core/schema"
|
|
4
4
|
import { LSP } from "@/lsp/lsp"
|
|
@@ -81,9 +81,7 @@ export const AgentPartInput = Schema.Struct({ id: Schema.optional(PartID), type:
|
|
|
81
81
|
export type AgentPartInput = Types.DeepMutable<Schema.Schema.Type<typeof AgentPartInput>>
|
|
82
82
|
export const SubtaskPartInput = Schema.Struct({ id: Schema.optional(PartID), type: Schema.Literal("subtask"), prompt: Schema.String, description: Schema.String, agent: Schema.String, model: Schema.optional(Schema.Struct({ providerID: ProviderID, modelID: ModelID })), command: Schema.optional(Schema.String) }).annotate({ identifier: "SubtaskPartInput" }).pipe(withStatics((s) => ({ zod: zod(s) })))
|
|
83
83
|
export type SubtaskPartInput = Types.DeepMutable<Schema.Schema.Type<typeof SubtaskPartInput>>
|
|
84
|
-
|
|
85
|
-
// Part union
|
|
86
84
|
const _Part = Schema.Union([TextPart, SubtaskPart, ReasoningPart, FilePart, ToolPart, StepStartPart, StepFinishPart, SnapshotPart, PatchPart, AgentPart, RetryPart, CompactionPart]).annotate({ discriminator: "type", identifier: "Part" })
|
|
87
85
|
export const Part = Object.assign(_Part, { zod: zod(_Part) as unknown as z.ZodType<TextPart | SubtaskPart | ReasoningPart | FilePart | ToolPart | StepStartPart | StepFinishPart | SnapshotPart | PatchPart | AgentPart | RetryPart | CompactionPart> })
|
|
88
86
|
export type Part = TextPart | SubtaskPart | ReasoningPart | FilePart | ToolPart | StepStartPart | StepFinishPart | SnapshotPart | PatchPart | AgentPart | RetryPart | CompactionPart
|
|
89
|
-
export { _Part }
|
|
87
|
+
export { _Part }
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
globalThis.AI_SDK_LOG_WARNINGS = false
|
|
1
|
+
globalThis.AI_SDK_LOG_WARNINGS = false
|
|
3
2
|
|
|
4
3
|
import { Effect, Layer, Scope, Latch, Context } from "effect"
|
|
5
4
|
import { ChildProcessSpawner } from "effect/unstable/process"
|
|
@@ -208,4 +207,4 @@ export const prompt = (input: PromptInput) => runPromise((svc) => svc.prompt(inp
|
|
|
208
207
|
export const loopExport = (input: LoopInput) => runPromise((svc) => svc.loop(input))
|
|
209
208
|
export const cancel = (sessionID: SessionID) => runPromise((svc) => svc.cancel(sessionID))
|
|
210
209
|
|
|
211
|
-
export * as SessionPrompt from "./prompt"
|
|
210
|
+
export * as SessionPrompt from "./prompt"
|
|
@@ -37,8 +37,6 @@ export const ApplyPatchTool = Tool.define(
|
|
|
37
37
|
if (!params.patchText) {
|
|
38
38
|
return yield* Effect.fail(new Error("patchText is required"))
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
// Parse the patch to get hunks
|
|
42
40
|
let hunks: Patch.Hunk[]
|
|
43
41
|
try {
|
|
44
42
|
const parseResult = Patch.parsePatch(params.patchText)
|
|
@@ -56,8 +54,6 @@ export const ApplyPatchTool = Tool.define(
|
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
const instance = yield* InstanceState.context
|
|
59
|
-
|
|
60
|
-
// Validate file paths and check permissions
|
|
61
57
|
const fileChanges: Array<{
|
|
62
58
|
filePath: string
|
|
63
59
|
oldContent: string
|
|
@@ -109,7 +105,6 @@ export const ApplyPatchTool = Tool.define(
|
|
|
109
105
|
}
|
|
110
106
|
|
|
111
107
|
case "update": {
|
|
112
|
-
// Check if file exists for update
|
|
113
108
|
const stats = yield* afs.stat(filePath).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
|
114
109
|
if (!stats || stats.type === "Directory") {
|
|
115
110
|
return yield* Effect.fail(
|
|
@@ -132,8 +127,6 @@ export const ApplyPatchTool = Tool.define(
|
|
|
132
127
|
let newContent = oldContent
|
|
133
128
|
let bom = source.bom
|
|
134
129
|
let encoding = read.encoding
|
|
135
|
-
|
|
136
|
-
// Apply the update chunks to get new content
|
|
137
130
|
try {
|
|
138
131
|
const fileUpdate = Patch.deriveNewContentsFromChunks(filePath, hunk.chunks)
|
|
139
132
|
newContent = fileUpdate.content
|
|
@@ -205,8 +198,6 @@ export const ApplyPatchTool = Tool.define(
|
|
|
205
198
|
}
|
|
206
199
|
}
|
|
207
200
|
}
|
|
208
|
-
|
|
209
|
-
// Build per-file metadata for UI rendering (used for both permission and result)
|
|
210
201
|
const files = fileChanges.map((change) => ({
|
|
211
202
|
filePath: change.filePath,
|
|
212
203
|
relativePath: path.relative(instance.worktree, change.movePath ?? change.filePath).replaceAll("\\", "/"),
|
|
@@ -216,8 +207,6 @@ export const ApplyPatchTool = Tool.define(
|
|
|
216
207
|
deletions: change.deletions,
|
|
217
208
|
movePath: change.movePath,
|
|
218
209
|
}))
|
|
219
|
-
|
|
220
|
-
// Check permissions if needed
|
|
221
210
|
const relativePaths = fileChanges.map((c) => path.relative(instance.worktree, c.filePath).replaceAll("\\", "/"))
|
|
222
211
|
yield* ctx.ask({
|
|
223
212
|
permission: "edit",
|
|
@@ -229,15 +218,12 @@ export const ApplyPatchTool = Tool.define(
|
|
|
229
218
|
files,
|
|
230
219
|
},
|
|
231
220
|
})
|
|
232
|
-
|
|
233
|
-
// Apply the changes
|
|
234
221
|
const updates: Array<{ file: string; event: "add" | "change" | "unlink" }> = []
|
|
235
222
|
|
|
236
223
|
for (const change of fileChanges) {
|
|
237
224
|
const edited = change.type === "delete" ? undefined : (change.movePath ?? change.filePath)
|
|
238
225
|
switch (change.type) {
|
|
239
226
|
case "add":
|
|
240
|
-
// Create parent directories (recursive: true is safe on existing/root dirs)
|
|
241
227
|
yield* EncodedIO.write(change.filePath, Bom.join(change.newContent, change.bom), change.encoding)
|
|
242
228
|
updates.push({ file: change.filePath, event: "add" })
|
|
243
229
|
break
|
|
@@ -249,7 +235,6 @@ export const ApplyPatchTool = Tool.define(
|
|
|
249
235
|
|
|
250
236
|
case "move":
|
|
251
237
|
if (change.movePath) {
|
|
252
|
-
// Create parent directories (recursive: true is safe on existing/root dirs)
|
|
253
238
|
yield* EncodedIO.write(change.movePath!, Bom.join(change.newContent, change.bom), change.encoding)
|
|
254
239
|
yield* afs.remove(change.filePath)
|
|
255
240
|
updates.push({ file: change.filePath, event: "unlink" })
|
|
@@ -270,21 +255,15 @@ export const ApplyPatchTool = Tool.define(
|
|
|
270
255
|
yield* bus.publish(File.Event.Edited, { file: edited })
|
|
271
256
|
}
|
|
272
257
|
}
|
|
273
|
-
|
|
274
|
-
// Publish file change events
|
|
275
258
|
for (const update of updates) {
|
|
276
259
|
yield* bus.publish(FileWatcher.Event.Updated, update)
|
|
277
260
|
}
|
|
278
|
-
|
|
279
|
-
// Notify LSP of file changes and collect diagnostics
|
|
280
261
|
for (const change of fileChanges) {
|
|
281
262
|
if (change.type === "delete") continue
|
|
282
263
|
const target = change.movePath ?? change.filePath
|
|
283
264
|
yield* lsp.touchFile(target, "document")
|
|
284
265
|
}
|
|
285
266
|
const diagnostics = yield* lsp.diagnostics()
|
|
286
|
-
|
|
287
|
-
// Generate output summary
|
|
288
267
|
const summaryLines = fileChanges.map((change) => {
|
|
289
268
|
if (change.type === "add") {
|
|
290
269
|
return `A ${path.relative(instance.worktree, change.filePath).replaceAll("\\", "/")}`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { levenshtein, SINGLE_CANDIDATE_SIMILARITY_THRESHOLD, MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD } from "./edit-utils"
|
|
1
|
+
import { levenshtein, SINGLE_CANDIDATE_SIMILARITY_THRESHOLD, MULTIPLE_CANDIDATES_SIMILARITY_THRESHOLD } from "./edit-utils"
|
|
2
2
|
|
|
3
3
|
export type Replacer = (content: string, find: string) => Generator<string, void, unknown>
|
|
4
4
|
|
|
@@ -151,7 +151,6 @@ export const WhitespaceNormalizedReplacer: Replacer = function* (content, find)
|
|
|
151
151
|
const match = line.match(regex)
|
|
152
152
|
if (match) yield match[0]
|
|
153
153
|
} catch {
|
|
154
|
-
// Invalid regex pattern, skip
|
|
155
154
|
}
|
|
156
155
|
}
|
|
157
156
|
}
|
|
@@ -285,4 +284,4 @@ export const ContextAwareReplacer: Replacer = function* (content, find) {
|
|
|
285
284
|
}
|
|
286
285
|
}
|
|
287
286
|
}
|
|
288
|
-
}
|
|
287
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Schema } from "effect"
|
|
1
|
+
import { Effect, Schema } from "effect"
|
|
2
2
|
import { Npm } from "@saeeol/core/npm"
|
|
3
3
|
import * as Bus from "@/bus"
|
|
4
4
|
import * as Tool from "../core/tool"
|
|
@@ -98,8 +98,6 @@ export const PackageTool = Tool.define(
|
|
|
98
98
|
]
|
|
99
99
|
return { title: "Available Providers", output: lines.join("\n"), metadata: {} }
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
// action === "install"
|
|
103
101
|
const packages = params.packages
|
|
104
102
|
if (!packages || packages.length === 0) {
|
|
105
103
|
return {
|
|
@@ -165,4 +163,4 @@ export const PackageTool = Tool.define(
|
|
|
165
163
|
}),
|
|
166
164
|
}
|
|
167
165
|
}),
|
|
168
|
-
)
|
|
166
|
+
)
|
|
@@ -7,9 +7,6 @@ import { Bus } from "../../bus"
|
|
|
7
7
|
import { TuiEvent } from "../../cli/cmd/tui/event"
|
|
8
8
|
import DESCRIPTION from "./warpgrep.txt"
|
|
9
9
|
|
|
10
|
-
// FREE_PERIOD_TODO: Remove SAEEOL_WARPGREP_PROXY_URL constant and the proxy
|
|
11
|
-
// fallback below. After the free period ends, require MORPH_API_KEY and
|
|
12
|
-
// return an error when it is missing.
|
|
13
10
|
const SAEEOL_WARPGREP_PROXY_URL = "https://api.saeeol.ai/api/gateway"
|
|
14
11
|
|
|
15
12
|
const Parameters = Schema.Struct({
|
|
@@ -36,8 +33,6 @@ export const CodebaseSearchTool = Tool.define(
|
|
|
36
33
|
|
|
37
34
|
const apiKey = process.env["MORPH_API_KEY"]
|
|
38
35
|
|
|
39
|
-
// FREE_PERIOD_TODO: Remove proxy fallback — require apiKey, error if missing:
|
|
40
|
-
// if (!apiKey) return { title: ..., output: "Set MORPH_API_KEY to use codebase search.", metadata: {} }
|
|
41
36
|
const client = new WarpGrepClient({
|
|
42
37
|
morphApiKey: apiKey ?? "saeeol-free",
|
|
43
38
|
...(apiKey ? {} : { morphApiUrl: SAEEOL_WARPGREP_PROXY_URL }),
|
|
@@ -52,9 +47,6 @@ export const CodebaseSearchTool = Tool.define(
|
|
|
52
47
|
)
|
|
53
48
|
|
|
54
49
|
if (!result.success || !result.contexts?.length) {
|
|
55
|
-
// FREE_PERIOD_TODO: When the proxy stops serving free requests, errors
|
|
56
|
-
// from the proxy (401/402/429) will surface here. The message below
|
|
57
|
-
// tells the user exactly what to do.
|
|
58
50
|
const isAuthOrRateLimit =
|
|
59
51
|
result.error && /401|402|429|rate.limit|free.period|unauthorized/i.test(result.error)
|
|
60
52
|
const apiKeyMsg =
|