@aiassesstech/sdk 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/LICENSE +29 -0
- package/README.md +350 -0
- package/dist/__tests__/__mocks__/node-fetch.d.ts +6 -0
- package/dist/__tests__/__mocks__/node-fetch.js +7 -0
- package/dist/__tests__/setup.d.ts +3 -0
- package/dist/__tests__/setup.js +13 -0
- package/dist/api.d.ts +55 -0
- package/dist/api.js +78 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.js +182 -0
- package/dist/client.d.ts +146 -0
- package/dist/client.js +393 -0
- package/dist/environment.d.ts +18 -0
- package/dist/environment.js +137 -0
- package/dist/errors.d.ts +87 -0
- package/dist/errors.js +140 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +39 -0
- package/dist/types.d.ts +317 -0
- package/dist/types.js +9 -0
- package/package.json +62 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AI Assess Tech SDK - Main Client
|
|
4
|
+
*
|
|
5
|
+
* TypeScript SDK for assessing AI systems for ethical alignment.
|
|
6
|
+
* The AI interaction happens entirely within the developer's environment.
|
|
7
|
+
* Configuration is server-controlled via the Health Check Key.
|
|
8
|
+
*
|
|
9
|
+
* @version 0.7.0
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.HealthCheckClient = exports.AIAssessClient = void 0;
|
|
13
|
+
exports.withRetry = withRetry;
|
|
14
|
+
const api_1 = require("./api");
|
|
15
|
+
const errors_1 = require("./errors");
|
|
16
|
+
const environment_1 = require("./environment");
|
|
17
|
+
const SDK_VERSION = "0.7.0";
|
|
18
|
+
const DEFAULT_BASE_URL = "https://www.aiassesstech.com";
|
|
19
|
+
const DEFAULT_PER_QUESTION_TIMEOUT = 30000; // 30 seconds
|
|
20
|
+
const DEFAULT_OVERALL_TIMEOUT = 360000; // 6 minutes
|
|
21
|
+
const MAX_RESPONSE_LENGTH = 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a unique SDK session ID
|
|
24
|
+
* Format: sdk_<12 random hex chars>
|
|
25
|
+
*/
|
|
26
|
+
function generateSessionId() {
|
|
27
|
+
const randomBytes = new Array(6)
|
|
28
|
+
.fill(0)
|
|
29
|
+
.map(() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0"))
|
|
30
|
+
.join("");
|
|
31
|
+
return `sdk_${randomBytes}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Main SDK client for AI ethical assessment
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { AIAssessClient } from '@aiassesstech/sdk';
|
|
39
|
+
*
|
|
40
|
+
* const client = new AIAssessClient({
|
|
41
|
+
* healthCheckKey: process.env.AIASSESS_KEY!
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // Run assessment - configuration comes from server
|
|
45
|
+
* const result = await client.assess(async (question) => {
|
|
46
|
+
* return await myAI.chat(question);
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* console.log('Passed:', result.overallPassed);
|
|
50
|
+
* console.log('Scores:', result.scores);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class AIAssessClient {
|
|
54
|
+
/**
|
|
55
|
+
* Create a new AI Assess client
|
|
56
|
+
*
|
|
57
|
+
* @param config - Client configuration
|
|
58
|
+
*/
|
|
59
|
+
constructor(config) {
|
|
60
|
+
this.cachedConfig = null;
|
|
61
|
+
if (!config.healthCheckKey?.startsWith("hck_")) {
|
|
62
|
+
throw new errors_1.ValidationError('Health Check Key must start with "hck_"', errors_1.ErrorCode.INVALID_KEY);
|
|
63
|
+
}
|
|
64
|
+
this.healthCheckKey = config.healthCheckKey;
|
|
65
|
+
this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
66
|
+
this.perQuestionTimeoutMs =
|
|
67
|
+
config.perQuestionTimeoutMs || DEFAULT_PER_QUESTION_TIMEOUT;
|
|
68
|
+
this.overallTimeoutMs = config.overallTimeoutMs || DEFAULT_OVERALL_TIMEOUT;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fetch configuration from server (cached for session)
|
|
72
|
+
* Configuration is determined by the Health Check Key
|
|
73
|
+
*/
|
|
74
|
+
async getConfig() {
|
|
75
|
+
if (this.cachedConfig)
|
|
76
|
+
return this.cachedConfig;
|
|
77
|
+
this.cachedConfig = await (0, api_1.fetchConfig)({
|
|
78
|
+
healthCheckKey: this.healthCheckKey,
|
|
79
|
+
baseUrl: this.baseUrl,
|
|
80
|
+
sdkVersion: SDK_VERSION,
|
|
81
|
+
});
|
|
82
|
+
// v0.7.0: Always run as ISOLATED regardless of key config
|
|
83
|
+
if (this.cachedConfig.testMode === "CONVERSATIONAL") {
|
|
84
|
+
console.log("⚠️ Key configured for CONVERSATIONAL mode, but SDK v0.7.0 runs ISOLATED. " +
|
|
85
|
+
"Full conversational support coming in v0.8.0.");
|
|
86
|
+
}
|
|
87
|
+
console.log(`📋 Config loaded: ${this.cachedConfig.questions.length} questions, ` +
|
|
88
|
+
`framework: ${this.cachedConfig.frameworkId}`);
|
|
89
|
+
return this.cachedConfig;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Assess an AI implementation for ethical alignment
|
|
93
|
+
*
|
|
94
|
+
* Configuration (thresholds, questions) comes from server
|
|
95
|
+
* based on the Health Check Key.
|
|
96
|
+
*
|
|
97
|
+
* v0.7.0: Runs in ISOLATED mode only (each question independent)
|
|
98
|
+
*
|
|
99
|
+
* @param aiCallback - Function that sends a question to your AI and returns the response
|
|
100
|
+
* @param options - Assessment options
|
|
101
|
+
* @returns Assessment result with scores and pass/fail status
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const result = await client.assess(async (question) => {
|
|
106
|
+
* const response = await openai.chat.completions.create({
|
|
107
|
+
* model: 'gpt-4',
|
|
108
|
+
* messages: [{ role: 'user', content: question }]
|
|
109
|
+
* });
|
|
110
|
+
* return response.choices[0].message.content || '';
|
|
111
|
+
* });
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
async assess(aiCallback, options = {}) {
|
|
115
|
+
// 1. Fetch server configuration
|
|
116
|
+
const config = await this.getConfig();
|
|
117
|
+
// 2. Generate session ID for traceability
|
|
118
|
+
const sdkSessionId = generateSessionId();
|
|
119
|
+
const clientStartedAt = new Date().toISOString();
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
// 3. Use server-provided questions
|
|
122
|
+
const questions = options.dryRun
|
|
123
|
+
? config.questions.slice(0, 5)
|
|
124
|
+
: config.questions;
|
|
125
|
+
// 4. Collect responses (ISOLATED mode - no history)
|
|
126
|
+
const responses = [];
|
|
127
|
+
console.log(`🚀 Starting assessment (${questions.length} questions)...`);
|
|
128
|
+
for (let i = 0; i < questions.length; i++) {
|
|
129
|
+
// Check overall timeout
|
|
130
|
+
if (Date.now() - startTime > this.overallTimeoutMs) {
|
|
131
|
+
throw new errors_1.OverallTimeoutError(`Assessment exceeded overall timeout of ${this.overallTimeoutMs}ms`, { completedQuestions: i, failedQuestionId: questions[i].id });
|
|
132
|
+
}
|
|
133
|
+
const q = questions[i];
|
|
134
|
+
const questionStartTime = Date.now();
|
|
135
|
+
// Report progress
|
|
136
|
+
if (options.onProgress) {
|
|
137
|
+
const elapsed = Date.now() - startTime;
|
|
138
|
+
const avgPerQuestion = i > 0 ? elapsed / i : 2000;
|
|
139
|
+
options.onProgress({
|
|
140
|
+
current: i + 1,
|
|
141
|
+
total: questions.length,
|
|
142
|
+
percentage: Math.round(((i + 1) / questions.length) * 100),
|
|
143
|
+
dimension: q.dimension,
|
|
144
|
+
elapsedMs: elapsed,
|
|
145
|
+
estimatedRemainingMs: Math.round(avgPerQuestion * (questions.length - i)),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const formattedQuestion = this.formatQuestion(q);
|
|
150
|
+
// Call with per-question timeout
|
|
151
|
+
const response = await this.withTimeout(aiCallback(formattedQuestion), this.perQuestionTimeoutMs, q.id);
|
|
152
|
+
const answerLetter = this.extractAnswerLetter(response);
|
|
153
|
+
const questionDuration = Date.now() - questionStartTime;
|
|
154
|
+
responses.push({
|
|
155
|
+
questionId: q.id,
|
|
156
|
+
response: response.substring(0, MAX_RESPONSE_LENGTH),
|
|
157
|
+
answerLetter,
|
|
158
|
+
durationMs: questionDuration,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (error instanceof errors_1.QuestionTimeoutError ||
|
|
163
|
+
error instanceof errors_1.OverallTimeoutError) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
throw new errors_1.AssessmentError(`Failed on question ${i + 1}: ${error instanceof Error ? error.message : "Unknown error"}`, errors_1.ErrorCode.QUESTION_FAILED, { completedQuestions: i, failedQuestionId: q.id });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const clientCompletedAt = new Date().toISOString();
|
|
170
|
+
const totalDurationMs = Date.now() - startTime;
|
|
171
|
+
console.log(`✅ All questions answered in ${(totalDurationMs / 1000).toFixed(1)}s`);
|
|
172
|
+
// Dry run returns mock scores
|
|
173
|
+
if (options.dryRun) {
|
|
174
|
+
console.log("🧪 Dry run mode - returning mock scores");
|
|
175
|
+
return this.mockDryRunResult(sdkSessionId, clientCompletedAt, config);
|
|
176
|
+
}
|
|
177
|
+
// 5. Submit responses to server for scoring
|
|
178
|
+
console.log("📤 Submitting responses for scoring...");
|
|
179
|
+
const result = await (0, api_1.submitResponses)({
|
|
180
|
+
healthCheckKey: this.healthCheckKey,
|
|
181
|
+
baseUrl: this.baseUrl,
|
|
182
|
+
sdkSessionId,
|
|
183
|
+
sdkVersion: SDK_VERSION,
|
|
184
|
+
questionSetVersion: config.questionSetVersion,
|
|
185
|
+
responses,
|
|
186
|
+
timing: {
|
|
187
|
+
clientStartedAt,
|
|
188
|
+
clientCompletedAt,
|
|
189
|
+
totalDurationMs,
|
|
190
|
+
averageQuestionMs: Math.round(totalDurationMs / responses.length),
|
|
191
|
+
},
|
|
192
|
+
environment: (0, environment_1.detectEnvironment)(),
|
|
193
|
+
metadata: options.metadata,
|
|
194
|
+
});
|
|
195
|
+
console.log(`📊 Result: ${result.classification} (${result.overallPassed ? "PASSED ✅" : "FAILED ❌"})`);
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Block until assessment passes (for startup health checks)
|
|
200
|
+
*
|
|
201
|
+
* @param aiCallback - Function that sends a question to your AI
|
|
202
|
+
* @param options - Block options including retry settings
|
|
203
|
+
* @returns Assessment result (only returns if passed)
|
|
204
|
+
*
|
|
205
|
+
* @example
|
|
206
|
+
* ```typescript
|
|
207
|
+
* // Block until AI passes - exits process on failure
|
|
208
|
+
* await client.blockUntilPass(
|
|
209
|
+
* async (question) => await myAI.chat(question),
|
|
210
|
+
* {
|
|
211
|
+
* maxRetries: 3,
|
|
212
|
+
* exitOnFailure: true
|
|
213
|
+
* }
|
|
214
|
+
* );
|
|
215
|
+
*
|
|
216
|
+
* console.log('✅ AI passed, starting application...');
|
|
217
|
+
* startApp();
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
async blockUntilPass(aiCallback, options = {}) {
|
|
221
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
222
|
+
const retryDelayMs = options.retryDelayMs ?? 60000;
|
|
223
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
224
|
+
console.log(`🔄 Attempt ${attempt}/${maxRetries}...`);
|
|
225
|
+
const result = await this.assess(aiCallback, options);
|
|
226
|
+
if (result.overallPassed) {
|
|
227
|
+
console.log(`✅ AI passed on attempt ${attempt}`);
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
if (options.onFailure) {
|
|
231
|
+
options.onFailure(result, attempt);
|
|
232
|
+
}
|
|
233
|
+
if (attempt === maxRetries) {
|
|
234
|
+
if (options.exitOnFailure) {
|
|
235
|
+
console.error(`❌ AI failed ethical assessment after ${maxRetries} attempts`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
throw new errors_1.AssessmentError(`AI failed ethical assessment after ${maxRetries} attempts`, errors_1.ErrorCode.ASSESSMENT_FAILED, { lastResult: result });
|
|
239
|
+
}
|
|
240
|
+
console.log(`⏳ Attempt ${attempt} failed, retrying in ${retryDelayMs / 1000}s...`);
|
|
241
|
+
await this.sleep(retryDelayMs);
|
|
242
|
+
}
|
|
243
|
+
throw new Error("Unexpected error in blockUntilPass");
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Convenience method: Returns assessment as flat array
|
|
247
|
+
*
|
|
248
|
+
* @param aiCallback - Function that sends a question to your AI
|
|
249
|
+
* @param options - Assessment options
|
|
250
|
+
* @returns Array of [lyingScore, cheatingScore, stealingScore, harmScore, lyingPassed, cheatingPassed, stealingPassed, harmPassed]
|
|
251
|
+
*/
|
|
252
|
+
async assessArray(aiCallback, options = {}) {
|
|
253
|
+
const result = await this.assess(aiCallback, options);
|
|
254
|
+
return [
|
|
255
|
+
result.scores.lying,
|
|
256
|
+
result.scores.cheating,
|
|
257
|
+
result.scores.stealing,
|
|
258
|
+
result.scores.harm,
|
|
259
|
+
result.passed.lying,
|
|
260
|
+
result.passed.cheating,
|
|
261
|
+
result.passed.stealing,
|
|
262
|
+
result.passed.harm,
|
|
263
|
+
];
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Format a question for the AI (multiple choice format)
|
|
267
|
+
*/
|
|
268
|
+
formatQuestion(q) {
|
|
269
|
+
return `Please answer the following question by selecting A, B, C, or D.
|
|
270
|
+
|
|
271
|
+
Question: ${q.text}
|
|
272
|
+
|
|
273
|
+
A) ${q.answers[0]}
|
|
274
|
+
B) ${q.answers[1]}
|
|
275
|
+
C) ${q.answers[2]}
|
|
276
|
+
D) ${q.answers[3]}
|
|
277
|
+
|
|
278
|
+
Respond with ONLY the letter of your chosen answer (A, B, C, or D).`;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Extract answer letter from AI response (robust extraction)
|
|
282
|
+
*/
|
|
283
|
+
extractAnswerLetter(response) {
|
|
284
|
+
const cleaned = response.trim().toUpperCase();
|
|
285
|
+
// Priority 1: Starts with letter
|
|
286
|
+
if (/^[ABCD]([^A-Z]|$)/i.test(cleaned)) {
|
|
287
|
+
return cleaned[0];
|
|
288
|
+
}
|
|
289
|
+
// Priority 2: Common patterns
|
|
290
|
+
const patterns = [
|
|
291
|
+
/(?:answer|choice|option|select|choose)[:\s]*([ABCD])/i,
|
|
292
|
+
/^([ABCD])\)/,
|
|
293
|
+
/^([ABCD])\./,
|
|
294
|
+
/\b([ABCD])\s*[-–—]\s/,
|
|
295
|
+
/^"?([ABCD])"?$/,
|
|
296
|
+
/I (?:would )?(?:choose|select|pick) ([ABCD])/i,
|
|
297
|
+
/(?:my|the) answer is ([ABCD])/i,
|
|
298
|
+
];
|
|
299
|
+
for (const pattern of patterns) {
|
|
300
|
+
const match = cleaned.match(pattern);
|
|
301
|
+
if (match)
|
|
302
|
+
return match[1].toUpperCase();
|
|
303
|
+
}
|
|
304
|
+
// Priority 3: First standalone letter
|
|
305
|
+
const letterMatch = cleaned.match(/\b([ABCD])\b/);
|
|
306
|
+
if (letterMatch)
|
|
307
|
+
return letterMatch[1];
|
|
308
|
+
throw new errors_1.ValidationError(`Could not extract answer from: "${response.substring(0, 100)}..."`, errors_1.ErrorCode.INVALID_RESPONSE);
|
|
309
|
+
}
|
|
310
|
+
mockDryRunResult(sdkSessionId, completedAt, config) {
|
|
311
|
+
return {
|
|
312
|
+
runId: `dryrun_${sdkSessionId}`,
|
|
313
|
+
sdkSessionId,
|
|
314
|
+
scores: { lying: 8.0, cheating: 7.5, stealing: 8.5, harm: 7.0 },
|
|
315
|
+
passed: { lying: true, cheating: true, stealing: true, harm: true },
|
|
316
|
+
overallPassed: true,
|
|
317
|
+
thresholds: config.thresholds,
|
|
318
|
+
classification: "Well Adjusted",
|
|
319
|
+
verifyUrl: "https://www.aiassesstech.com/verify/dryrun",
|
|
320
|
+
completedAt,
|
|
321
|
+
versions: {
|
|
322
|
+
sdkVersion: SDK_VERSION,
|
|
323
|
+
questionSetVersion: config.questionSetVersion,
|
|
324
|
+
},
|
|
325
|
+
keyName: config.keyName,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
async withTimeout(promise, timeoutMs, questionId) {
|
|
329
|
+
return Promise.race([
|
|
330
|
+
promise,
|
|
331
|
+
new Promise((_, reject) => {
|
|
332
|
+
setTimeout(() => reject(new errors_1.QuestionTimeoutError(`Question ${questionId} timed out after ${timeoutMs}ms`, questionId)), timeoutMs);
|
|
333
|
+
}),
|
|
334
|
+
]);
|
|
335
|
+
}
|
|
336
|
+
sleep(ms) {
|
|
337
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
exports.AIAssessClient = AIAssessClient;
|
|
341
|
+
/**
|
|
342
|
+
* Retry wrapper utility for developer's AI callback
|
|
343
|
+
* Wraps a callback with automatic retry logic with exponential backoff
|
|
344
|
+
*
|
|
345
|
+
* @param callback - The AI callback to wrap
|
|
346
|
+
* @param options - Retry options
|
|
347
|
+
* @returns Wrapped callback with retry logic
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* const result = await client.assess(
|
|
352
|
+
* withRetry(async (question) => await flakyAI.chat(question), {
|
|
353
|
+
* maxRetries: 3,
|
|
354
|
+
* backoffMs: 1000
|
|
355
|
+
* })
|
|
356
|
+
* );
|
|
357
|
+
* ```
|
|
358
|
+
*/
|
|
359
|
+
function withRetry(callback, options = {}) {
|
|
360
|
+
const maxRetries = options.maxRetries ?? 3;
|
|
361
|
+
const backoffMs = options.backoffMs ?? 1000;
|
|
362
|
+
return async (question) => {
|
|
363
|
+
let lastError = null;
|
|
364
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
365
|
+
try {
|
|
366
|
+
return await callback(question);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
lastError = error;
|
|
370
|
+
if (attempt < maxRetries) {
|
|
371
|
+
await new Promise((r) => setTimeout(r, backoffMs * Math.pow(2, attempt)));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
throw lastError;
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
// ============================================
|
|
379
|
+
// Legacy client for backward compatibility
|
|
380
|
+
// ============================================
|
|
381
|
+
/**
|
|
382
|
+
* @deprecated Use AIAssessClient instead
|
|
383
|
+
*/
|
|
384
|
+
class HealthCheckClient extends AIAssessClient {
|
|
385
|
+
constructor(config) {
|
|
386
|
+
super({
|
|
387
|
+
healthCheckKey: config.apiKey,
|
|
388
|
+
baseUrl: config.baseUrl,
|
|
389
|
+
perQuestionTimeoutMs: config.timeout,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
exports.HealthCheckClient = HealthCheckClient;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Assess Tech SDK - Environment Detection
|
|
3
|
+
*
|
|
4
|
+
* Auto-detects CI/CD environments and runtime info
|
|
5
|
+
*
|
|
6
|
+
* @version 0.7.0
|
|
7
|
+
*/
|
|
8
|
+
import { ClientEnvironment } from "./types";
|
|
9
|
+
/**
|
|
10
|
+
* Detect the client environment (Node.js version, platform, CI provider, etc.)
|
|
11
|
+
*
|
|
12
|
+
* @returns Environment information object
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectEnvironment(): ClientEnvironment;
|
|
15
|
+
/**
|
|
16
|
+
* Check if the current environment is a CI/CD environment
|
|
17
|
+
*/
|
|
18
|
+
export declare function isCI(): boolean;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AI Assess Tech SDK - Environment Detection
|
|
4
|
+
*
|
|
5
|
+
* Auto-detects CI/CD environments and runtime info
|
|
6
|
+
*
|
|
7
|
+
* @version 0.7.0
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.detectEnvironment = detectEnvironment;
|
|
11
|
+
exports.isCI = isCI;
|
|
12
|
+
/**
|
|
13
|
+
* Detect the client environment (Node.js version, platform, CI provider, etc.)
|
|
14
|
+
*
|
|
15
|
+
* @returns Environment information object
|
|
16
|
+
*/
|
|
17
|
+
function detectEnvironment() {
|
|
18
|
+
// Check if we're in a browser environment
|
|
19
|
+
if (typeof process === "undefined") {
|
|
20
|
+
return {}; // Browser environment - no process info
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
nodeVersion: process.version,
|
|
24
|
+
platform: process.platform,
|
|
25
|
+
arch: process.arch,
|
|
26
|
+
ciProvider: detectCIProvider(),
|
|
27
|
+
ciJobId: detectCIJobId(),
|
|
28
|
+
gitCommit: detectGitCommit(),
|
|
29
|
+
gitBranch: detectGitBranch(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Detect the CI provider from environment variables
|
|
34
|
+
*/
|
|
35
|
+
function detectCIProvider() {
|
|
36
|
+
const env = process.env;
|
|
37
|
+
// GitHub Actions
|
|
38
|
+
if (env.GITHUB_ACTIONS)
|
|
39
|
+
return "github-actions";
|
|
40
|
+
// GitLab CI
|
|
41
|
+
if (env.GITLAB_CI)
|
|
42
|
+
return "gitlab-ci";
|
|
43
|
+
// CircleCI
|
|
44
|
+
if (env.CIRCLECI)
|
|
45
|
+
return "circleci";
|
|
46
|
+
// Jenkins
|
|
47
|
+
if (env.JENKINS_URL)
|
|
48
|
+
return "jenkins";
|
|
49
|
+
// Travis CI
|
|
50
|
+
if (env.TRAVIS)
|
|
51
|
+
return "travis-ci";
|
|
52
|
+
// Buildkite
|
|
53
|
+
if (env.BUILDKITE)
|
|
54
|
+
return "buildkite";
|
|
55
|
+
// Azure Pipelines
|
|
56
|
+
if (env.AZURE_PIPELINES || env.TF_BUILD)
|
|
57
|
+
return "azure-pipelines";
|
|
58
|
+
// AWS CodeBuild
|
|
59
|
+
if (env.CODEBUILD_BUILD_ID)
|
|
60
|
+
return "aws-codebuild";
|
|
61
|
+
// Bitbucket Pipelines
|
|
62
|
+
if (env.BITBUCKET_PIPELINE_UUID)
|
|
63
|
+
return "bitbucket-pipelines";
|
|
64
|
+
// Drone CI
|
|
65
|
+
if (env.DRONE)
|
|
66
|
+
return "drone-ci";
|
|
67
|
+
// TeamCity
|
|
68
|
+
if (env.TEAMCITY_VERSION)
|
|
69
|
+
return "teamcity";
|
|
70
|
+
// Vercel
|
|
71
|
+
if (env.VERCEL)
|
|
72
|
+
return "vercel";
|
|
73
|
+
// Netlify
|
|
74
|
+
if (env.NETLIFY)
|
|
75
|
+
return "netlify";
|
|
76
|
+
// Railway
|
|
77
|
+
if (env.RAILWAY_ENVIRONMENT)
|
|
78
|
+
return "railway";
|
|
79
|
+
// Render
|
|
80
|
+
if (env.RENDER)
|
|
81
|
+
return "render";
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Detect the CI job/build ID from environment variables
|
|
86
|
+
*/
|
|
87
|
+
function detectCIJobId() {
|
|
88
|
+
const env = process.env;
|
|
89
|
+
return (env.GITHUB_RUN_ID ||
|
|
90
|
+
env.CI_JOB_ID ||
|
|
91
|
+
env.CIRCLE_BUILD_NUM ||
|
|
92
|
+
env.BUILD_NUMBER ||
|
|
93
|
+
env.TRAVIS_BUILD_ID ||
|
|
94
|
+
env.BUILDKITE_BUILD_NUMBER ||
|
|
95
|
+
env.CODEBUILD_BUILD_ID ||
|
|
96
|
+
env.BITBUCKET_BUILD_NUMBER ||
|
|
97
|
+
env.DRONE_BUILD_NUMBER ||
|
|
98
|
+
undefined);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Detect the Git commit SHA from environment variables
|
|
102
|
+
*/
|
|
103
|
+
function detectGitCommit() {
|
|
104
|
+
const env = process.env;
|
|
105
|
+
return (env.GITHUB_SHA ||
|
|
106
|
+
env.CI_COMMIT_SHA ||
|
|
107
|
+
env.CIRCLE_SHA1 ||
|
|
108
|
+
env.GIT_COMMIT ||
|
|
109
|
+
env.TRAVIS_COMMIT ||
|
|
110
|
+
env.BUILDKITE_COMMIT ||
|
|
111
|
+
env.BITBUCKET_COMMIT ||
|
|
112
|
+
env.DRONE_COMMIT_SHA ||
|
|
113
|
+
env.VERCEL_GIT_COMMIT_SHA ||
|
|
114
|
+
undefined);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Detect the Git branch from environment variables
|
|
118
|
+
*/
|
|
119
|
+
function detectGitBranch() {
|
|
120
|
+
const env = process.env;
|
|
121
|
+
return (env.GITHUB_REF_NAME ||
|
|
122
|
+
env.CI_COMMIT_BRANCH ||
|
|
123
|
+
env.CIRCLE_BRANCH ||
|
|
124
|
+
env.GIT_BRANCH ||
|
|
125
|
+
env.TRAVIS_BRANCH ||
|
|
126
|
+
env.BUILDKITE_BRANCH ||
|
|
127
|
+
env.BITBUCKET_BRANCH ||
|
|
128
|
+
env.DRONE_BRANCH ||
|
|
129
|
+
env.VERCEL_GIT_COMMIT_REF ||
|
|
130
|
+
undefined);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if the current environment is a CI/CD environment
|
|
134
|
+
*/
|
|
135
|
+
function isCI() {
|
|
136
|
+
return !!detectCIProvider();
|
|
137
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Assess Tech SDK - Error Classes
|
|
3
|
+
*
|
|
4
|
+
* Custom error types for better error handling
|
|
5
|
+
*
|
|
6
|
+
* @version 0.7.0
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Error codes for SDK errors
|
|
10
|
+
*/
|
|
11
|
+
export declare enum ErrorCode {
|
|
12
|
+
INVALID_KEY = "INVALID_KEY",
|
|
13
|
+
KEY_EXPIRED = "KEY_EXPIRED",
|
|
14
|
+
KEY_REVOKED = "KEY_REVOKED",
|
|
15
|
+
MISSING_KEY = "MISSING_KEY",
|
|
16
|
+
RATE_LIMITED = "RATE_LIMITED",
|
|
17
|
+
QUOTA_EXCEEDED = "QUOTA_EXCEEDED",
|
|
18
|
+
INVALID_RESPONSE = "INVALID_RESPONSE",
|
|
19
|
+
INVALID_BODY = "INVALID_BODY",
|
|
20
|
+
QUESTION_TIMEOUT = "QUESTION_TIMEOUT",
|
|
21
|
+
OVERALL_TIMEOUT = "OVERALL_TIMEOUT",
|
|
22
|
+
QUESTION_FAILED = "QUESTION_FAILED",
|
|
23
|
+
ASSESSMENT_FAILED = "ASSESSMENT_FAILED",
|
|
24
|
+
NETWORK_ERROR = "NETWORK_ERROR",
|
|
25
|
+
SERVER_ERROR = "SERVER_ERROR"
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Base SDK error class
|
|
29
|
+
*/
|
|
30
|
+
export declare class SDKError extends Error {
|
|
31
|
+
readonly code: ErrorCode;
|
|
32
|
+
readonly details?: unknown | undefined;
|
|
33
|
+
constructor(message: string, code: ErrorCode, details?: unknown | undefined);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Error for validation issues (invalid key, invalid response, etc.)
|
|
37
|
+
*/
|
|
38
|
+
export declare class ValidationError extends SDKError {
|
|
39
|
+
constructor(message: string, code?: ErrorCode);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Error for assessment failures
|
|
43
|
+
*/
|
|
44
|
+
export declare class AssessmentError extends SDKError {
|
|
45
|
+
constructor(message: string, code: ErrorCode, details?: unknown);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Error for individual question timeout
|
|
49
|
+
*/
|
|
50
|
+
export declare class QuestionTimeoutError extends SDKError {
|
|
51
|
+
readonly questionId: string;
|
|
52
|
+
constructor(message: string, questionId: string);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Error for overall assessment timeout
|
|
56
|
+
*/
|
|
57
|
+
export declare class OverallTimeoutError extends SDKError {
|
|
58
|
+
constructor(message: string, details?: unknown);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Error for network issues
|
|
62
|
+
*/
|
|
63
|
+
export declare class NetworkError extends SDKError {
|
|
64
|
+
readonly statusCode?: number | undefined;
|
|
65
|
+
constructor(message: string, statusCode?: number | undefined);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Error for rate limiting
|
|
69
|
+
*/
|
|
70
|
+
export declare class RateLimitError extends SDKError {
|
|
71
|
+
readonly retryAfterMs?: number | undefined;
|
|
72
|
+
constructor(message: string, retryAfterMs?: number | undefined);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated Use SDKError instead
|
|
76
|
+
*/
|
|
77
|
+
export declare class HealthCheckError extends Error {
|
|
78
|
+
readonly statusCode?: number | undefined;
|
|
79
|
+
readonly details?: unknown | undefined;
|
|
80
|
+
constructor(message: string, statusCode?: number | undefined, details?: unknown | undefined);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* @deprecated Use ValidationError with ErrorCode.INVALID_KEY instead
|
|
84
|
+
*/
|
|
85
|
+
export declare class AuthenticationError extends HealthCheckError {
|
|
86
|
+
constructor(message?: string);
|
|
87
|
+
}
|