xyne-plugin 1.0.2 → 1.2.0
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 +1 -1
- package/src/hooks.ts +242 -5
- package/src/index.ts +2 -2
- package/src/tool.ts +82 -4
package/package.json
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -18,6 +18,115 @@ export interface ModelSpec {
|
|
|
18
18
|
/** LLM message — opaque to plugins, passed through for transformation. */
|
|
19
19
|
export type Message = Record<string, unknown>
|
|
20
20
|
|
|
21
|
+
/** How a provider's API key was resolved. */
|
|
22
|
+
export type CredentialSource = "config" | "env" | "auth"
|
|
23
|
+
|
|
24
|
+
/** Provider metadata exposed to plugin hooks. */
|
|
25
|
+
export interface ProviderInfo {
|
|
26
|
+
id: string
|
|
27
|
+
name: string
|
|
28
|
+
source: CredentialSource
|
|
29
|
+
env: string[]
|
|
30
|
+
options: Record<string, unknown>
|
|
31
|
+
models: Record<string, { name: string; contextLength: number; maxOutputTokens: number; [key: string]: unknown }>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─── Message & Part types for hook signatures ────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/** Token usage breakdown. */
|
|
37
|
+
export interface TokenInfo {
|
|
38
|
+
input: number
|
|
39
|
+
output: number
|
|
40
|
+
cacheRead: number
|
|
41
|
+
cacheWrite: number
|
|
42
|
+
reasoning: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Error descriptor attached to failed messages. */
|
|
46
|
+
export type MessageError =
|
|
47
|
+
| { name: "AuthError"; providerID: string; message: string }
|
|
48
|
+
| { name: "APIError"; message: string; statusCode?: number; isRetryable: boolean }
|
|
49
|
+
| { name: "ContextOverflowError"; message: string }
|
|
50
|
+
| { name: "AbortedError"; message: string }
|
|
51
|
+
| { name: "OutputLengthError" }
|
|
52
|
+
| { name: "StructuredOutputError"; message: string; retries: number }
|
|
53
|
+
| { name: "Unknown"; message: string }
|
|
54
|
+
|
|
55
|
+
/** A message in the session (user or assistant turn). */
|
|
56
|
+
export interface MessageInfo {
|
|
57
|
+
id: string
|
|
58
|
+
sessionID: string
|
|
59
|
+
role: "user" | "assistant"
|
|
60
|
+
created: number
|
|
61
|
+
completed?: number
|
|
62
|
+
parentID?: string
|
|
63
|
+
agentID?: string
|
|
64
|
+
modelID?: string
|
|
65
|
+
providerID?: string
|
|
66
|
+
path?: { cwd: string; root: string }
|
|
67
|
+
finish?: string
|
|
68
|
+
error?: MessageError
|
|
69
|
+
cost?: number
|
|
70
|
+
tokens?: TokenInfo
|
|
71
|
+
requestedModel?: { providerID: string; modelID: string }
|
|
72
|
+
format?: { type: string; [key: string]: unknown }
|
|
73
|
+
structured?: unknown
|
|
74
|
+
summary?: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Tool call state. */
|
|
78
|
+
export type ToolState =
|
|
79
|
+
| { status: "pending" }
|
|
80
|
+
| { status: "running"; input: unknown; startTime: number; title?: string; metadata?: unknown }
|
|
81
|
+
| { status: "completed"; input: unknown; output: string; title: string; metadata: unknown; startTime: number; endTime: number; attachments?: Array<{ type: string; url: string; mime: string }>; compacted?: number }
|
|
82
|
+
| { status: "error"; input: unknown; error: string; startTime: number; endTime: number }
|
|
83
|
+
|
|
84
|
+
/** Discriminated union of all part types. */
|
|
85
|
+
export type PartInfo =
|
|
86
|
+
| { type: "text"; id: string; messageID: string; sessionID: string; text: string; startTime: number; endTime?: number; metadata?: Record<string, unknown>; synthetic?: boolean; ignored?: boolean }
|
|
87
|
+
| { type: "reasoning"; id: string; messageID: string; sessionID: string; text: string; startTime: number; endTime?: number; metadata?: Record<string, unknown> }
|
|
88
|
+
| { type: "tool"; id: string; messageID: string; sessionID: string; callID: string; tool: string; state: ToolState }
|
|
89
|
+
| { type: "step-start"; id: string; messageID: string; sessionID: string }
|
|
90
|
+
| { type: "step-finish"; id: string; messageID: string; sessionID: string; reason: string; tokens?: TokenInfo; cost?: number }
|
|
91
|
+
| { type: "retry"; id: string; messageID: string; sessionID: string; attempt: number; error: MessageError; time: { created: number } }
|
|
92
|
+
| { type: "compaction"; id: string; messageID: string; sessionID: string; auto: boolean; overflow?: boolean }
|
|
93
|
+
| { type: "subtask"; id: string; messageID: string; sessionID: string; prompt: string; description: string; agent: string; model?: { providerID: string; modelID: string }; command?: string }
|
|
94
|
+
| { type: "patch"; id: string; messageID: string; sessionID: string; hash: string; files: string[] }
|
|
95
|
+
| { type: "media"; id: string; messageID: string; sessionID: string; mime: string; filename?: string; url: string }
|
|
96
|
+
| { type: "agent"; id: string; messageID: string; sessionID: string; name: string }
|
|
97
|
+
|
|
98
|
+
// ─── Plugin agent types ─────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Simplified agent definition for plugins.
|
|
102
|
+
*
|
|
103
|
+
* Plugin agents follow the same Agent.Info shape but use a reduced surface:
|
|
104
|
+
* - `name` is derived from the record key
|
|
105
|
+
* - `tools` is a record of ToolDefinitions that are converted to Tool.Info[] at registration time
|
|
106
|
+
* - `permission` accepts a simple string map instead of a raw Permission.Ruleset
|
|
107
|
+
*/
|
|
108
|
+
export interface PluginAgentDef {
|
|
109
|
+
description?: string
|
|
110
|
+
mode?: "primary" | "subagent" | "all"
|
|
111
|
+
system?: string
|
|
112
|
+
/** Model override as "providerID/modelID" string or structured spec. */
|
|
113
|
+
model?: string | { providerID: string; modelID: string }
|
|
114
|
+
/** Tools scoped to this agent. These are added on top of session tools. */
|
|
115
|
+
tools?: Record<string, ToolDefinition>
|
|
116
|
+
/**
|
|
117
|
+
* If true, this agent ONLY has access to its own `tools` (built-in session tools are excluded).
|
|
118
|
+
* If false (default), the agent's tools are merged with the session's tool set.
|
|
119
|
+
*/
|
|
120
|
+
isolatedTools?: boolean
|
|
121
|
+
/** Permission overrides. Keys are tool IDs or "*", values are "allow" | "deny" | "ask". */
|
|
122
|
+
permission?: Record<string, "allow" | "deny" | "ask" | Record<string, "allow" | "deny" | "ask">>
|
|
123
|
+
maxSteps?: number
|
|
124
|
+
color?: string
|
|
125
|
+
hidden?: boolean
|
|
126
|
+
temperature?: number
|
|
127
|
+
topP?: number
|
|
128
|
+
}
|
|
129
|
+
|
|
21
130
|
// ─── Plugin function types ───────────────────────────────────────────────────
|
|
22
131
|
|
|
23
132
|
export type PluginInput = {
|
|
@@ -26,10 +135,110 @@ export type PluginInput = {
|
|
|
26
135
|
appName: string
|
|
27
136
|
/** SDK client — available for plugins that need session/message/tool APIs. */
|
|
28
137
|
client?: unknown
|
|
138
|
+
/** Base URL for the local server (if running). */
|
|
139
|
+
serverUrl?: URL
|
|
140
|
+
/** Bun shell for running commands. */
|
|
141
|
+
$?: unknown
|
|
29
142
|
}
|
|
30
143
|
|
|
31
144
|
export type Plugin = (input: PluginInput) => Promise<Hooks>
|
|
32
145
|
|
|
146
|
+
// ─── Auth types ─────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
export type AuthPrompt =
|
|
149
|
+
| {
|
|
150
|
+
type: "text"
|
|
151
|
+
key: string
|
|
152
|
+
message: string
|
|
153
|
+
placeholder?: string
|
|
154
|
+
validate?: (value: string) => string | undefined
|
|
155
|
+
condition?: (inputs: Record<string, string>) => boolean
|
|
156
|
+
}
|
|
157
|
+
| {
|
|
158
|
+
type: "select"
|
|
159
|
+
key: string
|
|
160
|
+
message: string
|
|
161
|
+
options: Array<{ label: string; value: string; hint?: string }>
|
|
162
|
+
condition?: (inputs: Record<string, string>) => boolean
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type AuthOAuthResult = { url: string; instructions: string } & (
|
|
166
|
+
| {
|
|
167
|
+
method: "auto"
|
|
168
|
+
callback(): Promise<
|
|
169
|
+
| ({ type: "success"; provider?: string } & (
|
|
170
|
+
| { refresh: string; access: string; expires: number; accountId?: string }
|
|
171
|
+
| { key: string }
|
|
172
|
+
))
|
|
173
|
+
| { type: "failed" }
|
|
174
|
+
>
|
|
175
|
+
}
|
|
176
|
+
| {
|
|
177
|
+
method: "code"
|
|
178
|
+
callback(code: string): Promise<
|
|
179
|
+
| ({ type: "success"; provider?: string } & (
|
|
180
|
+
| { refresh: string; access: string; expires: number; accountId?: string }
|
|
181
|
+
| { key: string }
|
|
182
|
+
))
|
|
183
|
+
| { type: "failed" }
|
|
184
|
+
>
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
export type AuthMethod =
|
|
189
|
+
| {
|
|
190
|
+
type: "oauth"
|
|
191
|
+
label: string
|
|
192
|
+
prompts?: AuthPrompt[]
|
|
193
|
+
authorize(inputs?: Record<string, string>): Promise<AuthOAuthResult>
|
|
194
|
+
}
|
|
195
|
+
| {
|
|
196
|
+
type: "api"
|
|
197
|
+
label: string
|
|
198
|
+
prompts?: AuthPrompt[]
|
|
199
|
+
authorize?(inputs?: Record<string, string>): Promise<
|
|
200
|
+
| { type: "success"; key: string; provider?: string }
|
|
201
|
+
| { type: "failed" }
|
|
202
|
+
>
|
|
203
|
+
}
|
|
204
|
+
| {
|
|
205
|
+
type: "custom"
|
|
206
|
+
label: string
|
|
207
|
+
/** Message shown in the TUI while authorize() runs (default: "Connecting..."). */
|
|
208
|
+
pendingMessage?: string
|
|
209
|
+
/** Hint shown below the pending message. */
|
|
210
|
+
pendingHint?: string
|
|
211
|
+
prompts?: AuthPrompt[]
|
|
212
|
+
/** Plugin handles the entire auth flow. UI shows pendingMessage while this runs. */
|
|
213
|
+
authorize(inputs?: Record<string, string>): Promise<
|
|
214
|
+
| { type: "success"; data: Record<string, unknown> }
|
|
215
|
+
| { type: "failed" }
|
|
216
|
+
>
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Options returned by an auth loader to configure the provider SDK.
|
|
221
|
+
*/
|
|
222
|
+
export interface AuthLoaderResult {
|
|
223
|
+
baseURL?: string
|
|
224
|
+
apiKey?: string
|
|
225
|
+
fetch?: typeof globalThis.fetch
|
|
226
|
+
/** Custom model selector — receives the SDK instance, model ID, and options. */
|
|
227
|
+
getModel?: (sdk: unknown, modelID: string, options: Record<string, unknown>) => Promise<unknown>
|
|
228
|
+
[key: string]: unknown
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export type AuthHook = {
|
|
232
|
+
provider: string
|
|
233
|
+
/**
|
|
234
|
+
* Called after auth succeeds to configure the provider SDK.
|
|
235
|
+
* Receives a getter for the auth entry and the provider info.
|
|
236
|
+
* Returns SDK options (baseURL, apiKey, fetch, getModel, etc.)
|
|
237
|
+
*/
|
|
238
|
+
loader?: (auth: () => Promise<unknown>, provider: unknown) => Promise<AuthLoaderResult>
|
|
239
|
+
methods: AuthMethod[]
|
|
240
|
+
}
|
|
241
|
+
|
|
33
242
|
// ─── Hook definitions ────────────────────────────────────────────────────────
|
|
34
243
|
|
|
35
244
|
export interface Hooks {
|
|
@@ -42,18 +251,34 @@ export interface Hooks {
|
|
|
42
251
|
/** Register custom tools. Keyed by tool ID. */
|
|
43
252
|
tool?: Record<string, ToolDefinition>
|
|
44
253
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
254
|
+
/** Register custom agents. Keyed by agent name. */
|
|
255
|
+
agent?: Record<string, PluginAgentDef>
|
|
256
|
+
|
|
257
|
+
/** Auth provider — OAuth, API key, or custom auth flows. */
|
|
258
|
+
auth?: AuthHook
|
|
259
|
+
|
|
260
|
+
/** Called when a new message is created (user or assistant). */
|
|
261
|
+
"chat.message"?: (
|
|
262
|
+
input: {
|
|
263
|
+
sessionID: string
|
|
264
|
+
messageID: string
|
|
265
|
+
role: string
|
|
266
|
+
agent?: string
|
|
267
|
+
model?: { providerID: string; modelID: string }
|
|
268
|
+
variant?: string
|
|
269
|
+
},
|
|
270
|
+
output: { message: MessageInfo; parts: PartInfo[] },
|
|
271
|
+
) => Promise<void>
|
|
47
272
|
|
|
48
273
|
/** Mutate LLM sampling parameters before a chat request. */
|
|
49
274
|
"chat.params"?: (
|
|
50
|
-
input: { sessionID: string; agent: string; model: ModelSpec },
|
|
51
|
-
output: { temperature?: number; topP?: number; topK?: number },
|
|
275
|
+
input: { sessionID: string; agent: string; model: ModelSpec; provider?: ProviderInfo; message?: MessageInfo },
|
|
276
|
+
output: { temperature?: number; topP?: number; topK?: number; options?: Record<string, unknown> },
|
|
52
277
|
) => Promise<void>
|
|
53
278
|
|
|
54
279
|
/** Inject extra HTTP headers into the provider request. */
|
|
55
280
|
"chat.headers"?: (
|
|
56
|
-
input: { sessionID: string; agent: string; model: ModelSpec },
|
|
281
|
+
input: { sessionID: string; agent: string; model: ModelSpec; provider?: ProviderInfo; message?: MessageInfo },
|
|
57
282
|
output: { headers: Record<string, string> },
|
|
58
283
|
) => Promise<void>
|
|
59
284
|
|
|
@@ -75,6 +300,12 @@ export interface Hooks {
|
|
|
75
300
|
output: { status: "ask" | "deny" | "allow" },
|
|
76
301
|
) => Promise<void>
|
|
77
302
|
|
|
303
|
+
/** Called before a slash command executes. */
|
|
304
|
+
"command.execute.before"?: (
|
|
305
|
+
input: { command: string; sessionID: string; arguments: string },
|
|
306
|
+
output: { parts: PartInfo[] },
|
|
307
|
+
) => Promise<void>
|
|
308
|
+
|
|
78
309
|
/** Modify tool args before execution. */
|
|
79
310
|
"tool.execute.before"?: (
|
|
80
311
|
input: { tool: string; sessionID: string; callID: string },
|
|
@@ -104,4 +335,10 @@ export interface Hooks {
|
|
|
104
335
|
input: { sessionID: string },
|
|
105
336
|
output: { context: string[]; prompt?: string },
|
|
106
337
|
) => Promise<void>
|
|
338
|
+
|
|
339
|
+
/** Text completion hook — modify or provide text completions. */
|
|
340
|
+
"text.complete"?: (
|
|
341
|
+
input: { text: string; cursorOffset: number },
|
|
342
|
+
output: { completions: string[] },
|
|
343
|
+
) => Promise<void>
|
|
107
344
|
}
|
package/src/index.ts
CHANGED
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
// Plugin types
|
|
25
|
-
export type { Plugin, PluginInput, Hooks } from "./hooks"
|
|
25
|
+
export type { Plugin, PluginInput, Hooks, PluginAgentDef, AuthHook, AuthMethod, AuthPrompt, AuthOAuthResult, AuthLoaderResult, ModelSpec, ProviderInfo, CredentialSource, MessageInfo, PartInfo, ToolState, TokenInfo, MessageError } from "./hooks"
|
|
26
26
|
|
|
27
27
|
// Tool authoring
|
|
28
28
|
export { tool } from "./tool"
|
|
29
|
-
export type { ToolDefinition, ToolContext } from "./tool"
|
|
29
|
+
export type { ToolDefinition, ToolContext, ToolAuthConfig, ServiceCredentials, ToolAttachment, PluginToolResult } from "./tool"
|
|
30
30
|
|
|
31
31
|
// Zod re-export for convenience
|
|
32
32
|
export { z } from "zod"
|
package/src/tool.ts
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
import { z } from "zod"
|
|
2
|
+
import type { AuthMethod } from "./hooks"
|
|
3
|
+
|
|
4
|
+
// ─── Service credentials ────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
/** Resolved service credentials injected into tool context. */
|
|
7
|
+
export type ServiceCredentials =
|
|
8
|
+
| { type: "oauth"; access: string; refresh: string; expires: number }
|
|
9
|
+
| { type: "api"; key: string }
|
|
10
|
+
| { type: "custom"; data: Record<string, unknown> }
|
|
11
|
+
|
|
12
|
+
// ─── Tool auth config ───────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Declares that a tool requires authentication with an external service.
|
|
16
|
+
*
|
|
17
|
+
* When a tool with `auth` is executed:
|
|
18
|
+
* 1. The system checks stored credentials for `serviceID`
|
|
19
|
+
* 2. If found and not expired → credentials injected into `ctx.credentials`
|
|
20
|
+
* 3. If expired → `refresh` callback is tried first (silent refresh)
|
|
21
|
+
* 4. If missing or refresh fails → auth flow triggered via bus event, execution blocks
|
|
22
|
+
* until user completes auth, then resumes with credentials
|
|
23
|
+
*/
|
|
24
|
+
export interface ToolAuthConfig {
|
|
25
|
+
/** Unique service identifier used as the storage key (e.g. "google-gmail", "jira"). */
|
|
26
|
+
serviceID: string
|
|
27
|
+
/** Human-readable label shown in auth prompts (e.g. "Google Gmail"). */
|
|
28
|
+
label: string
|
|
29
|
+
/** Auth methods — reuses the same AuthMethod type as provider auth (OAuth, API key, custom). */
|
|
30
|
+
methods: AuthMethod[]
|
|
31
|
+
/** Optional refresh callback — called when stored OAuth token is expired.
|
|
32
|
+
* Receives the current credentials, returns refreshed credentials.
|
|
33
|
+
* If this throws, falls through to full re-auth flow. */
|
|
34
|
+
refresh?: (entry: ServiceCredentials) => Promise<ServiceCredentials>
|
|
35
|
+
/** Optional custom credential storage. Defaults to auth.json (0o600 permissions).
|
|
36
|
+
* Plugins can provide their own storage (e.g. system keychain, encrypted vault). */
|
|
37
|
+
storage?: {
|
|
38
|
+
get(): Promise<ServiceCredentials | undefined>
|
|
39
|
+
set(entry: ServiceCredentials): Promise<void>
|
|
40
|
+
remove(): Promise<void>
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Tool context ───────────────────────────────────────────────────────────
|
|
2
45
|
|
|
3
46
|
/**
|
|
4
47
|
* Execution context passed to every custom tool's execute function.
|
|
@@ -20,6 +63,19 @@ export type ToolContext = {
|
|
|
20
63
|
abort: AbortSignal
|
|
21
64
|
metadata(input: { title?: string; metadata?: { [key: string]: unknown } }): void
|
|
22
65
|
ask(input: AskInput): Promise<void>
|
|
66
|
+
/**
|
|
67
|
+
* Service credentials for this tool's declared `auth` service.
|
|
68
|
+
* Populated automatically before execute() when the tool has a `ToolAuthConfig`
|
|
69
|
+
* and the user has authenticated. Undefined if no auth is declared.
|
|
70
|
+
*/
|
|
71
|
+
credentials?: ServiceCredentials
|
|
72
|
+
/**
|
|
73
|
+
* Clear cached credentials and re-trigger the auth flow.
|
|
74
|
+
* Call this when the external API returns 401/403 (token revoked).
|
|
75
|
+
* Returns fresh credentials after the user re-authenticates.
|
|
76
|
+
* Only available on tools that declare `auth`.
|
|
77
|
+
*/
|
|
78
|
+
reauth?: () => Promise<ServiceCredentials>
|
|
23
79
|
}
|
|
24
80
|
|
|
25
81
|
type AskInput = {
|
|
@@ -29,6 +85,28 @@ type AskInput = {
|
|
|
29
85
|
metadata: { [key: string]: unknown }
|
|
30
86
|
}
|
|
31
87
|
|
|
88
|
+
// ─── Tool definition ────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
export interface ToolAttachment {
|
|
91
|
+
type: "file"
|
|
92
|
+
mime: string
|
|
93
|
+
/** Data URL (data:mime;base64,...) or file:// URL */
|
|
94
|
+
url: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type PluginToolResult = string | { text: string; attachments: ToolAttachment[] }
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The wide storage type used in Hooks.tool and internal registries.
|
|
101
|
+
* Plugin authors don't construct this directly — use the `tool()` factory.
|
|
102
|
+
*/
|
|
103
|
+
export interface ToolDefinition {
|
|
104
|
+
description: string
|
|
105
|
+
args: Record<string, z.ZodType>
|
|
106
|
+
auth?: ToolAuthConfig
|
|
107
|
+
execute(args: Record<string, unknown>, context: ToolContext): Promise<PluginToolResult>
|
|
108
|
+
}
|
|
109
|
+
|
|
32
110
|
/**
|
|
33
111
|
* Define a custom tool. Use this in plugin packages or local tool files.
|
|
34
112
|
*
|
|
@@ -50,11 +128,11 @@ type AskInput = {
|
|
|
50
128
|
export function tool<Args extends z.ZodRawShape>(input: {
|
|
51
129
|
description: string
|
|
52
130
|
args: Args
|
|
53
|
-
|
|
54
|
-
|
|
131
|
+
/** Optional auth requirement — tool execution will pause for auth if credentials are missing. */
|
|
132
|
+
auth?: ToolAuthConfig
|
|
133
|
+
execute(args: z.infer<z.ZodObject<Args>>, context: ToolContext): Promise<PluginToolResult>
|
|
134
|
+
}): ToolDefinition {
|
|
55
135
|
return input
|
|
56
136
|
}
|
|
57
137
|
|
|
58
138
|
tool.schema = z
|
|
59
|
-
|
|
60
|
-
export type ToolDefinition = ReturnType<typeof tool>
|