lumiverse-spindle-types 0.6.4 → 0.6.6
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 +1 -1
- package/src/api.ts +316 -4
- package/src/council.ts +3 -0
- package/src/dom.ts +14 -2
- package/src/host.ts +32 -0
- package/src/index.ts +37 -0
- package/src/permissions.ts +5 -1
- package/src/spindle-api.ts +47 -7
- package/test/bound-generation-consumer.ts +254 -0
- package/test/final-response-and-host-compatibility.test.ts +24 -0
- package/test/host-runtime-contract-consumer.ts +137 -0
- package/test/interceptor-consumer.ts +196 -0
- package/tsconfig.consumer.json +1 -1
package/package.json
CHANGED
package/src/api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SpindleManifest } from "./manifest";
|
|
2
|
+
import type { SpindleHostDescriptorV1 } from "./host";
|
|
2
3
|
import type { CouncilMemberContext } from "./council";
|
|
3
4
|
import type {
|
|
4
5
|
ChatLinkAttachDTO,
|
|
@@ -16,13 +17,21 @@ export type LlmMessagePartDTO =
|
|
|
16
17
|
| { type: "text"; text: string; cache_control?: Record<string, unknown> }
|
|
17
18
|
| { type: "image"; data: string; mime_type: string; cache_control?: Record<string, unknown> }
|
|
18
19
|
| { type: "audio"; data: string; mime_type: string; cache_control?: Record<string, unknown> }
|
|
19
|
-
| { type: "tool_use"; id: string; name: string; input: Record<string, unknown>; cache_control?: Record<string, unknown
|
|
20
|
+
| { type: "tool_use"; id: string; name: string; input: Record<string, unknown>; cache_control?: Record<string, unknown>; thought_signature?: string }
|
|
20
21
|
| { type: "tool_result"; tool_use_id: string; content: string; is_error?: boolean; cache_control?: Record<string, unknown> };
|
|
21
22
|
|
|
23
|
+
export interface LlmThinkingBlockDTO {
|
|
24
|
+
type: "thinking" | "redacted_thinking";
|
|
25
|
+
thinking?: string;
|
|
26
|
+
signature?: string;
|
|
27
|
+
data?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
export interface LlmMessageDTO {
|
|
23
31
|
role: "system" | "user" | "assistant";
|
|
24
32
|
content: string | LlmMessagePartDTO[];
|
|
25
33
|
name?: string;
|
|
34
|
+
cache_control?: Record<string, unknown>;
|
|
26
35
|
/**
|
|
27
36
|
* Thinking-mode reasoning content from the previous assistant turn, echoed
|
|
28
37
|
* back on the next request. Required by DeepSeek's thinking-mode models
|
|
@@ -36,6 +45,8 @@ export interface LlmMessageDTO {
|
|
|
36
45
|
* only on `role: 'assistant'` messages.
|
|
37
46
|
*/
|
|
38
47
|
reasoning_content?: string;
|
|
48
|
+
thinking_blocks?: LlmThinkingBlockDTO[];
|
|
49
|
+
reasoning_details?: Record<string, unknown>[];
|
|
39
50
|
/**
|
|
40
51
|
* True when this message is a chat-history turn (as opposed to a depth-injected
|
|
41
52
|
* world-info/preset/author's-note block that was spliced into the chat-history
|
|
@@ -57,6 +68,80 @@ export interface LlmMessageDTO {
|
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
export type SpindleUserRoleDTO = "operator" | "admin" | "user";
|
|
71
|
+
export type InterceptorGenerationType =
|
|
72
|
+
| "normal"
|
|
73
|
+
| "continue"
|
|
74
|
+
| "regenerate"
|
|
75
|
+
| "swipe"
|
|
76
|
+
| "impersonate"
|
|
77
|
+
| "quiet";
|
|
78
|
+
|
|
79
|
+
export type InterceptorMatchScalar = string | number | boolean | null;
|
|
80
|
+
|
|
81
|
+
export interface InterceptorMatchDTO {
|
|
82
|
+
/**
|
|
83
|
+
* Serializable filter domain. Terminal callbacks currently run only for
|
|
84
|
+
* live `normal` and `continue`; every other value is a fail-closed filter.
|
|
85
|
+
*/
|
|
86
|
+
generationTypes?: InterceptorGenerationType[];
|
|
87
|
+
/** Terminal callbacks are never invoked for dry runs. */
|
|
88
|
+
isDryRun?: boolean;
|
|
89
|
+
presetField?: {
|
|
90
|
+
path: string[];
|
|
91
|
+
exists?: boolean;
|
|
92
|
+
oneOf?: InterceptorMatchScalar[];
|
|
93
|
+
notIn?: InterceptorMatchScalar[];
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface InterceptorRegistrationMatchOptions {
|
|
98
|
+
match?: InterceptorMatchDTO;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface InterceptorRegistrationOptions {
|
|
102
|
+
priority?: number;
|
|
103
|
+
match?: InterceptorMatchDTO;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Host-owned, immutable context for one bound interceptor callback.
|
|
108
|
+
* `signal` is local to the worker invocation and is never serialized.
|
|
109
|
+
*/
|
|
110
|
+
export interface InterceptorContextDTO {
|
|
111
|
+
readonly userId: string;
|
|
112
|
+
readonly chatId: string;
|
|
113
|
+
readonly generationId: string;
|
|
114
|
+
readonly generationType: InterceptorGenerationType;
|
|
115
|
+
readonly isDryRun: boolean;
|
|
116
|
+
readonly presetId: string | null;
|
|
117
|
+
/** Deep clone of only this extension's own preset metadata namespace. */
|
|
118
|
+
readonly presetMetadata: unknown;
|
|
119
|
+
readonly personaId: string | null;
|
|
120
|
+
readonly characterId: string | null;
|
|
121
|
+
readonly personaAddonStates: Readonly<Record<string, boolean>>;
|
|
122
|
+
readonly excludeMessageId?: string;
|
|
123
|
+
readonly rejectedSwipe?: string;
|
|
124
|
+
readonly regenFeedback?: string;
|
|
125
|
+
readonly regenFeedbackPosition?: "system" | "user";
|
|
126
|
+
readonly mainDispatch: {
|
|
127
|
+
readonly source: "main";
|
|
128
|
+
readonly descriptor: Readonly<ConnectionDispatchDescriptorDTO> | null;
|
|
129
|
+
readonly connectionDispatchRevision: string | null;
|
|
130
|
+
readonly dispatchKind: "concrete" | "roulette" | null;
|
|
131
|
+
};
|
|
132
|
+
readonly prefillCarrier: BoundPrefillAttachmentDTO;
|
|
133
|
+
readonly interceptorDeadlineAt: number;
|
|
134
|
+
readonly boundWorkDeadlineAt: number;
|
|
135
|
+
/** Worker-local cancellation signal; never serialized over the worker protocol. */
|
|
136
|
+
readonly signal: AbortSignal;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface DeferredGuidanceDTO {
|
|
140
|
+
id: string;
|
|
141
|
+
content: string;
|
|
142
|
+
role: "system";
|
|
143
|
+
}
|
|
144
|
+
|
|
60
145
|
|
|
61
146
|
/**
|
|
62
147
|
* Optional metadata returned by an interceptor so Lumiverse can surface
|
|
@@ -73,6 +158,13 @@ export interface InterceptorBreakdownEntryDTO {
|
|
|
73
158
|
name?: string;
|
|
74
159
|
}
|
|
75
160
|
|
|
161
|
+
/** Privileged response override returned by an interceptor. */
|
|
162
|
+
export interface FinalResponseDTO {
|
|
163
|
+
content: string;
|
|
164
|
+
reasoning?: string;
|
|
165
|
+
fallbackMessageIndex: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
76
168
|
/**
|
|
77
169
|
* Return type for interceptor handlers.
|
|
78
170
|
* Interceptors may return either a plain `LlmMessageDTO[]` (backwards-compatible)
|
|
@@ -84,8 +176,19 @@ export interface InterceptorResultDTO {
|
|
|
84
176
|
parameters?: Record<string, unknown>;
|
|
85
177
|
/** Optional prompt-breakdown entries for injected messages. */
|
|
86
178
|
breakdown?: InterceptorBreakdownEntryDTO[];
|
|
179
|
+
/** Host-owned system guidance retained for each authoritative Main attempt. */
|
|
180
|
+
deferredGuidance?: DeferredGuidanceDTO[];
|
|
181
|
+
/** Optional privileged response replacement. Requires the `final_response` permission. */
|
|
182
|
+
finalResponse?: FinalResponseDTO;
|
|
87
183
|
}
|
|
88
184
|
|
|
185
|
+
export type InterceptorDisposer = () => void;
|
|
186
|
+
|
|
187
|
+
export type InterceptorHandler = (
|
|
188
|
+
messages: LlmMessageDTO[],
|
|
189
|
+
context: InterceptorContextDTO,
|
|
190
|
+
) => Promise<LlmMessageDTO[] | InterceptorResultDTO>;
|
|
191
|
+
|
|
89
192
|
export interface MacroDefinitionDTO {
|
|
90
193
|
name: string;
|
|
91
194
|
category: string;
|
|
@@ -261,6 +364,15 @@ export interface ToolSchemaDTO {
|
|
|
261
364
|
parameters: Record<string, unknown>; // JSON Schema
|
|
262
365
|
}
|
|
263
366
|
|
|
367
|
+
export interface ToolDefinitionDTO {
|
|
368
|
+
name: string;
|
|
369
|
+
description: string;
|
|
370
|
+
parameters: Record<string, unknown>;
|
|
371
|
+
strict?: boolean;
|
|
372
|
+
inputExamples?: Array<Record<string, unknown>>;
|
|
373
|
+
cache_control?: Record<string, unknown>;
|
|
374
|
+
}
|
|
375
|
+
|
|
264
376
|
/** A single function call made by the LLM. */
|
|
265
377
|
export interface ToolCallDTO {
|
|
266
378
|
/** Tool name (as given in the schema). */
|
|
@@ -269,8 +381,35 @@ export interface ToolCallDTO {
|
|
|
269
381
|
args: Record<string, unknown>;
|
|
270
382
|
/** Provider call ID (e.g. Anthropic `id`, OpenAI `id`). Synthetic UUID for providers that don't supply one (e.g. Google). */
|
|
271
383
|
call_id: string;
|
|
384
|
+
/** Opaque provider signature preserved for tool-call continuations. */
|
|
385
|
+
thought_signature?: string;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export interface GenerationUsageDTO {
|
|
389
|
+
prompt_tokens?: number;
|
|
390
|
+
completion_tokens?: number;
|
|
391
|
+
total_tokens?: number;
|
|
392
|
+
provider_raw?: Record<string, unknown>;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export interface GenerationResponseDTO {
|
|
396
|
+
content: string;
|
|
397
|
+
reasoning?: string;
|
|
398
|
+
finish_reason: string;
|
|
399
|
+
tool_calls?: ToolCallDTO[];
|
|
400
|
+
thinking_blocks?: LlmThinkingBlockDTO[];
|
|
401
|
+
reasoning_details?: Record<string, unknown>[];
|
|
402
|
+
usage?: GenerationUsageDTO;
|
|
272
403
|
}
|
|
273
404
|
|
|
405
|
+
export type GenerationDispatchSourceDTO =
|
|
406
|
+
| { source: "main"; expectedConnectionDispatchRevision: string }
|
|
407
|
+
| {
|
|
408
|
+
source: "slot";
|
|
409
|
+
connectionId: string;
|
|
410
|
+
expectedConnectionDispatchRevision: string;
|
|
411
|
+
};
|
|
412
|
+
|
|
274
413
|
export interface GenerationRequestDTO {
|
|
275
414
|
type: "raw" | "quiet" | "batch";
|
|
276
415
|
messages?: LlmMessageDTO[];
|
|
@@ -535,6 +674,16 @@ export interface ConnectionProfileDTO {
|
|
|
535
674
|
updated_at: number;
|
|
536
675
|
}
|
|
537
676
|
|
|
677
|
+
export interface ConnectionDispatchDescriptorDTO {
|
|
678
|
+
connectionId: string;
|
|
679
|
+
connectionName: string;
|
|
680
|
+
provider: string;
|
|
681
|
+
model: string;
|
|
682
|
+
endpointOrigin: string;
|
|
683
|
+
dispatchKind: "concrete" | "roulette";
|
|
684
|
+
connectionDispatchRevision: string | null;
|
|
685
|
+
}
|
|
686
|
+
|
|
538
687
|
// ─── Image Generation DTOs ──────────────────────────────────────────────
|
|
539
688
|
|
|
540
689
|
/**
|
|
@@ -1753,6 +1902,126 @@ export interface AssembleResultDTO {
|
|
|
1753
1902
|
breakdown: AssemblyBreakdownEntryDTO[];
|
|
1754
1903
|
}
|
|
1755
1904
|
|
|
1905
|
+
export interface BoundPrefillAttachmentDTO {
|
|
1906
|
+
/**
|
|
1907
|
+
* Opaque parent-prefill attestation. It may be presented only by the live
|
|
1908
|
+
* worker invocation; it is not itself a reusable attachment capability,
|
|
1909
|
+
* message index, or carrier content.
|
|
1910
|
+
*/
|
|
1911
|
+
readonly id: string;
|
|
1912
|
+
readonly state: "absent" | "available" | "invalid";
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
export interface BoundAssembleRequestDTO {
|
|
1916
|
+
blocks: PromptBlockDTO[];
|
|
1917
|
+
promptVariableValues?: PromptVariableValuesDTO;
|
|
1918
|
+
dispatch: GenerationDispatchSourceDTO;
|
|
1919
|
+
deadlineAt: number;
|
|
1920
|
+
hookFailureMode?: "degrade" | "reject";
|
|
1921
|
+
macroFailureMode?: "degrade" | "reject";
|
|
1922
|
+
signal?: AbortSignal;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
export interface BoundAssemblySuccessDTO {
|
|
1926
|
+
messages: LlmMessageDTO[];
|
|
1927
|
+
breakdown: AssemblyBreakdownEntryDTO[];
|
|
1928
|
+
resolved: {
|
|
1929
|
+
source: "main" | "slot";
|
|
1930
|
+
connectionId: string | null;
|
|
1931
|
+
connectionDispatchRevision: string;
|
|
1932
|
+
dispatchKind: "concrete";
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
export type BoundAssemblyFailureDTO =
|
|
1937
|
+
| {
|
|
1938
|
+
kind: "hook";
|
|
1939
|
+
code: "ASSEMBLY_HOOK_FAILED";
|
|
1940
|
+
phase: "context" | "world_info" | "macro";
|
|
1941
|
+
reason: "error" | "timeout";
|
|
1942
|
+
message: string;
|
|
1943
|
+
}
|
|
1944
|
+
| {
|
|
1945
|
+
kind: "macro";
|
|
1946
|
+
code: "ASSEMBLY_MACRO_FAILED";
|
|
1947
|
+
reason: "definition" | "parse" | "recursion" | "budget" | "evaluation";
|
|
1948
|
+
message: string;
|
|
1949
|
+
}
|
|
1950
|
+
| {
|
|
1951
|
+
kind: "retrieval_snapshot";
|
|
1952
|
+
code: "ASSEMBLY_RETRIEVAL_SNAPSHOT_UNAVAILABLE";
|
|
1953
|
+
reason: "missing" | "expired" | "unavailable" | "oversize";
|
|
1954
|
+
message: string;
|
|
1955
|
+
}
|
|
1956
|
+
| {
|
|
1957
|
+
kind: "abort";
|
|
1958
|
+
code: "ASSEMBLY_ABORTED";
|
|
1959
|
+
name: "AbortError";
|
|
1960
|
+
message: string;
|
|
1961
|
+
}
|
|
1962
|
+
| {
|
|
1963
|
+
kind: "precondition" | "security" | "internal";
|
|
1964
|
+
code: string;
|
|
1965
|
+
message: string;
|
|
1966
|
+
};
|
|
1967
|
+
|
|
1968
|
+
export type BoundAssemblyOutcomeDTO =
|
|
1969
|
+
| { ok: true; result: BoundAssemblySuccessDTO }
|
|
1970
|
+
| { ok: false; error: BoundAssemblyFailureDTO };
|
|
1971
|
+
|
|
1972
|
+
export interface QuietTrackedRequestDTO {
|
|
1973
|
+
messages: LlmMessageDTO[];
|
|
1974
|
+
dispatch: GenerationDispatchSourceDTO;
|
|
1975
|
+
/**
|
|
1976
|
+
* When supplied, WorkerHost—not extension code—attaches the authenticated
|
|
1977
|
+
* parent assistant carrier as the final continuation carrier for this dispatch.
|
|
1978
|
+
*/
|
|
1979
|
+
continuation?: {
|
|
1980
|
+
parentPrefill: BoundPrefillAttachmentDTO;
|
|
1981
|
+
mode: "append-parent-carrier-last";
|
|
1982
|
+
};
|
|
1983
|
+
parameters?: Record<string, unknown>;
|
|
1984
|
+
reasoning?: Record<string, unknown>;
|
|
1985
|
+
tools?: ToolDefinitionDTO[];
|
|
1986
|
+
deadlineAt: number;
|
|
1987
|
+
signal?: AbortSignal;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
export interface QuietDispatchReceiptDTO {
|
|
1991
|
+
providerInvoked: boolean;
|
|
1992
|
+
terminalResponse: boolean;
|
|
1993
|
+
source: "main" | "slot";
|
|
1994
|
+
connectionId: string | null;
|
|
1995
|
+
connectionDispatchRevision: string;
|
|
1996
|
+
usage?: GenerationUsageDTO;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
export type QuietTrackedResultDTO =
|
|
2000
|
+
| { ok: true; response: GenerationResponseDTO; receipt: QuietDispatchReceiptDTO }
|
|
2001
|
+
| {
|
|
2002
|
+
ok: false;
|
|
2003
|
+
phase: "preflight";
|
|
2004
|
+
providerInvoked: false;
|
|
2005
|
+
receipt: null;
|
|
2006
|
+
error: {
|
|
2007
|
+
kind: "precondition" | "security";
|
|
2008
|
+
code: string;
|
|
2009
|
+
name: string;
|
|
2010
|
+
message: string;
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
| {
|
|
2014
|
+
ok: false;
|
|
2015
|
+
phase: "resolved";
|
|
2016
|
+
receipt: QuietDispatchReceiptDTO;
|
|
2017
|
+
error: {
|
|
2018
|
+
kind: "precondition" | "provider" | "abort" | "security" | "internal";
|
|
2019
|
+
code: string;
|
|
2020
|
+
name: string;
|
|
2021
|
+
message: string;
|
|
2022
|
+
};
|
|
2023
|
+
};
|
|
2024
|
+
|
|
1756
2025
|
// ─── Chat Memory DTOs ──────────────────────────────────────────────────
|
|
1757
2026
|
|
|
1758
2027
|
export interface ChatMemoryChunkDTO {
|
|
@@ -2641,13 +2910,22 @@ export type WorkerToHost =
|
|
|
2641
2910
|
| { type: "register_macro"; definition: MacroDefinitionDTO }
|
|
2642
2911
|
| { type: "unregister_macro"; name: string }
|
|
2643
2912
|
| { type: "update_macro_value"; name: string; value: string }
|
|
2644
|
-
| {
|
|
2913
|
+
| {
|
|
2914
|
+
type: "register_interceptor";
|
|
2915
|
+
registrationId: string;
|
|
2916
|
+
priority?: number;
|
|
2917
|
+
match?: InterceptorMatchDTO;
|
|
2918
|
+
}
|
|
2919
|
+
| { type: "unregister_interceptor"; registrationId: string }
|
|
2645
2920
|
| {
|
|
2646
2921
|
type: "intercept_result";
|
|
2647
2922
|
requestId: string;
|
|
2923
|
+
registrationId: string;
|
|
2648
2924
|
messages: LlmMessageDTO[];
|
|
2649
2925
|
parameters?: Record<string, unknown>;
|
|
2650
2926
|
breakdown?: InterceptorBreakdownEntryDTO[];
|
|
2927
|
+
deferredGuidance?: DeferredGuidanceDTO[];
|
|
2928
|
+
finalResponse?: FinalResponseDTO;
|
|
2651
2929
|
}
|
|
2652
2930
|
| {
|
|
2653
2931
|
type: "assemble_prompt";
|
|
@@ -2655,6 +2933,33 @@ export type WorkerToHost =
|
|
|
2655
2933
|
input: Omit<AssembleRequestDTO, "signal">;
|
|
2656
2934
|
userId?: string;
|
|
2657
2935
|
}
|
|
2936
|
+
/**
|
|
2937
|
+
* Assemble through the active bound interceptor generation context. The
|
|
2938
|
+
* worker-local signal is omitted from the structured-clone payload.
|
|
2939
|
+
*/
|
|
2940
|
+
| {
|
|
2941
|
+
type: "generate_assemble";
|
|
2942
|
+
requestId: string;
|
|
2943
|
+
input: Omit<BoundAssembleRequestDTO, "signal">;
|
|
2944
|
+
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Dispatch tracked quiet generation through the active bound context.
|
|
2947
|
+
* The worker-local signal is omitted from the structured-clone payload.
|
|
2948
|
+
*/
|
|
2949
|
+
| {
|
|
2950
|
+
type: "generate_quiet_tracked";
|
|
2951
|
+
requestId: string;
|
|
2952
|
+
input: Omit<QuietTrackedRequestDTO, "signal">;
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* Inspect an owned concrete slot through the active bound interceptor
|
|
2956
|
+
* context. The host derives user scope from the authenticated callback.
|
|
2957
|
+
*/
|
|
2958
|
+
| {
|
|
2959
|
+
type: "connections_resolve_dispatch";
|
|
2960
|
+
requestId: string;
|
|
2961
|
+
connectionId: string;
|
|
2962
|
+
}
|
|
2658
2963
|
| { type: "register_tool"; tool: ToolRegistrationDTO }
|
|
2659
2964
|
| { type: "unregister_tool"; name: string }
|
|
2660
2965
|
| { type: "request_generation"; requestId: string; input: GenerationRequestDTO }
|
|
@@ -3205,7 +3510,7 @@ export type WorkerToHost =
|
|
|
3205
3510
|
// ─── Host → Worker messages ──────────────────────────────────────────────
|
|
3206
3511
|
|
|
3207
3512
|
export type HostToWorker =
|
|
3208
|
-
| { type: "init"; manifest: SpindleManifest; storagePath: string }
|
|
3513
|
+
| { type: "init"; manifest: SpindleManifest; storagePath: string; host: SpindleHostDescriptorV1 }
|
|
3209
3514
|
| { type: "event"; event: string; payload: unknown; userId?: string }
|
|
3210
3515
|
| {
|
|
3211
3516
|
type: "rpc_pool_request";
|
|
@@ -3218,8 +3523,15 @@ export type HostToWorker =
|
|
|
3218
3523
|
| {
|
|
3219
3524
|
type: "intercept_request";
|
|
3220
3525
|
requestId: string;
|
|
3526
|
+
registrationId: string;
|
|
3221
3527
|
messages: LlmMessageDTO[];
|
|
3222
|
-
context:
|
|
3528
|
+
context: Omit<InterceptorContextDTO, "signal">;
|
|
3529
|
+
}
|
|
3530
|
+
| {
|
|
3531
|
+
type: "intercept_abort";
|
|
3532
|
+
requestId: string;
|
|
3533
|
+
registrationId: string;
|
|
3534
|
+
reason?: string;
|
|
3223
3535
|
}
|
|
3224
3536
|
| {
|
|
3225
3537
|
type: "context_handler_request";
|
package/src/council.ts
CHANGED
|
@@ -52,6 +52,8 @@ export interface CouncilToolsSettings {
|
|
|
52
52
|
timeoutMs: number;
|
|
53
53
|
/** Number of recent chat messages to include in sidecar context. */
|
|
54
54
|
sidecarContextWindow: number;
|
|
55
|
+
/** Omit the newest user-authored message from sidecar council-tool context. */
|
|
56
|
+
excludeLatestUserMessage: boolean;
|
|
55
57
|
includeUserPersona: boolean;
|
|
56
58
|
includeCharacterInfo: boolean;
|
|
57
59
|
includeWorldInfo: boolean;
|
|
@@ -214,6 +216,7 @@ export const COUNCIL_TOOLS_DEFAULTS: CouncilToolsSettings = {
|
|
|
214
216
|
mode: "sidecar",
|
|
215
217
|
timeoutMs: 30000,
|
|
216
218
|
sidecarContextWindow: 25,
|
|
219
|
+
excludeLatestUserMessage: false,
|
|
217
220
|
includeUserPersona: true,
|
|
218
221
|
includeCharacterInfo: true,
|
|
219
222
|
includeWorldInfo: true,
|
package/src/dom.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PromptBlockDTO, PromptVariableValuesDTO, RequestInitDTO } from "./api";
|
|
2
2
|
import type { SpindleComponentsHelper } from "./components";
|
|
3
|
+
import type { SpindleHostDescriptorV1, SpindleHostLocaleAPI } from "./host";
|
|
3
4
|
|
|
4
5
|
/** A chat-message DOM element paired with its stable message id. */
|
|
5
6
|
export interface SpindleMessageElement {
|
|
@@ -859,6 +860,10 @@ export interface SpindleDisplayResolverRegistry {
|
|
|
859
860
|
|
|
860
861
|
/** Context object provided to frontend extension modules */
|
|
861
862
|
export interface SpindleFrontendContext {
|
|
863
|
+
/** Immutable host compatibility descriptor for this extension runtime. */
|
|
864
|
+
readonly host: SpindleHostDescriptorV1;
|
|
865
|
+
/** Synchronous host locale access with removable live-change subscriptions. */
|
|
866
|
+
readonly locale: SpindleHostLocaleAPI;
|
|
862
867
|
dom: SpindleDOMHelper;
|
|
863
868
|
events: {
|
|
864
869
|
on(event: string, handler: (payload: unknown) => void): () => void;
|
|
@@ -923,6 +928,8 @@ export interface SpindleFrontendContext {
|
|
|
923
928
|
showConfirm(options: SpindleConfirmOptions): Promise<SpindleConfirmResult>;
|
|
924
929
|
/** Request a tab move to a specific drawer location. */
|
|
925
930
|
requestTabLocation(tabId: string, location: SpindleTabLocation): void;
|
|
931
|
+
/** Read the current drawer location for a built-in or extension tab. */
|
|
932
|
+
getTabLocation(tabId: string): SpindleTabLocation;
|
|
926
933
|
/** Get the display title of a built-in drawer tab by its id. */
|
|
927
934
|
getBuiltInTabTitle(tabId: string): string | undefined;
|
|
928
935
|
/** Get the root HTMLElement of a built-in drawer tab by its id, or undefined if not mounted. */
|
|
@@ -1045,8 +1052,13 @@ export interface SpindleFrontendContext {
|
|
|
1045
1052
|
manifest: import("./manifest").SpindleManifest;
|
|
1046
1053
|
}
|
|
1047
1054
|
|
|
1055
|
+
/** Cleanup returned by a frontend extension setup hook. */
|
|
1056
|
+
export type SpindleFrontendTeardown = () => void | Promise<void>;
|
|
1057
|
+
|
|
1048
1058
|
/** What a frontend extension module must export */
|
|
1049
1059
|
export interface SpindleFrontendModule {
|
|
1050
|
-
setup(
|
|
1051
|
-
|
|
1060
|
+
setup(
|
|
1061
|
+
ctx: SpindleFrontendContext,
|
|
1062
|
+
): void | SpindleFrontendTeardown | Promise<void | SpindleFrontendTeardown>;
|
|
1063
|
+
teardown?(): void | Promise<void>;
|
|
1052
1064
|
}
|
package/src/host.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const SPINDLE_HOST_CAPABILITIES = Object.freeze({
|
|
2
|
+
"preset-extension-data-v1": 1,
|
|
3
|
+
"preset-editor-v1": 1,
|
|
4
|
+
"loom-block-editor-v1": 1,
|
|
5
|
+
"generation-assembly-v1": 1,
|
|
6
|
+
"interceptor-context-v1": 1,
|
|
7
|
+
"interceptor-final-response-v1": 1,
|
|
8
|
+
}) as Readonly<Record<string, number>>;
|
|
9
|
+
/** Structured error code returned when host compatibility validation fails. */
|
|
10
|
+
export const SPINDLE_COMPATIBILITY_ERROR_CODE = "SPINDLE_COMPATIBILITY_ERROR" as const;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Immutable host/runtime compatibility information exposed to extensions.
|
|
14
|
+
*
|
|
15
|
+
* Hosts may add capability keys over time. Extensions should only depend on
|
|
16
|
+
* the capability names they understand and the descriptor version they support.
|
|
17
|
+
*/
|
|
18
|
+
export interface SpindleHostDescriptorV1 {
|
|
19
|
+
readonly descriptorVersion: 1;
|
|
20
|
+
readonly lumiverseVersion: string;
|
|
21
|
+
readonly capabilities: Readonly<Record<string, number>>;
|
|
22
|
+
readonly extensionInstallationId: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Locale identifiers supplied by the host's active UI locale. */
|
|
26
|
+
export type SpindleHostLocale = "en" | "zh" | "zh-TW" | "ja" | "fr" | "it";
|
|
27
|
+
|
|
28
|
+
/** Synchronous host locale lookup with a removable live-change subscription. */
|
|
29
|
+
export interface SpindleHostLocaleAPI {
|
|
30
|
+
get(): SpindleHostLocale;
|
|
31
|
+
subscribe(listener: (locale: SpindleHostLocale) => void): () => void;
|
|
32
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,16 @@ export {
|
|
|
8
8
|
validateIdentifier,
|
|
9
9
|
} from "./manifest";
|
|
10
10
|
|
|
11
|
+
export type {
|
|
12
|
+
SpindleHostDescriptorV1,
|
|
13
|
+
SpindleHostLocale,
|
|
14
|
+
SpindleHostLocaleAPI,
|
|
15
|
+
} from "./host";
|
|
16
|
+
export {
|
|
17
|
+
SPINDLE_HOST_CAPABILITIES,
|
|
18
|
+
SPINDLE_COMPATIBILITY_ERROR_CODE,
|
|
19
|
+
} from "./host";
|
|
20
|
+
|
|
11
21
|
export type { SpindlePermission as SpindlePermissionType } from "./permissions";
|
|
12
22
|
export { ALL_PERMISSIONS, isValidPermission } from "./permissions";
|
|
13
23
|
|
|
@@ -18,8 +28,19 @@ export { SpindleEvent, CoreEventType } from "./events";
|
|
|
18
28
|
|
|
19
29
|
export type {
|
|
20
30
|
LlmMessageDTO,
|
|
31
|
+
LlmThinkingBlockDTO,
|
|
21
32
|
SpindleUserRoleDTO,
|
|
33
|
+
InterceptorGenerationType,
|
|
34
|
+
InterceptorMatchScalar,
|
|
35
|
+
InterceptorMatchDTO,
|
|
36
|
+
InterceptorRegistrationMatchOptions,
|
|
37
|
+
InterceptorRegistrationOptions,
|
|
38
|
+
InterceptorContextDTO,
|
|
39
|
+
InterceptorDisposer,
|
|
40
|
+
InterceptorHandler,
|
|
41
|
+
DeferredGuidanceDTO,
|
|
22
42
|
InterceptorBreakdownEntryDTO,
|
|
43
|
+
FinalResponseDTO,
|
|
23
44
|
InterceptorResultDTO,
|
|
24
45
|
MacroDefinitionDTO,
|
|
25
46
|
MacroInvocationContextDTO,
|
|
@@ -28,6 +49,11 @@ export type {
|
|
|
28
49
|
ToolRegistrationDTO,
|
|
29
50
|
ToolSchemaDTO,
|
|
30
51
|
ToolCallDTO,
|
|
52
|
+
ToolDefinitionDTO,
|
|
53
|
+
GenerationUsageDTO,
|
|
54
|
+
GenerationResponseDTO,
|
|
55
|
+
GenerationDispatchSourceDTO,
|
|
56
|
+
ConnectionDispatchDescriptorDTO,
|
|
31
57
|
GenerationRequestDTO,
|
|
32
58
|
GenerationReasoningOverrideDTO,
|
|
33
59
|
ReasoningEffortDTO,
|
|
@@ -112,6 +138,14 @@ export type {
|
|
|
112
138
|
DryRunResultDTO,
|
|
113
139
|
AssembleRequestDTO,
|
|
114
140
|
AssembleResultDTO,
|
|
141
|
+
BoundPrefillAttachmentDTO,
|
|
142
|
+
BoundAssembleRequestDTO,
|
|
143
|
+
BoundAssemblySuccessDTO,
|
|
144
|
+
BoundAssemblyFailureDTO,
|
|
145
|
+
BoundAssemblyOutcomeDTO,
|
|
146
|
+
QuietTrackedRequestDTO,
|
|
147
|
+
QuietDispatchReceiptDTO,
|
|
148
|
+
QuietTrackedResultDTO,
|
|
115
149
|
AssemblyBreakdownEntryDTO,
|
|
116
150
|
ActivationStatsDTO,
|
|
117
151
|
MemoryStatsDTO,
|
|
@@ -216,6 +250,7 @@ export type {
|
|
|
216
250
|
PermissionRequestOptions,
|
|
217
251
|
SpindleFrontendContext,
|
|
218
252
|
SpindleFrontendModule,
|
|
253
|
+
SpindleFrontendTeardown,
|
|
219
254
|
SpindleDrawerTabOptions,
|
|
220
255
|
SpindleDrawerTabHandle,
|
|
221
256
|
SpindleCharacterEditorTabOptions,
|
|
@@ -381,6 +416,8 @@ export type {
|
|
|
381
416
|
|
|
382
417
|
export type {
|
|
383
418
|
SpindleAPI,
|
|
419
|
+
SpindleGenerateAPI,
|
|
420
|
+
SpindleConnectionsAPI,
|
|
384
421
|
SpindlePromptRegex,
|
|
385
422
|
FrontendProcessHandle,
|
|
386
423
|
BackendProcessHandle,
|
package/src/permissions.ts
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
* web search settings (never the API key).
|
|
27
27
|
* - "unsafe_eval" — opt a sandboxed widget frame into CSP 'unsafe-eval'
|
|
28
28
|
* (eval / new Function) for runtime-compiling frameworks.
|
|
29
|
+
* - "final_response" — replace the authoritative assistant response from
|
|
30
|
+
* an interceptor (privileged; never auto-granted)
|
|
29
31
|
*/
|
|
30
32
|
export type SpindlePermission =
|
|
31
33
|
| "generation"
|
|
@@ -54,7 +56,8 @@ export type SpindlePermission =
|
|
|
54
56
|
| "generation_parameters"
|
|
55
57
|
| "macro_interceptor"
|
|
56
58
|
| "web_search"
|
|
57
|
-
| "unsafe_eval"
|
|
59
|
+
| "unsafe_eval"
|
|
60
|
+
| "final_response";
|
|
58
61
|
|
|
59
62
|
export const ALL_PERMISSIONS: readonly SpindlePermission[] = [
|
|
60
63
|
"generation",
|
|
@@ -84,6 +87,7 @@ export const ALL_PERMISSIONS: readonly SpindlePermission[] = [
|
|
|
84
87
|
"macro_interceptor",
|
|
85
88
|
"web_search",
|
|
86
89
|
"unsafe_eval",
|
|
90
|
+
"final_response",
|
|
87
91
|
] as const;
|
|
88
92
|
|
|
89
93
|
export function isValidPermission(p: string): p is SpindlePermission {
|
package/src/spindle-api.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { SpindleManifest } from "./manifest";
|
|
2
|
+
import type { SpindleHostDescriptorV1 } from "./host";
|
|
2
3
|
import type {
|
|
3
4
|
CouncilMemberContext,
|
|
4
5
|
CouncilSettings,
|
|
5
6
|
} from "./council";
|
|
6
7
|
import type {
|
|
7
8
|
LlmMessageDTO,
|
|
8
|
-
|
|
9
|
+
InterceptorDisposer,
|
|
10
|
+
InterceptorHandler,
|
|
11
|
+
InterceptorRegistrationMatchOptions,
|
|
12
|
+
InterceptorRegistrationOptions,
|
|
9
13
|
MacroDefinitionDTO,
|
|
10
14
|
MacroResolveOptionsDTO,
|
|
11
15
|
MacroResolveResultDTO,
|
|
@@ -14,6 +18,7 @@ import type {
|
|
|
14
18
|
ChatAppendMessageOptionsDTO,
|
|
15
19
|
RequestInitDTO,
|
|
16
20
|
ConnectionProfileDTO,
|
|
21
|
+
ConnectionDispatchDescriptorDTO,
|
|
17
22
|
PermissionDeniedDetail,
|
|
18
23
|
PermissionChangedDetail,
|
|
19
24
|
CharacterDTO,
|
|
@@ -58,6 +63,10 @@ import type {
|
|
|
58
63
|
DryRunResultDTO,
|
|
59
64
|
AssembleRequestDTO,
|
|
60
65
|
AssembleResultDTO,
|
|
66
|
+
BoundAssembleRequestDTO,
|
|
67
|
+
BoundAssemblyOutcomeDTO,
|
|
68
|
+
QuietTrackedRequestDTO,
|
|
69
|
+
QuietTrackedResultDTO,
|
|
61
70
|
ChatMemoryResultDTO,
|
|
62
71
|
ImageGenRequestDTO,
|
|
63
72
|
ImageGenResultDTO,
|
|
@@ -235,8 +244,20 @@ export interface SpindlePromptRegex {
|
|
|
235
244
|
setOwnedChats(chatIds: string[]): void;
|
|
236
245
|
}
|
|
237
246
|
|
|
247
|
+
export type SpindleGenerateAPI = SpindleAPI["generate"];
|
|
248
|
+
|
|
249
|
+
export interface SpindleConnectionsAPI {
|
|
250
|
+
/**
|
|
251
|
+
* Resolve an owned concrete slot's current safe dispatch descriptor inside
|
|
252
|
+
* an active interceptor callback. Requires the `generation` permission.
|
|
253
|
+
*/
|
|
254
|
+
resolveDispatch(connectionId: string): Promise<ConnectionDispatchDescriptorDTO | null>;
|
|
255
|
+
}
|
|
256
|
+
|
|
238
257
|
/** The global `spindle` object available in backend extension workers */
|
|
239
258
|
export interface SpindleAPI {
|
|
259
|
+
/** Immutable host compatibility descriptor for this extension runtime. */
|
|
260
|
+
readonly host: SpindleHostDescriptorV1;
|
|
240
261
|
/**
|
|
241
262
|
* Subscribe to permission grant/revoke changes for this extension only.
|
|
242
263
|
* This is delivered directly by the worker host and does not subscribe to
|
|
@@ -314,12 +335,14 @@ export interface SpindleAPI {
|
|
|
314
335
|
* Without it, returned parameters are silently stripped.
|
|
315
336
|
*/
|
|
316
337
|
registerInterceptor(
|
|
317
|
-
handler:
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
338
|
+
handler: InterceptorHandler,
|
|
339
|
+
priority?: number,
|
|
340
|
+
options?: InterceptorRegistrationMatchOptions,
|
|
341
|
+
): InterceptorDisposer;
|
|
342
|
+
registerInterceptor(
|
|
343
|
+
handler: InterceptorHandler,
|
|
344
|
+
options?: InterceptorRegistrationOptions,
|
|
345
|
+
): InterceptorDisposer;
|
|
323
346
|
|
|
324
347
|
/**
|
|
325
348
|
* Assemble an arbitrary Loom block graph against a chat without calling an
|
|
@@ -378,6 +401,18 @@ export interface SpindleAPI {
|
|
|
378
401
|
* ```
|
|
379
402
|
*/
|
|
380
403
|
generate: {
|
|
404
|
+
/**
|
|
405
|
+
* Assemble against the active callback's frozen parent retrieval snapshot
|
|
406
|
+
* without provider work or fresh retrieval effects. Requires the
|
|
407
|
+
* `generation` permission and an active interceptor callback.
|
|
408
|
+
*/
|
|
409
|
+
assemble(input: BoundAssembleRequestDTO): Promise<BoundAssemblyOutcomeDTO>;
|
|
410
|
+
/**
|
|
411
|
+
* Dispatch a receipt-bearing quiet call through the active callback's
|
|
412
|
+
* revision-bound Main or explicit-slot source. Requires the `generation`
|
|
413
|
+
* permission and an active interceptor callback.
|
|
414
|
+
*/
|
|
415
|
+
quietTracked(input: QuietTrackedRequestDTO): Promise<QuietTrackedResultDTO>;
|
|
381
416
|
raw(input: GenerationRequestDTO): Promise<unknown>;
|
|
382
417
|
quiet(input: GenerationRequestDTO): Promise<unknown>;
|
|
383
418
|
batch(input: GenerationRequestDTO): Promise<unknown>;
|
|
@@ -716,6 +751,11 @@ export interface SpindleAPI {
|
|
|
716
751
|
* Returns null if the connection doesn't exist or isn't accessible.
|
|
717
752
|
*/
|
|
718
753
|
get(connectionId: string, userId?: string): Promise<ConnectionProfileDTO | null>;
|
|
754
|
+
/**
|
|
755
|
+
* Resolve an owned concrete slot's safe current dispatch descriptor inside
|
|
756
|
+
* an active interceptor callback. Requires the `generation` permission.
|
|
757
|
+
*/
|
|
758
|
+
resolveDispatch(connectionId: string): Promise<ConnectionDispatchDescriptorDTO | null>;
|
|
719
759
|
};
|
|
720
760
|
|
|
721
761
|
/** Server-side token counting helpers (free tier). */
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BoundAssembleRequestDTO,
|
|
3
|
+
BoundAssemblyFailureDTO,
|
|
4
|
+
BoundAssemblyOutcomeDTO,
|
|
5
|
+
BoundAssemblySuccessDTO,
|
|
6
|
+
BoundPrefillAttachmentDTO,
|
|
7
|
+
ConnectionDispatchDescriptorDTO,
|
|
8
|
+
GenerationDispatchSourceDTO,
|
|
9
|
+
GenerationResponseDTO,
|
|
10
|
+
GenerationUsageDTO,
|
|
11
|
+
LlmMessageDTO,
|
|
12
|
+
QuietDispatchReceiptDTO,
|
|
13
|
+
QuietTrackedRequestDTO,
|
|
14
|
+
QuietTrackedResultDTO,
|
|
15
|
+
SpindleAPI,
|
|
16
|
+
SpindleConnectionsAPI,
|
|
17
|
+
SpindleGenerateAPI,
|
|
18
|
+
ToolDefinitionDTO,
|
|
19
|
+
} from "lumiverse-spindle-types";
|
|
20
|
+
|
|
21
|
+
const mainDispatch: GenerationDispatchSourceDTO = {
|
|
22
|
+
source: "main",
|
|
23
|
+
expectedConnectionDispatchRevision: "main-revision",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const slotDispatch: GenerationDispatchSourceDTO = {
|
|
27
|
+
source: "slot",
|
|
28
|
+
connectionId: "connection-id",
|
|
29
|
+
expectedConnectionDispatchRevision: "slot-revision",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const messages: LlmMessageDTO[] = [
|
|
33
|
+
{
|
|
34
|
+
role: "assistant",
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "tool_use",
|
|
38
|
+
id: "call-1",
|
|
39
|
+
name: "lookup",
|
|
40
|
+
input: { query: "safe" },
|
|
41
|
+
thought_signature: "provider-signature",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
cache_control: { ttl: "request" },
|
|
45
|
+
reasoning_content: "reasoning carrier",
|
|
46
|
+
thinking_blocks: [
|
|
47
|
+
{ type: "thinking", thinking: "private provider reasoning", signature: "opaque" },
|
|
48
|
+
],
|
|
49
|
+
reasoning_details: [{ type: "summary_text", text: "provider detail" }],
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const promptBlocks = [] as BoundAssembleRequestDTO["blocks"];
|
|
54
|
+
const boundRequest: BoundAssembleRequestDTO = {
|
|
55
|
+
blocks: promptBlocks,
|
|
56
|
+
promptVariableValues: {},
|
|
57
|
+
dispatch: mainDispatch,
|
|
58
|
+
deadlineAt: Date.now() + 1_000,
|
|
59
|
+
hookFailureMode: "degrade",
|
|
60
|
+
macroFailureMode: "reject",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const boundSuccess: BoundAssemblySuccessDTO = {
|
|
64
|
+
messages,
|
|
65
|
+
breakdown: [],
|
|
66
|
+
resolved: {
|
|
67
|
+
source: "main",
|
|
68
|
+
connectionId: null,
|
|
69
|
+
connectionDispatchRevision: "main-revision",
|
|
70
|
+
dispatchKind: "concrete",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const boundFailures: BoundAssemblyFailureDTO[] = [
|
|
75
|
+
{
|
|
76
|
+
kind: "hook",
|
|
77
|
+
code: "ASSEMBLY_HOOK_FAILED",
|
|
78
|
+
phase: "context",
|
|
79
|
+
reason: "timeout",
|
|
80
|
+
message: "context hook timed out",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
kind: "macro",
|
|
84
|
+
code: "ASSEMBLY_MACRO_FAILED",
|
|
85
|
+
reason: "evaluation",
|
|
86
|
+
message: "macro failed",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
kind: "retrieval_snapshot",
|
|
90
|
+
code: "ASSEMBLY_RETRIEVAL_SNAPSHOT_UNAVAILABLE",
|
|
91
|
+
reason: "missing",
|
|
92
|
+
message: "snapshot unavailable",
|
|
93
|
+
},
|
|
94
|
+
{ kind: "abort", code: "ASSEMBLY_ABORTED", name: "AbortError", message: "aborted" },
|
|
95
|
+
{ kind: "precondition", code: "ASSEMBLY_PRECONDITION_FAILED", message: "stale dispatch" },
|
|
96
|
+
{ kind: "security", code: "ASSEMBLY_SECURITY_FAILED", message: "invalid source" },
|
|
97
|
+
{ kind: "internal", code: "ASSEMBLY_INTERNAL_FAILED", message: "host error" },
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
const boundOutcomes: BoundAssemblyOutcomeDTO[] = [
|
|
101
|
+
{ ok: true, result: boundSuccess },
|
|
102
|
+
...boundFailures.map((error) => ({ ok: false as const, error })),
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
function inspectBoundOutcome(outcome: BoundAssemblyOutcomeDTO): string {
|
|
106
|
+
if (outcome.ok) {
|
|
107
|
+
return outcome.result.resolved.connectionDispatchRevision;
|
|
108
|
+
}
|
|
109
|
+
switch (outcome.error.kind) {
|
|
110
|
+
case "hook":
|
|
111
|
+
return outcome.error.phase;
|
|
112
|
+
case "macro":
|
|
113
|
+
return outcome.error.reason;
|
|
114
|
+
case "retrieval_snapshot":
|
|
115
|
+
return outcome.error.reason;
|
|
116
|
+
case "abort":
|
|
117
|
+
return outcome.error.name;
|
|
118
|
+
case "precondition":
|
|
119
|
+
case "security":
|
|
120
|
+
case "internal":
|
|
121
|
+
return outcome.error.code;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const tool: ToolDefinitionDTO = {
|
|
126
|
+
name: "lookup",
|
|
127
|
+
description: "Look up a safe value",
|
|
128
|
+
parameters: { type: "object", properties: {} },
|
|
129
|
+
strict: true,
|
|
130
|
+
inputExamples: [{ query: "safe" }],
|
|
131
|
+
cache_control: { ttl: "request" },
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const continuation: BoundPrefillAttachmentDTO = {
|
|
135
|
+
id: "parent-attestation",
|
|
136
|
+
state: "available",
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const quietRequest: QuietTrackedRequestDTO = {
|
|
140
|
+
messages,
|
|
141
|
+
dispatch: slotDispatch,
|
|
142
|
+
continuation: {
|
|
143
|
+
parentPrefill: continuation,
|
|
144
|
+
mode: "append-parent-carrier-last",
|
|
145
|
+
},
|
|
146
|
+
parameters: { temperature: 0.2 },
|
|
147
|
+
reasoning: { effort: "low" },
|
|
148
|
+
tools: [tool],
|
|
149
|
+
deadlineAt: Date.now() + 1_000,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const usage: GenerationUsageDTO = {
|
|
153
|
+
prompt_tokens: 10,
|
|
154
|
+
completion_tokens: 5,
|
|
155
|
+
total_tokens: 15,
|
|
156
|
+
provider_raw: { cached: 2 },
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const response: GenerationResponseDTO = {
|
|
160
|
+
content: "safe response",
|
|
161
|
+
reasoning: "safe reasoning carrier",
|
|
162
|
+
finish_reason: "stop",
|
|
163
|
+
tool_calls: [
|
|
164
|
+
{ name: "lookup", args: { query: "safe" }, call_id: "call-1", thought_signature: "opaque" },
|
|
165
|
+
],
|
|
166
|
+
thinking_blocks: [{ type: "redacted_thinking", data: "opaque" }],
|
|
167
|
+
reasoning_details: [{ type: "summary_text", text: "provider detail" }],
|
|
168
|
+
usage,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const receipt: QuietDispatchReceiptDTO = {
|
|
172
|
+
providerInvoked: true,
|
|
173
|
+
terminalResponse: true,
|
|
174
|
+
source: "slot",
|
|
175
|
+
connectionId: "connection-id",
|
|
176
|
+
connectionDispatchRevision: "slot-revision",
|
|
177
|
+
usage,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const quietResults: QuietTrackedResultDTO[] = [
|
|
181
|
+
{ ok: true, response, receipt },
|
|
182
|
+
{
|
|
183
|
+
ok: false,
|
|
184
|
+
phase: "preflight",
|
|
185
|
+
providerInvoked: false,
|
|
186
|
+
receipt: null,
|
|
187
|
+
error: { kind: "precondition", code: "BOUND_INVOCATION_REQUIRED", name: "PreconditionError", message: "not bound" },
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
ok: false,
|
|
191
|
+
phase: "resolved",
|
|
192
|
+
receipt,
|
|
193
|
+
error: { kind: "provider", code: "PROVIDER_ACCESS_FAILED", name: "ProviderError", message: "provider unavailable" },
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
function inspectQuietResult(result: QuietTrackedResultDTO): string {
|
|
198
|
+
if (result.ok) return result.response.finish_reason;
|
|
199
|
+
if (result.phase === "preflight") return result.error.kind;
|
|
200
|
+
return `${result.error.kind}:${result.receipt.connectionDispatchRevision}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const descriptor: ConnectionDispatchDescriptorDTO = {
|
|
204
|
+
connectionId: "connection-id",
|
|
205
|
+
connectionName: "Safe connection",
|
|
206
|
+
provider: "provider",
|
|
207
|
+
model: "model",
|
|
208
|
+
endpointOrigin: "https://provider.example",
|
|
209
|
+
dispatchKind: "concrete",
|
|
210
|
+
connectionDispatchRevision: "slot-revision",
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const generateApi = {
|
|
214
|
+
assemble: async () => ({ ok: true as const, result: boundSuccess }),
|
|
215
|
+
quietTracked: async () => ({ ok: true as const, response, receipt }),
|
|
216
|
+
} satisfies Pick<SpindleGenerateAPI, "assemble" | "quietTracked">;
|
|
217
|
+
|
|
218
|
+
const connectionsApi: SpindleConnectionsAPI = {
|
|
219
|
+
resolveDispatch: async () => descriptor,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
declare const spindle: SpindleAPI;
|
|
223
|
+
const generateSurface: SpindleGenerateAPI = spindle.generate;
|
|
224
|
+
const connectionsSurface: SpindleConnectionsAPI = spindle.connections;
|
|
225
|
+
|
|
226
|
+
// These accesses intentionally fail if private runtime protocol material leaks
|
|
227
|
+
// into the public DTOs.
|
|
228
|
+
// @ts-expect-error invocation tokens are host-private
|
|
229
|
+
void ({} as BoundAssembleRequestDTO).invocationToken;
|
|
230
|
+
// @ts-expect-error snapshots are host-private
|
|
231
|
+
void ({} as QuietTrackedRequestDTO).parentGenerationSnapshot;
|
|
232
|
+
// @ts-expect-error leases are host-private
|
|
233
|
+
void ({} as QuietDispatchReceiptDTO).terminalFinalizationLease;
|
|
234
|
+
// @ts-expect-error API keys are not safe dispatch descriptor fields
|
|
235
|
+
void ({} as ConnectionDispatchDescriptorDTO).apiKey;
|
|
236
|
+
const invalidMainDispatch: GenerationDispatchSourceDTO = {
|
|
237
|
+
source: "main",
|
|
238
|
+
expectedConnectionDispatchRevision: "revision",
|
|
239
|
+
// @ts-expect-error a main source cannot carry an explicit slot id
|
|
240
|
+
connectionId: "must-not-be-public-on-main",
|
|
241
|
+
};
|
|
242
|
+
// @ts-expect-error attachment attestations are immutable
|
|
243
|
+
continuation.id = "mutated";
|
|
244
|
+
|
|
245
|
+
void inspectBoundOutcome;
|
|
246
|
+
void inspectQuietResult;
|
|
247
|
+
void generateApi;
|
|
248
|
+
void connectionsApi;
|
|
249
|
+
void generateSurface;
|
|
250
|
+
void connectionsSurface;
|
|
251
|
+
void boundRequest;
|
|
252
|
+
void quietRequest;
|
|
253
|
+
void boundOutcomes;
|
|
254
|
+
void quietResults;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
SPINDLE_COMPATIBILITY_ERROR_CODE,
|
|
4
|
+
SPINDLE_HOST_CAPABILITIES,
|
|
5
|
+
} from "../src/host";
|
|
6
|
+
import { ALL_PERMISSIONS, isValidPermission } from "../src/permissions";
|
|
7
|
+
|
|
8
|
+
test("final-response permission is valid and publicly declared", () => {
|
|
9
|
+
expect(ALL_PERMISSIONS).toContain("final_response");
|
|
10
|
+
expect(isValidPermission("final_response")).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test("host compatibility constants are canonical and immutable", () => {
|
|
14
|
+
expect(SPINDLE_COMPATIBILITY_ERROR_CODE).toBe("SPINDLE_COMPATIBILITY_ERROR");
|
|
15
|
+
expect(SPINDLE_HOST_CAPABILITIES).toEqual({
|
|
16
|
+
"preset-extension-data-v1": 1,
|
|
17
|
+
"preset-editor-v1": 1,
|
|
18
|
+
"loom-block-editor-v1": 1,
|
|
19
|
+
"generation-assembly-v1": 1,
|
|
20
|
+
"interceptor-context-v1": 1,
|
|
21
|
+
"interceptor-final-response-v1": 1,
|
|
22
|
+
});
|
|
23
|
+
expect(Object.isFrozen(SPINDLE_HOST_CAPABILITIES)).toBe(true);
|
|
24
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FinalResponseDTO,
|
|
3
|
+
HostToWorker,
|
|
4
|
+
InterceptorBreakdownEntryDTO,
|
|
5
|
+
InterceptorResultDTO,
|
|
6
|
+
SpindleAPI,
|
|
7
|
+
SpindleFrontendContext,
|
|
8
|
+
SpindleHostDescriptorV1,
|
|
9
|
+
SpindleHostLocale,
|
|
10
|
+
SpindleHostLocaleAPI,
|
|
11
|
+
SpindleManifest,
|
|
12
|
+
SpindlePermission,
|
|
13
|
+
SpindleTabLocation,
|
|
14
|
+
WorkerToHost,
|
|
15
|
+
} from "lumiverse-spindle-types";
|
|
16
|
+
import {
|
|
17
|
+
ALL_PERMISSIONS,
|
|
18
|
+
SPINDLE_COMPATIBILITY_ERROR_CODE,
|
|
19
|
+
SPINDLE_HOST_CAPABILITIES,
|
|
20
|
+
isValidPermission,
|
|
21
|
+
} from "lumiverse-spindle-types";
|
|
22
|
+
|
|
23
|
+
const host: SpindleHostDescriptorV1 = {
|
|
24
|
+
descriptorVersion: 1,
|
|
25
|
+
lumiverseVersion: "1.2.3",
|
|
26
|
+
capabilities: SPINDLE_HOST_CAPABILITIES,
|
|
27
|
+
extensionInstallationId: "550e8400-e29b-41d4-a716-446655440000",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const localeApi: SpindleHostLocaleAPI = {
|
|
31
|
+
get: () => "en",
|
|
32
|
+
subscribe: (listener) => {
|
|
33
|
+
listener("fr");
|
|
34
|
+
return () => undefined;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const locale: SpindleHostLocale = localeApi.get();
|
|
39
|
+
const permission: SpindlePermission = "final_response";
|
|
40
|
+
const allPermissions: readonly SpindlePermission[] = ALL_PERMISSIONS;
|
|
41
|
+
if (!isValidPermission(permission) || !allPermissions.includes(permission)) {
|
|
42
|
+
throw new Error("final_response must be a valid public permission");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const finalResponse: FinalResponseDTO = {
|
|
46
|
+
content: "An extension-authored final answer",
|
|
47
|
+
reasoning: "The selected branch completed successfully.",
|
|
48
|
+
fallbackMessageIndex: 0,
|
|
49
|
+
};
|
|
50
|
+
const breakdown: InterceptorBreakdownEntryDTO = { messageIndex: 0, name: "Additional context" };
|
|
51
|
+
|
|
52
|
+
const result: InterceptorResultDTO = {
|
|
53
|
+
messages: [{ role: "system", content: "Additional context" }],
|
|
54
|
+
breakdown: [breakdown],
|
|
55
|
+
finalResponse,
|
|
56
|
+
};
|
|
57
|
+
const wireResult: WorkerToHost = {
|
|
58
|
+
type: "intercept_result",
|
|
59
|
+
requestId: "request-id",
|
|
60
|
+
registrationId: "registration-id",
|
|
61
|
+
messages: result.messages,
|
|
62
|
+
breakdown: [breakdown],
|
|
63
|
+
finalResponse,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const init: HostToWorker = {
|
|
67
|
+
type: "init",
|
|
68
|
+
manifest: {
|
|
69
|
+
version: "1.0.0",
|
|
70
|
+
name: "Legacy extension",
|
|
71
|
+
identifier: "legacy_extension",
|
|
72
|
+
author: "Extension author",
|
|
73
|
+
github: "https://github.com/example/legacy-extension",
|
|
74
|
+
homepage: "https://example.com/legacy-extension",
|
|
75
|
+
permissions: [],
|
|
76
|
+
},
|
|
77
|
+
storagePath: "/extensions/legacy_extension",
|
|
78
|
+
host,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// The compatibility field is intentionally optional for legacy manifests.
|
|
82
|
+
const legacyManifest: SpindleManifest = {
|
|
83
|
+
version: "1.0.0",
|
|
84
|
+
name: "Legacy extension",
|
|
85
|
+
identifier: "legacy_extension",
|
|
86
|
+
author: "Extension author",
|
|
87
|
+
github: "https://github.com/example/legacy-extension",
|
|
88
|
+
homepage: "https://example.com/legacy-extension",
|
|
89
|
+
permissions: [],
|
|
90
|
+
};
|
|
91
|
+
// @ts-expect-error host descriptor is required on the public init message
|
|
92
|
+
const legacyInitWithoutHost: HostToWorker = {
|
|
93
|
+
type: "init",
|
|
94
|
+
manifest: legacyManifest,
|
|
95
|
+
storagePath: "/extensions/legacy_extension",
|
|
96
|
+
};
|
|
97
|
+
void locale;
|
|
98
|
+
void result;
|
|
99
|
+
void legacyManifest;
|
|
100
|
+
|
|
101
|
+
declare const spindle: SpindleAPI;
|
|
102
|
+
const backendHost: SpindleHostDescriptorV1 = spindle.host;
|
|
103
|
+
// @ts-expect-error the host descriptor is exposed as a readonly API property
|
|
104
|
+
spindle.host = host;
|
|
105
|
+
// @ts-expect-error nested capability records are readonly
|
|
106
|
+
spindle.host.capabilities["interceptor-final-response-v1"] = 2;
|
|
107
|
+
// @ts-expect-error descriptor scalar fields are readonly
|
|
108
|
+
spindle.host.lumiverseVersion = "2.0.0";
|
|
109
|
+
void backendHost;
|
|
110
|
+
|
|
111
|
+
declare const ctx: SpindleFrontendContext;
|
|
112
|
+
const frontendHost: SpindleHostDescriptorV1 = ctx.host;
|
|
113
|
+
const activeLocale: SpindleHostLocale = ctx.locale.get();
|
|
114
|
+
const tabLocation: SpindleTabLocation = ctx.ui.getTabLocation("profile");
|
|
115
|
+
const unsubscribe = ctx.locale.subscribe((nextLocale) => {
|
|
116
|
+
const checked: SpindleHostLocale = nextLocale;
|
|
117
|
+
void checked;
|
|
118
|
+
});
|
|
119
|
+
// @ts-expect-error the frontend host descriptor is exposed as a readonly API property
|
|
120
|
+
ctx.host = host;
|
|
121
|
+
// @ts-expect-error the frontend locale API is exposed as a readonly API property
|
|
122
|
+
ctx.locale = localeApi;
|
|
123
|
+
void frontendHost;
|
|
124
|
+
void activeLocale;
|
|
125
|
+
void unsubscribe;
|
|
126
|
+
void tabLocation;
|
|
127
|
+
const compatibilityCode: "SPINDLE_COMPATIBILITY_ERROR" = SPINDLE_COMPATIBILITY_ERROR_CODE;
|
|
128
|
+
void compatibilityCode;
|
|
129
|
+
|
|
130
|
+
const asyncFrontendModule = {
|
|
131
|
+
async setup(_ctx: SpindleFrontendContext) {
|
|
132
|
+
return async () => undefined;
|
|
133
|
+
},
|
|
134
|
+
async teardown() {},
|
|
135
|
+
};
|
|
136
|
+
const typedAsyncFrontendModule: import("lumiverse-spindle-types").SpindleFrontendModule = asyncFrontendModule;
|
|
137
|
+
void typedAsyncFrontendModule;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BoundAssembleRequestDTO,
|
|
3
|
+
BoundPrefillAttachmentDTO,
|
|
4
|
+
ConnectionDispatchDescriptorDTO,
|
|
5
|
+
DeferredGuidanceDTO,
|
|
6
|
+
HostToWorker,
|
|
7
|
+
InterceptorContextDTO,
|
|
8
|
+
InterceptorGenerationType,
|
|
9
|
+
InterceptorHandler,
|
|
10
|
+
InterceptorMatchDTO,
|
|
11
|
+
InterceptorRegistrationMatchOptions,
|
|
12
|
+
InterceptorRegistrationOptions,
|
|
13
|
+
InterceptorResultDTO,
|
|
14
|
+
InterceptorDisposer,
|
|
15
|
+
InterceptorBreakdownEntryDTO,
|
|
16
|
+
LlmMessageDTO,
|
|
17
|
+
QuietTrackedRequestDTO,
|
|
18
|
+
SpindleAPI,
|
|
19
|
+
WorkerToHost,
|
|
20
|
+
} from "lumiverse-spindle-types";
|
|
21
|
+
|
|
22
|
+
const generationType: InterceptorGenerationType = "continue";
|
|
23
|
+
const match: InterceptorMatchDTO = {
|
|
24
|
+
generationTypes: ["normal", generationType],
|
|
25
|
+
isDryRun: false,
|
|
26
|
+
presetField: {
|
|
27
|
+
path: ["activeMode"],
|
|
28
|
+
exists: true,
|
|
29
|
+
notIn: ["single", 0, false, null],
|
|
30
|
+
oneOf: ["sequential", "parallel"],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const matchOptions: InterceptorRegistrationMatchOptions = { match };
|
|
34
|
+
const options: InterceptorRegistrationOptions = { priority: 900, match };
|
|
35
|
+
|
|
36
|
+
const descriptor: ConnectionDispatchDescriptorDTO = {
|
|
37
|
+
connectionId: "connection-id",
|
|
38
|
+
connectionName: "Main",
|
|
39
|
+
provider: "provider",
|
|
40
|
+
model: "model",
|
|
41
|
+
endpointOrigin: "https://provider.example",
|
|
42
|
+
dispatchKind: "concrete",
|
|
43
|
+
connectionDispatchRevision: "revision-1",
|
|
44
|
+
};
|
|
45
|
+
const prefillCarrier: BoundPrefillAttachmentDTO = {
|
|
46
|
+
id: "prefill-attestation",
|
|
47
|
+
state: "available",
|
|
48
|
+
};
|
|
49
|
+
const context: InterceptorContextDTO = {
|
|
50
|
+
userId: "user-id",
|
|
51
|
+
chatId: "chat-id",
|
|
52
|
+
generationId: "generation-id",
|
|
53
|
+
generationType,
|
|
54
|
+
isDryRun: false,
|
|
55
|
+
presetId: "preset-id",
|
|
56
|
+
presetMetadata: { activeMode: "parallel" },
|
|
57
|
+
personaId: null,
|
|
58
|
+
characterId: "character-id",
|
|
59
|
+
personaAddonStates: { example: true },
|
|
60
|
+
excludeMessageId: "message-id",
|
|
61
|
+
rejectedSwipe: "rejected-swipe",
|
|
62
|
+
regenFeedback: "try again",
|
|
63
|
+
regenFeedbackPosition: "user",
|
|
64
|
+
mainDispatch: {
|
|
65
|
+
source: "main",
|
|
66
|
+
descriptor,
|
|
67
|
+
connectionDispatchRevision: "revision-1",
|
|
68
|
+
dispatchKind: "concrete",
|
|
69
|
+
},
|
|
70
|
+
prefillCarrier,
|
|
71
|
+
interceptorDeadlineAt: Date.now() + 1_000,
|
|
72
|
+
boundWorkDeadlineAt: Date.now() + 900,
|
|
73
|
+
signal: new AbortController().signal,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const guidance: DeferredGuidanceDTO = {
|
|
77
|
+
id: "7c6d2b1a-5f44-4e90-8a31-2d7b9c4e6f80",
|
|
78
|
+
content: "Keep the response concise.",
|
|
79
|
+
role: "system",
|
|
80
|
+
};
|
|
81
|
+
const breakdown: InterceptorBreakdownEntryDTO = { messageIndex: 0, name: "injected" };
|
|
82
|
+
const messages: LlmMessageDTO[] = [{ role: "user", content: "hello" }];
|
|
83
|
+
|
|
84
|
+
const callback: InterceptorHandler = async (input, callbackContext) => {
|
|
85
|
+
const userId: string = callbackContext.userId;
|
|
86
|
+
const generationId: string = callbackContext.generationId;
|
|
87
|
+
const dispatchKind: "concrete" | "roulette" | null = callbackContext.mainDispatch.dispatchKind;
|
|
88
|
+
const signal: AbortSignal = callbackContext.signal;
|
|
89
|
+
const metadata: unknown = callbackContext.presetMetadata;
|
|
90
|
+
void userId;
|
|
91
|
+
void generationId;
|
|
92
|
+
void dispatchKind;
|
|
93
|
+
void signal;
|
|
94
|
+
void metadata;
|
|
95
|
+
const boundAssembly = await spindle.generate.assemble({
|
|
96
|
+
blocks: [],
|
|
97
|
+
dispatch: {
|
|
98
|
+
source: "main",
|
|
99
|
+
expectedConnectionDispatchRevision: "revision-1",
|
|
100
|
+
},
|
|
101
|
+
deadlineAt: Date.now() + 900,
|
|
102
|
+
signal,
|
|
103
|
+
});
|
|
104
|
+
const boundQuiet = await spindle.generate.quietTracked({
|
|
105
|
+
messages: input,
|
|
106
|
+
dispatch: {
|
|
107
|
+
source: "main",
|
|
108
|
+
expectedConnectionDispatchRevision: "revision-1",
|
|
109
|
+
},
|
|
110
|
+
deadlineAt: Date.now() + 900,
|
|
111
|
+
signal,
|
|
112
|
+
});
|
|
113
|
+
void boundAssembly;
|
|
114
|
+
void boundQuiet;
|
|
115
|
+
// @ts-expect-error callback context is readonly
|
|
116
|
+
callbackContext.userId = "must-not-compile";
|
|
117
|
+
// @ts-expect-error nested dispatch context is readonly
|
|
118
|
+
callbackContext.mainDispatch.dispatchKind = "roulette";
|
|
119
|
+
return {
|
|
120
|
+
messages: input,
|
|
121
|
+
breakdown: [breakdown],
|
|
122
|
+
deferredGuidance: [guidance],
|
|
123
|
+
} satisfies InterceptorResultDTO;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const legacyArrayCallback: InterceptorHandler = async (input) => input;
|
|
127
|
+
const legacyResultCallback: InterceptorHandler = async (input) => ({ messages: input });
|
|
128
|
+
|
|
129
|
+
declare const spindle: SpindleAPI;
|
|
130
|
+
const priorityDisposer: InterceptorDisposer = spindle.registerInterceptor(callback, 900, matchOptions);
|
|
131
|
+
const optionsDisposer: InterceptorDisposer = spindle.registerInterceptor(callback, options);
|
|
132
|
+
const handlerOnlyDisposer: InterceptorDisposer = spindle.registerInterceptor(legacyArrayCallback);
|
|
133
|
+
const legacyPriorityDisposer: InterceptorDisposer = spindle.registerInterceptor(legacyResultCallback, 100);
|
|
134
|
+
priorityDisposer();
|
|
135
|
+
optionsDisposer();
|
|
136
|
+
handlerOnlyDisposer();
|
|
137
|
+
legacyPriorityDisposer();
|
|
138
|
+
|
|
139
|
+
const { signal: _signal, ...wireContext } = context;
|
|
140
|
+
const wireIntercept: HostToWorker = {
|
|
141
|
+
type: "intercept_request",
|
|
142
|
+
requestId: "request-id",
|
|
143
|
+
registrationId: "registration-id",
|
|
144
|
+
messages,
|
|
145
|
+
context: wireContext,
|
|
146
|
+
};
|
|
147
|
+
const abortIntercept: HostToWorker = {
|
|
148
|
+
type: "intercept_abort",
|
|
149
|
+
requestId: "request-id",
|
|
150
|
+
registrationId: "registration-id",
|
|
151
|
+
};
|
|
152
|
+
const register: WorkerToHost = {
|
|
153
|
+
type: "register_interceptor",
|
|
154
|
+
registrationId: "registration-id",
|
|
155
|
+
priority: 900,
|
|
156
|
+
match,
|
|
157
|
+
};
|
|
158
|
+
const unregister: WorkerToHost = {
|
|
159
|
+
type: "unregister_interceptor",
|
|
160
|
+
registrationId: "registration-id",
|
|
161
|
+
};
|
|
162
|
+
const result: WorkerToHost = {
|
|
163
|
+
type: "intercept_result",
|
|
164
|
+
requestId: "request-id",
|
|
165
|
+
registrationId: "registration-id",
|
|
166
|
+
messages,
|
|
167
|
+
deferredGuidance: [guidance],
|
|
168
|
+
};
|
|
169
|
+
const assembleBound: WorkerToHost = {
|
|
170
|
+
type: "generate_assemble",
|
|
171
|
+
requestId: "assemble-id",
|
|
172
|
+
input: {
|
|
173
|
+
blocks: [] as BoundAssembleRequestDTO["blocks"],
|
|
174
|
+
dispatch: { source: "main", expectedConnectionDispatchRevision: "revision-1" },
|
|
175
|
+
deadlineAt: Date.now() + 500,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const quietTracked: WorkerToHost = {
|
|
179
|
+
type: "generate_quiet_tracked",
|
|
180
|
+
requestId: "quiet-id",
|
|
181
|
+
input: {
|
|
182
|
+
messages,
|
|
183
|
+
dispatch: { source: "main", expectedConnectionDispatchRevision: "revision-1" },
|
|
184
|
+
deadlineAt: Date.now() + 500,
|
|
185
|
+
} satisfies Omit<QuietTrackedRequestDTO, "signal">,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
void abortIntercept;
|
|
189
|
+
void assembleBound;
|
|
190
|
+
void options;
|
|
191
|
+
void priorityDisposer;
|
|
192
|
+
void quietTracked;
|
|
193
|
+
void register;
|
|
194
|
+
void result;
|
|
195
|
+
void unregister;
|
|
196
|
+
void wireIntercept;
|
package/tsconfig.consumer.json
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
"outDir": "./.consumer-dist",
|
|
6
6
|
"declaration": false
|
|
7
7
|
},
|
|
8
|
-
"include": ["test/loom-block-editor-consumer.ts"]
|
|
8
|
+
"include": ["test/loom-block-editor-consumer.ts", "test/bound-generation-consumer.ts", "test/interceptor-consumer.ts", "test/host-runtime-contract-consumer.ts"]
|
|
9
9
|
}
|