saeeol 1.0.7 → 1.0.8

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.
@@ -21,6 +21,8 @@ export const DEFAULT_BATCH_SIZE = 10
21
21
  export const DEFAULT_KEEP_HEAD = 2
22
22
  export const DEFAULT_KEEP_TAIL = 2
23
23
  export const DEFAULT_AUTO_BATCHES = 5
24
+ export const BATCH_TOKEN_TARGET = 4_000
25
+ export const BATCH_TOKEN_MAX = 8_000
24
26
 
25
27
  export type Chunk = {
26
28
  index: number
@@ -108,9 +110,57 @@ export function batchSplit(messages: MessageV2.WithParts[], cfg: Config.Info): B
108
110
  const size = cfg.compaction?.batch_size ?? DEFAULT_BATCH_SIZE
109
111
  const head = cfg.compaction?.keep_head ?? DEFAULT_KEEP_HEAD
110
112
  const tail = cfg.compaction?.keep_tail ?? DEFAULT_KEEP_TAIL
113
+ const tokenTarget = BATCH_TOKEN_TARGET
114
+ const tokenMax = BATCH_TOKEN_MAX
111
115
  const batches: Batch[] = []
112
- for (let i = 0; i < messages.length; i += size) {
113
- const slice = messages.slice(i, i + size)
116
+ let i = 0
117
+ while (i < messages.length) {
118
+ const remaining = messages.length - i
119
+ const fixedEnd = Math.min(i + size, messages.length)
120
+ if (remaining <= size + head + tail) {
121
+ const slice = messages.slice(i)
122
+ const midStart = Math.min(head, slice.length)
123
+ const midEnd = Math.max(midStart, slice.length - tail)
124
+ batches.push({
125
+ index: batches.length,
126
+ first: slice.slice(0, midStart),
127
+ middle: slice.slice(midStart, midEnd),
128
+ last: slice.slice(midEnd),
129
+ })
130
+ break
131
+ }
132
+ let end = fixedEnd
133
+ let est = 0
134
+ for (let j = i; j < fixedEnd; j++) {
135
+ const parts = messages[j]!.parts
136
+ for (const p of parts) {
137
+ if (p.type === "text") est += p.text.length
138
+ else if (p.type === "tool" && p.state.status === "completed") est += Math.min(p.state.output?.length ?? 0, TOOL_OUTPUT_MAX_CHARS)
139
+ else if (p.type === "reasoning") est += p.text?.length ?? 0
140
+ }
141
+ }
142
+ est = Math.round(est / 4)
143
+ if (est < tokenTarget && fixedEnd < messages.length) {
144
+ let expandEst = est
145
+ for (let j = fixedEnd; j < messages.length; j++) {
146
+ const parts = messages[j]!.parts
147
+ let add = 0
148
+ for (const p of parts) {
149
+ if (p.type === "text") add += p.text.length
150
+ else if (p.type === "tool" && p.state.status === "completed") add += Math.min(p.state.output?.length ?? 0, TOOL_OUTPUT_MAX_CHARS)
151
+ }
152
+ expandEst += Math.round(add / 4)
153
+ if (expandEst >= tokenTarget) { end = j + 1; break }
154
+ if (expandEst >= tokenMax) { end = j + 1; break }
155
+ end = j + 1
156
+ }
157
+ } else if (est > tokenMax) {
158
+ for (let j = fixedEnd - 1; j > i + head; j--) {
159
+ end = j
160
+ break
161
+ }
162
+ }
163
+ const slice = messages.slice(i, end)
114
164
  const midStart = Math.min(head, slice.length)
115
165
  const midEnd = Math.max(midStart, slice.length - tail)
116
166
  batches.push({
@@ -119,6 +169,7 @@ export function batchSplit(messages: MessageV2.WithParts[], cfg: Config.Info): B
119
169
  middle: slice.slice(midStart, midEnd),
120
170
  last: slice.slice(midEnd),
121
171
  })
172
+ i = end
122
173
  }
123
174
  return batches
124
175
  }
@@ -6,6 +6,8 @@ import * as Log from "@saeeol/core/util/log"
6
6
  import { BusEvent } from "@/bus/bus-event"
7
7
  import { Bus } from "@/bus"
8
8
  import { AsyncQueue } from "@/util/queue"
9
+ // provider-events 등록: BusEvent.define 호출로 전역 레지스트리에 이벤트 타입 추가
10
+ import "@/provider/provider-events"
9
11
 
10
12
  const log = Log.create({ service: "server" })
11
13
 
@@ -19,6 +19,8 @@ import { SessionApi } from "./groups/session"
19
19
  import { SyncApi } from "./groups/sync"
20
20
  import { TuiApi } from "./groups/tui"
21
21
  import { WorkspaceApi } from "./groups/workspace"
22
+ // provider-events 등록: BusEvent.define 호출로 전역 레지스트리에 이벤트 타입 추가
23
+ import "@/provider/provider-events"
22
24
 
23
25
  // SSE event schemas built from the same BusEvent/SyncEvent registries that
24
26
  // the Hono spec uses, so both specs emit identical Event/SyncEvent components.
@@ -5,7 +5,6 @@ import { HttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstab
5
5
  import { Hono } from "hono"
6
6
  import { getMimeType } from "hono/utils/mime"
7
7
  import fs from "node:fs/promises"
8
- // @ts-expect-error - generated file
9
8
  import embeddedWebUIData from "../../saeeol-web-ui.gen.ts"
10
9
 
11
10
  const embeddedWebUI: Record<string, string> | null = embeddedWebUIData ?? null
@@ -39,8 +39,11 @@ export const PRUNE_PROTECT = 40_000
39
39
  const TOOL_OUTPUT_MAX_CHARS = 2_000
40
40
  const PRUNE_PROTECTED_TOOLS = ["skill"]
41
41
  const DEFAULT_TAIL_TURNS = 2
42
- const MIN_PRESERVE_RECENT_TOKENS = 2_000
43
- const MAX_PRESERVE_RECENT_TOKENS = 8_000
42
+ const MIN_PRESERVE_RECENT_TOKENS = 4_000
43
+ const MAX_PRESERVE_RECENT_TOKENS = 32_000
44
+ const TAIL_PRESERVE_RATIO = 0.20
45
+ const CONTEXT_HEALTH_WARNING = 0.75
46
+ const CONTEXT_HEALTH_CRITICAL = 0.90
44
47
  const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
45
48
  <template>
46
49
  ## Goal
@@ -70,12 +73,16 @@ const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <te
70
73
 
71
74
  ## Relevant Files
72
75
  - [file or directory path: why it matters, or "(none)"]
76
+
77
+ ## Code Artifacts
78
+ - [key code snippets, function signatures, type definitions that were created or modified]
73
79
  </template>
74
80
 
75
81
  Rules:
76
82
  - Keep every section, even when empty.
77
83
  - Use terse bullets, not prose paragraphs.
78
84
  - Preserve exact file paths, commands, error strings, and identifiers when known.
85
+ - Preserve critical code: function signatures, type definitions, config values, and error messages.
79
86
  - Do not mention the summary process or that context was compacted.`
80
87
  type Turn = {
81
88
  start: number
@@ -137,9 +144,11 @@ function buildPrompt(input: { previousSummary?: string; context: string[] }) {
137
144
  }
138
145
 
139
146
  function preserveRecentBudget(input: { cfg: Config.Info; model: Provider.Model }) {
147
+ const ctx = input.model.limit.context || 128_000
148
+ const autoBudget = Math.floor(ctx * TAIL_PRESERVE_RATIO)
140
149
  return (
141
150
  input.cfg.compaction?.preserve_recent_tokens ??
142
- clamp(Math.floor(usable(input) * 0.25), MIN_PRESERVE_RECENT_TOKENS, MAX_PRESERVE_RECENT_TOKENS)
151
+ clamp(autoBudget, MIN_PRESERVE_RECENT_TOKENS, MAX_PRESERVE_RECENT_TOKENS)
143
152
  )
144
153
  }
145
154
 
@@ -267,11 +276,26 @@ export const layer: Layer.Layer<
267
276
  { concurrency: 1 },
268
277
  )
269
278
 
279
+ const totalRecent = sizes.reduce((s, n) => s + n, 0)
280
+ const expandedLimit = totalRecent > budget
281
+ ? Math.min(limit + Math.ceil(totalRecent / budget), all.length)
282
+ : limit
283
+ const expanded = expandedLimit > limit ? all.slice(-expandedLimit) : recent
284
+ const expandedSizes = expandedLimit > limit
285
+ ? yield* Effect.forEach(
286
+ expanded,
287
+ (turn) => estimate({ messages: input.messages.slice(turn.start, turn.end), model: input.model }),
288
+ { concurrency: 1 },
289
+ )
290
+ : sizes
291
+
270
292
  let total = 0
271
293
  let keep: Tail | undefined
272
- for (let i = recent.length - 1; i >= 0; i--) {
273
- const turn = recent[i]!
274
- const size = sizes[i]
294
+ const iterLimit = expandedLimit > limit ? expanded : recent
295
+ const iterSizes = expandedLimit > limit ? expandedSizes : sizes
296
+ for (let i = iterLimit.length - 1; i >= 0; i--) {
297
+ const turn = iterLimit[i]!
298
+ const size = iterSizes[i]
275
299
  if (total + size <= budget) {
276
300
  total += size
277
301
  keep = { start: turn.start, id: turn.id }
@@ -1,4 +1,4 @@
1
- You are Kilo, the best coding agent on the planet.
1
+ You are SAEEOL, the best coding agent on the planet.
2
2
 
3
3
  You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
4
4
 
@@ -7,9 +7,9 @@ IMPORTANT: You must NEVER generate or guess URLs for the user unless you are con
7
7
  If the user asks for help or wants to give feedback inform them of the following:
8
8
  - ctrl+p to list available actions
9
9
  - To give feedback, users should report the issue at
10
- https://github.com/Kilo-Org/saeeol
10
+ https://github.com/byfabulist/saeeol
11
11
 
12
- When the user directly asks about Kilo (eg. "can Kilo do...", "does Kilo have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific Kilo feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from Kilo docs. The list of available docs is available at https://kilo.ai/docs
12
+ When the user directly asks about SAEEOL (eg. "can SAEEOL do...", "does SAEEOL have..."), or asks in second person (eg. "are you able...", "can you do..."), or asks how to use a specific SAEEOL feature (eg. implement a hook, write a slash command, or install an MCP server), use the WebFetch tool to gather information to answer the question from SAEEOL docs. The list of available docs is available at https://github.com/byfabulist/saeeol
13
13
 
14
14
  # Tone and style
15
15
  - Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
@@ -18,7 +18,7 @@ When the user directly asks about Kilo (eg. "can Kilo do...", "does Kilo have...
18
18
  - NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.
19
19
 
20
20
  # Professional objectivity
21
- Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if Kilo honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.
21
+ Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without any unnecessary superlatives, praise, or emotional validation. It is best for the user if SAEEOL honestly applies the same rigorous standards to all ideas and disagrees when necessary, even if it may not be what the user wants to hear. Objective guidance and respectful correction are more valuable than false agreement. Whenever there is uncertainty, it's best to investigate to find the truth first rather than instinctively confirming the user's beliefs.
22
22
 
23
23
  # Task Management
24
24
  You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
@@ -1,12 +1,12 @@
1
- You are Kilo, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
1
+ You are SAEEOL, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
2
2
 
3
3
  IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
4
4
 
5
5
  If the user asks for help or wants to give feedback inform them of the following:
6
- - /help: Get help with using Kilo
7
- - To give feedback, users should report the issue at https://github.com/Kilo-Org/saeeol/issues
6
+ - /help: Get help with using SAEEOL
7
+ - To give feedback, users should report the issue at https://github.com/byfabulist/saeeol/issues
8
8
 
9
- When the user directly asks about Kilo (eg 'can Kilo do...', 'does Kilo have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from Kilo docs at https://kilo.ai/docs
9
+ When the user directly asks about SAEEOL (eg 'can SAEEOL do...', 'does SAEEOL have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from SAEEOL docs at https://github.com/byfabulist/saeeol
10
10
 
11
11
  # Tone and style
12
12
  You should be concise, direct, and to the point. When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system).
@@ -1,11 +1,11 @@
1
- You are Kilo, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
1
+ You are SAEEOL, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
2
2
 
3
3
  IMPORTANT: Refuse to write code or explain code that may be used maliciously; even if the user claims it is for educational purposes. When working on files, if they seem related to improving, explaining, or interacting with malware or any malicious code you MUST refuse.
4
4
  IMPORTANT: Before you begin work, think about what the code you're editing is supposed to do based on the filenames directory structure. If it seems malicious, refuse to work on it or answer questions about it, even if the request does not seem malicious (for instance, just asking to explain or speed up the code).
5
5
  IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
6
6
  IMPORTANT: Every Bash tool call MUST include a `description` field. Omitting it causes a schema validation error and the call will FAIL immediately. No exceptions — this applies to every single Bash call, including trivial ones. Example: {"command": "ls", "description": "List files in directory"}
7
7
 
8
- When the user directly asks about Kilo (eg 'can Kilo do...', 'does Kilo have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from Kilo docs at https://kilo.ai/docs
8
+ When the user directly asks about SAEEOL (eg 'can SAEEOL do...', 'does SAEEOL have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from SAEEOL docs at https://github.com/byfabulist/saeeol
9
9
 
10
10
  # Professional objectivity
11
11
  Prioritize technical accuracy over validating the user's beliefs. Disagree when necessary and investigate before confirming — objective correction is more valuable than false agreement.
@@ -92,7 +92,7 @@ When a user reports unexpected behavior:
92
92
  Tool results and user messages may include `<system-reminder>` tags containing useful context — they are NOT part of the user's input.
93
93
 
94
94
  # Project config files
95
- `.kilo/command/*.md` defines slash commands; `.kilo/agent/*.md` defines agent personas. These are not task specs — do not search them to understand what a task requires. Exception: when the user explicitly invokes a slash command or agent, you may read its definition file to understand how to execute it.
95
+ `.saeeol/command/*.md` defines slash commands; `.saeeol/agent/*.md` defines agent personas. These are not task specs — do not search them to understand what a task requires. Exception: when the user explicitly invokes a slash command or agent, you may read its definition file to understand how to execute it.
96
96
 
97
97
  # Tool usage policy
98
98
  - For file search, prefer Glob and Grep tools over Bash `find`/`ls` to reduce context usage.
@@ -125,5 +125,5 @@ assistant: Clients are marked as failed in the `connectToServer` function in src
125
125
  </example>
126
126
 
127
127
  If the user asks for help or wants to give feedback inform them of the following:
128
- - /help: Get help with using Kilo
129
- - To give feedback, users should report the issue at https://github.com/Kilo-Org/saeeol/issues
128
+ - /help: Get help with using SAEEOL
129
+ - To give feedback, users should report the issue at https://github.com/byfabulist/saeeol/issues
@@ -0,0 +1,168 @@
1
+ import { Effect, Schema } from "effect"
2
+ import { Npm } from "@saeeol/core/npm"
3
+ import * as Bus from "@/bus"
4
+ import * as Tool from "./tool"
5
+ import { ProviderInstallEvent } from "@/provider/provider-events"
6
+
7
+ const Parameters = Schema.Struct({
8
+ action: Schema.Literals(["install", "list"]).annotate({
9
+ description: '"install" to install provider packages, "list" to show available providers',
10
+ }),
11
+ packages: Schema.optional(Schema.Array(Schema.String)).annotate({
12
+ description:
13
+ 'Package names to install. npm names like "@ai-sdk/anthropic" or short names like "anthropic".',
14
+ }),
15
+ })
16
+
17
+ type Params = Schema.Schema.Type<typeof Parameters>
18
+
19
+ const SHORT_NAME_MAP: Record<string, string> = {
20
+ anthropic: "@ai-sdk/anthropic",
21
+ openai: "@ai-sdk/openai",
22
+ "openai-compatible": "@ai-sdk/openai-compatible",
23
+ google: "@ai-sdk/google",
24
+ "google-vertex": "@ai-sdk/google-vertex",
25
+ bedrock: "@ai-sdk/amazon-bedrock",
26
+ "amazon-bedrock": "@ai-sdk/amazon-bedrock",
27
+ azure: "@ai-sdk/azure",
28
+ openrouter: "@openrouter/ai-sdk-provider",
29
+ groq: "@ai-sdk/groq",
30
+ deepinfra: "@ai-sdk/deepinfra",
31
+ gateway: "@ai-sdk/gateway",
32
+ alibaba: "@ai-sdk/alibaba",
33
+ cerebras: "@ai-sdk/cerebras",
34
+ xai: "@ai-sdk/xai",
35
+ mistral: "@ai-sdk/mistral",
36
+ cohere: "@ai-sdk/cohere",
37
+ togetherai: "@ai-sdk/togetherai",
38
+ perplexity: "@ai-sdk/perplexity",
39
+ vercel: "@ai-sdk/vercel",
40
+ gitlab: "gitlab-ai-provider",
41
+ venice: "venice-ai-sdk-provider",
42
+ }
43
+
44
+ const KNOWN_PROVIDERS = [
45
+ { npm: "@saeeol/gateway", label: "Saeeol Gateway (bundled)" },
46
+ { npm: "@ai-sdk/anthropic", label: "Anthropic (Claude)" },
47
+ { npm: "@ai-sdk/openai", label: "OpenAI (GPT)" },
48
+ { npm: "@ai-sdk/openai-compatible", label: "OpenAI Compatible" },
49
+ { npm: "@ai-sdk/google", label: "Google (Gemini)" },
50
+ { npm: "@ai-sdk/github-copilot", label: "GitHub Copilot" },
51
+ { npm: "@ai-sdk/amazon-bedrock", label: "Amazon Bedrock" },
52
+ { npm: "@ai-sdk/azure", label: "Azure OpenAI" },
53
+ { npm: "@ai-sdk/google-vertex", label: "Google Vertex AI" },
54
+ { npm: "@openrouter/ai-sdk-provider", label: "OpenRouter" },
55
+ { npm: "@ai-sdk/groq", label: "Groq" },
56
+ { npm: "@ai-sdk/deepinfra", label: "DeepInfra" },
57
+ { npm: "@ai-sdk/gateway", label: "Vercel AI Gateway" },
58
+ { npm: "@ai-sdk/alibaba", label: "Alibaba (Qwen)" },
59
+ { npm: "@ai-sdk/cerebras", label: "Cerebras" },
60
+ { npm: "@ai-sdk/xai", label: "xAI (Grok)" },
61
+ { npm: "@ai-sdk/mistral", label: "Mistral" },
62
+ { npm: "@ai-sdk/cohere", label: "Cohere" },
63
+ { npm: "@ai-sdk/togetherai", label: "Together AI" },
64
+ { npm: "@ai-sdk/perplexity", label: "Perplexity" },
65
+ { npm: "@ai-sdk/vercel", label: "Vercel" },
66
+ ]
67
+
68
+ function resolve(pkg: string) {
69
+ if (pkg.startsWith("@") || pkg.startsWith("file://")) return pkg
70
+ return SHORT_NAME_MAP[pkg] ?? pkg
71
+ }
72
+
73
+ export const PackageTool = Tool.define(
74
+ "package",
75
+ Effect.gen(function* () {
76
+ return {
77
+ description: [
78
+ "Manage saeeol provider SDK packages.",
79
+ "",
80
+ "Use this tool when the user asks to install additional AI providers,",
81
+ "or when you detect that a required provider is not available.",
82
+ "",
83
+ 'Actions: "install" — install provider packages, "list" — show available providers',
84
+ "",
85
+ "Short names: anthropic, openai, google, bedrock, azure, openrouter, groq,",
86
+ "deepinfra, cerebras, xai, mistral, cohere, togetherai, perplexity, vercel",
87
+ ].join("\n"),
88
+ parameters: Parameters,
89
+ execute: (params: Params, ctx: Tool.Context) =>
90
+ Effect.gen(function* () {
91
+ if (params.action === "list") {
92
+ const lines = [
93
+ "Available provider SDKs:",
94
+ "",
95
+ ...KNOWN_PROVIDERS.map((p) => ` ${p.label}\n npm: ${p.npm}`),
96
+ "",
97
+ 'Install: { action: "install", packages: ["anthropic"] }',
98
+ ]
99
+ return { title: "Available Providers", output: lines.join("\n"), metadata: {} }
100
+ }
101
+
102
+ // action === "install"
103
+ const packages = params.packages
104
+ if (!packages || packages.length === 0) {
105
+ return {
106
+ title: "Package Install",
107
+ output: "No packages specified. Provide package names in the 'packages' array.",
108
+ metadata: {},
109
+ }
110
+ }
111
+
112
+ const resolved = packages.map(resolve)
113
+
114
+ yield* ctx.ask({
115
+ permission: "package",
116
+ patterns: resolved,
117
+ always: resolved,
118
+ metadata: {},
119
+ })
120
+
121
+ const results: string[] = []
122
+ for (const pkg of resolved) {
123
+ void Bus.publish(ProviderInstallEvent.Started, {
124
+ providerID: pkg,
125
+ pkg,
126
+ })
127
+ try {
128
+ const entry = yield* Effect.promise(() => Npm.add(pkg))
129
+ if (!entry.entrypoint) {
130
+ void Bus.publish(ProviderInstallEvent.Failed, {
131
+ providerID: pkg,
132
+ pkg,
133
+ error: "No import entrypoint found",
134
+ })
135
+ results.push(`X ${pkg}: no import entrypoint`)
136
+ continue
137
+ }
138
+ void Bus.publish(ProviderInstallEvent.Completed, {
139
+ providerID: pkg,
140
+ pkg,
141
+ })
142
+ results.push(`OK ${pkg}`)
143
+ } catch (err) {
144
+ const msg = err instanceof Error ? err.message : String(err)
145
+ void Bus.publish(ProviderInstallEvent.Failed, {
146
+ providerID: pkg,
147
+ pkg,
148
+ error: msg,
149
+ })
150
+ results.push(`X ${pkg}: ${msg}`)
151
+ }
152
+ }
153
+
154
+ return {
155
+ title: "Package Install",
156
+ output: [
157
+ `${resolved.length} package(s) processed:`,
158
+ "",
159
+ ...results,
160
+ "",
161
+ "Installed providers are now available.",
162
+ ].join("\n"),
163
+ metadata: {},
164
+ }
165
+ }),
166
+ }
167
+ }),
168
+ )
@@ -13,6 +13,7 @@ import { WebFetchTool } from "./webfetch"
13
13
  import { WriteTool } from "./write"
14
14
  import { InvalidTool } from "./invalid"
15
15
  import { SkillTool } from "./skill"
16
+ import { PackageTool } from "./package"
16
17
  import * as Tool from "./tool"
17
18
  import { Config } from "@/config/config"
18
19
  import { type ToolContext as PluginToolContext, type ToolDefinition } from "@saeeol/plugin"
@@ -116,6 +117,7 @@ export const layer: Layer.Layer<
116
117
  const greptool = yield* GrepTool
117
118
  const patchtool = yield* ApplyPatchTool
118
119
  const skilltool = yield* SkillTool
120
+ const packagetool = yield* PackageTool
119
121
  const agent = yield* Agent.Service
120
122
  const suggesttool = yield* SuggestTool
121
123
  const saeeolToolInfos = yield* SaeeolToolRegistry.infos()
@@ -211,6 +213,7 @@ export const layer: Layer.Layer<
211
213
  todo: Tool.init(todo),
212
214
  search: Tool.init(websearch),
213
215
  skill: Tool.init(skilltool),
216
+ package: Tool.init(packagetool),
214
217
  patch: Tool.init(patchtool),
215
218
  question: Tool.init(question),
216
219
  lsp: Tool.init(lsptool),
@@ -237,6 +240,7 @@ export const layer: Layer.Layer<
237
240
  tool.todo,
238
241
  tool.search,
239
242
  tool.skill,
243
+ tool.package,
240
244
  tool.patch,
241
245
  tool.plan,
242
246
  ...(["cli", "vscode"].includes(Flag.SAEEOL_CLIENT) ? [tool.suggest] : []),
@@ -11,21 +11,21 @@ description: Build AI agents on Cloudflare Workers using the Agents SDK. Load wh
11
11
 
12
12
  Fetch current docs from `https://github.com/cloudflare/agents/tree/main/docs` before implementing.
13
13
 
14
- | Topic | Doc | Use for |
15
- | ------------------- | ----------------------------- | ---------------------------------------------- |
16
- | Getting started | `docs/getting-started.md` | First agent, project setup |
17
- | State | `docs/state.md` | `setState`, `validateStateChange`, persistence |
18
- | Routing | `docs/routing.md` | URL patterns, `routeAgentRequest`, `basePath` |
19
- | Callable methods | `docs/callable-methods.md` | `@callable`, RPC, streaming, timeouts |
20
- | Scheduling | `docs/scheduling.md` | `schedule()`, `scheduleEvery()`, cron |
21
- | Workflows | `docs/workflows.md` | `AgentWorkflow`, durable multi-step tasks |
22
- | HTTP/WebSockets | `docs/http-websockets.md` | Lifecycle hooks, hibernation |
23
- | Email | `docs/email.md` | Email routing, secure reply resolver |
24
- | MCP client | `docs/mcp-client.md` | Connecting to MCP servers |
25
- | MCP server | `docs/mcp-servers.md` | Building MCP servers with `McpAgent` |
26
- | Client SDK | `docs/client-sdk.md` | `useAgent`, `useAgentChat`, React hooks |
27
- | Human-in-the-loop | `docs/human-in-the-loop.md` | Approval flows, pausing workflows |
28
- | Resumable streaming | `docs/resumable-streaming.md` | Stream recovery on disconnect |
14
+ | Topic | Doc | Use for |
15
+ |---|---|---|
16
+ | Getting started | `docs/getting-started.md` | First agent, project setup |
17
+ | State | `docs/state.md` | `setState`, `validateStateChange`, persistence |
18
+ | Routing | `docs/routing.md` | URL patterns, `routeAgentRequest`, `basePath` |
19
+ | Callable methods | `docs/callable-methods.md` | `@callable`, RPC, streaming, timeouts |
20
+ | Scheduling | `docs/scheduling.md` | `schedule()`, `scheduleEvery()`, cron |
21
+ | Workflows | `docs/workflows.md` | `AgentWorkflow`, durable multi-step tasks |
22
+ | HTTP/WebSockets | `docs/http-websockets.md` | Lifecycle hooks, hibernation |
23
+ | Email | `docs/email.md` | Email routing, secure reply resolver |
24
+ | MCP client | `docs/mcp-client.md` | Connecting to MCP servers |
25
+ | MCP server | `docs/mcp-servers.md` | Building MCP servers with `McpAgent` |
26
+ | Client SDK | `docs/client-sdk.md` | `useAgent`, `useAgentChat`, React hooks |
27
+ | Human-in-the-loop | `docs/human-in-the-loop.md` | Approval flows, pausing workflows |
28
+ | Resumable streaming | `docs/resumable-streaming.md` | Stream recovery on disconnect |
29
29
 
30
30
  Cloudflare docs: https://developers.cloudflare.com/agents/
31
31
 
@@ -101,26 +101,26 @@ export default {
101
101
 
102
102
  Requests route to `/agents/{agent-name}/{instance-name}`:
103
103
 
104
- | Class | URL |
105
- | ---------- | -------------------------- |
106
- | `Counter` | `/agents/counter/user-123` |
107
- | `ChatRoom` | `/agents/chat-room/lobby` |
104
+ | Class | URL |
105
+ |---|---|
106
+ | `Counter` | `/agents/counter/user-123` |
107
+ | `ChatRoom` | `/agents/chat-room/lobby` |
108
108
 
109
109
  Client: `useAgent({ agent: "Counter", name: "user-123" })`
110
110
 
111
111
  ## Core APIs
112
112
 
113
- | Task | API |
114
- | ------------------- | ------------------------------------------------------ |
115
- | Read state | `this.state.count` |
116
- | Write state | `this.setState({ count: 1 })` |
117
- | SQL query | `` this.sql`SELECT * FROM users WHERE id = ${id}` `` |
118
- | Schedule (delay) | `await this.schedule(60, "task", payload)` |
119
- | Schedule (cron) | `await this.schedule("0 * * * *", "task", payload)` |
120
- | Schedule (interval) | `await this.scheduleEvery(30, "poll")` |
121
- | RPC method | `@callable() myMethod() { ... }` |
122
- | Streaming RPC | `@callable({ streaming: true }) stream(res) { ... }` |
123
- | Start workflow | `await this.runWorkflow("ProcessingWorkflow", params)` |
113
+ | Task | API |
114
+ |---|---|
115
+ | Read state | `this.state.count` |
116
+ | Write state | `this.setState({ count: 1 })` |
117
+ | SQL query | `` this.sql`SELECT * FROM users WHERE id = ${id}` `` |
118
+ | Schedule (delay) | `await this.schedule(60, "task", payload)` |
119
+ | Schedule (cron) | `await this.schedule("0 * * * *", "task", payload)` |
120
+ | Schedule (interval) | `await this.scheduleEvery(30, "poll")` |
121
+ | RPC method | `@callable() myMethod() { ... }` |
122
+ | Streaming RPC | `@callable({ streaming: true }) stream(res) { ... }` |
123
+ | Start workflow | `await this.runWorkflow("ProcessingWorkflow", params)` |
124
124
 
125
125
  ## React Client
126
126