@artemiskit/core 0.5.1 → 0.5.3

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.js CHANGED
@@ -19824,7 +19824,7 @@ var TestCaseSchema = objectType({
19824
19824
  tags: arrayType(stringType()).optional().default([]),
19825
19825
  metadata: recordType(unknownType()).optional().default({}),
19826
19826
  timeout: numberType().optional(),
19827
- retries: numberType().optional().default(0),
19827
+ retries: numberType().int().min(0).max(99).optional().default(0),
19828
19828
  provider: ProviderSchema.optional(),
19829
19829
  model: stringType().optional(),
19830
19830
  variables: VariablesSchema.optional(),
@@ -20524,17 +20524,48 @@ function mergeRedactionConfig(scenarioConfig, caseConfig, cliConfig) {
20524
20524
  }
20525
20525
  async function executeCase(testCase, context) {
20526
20526
  const { timeout, retries = 0 } = context;
20527
+ if (!Number.isSafeInteger(retries) || retries < 0 || retries > 99) {
20528
+ throw new RangeError("retries must be a whole number between 0 and 99");
20529
+ }
20527
20530
  const caseStartTime = Date.now();
20528
20531
  const requestedModel = testCase.model || context.requestedModel || context.scenario.model;
20532
+ const retryChainId = `${context.runId ?? "untracked"}:${testCase.id}`;
20533
+ const repetitionIndex = context.repetition?.index ?? 1;
20534
+ const attemptEvidence = [];
20529
20535
  let lastError = null;
20530
20536
  for (let attempt = 0;attempt <= retries; attempt++) {
20537
+ const attemptStartTime = Date.now();
20531
20538
  try {
20532
20539
  const result = await executeCaseAttempt(testCase, context, timeout);
20533
- return { ...result, attempts: attempt + 1 };
20540
+ return withAttemptEvidence(result, attemptEvidence, {
20541
+ retryChainId,
20542
+ repetitionIndex,
20543
+ attemptNumber: attempt + 1,
20544
+ includedInOutcome: true,
20545
+ latencyMs: result.latencyMs
20546
+ });
20534
20547
  } catch (error) {
20535
20548
  lastError = error;
20536
- if (error instanceof ToolLoopError)
20537
- return { ...error.caseResult, attempts: attempt + 1 };
20549
+ if (error instanceof ToolLoopError) {
20550
+ return withAttemptEvidence(error.caseResult, attemptEvidence, {
20551
+ retryChainId,
20552
+ repetitionIndex,
20553
+ attemptNumber: attempt + 1,
20554
+ includedInOutcome: true,
20555
+ latencyMs: error.caseResult.latencyMs,
20556
+ errorCode: "tool_error"
20557
+ });
20558
+ }
20559
+ attemptEvidence.push({
20560
+ attempt_id: `${retryChainId}:${attempt + 1}`,
20561
+ retry_chain_id: retryChainId,
20562
+ repetition_index: repetitionIndex,
20563
+ attempt_number: attempt + 1,
20564
+ status: "error",
20565
+ included_in_outcome: false,
20566
+ latency_ms: Date.now() - attemptStartTime,
20567
+ error_code: error instanceof TimeoutError ? "timeout" : "target_error"
20568
+ });
20538
20569
  if (attempt < retries) {
20539
20570
  await sleep(2 ** attempt * 1000);
20540
20571
  }
@@ -20547,6 +20578,10 @@ async function executeCase(testCase, context) {
20547
20578
  ok: false,
20548
20579
  status: "error",
20549
20580
  attempts: retries + 1,
20581
+ attempt_evidence: attemptEvidence.map((entry, index) => ({
20582
+ ...entry,
20583
+ included_in_outcome: index === attemptEvidence.length - 1
20584
+ })),
20550
20585
  score: 0,
20551
20586
  matcherType: testCase.expected.type,
20552
20587
  reason: `Failed after ${retries + 1} attempts: ${lastError?.message}`,
@@ -20560,6 +20595,28 @@ async function executeCase(testCase, context) {
20560
20595
  target: targetEvidence(context.client.provider, requestedModel)
20561
20596
  };
20562
20597
  }
20598
+ function withAttemptEvidence(result, priorAttempts, input) {
20599
+ return {
20600
+ ...result,
20601
+ attempts: input.attemptNumber,
20602
+ attempt_evidence: [
20603
+ ...priorAttempts,
20604
+ {
20605
+ attempt_id: `${input.retryChainId}:${input.attemptNumber}`,
20606
+ retry_chain_id: input.retryChainId,
20607
+ repetition_index: input.repetitionIndex,
20608
+ attempt_number: input.attemptNumber,
20609
+ status: getTerminalStatus(result),
20610
+ included_in_outcome: input.includedInOutcome,
20611
+ latency_ms: input.latencyMs,
20612
+ ...input.errorCode ? { error_code: input.errorCode } : {}
20613
+ }
20614
+ ]
20615
+ };
20616
+ }
20617
+ function getTerminalStatus(result) {
20618
+ return result.status ?? (result.ok ? "passed" : result.error ? "error" : "failed");
20619
+ }
20563
20620
  async function executeCaseAttempt(testCase, context, timeout) {
20564
20621
  const { client, scenario, requestedModel, redaction: cliRedaction, toolExecutor } = context;
20565
20622
  const variables = mergeVariables(scenario.variables, testCase.variables);
@@ -20928,374 +20985,61 @@ function nanoid(size = 21) {
20928
20985
  return id;
20929
20986
  }
20930
20987
 
20931
- // src/cost/pricing.ts
20932
- var MODEL_PRICING = {
20933
- "gpt-5": {
20934
- promptPer1K: 0.00125,
20935
- completionPer1K: 0.01,
20936
- lastUpdated: "2026-01",
20937
- notes: "400K context window"
20938
- },
20939
- "gpt-5.1": {
20940
- promptPer1K: 0.00125,
20941
- completionPer1K: 0.01,
20942
- lastUpdated: "2026-01"
20943
- },
20944
- "gpt-5.2": {
20945
- promptPer1K: 0.00175,
20946
- completionPer1K: 0.014,
20947
- lastUpdated: "2026-01"
20948
- },
20949
- "gpt-5-mini": {
20950
- promptPer1K: 0.00025,
20951
- completionPer1K: 0.002,
20952
- lastUpdated: "2026-01"
20953
- },
20954
- "gpt-5-nano": {
20955
- promptPer1K: 0.00005,
20956
- completionPer1K: 0.0004,
20957
- lastUpdated: "2026-01"
20958
- },
20959
- "gpt-4.1": {
20960
- promptPer1K: 0.002,
20961
- completionPer1K: 0.008,
20962
- lastUpdated: "2026-01",
20963
- notes: "1M context window"
20964
- },
20965
- "gpt-4.1-mini": {
20966
- promptPer1K: 0.0004,
20967
- completionPer1K: 0.0016,
20968
- lastUpdated: "2026-01"
20969
- },
20970
- "gpt-4.1-nano": {
20971
- promptPer1K: 0.0001,
20972
- completionPer1K: 0.0004,
20973
- lastUpdated: "2026-01"
20974
- },
20975
- "gpt-4o": {
20976
- promptPer1K: 0.0025,
20977
- completionPer1K: 0.01,
20978
- lastUpdated: "2026-01",
20979
- notes: "128K context window"
20980
- },
20981
- "gpt-4o-mini": {
20982
- promptPer1K: 0.00015,
20983
- completionPer1K: 0.0006,
20984
- lastUpdated: "2026-01",
20985
- notes: "128K context window"
20986
- },
20987
- o1: {
20988
- promptPer1K: 0.015,
20989
- completionPer1K: 0.06,
20990
- lastUpdated: "2026-01",
20991
- notes: "Reasoning model - internal thinking tokens billed as output"
20992
- },
20993
- o3: {
20994
- promptPer1K: 0.002,
20995
- completionPer1K: 0.008,
20996
- lastUpdated: "2026-01"
20997
- },
20998
- "o3-mini": {
20999
- promptPer1K: 0.0011,
21000
- completionPer1K: 0.0044,
21001
- lastUpdated: "2026-01"
21002
- },
21003
- "o4-mini": {
21004
- promptPer1K: 0.0011,
21005
- completionPer1K: 0.0044,
21006
- lastUpdated: "2026-01"
21007
- },
21008
- "gpt-4-turbo": {
21009
- promptPer1K: 0.01,
21010
- completionPer1K: 0.03,
21011
- lastUpdated: "2026-01"
21012
- },
21013
- "gpt-4": {
21014
- promptPer1K: 0.03,
21015
- completionPer1K: 0.06,
21016
- lastUpdated: "2026-01"
21017
- },
21018
- "gpt-3.5-turbo": {
21019
- promptPer1K: 0.0005,
21020
- completionPer1K: 0.0015,
21021
- lastUpdated: "2026-01"
21022
- },
21023
- "claude-opus-4.5": {
21024
- promptPer1K: 0.005,
21025
- completionPer1K: 0.025,
21026
- lastUpdated: "2026-01",
21027
- notes: "Most capable Claude model"
21028
- },
21029
- "claude-sonnet-4.5": {
21030
- promptPer1K: 0.003,
21031
- completionPer1K: 0.015,
21032
- lastUpdated: "2026-01",
21033
- notes: "Balanced performance and cost"
21034
- },
21035
- "claude-haiku-4.5": {
21036
- promptPer1K: 0.001,
21037
- completionPer1K: 0.005,
21038
- lastUpdated: "2026-01",
21039
- notes: "Fastest Claude model"
21040
- },
21041
- "claude-opus-4": {
21042
- promptPer1K: 0.015,
21043
- completionPer1K: 0.075,
21044
- lastUpdated: "2026-01"
21045
- },
21046
- "claude-opus-4.1": {
21047
- promptPer1K: 0.015,
21048
- completionPer1K: 0.075,
21049
- lastUpdated: "2026-01"
21050
- },
21051
- "claude-sonnet-4": {
21052
- promptPer1K: 0.003,
21053
- completionPer1K: 0.015,
21054
- lastUpdated: "2026-01"
21055
- },
21056
- "claude-sonnet-3.7": {
21057
- promptPer1K: 0.003,
21058
- completionPer1K: 0.015,
21059
- lastUpdated: "2026-01"
21060
- },
21061
- "claude-3-7-sonnet": {
21062
- promptPer1K: 0.003,
21063
- completionPer1K: 0.015,
21064
- lastUpdated: "2026-01"
21065
- },
21066
- "claude-3-5-sonnet-20241022": {
21067
- promptPer1K: 0.003,
21068
- completionPer1K: 0.015,
21069
- lastUpdated: "2026-01"
21070
- },
21071
- "claude-3-5-haiku-20241022": {
21072
- promptPer1K: 0.0008,
21073
- completionPer1K: 0.004,
21074
- lastUpdated: "2026-01"
21075
- },
21076
- "claude-haiku-3.5": {
21077
- promptPer1K: 0.0008,
21078
- completionPer1K: 0.004,
21079
- lastUpdated: "2026-01"
21080
- },
21081
- "claude-3-opus": {
21082
- promptPer1K: 0.015,
21083
- completionPer1K: 0.075,
21084
- lastUpdated: "2026-01"
21085
- },
21086
- "claude-3-sonnet": {
21087
- promptPer1K: 0.003,
21088
- completionPer1K: 0.015,
21089
- lastUpdated: "2026-01"
21090
- },
21091
- "claude-3-haiku": {
21092
- promptPer1K: 0.00025,
21093
- completionPer1K: 0.00125,
21094
- lastUpdated: "2026-01"
21095
- },
21096
- "claude-3.5-sonnet": {
21097
- promptPer1K: 0.003,
21098
- completionPer1K: 0.015,
21099
- lastUpdated: "2026-01"
21100
- },
21101
- "claude-3.5-haiku": {
21102
- promptPer1K: 0.0008,
21103
- completionPer1K: 0.004,
21104
- lastUpdated: "2026-01"
21105
- }
21106
- };
21107
- var DEFAULT_PRICING = {
21108
- promptPer1K: 0.003,
21109
- completionPer1K: 0.015,
21110
- lastUpdated: "2026-01",
21111
- notes: "Default pricing - verify with provider"
21112
- };
21113
- function getModelPricing(model) {
21114
- if (MODEL_PRICING[model]) {
21115
- return MODEL_PRICING[model];
20988
+ // src/provenance/environment.ts
20989
+ function getEnvironmentInfo() {
20990
+ return {
20991
+ node_version: process.version,
20992
+ platform: process.platform,
20993
+ arch: process.arch
20994
+ };
20995
+ }
20996
+
20997
+ // src/provenance/git.ts
20998
+ import { execSync } from "child_process";
20999
+ function getGitInfo() {
21000
+ try {
21001
+ const commit = execGit("rev-parse HEAD");
21002
+ const branch = execGit("rev-parse --abbrev-ref HEAD");
21003
+ const dirty = execGit("status --porcelain").length > 0;
21004
+ const remote = execGit("remote get-url origin", true);
21005
+ return {
21006
+ commit,
21007
+ branch,
21008
+ dirty,
21009
+ remote: remote || undefined
21010
+ };
21011
+ } catch {
21012
+ return {
21013
+ commit: "unknown",
21014
+ branch: "unknown",
21015
+ dirty: false
21016
+ };
21116
21017
  }
21117
- const lowerModel = model.toLowerCase();
21118
- for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
21119
- if (key.toLowerCase() === lowerModel) {
21120
- return pricing;
21018
+ }
21019
+ function execGit(command, allowFailure = false) {
21020
+ try {
21021
+ return execSync(`git ${command}`, {
21022
+ encoding: "utf-8",
21023
+ stdio: ["pipe", "pipe", "pipe"]
21024
+ }).trim();
21025
+ } catch {
21026
+ if (allowFailure) {
21027
+ return "";
21121
21028
  }
21029
+ throw new Error(`Git command failed: ${command}`);
21122
21030
  }
21123
- if (lowerModel.includes("gpt-5.2")) {
21124
- return MODEL_PRICING["gpt-5.2"];
21125
- }
21126
- if (lowerModel.includes("gpt-5.1")) {
21127
- return MODEL_PRICING["gpt-5.1"];
21128
- }
21129
- if (lowerModel.includes("gpt-5-mini")) {
21130
- return MODEL_PRICING["gpt-5-mini"];
21131
- }
21132
- if (lowerModel.includes("gpt-5-nano")) {
21133
- return MODEL_PRICING["gpt-5-nano"];
21134
- }
21135
- if (lowerModel.includes("gpt-5")) {
21136
- return MODEL_PRICING["gpt-5"];
21137
- }
21138
- if (lowerModel.includes("gpt-4.1-mini")) {
21139
- return MODEL_PRICING["gpt-4.1-mini"];
21140
- }
21141
- if (lowerModel.includes("gpt-4.1-nano")) {
21142
- return MODEL_PRICING["gpt-4.1-nano"];
21143
- }
21144
- if (lowerModel.includes("gpt-4.1")) {
21145
- return MODEL_PRICING["gpt-4.1"];
21146
- }
21147
- if (lowerModel.includes("gpt-4o-mini")) {
21148
- return MODEL_PRICING["gpt-4o-mini"];
21149
- }
21150
- if (lowerModel.includes("gpt-4o")) {
21151
- return MODEL_PRICING["gpt-4o"];
21152
- }
21153
- if (lowerModel.includes("o4-mini")) {
21154
- return MODEL_PRICING["o4-mini"];
21155
- }
21156
- if (lowerModel.includes("o3-mini")) {
21157
- return MODEL_PRICING["o3-mini"];
21158
- }
21159
- if (lowerModel.includes("o3")) {
21160
- return MODEL_PRICING.o3;
21161
- }
21162
- if (lowerModel.includes("o1")) {
21163
- return MODEL_PRICING.o1;
21164
- }
21165
- if (lowerModel.includes("gpt-4-turbo")) {
21166
- return MODEL_PRICING["gpt-4-turbo"];
21167
- }
21168
- if (lowerModel.includes("gpt-4")) {
21169
- return MODEL_PRICING["gpt-4"];
21170
- }
21171
- if (lowerModel.includes("gpt-3.5")) {
21172
- return MODEL_PRICING["gpt-3.5-turbo"];
21173
- }
21174
- if (lowerModel.includes("opus-4.5") || lowerModel.includes("opus-4-5")) {
21175
- return MODEL_PRICING["claude-opus-4.5"];
21176
- }
21177
- if (lowerModel.includes("sonnet-4.5") || lowerModel.includes("sonnet-4-5")) {
21178
- return MODEL_PRICING["claude-sonnet-4.5"];
21179
- }
21180
- if (lowerModel.includes("haiku-4.5") || lowerModel.includes("haiku-4-5")) {
21181
- return MODEL_PRICING["claude-haiku-4.5"];
21182
- }
21183
- if (lowerModel.includes("opus-4.1") || lowerModel.includes("opus-4-1")) {
21184
- return MODEL_PRICING["claude-opus-4.1"];
21185
- }
21186
- if (lowerModel.includes("opus-4")) {
21187
- return MODEL_PRICING["claude-opus-4"];
21188
- }
21189
- if (lowerModel.includes("sonnet-4")) {
21190
- return MODEL_PRICING["claude-sonnet-4"];
21191
- }
21192
- if (lowerModel.includes("sonnet-3.7") || lowerModel.includes("sonnet-3-7")) {
21193
- return MODEL_PRICING["claude-sonnet-3.7"];
21194
- }
21195
- if (lowerModel.includes("claude-3-5-sonnet") || lowerModel.includes("claude-3.5-sonnet")) {
21196
- return MODEL_PRICING["claude-3.5-sonnet"];
21197
- }
21198
- if (lowerModel.includes("claude-3-5-haiku") || lowerModel.includes("claude-3.5-haiku")) {
21199
- return MODEL_PRICING["claude-3.5-haiku"];
21200
- }
21201
- if (lowerModel.includes("claude-3-opus")) {
21202
- return MODEL_PRICING["claude-3-opus"];
21203
- }
21204
- if (lowerModel.includes("claude-3-sonnet")) {
21205
- return MODEL_PRICING["claude-3-sonnet"];
21206
- }
21207
- if (lowerModel.includes("claude-3-haiku")) {
21208
- return MODEL_PRICING["claude-3-haiku"];
21209
- }
21210
- if (lowerModel.includes("claude")) {
21211
- return MODEL_PRICING["claude-sonnet-4.5"];
21212
- }
21213
- return DEFAULT_PRICING;
21214
- }
21215
- function estimateCost(promptTokens, completionTokens, model) {
21216
- const pricing = getModelPricing(model);
21217
- const promptCostUsd = promptTokens / 1000 * pricing.promptPer1K;
21218
- const completionCostUsd = completionTokens / 1000 * pricing.completionPer1K;
21219
- const totalUsd = promptCostUsd + completionCostUsd;
21220
- return {
21221
- totalUsd,
21222
- promptCostUsd,
21223
- completionCostUsd,
21224
- model,
21225
- pricing
21226
- };
21227
- }
21228
- function formatCost(costUsd) {
21229
- if (costUsd < 0.01) {
21230
- return `$${(costUsd * 100).toFixed(4)} cents`;
21231
- }
21232
- if (costUsd < 1) {
21233
- return `$${costUsd.toFixed(4)}`;
21234
- }
21235
- return `$${costUsd.toFixed(2)}`;
21236
- }
21237
- function listKnownModels() {
21238
- return Object.entries(MODEL_PRICING).map(([model, pricing]) => ({
21239
- model,
21240
- pricing
21241
- }));
21242
- }
21243
-
21244
- // src/provenance/environment.ts
21245
- function getEnvironmentInfo() {
21246
- return {
21247
- node_version: process.version,
21248
- platform: process.platform,
21249
- arch: process.arch
21250
- };
21251
- }
21252
-
21253
- // src/provenance/git.ts
21254
- import { execSync } from "child_process";
21255
- function getGitInfo() {
21256
- try {
21257
- const commit = execGit("rev-parse HEAD");
21258
- const branch = execGit("rev-parse --abbrev-ref HEAD");
21259
- const dirty = execGit("status --porcelain").length > 0;
21260
- const remote = execGit("remote get-url origin", true);
21261
- return {
21262
- commit,
21263
- branch,
21264
- dirty,
21265
- remote: remote || undefined
21266
- };
21267
- } catch {
21268
- return {
21269
- commit: "unknown",
21270
- branch: "unknown",
21271
- dirty: false
21272
- };
21273
- }
21274
- }
21275
- function execGit(command, allowFailure = false) {
21276
- try {
21277
- return execSync(`git ${command}`, {
21278
- encoding: "utf-8",
21279
- stdio: ["pipe", "pipe", "pipe"]
21280
- }).trim();
21281
- } catch {
21282
- if (allowFailure) {
21283
- return "";
21284
- }
21285
- throw new Error(`Git command failed: ${command}`);
21286
- }
21287
- }
21288
-
21289
- // src/artifacts/types.ts
21290
- var CASE_EVALUATION_STATUS_LABELS = {
21291
- passed: "Passed",
21292
- failed: "Failed criteria",
21293
- invalid: "Invalid measurement",
21294
- error: "Execution error"
21295
- };
21296
- function getCaseEvaluationStatus(caseResult) {
21297
- if (caseResult.status === "passed" || caseResult.status === "failed" || caseResult.status === "invalid" || caseResult.status === "error") {
21298
- return caseResult.status;
21031
+ }
21032
+
21033
+ // src/artifacts/types.ts
21034
+ var CASE_EVALUATION_STATUS_LABELS = {
21035
+ passed: "Passed",
21036
+ failed: "Failed criteria",
21037
+ invalid: "Invalid measurement",
21038
+ error: "Execution error"
21039
+ };
21040
+ function getCaseEvaluationStatus(caseResult) {
21041
+ if (caseResult.status === "passed" || caseResult.status === "failed" || caseResult.status === "invalid" || caseResult.status === "error") {
21042
+ return caseResult.status;
21299
21043
  }
21300
21044
  if (caseResult.ok)
21301
21045
  return "passed";
@@ -21314,6 +21058,12 @@ function assertRunManifestIntegrity(manifest) {
21314
21058
  if (manifest.execution_provenance !== undefined) {
21315
21059
  assertExecutionProvenance(manifest.execution_provenance);
21316
21060
  }
21061
+ if (manifest.attempt_evidence !== undefined) {
21062
+ assertRunAttemptEvidence(manifest.attempt_evidence);
21063
+ }
21064
+ if (isRecord2(manifest.metrics) && manifest.metrics.cost_provenance !== undefined) {
21065
+ assertCostProvenance(manifest.metrics.cost_provenance);
21066
+ }
21317
21067
  for (const [index, caseResult] of manifest.cases.entries()) {
21318
21068
  if (!isRecord2(caseResult)) {
21319
21069
  throw new Error(`Invalid run manifest: case ${index} is not an object`);
@@ -21327,8 +21077,61 @@ function assertRunManifestIntegrity(manifest) {
21327
21077
  if (caseResult.target !== undefined) {
21328
21078
  assertCaseTargetEvidence(caseResult.target);
21329
21079
  }
21080
+ if (caseResult.attempt_evidence !== undefined) {
21081
+ assertCaseAttemptEvidence(caseResult.attempt_evidence, index);
21082
+ }
21083
+ }
21084
+ }
21085
+ function assertRunAttemptEvidence(evidence) {
21086
+ if (!isRecord2(evidence) || evidence.schema_version !== "1" || !isRecord2(evidence.repetition) || !isPositiveSafeInteger(evidence.repetition.index) || !isPositiveSafeInteger(evidence.repetition.total) || evidence.repetition.index > evidence.repetition.total || !isRecord2(evidence.retry_policy) || !isNonnegativeSafeInteger(evidence.retry_policy.default_max_retries) || evidence.retry_policy.backoff !== "exponential" || !isNonnegativeFiniteNumber(evidence.retry_policy.initial_delay_ms) || evidence.timeout !== undefined && (!isRecord2(evidence.timeout) || !isPositiveFiniteNumber(evidence.timeout.default_ms))) {
21087
+ throw new Error("Invalid run manifest: malformed attempt evidence");
21088
+ }
21089
+ }
21090
+ function assertCaseAttemptEvidence(evidence, index) {
21091
+ if (!Array.isArray(evidence) || evidence.length === 0 || evidence.length > 100) {
21092
+ throw new Error(`Invalid run manifest: case ${index} has malformed attempt evidence`);
21093
+ }
21094
+ for (const attempt of evidence) {
21095
+ if (!isRecord2(attempt) || !isBoundedNonemptyString(attempt.attempt_id, 200) || !isBoundedNonemptyString(attempt.retry_chain_id, 200) || !isPositiveSafeInteger(attempt.repetition_index) || !isPositiveSafeInteger(attempt.attempt_number) || !isCaseEvaluationStatus(attempt.status) || typeof attempt.included_in_outcome !== "boolean" || !isNonnegativeFiniteNumber(attempt.latency_ms) || attempt.error_code !== undefined && attempt.error_code !== "timeout" && attempt.error_code !== "target_error" && attempt.error_code !== "tool_error") {
21096
+ throw new Error(`Invalid run manifest: case ${index} has malformed attempt evidence`);
21097
+ }
21098
+ }
21099
+ }
21100
+ function assertCostProvenance(cost) {
21101
+ if (!isRecord2(cost) || cost.schema_version !== "1") {
21102
+ throw new Error("Invalid run manifest: malformed cost provenance");
21103
+ }
21104
+ if (cost.status === "unavailable") {
21105
+ if (cost.amount !== undefined || cost.currency !== undefined || cost.source !== undefined || cost.recorded_at !== undefined || cost.unavailable_reason !== "provider_billing_not_recorded" && cost.unavailable_reason !== "unsupported_provider" && cost.unavailable_reason !== "not_requested") {
21106
+ throw new Error("Invalid run manifest: malformed cost provenance");
21107
+ }
21108
+ return;
21109
+ }
21110
+ if (cost.status !== "known" && cost.status !== "user_supplied" || !isNonnegativeFiniteNumber(cost.amount) || !isBoundedNonemptyString(cost.currency, 3) || cost.status === "known" && cost.source !== "provider_billing" || cost.status === "user_supplied" && cost.source !== "operator_input" || !isIsoTimestamp(cost.recorded_at) || cost.unavailable_reason !== undefined) {
21111
+ throw new Error("Invalid run manifest: malformed cost provenance");
21330
21112
  }
21331
21113
  }
21114
+ function isCaseEvaluationStatus(value) {
21115
+ return value === "passed" || value === "failed" || value === "invalid" || value === "error";
21116
+ }
21117
+ function isBoundedNonemptyString(value, maxLength) {
21118
+ return typeof value === "string" && value.length > 0 && value.length <= maxLength;
21119
+ }
21120
+ function isNonnegativeFiniteNumber(value) {
21121
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
21122
+ }
21123
+ function isPositiveFiniteNumber(value) {
21124
+ return isNonnegativeFiniteNumber(value) && value > 0;
21125
+ }
21126
+ function isNonnegativeSafeInteger(value) {
21127
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
21128
+ }
21129
+ function isPositiveSafeInteger(value) {
21130
+ return isNonnegativeSafeInteger(value) && value > 0;
21131
+ }
21132
+ function isIsoTimestamp(value) {
21133
+ return typeof value === "string" && Number.isFinite(Date.parse(value));
21134
+ }
21332
21135
  function assertCaseTargetEvidence(target) {
21333
21136
  if (!isRecord2(target) || typeof target.provider !== "string" || target.provider.length === 0 || target.provider.length > 100 || target.requested_model !== undefined && (typeof target.requested_model !== "string" || target.requested_model.length > 200) || !isBoundedStringList(target.observed_models)) {
21334
21137
  throw new Error("Invalid run manifest: malformed target evidence");
@@ -21407,6 +21210,9 @@ function createRunManifest(options) {
21407
21210
  resolvedConfig,
21408
21211
  workloadIdentity,
21409
21212
  executionProvenance,
21213
+ attemptEvidence,
21214
+ costProvenance,
21215
+ runId,
21410
21216
  cases,
21411
21217
  startTime,
21412
21218
  endTime,
@@ -21414,13 +21220,12 @@ function createRunManifest(options) {
21414
21220
  runReason,
21415
21221
  redaction
21416
21222
  } = options;
21417
- const modelForCost = resolvedConfig?.model || config.model;
21418
- const metrics = calculateMetrics(cases, modelForCost);
21223
+ const metrics = calculateMetrics(cases, costProvenance);
21419
21224
  const git = getGitInfo();
21420
21225
  const environment = getEnvironmentInfo();
21421
21226
  return {
21422
- version: "1.3",
21423
- run_id: nanoid(12),
21227
+ version: "1.4",
21228
+ run_id: runId ?? nanoid(12),
21424
21229
  project,
21425
21230
  start_time: startTime.toISOString(),
21426
21231
  end_time: endTime.toISOString(),
@@ -21429,6 +21234,7 @@ function createRunManifest(options) {
21429
21234
  resolved_config: resolvedConfig,
21430
21235
  workload_identity: workloadIdentity,
21431
21236
  execution_provenance: executionProvenance,
21237
+ attempt_evidence: attemptEvidence,
21432
21238
  metrics,
21433
21239
  git,
21434
21240
  provenance: {
@@ -21441,7 +21247,7 @@ function createRunManifest(options) {
21441
21247
  redaction
21442
21248
  };
21443
21249
  }
21444
- function calculateMetrics(cases, model) {
21250
+ function calculateMetrics(cases, costProvenance) {
21445
21251
  const passedCases = cases.filter((c) => getCaseEvaluationStatus(c) === "passed");
21446
21252
  const validCases = cases.filter((c) => {
21447
21253
  const status = getCaseEvaluationStatus(c);
@@ -21453,21 +21259,11 @@ function calculateMetrics(cases, model) {
21453
21259
  const p95Latency = latencies.length > 0 ? latencies[p95Index] : 0;
21454
21260
  const totalPromptTokens = cases.reduce((sum, c) => sum + c.tokens.prompt, 0);
21455
21261
  const totalCompletionTokens = cases.reduce((sum, c) => sum + c.tokens.completion, 0);
21456
- let cost;
21457
- if (model && !model.toLowerCase().includes("ling-") && (totalPromptTokens > 0 || totalCompletionTokens > 0)) {
21458
- const costEstimate = estimateCost(totalPromptTokens, totalCompletionTokens, model);
21459
- const pricing = getModelPricing(model);
21460
- cost = {
21461
- total_usd: costEstimate.totalUsd,
21462
- prompt_cost_usd: costEstimate.promptCostUsd,
21463
- completion_cost_usd: costEstimate.completionCostUsd,
21464
- model: costEstimate.model,
21465
- pricing: {
21466
- prompt_per_1k: pricing.promptPer1K,
21467
- completion_per_1k: pricing.completionPer1K
21468
- }
21469
- };
21470
- }
21262
+ const cost_provenance = costProvenance ?? {
21263
+ schema_version: "1",
21264
+ status: "unavailable",
21265
+ unavailable_reason: "provider_billing_not_recorded"
21266
+ };
21471
21267
  return {
21472
21268
  success_rate: validCases.length > 0 ? passedCases.length / validCases.length : 0,
21473
21269
  total_attempts: cases.reduce((sum, c) => sum + (c.attempts ?? 1), 0),
@@ -21482,7 +21278,7 @@ function calculateMetrics(cases, model) {
21482
21278
  total_tokens: totalPromptTokens + totalCompletionTokens,
21483
21279
  total_prompt_tokens: totalPromptTokens,
21484
21280
  total_completion_tokens: totalCompletionTokens,
21485
- cost
21281
+ cost_provenance
21486
21282
  };
21487
21283
  }
21488
21284
  function detectCIEnvironment() {
@@ -21625,6 +21421,8 @@ async function runScenario(options) {
21625
21421
  concurrency = 1,
21626
21422
  timeout,
21627
21423
  retries,
21424
+ repetition = { index: 1, total: 1 },
21425
+ costProvenance,
21628
21426
  redaction,
21629
21427
  toolExecutor,
21630
21428
  onCaseComplete,
@@ -21640,6 +21438,7 @@ async function runScenario(options) {
21640
21438
  }
21641
21439
  onProgress?.(`Running ${cases.length} test cases...`);
21642
21440
  const startTime = new Date;
21441
+ const runId = nanoid(12);
21643
21442
  const results = [];
21644
21443
  if (concurrency === 1) {
21645
21444
  for (let i = 0;i < cases.length; i++) {
@@ -21650,6 +21449,8 @@ async function runScenario(options) {
21650
21449
  requestedModel: resolvedConfig?.model,
21651
21450
  timeout: testCase.timeout || timeout,
21652
21451
  retries: testCase.retries ?? retries,
21452
+ runId,
21453
+ repetition,
21653
21454
  redaction,
21654
21455
  toolExecutor
21655
21456
  });
@@ -21667,6 +21468,8 @@ async function runScenario(options) {
21667
21468
  requestedModel: resolvedConfig?.model,
21668
21469
  timeout: testCase.timeout || timeout,
21669
21470
  retries: testCase.retries ?? retries,
21471
+ runId,
21472
+ repetition,
21670
21473
  redaction,
21671
21474
  toolExecutor
21672
21475
  });
@@ -21717,9 +21520,21 @@ async function runScenario(options) {
21717
21520
  seed: scenario.seed,
21718
21521
  cases: results
21719
21522
  }),
21523
+ attemptEvidence: {
21524
+ schema_version: "1",
21525
+ repetition,
21526
+ retry_policy: {
21527
+ default_max_retries: retries ?? 0,
21528
+ backoff: "exponential",
21529
+ initial_delay_ms: 1000
21530
+ },
21531
+ ...timeout ? { timeout: { default_ms: timeout } } : {}
21532
+ },
21533
+ costProvenance,
21720
21534
  cases: results,
21721
21535
  startTime,
21722
21536
  endTime,
21537
+ runId,
21723
21538
  redaction: redactionInfo
21724
21539
  });
21725
21540
  const success = manifest.metrics.failed_cases === 0 && (manifest.metrics.invalid_evaluations ?? 0) === 0;
@@ -21747,6 +21562,65 @@ function chunkArray(array, size) {
21747
21562
  // src/storage/local.ts
21748
21563
  import { mkdir, readFile as readFile2, readdir as readdir2, unlink, writeFile } from "fs/promises";
21749
21564
  import { join as join2, resolve as resolve2 } from "path";
21565
+
21566
+ // src/comparison/eligibility.ts
21567
+ function assessComparisonEligibility(baseline, current) {
21568
+ const reasons = [];
21569
+ if (baseline.config.scenario !== current.config.scenario) {
21570
+ reasons.push({ code: "scenario_mismatch" });
21571
+ }
21572
+ const baselineIdentity = baseline.workload_identity;
21573
+ const currentIdentity = current.workload_identity;
21574
+ if (!baselineIdentity || !currentIdentity) {
21575
+ if (!baselineIdentity || !currentIdentity)
21576
+ reasons.push({ code: "workload_identity_missing" });
21577
+ if (!baselineIdentity || !currentIdentity)
21578
+ reasons.push({ code: "rubric_identity_missing" });
21579
+ } else {
21580
+ if (baselineIdentity.workload.digest !== currentIdentity.workload.digest) {
21581
+ reasons.push({ code: "workload_mismatch" });
21582
+ }
21583
+ if (baselineIdentity.rubric.digest !== currentIdentity.rubric.digest) {
21584
+ reasons.push({ code: "rubric_mismatch" });
21585
+ }
21586
+ }
21587
+ if (reasons.some((reason) => isIncomparableReason(reason.code))) {
21588
+ return { schema_version: "1", status: "incomparable", reasons };
21589
+ }
21590
+ const baselineExecution = baseline.execution_provenance;
21591
+ const currentExecution = current.execution_provenance;
21592
+ if (!baselineExecution || !currentExecution) {
21593
+ reasons.push({ code: "execution_provenance_missing" });
21594
+ } else {
21595
+ if (baselineExecution.target.provider !== currentExecution.target.provider) {
21596
+ reasons.push({ code: "target_provider_changed" });
21597
+ }
21598
+ if (!sameStringSet(baselineExecution.target.requested_models, currentExecution.target.requested_models)) {
21599
+ reasons.push({ code: "target_model_changed" });
21600
+ }
21601
+ if (!sameGeneration(baselineExecution.target.generation, currentExecution.target.generation)) {
21602
+ reasons.push({ code: "generation_settings_changed" });
21603
+ }
21604
+ }
21605
+ return {
21606
+ schema_version: "1",
21607
+ status: reasons.length === 0 ? "compatible" : "qualified",
21608
+ reasons
21609
+ };
21610
+ }
21611
+ function isComparisonAvailable(eligibility) {
21612
+ return eligibility.status !== "incomparable";
21613
+ }
21614
+ function isIncomparableReason(code) {
21615
+ return code === "scenario_mismatch" || code === "workload_mismatch" || code === "rubric_mismatch";
21616
+ }
21617
+ function sameStringSet(left, right) {
21618
+ return JSON.stringify([...left ?? []].sort()) === JSON.stringify([...right ?? []].sort());
21619
+ }
21620
+ function sameGeneration(left, right) {
21621
+ return left?.temperature === right?.temperature && left?.max_tokens === right?.max_tokens && left?.seed === right?.seed;
21622
+ }
21623
+ // src/storage/local.ts
21750
21624
  function getManifestType(manifest) {
21751
21625
  if ("type" in manifest) {
21752
21626
  if (manifest.type === "redteam")
@@ -21886,9 +21760,14 @@ class LocalStorageAdapter {
21886
21760
  this.loadRun(baselineId),
21887
21761
  this.loadRun(currentId)
21888
21762
  ]);
21763
+ const eligibility = assessComparisonEligibility(baseline, current);
21764
+ if (!isComparisonAvailable(eligibility)) {
21765
+ return { baseline, current, eligibility };
21766
+ }
21889
21767
  return {
21890
21768
  baseline,
21891
21769
  current,
21770
+ eligibility,
21892
21771
  delta: {
21893
21772
  successRate: current.metrics.success_rate - baseline.metrics.success_rate,
21894
21773
  latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
@@ -21987,7 +21866,7 @@ class LocalStorageAdapter {
21987
21866
  return null;
21988
21867
  }
21989
21868
  const comparison = await this.compare(baseline.runId, runId);
21990
- const hasRegression = comparison.delta.successRate < -regressionThreshold;
21869
+ const hasRegression = comparison.delta !== undefined && comparison.delta.successRate < -regressionThreshold;
21991
21870
  return {
21992
21871
  baseline,
21993
21872
  comparison,
@@ -30633,9 +30512,14 @@ class SupabaseStorageAdapter {
30633
30512
  }
30634
30513
  async compare(baselineId, currentId) {
30635
30514
  const [baseline, current] = await Promise.all([this.load(baselineId), this.load(currentId)]);
30515
+ const eligibility = assessComparisonEligibility(baseline, current);
30516
+ if (!isComparisonAvailable(eligibility)) {
30517
+ return { baseline, current, eligibility };
30518
+ }
30636
30519
  return {
30637
30520
  baseline,
30638
30521
  current,
30522
+ eligibility,
30639
30523
  delta: {
30640
30524
  successRate: current.metrics.success_rate - baseline.metrics.success_rate,
30641
30525
  latency: current.metrics.median_latency_ms - baseline.metrics.median_latency_ms,
@@ -30766,7 +30650,7 @@ class SupabaseStorageAdapter {
30766
30650
  return null;
30767
30651
  }
30768
30652
  const comparison = await this.compare(baseline.runId, runId);
30769
- const hasRegression = comparison.delta.successRate < -regressionThreshold;
30653
+ const hasRegression = comparison.delta !== undefined && comparison.delta.successRate < -regressionThreshold;
30770
30654
  return {
30771
30655
  baseline,
30772
30656
  comparison,
@@ -32101,6 +31985,318 @@ class Logger {
32101
31985
  }
32102
31986
  }
32103
31987
  var logger = new Logger("artemis");
31988
+ // src/cost/pricing.ts
31989
+ var MODEL_PRICING = {
31990
+ "gpt-5": {
31991
+ promptPer1K: 0.00125,
31992
+ completionPer1K: 0.01,
31993
+ lastUpdated: "2026-01",
31994
+ notes: "400K context window"
31995
+ },
31996
+ "gpt-5.1": {
31997
+ promptPer1K: 0.00125,
31998
+ completionPer1K: 0.01,
31999
+ lastUpdated: "2026-01"
32000
+ },
32001
+ "gpt-5.2": {
32002
+ promptPer1K: 0.00175,
32003
+ completionPer1K: 0.014,
32004
+ lastUpdated: "2026-01"
32005
+ },
32006
+ "gpt-5-mini": {
32007
+ promptPer1K: 0.00025,
32008
+ completionPer1K: 0.002,
32009
+ lastUpdated: "2026-01"
32010
+ },
32011
+ "gpt-5-nano": {
32012
+ promptPer1K: 0.00005,
32013
+ completionPer1K: 0.0004,
32014
+ lastUpdated: "2026-01"
32015
+ },
32016
+ "gpt-4.1": {
32017
+ promptPer1K: 0.002,
32018
+ completionPer1K: 0.008,
32019
+ lastUpdated: "2026-01",
32020
+ notes: "1M context window"
32021
+ },
32022
+ "gpt-4.1-mini": {
32023
+ promptPer1K: 0.0004,
32024
+ completionPer1K: 0.0016,
32025
+ lastUpdated: "2026-01"
32026
+ },
32027
+ "gpt-4.1-nano": {
32028
+ promptPer1K: 0.0001,
32029
+ completionPer1K: 0.0004,
32030
+ lastUpdated: "2026-01"
32031
+ },
32032
+ "gpt-4o": {
32033
+ promptPer1K: 0.0025,
32034
+ completionPer1K: 0.01,
32035
+ lastUpdated: "2026-01",
32036
+ notes: "128K context window"
32037
+ },
32038
+ "gpt-4o-mini": {
32039
+ promptPer1K: 0.00015,
32040
+ completionPer1K: 0.0006,
32041
+ lastUpdated: "2026-01",
32042
+ notes: "128K context window"
32043
+ },
32044
+ o1: {
32045
+ promptPer1K: 0.015,
32046
+ completionPer1K: 0.06,
32047
+ lastUpdated: "2026-01",
32048
+ notes: "Reasoning model - internal thinking tokens billed as output"
32049
+ },
32050
+ o3: {
32051
+ promptPer1K: 0.002,
32052
+ completionPer1K: 0.008,
32053
+ lastUpdated: "2026-01"
32054
+ },
32055
+ "o3-mini": {
32056
+ promptPer1K: 0.0011,
32057
+ completionPer1K: 0.0044,
32058
+ lastUpdated: "2026-01"
32059
+ },
32060
+ "o4-mini": {
32061
+ promptPer1K: 0.0011,
32062
+ completionPer1K: 0.0044,
32063
+ lastUpdated: "2026-01"
32064
+ },
32065
+ "gpt-4-turbo": {
32066
+ promptPer1K: 0.01,
32067
+ completionPer1K: 0.03,
32068
+ lastUpdated: "2026-01"
32069
+ },
32070
+ "gpt-4": {
32071
+ promptPer1K: 0.03,
32072
+ completionPer1K: 0.06,
32073
+ lastUpdated: "2026-01"
32074
+ },
32075
+ "gpt-3.5-turbo": {
32076
+ promptPer1K: 0.0005,
32077
+ completionPer1K: 0.0015,
32078
+ lastUpdated: "2026-01"
32079
+ },
32080
+ "claude-opus-4.5": {
32081
+ promptPer1K: 0.005,
32082
+ completionPer1K: 0.025,
32083
+ lastUpdated: "2026-01",
32084
+ notes: "Most capable Claude model"
32085
+ },
32086
+ "claude-sonnet-4.5": {
32087
+ promptPer1K: 0.003,
32088
+ completionPer1K: 0.015,
32089
+ lastUpdated: "2026-01",
32090
+ notes: "Balanced performance and cost"
32091
+ },
32092
+ "claude-haiku-4.5": {
32093
+ promptPer1K: 0.001,
32094
+ completionPer1K: 0.005,
32095
+ lastUpdated: "2026-01",
32096
+ notes: "Fastest Claude model"
32097
+ },
32098
+ "claude-opus-4": {
32099
+ promptPer1K: 0.015,
32100
+ completionPer1K: 0.075,
32101
+ lastUpdated: "2026-01"
32102
+ },
32103
+ "claude-opus-4.1": {
32104
+ promptPer1K: 0.015,
32105
+ completionPer1K: 0.075,
32106
+ lastUpdated: "2026-01"
32107
+ },
32108
+ "claude-sonnet-4": {
32109
+ promptPer1K: 0.003,
32110
+ completionPer1K: 0.015,
32111
+ lastUpdated: "2026-01"
32112
+ },
32113
+ "claude-sonnet-3.7": {
32114
+ promptPer1K: 0.003,
32115
+ completionPer1K: 0.015,
32116
+ lastUpdated: "2026-01"
32117
+ },
32118
+ "claude-3-7-sonnet": {
32119
+ promptPer1K: 0.003,
32120
+ completionPer1K: 0.015,
32121
+ lastUpdated: "2026-01"
32122
+ },
32123
+ "claude-3-5-sonnet-20241022": {
32124
+ promptPer1K: 0.003,
32125
+ completionPer1K: 0.015,
32126
+ lastUpdated: "2026-01"
32127
+ },
32128
+ "claude-3-5-haiku-20241022": {
32129
+ promptPer1K: 0.0008,
32130
+ completionPer1K: 0.004,
32131
+ lastUpdated: "2026-01"
32132
+ },
32133
+ "claude-haiku-3.5": {
32134
+ promptPer1K: 0.0008,
32135
+ completionPer1K: 0.004,
32136
+ lastUpdated: "2026-01"
32137
+ },
32138
+ "claude-3-opus": {
32139
+ promptPer1K: 0.015,
32140
+ completionPer1K: 0.075,
32141
+ lastUpdated: "2026-01"
32142
+ },
32143
+ "claude-3-sonnet": {
32144
+ promptPer1K: 0.003,
32145
+ completionPer1K: 0.015,
32146
+ lastUpdated: "2026-01"
32147
+ },
32148
+ "claude-3-haiku": {
32149
+ promptPer1K: 0.00025,
32150
+ completionPer1K: 0.00125,
32151
+ lastUpdated: "2026-01"
32152
+ },
32153
+ "claude-3.5-sonnet": {
32154
+ promptPer1K: 0.003,
32155
+ completionPer1K: 0.015,
32156
+ lastUpdated: "2026-01"
32157
+ },
32158
+ "claude-3.5-haiku": {
32159
+ promptPer1K: 0.0008,
32160
+ completionPer1K: 0.004,
32161
+ lastUpdated: "2026-01"
32162
+ }
32163
+ };
32164
+ var DEFAULT_PRICING = {
32165
+ promptPer1K: 0.003,
32166
+ completionPer1K: 0.015,
32167
+ lastUpdated: "2026-01",
32168
+ notes: "Default pricing - verify with provider"
32169
+ };
32170
+ function getModelPricing(model) {
32171
+ if (MODEL_PRICING[model]) {
32172
+ return MODEL_PRICING[model];
32173
+ }
32174
+ const lowerModel = model.toLowerCase();
32175
+ for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
32176
+ if (key.toLowerCase() === lowerModel) {
32177
+ return pricing;
32178
+ }
32179
+ }
32180
+ if (lowerModel.includes("gpt-5.2")) {
32181
+ return MODEL_PRICING["gpt-5.2"];
32182
+ }
32183
+ if (lowerModel.includes("gpt-5.1")) {
32184
+ return MODEL_PRICING["gpt-5.1"];
32185
+ }
32186
+ if (lowerModel.includes("gpt-5-mini")) {
32187
+ return MODEL_PRICING["gpt-5-mini"];
32188
+ }
32189
+ if (lowerModel.includes("gpt-5-nano")) {
32190
+ return MODEL_PRICING["gpt-5-nano"];
32191
+ }
32192
+ if (lowerModel.includes("gpt-5")) {
32193
+ return MODEL_PRICING["gpt-5"];
32194
+ }
32195
+ if (lowerModel.includes("gpt-4.1-mini")) {
32196
+ return MODEL_PRICING["gpt-4.1-mini"];
32197
+ }
32198
+ if (lowerModel.includes("gpt-4.1-nano")) {
32199
+ return MODEL_PRICING["gpt-4.1-nano"];
32200
+ }
32201
+ if (lowerModel.includes("gpt-4.1")) {
32202
+ return MODEL_PRICING["gpt-4.1"];
32203
+ }
32204
+ if (lowerModel.includes("gpt-4o-mini")) {
32205
+ return MODEL_PRICING["gpt-4o-mini"];
32206
+ }
32207
+ if (lowerModel.includes("gpt-4o")) {
32208
+ return MODEL_PRICING["gpt-4o"];
32209
+ }
32210
+ if (lowerModel.includes("o4-mini")) {
32211
+ return MODEL_PRICING["o4-mini"];
32212
+ }
32213
+ if (lowerModel.includes("o3-mini")) {
32214
+ return MODEL_PRICING["o3-mini"];
32215
+ }
32216
+ if (lowerModel.includes("o3")) {
32217
+ return MODEL_PRICING.o3;
32218
+ }
32219
+ if (lowerModel.includes("o1")) {
32220
+ return MODEL_PRICING.o1;
32221
+ }
32222
+ if (lowerModel.includes("gpt-4-turbo")) {
32223
+ return MODEL_PRICING["gpt-4-turbo"];
32224
+ }
32225
+ if (lowerModel.includes("gpt-4")) {
32226
+ return MODEL_PRICING["gpt-4"];
32227
+ }
32228
+ if (lowerModel.includes("gpt-3.5")) {
32229
+ return MODEL_PRICING["gpt-3.5-turbo"];
32230
+ }
32231
+ if (lowerModel.includes("opus-4.5") || lowerModel.includes("opus-4-5")) {
32232
+ return MODEL_PRICING["claude-opus-4.5"];
32233
+ }
32234
+ if (lowerModel.includes("sonnet-4.5") || lowerModel.includes("sonnet-4-5")) {
32235
+ return MODEL_PRICING["claude-sonnet-4.5"];
32236
+ }
32237
+ if (lowerModel.includes("haiku-4.5") || lowerModel.includes("haiku-4-5")) {
32238
+ return MODEL_PRICING["claude-haiku-4.5"];
32239
+ }
32240
+ if (lowerModel.includes("opus-4.1") || lowerModel.includes("opus-4-1")) {
32241
+ return MODEL_PRICING["claude-opus-4.1"];
32242
+ }
32243
+ if (lowerModel.includes("opus-4")) {
32244
+ return MODEL_PRICING["claude-opus-4"];
32245
+ }
32246
+ if (lowerModel.includes("sonnet-4")) {
32247
+ return MODEL_PRICING["claude-sonnet-4"];
32248
+ }
32249
+ if (lowerModel.includes("sonnet-3.7") || lowerModel.includes("sonnet-3-7")) {
32250
+ return MODEL_PRICING["claude-sonnet-3.7"];
32251
+ }
32252
+ if (lowerModel.includes("claude-3-5-sonnet") || lowerModel.includes("claude-3.5-sonnet")) {
32253
+ return MODEL_PRICING["claude-3.5-sonnet"];
32254
+ }
32255
+ if (lowerModel.includes("claude-3-5-haiku") || lowerModel.includes("claude-3.5-haiku")) {
32256
+ return MODEL_PRICING["claude-3.5-haiku"];
32257
+ }
32258
+ if (lowerModel.includes("claude-3-opus")) {
32259
+ return MODEL_PRICING["claude-3-opus"];
32260
+ }
32261
+ if (lowerModel.includes("claude-3-sonnet")) {
32262
+ return MODEL_PRICING["claude-3-sonnet"];
32263
+ }
32264
+ if (lowerModel.includes("claude-3-haiku")) {
32265
+ return MODEL_PRICING["claude-3-haiku"];
32266
+ }
32267
+ if (lowerModel.includes("claude")) {
32268
+ return MODEL_PRICING["claude-sonnet-4.5"];
32269
+ }
32270
+ return DEFAULT_PRICING;
32271
+ }
32272
+ function estimateCost(promptTokens, completionTokens, model) {
32273
+ const pricing = getModelPricing(model);
32274
+ const promptCostUsd = promptTokens / 1000 * pricing.promptPer1K;
32275
+ const completionCostUsd = completionTokens / 1000 * pricing.completionPer1K;
32276
+ const totalUsd = promptCostUsd + completionCostUsd;
32277
+ return {
32278
+ totalUsd,
32279
+ promptCostUsd,
32280
+ completionCostUsd,
32281
+ model,
32282
+ pricing
32283
+ };
32284
+ }
32285
+ function formatCost(costUsd) {
32286
+ if (costUsd < 0.01) {
32287
+ return `$${(costUsd * 100).toFixed(4)} cents`;
32288
+ }
32289
+ if (costUsd < 1) {
32290
+ return `$${costUsd.toFixed(4)}`;
32291
+ }
32292
+ return `$${costUsd.toFixed(2)}`;
32293
+ }
32294
+ function listKnownModels() {
32295
+ return Object.entries(MODEL_PRICING).map(([model, pricing]) => ({
32296
+ model,
32297
+ pricing
32298
+ }));
32299
+ }
32104
32300
  // src/agent-evaluation/types.ts
32105
32301
  function actionBudgetExceeded(task, trace) {
32106
32302
  return trace.actions.length > task.maxActions;
@@ -32797,6 +32993,7 @@ export {
32797
32993
  actionBudgetExceeded,
32798
32994
  adapterRegistry,
32799
32995
  assertRunManifestIntegrity,
32996
+ assessComparisonEligibility,
32800
32997
  createAdapter,
32801
32998
  createDefaultRedactor,
32802
32999
  createExecutionProvenance,
@@ -32818,6 +33015,7 @@ export {
32818
33015
  getModelPricing,
32819
33016
  hashText,
32820
33017
  isArtemisError,
33018
+ isComparisonAvailable,
32821
33019
  isRedTeamManifest,
32822
33020
  isRunManifest,
32823
33021
  isStressManifest,