@alisio/sdk 0.1.0-alpha.10

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gustavo Gutiérrez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # @alisio/sdk
2
+
3
+ **The typed plugin contract for [Alisio](https://github.com/GustavoGutierrez/alisio).** Types plus
4
+ two tiny helpers (`definePlugin`, `textResult`) — no runtime dependencies and no provider SDKs.
5
+
6
+ ## What it is
7
+
8
+ `@alisio/sdk` is what plugins depend on. It declares the `Plugin` and `PluginAPI` shapes, the tool
9
+ and provider contracts, compaction and session hooks, the SQLite storage port and the extension
10
+ points. A plugin ships JavaScript, declares the `alisio-plugin` npm keyword and lists
11
+ `@alisio/sdk` as a peer dependency. Because the package is types-only plus helpers, it is safe to
12
+ import from any runtime Alisio runs on.
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ npm i @alisio/sdk # peer dependency of every plugin; use it in devDependencies too
18
+ ```
19
+
20
+ ## Quick start: your first plugin
21
+
22
+ ```ts
23
+ import { definePlugin, textResult } from "@alisio/sdk";
24
+
25
+ export default definePlugin({
26
+ id: "acme.hello",
27
+ version: "0.1.0",
28
+ apiVersion: 1,
29
+ setup(api) {
30
+ api.tools.register({
31
+ name: "hello",
32
+ description: "Greets the user.",
33
+ effect: "read",
34
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
35
+ async execute() {
36
+ return textResult("Hello!");
37
+ },
38
+ });
39
+ },
40
+ });
41
+ ```
42
+
43
+ Load it with `alisio --plugin ./dist/index.js` (path) or `alisio install npm:acme-hello` (npm
44
+ package). See [Writing plugins](https://gustavogutierrez.github.io/alisio/plugins) for the full
45
+ walkthrough including tool permissions, naming/prefix rules and an example package.
46
+
47
+ ## The PluginAPI
48
+
49
+ Everything a plugin registers is removed automatically when it unloads; every `register`/`on`
50
+ returns an unregister function.
51
+
52
+ - **tools** — `tools.register(ToolDefinition)`; effects `read | write | process | external |
53
+ internal`, optional `paths()` for write checks, `concurrent` for same-turn parallel calls.
54
+ - **commands** — `commands.register(name, handler, { description?, argumentHint? })`, invoked as
55
+ `/command plugin.id:name args`.
56
+ - **events** — versioned `RunEvent`s (`schemaVersion: 1`).
57
+ - **context** — `context.register(() => Promise<string>)` adds text to the model context.
58
+ - **resources** — `skills(path)`, `prompts(path)`, `agents(path)` and `list(kind)` for skill,
59
+ prompt-template and agent-definition directories.
60
+ - **state** — small per-plugin JSON state persisted in the session database.
61
+ - **compaction** — `compaction.register({ beforeCompact, afterCompact })` contributes instructions,
62
+ extra summarizer output fields, recalled context and reports.
63
+ - **session** — `session.onStart(info)` (text injected on a fresh session) and
64
+ `session.onEnd(info)` (called on `/clear`, `/exit`, quit; bounded by a timeout).
65
+ - **model** — provider-agnostic `model.complete(request)`; plugins never import provider SDKs.
66
+ - **models** — credential-free `models.list()` / `models.resolve(reference)` over configured
67
+ `/connect` profiles.
68
+ - **providers** — `providers.register(registration)` adds a selectable model provider to `/connect`;
69
+ registrations coexist instead of competing.
70
+ - **storage** — `storage.sqlite(path)` opens a private (0600) SQLite file (FTS5 available); the
71
+ host provides the driver, so plugins never depend on a specific runtime.
72
+ - **sessions** — child sessions (`spawn`, `create`, `run`, `get`, `children`, `cancel`, `enqueue`,
73
+ …): separate conversations with fresh context and narrowed permissions that never exceed the
74
+ parent.
75
+ - **ui** — `status`, `panel`, `select`, `askQuestions`, `open`, `interactive()`; interactive-only
76
+ calls resolve `undefined` headless instead of hanging.
77
+
78
+ ## Extension points
79
+
80
+ Typed, generic extension points let plugins replace parts of the experience without core changes.
81
+ Today: `mascot`, `startup-screen` and `websearch`.
82
+
83
+ ```ts
84
+ api.extensions.register("mascot", {
85
+ id: "kite",
86
+ render: ({ terminal }) => (terminal.unicode ? [" ◢◣", " ◢██◣", " ◥██◤", " ◥◤"] : [" /\\", " / \\", " \\ /", " \\/"]),
87
+ }, { priority: 10 });
88
+ ```
89
+
90
+ The highest `priority` wins; ties break by plugin `id`, then registration order (reported as
91
+ `extension_conflict`). A renderable provider receives only its context (`terminal.color`,
92
+ `unicode`, `columns`); output is sanitized and clamped, and a failing provider falls back to the
93
+ default. `websearch` fully replaces Alisio's built-in search-provider resolution while registered.
94
+ A declarative `extensions` field on the plugin is accepted too, at priority 0. Plugins are
95
+ identified by `id`, not `name`.
96
+
97
+ ## Prompt templates
98
+
99
+ `api.resources.prompts("./prompts")` registers a directory of Markdown templates that become slash
100
+ commands (for example `/review src/app.ts`):
101
+
102
+ ```md
103
+ ---
104
+ description: Review a file for bugs
105
+ argument-hint: <path>
106
+ requires: [write] # optional: write | process
107
+ ---
108
+ Review $1 carefully. Extra focus: $ARGUMENTS
109
+ ```
110
+
111
+ `$ARGUMENTS` is the whole argument string and `$1`..`$9` are shell-like positional arguments.
112
+ Precedence: built-in < plugin < user (`~/.config/alisio/prompts`) < trusted project
113
+ (`.alisio/prompts`).
114
+
115
+ ## Publishing
116
+
117
+ Publish plugins as JavaScript with the `alisio-plugin` keyword and `@alisio/sdk` as a peer
118
+ dependency; the entry resolves from `exports["."]`, then `main`, then `./index.js`. Guide:
119
+ [Writing plugins](https://gustavogutierrez.github.io/alisio/plugins) ·
120
+ [Publishing](https://gustavogutierrez.github.io/alisio/publishing).
121
+
122
+ ## Requirements
123
+
124
+ Node.js **>= 22.16** (types-only package; consumers run on Node or Bun).
125
+
126
+ ## License
127
+
128
+ MIT. Maintained by Gustavo Gutiérrez Mercado. Source: <https://github.com/GustavoGutierrez/alisio> ·
129
+ npm: <https://www.npmjs.com/settings/alisio/packages>.
@@ -0,0 +1,711 @@
1
+ /** Public, runtime-independent contracts. This package has no Bun or provider imports. */
2
+ export type JsonSchema = Record<string, unknown>;
3
+ /**
4
+ * `internal` writes only Alisio-owned state (never the workspace or network). It is always
5
+ * allowed and is honored only for built-in plugins; external plugins are downgraded to `external`.
6
+ */
7
+ export type Effect = "read" | "write" | "process" | "external" | "internal";
8
+ export interface ToolCall {
9
+ id: string;
10
+ name: string;
11
+ arguments: string;
12
+ }
13
+ /** A node of a `{ kind: "tree" }` UI block. */
14
+ export interface TreeNode {
15
+ label: string;
16
+ children?: TreeNode[];
17
+ /** Optional annotation rendered after the label, e.g. a count or a status word. */
18
+ meta?: string;
19
+ }
20
+ /**
21
+ * Alisio-owned structured rendering of a tool result, produced by core adapters (for example
22
+ * the MCP connector) when a tool returns structured data. It never carries MCP protocol types:
23
+ * plugins wanting a structured block build one of these shapes directly. The runner always
24
+ * keeps a plain-text projection alongside a `ui` part, so providers, compaction and headless
25
+ * output only ever see text; the block is a display hint for TUI rendering.
26
+ */
27
+ export type UiBlock = {
28
+ kind: "table";
29
+ columns: string[];
30
+ rows: Array<Array<string>>;
31
+ caption?: string;
32
+ } | {
33
+ kind: "key-value";
34
+ entries: Array<[string, string]>;
35
+ caption?: string;
36
+ } | {
37
+ kind: "tree";
38
+ nodes: Array<TreeNode>;
39
+ } | {
40
+ kind: "code";
41
+ lang?: string;
42
+ code: string;
43
+ caption?: string;
44
+ } | {
45
+ kind: "markdown";
46
+ text: string;
47
+ };
48
+ export interface ToolResult {
49
+ content: Array<{
50
+ type: "text";
51
+ text: string;
52
+ } | {
53
+ type: "image";
54
+ mimeType: string;
55
+ data: string;
56
+ } | {
57
+ type: "ui";
58
+ block: UiBlock;
59
+ }>;
60
+ isError?: boolean;
61
+ }
62
+ /**
63
+ * An image attached to a user message. Persisted verbatim in the session store; sent to the
64
+ * provider as a vision content part alongside the text. Never carried into a compaction summary
65
+ * (only its mime type and dimensions are, as plain text) so raw bytes never reach the model twice.
66
+ */
67
+ export interface Attachment {
68
+ kind: "image";
69
+ mimeType: string;
70
+ /** Base64-encoded bytes, no `data:` prefix. */
71
+ data: string;
72
+ bytes: number;
73
+ width?: number;
74
+ height?: number;
75
+ }
76
+ export type Message =
77
+ /**
78
+ * `summary` marks injected context (e.g. a compaction checkpoint); `display` is what UIs show
79
+ * instead of `text` (e.g. `/init` for an expanded prompt template). Providers ignore both.
80
+ */
81
+ {
82
+ role: "user";
83
+ text: string;
84
+ summary?: boolean;
85
+ display?: string;
86
+ attachments?: Attachment[];
87
+ } | {
88
+ role: "assistant";
89
+ text: string;
90
+ calls: ToolCall[];
91
+ providerData?: unknown[];
92
+ /**
93
+ * The provider reported `finish_reason: "length"` (or an equivalent incomplete-stop signal):
94
+ * the response was cut by the output token budget. Per-answer text and tool calls are
95
+ * complete as far as they went; consumers decide whether to warn or accept a partial result.
96
+ */
97
+ truncated?: boolean;
98
+ } | {
99
+ role: "tool";
100
+ callId: string;
101
+ result: ToolResult;
102
+ };
103
+ export interface Usage {
104
+ input: number;
105
+ output: number;
106
+ /** Input tokens served from a provider prompt cache, when reported. */
107
+ cachedInput?: number;
108
+ }
109
+ export type ProviderEvent = {
110
+ type: "text_delta";
111
+ delta: string;
112
+ }
113
+ /** Provider-visible reasoning text. Display only; never persisted or replayed. */
114
+ | {
115
+ type: "reasoning_delta";
116
+ delta: string;
117
+ } | {
118
+ type: "completed";
119
+ message: Extract<Message, {
120
+ role: "assistant";
121
+ }>;
122
+ usage?: Usage;
123
+ };
124
+ export interface ModelInfo {
125
+ id: string;
126
+ name?: string;
127
+ /** Organization that owns the model, when required by the provider catalog. */
128
+ ownedBy?: string;
129
+ contextWindow?: number;
130
+ maxOutputTokens?: number;
131
+ /** Modalities accepted by the model, as reported by its catalog. */
132
+ inputModalities?: string[];
133
+ /** Modalities produced by the model, as reported by its catalog. */
134
+ outputModalities?: string[];
135
+ /** Provider-declared, per-protocol API metadata. Preserved without flattening. */
136
+ apiCapabilities?: Record<string, JsonValue>;
137
+ /** Provider-declared reasoning effort levels. */
138
+ effort?: {
139
+ supportedLevels: string[];
140
+ defaultLevel?: string;
141
+ };
142
+ /** Legacy combined modality metadata from OpenAI-compatible catalogs. */
143
+ modalities?: string[];
144
+ /** Provider-declared API/capability metadata; informational and never contains credentials. */
145
+ capabilities?: Record<string, boolean | string | number>;
146
+ }
147
+ /** Credential-free model target exposed by the host resolver. */
148
+ export interface AvailableProviderModel {
149
+ /** Canonical selector, always `<provider>/<model>` (the model may itself contain `/`). */
150
+ reference: string;
151
+ provider: string;
152
+ profile: string;
153
+ providerName: string;
154
+ model: ModelInfo;
155
+ }
156
+ /** A validated model target. Credentials remain inside the host. */
157
+ export interface ResolvedProviderModel extends AvailableProviderModel {
158
+ }
159
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
160
+ [key: string]: JsonValue;
161
+ };
162
+ export interface ModelProvider {
163
+ id: string;
164
+ /** Default model; a request may override it for one call. */
165
+ model: string;
166
+ stream(request: {
167
+ instructions: string;
168
+ messages: Message[];
169
+ tools: ToolDefinition[];
170
+ maxOutputTokens: number;
171
+ signal: AbortSignal;
172
+ model?: string;
173
+ /** Opaque persisted conversation id. Providers may use it for cache affinity only. */
174
+ sessionId?: string;
175
+ /**
176
+ * Raw provider-native tool definitions (for example a hosted `web_search` tool), appended to
177
+ * the request's `tools` array verbatim, alongside the function tools built from `tools`. Only
178
+ * meaningful for providers that document an equivalent server-side tool; an implementation
179
+ * that does not support one may ignore this or let the provider reject it.
180
+ */
181
+ nativeTools?: Array<Record<string, unknown>>;
182
+ /**
183
+ * Provider-declared reasoning effort level (a value from `ModelInfo.effort.supportedLevels`).
184
+ * Providers that advertise effort levels map it to their request field (for example DeepSeek
185
+ * `reasoning_effort`); providers without a concept ignore it.
186
+ */
187
+ reasoningEffort?: string;
188
+ }): AsyncIterable<ProviderEvent>;
189
+ /** Optional model catalog. Implementations must not expose credentials. */
190
+ listModels?(signal: AbortSignal): Promise<ModelInfo[]>;
191
+ /** Releases provider-owned clients or transports. */
192
+ dispose?(): void | Promise<void>;
193
+ }
194
+ export type ProviderConfigurationValue = string | boolean | number;
195
+ export interface ProviderConfigurationField {
196
+ key: string;
197
+ label: string;
198
+ kind: "text" | "secret" | "url" | "select" | "boolean";
199
+ required?: boolean;
200
+ description?: string;
201
+ defaultValue?: ProviderConfigurationValue;
202
+ options?: Array<{
203
+ value: string;
204
+ label: string;
205
+ }>;
206
+ }
207
+ export interface ProviderCapabilities {
208
+ nativeWebSearch?: boolean | {
209
+ field: string;
210
+ values: ProviderConfigurationValue[];
211
+ };
212
+ }
213
+ export interface ProviderCreateRequest {
214
+ /** Non-secret, globally persisted profile values. */
215
+ profile: Record<string, ProviderConfigurationValue>;
216
+ /** Secret values loaded from the dedicated credentials store. */
217
+ credentials: Record<string, string>;
218
+ /** Legacy root provider configuration, supplied without rewriting it. */
219
+ legacy?: Record<string, unknown>;
220
+ }
221
+ /** Additive model-provider contribution. Multiple registrations coexist in the host registry. */
222
+ export interface ProviderRegistration {
223
+ id: string;
224
+ name: string;
225
+ description?: string;
226
+ fields: ProviderConfigurationField[];
227
+ capabilities?: ProviderCapabilities;
228
+ create(request: ProviderCreateRequest): ModelProvider | Promise<ModelProvider>;
229
+ }
230
+ export interface ToolContext {
231
+ signal: AbortSignal;
232
+ workspace: string;
233
+ emit: (data: unknown) => void;
234
+ /** Session that issued the call, when run by the agent loop. */
235
+ session?: string;
236
+ /** Who is asking, e.g. an agent path such as "general › explore" (child sessions only). */
237
+ label?: string;
238
+ }
239
+ export interface ToolDefinition {
240
+ name: string;
241
+ description: string;
242
+ inputSchema: JsonSchema;
243
+ /** Unknown/plugin operations default to external; only declare read for side-effect-free tools. */
244
+ effect?: Effect;
245
+ /** May run concurrently with other read/concurrent calls of the same turn (e.g. delegation). */
246
+ concurrent?: boolean;
247
+ paths?: (input: Record<string, unknown>) => string[];
248
+ execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolResult>;
249
+ }
250
+ export interface RunEvent {
251
+ schemaVersion: 1;
252
+ runId: string;
253
+ sessionId: string;
254
+ seq: number;
255
+ type: string;
256
+ timestamp: string;
257
+ data: unknown;
258
+ }
259
+ /** Generic structured checkpoint produced by core context compaction. */
260
+ export interface CompactionCheckpoint {
261
+ goal: string;
262
+ instructions: string[];
263
+ discoveries: string[];
264
+ accomplished: string[];
265
+ currentState: string;
266
+ nextSteps: string[];
267
+ relevantFiles: string[];
268
+ }
269
+ export interface CompactionStart {
270
+ sessionId: string;
271
+ reason: "manual" | "auto";
272
+ /** Messages about to be replaced by the checkpoint. */
273
+ messages: readonly Message[];
274
+ focus?: string;
275
+ signal: AbortSignal;
276
+ }
277
+ export interface CompactionContribution {
278
+ /** Extra guidance appended to the summarizer instructions. */
279
+ instructions?: string;
280
+ /**
281
+ * Extra top-level JSON fields requested in the same summarizer call, as name → description.
282
+ * Values are returned unvalidated in `CompactionResult.extracted`; validate them yourself.
283
+ */
284
+ outputFields?: Record<string, string>;
285
+ }
286
+ export interface CompactionResult extends CompactionStart {
287
+ model: string;
288
+ replaced: number;
289
+ /** False when the summarizer did not return valid JSON (text-only checkpoint). */
290
+ structured: boolean;
291
+ checkpoint?: CompactionCheckpoint;
292
+ checkpointText: string;
293
+ /** This plugin's requested output fields, when present. */
294
+ extracted: Record<string, unknown>;
295
+ }
296
+ export interface CompactionOutcome {
297
+ /** Text appended after the checkpoint in the compacted history (keep it budgeted). */
298
+ injectContext?: string;
299
+ /** Shown to the user; `summary` is displayed verbatim when present. */
300
+ report?: {
301
+ summary?: string;
302
+ } & Record<string, unknown>;
303
+ }
304
+ export interface CompactionHooks {
305
+ beforeCompact?(input: CompactionStart): Promise<CompactionContribution | undefined | void>;
306
+ afterCompact?(input: CompactionResult): Promise<CompactionOutcome | undefined | void>;
307
+ }
308
+ export interface SessionInfo {
309
+ sessionId: string;
310
+ model: string;
311
+ workspace: string;
312
+ reason: "start" | "clear" | "exit";
313
+ messages: readonly Message[];
314
+ /** Aborted when the host timeout for this hook expires. */
315
+ signal: AbortSignal;
316
+ }
317
+ export interface CompletionRequest {
318
+ system: string;
319
+ messages: Array<{
320
+ role: "user" | "assistant";
321
+ text: string;
322
+ }>;
323
+ maxTokens?: number;
324
+ model?: string;
325
+ /** Session whose provider binding should be used when model is omitted. */
326
+ sessionId?: string;
327
+ /** Reasoning effort level when the target model advertises `ModelInfo.effort`. */
328
+ reasoningEffort?: string;
329
+ signal?: AbortSignal;
330
+ }
331
+ export type SqlValue = string | number | bigint | null | Uint8Array;
332
+ /** Row objects are plain; column types depend on the query, so they are typed loosely. */
333
+ export type SqlRow = Record<string, any>;
334
+ export interface SqlStatement {
335
+ run(...params: SqlValue[]): {
336
+ changes: number | bigint;
337
+ lastInsertRowid: number | bigint;
338
+ };
339
+ /** First row as a plain object, or undefined. */
340
+ get(...params: SqlValue[]): SqlRow | undefined;
341
+ all(...params: SqlValue[]): SqlRow[];
342
+ }
343
+ /**
344
+ * Storage port: a synchronous SQLite database (FTS5 available) provided by the host, so plugins
345
+ * never depend on a specific runtime driver.
346
+ */
347
+ export interface SqlDatabase {
348
+ exec(sql: string): void;
349
+ /** Prepared statements are cached per SQL text. */
350
+ prepare(sql: string): SqlStatement;
351
+ /** Runs `fn` in an immediate transaction (nested calls join the outer one). */
352
+ transaction<T>(fn: () => T): T;
353
+ close(): void;
354
+ }
355
+ /** What the host knows about the terminal; providers must honor it (no globals, env or fs). */
356
+ export interface TerminalCapabilities {
357
+ /** ANSI SGR colors allowed. When false, output must be plain text. */
358
+ color: boolean;
359
+ /** Non-ASCII glyphs allowed. When false, output must be ASCII. */
360
+ unicode: boolean;
361
+ columns: number;
362
+ interactive: boolean;
363
+ }
364
+ export interface MascotContext {
365
+ terminal: TerminalCapabilities;
366
+ version: string;
367
+ }
368
+ /** A small piece of character art; one string (may contain newlines) or lines. */
369
+ export interface MascotProvider {
370
+ id: string;
371
+ render(ctx: MascotContext): string | string[];
372
+ }
373
+ /**
374
+ * Catalog grouping a plugin declares or the host derives from its registrations. Accepted
375
+ * values: `"model-provider"` (registers model providers) and `"methodology-harness"` (bundles a
376
+ * development-methodology workflow).
377
+ */
378
+ export type PluginCategory = "model-provider" | "methodology-harness";
379
+ export interface PluginMetadata {
380
+ id: string;
381
+ version: string;
382
+ builtin: boolean;
383
+ name?: string;
384
+ description?: string;
385
+ /**
386
+ * Categories declared by the plugin or derived by the host from its registrations
387
+ * (`"model-provider"`, `"methodology-harness"`).
388
+ */
389
+ categories?: PluginCategory[];
390
+ }
391
+ export interface StartupFact {
392
+ label: string;
393
+ value: string;
394
+ }
395
+ export interface StartupContext {
396
+ version: string;
397
+ cwd: string;
398
+ model?: string;
399
+ /** Provider host only (never credentials). */
400
+ provider?: string;
401
+ userName?: string;
402
+ terminal: TerminalCapabilities;
403
+ plugins: readonly PluginMetadata[];
404
+ /** The resolved (and validated) mascot, so custom screens can reuse it. */
405
+ mascot: MascotProvider;
406
+ tips: readonly string[];
407
+ /** Host-provided contextual facts such as permissions or plugin state. */
408
+ facts?: readonly StartupFact[];
409
+ }
410
+ /** Renders the startup screen as plain lines (ANSI SGR only when `terminal.color`). */
411
+ export interface StartupScreenProvider {
412
+ id: string;
413
+ render(ctx: StartupContext): string[];
414
+ }
415
+ export interface SearchResult {
416
+ title: string;
417
+ url: string;
418
+ snippet: string;
419
+ }
420
+ /** A pluggable web-search backend for the `websearch` tool. */
421
+ export interface SearchProvider {
422
+ id: string;
423
+ search(query: string, options?: {
424
+ signal?: AbortSignal;
425
+ }): Promise<SearchResult[]>;
426
+ }
427
+ /** Typed map of extension points; new points are added here without breaking existing ones. */
428
+ export interface ExtensionPoints {
429
+ mascot: MascotProvider;
430
+ "startup-screen": StartupScreenProvider;
431
+ /** Replaces the built-in websearch resolution (SearXNG/DuckDuckGo/configured/native) entirely. */
432
+ websearch: SearchProvider;
433
+ }
434
+ export interface ExtensionOptions {
435
+ /** Higher wins (default 0). Ties break by plugin id, then registration order. */
436
+ priority?: number;
437
+ }
438
+ /** Lifecycle of a child session (delegated work). */
439
+ export type SessionStatus = "queued" | "running" | "completed" | "failed" | "cancelled" | "interrupted";
440
+ /** Capability narrowing for a child session: it can never exceed its parent. */
441
+ export type PermissionLevel = "allow" | "ask" | "deny";
442
+ export interface ChildSessionSpec {
443
+ parentId: string;
444
+ /** Optional preassigned id (UUID), e.g. to name a git branch before spawning. */
445
+ id?: string;
446
+ title: string;
447
+ /** Free-form label shown in UIs and approvals (e.g. an agent name). */
448
+ agent: string;
449
+ /** System instructions appended for this child (persona and rules). */
450
+ instructions?: string;
451
+ /** Tool names; `*` allows every tool the parent has. Applied on top of the parent's tools. */
452
+ tools?: {
453
+ allow?: string[];
454
+ deny?: string[];
455
+ };
456
+ /** Child model selector (`provider/model` or an unambiguous model id); inherits when omitted. */
457
+ model?: string;
458
+ readOnly?: boolean;
459
+ permission?: {
460
+ write?: PermissionLevel;
461
+ process?: PermissionLevel;
462
+ };
463
+ /** Workspace root for the child (e.g. a git worktree); defaults to the parent's. */
464
+ workspace?: string;
465
+ maxTurns?: number;
466
+ timeoutMs?: number;
467
+ maxTokens?: number;
468
+ /** Per-call output token budget for the child; beats the global agent-loop budget. */
469
+ maxOutputTokens?: number;
470
+ }
471
+ export interface ChildSessionInfo {
472
+ id: string;
473
+ parentId: string;
474
+ depth: number;
475
+ agent: string;
476
+ title: string;
477
+ status: SessionStatus;
478
+ model: string;
479
+ provider: string;
480
+ workspace: string;
481
+ usage: {
482
+ input: number;
483
+ output: number;
484
+ };
485
+ /** Effective capabilities after narrowing. */
486
+ capabilities: {
487
+ write: boolean;
488
+ process: boolean;
489
+ approvals: boolean;
490
+ };
491
+ createdAt: number;
492
+ updatedAt: number;
493
+ }
494
+ export interface ChildRunResult {
495
+ id: string;
496
+ status: SessionStatus;
497
+ text: string;
498
+ usage: {
499
+ input: number;
500
+ output: number;
501
+ };
502
+ error?: string;
503
+ /** True when the child hit its turn limit: `text` is a usable partial result, not an error. */
504
+ turnsExceeded?: boolean;
505
+ }
506
+ /** Generic tree node contributed to an interactive panel (e.g. running agents). */
507
+ export interface PanelNode {
508
+ id: string;
509
+ parentId?: string;
510
+ label: string;
511
+ /** Named color: red, green, yellow, blue, magenta, cyan, gray. */
512
+ color?: string;
513
+ status: string;
514
+ startedAt?: number;
515
+ endedAt?: number;
516
+ tokens?: number;
517
+ /** One-line live summary. */
518
+ detail?: string;
519
+ /** Session to open in a read-only view when the node is selected. */
520
+ sessionId?: string;
521
+ }
522
+ export interface PanelProvider {
523
+ title: string;
524
+ /** Nodes under the given root session, parents before children. */
525
+ nodes(context: {
526
+ sessionId: string;
527
+ }): PanelNode[];
528
+ /** UI actions: cancel a node (and its subtree) or move foreground work to the background. */
529
+ action?(action: "cancel" | "background", nodeId: string | undefined, context: {
530
+ sessionId: string;
531
+ }): void | Promise<void>;
532
+ }
533
+ export interface SelectRequest {
534
+ title: string;
535
+ options: Array<{
536
+ value: string;
537
+ label: string;
538
+ description?: string;
539
+ }>;
540
+ }
541
+ export interface QuestionOption {
542
+ /** Stable value returned by `ui.askQuestions`; not necessarily shown to the user. */
543
+ value: string;
544
+ label: string;
545
+ description?: string;
546
+ /** At most one option per question should be marked recommended. A suggestion, never forced. */
547
+ recommended?: boolean;
548
+ }
549
+ export interface Question {
550
+ id: string;
551
+ /** Short chip label (e.g. shown as a breadcrumb/heading), distinct from the full `question` text. */
552
+ header: string;
553
+ question: string;
554
+ /** 2-4 options. */
555
+ options: QuestionOption[];
556
+ multiSelect?: boolean;
557
+ }
558
+ export interface AskQuestionsRequest {
559
+ /** 1-4 questions, asked one after another. */
560
+ questions: Question[];
561
+ /** Session asking (a child session when delegated). */
562
+ session?: string;
563
+ /** Who is asking, e.g. an agent path such as "general › explore". */
564
+ label?: string;
565
+ signal?: AbortSignal;
566
+ }
567
+ /** Each question id maps to the chosen value(s), or undefined when the question was skipped. */
568
+ export type AskQuestionsResult = Record<string, string | string[] | undefined>;
569
+ /** Where a command was invoked (the interactive UI's current session, when known). */
570
+ export interface CommandContext {
571
+ sessionId?: string;
572
+ }
573
+ export interface CommandOptions {
574
+ description?: string;
575
+ argumentHint?: string;
576
+ }
577
+ export interface PluginAPI {
578
+ tools: {
579
+ register(tool: ToolDefinition): () => void;
580
+ };
581
+ commands: {
582
+ register(name: string, handler: (args: string, context?: CommandContext) => Promise<string>, options?: CommandOptions): () => void;
583
+ };
584
+ events: {
585
+ on(handler: (event: Readonly<RunEvent>) => void): () => void;
586
+ };
587
+ context: {
588
+ register(provider: () => Promise<string>): () => void;
589
+ };
590
+ resources: {
591
+ skills(path: string): void;
592
+ prompts(path: string): void;
593
+ /** A directory of agent definitions (Markdown + frontmatter) for delegation plugins. */
594
+ agents(path: string): void;
595
+ /** Directories registered by every plugin for a resource kind, with the plugin id. */
596
+ list(kind: "skills" | "prompts" | "agents"): Array<{
597
+ plugin: string;
598
+ dir: string;
599
+ }>;
600
+ };
601
+ state: {
602
+ get(key: string): unknown;
603
+ set(key: string, value: unknown): void;
604
+ };
605
+ /** Contribute to core context compaction (hooks run with a host timeout; failures are isolated). */
606
+ compaction: {
607
+ register(hooks: CompactionHooks): () => void;
608
+ };
609
+ session: {
610
+ /** Returned text is injected once at the start of a new, empty session. */
611
+ onStart(handler: (info: SessionInfo) => Promise<string | undefined | void>): () => void;
612
+ /** Called when an interactive session ends (/clear, /exit, quit), bounded by a timeout. */
613
+ onEnd(handler: (info: SessionInfo) => Promise<void>): () => void;
614
+ };
615
+ /** Provider-agnostic text completion; plugins never import provider SDKs. */
616
+ model: {
617
+ complete(request: CompletionRequest): Promise<string>;
618
+ };
619
+ /** Credential-free access to configured `/connect` provider models. */
620
+ models: {
621
+ list(signal?: AbortSignal): Promise<AvailableProviderModel[]>;
622
+ resolve(reference: string, signal?: AbortSignal): Promise<ResolvedProviderModel>;
623
+ };
624
+ /** Register a selectable model provider; unlike extensions, registrations do not compete. */
625
+ providers: {
626
+ register(provider: ProviderRegistration): () => void;
627
+ };
628
+ /** Opens a private (0600) SQLite file, creating parent directories (0700). */
629
+ storage: {
630
+ sqlite(path: string): SqlDatabase;
631
+ };
632
+ /** Provide an implementation for a named extension point (e.g. mascot, startup-screen). */
633
+ extensions: {
634
+ register<K extends keyof ExtensionPoints>(point: K, provider: ExtensionPoints[K], options?: ExtensionOptions): () => void;
635
+ };
636
+ /**
637
+ * Child sessions: separate conversations (fresh context) that run with narrowed permissions,
638
+ * persisted with parent links. Aborting a parent run aborts running descendants.
639
+ */
640
+ sessions: {
641
+ spawn(spec: ChildSessionSpec): ChildSessionInfo;
642
+ /** Resolves an optional model selector before creating the child. */
643
+ create(spec: ChildSessionSpec): Promise<ChildSessionInfo>;
644
+ run(id: string, prompt: string, options?: {
645
+ signal?: AbortSignal;
646
+ }): Promise<ChildRunResult>;
647
+ get(id: string): ChildSessionInfo | undefined;
648
+ children(parentId: string): ChildSessionInfo[];
649
+ /** Ancestor ids, nearest first (empty for a root session). */
650
+ ancestors(id: string): string[];
651
+ /** Cancels a session's run and all running descendants; returns how many were running. */
652
+ cancel(id: string): number;
653
+ /** Queues a user message for the session's next turn (or next run when idle). */
654
+ enqueue(id: string, text: string): void;
655
+ isRunning(id: string): boolean;
656
+ /** Effective capabilities of a (root or child) session. */
657
+ capabilities(id: string): {
658
+ write: boolean;
659
+ process: boolean;
660
+ approvals: boolean;
661
+ readOnly: boolean;
662
+ };
663
+ /** Model a new child would inherit from this session. */
664
+ model(id: string): string;
665
+ workspace(id: string): string;
666
+ setStatus(id: string, status: SessionStatus): void;
667
+ };
668
+ ui: {
669
+ /** Short status text shown by interactive UIs (footer); `detail` feeds /stats. */
670
+ status(key: string, text: string | undefined, detail?: string): void;
671
+ /** A collapsible tree panel (interactive UIs only). */
672
+ panel(id: string, provider: PanelProvider): () => void;
673
+ /** Ask the user to choose; resolves undefined when no interactive UI is available. */
674
+ select(request: SelectRequest): Promise<string | undefined>;
675
+ /**
676
+ * Ask the user one or more multiple-choice questions. Resolves every question id to
677
+ * undefined when no interactive UI is available (never hangs headless).
678
+ */
679
+ askQuestions(request: AskQuestionsRequest): Promise<AskQuestionsResult>;
680
+ /** Open a session in a read-only view (interactive UIs only). */
681
+ open(sessionId: string): boolean;
682
+ /** True when an interactive UI is bound at all (globally, not per-session). */
683
+ interactive(): boolean;
684
+ };
685
+ }
686
+ export interface Plugin {
687
+ /** Stable, unique plugin id (lowercase, dots and dashes). Plugins are identified by `id`. */
688
+ id: string;
689
+ version: string;
690
+ apiVersion: 1;
691
+ /** Provider-neutral catalog metadata. Hosts may derive additional categories from registrations. */
692
+ name?: string;
693
+ description?: string;
694
+ /** Accepted values: `"model-provider"` and `"methodology-harness"` (see `PluginCategory`). */
695
+ categories?: PluginCategory[];
696
+ /** Declarative sugar for `api.extensions.register(point, provider)` at priority 0. */
697
+ extensions?: {
698
+ [K in keyof ExtensionPoints]?: ExtensionPoints[K];
699
+ };
700
+ setup(api: PluginAPI): void | Promise<void>;
701
+ dispose?(): void | Promise<void>;
702
+ }
703
+ export declare function definePlugin<T extends Plugin>(plugin: T): T;
704
+ export declare const textResult: (text: string, isError?: boolean) => ToolResult;
705
+ /**
706
+ * Text-only view of a tool result: the content filtered to its text parts, order preserved.
707
+ * This is what the runner hands to providers, what compaction summarizes and what headless
708
+ * output shows; `ui`/`image` parts stay only in the persisted transcript for TUI replay, so
709
+ * raw bytes or structured blocks never reach the model prompt.
710
+ */
711
+ export declare function textProjection(result: ToolResult): ToolResult;
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export function definePlugin(plugin) {
2
+ return plugin;
3
+ }
4
+ export const textResult = (text, isError = false) => ({
5
+ content: [{ type: "text", text }],
6
+ ...(isError ? { isError: true } : {}),
7
+ });
8
+ /**
9
+ * Text-only view of a tool result: the content filtered to its text parts, order preserved.
10
+ * This is what the runner hands to providers, what compaction summarizes and what headless
11
+ * output shows; `ui`/`image` parts stay only in the persisted transcript for TUI replay, so
12
+ * raw bytes or structured blocks never reach the model prompt.
13
+ */
14
+ export function textProjection(result) {
15
+ if (result.content.every((part) => part.type === "text"))
16
+ return result;
17
+ return { ...result, content: result.content.filter((part) => part.type === "text") };
18
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@alisio/sdk",
3
+ "version": "0.1.0-alpha.10",
4
+ "description": "Typed plugin SDK for Alisio: the stable contract for tools, commands, context, compaction and session hooks, model completions and storage. Types only plus tiny helpers; zero runtime dependencies.",
5
+ "author": "Gustavo Gutiérrez",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/GustavoGutierrez/alisio.git",
10
+ "directory": "packages/sdk"
11
+ },
12
+ "homepage": "https://gustavogutierrez.github.io/alisio/",
13
+ "bugs": {
14
+ "url": "https://github.com/GustavoGutierrez/alisio/issues"
15
+ },
16
+ "keywords": [
17
+ "ai",
18
+ "coding-agent",
19
+ "llm",
20
+ "agent-harness",
21
+ "openai-compatible",
22
+ "plugins",
23
+ "sdk",
24
+ "typescript"
25
+ ],
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ }
32
+ },
33
+ "types": "./dist/index.d.ts",
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "sideEffects": false,
40
+ "engines": {
41
+ "node": ">=22.16"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public",
45
+ "registry": "https://registry.npmjs.com/"
46
+ },
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.build.json"
49
+ }
50
+ }