@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.
package/dist/index.cjs CHANGED
@@ -32,19 +32,30 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  SinasAuthError: () => SinasAuthError,
34
34
  SinasClient: () => SinasClient,
35
+ SinasClientAuth: () => SinasClientAuth,
35
36
  SinasError: () => SinasError,
36
37
  SinasNotFoundError: () => SinasNotFoundError,
37
38
  SinasPermissionError: () => SinasPermissionError,
38
39
  SinasProvider: () => SinasProvider,
39
40
  SinasTimeoutError: () => SinasTimeoutError,
40
41
  approveToolCall: () => approveToolCall,
42
+ cancelBatch: () => cancelBatch,
43
+ checkPermissions: () => checkPermissions,
41
44
  createChat: () => createChat,
45
+ enqueueFunction: () => enqueueFunction,
42
46
  executeFunction: () => executeFunction,
43
47
  executeQuery: () => executeQuery,
44
48
  generateFileUrl: () => generateFileUrl,
49
+ getBatch: () => getBatch,
45
50
  getChat: () => getChat,
51
+ getInfo: () => getInfo,
52
+ getMe: () => getMe,
53
+ listBatchExecutions: () => listBatchExecutions,
46
54
  listCollections: () => listCollections,
47
55
  listFiles: () => listFiles,
56
+ login: () => login,
57
+ logout: () => logout,
58
+ refresh: () => refresh,
48
59
  sendMessage: () => sendMessage,
49
60
  stateDelete: () => stateDelete,
50
61
  stateGet: () => stateGet,
@@ -52,13 +63,17 @@ __export(src_exports, {
52
63
  stateSet: () => stateSet,
53
64
  streamFromChannel: () => streamFromChannel,
54
65
  streamMessage: () => streamMessage,
66
+ submitAgentBatch: () => submitAgentBatch,
67
+ submitFunctionBatch: () => submitFunctionBatch,
55
68
  uploadFile: () => uploadFile,
69
+ useAuth: () => useAuth,
56
70
  useChat: () => useChat,
57
71
  useClient: () => useClient,
58
72
  useExecute: () => useExecute,
59
73
  useQuery: () => useQuery,
60
74
  useSinas: () => useSinas,
61
- useStateStore: () => useStateStore
75
+ useStateStore: () => useStateStore,
76
+ verifyOTP: () => verifyOTP
62
77
  });
63
78
  module.exports = __toCommonJS(src_exports);
64
79
 
@@ -96,16 +111,131 @@ var SinasTimeoutError = class extends SinasError {
96
111
  }
97
112
  };
98
113
 
114
+ // src/client/auth.ts
115
+ function trimBase(baseUrl) {
116
+ return baseUrl.replace(/\/+$/, "");
117
+ }
118
+ async function authFetch(baseUrl, path, init = {}, accessToken) {
119
+ const headers = new Headers(init.headers);
120
+ if (!headers.has("Content-Type") && init.body !== void 0) {
121
+ headers.set("Content-Type", "application/json");
122
+ }
123
+ if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
124
+ const res = await fetch(`${trimBase(baseUrl)}${path}`, { ...init, headers });
125
+ if (res.ok || res.status === 204) return res;
126
+ let detail = res.statusText;
127
+ try {
128
+ const body = await res.clone().json();
129
+ detail = body.detail || body.message || detail;
130
+ } catch {
131
+ }
132
+ if (res.status === 401) throw new SinasAuthError(detail);
133
+ if (res.status === 403) throw new SinasPermissionError(detail);
134
+ if (res.status === 404) throw new SinasNotFoundError(detail);
135
+ throw new SinasError(detail, res.status, detail);
136
+ }
137
+ async function getInfo(baseUrl) {
138
+ const res = await authFetch(baseUrl, "/info");
139
+ return await res.json();
140
+ }
141
+ async function login(baseUrl, request) {
142
+ const res = await authFetch(baseUrl, "/auth/login", {
143
+ method: "POST",
144
+ body: JSON.stringify(request)
145
+ });
146
+ return await res.json();
147
+ }
148
+ async function verifyOTP(baseUrl, request) {
149
+ const res = await authFetch(baseUrl, "/auth/verify-otp", {
150
+ method: "POST",
151
+ body: JSON.stringify(request)
152
+ });
153
+ return await res.json();
154
+ }
155
+ async function refresh(baseUrl, refreshToken) {
156
+ const res = await authFetch(baseUrl, "/auth/refresh", {
157
+ method: "POST",
158
+ body: JSON.stringify({ refresh_token: refreshToken })
159
+ });
160
+ return await res.json();
161
+ }
162
+ async function logout(baseUrl, refreshToken) {
163
+ try {
164
+ await authFetch(baseUrl, "/auth/logout", {
165
+ method: "POST",
166
+ body: JSON.stringify({ refresh_token: refreshToken })
167
+ });
168
+ } catch (e) {
169
+ if (e instanceof SinasNotFoundError) return;
170
+ throw e;
171
+ }
172
+ }
173
+ async function getMe(baseUrl, accessToken) {
174
+ const res = await authFetch(baseUrl, "/auth/me", { method: "GET" }, accessToken);
175
+ return await res.json();
176
+ }
177
+ async function checkPermissions(baseUrl, accessToken, request) {
178
+ const res = await authFetch(
179
+ baseUrl,
180
+ "/auth/check-permissions",
181
+ {
182
+ method: "POST",
183
+ body: JSON.stringify({ logic: "AND", ...request })
184
+ },
185
+ accessToken
186
+ );
187
+ return await res.json();
188
+ }
189
+
99
190
  // src/client/SinasClient.ts
191
+ function hasWindow() {
192
+ return typeof window !== "undefined";
193
+ }
194
+ function trim(url) {
195
+ return url.replace(/\/+$/, "");
196
+ }
197
+ var SinasClientAuth = class {
198
+ constructor(client) {
199
+ this.client = client;
200
+ }
201
+ getInfo() {
202
+ return getInfo(this.client.requireBaseUrl());
203
+ }
204
+ login(request) {
205
+ return login(this.client.requireBaseUrl(), request);
206
+ }
207
+ verifyOTP(request) {
208
+ return verifyOTP(this.client.requireBaseUrl(), request);
209
+ }
210
+ refresh(refreshToken) {
211
+ return refresh(this.client.requireBaseUrl(), refreshToken);
212
+ }
213
+ logout(refreshToken) {
214
+ return logout(this.client.requireBaseUrl(), refreshToken);
215
+ }
216
+ getMe() {
217
+ const token = this.client.getAccessToken();
218
+ if (!token) throw new SinasAuthError("No access token available");
219
+ return getMe(this.client.requireBaseUrl(), token);
220
+ }
221
+ checkPermissions(request) {
222
+ const token = this.client.getAccessToken();
223
+ if (!token) throw new SinasAuthError("No access token available");
224
+ return checkPermissions(this.client.requireBaseUrl(), token, request);
225
+ }
226
+ };
100
227
  var SinasClient = class {
101
- constructor(config) {
102
- this.configOverride = config;
228
+ constructor(options = {}) {
229
+ this.refreshInFlight = null;
230
+ this.options = options;
231
+ this.auth = new SinasClientAuth(this);
103
232
  }
233
+ // ─── Config / base URL ───
104
234
  getConfig() {
105
- const config = this.configOverride || window.__SINAS_CONFIG__;
235
+ const config = this.options.config || (hasWindow() ? window.__SINAS_CONFIG__ : void 0);
106
236
  if (!config) {
107
237
  return {
108
- apiBase: "",
238
+ apiBase: this.options.baseUrl ? `${trim(this.options.baseUrl)}/api/v1` : "",
109
239
  component: { namespace: "", name: "", version: "" },
110
240
  resources: {
111
241
  enabledAgents: [],
@@ -122,12 +252,56 @@ var SinasClient = class {
122
252
  getInput() {
123
253
  return this.getConfig().input || {};
124
254
  }
255
+ /** Workspace base URL (root of the Sinas instance). Throws if unset. */
256
+ requireBaseUrl() {
257
+ const url = this.getBaseUrl();
258
+ if (!url) {
259
+ throw new SinasError(
260
+ "No baseUrl configured. Construct with `new SinasClient({ baseUrl })` for standalone use.",
261
+ 0
262
+ );
263
+ }
264
+ return url;
265
+ }
266
+ getBaseUrl() {
267
+ if (this.options.baseUrl) return trim(this.options.baseUrl);
268
+ const apiBase = this.getConfig().apiBase;
269
+ if (!apiBase) return null;
270
+ return trim(apiBase).replace(/\/api\/v1$/, "");
271
+ }
272
+ // ─── Tokens ───
273
+ getAccessToken() {
274
+ if (this.options.getAccessToken) return this.options.getAccessToken();
275
+ if (hasWindow()) return window.__SINAS_AUTH_TOKEN__ ?? null;
276
+ return null;
277
+ }
278
+ getRefreshToken() {
279
+ return this.options.getRefreshToken ? this.options.getRefreshToken() : null;
280
+ }
281
+ async tryRefresh() {
282
+ const refreshToken = this.getRefreshToken();
283
+ if (!refreshToken) return null;
284
+ if (this.refreshInFlight) return this.refreshInFlight;
285
+ const baseUrl = this.getBaseUrl();
286
+ if (!baseUrl) return null;
287
+ this.refreshInFlight = (async () => {
288
+ try {
289
+ const resp = await refresh(baseUrl, refreshToken);
290
+ this.options.onTokenRefresh?.(resp.access_token);
291
+ return resp.access_token;
292
+ } catch {
293
+ return null;
294
+ } finally {
295
+ this.refreshInFlight = null;
296
+ }
297
+ })();
298
+ return this.refreshInFlight;
299
+ }
300
+ // ─── Headers / proxy ───
125
301
  getAuthHeaders() {
126
- const token = window.__SINAS_AUTH_TOKEN__;
302
+ const token = this.getAccessToken();
127
303
  const headers = { "Content-Type": "application/json" };
128
- if (token) {
129
- headers["Authorization"] = `Bearer ${token}`;
130
- }
304
+ if (token) headers["Authorization"] = `Bearer ${token}`;
131
305
  return headers;
132
306
  }
133
307
  getProxyBase() {
@@ -135,7 +309,8 @@ var SinasClient = class {
135
309
  const comp = config.component;
136
310
  return `${config.apiBase}/components/${comp.namespace}/${comp.name}/proxy`;
137
311
  }
138
- async fetch(url, options = {}) {
312
+ // ─── Fetch with refresh-on-401 ───
313
+ async fetch(url, options = {}, _retried = false) {
139
314
  const res = await fetch(url, {
140
315
  ...options,
141
316
  headers: {
@@ -143,15 +318,18 @@ var SinasClient = class {
143
318
  ...options.headers || {}
144
319
  }
145
320
  });
146
- if (!res.ok) {
147
- const body = await res.json().catch(() => ({ detail: res.statusText }));
148
- const message = body.detail || body.message || res.statusText;
149
- if (res.status === 401) throw new SinasAuthError(message);
150
- if (res.status === 403) throw new SinasPermissionError(message);
151
- if (res.status === 404) throw new SinasNotFoundError(message);
152
- throw new SinasError(message, res.status);
321
+ if (res.ok || res.status === 204) return res;
322
+ if (res.status === 401 && !_retried) {
323
+ const newToken = await this.tryRefresh();
324
+ if (newToken) return this.fetch(url, options, true);
325
+ this.options.onUnauthenticated?.();
153
326
  }
154
- return res;
327
+ const body = await res.json().catch(() => ({ detail: res.statusText }));
328
+ const message = body.detail || body.message || res.statusText;
329
+ if (res.status === 401) throw new SinasAuthError(message);
330
+ if (res.status === 403) throw new SinasPermissionError(message);
331
+ if (res.status === 404) throw new SinasNotFoundError(message);
332
+ throw new SinasError(message, res.status);
155
333
  }
156
334
  };
157
335
 
@@ -208,6 +386,124 @@ async function executeFunction(client, ref, input = {}, timeout) {
208
386
  error: data.error
209
387
  };
210
388
  }
389
+ async function enqueueFunction(client, ref, input = {}, options = {}) {
390
+ const [ns, name] = parseRef2(ref);
391
+ const baseUrl = client.requireBaseUrl();
392
+ const body = { input };
393
+ if (options.triggerId !== void 0) body.trigger_id = options.triggerId;
394
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
395
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
396
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/async`, {
397
+ method: "POST",
398
+ body: JSON.stringify(body)
399
+ });
400
+ const data = await res.json();
401
+ return {
402
+ executionId: data.execution_id,
403
+ status: data.status
404
+ };
405
+ }
406
+ async function submitFunctionBatch(client, ref, inputs, options = {}) {
407
+ const [ns, name] = parseRef2(ref);
408
+ const baseUrl = client.requireBaseUrl();
409
+ const body = { inputs };
410
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
411
+ if (options.delaySeconds !== void 0) body.delay_seconds = options.delaySeconds;
412
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
413
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
414
+ const res = await client.fetch(`${baseUrl}/functions/${ns}/${name}/execute/batch`, {
415
+ method: "POST",
416
+ body: JSON.stringify(body)
417
+ });
418
+ const data = await res.json();
419
+ return {
420
+ batchId: data.batch_id,
421
+ executionIds: data.execution_ids,
422
+ total: data.total,
423
+ status: data.status
424
+ };
425
+ }
426
+ async function submitAgentBatch(client, ref, inputs, options = {}) {
427
+ const [ns, name] = parseRef2(ref);
428
+ const baseUrl = client.requireBaseUrl();
429
+ const body = {
430
+ inputs: inputs.map((item) => ({
431
+ input_variables: item.inputVariables || {},
432
+ message: item.message
433
+ }))
434
+ };
435
+ if (options.triggerIdPrefix !== void 0) body.trigger_id_prefix = options.triggerIdPrefix;
436
+ if (options.callbackUrl !== void 0) body.callback_url = options.callbackUrl;
437
+ if (options.batchCallbackUrl !== void 0) body.batch_callback_url = options.batchCallbackUrl;
438
+ const res = await client.fetch(`${baseUrl}/agents/${ns}/${name}/chats/batch`, {
439
+ method: "POST",
440
+ body: JSON.stringify(body)
441
+ });
442
+ const data = await res.json();
443
+ return {
444
+ batchId: data.batch_id,
445
+ executionIds: data.execution_ids,
446
+ chatIds: data.chat_ids,
447
+ total: data.total,
448
+ status: data.status
449
+ };
450
+ }
451
+ async function getBatch(client, batchId) {
452
+ const baseUrl = client.requireBaseUrl();
453
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}`);
454
+ const data = await res.json();
455
+ return {
456
+ batchId: data.batch_id,
457
+ kind: data.kind,
458
+ target: data.target,
459
+ userId: data.user_id,
460
+ total: data.total,
461
+ completed: data.completed,
462
+ failed: data.failed,
463
+ cancelled: data.cancelled,
464
+ running: data.running,
465
+ queued: data.queued,
466
+ status: data.status,
467
+ startedAt: data.started_at,
468
+ finishedAt: data.finished_at,
469
+ triggerIdPrefix: data.trigger_id_prefix
470
+ };
471
+ }
472
+ async function listBatchExecutions(client, batchId, options = {}) {
473
+ const baseUrl = client.requireBaseUrl();
474
+ const params = new URLSearchParams();
475
+ if (options.status) params.set("status", options.status);
476
+ if (options.limit !== void 0) params.set("limit", String(options.limit));
477
+ if (options.offset !== void 0) params.set("offset", String(options.offset));
478
+ const q = params.toString();
479
+ const res = await client.fetch(
480
+ `${baseUrl}/batches/${batchId}/executions${q ? "?" + q : ""}`
481
+ );
482
+ const data = await res.json();
483
+ return (data.executions || []).map((row) => ({
484
+ executionId: row.execution_id,
485
+ status: row.status,
486
+ functionName: row.function_name,
487
+ chatId: row.chat_id ?? null,
488
+ triggerId: row.trigger_id,
489
+ startedAt: row.started_at ?? null,
490
+ completedAt: row.completed_at ?? null,
491
+ durationMs: row.duration_ms ?? null,
492
+ error: row.error ?? null
493
+ }));
494
+ }
495
+ async function cancelBatch(client, batchId) {
496
+ const baseUrl = client.requireBaseUrl();
497
+ const res = await client.fetch(`${baseUrl}/batches/${batchId}/cancel`, {
498
+ method: "POST"
499
+ });
500
+ const data = await res.json();
501
+ return {
502
+ batchId: data.batch_id,
503
+ status: data.status,
504
+ cancelledChildren: data.cancelled_children
505
+ };
506
+ }
211
507
 
212
508
  // src/client/states.ts
213
509
  async function stateGet(client, ns, key) {
@@ -475,8 +771,14 @@ var import_react2 = __toESM(require("react"), 1);
475
771
  // src/hooks/context.ts
476
772
  var import_react = __toESM(require("react"), 1);
477
773
  var SinasContext = import_react.default.createContext(null);
478
- function SinasProvider({ client, children }) {
479
- const value = import_react.default.useMemo(() => client || new SinasClient(), [client]);
774
+ function SinasProvider({ client, options, children }) {
775
+ const value = import_react.default.useMemo(
776
+ () => client || new SinasClient(options),
777
+ // We intentionally only re-create when `client` reference changes; `options`
778
+ // is expected to be a stable object (callbacks reading from localStorage).
779
+ // eslint-disable-next-line react-hooks/exhaustive-deps
780
+ [client]
781
+ );
480
782
  return import_react.default.createElement(SinasContext.Provider, { value }, children);
481
783
  }
482
784
  function useClient() {
@@ -951,23 +1253,109 @@ function useSinas() {
951
1253
  useChat
952
1254
  };
953
1255
  }
1256
+
1257
+ // src/hooks/useAuth.ts
1258
+ var import_react6 = __toESM(require("react"), 1);
1259
+ function useAuth(options = {}) {
1260
+ const { autoRefresh = true } = options;
1261
+ const client = useClient();
1262
+ const [status, setStatus] = import_react6.default.useState("loading");
1263
+ const [user, setUser] = import_react6.default.useState(null);
1264
+ const [info, setInfo] = import_react6.default.useState(null);
1265
+ const [error, setError] = import_react6.default.useState(null);
1266
+ const refresh2 = import_react6.default.useCallback(async () => {
1267
+ setError(null);
1268
+ try {
1269
+ const [infoResp, meResp] = await Promise.all([
1270
+ client.auth.getInfo().catch(() => null),
1271
+ client.getAccessToken() ? client.auth.getMe().catch(() => null) : Promise.resolve(null)
1272
+ ]);
1273
+ if (infoResp) setInfo(infoResp);
1274
+ if (meResp) {
1275
+ setUser(meResp);
1276
+ setStatus("authenticated");
1277
+ } else {
1278
+ setUser(null);
1279
+ setStatus("unauthenticated");
1280
+ }
1281
+ } catch (e) {
1282
+ setError(e instanceof Error ? e.message : String(e));
1283
+ setStatus("unauthenticated");
1284
+ }
1285
+ }, [client]);
1286
+ import_react6.default.useEffect(() => {
1287
+ if (autoRefresh) void refresh2();
1288
+ }, [autoRefresh, refresh2]);
1289
+ const login2 = import_react6.default.useCallback(
1290
+ async (email, password) => {
1291
+ setError(null);
1292
+ try {
1293
+ return await client.auth.login({ email, password });
1294
+ } catch (e) {
1295
+ const msg = e instanceof Error ? e.message : String(e);
1296
+ setError(msg);
1297
+ throw e;
1298
+ }
1299
+ },
1300
+ [client]
1301
+ );
1302
+ const verifyOTP2 = import_react6.default.useCallback(
1303
+ async (sessionId, otpCode) => {
1304
+ setError(null);
1305
+ try {
1306
+ return await client.auth.verifyOTP({ session_id: sessionId, otp_code: otpCode });
1307
+ } catch (e) {
1308
+ const msg = e instanceof Error ? e.message : String(e);
1309
+ setError(msg);
1310
+ throw e;
1311
+ }
1312
+ },
1313
+ [client]
1314
+ );
1315
+ const logout2 = import_react6.default.useCallback(
1316
+ async (refreshToken) => {
1317
+ if (refreshToken) {
1318
+ try {
1319
+ await client.auth.logout(refreshToken);
1320
+ } catch (e) {
1321
+ if (!(e instanceof SinasAuthError)) throw e;
1322
+ }
1323
+ }
1324
+ setUser(null);
1325
+ setStatus("unauthenticated");
1326
+ },
1327
+ [client]
1328
+ );
1329
+ return { status, user, info, error, refresh: refresh2, login: login2, verifyOTP: verifyOTP2, logout: logout2 };
1330
+ }
954
1331
  // Annotate the CommonJS export names for ESM import in node:
955
1332
  0 && (module.exports = {
956
1333
  SinasAuthError,
957
1334
  SinasClient,
1335
+ SinasClientAuth,
958
1336
  SinasError,
959
1337
  SinasNotFoundError,
960
1338
  SinasPermissionError,
961
1339
  SinasProvider,
962
1340
  SinasTimeoutError,
963
1341
  approveToolCall,
1342
+ cancelBatch,
1343
+ checkPermissions,
964
1344
  createChat,
1345
+ enqueueFunction,
965
1346
  executeFunction,
966
1347
  executeQuery,
967
1348
  generateFileUrl,
1349
+ getBatch,
968
1350
  getChat,
1351
+ getInfo,
1352
+ getMe,
1353
+ listBatchExecutions,
969
1354
  listCollections,
970
1355
  listFiles,
1356
+ login,
1357
+ logout,
1358
+ refresh,
971
1359
  sendMessage,
972
1360
  stateDelete,
973
1361
  stateGet,
@@ -975,12 +1363,16 @@ function useSinas() {
975
1363
  stateSet,
976
1364
  streamFromChannel,
977
1365
  streamMessage,
1366
+ submitAgentBatch,
1367
+ submitFunctionBatch,
978
1368
  uploadFile,
1369
+ useAuth,
979
1370
  useChat,
980
1371
  useClient,
981
1372
  useExecute,
982
1373
  useQuery,
983
1374
  useSinas,
984
- useStateStore
1375
+ useStateStore,
1376
+ verifyOTP
985
1377
  });
986
1378
  //# sourceMappingURL=index.cjs.map