@dotbots-boutique/auth-sdk 1.0.16 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -190,6 +190,81 @@ Possible `PAYMENT_FAILED` messages: `INSUFFICIENT_BALANCE`, `BUDGET_EXCEEDED`, `
190
190
 
191
191
  ---
192
192
 
193
+ ## AI Gateway
194
+
195
+ The SDK provides access to the platform AI gateway. The developer never needs to know which provider or model is used — the platform handles routing, billing and rate limiting.
196
+
197
+ ### Non-streaming
198
+
199
+ ```typescript
200
+ import type { AiResponse } from '@dotbots-boutique/auth-sdk';
201
+
202
+ const response: AiResponse = await auth.ai('generate-text', {
203
+ messages: [{ role: 'user', content: 'Summarise this document' }],
204
+ maxTokens: 1000,
205
+ });
206
+
207
+ console.log(response.content);
208
+ console.log(response.usage.totalTokens);
209
+ console.log(response.cost.eur);
210
+ ```
211
+
212
+ ### Streaming
213
+
214
+ ```typescript
215
+ let fullText = '';
216
+
217
+ await auth.aiStream('generate-text', {
218
+ messages: [{ role: 'user', content: 'Write a poem' }],
219
+ }, (delta) => {
220
+ fullText += delta;
221
+ updateUI(fullText);
222
+ }, (response) => {
223
+ console.log('Done — model:', response.model, 'tokens:', response.usage.totalTokens);
224
+ });
225
+ ```
226
+
227
+ ### Tool calling
228
+
229
+ ```typescript
230
+ const response = await auth.ai('agent', {
231
+ messages: [{ role: 'user', content: 'What is the weather in Brussels?' }],
232
+ tools: [{
233
+ name: 'get_weather',
234
+ description: 'Get current weather for a city',
235
+ parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
236
+ }],
237
+ });
238
+
239
+ if (response.toolCalls?.length) {
240
+ console.log(response.toolCalls[0].name, response.toolCalls[0].input);
241
+ }
242
+ ```
243
+
244
+ ### Error handling
245
+
246
+ ```typescript
247
+ try {
248
+ const response = await auth.ai('generate-text', { messages });
249
+ } catch (error) {
250
+ if (error instanceof DotBotsAuthError) {
251
+ switch (error.code) {
252
+ case 'AI_INSUFFICIENT_BALANCE':
253
+ // show top-up prompt
254
+ break;
255
+ case 'AI_FEATURE_NOT_FOUND':
256
+ // feature not configured
257
+ break;
258
+ case 'AI_CALL_FAILED':
259
+ // provider error
260
+ break;
261
+ }
262
+ }
263
+ }
264
+ ```
265
+
266
+ ---
267
+
193
268
  ## Checking permissions
194
269
 
195
270
  ```typescript
@@ -262,6 +337,11 @@ try {
262
337
  | `NOT_INITIALIZED` | `initialize()` has not been called yet | Yes |
263
338
  | `PROXY_UNAVAILABLE` | Proxy config could not be fetched | No — falls back to direct API |
264
339
  | `PAYMENT_FAILED` | Payment was rejected (402) — see `message` for reason | No |
340
+ | `AI_FEATURE_NOT_FOUND` | AI feature code not configured in platform | No |
341
+ | `AI_PROVIDER_NOT_CONFIGURED` | API key not set for AI provider | No |
342
+ | `AI_INSUFFICIENT_BALANCE` | Organisation balance is 0 | No |
343
+ | `AI_CALL_FAILED` | AI provider returned an error | No |
344
+ | `AI_STREAM_ERROR` | Streaming connection failed | No |
265
345
 
266
346
  ---
267
347
 
@@ -342,6 +422,14 @@ Authenticated fetch wrapper. Routes through the proxy when available, falls back
342
422
 
343
423
  Charges a feature usage. Calls `POST {proxyUrl}/payments/charge`. On success, sends a `DOTBOTS_CHARGE` postMessage to the parent window (iframe only) and emits the `charged` event. Throws `PAYMENT_FAILED` on 402 with a message indicating the reason (`INSUFFICIENT_BALANCE`, `BUDGET_EXCEEDED`, `FEATURE_NOT_FOUND`, `PAYMENT_REJECTED`).
344
424
 
425
+ #### `ai(feature: string, request: AiRequest): Promise<AiResponse>`
426
+
427
+ Non-streaming AI call via the platform AI gateway. Calls `POST {proxyUrl}/ai/call`. Returns the full response with content, model, usage and cost.
428
+
429
+ #### `aiStream(feature: string, request: AiRequest, onDelta: (delta: string) => void, onDone?: (response: AiResponse) => void): Promise<void>`
430
+
431
+ Streaming AI call. `onDelta` fires for each text chunk, `onDone` fires with usage/cost metadata when the stream completes.
432
+
345
433
  #### `logout(): Promise<void>`
346
434
 
347
435
  Logs out the user. Revokes tokens on `apiUrl`, clears state, and redirects (standalone) or notifies the parent (iframe).
@@ -367,6 +455,8 @@ const {
367
455
  hasRole, // (role: string) => boolean
368
456
  fetch, // auth.fetch proxied
369
457
  charge, // auth.charge proxied
458
+ ai, // auth.ai proxied
459
+ aiStream, // auth.aiStream proxied
370
460
  logout, // auth.logout proxied
371
461
  } = useDotBotsAuth();
372
462
  ```
@@ -462,7 +552,7 @@ To report a security vulnerability, please email [security@dotbots.ai](mailto:se
462
552
 
463
553
  ```typescript
464
554
  import { DotBotsAuth, DotBotsAuthError } from '@dotbots-boutique/auth-sdk';
465
- import type { DotBotsUser, DotBotsConfig, DotBotsAuthEvent, DotBotsProxyConfig } from '@dotbots-boutique/auth-sdk';
555
+ import type { DotBotsUser, DotBotsConfig, DotBotsAuthEvent, DotBotsProxyConfig, AiRequest, AiResponse, AiMessage, AiTool, AiToolCall } from '@dotbots-boutique/auth-sdk';
466
556
  import { DotBotsAuthProvider, useDotBotsAuth } from '@dotbots-boutique/auth-sdk/react';
467
557
  ```
468
558
 
package/dist/cjs/index.js CHANGED
@@ -297,8 +297,8 @@ class DotBotsAuth {
297
297
  this.cachedUser = null;
298
298
  this.tokenManager.clear();
299
299
  await this.tokenManager.exchangeCode(event.data.code);
300
- await this.getUser();
301
- this.emit('userChanged');
300
+ const newUser = await this.getUser();
301
+ this.emit('userChanged', newUser);
302
302
  });
303
303
  }
304
304
  async getUser() {
@@ -382,6 +382,100 @@ class DotBotsAuth {
382
382
  this.emit('charged');
383
383
  return data;
384
384
  }
385
+ async ai(feature, request) {
386
+ this.assertInitialized();
387
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
388
+ console.warn(`[DotBotsAuth] ai() — feature: ${feature}, messages: ${request.messages.length}`);
389
+ const response = await this.buildRequest(`${baseUrl}/ai/call`, {
390
+ method: 'POST',
391
+ headers: { 'Content-Type': 'application/json' },
392
+ body: JSON.stringify({
393
+ feature,
394
+ messages: request.messages,
395
+ tools: request.tools,
396
+ stream: false,
397
+ maxTokens: request.maxTokens,
398
+ }),
399
+ });
400
+ if (response.status === 402) {
401
+ console.error('[DotBotsAuth] ai() failed — 402: insufficient balance');
402
+ throw new DotBotsAuthError('AI_INSUFFICIENT_BALANCE', 'Insufficient balance for AI call');
403
+ }
404
+ if (response.status === 400) {
405
+ const body = await response.json();
406
+ console.error(`[DotBotsAuth] ai() failed — 400: ${body.error}`);
407
+ throw new DotBotsAuthError(body.error || 'AI_FEATURE_NOT_FOUND', body.message ?? 'AI request failed');
408
+ }
409
+ if (!response.ok) {
410
+ console.error(`[DotBotsAuth] ai() failed — HTTP ${response.status}`);
411
+ throw new DotBotsAuthError('AI_CALL_FAILED', `AI call failed: ${response.status}`);
412
+ }
413
+ const data = await response.json();
414
+ console.warn(`[DotBotsAuth] ai() success — model: ${data.model}, tokens: ${data.usage.totalTokens}, transactionId: ${data.transactionId}`);
415
+ return data;
416
+ }
417
+ async aiStream(feature, request, onDelta, onDone) {
418
+ this.assertInitialized();
419
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
420
+ console.warn(`[DotBotsAuth] aiStream() — feature: ${feature}, messages: ${request.messages.length}`);
421
+ const response = await this.buildRequest(`${baseUrl}/ai/call`, {
422
+ method: 'POST',
423
+ headers: { 'Content-Type': 'application/json' },
424
+ body: JSON.stringify({
425
+ feature,
426
+ messages: request.messages,
427
+ tools: request.tools,
428
+ stream: true,
429
+ maxTokens: request.maxTokens,
430
+ }),
431
+ });
432
+ if (response.status === 402) {
433
+ console.error('[DotBotsAuth] aiStream() failed — 402: insufficient balance');
434
+ throw new DotBotsAuthError('AI_INSUFFICIENT_BALANCE', 'Insufficient balance for AI call');
435
+ }
436
+ if (!response.ok) {
437
+ console.error(`[DotBotsAuth] aiStream() failed — HTTP ${response.status}`);
438
+ throw new DotBotsAuthError('AI_CALL_FAILED', `AI stream failed: ${response.status}`);
439
+ }
440
+ const reader = response.body.getReader();
441
+ const decoder = new TextDecoder();
442
+ let buffer = '';
443
+ try {
444
+ while (true) {
445
+ const { done, value } = await reader.read();
446
+ if (done)
447
+ break;
448
+ buffer += decoder.decode(value, { stream: true });
449
+ const lines = buffer.split('\n');
450
+ buffer = lines.pop() ?? '';
451
+ for (const line of lines) {
452
+ if (!line.startsWith('data: '))
453
+ continue;
454
+ const data = JSON.parse(line.slice(6));
455
+ if (data.type === 'text') {
456
+ onDelta(data.delta);
457
+ }
458
+ else if (data.type === 'done') {
459
+ const finalResponse = {
460
+ content: '',
461
+ model: data.model,
462
+ provider: data.provider,
463
+ usage: data.usage,
464
+ cost: data.cost,
465
+ transactionId: data.transactionId,
466
+ toolCalls: data.toolCalls,
467
+ };
468
+ console.warn(`[DotBotsAuth] aiStream() done — model: ${data.model}, tokens: ${data.usage.totalTokens}, transactionId: ${data.transactionId}`);
469
+ onDone?.(finalResponse);
470
+ }
471
+ }
472
+ }
473
+ }
474
+ catch (err) {
475
+ console.error('[DotBotsAuth] aiStream() error:', err instanceof Error ? err.message : err);
476
+ throw new DotBotsAuthError('AI_STREAM_ERROR', 'Streaming connection failed', err instanceof Error ? err : undefined);
477
+ }
478
+ }
385
479
  async logout() {
386
480
  this.assertInitialized();
387
481
  await this.tokenManager.revoke();
@@ -458,12 +552,12 @@ class DotBotsAuth {
458
552
  throw new DotBotsAuthError('NOT_INITIALIZED', 'Call initialize() before using the SDK');
459
553
  }
460
554
  }
461
- emit(event) {
555
+ emit(event, data) {
462
556
  const handlers = this.listeners.get(event);
463
557
  if (handlers) {
464
558
  for (const handler of handlers) {
465
559
  try {
466
- handler();
560
+ handler(data);
467
561
  }
468
562
  catch {
469
563
  // Don't let listener errors break the SDK
@@ -472,7 +566,7 @@ class DotBotsAuth {
472
566
  }
473
567
  }
474
568
  }
475
- DotBotsAuth.SDK_VERSION = '1.0.16';
569
+ DotBotsAuth.SDK_VERSION = '1.0.18';
476
570
 
477
571
  exports.DotBotsAuth = DotBotsAuth;
478
572
  exports.DotBotsAuthError = DotBotsAuthError;
@@ -31,6 +31,15 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
31
31
  cancelled = true;
32
32
  };
33
33
  }, [auth]);
34
+ react.useEffect(() => {
35
+ const handler = (newUser) => {
36
+ setUser(newUser);
37
+ };
38
+ auth.on('userChanged', handler);
39
+ return () => {
40
+ auth.off('userChanged', handler);
41
+ };
42
+ }, [auth]);
34
43
  if (isLoading) {
35
44
  return jsxRuntime.jsx(jsxRuntime.Fragment, { children: loadingComponent ?? jsxRuntime.jsx("div", { children: "Loading..." }) });
36
45
  }
@@ -50,6 +59,8 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
50
59
  hasRole: (role) => auth.hasRole(role),
51
60
  fetch: (url, options) => auth.fetch(url, options),
52
61
  charge: (featureCode, paidBy, quantity) => auth.charge(featureCode, paidBy, quantity),
62
+ ai: (feature, request) => auth.ai(feature, request),
63
+ aiStream: (feature, request, onDelta, onDone) => auth.aiStream(feature, request, onDelta, onDone),
53
64
  logout: () => auth.logout(),
54
65
  };
55
66
  return (jsxRuntime.jsx(DotBotsAuthContext.Provider, { value: value, children: children }));
package/dist/esm/index.js CHANGED
@@ -295,8 +295,8 @@ class DotBotsAuth {
295
295
  this.cachedUser = null;
296
296
  this.tokenManager.clear();
297
297
  await this.tokenManager.exchangeCode(event.data.code);
298
- await this.getUser();
299
- this.emit('userChanged');
298
+ const newUser = await this.getUser();
299
+ this.emit('userChanged', newUser);
300
300
  });
301
301
  }
302
302
  async getUser() {
@@ -380,6 +380,100 @@ class DotBotsAuth {
380
380
  this.emit('charged');
381
381
  return data;
382
382
  }
383
+ async ai(feature, request) {
384
+ this.assertInitialized();
385
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
386
+ console.warn(`[DotBotsAuth] ai() — feature: ${feature}, messages: ${request.messages.length}`);
387
+ const response = await this.buildRequest(`${baseUrl}/ai/call`, {
388
+ method: 'POST',
389
+ headers: { 'Content-Type': 'application/json' },
390
+ body: JSON.stringify({
391
+ feature,
392
+ messages: request.messages,
393
+ tools: request.tools,
394
+ stream: false,
395
+ maxTokens: request.maxTokens,
396
+ }),
397
+ });
398
+ if (response.status === 402) {
399
+ console.error('[DotBotsAuth] ai() failed — 402: insufficient balance');
400
+ throw new DotBotsAuthError('AI_INSUFFICIENT_BALANCE', 'Insufficient balance for AI call');
401
+ }
402
+ if (response.status === 400) {
403
+ const body = await response.json();
404
+ console.error(`[DotBotsAuth] ai() failed — 400: ${body.error}`);
405
+ throw new DotBotsAuthError(body.error || 'AI_FEATURE_NOT_FOUND', body.message ?? 'AI request failed');
406
+ }
407
+ if (!response.ok) {
408
+ console.error(`[DotBotsAuth] ai() failed — HTTP ${response.status}`);
409
+ throw new DotBotsAuthError('AI_CALL_FAILED', `AI call failed: ${response.status}`);
410
+ }
411
+ const data = await response.json();
412
+ console.warn(`[DotBotsAuth] ai() success — model: ${data.model}, tokens: ${data.usage.totalTokens}, transactionId: ${data.transactionId}`);
413
+ return data;
414
+ }
415
+ async aiStream(feature, request, onDelta, onDone) {
416
+ this.assertInitialized();
417
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
418
+ console.warn(`[DotBotsAuth] aiStream() — feature: ${feature}, messages: ${request.messages.length}`);
419
+ const response = await this.buildRequest(`${baseUrl}/ai/call`, {
420
+ method: 'POST',
421
+ headers: { 'Content-Type': 'application/json' },
422
+ body: JSON.stringify({
423
+ feature,
424
+ messages: request.messages,
425
+ tools: request.tools,
426
+ stream: true,
427
+ maxTokens: request.maxTokens,
428
+ }),
429
+ });
430
+ if (response.status === 402) {
431
+ console.error('[DotBotsAuth] aiStream() failed — 402: insufficient balance');
432
+ throw new DotBotsAuthError('AI_INSUFFICIENT_BALANCE', 'Insufficient balance for AI call');
433
+ }
434
+ if (!response.ok) {
435
+ console.error(`[DotBotsAuth] aiStream() failed — HTTP ${response.status}`);
436
+ throw new DotBotsAuthError('AI_CALL_FAILED', `AI stream failed: ${response.status}`);
437
+ }
438
+ const reader = response.body.getReader();
439
+ const decoder = new TextDecoder();
440
+ let buffer = '';
441
+ try {
442
+ while (true) {
443
+ const { done, value } = await reader.read();
444
+ if (done)
445
+ break;
446
+ buffer += decoder.decode(value, { stream: true });
447
+ const lines = buffer.split('\n');
448
+ buffer = lines.pop() ?? '';
449
+ for (const line of lines) {
450
+ if (!line.startsWith('data: '))
451
+ continue;
452
+ const data = JSON.parse(line.slice(6));
453
+ if (data.type === 'text') {
454
+ onDelta(data.delta);
455
+ }
456
+ else if (data.type === 'done') {
457
+ const finalResponse = {
458
+ content: '',
459
+ model: data.model,
460
+ provider: data.provider,
461
+ usage: data.usage,
462
+ cost: data.cost,
463
+ transactionId: data.transactionId,
464
+ toolCalls: data.toolCalls,
465
+ };
466
+ console.warn(`[DotBotsAuth] aiStream() done — model: ${data.model}, tokens: ${data.usage.totalTokens}, transactionId: ${data.transactionId}`);
467
+ onDone?.(finalResponse);
468
+ }
469
+ }
470
+ }
471
+ }
472
+ catch (err) {
473
+ console.error('[DotBotsAuth] aiStream() error:', err instanceof Error ? err.message : err);
474
+ throw new DotBotsAuthError('AI_STREAM_ERROR', 'Streaming connection failed', err instanceof Error ? err : undefined);
475
+ }
476
+ }
383
477
  async logout() {
384
478
  this.assertInitialized();
385
479
  await this.tokenManager.revoke();
@@ -456,12 +550,12 @@ class DotBotsAuth {
456
550
  throw new DotBotsAuthError('NOT_INITIALIZED', 'Call initialize() before using the SDK');
457
551
  }
458
552
  }
459
- emit(event) {
553
+ emit(event, data) {
460
554
  const handlers = this.listeners.get(event);
461
555
  if (handlers) {
462
556
  for (const handler of handlers) {
463
557
  try {
464
- handler();
558
+ handler(data);
465
559
  }
466
560
  catch {
467
561
  // Don't let listener errors break the SDK
@@ -470,6 +564,6 @@ class DotBotsAuth {
470
564
  }
471
565
  }
472
566
  }
473
- DotBotsAuth.SDK_VERSION = '1.0.16';
567
+ DotBotsAuth.SDK_VERSION = '1.0.18';
474
568
 
475
569
  export { DotBotsAuth, DotBotsAuthError };
@@ -29,6 +29,15 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
29
29
  cancelled = true;
30
30
  };
31
31
  }, [auth]);
32
+ useEffect(() => {
33
+ const handler = (newUser) => {
34
+ setUser(newUser);
35
+ };
36
+ auth.on('userChanged', handler);
37
+ return () => {
38
+ auth.off('userChanged', handler);
39
+ };
40
+ }, [auth]);
32
41
  if (isLoading) {
33
42
  return jsx(Fragment, { children: loadingComponent ?? jsx("div", { children: "Loading..." }) });
34
43
  }
@@ -48,6 +57,8 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
48
57
  hasRole: (role) => auth.hasRole(role),
49
58
  fetch: (url, options) => auth.fetch(url, options),
50
59
  charge: (featureCode, paidBy, quantity) => auth.charge(featureCode, paidBy, quantity),
60
+ ai: (feature, request) => auth.ai(feature, request),
61
+ aiStream: (feature, request, onDelta, onDone) => auth.aiStream(feature, request, onDelta, onDone),
51
62
  logout: () => auth.logout(),
52
63
  };
53
64
  return (jsx(DotBotsAuthContext.Provider, { value: value, children: children }));
@@ -1,4 +1,4 @@
1
- import type { DotBotsConfig, DotBotsUser, DotBotsAuthEvent, DotBotsProxyConfig } from './types.js';
1
+ import type { DotBotsConfig, DotBotsUser, DotBotsAuthEvent, DotBotsProxyConfig, AiRequest, AiResponse } from './types.js';
2
2
  export declare class DotBotsAuth {
3
3
  private readonly config;
4
4
  private readonly environment;
@@ -10,7 +10,7 @@ export declare class DotBotsAuth {
10
10
  private cachedUser;
11
11
  private initialized;
12
12
  private initializePromise;
13
- static readonly SDK_VERSION = "1.0.16";
13
+ static readonly SDK_VERSION = "1.0.18";
14
14
  constructor(config: DotBotsConfig);
15
15
  initialize(): Promise<void>;
16
16
  private _doInitialize;
@@ -23,6 +23,8 @@ export declare class DotBotsAuth {
23
23
  charge(featureCode: string, paidBy: 'org' | 'app', quantity?: number): Promise<{
24
24
  transactionId: string;
25
25
  }>;
26
+ ai(feature: string, request: AiRequest): Promise<AiResponse>;
27
+ aiStream(feature: string, request: AiRequest, onDelta: (delta: string) => void, onDone?: (response: AiResponse) => void): Promise<void>;
26
28
  logout(): Promise<void>;
27
29
  on(event: DotBotsAuthEvent, handler: Function): void;
28
30
  off(event: DotBotsAuthEvent, handler: Function): void;
@@ -1,3 +1,3 @@
1
1
  export { DotBotsAuth } from './DotBotsAuth.js';
2
2
  export { DotBotsAuthError } from './DotBotsAuthError.js';
3
- export type { DotBotsConfig, DotBotsUser, DotBotsAuthEvent, DotBotsAuthErrorCode, DotBotsProxyConfig, ProxyFeature, DotBotsEnvironment, } from './types.js';
3
+ export type { DotBotsConfig, DotBotsUser, DotBotsAuthEvent, DotBotsAuthErrorCode, DotBotsProxyConfig, ProxyFeature, DotBotsEnvironment, AiMessage, AiContentBlock, AiTool, AiRequest, AiResponse, AiToolCall, } from './types.js';
@@ -295,8 +295,8 @@ class DotBotsAuth {
295
295
  this.cachedUser = null;
296
296
  this.tokenManager.clear();
297
297
  await this.tokenManager.exchangeCode(event.data.code);
298
- await this.getUser();
299
- this.emit('userChanged');
298
+ const newUser = await this.getUser();
299
+ this.emit('userChanged', newUser);
300
300
  });
301
301
  }
302
302
  async getUser() {
@@ -380,6 +380,100 @@ class DotBotsAuth {
380
380
  this.emit('charged');
381
381
  return data;
382
382
  }
383
+ async ai(feature, request) {
384
+ this.assertInitialized();
385
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
386
+ console.warn(`[DotBotsAuth] ai() — feature: ${feature}, messages: ${request.messages.length}`);
387
+ const response = await this.buildRequest(`${baseUrl}/ai/call`, {
388
+ method: 'POST',
389
+ headers: { 'Content-Type': 'application/json' },
390
+ body: JSON.stringify({
391
+ feature,
392
+ messages: request.messages,
393
+ tools: request.tools,
394
+ stream: false,
395
+ maxTokens: request.maxTokens,
396
+ }),
397
+ });
398
+ if (response.status === 402) {
399
+ console.error('[DotBotsAuth] ai() failed — 402: insufficient balance');
400
+ throw new DotBotsAuthError('AI_INSUFFICIENT_BALANCE', 'Insufficient balance for AI call');
401
+ }
402
+ if (response.status === 400) {
403
+ const body = await response.json();
404
+ console.error(`[DotBotsAuth] ai() failed — 400: ${body.error}`);
405
+ throw new DotBotsAuthError(body.error || 'AI_FEATURE_NOT_FOUND', body.message ?? 'AI request failed');
406
+ }
407
+ if (!response.ok) {
408
+ console.error(`[DotBotsAuth] ai() failed — HTTP ${response.status}`);
409
+ throw new DotBotsAuthError('AI_CALL_FAILED', `AI call failed: ${response.status}`);
410
+ }
411
+ const data = await response.json();
412
+ console.warn(`[DotBotsAuth] ai() success — model: ${data.model}, tokens: ${data.usage.totalTokens}, transactionId: ${data.transactionId}`);
413
+ return data;
414
+ }
415
+ async aiStream(feature, request, onDelta, onDone) {
416
+ this.assertInitialized();
417
+ const baseUrl = this.proxyConfigManager.getBaseUrl();
418
+ console.warn(`[DotBotsAuth] aiStream() — feature: ${feature}, messages: ${request.messages.length}`);
419
+ const response = await this.buildRequest(`${baseUrl}/ai/call`, {
420
+ method: 'POST',
421
+ headers: { 'Content-Type': 'application/json' },
422
+ body: JSON.stringify({
423
+ feature,
424
+ messages: request.messages,
425
+ tools: request.tools,
426
+ stream: true,
427
+ maxTokens: request.maxTokens,
428
+ }),
429
+ });
430
+ if (response.status === 402) {
431
+ console.error('[DotBotsAuth] aiStream() failed — 402: insufficient balance');
432
+ throw new DotBotsAuthError('AI_INSUFFICIENT_BALANCE', 'Insufficient balance for AI call');
433
+ }
434
+ if (!response.ok) {
435
+ console.error(`[DotBotsAuth] aiStream() failed — HTTP ${response.status}`);
436
+ throw new DotBotsAuthError('AI_CALL_FAILED', `AI stream failed: ${response.status}`);
437
+ }
438
+ const reader = response.body.getReader();
439
+ const decoder = new TextDecoder();
440
+ let buffer = '';
441
+ try {
442
+ while (true) {
443
+ const { done, value } = await reader.read();
444
+ if (done)
445
+ break;
446
+ buffer += decoder.decode(value, { stream: true });
447
+ const lines = buffer.split('\n');
448
+ buffer = lines.pop() ?? '';
449
+ for (const line of lines) {
450
+ if (!line.startsWith('data: '))
451
+ continue;
452
+ const data = JSON.parse(line.slice(6));
453
+ if (data.type === 'text') {
454
+ onDelta(data.delta);
455
+ }
456
+ else if (data.type === 'done') {
457
+ const finalResponse = {
458
+ content: '',
459
+ model: data.model,
460
+ provider: data.provider,
461
+ usage: data.usage,
462
+ cost: data.cost,
463
+ transactionId: data.transactionId,
464
+ toolCalls: data.toolCalls,
465
+ };
466
+ console.warn(`[DotBotsAuth] aiStream() done — model: ${data.model}, tokens: ${data.usage.totalTokens}, transactionId: ${data.transactionId}`);
467
+ onDone?.(finalResponse);
468
+ }
469
+ }
470
+ }
471
+ }
472
+ catch (err) {
473
+ console.error('[DotBotsAuth] aiStream() error:', err instanceof Error ? err.message : err);
474
+ throw new DotBotsAuthError('AI_STREAM_ERROR', 'Streaming connection failed', err instanceof Error ? err : undefined);
475
+ }
476
+ }
383
477
  async logout() {
384
478
  this.assertInitialized();
385
479
  await this.tokenManager.revoke();
@@ -456,12 +550,12 @@ class DotBotsAuth {
456
550
  throw new DotBotsAuthError('NOT_INITIALIZED', 'Call initialize() before using the SDK');
457
551
  }
458
552
  }
459
- emit(event) {
553
+ emit(event, data) {
460
554
  const handlers = this.listeners.get(event);
461
555
  if (handlers) {
462
556
  for (const handler of handlers) {
463
557
  try {
464
- handler();
558
+ handler(data);
465
559
  }
466
560
  catch {
467
561
  // Don't let listener errors break the SDK
@@ -470,6 +564,6 @@ class DotBotsAuth {
470
564
  }
471
565
  }
472
566
  }
473
- DotBotsAuth.SDK_VERSION = '1.0.16';
567
+ DotBotsAuth.SDK_VERSION = '1.0.18';
474
568
 
475
569
  export { DotBotsAuth, DotBotsAuthError };
@@ -1,6 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { DotBotsAuth } from '../DotBotsAuth.js';
3
- import type { DotBotsUser } from '../types.js';
3
+ import type { DotBotsUser, AiRequest, AiResponse } from '../types.js';
4
4
  import type { DotBotsAuthError } from '../DotBotsAuthError.js';
5
5
  export interface DotBotsAuthContextValue {
6
6
  user: DotBotsUser | null;
@@ -14,6 +14,8 @@ export interface DotBotsAuthContextValue {
14
14
  charge: (featureCode: string, paidBy: 'org' | 'app', quantity?: number) => Promise<{
15
15
  transactionId: string;
16
16
  }>;
17
+ ai: (feature: string, request: AiRequest) => Promise<AiResponse>;
18
+ aiStream: (feature: string, request: AiRequest, onDelta: (delta: string) => void, onDone?: (response: AiResponse) => void) => Promise<void>;
17
19
  logout: () => Promise<void>;
18
20
  }
19
21
  export declare const DotBotsAuthContext: import("react").Context<DotBotsAuthContextValue | null>;
@@ -29,6 +29,15 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
29
29
  cancelled = true;
30
30
  };
31
31
  }, [auth]);
32
+ useEffect(() => {
33
+ const handler = (newUser) => {
34
+ setUser(newUser);
35
+ };
36
+ auth.on('userChanged', handler);
37
+ return () => {
38
+ auth.off('userChanged', handler);
39
+ };
40
+ }, [auth]);
32
41
  if (isLoading) {
33
42
  return jsx(Fragment, { children: loadingComponent ?? jsx("div", { children: "Loading..." }) });
34
43
  }
@@ -48,6 +57,8 @@ function DotBotsAuthProvider({ auth, children, loadingComponent, errorComponent,
48
57
  hasRole: (role) => auth.hasRole(role),
49
58
  fetch: (url, options) => auth.fetch(url, options),
50
59
  charge: (featureCode, paidBy, quantity) => auth.charge(featureCode, paidBy, quantity),
60
+ ai: (feature, request) => auth.ai(feature, request),
61
+ aiStream: (feature, request, onDelta, onDone) => auth.aiStream(feature, request, onDelta, onDone),
51
62
  logout: () => auth.logout(),
52
63
  };
53
64
  return (jsx(DotBotsAuthContext.Provider, { value: value, children: children }));
@@ -41,10 +41,51 @@ export interface DotBotsProxyConfig {
41
41
  }
42
42
  export type ProxyFeature = 'cache' | 'localdb' | 'webhooks' | 'ratelimit';
43
43
  export type DotBotsAuthEvent = 'tokenRefreshed' | 'loggedOut' | 'sessionExpired' | 'userLoaded' | 'charged' | 'userChanged';
44
- export type DotBotsAuthErrorCode = 'IFRAME_TIMEOUT' | 'CODE_EXPIRED' | 'UNAUTHORIZED' | 'REFRESH_FAILED' | 'NETWORK_ERROR' | 'NOT_INITIALIZED' | 'PROXY_UNAVAILABLE' | 'PAYMENT_FAILED';
44
+ export type DotBotsAuthErrorCode = 'IFRAME_TIMEOUT' | 'CODE_EXPIRED' | 'UNAUTHORIZED' | 'REFRESH_FAILED' | 'NETWORK_ERROR' | 'NOT_INITIALIZED' | 'PROXY_UNAVAILABLE' | 'PAYMENT_FAILED' | 'AI_FEATURE_NOT_FOUND' | 'AI_PROVIDER_NOT_CONFIGURED' | 'AI_INSUFFICIENT_BALANCE' | 'AI_CALL_FAILED' | 'AI_STREAM_ERROR';
45
45
  export interface TokenPair {
46
46
  accessToken: string;
47
47
  refreshToken: string;
48
48
  expiresIn: number;
49
49
  }
50
50
  export type DotBotsEnvironment = 'test' | 'prod';
51
+ export interface AiMessage {
52
+ role: 'user' | 'assistant' | 'system';
53
+ content: string | AiContentBlock[];
54
+ }
55
+ export interface AiContentBlock {
56
+ type: 'text' | 'image_url';
57
+ text?: string;
58
+ image_url?: {
59
+ url: string;
60
+ };
61
+ }
62
+ export interface AiTool {
63
+ name: string;
64
+ description: string;
65
+ parameters: Record<string, unknown>;
66
+ }
67
+ export interface AiRequest {
68
+ messages: AiMessage[];
69
+ tools?: AiTool[];
70
+ maxTokens?: number;
71
+ }
72
+ export interface AiResponse {
73
+ content: string;
74
+ model: string;
75
+ provider: string;
76
+ usage: {
77
+ inputTokens: number;
78
+ outputTokens: number;
79
+ totalTokens: number;
80
+ };
81
+ cost: {
82
+ tokens: number;
83
+ eur: number;
84
+ };
85
+ transactionId: string;
86
+ toolCalls?: AiToolCall[];
87
+ }
88
+ export interface AiToolCall {
89
+ name: string;
90
+ input: Record<string, unknown>;
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotbots-boutique/auth-sdk",
3
- "version": "1.0.16",
3
+ "version": "1.0.18",
4
4
  "description": "Authentication SDK for DotBots marketplace apps",
5
5
  "license": "MIT",
6
6
  "type": "module",