lumiverse-spindle-types 0.4.37 → 0.4.38

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.37",
3
+ "version": "0.4.38",
4
4
  "types": "./src/index.ts",
5
5
  "keywords": [
6
6
  "lumiverse",
package/src/api.ts CHANGED
@@ -67,6 +67,112 @@ export interface MacroResolveResultDTO {
67
67
  diagnostics: Array<{ message: string; offset: number; length: number }>;
68
68
  }
69
69
 
70
+ // ─── Macro Interceptor (permission: "macro_interceptor") ────────────────
71
+
72
+ /**
73
+ * Where a macro evaluation originated. Useful for interceptors that only
74
+ * want to fire for certain call sites (e.g. prompt assembly vs. response
75
+ * post-processing vs. display-time resolution).
76
+ */
77
+ export type MacroInterceptorPhase =
78
+ | "prompt"
79
+ | "display"
80
+ | "response"
81
+ | "other";
82
+
83
+ /**
84
+ * Structured-clone snapshot of the live macro evaluation environment,
85
+ * passed to a macro interceptor. All values are read-only copies — mutating
86
+ * them has no effect on the real environment. Persist state via
87
+ * `spindle.variables.*` helpers instead.
88
+ */
89
+ export interface MacroInterceptorEnvDTO {
90
+ readonly commit: boolean;
91
+ readonly names: Record<string, string>;
92
+ readonly character: Record<string, unknown>;
93
+ readonly chat: Record<string, unknown>;
94
+ readonly system: Record<string, unknown>;
95
+ readonly variables: {
96
+ readonly local: Record<string, string>;
97
+ readonly global: Record<string, string>;
98
+ readonly chat: Record<string, string>;
99
+ };
100
+ readonly extra: Record<string, unknown>;
101
+ }
102
+
103
+ /**
104
+ * Context passed to a macro interceptor handler on every iteration of
105
+ * `MacroEvaluator.evaluate()`. The handler receives the current raw
106
+ * template (already transformed by any earlier interceptors in the chain)
107
+ * and returns either a transformed template string or `void` to pass through.
108
+ */
109
+ export interface MacroInterceptorCtxDTO {
110
+ readonly template: string;
111
+ readonly env: MacroInterceptorEnvDTO;
112
+ readonly commit: boolean;
113
+ readonly phase: MacroInterceptorPhase;
114
+ readonly sourceHint?: string;
115
+ /**
116
+ * User ID that initiated the macro resolution (when available). Relevant
117
+ * for operator-scoped extensions that need to route work through other
118
+ * Spindle APIs on that user's behalf.
119
+ */
120
+ readonly userId?: string;
121
+ }
122
+
123
+ /**
124
+ * Return value of a macro interceptor handler.
125
+ * - `string` replaces the template for subsequent interceptors + parsing.
126
+ * - `void` / `undefined` passes the template through unchanged.
127
+ */
128
+ export type MacroInterceptorResultDTO = string | void;
129
+
130
+ // ─── Message Content Processor (permission: "chat_mutation") ───────────
131
+
132
+ /**
133
+ * Which content-write path triggered a message content processor run.
134
+ * `"create"` covers both user-initiated `POST .../messages` writes and
135
+ * auto-inserted greeting rows.
136
+ */
137
+ export type MessageContentProcessorOrigin =
138
+ | "create"
139
+ | "update"
140
+ | "swipe_add"
141
+ | "swipe_update";
142
+
143
+ /**
144
+ * Context passed to a message content processor before a user-initiated
145
+ * message write reaches SQLite. Handlers can inspect this and return a
146
+ * patch (new `content` / merged `extra`) to transform what is stored and
147
+ * what WebSocket subscribers observe on first paint.
148
+ */
149
+ export interface MessageContentProcessorCtxDTO {
150
+ chatId: string;
151
+ /** Undefined for `"create"` origins (the row doesn't exist yet). */
152
+ messageId?: string;
153
+ content: string;
154
+ extra?: Record<string, unknown>;
155
+ origin: MessageContentProcessorOrigin;
156
+ /** Set for `"swipe_update"` only — the zero-based index of the swipe being rewritten. */
157
+ swipeIndex?: number;
158
+ /** Owning user for the write. Pass this through to operator-scoped Spindle calls. */
159
+ userId: string;
160
+ }
161
+
162
+ /**
163
+ * Return value for a message content processor handler. Return `undefined`
164
+ * / `void` to pass through, or a partial patch to modify the write:
165
+ * - `content` (if present) replaces the content for downstream processors
166
+ * and the DB write.
167
+ * - `extra` (if present) shallow-merges into the existing `extra` — keys
168
+ * you omit are preserved. Ignored on swipe origins (swipes share the
169
+ * parent message's `extra`).
170
+ */
171
+ export interface MessageContentProcessorResultDTO {
172
+ content?: string;
173
+ extra?: Record<string, unknown>;
174
+ }
175
+
70
176
  export interface ToolRegistrationDTO {
71
177
  name: string;
72
178
  display_name: string;
@@ -1304,6 +1410,20 @@ export type WorkerToHost =
1304
1410
  requestId: string;
1305
1411
  context: unknown;
1306
1412
  }
1413
+ // ─── Macro Interceptor (gated: "macro_interceptor") ────────────────
1414
+ | { type: "register_macro_interceptor"; priority?: number }
1415
+ | {
1416
+ type: "macro_interceptor_result";
1417
+ requestId: string;
1418
+ result: MacroInterceptorResultDTO;
1419
+ }
1420
+ // ─── Message Content Processor (gated: "chat_mutation") ────────────
1421
+ | { type: "register_message_content_processor"; priority?: number }
1422
+ | {
1423
+ type: "message_content_processor_result";
1424
+ requestId: string;
1425
+ result: MessageContentProcessorResultDTO | void;
1426
+ }
1307
1427
  | {
1308
1428
  type: "macro_result";
1309
1429
  requestId: string;
@@ -1477,6 +1597,16 @@ export type HostToWorker =
1477
1597
  requestId: string;
1478
1598
  context: unknown;
1479
1599
  }
1600
+ | {
1601
+ type: "macro_interceptor_request";
1602
+ requestId: string;
1603
+ ctx: MacroInterceptorCtxDTO;
1604
+ }
1605
+ | {
1606
+ type: "message_content_processor_request";
1607
+ requestId: string;
1608
+ ctx: MessageContentProcessorCtxDTO;
1609
+ }
1480
1610
  | {
1481
1611
  type: "response";
1482
1612
  requestId: string;
package/src/index.ts CHANGED
@@ -81,6 +81,13 @@ export type {
81
81
  TokenModelSourceDTO,
82
82
  TokenCountOptionsDTO,
83
83
  TokenCountResultDTO,
84
+ MacroInterceptorPhase,
85
+ MacroInterceptorEnvDTO,
86
+ MacroInterceptorCtxDTO,
87
+ MacroInterceptorResultDTO,
88
+ MessageContentProcessorOrigin,
89
+ MessageContentProcessorCtxDTO,
90
+ MessageContentProcessorResultDTO,
84
91
  WorkerToHost,
85
92
  HostToWorker,
86
93
  } from "./api";
@@ -4,15 +4,16 @@
4
4
  * Free tier (no declaration needed): events, storage, macros, dom, variables
5
5
  *
6
6
  * Gated tier (must declare):
7
- * - "generation" — fire generations on behalf of user
8
- * - "interceptor" — pre-generation prompt modification
9
- * - "tools" — register LLM tools
10
- * - "cors_proxy" — use CORS proxy
11
- * - "context_handler" — register global context middleware
7
+ * - "generation" — fire generations on behalf of user
8
+ * - "interceptor" — pre-generation prompt modification
9
+ * - "tools" — register LLM tools
10
+ * - "cors_proxy" — use CORS proxy
11
+ * - "context_handler" — register global context middleware
12
12
  * - "generation_parameters" — inject parameters into in-flight generations via interceptors
13
- * - "characters" — CRUD on character cards
14
- * - "chats" — CRUD on chat sessions
15
- * - "personas" — CRUD on personas
13
+ * - "characters" — CRUD on character cards
14
+ * - "chats" — CRUD on chat sessions
15
+ * - "personas" — CRUD on personas
16
+ * - "macro_interceptor" — transform raw templates before macro parsing/dispatch
16
17
  */
17
18
  export type SpindlePermission =
18
19
  | "generation"
@@ -32,7 +33,8 @@ export type SpindlePermission =
32
33
  | "personas"
33
34
  | "push_notification"
34
35
  | "image_gen"
35
- | "generation_parameters";
36
+ | "generation_parameters"
37
+ | "macro_interceptor";
36
38
 
37
39
  export const ALL_PERMISSIONS: readonly SpindlePermission[] = [
38
40
  "generation",
@@ -53,6 +55,7 @@ export const ALL_PERMISSIONS: readonly SpindlePermission[] = [
53
55
  "push_notification",
54
56
  "image_gen",
55
57
  "generation_parameters",
58
+ "macro_interceptor",
56
59
  ] as const;
57
60
 
58
61
  export function isValidPermission(p: string): p is SpindlePermission {
@@ -53,6 +53,10 @@ import type {
53
53
  StreamChunkDTO,
54
54
  TokenCountOptionsDTO,
55
55
  TokenCountResultDTO,
56
+ MacroInterceptorCtxDTO,
57
+ MacroInterceptorResultDTO,
58
+ MessageContentProcessorCtxDTO,
59
+ MessageContentProcessorResultDTO,
56
60
  } from "./api";
57
61
 
58
62
  /** The global `spindle` object available in backend extension workers */
@@ -676,6 +680,82 @@ export interface SpindleAPI {
676
680
  priority?: number
677
681
  ): void;
678
682
 
683
+ /**
684
+ * Register a macro interceptor (permission: `macro_interceptor`).
685
+ *
686
+ * Runs at the top of `MacroEvaluator.evaluate()`, once per fixed-point
687
+ * iteration, before Lumiverse parses the template. Receives the raw
688
+ * template plus a read-only env snapshot and returns either a transformed
689
+ * template or `void` to pass through.
690
+ *
691
+ * Use this when per-macro RPC cost dominates iteration-heavy templates
692
+ * (e.g. `{{#each LARGE_LIST}}…{{my_macro}}…{{/each}}`). For single macros
693
+ * without iteration, prefer {@link SpindleAPI.registerMacro}.
694
+ *
695
+ * Each invocation runs inside a 10-second wall-clock budget on the host.
696
+ * On timeout or thrown error the chain logs the failure and forwards the
697
+ * previous template to the next handler — macro evaluation itself never
698
+ * aborts. A second registration from the same extension replaces the
699
+ * previous handler.
700
+ *
701
+ * @param handler Returns the transformed template, or `void` to pass through.
702
+ * @param priority Lower values run first. Default `100`.
703
+ *
704
+ * @example
705
+ * ```ts
706
+ * spindle.registerMacroInterceptor(async (ctx) => {
707
+ * if (!ctx.template.includes('{{my_macro')) return
708
+ * return resolveInWorker(ctx.template, ctx.env)
709
+ * }, 100)
710
+ * ```
711
+ */
712
+ registerMacroInterceptor(
713
+ handler: (
714
+ ctx: MacroInterceptorCtxDTO
715
+ ) => Promise<MacroInterceptorResultDTO>,
716
+ priority?: number
717
+ ): void;
718
+
719
+ /**
720
+ * Register a message content processor (permission: `chat_mutation`).
721
+ *
722
+ * Runs synchronously inside every user-initiated message-write REST
723
+ * route (create, update, swipe add/update) and the auto-greeting path,
724
+ * before the row reaches SQLite. The returned patch transforms both the
725
+ * stored row and every `MESSAGE_SENT` / `MESSAGE_EDITED` / `MESSAGE_SWIPED`
726
+ * subscriber on first paint.
727
+ *
728
+ * Not invoked for `spindle.chat.*` mutations — those paths intentionally
729
+ * bypass the processor chain to avoid loops on an extension's own writes.
730
+ *
731
+ * Each invocation runs inside a 10-second wall-clock budget on the host.
732
+ * On timeout or thrown error the chain logs the failure and forwards the
733
+ * previous content to the next handler — the write still proceeds. A
734
+ * second registration from the same extension replaces the previous
735
+ * handler.
736
+ *
737
+ * Every millisecond of handler work is visible latency on send/edit/swipe.
738
+ * Keep handlers tight.
739
+ *
740
+ * @param handler Receives the about-to-be-committed content; returns a
741
+ * patch, or `void` to pass through.
742
+ * @param priority Lower values run first. Default `100`.
743
+ *
744
+ * @example
745
+ * ```ts
746
+ * spindle.registerMessageContentProcessor(async (ctx) => {
747
+ * if (!ctx.content.includes('{{my_macro}}')) return
748
+ * return { content: ctx.content.replaceAll('{{my_macro}}', 'resolved') }
749
+ * }, 50)
750
+ * ```
751
+ */
752
+ registerMessageContentProcessor(
753
+ handler: (
754
+ ctx: MessageContentProcessorCtxDTO
755
+ ) => Promise<MessageContentProcessorResultDTO | void>,
756
+ priority?: number
757
+ ): void;
758
+
679
759
  /**
680
760
  * Send a message to the frontend module.
681
761
  *