@bandito-ai/sdk 0.1.7 → 0.1.8

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/dist/index.d.mts CHANGED
@@ -70,9 +70,12 @@ declare class BanditoClient {
70
70
  private bandits;
71
71
  private connected;
72
72
  private flushInterval;
73
+ private s3FlushInterval;
73
74
  private flushInProgress;
74
75
  private deadUuids;
75
76
  private retryCounts;
77
+ private _s3Client;
78
+ private _s3Config;
76
79
  constructor(options?: ClientOptions);
77
80
  /**
78
81
  * Bootstrap: authenticate and hydrate in-memory state from cloud.
@@ -105,6 +108,13 @@ declare class BanditoClient {
105
108
  private ensureConnected;
106
109
  private applySync;
107
110
  private flushPending;
111
+ /**
112
+ * Export un-uploaded events to S3 as raw event JSON. One file per event.
113
+ *
114
+ * Key pattern: {prefix}/{bandit_name}/{YYYY/MM/DD}/{local_event_uuid}.json
115
+ * Body: the raw event object (same structure as SQLite payload column).
116
+ */
117
+ private dumpPendingToS3;
108
118
  }
109
119
 
110
120
  /**
package/dist/index.d.ts CHANGED
@@ -70,9 +70,12 @@ declare class BanditoClient {
70
70
  private bandits;
71
71
  private connected;
72
72
  private flushInterval;
73
+ private s3FlushInterval;
73
74
  private flushInProgress;
74
75
  private deadUuids;
75
76
  private retryCounts;
77
+ private _s3Client;
78
+ private _s3Config;
76
79
  constructor(options?: ClientOptions);
77
80
  /**
78
81
  * Bootstrap: authenticate and hydrate in-memory state from cloud.
@@ -105,6 +108,13 @@ declare class BanditoClient {
105
108
  private ensureConnected;
106
109
  private applySync;
107
110
  private flushPending;
111
+ /**
112
+ * Export un-uploaded events to S3 as raw event JSON. One file per event.
113
+ *
114
+ * Key pattern: {prefix}/{bandit_name}/{YYYY/MM/DD}/{local_event_uuid}.json
115
+ * Body: the raw event object (same structure as SQLite payload column).
116
+ */
117
+ private dumpPendingToS3;
108
118
  }
109
119
 
110
120
  /**
package/dist/index.js CHANGED
@@ -57,7 +57,13 @@ function createEngine(banditJson) {
57
57
  if (!wasmModule) {
58
58
  throw new Error("WASM not initialized \u2014 call initWasm() first");
59
59
  }
60
- return new wasmModule.BanditEngine(banditJson);
60
+ const seedBytes = new Uint8Array(8);
61
+ globalThis.crypto.getRandomValues(seedBytes);
62
+ let seed = 0n;
63
+ for (let i = 0; i < 8; i++) {
64
+ seed |= BigInt(seedBytes[i]) << BigInt(i * 8);
65
+ }
66
+ return wasmModule.BanditEngine.newWithSeed(banditJson, seed);
61
67
  }
62
68
  function updateEngine(engine, banditJson) {
63
69
  engine.updateFromSync(banditJson);
@@ -110,7 +116,9 @@ function loadConfig() {
110
116
  const config = {
111
117
  apiKey: null,
112
118
  baseUrl: DEFAULT_BASE_URL,
113
- dataStorage: "local"
119
+ dataStorage: "local",
120
+ s3: null,
121
+ judge: { apiKey: null, model: "gpt-4o-mini" }
114
122
  };
115
123
  if (fs.existsSync(CONFIG_FILE)) {
116
124
  try {
@@ -119,6 +127,20 @@ function loadConfig() {
119
127
  if (data.api_key) config.apiKey = data.api_key;
120
128
  if (data.base_url) config.baseUrl = data.base_url;
121
129
  if (data.data_storage) config.dataStorage = data.data_storage;
130
+ const s3Data = data.s3;
131
+ const s3Bucket = (s3Data?.bucket ?? "").trim();
132
+ if (s3Bucket) {
133
+ const s3Endpoint = (s3Data?.endpoint ?? "").trim() || void 0;
134
+ config.s3 = {
135
+ bucket: s3Bucket,
136
+ prefix: (s3Data?.prefix ?? "").trim() || "bandito",
137
+ region: (s3Data?.region ?? "").trim() || "us-east-1",
138
+ ...s3Endpoint ? { endpoint: s3Endpoint } : {}
139
+ };
140
+ }
141
+ const judgeData = data.judge;
142
+ if (judgeData?.api_key) config.judge.apiKey = judgeData.api_key;
143
+ if (judgeData?.model) config.judge.model = judgeData.model;
122
144
  } catch {
123
145
  }
124
146
  }
@@ -128,6 +150,28 @@ function loadConfig() {
128
150
  if (envUrl) config.baseUrl = envUrl;
129
151
  const envStorage = process.env.BANDITO_DATA_STORAGE;
130
152
  if (envStorage) config.dataStorage = envStorage;
153
+ const envS3Bucket = (process.env.BANDITO_S3_BUCKET ?? "").trim();
154
+ if (envS3Bucket) {
155
+ const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? config.s3?.endpoint ?? "").trim() || void 0;
156
+ config.s3 = {
157
+ bucket: envS3Bucket,
158
+ prefix: (process.env.BANDITO_S3_PREFIX ?? config.s3?.prefix ?? "").trim() || "bandito",
159
+ region: (process.env.BANDITO_S3_REGION ?? config.s3?.region ?? "").trim() || "us-east-1",
160
+ ...envEndpoint ? { endpoint: envEndpoint } : {}
161
+ };
162
+ if (!process.env.BANDITO_DATA_STORAGE) {
163
+ config.dataStorage = "s3";
164
+ }
165
+ } else if (config.s3) {
166
+ const envPrefix = (process.env.BANDITO_S3_PREFIX ?? "").trim();
167
+ const envRegion = (process.env.BANDITO_S3_REGION ?? "").trim();
168
+ const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? "").trim();
169
+ if (envPrefix) config.s3.prefix = envPrefix;
170
+ if (envRegion) config.s3.region = envRegion;
171
+ if (envEndpoint) config.s3.endpoint = envEndpoint;
172
+ }
173
+ const envJudgeKey = process.env.JUDGE_API_KEY;
174
+ if (envJudgeKey) config.judge.apiKey = envJudgeKey;
131
175
  return config;
132
176
  }
133
177
 
@@ -145,6 +189,17 @@ var BanditoHTTP = class {
145
189
  apiKey;
146
190
  timeout;
147
191
  constructor(baseUrl, apiKey, timeout = 1e4) {
192
+ let parsed;
193
+ try {
194
+ parsed = new URL(baseUrl);
195
+ } catch {
196
+ throw new Error(`Invalid baseUrl: "${baseUrl}"`);
197
+ }
198
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
199
+ throw new Error(
200
+ `baseUrl must use http or https, got "${parsed.protocol}" \u2014 check your config`
201
+ );
202
+ }
148
203
  this.baseUrl = `${baseUrl.replace(/\/$/, "")}/api/v1`;
149
204
  this.apiKey = apiKey;
150
205
  this.timeout = timeout;
@@ -167,13 +222,14 @@ var BanditoHTTP = class {
167
222
  clearTimeout(timer);
168
223
  if (!resp.ok) {
169
224
  const text = await resp.text().catch(() => "");
225
+ const preview = text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
170
226
  if (!isRetryable(resp.status) || attempt === MAX_RETRIES - 1) {
171
227
  throw new Error(
172
- `HTTP ${resp.status} on ${method} ${path3}: ${text}`
228
+ `HTTP ${resp.status} on ${method} ${path3}: ${preview}`
173
229
  );
174
230
  }
175
231
  lastError = new Error(
176
- `HTTP ${resp.status} on ${method} ${path3}: ${text}`
232
+ `HTTP ${resp.status} on ${method} ${path3}: ${preview}`
177
233
  );
178
234
  } else {
179
235
  return await resp.json();
@@ -226,13 +282,15 @@ CREATE TABLE IF NOT EXISTS events (
226
282
  status TEXT NOT NULL DEFAULT 'pending',
227
283
  created_at REAL NOT NULL,
228
284
  human_reward REAL,
229
- graded_at REAL
285
+ graded_at REAL,
286
+ s3_exported INTEGER NOT NULL DEFAULT 0
230
287
  );
231
288
  CREATE INDEX IF NOT EXISTS idx_events_status ON events(status);
232
289
  `;
233
290
  var MIGRATION_GRADING = [
234
291
  "ALTER TABLE events ADD COLUMN human_reward REAL",
235
- "ALTER TABLE events ADD COLUMN graded_at REAL"
292
+ "ALTER TABLE events ADD COLUMN graded_at REAL",
293
+ "ALTER TABLE events ADD COLUMN s3_exported INTEGER NOT NULL DEFAULT 0"
236
294
  ];
237
295
  var EventStore = class {
238
296
  db;
@@ -251,7 +309,7 @@ var EventStore = class {
251
309
  VALUES (?, ?, ?, ?, 'pending', ?)`
252
310
  );
253
311
  this.pendingStmt = this.db.prepare(
254
- `SELECT payload FROM events WHERE status = 'pending'
312
+ `SELECT local_event_uuid, payload FROM events WHERE status = 'pending'
255
313
  ORDER BY created_at ASC LIMIT ?`
256
314
  );
257
315
  }
@@ -268,7 +326,22 @@ var EventStore = class {
268
326
  /** Return up to `limit` pending events (oldest first). */
269
327
  pending(limit = 50) {
270
328
  const rows = this.pendingStmt.all(limit);
271
- return rows.map((row) => JSON.parse(row.payload));
329
+ const results = [];
330
+ const corrupt = [];
331
+ for (const row of rows) {
332
+ try {
333
+ results.push(JSON.parse(row.payload));
334
+ } catch {
335
+ console.warn(
336
+ `[bandito] Discarding corrupt event ${row.local_event_uuid} from store`
337
+ );
338
+ corrupt.push(row.local_event_uuid);
339
+ }
340
+ }
341
+ if (corrupt.length > 0) {
342
+ this.markFlushed(corrupt);
343
+ }
344
+ return results;
272
345
  }
273
346
  /** Mark events as successfully flushed to cloud. */
274
347
  markFlushed(uuids) {
@@ -286,6 +359,20 @@ var EventStore = class {
286
359
  WHERE local_event_uuid = ?`
287
360
  ).run(reward, Date.now() / 1e3, uuid);
288
361
  }
362
+ /** Return un-exported events as {event, ts} for S3 dump. */
363
+ pendingS3(limit = 100) {
364
+ const rows = this.db.prepare(
365
+ `SELECT payload, created_at FROM events WHERE s3_exported = 0
366
+ ORDER BY created_at ASC LIMIT ?`
367
+ ).all(limit);
368
+ return rows.map((r) => ({ event: JSON.parse(r.payload), ts: r.created_at }));
369
+ }
370
+ /** Mark events as successfully exported to S3. */
371
+ markS3Exported(uuids) {
372
+ if (uuids.length === 0) return;
373
+ const placeholders = uuids.map(() => "?").join(",");
374
+ this.db.prepare(`UPDATE events SET s3_exported = 1 WHERE local_event_uuid IN (${placeholders})`).run(...uuids);
375
+ }
289
376
  /** Close the database connection. */
290
377
  close() {
291
378
  this.db.close();
@@ -333,9 +420,14 @@ var BanditoClient = class {
333
420
  bandits = /* @__PURE__ */ new Map();
334
421
  connected = false;
335
422
  flushInterval = null;
423
+ s3FlushInterval = null;
336
424
  flushInProgress = false;
337
425
  deadUuids = /* @__PURE__ */ new Set();
338
426
  retryCounts = /* @__PURE__ */ new Map();
427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
428
+ _s3Client = null;
429
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
430
+ _s3Config = null;
339
431
  constructor(options = {}) {
340
432
  this.apiKey = options.apiKey;
341
433
  this.baseUrl = options.baseUrl;
@@ -374,6 +466,22 @@ var BanditoClient = class {
374
466
  }
375
467
  }
376
468
  this.store = new EventStore(storePath);
469
+ this._s3Client = null;
470
+ this._s3Config = null;
471
+ if (this.dataStorage === "s3" && config.s3) {
472
+ try {
473
+ const { S3Client } = await import("@aws-sdk/client-s3");
474
+ const s3ClientConfig = { region: config.s3.region };
475
+ if (config.s3.endpoint) {
476
+ s3ClientConfig.endpoint = config.s3.endpoint;
477
+ s3ClientConfig.forcePathStyle = true;
478
+ }
479
+ this._s3Client = new S3Client(s3ClientConfig);
480
+ this._s3Config = config.s3;
481
+ } catch {
482
+ console.warn("[bandito] data_storage='s3' requires @aws-sdk/client-s3");
483
+ }
484
+ }
377
485
  try {
378
486
  const data = await this.http.connect();
379
487
  this.applySync(data);
@@ -381,9 +489,17 @@ var BanditoClient = class {
381
489
  this.retryCounts.clear();
382
490
  await this.flushPending();
383
491
  this.flushInterval = setInterval(() => {
384
- this.flushPending().catch(() => {
385
- });
492
+ this.flushPending().catch(
493
+ (err) => console.warn("[bandito] Periodic flush error", err)
494
+ );
386
495
  }, 3e4);
496
+ if (this._s3Client !== null) {
497
+ this.s3FlushInterval = setInterval(() => {
498
+ this.dumpPendingToS3().catch(
499
+ (err) => console.warn("[bandito] Periodic S3 export error", err)
500
+ );
501
+ }, 3e4);
502
+ }
387
503
  this.connected = true;
388
504
  } catch (err) {
389
505
  this.store?.close();
@@ -446,7 +562,8 @@ var BanditoClient = class {
446
562
  bandit_id: pullResult.banditId,
447
563
  arm_id: pullResult.arm.armId,
448
564
  model_name: pullResult.arm.modelName,
449
- model_provider: pullResult.arm.modelProvider
565
+ model_provider: pullResult.arm.modelProvider,
566
+ bandit_name: pullResult.banditName
450
567
  };
451
568
  if (options.queryText != null) {
452
569
  event.query_text = options.queryText;
@@ -476,8 +593,9 @@ var BanditoClient = class {
476
593
  event.run_error = true;
477
594
  }
478
595
  this.store.push(event);
479
- this.flushPending().catch(() => {
480
- });
596
+ this.flushPending().catch(
597
+ (err) => console.warn("[bandito] Flush error", err)
598
+ );
481
599
  }
482
600
  /**
483
601
  * Send a human grade for an existing event. Async (HTTP).
@@ -510,9 +628,16 @@ var BanditoClient = class {
510
628
  clearInterval(this.flushInterval);
511
629
  this.flushInterval = null;
512
630
  }
631
+ if (this.s3FlushInterval) {
632
+ clearInterval(this.s3FlushInterval);
633
+ this.s3FlushInterval = null;
634
+ }
513
635
  if (this.store && this.http) {
514
636
  await this.flushPending();
515
637
  }
638
+ if (this._s3Client !== null && this.store) {
639
+ await this.dumpPendingToS3();
640
+ }
516
641
  this.store?.close();
517
642
  this.store = null;
518
643
  this.http = null;
@@ -592,6 +717,45 @@ var BanditoClient = class {
592
717
  this.flushInProgress = false;
593
718
  }
594
719
  }
720
+ /**
721
+ * Export un-uploaded events to S3 as raw event JSON. One file per event.
722
+ *
723
+ * Key pattern: {prefix}/{bandit_name}/{YYYY/MM/DD}/{local_event_uuid}.json
724
+ * Body: the raw event object (same structure as SQLite payload column).
725
+ */
726
+ async dumpPendingToS3() {
727
+ const eventsWithTs = this.store.pendingS3(100);
728
+ if (eventsWithTs.length === 0) return;
729
+ const { PutObjectCommand } = await import("@aws-sdk/client-s3");
730
+ const uploaded = [];
731
+ try {
732
+ for (const { event, ts } of eventsWithTs) {
733
+ const uuid = event.local_event_uuid;
734
+ const rawName = event.bandit_name || "unknown";
735
+ const banditName = rawName.replace(/[^\w\-.]/g, "_");
736
+ const date = new Date(ts * 1e3);
737
+ const datePath = [
738
+ date.getUTCFullYear(),
739
+ String(date.getUTCMonth() + 1).padStart(2, "0"),
740
+ String(date.getUTCDate()).padStart(2, "0")
741
+ ].join("/");
742
+ const key = `${this._s3Config.prefix}/${banditName}/${datePath}/${uuid}.json`;
743
+ await this._s3Client.send(new PutObjectCommand({
744
+ Bucket: this._s3Config.bucket,
745
+ Key: key,
746
+ Body: JSON.stringify(event),
747
+ ContentType: "application/json"
748
+ }));
749
+ uploaded.push(uuid);
750
+ }
751
+ } catch (err) {
752
+ console.warn("[bandito] S3 export failed \u2014 will retry", err);
753
+ } finally {
754
+ if (uploaded.length > 0) {
755
+ this.store.markS3Exported(uploaded);
756
+ }
757
+ }
758
+ }
595
759
  };
596
760
 
597
761
  // src/index.ts
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/engine.ts","../src/models.ts","../src/config.ts","../src/http.ts","../src/store.ts","../src/worker.ts"],"sourcesContent":["/**\n * Bandito SDK — contextual bandit optimization for LLM selection.\n *\n * Recommended (explicit client):\n * import { BanditoClient } from 'bandito';\n *\n * const client = new BanditoClient({ apiKey: 'bnd_...' });\n * await client.connect();\n * const result = client.pull('my-chatbot', { query: userMessage });\n * // ... call LLM with result.model, result.prompt ...\n * client.update(result, { response: response.text });\n * await client.close();\n *\n * Module-level singleton (convenience):\n * import { connect, pull, update, close } from 'bandito';\n *\n * await connect({ apiKey: 'bnd_...' });\n * const result = pull('my-chatbot', { query: userMessage });\n * update(result, { response: response.text });\n * await close();\n */\n\nexport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nexport { type Arm, type PullResult } from \"./models.js\";\n\nimport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nimport type { PullResult } from \"./models.js\";\n\nlet _client: BanditoClient | null = null;\n\nfunction getClient(): BanditoClient {\n if (!_client) {\n throw new Error(\"Not connected — call connect() first\");\n }\n return _client;\n}\n\n/** Connect to the Bandito cloud and hydrate local state. */\nexport async function connect(options: ClientOptions = {}): Promise<void> {\n if (_client) {\n await _client.close();\n }\n _client = new BanditoClient(options);\n await _client.connect();\n}\n\n/** Local Thompson Sampling decision. <1ms, no network. */\nexport function pull(banditName: string, options?: PullOptions): PullResult {\n return getClient().pull(banditName, options);\n}\n\n/** Record an LLM call outcome (writes to SQLite, fire-and-forget flush). */\nexport function update(pullResult: PullResult, options?: UpdateOptions): void {\n getClient().update(pullResult, options);\n}\n\n/** Send a human grade for an existing event. */\nexport async function grade(eventId: string, gradeValue: number): Promise<void> {\n await getClient().grade(eventId, gradeValue);\n}\n\n/** Explicit state refresh from cloud. */\nexport async function sync(): Promise<void> {\n await getClient().sync();\n}\n\n/** Shut down: flush events, close connections. */\nexport async function close(): Promise<void> {\n if (_client) {\n await _client.close();\n _client = null;\n }\n}\n","/**\n * BanditoClient — main orchestrator for the JS/TS SDK.\n *\n * Mirrors the Python SDK's sync-first design:\n * - pull() is synchronous (WASM math, <1ms)\n * - connect(), grade(), sync(), close() are async (HTTP I/O)\n * - update() is synchronous (SQLite write + fire-and-forget flush)\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { initWasm, createEngine, updateEngine, type BanditEngine, type EnginePullResult } from \"./engine.js\";\nimport {\n type Arm,\n type PullResult,\n type BanditCache,\n type ArmWire,\n createArm,\n createPullResult,\n} from \"./models.js\";\nimport { loadConfig, DEFAULT_BASE_URL } from \"./config.js\";\nimport { BanditoHTTP } from \"./http.js\";\nimport { EventStore, type EventPayload } from \"./store.js\";\nimport { prepareCloudPayload } from \"./worker.js\";\n\nconst DEFAULT_STORE_PATH = path.join(os.homedir(), \".bandito\", \"events.db\");\nconst MAX_EVENT_RETRIES = 5;\n\nexport interface ClientOptions {\n apiKey?: string;\n baseUrl?: string;\n storePath?: string;\n dataStorage?: string;\n}\n\nexport interface PullOptions {\n query?: string;\n exclude?: number[];\n}\n\nexport interface UpdateOptions {\n queryText?: string;\n response?: string | Record<string, unknown>;\n reward?: number;\n cost?: number;\n latency?: number;\n inputTokens?: number;\n outputTokens?: number;\n segment?: Record<string, string>;\n failed?: boolean;\n}\n\nexport class BanditoClient {\n private apiKey: string | undefined;\n private baseUrl: string | undefined;\n private storePath: string | undefined;\n private dataStorageArg: string | undefined;\n private dataStorage: string;\n\n private http: BanditoHTTP | null = null;\n private store: EventStore | null = null;\n private engines: Map<string, BanditEngine> = new Map();\n private bandits: Map<string, BanditCache> = new Map();\n private connected = false;\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private flushInProgress = false;\n private deadUuids: Set<string> = new Set();\n private retryCounts: Map<string, number> = new Map();\n\n constructor(options: ClientOptions = {}) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl;\n this.storePath = options.storePath;\n this.dataStorageArg = options.dataStorage;\n this.dataStorage = options.dataStorage ?? \"local\";\n }\n\n /**\n * Bootstrap: authenticate and hydrate in-memory state from cloud.\n *\n * Resolves config from: constructor args → env vars → ~/.bandito/config.toml.\n * Initializes WASM, creates HTTP client, SQLite store, fetches full state.\n */\n async connect(): Promise<void> {\n // Tear down previous connection if reconnecting\n if (this.connected) {\n await this.close();\n }\n\n // Init WASM (loads .wasm binary once)\n await initWasm();\n\n // Resolve config\n const config = loadConfig();\n const apiKey = this.apiKey ?? config.apiKey;\n if (!apiKey) {\n throw new Error(\n \"apiKey required — pass it to constructor, set BANDITO_API_KEY, \" +\n \"or run `bandito signup`\",\n );\n }\n\n const baseUrl = this.baseUrl ?? config.baseUrl;\n if (!this.dataStorageArg) {\n this.dataStorage = config.dataStorage;\n }\n\n this.http = new BanditoHTTP(baseUrl, apiKey);\n const storePath = this.storePath ?? DEFAULT_STORE_PATH;\n if (storePath !== \":memory:\") {\n const dir = path.dirname(storePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n }\n this.store = new EventStore(storePath);\n\n // Bootstrap: fetch state, hydrate cache, flush pending\n try {\n const data = await this.http.connect();\n this.applySync(data);\n\n // Reset retry state\n this.deadUuids.clear();\n this.retryCounts.clear();\n\n // Flush pending events from previous crash\n await this.flushPending();\n\n // Start periodic flush (every 30s)\n this.flushInterval = setInterval(() => {\n this.flushPending().catch(() => {});\n }, 30_000);\n\n this.connected = true;\n } catch (err) {\n this.store?.close();\n this.store = null;\n this.http = null;\n throw err;\n }\n }\n\n /**\n * Local Thompson Sampling decision. Synchronous, <1ms, no network.\n */\n pull(banditName: string, options: PullOptions = {}): PullResult {\n this.ensureConnected();\n\n const cache = this.bandits.get(banditName);\n if (!cache) {\n const available = [...this.bandits.keys()];\n throw new Error(\n `Unknown bandit '${banditName}'. Available: [${available.join(\", \")}]`,\n );\n }\n\n if (cache.arms.length === 0) {\n throw new Error(`Bandit '${banditName}' has no active arms`);\n }\n\n const engine = this.engines.get(banditName)!;\n const queryLength = options.query?.length ?? undefined;\n const excludeIds = options.exclude ? Int32Array.from(options.exclude) : undefined;\n\n const resultJson = engine.pull(queryLength, excludeIds);\n const raw: EnginePullResult = JSON.parse(resultJson);\n\n // Look up the winning arm from our cached active arms\n const winnerArm = cache.arms.find((a) => a.armId === raw.arm_id);\n if (!winnerArm) {\n throw new Error(\n `Engine selected arm ${raw.arm_id} but it's not in active arm cache for \"${banditName}\". ` +\n \"This is likely a bug — please report it at https://github.com/bandito-ai/bandito/issues\",\n );\n }\n\n return createPullResult({\n arm: winnerArm,\n eventId: randomUUID(),\n banditId: cache.banditId,\n banditName,\n scores: raw.scores,\n pullTime: performance.now(),\n });\n }\n\n /**\n * Record an LLM call outcome. Writes to SQLite first (crash-safe),\n * then fires off a non-blocking flush to cloud.\n */\n update(pullResult: PullResult, options: UpdateOptions = {}): void {\n this.ensureConnected();\n\n let reward = options.reward;\n if (options.failed && reward == null) {\n reward = 0.0;\n }\n\n // Auto-calculate latency from pull timestamp\n let latency = options.latency;\n if (latency == null && pullResult._pullTime > 0) {\n latency = performance.now() - pullResult._pullTime;\n }\n\n // Build event payload (snake_case for wire format)\n const event: EventPayload = {\n local_event_uuid: pullResult.eventId,\n bandit_id: pullResult.banditId,\n arm_id: pullResult.arm.armId,\n model_name: pullResult.arm.modelName,\n model_provider: pullResult.arm.modelProvider,\n };\n\n if (options.queryText != null) {\n (event as Record<string, unknown>).query_text = options.queryText;\n }\n if (options.response != null) {\n (event as Record<string, unknown>).response =\n typeof options.response === \"string\"\n ? { response: options.response }\n : options.response;\n }\n if (reward != null) {\n (event as Record<string, unknown>).early_reward = reward;\n }\n if (options.cost != null) {\n (event as Record<string, unknown>).cost = options.cost;\n }\n if (latency != null) {\n (event as Record<string, unknown>).latency = latency;\n }\n if (options.inputTokens != null) {\n (event as Record<string, unknown>).input_tokens = options.inputTokens;\n }\n if (options.outputTokens != null) {\n (event as Record<string, unknown>).output_tokens = options.outputTokens;\n }\n if (options.segment != null) {\n (event as Record<string, unknown>).segment = options.segment;\n }\n if (options.failed) {\n (event as Record<string, unknown>).run_error = true;\n }\n\n // Write to SQLite WAL first — survives crashes\n this.store!.push(event);\n\n // Fire-and-forget flush (errors logged inside flushPending)\n this.flushPending().catch(() => {});\n }\n\n /**\n * Send a human grade for an existing event. Async (HTTP).\n */\n async grade(eventId: string, grade: number): Promise<void> {\n this.ensureConnected();\n await this.http!.submitGrade(eventId, grade);\n }\n\n /**\n * Explicit state refresh from cloud.\n */\n async sync(): Promise<void> {\n this.ensureConnected();\n const data = await this.http!.heartbeat();\n\n const prevBandits = new Map(this.bandits);\n const prevEngines = new Map(this.engines);\n try {\n this.applySync(data);\n } catch (err) {\n // Rollback on malformed response — keep last-known-good state\n this.bandits = prevBandits;\n this.engines = prevEngines;\n console.warn(\"[bandito] Sync response malformed — keeping last-known-good state\", err);\n }\n }\n\n /**\n * Shut down: clear interval, flush remaining events, close connections.\n */\n async close(): Promise<void> {\n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n\n // Final flush\n if (this.store && this.http) {\n await this.flushPending();\n }\n\n this.store?.close();\n this.store = null;\n this.http = null;\n this.engines.clear();\n this.bandits.clear();\n this.connected = false;\n }\n\n // --- Internal ---\n\n private ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\"Not connected — call connect() first\");\n }\n }\n\n private applySync(data: Record<string, unknown>): void {\n const banditsData = (data.bandits ?? []) as Record<string, unknown>[];\n\n const newBandits = new Map<string, BanditCache>();\n const newEngines = new Map<string, BanditEngine>();\n\n for (const b of banditsData) {\n const arms = (b.arms ?? []) as ArmWire[];\n if (arms.length === 0) continue;\n\n const activeArms: Arm[] = arms\n .filter((a) => a.is_active)\n .map((a) => createArm(a));\n\n const name = b.name as string;\n const cache: BanditCache = {\n banditId: Number(b.bandit_id),\n name,\n arms: activeArms,\n armWire: arms,\n optimizationMode: (b.optimization_mode as string) ?? \"base\",\n avgLatencyLastN: b.avg_latency_last_n as number | null,\n budget: b.budget as number | null,\n totalCost: b.total_cost as number | null,\n };\n\n newBandits.set(name, cache);\n\n // Reuse existing engine (preserves RNG state) or create new one\n const existingEngine = this.engines.get(name);\n const banditJson = JSON.stringify(b);\n if (existingEngine) {\n updateEngine(existingEngine, banditJson);\n newEngines.set(name, existingEngine);\n } else {\n newEngines.set(name, createEngine(banditJson));\n }\n }\n\n this.bandits = newBandits;\n this.engines = newEngines;\n }\n\n private async flushPending(): Promise<void> {\n if (this.flushInProgress || !this.store || !this.http) return;\n this.flushInProgress = true;\n\n try {\n const pending = this.store.pending();\n if (pending.length === 0) return;\n\n // Filter out dead events\n const alive = this.deadUuids.size > 0\n ? pending.filter((e) => !this.deadUuids.has(e.local_event_uuid))\n : pending;\n if (alive.length === 0) return;\n\n // Prepare payload (strip metadata/text as configured)\n const payload = prepareCloudPayload(\n alive as Record<string, unknown>[],\n this.dataStorage !== \"local\",\n );\n\n const result = await this.http.ingestEvents(payload);\n\n // Parse per-event errors\n const errors = (result.errors ?? []) as {\n local_event_uuid?: string;\n reason?: string;\n }[];\n const erroredUuids = new Set(\n errors.map((e) => e.local_event_uuid).filter(Boolean) as string[],\n );\n\n // Update retry counts\n for (const uid of erroredUuids) {\n const count = (this.retryCounts.get(uid) ?? 0) + 1;\n this.retryCounts.set(uid, count);\n if (count >= MAX_EVENT_RETRIES) {\n this.deadUuids.add(uid);\n }\n }\n\n // Mark accepted events as flushed\n const flushedUuids = alive\n .map((e) => e.local_event_uuid)\n .filter((uid) => !erroredUuids.has(uid));\n if (flushedUuids.length > 0 && this.store) {\n this.store.markFlushed(flushedUuids);\n }\n } catch (err) {\n // Flush failure is non-fatal — events stay pending for next attempt\n console.warn(\"[bandito] Event flush failed — will retry\", err);\n } finally {\n this.flushInProgress = false;\n }\n }\n}\n","/**\n * Thin wrapper around the WASM engine import.\n *\n * Handles async WASM initialization (loading .wasm binary happens once)\n * and re-exports the BanditEngine constructor for the client.\n */\n\nimport type { BanditEngine as WasmBanditEngine } from \"../wasm/bandito_engine\";\n\nlet wasmModule: typeof import(\"../wasm/bandito_engine\") | null = null;\n\n/**\n * Initialize the WASM module. Must be called before creating BanditEngine instances.\n * Safe to call multiple times — only loads once.\n */\nexport async function initWasm(): Promise<void> {\n if (wasmModule) return;\n wasmModule = await import(\"../wasm/bandito_engine\");\n}\n\n/**\n * Create a BanditEngine from a sync response JSON string.\n * Requires initWasm() to have been called first.\n */\nexport function createEngine(banditJson: string): WasmBanditEngine {\n if (!wasmModule) {\n throw new Error(\"WASM not initialized — call initWasm() first\");\n }\n return new wasmModule.BanditEngine(banditJson);\n}\n\n/**\n * Update an existing BanditEngine with new sync response data.\n * Preserves RNG state (avoids the \"always picks same arm\" bug).\n */\nexport function updateEngine(engine: WasmBanditEngine, banditJson: string): void {\n engine.updateFromSync(banditJson);\n}\n\nexport type { WasmBanditEngine as BanditEngine };\n\n/**\n * Pull result parsed from engine JSON output.\n */\nexport interface EnginePullResult {\n arm_index: number;\n arm_id: number;\n scores: Record<number, number>;\n}\n","/**\n * SDK types: Arm, PullResult, and internal cache structures.\n */\n\n/** An arm returned to the user after pull(). Immutable. */\nexport interface Arm {\n readonly armId: number;\n readonly modelName: string;\n readonly modelProvider: string;\n readonly systemPrompt: string;\n readonly isPromptTemplated: boolean;\n /** Convenience alias for modelName. */\n readonly model: string;\n /** Convenience alias for systemPrompt. */\n readonly prompt: string;\n}\n\n/** Returned by pull(), passed to update(). Immutable. */\nexport interface PullResult {\n readonly arm: Arm;\n readonly eventId: string;\n readonly banditId: number;\n readonly banditName: string;\n readonly scores: Readonly<Record<number, number>>;\n /** Convenience reach-through to arm.modelName. */\n readonly model: string;\n /** Convenience reach-through to arm.systemPrompt. */\n readonly prompt: string;\n /** @internal perf timestamp */\n readonly _pullTime: number;\n}\n\n/** Create a frozen Arm from raw wire data. */\nexport function createArm(data: {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated?: boolean;\n}): Arm {\n const arm: Arm = {\n armId: data.arm_id,\n modelName: data.model_name,\n modelProvider: data.model_provider,\n systemPrompt: data.system_prompt,\n isPromptTemplated: data.is_prompt_templated ?? false,\n get model() {\n return this.modelName;\n },\n get prompt() {\n return this.systemPrompt;\n },\n };\n return Object.freeze(arm);\n}\n\n/** Create a frozen PullResult. */\nexport function createPullResult(data: {\n arm: Arm;\n eventId: string;\n banditId: number;\n banditName: string;\n scores: Record<number, number>;\n pullTime: number;\n}): PullResult {\n const result: PullResult = {\n arm: data.arm,\n eventId: data.eventId,\n banditId: data.banditId,\n banditName: data.banditName,\n scores: Object.freeze({ ...data.scores }),\n get model() {\n return this.arm.modelName;\n },\n get prompt() {\n return this.arm.systemPrompt;\n },\n _pullTime: data.pullTime,\n };\n return Object.freeze(result);\n}\n\n/** Raw arm data from sync response (snake_case wire format). */\nexport interface ArmWire {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated: boolean;\n is_active: boolean;\n avg_latency_last_n: number | null;\n}\n\n/** Internal mutable cache for a bandit's state. */\nexport interface BanditCache {\n banditId: number;\n name: string;\n arms: Arm[]; // active only\n armWire: ArmWire[]; // all arms (for engine JSON)\n optimizationMode: string;\n avgLatencyLastN: number | null;\n budget: number | null;\n totalCost: number | null;\n}\n","/**\n * Config loader — reads ~/.bandito/config.toml and env vars.\n *\n * Resolution order: constructor args → env vars → TOML → defaults.\n * Same file as the Python SDK uses.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { parse as parseToml } from \"smol-toml\";\n\nexport const DEFAULT_BASE_URL = \"https://bandito-api.onrender.com\";\nconst CONFIG_DIR = path.join(os.homedir(), \".bandito\");\nconst CONFIG_FILE = path.join(CONFIG_DIR, \"config.toml\");\n\nexport interface BanditoConfig {\n apiKey: string | null;\n baseUrl: string;\n dataStorage: string; // \"local\" or \"cloud\"\n}\n\n/**\n * Load config from TOML file + env var overrides.\n */\nexport function loadConfig(): BanditoConfig {\n const config: BanditoConfig = {\n apiKey: null,\n baseUrl: DEFAULT_BASE_URL,\n dataStorage: \"local\",\n };\n\n // TOML file first\n if (fs.existsSync(CONFIG_FILE)) {\n try {\n const content = fs.readFileSync(CONFIG_FILE, \"utf-8\");\n const data = parseToml(content);\n if (data.api_key) config.apiKey = data.api_key;\n if (data.base_url) config.baseUrl = data.base_url;\n if (data.data_storage) config.dataStorage = data.data_storage;\n } catch {\n // Failed to parse TOML — fall back to env vars\n }\n }\n\n // Env vars override\n const envKey = process.env.BANDITO_API_KEY;\n if (envKey) config.apiKey = envKey;\n\n const envUrl = process.env.BANDITO_BASE_URL;\n if (envUrl) config.baseUrl = envUrl;\n\n const envStorage = process.env.BANDITO_DATA_STORAGE;\n if (envStorage) config.dataStorage = envStorage;\n\n return config;\n}\n","/**\n * HTTP transport — fetch-based client for cloud API.\n *\n * Retry config: 3 attempts, exponential backoff (0.5s, 1s, 2s),\n * retries only 5xx and network errors. Never retries 4xx.\n */\n\nconst MAX_RETRIES = 3;\nconst RETRY_BACKOFF_BASE = 500; // ms — 500, 1000, 2000\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500;\n}\n\nexport class BanditoHTTP {\n private baseUrl: string;\n private apiKey: string;\n private timeout: number;\n\n constructor(baseUrl: string, apiKey: string, timeout: number = 10_000) {\n this.baseUrl = `${baseUrl.replace(/\\/$/, \"\")}/api/v1`;\n this.apiKey = apiKey;\n this.timeout = timeout;\n }\n\n private async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Record<string, unknown>> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const resp = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: body != null ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => \"\");\n if (!isRetryable(resp.status) || attempt === MAX_RETRIES - 1) {\n throw new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${text}`,\n );\n }\n // Retryable server error — fall through to retry\n lastError = new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${text}`,\n );\n } else {\n return (await resp.json()) as Record<string, unknown>;\n }\n } catch (err) {\n clearTimeout(timer);\n lastError = err as Error;\n\n // AbortError = timeout, TypeError = network error (in fetch)\n const isNetworkOrTimeout =\n (err as Error).name === \"AbortError\" ||\n (err as Error).name === \"TypeError\";\n if (!isNetworkOrTimeout && attempt < MAX_RETRIES - 1) {\n // Non-retryable (4xx already handled above)\n throw err;\n }\n if (attempt === MAX_RETRIES - 1) {\n throw err;\n }\n }\n\n // Exponential backoff\n const delay = RETRY_BACKOFF_BASE * 2 ** attempt;\n await sleep(delay);\n }\n\n throw lastError!;\n }\n\n /** POST /sync/connect — SDK bootstrap. */\n async connect(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/connect\");\n }\n\n /** POST /sync/heartbeat — periodic state refresh. */\n async heartbeat(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/heartbeat\", {});\n }\n\n /** POST /events — batch event ingestion. */\n async ingestEvents(\n events: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/events\", { events });\n }\n\n /** PATCH /events/{uuid}/grade — submit human grade. */\n async submitGrade(\n eventUuid: string,\n grade: number,\n ): Promise<Record<string, unknown>> {\n return this.request(\"PATCH\", `/events/${eventUuid}/grade`, {\n grade,\n is_graded: true,\n });\n }\n}\n","/**\n * SQLite WAL durability layer — crash-safe event storage.\n *\n * Events are written here immediately after update(). Background flush\n * sends them to cloud. If the process crashes, pending events survive\n * and are retried on next connect().\n */\n\nimport Database from \"better-sqlite3\";\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n local_event_uuid TEXT PRIMARY KEY,\n bandit_id INTEGER NOT NULL,\n arm_id INTEGER NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n created_at REAL NOT NULL,\n human_reward REAL,\n graded_at REAL\n);\nCREATE INDEX IF NOT EXISTS idx_events_status ON events(status);\n`;\n\nconst MIGRATION_GRADING = [\n \"ALTER TABLE events ADD COLUMN human_reward REAL\",\n \"ALTER TABLE events ADD COLUMN graded_at REAL\",\n];\n\nexport interface EventPayload {\n local_event_uuid: string;\n bandit_id: number;\n arm_id: number;\n [key: string]: unknown;\n}\n\nexport class EventStore {\n private db: Database.Database;\n private pushStmt: Database.Statement;\n private pendingStmt: Database.Statement;\n\n constructor(dbPath: string = \":memory:\") {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n this.db.pragma(\"busy_timeout = 5000\");\n this.db.pragma(\"synchronous = NORMAL\");\n this.db.exec(SCHEMA);\n this.migrate();\n\n // Pre-compile frequently-used statements\n this.pushStmt = this.db.prepare(\n `INSERT OR IGNORE INTO events\n (local_event_uuid, bandit_id, arm_id, payload, status, created_at)\n VALUES (?, ?, ?, ?, 'pending', ?)`,\n );\n this.pendingStmt = this.db.prepare(\n `SELECT payload FROM events WHERE status = 'pending'\n ORDER BY created_at ASC LIMIT ?`,\n );\n }\n\n /** Insert a pending event. */\n push(event: EventPayload): void {\n this.pushStmt.run(\n event.local_event_uuid,\n event.bandit_id,\n event.arm_id,\n JSON.stringify(event),\n Date.now() / 1000,\n );\n }\n\n /** Return up to `limit` pending events (oldest first). */\n pending(limit: number = 50): EventPayload[] {\n const rows = this.pendingStmt.all(limit) as { payload: string }[];\n return rows.map((row) => JSON.parse(row.payload));\n }\n\n /** Mark events as successfully flushed to cloud. */\n markFlushed(uuids: string[]): void {\n if (uuids.length === 0) return;\n const placeholders = uuids.map(() => \"?\").join(\",\");\n this.db\n .prepare(\n `UPDATE events SET status = 'flushed'\n WHERE local_event_uuid IN (${placeholders})`,\n )\n .run(...uuids);\n }\n\n /** Record a human grade locally. */\n markGraded(uuid: string, reward: number): void {\n this.db\n .prepare(\n `UPDATE events SET human_reward = ?, graded_at = ?\n WHERE local_event_uuid = ?`,\n )\n .run(reward, Date.now() / 1000, uuid);\n }\n\n /** Close the database connection. */\n close(): void {\n this.db.close();\n }\n\n private migrate(): void {\n for (const stmt of MIGRATION_GRADING) {\n try {\n this.db.exec(stmt);\n } catch {\n // Column already exists\n }\n }\n }\n}\n","/**\n * Payload utilities for cloud event ingestion.\n */\n\nconst TEXT_FIELDS = [\"query_text\", \"response\"] as const;\nconst METADATA_FIELDS = [\"model_name\", \"model_provider\"] as const;\n\n/**\n * Return shallow copies of events ready for cloud ingest.\n *\n * Always strips model_name/model_provider (only needed in local SQLite for TUI).\n * Strips query_text/response when includeText is false (dataStorage=\"local\").\n */\nexport function prepareCloudPayload(\n events: Record<string, unknown>[],\n includeText: boolean,\n): Record<string, unknown>[] {\n return events.map((event) => {\n const copy = { ...event };\n for (const field of METADATA_FIELDS) {\n delete copy[field];\n }\n if (!includeText) {\n for (const field of TEXT_FIELDS) {\n delete copy[field];\n }\n }\n return copy;\n });\n}\n"],"mappings":"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","names":["fs","path","os","parseToml","path","grade","Database","grade"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/engine.ts","../src/models.ts","../src/config.ts","../src/http.ts","../src/store.ts","../src/worker.ts"],"sourcesContent":["/**\n * Bandito SDK — contextual bandit optimization for LLM selection.\n *\n * Recommended (explicit client):\n * import { BanditoClient } from 'bandito';\n *\n * const client = new BanditoClient({ apiKey: 'bnd_...' });\n * await client.connect();\n * const result = client.pull('my-chatbot', { query: userMessage });\n * // ... call LLM with result.model, result.prompt ...\n * client.update(result, { response: response.text });\n * await client.close();\n *\n * Module-level singleton (convenience):\n * import { connect, pull, update, close } from 'bandito';\n *\n * await connect({ apiKey: 'bnd_...' });\n * const result = pull('my-chatbot', { query: userMessage });\n * update(result, { response: response.text });\n * await close();\n */\n\nexport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nexport { type Arm, type PullResult } from \"./models.js\";\n\nimport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nimport type { PullResult } from \"./models.js\";\n\nlet _client: BanditoClient | null = null;\n\nfunction getClient(): BanditoClient {\n if (!_client) {\n throw new Error(\"Not connected — call connect() first\");\n }\n return _client;\n}\n\n/** Connect to the Bandito cloud and hydrate local state. */\nexport async function connect(options: ClientOptions = {}): Promise<void> {\n if (_client) {\n await _client.close();\n }\n _client = new BanditoClient(options);\n await _client.connect();\n}\n\n/** Local Thompson Sampling decision. <1ms, no network. */\nexport function pull(banditName: string, options?: PullOptions): PullResult {\n return getClient().pull(banditName, options);\n}\n\n/** Record an LLM call outcome (writes to SQLite, fire-and-forget flush). */\nexport function update(pullResult: PullResult, options?: UpdateOptions): void {\n getClient().update(pullResult, options);\n}\n\n/** Send a human grade for an existing event. */\nexport async function grade(eventId: string, gradeValue: number): Promise<void> {\n await getClient().grade(eventId, gradeValue);\n}\n\n/** Explicit state refresh from cloud. */\nexport async function sync(): Promise<void> {\n await getClient().sync();\n}\n\n/** Shut down: flush events, close connections. */\nexport async function close(): Promise<void> {\n if (_client) {\n await _client.close();\n _client = null;\n }\n}\n","/**\n * BanditoClient — main orchestrator for the JS/TS SDK.\n *\n * Mirrors the Python SDK's sync-first design:\n * - pull() is synchronous (WASM math, <1ms)\n * - connect(), grade(), sync(), close() are async (HTTP I/O)\n * - update() is synchronous (SQLite write + fire-and-forget flush)\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { initWasm, createEngine, updateEngine, type BanditEngine, type EnginePullResult } from \"./engine.js\";\nimport {\n type Arm,\n type PullResult,\n type BanditCache,\n type ArmWire,\n createArm,\n createPullResult,\n} from \"./models.js\";\nimport { loadConfig, DEFAULT_BASE_URL } from \"./config.js\";\nimport { BanditoHTTP } from \"./http.js\";\nimport { EventStore, type EventPayload } from \"./store.js\";\nimport { prepareCloudPayload } from \"./worker.js\";\n\nconst DEFAULT_STORE_PATH = path.join(os.homedir(), \".bandito\", \"events.db\");\nconst MAX_EVENT_RETRIES = 5;\n\nexport interface ClientOptions {\n apiKey?: string;\n baseUrl?: string;\n storePath?: string;\n dataStorage?: string;\n}\n\nexport interface PullOptions {\n query?: string;\n exclude?: number[];\n}\n\nexport interface UpdateOptions {\n queryText?: string;\n response?: string | Record<string, unknown>;\n reward?: number;\n cost?: number;\n latency?: number;\n inputTokens?: number;\n outputTokens?: number;\n segment?: Record<string, string>;\n failed?: boolean;\n}\n\nexport class BanditoClient {\n private apiKey: string | undefined;\n private baseUrl: string | undefined;\n private storePath: string | undefined;\n private dataStorageArg: string | undefined;\n private dataStorage: string;\n\n private http: BanditoHTTP | null = null;\n private store: EventStore | null = null;\n private engines: Map<string, BanditEngine> = new Map();\n private bandits: Map<string, BanditCache> = new Map();\n private connected = false;\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private s3FlushInterval: ReturnType<typeof setInterval> | null = null;\n private flushInProgress = false;\n private deadUuids: Set<string> = new Set();\n private retryCounts: Map<string, number> = new Map();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _s3Client: any = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _s3Config: any = null;\n\n constructor(options: ClientOptions = {}) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl;\n this.storePath = options.storePath;\n this.dataStorageArg = options.dataStorage;\n this.dataStorage = options.dataStorage ?? \"local\";\n }\n\n /**\n * Bootstrap: authenticate and hydrate in-memory state from cloud.\n *\n * Resolves config from: constructor args → env vars → ~/.bandito/config.toml.\n * Initializes WASM, creates HTTP client, SQLite store, fetches full state.\n */\n async connect(): Promise<void> {\n // Tear down previous connection if reconnecting\n if (this.connected) {\n await this.close();\n }\n\n // Init WASM (loads .wasm binary once)\n await initWasm();\n\n // Resolve config\n const config = loadConfig();\n const apiKey = this.apiKey ?? config.apiKey;\n if (!apiKey) {\n throw new Error(\n \"apiKey required — pass it to constructor, set BANDITO_API_KEY, \" +\n \"or run `bandito signup`\",\n );\n }\n\n const baseUrl = this.baseUrl ?? config.baseUrl;\n if (!this.dataStorageArg) {\n this.dataStorage = config.dataStorage;\n }\n\n this.http = new BanditoHTTP(baseUrl, apiKey);\n const storePath = this.storePath ?? DEFAULT_STORE_PATH;\n if (storePath !== \":memory:\") {\n const dir = path.dirname(storePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n }\n this.store = new EventStore(storePath);\n\n // Init S3 exporter if data_storage is \"s3\"\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this._s3Client = null as any;\n this._s3Config = null;\n if (this.dataStorage === \"s3\" && config.s3) {\n try {\n const { S3Client } = await import(\"@aws-sdk/client-s3\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const s3ClientConfig: Record<string, any> = { region: config.s3.region };\n if (config.s3.endpoint) {\n s3ClientConfig.endpoint = config.s3.endpoint;\n s3ClientConfig.forcePathStyle = true; // required for MinIO / path-style S3\n }\n this._s3Client = new S3Client(s3ClientConfig);\n this._s3Config = config.s3;\n } catch {\n console.warn(\"[bandito] data_storage='s3' requires @aws-sdk/client-s3\");\n }\n }\n\n // Bootstrap: fetch state, hydrate cache, flush pending\n try {\n const data = await this.http.connect();\n this.applySync(data);\n\n // Reset retry state\n this.deadUuids.clear();\n this.retryCounts.clear();\n\n // Flush pending events from previous crash\n await this.flushPending();\n\n // Start periodic cloud flush (every 30s)\n this.flushInterval = setInterval(() => {\n this.flushPending().catch((err) =>\n console.warn(\"[bandito] Periodic flush error\", err),\n );\n }, 30_000);\n\n // S3 dumps run on a separate 30s timer, independent of cloud flush\n if (this._s3Client !== null) {\n this.s3FlushInterval = setInterval(() => {\n this.dumpPendingToS3().catch((err) =>\n console.warn(\"[bandito] Periodic S3 export error\", err),\n );\n }, 30_000);\n }\n\n this.connected = true;\n } catch (err) {\n this.store?.close();\n this.store = null;\n this.http = null;\n throw err;\n }\n }\n\n /**\n * Local Thompson Sampling decision. Synchronous, <1ms, no network.\n */\n pull(banditName: string, options: PullOptions = {}): PullResult {\n this.ensureConnected();\n\n const cache = this.bandits.get(banditName);\n if (!cache) {\n const available = [...this.bandits.keys()];\n throw new Error(\n `Unknown bandit '${banditName}'. Available: [${available.join(\", \")}]`,\n );\n }\n\n if (cache.arms.length === 0) {\n throw new Error(`Bandit '${banditName}' has no active arms`);\n }\n\n const engine = this.engines.get(banditName)!;\n const queryLength = options.query?.length ?? undefined;\n const excludeIds = options.exclude ? Int32Array.from(options.exclude) : undefined;\n\n const resultJson = engine.pull(queryLength, excludeIds);\n const raw: EnginePullResult = JSON.parse(resultJson);\n\n // Look up the winning arm from our cached active arms\n const winnerArm = cache.arms.find((a) => a.armId === raw.arm_id);\n if (!winnerArm) {\n throw new Error(\n `Engine selected arm ${raw.arm_id} but it's not in active arm cache for \"${banditName}\". ` +\n \"This is likely a bug — please report it at https://github.com/bandito-ai/bandito/issues\",\n );\n }\n\n return createPullResult({\n arm: winnerArm,\n eventId: randomUUID(),\n banditId: cache.banditId,\n banditName,\n scores: raw.scores,\n pullTime: performance.now(),\n });\n }\n\n /**\n * Record an LLM call outcome. Writes to SQLite first (crash-safe),\n * then fires off a non-blocking flush to cloud.\n */\n update(pullResult: PullResult, options: UpdateOptions = {}): void {\n this.ensureConnected();\n\n let reward = options.reward;\n if (options.failed && reward == null) {\n reward = 0.0;\n }\n\n // Auto-calculate latency from pull timestamp\n let latency = options.latency;\n if (latency == null && pullResult._pullTime > 0) {\n latency = performance.now() - pullResult._pullTime;\n }\n\n // Build event payload (snake_case for wire format)\n const event: EventPayload = {\n local_event_uuid: pullResult.eventId,\n bandit_id: pullResult.banditId,\n arm_id: pullResult.arm.armId,\n model_name: pullResult.arm.modelName,\n model_provider: pullResult.arm.modelProvider,\n bandit_name: pullResult.banditName,\n };\n\n if (options.queryText != null) {\n (event as Record<string, unknown>).query_text = options.queryText;\n }\n if (options.response != null) {\n (event as Record<string, unknown>).response =\n typeof options.response === \"string\"\n ? { response: options.response }\n : options.response;\n }\n if (reward != null) {\n (event as Record<string, unknown>).early_reward = reward;\n }\n if (options.cost != null) {\n (event as Record<string, unknown>).cost = options.cost;\n }\n if (latency != null) {\n (event as Record<string, unknown>).latency = latency;\n }\n if (options.inputTokens != null) {\n (event as Record<string, unknown>).input_tokens = options.inputTokens;\n }\n if (options.outputTokens != null) {\n (event as Record<string, unknown>).output_tokens = options.outputTokens;\n }\n if (options.segment != null) {\n (event as Record<string, unknown>).segment = options.segment;\n }\n if (options.failed) {\n (event as Record<string, unknown>).run_error = true;\n }\n\n // Write to SQLite WAL first — survives crashes\n this.store!.push(event);\n\n // Fire-and-forget flush (errors logged inside flushPending)\n this.flushPending().catch((err) =>\n console.warn(\"[bandito] Flush error\", err),\n );\n }\n\n /**\n * Send a human grade for an existing event. Async (HTTP).\n */\n async grade(eventId: string, grade: number): Promise<void> {\n this.ensureConnected();\n await this.http!.submitGrade(eventId, grade);\n }\n\n /**\n * Explicit state refresh from cloud.\n */\n async sync(): Promise<void> {\n this.ensureConnected();\n const data = await this.http!.heartbeat();\n\n const prevBandits = new Map(this.bandits);\n const prevEngines = new Map(this.engines);\n try {\n this.applySync(data);\n } catch (err) {\n // Rollback on malformed response — keep last-known-good state\n this.bandits = prevBandits;\n this.engines = prevEngines;\n console.warn(\"[bandito] Sync response malformed — keeping last-known-good state\", err);\n }\n }\n\n /**\n * Shut down: clear interval, flush remaining events, close connections.\n */\n async close(): Promise<void> {\n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n\n if (this.s3FlushInterval) {\n clearInterval(this.s3FlushInterval);\n this.s3FlushInterval = null;\n }\n\n // Final cloud flush\n if (this.store && this.http) {\n await this.flushPending();\n }\n\n // Final S3 drain — catches events not yet exported by the periodic timer\n if (this._s3Client !== null && this.store) {\n await this.dumpPendingToS3();\n }\n\n this.store?.close();\n this.store = null;\n this.http = null;\n this.engines.clear();\n this.bandits.clear();\n this.connected = false;\n }\n\n // --- Internal ---\n\n private ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\"Not connected — call connect() first\");\n }\n }\n\n private applySync(data: Record<string, unknown>): void {\n const banditsData = (data.bandits ?? []) as Record<string, unknown>[];\n\n const newBandits = new Map<string, BanditCache>();\n const newEngines = new Map<string, BanditEngine>();\n\n for (const b of banditsData) {\n const arms = (b.arms ?? []) as ArmWire[];\n if (arms.length === 0) continue;\n\n const activeArms: Arm[] = arms\n .filter((a) => a.is_active)\n .map((a) => createArm(a));\n\n const name = b.name as string;\n const cache: BanditCache = {\n banditId: Number(b.bandit_id),\n name,\n arms: activeArms,\n armWire: arms,\n optimizationMode: (b.optimization_mode as string) ?? \"base\",\n avgLatencyLastN: b.avg_latency_last_n as number | null,\n budget: b.budget as number | null,\n totalCost: b.total_cost as number | null,\n };\n\n newBandits.set(name, cache);\n\n // Reuse existing engine (preserves RNG state) or create new one\n const existingEngine = this.engines.get(name);\n const banditJson = JSON.stringify(b);\n if (existingEngine) {\n updateEngine(existingEngine, banditJson);\n newEngines.set(name, existingEngine);\n } else {\n newEngines.set(name, createEngine(banditJson));\n }\n }\n\n this.bandits = newBandits;\n this.engines = newEngines;\n }\n\n private async flushPending(): Promise<void> {\n if (this.flushInProgress || !this.store || !this.http) return;\n this.flushInProgress = true;\n\n try {\n const pending = this.store.pending();\n if (pending.length === 0) return;\n\n // Filter out dead events\n const alive = this.deadUuids.size > 0\n ? pending.filter((e) => !this.deadUuids.has(e.local_event_uuid))\n : pending;\n if (alive.length === 0) return;\n\n // Prepare payload (strip metadata/text as configured)\n const payload = prepareCloudPayload(\n alive as Record<string, unknown>[],\n this.dataStorage !== \"local\",\n );\n\n const result = await this.http.ingestEvents(payload);\n\n // Parse per-event errors\n const errors = (result.errors ?? []) as {\n local_event_uuid?: string;\n reason?: string;\n }[];\n const erroredUuids = new Set(\n errors.map((e) => e.local_event_uuid).filter(Boolean) as string[],\n );\n\n // Update retry counts\n for (const uid of erroredUuids) {\n const count = (this.retryCounts.get(uid) ?? 0) + 1;\n this.retryCounts.set(uid, count);\n if (count >= MAX_EVENT_RETRIES) {\n this.deadUuids.add(uid);\n }\n }\n\n // Mark accepted events as flushed\n const flushedUuids = alive\n .map((e) => e.local_event_uuid)\n .filter((uid) => !erroredUuids.has(uid));\n if (flushedUuids.length > 0 && this.store) {\n this.store.markFlushed(flushedUuids);\n }\n\n } catch (err) {\n // Flush failure is non-fatal — events stay pending for next attempt\n console.warn(\"[bandito] Event flush failed — will retry\", err);\n } finally {\n this.flushInProgress = false;\n }\n }\n\n /**\n * Export un-uploaded events to S3 as raw event JSON. One file per event.\n *\n * Key pattern: {prefix}/{bandit_name}/{YYYY/MM/DD}/{local_event_uuid}.json\n * Body: the raw event object (same structure as SQLite payload column).\n */\n private async dumpPendingToS3(): Promise<void> {\n const eventsWithTs = this.store!.pendingS3(100);\n if (eventsWithTs.length === 0) return;\n\n const { PutObjectCommand } = await import(\"@aws-sdk/client-s3\");\n const uploaded: string[] = [];\n\n try {\n for (const { event, ts } of eventsWithTs) {\n const uuid = event.local_event_uuid as string;\n const rawName = (event.bandit_name as string | undefined) || \"unknown\";\n const banditName = rawName.replace(/[^\\w\\-.]/g, \"_\");\n const date = new Date(ts * 1000);\n const datePath = [\n date.getUTCFullYear(),\n String(date.getUTCMonth() + 1).padStart(2, \"0\"),\n String(date.getUTCDate()).padStart(2, \"0\"),\n ].join(\"/\");\n const key = `${this._s3Config.prefix}/${banditName}/${datePath}/${uuid}.json`;\n\n await this._s3Client.send(new PutObjectCommand({\n Bucket: this._s3Config.bucket,\n Key: key,\n Body: JSON.stringify(event),\n ContentType: \"application/json\",\n }));\n uploaded.push(uuid);\n }\n } catch (err) {\n console.warn(\"[bandito] S3 export failed — will retry\", err);\n } finally {\n if (uploaded.length > 0) {\n this.store!.markS3Exported(uploaded);\n }\n }\n }\n}\n","/**\n * Thin wrapper around the WASM engine import.\n *\n * Handles async WASM initialization (loading .wasm binary happens once)\n * and re-exports the BanditEngine constructor for the client.\n */\n\nimport type { BanditEngine as WasmBanditEngine } from \"../wasm/bandito_engine\";\n\nlet wasmModule: typeof import(\"../wasm/bandito_engine\") | null = null;\n\n/**\n * Initialize the WASM module. Must be called before creating BanditEngine instances.\n * Safe to call multiple times — only loads once.\n */\nexport async function initWasm(): Promise<void> {\n if (wasmModule) return;\n wasmModule = await import(\"../wasm/bandito_engine\");\n}\n\n/**\n * Create a BanditEngine from a sync response JSON string.\n * Seeds the RNG with OS entropy to prevent predictable arm selection.\n * Requires initWasm() to have been called first.\n */\nexport function createEngine(banditJson: string): WasmBanditEngine {\n if (!wasmModule) {\n throw new Error(\"WASM not initialized — call initWasm() first\");\n }\n // Generate 8 bytes of OS entropy and pack into a u64 BigInt seed\n const seedBytes = new Uint8Array(8);\n globalThis.crypto.getRandomValues(seedBytes);\n let seed = 0n;\n for (let i = 0; i < 8; i++) {\n seed |= BigInt(seedBytes[i]) << BigInt(i * 8);\n }\n return wasmModule.BanditEngine.newWithSeed(banditJson, seed);\n}\n\n\n/**\n * Update an existing BanditEngine with new sync response data.\n * Preserves RNG state (avoids the \"always picks same arm\" bug).\n */\nexport function updateEngine(engine: WasmBanditEngine, banditJson: string): void {\n engine.updateFromSync(banditJson);\n}\n\nexport type { WasmBanditEngine as BanditEngine };\n\n/**\n * Pull result parsed from engine JSON output.\n */\nexport interface EnginePullResult {\n arm_index: number;\n arm_id: number;\n scores: Record<number, number>;\n}\n","/**\n * SDK types: Arm, PullResult, and internal cache structures.\n */\n\n/** An arm returned to the user after pull(). Immutable. */\nexport interface Arm {\n readonly armId: number;\n readonly modelName: string;\n readonly modelProvider: string;\n readonly systemPrompt: string;\n readonly isPromptTemplated: boolean;\n /** Convenience alias for modelName. */\n readonly model: string;\n /** Convenience alias for systemPrompt. */\n readonly prompt: string;\n}\n\n/** Returned by pull(), passed to update(). Immutable. */\nexport interface PullResult {\n readonly arm: Arm;\n readonly eventId: string;\n readonly banditId: number;\n readonly banditName: string;\n readonly scores: Readonly<Record<number, number>>;\n /** Convenience reach-through to arm.modelName. */\n readonly model: string;\n /** Convenience reach-through to arm.systemPrompt. */\n readonly prompt: string;\n /** @internal perf timestamp */\n readonly _pullTime: number;\n}\n\n/** Create a frozen Arm from raw wire data. */\nexport function createArm(data: {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated?: boolean;\n}): Arm {\n const arm: Arm = {\n armId: data.arm_id,\n modelName: data.model_name,\n modelProvider: data.model_provider,\n systemPrompt: data.system_prompt,\n isPromptTemplated: data.is_prompt_templated ?? false,\n get model() {\n return this.modelName;\n },\n get prompt() {\n return this.systemPrompt;\n },\n };\n return Object.freeze(arm);\n}\n\n/** Create a frozen PullResult. */\nexport function createPullResult(data: {\n arm: Arm;\n eventId: string;\n banditId: number;\n banditName: string;\n scores: Record<number, number>;\n pullTime: number;\n}): PullResult {\n const result: PullResult = {\n arm: data.arm,\n eventId: data.eventId,\n banditId: data.banditId,\n banditName: data.banditName,\n scores: Object.freeze({ ...data.scores }),\n get model() {\n return this.arm.modelName;\n },\n get prompt() {\n return this.arm.systemPrompt;\n },\n _pullTime: data.pullTime,\n };\n return Object.freeze(result);\n}\n\n/** Raw arm data from sync response (snake_case wire format). */\nexport interface ArmWire {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated: boolean;\n is_active: boolean;\n avg_latency_last_n: number | null;\n}\n\n/** Internal mutable cache for a bandit's state. */\nexport interface BanditCache {\n banditId: number;\n name: string;\n arms: Arm[]; // active only\n armWire: ArmWire[]; // all arms (for engine JSON)\n optimizationMode: string;\n avgLatencyLastN: number | null;\n budget: number | null;\n totalCost: number | null;\n}\n","/**\n * Config loader — reads ~/.bandito/config.toml and env vars.\n *\n * Priority order (highest wins): constructor args → env vars → TOML → defaults.\n * Same file as the Python SDK uses.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { parse as parseToml } from \"smol-toml\";\n\nexport const DEFAULT_BASE_URL = \"https://bandito-api.onrender.com\";\nconst CONFIG_DIR = path.join(os.homedir(), \".bandito\");\nconst CONFIG_FILE = path.join(CONFIG_DIR, \"config.toml\");\n\nexport interface S3Config {\n bucket: string;\n prefix: string;\n region: string;\n /** Optional custom endpoint for MinIO / LocalStack / other S3-compatible stores. */\n endpoint?: string;\n}\n\nexport interface JudgeConfig {\n apiKey: string | null;\n model: string;\n}\n\nexport interface BanditoConfig {\n apiKey: string | null;\n baseUrl: string;\n dataStorage: string; // \"local\", \"cloud\", or \"s3\"\n s3: S3Config | null;\n judge: JudgeConfig;\n}\n\n/**\n * Load config from TOML file + env var overrides.\n */\nexport function loadConfig(): BanditoConfig {\n const config: BanditoConfig = {\n apiKey: null,\n baseUrl: DEFAULT_BASE_URL,\n dataStorage: \"local\",\n s3: null,\n judge: { apiKey: null, model: \"gpt-4o-mini\" },\n };\n\n // TOML file first\n if (fs.existsSync(CONFIG_FILE)) {\n try {\n const content = fs.readFileSync(CONFIG_FILE, \"utf-8\");\n const data = parseToml(content) as Record<string, unknown>;\n if (data.api_key) config.apiKey = data.api_key as string;\n if (data.base_url) config.baseUrl = data.base_url as string;\n if (data.data_storage) config.dataStorage = data.data_storage as string;\n const s3Data = data.s3 as Record<string, unknown> | undefined;\n const s3Bucket = ((s3Data?.bucket as string | undefined) ?? \"\").trim();\n if (s3Bucket) {\n const s3Endpoint = ((s3Data?.endpoint as string | undefined) ?? \"\").trim() || undefined;\n config.s3 = {\n bucket: s3Bucket,\n prefix: ((s3Data?.prefix as string | undefined) ?? \"\").trim() || \"bandito\",\n region: ((s3Data?.region as string | undefined) ?? \"\").trim() || \"us-east-1\",\n ...(s3Endpoint ? { endpoint: s3Endpoint } : {}),\n };\n }\n const judgeData = data.judge as Record<string, unknown> | undefined;\n if (judgeData?.api_key) config.judge.apiKey = judgeData.api_key as string;\n if (judgeData?.model) config.judge.model = judgeData.model as string;\n } catch {\n // Failed to parse TOML — fall back to env vars\n }\n }\n\n // Env vars override\n const envKey = process.env.BANDITO_API_KEY;\n if (envKey) config.apiKey = envKey;\n\n const envUrl = process.env.BANDITO_BASE_URL;\n if (envUrl) config.baseUrl = envUrl;\n\n const envStorage = process.env.BANDITO_DATA_STORAGE;\n if (envStorage) config.dataStorage = envStorage;\n\n // S3 env vars (useful for container deployments without a config file)\n const envS3Bucket = (process.env.BANDITO_S3_BUCKET ?? \"\").trim();\n if (envS3Bucket) {\n const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? config.s3?.endpoint ?? \"\").trim() || undefined;\n config.s3 = {\n bucket: envS3Bucket,\n prefix: (process.env.BANDITO_S3_PREFIX ?? config.s3?.prefix ?? \"\").trim() || \"bandito\",\n region: (process.env.BANDITO_S3_REGION ?? config.s3?.region ?? \"\").trim() || \"us-east-1\",\n ...(envEndpoint ? { endpoint: envEndpoint } : {}),\n };\n // Setting BANDITO_S3_BUCKET implicitly activates s3 mode unless\n // the user has explicitly chosen a different mode via BANDITO_DATA_STORAGE.\n if (!process.env.BANDITO_DATA_STORAGE) {\n config.dataStorage = \"s3\";\n }\n } else if (config.s3) {\n const envPrefix = (process.env.BANDITO_S3_PREFIX ?? \"\").trim();\n const envRegion = (process.env.BANDITO_S3_REGION ?? \"\").trim();\n const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? \"\").trim();\n if (envPrefix) config.s3.prefix = envPrefix;\n if (envRegion) config.s3.region = envRegion;\n if (envEndpoint) config.s3.endpoint = envEndpoint;\n }\n\n const envJudgeKey = process.env.JUDGE_API_KEY;\n if (envJudgeKey) config.judge.apiKey = envJudgeKey;\n\n return config;\n}\n","/**\n * HTTP transport — fetch-based client for cloud API.\n *\n * Retry config: 3 attempts, exponential backoff (0.5s, 1s, 2s),\n * retries only 5xx and network errors. Never retries 4xx.\n */\n\nconst MAX_RETRIES = 3;\nconst RETRY_BACKOFF_BASE = 500; // ms — 500, 1000, 2000\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500;\n}\n\nexport class BanditoHTTP {\n private baseUrl: string;\n private apiKey: string;\n private timeout: number;\n\n constructor(baseUrl: string, apiKey: string, timeout: number = 10_000) {\n // Validate URL scheme — reject non-http(s) protocols (file://, javascript:, etc.)\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new Error(`Invalid baseUrl: \"${baseUrl}\"`);\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\n `baseUrl must use http or https, got \"${parsed.protocol}\" — check your config`,\n );\n }\n this.baseUrl = `${baseUrl.replace(/\\/$/, \"\")}/api/v1`;\n this.apiKey = apiKey;\n this.timeout = timeout;\n }\n\n private async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Record<string, unknown>> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const resp = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: body != null ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => \"\");\n // Truncate body to avoid leaking sensitive content into error messages/logs\n const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text;\n if (!isRetryable(resp.status) || attempt === MAX_RETRIES - 1) {\n throw new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${preview}`,\n );\n }\n // Retryable server error — fall through to retry\n lastError = new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${preview}`,\n );\n } else {\n return (await resp.json()) as Record<string, unknown>;\n }\n } catch (err) {\n clearTimeout(timer);\n lastError = err as Error;\n\n // AbortError = timeout, TypeError = network error (in fetch)\n const isNetworkOrTimeout =\n (err as Error).name === \"AbortError\" ||\n (err as Error).name === \"TypeError\";\n if (!isNetworkOrTimeout && attempt < MAX_RETRIES - 1) {\n // Non-retryable (4xx already handled above)\n throw err;\n }\n if (attempt === MAX_RETRIES - 1) {\n throw err;\n }\n }\n\n // Exponential backoff\n const delay = RETRY_BACKOFF_BASE * 2 ** attempt;\n await sleep(delay);\n }\n\n throw lastError!;\n }\n\n /** POST /sync/connect — SDK bootstrap. */\n async connect(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/connect\");\n }\n\n /** POST /sync/heartbeat — periodic state refresh. */\n async heartbeat(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/heartbeat\", {});\n }\n\n /** POST /events — batch event ingestion. */\n async ingestEvents(\n events: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/events\", { events });\n }\n\n /** PATCH /events/{uuid}/grade — submit human grade. */\n async submitGrade(\n eventUuid: string,\n grade: number,\n ): Promise<Record<string, unknown>> {\n return this.request(\"PATCH\", `/events/${eventUuid}/grade`, {\n grade,\n is_graded: true,\n });\n }\n}\n","/**\n * SQLite WAL durability layer — crash-safe event storage.\n *\n * Events are written here immediately after update(). Background flush\n * sends them to cloud. If the process crashes, pending events survive\n * and are retried on next connect().\n */\n\nimport Database from \"better-sqlite3\";\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n local_event_uuid TEXT PRIMARY KEY,\n bandit_id INTEGER NOT NULL,\n arm_id INTEGER NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n created_at REAL NOT NULL,\n human_reward REAL,\n graded_at REAL,\n s3_exported INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_events_status ON events(status);\n`;\n\nconst MIGRATION_GRADING = [\n \"ALTER TABLE events ADD COLUMN human_reward REAL\",\n \"ALTER TABLE events ADD COLUMN graded_at REAL\",\n \"ALTER TABLE events ADD COLUMN s3_exported INTEGER NOT NULL DEFAULT 0\",\n];\n\nexport interface EventPayload {\n local_event_uuid: string;\n bandit_id: number;\n arm_id: number;\n model_name: string;\n model_provider: string;\n bandit_name?: string;\n [key: string]: unknown;\n}\n\nexport class EventStore {\n private db: Database.Database;\n private pushStmt: Database.Statement;\n private pendingStmt: Database.Statement;\n\n constructor(dbPath: string = \":memory:\") {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n this.db.pragma(\"busy_timeout = 5000\");\n this.db.pragma(\"synchronous = NORMAL\");\n this.db.exec(SCHEMA);\n this.migrate();\n\n // Pre-compile frequently-used statements\n this.pushStmt = this.db.prepare(\n `INSERT OR IGNORE INTO events\n (local_event_uuid, bandit_id, arm_id, payload, status, created_at)\n VALUES (?, ?, ?, ?, 'pending', ?)`,\n );\n this.pendingStmt = this.db.prepare(\n `SELECT local_event_uuid, payload FROM events WHERE status = 'pending'\n ORDER BY created_at ASC LIMIT ?`,\n );\n }\n\n /** Insert a pending event. */\n push(event: EventPayload): void {\n this.pushStmt.run(\n event.local_event_uuid,\n event.bandit_id,\n event.arm_id,\n JSON.stringify(event),\n Date.now() / 1000,\n );\n }\n\n /** Return up to `limit` pending events (oldest first). */\n pending(limit: number = 50): EventPayload[] {\n const rows = this.pendingStmt.all(limit) as {\n local_event_uuid: string;\n payload: string;\n }[];\n const results: EventPayload[] = [];\n const corrupt: string[] = [];\n for (const row of rows) {\n try {\n results.push(JSON.parse(row.payload) as EventPayload);\n } catch {\n console.warn(\n `[bandito] Discarding corrupt event ${row.local_event_uuid} from store`,\n );\n corrupt.push(row.local_event_uuid);\n }\n }\n if (corrupt.length > 0) {\n this.markFlushed(corrupt);\n }\n return results;\n }\n\n /** Mark events as successfully flushed to cloud. */\n markFlushed(uuids: string[]): void {\n if (uuids.length === 0) return;\n const placeholders = uuids.map(() => \"?\").join(\",\");\n this.db\n .prepare(\n `UPDATE events SET status = 'flushed'\n WHERE local_event_uuid IN (${placeholders})`,\n )\n .run(...uuids);\n }\n\n /** Record a human grade locally. */\n markGraded(uuid: string, reward: number): void {\n this.db\n .prepare(\n `UPDATE events SET human_reward = ?, graded_at = ?\n WHERE local_event_uuid = ?`,\n )\n .run(reward, Date.now() / 1000, uuid);\n }\n\n /** Return un-exported events as {event, ts} for S3 dump. */\n pendingS3(limit: number = 100): Array<{ event: Record<string, unknown>; ts: number }> {\n const rows = this.db\n .prepare(\n `SELECT payload, created_at FROM events WHERE s3_exported = 0\n ORDER BY created_at ASC LIMIT ?`,\n )\n .all(limit) as { payload: string; created_at: number }[];\n return rows.map((r) => ({ event: JSON.parse(r.payload) as Record<string, unknown>, ts: r.created_at }));\n }\n\n /** Mark events as successfully exported to S3. */\n markS3Exported(uuids: string[]): void {\n if (uuids.length === 0) return;\n const placeholders = uuids.map(() => \"?\").join(\",\");\n this.db\n .prepare(`UPDATE events SET s3_exported = 1 WHERE local_event_uuid IN (${placeholders})`)\n .run(...uuids);\n }\n\n /** Close the database connection. */\n close(): void {\n this.db.close();\n }\n\n private migrate(): void {\n for (const stmt of MIGRATION_GRADING) {\n try {\n this.db.exec(stmt);\n } catch {\n // Column already exists\n }\n }\n }\n}\n","/**\n * Payload utilities for cloud event ingestion.\n */\n\nconst TEXT_FIELDS = [\"query_text\", \"response\"] as const;\nconst METADATA_FIELDS = [\"model_name\", \"model_provider\"] as const;\n\n/**\n * Return shallow copies of events ready for cloud ingest.\n *\n * Always strips model_name/model_provider (only needed in local SQLite for TUI).\n * Strips query_text/response when includeText is false (dataStorage=\"local\").\n */\nexport function prepareCloudPayload(\n events: Record<string, unknown>[],\n includeText: boolean,\n): Record<string, unknown>[] {\n return events.map((event) => {\n const copy = { ...event };\n for (const field of METADATA_FIELDS) {\n delete copy[field];\n }\n if (!includeText) {\n for (const field of TEXT_FIELDS) {\n delete copy[field];\n }\n }\n return copy;\n });\n}\n"],"mappings":"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","names":["fs","path","os","parseToml","path","grade","Database","grade"]}
package/dist/index.mjs CHANGED
@@ -15,7 +15,13 @@ function createEngine(banditJson) {
15
15
  if (!wasmModule) {
16
16
  throw new Error("WASM not initialized \u2014 call initWasm() first");
17
17
  }
18
- return new wasmModule.BanditEngine(banditJson);
18
+ const seedBytes = new Uint8Array(8);
19
+ globalThis.crypto.getRandomValues(seedBytes);
20
+ let seed = 0n;
21
+ for (let i = 0; i < 8; i++) {
22
+ seed |= BigInt(seedBytes[i]) << BigInt(i * 8);
23
+ }
24
+ return wasmModule.BanditEngine.newWithSeed(banditJson, seed);
19
25
  }
20
26
  function updateEngine(engine, banditJson) {
21
27
  engine.updateFromSync(banditJson);
@@ -68,7 +74,9 @@ function loadConfig() {
68
74
  const config = {
69
75
  apiKey: null,
70
76
  baseUrl: DEFAULT_BASE_URL,
71
- dataStorage: "local"
77
+ dataStorage: "local",
78
+ s3: null,
79
+ judge: { apiKey: null, model: "gpt-4o-mini" }
72
80
  };
73
81
  if (fs.existsSync(CONFIG_FILE)) {
74
82
  try {
@@ -77,6 +85,20 @@ function loadConfig() {
77
85
  if (data.api_key) config.apiKey = data.api_key;
78
86
  if (data.base_url) config.baseUrl = data.base_url;
79
87
  if (data.data_storage) config.dataStorage = data.data_storage;
88
+ const s3Data = data.s3;
89
+ const s3Bucket = (s3Data?.bucket ?? "").trim();
90
+ if (s3Bucket) {
91
+ const s3Endpoint = (s3Data?.endpoint ?? "").trim() || void 0;
92
+ config.s3 = {
93
+ bucket: s3Bucket,
94
+ prefix: (s3Data?.prefix ?? "").trim() || "bandito",
95
+ region: (s3Data?.region ?? "").trim() || "us-east-1",
96
+ ...s3Endpoint ? { endpoint: s3Endpoint } : {}
97
+ };
98
+ }
99
+ const judgeData = data.judge;
100
+ if (judgeData?.api_key) config.judge.apiKey = judgeData.api_key;
101
+ if (judgeData?.model) config.judge.model = judgeData.model;
80
102
  } catch {
81
103
  }
82
104
  }
@@ -86,6 +108,28 @@ function loadConfig() {
86
108
  if (envUrl) config.baseUrl = envUrl;
87
109
  const envStorage = process.env.BANDITO_DATA_STORAGE;
88
110
  if (envStorage) config.dataStorage = envStorage;
111
+ const envS3Bucket = (process.env.BANDITO_S3_BUCKET ?? "").trim();
112
+ if (envS3Bucket) {
113
+ const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? config.s3?.endpoint ?? "").trim() || void 0;
114
+ config.s3 = {
115
+ bucket: envS3Bucket,
116
+ prefix: (process.env.BANDITO_S3_PREFIX ?? config.s3?.prefix ?? "").trim() || "bandito",
117
+ region: (process.env.BANDITO_S3_REGION ?? config.s3?.region ?? "").trim() || "us-east-1",
118
+ ...envEndpoint ? { endpoint: envEndpoint } : {}
119
+ };
120
+ if (!process.env.BANDITO_DATA_STORAGE) {
121
+ config.dataStorage = "s3";
122
+ }
123
+ } else if (config.s3) {
124
+ const envPrefix = (process.env.BANDITO_S3_PREFIX ?? "").trim();
125
+ const envRegion = (process.env.BANDITO_S3_REGION ?? "").trim();
126
+ const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? "").trim();
127
+ if (envPrefix) config.s3.prefix = envPrefix;
128
+ if (envRegion) config.s3.region = envRegion;
129
+ if (envEndpoint) config.s3.endpoint = envEndpoint;
130
+ }
131
+ const envJudgeKey = process.env.JUDGE_API_KEY;
132
+ if (envJudgeKey) config.judge.apiKey = envJudgeKey;
89
133
  return config;
90
134
  }
91
135
 
@@ -103,6 +147,17 @@ var BanditoHTTP = class {
103
147
  apiKey;
104
148
  timeout;
105
149
  constructor(baseUrl, apiKey, timeout = 1e4) {
150
+ let parsed;
151
+ try {
152
+ parsed = new URL(baseUrl);
153
+ } catch {
154
+ throw new Error(`Invalid baseUrl: "${baseUrl}"`);
155
+ }
156
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
157
+ throw new Error(
158
+ `baseUrl must use http or https, got "${parsed.protocol}" \u2014 check your config`
159
+ );
160
+ }
106
161
  this.baseUrl = `${baseUrl.replace(/\/$/, "")}/api/v1`;
107
162
  this.apiKey = apiKey;
108
163
  this.timeout = timeout;
@@ -125,13 +180,14 @@ var BanditoHTTP = class {
125
180
  clearTimeout(timer);
126
181
  if (!resp.ok) {
127
182
  const text = await resp.text().catch(() => "");
183
+ const preview = text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
128
184
  if (!isRetryable(resp.status) || attempt === MAX_RETRIES - 1) {
129
185
  throw new Error(
130
- `HTTP ${resp.status} on ${method} ${path3}: ${text}`
186
+ `HTTP ${resp.status} on ${method} ${path3}: ${preview}`
131
187
  );
132
188
  }
133
189
  lastError = new Error(
134
- `HTTP ${resp.status} on ${method} ${path3}: ${text}`
190
+ `HTTP ${resp.status} on ${method} ${path3}: ${preview}`
135
191
  );
136
192
  } else {
137
193
  return await resp.json();
@@ -184,13 +240,15 @@ CREATE TABLE IF NOT EXISTS events (
184
240
  status TEXT NOT NULL DEFAULT 'pending',
185
241
  created_at REAL NOT NULL,
186
242
  human_reward REAL,
187
- graded_at REAL
243
+ graded_at REAL,
244
+ s3_exported INTEGER NOT NULL DEFAULT 0
188
245
  );
189
246
  CREATE INDEX IF NOT EXISTS idx_events_status ON events(status);
190
247
  `;
191
248
  var MIGRATION_GRADING = [
192
249
  "ALTER TABLE events ADD COLUMN human_reward REAL",
193
- "ALTER TABLE events ADD COLUMN graded_at REAL"
250
+ "ALTER TABLE events ADD COLUMN graded_at REAL",
251
+ "ALTER TABLE events ADD COLUMN s3_exported INTEGER NOT NULL DEFAULT 0"
194
252
  ];
195
253
  var EventStore = class {
196
254
  db;
@@ -209,7 +267,7 @@ var EventStore = class {
209
267
  VALUES (?, ?, ?, ?, 'pending', ?)`
210
268
  );
211
269
  this.pendingStmt = this.db.prepare(
212
- `SELECT payload FROM events WHERE status = 'pending'
270
+ `SELECT local_event_uuid, payload FROM events WHERE status = 'pending'
213
271
  ORDER BY created_at ASC LIMIT ?`
214
272
  );
215
273
  }
@@ -226,7 +284,22 @@ var EventStore = class {
226
284
  /** Return up to `limit` pending events (oldest first). */
227
285
  pending(limit = 50) {
228
286
  const rows = this.pendingStmt.all(limit);
229
- return rows.map((row) => JSON.parse(row.payload));
287
+ const results = [];
288
+ const corrupt = [];
289
+ for (const row of rows) {
290
+ try {
291
+ results.push(JSON.parse(row.payload));
292
+ } catch {
293
+ console.warn(
294
+ `[bandito] Discarding corrupt event ${row.local_event_uuid} from store`
295
+ );
296
+ corrupt.push(row.local_event_uuid);
297
+ }
298
+ }
299
+ if (corrupt.length > 0) {
300
+ this.markFlushed(corrupt);
301
+ }
302
+ return results;
230
303
  }
231
304
  /** Mark events as successfully flushed to cloud. */
232
305
  markFlushed(uuids) {
@@ -244,6 +317,20 @@ var EventStore = class {
244
317
  WHERE local_event_uuid = ?`
245
318
  ).run(reward, Date.now() / 1e3, uuid);
246
319
  }
320
+ /** Return un-exported events as {event, ts} for S3 dump. */
321
+ pendingS3(limit = 100) {
322
+ const rows = this.db.prepare(
323
+ `SELECT payload, created_at FROM events WHERE s3_exported = 0
324
+ ORDER BY created_at ASC LIMIT ?`
325
+ ).all(limit);
326
+ return rows.map((r) => ({ event: JSON.parse(r.payload), ts: r.created_at }));
327
+ }
328
+ /** Mark events as successfully exported to S3. */
329
+ markS3Exported(uuids) {
330
+ if (uuids.length === 0) return;
331
+ const placeholders = uuids.map(() => "?").join(",");
332
+ this.db.prepare(`UPDATE events SET s3_exported = 1 WHERE local_event_uuid IN (${placeholders})`).run(...uuids);
333
+ }
247
334
  /** Close the database connection. */
248
335
  close() {
249
336
  this.db.close();
@@ -291,9 +378,14 @@ var BanditoClient = class {
291
378
  bandits = /* @__PURE__ */ new Map();
292
379
  connected = false;
293
380
  flushInterval = null;
381
+ s3FlushInterval = null;
294
382
  flushInProgress = false;
295
383
  deadUuids = /* @__PURE__ */ new Set();
296
384
  retryCounts = /* @__PURE__ */ new Map();
385
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
+ _s3Client = null;
387
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
388
+ _s3Config = null;
297
389
  constructor(options = {}) {
298
390
  this.apiKey = options.apiKey;
299
391
  this.baseUrl = options.baseUrl;
@@ -332,6 +424,22 @@ var BanditoClient = class {
332
424
  }
333
425
  }
334
426
  this.store = new EventStore(storePath);
427
+ this._s3Client = null;
428
+ this._s3Config = null;
429
+ if (this.dataStorage === "s3" && config.s3) {
430
+ try {
431
+ const { S3Client } = await import("@aws-sdk/client-s3");
432
+ const s3ClientConfig = { region: config.s3.region };
433
+ if (config.s3.endpoint) {
434
+ s3ClientConfig.endpoint = config.s3.endpoint;
435
+ s3ClientConfig.forcePathStyle = true;
436
+ }
437
+ this._s3Client = new S3Client(s3ClientConfig);
438
+ this._s3Config = config.s3;
439
+ } catch {
440
+ console.warn("[bandito] data_storage='s3' requires @aws-sdk/client-s3");
441
+ }
442
+ }
335
443
  try {
336
444
  const data = await this.http.connect();
337
445
  this.applySync(data);
@@ -339,9 +447,17 @@ var BanditoClient = class {
339
447
  this.retryCounts.clear();
340
448
  await this.flushPending();
341
449
  this.flushInterval = setInterval(() => {
342
- this.flushPending().catch(() => {
343
- });
450
+ this.flushPending().catch(
451
+ (err) => console.warn("[bandito] Periodic flush error", err)
452
+ );
344
453
  }, 3e4);
454
+ if (this._s3Client !== null) {
455
+ this.s3FlushInterval = setInterval(() => {
456
+ this.dumpPendingToS3().catch(
457
+ (err) => console.warn("[bandito] Periodic S3 export error", err)
458
+ );
459
+ }, 3e4);
460
+ }
345
461
  this.connected = true;
346
462
  } catch (err) {
347
463
  this.store?.close();
@@ -404,7 +520,8 @@ var BanditoClient = class {
404
520
  bandit_id: pullResult.banditId,
405
521
  arm_id: pullResult.arm.armId,
406
522
  model_name: pullResult.arm.modelName,
407
- model_provider: pullResult.arm.modelProvider
523
+ model_provider: pullResult.arm.modelProvider,
524
+ bandit_name: pullResult.banditName
408
525
  };
409
526
  if (options.queryText != null) {
410
527
  event.query_text = options.queryText;
@@ -434,8 +551,9 @@ var BanditoClient = class {
434
551
  event.run_error = true;
435
552
  }
436
553
  this.store.push(event);
437
- this.flushPending().catch(() => {
438
- });
554
+ this.flushPending().catch(
555
+ (err) => console.warn("[bandito] Flush error", err)
556
+ );
439
557
  }
440
558
  /**
441
559
  * Send a human grade for an existing event. Async (HTTP).
@@ -468,9 +586,16 @@ var BanditoClient = class {
468
586
  clearInterval(this.flushInterval);
469
587
  this.flushInterval = null;
470
588
  }
589
+ if (this.s3FlushInterval) {
590
+ clearInterval(this.s3FlushInterval);
591
+ this.s3FlushInterval = null;
592
+ }
471
593
  if (this.store && this.http) {
472
594
  await this.flushPending();
473
595
  }
596
+ if (this._s3Client !== null && this.store) {
597
+ await this.dumpPendingToS3();
598
+ }
474
599
  this.store?.close();
475
600
  this.store = null;
476
601
  this.http = null;
@@ -550,6 +675,45 @@ var BanditoClient = class {
550
675
  this.flushInProgress = false;
551
676
  }
552
677
  }
678
+ /**
679
+ * Export un-uploaded events to S3 as raw event JSON. One file per event.
680
+ *
681
+ * Key pattern: {prefix}/{bandit_name}/{YYYY/MM/DD}/{local_event_uuid}.json
682
+ * Body: the raw event object (same structure as SQLite payload column).
683
+ */
684
+ async dumpPendingToS3() {
685
+ const eventsWithTs = this.store.pendingS3(100);
686
+ if (eventsWithTs.length === 0) return;
687
+ const { PutObjectCommand } = await import("@aws-sdk/client-s3");
688
+ const uploaded = [];
689
+ try {
690
+ for (const { event, ts } of eventsWithTs) {
691
+ const uuid = event.local_event_uuid;
692
+ const rawName = event.bandit_name || "unknown";
693
+ const banditName = rawName.replace(/[^\w\-.]/g, "_");
694
+ const date = new Date(ts * 1e3);
695
+ const datePath = [
696
+ date.getUTCFullYear(),
697
+ String(date.getUTCMonth() + 1).padStart(2, "0"),
698
+ String(date.getUTCDate()).padStart(2, "0")
699
+ ].join("/");
700
+ const key = `${this._s3Config.prefix}/${banditName}/${datePath}/${uuid}.json`;
701
+ await this._s3Client.send(new PutObjectCommand({
702
+ Bucket: this._s3Config.bucket,
703
+ Key: key,
704
+ Body: JSON.stringify(event),
705
+ ContentType: "application/json"
706
+ }));
707
+ uploaded.push(uuid);
708
+ }
709
+ } catch (err) {
710
+ console.warn("[bandito] S3 export failed \u2014 will retry", err);
711
+ } finally {
712
+ if (uploaded.length > 0) {
713
+ this.store.markS3Exported(uploaded);
714
+ }
715
+ }
716
+ }
553
717
  };
554
718
 
555
719
  // src/index.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/engine.ts","../src/models.ts","../src/config.ts","../src/http.ts","../src/store.ts","../src/worker.ts","../src/index.ts"],"sourcesContent":["/**\n * BanditoClient — main orchestrator for the JS/TS SDK.\n *\n * Mirrors the Python SDK's sync-first design:\n * - pull() is synchronous (WASM math, <1ms)\n * - connect(), grade(), sync(), close() are async (HTTP I/O)\n * - update() is synchronous (SQLite write + fire-and-forget flush)\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { initWasm, createEngine, updateEngine, type BanditEngine, type EnginePullResult } from \"./engine.js\";\nimport {\n type Arm,\n type PullResult,\n type BanditCache,\n type ArmWire,\n createArm,\n createPullResult,\n} from \"./models.js\";\nimport { loadConfig, DEFAULT_BASE_URL } from \"./config.js\";\nimport { BanditoHTTP } from \"./http.js\";\nimport { EventStore, type EventPayload } from \"./store.js\";\nimport { prepareCloudPayload } from \"./worker.js\";\n\nconst DEFAULT_STORE_PATH = path.join(os.homedir(), \".bandito\", \"events.db\");\nconst MAX_EVENT_RETRIES = 5;\n\nexport interface ClientOptions {\n apiKey?: string;\n baseUrl?: string;\n storePath?: string;\n dataStorage?: string;\n}\n\nexport interface PullOptions {\n query?: string;\n exclude?: number[];\n}\n\nexport interface UpdateOptions {\n queryText?: string;\n response?: string | Record<string, unknown>;\n reward?: number;\n cost?: number;\n latency?: number;\n inputTokens?: number;\n outputTokens?: number;\n segment?: Record<string, string>;\n failed?: boolean;\n}\n\nexport class BanditoClient {\n private apiKey: string | undefined;\n private baseUrl: string | undefined;\n private storePath: string | undefined;\n private dataStorageArg: string | undefined;\n private dataStorage: string;\n\n private http: BanditoHTTP | null = null;\n private store: EventStore | null = null;\n private engines: Map<string, BanditEngine> = new Map();\n private bandits: Map<string, BanditCache> = new Map();\n private connected = false;\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private flushInProgress = false;\n private deadUuids: Set<string> = new Set();\n private retryCounts: Map<string, number> = new Map();\n\n constructor(options: ClientOptions = {}) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl;\n this.storePath = options.storePath;\n this.dataStorageArg = options.dataStorage;\n this.dataStorage = options.dataStorage ?? \"local\";\n }\n\n /**\n * Bootstrap: authenticate and hydrate in-memory state from cloud.\n *\n * Resolves config from: constructor args → env vars → ~/.bandito/config.toml.\n * Initializes WASM, creates HTTP client, SQLite store, fetches full state.\n */\n async connect(): Promise<void> {\n // Tear down previous connection if reconnecting\n if (this.connected) {\n await this.close();\n }\n\n // Init WASM (loads .wasm binary once)\n await initWasm();\n\n // Resolve config\n const config = loadConfig();\n const apiKey = this.apiKey ?? config.apiKey;\n if (!apiKey) {\n throw new Error(\n \"apiKey required — pass it to constructor, set BANDITO_API_KEY, \" +\n \"or run `bandito signup`\",\n );\n }\n\n const baseUrl = this.baseUrl ?? config.baseUrl;\n if (!this.dataStorageArg) {\n this.dataStorage = config.dataStorage;\n }\n\n this.http = new BanditoHTTP(baseUrl, apiKey);\n const storePath = this.storePath ?? DEFAULT_STORE_PATH;\n if (storePath !== \":memory:\") {\n const dir = path.dirname(storePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n }\n this.store = new EventStore(storePath);\n\n // Bootstrap: fetch state, hydrate cache, flush pending\n try {\n const data = await this.http.connect();\n this.applySync(data);\n\n // Reset retry state\n this.deadUuids.clear();\n this.retryCounts.clear();\n\n // Flush pending events from previous crash\n await this.flushPending();\n\n // Start periodic flush (every 30s)\n this.flushInterval = setInterval(() => {\n this.flushPending().catch(() => {});\n }, 30_000);\n\n this.connected = true;\n } catch (err) {\n this.store?.close();\n this.store = null;\n this.http = null;\n throw err;\n }\n }\n\n /**\n * Local Thompson Sampling decision. Synchronous, <1ms, no network.\n */\n pull(banditName: string, options: PullOptions = {}): PullResult {\n this.ensureConnected();\n\n const cache = this.bandits.get(banditName);\n if (!cache) {\n const available = [...this.bandits.keys()];\n throw new Error(\n `Unknown bandit '${banditName}'. Available: [${available.join(\", \")}]`,\n );\n }\n\n if (cache.arms.length === 0) {\n throw new Error(`Bandit '${banditName}' has no active arms`);\n }\n\n const engine = this.engines.get(banditName)!;\n const queryLength = options.query?.length ?? undefined;\n const excludeIds = options.exclude ? Int32Array.from(options.exclude) : undefined;\n\n const resultJson = engine.pull(queryLength, excludeIds);\n const raw: EnginePullResult = JSON.parse(resultJson);\n\n // Look up the winning arm from our cached active arms\n const winnerArm = cache.arms.find((a) => a.armId === raw.arm_id);\n if (!winnerArm) {\n throw new Error(\n `Engine selected arm ${raw.arm_id} but it's not in active arm cache for \"${banditName}\". ` +\n \"This is likely a bug — please report it at https://github.com/bandito-ai/bandito/issues\",\n );\n }\n\n return createPullResult({\n arm: winnerArm,\n eventId: randomUUID(),\n banditId: cache.banditId,\n banditName,\n scores: raw.scores,\n pullTime: performance.now(),\n });\n }\n\n /**\n * Record an LLM call outcome. Writes to SQLite first (crash-safe),\n * then fires off a non-blocking flush to cloud.\n */\n update(pullResult: PullResult, options: UpdateOptions = {}): void {\n this.ensureConnected();\n\n let reward = options.reward;\n if (options.failed && reward == null) {\n reward = 0.0;\n }\n\n // Auto-calculate latency from pull timestamp\n let latency = options.latency;\n if (latency == null && pullResult._pullTime > 0) {\n latency = performance.now() - pullResult._pullTime;\n }\n\n // Build event payload (snake_case for wire format)\n const event: EventPayload = {\n local_event_uuid: pullResult.eventId,\n bandit_id: pullResult.banditId,\n arm_id: pullResult.arm.armId,\n model_name: pullResult.arm.modelName,\n model_provider: pullResult.arm.modelProvider,\n };\n\n if (options.queryText != null) {\n (event as Record<string, unknown>).query_text = options.queryText;\n }\n if (options.response != null) {\n (event as Record<string, unknown>).response =\n typeof options.response === \"string\"\n ? { response: options.response }\n : options.response;\n }\n if (reward != null) {\n (event as Record<string, unknown>).early_reward = reward;\n }\n if (options.cost != null) {\n (event as Record<string, unknown>).cost = options.cost;\n }\n if (latency != null) {\n (event as Record<string, unknown>).latency = latency;\n }\n if (options.inputTokens != null) {\n (event as Record<string, unknown>).input_tokens = options.inputTokens;\n }\n if (options.outputTokens != null) {\n (event as Record<string, unknown>).output_tokens = options.outputTokens;\n }\n if (options.segment != null) {\n (event as Record<string, unknown>).segment = options.segment;\n }\n if (options.failed) {\n (event as Record<string, unknown>).run_error = true;\n }\n\n // Write to SQLite WAL first — survives crashes\n this.store!.push(event);\n\n // Fire-and-forget flush (errors logged inside flushPending)\n this.flushPending().catch(() => {});\n }\n\n /**\n * Send a human grade for an existing event. Async (HTTP).\n */\n async grade(eventId: string, grade: number): Promise<void> {\n this.ensureConnected();\n await this.http!.submitGrade(eventId, grade);\n }\n\n /**\n * Explicit state refresh from cloud.\n */\n async sync(): Promise<void> {\n this.ensureConnected();\n const data = await this.http!.heartbeat();\n\n const prevBandits = new Map(this.bandits);\n const prevEngines = new Map(this.engines);\n try {\n this.applySync(data);\n } catch (err) {\n // Rollback on malformed response — keep last-known-good state\n this.bandits = prevBandits;\n this.engines = prevEngines;\n console.warn(\"[bandito] Sync response malformed — keeping last-known-good state\", err);\n }\n }\n\n /**\n * Shut down: clear interval, flush remaining events, close connections.\n */\n async close(): Promise<void> {\n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n\n // Final flush\n if (this.store && this.http) {\n await this.flushPending();\n }\n\n this.store?.close();\n this.store = null;\n this.http = null;\n this.engines.clear();\n this.bandits.clear();\n this.connected = false;\n }\n\n // --- Internal ---\n\n private ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\"Not connected — call connect() first\");\n }\n }\n\n private applySync(data: Record<string, unknown>): void {\n const banditsData = (data.bandits ?? []) as Record<string, unknown>[];\n\n const newBandits = new Map<string, BanditCache>();\n const newEngines = new Map<string, BanditEngine>();\n\n for (const b of banditsData) {\n const arms = (b.arms ?? []) as ArmWire[];\n if (arms.length === 0) continue;\n\n const activeArms: Arm[] = arms\n .filter((a) => a.is_active)\n .map((a) => createArm(a));\n\n const name = b.name as string;\n const cache: BanditCache = {\n banditId: Number(b.bandit_id),\n name,\n arms: activeArms,\n armWire: arms,\n optimizationMode: (b.optimization_mode as string) ?? \"base\",\n avgLatencyLastN: b.avg_latency_last_n as number | null,\n budget: b.budget as number | null,\n totalCost: b.total_cost as number | null,\n };\n\n newBandits.set(name, cache);\n\n // Reuse existing engine (preserves RNG state) or create new one\n const existingEngine = this.engines.get(name);\n const banditJson = JSON.stringify(b);\n if (existingEngine) {\n updateEngine(existingEngine, banditJson);\n newEngines.set(name, existingEngine);\n } else {\n newEngines.set(name, createEngine(banditJson));\n }\n }\n\n this.bandits = newBandits;\n this.engines = newEngines;\n }\n\n private async flushPending(): Promise<void> {\n if (this.flushInProgress || !this.store || !this.http) return;\n this.flushInProgress = true;\n\n try {\n const pending = this.store.pending();\n if (pending.length === 0) return;\n\n // Filter out dead events\n const alive = this.deadUuids.size > 0\n ? pending.filter((e) => !this.deadUuids.has(e.local_event_uuid))\n : pending;\n if (alive.length === 0) return;\n\n // Prepare payload (strip metadata/text as configured)\n const payload = prepareCloudPayload(\n alive as Record<string, unknown>[],\n this.dataStorage !== \"local\",\n );\n\n const result = await this.http.ingestEvents(payload);\n\n // Parse per-event errors\n const errors = (result.errors ?? []) as {\n local_event_uuid?: string;\n reason?: string;\n }[];\n const erroredUuids = new Set(\n errors.map((e) => e.local_event_uuid).filter(Boolean) as string[],\n );\n\n // Update retry counts\n for (const uid of erroredUuids) {\n const count = (this.retryCounts.get(uid) ?? 0) + 1;\n this.retryCounts.set(uid, count);\n if (count >= MAX_EVENT_RETRIES) {\n this.deadUuids.add(uid);\n }\n }\n\n // Mark accepted events as flushed\n const flushedUuids = alive\n .map((e) => e.local_event_uuid)\n .filter((uid) => !erroredUuids.has(uid));\n if (flushedUuids.length > 0 && this.store) {\n this.store.markFlushed(flushedUuids);\n }\n } catch (err) {\n // Flush failure is non-fatal — events stay pending for next attempt\n console.warn(\"[bandito] Event flush failed — will retry\", err);\n } finally {\n this.flushInProgress = false;\n }\n }\n}\n","/**\n * Thin wrapper around the WASM engine import.\n *\n * Handles async WASM initialization (loading .wasm binary happens once)\n * and re-exports the BanditEngine constructor for the client.\n */\n\nimport type { BanditEngine as WasmBanditEngine } from \"../wasm/bandito_engine\";\n\nlet wasmModule: typeof import(\"../wasm/bandito_engine\") | null = null;\n\n/**\n * Initialize the WASM module. Must be called before creating BanditEngine instances.\n * Safe to call multiple times — only loads once.\n */\nexport async function initWasm(): Promise<void> {\n if (wasmModule) return;\n wasmModule = await import(\"../wasm/bandito_engine\");\n}\n\n/**\n * Create a BanditEngine from a sync response JSON string.\n * Requires initWasm() to have been called first.\n */\nexport function createEngine(banditJson: string): WasmBanditEngine {\n if (!wasmModule) {\n throw new Error(\"WASM not initialized — call initWasm() first\");\n }\n return new wasmModule.BanditEngine(banditJson);\n}\n\n/**\n * Update an existing BanditEngine with new sync response data.\n * Preserves RNG state (avoids the \"always picks same arm\" bug).\n */\nexport function updateEngine(engine: WasmBanditEngine, banditJson: string): void {\n engine.updateFromSync(banditJson);\n}\n\nexport type { WasmBanditEngine as BanditEngine };\n\n/**\n * Pull result parsed from engine JSON output.\n */\nexport interface EnginePullResult {\n arm_index: number;\n arm_id: number;\n scores: Record<number, number>;\n}\n","/**\n * SDK types: Arm, PullResult, and internal cache structures.\n */\n\n/** An arm returned to the user after pull(). Immutable. */\nexport interface Arm {\n readonly armId: number;\n readonly modelName: string;\n readonly modelProvider: string;\n readonly systemPrompt: string;\n readonly isPromptTemplated: boolean;\n /** Convenience alias for modelName. */\n readonly model: string;\n /** Convenience alias for systemPrompt. */\n readonly prompt: string;\n}\n\n/** Returned by pull(), passed to update(). Immutable. */\nexport interface PullResult {\n readonly arm: Arm;\n readonly eventId: string;\n readonly banditId: number;\n readonly banditName: string;\n readonly scores: Readonly<Record<number, number>>;\n /** Convenience reach-through to arm.modelName. */\n readonly model: string;\n /** Convenience reach-through to arm.systemPrompt. */\n readonly prompt: string;\n /** @internal perf timestamp */\n readonly _pullTime: number;\n}\n\n/** Create a frozen Arm from raw wire data. */\nexport function createArm(data: {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated?: boolean;\n}): Arm {\n const arm: Arm = {\n armId: data.arm_id,\n modelName: data.model_name,\n modelProvider: data.model_provider,\n systemPrompt: data.system_prompt,\n isPromptTemplated: data.is_prompt_templated ?? false,\n get model() {\n return this.modelName;\n },\n get prompt() {\n return this.systemPrompt;\n },\n };\n return Object.freeze(arm);\n}\n\n/** Create a frozen PullResult. */\nexport function createPullResult(data: {\n arm: Arm;\n eventId: string;\n banditId: number;\n banditName: string;\n scores: Record<number, number>;\n pullTime: number;\n}): PullResult {\n const result: PullResult = {\n arm: data.arm,\n eventId: data.eventId,\n banditId: data.banditId,\n banditName: data.banditName,\n scores: Object.freeze({ ...data.scores }),\n get model() {\n return this.arm.modelName;\n },\n get prompt() {\n return this.arm.systemPrompt;\n },\n _pullTime: data.pullTime,\n };\n return Object.freeze(result);\n}\n\n/** Raw arm data from sync response (snake_case wire format). */\nexport interface ArmWire {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated: boolean;\n is_active: boolean;\n avg_latency_last_n: number | null;\n}\n\n/** Internal mutable cache for a bandit's state. */\nexport interface BanditCache {\n banditId: number;\n name: string;\n arms: Arm[]; // active only\n armWire: ArmWire[]; // all arms (for engine JSON)\n optimizationMode: string;\n avgLatencyLastN: number | null;\n budget: number | null;\n totalCost: number | null;\n}\n","/**\n * Config loader — reads ~/.bandito/config.toml and env vars.\n *\n * Resolution order: constructor args → env vars → TOML → defaults.\n * Same file as the Python SDK uses.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { parse as parseToml } from \"smol-toml\";\n\nexport const DEFAULT_BASE_URL = \"https://bandito-api.onrender.com\";\nconst CONFIG_DIR = path.join(os.homedir(), \".bandito\");\nconst CONFIG_FILE = path.join(CONFIG_DIR, \"config.toml\");\n\nexport interface BanditoConfig {\n apiKey: string | null;\n baseUrl: string;\n dataStorage: string; // \"local\" or \"cloud\"\n}\n\n/**\n * Load config from TOML file + env var overrides.\n */\nexport function loadConfig(): BanditoConfig {\n const config: BanditoConfig = {\n apiKey: null,\n baseUrl: DEFAULT_BASE_URL,\n dataStorage: \"local\",\n };\n\n // TOML file first\n if (fs.existsSync(CONFIG_FILE)) {\n try {\n const content = fs.readFileSync(CONFIG_FILE, \"utf-8\");\n const data = parseToml(content);\n if (data.api_key) config.apiKey = data.api_key;\n if (data.base_url) config.baseUrl = data.base_url;\n if (data.data_storage) config.dataStorage = data.data_storage;\n } catch {\n // Failed to parse TOML — fall back to env vars\n }\n }\n\n // Env vars override\n const envKey = process.env.BANDITO_API_KEY;\n if (envKey) config.apiKey = envKey;\n\n const envUrl = process.env.BANDITO_BASE_URL;\n if (envUrl) config.baseUrl = envUrl;\n\n const envStorage = process.env.BANDITO_DATA_STORAGE;\n if (envStorage) config.dataStorage = envStorage;\n\n return config;\n}\n","/**\n * HTTP transport — fetch-based client for cloud API.\n *\n * Retry config: 3 attempts, exponential backoff (0.5s, 1s, 2s),\n * retries only 5xx and network errors. Never retries 4xx.\n */\n\nconst MAX_RETRIES = 3;\nconst RETRY_BACKOFF_BASE = 500; // ms — 500, 1000, 2000\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500;\n}\n\nexport class BanditoHTTP {\n private baseUrl: string;\n private apiKey: string;\n private timeout: number;\n\n constructor(baseUrl: string, apiKey: string, timeout: number = 10_000) {\n this.baseUrl = `${baseUrl.replace(/\\/$/, \"\")}/api/v1`;\n this.apiKey = apiKey;\n this.timeout = timeout;\n }\n\n private async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Record<string, unknown>> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const resp = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: body != null ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => \"\");\n if (!isRetryable(resp.status) || attempt === MAX_RETRIES - 1) {\n throw new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${text}`,\n );\n }\n // Retryable server error — fall through to retry\n lastError = new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${text}`,\n );\n } else {\n return (await resp.json()) as Record<string, unknown>;\n }\n } catch (err) {\n clearTimeout(timer);\n lastError = err as Error;\n\n // AbortError = timeout, TypeError = network error (in fetch)\n const isNetworkOrTimeout =\n (err as Error).name === \"AbortError\" ||\n (err as Error).name === \"TypeError\";\n if (!isNetworkOrTimeout && attempt < MAX_RETRIES - 1) {\n // Non-retryable (4xx already handled above)\n throw err;\n }\n if (attempt === MAX_RETRIES - 1) {\n throw err;\n }\n }\n\n // Exponential backoff\n const delay = RETRY_BACKOFF_BASE * 2 ** attempt;\n await sleep(delay);\n }\n\n throw lastError!;\n }\n\n /** POST /sync/connect — SDK bootstrap. */\n async connect(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/connect\");\n }\n\n /** POST /sync/heartbeat — periodic state refresh. */\n async heartbeat(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/heartbeat\", {});\n }\n\n /** POST /events — batch event ingestion. */\n async ingestEvents(\n events: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/events\", { events });\n }\n\n /** PATCH /events/{uuid}/grade — submit human grade. */\n async submitGrade(\n eventUuid: string,\n grade: number,\n ): Promise<Record<string, unknown>> {\n return this.request(\"PATCH\", `/events/${eventUuid}/grade`, {\n grade,\n is_graded: true,\n });\n }\n}\n","/**\n * SQLite WAL durability layer — crash-safe event storage.\n *\n * Events are written here immediately after update(). Background flush\n * sends them to cloud. If the process crashes, pending events survive\n * and are retried on next connect().\n */\n\nimport Database from \"better-sqlite3\";\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n local_event_uuid TEXT PRIMARY KEY,\n bandit_id INTEGER NOT NULL,\n arm_id INTEGER NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n created_at REAL NOT NULL,\n human_reward REAL,\n graded_at REAL\n);\nCREATE INDEX IF NOT EXISTS idx_events_status ON events(status);\n`;\n\nconst MIGRATION_GRADING = [\n \"ALTER TABLE events ADD COLUMN human_reward REAL\",\n \"ALTER TABLE events ADD COLUMN graded_at REAL\",\n];\n\nexport interface EventPayload {\n local_event_uuid: string;\n bandit_id: number;\n arm_id: number;\n [key: string]: unknown;\n}\n\nexport class EventStore {\n private db: Database.Database;\n private pushStmt: Database.Statement;\n private pendingStmt: Database.Statement;\n\n constructor(dbPath: string = \":memory:\") {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n this.db.pragma(\"busy_timeout = 5000\");\n this.db.pragma(\"synchronous = NORMAL\");\n this.db.exec(SCHEMA);\n this.migrate();\n\n // Pre-compile frequently-used statements\n this.pushStmt = this.db.prepare(\n `INSERT OR IGNORE INTO events\n (local_event_uuid, bandit_id, arm_id, payload, status, created_at)\n VALUES (?, ?, ?, ?, 'pending', ?)`,\n );\n this.pendingStmt = this.db.prepare(\n `SELECT payload FROM events WHERE status = 'pending'\n ORDER BY created_at ASC LIMIT ?`,\n );\n }\n\n /** Insert a pending event. */\n push(event: EventPayload): void {\n this.pushStmt.run(\n event.local_event_uuid,\n event.bandit_id,\n event.arm_id,\n JSON.stringify(event),\n Date.now() / 1000,\n );\n }\n\n /** Return up to `limit` pending events (oldest first). */\n pending(limit: number = 50): EventPayload[] {\n const rows = this.pendingStmt.all(limit) as { payload: string }[];\n return rows.map((row) => JSON.parse(row.payload));\n }\n\n /** Mark events as successfully flushed to cloud. */\n markFlushed(uuids: string[]): void {\n if (uuids.length === 0) return;\n const placeholders = uuids.map(() => \"?\").join(\",\");\n this.db\n .prepare(\n `UPDATE events SET status = 'flushed'\n WHERE local_event_uuid IN (${placeholders})`,\n )\n .run(...uuids);\n }\n\n /** Record a human grade locally. */\n markGraded(uuid: string, reward: number): void {\n this.db\n .prepare(\n `UPDATE events SET human_reward = ?, graded_at = ?\n WHERE local_event_uuid = ?`,\n )\n .run(reward, Date.now() / 1000, uuid);\n }\n\n /** Close the database connection. */\n close(): void {\n this.db.close();\n }\n\n private migrate(): void {\n for (const stmt of MIGRATION_GRADING) {\n try {\n this.db.exec(stmt);\n } catch {\n // Column already exists\n }\n }\n }\n}\n","/**\n * Payload utilities for cloud event ingestion.\n */\n\nconst TEXT_FIELDS = [\"query_text\", \"response\"] as const;\nconst METADATA_FIELDS = [\"model_name\", \"model_provider\"] as const;\n\n/**\n * Return shallow copies of events ready for cloud ingest.\n *\n * Always strips model_name/model_provider (only needed in local SQLite for TUI).\n * Strips query_text/response when includeText is false (dataStorage=\"local\").\n */\nexport function prepareCloudPayload(\n events: Record<string, unknown>[],\n includeText: boolean,\n): Record<string, unknown>[] {\n return events.map((event) => {\n const copy = { ...event };\n for (const field of METADATA_FIELDS) {\n delete copy[field];\n }\n if (!includeText) {\n for (const field of TEXT_FIELDS) {\n delete copy[field];\n }\n }\n return copy;\n });\n}\n","/**\n * Bandito SDK — contextual bandit optimization for LLM selection.\n *\n * Recommended (explicit client):\n * import { BanditoClient } from 'bandito';\n *\n * const client = new BanditoClient({ apiKey: 'bnd_...' });\n * await client.connect();\n * const result = client.pull('my-chatbot', { query: userMessage });\n * // ... call LLM with result.model, result.prompt ...\n * client.update(result, { response: response.text });\n * await client.close();\n *\n * Module-level singleton (convenience):\n * import { connect, pull, update, close } from 'bandito';\n *\n * await connect({ apiKey: 'bnd_...' });\n * const result = pull('my-chatbot', { query: userMessage });\n * update(result, { response: response.text });\n * await close();\n */\n\nexport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nexport { type Arm, type PullResult } from \"./models.js\";\n\nimport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nimport type { PullResult } from \"./models.js\";\n\nlet _client: BanditoClient | null = null;\n\nfunction getClient(): BanditoClient {\n if (!_client) {\n throw new Error(\"Not connected — call connect() first\");\n }\n return _client;\n}\n\n/** Connect to the Bandito cloud and hydrate local state. */\nexport async function connect(options: ClientOptions = {}): Promise<void> {\n if (_client) {\n await _client.close();\n }\n _client = new BanditoClient(options);\n await _client.connect();\n}\n\n/** Local Thompson Sampling decision. <1ms, no network. */\nexport function pull(banditName: string, options?: PullOptions): PullResult {\n return getClient().pull(banditName, options);\n}\n\n/** Record an LLM call outcome (writes to SQLite, fire-and-forget flush). */\nexport function update(pullResult: PullResult, options?: UpdateOptions): void {\n getClient().update(pullResult, options);\n}\n\n/** Send a human grade for an existing event. */\nexport async function grade(eventId: string, gradeValue: number): Promise<void> {\n await getClient().grade(eventId, gradeValue);\n}\n\n/** Explicit state refresh from cloud. */\nexport async function sync(): Promise<void> {\n await getClient().sync();\n}\n\n/** Shut down: flush events, close connections. */\nexport async function close(): Promise<void> {\n if (_client) {\n await _client.close();\n _client = null;\n }\n}\n"],"mappings":";AASA,SAAS,kBAAkB;AAC3B,YAAYA,SAAQ;AACpB,YAAYC,WAAU;AACtB,YAAYC,SAAQ;AACpB,SAAS,mBAAmB;;;ACJ5B,IAAI,aAA6D;AAMjE,eAAsB,WAA0B;AAC9C,MAAI,WAAY;AAChB,eAAa,MAAM,OAAO,wBAAwB;AACpD;AAMO,SAAS,aAAa,YAAsC;AACjE,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,mDAA8C;AAAA,EAChE;AACA,SAAO,IAAI,WAAW,aAAa,UAAU;AAC/C;AAMO,SAAS,aAAa,QAA0B,YAA0B;AAC/E,SAAO,eAAe,UAAU;AAClC;;;ACJO,SAAS,UAAU,MAMlB;AACN,QAAM,MAAW;AAAA,IACf,OAAO,KAAK;AAAA,IACZ,WAAW,KAAK;AAAA,IAChB,eAAe,KAAK;AAAA,IACpB,cAAc,KAAK;AAAA,IACnB,mBAAmB,KAAK,uBAAuB;AAAA,IAC/C,IAAI,QAAQ;AACV,aAAO,KAAK;AAAA,IACd;AAAA,IACA,IAAI,SAAS;AACX,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AACA,SAAO,OAAO,OAAO,GAAG;AAC1B;AAGO,SAAS,iBAAiB,MAOlB;AACb,QAAM,SAAqB;AAAA,IACzB,KAAK,KAAK;AAAA,IACV,SAAS,KAAK;AAAA,IACd,UAAU,KAAK;AAAA,IACf,YAAY,KAAK;AAAA,IACjB,QAAQ,OAAO,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;AAAA,IACxC,IAAI,QAAQ;AACV,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IACA,IAAI,SAAS;AACX,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IACA,WAAW,KAAK;AAAA,EAClB;AACA,SAAO,OAAO,OAAO,MAAM;AAC7B;;;ACzEA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,YAAY,QAAQ;AACpB,SAAS,SAAS,iBAAiB;AAE5B,IAAM,mBAAmB;AAChC,IAAM,aAAkB,UAAQ,WAAQ,GAAG,UAAU;AACrD,IAAM,cAAmB,UAAK,YAAY,aAAa;AAWhD,SAAS,aAA4B;AAC1C,QAAM,SAAwB;AAAA,IAC5B,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,aAAa;AAAA,EACf;AAGA,MAAO,cAAW,WAAW,GAAG;AAC9B,QAAI;AACF,YAAM,UAAa,gBAAa,aAAa,OAAO;AACpD,YAAM,OAAO,UAAU,OAAO;AAC9B,UAAI,KAAK,QAAS,QAAO,SAAS,KAAK;AACvC,UAAI,KAAK,SAAU,QAAO,UAAU,KAAK;AACzC,UAAI,KAAK,aAAc,QAAO,cAAc,KAAK;AAAA,IACnD,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,OAAQ,QAAO,SAAS;AAE5B,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,OAAQ,QAAO,UAAU;AAE7B,QAAM,aAAa,QAAQ,IAAI;AAC/B,MAAI,WAAY,QAAO,cAAc;AAErC,SAAO;AACT;;;ACjDA,IAAM,cAAc;AACpB,IAAM,qBAAqB;AAE3B,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAEA,SAAS,YAAY,QAAyB;AAC5C,SAAO,UAAU;AACnB;AAEO,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAAiB,QAAgB,UAAkB,KAAQ;AACrE,SAAK,UAAU,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC;AAC5C,SAAK,SAAS;AACd,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAc,QACZ,QACAC,OACA,MACkC;AAClC,QAAI,YAA0B;AAE9B,aAAS,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE/D,UAAI;AACF,cAAM,OAAO,MAAM,MAAM,GAAG,KAAK,OAAO,GAAGA,KAAI,IAAI;AAAA,UACjD;AAAA,UACA,SAAS;AAAA,YACP,aAAa,KAAK;AAAA,YAClB,gBAAgB;AAAA,UAClB;AAAA,UACA,MAAM,QAAQ,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,UAC5C,QAAQ,WAAW;AAAA,QACrB,CAAC;AAED,qBAAa,KAAK;AAElB,YAAI,CAAC,KAAK,IAAI;AACZ,gBAAM,OAAO,MAAM,KAAK,KAAK,EAAE,MAAM,MAAM,EAAE;AAC7C,cAAI,CAAC,YAAY,KAAK,MAAM,KAAK,YAAY,cAAc,GAAG;AAC5D,kBAAM,IAAI;AAAA,cACR,QAAQ,KAAK,MAAM,OAAO,MAAM,IAAIA,KAAI,KAAK,IAAI;AAAA,YACnD;AAAA,UACF;AAEA,sBAAY,IAAI;AAAA,YACd,QAAQ,KAAK,MAAM,OAAO,MAAM,IAAIA,KAAI,KAAK,IAAI;AAAA,UACnD;AAAA,QACF,OAAO;AACL,iBAAQ,MAAM,KAAK,KAAK;AAAA,QAC1B;AAAA,MACF,SAAS,KAAK;AACZ,qBAAa,KAAK;AAClB,oBAAY;AAGZ,cAAM,qBACH,IAAc,SAAS,gBACvB,IAAc,SAAS;AAC1B,YAAI,CAAC,sBAAsB,UAAU,cAAc,GAAG;AAEpD,gBAAM;AAAA,QACR;AACA,YAAI,YAAY,cAAc,GAAG;AAC/B,gBAAM;AAAA,QACR;AAAA,MACF;AAGA,YAAM,QAAQ,qBAAqB,KAAK;AACxC,YAAM,MAAM,KAAK;AAAA,IACnB;AAEA,UAAM;AAAA,EACR;AAAA;AAAA,EAGA,MAAM,UAA4C;AAChD,WAAO,KAAK,QAAQ,QAAQ,eAAe;AAAA,EAC7C;AAAA;AAAA,EAGA,MAAM,YAA8C;AAClD,WAAO,KAAK,QAAQ,QAAQ,mBAAmB,CAAC,CAAC;AAAA,EACnD;AAAA;AAAA,EAGA,MAAM,aACJ,QACkC;AAClC,WAAO,KAAK,QAAQ,QAAQ,WAAW,EAAE,OAAO,CAAC;AAAA,EACnD;AAAA;AAAA,EAGA,MAAM,YACJ,WACAC,QACkC;AAClC,WAAO,KAAK,QAAQ,SAAS,WAAW,SAAS,UAAU;AAAA,MACzD,OAAAA;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AACF;;;AC/GA,OAAO,cAAc;AAErB,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcf,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AACF;AASO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAAiB,YAAY;AACvC,SAAK,KAAK,IAAI,SAAS,MAAM;AAC7B,SAAK,GAAG,OAAO,oBAAoB;AACnC,SAAK,GAAG,OAAO,qBAAqB;AACpC,SAAK,GAAG,OAAO,sBAAsB;AACrC,SAAK,GAAG,KAAK,MAAM;AACnB,SAAK,QAAQ;AAGb,SAAK,WAAW,KAAK,GAAG;AAAA,MACtB;AAAA;AAAA;AAAA,IAGF;AACA,SAAK,cAAc,KAAK,GAAG;AAAA,MACzB;AAAA;AAAA,IAEF;AAAA,EACF;AAAA;AAAA,EAGA,KAAK,OAA2B;AAC9B,SAAK,SAAS;AAAA,MACZ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK,UAAU,KAAK;AAAA,MACpB,KAAK,IAAI,IAAI;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAGA,QAAQ,QAAgB,IAAoB;AAC1C,UAAM,OAAO,KAAK,YAAY,IAAI,KAAK;AACvC,WAAO,KAAK,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAI,OAAO,CAAC;AAAA,EAClD;AAAA;AAAA,EAGA,YAAY,OAAuB;AACjC,QAAI,MAAM,WAAW,EAAG;AACxB,UAAM,eAAe,MAAM,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AAClD,SAAK,GACF;AAAA,MACC;AAAA,sCAC8B,YAAY;AAAA,IAC5C,EACC,IAAI,GAAG,KAAK;AAAA,EACjB;AAAA;AAAA,EAGA,WAAW,MAAc,QAAsB;AAC7C,SAAK,GACF;AAAA,MACC;AAAA;AAAA,IAEF,EACC,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAM,IAAI;AAAA,EACxC;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,GAAG,MAAM;AAAA,EAChB;AAAA,EAEQ,UAAgB;AACtB,eAAW,QAAQ,mBAAmB;AACpC,UAAI;AACF,aAAK,GAAG,KAAK,IAAI;AAAA,MACnB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;AC9GA,IAAM,cAAc,CAAC,cAAc,UAAU;AAC7C,IAAM,kBAAkB,CAAC,cAAc,gBAAgB;AAQhD,SAAS,oBACd,QACA,aAC2B;AAC3B,SAAO,OAAO,IAAI,CAAC,UAAU;AAC3B,UAAM,OAAO,EAAE,GAAG,MAAM;AACxB,eAAW,SAAS,iBAAiB;AACnC,aAAO,KAAK,KAAK;AAAA,IACnB;AACA,QAAI,CAAC,aAAa;AAChB,iBAAW,SAAS,aAAa;AAC/B,eAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IACF;AACA,WAAO;AAAA,EACT,CAAC;AACH;;;ANAA,IAAM,qBAA0B,WAAQ,YAAQ,GAAG,YAAY,WAAW;AAC1E,IAAM,oBAAoB;AA0BnB,IAAM,gBAAN,MAAoB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,OAA2B;AAAA,EAC3B,QAA2B;AAAA,EAC3B,UAAqC,oBAAI,IAAI;AAAA,EAC7C,UAAoC,oBAAI,IAAI;AAAA,EAC5C,YAAY;AAAA,EACZ,gBAAuD;AAAA,EACvD,kBAAkB;AAAA,EAClB,YAAyB,oBAAI,IAAI;AAAA,EACjC,cAAmC,oBAAI,IAAI;AAAA,EAEnD,YAAY,UAAyB,CAAC,GAAG;AACvC,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ;AACvB,SAAK,YAAY,QAAQ;AACzB,SAAK,iBAAiB,QAAQ;AAC9B,SAAK,cAAc,QAAQ,eAAe;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAyB;AAE7B,QAAI,KAAK,WAAW;AAClB,YAAM,KAAK,MAAM;AAAA,IACnB;AAGA,UAAM,SAAS;AAGf,UAAM,SAAS,WAAW;AAC1B,UAAM,SAAS,KAAK,UAAU,OAAO;AACrC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,WAAW,OAAO;AACvC,QAAI,CAAC,KAAK,gBAAgB;AACxB,WAAK,cAAc,OAAO;AAAA,IAC5B;AAEA,SAAK,OAAO,IAAI,YAAY,SAAS,MAAM;AAC3C,UAAM,YAAY,KAAK,aAAa;AACpC,QAAI,cAAc,YAAY;AAC5B,YAAM,MAAW,cAAQ,SAAS;AAClC,UAAI,CAAI,eAAW,GAAG,GAAG;AACvB,QAAG,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,MACvC;AAAA,IACF;AACA,SAAK,QAAQ,IAAI,WAAW,SAAS;AAGrC,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,KAAK,QAAQ;AACrC,WAAK,UAAU,IAAI;AAGnB,WAAK,UAAU,MAAM;AACrB,WAAK,YAAY,MAAM;AAGvB,YAAM,KAAK,aAAa;AAGxB,WAAK,gBAAgB,YAAY,MAAM;AACrC,aAAK,aAAa,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MACpC,GAAG,GAAM;AAET,WAAK,YAAY;AAAA,IACnB,SAAS,KAAK;AACZ,WAAK,OAAO,MAAM;AAClB,WAAK,QAAQ;AACb,WAAK,OAAO;AACZ,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,YAAoB,UAAuB,CAAC,GAAe;AAC9D,SAAK,gBAAgB;AAErB,UAAM,QAAQ,KAAK,QAAQ,IAAI,UAAU;AACzC,QAAI,CAAC,OAAO;AACV,YAAM,YAAY,CAAC,GAAG,KAAK,QAAQ,KAAK,CAAC;AACzC,YAAM,IAAI;AAAA,QACR,mBAAmB,UAAU,kBAAkB,UAAU,KAAK,IAAI,CAAC;AAAA,MACrE;AAAA,IACF;AAEA,QAAI,MAAM,KAAK,WAAW,GAAG;AAC3B,YAAM,IAAI,MAAM,WAAW,UAAU,sBAAsB;AAAA,IAC7D;AAEA,UAAM,SAAS,KAAK,QAAQ,IAAI,UAAU;AAC1C,UAAM,cAAc,QAAQ,OAAO,UAAU;AAC7C,UAAM,aAAa,QAAQ,UAAU,WAAW,KAAK,QAAQ,OAAO,IAAI;AAExE,UAAM,aAAa,OAAO,KAAK,aAAa,UAAU;AACtD,UAAM,MAAwB,KAAK,MAAM,UAAU;AAGnD,UAAM,YAAY,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,UAAU,IAAI,MAAM;AAC/D,QAAI,CAAC,WAAW;AACd,YAAM,IAAI;AAAA,QACR,uBAAuB,IAAI,MAAM,0CAA0C,UAAU;AAAA,MAEvF;AAAA,IACF;AAEA,WAAO,iBAAiB;AAAA,MACtB,KAAK;AAAA,MACL,SAAS,WAAW;AAAA,MACpB,UAAU,MAAM;AAAA,MAChB;AAAA,MACA,QAAQ,IAAI;AAAA,MACZ,UAAU,YAAY,IAAI;AAAA,IAC5B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,YAAwB,UAAyB,CAAC,GAAS;AAChE,SAAK,gBAAgB;AAErB,QAAI,SAAS,QAAQ;AACrB,QAAI,QAAQ,UAAU,UAAU,MAAM;AACpC,eAAS;AAAA,IACX;AAGA,QAAI,UAAU,QAAQ;AACtB,QAAI,WAAW,QAAQ,WAAW,YAAY,GAAG;AAC/C,gBAAU,YAAY,IAAI,IAAI,WAAW;AAAA,IAC3C;AAGA,UAAM,QAAsB;AAAA,MAC1B,kBAAkB,WAAW;AAAA,MAC7B,WAAW,WAAW;AAAA,MACtB,QAAQ,WAAW,IAAI;AAAA,MACvB,YAAY,WAAW,IAAI;AAAA,MAC3B,gBAAgB,WAAW,IAAI;AAAA,IACjC;AAEA,QAAI,QAAQ,aAAa,MAAM;AAC7B,MAAC,MAAkC,aAAa,QAAQ;AAAA,IAC1D;AACA,QAAI,QAAQ,YAAY,MAAM;AAC5B,MAAC,MAAkC,WACjC,OAAO,QAAQ,aAAa,WACxB,EAAE,UAAU,QAAQ,SAAS,IAC7B,QAAQ;AAAA,IAChB;AACA,QAAI,UAAU,MAAM;AAClB,MAAC,MAAkC,eAAe;AAAA,IACpD;AACA,QAAI,QAAQ,QAAQ,MAAM;AACxB,MAAC,MAAkC,OAAO,QAAQ;AAAA,IACpD;AACA,QAAI,WAAW,MAAM;AACnB,MAAC,MAAkC,UAAU;AAAA,IAC/C;AACA,QAAI,QAAQ,eAAe,MAAM;AAC/B,MAAC,MAAkC,eAAe,QAAQ;AAAA,IAC5D;AACA,QAAI,QAAQ,gBAAgB,MAAM;AAChC,MAAC,MAAkC,gBAAgB,QAAQ;AAAA,IAC7D;AACA,QAAI,QAAQ,WAAW,MAAM;AAC3B,MAAC,MAAkC,UAAU,QAAQ;AAAA,IACvD;AACA,QAAI,QAAQ,QAAQ;AAClB,MAAC,MAAkC,YAAY;AAAA,IACjD;AAGA,SAAK,MAAO,KAAK,KAAK;AAGtB,SAAK,aAAa,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM,SAAiBC,QAA8B;AACzD,SAAK,gBAAgB;AACrB,UAAM,KAAK,KAAM,YAAY,SAASA,MAAK;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAsB;AAC1B,SAAK,gBAAgB;AACrB,UAAM,OAAO,MAAM,KAAK,KAAM,UAAU;AAExC,UAAM,cAAc,IAAI,IAAI,KAAK,OAAO;AACxC,UAAM,cAAc,IAAI,IAAI,KAAK,OAAO;AACxC,QAAI;AACF,WAAK,UAAU,IAAI;AAAA,IACrB,SAAS,KAAK;AAEZ,WAAK,UAAU;AACf,WAAK,UAAU;AACf,cAAQ,KAAK,0EAAqE,GAAG;AAAA,IACvF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,eAAe;AACtB,oBAAc,KAAK,aAAa;AAChC,WAAK,gBAAgB;AAAA,IACvB;AAGA,QAAI,KAAK,SAAS,KAAK,MAAM;AAC3B,YAAM,KAAK,aAAa;AAAA,IAC1B;AAEA,SAAK,OAAO,MAAM;AAClB,SAAK,QAAQ;AACb,SAAK,OAAO;AACZ,SAAK,QAAQ,MAAM;AACnB,SAAK,QAAQ,MAAM;AACnB,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA,EAIQ,kBAAwB;AAC9B,QAAI,CAAC,KAAK,WAAW;AACnB,YAAM,IAAI,MAAM,2CAAsC;AAAA,IACxD;AAAA,EACF;AAAA,EAEQ,UAAU,MAAqC;AACrD,UAAM,cAAe,KAAK,WAAW,CAAC;AAEtC,UAAM,aAAa,oBAAI,IAAyB;AAChD,UAAM,aAAa,oBAAI,IAA0B;AAEjD,eAAW,KAAK,aAAa;AAC3B,YAAM,OAAQ,EAAE,QAAQ,CAAC;AACzB,UAAI,KAAK,WAAW,EAAG;AAEvB,YAAM,aAAoB,KACvB,OAAO,CAAC,MAAM,EAAE,SAAS,EACzB,IAAI,CAAC,MAAM,UAAU,CAAC,CAAC;AAE1B,YAAM,OAAO,EAAE;AACf,YAAM,QAAqB;AAAA,QACzB,UAAU,OAAO,EAAE,SAAS;AAAA,QAC5B;AAAA,QACA,MAAM;AAAA,QACN,SAAS;AAAA,QACT,kBAAmB,EAAE,qBAAgC;AAAA,QACrD,iBAAiB,EAAE;AAAA,QACnB,QAAQ,EAAE;AAAA,QACV,WAAW,EAAE;AAAA,MACf;AAEA,iBAAW,IAAI,MAAM,KAAK;AAG1B,YAAM,iBAAiB,KAAK,QAAQ,IAAI,IAAI;AAC5C,YAAM,aAAa,KAAK,UAAU,CAAC;AACnC,UAAI,gBAAgB;AAClB,qBAAa,gBAAgB,UAAU;AACvC,mBAAW,IAAI,MAAM,cAAc;AAAA,MACrC,OAAO;AACL,mBAAW,IAAI,MAAM,aAAa,UAAU,CAAC;AAAA,MAC/C;AAAA,IACF;AAEA,SAAK,UAAU;AACf,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,KAAK,mBAAmB,CAAC,KAAK,SAAS,CAAC,KAAK,KAAM;AACvD,SAAK,kBAAkB;AAEvB,QAAI;AACF,YAAM,UAAU,KAAK,MAAM,QAAQ;AACnC,UAAI,QAAQ,WAAW,EAAG;AAG1B,YAAM,QAAQ,KAAK,UAAU,OAAO,IAChC,QAAQ,OAAO,CAAC,MAAM,CAAC,KAAK,UAAU,IAAI,EAAE,gBAAgB,CAAC,IAC7D;AACJ,UAAI,MAAM,WAAW,EAAG;AAGxB,YAAM,UAAU;AAAA,QACd;AAAA,QACA,KAAK,gBAAgB;AAAA,MACvB;AAEA,YAAM,SAAS,MAAM,KAAK,KAAK,aAAa,OAAO;AAGnD,YAAM,SAAU,OAAO,UAAU,CAAC;AAIlC,YAAM,eAAe,IAAI;AAAA,QACvB,OAAO,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,OAAO;AAAA,MACtD;AAGA,iBAAW,OAAO,cAAc;AAC9B,cAAM,SAAS,KAAK,YAAY,IAAI,GAAG,KAAK,KAAK;AACjD,aAAK,YAAY,IAAI,KAAK,KAAK;AAC/B,YAAI,SAAS,mBAAmB;AAC9B,eAAK,UAAU,IAAI,GAAG;AAAA,QACxB;AAAA,MACF;AAGA,YAAM,eAAe,MAClB,IAAI,CAAC,MAAM,EAAE,gBAAgB,EAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,IAAI,GAAG,CAAC;AACzC,UAAI,aAAa,SAAS,KAAK,KAAK,OAAO;AACzC,aAAK,MAAM,YAAY,YAAY;AAAA,MACrC;AAAA,IACF,SAAS,KAAK;AAEZ,cAAQ,KAAK,kDAA6C,GAAG;AAAA,IAC/D,UAAE;AACA,WAAK,kBAAkB;AAAA,IACzB;AAAA,EACF;AACF;;;AO9XA,IAAI,UAAgC;AAEpC,SAAS,YAA2B;AAClC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,2CAAsC;AAAA,EACxD;AACA,SAAO;AACT;AAGA,eAAsB,QAAQ,UAAyB,CAAC,GAAkB;AACxE,MAAI,SAAS;AACX,UAAM,QAAQ,MAAM;AAAA,EACtB;AACA,YAAU,IAAI,cAAc,OAAO;AACnC,QAAM,QAAQ,QAAQ;AACxB;AAGO,SAAS,KAAK,YAAoB,SAAmC;AAC1E,SAAO,UAAU,EAAE,KAAK,YAAY,OAAO;AAC7C;AAGO,SAAS,OAAO,YAAwB,SAA+B;AAC5E,YAAU,EAAE,OAAO,YAAY,OAAO;AACxC;AAGA,eAAsB,MAAM,SAAiB,YAAmC;AAC9E,QAAM,UAAU,EAAE,MAAM,SAAS,UAAU;AAC7C;AAGA,eAAsB,OAAsB;AAC1C,QAAM,UAAU,EAAE,KAAK;AACzB;AAGA,eAAsB,QAAuB;AAC3C,MAAI,SAAS;AACX,UAAM,QAAQ,MAAM;AACpB,cAAU;AAAA,EACZ;AACF;","names":["fs","path","os","path","grade","grade"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/engine.ts","../src/models.ts","../src/config.ts","../src/http.ts","../src/store.ts","../src/worker.ts","../src/index.ts"],"sourcesContent":["/**\n * BanditoClient — main orchestrator for the JS/TS SDK.\n *\n * Mirrors the Python SDK's sync-first design:\n * - pull() is synchronous (WASM math, <1ms)\n * - connect(), grade(), sync(), close() are async (HTTP I/O)\n * - update() is synchronous (SQLite write + fire-and-forget flush)\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { performance } from \"node:perf_hooks\";\n\nimport { initWasm, createEngine, updateEngine, type BanditEngine, type EnginePullResult } from \"./engine.js\";\nimport {\n type Arm,\n type PullResult,\n type BanditCache,\n type ArmWire,\n createArm,\n createPullResult,\n} from \"./models.js\";\nimport { loadConfig, DEFAULT_BASE_URL } from \"./config.js\";\nimport { BanditoHTTP } from \"./http.js\";\nimport { EventStore, type EventPayload } from \"./store.js\";\nimport { prepareCloudPayload } from \"./worker.js\";\n\nconst DEFAULT_STORE_PATH = path.join(os.homedir(), \".bandito\", \"events.db\");\nconst MAX_EVENT_RETRIES = 5;\n\nexport interface ClientOptions {\n apiKey?: string;\n baseUrl?: string;\n storePath?: string;\n dataStorage?: string;\n}\n\nexport interface PullOptions {\n query?: string;\n exclude?: number[];\n}\n\nexport interface UpdateOptions {\n queryText?: string;\n response?: string | Record<string, unknown>;\n reward?: number;\n cost?: number;\n latency?: number;\n inputTokens?: number;\n outputTokens?: number;\n segment?: Record<string, string>;\n failed?: boolean;\n}\n\nexport class BanditoClient {\n private apiKey: string | undefined;\n private baseUrl: string | undefined;\n private storePath: string | undefined;\n private dataStorageArg: string | undefined;\n private dataStorage: string;\n\n private http: BanditoHTTP | null = null;\n private store: EventStore | null = null;\n private engines: Map<string, BanditEngine> = new Map();\n private bandits: Map<string, BanditCache> = new Map();\n private connected = false;\n private flushInterval: ReturnType<typeof setInterval> | null = null;\n private s3FlushInterval: ReturnType<typeof setInterval> | null = null;\n private flushInProgress = false;\n private deadUuids: Set<string> = new Set();\n private retryCounts: Map<string, number> = new Map();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _s3Client: any = null;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private _s3Config: any = null;\n\n constructor(options: ClientOptions = {}) {\n this.apiKey = options.apiKey;\n this.baseUrl = options.baseUrl;\n this.storePath = options.storePath;\n this.dataStorageArg = options.dataStorage;\n this.dataStorage = options.dataStorage ?? \"local\";\n }\n\n /**\n * Bootstrap: authenticate and hydrate in-memory state from cloud.\n *\n * Resolves config from: constructor args → env vars → ~/.bandito/config.toml.\n * Initializes WASM, creates HTTP client, SQLite store, fetches full state.\n */\n async connect(): Promise<void> {\n // Tear down previous connection if reconnecting\n if (this.connected) {\n await this.close();\n }\n\n // Init WASM (loads .wasm binary once)\n await initWasm();\n\n // Resolve config\n const config = loadConfig();\n const apiKey = this.apiKey ?? config.apiKey;\n if (!apiKey) {\n throw new Error(\n \"apiKey required — pass it to constructor, set BANDITO_API_KEY, \" +\n \"or run `bandito signup`\",\n );\n }\n\n const baseUrl = this.baseUrl ?? config.baseUrl;\n if (!this.dataStorageArg) {\n this.dataStorage = config.dataStorage;\n }\n\n this.http = new BanditoHTTP(baseUrl, apiKey);\n const storePath = this.storePath ?? DEFAULT_STORE_PATH;\n if (storePath !== \":memory:\") {\n const dir = path.dirname(storePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n }\n this.store = new EventStore(storePath);\n\n // Init S3 exporter if data_storage is \"s3\"\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this._s3Client = null as any;\n this._s3Config = null;\n if (this.dataStorage === \"s3\" && config.s3) {\n try {\n const { S3Client } = await import(\"@aws-sdk/client-s3\");\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const s3ClientConfig: Record<string, any> = { region: config.s3.region };\n if (config.s3.endpoint) {\n s3ClientConfig.endpoint = config.s3.endpoint;\n s3ClientConfig.forcePathStyle = true; // required for MinIO / path-style S3\n }\n this._s3Client = new S3Client(s3ClientConfig);\n this._s3Config = config.s3;\n } catch {\n console.warn(\"[bandito] data_storage='s3' requires @aws-sdk/client-s3\");\n }\n }\n\n // Bootstrap: fetch state, hydrate cache, flush pending\n try {\n const data = await this.http.connect();\n this.applySync(data);\n\n // Reset retry state\n this.deadUuids.clear();\n this.retryCounts.clear();\n\n // Flush pending events from previous crash\n await this.flushPending();\n\n // Start periodic cloud flush (every 30s)\n this.flushInterval = setInterval(() => {\n this.flushPending().catch((err) =>\n console.warn(\"[bandito] Periodic flush error\", err),\n );\n }, 30_000);\n\n // S3 dumps run on a separate 30s timer, independent of cloud flush\n if (this._s3Client !== null) {\n this.s3FlushInterval = setInterval(() => {\n this.dumpPendingToS3().catch((err) =>\n console.warn(\"[bandito] Periodic S3 export error\", err),\n );\n }, 30_000);\n }\n\n this.connected = true;\n } catch (err) {\n this.store?.close();\n this.store = null;\n this.http = null;\n throw err;\n }\n }\n\n /**\n * Local Thompson Sampling decision. Synchronous, <1ms, no network.\n */\n pull(banditName: string, options: PullOptions = {}): PullResult {\n this.ensureConnected();\n\n const cache = this.bandits.get(banditName);\n if (!cache) {\n const available = [...this.bandits.keys()];\n throw new Error(\n `Unknown bandit '${banditName}'. Available: [${available.join(\", \")}]`,\n );\n }\n\n if (cache.arms.length === 0) {\n throw new Error(`Bandit '${banditName}' has no active arms`);\n }\n\n const engine = this.engines.get(banditName)!;\n const queryLength = options.query?.length ?? undefined;\n const excludeIds = options.exclude ? Int32Array.from(options.exclude) : undefined;\n\n const resultJson = engine.pull(queryLength, excludeIds);\n const raw: EnginePullResult = JSON.parse(resultJson);\n\n // Look up the winning arm from our cached active arms\n const winnerArm = cache.arms.find((a) => a.armId === raw.arm_id);\n if (!winnerArm) {\n throw new Error(\n `Engine selected arm ${raw.arm_id} but it's not in active arm cache for \"${banditName}\". ` +\n \"This is likely a bug — please report it at https://github.com/bandito-ai/bandito/issues\",\n );\n }\n\n return createPullResult({\n arm: winnerArm,\n eventId: randomUUID(),\n banditId: cache.banditId,\n banditName,\n scores: raw.scores,\n pullTime: performance.now(),\n });\n }\n\n /**\n * Record an LLM call outcome. Writes to SQLite first (crash-safe),\n * then fires off a non-blocking flush to cloud.\n */\n update(pullResult: PullResult, options: UpdateOptions = {}): void {\n this.ensureConnected();\n\n let reward = options.reward;\n if (options.failed && reward == null) {\n reward = 0.0;\n }\n\n // Auto-calculate latency from pull timestamp\n let latency = options.latency;\n if (latency == null && pullResult._pullTime > 0) {\n latency = performance.now() - pullResult._pullTime;\n }\n\n // Build event payload (snake_case for wire format)\n const event: EventPayload = {\n local_event_uuid: pullResult.eventId,\n bandit_id: pullResult.banditId,\n arm_id: pullResult.arm.armId,\n model_name: pullResult.arm.modelName,\n model_provider: pullResult.arm.modelProvider,\n bandit_name: pullResult.banditName,\n };\n\n if (options.queryText != null) {\n (event as Record<string, unknown>).query_text = options.queryText;\n }\n if (options.response != null) {\n (event as Record<string, unknown>).response =\n typeof options.response === \"string\"\n ? { response: options.response }\n : options.response;\n }\n if (reward != null) {\n (event as Record<string, unknown>).early_reward = reward;\n }\n if (options.cost != null) {\n (event as Record<string, unknown>).cost = options.cost;\n }\n if (latency != null) {\n (event as Record<string, unknown>).latency = latency;\n }\n if (options.inputTokens != null) {\n (event as Record<string, unknown>).input_tokens = options.inputTokens;\n }\n if (options.outputTokens != null) {\n (event as Record<string, unknown>).output_tokens = options.outputTokens;\n }\n if (options.segment != null) {\n (event as Record<string, unknown>).segment = options.segment;\n }\n if (options.failed) {\n (event as Record<string, unknown>).run_error = true;\n }\n\n // Write to SQLite WAL first — survives crashes\n this.store!.push(event);\n\n // Fire-and-forget flush (errors logged inside flushPending)\n this.flushPending().catch((err) =>\n console.warn(\"[bandito] Flush error\", err),\n );\n }\n\n /**\n * Send a human grade for an existing event. Async (HTTP).\n */\n async grade(eventId: string, grade: number): Promise<void> {\n this.ensureConnected();\n await this.http!.submitGrade(eventId, grade);\n }\n\n /**\n * Explicit state refresh from cloud.\n */\n async sync(): Promise<void> {\n this.ensureConnected();\n const data = await this.http!.heartbeat();\n\n const prevBandits = new Map(this.bandits);\n const prevEngines = new Map(this.engines);\n try {\n this.applySync(data);\n } catch (err) {\n // Rollback on malformed response — keep last-known-good state\n this.bandits = prevBandits;\n this.engines = prevEngines;\n console.warn(\"[bandito] Sync response malformed — keeping last-known-good state\", err);\n }\n }\n\n /**\n * Shut down: clear interval, flush remaining events, close connections.\n */\n async close(): Promise<void> {\n if (this.flushInterval) {\n clearInterval(this.flushInterval);\n this.flushInterval = null;\n }\n\n if (this.s3FlushInterval) {\n clearInterval(this.s3FlushInterval);\n this.s3FlushInterval = null;\n }\n\n // Final cloud flush\n if (this.store && this.http) {\n await this.flushPending();\n }\n\n // Final S3 drain — catches events not yet exported by the periodic timer\n if (this._s3Client !== null && this.store) {\n await this.dumpPendingToS3();\n }\n\n this.store?.close();\n this.store = null;\n this.http = null;\n this.engines.clear();\n this.bandits.clear();\n this.connected = false;\n }\n\n // --- Internal ---\n\n private ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\"Not connected — call connect() first\");\n }\n }\n\n private applySync(data: Record<string, unknown>): void {\n const banditsData = (data.bandits ?? []) as Record<string, unknown>[];\n\n const newBandits = new Map<string, BanditCache>();\n const newEngines = new Map<string, BanditEngine>();\n\n for (const b of banditsData) {\n const arms = (b.arms ?? []) as ArmWire[];\n if (arms.length === 0) continue;\n\n const activeArms: Arm[] = arms\n .filter((a) => a.is_active)\n .map((a) => createArm(a));\n\n const name = b.name as string;\n const cache: BanditCache = {\n banditId: Number(b.bandit_id),\n name,\n arms: activeArms,\n armWire: arms,\n optimizationMode: (b.optimization_mode as string) ?? \"base\",\n avgLatencyLastN: b.avg_latency_last_n as number | null,\n budget: b.budget as number | null,\n totalCost: b.total_cost as number | null,\n };\n\n newBandits.set(name, cache);\n\n // Reuse existing engine (preserves RNG state) or create new one\n const existingEngine = this.engines.get(name);\n const banditJson = JSON.stringify(b);\n if (existingEngine) {\n updateEngine(existingEngine, banditJson);\n newEngines.set(name, existingEngine);\n } else {\n newEngines.set(name, createEngine(banditJson));\n }\n }\n\n this.bandits = newBandits;\n this.engines = newEngines;\n }\n\n private async flushPending(): Promise<void> {\n if (this.flushInProgress || !this.store || !this.http) return;\n this.flushInProgress = true;\n\n try {\n const pending = this.store.pending();\n if (pending.length === 0) return;\n\n // Filter out dead events\n const alive = this.deadUuids.size > 0\n ? pending.filter((e) => !this.deadUuids.has(e.local_event_uuid))\n : pending;\n if (alive.length === 0) return;\n\n // Prepare payload (strip metadata/text as configured)\n const payload = prepareCloudPayload(\n alive as Record<string, unknown>[],\n this.dataStorage !== \"local\",\n );\n\n const result = await this.http.ingestEvents(payload);\n\n // Parse per-event errors\n const errors = (result.errors ?? []) as {\n local_event_uuid?: string;\n reason?: string;\n }[];\n const erroredUuids = new Set(\n errors.map((e) => e.local_event_uuid).filter(Boolean) as string[],\n );\n\n // Update retry counts\n for (const uid of erroredUuids) {\n const count = (this.retryCounts.get(uid) ?? 0) + 1;\n this.retryCounts.set(uid, count);\n if (count >= MAX_EVENT_RETRIES) {\n this.deadUuids.add(uid);\n }\n }\n\n // Mark accepted events as flushed\n const flushedUuids = alive\n .map((e) => e.local_event_uuid)\n .filter((uid) => !erroredUuids.has(uid));\n if (flushedUuids.length > 0 && this.store) {\n this.store.markFlushed(flushedUuids);\n }\n\n } catch (err) {\n // Flush failure is non-fatal — events stay pending for next attempt\n console.warn(\"[bandito] Event flush failed — will retry\", err);\n } finally {\n this.flushInProgress = false;\n }\n }\n\n /**\n * Export un-uploaded events to S3 as raw event JSON. One file per event.\n *\n * Key pattern: {prefix}/{bandit_name}/{YYYY/MM/DD}/{local_event_uuid}.json\n * Body: the raw event object (same structure as SQLite payload column).\n */\n private async dumpPendingToS3(): Promise<void> {\n const eventsWithTs = this.store!.pendingS3(100);\n if (eventsWithTs.length === 0) return;\n\n const { PutObjectCommand } = await import(\"@aws-sdk/client-s3\");\n const uploaded: string[] = [];\n\n try {\n for (const { event, ts } of eventsWithTs) {\n const uuid = event.local_event_uuid as string;\n const rawName = (event.bandit_name as string | undefined) || \"unknown\";\n const banditName = rawName.replace(/[^\\w\\-.]/g, \"_\");\n const date = new Date(ts * 1000);\n const datePath = [\n date.getUTCFullYear(),\n String(date.getUTCMonth() + 1).padStart(2, \"0\"),\n String(date.getUTCDate()).padStart(2, \"0\"),\n ].join(\"/\");\n const key = `${this._s3Config.prefix}/${banditName}/${datePath}/${uuid}.json`;\n\n await this._s3Client.send(new PutObjectCommand({\n Bucket: this._s3Config.bucket,\n Key: key,\n Body: JSON.stringify(event),\n ContentType: \"application/json\",\n }));\n uploaded.push(uuid);\n }\n } catch (err) {\n console.warn(\"[bandito] S3 export failed — will retry\", err);\n } finally {\n if (uploaded.length > 0) {\n this.store!.markS3Exported(uploaded);\n }\n }\n }\n}\n","/**\n * Thin wrapper around the WASM engine import.\n *\n * Handles async WASM initialization (loading .wasm binary happens once)\n * and re-exports the BanditEngine constructor for the client.\n */\n\nimport type { BanditEngine as WasmBanditEngine } from \"../wasm/bandito_engine\";\n\nlet wasmModule: typeof import(\"../wasm/bandito_engine\") | null = null;\n\n/**\n * Initialize the WASM module. Must be called before creating BanditEngine instances.\n * Safe to call multiple times — only loads once.\n */\nexport async function initWasm(): Promise<void> {\n if (wasmModule) return;\n wasmModule = await import(\"../wasm/bandito_engine\");\n}\n\n/**\n * Create a BanditEngine from a sync response JSON string.\n * Seeds the RNG with OS entropy to prevent predictable arm selection.\n * Requires initWasm() to have been called first.\n */\nexport function createEngine(banditJson: string): WasmBanditEngine {\n if (!wasmModule) {\n throw new Error(\"WASM not initialized — call initWasm() first\");\n }\n // Generate 8 bytes of OS entropy and pack into a u64 BigInt seed\n const seedBytes = new Uint8Array(8);\n globalThis.crypto.getRandomValues(seedBytes);\n let seed = 0n;\n for (let i = 0; i < 8; i++) {\n seed |= BigInt(seedBytes[i]) << BigInt(i * 8);\n }\n return wasmModule.BanditEngine.newWithSeed(banditJson, seed);\n}\n\n\n/**\n * Update an existing BanditEngine with new sync response data.\n * Preserves RNG state (avoids the \"always picks same arm\" bug).\n */\nexport function updateEngine(engine: WasmBanditEngine, banditJson: string): void {\n engine.updateFromSync(banditJson);\n}\n\nexport type { WasmBanditEngine as BanditEngine };\n\n/**\n * Pull result parsed from engine JSON output.\n */\nexport interface EnginePullResult {\n arm_index: number;\n arm_id: number;\n scores: Record<number, number>;\n}\n","/**\n * SDK types: Arm, PullResult, and internal cache structures.\n */\n\n/** An arm returned to the user after pull(). Immutable. */\nexport interface Arm {\n readonly armId: number;\n readonly modelName: string;\n readonly modelProvider: string;\n readonly systemPrompt: string;\n readonly isPromptTemplated: boolean;\n /** Convenience alias for modelName. */\n readonly model: string;\n /** Convenience alias for systemPrompt. */\n readonly prompt: string;\n}\n\n/** Returned by pull(), passed to update(). Immutable. */\nexport interface PullResult {\n readonly arm: Arm;\n readonly eventId: string;\n readonly banditId: number;\n readonly banditName: string;\n readonly scores: Readonly<Record<number, number>>;\n /** Convenience reach-through to arm.modelName. */\n readonly model: string;\n /** Convenience reach-through to arm.systemPrompt. */\n readonly prompt: string;\n /** @internal perf timestamp */\n readonly _pullTime: number;\n}\n\n/** Create a frozen Arm from raw wire data. */\nexport function createArm(data: {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated?: boolean;\n}): Arm {\n const arm: Arm = {\n armId: data.arm_id,\n modelName: data.model_name,\n modelProvider: data.model_provider,\n systemPrompt: data.system_prompt,\n isPromptTemplated: data.is_prompt_templated ?? false,\n get model() {\n return this.modelName;\n },\n get prompt() {\n return this.systemPrompt;\n },\n };\n return Object.freeze(arm);\n}\n\n/** Create a frozen PullResult. */\nexport function createPullResult(data: {\n arm: Arm;\n eventId: string;\n banditId: number;\n banditName: string;\n scores: Record<number, number>;\n pullTime: number;\n}): PullResult {\n const result: PullResult = {\n arm: data.arm,\n eventId: data.eventId,\n banditId: data.banditId,\n banditName: data.banditName,\n scores: Object.freeze({ ...data.scores }),\n get model() {\n return this.arm.modelName;\n },\n get prompt() {\n return this.arm.systemPrompt;\n },\n _pullTime: data.pullTime,\n };\n return Object.freeze(result);\n}\n\n/** Raw arm data from sync response (snake_case wire format). */\nexport interface ArmWire {\n arm_id: number;\n model_name: string;\n model_provider: string;\n system_prompt: string;\n is_prompt_templated: boolean;\n is_active: boolean;\n avg_latency_last_n: number | null;\n}\n\n/** Internal mutable cache for a bandit's state. */\nexport interface BanditCache {\n banditId: number;\n name: string;\n arms: Arm[]; // active only\n armWire: ArmWire[]; // all arms (for engine JSON)\n optimizationMode: string;\n avgLatencyLastN: number | null;\n budget: number | null;\n totalCost: number | null;\n}\n","/**\n * Config loader — reads ~/.bandito/config.toml and env vars.\n *\n * Priority order (highest wins): constructor args → env vars → TOML → defaults.\n * Same file as the Python SDK uses.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { parse as parseToml } from \"smol-toml\";\n\nexport const DEFAULT_BASE_URL = \"https://bandito-api.onrender.com\";\nconst CONFIG_DIR = path.join(os.homedir(), \".bandito\");\nconst CONFIG_FILE = path.join(CONFIG_DIR, \"config.toml\");\n\nexport interface S3Config {\n bucket: string;\n prefix: string;\n region: string;\n /** Optional custom endpoint for MinIO / LocalStack / other S3-compatible stores. */\n endpoint?: string;\n}\n\nexport interface JudgeConfig {\n apiKey: string | null;\n model: string;\n}\n\nexport interface BanditoConfig {\n apiKey: string | null;\n baseUrl: string;\n dataStorage: string; // \"local\", \"cloud\", or \"s3\"\n s3: S3Config | null;\n judge: JudgeConfig;\n}\n\n/**\n * Load config from TOML file + env var overrides.\n */\nexport function loadConfig(): BanditoConfig {\n const config: BanditoConfig = {\n apiKey: null,\n baseUrl: DEFAULT_BASE_URL,\n dataStorage: \"local\",\n s3: null,\n judge: { apiKey: null, model: \"gpt-4o-mini\" },\n };\n\n // TOML file first\n if (fs.existsSync(CONFIG_FILE)) {\n try {\n const content = fs.readFileSync(CONFIG_FILE, \"utf-8\");\n const data = parseToml(content) as Record<string, unknown>;\n if (data.api_key) config.apiKey = data.api_key as string;\n if (data.base_url) config.baseUrl = data.base_url as string;\n if (data.data_storage) config.dataStorage = data.data_storage as string;\n const s3Data = data.s3 as Record<string, unknown> | undefined;\n const s3Bucket = ((s3Data?.bucket as string | undefined) ?? \"\").trim();\n if (s3Bucket) {\n const s3Endpoint = ((s3Data?.endpoint as string | undefined) ?? \"\").trim() || undefined;\n config.s3 = {\n bucket: s3Bucket,\n prefix: ((s3Data?.prefix as string | undefined) ?? \"\").trim() || \"bandito\",\n region: ((s3Data?.region as string | undefined) ?? \"\").trim() || \"us-east-1\",\n ...(s3Endpoint ? { endpoint: s3Endpoint } : {}),\n };\n }\n const judgeData = data.judge as Record<string, unknown> | undefined;\n if (judgeData?.api_key) config.judge.apiKey = judgeData.api_key as string;\n if (judgeData?.model) config.judge.model = judgeData.model as string;\n } catch {\n // Failed to parse TOML — fall back to env vars\n }\n }\n\n // Env vars override\n const envKey = process.env.BANDITO_API_KEY;\n if (envKey) config.apiKey = envKey;\n\n const envUrl = process.env.BANDITO_BASE_URL;\n if (envUrl) config.baseUrl = envUrl;\n\n const envStorage = process.env.BANDITO_DATA_STORAGE;\n if (envStorage) config.dataStorage = envStorage;\n\n // S3 env vars (useful for container deployments without a config file)\n const envS3Bucket = (process.env.BANDITO_S3_BUCKET ?? \"\").trim();\n if (envS3Bucket) {\n const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? config.s3?.endpoint ?? \"\").trim() || undefined;\n config.s3 = {\n bucket: envS3Bucket,\n prefix: (process.env.BANDITO_S3_PREFIX ?? config.s3?.prefix ?? \"\").trim() || \"bandito\",\n region: (process.env.BANDITO_S3_REGION ?? config.s3?.region ?? \"\").trim() || \"us-east-1\",\n ...(envEndpoint ? { endpoint: envEndpoint } : {}),\n };\n // Setting BANDITO_S3_BUCKET implicitly activates s3 mode unless\n // the user has explicitly chosen a different mode via BANDITO_DATA_STORAGE.\n if (!process.env.BANDITO_DATA_STORAGE) {\n config.dataStorage = \"s3\";\n }\n } else if (config.s3) {\n const envPrefix = (process.env.BANDITO_S3_PREFIX ?? \"\").trim();\n const envRegion = (process.env.BANDITO_S3_REGION ?? \"\").trim();\n const envEndpoint = (process.env.BANDITO_S3_ENDPOINT ?? \"\").trim();\n if (envPrefix) config.s3.prefix = envPrefix;\n if (envRegion) config.s3.region = envRegion;\n if (envEndpoint) config.s3.endpoint = envEndpoint;\n }\n\n const envJudgeKey = process.env.JUDGE_API_KEY;\n if (envJudgeKey) config.judge.apiKey = envJudgeKey;\n\n return config;\n}\n","/**\n * HTTP transport — fetch-based client for cloud API.\n *\n * Retry config: 3 attempts, exponential backoff (0.5s, 1s, 2s),\n * retries only 5xx and network errors. Never retries 4xx.\n */\n\nconst MAX_RETRIES = 3;\nconst RETRY_BACKOFF_BASE = 500; // ms — 500, 1000, 2000\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction isRetryable(status: number): boolean {\n return status >= 500;\n}\n\nexport class BanditoHTTP {\n private baseUrl: string;\n private apiKey: string;\n private timeout: number;\n\n constructor(baseUrl: string, apiKey: string, timeout: number = 10_000) {\n // Validate URL scheme — reject non-http(s) protocols (file://, javascript:, etc.)\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new Error(`Invalid baseUrl: \"${baseUrl}\"`);\n }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") {\n throw new Error(\n `baseUrl must use http or https, got \"${parsed.protocol}\" — check your config`,\n );\n }\n this.baseUrl = `${baseUrl.replace(/\\/$/, \"\")}/api/v1`;\n this.apiKey = apiKey;\n this.timeout = timeout;\n }\n\n private async request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<Record<string, unknown>> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const resp = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: body != null ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n clearTimeout(timer);\n\n if (!resp.ok) {\n const text = await resp.text().catch(() => \"\");\n // Truncate body to avoid leaking sensitive content into error messages/logs\n const preview = text.length > 200 ? `${text.slice(0, 200)}…` : text;\n if (!isRetryable(resp.status) || attempt === MAX_RETRIES - 1) {\n throw new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${preview}`,\n );\n }\n // Retryable server error — fall through to retry\n lastError = new Error(\n `HTTP ${resp.status} on ${method} ${path}: ${preview}`,\n );\n } else {\n return (await resp.json()) as Record<string, unknown>;\n }\n } catch (err) {\n clearTimeout(timer);\n lastError = err as Error;\n\n // AbortError = timeout, TypeError = network error (in fetch)\n const isNetworkOrTimeout =\n (err as Error).name === \"AbortError\" ||\n (err as Error).name === \"TypeError\";\n if (!isNetworkOrTimeout && attempt < MAX_RETRIES - 1) {\n // Non-retryable (4xx already handled above)\n throw err;\n }\n if (attempt === MAX_RETRIES - 1) {\n throw err;\n }\n }\n\n // Exponential backoff\n const delay = RETRY_BACKOFF_BASE * 2 ** attempt;\n await sleep(delay);\n }\n\n throw lastError!;\n }\n\n /** POST /sync/connect — SDK bootstrap. */\n async connect(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/connect\");\n }\n\n /** POST /sync/heartbeat — periodic state refresh. */\n async heartbeat(): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/sync/heartbeat\", {});\n }\n\n /** POST /events — batch event ingestion. */\n async ingestEvents(\n events: Record<string, unknown>[],\n ): Promise<Record<string, unknown>> {\n return this.request(\"POST\", \"/events\", { events });\n }\n\n /** PATCH /events/{uuid}/grade — submit human grade. */\n async submitGrade(\n eventUuid: string,\n grade: number,\n ): Promise<Record<string, unknown>> {\n return this.request(\"PATCH\", `/events/${eventUuid}/grade`, {\n grade,\n is_graded: true,\n });\n }\n}\n","/**\n * SQLite WAL durability layer — crash-safe event storage.\n *\n * Events are written here immediately after update(). Background flush\n * sends them to cloud. If the process crashes, pending events survive\n * and are retried on next connect().\n */\n\nimport Database from \"better-sqlite3\";\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS events (\n local_event_uuid TEXT PRIMARY KEY,\n bandit_id INTEGER NOT NULL,\n arm_id INTEGER NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n created_at REAL NOT NULL,\n human_reward REAL,\n graded_at REAL,\n s3_exported INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_events_status ON events(status);\n`;\n\nconst MIGRATION_GRADING = [\n \"ALTER TABLE events ADD COLUMN human_reward REAL\",\n \"ALTER TABLE events ADD COLUMN graded_at REAL\",\n \"ALTER TABLE events ADD COLUMN s3_exported INTEGER NOT NULL DEFAULT 0\",\n];\n\nexport interface EventPayload {\n local_event_uuid: string;\n bandit_id: number;\n arm_id: number;\n model_name: string;\n model_provider: string;\n bandit_name?: string;\n [key: string]: unknown;\n}\n\nexport class EventStore {\n private db: Database.Database;\n private pushStmt: Database.Statement;\n private pendingStmt: Database.Statement;\n\n constructor(dbPath: string = \":memory:\") {\n this.db = new Database(dbPath);\n this.db.pragma(\"journal_mode = WAL\");\n this.db.pragma(\"busy_timeout = 5000\");\n this.db.pragma(\"synchronous = NORMAL\");\n this.db.exec(SCHEMA);\n this.migrate();\n\n // Pre-compile frequently-used statements\n this.pushStmt = this.db.prepare(\n `INSERT OR IGNORE INTO events\n (local_event_uuid, bandit_id, arm_id, payload, status, created_at)\n VALUES (?, ?, ?, ?, 'pending', ?)`,\n );\n this.pendingStmt = this.db.prepare(\n `SELECT local_event_uuid, payload FROM events WHERE status = 'pending'\n ORDER BY created_at ASC LIMIT ?`,\n );\n }\n\n /** Insert a pending event. */\n push(event: EventPayload): void {\n this.pushStmt.run(\n event.local_event_uuid,\n event.bandit_id,\n event.arm_id,\n JSON.stringify(event),\n Date.now() / 1000,\n );\n }\n\n /** Return up to `limit` pending events (oldest first). */\n pending(limit: number = 50): EventPayload[] {\n const rows = this.pendingStmt.all(limit) as {\n local_event_uuid: string;\n payload: string;\n }[];\n const results: EventPayload[] = [];\n const corrupt: string[] = [];\n for (const row of rows) {\n try {\n results.push(JSON.parse(row.payload) as EventPayload);\n } catch {\n console.warn(\n `[bandito] Discarding corrupt event ${row.local_event_uuid} from store`,\n );\n corrupt.push(row.local_event_uuid);\n }\n }\n if (corrupt.length > 0) {\n this.markFlushed(corrupt);\n }\n return results;\n }\n\n /** Mark events as successfully flushed to cloud. */\n markFlushed(uuids: string[]): void {\n if (uuids.length === 0) return;\n const placeholders = uuids.map(() => \"?\").join(\",\");\n this.db\n .prepare(\n `UPDATE events SET status = 'flushed'\n WHERE local_event_uuid IN (${placeholders})`,\n )\n .run(...uuids);\n }\n\n /** Record a human grade locally. */\n markGraded(uuid: string, reward: number): void {\n this.db\n .prepare(\n `UPDATE events SET human_reward = ?, graded_at = ?\n WHERE local_event_uuid = ?`,\n )\n .run(reward, Date.now() / 1000, uuid);\n }\n\n /** Return un-exported events as {event, ts} for S3 dump. */\n pendingS3(limit: number = 100): Array<{ event: Record<string, unknown>; ts: number }> {\n const rows = this.db\n .prepare(\n `SELECT payload, created_at FROM events WHERE s3_exported = 0\n ORDER BY created_at ASC LIMIT ?`,\n )\n .all(limit) as { payload: string; created_at: number }[];\n return rows.map((r) => ({ event: JSON.parse(r.payload) as Record<string, unknown>, ts: r.created_at }));\n }\n\n /** Mark events as successfully exported to S3. */\n markS3Exported(uuids: string[]): void {\n if (uuids.length === 0) return;\n const placeholders = uuids.map(() => \"?\").join(\",\");\n this.db\n .prepare(`UPDATE events SET s3_exported = 1 WHERE local_event_uuid IN (${placeholders})`)\n .run(...uuids);\n }\n\n /** Close the database connection. */\n close(): void {\n this.db.close();\n }\n\n private migrate(): void {\n for (const stmt of MIGRATION_GRADING) {\n try {\n this.db.exec(stmt);\n } catch {\n // Column already exists\n }\n }\n }\n}\n","/**\n * Payload utilities for cloud event ingestion.\n */\n\nconst TEXT_FIELDS = [\"query_text\", \"response\"] as const;\nconst METADATA_FIELDS = [\"model_name\", \"model_provider\"] as const;\n\n/**\n * Return shallow copies of events ready for cloud ingest.\n *\n * Always strips model_name/model_provider (only needed in local SQLite for TUI).\n * Strips query_text/response when includeText is false (dataStorage=\"local\").\n */\nexport function prepareCloudPayload(\n events: Record<string, unknown>[],\n includeText: boolean,\n): Record<string, unknown>[] {\n return events.map((event) => {\n const copy = { ...event };\n for (const field of METADATA_FIELDS) {\n delete copy[field];\n }\n if (!includeText) {\n for (const field of TEXT_FIELDS) {\n delete copy[field];\n }\n }\n return copy;\n });\n}\n","/**\n * Bandito SDK — contextual bandit optimization for LLM selection.\n *\n * Recommended (explicit client):\n * import { BanditoClient } from 'bandito';\n *\n * const client = new BanditoClient({ apiKey: 'bnd_...' });\n * await client.connect();\n * const result = client.pull('my-chatbot', { query: userMessage });\n * // ... call LLM with result.model, result.prompt ...\n * client.update(result, { response: response.text });\n * await client.close();\n *\n * Module-level singleton (convenience):\n * import { connect, pull, update, close } from 'bandito';\n *\n * await connect({ apiKey: 'bnd_...' });\n * const result = pull('my-chatbot', { query: userMessage });\n * update(result, { response: response.text });\n * await close();\n */\n\nexport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nexport { type Arm, type PullResult } from \"./models.js\";\n\nimport { BanditoClient, type ClientOptions, type PullOptions, type UpdateOptions } from \"./client.js\";\nimport type { PullResult } from \"./models.js\";\n\nlet _client: BanditoClient | null = null;\n\nfunction getClient(): BanditoClient {\n if (!_client) {\n throw new Error(\"Not connected — call connect() first\");\n }\n return _client;\n}\n\n/** Connect to the Bandito cloud and hydrate local state. */\nexport async function connect(options: ClientOptions = {}): Promise<void> {\n if (_client) {\n await _client.close();\n }\n _client = new BanditoClient(options);\n await _client.connect();\n}\n\n/** Local Thompson Sampling decision. <1ms, no network. */\nexport function pull(banditName: string, options?: PullOptions): PullResult {\n return getClient().pull(banditName, options);\n}\n\n/** Record an LLM call outcome (writes to SQLite, fire-and-forget flush). */\nexport function update(pullResult: PullResult, options?: UpdateOptions): void {\n getClient().update(pullResult, options);\n}\n\n/** Send a human grade for an existing event. */\nexport async function grade(eventId: string, gradeValue: number): Promise<void> {\n await getClient().grade(eventId, gradeValue);\n}\n\n/** Explicit state refresh from cloud. */\nexport async function sync(): Promise<void> {\n await getClient().sync();\n}\n\n/** Shut down: flush events, close connections. */\nexport async function close(): Promise<void> {\n if (_client) {\n await _client.close();\n _client = null;\n }\n}\n"],"mappings":"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","names":["fs","path","os","path","grade","grade"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bandito-ai/sdk",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Contextual bandit optimizer for LLM model and prompt selection",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -21,12 +21,36 @@
21
21
  "smol-toml": "^1.3.0"
22
22
  },
23
23
  "devDependencies": {
24
+ "@aws-sdk/client-s3": "^3.0.0",
25
+ "@opentelemetry/api": "^1.9.0",
26
+ "@opentelemetry/exporter-trace-otlp-http": "^0.53.0",
27
+ "@opentelemetry/sdk-trace-node": "^1.26.0",
24
28
  "@types/better-sqlite3": "^7.6.0",
25
29
  "msw": "^2.0.0",
26
30
  "tsup": "^8.0.0",
27
31
  "typescript": "^5.7.0",
28
32
  "vitest": "^3.0.0"
29
33
  },
34
+ "peerDependencies": {
35
+ "@aws-sdk/client-s3": "^3.0.0",
36
+ "@opentelemetry/api": ">=1.9.0",
37
+ "@opentelemetry/exporter-trace-otlp-http": ">=0.53.0",
38
+ "@opentelemetry/sdk-trace-node": ">=1.26.0"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "@aws-sdk/client-s3": {
42
+ "optional": true
43
+ },
44
+ "@opentelemetry/api": {
45
+ "optional": true
46
+ },
47
+ "@opentelemetry/exporter-trace-otlp-http": {
48
+ "optional": true
49
+ },
50
+ "@opentelemetry/sdk-trace-node": {
51
+ "optional": true
52
+ }
53
+ },
30
54
  "engines": {
31
55
  "node": ">=18.0.0"
32
56
  },
@@ -21,6 +21,7 @@ export class BanditEngine {
21
21
  static newWithSeed(bandit_json: string, seed?: bigint | null): BanditEngine;
22
22
  /**
23
23
  * Pure math pull — returns JSON { arm_index, arm_id, scores }.
24
+ * exclude_ids uses i32 (JS number) at the WASM boundary; cast to i64 internally.
24
25
  */
25
26
  pull(query_length?: number | null, exclude_ids?: Int32Array | null): string;
26
27
  /**
@@ -109,6 +109,7 @@ class BanditEngine {
109
109
  }
110
110
  /**
111
111
  * Pure math pull — returns JSON { arm_index, arm_id, scores }.
112
+ * exclude_ids uses i32 (JS number) at the WASM boundary; cast to i64 internally.
112
113
  * @param {number | null} [query_length]
113
114
  * @param {Int32Array | null} [exclude_ids]
114
115
  * @returns {string}
Binary file