@sinas/sdk 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,19 +44,30 @@ var SinasSDK = (() => {
44
44
  __export(src_exports, {
45
45
  SinasAuthError: () => SinasAuthError,
46
46
  SinasClient: () => SinasClient,
47
+ SinasClientAuth: () => SinasClientAuth,
47
48
  SinasError: () => SinasError,
48
49
  SinasNotFoundError: () => SinasNotFoundError,
49
50
  SinasPermissionError: () => SinasPermissionError,
50
51
  SinasProvider: () => SinasProvider,
51
52
  SinasTimeoutError: () => SinasTimeoutError,
52
53
  approveToolCall: () => approveToolCall,
54
+ cancelBatch: () => cancelBatch,
55
+ checkPermissions: () => checkPermissions,
53
56
  createChat: () => createChat,
57
+ enqueueFunction: () => enqueueFunction,
54
58
  executeFunction: () => executeFunction,
55
59
  executeQuery: () => executeQuery,
56
60
  generateFileUrl: () => generateFileUrl,
61
+ getBatch: () => getBatch,
57
62
  getChat: () => getChat,
63
+ getInfo: () => getInfo,
64
+ getMe: () => getMe,
65
+ listBatchExecutions: () => listBatchExecutions,
58
66
  listCollections: () => listCollections,
59
67
  listFiles: () => listFiles,
68
+ login: () => login,
69
+ logout: () => logout,
70
+ refresh: () => refresh,
60
71
  sendMessage: () => sendMessage,
61
72
  stateDelete: () => stateDelete,
62
73
  stateGet: () => stateGet,
@@ -64,13 +75,17 @@ var SinasSDK = (() => {
64
75
  stateSet: () => stateSet,
65
76
  streamFromChannel: () => streamFromChannel,
66
77
  streamMessage: () => streamMessage,
78
+ submitAgentBatch: () => submitAgentBatch,
79
+ submitFunctionBatch: () => submitFunctionBatch,
67
80
  uploadFile: () => uploadFile,
81
+ useAuth: () => useAuth,
68
82
  useChat: () => useChat,
69
83
  useClient: () => useClient,
70
84
  useExecute: () => useExecute,
71
85
  useQuery: () => useQuery,
72
86
  useSinas: () => useSinas,
73
- useStateStore: () => useStateStore
87
+ useStateStore: () => useStateStore,
88
+ verifyOTP: () => verifyOTP
74
89
  });
75
90
 
76
91
  // src/client/errors.ts
@@ -107,16 +122,131 @@ var SinasSDK = (() => {
107
122
  }
108
123
  };
109
124
 
125
+ // src/client/auth.ts
126
+ function trimBase(baseUrl) {
127
+ return baseUrl.replace(/\/+$/, "");
128
+ }
129
+ async function authFetch(baseUrl, path, init = {}, accessToken) {
130
+ const headers = new Headers(init.headers);
131
+ if (!headers.has("Content-Type") && init.body !== void 0) {
132
+ headers.set("Content-Type", "application/json");
133
+ }
134
+ if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
135
+ const res = await fetch(`${trimBase(baseUrl)}${path}`, { ...init, headers });
136
+ if (res.ok || res.status === 204) return res;
137
+ let detail = res.statusText;
138
+ try {
139
+ const body = await res.clone().json();
140
+ detail = body.detail || body.message || detail;
141
+ } catch {
142
+ }
143
+ if (res.status === 401) throw new SinasAuthError(detail);
144
+ if (res.status === 403) throw new SinasPermissionError(detail);
145
+ if (res.status === 404) throw new SinasNotFoundError(detail);
146
+ throw new SinasError(detail, res.status, detail);
147
+ }
148
+ async function getInfo(baseUrl) {
149
+ const res = await authFetch(baseUrl, "/info");
150
+ return await res.json();
151
+ }
152
+ async function login(baseUrl, request) {
153
+ const res = await authFetch(baseUrl, "/auth/login", {
154
+ method: "POST",
155
+ body: JSON.stringify(request)
156
+ });
157
+ return await res.json();
158
+ }
159
+ async function verifyOTP(baseUrl, request) {
160
+ const res = await authFetch(baseUrl, "/auth/verify-otp", {
161
+ method: "POST",
162
+ body: JSON.stringify(request)
163
+ });
164
+ return await res.json();
165
+ }
166
+ async function refresh(baseUrl, refreshToken) {
167
+ const res = await authFetch(baseUrl, "/auth/refresh", {
168
+ method: "POST",
169
+ body: JSON.stringify({ refresh_token: refreshToken })
170
+ });
171
+ return await res.json();
172
+ }
173
+ async function logout(baseUrl, refreshToken) {
174
+ try {
175
+ await authFetch(baseUrl, "/auth/logout", {
176
+ method: "POST",
177
+ body: JSON.stringify({ refresh_token: refreshToken })
178
+ });
179
+ } catch (e) {
180
+ if (e instanceof SinasNotFoundError) return;
181
+ throw e;
182
+ }
183
+ }
184
+ async function getMe(baseUrl, accessToken) {
185
+ const res = await authFetch(baseUrl, "/auth/me", { method: "GET" }, accessToken);
186
+ return await res.json();
187
+ }
188
+ async function checkPermissions(baseUrl, accessToken, request) {
189
+ const res = await authFetch(
190
+ baseUrl,
191
+ "/auth/check-permissions",
192
+ {
193
+ method: "POST",
194
+ body: JSON.stringify({ logic: "AND", ...request })
195
+ },
196
+ accessToken
197
+ );
198
+ return await res.json();
199
+ }
200
+
110
201
  // src/client/SinasClient.ts
202
+ function hasWindow() {
203
+ return typeof window !== "undefined";
204
+ }
205
+ function trim(url) {
206
+ return url.replace(/\/+$/, "");
207
+ }
208
+ var SinasClientAuth = class {
209
+ constructor(client) {
210
+ this.client = client;
211
+ }
212
+ getInfo() {
213
+ return getInfo(this.client.requireBaseUrl());
214
+ }
215
+ login(request) {
216
+ return login(this.client.requireBaseUrl(), request);
217
+ }
218
+ verifyOTP(request) {
219
+ return verifyOTP(this.client.requireBaseUrl(), request);
220
+ }
221
+ refresh(refreshToken) {
222
+ return refresh(this.client.requireBaseUrl(), refreshToken);
223
+ }
224
+ logout(refreshToken) {
225
+ return logout(this.client.requireBaseUrl(), refreshToken);
226
+ }
227
+ getMe() {
228
+ const token = this.client.getAccessToken();
229
+ if (!token) throw new SinasAuthError("No access token available");
230
+ return getMe(this.client.requireBaseUrl(), token);
231
+ }
232
+ checkPermissions(request) {
233
+ const token = this.client.getAccessToken();
234
+ if (!token) throw new SinasAuthError("No access token available");
235
+ return checkPermissions(this.client.requireBaseUrl(), token, request);
236
+ }
237
+ };
111
238
  var SinasClient = class {
112
- constructor(config) {
113
- this.configOverride = config;
239
+ constructor(options = {}) {
240
+ this.refreshInFlight = null;
241
+ this.options = options;
242
+ this.auth = new SinasClientAuth(this);
114
243
  }
244
+ // ─── Config / base URL ───
115
245
  getConfig() {
116
- const config = this.configOverride || window.__SINAS_CONFIG__;
246
+ const config = this.options.config || (hasWindow() ? window.__SINAS_CONFIG__ : void 0);
117
247
  if (!config) {
118
248
  return {
119
- apiBase: "",
249
+ apiBase: this.options.baseUrl ? `${trim(this.options.baseUrl)}/api/v1` : "",
120
250
  component: { namespace: "", name: "", version: "" },
121
251
  resources: {
122
252
  enabledAgents: [],
@@ -133,12 +263,56 @@ var SinasSDK = (() => {
133
263
  getInput() {
134
264
  return this.getConfig().input || {};
135
265
  }
266
+ /** Workspace base URL (root of the Sinas instance). Throws if unset. */
267
+ requireBaseUrl() {
268
+ const url = this.getBaseUrl();
269
+ if (!url) {
270
+ throw new SinasError(
271
+ "No baseUrl configured. Construct with `new SinasClient({ baseUrl })` for standalone use.",
272
+ 0
273
+ );
274
+ }
275
+ return url;
276
+ }
277
+ getBaseUrl() {
278
+ if (this.options.baseUrl) return trim(this.options.baseUrl);
279
+ const apiBase = this.getConfig().apiBase;
280
+ if (!apiBase) return null;
281
+ return trim(apiBase).replace(/\/api\/v1$/, "");
282
+ }
283
+ // ─── Tokens ───
284
+ getAccessToken() {
285
+ if (this.options.getAccessToken) return this.options.getAccessToken();
286
+ if (hasWindow()) return window.__SINAS_AUTH_TOKEN__ ?? null;
287
+ return null;
288
+ }
289
+ getRefreshToken() {
290
+ return this.options.getRefreshToken ? this.options.getRefreshToken() : null;
291
+ }
292
+ async tryRefresh() {
293
+ const refreshToken = this.getRefreshToken();
294
+ if (!refreshToken) return null;
295
+ if (this.refreshInFlight) return this.refreshInFlight;
296
+ const baseUrl = this.getBaseUrl();
297
+ if (!baseUrl) return null;
298
+ this.refreshInFlight = (async () => {
299
+ try {
300
+ const resp = await refresh(baseUrl, refreshToken);
301
+ this.options.onTokenRefresh?.(resp.access_token);
302
+ return resp.access_token;
303
+ } catch {
304
+ return null;
305
+ } finally {
306
+ this.refreshInFlight = null;
307
+ }
308
+ })();
309
+ return this.refreshInFlight;
310
+ }
311
+ // ─── Headers / proxy ───
136
312
  getAuthHeaders() {
137
- const token = window.__SINAS_AUTH_TOKEN__;
313
+ const token = this.getAccessToken();
138
314
  const headers = { "Content-Type": "application/json" };
139
- if (token) {
140
- headers["Authorization"] = `Bearer ${token}`;
141
- }
315
+ if (token) headers["Authorization"] = `Bearer ${token}`;
142
316
  return headers;
143
317
  }
144
318
  getProxyBase() {
@@ -146,7 +320,8 @@ var SinasSDK = (() => {
146
320
  const comp = config.component;
147
321
  return `${config.apiBase}/components/${comp.namespace}/${comp.name}/proxy`;
148
322
  }
149
- async fetch(url, options = {}) {
323
+ // ─── Fetch with refresh-on-401 ───
324
+ async fetch(url, options = {}, _retried = false) {
150
325
  const res = await fetch(url, {
151
326
  ...options,
152
327
  headers: {
@@ -154,15 +329,18 @@ var SinasSDK = (() => {
154
329
  ...options.headers || {}
155
330
  }
156
331
  });
157
- if (!res.ok) {
158
- const body = await res.json().catch(() => ({ detail: res.statusText }));
159
- const message = body.detail || body.message || res.statusText;
160
- if (res.status === 401) throw new SinasAuthError(message);
161
- if (res.status === 403) throw new SinasPermissionError(message);
162
- if (res.status === 404) throw new SinasNotFoundError(message);
163
- throw new SinasError(message, res.status);
332
+ if (res.ok || res.status === 204) return res;
333
+ if (res.status === 401 && !_retried) {
334
+ const newToken = await this.tryRefresh();
335
+ if (newToken) return this.fetch(url, options, true);
336
+ this.options.onUnauthenticated?.();
164
337
  }
165
- return res;
338
+ const body = await res.json().catch(() => ({ detail: res.statusText }));
339
+ const message = body.detail || body.message || res.statusText;
340
+ if (res.status === 401) throw new SinasAuthError(message);
341
+ if (res.status === 403) throw new SinasPermissionError(message);
342
+ if (res.status === 404) throw new SinasNotFoundError(message);
343
+ throw new SinasError(message, res.status);
166
344
  }
167
345
  };
168
346
 
@@ -219,6 +397,124 @@ var SinasSDK = (() => {
219
397
  error: data.error
220
398
  };
221
399
  }
400
+ async function enqueueFunction(client, ref, input = {}, options = {}) {
401
+ const [ns, name] = parseRef2(ref);
402
+ const baseUrl = client.requireBaseUrl();
403
+ const body = { input };
404
+ if (options.triggerId !== void 0) body.trigger_id = options.triggerId;
405
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
406
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
407
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/async`, {
408
+ method: "POST",
409
+ body: JSON.stringify(body)
410
+ });
411
+ const data = await res.json();
412
+ return {
413
+ executionId: data.execution_id,
414
+ status: data.status
415
+ };
416
+ }
417
+ async function submitFunctionBatch(client, ref, inputs, options = {}) {
418
+ const [ns, name] = parseRef2(ref);
419
+ const baseUrl = client.requireBaseUrl();
420
+ const body = { inputs };
421
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
422
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
423
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
424
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
425
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/batch`, {
426
+ method: "POST",
427
+ body: JSON.stringify(body)
428
+ });
429
+ const data = await res.json();
430
+ return {
431
+ batchId: data.batch_id,
432
+ executionIds: data.execution_ids,
433
+ total: data.total,
434
+ status: data.status
435
+ };
436
+ }
437
+ async function submitAgentBatch(client, ref, inputs, options = {}) {
438
+ const [ns, name] = parseRef2(ref);
439
+ const baseUrl = client.requireBaseUrl();
440
+ const body = {
441
+ inputs: inputs.map((item) => ({
442
+ input_variables: item.inputVariables || {},
443
+ message: item.message
444
+ }))
445
+ };
446
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
447
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
448
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
449
+ const res = await client.fetch(`${baseUrl}/agents/${ns}/${name}/chats/batch`, {
450
+ method: "POST",
451
+ body: JSON.stringify(body)
452
+ });
453
+ const data = await res.json();
454
+ return {
455
+ batchId: data.batch_id,
456
+ executionIds: data.execution_ids,
457
+ chatIds: data.chat_ids,
458
+ total: data.total,
459
+ status: data.status
460
+ };
461
+ }
462
+ async function getBatch(client, batchId) {
463
+ const baseUrl = client.requireBaseUrl();
464
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}`);
465
+ const data = await res.json();
466
+ return {
467
+ batchId: data.batch_id,
468
+ kind: data.kind,
469
+ target: data.target,
470
+ userId: data.user_id,
471
+ total: data.total,
472
+ completed: data.completed,
473
+ failed: data.failed,
474
+ cancelled: data.cancelled,
475
+ running: data.running,
476
+ queued: data.queued,
477
+ status: data.status,
478
+ startedAt: data.started_at,
479
+ finishedAt: data.finished_at,
480
+ triggerIdPrefix: data.trigger_id_prefix
481
+ };
482
+ }
483
+ async function listBatchExecutions(client, batchId, options = {}) {
484
+ const baseUrl = client.requireBaseUrl();
485
+ const params = new URLSearchParams();
486
+ if (options.status) params.set("status", options.status);
487
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
488
+ if (options.offset !== void 0) params.set("offset", String(options.offset));
489
+ const q = params.toString();
490
+ const res = await client.fetch(
491
+ `${baseUrl}/batches/${batchId}/executions${q ? "?" + q : ""}`
492
+ );
493
+ const data = await res.json();
494
+ return (data.executions || []).map((row) => ({
495
+ executionId: row.execution_id,
496
+ status: row.status,
497
+ functionName: row.function_name,
498
+ chatId: row.chat_id ?? null,
499
+ triggerId: row.trigger_id,
500
+ startedAt: row.started_at ?? null,
501
+ completedAt: row.completed_at ?? null,
502
+ durationMs: row.duration_ms ?? null,
503
+ error: row.error ?? null
504
+ }));
505
+ }
506
+ async function cancelBatch(client, batchId) {
507
+ const baseUrl = client.requireBaseUrl();
508
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}/cancel`, {
509
+ method: "POST"
510
+ });
511
+ const data = await res.json();
512
+ return {
513
+ batchId: data.batch_id,
514
+ status: data.status,
515
+ cancelledChildren: data.cancelled_children
516
+ };
517
+ }
222
518
 
223
519
  // src/client/states.ts
224
520
  async function stateGet(client, ns, key) {
@@ -486,8 +782,14 @@ var SinasSDK = (() => {
486
782
  // src/hooks/context.ts
487
783
  var import_react = __toESM(require_react(), 1);
488
784
  var SinasContext = import_react.default.createContext(null);
489
- function SinasProvider({ client, children }) {
490
- const value = import_react.default.useMemo(() => client || new SinasClient(), [client]);
785
+ function SinasProvider({ client, options, children }) {
786
+ const value = import_react.default.useMemo(
787
+ () => client || new SinasClient(options),
788
+ // We intentionally only re-create when `client` reference changes; `options`
789
+ // is expected to be a stable object (callbacks reading from localStorage).
790
+ // eslint-disable-next-line react-hooks/exhaustive-deps
791
+ [client]
792
+ );
491
793
  return import_react.default.createElement(SinasContext.Provider, { value }, children);
492
794
  }
493
795
  function useClient() {
@@ -962,6 +1264,81 @@ var SinasSDK = (() => {
962
1264
  useChat
963
1265
  };
964
1266
  }
1267
+
1268
+ // src/hooks/useAuth.ts
1269
+ var import_react6 = __toESM(require_react(), 1);
1270
+ function useAuth(options = {}) {
1271
+ const { autoRefresh = true } = options;
1272
+ const client = useClient();
1273
+ const [status, setStatus] = import_react6.default.useState("loading");
1274
+ const [user, setUser] = import_react6.default.useState(null);
1275
+ const [info, setInfo] = import_react6.default.useState(null);
1276
+ const [error, setError] = import_react6.default.useState(null);
1277
+ const refresh2 = import_react6.default.useCallback(async () => {
1278
+ setError(null);
1279
+ try {
1280
+ const [infoResp, meResp] = await Promise.all([
1281
+ client.auth.getInfo().catch(() => null),
1282
+ client.getAccessToken() ? client.auth.getMe().catch(() => null) : Promise.resolve(null)
1283
+ ]);
1284
+ if (infoResp) setInfo(infoResp);
1285
+ if (meResp) {
1286
+ setUser(meResp);
1287
+ setStatus("authenticated");
1288
+ } else {
1289
+ setUser(null);
1290
+ setStatus("unauthenticated");
1291
+ }
1292
+ } catch (e) {
1293
+ setError(e instanceof Error ? e.message : String(e));
1294
+ setStatus("unauthenticated");
1295
+ }
1296
+ }, [client]);
1297
+ import_react6.default.useEffect(() => {
1298
+ if (autoRefresh) void refresh2();
1299
+ }, [autoRefresh, refresh2]);
1300
+ const login2 = import_react6.default.useCallback(
1301
+ async (email, password) => {
1302
+ setError(null);
1303
+ try {
1304
+ return await client.auth.login({ email, password });
1305
+ } catch (e) {
1306
+ const msg = e instanceof Error ? e.message : String(e);
1307
+ setError(msg);
1308
+ throw e;
1309
+ }
1310
+ },
1311
+ [client]
1312
+ );
1313
+ const verifyOTP2 = import_react6.default.useCallback(
1314
+ async (sessionId, otpCode) => {
1315
+ setError(null);
1316
+ try {
1317
+ return await client.auth.verifyOTP({ session_id: sessionId, otp_code: otpCode });
1318
+ } catch (e) {
1319
+ const msg = e instanceof Error ? e.message : String(e);
1320
+ setError(msg);
1321
+ throw e;
1322
+ }
1323
+ },
1324
+ [client]
1325
+ );
1326
+ const logout2 = import_react6.default.useCallback(
1327
+ async (refreshToken) => {
1328
+ if (refreshToken) {
1329
+ try {
1330
+ await client.auth.logout(refreshToken);
1331
+ } catch (e) {
1332
+ if (!(e instanceof SinasAuthError)) throw e;
1333
+ }
1334
+ }
1335
+ setUser(null);
1336
+ setStatus("unauthenticated");
1337
+ },
1338
+ [client]
1339
+ );
1340
+ return { status, user, info, error, refresh: refresh2, login: login2, verifyOTP: verifyOTP2, logout: logout2 };
1341
+ }
965
1342
  return __toCommonJS(src_exports);
966
1343
  })();
967
1344
  //# sourceMappingURL=sinas-sdk.umd.js.map