@ottimis/jack-provider-sdk 0.1.0 → 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.
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Usage / billing capability — provider-owned data flow.
3
+ *
4
+ * Each provider knows how to talk to its own billing surface (Claude
5
+ * cookie API for Pro/Max, OpenAI usage endpoints for Codex, Gemini's
6
+ * Google Cloud quotas, …) and how to map its SDK's per-message token
7
+ * counts into a canonical shape. The host runs a generic poll loop and
8
+ * a generic chip — it never special-cases any provider.
9
+ *
10
+ * Two surfaces:
11
+ *
12
+ * - `fetch()` for **account-level** snapshots. Pulled by a host poller
13
+ * on `recommendedPollIntervalSec` cadence (clamped to host bounds).
14
+ * What "account" means is provider-defined: Claude → org, Codex →
15
+ * OpenAI project, Gemini → Cloud Billing project.
16
+ *
17
+ * - `formatSessionMetrics()` for **per-session** translation. The
18
+ * manager already calls `backend.getContextUsage()` after every
19
+ * `assistant` message; that returns a loose `AgentContextUsage`
20
+ * bag. This hook lets the provider lift it into canonical
21
+ * {@link UsageMetric}[] without the host trying to interpret
22
+ * provider-specific fields.
23
+ *
24
+ * Single source of truth: the provider. Host plumbs, never decodes.
25
+ *
26
+ * Optional everywhere — providers without billing visibility (Codex
27
+ * without admin keys, Gemini without OAuth) leave `fetch()` returning
28
+ * an empty `metrics: []`. The capability flag stays `true` if the
29
+ * provider can format per-session metrics, `false` if it has nothing
30
+ * to say at all.
31
+ */
32
+ import type { AgentContextUsage } from './backend';
33
+ /**
34
+ * Canonical metric kinds. New kinds extend this union additively when a
35
+ * provider has a meaningfully different shape (e.g. a future
36
+ * `quarterly_burn` for enterprise billing). Adding a kind is a minor
37
+ * SDK bump; the renderer's dispatch table grows alongside.
38
+ */
39
+ export type UsageMetric = TimeWindowMetric | TokenUtilizationMetric | MonthlySpendMetric;
40
+ /**
41
+ * Rolling-window utilization. The classic Claude Pro/Max bucket
42
+ * (5-hour, 7-day) — utilization 0..1 with a hard reset boundary.
43
+ *
44
+ * Optional `used` / `limit` / `unit` carry raw counts when the
45
+ * provider exposes them (Gemini free-tier daily quota: 12 of 50
46
+ * requests). Claude's cookie API gives only `utilization`, so those
47
+ * stay undefined and the chip falls back to "X% of N-hour usage".
48
+ */
49
+ export type TimeWindowMetric = {
50
+ kind: 'time_window';
51
+ /** Stable, provider-defined key. e.g. `'five_hour'`, `'seven_day_opus'`, `'daily'`. */
52
+ id: string;
53
+ /** Human-readable label for UI. Provider supplies (i18n is its concern). */
54
+ label: string;
55
+ /** Window utilization in [0, 1]. */
56
+ utilization: number;
57
+ /** Optional: raw count consumed in the window (when known). */
58
+ used?: number;
59
+ /** Optional: raw cap of the window (when known). */
60
+ limit?: number;
61
+ /** Optional: unit of `used`/`limit`. Default `'tokens'`. Chip uses for formatting. */
62
+ unit?: 'tokens' | 'requests' | (string & {});
63
+ /** ISO 8601 timestamp when the window resets. */
64
+ resetsAt: string;
65
+ /** Window length in seconds. Used for elapsed-progress UI. */
66
+ windowSeconds: number;
67
+ };
68
+ /**
69
+ * Count-based utilization without a time boundary. Used for context
70
+ * window pressure ("X tokens of Y max") and any cumulative provider
71
+ * metric that doesn't reset on a clock (lifetime / billing-period
72
+ * tokens, etc).
73
+ *
74
+ * `max` is optional — a provider tracking total token consumption
75
+ * without a hard cap (analytics, not gating) leaves it undefined and
76
+ * the chip shows just the count.
77
+ */
78
+ export type TokenUtilizationMetric = {
79
+ kind: 'token_utilization';
80
+ /** Stable, provider-defined key. e.g. `'context'`, `'session_total'`. */
81
+ id: string;
82
+ /** Human-readable label for UI. */
83
+ label: string;
84
+ /** Tokens consumed. */
85
+ used: number;
86
+ /** Optional cap. Undefined = no cap, chip hides the ratio. */
87
+ max?: number;
88
+ };
89
+ /**
90
+ * Spend on a billing cycle (typically monthly). Only meaningful for
91
+ * providers using API-key auth — subscription users have rolling-window
92
+ * quotas, not $-spend.
93
+ *
94
+ * `budgetUsd` is optional: most users don't set a budget, so the chip
95
+ * shows raw spend without a denominator. When present, chip can render
96
+ * a budget bar.
97
+ */
98
+ export type MonthlySpendMetric = {
99
+ kind: 'monthly_spend';
100
+ id: string;
101
+ /** Cycle label, e.g. "May 2026" or "Current cycle". */
102
+ label: string;
103
+ spentUsd: number;
104
+ budgetUsd?: number;
105
+ /** ISO 8601 — start of the billing cycle. */
106
+ cycleStart: string;
107
+ /** ISO 8601 — end of the billing cycle. */
108
+ cycleEnd: string;
109
+ };
110
+ /**
111
+ * One snapshot of provider-side usage data, as returned by
112
+ * {@link UsageApi.fetch}. `metrics` may be empty when the provider has
113
+ * no account-level data to report (Codex without billing key, etc.).
114
+ */
115
+ export type UsageSnapshot = {
116
+ metrics: UsageMetric[];
117
+ /** ISO 8601 — when this snapshot was observed. */
118
+ observedAt: string;
119
+ /** Verbatim provider payload, kept for debug / future analytics.
120
+ * Never consumed by host or chip directly. */
121
+ raw?: unknown;
122
+ };
123
+ /**
124
+ * Connection / authorization state of the provider's usage surface.
125
+ * Surface in the chip's pop-over so the user knows whether they're
126
+ * tracking real numbers or seeing a cached / disconnected snapshot.
127
+ */
128
+ export type UsageStatus = {
129
+ connected: boolean;
130
+ /** Optional human-readable identity (org name, email, project id). */
131
+ identity?: string;
132
+ /**
133
+ * Provider-defined auth-mode hint. Lets the renderer adapt copy
134
+ * (e.g. "Connected as API user" vs "Pro subscription"). Stable
135
+ * provider-supplied strings; host doesn't interpret beyond
136
+ * passthrough.
137
+ */
138
+ authMode?: 'subscription' | 'api_key' | (string & {});
139
+ /** Last error message, when not connected. */
140
+ error?: string;
141
+ };
142
+ /**
143
+ * Result of {@link UsageApi.connect}. The `'choose'` branch covers
144
+ * multi-org / multi-project accounts where the user must pick one
145
+ * (Claude's multi-org case). Generalising to a generic option list
146
+ * means the chip's UI doesn't special-case any provider.
147
+ */
148
+ export type UsageConnectResult = {
149
+ kind: 'ready';
150
+ identity: string;
151
+ } | {
152
+ kind: 'choose';
153
+ options: UsageConnectOption[];
154
+ } | {
155
+ kind: 'error';
156
+ error: string;
157
+ } | {
158
+ kind: 'cancelled';
159
+ };
160
+ export type UsageConnectOption = {
161
+ id: string;
162
+ label: string;
163
+ };
164
+ /**
165
+ * Context handed to {@link UsageApi.connect}. Currently just a parent
166
+ * window for Electron-coupled flows (Claude opens a child BrowserWindow
167
+ * on `claude.ai/login`). Typed as `unknown` here so the SDK doesn't
168
+ * pull in `electron` as a peer dep — the host narrows to
169
+ * `BrowserWindow` at the call site.
170
+ */
171
+ export type UsageConnectContext = {
172
+ /** Parent window for any modal flow the provider needs to open. */
173
+ parentWindow?: unknown;
174
+ };
175
+ /**
176
+ * Provider-owned usage capability. Optional on {@link JackProvider};
177
+ * absent = host hides the chip's "Connect" affordance and the
178
+ * capability flag is `false`.
179
+ */
180
+ export type UsageApi = {
181
+ /** Current connection state — used for chip display + gating. */
182
+ status(): Promise<UsageStatus>;
183
+ /**
184
+ * Open the provider's connect flow. Whatever modality the provider
185
+ * needs (login window, API-key picker, OAuth redirect) lives here.
186
+ */
187
+ connect(ctx: UsageConnectContext): Promise<UsageConnectResult>;
188
+ /**
189
+ * When `connect()` returned `'choose'`, host calls this with the
190
+ * user's pick. Optional — providers that never choose omit it.
191
+ */
192
+ selectOption?(optionId: string): Promise<UsageConnectResult>;
193
+ /** Drop credentials and stop any provider-side polling. */
194
+ disconnect(): Promise<void>;
195
+ /**
196
+ * Fetch one fresh account-level snapshot. Empty `metrics: []` is
197
+ * fine when the provider has no billing surface yet — the capability
198
+ * stays `true` for the per-session bridge.
199
+ */
200
+ fetch(): Promise<UsageSnapshot>;
201
+ /**
202
+ * Recommended poll cadence (seconds). Host clamps to its bounds.
203
+ * Optional — host falls back to its own default if unset.
204
+ */
205
+ recommendedPollIntervalSec?: number;
206
+ /**
207
+ * Translate the loose {@link AgentContextUsage} the manager pulls from
208
+ * `backend.getContextUsage()` into canonical {@link UsageMetric}[].
209
+ *
210
+ * Pure function (no side effects) — provider knows its own SDK's
211
+ * shape and lifts it. Empty array OK when the raw bag has nothing
212
+ * useful (e.g. fresh session before any message lands).
213
+ *
214
+ * Optional. Providers without per-session token visibility omit it
215
+ * and the chip skips the per-session row.
216
+ */
217
+ formatSessionMetrics?(raw: AgentContextUsage): UsageMetric[];
218
+ };
219
+ //# sourceMappingURL=usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
package/dist/usage.js ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Usage / billing capability — provider-owned data flow.
3
+ *
4
+ * Each provider knows how to talk to its own billing surface (Claude
5
+ * cookie API for Pro/Max, OpenAI usage endpoints for Codex, Gemini's
6
+ * Google Cloud quotas, …) and how to map its SDK's per-message token
7
+ * counts into a canonical shape. The host runs a generic poll loop and
8
+ * a generic chip — it never special-cases any provider.
9
+ *
10
+ * Two surfaces:
11
+ *
12
+ * - `fetch()` for **account-level** snapshots. Pulled by a host poller
13
+ * on `recommendedPollIntervalSec` cadence (clamped to host bounds).
14
+ * What "account" means is provider-defined: Claude → org, Codex →
15
+ * OpenAI project, Gemini → Cloud Billing project.
16
+ *
17
+ * - `formatSessionMetrics()` for **per-session** translation. The
18
+ * manager already calls `backend.getContextUsage()` after every
19
+ * `assistant` message; that returns a loose `AgentContextUsage`
20
+ * bag. This hook lets the provider lift it into canonical
21
+ * {@link UsageMetric}[] without the host trying to interpret
22
+ * provider-specific fields.
23
+ *
24
+ * Single source of truth: the provider. Host plumbs, never decodes.
25
+ *
26
+ * Optional everywhere — providers without billing visibility (Codex
27
+ * without admin keys, Gemini without OAuth) leave `fetch()` returning
28
+ * an empty `metrics: []`. The capability flag stays `true` if the
29
+ * provider can format per-session metrics, `false` if it has nothing
30
+ * to say at all.
31
+ */
32
+ export {};
33
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottimis/jack-provider-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Plugin contract for AI provider integrations in Jack — backend interface, capability matrix, spawner primitives, knowledge context. Consumed both by in-tree providers and external packages.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,12 +29,14 @@
29
29
  "clean": "rm -rf dist"
30
30
  },
31
31
  "peerDependencies": {
32
- "@ottimis/jack-chat-core": ">=0.5.5"
32
+ "@ottimis/jack-chat-core": ">=0.5.5",
33
+ "zod": ">=3.22.0"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@ottimis/jack-chat-core": "^0.5.5",
36
37
  "@types/node": "^22.14.1",
37
38
  "tsx": "^4.19.2",
38
- "typescript": "^5.8.3"
39
+ "typescript": "^5.8.3",
40
+ "zod": "^4.3.6"
39
41
  }
40
42
  }
package/src/backend.ts CHANGED
@@ -65,10 +65,18 @@ export type AgentSystemPrompt =
65
65
  | { type: 'preset'; preset: string; append?: string }
66
66
 
67
67
  /**
68
- * MCP server configuration opaque to the host. The host never inspects
69
- * the inner shape; each provider validates / consumes its own format.
68
+ * MCP server configuration handed to the provider via
69
+ * {@link AgentQueryOptions.mcpServers}. Mirrors the official MCP wire
70
+ * format (the same shape Anthropic, OpenAI, and Google all consume).
71
+ *
72
+ * Replaces the legacy opaque `AgentMcpServerConfig = unknown` so the
73
+ * type system enforces the contract end-to-end and the host can inspect
74
+ * the bag for telemetry / preview without double-translating.
70
75
  */
71
- export type AgentMcpServerConfig = unknown
76
+ export type McpServerSpec =
77
+ | { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
78
+ | { type: 'http'; url: string; headers?: Record<string, string> }
79
+ | { type: 'sse'; url: string; headers?: Record<string, string> }
72
80
 
73
81
  /** Reasoning-effort knob. Provider-validated; not all providers honor every value. */
74
82
  export type AgentEffortLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max'
@@ -161,7 +169,7 @@ export type AgentQueryOptions = {
161
169
  * knowledge sources).
162
170
  */
163
171
  additionalDirectories?: string[]
164
- mcpServers?: Record<string, AgentMcpServerConfig>
172
+ mcpServers?: Record<string, McpServerSpec>
165
173
  resume?: string
166
174
  /**
167
175
  * Initial model for the spawn. Live switches use
@@ -231,16 +239,24 @@ export interface AgentSession extends AsyncIterable<NormalizedMessage> {
231
239
  */
232
240
  setModel(model?: string): Promise<void>
233
241
  /**
234
- * Merge arbitrary settings into the flag-settings layer at runtime.
235
- * Used for values the provider exposes only as persisted flags
236
- * (notably `effortLevel` on Claude).
242
+ * Switch the reasoning-effort tier live, without respawning the child
243
+ * process. Pass `undefined` to clear any override and let the provider
244
+ * fall back to its default. Gated by `CapabilityMatrix.liveEffortSwitch`
245
+ * — providers without live switching declare `false` and the renderer
246
+ * hides the inline Effort dropdown.
247
+ *
248
+ * Replaces the legacy `applyFlagSettings({ effortLevel })` bag — Claude
249
+ * was the only producer and Codex/Gemini both threw `UNSUPPORTED`. The
250
+ * host now calls this method by name and the type system tells the
251
+ * provider author exactly what to wire.
237
252
  */
238
- applyFlagSettings(settings: Record<string, unknown>): Promise<void>
253
+ setEffortLevel(effort: AgentEffortLevel | undefined): Promise<void>
239
254
  /**
240
- * Read the effective merged settings layer. The response shape is
241
- * `{ effective, sources }` where `effective` is the deep-merged result.
242
- * The host uses it to discover the runtime `effortLevel` the provider
243
- * booted with.
255
+ * Read the effective runtime settings the provider booted with. Today
256
+ * the host only consumes `effective.effortLevel` to populate the
257
+ * Effort dropdown's initial value; the rest of the bag is opaque so
258
+ * providers with richer settings layers can passthrough additional
259
+ * keys without an SDK bump.
244
260
  */
245
261
  getSettings(): Promise<AgentSettingsResponse>
246
262
  }
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@
24
24
  export * from './backend'
25
25
  export * from './spawner'
26
26
  export * from './provider'
27
+ export * from './usage'
27
28
 
28
29
  /**
29
30
  * Re-export of `NormalizedMessage` from chat-core so consumers don't need
package/src/provider.ts CHANGED
@@ -16,7 +16,9 @@
16
16
  * it free of provider-specific imports.
17
17
  */
18
18
 
19
- import type { AgentBackend, AgentQueryOptions } from './backend'
19
+ import type { AgentBackend, AgentQueryOptions, McpServerSpec } from './backend'
20
+ import type { UsageApi } from './usage'
21
+ import type { ZodType } from 'zod'
20
22
  import type {
21
23
  ClientToolHandler,
22
24
  NormalizedMessage,
@@ -28,20 +30,48 @@ import type {
28
30
  export type ProviderId = string
29
31
 
30
32
  /**
31
- * Slash-command definition surfaced by a provider. Different providers
32
- * source these differently Claude scans `.claude/commands/` and
33
- * declares a fixed list of CLI builtins; Codex / Gemini have their own
34
- * conventions or none at all. The rendered shape is the same.
33
+ * Where the provider sourced a slash command from. Drives the
34
+ * {@link SlashCommandDef} discriminated union below file-sourced
35
+ * commands carry `body` + `filePath`, builtin and wire-sourced ones
36
+ * don't (they don't *have* a markdown file behind them).
35
37
  */
36
- export type SlashCommandDef = {
38
+ export type SlashCommandScope = 'builtin' | 'wire' | 'user' | 'project' | (string & {})
39
+
40
+ /**
41
+ * Common surface every slash command def carries regardless of source.
42
+ * The renderer uses these for autocomplete + chip rendering.
43
+ */
44
+ type SlashCommandDefBase = {
37
45
  name: string
38
- scope: 'user' | 'project' | 'builtin'
39
46
  description?: string
40
47
  argumentHint?: string
41
- body: string
42
- filePath: string
43
48
  }
44
49
 
50
+ /**
51
+ * Slash-command definition surfaced by a provider. Three sources can
52
+ * coexist (see {@link SlashCommandSupport}):
53
+ *
54
+ * - `'builtin'` — static catalog the runtime intercepts. The renderer
55
+ * never opens the file (there is none); the executor is the agent.
56
+ * - `'wire'` — pushed live by the agent over the wire (Gemini ACP
57
+ * `available_commands_update`). Same render contract as builtin —
58
+ * no on-disk artifact.
59
+ * - `'user' | 'project'` — file-based commands the user authored
60
+ * (Claude `.claude/commands/foo.md`, future per-provider analogs).
61
+ * `filePath` + `body` are required so the renderer can offer "open
62
+ * in editor" affordances and the host can expand `$ARGUMENTS` /
63
+ * `$N` placeholders.
64
+ *
65
+ * Discriminated by `scope` so consumers narrow before reading the
66
+ * file-only fields. Replaces the legacy uniform shape that forced
67
+ * builtin/wire commands to ship synthetic empty `body: ''` /
68
+ * `filePath: ''`.
69
+ */
70
+ export type SlashCommandDef =
71
+ | (SlashCommandDefBase & { scope: 'builtin' })
72
+ | (SlashCommandDefBase & { scope: 'wire' })
73
+ | (SlashCommandDefBase & { scope: 'user' | 'project'; body: string; filePath: string })
74
+
45
75
  /**
46
76
  * Parsed envelope a provider's CLI may wrap slash commands in when it logs
47
77
  * them into the session transcript. Claude uses
@@ -212,6 +242,14 @@ export type CapabilityMatrix = {
212
242
  resumeSession: boolean
213
243
  /** Switch model live without respawn (Claude control request `set_model`). */
214
244
  liveModelSwitch: boolean
245
+ /**
246
+ * Switch reasoning-effort tier live without respawn. Drives whether the
247
+ * inline Effort dropdown fires `setEffortLevel()` (true) or requires a
248
+ * spawn-time setting (false → dropdown hidden / annotated). Decoupled
249
+ * from `liveModelSwitch` because Codex has live model but spawn-time
250
+ * effort.
251
+ */
252
+ liveEffortSwitch: boolean
215
253
  /** Switch permission mode live without respawn. */
216
254
  livePermissionModeSwitch: boolean
217
255
  /**
@@ -228,6 +266,13 @@ export type CapabilityMatrix = {
228
266
  * renderer hides it and only shows the post-fact audit log.
229
267
  */
230
268
  permissionGranularity: 'callback' | 'sandbox-only'
269
+ /**
270
+ * Provider exposes a usage / billing surface (account-level snapshot
271
+ * via `provider.usage.fetch()` and/or per-session metric translation
272
+ * via `formatSessionMetrics()`). When `false`, the chip hides the
273
+ * usage bars and no Connect affordance is offered.
274
+ */
275
+ usage: boolean
231
276
  }
232
277
 
233
278
  /**
@@ -353,17 +398,19 @@ export type PrepareSpawnContext = {
353
398
  }
354
399
 
355
400
  /**
356
- * MCP server registration in canonical Anthropic spec shape. Used by
357
- * {@link KnowledgeContext.mcpServers} as the cross-provider exchange format
358
- * for `kind=mcp` knowledge sources. Each provider's
359
- * {@link JackProvider.applyKnowledgeContext} translates this into whatever
360
- * its native runtime expects (Claude SDK `mcpServers` map; Codex
361
- * `mcp_servers.toml`; …).
401
+ * MCP server registration in canonical wire-format shape. Same type
402
+ * used at both ends of the knowledge pipeline: as
403
+ * {@link KnowledgeContext.mcpServers} (input to the provider) and as
404
+ * {@link AgentQueryOptions.mcpServers} (output from
405
+ * {@link JackProvider.applyKnowledgeContext}). Each provider's
406
+ * applyKnowledgeContext translates the merged context into its native
407
+ * runtime layout (Claude SDK `mcpServers` map; Codex `mcp_servers.toml`;
408
+ * Gemini ACP `session/new { mcpServers }`).
409
+ *
410
+ * Re-exported as `McpServerSpec` from `./backend` — same type, two names
411
+ * for ergonomics in different code paths.
362
412
  */
363
- export type KnowledgeMcpResolution =
364
- | { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
365
- | { type: 'http'; url: string; headers?: Record<string, string> }
366
- | { type: 'sse'; url: string; headers?: Record<string, string> }
413
+ export type KnowledgeMcpResolution = McpServerSpec
367
414
 
368
415
  /**
369
416
  * Provider-neutral container for everything the host has computed about the
@@ -400,6 +447,24 @@ export type KnowledgeContext = {
400
447
  * names the renderer maps to React components. Providers that don't
401
448
  * declare branding fall back to neutral defaults.
402
449
  */
450
+ /**
451
+ * Curated icon catalog keys the renderer knows how to map to lucide React
452
+ * components. Hybrid closed/open: well-known values get autocomplete;
453
+ * arbitrary strings still type-check (the renderer falls back to a default
454
+ * icon for unknown keys, so a provider can ship a forward-looking key
455
+ * without breaking older hosts).
456
+ */
457
+ export type ProviderIconKey =
458
+ | 'sparkles'
459
+ | 'cpu'
460
+ | 'gem'
461
+ | 'bot'
462
+ | 'brain'
463
+ | 'star'
464
+ | 'wand'
465
+ | 'zap'
466
+ | (string & {})
467
+
403
468
  export type ProviderBranding = {
404
469
  /**
405
470
  * Primary accent color. Used as a subtle border on the chat composer +
@@ -412,12 +477,13 @@ export type ProviderBranding = {
412
477
  */
413
478
  accentColor: string
414
479
  /**
415
- * Curated icon key — one of the names the renderer maps to a lucide
416
- * component. Keeping this an enum (instead of free-form SVG/asset)
417
- * means providers don't ship rendering assets and the host stays in
418
- * control of what shapes can land in the UI.
480
+ * Curated icon key — one of {@link ProviderIconKey}. Keeping this a
481
+ * closed/open enum (instead of free-form SVG/asset) means providers
482
+ * don't ship rendering assets and the host stays in control of what
483
+ * shapes can land in the UI. Unknown keys fall back to a default icon
484
+ * in the renderer.
419
485
  */
420
- iconKey?: string
486
+ iconKey?: ProviderIconKey
421
487
  }
422
488
 
423
489
  export type JackProvider = {
@@ -572,16 +638,10 @@ export type JackProvider = {
572
638
  *
573
639
  * Pattern A providers (Claude, Codex) leave this undefined; the host
574
640
  * detects the pattern by absence and skips wiring.
575
- *
576
- * `ctx` carries the host's correlation ids the provider may want to
577
- * bridge to wire-driven side channels (e.g. mapping
578
- * `available_commands_update` notifications back to the renderer's
579
- * slash command store via the host's session id). Optional for
580
- * providers that don't need it.
581
641
  */
582
642
  attachClientToolHandler?(
583
643
  handler: ClientToolHandler,
584
- ctx?: { jackSessionId?: string }
644
+ ctx: ClientToolHandlerAttachContext
585
645
  ): void
586
646
  /**
587
647
  * Persisted permission rules manager. The host's
@@ -590,6 +650,14 @@ export type JackProvider = {
590
650
  * leave it undefined and the host returns empty snapshots.
591
651
  */
592
652
  persistedPermissions?: PersistedPermissionsApi
653
+ /**
654
+ * Usage / billing capability — provider-owned data flow. See
655
+ * {@link UsageApi}. Optional; when undefined the chip degrades to
656
+ * showing nothing (and `capabilities.usage` MUST be `false`). The
657
+ * provider stays the single source of truth: host plumbs, never
658
+ * decodes.
659
+ */
660
+ usage?: UsageApi
593
661
  }
594
662
 
595
663
  /**
@@ -608,6 +676,34 @@ export type InProcessMcpServerSpec = {
608
676
  tools: InProcessMcpToolSpec[]
609
677
  }
610
678
 
679
+ /**
680
+ * Context the host hands to {@link JackProvider.attachClientToolHandler}
681
+ * so the provider can bridge wire-driven side channels back to the host
682
+ * (e.g. mapping Gemini's `available_commands_update` notifications to
683
+ * the renderer's per-session slash command store).
684
+ *
685
+ * Today only `sessionId` is consumed. `actorId` is reserved for the
686
+ * future team-tier multi-user mode (north-star: every entity carries an
687
+ * actor id so coordination scales beyond single-user). Adding a new
688
+ * required field here would be a major bump; new optional fields ride
689
+ * on a minor.
690
+ */
691
+ export type ClientToolHandlerAttachContext = {
692
+ /**
693
+ * Host correlation id for the session being spawned. Required —
694
+ * the provider stores it on its per-spawn slot so wire notifications
695
+ * can route back to the right host-side consumer.
696
+ */
697
+ sessionId: string
698
+ /**
699
+ * Actor identity placeholder for future multi-user / team-tier
700
+ * support. Today the host always passes `'self'` (or omits) since
701
+ * Jack runs single-user; future remote-agent flows will populate
702
+ * with `'user_xxx@team_yyy'` style strings.
703
+ */
704
+ actorId?: string
705
+ }
706
+
611
707
  /**
612
708
  * Behaviour token the provider persists alongside each rule. Mirror of
613
709
  * Claude's `permissions.{allow,deny,ask}` arrays — providers with a
@@ -624,17 +720,33 @@ export type PermissionBehavior = 'allow' | 'deny' | 'ask'
624
720
  export type PermissionSource = 'user' | 'userLocal' | 'project' | 'projectLocal'
625
721
 
626
722
  /**
627
- * One persisted rule as the provider stores it. `tool` + `pattern` are a
628
- * convenience parse `raw` is the source of truth for round-trip writes
629
- * (remove/add use the raw string verbatim).
723
+ * Optional human-readable parse hint for {@link PermissionRule}. Providers
724
+ * whose rule grammar has a recognisable "tool" + "pattern" decomposition
725
+ * (Claude's `Bash(npm install)`, `Edit(*.ts)`) populate this so the UI can
726
+ * render two columns instead of a raw string. Providers with a different
727
+ * grammar (Codex `approval_policy` keyed by command prefix) leave it
728
+ * undefined; the UI falls back to displaying `raw`.
729
+ */
730
+ export type PermissionRuleHumanReadable = {
731
+ /** Best-effort tool name extracted by the provider (e.g. `Bash`, `Edit`). */
732
+ tool?: string
733
+ /** Best-effort pattern extracted by the provider (the bit inside the parens, etc.). */
734
+ pattern?: string
735
+ }
736
+
737
+ /**
738
+ * One persisted rule as the provider stores it. `raw` is the only
739
+ * field guaranteed across providers — it's the source of truth for
740
+ * round-trip writes (remove/add use the raw string verbatim) and the
741
+ * fallback display when no parse hint is available. The
742
+ * `humanReadable` sidecar is a Claude-style ergonomic split that
743
+ * other providers may opt out of.
630
744
  */
631
745
  export type PermissionRule = {
632
- /** Tool name (e.g. `Bash`, `Edit`, `mcp__figma__authenticate`). */
633
- tool: string
634
- /** Content inside the parens (the glob / pattern), or null if the rule has no parens. */
635
- pattern: string | null
636
- /** Original string as stored in the settings file — source of truth for round-trip writes. */
746
+ /** Original string as stored by the provider — source of truth for round-trip writes. */
637
747
  raw: string
748
+ /** Optional parse hint for two-column UI rendering. */
749
+ humanReadable?: PermissionRuleHumanReadable
638
750
  }
639
751
 
640
752
  export type PermissionsSourceBlock = {
@@ -687,12 +799,17 @@ export type InProcessMcpToolSpec = {
687
799
  name: string
688
800
  description: string
689
801
  /**
690
- * Zod schema for the tool arguments. Providers that don't speak zod
691
- * natively can call `.shape` to inspect fields. We keep zod here
692
- * instead of JSON Schema because the host already uses it everywhere
693
- * (one source of truth for tool shapes).
802
+ * Zod schema for the tool arguments a `Record<fieldName, ZodType>`
803
+ * (zod's "shape" form, what `z.object(...)` accepts). Provider
804
+ * implementations consume it via the SDK helper of their choice
805
+ * (Claude wraps with `tool(name, desc, schema, handler)` from
806
+ * `@anthropic-ai/claude-agent-sdk`).
807
+ *
808
+ * `zod` is a peer dep of this SDK so consumer + provider type-check
809
+ * against the same instance. The host always produces zod; trying
810
+ * to stuff JSON Schema here would silently break Claude's wrapper.
694
811
  */
695
- schema: Record<string, unknown>
812
+ schema: Record<string, ZodType>
696
813
  handler: (args: Record<string, unknown>) => Promise<{
697
814
  content: Array<{ type: 'text'; text: string }>
698
815
  isError?: boolean