@starcite/sdk 0.0.3 → 0.0.5

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 CHANGED
@@ -2,262 +2,324 @@
2
2
 
3
3
  TypeScript SDK for [Starcite](https://starcite.ai), built for multi-agent systems.
4
4
 
5
- Built for teams where multiple producers need shared, ordered context.
5
+ If you need a single ordered session stream across multiple producers, this is the SDK you use.
6
6
 
7
- `@starcite/sdk` helps you:
7
+ ## Install
8
8
 
9
- - listen and monitor what multiple agents are doing,
10
- - keep frontend state consistent with a single ordered event source,
11
- - replay history and continue sessions reliably.
9
+ ```bash
10
+ npm install @starcite/sdk
11
+ ```
12
12
 
13
- Typical flow:
13
+ ## Runtime Requirements
14
14
 
15
- 1. create a session
16
- 2. list sessions when needed
17
- 3. append ordered events
18
- 4. tail from a cursor over WebSocket
15
+ - Node.js 22+ (or Bun / modern runtime with `fetch` + `WebSocket`)
16
+ - Starcite base URL (for example `https://<your-instance>.starcite.io`)
17
+ - API key JWT for backend flows
18
+ - Session token JWTs for frontend/session-scoped flows
19
19
 
20
- For multi-agent systems:
20
+ The SDK normalizes the API URL to `/v1` automatically.
21
21
 
22
- - a) listen and monitor all producers in real time,
23
- - b) keep frontend consistency by reading from the same ordered stream.
22
+ ## The Core Model
24
23
 
25
- ## Install
24
+ - `Starcite`: tenant-scoped client
25
+ - `StarciteIdentity`: `user` or `agent` principal tied to tenant
26
+ - `StarciteSession`: session-scoped handle for append/tail/consume/live-sync
26
27
 
27
- ```bash
28
- npm install @starcite/sdk
29
- ```
28
+ The key split:
30
29
 
31
- ## Requirements
30
+ - backend: construct `Starcite` with `apiKey`
31
+ - frontend: construct `Starcite` without `apiKey`, bind with `session({ token })`
32
32
 
33
- - Node.js 22+, Bun, or any modern runtime with `fetch` and `WebSocket`
34
- - Your Starcite Cloud instance URL (`https://<your-instance>.starcite.io`)
35
- - Your Starcite API key / service JWT
33
+ ## How You Use This SDK
36
34
 
37
- The SDK normalizes the base URL to `/v1` automatically.
35
+ This is the practical shape teams end up using in production.
38
36
 
39
- ## Quick Start
37
+ ### A) Agent Backend (worker/service)
38
+
39
+ Use the identity flow. This creates or binds a session and mints a session token.
40
40
 
41
41
  ```ts
42
- import { createStarciteClient } from "@starcite/sdk";
42
+ import { InMemoryCursorStore, Starcite } from "@starcite/sdk";
43
43
 
44
- const client = createStarciteClient({
45
- baseUrl: process.env.STARCITE_BASE_URL ?? "https://<your-instance>.starcite.io",
44
+ const starcite = new Starcite({
45
+ baseUrl: process.env.STARCITE_BASE_URL,
46
46
  apiKey: process.env.STARCITE_API_KEY,
47
47
  });
48
48
 
49
- const session = await client.create({
50
- id: "ses_demo",
51
- title: "Draft contract",
52
- metadata: { tenant_id: "acme" },
49
+ // Use a persistent store in production
50
+ const cursorStore = new InMemoryCursorStore();
51
+
52
+ export async function runPlanner(prompt: string, sessionId?: string) {
53
+ const planner = starcite.agent({ id: "planner" });
54
+
55
+ const session = await starcite.session({
56
+ identity: planner,
57
+ id: sessionId,
58
+ title: "Planning session",
59
+ metadata: { workflow: "planner" },
60
+ });
61
+
62
+ await session.append({ text: `Planning started: ${prompt}` });
63
+
64
+ await session.consume({
65
+ cursorStore,
66
+ reconnectPolicy: { mode: "fixed", initialDelayMs: 500, maxAttempts: 20 },
67
+ handler: async (event) => {
68
+ if (event.type === "content") {
69
+ // Your business logic here.
70
+ }
71
+ },
72
+ });
73
+
74
+ await session.append({ text: "Planning complete." });
75
+
76
+ return {
77
+ sessionId: session.id,
78
+ sessionToken: session.token, // hand off to UI when needed
79
+ };
80
+ }
81
+ ```
82
+
83
+ ### B) User Frontend (browser)
84
+
85
+ Do not use API keys in browser code. Your backend mints a per-session token and sends it to the UI.
86
+
87
+ ```ts
88
+ import { Starcite } from "@starcite/sdk";
89
+
90
+ const starcite = new Starcite({
91
+ baseUrl: import.meta.env.VITE_STARCITE_BASE_URL,
53
92
  });
54
93
 
55
- await session.append({
56
- agent: "researcher",
57
- producerId: "producer:researcher",
58
- producerSeq: 1,
59
- text: "Found 8 relevant cases.",
94
+ const { token } = await fetch("/api/chat/session", {
95
+ method: "POST",
96
+ }).then((res) => res.json());
97
+
98
+ const session = starcite.session({ token });
99
+
100
+ const stopEvents = session.on("event", (event) => {
101
+ // Replay + live events from canonical ordered session log.
102
+ renderEvent(event);
60
103
  });
61
104
 
62
- await session.append({
63
- agent: "drafter",
64
- producerId: "producer:drafter",
65
- producerSeq: 1,
66
- text: "Drafted clause 4.2 with references.",
105
+ session.on("error", (error) => {
106
+ console.error("Session live-sync error", error);
67
107
  });
68
108
 
69
- for await (const event of session.tail({ cursor: 0 })) {
70
- const actor = event.agent ?? event.actor;
71
- const text =
72
- event.text ?? (typeof event.payload.text === "string" ? event.payload.text : "");
109
+ await session.append({
110
+ text: "Can you summarize the last 3 updates?",
111
+ source: "user",
112
+ });
73
113
 
74
- console.log(`[${actor}] ${text}`);
75
- }
114
+ // cleanup on unmount/navigation
115
+ stopEvents();
116
+ session.disconnect();
76
117
  ```
77
118
 
78
- ## Authentication
119
+ ### C) Admin Panel (ops/audit)
120
+
121
+ Typical split:
79
122
 
80
- Use your service JWT/API key once at client creation. The SDK injects
81
- `Authorization: Bearer <token>` for all HTTP calls and WebSocket tail upgrades.
123
+ 1. Backend lists sessions using API key.
124
+ 2. Backend mints an admin viewer token for a selected session.
125
+ 3. Frontend binds with `session({ token })` and tails/replays safely.
126
+
127
+ Backend:
82
128
 
83
129
  ```ts
84
- import { createStarciteClient } from "@starcite/sdk";
130
+ import { Starcite } from "@starcite/sdk";
85
131
 
86
- const client = createStarciteClient({
87
- baseUrl: process.env.STARCITE_BASE_URL ?? "https://<your-instance>.starcite.io",
132
+ const starcite = new Starcite({
133
+ baseUrl: process.env.STARCITE_BASE_URL,
88
134
  apiKey: process.env.STARCITE_API_KEY,
135
+ authUrl: process.env.STARCITE_AUTH_URL,
89
136
  });
90
- ```
91
137
 
92
- Token refresh is not built in. If a key is revoked/rotated and requests start
93
- returning `401`, create a new client with the replacement key and reconnect
94
- tails from your last processed cursor.
138
+ export async function listSessionsForAdmin() {
139
+ return await starcite.listSessions({ limit: 50 });
140
+ }
141
+
142
+ export async function mintAdminViewerToken(sessionId: string) {
143
+ const response = await fetch(
144
+ `${process.env.STARCITE_AUTH_URL}/api/v1/session-tokens`,
145
+ {
146
+ method: "POST",
147
+ headers: {
148
+ authorization: `Bearer ${process.env.STARCITE_API_KEY}`,
149
+ "content-type": "application/json",
150
+ },
151
+ body: JSON.stringify({
152
+ session_id: sessionId,
153
+ principal: { type: "user", id: "admin:dashboard" },
154
+ scopes: ["session:read"],
155
+ }),
156
+ }
157
+ );
158
+
159
+ if (!response.ok) {
160
+ throw new Error(`Failed to mint admin token: ${response.status}`);
161
+ }
95
162
 
96
- ## List Sessions
163
+ return (await response.json()) as { token: string; expires_in: number };
164
+ }
165
+ ```
166
+
167
+ Frontend admin inspector:
97
168
 
98
169
  ```ts
99
- const page = await client.listSessions({
100
- limit: 20,
101
- metadata: { tenant_id: "acme" },
170
+ import { Starcite } from "@starcite/sdk";
171
+
172
+ const starcite = new Starcite({
173
+ baseUrl: import.meta.env.VITE_STARCITE_BASE_URL,
102
174
  });
103
175
 
104
- for (const session of page.sessions) {
105
- console.log(session.id, session.title, session.created_at);
106
- }
176
+ export async function inspectSession(sessionId: string) {
177
+ const { token } = await fetch(`/admin/api/sessions/${sessionId}/viewer-token`).then(
178
+ (res) => res.json()
179
+ );
107
180
 
108
- console.log("next cursor:", page.next_cursor);
109
- ```
181
+ const session = starcite.session({ token });
110
182
 
111
- ## Append Modes
183
+ const stop = session.on("event", (event) => {
184
+ appendAuditRow(event);
185
+ });
112
186
 
113
- Every append requires producer identity fields:
187
+ session.on("error", (error) => {
188
+ showBanner(`Stream error: ${error.message}`);
189
+ });
114
190
 
115
- - `producerId`: stable producer identifier (for example `producer:drafter`)
116
- - `producerSeq`: per-producer positive sequence number (1, 2, 3, ...)
191
+ return () => {
192
+ stop();
193
+ session.disconnect();
194
+ };
195
+ }
196
+ ```
117
197
 
118
- High-level append:
198
+ ## Public API (Current)
119
199
 
120
200
  ```ts
121
- await client.session("ses_demo").append({
122
- agent: "drafter",
123
- producerId: "producer:drafter",
124
- producerSeq: 1,
125
- text: "Reviewing clause 4.2...",
126
- });
127
- ```
201
+ import {
202
+ MemoryStore,
203
+ Starcite,
204
+ type AppendResult,
205
+ type SessionEvent,
206
+ type SessionStore,
207
+ type StarciteWebSocket,
208
+ } from "@starcite/sdk";
128
209
 
129
- Raw append:
210
+ // ── Construction ────────────────────────────────────────────────────────────
130
211
 
131
- ```ts
132
- await client.session("ses_demo").appendRaw({
133
- type: "content",
134
- actor: "agent:drafter",
135
- producer_id: "producer:drafter",
136
- producer_seq: 3,
137
- payload: { text: "Reviewing clause 4.2..." },
138
- idempotency_key: "req-123",
139
- expected_seq: 3,
212
+ const starcite = new Starcite({
213
+ apiKey: process.env.STARCITE_API_KEY, // required for server-side session creation
214
+ baseUrl: process.env.STARCITE_BASE_URL, // default: STARCITE_BASE_URL or http://localhost:4000
215
+ authUrl: process.env.STARCITE_AUTH_URL, // overrides iss-derived auth URL for token minting
216
+ fetch: globalThis.fetch,
217
+ websocketFactory: (url) => new WebSocket(url),
218
+ store: new MemoryStore(), // cursor + event persistence (default: MemoryStore)
140
219
  });
141
- ```
142
220
 
143
- ## Tail Options
221
+ // WebSocketFactory — simplified, auth is always in access_token query string.
222
+ type WebSocketFactory = (url: string) => StarciteWebSocket;
144
223
 
145
- ```ts
146
- const session = client.session("ses_demo");
147
- const controller = new AbortController();
148
-
149
- setTimeout(() => controller.abort(), 5000);
150
-
151
- for await (const event of session.tail({
152
- cursor: 0,
153
- agent: "drafter",
154
- reconnect: true,
155
- reconnectDelayMs: 3000,
156
- signal: controller.signal,
157
- })) {
158
- console.log(event);
159
- }
160
- ```
224
+ // ── Identities (server-side, require apiKey) ───────────────────────────────
161
225
 
162
- `tail()` replays `seq > cursor`, streams live events, and automatically reconnects
163
- on transport failures while resuming from the last observed `seq`.
226
+ const alice = starcite.user({ id: "u_123" });
227
+ const bot = starcite.agent({ id: "planner" });
164
228
 
165
- - Set `reconnect: false` to disable automatic reconnect behavior.
166
- - By default, reconnect retries continue until the stream is aborted or closes gracefully.
167
- - Use `reconnectDelayMs` to control retry cadence for spotty networks.
229
+ // ── Sessions ────────────────────────────────────────────────────────────────
168
230
 
169
- ## Browser Restart Resilience
231
+ // Server-side: creates session + mints token (async)
232
+ const aliceSession = await starcite.session({ identity: alice });
233
+ const botSession = await starcite.session({
234
+ identity: bot,
235
+ id: aliceSession.id,
236
+ });
170
237
 
171
- `tail()` reconnects robustly for transport failures, but browser refresh/crash
172
- resets in-memory state. Persist your last processed `seq` and restart from it.
238
+ // Client-side: wraps existing JWT (sync, no network calls)
239
+ const session = starcite.session({ token: "<jwt>" });
173
240
 
174
- ```ts
175
- const sessionId = "ses_demo";
176
- const cursorKey = `starcite:${sessionId}:lastSeq`;
241
+ // ── Session properties ──────────────────────────────────────────────────────
177
242
 
178
- const rawCursor = localStorage.getItem(cursorKey) ?? "0";
179
- let lastSeq = Number.parseInt(rawCursor, 10);
243
+ session.id; // string
244
+ session.token; // string
245
+ session.identity; // StarciteIdentity
246
+ session.log; // SessionLog — canonical source of truth
180
247
 
181
- if (!Number.isInteger(lastSeq) || lastSeq < 0) {
182
- lastSeq = 0;
183
- }
248
+ // ── Session log ─────────────────────────────────────────────────────────────
184
249
 
185
- for await (const event of client.session(sessionId).tail({
186
- cursor: lastSeq,
187
- reconnect: true,
188
- reconnectDelayMs: 3000,
189
- })) {
190
- // Process event first, then persist cursor when your side effects succeed.
191
- await renderOrStore(event);
192
- lastSeq = event.seq;
193
- localStorage.setItem(cursorKey, `${lastSeq}`);
194
- }
195
- ```
250
+ session.log.events; // readonly SessionEvent[] ordered by seq, no gaps
251
+ session.log.cursor; // number — highest applied seq
196
252
 
197
- This pattern protects against missed events across browser restarts. Design your
198
- event handler to be idempotent by `seq` to safely tolerate replays.
253
+ // ── Append ──────────────────────────────────────────────────────────────────
199
254
 
200
- ## Error Handling
255
+ await session.append({ text: "hello" });
256
+ await session.append({ payload: { ok: true }, type: "custom", source: "user" });
257
+ // -> Promise<AppendResult> = { seq: number, deduped: boolean }
201
258
 
202
- ```ts
203
- import {
204
- StarciteApiError,
205
- StarciteConnectionError,
206
- createStarciteClient,
207
- } from "@starcite/sdk";
259
+ // ── Subscribe ───────────────────────────────────────────────────────────────
208
260
 
209
- const client = createStarciteClient({
210
- baseUrl: process.env.STARCITE_BASE_URL ?? "https://<your-instance>.starcite.io",
211
- apiKey: process.env.STARCITE_API_KEY,
261
+ // Late subscribers get synchronous replay of log.events, then live events.
262
+ // WS connects lazily on first subscriber, disconnects when all leave.
263
+ const unsub = session.on("event", (event) => {
264
+ console.log(event.seq);
212
265
  });
213
266
 
214
- try {
215
- await client.create();
216
- } catch (error) {
217
- if (error instanceof StarciteApiError) {
218
- console.error(error.status, error.code, error.message);
219
- } else if (error instanceof StarciteConnectionError) {
220
- console.error(error.message);
221
- } else {
222
- throw error;
223
- }
224
- }
267
+ // Fatal errors only (for example token expiry). Transient drops auto-reconnect.
268
+ const unsubErr = session.on("error", (error) => {
269
+ console.error(error.message);
270
+ });
271
+
272
+ unsub();
273
+ unsubErr();
274
+
275
+ // ── Teardown ────────────────────────────────────────────────────────────────
276
+
277
+ session.disconnect(); // stops WS immediately, removes all listeners
225
278
  ```
226
279
 
227
- ## API Surface
228
-
229
- - `createStarciteClient(options?)`
230
- - `starcite` (default client instance)
231
- - `StarciteClient`
232
- - `create(input?)`
233
- - `createSession(input?)`
234
- - `listSessions(options?)`
235
- - `session(id, record?)`
236
- - `appendEvent(sessionId, input)`
237
- - `tailEvents(sessionId, options?)`
238
- - `tailRawEvents(sessionId, options?)`
239
- - `StarciteSession`
240
- - `append(input)`
241
- - `appendRaw(input)`
242
- - `tail(options?)`
243
- - `tailRaw(options?)`
280
+ ## Tail Reliability Controls
281
+
282
+ `SessionTailOptions` supports:
283
+
284
+ - `cursor`, `batchSize`, `agent`
285
+ - `follow`, `reconnect`, `reconnectPolicy`
286
+ - `connectionTimeoutMs`, `inactivityTimeoutMs`
287
+ - `maxBufferedBatches`
288
+ - `signal`
289
+ - `onLifecycleEvent`
290
+
291
+ This is designed for robust reconnect + resume semantics in long-running multi-agent workflows.
292
+
293
+ ## Session Stores
294
+
295
+ `new Starcite({ store })` accepts a `SessionStore` for cursor + retained-event
296
+ persistence across session rebinds.
297
+
298
+ - Default: `MemoryStore`
299
+ - Bring your own by implementing:
300
+ - `load(sessionId)`
301
+ - `save(sessionId, { cursor, events })`
302
+ - optional `clear(sessionId)`
303
+
304
+ ## Error Types You Should Handle
305
+
306
+ - `StarciteApiError` for non-2xx responses
307
+ - `StarciteConnectionError` for transport/JSON issues
308
+ - `StarciteTailError` for streaming failures
309
+ - `StarciteTokenExpiredError` when close code `4001` is observed
310
+ - `StarciteRetryLimitError` when reconnect budget is exhausted
311
+ - `StarciteBackpressureError` when consumer buffering limits are exceeded
244
312
 
245
313
  ## Local Development
246
314
 
247
315
  ```bash
248
316
  bun install
249
317
  bun run --cwd packages/typescript-sdk build
250
- bun run --cwd packages/typescript-sdk test
251
- ```
252
-
253
- Optional reconnect soak test (runs ~40s, disabled by default):
254
-
255
- ```bash
256
- STARCITE_SDK_RUN_SOAK=1 bun run --cwd packages/typescript-sdk test -- test/client.reconnect.integration.test.ts
318
+ bun run --cwd packages/typescript-sdk check
319
+ bun run --cwd packages/typescript-sdk check:browser:all
257
320
  ```
258
321
 
259
322
  ## Links
260
323
 
261
- - Product docs and examples: https://starcite.ai
262
- - API contract: https://github.com/fastpaca/starcite/blob/main/docs/api/rest.md
263
- - WebSocket tail docs: https://github.com/fastpaca/starcite/blob/main/docs/api/websocket.md
324
+ - Product: <https://starcite.ai>
325
+ - Repository: <https://github.com/fastpaca/starcite-clients>