@artemiskit/core 0.1.2
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/CHANGELOG.md +48 -0
- package/dist/adapters/factory.d.ts +23 -0
- package/dist/adapters/factory.d.ts.map +1 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/registry.d.ts +56 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/types.d.ts +151 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/artifacts/index.d.ts +6 -0
- package/dist/artifacts/index.d.ts.map +1 -0
- package/dist/artifacts/manifest.d.ts +19 -0
- package/dist/artifacts/manifest.d.ts.map +1 -0
- package/dist/artifacts/types.d.ts +368 -0
- package/dist/artifacts/types.d.ts.map +1 -0
- package/dist/evaluators/contains.d.ts +10 -0
- package/dist/evaluators/contains.d.ts.map +1 -0
- package/dist/evaluators/exact.d.ts +10 -0
- package/dist/evaluators/exact.d.ts.map +1 -0
- package/dist/evaluators/fuzzy.d.ts +10 -0
- package/dist/evaluators/fuzzy.d.ts.map +1 -0
- package/dist/evaluators/index.d.ts +24 -0
- package/dist/evaluators/index.d.ts.map +1 -0
- package/dist/evaluators/json-schema.d.ts +11 -0
- package/dist/evaluators/json-schema.d.ts.map +1 -0
- package/dist/evaluators/llm-grader.d.ts +11 -0
- package/dist/evaluators/llm-grader.d.ts.map +1 -0
- package/dist/evaluators/regex.d.ts +10 -0
- package/dist/evaluators/regex.d.ts.map +1 -0
- package/dist/evaluators/types.d.ts +29 -0
- package/dist/evaluators/types.d.ts.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26021 -0
- package/dist/provenance/environment.d.ts +12 -0
- package/dist/provenance/environment.d.ts.map +1 -0
- package/dist/provenance/git.d.ts +9 -0
- package/dist/provenance/git.d.ts.map +1 -0
- package/dist/provenance/index.d.ts +6 -0
- package/dist/provenance/index.d.ts.map +1 -0
- package/dist/redaction/index.d.ts +3 -0
- package/dist/redaction/index.d.ts.map +1 -0
- package/dist/redaction/redactor.d.ts +79 -0
- package/dist/redaction/redactor.d.ts.map +1 -0
- package/dist/redaction/types.d.ts +120 -0
- package/dist/redaction/types.d.ts.map +1 -0
- package/dist/runner/executor.d.ts +11 -0
- package/dist/runner/executor.d.ts.map +1 -0
- package/dist/runner/index.d.ts +7 -0
- package/dist/runner/index.d.ts.map +1 -0
- package/dist/runner/runner.d.ts +13 -0
- package/dist/runner/runner.d.ts.map +1 -0
- package/dist/runner/types.d.ts +57 -0
- package/dist/runner/types.d.ts.map +1 -0
- package/dist/scenario/index.d.ts +7 -0
- package/dist/scenario/index.d.ts.map +1 -0
- package/dist/scenario/parser.d.ts +17 -0
- package/dist/scenario/parser.d.ts.map +1 -0
- package/dist/scenario/schema.d.ts +945 -0
- package/dist/scenario/schema.d.ts.map +1 -0
- package/dist/scenario/variables.d.ts +19 -0
- package/dist/scenario/variables.d.ts.map +1 -0
- package/dist/storage/factory.d.ts +13 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/local.d.ts +20 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/supabase.d.ts +21 -0
- package/dist/storage/supabase.d.ts.map +1 -0
- package/dist/storage/types.d.ts +86 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/utils/errors.d.ts +25 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +21 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/package.json +56 -0
- package/src/adapters/factory.ts +75 -0
- package/src/adapters/index.ts +7 -0
- package/src/adapters/registry.ts +143 -0
- package/src/adapters/types.ts +184 -0
- package/src/artifacts/index.ts +6 -0
- package/src/artifacts/manifest.test.ts +206 -0
- package/src/artifacts/manifest.ts +136 -0
- package/src/artifacts/types.ts +426 -0
- package/src/evaluators/contains.test.ts +58 -0
- package/src/evaluators/contains.ts +41 -0
- package/src/evaluators/exact.test.ts +48 -0
- package/src/evaluators/exact.ts +33 -0
- package/src/evaluators/fuzzy.test.ts +50 -0
- package/src/evaluators/fuzzy.ts +39 -0
- package/src/evaluators/index.ts +53 -0
- package/src/evaluators/json-schema.ts +98 -0
- package/src/evaluators/llm-grader.ts +100 -0
- package/src/evaluators/regex.test.ts +73 -0
- package/src/evaluators/regex.ts +43 -0
- package/src/evaluators/types.ts +37 -0
- package/src/index.ts +31 -0
- package/src/provenance/environment.ts +18 -0
- package/src/provenance/git.ts +48 -0
- package/src/provenance/index.ts +6 -0
- package/src/redaction/index.ts +23 -0
- package/src/redaction/redactor.test.ts +258 -0
- package/src/redaction/redactor.ts +246 -0
- package/src/redaction/types.ts +135 -0
- package/src/runner/executor.ts +251 -0
- package/src/runner/index.ts +7 -0
- package/src/runner/runner.ts +153 -0
- package/src/runner/types.ts +60 -0
- package/src/scenario/index.ts +7 -0
- package/src/scenario/parser.test.ts +99 -0
- package/src/scenario/parser.ts +108 -0
- package/src/scenario/schema.ts +176 -0
- package/src/scenario/variables.test.ts +150 -0
- package/src/scenario/variables.ts +60 -0
- package/src/storage/factory.ts +52 -0
- package/src/storage/index.ts +8 -0
- package/src/storage/local.test.ts +165 -0
- package/src/storage/local.ts +194 -0
- package/src/storage/supabase.ts +151 -0
- package/src/storage/types.ts +98 -0
- package/src/utils/errors.ts +76 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/logger.ts +59 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local filesystem storage adapter
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { mkdir, readFile, readdir, unlink, writeFile } from 'node:fs/promises';
|
|
6
|
+
import { join, resolve } from 'node:path';
|
|
7
|
+
import type { AnyManifest, RedTeamManifest, RunManifest, StressManifest } from '../artifacts/types';
|
|
8
|
+
import type { ComparisonResult, ListOptions, RunListItem, StorageAdapter } from './types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get manifest type from a manifest object
|
|
12
|
+
*/
|
|
13
|
+
function getManifestType(manifest: AnyManifest): 'run' | 'redteam' | 'stress' {
|
|
14
|
+
if ('type' in manifest) {
|
|
15
|
+
if (manifest.type === 'redteam') return 'redteam';
|
|
16
|
+
if (manifest.type === 'stress') return 'stress';
|
|
17
|
+
}
|
|
18
|
+
return 'run';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get success/defense rate from any manifest type
|
|
23
|
+
*/
|
|
24
|
+
function getSuccessRate(manifest: AnyManifest): number {
|
|
25
|
+
const type = getManifestType(manifest);
|
|
26
|
+
if (type === 'redteam') {
|
|
27
|
+
return (manifest as RedTeamManifest).metrics.defense_rate;
|
|
28
|
+
}
|
|
29
|
+
if (type === 'stress') {
|
|
30
|
+
return (manifest as StressManifest).metrics.success_rate;
|
|
31
|
+
}
|
|
32
|
+
return (manifest as RunManifest).metrics.success_rate;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get scenario name from any manifest type
|
|
37
|
+
*/
|
|
38
|
+
function getScenario(manifest: AnyManifest): string {
|
|
39
|
+
return manifest.config.scenario;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class LocalStorageAdapter implements StorageAdapter {
|
|
43
|
+
private basePath: string;
|
|
44
|
+
|
|
45
|
+
constructor(basePath = './artemis-runs') {
|
|
46
|
+
this.basePath = resolve(basePath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async save(manifest: AnyManifest): Promise<string> {
|
|
50
|
+
const dir = join(this.basePath, manifest.project);
|
|
51
|
+
await mkdir(dir, { recursive: true });
|
|
52
|
+
|
|
53
|
+
const filePath = join(dir, `${manifest.run_id}.json`);
|
|
54
|
+
await writeFile(filePath, JSON.stringify(manifest, null, 2));
|
|
55
|
+
|
|
56
|
+
return filePath;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async load(runId: string): Promise<AnyManifest> {
|
|
60
|
+
const projects = await this.listDirectories(this.basePath);
|
|
61
|
+
|
|
62
|
+
for (const project of projects) {
|
|
63
|
+
const filePath = join(this.basePath, project, `${runId}.json`);
|
|
64
|
+
try {
|
|
65
|
+
const content = await readFile(filePath, 'utf-8');
|
|
66
|
+
return JSON.parse(content);
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
throw new Error(`Run not found: ${runId}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async loadRun(runId: string): Promise<RunManifest> {
|
|
74
|
+
const manifest = await this.load(runId);
|
|
75
|
+
if (getManifestType(manifest) !== 'run') {
|
|
76
|
+
throw new Error(`Run ${runId} is not a standard run manifest`);
|
|
77
|
+
}
|
|
78
|
+
return manifest as RunManifest;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async loadRedTeam(runId: string): Promise<RedTeamManifest> {
|
|
82
|
+
const manifest = await this.load(runId);
|
|
83
|
+
if (getManifestType(manifest) !== 'redteam') {
|
|
84
|
+
throw new Error(`Run ${runId} is not a red team manifest`);
|
|
85
|
+
}
|
|
86
|
+
return manifest as RedTeamManifest;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async loadStress(runId: string): Promise<StressManifest> {
|
|
90
|
+
const manifest = await this.load(runId);
|
|
91
|
+
if (getManifestType(manifest) !== 'stress') {
|
|
92
|
+
throw new Error(`Run ${runId} is not a stress test manifest`);
|
|
93
|
+
}
|
|
94
|
+
return manifest as StressManifest;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async list(options?: ListOptions): Promise<RunListItem[]> {
|
|
98
|
+
const results: RunListItem[] = [];
|
|
99
|
+
|
|
100
|
+
const projects = options?.project
|
|
101
|
+
? [options.project]
|
|
102
|
+
: await this.listDirectories(this.basePath);
|
|
103
|
+
|
|
104
|
+
for (const project of projects) {
|
|
105
|
+
const projectDir = join(this.basePath, project);
|
|
106
|
+
const files = await this.listFiles(projectDir);
|
|
107
|
+
|
|
108
|
+
for (const file of files) {
|
|
109
|
+
if (!file.endsWith('.json')) continue;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const content = await readFile(join(projectDir, file), 'utf-8');
|
|
113
|
+
const manifest: AnyManifest = JSON.parse(content);
|
|
114
|
+
const manifestType = getManifestType(manifest);
|
|
115
|
+
|
|
116
|
+
// Filter by type if specified
|
|
117
|
+
if (options?.type && manifestType !== options.type) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (options?.scenario && getScenario(manifest) !== options.scenario) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
results.push({
|
|
126
|
+
runId: manifest.run_id,
|
|
127
|
+
scenario: getScenario(manifest),
|
|
128
|
+
successRate: getSuccessRate(manifest),
|
|
129
|
+
createdAt: manifest.start_time,
|
|
130
|
+
type: manifestType,
|
|
131
|
+
});
|
|
132
|
+
} catch {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
results.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
137
|
+
|
|
138
|
+
const offset = options?.offset || 0;
|
|
139
|
+
const limit = options?.limit;
|
|
140
|
+
|
|
141
|
+
if (limit) {
|
|
142
|
+
return results.slice(offset, offset + limit);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return results.slice(offset);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async delete(runId: string): Promise<void> {
|
|
149
|
+
const projects = await this.listDirectories(this.basePath);
|
|
150
|
+
|
|
151
|
+
for (const project of projects) {
|
|
152
|
+
const filePath = join(this.basePath, project, `${runId}.json`);
|
|
153
|
+
try {
|
|
154
|
+
await unlink(filePath);
|
|
155
|
+
return;
|
|
156
|
+
} catch {}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async compare(baselineId: string, currentId: string): Promise<ComparisonResult> {
|
|
161
|
+
const [baseline, current] = await Promise.all([
|
|
162
|
+
this.loadRun(baselineId),
|
|
163
|
+
this.loadRun(currentId),
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
baseline,
|
|
168
|
+
current,
|
|
169
|
+
delta: {
|
|
170
|
+
successRate: current.metrics.success_rate - baseline.metrics.success_rate,
|
|
171
|
+
latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
|
|
172
|
+
tokens: current.metrics.total_tokens - baseline.metrics.total_tokens,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async listDirectories(path: string): Promise<string[]> {
|
|
178
|
+
try {
|
|
179
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
180
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
181
|
+
} catch {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async listFiles(path: string): Promise<string[]> {
|
|
187
|
+
try {
|
|
188
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
189
|
+
return entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
190
|
+
} catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase storage adapter
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type SupabaseClient, createClient } from '@supabase/supabase-js';
|
|
6
|
+
import type { RunManifest } from '../artifacts/types';
|
|
7
|
+
import type { ComparisonResult, ListOptions, RunListItem, StorageAdapter } from './types';
|
|
8
|
+
|
|
9
|
+
export interface SupabaseStorageConfig {
|
|
10
|
+
url: string;
|
|
11
|
+
anonKey: string;
|
|
12
|
+
bucket?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SupabaseStorageAdapter implements StorageAdapter {
|
|
16
|
+
private client: SupabaseClient;
|
|
17
|
+
private bucket: string;
|
|
18
|
+
|
|
19
|
+
constructor(config: SupabaseStorageConfig) {
|
|
20
|
+
this.client = createClient(config.url, config.anonKey);
|
|
21
|
+
this.bucket = config.bucket || 'artemis-runs';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async save(manifest: RunManifest): Promise<string> {
|
|
25
|
+
const filePath = `${manifest.project}/${manifest.run_id}.json`;
|
|
26
|
+
|
|
27
|
+
const { error: uploadError } = await this.client.storage
|
|
28
|
+
.from(this.bucket)
|
|
29
|
+
.upload(filePath, JSON.stringify(manifest, null, 2), {
|
|
30
|
+
contentType: 'application/json',
|
|
31
|
+
upsert: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (uploadError) {
|
|
35
|
+
throw new Error(`Failed to upload manifest: ${uploadError.message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { error: dbError } = await this.client.from('runs').upsert({
|
|
39
|
+
run_id: manifest.run_id,
|
|
40
|
+
project: manifest.project,
|
|
41
|
+
scenario: manifest.config.scenario,
|
|
42
|
+
provider: manifest.config.provider,
|
|
43
|
+
model: manifest.config.model,
|
|
44
|
+
success_rate: manifest.metrics.success_rate,
|
|
45
|
+
total_cases: manifest.metrics.total_cases,
|
|
46
|
+
passed_cases: manifest.metrics.passed_cases,
|
|
47
|
+
failed_cases: manifest.metrics.failed_cases,
|
|
48
|
+
median_latency_ms: manifest.metrics.median_latency_ms,
|
|
49
|
+
p95_latency_ms: manifest.metrics.p95_latency_ms,
|
|
50
|
+
total_tokens: manifest.metrics.total_tokens,
|
|
51
|
+
git_commit: manifest.git.commit,
|
|
52
|
+
git_branch: manifest.git.branch,
|
|
53
|
+
git_dirty: manifest.git.dirty,
|
|
54
|
+
run_by: manifest.provenance.run_by,
|
|
55
|
+
run_reason: manifest.provenance.run_reason,
|
|
56
|
+
started_at: manifest.start_time,
|
|
57
|
+
ended_at: manifest.end_time,
|
|
58
|
+
manifest_path: filePath,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (dbError) {
|
|
62
|
+
throw new Error(`Failed to save run metadata: ${dbError.message}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return filePath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async load(runId: string): Promise<RunManifest> {
|
|
69
|
+
const { data: run, error: dbError } = await this.client
|
|
70
|
+
.from('runs')
|
|
71
|
+
.select('manifest_path')
|
|
72
|
+
.eq('run_id', runId)
|
|
73
|
+
.single();
|
|
74
|
+
|
|
75
|
+
if (dbError || !run) {
|
|
76
|
+
throw new Error(`Run not found: ${runId}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { data, error: downloadError } = await this.client.storage
|
|
80
|
+
.from(this.bucket)
|
|
81
|
+
.download(run.manifest_path);
|
|
82
|
+
|
|
83
|
+
if (downloadError || !data) {
|
|
84
|
+
throw new Error(`Failed to download manifest: ${downloadError?.message}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const text = await data.text();
|
|
88
|
+
return JSON.parse(text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async list(options?: ListOptions): Promise<RunListItem[]> {
|
|
92
|
+
let query = this.client
|
|
93
|
+
.from('runs')
|
|
94
|
+
.select('run_id, scenario, success_rate, started_at')
|
|
95
|
+
.order('started_at', { ascending: false });
|
|
96
|
+
|
|
97
|
+
if (options?.project) {
|
|
98
|
+
query = query.eq('project', options.project);
|
|
99
|
+
}
|
|
100
|
+
if (options?.scenario) {
|
|
101
|
+
query = query.eq('scenario', options.scenario);
|
|
102
|
+
}
|
|
103
|
+
if (options?.limit) {
|
|
104
|
+
query = query.limit(options.limit);
|
|
105
|
+
}
|
|
106
|
+
if (options?.offset) {
|
|
107
|
+
query = query.range(options.offset, options.offset + (options.limit || 10) - 1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { data, error } = await query;
|
|
111
|
+
|
|
112
|
+
if (error) {
|
|
113
|
+
throw new Error(`Failed to list runs: ${error.message}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (data || []).map((r) => ({
|
|
117
|
+
runId: r.run_id,
|
|
118
|
+
scenario: r.scenario,
|
|
119
|
+
successRate: r.success_rate,
|
|
120
|
+
createdAt: r.started_at,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async delete(runId: string): Promise<void> {
|
|
125
|
+
const { data: run } = await this.client
|
|
126
|
+
.from('runs')
|
|
127
|
+
.select('manifest_path')
|
|
128
|
+
.eq('run_id', runId)
|
|
129
|
+
.single();
|
|
130
|
+
|
|
131
|
+
if (run) {
|
|
132
|
+
await this.client.storage.from(this.bucket).remove([run.manifest_path]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
await this.client.from('runs').delete().eq('run_id', runId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async compare(baselineId: string, currentId: string): Promise<ComparisonResult> {
|
|
139
|
+
const [baseline, current] = await Promise.all([this.load(baselineId), this.load(currentId)]);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
baseline,
|
|
143
|
+
current,
|
|
144
|
+
delta: {
|
|
145
|
+
successRate: current.metrics.success_rate - baseline.metrics.success_rate,
|
|
146
|
+
latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
|
|
147
|
+
tokens: current.metrics.total_tokens - baseline.metrics.total_tokens,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage types and interfaces
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AnyManifest, RedTeamManifest, RunManifest, StressManifest } from '../artifacts/types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run listing item
|
|
9
|
+
*/
|
|
10
|
+
export interface RunListItem {
|
|
11
|
+
runId: string;
|
|
12
|
+
scenario: string;
|
|
13
|
+
successRate: number;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
/** Type of manifest (run, redteam, stress) */
|
|
16
|
+
type?: 'run' | 'redteam' | 'stress';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Comparison result between two runs
|
|
21
|
+
*/
|
|
22
|
+
export interface ComparisonResult {
|
|
23
|
+
baseline: RunManifest;
|
|
24
|
+
current: RunManifest;
|
|
25
|
+
delta: {
|
|
26
|
+
successRate: number;
|
|
27
|
+
latency: number;
|
|
28
|
+
tokens: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* List options for filtering runs
|
|
34
|
+
*/
|
|
35
|
+
export interface ListOptions {
|
|
36
|
+
project?: string;
|
|
37
|
+
scenario?: string;
|
|
38
|
+
limit?: number;
|
|
39
|
+
offset?: number;
|
|
40
|
+
/** Filter by manifest type */
|
|
41
|
+
type?: 'run' | 'redteam' | 'stress';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Storage adapter interface - implement to create custom storage backends
|
|
46
|
+
*/
|
|
47
|
+
export interface StorageAdapter {
|
|
48
|
+
/**
|
|
49
|
+
* Save a run manifest (any type)
|
|
50
|
+
*/
|
|
51
|
+
save(manifest: AnyManifest): Promise<string>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Load a run manifest by ID
|
|
55
|
+
*/
|
|
56
|
+
load(runId: string): Promise<AnyManifest>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Load a standard run manifest by ID
|
|
60
|
+
*/
|
|
61
|
+
loadRun?(runId: string): Promise<RunManifest>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Load a red team manifest by ID
|
|
65
|
+
*/
|
|
66
|
+
loadRedTeam?(runId: string): Promise<RedTeamManifest>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load a stress manifest by ID
|
|
70
|
+
*/
|
|
71
|
+
loadStress?(runId: string): Promise<StressManifest>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* List runs with optional filters
|
|
75
|
+
*/
|
|
76
|
+
list(options?: ListOptions): Promise<RunListItem[]>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Delete a run
|
|
80
|
+
*/
|
|
81
|
+
delete(runId: string): Promise<void>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compare two runs
|
|
85
|
+
*/
|
|
86
|
+
compare?(baselineId: string, currentId: string): Promise<ComparisonResult>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Storage configuration
|
|
91
|
+
*/
|
|
92
|
+
export interface StorageConfig {
|
|
93
|
+
type: 'supabase' | 'local';
|
|
94
|
+
url?: string;
|
|
95
|
+
anonKey?: string;
|
|
96
|
+
bucket?: string;
|
|
97
|
+
basePath?: string;
|
|
98
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error handling utilities for Artemis
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Error codes used throughout Artemis
|
|
7
|
+
*/
|
|
8
|
+
export type ArtemisErrorCode =
|
|
9
|
+
| 'UNKNOWN_PROVIDER'
|
|
10
|
+
| 'PROVIDER_UNAVAILABLE'
|
|
11
|
+
| 'SCENARIO_READ_ERROR'
|
|
12
|
+
| 'SCENARIO_PARSE_ERROR'
|
|
13
|
+
| 'SCENARIO_VALIDATION_ERROR'
|
|
14
|
+
| 'ADAPTER_ERROR'
|
|
15
|
+
| 'GENERATION_ERROR'
|
|
16
|
+
| 'EVALUATION_ERROR'
|
|
17
|
+
| 'STORAGE_ERROR'
|
|
18
|
+
| 'CONFIG_ERROR'
|
|
19
|
+
| 'UNKNOWN_ERROR';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Custom error class for Artemis
|
|
23
|
+
*/
|
|
24
|
+
export class ArtemisError extends Error {
|
|
25
|
+
readonly code: ArtemisErrorCode;
|
|
26
|
+
readonly details?: Record<string, unknown>;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
message: string,
|
|
30
|
+
code: ArtemisErrorCode = 'UNKNOWN_ERROR',
|
|
31
|
+
details?: Record<string, unknown>
|
|
32
|
+
) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = 'ArtemisError';
|
|
35
|
+
this.code = code;
|
|
36
|
+
this.details = details;
|
|
37
|
+
|
|
38
|
+
Error.captureStackTrace(this, this.constructor);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
toJSON(): Record<string, unknown> {
|
|
42
|
+
return {
|
|
43
|
+
name: this.name,
|
|
44
|
+
code: this.code,
|
|
45
|
+
message: this.message,
|
|
46
|
+
details: this.details,
|
|
47
|
+
stack: this.stack,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if error is an ArtemisError
|
|
54
|
+
*/
|
|
55
|
+
export function isArtemisError(error: unknown): error is ArtemisError {
|
|
56
|
+
return error instanceof ArtemisError;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wrap unknown errors in ArtemisError
|
|
61
|
+
*/
|
|
62
|
+
export function wrapError(
|
|
63
|
+
error: unknown,
|
|
64
|
+
code: ArtemisErrorCode = 'UNKNOWN_ERROR',
|
|
65
|
+
context?: string
|
|
66
|
+
): ArtemisError {
|
|
67
|
+
if (error instanceof ArtemisError) {
|
|
68
|
+
return error;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
|
|
73
|
+
return new ArtemisError(context ? `${context}: ${message}` : message, code, {
|
|
74
|
+
originalError: error,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility for Artemis
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import pino from 'pino';
|
|
6
|
+
|
|
7
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
8
|
+
|
|
9
|
+
const level = process.env.ARTEMIS_LOG_LEVEL || 'info';
|
|
10
|
+
|
|
11
|
+
const baseLogger = pino({
|
|
12
|
+
level,
|
|
13
|
+
transport:
|
|
14
|
+
process.env.NODE_ENV === 'development'
|
|
15
|
+
? { target: 'pino-pretty', options: { colorize: true } }
|
|
16
|
+
: undefined,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Logger class for consistent logging across Artemis
|
|
21
|
+
*/
|
|
22
|
+
export class Logger {
|
|
23
|
+
private logger: pino.Logger;
|
|
24
|
+
|
|
25
|
+
constructor(name: string) {
|
|
26
|
+
this.logger = baseLogger.child({ name });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
debug(message: string, data?: Record<string, unknown>): void {
|
|
30
|
+
this.logger.debug(data, message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
info(message: string, data?: Record<string, unknown>): void {
|
|
34
|
+
this.logger.info(data, message);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
warn(message: string, data?: Record<string, unknown>): void {
|
|
38
|
+
this.logger.warn(data, message);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
error(message: string, error?: Error | unknown, data?: Record<string, unknown>): void {
|
|
42
|
+
const errorData =
|
|
43
|
+
error instanceof Error
|
|
44
|
+
? { error: { message: error.message, stack: error.stack, name: error.name } }
|
|
45
|
+
: { error };
|
|
46
|
+
this.logger.error({ ...data, ...errorData }, message);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
child(bindings: Record<string, unknown>): Logger {
|
|
50
|
+
const childLogger = new Logger('');
|
|
51
|
+
childLogger.logger = this.logger.child(bindings);
|
|
52
|
+
return childLogger;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Default logger instance
|
|
58
|
+
*/
|
|
59
|
+
export const logger = new Logger('artemis');
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"noEmit": false,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"emitDeclarationOnly": true
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*"],
|
|
12
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
13
|
+
}
|