@blastlauncher/protocol 0.0.0-stage → 3.0.0-alpha.1

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,115 @@
1
+ /** 8 MiB, per docs/decisions/0001: lines have a size cap. */
2
+ export const DEFAULT_MAX_LINE_BYTES = 8 * 1024 * 1024;
3
+ /** One message as one JSON line, ready to write to the stream. */
4
+ export function encodeMessage(message) {
5
+ return `${JSON.stringify(message)}\n`;
6
+ }
7
+ const NEWLINE = 0x0a;
8
+ const HIGH_SURROGATE_START = 0xd800;
9
+ const HIGH_SURROGATE_END = 0xdbff;
10
+ export function createLineDecoder(options) {
11
+ const maxLineBytes = options.maxLineBytes ?? DEFAULT_MAX_LINE_BYTES;
12
+ if (!Number.isSafeInteger(maxLineBytes) || maxLineBytes < 1) {
13
+ throw new RangeError("maxLineBytes must be a positive integer");
14
+ }
15
+ const encoder = new TextEncoder();
16
+ // `fatal` turns a truncated or overlong sequence into a protocol error rather
17
+ // than silently writing U+FFFD into the message.
18
+ const decoder = new TextDecoder("utf-8", { fatal: true });
19
+ let buffer = new Uint8Array(0);
20
+ // A string chunk can end between the halves of a surrogate pair; the high
21
+ // half waits here so the pair is encoded as one character.
22
+ let heldHighSurrogate = "";
23
+ let stopped = false;
24
+ function stop(error) {
25
+ stopped = true;
26
+ options.onError(error);
27
+ }
28
+ function append(bytes) {
29
+ const joined = new Uint8Array(buffer.length + bytes.length);
30
+ joined.set(buffer);
31
+ joined.set(bytes, buffer.length);
32
+ buffer = joined;
33
+ }
34
+ function emitLine(line) {
35
+ if (line.length > maxLineBytes) {
36
+ stop(`line exceeds ${maxLineBytes} bytes`);
37
+ return;
38
+ }
39
+ let text;
40
+ try {
41
+ text = decoder.decode(line);
42
+ }
43
+ catch {
44
+ stop("line is not valid UTF-8");
45
+ return;
46
+ }
47
+ // Whitespace-only lines are framing noise, not messages.
48
+ if (text.trim() === "")
49
+ return;
50
+ let value;
51
+ try {
52
+ value = JSON.parse(text);
53
+ }
54
+ catch (error) {
55
+ stop(`invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
56
+ return;
57
+ }
58
+ options.onLine(value);
59
+ }
60
+ function drain() {
61
+ let start = 0;
62
+ for (;;) {
63
+ const index = buffer.indexOf(NEWLINE, start);
64
+ if (index === -1)
65
+ break;
66
+ emitLine(buffer.subarray(start, index));
67
+ if (stopped)
68
+ return;
69
+ start = index + 1;
70
+ }
71
+ if (start > 0)
72
+ buffer = buffer.slice(start);
73
+ // Checked before the terminator arrives so one bad line cannot grow the
74
+ // buffer without bound.
75
+ if (buffer.length > maxLineBytes)
76
+ stop(`line exceeds ${maxLineBytes} bytes`);
77
+ }
78
+ function encodeChunk(text) {
79
+ let value = heldHighSurrogate + text;
80
+ heldHighSurrogate = "";
81
+ const last = value.charCodeAt(value.length - 1);
82
+ if (last >= HIGH_SURROGATE_START && last <= HIGH_SURROGATE_END) {
83
+ heldHighSurrogate = value.slice(-1);
84
+ value = value.slice(0, -1);
85
+ }
86
+ return encoder.encode(value);
87
+ }
88
+ return {
89
+ push(chunk) {
90
+ if (stopped)
91
+ return;
92
+ // Copy the caller's bytes: the buffer outlives the call.
93
+ const bytes = typeof chunk === "string" ? encodeChunk(chunk) : new Uint8Array(chunk);
94
+ if (bytes.length === 0)
95
+ return;
96
+ append(bytes);
97
+ drain();
98
+ },
99
+ end() {
100
+ if (stopped)
101
+ return;
102
+ if (heldHighSurrogate !== "") {
103
+ append(encoder.encode(heldHighSurrogate));
104
+ heldHighSurrogate = "";
105
+ }
106
+ const leftover = buffer;
107
+ buffer = new Uint8Array(0);
108
+ if (leftover.length === 0) {
109
+ stopped = true;
110
+ return;
111
+ }
112
+ stop(`unterminated line: ${leftover.length} bytes without a newline`);
113
+ },
114
+ };
115
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./capabilities.js";
2
+ export * from "./framing.js";
3
+ export * from "./messages.js";
4
+ export * from "./provider.js";
5
+ export * from "./provider-codec.js";
6
+ export * from "./embedder.js";
7
+ export * from "./embedder-schema.js";
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Public surface: everything a host or a runtime needs to speak the protocol.
2
+ export * from "./capabilities.js";
3
+ export * from "./framing.js";
4
+ export * from "./messages.js";
5
+ export * from "./provider.js";
6
+ export * from "./provider-codec.js";
7
+ export * from "./embedder.js";
8
+ export * from "./embedder-schema.js";
@@ -0,0 +1,153 @@
1
+ import type { ModelStreamPart, RegisteredModel } from "./provider.js";
2
+ export declare const PROTOCOL_VERSION = 1;
3
+ /** A value that survives `JSON.stringify` -> `JSON.parse` unchanged. */
4
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
5
+ [key: string]: JsonValue;
6
+ };
7
+ /** A callback prop on the wire; the host answers with an `event` message. */
8
+ export type HandlerRef = {
9
+ $handler: string;
10
+ };
11
+ export type SceneNode = {
12
+ id: string;
13
+ type: string;
14
+ props: Record<string, JsonValue | HandlerRef>;
15
+ children: SceneNode[];
16
+ };
17
+ /** How the host expects the command to run. */
18
+ export type CommandMode = "view" | "no-view" | "menu-bar" | "model-provider";
19
+ export type InitCommand = {
20
+ extension: string;
21
+ name: string;
22
+ entry: string;
23
+ mode: CommandMode;
24
+ /** Manifest argument values, keyed by argument name (decision 0001). */
25
+ arguments?: Record<string, string>;
26
+ /** The `launchCommand({ context })` payload of this launch, if any. */
27
+ launchContext?: Record<string, JsonValue>;
28
+ /** Search text supplied when invoking a fallback command. */
29
+ fallbackText?: string;
30
+ /** A stored `Form` draft for this command, read by the host before launch. */
31
+ draftValues?: Record<string, JsonValue>;
32
+ };
33
+ export type InitEnvironment = {
34
+ availableAPIs?: ("AI" | "BrowserExtension")[];
35
+ extensionName: string;
36
+ commandName: string;
37
+ /** The manifest's `owner`, falling back to `author` (Raycast's `ownerOrAuthorName`). */
38
+ ownerOrAuthorName: string;
39
+ assetsPath: string;
40
+ supportPath: string;
41
+ isDevelopment: boolean;
42
+ /** The active theme; a headless host has no UI, so the default is `"dark"`. */
43
+ appearance: "light" | "dark";
44
+ textSize: "medium" | "large";
45
+ };
46
+ export type InitMessage = {
47
+ type: "init";
48
+ protocol: number;
49
+ command: InitCommand;
50
+ preferences: Record<string, JsonValue>;
51
+ environment: InitEnvironment;
52
+ };
53
+ export type EventMessage = {
54
+ type: "event";
55
+ node: string;
56
+ handler: string;
57
+ args: JsonValue[];
58
+ };
59
+ export type ErrorInfo = {
60
+ code: string;
61
+ message: string;
62
+ };
63
+ export type CapabilityResultMessage = {
64
+ type: "capability.result";
65
+ id: string;
66
+ ok: true;
67
+ value: JsonValue;
68
+ } | {
69
+ type: "capability.result";
70
+ id: string;
71
+ ok: false;
72
+ error: ErrorInfo;
73
+ };
74
+ export type ShutdownMessage = {
75
+ type: "shutdown";
76
+ };
77
+ /** Messages the host sends to the runtime. */
78
+ export type ProviderRequest = {
79
+ type: "provider.getModels";
80
+ id: string;
81
+ } | {
82
+ type: "provider.stream";
83
+ id: string;
84
+ model: RegisteredModel;
85
+ request: Record<string, JsonValue>;
86
+ } | {
87
+ type: "provider.abort";
88
+ id: string;
89
+ };
90
+ export type ProviderResponse = {
91
+ type: "provider.models";
92
+ id: string;
93
+ models: RegisteredModel[];
94
+ } | {
95
+ type: "provider.part";
96
+ id: string;
97
+ part: ModelStreamPart;
98
+ } | {
99
+ type: "provider.end";
100
+ id: string;
101
+ } | {
102
+ type: "provider.error";
103
+ id: string;
104
+ error: ErrorInfo;
105
+ };
106
+ export type HostMessage = InitMessage | EventMessage | CapabilityResultMessage | {
107
+ type: "capability.progress";
108
+ id: string;
109
+ value: JsonValue;
110
+ } | ShutdownMessage | ProviderRequest;
111
+ export type ReadyMessage = {
112
+ type: "ready";
113
+ protocol: number;
114
+ };
115
+ export type SceneMessage = {
116
+ type: "scene";
117
+ revision: number;
118
+ root: SceneNode | null;
119
+ };
120
+ export type CapabilityRequestMessage = {
121
+ type: "capability.request";
122
+ id: string;
123
+ name: string;
124
+ params: JsonValue;
125
+ };
126
+ export type LogLevel = "debug" | "info" | "warn" | "error";
127
+ export type LogMessage = {
128
+ type: "log";
129
+ level: LogLevel;
130
+ message: string;
131
+ };
132
+ export type ErrorMessage = {
133
+ type: "error";
134
+ code: string;
135
+ message: string;
136
+ fatal: boolean;
137
+ };
138
+ /** Messages the runtime sends to the host. */
139
+ export type RuntimeMessage = ProviderResponse | ReadyMessage | SceneMessage | CapabilityRequestMessage | {
140
+ type: "capability.cancel";
141
+ id: string;
142
+ } | LogMessage | ErrorMessage;
143
+ export type ParseResult<T> = {
144
+ ok: true;
145
+ message: T;
146
+ } | {
147
+ ok: false;
148
+ error: string;
149
+ };
150
+ /** Validates an untyped value as a host -> runtime message. */
151
+ export declare function parseHostMessage(value: unknown): ParseResult<HostMessage>;
152
+ /** Validates an untyped value as a runtime -> host message. */
153
+ export declare function parseRuntimeMessage(value: unknown): ParseResult<RuntimeMessage>;