@alisio/sdk 0.2.1 → 0.3.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/dist/index.d.ts CHANGED
@@ -489,6 +489,11 @@ export interface ToolContext {
489
489
  * (headless, `--read-only`), because no flag grants an installation.
490
490
  */
491
491
  approveInstall?: InstallApprover;
492
+ /**
493
+ * Decision Intelligence, consumer side, bound by the host to the current run. Absent when the
494
+ * host does not offer it: feature-detect with `context.decisions?.tryDecide(...)`.
495
+ */
496
+ decisions?: ToolDecisions;
492
497
  }
493
498
  export interface ToolDefinition {
494
499
  name: string;
@@ -761,6 +766,32 @@ export interface RunEventDataMap {
761
766
  error: string;
762
767
  continued: true;
763
768
  };
769
+ /** A Decision Intelligence call that produced usable answers. Metadata only, never the state. */
770
+ decision_completed: {
771
+ decisionId: string;
772
+ pack?: {
773
+ id: string;
774
+ version: number;
775
+ };
776
+ provider: string;
777
+ latencyMs: number;
778
+ /** Accepted answers. */
779
+ decisionCount: number;
780
+ rejectedCount: number;
781
+ /** Lowest confidence among the accepted answers. */
782
+ confidenceMin: number;
783
+ };
784
+ /** A Decision Intelligence call that fell back to the consumer's deterministic default. */
785
+ decision_fallback: {
786
+ decisionId: string;
787
+ pack?: {
788
+ id: string;
789
+ version: number;
790
+ };
791
+ provider: string;
792
+ latencyMs: number;
793
+ reason: DecisionFallbackReason;
794
+ };
764
795
  }
765
796
  /** Every `RunEvent.type` the core emits today. `RunEvent.type` itself stays `string`. */
766
797
  export type RunEventType = keyof RunEventDataMap;
@@ -908,8 +939,9 @@ export interface MascotProvider {
908
939
  * - `"mcp"` — MCP-server management or bundling helpers.
909
940
  * - `"storage"` — durable storage backends beyond the default SQLite state.
910
941
  * - `"ui"` — TUI presentation providers (startup screens, mascots, panels).
942
+ * - `"decisions"` — Decision Intelligence providers (`api.decisions.registerProvider`).
911
943
  */
912
- export type PluginCategory = "model-provider" | "methodology-harness" | "memory" | "subagents" | "search" | "tools" | "security" | "analytics" | "mcp" | "storage" | "ui";
944
+ export type PluginCategory = "model-provider" | "methodology-harness" | "memory" | "subagents" | "search" | "tools" | "security" | "analytics" | "mcp" | "storage" | "ui" | "decisions";
913
945
  export interface PluginMetadata {
914
946
  id: string;
915
947
  version: string;
@@ -1324,6 +1356,163 @@ export declare class ViewParamsError extends Error {
1324
1356
  readonly code = "view_invalid_params";
1325
1357
  constructor(message?: string);
1326
1358
  }
1359
+ export interface DecisionsApi {
1360
+ /** Registers a provider (does NOT activate it; activation is `decisions.provider` in config). */
1361
+ registerProvider(provider: DecisionProvider): () => void;
1362
+ /** True when decisions are enabled and the configured provider is registered. */
1363
+ available(): boolean;
1364
+ activeProvider(): {
1365
+ id: string;
1366
+ name: string;
1367
+ } | null;
1368
+ /**
1369
+ * Never throws for infrastructure problems: resolves null (disabled, no provider, timeout,
1370
+ * provider error, circuit open, or every answer rejected). Throws `DecisionRequestError` (a
1371
+ * `TypeError`) for a malformed request and rethrows an abort requested by the caller.
1372
+ */
1373
+ tryDecide(request: DecisionRequest, options?: DecisionOptions): Promise<DecisionResponse | null>;
1374
+ }
1375
+ /** What a tool receives in `ToolContext.decisions`: the consumer side, bound to the run. */
1376
+ export type ToolDecisions = Omit<DecisionsApi, "registerProvider">;
1377
+ export interface DecisionProvider {
1378
+ /** `^[a-z0-9][a-z0-9.-]{0,63}$`, unique among registered providers. */
1379
+ id: string;
1380
+ name: string;
1381
+ capabilities: {
1382
+ select: boolean;
1383
+ boolean: boolean;
1384
+ ordinal: boolean;
1385
+ };
1386
+ /** Called ONLY by `/decisions` (own 1 s timeout), never on the decide path. */
1387
+ health?(signal?: AbortSignal): Promise<{
1388
+ status: "ready" | "starting" | "unavailable";
1389
+ detail?: string;
1390
+ }>;
1391
+ /** Called when this provider becomes the active one. Bounded, never fatal, never awaited at startup. Cold start belongs here. */
1392
+ activate?(): void | Promise<void>;
1393
+ /** Called when it stops being the active one (config change, unregister, shutdown). */
1394
+ deactivate?(): void | Promise<void>;
1395
+ /** Fail fast with `DecisionProviderError("not_ready" | "unavailable")` instead of waiting. */
1396
+ decide(request: DecisionRequest, context: {
1397
+ signal: AbortSignal;
1398
+ timeoutMs: number;
1399
+ }): Promise<DecisionProviderResult>;
1400
+ }
1401
+ export interface DecisionRequest {
1402
+ version: 1;
1403
+ /** BCP 47 hint (for example "es-CO"); providers may ignore it. */
1404
+ language?: string;
1405
+ /** Non-sensitive call-site label (becomes `decisionId` in events), for example "smart-dashboard-v1". Never data-derived. */
1406
+ id: string;
1407
+ pack?: {
1408
+ id: string;
1409
+ version: number;
1410
+ };
1411
+ /** JSON sent to the provider. Keep it minimal; a provider may be remote. At most 16 KB serialized (the whole request at most 32 KB). */
1412
+ state: JsonValue;
1413
+ /** 1..16 decisions keyed by `^[A-Za-z][A-Za-z0-9_]{0,63}$`. */
1414
+ decisions: Record<string, DecisionDefinition>;
1415
+ }
1416
+ export type DecisionDefinition = {
1417
+ type: "select";
1418
+ instruction: string;
1419
+ options: Record<string, string>;
1420
+ } | {
1421
+ type: "boolean";
1422
+ instruction: string;
1423
+ trueMeaning?: string;
1424
+ falseMeaning?: string;
1425
+ } | {
1426
+ type: "ordinal";
1427
+ instruction: string;
1428
+ levels: string[];
1429
+ };
1430
+ /**
1431
+ * `confidence` is the provider's best estimate, in [0,1], that the answer is correct. The core does
1432
+ * not assume it is calibrated: `minConfidence` is a heuristic filter. Each adapter chooses which
1433
+ * native field feeds it.
1434
+ */
1435
+ export type DecisionAnswer = {
1436
+ type: "select";
1437
+ value: string;
1438
+ confidence: number;
1439
+ probabilities?: Record<string, number>;
1440
+ } | {
1441
+ type: "boolean";
1442
+ value: boolean;
1443
+ confidence: number;
1444
+ probability: number;
1445
+ } | {
1446
+ type: "ordinal";
1447
+ level: string;
1448
+ index: number;
1449
+ confidence: number;
1450
+ distribution?: number[];
1451
+ };
1452
+ export interface DecisionProviderResult {
1453
+ decisions: Record<string, DecisionAnswer>;
1454
+ usage?: {
1455
+ inputUnits?: number;
1456
+ outputUnits?: number;
1457
+ };
1458
+ }
1459
+ export interface DecisionOptions {
1460
+ /** Default: config `decisions.timeoutMs`. */
1461
+ timeoutMs?: number;
1462
+ /** Default: config `decisions.minConfidence`. */
1463
+ minConfidence?: number;
1464
+ signal?: AbortSignal;
1465
+ }
1466
+ export type DecisionRejection = "low_confidence" | "invalid" | "unsupported" | "missing";
1467
+ export interface DecisionResponse {
1468
+ provider: string;
1469
+ latencyMs: number;
1470
+ /** Only validated answers that reached `minConfidence`. */
1471
+ decisions: Record<string, DecisionAnswer>;
1472
+ rejected: Record<string, DecisionRejection>;
1473
+ usage?: {
1474
+ inputUnits?: number;
1475
+ outputUnits?: number;
1476
+ };
1477
+ }
1478
+ export type DecisionFallbackReason = "timeout" | "not_ready" | "unavailable" | "invalid_response" | "provider_error" | "circuit_open" | "unsupported" | "all_rejected";
1479
+ export type DecisionProviderErrorCode = "not_ready" | "unavailable" | "timeout" | "invalid_response" | "internal";
1480
+ /**
1481
+ * Providers throw this. `not_ready` and `unavailable` are fast fallbacks that do NOT count toward
1482
+ * the circuit breaker. Hosts match on `code`/`name` (not `instanceof`), because a plugin may
1483
+ * bundle its own copy of this package.
1484
+ */
1485
+ export declare class DecisionProviderError extends Error {
1486
+ readonly code: DecisionProviderErrorCode;
1487
+ constructor(code: DecisionProviderErrorCode, message?: string);
1488
+ }
1489
+ /** A malformed `DecisionRequest`: a programming error of the consumer, never swallowed by the host. */
1490
+ export declare class DecisionRequestError extends TypeError {
1491
+ readonly code = "decision_invalid_request";
1492
+ constructor(message: string);
1493
+ }
1494
+ export interface DecisionStats {
1495
+ requests: number;
1496
+ completed: number;
1497
+ fallbacks: number;
1498
+ /** Over the completed calls. */
1499
+ avgLatencyMs?: number;
1500
+ p95LatencyMs?: number;
1501
+ lastFallback?: {
1502
+ reason: DecisionFallbackReason;
1503
+ decisionId: string;
1504
+ at: string;
1505
+ };
1506
+ /** Calls per pack id. */
1507
+ packs: Record<string, number>;
1508
+ /** Provider of the most recent decision event. */
1509
+ provider?: string;
1510
+ }
1511
+ /**
1512
+ * Pure: summarizes `decision_completed` / `decision_fallback` events (other events are ignored).
1513
+ * The TUI, the web UI and `/stats` all call this so the three surfaces agree.
1514
+ */
1515
+ export declare function summarizeDecisionEvents(events: readonly RunEvent[]): DecisionStats;
1327
1516
  export interface PluginAPI {
1328
1517
  tools: {
1329
1518
  register(tool: ToolDefinition): () => void;
@@ -1386,6 +1575,25 @@ export interface PluginAPI {
1386
1575
  views?: {
1387
1576
  register(view: ViewDefinition): () => void;
1388
1577
  };
1578
+ /**
1579
+ * Decision Intelligence: register a `DecisionProvider` or consume decisions. Absent on a core
1580
+ * that predates it: feature-detect with `api.decisions?.registerProvider(...)`.
1581
+ */
1582
+ decisions?: DecisionsApi;
1583
+ /**
1584
+ * Per-plugin directories resolved by the host and created with mode 0700. Absent on a core that
1585
+ * predates it.
1586
+ */
1587
+ paths?: {
1588
+ state: string;
1589
+ config: string;
1590
+ cache: string;
1591
+ };
1592
+ /**
1593
+ * Options from `pluginOverrides[id].options` (`{}` when none), frozen at `setup`. Absent on a
1594
+ * core that predates it.
1595
+ */
1596
+ options?: Readonly<Record<string, JsonValue>>;
1389
1597
  /** Provide an implementation for a named extension point (e.g. mascot, startup-screen). */
1390
1598
  extensions: {
1391
1599
  register<K extends keyof ExtensionPoints>(point: K, provider: ExtensionPoints[K], options?: ExtensionOptions): () => void;
package/dist/index.js CHANGED
@@ -35,6 +35,75 @@ export class ViewParamsError extends Error {
35
35
  this.name = "ViewParamsError";
36
36
  }
37
37
  }
38
+ /**
39
+ * Providers throw this. `not_ready` and `unavailable` are fast fallbacks that do NOT count toward
40
+ * the circuit breaker. Hosts match on `code`/`name` (not `instanceof`), because a plugin may
41
+ * bundle its own copy of this package.
42
+ */
43
+ export class DecisionProviderError extends Error {
44
+ code;
45
+ constructor(code, message) {
46
+ super(message ?? code);
47
+ this.code = code;
48
+ this.name = "DecisionProviderError";
49
+ }
50
+ }
51
+ /** A malformed `DecisionRequest`: a programming error of the consumer, never swallowed by the host. */
52
+ export class DecisionRequestError extends TypeError {
53
+ code = "decision_invalid_request";
54
+ constructor(message) {
55
+ super(message);
56
+ this.name = "DecisionRequestError";
57
+ }
58
+ }
59
+ const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
60
+ /**
61
+ * Pure: summarizes `decision_completed` / `decision_fallback` events (other events are ignored).
62
+ * The TUI, the web UI and `/stats` all call this so the three surfaces agree.
63
+ */
64
+ export function summarizeDecisionEvents(events) {
65
+ const latencies = [];
66
+ const packs = {};
67
+ let completed = 0;
68
+ let fallbacks = 0;
69
+ let provider;
70
+ let lastFallback;
71
+ for (const event of events) {
72
+ if (event.type !== "decision_completed" && event.type !== "decision_fallback")
73
+ continue;
74
+ if (!isRecord(event.data))
75
+ continue;
76
+ const data = event.data;
77
+ if (event.type === "decision_completed") {
78
+ completed++;
79
+ if (typeof data.latencyMs === "number" && Number.isFinite(data.latencyMs))
80
+ latencies.push(data.latencyMs);
81
+ }
82
+ else {
83
+ fallbacks++;
84
+ lastFallback = {
85
+ reason: data.reason,
86
+ decisionId: String(data.decisionId),
87
+ at: event.timestamp,
88
+ };
89
+ }
90
+ if (typeof data.provider === "string")
91
+ provider = data.provider;
92
+ if (isRecord(data.pack) && typeof data.pack.id === "string")
93
+ packs[data.pack.id] = (packs[data.pack.id] ?? 0) + 1;
94
+ }
95
+ const stats = { requests: completed + fallbacks, completed, fallbacks, packs };
96
+ if (latencies.length) {
97
+ const sorted = [...latencies].sort((a, b) => a - b);
98
+ stats.avgLatencyMs = Math.round(sorted.reduce((sum, v) => sum + v, 0) / sorted.length);
99
+ stats.p95LatencyMs = sorted[Math.ceil(0.95 * sorted.length) - 1];
100
+ }
101
+ if (lastFallback)
102
+ stats.lastFallback = lastFallback;
103
+ if (provider)
104
+ stats.provider = provider;
105
+ return stats;
106
+ }
38
107
  export function definePlugin(plugin) {
39
108
  return plugin;
40
109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alisio/sdk",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
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
5
  "author": "Gustavo Gutiérrez",
6
6
  "license": "MIT",