@fluxy-chat/protocol 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @fluxy-chat/protocol
2
+
3
+ Shared wire protocol for FluxyChat clients and the worker.
4
+
5
+ ## What it covers
6
+
7
+ - Canonical **inbound** WebSocket event types (server → client)
8
+ - Canonical **outbound** client event types (client → room)
9
+ - Lightweight runtime guards (`isFluxyInboundEvent`, `isFluxyOutboundEvent`)
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import { isFluxyInboundEvent, FLUXY_INBOUND_EVENT_TYPES } from "@fluxy-chat/protocol";
15
+
16
+ socket.onmessage = (raw) => {
17
+ const data = JSON.parse(raw.data);
18
+ if (!isFluxyInboundEvent(data)) return;
19
+ // dispatch by data.type
20
+ };
21
+ ```
22
+
23
+ ## P21 contract tests
24
+
25
+ SDK packages should keep their event unions aligned with `FLUXY_INBOUND_EVENT_TYPES`. When adding a new WS event in the worker:
26
+
27
+ 1. Add to `@fluxy-chat/protocol` inbound/outbound lists
28
+ 2. Update Room DO handler
29
+ 3. Extend SDK `FluxyChatEvent` union
30
+ 4. Run `pnpm --filter @fluxy-chat/protocol test`
@@ -0,0 +1,10 @@
1
+ export interface InboundDispatchHandlers {
2
+ onPong: () => void;
3
+ onReplay: (messages: unknown[]) => void;
4
+ onHistoryMarker: () => void;
5
+ onWorkerError: (message?: string) => void;
6
+ onEvent: (event: Record<string, unknown>) => void;
7
+ }
8
+ /** Shared WS frame dispatch for all FluxyChat client SDKs. */
9
+ export declare function dispatchInboundWsFrame(raw: string, handlers: InboundDispatchHandlers): void;
10
+ //# sourceMappingURL=dispatch-inbound-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-inbound-frame.d.ts","sourceRoot":"","sources":["../src/dispatch-inbound-frame.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACxC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,aAAa,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACnD;AAED,8DAA8D;AAC9D,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,uBAAuB,GAAG,IAAI,CA4B3F"}
@@ -0,0 +1,27 @@
1
+ import { parseInboundWsFrame } from "./parse-inbound-frame.js";
2
+ /** Shared WS frame dispatch for all FluxyChat client SDKs. */
3
+ export function dispatchInboundWsFrame(raw, handlers) {
4
+ const frame = parseInboundWsFrame(raw);
5
+ if (!frame)
6
+ return;
7
+ if (frame.kind === "pong") {
8
+ handlers.onPong();
9
+ return;
10
+ }
11
+ if (frame.kind === "replay") {
12
+ handlers.onReplay(frame.messages ?? []);
13
+ return;
14
+ }
15
+ if (frame.kind === "ignored")
16
+ return;
17
+ const event = frame.event;
18
+ if (!event || typeof event.type !== "string")
19
+ return;
20
+ if (event.type === "history") {
21
+ handlers.onHistoryMarker();
22
+ }
23
+ if (event.type === "error") {
24
+ handlers.onWorkerError(typeof event.message === "string" ? event.message : undefined);
25
+ }
26
+ handlers.onEvent(event);
27
+ }
@@ -0,0 +1,12 @@
1
+ /** Inbound WebSocket events broadcast by the worker / Room DO. */
2
+ export declare const FLUXY_INBOUND_EVENT_TYPES: readonly ["message", "message_edit", "message_delete", "message_expired", "typing", "subscription_succeeded", "subscription_count", "member_joined", "member_left", "client_event", "agentTyping", "tool_call", "tool_result", "tool_error", "agentRun", "presence", "cache_snapshot", "server_event", "user_event", "user_subscription_succeeded", "state_change", "stream", "pong", "error"];
3
+ /** Outbound client → room events (Room DO handlers). */
4
+ export declare const FLUXY_OUTBOUND_EVENT_TYPES: readonly ["ping", "message", "stream", "edit", "reaction", "read", "delete", "typing", "client_event", "agentTyping"];
5
+ /** Worker → client transport frames handled before dispatch. */
6
+ export declare const FLUXY_TRANSPORT_INBOUND_TYPES: readonly ["pong", "replay"];
7
+ /** Client-side synthetic events (REST replay / local merge). */
8
+ export declare const FLUXY_SDK_SYNTHETIC_INBOUND_TYPES: readonly ["history"];
9
+ export type FluxyInboundEventType = (typeof FLUXY_INBOUND_EVENT_TYPES)[number];
10
+ export type FluxyOutboundEventType = (typeof FLUXY_OUTBOUND_EVENT_TYPES)[number];
11
+ export declare const FLUXY_PROTOCOL_VERSION = "1.0.0";
12
+ //# sourceMappingURL=event-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-types.d.ts","sourceRoot":"","sources":["../src/event-types.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,eAAO,MAAM,yBAAyB,gYAyB5B,CAAC;AAEX,wDAAwD;AACxD,eAAO,MAAM,0BAA0B,uHAW7B,CAAC;AAEX,gEAAgE;AAChE,eAAO,MAAM,6BAA6B,6BAA8B,CAAC;AAEzE,gEAAgE;AAChE,eAAO,MAAM,iCAAiC,sBAAuB,CAAC;AAEtE,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/E,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF,eAAO,MAAM,sBAAsB,UAAU,CAAC"}
@@ -0,0 +1,45 @@
1
+ /** Inbound WebSocket events broadcast by the worker / Room DO. */
2
+ export const FLUXY_INBOUND_EVENT_TYPES = [
3
+ "message",
4
+ "message_edit",
5
+ "message_delete",
6
+ "message_expired",
7
+ "typing",
8
+ "subscription_succeeded",
9
+ "subscription_count",
10
+ "member_joined",
11
+ "member_left",
12
+ "client_event",
13
+ "agentTyping",
14
+ "tool_call",
15
+ "tool_result",
16
+ "tool_error",
17
+ "agentRun",
18
+ "presence",
19
+ "cache_snapshot",
20
+ "server_event",
21
+ "user_event",
22
+ "user_subscription_succeeded",
23
+ "state_change",
24
+ "stream",
25
+ "pong",
26
+ "error",
27
+ ];
28
+ /** Outbound client → room events (Room DO handlers). */
29
+ export const FLUXY_OUTBOUND_EVENT_TYPES = [
30
+ "ping",
31
+ "message",
32
+ "stream",
33
+ "edit",
34
+ "reaction",
35
+ "read",
36
+ "delete",
37
+ "typing",
38
+ "client_event",
39
+ "agentTyping",
40
+ ];
41
+ /** Worker → client transport frames handled before dispatch. */
42
+ export const FLUXY_TRANSPORT_INBOUND_TYPES = ["pong", "replay"];
43
+ /** Client-side synthetic events (REST replay / local merge). */
44
+ export const FLUXY_SDK_SYNTHETIC_INBOUND_TYPES = ["history"];
45
+ export const FLUXY_PROTOCOL_VERSION = "1.0.0";
@@ -0,0 +1,16 @@
1
+ import { FLUXY_INBOUND_EVENT_TYPES, FLUXY_OUTBOUND_EVENT_TYPES, FLUXY_PROTOCOL_VERSION, FLUXY_SDK_SYNTHETIC_INBOUND_TYPES, FLUXY_TRANSPORT_INBOUND_TYPES, type FluxyInboundEventType, type FluxyOutboundEventType } from "./event-types.js";
2
+ export { FLUXY_INBOUND_EVENT_TYPES, FLUXY_OUTBOUND_EVENT_TYPES, FLUXY_PROTOCOL_VERSION, FLUXY_SDK_SYNTHETIC_INBOUND_TYPES, FLUXY_TRANSPORT_INBOUND_TYPES, type FluxyInboundEventType, type FluxyOutboundEventType, };
3
+ export interface FluxyProtocolEventBase {
4
+ type: string;
5
+ }
6
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
7
+ export declare function isFluxyInboundEvent(value: unknown): value is FluxyProtocolEventBase & {
8
+ type: FluxyInboundEventType;
9
+ };
10
+ export declare function isFluxyOutboundEvent(value: unknown): value is FluxyProtocolEventBase & {
11
+ type: FluxyOutboundEventType;
12
+ };
13
+ export declare function assertInboundEventType(type: string): FluxyInboundEventType | null;
14
+ export { parseInboundWsFrame, isKnownOutboundClientEvent, type ParsedInboundWsFrame, type InboundFrameKind, } from "./parse-inbound-frame.js";
15
+ export { dispatchInboundWsFrame, type InboundDispatchHandlers, } from "./dispatch-inbound-frame.js";
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,iCAAiC,EACjC,6BAA6B,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,EACtB,iCAAiC,EACjC,6BAA6B,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,GAAG;IAAE,IAAI,EAAE,qBAAqB,CAAA;CAAE,CAGrH;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,GAAG;IAAE,IAAI,EAAE,sBAAsB,CAAA;CAAE,CAGvH;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAIjF;AAED,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACL,sBAAsB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ import { FLUXY_INBOUND_EVENT_TYPES, FLUXY_OUTBOUND_EVENT_TYPES, FLUXY_PROTOCOL_VERSION, FLUXY_SDK_SYNTHETIC_INBOUND_TYPES, FLUXY_TRANSPORT_INBOUND_TYPES, } from "./event-types.js";
2
+ export { FLUXY_INBOUND_EVENT_TYPES, FLUXY_OUTBOUND_EVENT_TYPES, FLUXY_PROTOCOL_VERSION, FLUXY_SDK_SYNTHETIC_INBOUND_TYPES, FLUXY_TRANSPORT_INBOUND_TYPES, };
3
+ export function isRecord(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5
+ }
6
+ export function isFluxyInboundEvent(value) {
7
+ if (!isRecord(value) || typeof value.type !== "string")
8
+ return false;
9
+ return FLUXY_INBOUND_EVENT_TYPES.includes(value.type);
10
+ }
11
+ export function isFluxyOutboundEvent(value) {
12
+ if (!isRecord(value) || typeof value.type !== "string")
13
+ return false;
14
+ return FLUXY_OUTBOUND_EVENT_TYPES.includes(value.type);
15
+ }
16
+ export function assertInboundEventType(type) {
17
+ return FLUXY_INBOUND_EVENT_TYPES.includes(type)
18
+ ? type
19
+ : null;
20
+ }
21
+ export { parseInboundWsFrame, isKnownOutboundClientEvent, } from "./parse-inbound-frame.js";
22
+ export { dispatchInboundWsFrame, } from "./dispatch-inbound-frame.js";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { FLUXY_INBOUND_EVENT_TYPES, FLUXY_OUTBOUND_EVENT_TYPES, FLUXY_PROTOCOL_VERSION, assertInboundEventType, isFluxyInboundEvent, isFluxyOutboundEvent, } from "./index.js";
3
+ describe("@fluxy-chat/protocol", () => {
4
+ it("exposes a stable protocol version", () => {
5
+ expect(FLUXY_PROTOCOL_VERSION).toMatch(/^\d+\.\d+\.\d+$/);
6
+ });
7
+ it("recognizes core inbound events", () => {
8
+ expect(isFluxyInboundEvent({ type: "message", id: 1 })).toBe(true);
9
+ expect(isFluxyInboundEvent({ type: "tool_call", runId: "r1" })).toBe(true);
10
+ expect(isFluxyInboundEvent({ type: "not_a_real_event" })).toBe(false);
11
+ expect(isFluxyInboundEvent(null)).toBe(false);
12
+ });
13
+ it("recognizes outbound client events", () => {
14
+ expect(isFluxyOutboundEvent({ type: "ping" })).toBe(true);
15
+ expect(isFluxyOutboundEvent({ type: "stream", op: "start" })).toBe(true);
16
+ expect(isFluxyOutboundEvent({ type: "message" })).toBe(true);
17
+ });
18
+ it("maps unknown inbound types to null", () => {
19
+ expect(assertInboundEventType("presence")).toBe("presence");
20
+ expect(assertInboundEventType("bogus")).toBeNull();
21
+ });
22
+ it("keeps inbound/outbound lists disjoint", () => {
23
+ const overlap = FLUXY_INBOUND_EVENT_TYPES.filter((t) => FLUXY_OUTBOUND_EVENT_TYPES.includes(t));
24
+ expect(overlap.sort()).toEqual(["agentTyping", "client_event", "message", "stream", "typing"]);
25
+ });
26
+ });
27
+ describe("outbound client events", () => {
28
+ it("matches Room DO handlers", () => {
29
+ const roomDoTypes = [
30
+ "ping",
31
+ "message",
32
+ "stream",
33
+ "edit",
34
+ "reaction",
35
+ "read",
36
+ "delete",
37
+ "client_event",
38
+ "typing",
39
+ "agentTyping",
40
+ ];
41
+ expect([...FLUXY_OUTBOUND_EVENT_TYPES].sort()).toEqual(roomDoTypes.sort());
42
+ });
43
+ });
@@ -0,0 +1,11 @@
1
+ export type InboundFrameKind = "pong" | "replay" | "event" | "ignored";
2
+ export interface ParsedInboundWsFrame {
3
+ kind: InboundFrameKind;
4
+ event?: Record<string, unknown>;
5
+ messages?: unknown[];
6
+ }
7
+ export declare function parseInboundWsFrame(raw: string): ParsedInboundWsFrame | null;
8
+ export declare function isKnownOutboundClientEvent(value: unknown): value is Record<string, unknown> & {
9
+ type: string;
10
+ };
11
+ //# sourceMappingURL=parse-inbound-frame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-inbound-frame.d.ts","sourceRoot":"","sources":["../src/parse-inbound-frame.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAqBD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAe5E;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAG9G"}
@@ -0,0 +1,36 @@
1
+ import { FLUXY_INBOUND_EVENT_TYPES, FLUXY_OUTBOUND_EVENT_TYPES, FLUXY_SDK_SYNTHETIC_INBOUND_TYPES, } from "./event-types.js";
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ function parseJson(raw) {
6
+ try {
7
+ return JSON.parse(raw);
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ function isDeliverableInboundType(type) {
14
+ return (FLUXY_INBOUND_EVENT_TYPES.includes(type) ||
15
+ FLUXY_SDK_SYNTHETIC_INBOUND_TYPES.includes(type));
16
+ }
17
+ export function parseInboundWsFrame(raw) {
18
+ const data = parseJson(raw);
19
+ if (!isRecord(data) || typeof data.type !== "string")
20
+ return null;
21
+ if (data.type === "pong")
22
+ return { kind: "pong" };
23
+ if (data.type === "replay") {
24
+ const messages = Array.isArray(data.messages) ? data.messages : [];
25
+ return { kind: "replay", messages };
26
+ }
27
+ if (!isDeliverableInboundType(data.type)) {
28
+ return { kind: "ignored" };
29
+ }
30
+ return { kind: "event", event: data };
31
+ }
32
+ export function isKnownOutboundClientEvent(value) {
33
+ if (!isRecord(value) || typeof value.type !== "string")
34
+ return false;
35
+ return FLUXY_OUTBOUND_EVENT_TYPES.includes(value.type);
36
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parse-inbound-frame.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-inbound-frame.test.d.ts","sourceRoot":"","sources":["../src/parse-inbound-frame.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { parseInboundWsFrame, isKnownOutboundClientEvent } from "./parse-inbound-frame.js";
3
+ describe("parseInboundWsFrame", () => {
4
+ it("parses pong", () => {
5
+ expect(parseInboundWsFrame(JSON.stringify({ type: "pong", ts: 1 }))).toEqual({ kind: "pong" });
6
+ });
7
+ it("parses replay snapshot", () => {
8
+ expect(parseInboundWsFrame(JSON.stringify({ type: "replay", messages: [{ id: 1 }] }))).toEqual({
9
+ kind: "replay",
10
+ messages: [{ id: 1 }],
11
+ });
12
+ });
13
+ it("parses deliverable events", () => {
14
+ const frame = parseInboundWsFrame(JSON.stringify({ type: "message", id: 2, content: "hi" }));
15
+ expect(frame?.kind).toBe("event");
16
+ expect(frame?.event?.type).toBe("message");
17
+ });
18
+ it("ignores unknown inbound types", () => {
19
+ expect(parseInboundWsFrame(JSON.stringify({ type: "totally_unknown" }))).toEqual({ kind: "ignored" });
20
+ });
21
+ it("returns null on invalid JSON", () => {
22
+ expect(parseInboundWsFrame("{")).toBeNull();
23
+ });
24
+ });
25
+ describe("isKnownOutboundClientEvent", () => {
26
+ it("accepts room DO client events", () => {
27
+ expect(isKnownOutboundClientEvent({ type: "message", content: "x" })).toBe(true);
28
+ expect(isKnownOutboundClientEvent({ type: "edit", id: 1 })).toBe(true);
29
+ });
30
+ it("rejects unknown outbound types", () => {
31
+ expect(isKnownOutboundClientEvent({ type: "subscribe" })).toBe(false);
32
+ expect(isKnownOutboundClientEvent(null)).toBe(false);
33
+ });
34
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=protocol-events-json.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol-events-json.test.d.ts","sourceRoot":"","sources":["../src/protocol-events-json.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import protocolEvents from "../protocol-events.json" with { type: "json" };
3
+ import { FLUXY_INBOUND_EVENT_TYPES, FLUXY_OUTBOUND_EVENT_TYPES, FLUXY_PROTOCOL_VERSION, } from "./event-types.js";
4
+ describe("protocol-events.json cross-SDK manifest", () => {
5
+ it("matches TypeScript event registries", () => {
6
+ expect([...protocolEvents.inbound].sort()).toEqual([...FLUXY_INBOUND_EVENT_TYPES].sort());
7
+ expect([...protocolEvents.outbound].sort()).toEqual([...FLUXY_OUTBOUND_EVENT_TYPES].sort());
8
+ expect(protocolEvents.version).toBe(FLUXY_PROTOCOL_VERSION);
9
+ });
10
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sdk-contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-contract.test.d.ts","sourceRoot":"","sources":["../src/sdk-contract.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { FLUXY_INBOUND_EVENT_TYPES } from "./index.js";
3
+ /**
4
+ * Subset of @fluxy-chat/sdk FluxyChatEvent types that must stay in sync.
5
+ * Expand when the TS SDK union grows.
6
+ */
7
+ const SDK_INBOUND_EVENT_TYPES = [
8
+ "message",
9
+ "message_edit",
10
+ "message_delete",
11
+ "message_expired",
12
+ "typing",
13
+ "subscription_succeeded",
14
+ "subscription_count",
15
+ "member_joined",
16
+ "member_left",
17
+ "client_event",
18
+ "agentTyping",
19
+ "tool_call",
20
+ "tool_result",
21
+ "tool_error",
22
+ "agentRun",
23
+ "presence",
24
+ "cache_snapshot",
25
+ "server_event",
26
+ "user_event",
27
+ "user_subscription_succeeded",
28
+ "state_change",
29
+ "stream",
30
+ "error",
31
+ ];
32
+ describe("SDK contract", () => {
33
+ it("includes every SDK inbound event in the protocol registry", () => {
34
+ const protocol = new Set(FLUXY_INBOUND_EVENT_TYPES);
35
+ const missing = SDK_INBOUND_EVENT_TYPES.filter((t) => !protocol.has(t));
36
+ expect(missing).toEqual([]);
37
+ });
38
+ });
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@fluxy-chat/protocol",
3
+ "version": "0.1.0",
4
+ "description": "Shared FluxyChat wire protocol — WebSocket event types and validators for SDKs and the worker",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "protocol-events.json"
18
+ ],
19
+ "scripts": {
20
+ "build": "pnpm exec tsc -p tsconfig.json",
21
+ "test": "vitest run",
22
+ "typecheck": "tsc -p tsconfig.json --noEmit",
23
+ "prepublishOnly": "pnpm run build"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.8.3",
27
+ "vite": "^8.0.16",
28
+ "vitest": "^4.1.5"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "inbound": [
4
+ "message",
5
+ "message_edit",
6
+ "message_delete",
7
+ "message_expired",
8
+ "typing",
9
+ "subscription_succeeded",
10
+ "subscription_count",
11
+ "member_joined",
12
+ "member_left",
13
+ "client_event",
14
+ "agentTyping",
15
+ "tool_call",
16
+ "tool_result",
17
+ "tool_error",
18
+ "agentRun",
19
+ "presence",
20
+ "cache_snapshot",
21
+ "server_event",
22
+ "user_event",
23
+ "user_subscription_succeeded",
24
+ "state_change",
25
+ "stream",
26
+ "pong",
27
+ "error"
28
+ ],
29
+ "outbound": [
30
+ "ping",
31
+ "message",
32
+ "stream",
33
+ "edit",
34
+ "reaction",
35
+ "read",
36
+ "delete",
37
+ "typing",
38
+ "client_event",
39
+ "agentTyping"
40
+ ]
41
+ }