@cdot65/prisma-airs-cli 7.1.3 → 7.1.5

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +47 -85
  2. package/package.json +3 -2
package/dist/cli/index.js CHANGED
@@ -6289,61 +6289,10 @@ function isDict(value) {
6289
6289
  function digest(text2) {
6290
6290
  return createHash("sha256").update(text2, "utf8").digest("hex").slice(0, 16);
6291
6291
  }
6292
- function pythonJson(value) {
6293
- if (Array.isArray(value)) return `[${value.map(pythonJson).join(", ")}]`;
6294
- if (isDict(value))
6295
- return `{${Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => `${JSON.stringify(k)}: ${pythonJson(v)}`).join(", ")}}`;
6296
- return JSON.stringify(value ?? null);
6297
- }
6298
- function textFromContentList(content) {
6299
- if (!Array.isArray(content)) return null;
6300
- const parts = content.filter((c) => isDict(c) && typeof c.text === "string").map((c) => c.text);
6301
- return parts.length ? parts.join("\n") : null;
6302
- }
6303
6292
  function extractResponseText(raw) {
6304
6293
  if (raw === null || raw === void 0) return ["", "empty"];
6305
- let value = raw;
6306
- let method = "plain";
6307
- if (typeof raw === "string") {
6308
- const stripped = raw.trim();
6309
- if (!stripped) return ["", "empty"];
6310
- if (stripped[0] !== "{" && stripped[0] !== "[") return [raw, "plain"];
6311
- [value, method] = decodeContainer(stripped);
6312
- if (method === "plain") return [raw, "plain"];
6313
- }
6314
- const message = messageText(value);
6315
- if (message !== null) return message;
6316
- if (isDict(value)) {
6317
- const outputs = value.output;
6318
- if (Array.isArray(outputs)) {
6319
- const texts = outputs.map((item) => isDict(item) ? textFromContentList(item.content) : null).filter((text2) => Boolean(text2));
6320
- if (texts.length) return [texts.join("\n"), "responses_api"];
6321
- }
6322
- const choices = value.choices;
6323
- if (Array.isArray(choices) && choices.length && isDict(choices[0])) {
6324
- const message2 = choices[0].message;
6325
- if (isDict(message2) && typeof message2.content === "string")
6326
- return [message2.content, "chat_completions"];
6327
- }
6328
- for (const key of ["output_text", "response", "text", "content", "message", "answer"]) {
6329
- if (typeof value[key] === "string") return [value[key], `json.${key}`];
6330
- }
6331
- const turns = value.messages || value.turns || value.conversation;
6332
- if (Array.isArray(turns) && turns.length) {
6333
- const last = turns[turns.length - 1];
6334
- if (isDict(last)) {
6335
- const text2 = typeof last.content === "string" ? last.content : textFromContentList(last.content);
6336
- if (text2) return [text2, "multi_turn_last"];
6337
- }
6338
- }
6339
- return [pythonJson(value), `${method}_unparsed`];
6340
- }
6341
- if (Array.isArray(value)) {
6342
- const texts = value.filter((item) => typeof item === "string");
6343
- if (texts.length) return [texts.join("\n"), "list_of_strings"];
6344
- return [pythonJson(value), `${method}_unparsed`];
6345
- }
6346
- return [String(value), "coerced"];
6294
+ if (typeof raw !== "string") throw new Error("Expected output to be a string");
6295
+ return [raw, raw.trim() ? "plain" : "empty"];
6347
6296
  }
6348
6297
  function recordsFromDocument(document) {
6349
6298
  if (Array.isArray(document)) return [document.filter(isDict), "array"];
@@ -6412,10 +6361,6 @@ function normalizeScan(document, options = {}) {
6412
6361
  const airsAsr = typeof record.asr === "number" ? record.asr : null;
6413
6362
  for (const [outputIndex, output] of rawOutputs.entries()) {
6414
6363
  const [text2, extraction] = extractResponseText(output.output);
6415
- if (extraction.startsWith("a2a_"))
6416
- notes.response_envelopes = (notes.response_envelopes ?? 0) + 1;
6417
- if (extraction === "a2a_unsupported_parts")
6418
- notes.unsupported_response_envelopes = (notes.unsupported_response_envelopes ?? 0) + 1;
6419
6364
  const isError = Boolean(output.error) || !text2.trim();
6420
6365
  if (isError) notes.error_outputs += 1;
6421
6366
  const outputId = label(output.uuid) || String(outputIndex);
@@ -6474,6 +6419,7 @@ import pLimit2 from "p-limit";
6474
6419
 
6475
6420
  // src/redteam/judge/providers.ts
6476
6421
  import { createHash as createHash2 } from "crypto";
6422
+ import { TypeSafeClient } from "@typesafe-ai/sdk";
6477
6423
  var ProviderError = class extends Error {
6478
6424
  constructor(message) {
6479
6425
  super(message);
@@ -6510,7 +6456,7 @@ function typesafeEndpoint(baseUrl, path3) {
6510
6456
  return `${url.toString().replace(/\/+$/, "")}${path3}`;
6511
6457
  }
6512
6458
  var TypeSafeHttpProvider = class {
6513
- name = "typesafe-http";
6459
+ name = "typesafe-sdk";
6514
6460
  model;
6515
6461
  url;
6516
6462
  apiKey;
@@ -6528,33 +6474,41 @@ var TypeSafeHttpProvider = class {
6528
6474
  this.sleep = options.sleep ?? defaultSleep;
6529
6475
  }
6530
6476
  async judge(request) {
6531
- const body = JSON.stringify({
6532
- state: request.state,
6533
- model: this.model,
6534
- questions: request.questions
6477
+ let received;
6478
+ const client = new TypeSafeClient({
6479
+ apiKey: this.apiKey,
6480
+ baseURL: this.url.slice(0, -"/v1/systemone".length),
6481
+ timeout: this.timeoutMs,
6482
+ retry: { maxRetries: 0 },
6483
+ // One retry owner: the adapter below.
6484
+ logLevel: "off",
6485
+ // Do not allow SDK environment settings to log scan text.
6486
+ fetch: async (input2, init2) => {
6487
+ received = await this.fetch(input2, { ...init2, redirect: "error" });
6488
+ return received;
6489
+ }
6535
6490
  });
6536
6491
  for (let attempt = 0; ; attempt += 1) {
6537
6492
  const started = performance.now();
6538
6493
  let response;
6539
6494
  try {
6540
- response = await this.fetch(this.url, {
6541
- method: "POST",
6542
- redirect: "error",
6543
- headers: {
6544
- Authorization: `Bearer ${this.apiKey}`,
6545
- "Content-Type": "application/json",
6546
- Accept: "application/json"
6547
- },
6548
- body,
6549
- signal: AbortSignal.timeout(this.timeoutMs)
6550
- });
6495
+ received = void 0;
6496
+ response = await client.systemOne({
6497
+ state: request.state,
6498
+ model: this.model,
6499
+ questions: request.questions
6500
+ }).asResponse();
6551
6501
  } catch (error) {
6552
- if (attempt >= this.maxRetries)
6553
- throw new ProviderError(
6554
- `TypeSafe API connection failed: ${error instanceof Error ? error.name : "Error"}`
6555
- );
6556
- await this.sleep(retryDelayMs(attempt, null));
6557
- continue;
6502
+ if (received && !received.ok) {
6503
+ response = received;
6504
+ } else {
6505
+ if (attempt >= this.maxRetries)
6506
+ throw new ProviderError(
6507
+ `TypeSafe API connection failed: ${error instanceof Error ? error.name : "Error"}`
6508
+ );
6509
+ await this.sleep(retryDelayMs(attempt, null));
6510
+ continue;
6511
+ }
6558
6512
  }
6559
6513
  if (response.ok) {
6560
6514
  let decoded;
@@ -6902,6 +6856,13 @@ function renderSummary(results) {
6902
6856
  "| Category | Judged | Judge ASR | AIRS ASR | Agreement |",
6903
6857
  "|---|---|---|---|---|"
6904
6858
  ];
6859
+ if (results.provider === "replay")
6860
+ lines.splice(
6861
+ 2,
6862
+ 0,
6863
+ "REPLAY ONLY: reused recorded answers; no new Jev evaluation or credential check was performed.",
6864
+ ""
6865
+ );
6905
6866
  for (const [name, block] of Object.entries(results.by_category))
6906
6867
  lines.push(
6907
6868
  `| ${name} | ${block.judged} | ${pct(block.asr)} | ${pct(block.agreement.airs_asr_from_threat_flags)} | ${pct(block.agreement.agreement_rate)} |`
@@ -8102,6 +8063,7 @@ async function backupTargets(opts) {
8102
8063
  import { lstat as lstat3, mkdir, readFile as readFile5 } from "fs/promises";
8103
8064
  import { join as join2, resolve as resolve3 } from "path";
8104
8065
  import { RedTeamClient } from "@cdot65/prisma-airs-sdk";
8066
+ import { Option as Option2 } from "commander";
8105
8067
  var JUDGE_PROVIDER_ERROR_EXIT_CODE = 4;
8106
8068
  var JUDGE_OUTPUT_FILES = ["results.json", "judgments.json", "summary.md"];
8107
8069
  function probability(raw, flag) {
@@ -8153,7 +8115,7 @@ function safeContext2() {
8153
8115
  function registerRedTeamJudgeCommand(redteam) {
8154
8116
  const command = redteam.command("judge [scanFile]").description(
8155
8117
  "Judge red-team attack outputs with TypeSafe Jev and compute an independent attack success rate"
8156
- ).option("--job <jobId>", "Fetch the attacks of a completed scan instead of reading a file").requiredOption("--out <dir>", "Directory for results.json, judgments.json and summary.md").option("--provider <name>", "typesafe (network) or replay (recorded answers)", "typesafe").option("--replay <file>", "Recorded judgments file for --provider replay").option("--record <file>", "Write raw provider answers here for later replay").option("--model <id>", `TypeSafe model (default: typesafeModel or ${DEFAULT_TYPESAFE_MODEL})`).option(
8118
+ ).option("--job <jobId>", "Fetch the attacks of a completed scan instead of reading a file").requiredOption("--out <dir>", "Directory for results.json, judgments.json and summary.md").option("--provider <name>", "typesafe (network) or replay (recorded answers)", "typesafe").option("--replay <file>", "Recorded judgments file for --provider replay").option("--record <file>", "Write raw provider answers here for later replay").addOption(new Option2("--harness-credentials").hideHelp()).option("--model <id>", `TypeSafe model (default: typesafeModel or ${DEFAULT_TYPESAFE_MODEL})`).option(
8157
8119
  "--base-url <url>",
8158
8120
  `TypeSafe API base URL (default: typesafeBaseUrl or ${DEFAULT_TYPESAFE_BASE_URL})`
8159
8121
  ).option(
@@ -8199,7 +8161,7 @@ async function runJudge(command, scanFile, opts) {
8199
8161
  const maxRetries = opts.maxRetries === "0" ? 0 : integer3(opts.maxRetries, "--max-retries", 10);
8200
8162
  const timeoutMs = integer3(opts.timeout, "--timeout");
8201
8163
  if (!opts.out.trim()) throw new CliUsageError("--out cannot be empty");
8202
- const needsConfig = opts.job !== void 0 || opts.provider === "typesafe" && !opts.dryRun;
8164
+ const needsConfig = opts.job !== void 0 || opts.provider === "typesafe" && !opts.dryRun && !opts.harnessCredentials;
8203
8165
  const config = needsConfig ? await loadConfig() : await loadConfig().catch(() => void 0);
8204
8166
  const format = await resolveOutput(command, opts, {
8205
8167
  allowed: ["pretty", "json"],
@@ -8238,8 +8200,8 @@ async function runJudge(command, scanFile, opts) {
8238
8200
  );
8239
8201
  }
8240
8202
  const { units, notes } = normalized;
8241
- const model = opts.model ?? config?.typesafeModel ?? DEFAULT_TYPESAFE_MODEL;
8242
- const baseUrl = opts.baseUrl ?? config?.typesafeBaseUrl ?? DEFAULT_TYPESAFE_BASE_URL;
8203
+ const model = opts.model ?? (opts.harnessCredentials ? process.env.TYPESAFE_DEFAULT_MODEL : config?.typesafeModel) ?? DEFAULT_TYPESAFE_MODEL;
8204
+ const baseUrl = opts.baseUrl ?? (opts.harnessCredentials ? process.env.TYPESAFE_BASE_URL : config?.typesafeBaseUrl) ?? DEFAULT_TYPESAFE_BASE_URL;
8243
8205
  if (opts.dryRun) {
8244
8206
  const first = units.find((unit) => !unit.is_error);
8245
8207
  const payload = {
@@ -8259,10 +8221,10 @@ async function runJudge(command, scanFile, opts) {
8259
8221
  throw new CliUsageError("--replay must contain a JSON object");
8260
8222
  provider = new ReplayProvider(recorded);
8261
8223
  } else {
8262
- const apiKey = config?.typesafeApiKey;
8224
+ const apiKey = opts.harnessCredentials ? process.env.TYPESAFE_API_KEY : config?.typesafeApiKey;
8263
8225
  if (typeof apiKey !== "string" || !apiKey.trim())
8264
8226
  throw new Error(
8265
- `TypeSafe API key is not configured (typesafeApiKey). ${settingRemedy(["typesafeApiKey"], safeContext2())}`
8227
+ opts.harnessCredentials ? "No TypeSafe key reached the judge from its AIRS environment. Check airs env typesafe status, or save a key with airs env typesafe set. No live judgment was performed; replay was not substituted." : `TypeSafe API key is not configured (typesafeApiKey). ${settingRemedy(["typesafeApiKey"], safeContext2())}`
8266
8228
  );
8267
8229
  provider = new TypeSafeHttpProvider({ apiKey, model, baseUrl, maxRetries, timeoutMs });
8268
8230
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@cdot65/prisma-airs-cli",
3
3
  "packageManager": "pnpm@10.6.5",
4
- "version": "7.1.3",
5
- "description": "CLI and library for Palo Alto Prisma AIRS — guardrail refinement, AI red teaming, model security scanning, profile audits",
4
+ "version": "7.1.5",
5
+ "description": "CLI and library for Palo Alto Prisma AIRS \u2014 guardrail refinement, AI red teaming, model security scanning, profile audits",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
@@ -61,6 +61,7 @@
61
61
  "dependencies": {
62
62
  "@cdot65/prisma-airs-sdk": "0.33.0",
63
63
  "@inquirer/prompts": "^8.3.0",
64
+ "@typesafe-ai/sdk": "0.6.0",
64
65
  "chalk": "^5.6.2",
65
66
  "commander": "^14.0.3",
66
67
  "js-yaml": "^4.3.2",