@saeeol/plugin 7.3.2 → 7.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,53 +1,50 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@saeeol/plugin",
4
- "version": "7.3.2",
4
+ "version": "7.3.4",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
- "publishConfig": {
8
- "access": "public",
9
- "registry": "https://registry.npmjs.org"
10
- },
11
- "scripts": {
12
- "typecheck": "tsgo --noEmit",
13
- "build": "tsc"
14
- },
15
- "exports": {
16
- ".": "./src/index.ts",
17
- "./tool": "./src/tool.ts",
18
- "./tui": "./src/tui.ts"
19
- },
20
- "files": [
21
- "dist"
22
- ],
23
- "dependencies": {
24
- "@saeeol/sdk": "7.3.1",
25
- "effect": "4.0.0-beta.57",
26
- "zod": "4.1.8"
27
- },
28
- "peerDependencies": {
29
- "@opentui/core": ">=0.2.2",
30
- "@opentui/solid": ">=0.2.2"
31
- },
32
- "peerDependenciesMeta": {
33
- "@opentui/core": {
34
- "optional": true
7
+ "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" },
8
+ "scripts": {
9
+ "typecheck": "tsgo --noEmit",
10
+ "build": "tsc"
35
11
  },
36
- "@opentui/solid": {
37
- "optional": true
12
+ "exports": {
13
+ ".": "./src/index.ts",
14
+ "./tool": "./src/tool.ts",
15
+ "./tui": "./src/tui.ts"
16
+ },
17
+ "files": [
18
+ "src"
19
+ ],
20
+ "dependencies": {
21
+ "@saeeol/sdk": "7.3.3",
22
+ "effect": "4.0.0-beta.57",
23
+ "zod": "4.1.8"
24
+ },
25
+ "peerDependencies": {
26
+ "@opentui/core": ">=0.2.2",
27
+ "@opentui/solid": ">=0.2.2"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "@opentui/core": {
31
+ "optional": true
32
+ },
33
+ "@opentui/solid": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "devDependencies": {
38
+ "@opentui/core": "0.2.2",
39
+ "@opentui/solid": "0.2.2",
40
+ "@tsconfig/node22": "22.0.2",
41
+ "@types/node": "22.13.9",
42
+ "typescript": "5.8.2",
43
+ "@typescript/native-preview": "7.0.0-dev.20260316.1"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/SAEEOL/saeeol",
48
+ "directory": "packages/plugin"
38
49
  }
39
- },
40
- "devDependencies": {
41
- "@opentui/core": "0.2.2",
42
- "@opentui/solid": "0.2.2",
43
- "@tsconfig/node22": "22.0.2",
44
- "@types/node": "22.13.9",
45
- "typescript": "5.8.2",
46
- "@typescript/native-preview": "7.0.0-dev.20260316.1"
47
- },
48
- "repository": {
49
- "type": "git",
50
- "url": "https://github.com/SAEEOL/saeeol",
51
- "directory": "packages/plugin"
52
- }
53
50
  }
@@ -0,0 +1,34 @@
1
+ import type { Plugin } from "@saeeol/plugin"
2
+ import { mkdir, rm } from "node:fs/promises"
3
+
4
+ export const FolderWorkspacePlugin: Plugin = async ({ experimental_workspace }) => {
5
+ experimental_workspace.register("folder", {
6
+ name: "Folder",
7
+ description: "Create a blank folder",
8
+ configure(config) {
9
+ const rand = "" + Math.random()
10
+
11
+ return {
12
+ ...config,
13
+ directory: `/tmp/folder/folder-${rand}`,
14
+ }
15
+ },
16
+ async create(config) {
17
+ if (!config.directory) return
18
+ await mkdir(config.directory, { recursive: true })
19
+ },
20
+ async remove(config) {
21
+ await rm(config.directory!, { recursive: true, force: true })
22
+ },
23
+ target(config) {
24
+ return {
25
+ type: "local",
26
+ directory: config.directory!,
27
+ }
28
+ },
29
+ })
30
+
31
+ return {}
32
+ }
33
+
34
+ export default FolderWorkspacePlugin
package/src/example.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { Plugin } from "./index.js"
2
+ import { tool } from "./tool.js"
3
+
4
+ export const ExamplePlugin: Plugin = async (_ctx) => {
5
+ return {
6
+ tool: {
7
+ mytool: tool({
8
+ description: "This is a custom tool",
9
+ args: {
10
+ foo: tool.schema.string().describe("foo"),
11
+ },
12
+ async execute(args) {
13
+ return `Hello ${args.foo}!`
14
+ },
15
+ }),
16
+ },
17
+ }
18
+ }
package/src/index.ts ADDED
@@ -0,0 +1,333 @@
1
+ import type {
2
+ Event,
3
+ createSaeeolClient,
4
+ Project,
5
+ Model,
6
+ Provider,
7
+ Permission,
8
+ UserMessage,
9
+ Message,
10
+ Part,
11
+ Auth,
12
+ Config as SDKConfig,
13
+ } from "@saeeol/sdk"
14
+ import type { Provider as ProviderV2, Model as ModelV2 } from "@saeeol/sdk/v2"
15
+
16
+ import type { BunShell } from "./shell.js"
17
+ import { type ToolDefinition } from "./tool.js"
18
+
19
+ export * from "./tool.js"
20
+
21
+ export type ProviderContext = {
22
+ source: "env" | "config" | "custom" | "api"
23
+ info: Provider
24
+ options: Record<string, any>
25
+ }
26
+
27
+ export type WorkspaceInfo = {
28
+ id: string
29
+ type: string
30
+ name: string
31
+ branch: string | null
32
+ directory: string | null
33
+ extra: unknown | null
34
+ projectID: string
35
+ }
36
+
37
+ export type WorkspaceTarget =
38
+ | {
39
+ type: "local"
40
+ directory: string
41
+ }
42
+ | {
43
+ type: "remote"
44
+ url: string | URL
45
+ headers?: HeadersInit
46
+ }
47
+
48
+ export type WorkspaceAdapter = {
49
+ name: string
50
+ description: string
51
+ configure(config: WorkspaceInfo): WorkspaceInfo | Promise<WorkspaceInfo>
52
+ create(config: WorkspaceInfo, env: Record<string, string | undefined>, from?: WorkspaceInfo): Promise<void>
53
+ remove(config: WorkspaceInfo): Promise<void>
54
+ target(config: WorkspaceInfo): WorkspaceTarget | Promise<WorkspaceTarget>
55
+ }
56
+
57
+ export type PluginInput = {
58
+ client: ReturnType<typeof createSaeeolClient>
59
+ project: Project
60
+ directory: string
61
+ worktree: string
62
+ experimental_workspace: {
63
+ register(type: string, adapter: WorkspaceAdapter): void
64
+ }
65
+ serverUrl: URL
66
+ $: BunShell
67
+ }
68
+
69
+ export type PluginOptions = Record<string, unknown>
70
+
71
+ export type Config = Omit<SDKConfig, "plugin"> & {
72
+ plugin?: Array<string | [string, PluginOptions]>
73
+ }
74
+
75
+ export type Plugin = (input: PluginInput, options?: PluginOptions) => Promise<Hooks>
76
+
77
+ export type PluginModule = {
78
+ id?: string
79
+ server: Plugin
80
+ tui?: never
81
+ }
82
+
83
+ type Rule = {
84
+ key: string
85
+ op: "eq" | "neq"
86
+ value: string
87
+ }
88
+
89
+ export type AuthHook = {
90
+ provider: string
91
+ loader?: (auth: () => Promise<Auth>, provider: Provider) => Promise<Record<string, any>>
92
+ methods: (
93
+ | {
94
+ type: "oauth"
95
+ label: string
96
+ prompts?: Array<
97
+ | {
98
+ type: "text"
99
+ key: string
100
+ message: string
101
+ placeholder?: string
102
+ validate?: (value: string) => string | undefined
103
+ /** @deprecated Use `when` instead */
104
+ condition?: (inputs: Record<string, string>) => boolean
105
+ when?: Rule
106
+ }
107
+ | {
108
+ type: "select"
109
+ key: string
110
+ message: string
111
+ options: Array<{
112
+ label: string
113
+ value: string
114
+ hint?: string
115
+ }>
116
+ /** @deprecated Use `when` instead */
117
+ condition?: (inputs: Record<string, string>) => boolean
118
+ when?: Rule
119
+ }
120
+ >
121
+ authorize(inputs?: Record<string, string>): Promise<AuthOAuthResult>
122
+ }
123
+ | {
124
+ type: "api"
125
+ label: string
126
+ prompts?: Array<
127
+ | {
128
+ type: "text"
129
+ key: string
130
+ message: string
131
+ placeholder?: string
132
+ validate?: (value: string) => string | undefined
133
+ /** @deprecated Use `when` instead */
134
+ condition?: (inputs: Record<string, string>) => boolean
135
+ when?: Rule
136
+ }
137
+ | {
138
+ type: "select"
139
+ key: string
140
+ message: string
141
+ options: Array<{
142
+ label: string
143
+ value: string
144
+ hint?: string
145
+ }>
146
+ /** @deprecated Use `when` instead */
147
+ condition?: (inputs: Record<string, string>) => boolean
148
+ when?: Rule
149
+ }
150
+ >
151
+ authorize?(inputs?: Record<string, string>): Promise<
152
+ | {
153
+ type: "success"
154
+ key: string
155
+ provider?: string
156
+ }
157
+ | {
158
+ type: "failed"
159
+ }
160
+ >
161
+ }
162
+ )[]
163
+ }
164
+
165
+ export type AuthOAuthResult = { url: string; instructions: string } & (
166
+ | {
167
+ method: "auto"
168
+ callback(): Promise<
169
+ | ({
170
+ type: "success"
171
+ provider?: string
172
+ } & (
173
+ | {
174
+ refresh: string
175
+ access: string
176
+ expires: number
177
+ accountId?: string
178
+ enterpriseUrl?: string
179
+ }
180
+ | { key: string }
181
+ ))
182
+ | {
183
+ type: "failed"
184
+ }
185
+ >
186
+ }
187
+ | {
188
+ method: "code"
189
+ callback(code: string): Promise<
190
+ | ({
191
+ type: "success"
192
+ provider?: string
193
+ } & (
194
+ | {
195
+ refresh: string
196
+ access: string
197
+ expires: number
198
+ accountId?: string
199
+ enterpriseUrl?: string
200
+ }
201
+ | { key: string }
202
+ ))
203
+ | {
204
+ type: "failed"
205
+ }
206
+ >
207
+ }
208
+ )
209
+
210
+ export type ProviderHookContext = {
211
+ auth?: Auth
212
+ }
213
+
214
+ export type ProviderHook = {
215
+ id: string
216
+ models?: (provider: ProviderV2, ctx: ProviderHookContext) => Promise<Record<string, ModelV2>>
217
+ }
218
+
219
+ /** @deprecated Use AuthOAuthResult instead. */
220
+ export type AuthOuathResult = AuthOAuthResult
221
+
222
+ export interface Hooks {
223
+ event?: (input: { event: Event }) => Promise<void>
224
+ config?: (input: Config) => Promise<void>
225
+ tool?: {
226
+ [key: string]: ToolDefinition
227
+ }
228
+ auth?: AuthHook
229
+ provider?: ProviderHook
230
+ /**
231
+ * Called when a new message is received
232
+ */
233
+ "chat.message"?: (
234
+ input: {
235
+ sessionID: string
236
+ agent?: string
237
+ model?: { providerID: string; modelID: string }
238
+ messageID?: string
239
+ variant?: string
240
+ },
241
+ output: { message: UserMessage; parts: Part[] },
242
+ ) => Promise<void>
243
+ /**
244
+ * Modify parameters sent to LLM
245
+ */
246
+ "chat.params"?: (
247
+ input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
248
+ output: {
249
+ temperature: number
250
+ topP: number
251
+ topK: number
252
+ maxOutputTokens: number | undefined
253
+ options: Record<string, any>
254
+ },
255
+ ) => Promise<void>
256
+ "chat.headers"?: (
257
+ input: { sessionID: string; agent: string; model: Model; provider: ProviderContext; message: UserMessage },
258
+ output: { headers: Record<string, string> },
259
+ ) => Promise<void>
260
+ "permission.ask"?: (input: Permission, output: { status: "ask" | "deny" | "allow" }) => Promise<void>
261
+ "command.execute.before"?: (
262
+ input: { command: string; sessionID: string; arguments: string },
263
+ output: { parts: Part[] },
264
+ ) => Promise<void>
265
+ "tool.execute.before"?: (
266
+ input: { tool: string; sessionID: string; callID: string },
267
+ output: { args: any },
268
+ ) => Promise<void>
269
+ "shell.env"?: (
270
+ input: { cwd: string; sessionID?: string; callID?: string },
271
+ output: { env: Record<string, string> },
272
+ ) => Promise<void>
273
+ "tool.execute.after"?: (
274
+ input: { tool: string; sessionID: string; callID: string; args: any },
275
+ output: {
276
+ title: string
277
+ output: string
278
+ metadata: any
279
+ },
280
+ ) => Promise<void>
281
+ "experimental.chat.messages.transform"?: (
282
+ input: {},
283
+ output: {
284
+ messages: {
285
+ info: Message
286
+ parts: Part[]
287
+ }[]
288
+ },
289
+ ) => Promise<void>
290
+ "experimental.chat.system.transform"?: (
291
+ input: { sessionID?: string; model: Model },
292
+ output: {
293
+ system: string[]
294
+ },
295
+ ) => Promise<void>
296
+ /**
297
+ * Called before session compaction starts. Allows plugins to customize
298
+ * the compaction prompt.
299
+ *
300
+ * - `context`: Additional context strings appended to the default prompt
301
+ * - `prompt`: If set, replaces the default compaction prompt entirely
302
+ */
303
+ "experimental.session.compacting"?: (
304
+ input: { sessionID: string },
305
+ output: { context: string[]; prompt?: string },
306
+ ) => Promise<void>
307
+ /**
308
+ * Called after compaction succeeds and before a synthetic user
309
+ * auto-continue message is added.
310
+ *
311
+ * - `enabled`: Defaults to `true`. Set to `false` to skip the synthetic
312
+ * user "continue" turn.
313
+ */
314
+ "experimental.compaction.autocontinue"?: (
315
+ input: {
316
+ sessionID: string
317
+ agent: string
318
+ model: Model
319
+ provider: ProviderContext
320
+ message: UserMessage
321
+ overflow: boolean
322
+ },
323
+ output: { enabled: boolean },
324
+ ) => Promise<void>
325
+ "experimental.text.complete"?: (
326
+ input: { sessionID: string; messageID: string; partID: string },
327
+ output: { text: string },
328
+ ) => Promise<void>
329
+ /**
330
+ * Modify tool definitions (description and parameters) sent to LLM
331
+ */
332
+ "tool.definition"?: (input: { toolID: string }, output: { description: string; parameters: any }) => Promise<void>
333
+ }
package/src/shell.ts ADDED
@@ -0,0 +1,136 @@
1
+ export type ShellFunction = (input: Uint8Array) => Uint8Array
2
+
3
+ export type ShellExpression =
4
+ | { toString(): string }
5
+ | Array<ShellExpression>
6
+ | string
7
+ | { raw: string }
8
+ | ReadableStream
9
+
10
+ export interface BunShell {
11
+ (strings: TemplateStringsArray, ...expressions: ShellExpression[]): BunShellPromise
12
+
13
+ /**
14
+ * Perform bash-like brace expansion on the given pattern.
15
+ * @param pattern - Brace pattern to expand
16
+ */
17
+ braces(pattern: string): string[]
18
+
19
+ /**
20
+ * Escape strings for input into shell commands.
21
+ */
22
+ escape(input: string): string
23
+
24
+ /**
25
+ * Change the default environment variables for shells created by this instance.
26
+ */
27
+ env(newEnv?: Record<string, string | undefined>): BunShell
28
+
29
+ /**
30
+ * Default working directory to use for shells created by this instance.
31
+ */
32
+ cwd(newCwd?: string): BunShell
33
+
34
+ /**
35
+ * Configure the shell to not throw an exception on non-zero exit codes.
36
+ */
37
+ nothrow(): BunShell
38
+
39
+ /**
40
+ * Configure whether or not the shell should throw an exception on non-zero exit codes.
41
+ */
42
+ throws(shouldThrow: boolean): BunShell
43
+ }
44
+
45
+ export interface BunShellPromise extends Promise<BunShellOutput> {
46
+ readonly stdin: WritableStream
47
+
48
+ /**
49
+ * Change the current working directory of the shell.
50
+ */
51
+ cwd(newCwd: string): this
52
+
53
+ /**
54
+ * Set environment variables for the shell.
55
+ */
56
+ env(newEnv: Record<string, string> | undefined): this
57
+
58
+ /**
59
+ * By default, the shell will write to the current process's stdout and stderr, as well as buffering that output.
60
+ * This configures the shell to only buffer the output.
61
+ */
62
+ quiet(): this
63
+
64
+ /**
65
+ * Read from stdout as a string, line by line
66
+ * Automatically calls quiet() to disable echoing to stdout.
67
+ */
68
+ lines(): AsyncIterable<string>
69
+
70
+ /**
71
+ * Read from stdout as a string.
72
+ * Automatically calls quiet() to disable echoing to stdout.
73
+ */
74
+ text(encoding?: BufferEncoding): Promise<string>
75
+
76
+ /**
77
+ * Read from stdout as a JSON object
78
+ * Automatically calls quiet()
79
+ */
80
+ json(): Promise<any>
81
+
82
+ /**
83
+ * Read from stdout as an ArrayBuffer
84
+ * Automatically calls quiet()
85
+ */
86
+ arrayBuffer(): Promise<ArrayBuffer>
87
+
88
+ /**
89
+ * Read from stdout as a Blob
90
+ * Automatically calls quiet()
91
+ */
92
+ blob(): Promise<Blob>
93
+
94
+ /**
95
+ * Configure the shell to not throw an exception on non-zero exit codes.
96
+ */
97
+ nothrow(): this
98
+
99
+ /**
100
+ * Configure whether or not the shell should throw an exception on non-zero exit codes.
101
+ */
102
+ throws(shouldThrow: boolean): this
103
+ }
104
+
105
+ export interface BunShellOutput {
106
+ readonly stdout: Buffer
107
+ readonly stderr: Buffer
108
+ readonly exitCode: number
109
+
110
+ /**
111
+ * Read from stdout as a string
112
+ */
113
+ text(encoding?: BufferEncoding): string
114
+
115
+ /**
116
+ * Read from stdout as a JSON object
117
+ */
118
+ json(): any
119
+
120
+ /**
121
+ * Read from stdout as an ArrayBuffer
122
+ */
123
+ arrayBuffer(): ArrayBuffer
124
+
125
+ /**
126
+ * Read from stdout as an Uint8Array
127
+ */
128
+ bytes(): Uint8Array
129
+
130
+ /**
131
+ * Read from stdout as a Blob
132
+ */
133
+ blob(): Blob
134
+ }
135
+
136
+ export type BunShellError = Error & BunShellOutput
package/src/tool.ts ADDED
@@ -0,0 +1,41 @@
1
+ import { z } from "zod"
2
+ import { Effect } from "effect"
3
+
4
+ export type ToolContext = {
5
+ sessionID: string
6
+ messageID: string
7
+ agent: string
8
+ /**
9
+ * Current project directory for this session.
10
+ * Prefer this over process.cwd() when resolving relative paths.
11
+ */
12
+ directory: string
13
+ /**
14
+ * Project worktree root for this session.
15
+ * Useful for generating stable relative paths (e.g. path.relative(worktree, absPath)).
16
+ */
17
+ worktree: string
18
+ abort: AbortSignal
19
+ metadata(input: { title?: string; metadata?: { [key: string]: any } }): void
20
+ ask(input: AskInput): Effect.Effect<void>
21
+ }
22
+
23
+ type AskInput = {
24
+ permission: string
25
+ patterns: string[]
26
+ always: string[]
27
+ metadata: { [key: string]: any }
28
+ }
29
+
30
+ export type ToolResult = string | { output: string; metadata?: { [key: string]: any } }
31
+
32
+ export function tool<Args extends z.ZodRawShape>(input: {
33
+ description: string
34
+ args: Args
35
+ execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<ToolResult>
36
+ }) {
37
+ return input
38
+ }
39
+ tool.schema = z
40
+
41
+ export type ToolDefinition = ReturnType<typeof tool>