lumiverse-spindle-types 0.4.21 → 0.4.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lumiverse-spindle-types",
3
- "version": "0.4.21",
3
+ "version": "0.4.25",
4
4
  "types": "./src/index.ts",
5
5
  "keywords": [
6
6
  "lumiverse",
package/src/api.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { SpindleManifest } from "./manifest";
2
+ import type { CouncilMemberContext } from "./council";
2
3
 
3
4
  // ─── DTO types for messages ──────────────────────────────────────────────
4
5
 
@@ -66,8 +67,67 @@ export interface GenerationRequestDTO {
66
67
  * is inferred from the extension owner and can be omitted.
67
68
  */
68
69
  userId?: string;
70
+ /**
71
+ * Optional `AbortSignal` to cancel an in-flight generation. When the
72
+ * signal fires, the upstream LLM HTTP request is torn down and the
73
+ * returned promise rejects with an `AbortError` (`err.name === "AbortError"`).
74
+ *
75
+ * The signal is consumed inside the extension worker and never crosses
76
+ * the host boundary — it is stripped before the RPC message is posted.
77
+ * The worker notifies the host via an internal `cancel_generation`
78
+ * message so the host can abort the in-flight request.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * const controller = new AbortController()
83
+ * const timer = setTimeout(() => controller.abort(), 10_000)
84
+ * try {
85
+ * const result = await spindle.generate.raw({
86
+ * provider: "openai",
87
+ * model: "gpt-4o-mini",
88
+ * messages: [{ role: "user", content: "hello" }],
89
+ * signal: controller.signal,
90
+ * })
91
+ * } catch (err) {
92
+ * if (err instanceof Error && err.name === "AbortError") {
93
+ * // user/timeout cancelled — not an error condition
94
+ * }
95
+ * } finally {
96
+ * clearTimeout(timer)
97
+ * }
98
+ * ```
99
+ */
100
+ signal?: AbortSignal;
69
101
  }
70
102
 
103
+ /**
104
+ * Streamed chunk yielded by `spindle.generate.rawStream()` and
105
+ * `spindle.generate.quietStream()`.
106
+ *
107
+ * The stream emits one or more `token` / `reasoning` chunks and then
108
+ * exactly one terminal `done` chunk carrying the aggregated response.
109
+ * If the stream fails or is aborted, the async generator rejects instead
110
+ * of emitting `done`.
111
+ */
112
+ export type StreamChunkDTO =
113
+ /** Incremental content token. */
114
+ | { type: "token"; token: string }
115
+ /** Incremental chain-of-thought / reasoning token. */
116
+ | { type: "reasoning"; token: string }
117
+ /** Terminal chunk — emitted exactly once, on successful completion. */
118
+ | {
119
+ type: "done";
120
+ content: string;
121
+ reasoning?: string;
122
+ finish_reason: string;
123
+ tool_calls?: ToolCallDTO[];
124
+ usage?: {
125
+ prompt_tokens: number;
126
+ completion_tokens: number;
127
+ total_tokens: number;
128
+ };
129
+ };
130
+
71
131
  export interface RequestInitDTO {
72
132
  method?: string;
73
133
  headers?: Record<string, string>;
@@ -887,6 +947,29 @@ export interface MessageSwipedPayloadDTO {
887
947
  previousSwipeId?: number;
888
948
  }
889
949
 
950
+ /**
951
+ * Payload delivered to `spindle.on("TOOL_INVOCATION", ...)` handlers.
952
+ *
953
+ * Fires whenever an extension-registered tool is invoked by Lumiverse. Handlers
954
+ * must return a string (or promise thereof) with the tool's result — the host
955
+ * coerces `undefined` / `null` to an empty string.
956
+ *
957
+ * `councilMember` is populated when the invocation originates from a council
958
+ * execution cycle, providing the assigned member's identity, role, chance,
959
+ * avatar URL, and Lumia personality fields. It is `undefined` for all other
960
+ * invocation paths.
961
+ */
962
+ export interface ToolInvocationPayloadDTO {
963
+ /** The bare (unqualified) tool name, matching what was passed to `registerTool`. */
964
+ toolName: string;
965
+ /** Arguments delivered to the tool. Shape depends on the tool's JSON Schema. */
966
+ args: Record<string, unknown>;
967
+ /** Host-side correlation id for this invocation. */
968
+ requestId: string;
969
+ /** Council member snapshot when invoked via council — otherwise `undefined`. */
970
+ councilMember?: CouncilMemberContext;
971
+ }
972
+
890
973
  /**
891
974
  * Observer handle returned by `spindle.generate.observe()`.
892
975
  * Provides a high-level API for watching an in-flight generation on a
@@ -924,6 +1007,18 @@ export type WorkerToHost =
924
1007
  | { type: "register_tool"; tool: ToolRegistrationDTO }
925
1008
  | { type: "unregister_tool"; name: string }
926
1009
  | { type: "request_generation"; requestId: string; input: GenerationRequestDTO }
1010
+ /**
1011
+ * Start a streaming generation. The host responds asynchronously with
1012
+ * one or more `generation_stream_chunk` messages, terminating with a
1013
+ * `done` chunk on success or a `generation_stream_error` on failure.
1014
+ */
1015
+ | { type: "request_generation_stream"; requestId: string; input: GenerationRequestDTO }
1016
+ /**
1017
+ * Cancel an in-flight generation started via `request_generation` or
1018
+ * `request_generation_stream`. `requestId` matches the original request.
1019
+ * The host aborts the upstream LLM fetch and responds with an `AbortError`.
1020
+ */
1021
+ | { type: "cancel_generation"; requestId: string }
927
1022
  | { type: "storage_read"; requestId: string; path: string }
928
1023
  | { type: "storage_write"; requestId: string; path: string; data: string }
929
1024
  | { type: "storage_read_binary"; requestId: string; path: string }
@@ -1230,6 +1325,15 @@ export type HostToWorker =
1230
1325
  requestId: string;
1231
1326
  toolName: string;
1232
1327
  args: Record<string, unknown>;
1328
+ /**
1329
+ * Populated when the invocation originates from a council execution
1330
+ * cycle — carries the assigned council member's identity, role, chance,
1331
+ * avatar URL, and Lumia personality fields so the extension can tailor
1332
+ * its tool pipeline to the member on whose behalf it is running.
1333
+ *
1334
+ * Undefined for non-council invocation paths.
1335
+ */
1336
+ councilMember?: CouncilMemberContext;
1233
1337
  }
1234
1338
  | { type: "shutdown" }
1235
1339
  | { type: "frontend_message"; payload: unknown; userId: string }
@@ -1243,4 +1347,16 @@ export type HostToWorker =
1243
1347
  commandId: string;
1244
1348
  context: SpindleCommandContextDTO;
1245
1349
  userId: string;
1246
- };
1350
+ }
1351
+ /**
1352
+ * One streamed chunk for a generation started via
1353
+ * `request_generation_stream`. Multiple `token` / `reasoning` chunks
1354
+ * may arrive, terminating with exactly one `done` chunk on success.
1355
+ */
1356
+ | { type: "generation_stream_chunk"; requestId: string; chunk: StreamChunkDTO }
1357
+ /**
1358
+ * Terminal failure for a generation started via
1359
+ * `request_generation_stream`. Mutually exclusive with the `done`
1360
+ * chunk in `generation_stream_chunk`. Aborts surface here too.
1361
+ */
1362
+ | { type: "generation_stream_error"; requestId: string; error: string };
package/src/council.ts CHANGED
@@ -77,6 +77,47 @@ export interface CouncilSettings {
77
77
  toolsSettings: CouncilToolsSettings;
78
78
  }
79
79
 
80
+ // ---- Tool Invocation Context ----
81
+
82
+ /**
83
+ * Personality snapshot of the council member that triggered a tool invocation.
84
+ *
85
+ * Delivered to extension `TOOL_INVOCATION` handlers when an extension-provided
86
+ * council tool is executed as part of a council cycle. Allows extensions to
87
+ * personalise their tool pipeline with the assigned member's identity, role,
88
+ * and Lumia personality fields.
89
+ *
90
+ * This field is optional — it is only populated when the tool is invoked via
91
+ * the council execution path. Tools invoked outside council (e.g. future
92
+ * inline function calling) will not see this context.
93
+ */
94
+ export interface CouncilMemberContext {
95
+ /** Unique council member id (council settings row id). */
96
+ memberId: string;
97
+ /** Source Lumia item id this member is backed by. */
98
+ itemId: string;
99
+ /** Pack id the Lumia item lives in. */
100
+ packId: string;
101
+ /** Pack name the Lumia item lives in. */
102
+ packName: string;
103
+ /** Display name of the Lumia item (also used as the member name). */
104
+ name: string;
105
+ /** Freeform role description assigned by the user (e.g. "Plot Enforcer"). */
106
+ role: string;
107
+ /** Probability (0–100) that this member participates each generation. */
108
+ chance: number;
109
+ /** Relative URL to the member's avatar (e.g. `/api/v1/images/{id}`), or null. */
110
+ avatarUrl: string | null;
111
+ /** Lumia item "definition" field — physical/identity description. */
112
+ definition: string;
113
+ /** Lumia item "personality" field. */
114
+ personality: string;
115
+ /** Lumia item "behavior" field — behavioural patterns. */
116
+ behavior: string;
117
+ /** Gender identity marker (0=unspecified, 1=feminine, 2=masculine). */
118
+ genderIdentity: 0 | 1 | 2;
119
+ }
120
+
80
121
  // ---- Execution Results ----
81
122
 
82
123
  /** Result of a single tool invocation for a single member. */
package/src/index.ts CHANGED
@@ -20,6 +20,7 @@ export type {
20
20
  ToolSchemaDTO,
21
21
  ToolCallDTO,
22
22
  GenerationRequestDTO,
23
+ StreamChunkDTO,
23
24
  RequestInitDTO,
24
25
  ConnectionProfileDTO,
25
26
  PermissionDeniedDetail,
@@ -70,6 +71,7 @@ export type {
70
71
  ChatMessageDTO,
71
72
  MessageSwipeAction,
72
73
  MessageSwipedPayloadDTO,
74
+ ToolInvocationPayloadDTO,
73
75
  WorkerToHost,
74
76
  HostToWorker,
75
77
  } from "./api";
@@ -113,6 +115,7 @@ export type { SpindleAPI } from "./spindle-api";
113
115
 
114
116
  export type {
115
117
  CouncilMember,
118
+ CouncilMemberContext,
116
119
  SidecarConfig,
117
120
  CouncilSidecarConfig,
118
121
  CouncilToolsSettings,
@@ -45,6 +45,8 @@ import type {
45
45
  GenerationStoppedPayloadDTO,
46
46
  GenerationObserver,
47
47
  MessageSwipedPayloadDTO,
48
+ ToolInvocationPayloadDTO,
49
+ StreamChunkDTO,
48
50
  } from "./api";
49
51
 
50
52
  /** The global `spindle` object available in backend extension workers */
@@ -63,6 +65,21 @@ export interface SpindleAPI {
63
65
  * `swipeId` identifies which slot the event concerns.
64
66
  */
65
67
  on(event: "MESSAGE_SWIPED", handler: (payload: MessageSwipedPayloadDTO, userId?: string) => void): () => void;
68
+ /**
69
+ * Receive invocations for tools registered via {@link SpindleAPI.registerTool}.
70
+ *
71
+ * The handler must return the tool's textual result (or a promise thereof).
72
+ * Undefined / null returns are coerced to an empty string by the host.
73
+ *
74
+ * When the invocation originates from a council execution cycle, the payload
75
+ * includes a `councilMember` snapshot describing the assigned member
76
+ * (identity, role, chance, avatar URL, and Lumia personality fields) so the
77
+ * extension can tailor its tool output to that member's voice.
78
+ */
79
+ on(
80
+ event: "TOOL_INVOCATION",
81
+ handler: (payload: ToolInvocationPayloadDTO) => string | Promise<string> | void | Promise<void>
82
+ ): () => void;
66
83
  /** Subscribe to a Lumiverse event. */
67
84
  on(event: string, handler: (payload: unknown, userId?: string) => void): () => void;
68
85
 
@@ -102,11 +119,76 @@ export interface SpindleAPI {
102
119
  /** Unregister an LLM tool */
103
120
  unregisterTool(name: string): void;
104
121
 
105
- /** Generation helpers */
122
+ /**
123
+ * Generation helpers.
124
+ *
125
+ * All three entry points (`raw`, `quiet`, `batch`) accept a standard
126
+ * `AbortSignal` via `input.signal`. Aborting the signal tears down the
127
+ * upstream LLM HTTP request and rejects the returned promise with an
128
+ * `AbortError` (`err.name === "AbortError"`). This is the same pattern
129
+ * `fetch()` uses, so it composes with `AbortSignal.timeout()` and
130
+ * `AbortSignal.any([...])`.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const controller = new AbortController()
135
+ * const result = spindle.generate.raw({
136
+ * provider: "openai",
137
+ * model: "gpt-4o-mini",
138
+ * messages,
139
+ * signal: controller.signal,
140
+ * })
141
+ * // Cancel from elsewhere — e.g. user closed the panel
142
+ * controller.abort()
143
+ * ```
144
+ */
106
145
  generate: {
107
146
  raw(input: GenerationRequestDTO): Promise<unknown>;
108
147
  quiet(input: GenerationRequestDTO): Promise<unknown>;
109
148
  batch(input: GenerationRequestDTO): Promise<unknown>;
149
+ /**
150
+ * Streaming variant of {@link raw}. Returns an async generator that
151
+ * yields incremental {@link StreamChunkDTO} values:
152
+ *
153
+ * - `{ type: 'token', token }` — content chunk
154
+ * - `{ type: 'reasoning', token }` — chain-of-thought chunk
155
+ * - `{ type: 'done', ... }` — final aggregated response (emitted exactly once)
156
+ *
157
+ * Tool-call deltas, finish reason, and token usage live on the terminal
158
+ * `done` chunk. If the upstream call fails or the request is aborted
159
+ * via `input.signal`, the generator rejects with the underlying error
160
+ * (`AbortError` for cancellations).
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const ctrl = new AbortController()
165
+ * setTimeout(() => ctrl.abort(), 30_000)
166
+ * try {
167
+ * for await (const chunk of spindle.generate.rawStream({
168
+ * provider: 'openai',
169
+ * model: 'gpt-4o-mini',
170
+ * messages,
171
+ * signal: ctrl.signal,
172
+ * })) {
173
+ * if (chunk.type === 'token') process.stdout.write(chunk.token)
174
+ * else if (chunk.type === 'done') usage = chunk.usage
175
+ * }
176
+ * } catch (err) {
177
+ * if (err instanceof Error && err.name === 'AbortError') return
178
+ * throw err
179
+ * }
180
+ * ```
181
+ */
182
+ rawStream(input: GenerationRequestDTO): AsyncGenerator<StreamChunkDTO, void, void>;
183
+ /**
184
+ * Streaming variant of {@link quiet}. Same chunk schema and abort
185
+ * semantics as {@link rawStream}.
186
+ *
187
+ * Note: streaming is not exposed for `batch` — compose multiple
188
+ * `rawStream` / `quietStream` calls yourself if you need parallel
189
+ * streamed responses.
190
+ */
191
+ quietStream(input: GenerationRequestDTO): AsyncGenerator<StreamChunkDTO, void, void>;
110
192
  /** Run a dry-run prompt assembly without calling the LLM. */
111
193
  dryRun(input: DryRunRequestDTO, userId?: string): Promise<DryRunResultDTO>;
112
194
  /**