@gizmo-ai/client 0.2.2

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,87 @@
1
+ /**
2
+ * SSE Subscription
3
+ *
4
+ * Opens an EventSource to the agent's stream endpoint, maintains local state,
5
+ * applies JSON Patches (RFC 6902), and calls back with the full patched state
6
+ * plus the triggering event.
7
+ */
8
+
9
+ import { applyPatch } from "fast-json-patch";
10
+ import type {
11
+ SSEEvent,
12
+ InitialStateEvent,
13
+ StateUpdateEvent,
14
+ ExecutionStatusEvent,
15
+ HeartbeatEvent,
16
+ CustomEvent,
17
+ SubscribeCallback,
18
+ SubscribeOptions,
19
+ Unsubscribe,
20
+ } from "./types.ts";
21
+
22
+ /**
23
+ * Subscribe to an agent's SSE stream.
24
+ *
25
+ * Maintains a local state object, applies incoming patches, and fires
26
+ * the callback with a consistent snapshot after every event.
27
+ *
28
+ * @returns An unsubscribe function that closes the EventSource.
29
+ */
30
+ export function subscribe(
31
+ baseUrl: string,
32
+ callback: SubscribeCallback,
33
+ options?: SubscribeOptions,
34
+ ): Unsubscribe {
35
+ const endpoint = options?.endpoint ?? "/events";
36
+ const url = `${baseUrl}${endpoint}`;
37
+
38
+ const slices: Record<string, unknown> = {};
39
+ const es = new EventSource(url);
40
+
41
+ // state.initial — set full local state
42
+ es.addEventListener("state.initial", (e: MessageEvent) => {
43
+ const event: InitialStateEvent = JSON.parse(e.data);
44
+ // Reset and populate slices
45
+ for (const key of Object.keys(slices)) delete slices[key];
46
+ Object.assign(slices, event.slices);
47
+ callback({ slices: { ...slices }, event });
48
+ });
49
+
50
+ // state.update — apply patches or full value replacement
51
+ es.addEventListener("state.update", (e: MessageEvent) => {
52
+ const event: StateUpdateEvent = JSON.parse(e.data);
53
+
54
+ if (event.patches && event.patches.length > 0) {
55
+ const current = slices[event.slice] ?? {};
56
+ const cloned = structuredClone(current);
57
+ const result = applyPatch(cloned, event.patches);
58
+ slices[event.slice] = result.newDocument;
59
+ } else if ("value" in event) {
60
+ slices[event.slice] = event.value;
61
+ }
62
+
63
+ callback({ slices: { ...slices }, event });
64
+ });
65
+
66
+ // execution.status — fire callback, no state mutation
67
+ es.addEventListener("execution.status", (e: MessageEvent) => {
68
+ const event: ExecutionStatusEvent = JSON.parse(e.data);
69
+ callback({ slices: { ...slices }, event });
70
+ });
71
+
72
+ // heartbeat — fire callback, no state mutation
73
+ es.addEventListener("heartbeat", (e: MessageEvent) => {
74
+ const event: HeartbeatEvent = JSON.parse(e.data);
75
+ callback({ slices: { ...slices }, event });
76
+ });
77
+
78
+ // custom-event — fire callback, no state mutation
79
+ es.addEventListener("custom-event", (e: MessageEvent) => {
80
+ const event: CustomEvent = JSON.parse(e.data);
81
+ callback({ slices: { ...slices }, event });
82
+ });
83
+
84
+ return () => {
85
+ es.close();
86
+ };
87
+ }
package/src/types.ts ADDED
@@ -0,0 +1,271 @@
1
+ /**
2
+ * @gizmo-ai/client — Standalone type definitions
3
+ *
4
+ * All types are redeclared here — no imports from @gizmo-ai/server or @gizmo-ai/runtime.
5
+ * This keeps the client zero-coupling with the server packages.
6
+ */
7
+
8
+ import type { Operation } from "fast-json-patch";
9
+
10
+ // ============================================================================
11
+ // Manifest
12
+ // ============================================================================
13
+
14
+ export interface GizmoManifest {
15
+ manifestVersion: string;
16
+ identity: {
17
+ agentId: string;
18
+ name: string;
19
+ version: string;
20
+ description?: string;
21
+ documentationUrl?: string;
22
+ };
23
+ auth?: {
24
+ type?: string;
25
+ scopes?: string[];
26
+ };
27
+ endpoints: {
28
+ invoke: string;
29
+ state: string;
30
+ runs: string;
31
+ streams: {
32
+ state: string;
33
+ actions: string;
34
+ events: string;
35
+ };
36
+ control?: {
37
+ cancel: string;
38
+ continue: string;
39
+ };
40
+ plugins?: Record<string, string>;
41
+ };
42
+ state: {
43
+ slices: string[];
44
+ schemaVersion: string;
45
+ schemaRef?: string;
46
+ schema?: {
47
+ type: "object";
48
+ properties: Record<string, unknown>;
49
+ $defs?: Record<string, unknown>;
50
+ };
51
+ };
52
+ actions?: {
53
+ publicContractsRef: string;
54
+ };
55
+ capabilities: {
56
+ tools?: string[];
57
+ skills?: Array<{
58
+ name: string;
59
+ description?: string;
60
+ source?: string;
61
+ }>;
62
+ models?: string[];
63
+ features?: string[];
64
+ plugins?: string[];
65
+ };
66
+ }
67
+
68
+ // ============================================================================
69
+ // SSE Events
70
+ // ============================================================================
71
+
72
+ export interface InitialStateEvent {
73
+ type: "state.initial";
74
+ slices: Record<string, unknown>;
75
+ timestamp: number;
76
+ }
77
+
78
+ export interface StateUpdateEvent {
79
+ type: "state.update";
80
+ slice: string;
81
+ patches?: Operation[];
82
+ value?: unknown;
83
+ cause?: {
84
+ type: string;
85
+ id?: string;
86
+ payload?: unknown;
87
+ };
88
+ timestamp: number;
89
+ }
90
+
91
+ export interface ExecutionStatusEvent {
92
+ type: "execution.status";
93
+ status: "started" | "completed" | "failed" | "aborted" | "interrupted";
94
+ executionId?: string;
95
+ turnId?: string;
96
+ error?: string;
97
+ timestamp: number;
98
+ }
99
+
100
+ export interface HeartbeatEvent {
101
+ type: "heartbeat";
102
+ timestamp: number;
103
+ }
104
+
105
+ export interface CustomEvent {
106
+ type: "custom-event";
107
+ data: unknown;
108
+ timestamp: number;
109
+ }
110
+
111
+ export type SSEEvent =
112
+ | InitialStateEvent
113
+ | StateUpdateEvent
114
+ | ExecutionStatusEvent
115
+ | HeartbeatEvent
116
+ | CustomEvent;
117
+
118
+ // ============================================================================
119
+ // API Request/Response Types
120
+ // ============================================================================
121
+
122
+ export interface InvokeResponse {
123
+ executionId: string;
124
+ turnId: string;
125
+ }
126
+
127
+ export interface AbortResponse {
128
+ status: "aborted";
129
+ executionId: string;
130
+ }
131
+
132
+ export interface RunSummary {
133
+ executionId: string;
134
+ startTime: number;
135
+ endTime?: number;
136
+ status: "running" | "completed" | "failed" | "aborted";
137
+ actionCount: number;
138
+ error?: string;
139
+ digest?: string;
140
+ }
141
+
142
+ export interface RunDetails extends RunSummary {
143
+ actions: Array<{ type: string; payload?: unknown }>;
144
+ }
145
+
146
+ export interface RunsResponse {
147
+ runs: RunSummary[];
148
+ }
149
+
150
+ export interface RunListParams {
151
+ after?: number;
152
+ before?: number;
153
+ status?: "running" | "completed" | "failed" | "aborted";
154
+ limit?: number;
155
+ }
156
+
157
+ export interface HydrateOptions {
158
+ upToSeq?: number;
159
+ upToTurn?: number;
160
+ thenExecute?: string;
161
+ hydrateMode?: "full" | "slim";
162
+ }
163
+
164
+ export interface HydrateResponse {
165
+ status: "hydrated";
166
+ actionCount: number;
167
+ state: {
168
+ conversationLength: number;
169
+ loopCount: number;
170
+ };
171
+ execution?: {
172
+ executionId: string;
173
+ turnId: string;
174
+ };
175
+ }
176
+
177
+ export interface HealthResponse {
178
+ status: "ok";
179
+ }
180
+
181
+ export interface DispatchRequest {
182
+ type: string;
183
+ payload?: unknown;
184
+ }
185
+
186
+ // ============================================================================
187
+ // Approval Types
188
+ // ============================================================================
189
+
190
+ export type ApprovalStatus = "pending" | "approved" | "rejected" | "expired";
191
+
192
+ export interface Approval {
193
+ id: string;
194
+ executionId: string;
195
+ toolCall: {
196
+ name: string;
197
+ args: Record<string, unknown>;
198
+ };
199
+ status: ApprovalStatus;
200
+ requestedAt: number;
201
+ expiresAt?: number;
202
+ decidedAt?: number;
203
+ reason?: string;
204
+ }
205
+
206
+ export interface ApprovalsListResponse {
207
+ approvals: Approval[];
208
+ }
209
+
210
+ export interface ApprovalResponse {
211
+ approval: Approval;
212
+ }
213
+
214
+ export interface HistoryResponse {
215
+ history: Approval[];
216
+ }
217
+
218
+ // ============================================================================
219
+ // Subscribe Types
220
+ // ============================================================================
221
+
222
+ export interface StateSnapshot {
223
+ slices: Record<string, unknown>;
224
+ event: SSEEvent;
225
+ }
226
+
227
+ export type SubscribeCallback = (snapshot: StateSnapshot) => void;
228
+
229
+ export type Unsubscribe = () => void;
230
+
231
+ export interface SubscribeOptions {
232
+ /** SSE endpoint path (default: "/events") */
233
+ endpoint?: string;
234
+ }
235
+
236
+ // ============================================================================
237
+ // Client Types
238
+ // ============================================================================
239
+
240
+ export interface ClientOptions {
241
+ /** Custom fetch implementation (for testing or environments without global fetch) */
242
+ fetch?: typeof globalThis.fetch;
243
+ /** Default headers to include on every request (e.g., auth tokens) */
244
+ headers?: Record<string, string>;
245
+ }
246
+
247
+ export interface GizmoClient {
248
+ discover(): Promise<GizmoManifest>;
249
+ invoke(input: string): Promise<InvokeResponse>;
250
+ abort(): Promise<AbortResponse>;
251
+ state(slice?: string): Promise<unknown>;
252
+ subscribe(callback: SubscribeCallback, options?: SubscribeOptions): Unsubscribe;
253
+ health(): Promise<HealthResponse>;
254
+ dispatch(action: DispatchRequest): Promise<unknown>;
255
+
256
+ runs: {
257
+ list(params?: RunListParams): Promise<RunSummary[]>;
258
+ get(executionId: string): Promise<RunDetails>;
259
+ actions(executionId: string): Promise<RunDetails["actions"]>;
260
+ cancel(executionId: string): Promise<{ status: string; executionId: string }>;
261
+ hydrate(executionId: string, options?: HydrateOptions): Promise<HydrateResponse>;
262
+ };
263
+
264
+ approvals: {
265
+ list(): Promise<Approval[]>;
266
+ history(): Promise<Approval[]>;
267
+ get(id: string): Promise<Approval>;
268
+ approve(id: string, reason?: string): Promise<Approval>;
269
+ reject(id: string, reason?: string): Promise<Approval>;
270
+ };
271
+ }