@seclai/sdk 1.1.0 → 1.1.1
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 +46 -5
- package/dist/index.cjs +350 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +476 -364
- package/dist/index.d.ts +476 -364
- package/dist/index.js +340 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,25 +33,66 @@ console.log(result);
|
|
|
33
33
|
|
|
34
34
|
| Option | Environment variable | Default |
|
|
35
35
|
| --- | --- | --- |
|
|
36
|
-
| `apiKey` | `SECLAI_API_KEY` |
|
|
36
|
+
| `apiKey` | `SECLAI_API_KEY` | — |
|
|
37
|
+
| `accessToken` | — | — |
|
|
38
|
+
| `profile` | `SECLAI_PROFILE` | `"default"` |
|
|
39
|
+
| `configDir` | `SECLAI_CONFIG_DIR` | `~/.seclai` |
|
|
40
|
+
| `autoRefresh` | — | `true` |
|
|
41
|
+
| `accountId` | — | — |
|
|
37
42
|
| `baseUrl` | `SECLAI_API_URL` | `https://api.seclai.com` |
|
|
38
43
|
| `apiKeyHeader` | — | `x-api-key` |
|
|
39
44
|
| `defaultHeaders` | — | `{}` |
|
|
40
45
|
| `fetch` | — | `globalThis.fetch` |
|
|
41
46
|
|
|
47
|
+
### Authentication
|
|
48
|
+
|
|
49
|
+
Credentials are resolved via a chain (first match wins):
|
|
50
|
+
|
|
51
|
+
1. Explicit `apiKey` option
|
|
52
|
+
2. Explicit `accessToken` option (string or `() => string | Promise<string>`)
|
|
53
|
+
3. `SECLAI_API_KEY` environment variable
|
|
54
|
+
4. SSO profile from `~/.seclai/config` with cached tokens in `~/.seclai/sso/cache/`
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// API key
|
|
58
|
+
const client = new Seclai({ apiKey: "sk-..." });
|
|
59
|
+
```
|
|
60
|
+
|
|
42
61
|
```ts
|
|
62
|
+
// Static bearer token
|
|
63
|
+
const client = new Seclai({ accessToken: "eyJhbGciOi..." });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// Dynamic bearer token provider (called per request)
|
|
43
68
|
const client = new Seclai({
|
|
44
|
-
|
|
45
|
-
baseUrl: "https://staging-api.seclai.com",
|
|
46
|
-
defaultHeaders: { "X-Custom": "value" },
|
|
69
|
+
accessToken: async () => fetchTokenFromVault(),
|
|
47
70
|
});
|
|
48
71
|
```
|
|
49
72
|
|
|
73
|
+
```ts
|
|
74
|
+
// SSO profile (uses cached tokens, auto-refreshes)
|
|
75
|
+
const client = new Seclai({ profile: "my-profile" });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// Environment variable (no options needed)
|
|
80
|
+
// export SECLAI_API_KEY="sk-..."
|
|
81
|
+
const client = new Seclai();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
To set up SSO authentication, install the [Seclai CLI](https://www.npmjs.com/package/seclai) and run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
seclai configure sso # set up an SSO profile
|
|
88
|
+
seclai auth login # authenticate via browser
|
|
89
|
+
```
|
|
90
|
+
|
|
50
91
|
## API documentation
|
|
51
92
|
|
|
52
93
|
Online API documentation (latest):
|
|
53
94
|
|
|
54
|
-
https://seclai.github.io/seclai-javascript/1.1.
|
|
95
|
+
https://seclai.github.io/seclai-javascript/1.1.1/
|
|
55
96
|
|
|
56
97
|
## Resources
|
|
57
98
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -80,9 +90,283 @@ var SeclaiStreamingError = class extends SeclaiError {
|
|
|
80
90
|
}
|
|
81
91
|
};
|
|
82
92
|
|
|
93
|
+
// src/auth.ts
|
|
94
|
+
var DEFAULT_CONFIG_DIR = ".seclai";
|
|
95
|
+
var SSO_CACHE_DIR = "sso/cache";
|
|
96
|
+
var CONFIG_FILE = "config";
|
|
97
|
+
var EXPIRY_BUFFER_MS = 3e4;
|
|
98
|
+
var DEFAULT_API_KEY_HEADER = "x-api-key";
|
|
99
|
+
function getEnv(name) {
|
|
100
|
+
const p = globalThis?.process;
|
|
101
|
+
return p?.env?.[name];
|
|
102
|
+
}
|
|
103
|
+
function getHomeDir() {
|
|
104
|
+
const p = globalThis?.process;
|
|
105
|
+
return p?.env?.HOME ?? p?.env?.USERPROFILE;
|
|
106
|
+
}
|
|
107
|
+
async function sha1Hex(input) {
|
|
108
|
+
try {
|
|
109
|
+
const { createHash } = await import("crypto");
|
|
110
|
+
return createHash("sha1").update(input).digest("hex");
|
|
111
|
+
} catch {
|
|
112
|
+
const encoded = new TextEncoder().encode(input);
|
|
113
|
+
const buffer = await crypto.subtle.digest("SHA-1", encoded);
|
|
114
|
+
return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function cacheFileName(domain, clientId) {
|
|
118
|
+
return sha1Hex(`${domain}|${clientId}`);
|
|
119
|
+
}
|
|
120
|
+
function parseIni(content) {
|
|
121
|
+
const sections = {};
|
|
122
|
+
let currentSection = null;
|
|
123
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
124
|
+
const line = rawLine.trim();
|
|
125
|
+
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
|
|
126
|
+
const sectionMatch = line.match(/^\[(.+)\]$/);
|
|
127
|
+
if (sectionMatch) {
|
|
128
|
+
const raw = sectionMatch[1].trim();
|
|
129
|
+
currentSection = raw.startsWith("profile ") ? raw.slice("profile ".length).trim() : raw;
|
|
130
|
+
sections[currentSection] ??= {};
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (currentSection !== null) {
|
|
134
|
+
const eqIdx = line.indexOf("=");
|
|
135
|
+
if (eqIdx > 0) {
|
|
136
|
+
const key = line.slice(0, eqIdx).trim();
|
|
137
|
+
const value = line.slice(eqIdx + 1).trim();
|
|
138
|
+
sections[currentSection][key] = value;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return sections;
|
|
143
|
+
}
|
|
144
|
+
var _fs = null;
|
|
145
|
+
var _path = null;
|
|
146
|
+
async function getFs() {
|
|
147
|
+
if (!_fs) {
|
|
148
|
+
_fs = await import("fs");
|
|
149
|
+
}
|
|
150
|
+
return _fs;
|
|
151
|
+
}
|
|
152
|
+
async function getPath() {
|
|
153
|
+
if (!_path) {
|
|
154
|
+
_path = await import("path");
|
|
155
|
+
}
|
|
156
|
+
return _path;
|
|
157
|
+
}
|
|
158
|
+
async function resolveConfigDir(override) {
|
|
159
|
+
if (override) return override;
|
|
160
|
+
const envDir = getEnv("SECLAI_CONFIG_DIR");
|
|
161
|
+
if (envDir) return envDir;
|
|
162
|
+
const home = getHomeDir();
|
|
163
|
+
if (!home) {
|
|
164
|
+
throw new Error("Cannot determine home directory. Set SECLAI_CONFIG_DIR.");
|
|
165
|
+
}
|
|
166
|
+
const pathMod = await getPath();
|
|
167
|
+
return pathMod.join(home, DEFAULT_CONFIG_DIR);
|
|
168
|
+
}
|
|
169
|
+
async function loadSsoProfile(configDir, profileName) {
|
|
170
|
+
const fs = await getFs();
|
|
171
|
+
const pathMod = await getPath();
|
|
172
|
+
const configPath = pathMod.join(configDir, CONFIG_FILE);
|
|
173
|
+
if (!fs.existsSync(configPath)) return null;
|
|
174
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
175
|
+
const sections = parseIni(content);
|
|
176
|
+
const defaultSection = sections["default"] ?? {};
|
|
177
|
+
const profileSection = profileName === "default" ? defaultSection : sections[profileName];
|
|
178
|
+
if (!profileSection) return null;
|
|
179
|
+
const merged = profileName === "default" ? profileSection : { ...defaultSection, ...profileSection };
|
|
180
|
+
const ssoAccountId = merged["sso_account_id"];
|
|
181
|
+
const ssoRegion = merged["sso_region"];
|
|
182
|
+
const ssoClientId = merged["sso_client_id"];
|
|
183
|
+
const ssoDomain = merged["sso_domain"];
|
|
184
|
+
if (!ssoAccountId || !ssoRegion || !ssoClientId || !ssoDomain) return null;
|
|
185
|
+
return { ssoAccountId, ssoRegion, ssoClientId, ssoDomain };
|
|
186
|
+
}
|
|
187
|
+
async function resolveCachePath(configDir, profile) {
|
|
188
|
+
const pathMod = await getPath();
|
|
189
|
+
const hash = await cacheFileName(profile.ssoDomain, profile.ssoClientId);
|
|
190
|
+
return pathMod.join(configDir, SSO_CACHE_DIR, `${hash}.json`);
|
|
191
|
+
}
|
|
192
|
+
async function readSsoCache(configDir, profile) {
|
|
193
|
+
const fs = await getFs();
|
|
194
|
+
const cachePath = await resolveCachePath(configDir, profile);
|
|
195
|
+
if (!fs.existsSync(cachePath)) return null;
|
|
196
|
+
try {
|
|
197
|
+
const raw = fs.readFileSync(cachePath, "utf-8");
|
|
198
|
+
return JSON.parse(raw);
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function writeSsoCache(configDir, profile, entry) {
|
|
204
|
+
const fs = await getFs();
|
|
205
|
+
const pathMod = await getPath();
|
|
206
|
+
const cacheDir = pathMod.join(configDir, SSO_CACHE_DIR);
|
|
207
|
+
fs.mkdirSync(cacheDir, { recursive: true, mode: 448 });
|
|
208
|
+
const cachePath = await resolveCachePath(configDir, profile);
|
|
209
|
+
const tmpPath = `${cachePath}.tmp`;
|
|
210
|
+
fs.writeFileSync(tmpPath, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
211
|
+
if (fs.existsSync(cachePath)) {
|
|
212
|
+
try {
|
|
213
|
+
fs.unlinkSync(cachePath);
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
fs.renameSync(tmpPath, cachePath);
|
|
218
|
+
}
|
|
219
|
+
function isTokenValid(entry) {
|
|
220
|
+
const expiresAt = new Date(entry.expiresAt).getTime();
|
|
221
|
+
return Date.now() + EXPIRY_BUFFER_MS < expiresAt;
|
|
222
|
+
}
|
|
223
|
+
async function refreshToken(profile, refreshTokenValue, fetcher) {
|
|
224
|
+
const tokenUrl = `https://${profile.ssoDomain}/oauth2/token`;
|
|
225
|
+
const body = new URLSearchParams({
|
|
226
|
+
grant_type: "refresh_token",
|
|
227
|
+
client_id: profile.ssoClientId,
|
|
228
|
+
refresh_token: refreshTokenValue
|
|
229
|
+
});
|
|
230
|
+
const response = await fetcher(tokenUrl, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
233
|
+
body: body.toString()
|
|
234
|
+
});
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const text = await response.text().catch(() => "");
|
|
237
|
+
throw new Error(`Token refresh failed (HTTP ${response.status}): ${text}`);
|
|
238
|
+
}
|
|
239
|
+
const data = await response.json();
|
|
240
|
+
const expiresAt = new Date(Date.now() + data.expires_in * 1e3).toISOString();
|
|
241
|
+
return {
|
|
242
|
+
accessToken: data.access_token,
|
|
243
|
+
refreshToken: data.refresh_token ?? refreshTokenValue,
|
|
244
|
+
idToken: data.id_token ?? void 0,
|
|
245
|
+
expiresAt,
|
|
246
|
+
clientId: profile.ssoClientId,
|
|
247
|
+
region: profile.ssoRegion,
|
|
248
|
+
cognitoDomain: profile.ssoDomain
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async function resolveCredentialChain(opts) {
|
|
252
|
+
const apiKeyHeader = opts.apiKeyHeader ?? DEFAULT_API_KEY_HEADER;
|
|
253
|
+
if (opts.apiKey) {
|
|
254
|
+
return {
|
|
255
|
+
mode: "apiKey",
|
|
256
|
+
apiKey: opts.apiKey,
|
|
257
|
+
apiKeyHeader,
|
|
258
|
+
accountId: opts.accountId,
|
|
259
|
+
autoRefresh: false
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (opts.accessToken) {
|
|
263
|
+
return {
|
|
264
|
+
mode: "bearerStatic",
|
|
265
|
+
accessToken: opts.accessToken,
|
|
266
|
+
apiKeyHeader,
|
|
267
|
+
accountId: opts.accountId,
|
|
268
|
+
autoRefresh: false
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (opts.accessTokenProvider) {
|
|
272
|
+
return {
|
|
273
|
+
mode: "bearerProvider",
|
|
274
|
+
accessTokenProvider: opts.accessTokenProvider,
|
|
275
|
+
apiKeyHeader,
|
|
276
|
+
accountId: opts.accountId,
|
|
277
|
+
autoRefresh: false
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const envApiKey = getEnv("SECLAI_API_KEY");
|
|
281
|
+
if (envApiKey) {
|
|
282
|
+
return {
|
|
283
|
+
mode: "apiKey",
|
|
284
|
+
apiKey: envApiKey,
|
|
285
|
+
apiKeyHeader,
|
|
286
|
+
accountId: opts.accountId,
|
|
287
|
+
autoRefresh: false
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const configDir = await resolveConfigDir(opts.configDir);
|
|
292
|
+
const profileName = opts.profile ?? getEnv("SECLAI_PROFILE") ?? "default";
|
|
293
|
+
const ssoProfile = await loadSsoProfile(configDir, profileName);
|
|
294
|
+
if (ssoProfile) {
|
|
295
|
+
return {
|
|
296
|
+
mode: "sso",
|
|
297
|
+
apiKeyHeader,
|
|
298
|
+
accountId: opts.accountId ?? ssoProfile.ssoAccountId,
|
|
299
|
+
ssoProfile,
|
|
300
|
+
configDir,
|
|
301
|
+
autoRefresh: opts.autoRefresh !== false,
|
|
302
|
+
fetcher: opts.fetch
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
307
|
+
throw new Error(
|
|
308
|
+
"Missing credentials. Provide apiKey, accessToken, set SECLAI_API_KEY, or run `seclai auth login`."
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
async function resolveAuthHeaders(state) {
|
|
312
|
+
const headers = {};
|
|
313
|
+
switch (state.mode) {
|
|
314
|
+
case "apiKey":
|
|
315
|
+
headers[state.apiKeyHeader] = state.apiKey;
|
|
316
|
+
break;
|
|
317
|
+
case "bearerStatic":
|
|
318
|
+
headers["authorization"] = `Bearer ${state.accessToken}`;
|
|
319
|
+
break;
|
|
320
|
+
case "bearerProvider": {
|
|
321
|
+
const token = await Promise.resolve(state.accessTokenProvider());
|
|
322
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case "sso": {
|
|
326
|
+
const token = await resolveSsoToken(state);
|
|
327
|
+
headers["authorization"] = `Bearer ${token}`;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (state.accountId) {
|
|
332
|
+
headers["x-account-id"] = state.accountId;
|
|
333
|
+
}
|
|
334
|
+
return headers;
|
|
335
|
+
}
|
|
336
|
+
async function resolveSsoToken(state) {
|
|
337
|
+
const profile = state.ssoProfile;
|
|
338
|
+
const configDir = state.configDir;
|
|
339
|
+
const cached = await readSsoCache(configDir, profile);
|
|
340
|
+
if (cached && isTokenValid(cached)) {
|
|
341
|
+
return cached.accessToken;
|
|
342
|
+
}
|
|
343
|
+
if (cached?.refreshToken && state.autoRefresh) {
|
|
344
|
+
if (state._refreshPromise) {
|
|
345
|
+
return state._refreshPromise;
|
|
346
|
+
}
|
|
347
|
+
const fetcher = state.fetcher ?? globalThis.fetch;
|
|
348
|
+
if (!fetcher) {
|
|
349
|
+
throw new Error("No fetch implementation available for token refresh.");
|
|
350
|
+
}
|
|
351
|
+
state._refreshPromise = (async () => {
|
|
352
|
+
try {
|
|
353
|
+
const refreshed = await refreshToken(profile, cached.refreshToken, fetcher);
|
|
354
|
+
await writeSsoCache(configDir, profile, refreshed);
|
|
355
|
+
return refreshed.accessToken;
|
|
356
|
+
} finally {
|
|
357
|
+
state._refreshPromise = void 0;
|
|
358
|
+
}
|
|
359
|
+
})();
|
|
360
|
+
return state._refreshPromise;
|
|
361
|
+
}
|
|
362
|
+
throw new Error(
|
|
363
|
+
`SSO token expired. Run \`seclai auth login\` to re-authenticate.`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
83
367
|
// src/client.ts
|
|
84
368
|
var SECLAI_API_URL = "https://api.seclai.com";
|
|
85
|
-
function
|
|
369
|
+
function getEnv2(name) {
|
|
86
370
|
const p = globalThis?.process;
|
|
87
371
|
return p?.env?.[name];
|
|
88
372
|
}
|
|
@@ -209,23 +493,29 @@ function inferMimeType(fileName) {
|
|
|
209
493
|
return ext ? MIME_TYPES[ext] : void 0;
|
|
210
494
|
}
|
|
211
495
|
var Seclai = class {
|
|
212
|
-
apiKey;
|
|
213
496
|
baseUrl;
|
|
214
|
-
apiKeyHeader;
|
|
215
497
|
defaultHeaders;
|
|
216
498
|
fetcher;
|
|
499
|
+
_authState = null;
|
|
500
|
+
_authInitPromise = null;
|
|
501
|
+
_authInitError = null;
|
|
217
502
|
/**
|
|
218
503
|
* Create a new Seclai client.
|
|
219
504
|
*
|
|
505
|
+
* Credentials are resolved via a chain (first match wins):
|
|
506
|
+
* 1. Explicit `apiKey` option
|
|
507
|
+
* 2. Explicit `accessToken` option (static string or provider function)
|
|
508
|
+
* 3. `SECLAI_API_KEY` environment variable
|
|
509
|
+
* 4. SSO profile from `~/.seclai/config` + cached tokens in `~/.seclai/sso/cache/`
|
|
510
|
+
*
|
|
220
511
|
* @param opts - Client configuration.
|
|
221
|
-
* @throws {@link SeclaiConfigurationError} If no API key is provided (and `SECLAI_API_KEY` is not set).
|
|
222
512
|
* @throws {@link SeclaiConfigurationError} If no `fetch` implementation is available.
|
|
513
|
+
* @throws {@link SeclaiConfigurationError} If both `apiKey` and `accessToken` are provided.
|
|
223
514
|
*/
|
|
224
515
|
constructor(opts = {}) {
|
|
225
|
-
|
|
226
|
-
if (!apiKey) {
|
|
516
|
+
if (opts.apiKey && opts.accessToken) {
|
|
227
517
|
throw new SeclaiConfigurationError(
|
|
228
|
-
"
|
|
518
|
+
"Provide either apiKey or accessToken, not both."
|
|
229
519
|
);
|
|
230
520
|
}
|
|
231
521
|
const fetcher = opts.fetch ?? globalThis.fetch;
|
|
@@ -234,11 +524,49 @@ var Seclai = class {
|
|
|
234
524
|
"No fetch implementation available. Provide opts.fetch or run in an environment with global fetch."
|
|
235
525
|
);
|
|
236
526
|
}
|
|
237
|
-
this.
|
|
238
|
-
this.baseUrl = opts.baseUrl ?? getEnv("SECLAI_API_URL") ?? SECLAI_API_URL;
|
|
239
|
-
this.apiKeyHeader = opts.apiKeyHeader ?? "x-api-key";
|
|
527
|
+
this.baseUrl = opts.baseUrl ?? getEnv2("SECLAI_API_URL") ?? SECLAI_API_URL;
|
|
240
528
|
this.defaultHeaders = { ...opts.defaultHeaders ?? {} };
|
|
241
529
|
this.fetcher = fetcher;
|
|
530
|
+
const accessTokenProvider = typeof opts.accessToken === "function" ? opts.accessToken : void 0;
|
|
531
|
+
const accessTokenStatic = typeof opts.accessToken === "string" ? opts.accessToken : void 0;
|
|
532
|
+
this._authInitPromise = resolveCredentialChain({
|
|
533
|
+
apiKey: opts.apiKey,
|
|
534
|
+
accessToken: accessTokenStatic,
|
|
535
|
+
accessTokenProvider,
|
|
536
|
+
profile: opts.profile,
|
|
537
|
+
configDir: opts.configDir,
|
|
538
|
+
autoRefresh: opts.autoRefresh,
|
|
539
|
+
accountId: opts.accountId,
|
|
540
|
+
apiKeyHeader: opts.apiKeyHeader,
|
|
541
|
+
fetch: fetcher
|
|
542
|
+
}).then((state) => {
|
|
543
|
+
this._authState = state;
|
|
544
|
+
}).catch((err) => {
|
|
545
|
+
this._authInitError = new SeclaiConfigurationError(
|
|
546
|
+
err instanceof Error ? err.message : String(err)
|
|
547
|
+
);
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
/** Ensure the credential chain has been resolved. */
|
|
551
|
+
async ensureAuth() {
|
|
552
|
+
if (this._authInitPromise) {
|
|
553
|
+
await this._authInitPromise;
|
|
554
|
+
this._authInitPromise = null;
|
|
555
|
+
}
|
|
556
|
+
if (this._authInitError) {
|
|
557
|
+
throw this._authInitError;
|
|
558
|
+
}
|
|
559
|
+
if (!this._authState) {
|
|
560
|
+
throw new SeclaiConfigurationError(
|
|
561
|
+
"Missing credentials. Provide apiKey, accessToken, set SECLAI_API_KEY, or run `seclai auth login`."
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
return this._authState;
|
|
565
|
+
}
|
|
566
|
+
/** Resolve auth headers for the current request. */
|
|
567
|
+
async authHeaders() {
|
|
568
|
+
const state = await this.ensureAuth();
|
|
569
|
+
return resolveAuthHeaders(state);
|
|
242
570
|
}
|
|
243
571
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
244
572
|
// Low-level request
|
|
@@ -257,10 +585,11 @@ var Seclai = class {
|
|
|
257
585
|
*/
|
|
258
586
|
async request(method, path, opts) {
|
|
259
587
|
const url = buildURL(this.baseUrl, path, opts?.query);
|
|
588
|
+
const authHeaders = await this.authHeaders();
|
|
260
589
|
const headers = {
|
|
261
590
|
...this.defaultHeaders,
|
|
262
591
|
...opts?.headers ?? {},
|
|
263
|
-
|
|
592
|
+
...authHeaders
|
|
264
593
|
};
|
|
265
594
|
let body;
|
|
266
595
|
if (opts?.json !== void 0) {
|
|
@@ -312,13 +641,16 @@ var Seclai = class {
|
|
|
312
641
|
* @param path - Request path relative to `baseUrl`.
|
|
313
642
|
* @param opts - Query params, JSON body, per-request headers, and optional AbortSignal.
|
|
314
643
|
* @returns The raw `Response` object.
|
|
644
|
+
* @throws {SeclaiAPIValidationError} On HTTP 422 responses.
|
|
645
|
+
* @throws {SeclaiAPIStatusError} On other non-2xx responses.
|
|
315
646
|
*/
|
|
316
647
|
async requestRaw(method, path, opts) {
|
|
317
648
|
const url = buildURL(this.baseUrl, path, opts?.query);
|
|
649
|
+
const authHeaders = await this.authHeaders();
|
|
318
650
|
const headers = {
|
|
319
651
|
...this.defaultHeaders,
|
|
320
652
|
...opts?.headers ?? {},
|
|
321
|
-
|
|
653
|
+
...authHeaders
|
|
322
654
|
};
|
|
323
655
|
let body;
|
|
324
656
|
if (opts?.json !== void 0) {
|
|
@@ -355,9 +687,10 @@ var Seclai = class {
|
|
|
355
687
|
/** Shared multipart upload helper. */
|
|
356
688
|
async uploadFile(path, opts) {
|
|
357
689
|
const url = buildURL(this.baseUrl, path);
|
|
690
|
+
const authHeaders = await this.authHeaders();
|
|
358
691
|
const headers = {
|
|
359
692
|
...this.defaultHeaders,
|
|
360
|
-
|
|
693
|
+
...authHeaders
|
|
361
694
|
};
|
|
362
695
|
delete headers["content-type"];
|
|
363
696
|
delete headers["Content-Type"];
|
|
@@ -545,9 +878,10 @@ var Seclai = class {
|
|
|
545
878
|
*/
|
|
546
879
|
async runStreamingAgentAndWait(agentId, body, opts) {
|
|
547
880
|
const url = buildURL(this.baseUrl, `/agents/${agentId}/runs/stream`);
|
|
881
|
+
const authHdrs = await this.authHeaders();
|
|
548
882
|
const headers = {
|
|
549
883
|
...this.defaultHeaders,
|
|
550
|
-
|
|
884
|
+
...authHdrs,
|
|
551
885
|
accept: "text/event-stream",
|
|
552
886
|
"content-type": "application/json"
|
|
553
887
|
};
|
|
@@ -654,9 +988,10 @@ var Seclai = class {
|
|
|
654
988
|
*/
|
|
655
989
|
async *runStreamingAgent(agentId, body, opts) {
|
|
656
990
|
const url = buildURL(this.baseUrl, `/agents/${agentId}/runs/stream`);
|
|
991
|
+
const authHdrs = await this.authHeaders();
|
|
657
992
|
const headers = {
|
|
658
993
|
...this.defaultHeaders,
|
|
659
|
-
|
|
994
|
+
...authHdrs,
|
|
660
995
|
accept: "text/event-stream",
|
|
661
996
|
"content-type": "application/json"
|
|
662
997
|
};
|