@cdot65/prisma-airs-cli 7.1.4 → 7.2.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.
Files changed (2) hide show
  1. package/dist/cli/index.js +838 -679
  2. package/package.json +3 -2
package/dist/cli/index.js CHANGED
@@ -3562,409 +3562,18 @@ ${lines.map((l) => ` $ ${commandHint(l)}`).join("\n")}
3562
3562
  `;
3563
3563
  }
3564
3564
 
3565
- // src/cli/commands/aigateway/inference.ts
3566
- import { readFile } from "fs/promises";
3567
- import {
3568
- AIGatewayInferenceClient,
3569
- ErrorType,
3570
- GatewayInferenceInputCreateChatCompletionRequestSchema,
3571
- GatewayInferenceInputCreateEmbeddingRequestSchema,
3572
- GatewayInferenceInputCreateResponseSchema
3573
- } from "@cdot65/prisma-airs-sdk";
3574
- import { dump as dump4 } from "js-yaml";
3575
- function integer2(raw, flag) {
3576
- const value = Number(raw);
3577
- if (!Number.isSafeInteger(value) || value < 1)
3578
- throw new CliUsageError(`${flag} must be a positive integer`);
3579
- return value;
3580
- }
3581
- async function inferenceBody(kind, prompt, opts, defaultModel) {
3582
- if (prompt !== void 0 && opts.file)
3583
- throw new CliUsageError("Pass a prompt or --file, not both");
3584
- if (prompt === void 0 && !opts.file) throw new CliUsageError("A prompt or --file is required");
3585
- let body;
3586
- if (opts.file) {
3587
- let value;
3588
- try {
3589
- value = JSON.parse(await readFile(opts.file, "utf8"));
3590
- } catch {
3591
- throw new CliUsageError("--file must name a readable JSON request file");
3592
- }
3593
- if (!value || typeof value !== "object" || Array.isArray(value))
3594
- throw new CliUsageError("--file must contain a JSON object");
3595
- body = value;
3596
- } else {
3597
- body = kind === "chat" ? { messages: [{ role: "user", content: prompt }] } : { input: prompt };
3598
- }
3599
- body = { ...body };
3600
- const model = opts.model ?? body.model ?? defaultModel;
3601
- if (typeof model !== "string" || !model.trim())
3602
- throw new CliUsageError(
3603
- "Set --model, the request model, or the matching inference model config"
3604
- );
3605
- body.model = model;
3606
- if (opts.stream !== void 0) body.stream = opts.stream;
3607
- if (opts.maxTokens !== void 0)
3608
- body[kind === "responses" ? "max_output_tokens" : "max_completion_tokens"] = integer2(
3609
- opts.maxTokens,
3610
- "--max-tokens"
3611
- );
3612
- if (opts.dimensions !== void 0) body.dimensions = integer2(opts.dimensions, "--dimensions");
3613
- if (opts.encoding !== void 0) body.encoding_format = opts.encoding;
3614
- if (kind === "responses" && body.store === void 0) body.store = false;
3615
- return body;
3616
- }
3617
- function responseText(value) {
3618
- if (!value || typeof value !== "object") return "";
3619
- const record = value;
3620
- if (record.type === "response.output_text.delta" && typeof record.delta === "string")
3621
- return record.delta;
3622
- if (Array.isArray(record.choices))
3623
- return record.choices.map((choice) => choice.message?.content ?? choice.delta?.content ?? "").join("");
3624
- if (Array.isArray(record.output))
3625
- return record.output.flatMap((item) => Array.isArray(item.content) ? item.content : []).filter((part) => part.type === "output_text").map((part) => part.text).join("");
3626
- return "";
3627
- }
3628
- function registerAiGatewayInference(parent) {
3629
- const group = parent.command("inference").description("Call the gateway runtime (separate API key; no SCM OAuth required)");
3630
- for (const kind of ["chat", "responses", "embeddings"]) {
3631
- const command = group.command(`${kind} [prompt]`).description(
3632
- `${kind === "embeddings" ? "Create embeddings" : "Generate a model response"} through your runtime gateway`
3633
- ).option("--model <id>", "Provider-prefixed model ID; overrides request file and config").option("--endpoint <url>", "Runtime base URL including /v1; overrides config").option("--file <path>", "JSON request file (mutually exclusive with prompt)").option("--timeout <ms>", "Total deadline including streaming, in milliseconds", "60000").option(
3634
- "--output <format>",
3635
- "pretty, json or yaml; streaming json emits one chunk per line (JSONL)"
3636
- );
3637
- if (kind === "embeddings")
3638
- command.option("--dimensions <n>", "Embedding dimensions").option("--encoding <format>", "float or base64");
3639
- else
3640
- command.option("--stream", "Stream text (pretty) or validated events (json/JSONL)").option("--max-tokens <n>", "Maximum completion/output tokens");
3641
- command.action(async (prompt, opts) => {
3642
- const controller = new AbortController();
3643
- const interrupt = () => controller.abort(new Error("Inference interrupted"));
3644
- const outputError = (error) => controller.abort(error);
3645
- let stream;
3646
- let failed = false;
3647
- let failure3;
3648
- try {
3649
- const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
3650
- const config = await loadConfig({ aiGwInferenceEndpoint: opts.endpoint });
3651
- const body = await inferenceBody(
3652
- kind,
3653
- prompt,
3654
- opts,
3655
- kind === "embeddings" ? config.aiGwEmbeddingModel : config.aiGwInferenceModel
3656
- );
3657
- if (body.stream === true && format === "yaml")
3658
- throw new CliUsageError("Streaming supports --output pretty or json (JSONL), not yaml");
3659
- const schema = kind === "chat" ? GatewayInferenceInputCreateChatCompletionRequestSchema : kind === "responses" ? GatewayInferenceInputCreateResponseSchema : GatewayInferenceInputCreateEmbeddingRequestSchema;
3660
- const parsed = schema.safeParse(body);
3661
- if (!parsed.success)
3662
- throw new CliUsageError(
3663
- `Invalid inference request: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.code}`).join("; ")}`
3664
- );
3665
- const client = new AIGatewayInferenceClient({
3666
- endpoint: config.aiGwInferenceEndpoint,
3667
- apiKey: config.aiGwInferenceApiKey,
3668
- timeoutMs: integer2(opts.timeout ?? "60000", "--timeout"),
3669
- numRetries: 0
3670
- });
3671
- process.once("SIGINT", interrupt);
3672
- process.once("SIGTERM", interrupt);
3673
- process.stdout.on("error", outputError);
3674
- const write = (text2) => new Promise((resolve10, reject) => {
3675
- process.stdout.write(text2, (error) => error ? reject(error) : resolve10());
3676
- });
3677
- const requestOptions = { signal: controller.signal };
3678
- const result = kind === "chat" ? await client.createChatCompletion(
3679
- GatewayInferenceInputCreateChatCompletionRequestSchema.parse(body),
3680
- requestOptions
3681
- ) : kind === "responses" ? await client.createResponse(
3682
- GatewayInferenceInputCreateResponseSchema.parse(body),
3683
- requestOptions
3684
- ) : await client.createEmbedding(
3685
- GatewayInferenceInputCreateEmbeddingRequestSchema.parse(body),
3686
- requestOptions
3687
- );
3688
- if (Symbol.asyncIterator in result) {
3689
- stream = result;
3690
- for await (const event of result) {
3691
- if (format === "json") await write(`${JSON.stringify(event)}
3692
- `);
3693
- else {
3694
- const text2 = responseText(event);
3695
- if (text2) await write(text2);
3696
- }
3697
- if ("type" in event && (event.type === "response.failed" || event.type === "response.incomplete"))
3698
- throw new Error(`Gateway generation ended with ${event.type}`);
3699
- }
3700
- if (format === "pretty") await write("\n");
3701
- } else {
3702
- const text2 = format === "yaml" ? dump4(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
3703
- await write(`${text2}
3704
- `);
3705
- if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
3706
- throw new Error(`Gateway generation ended with ${result.status}`);
3707
- }
3708
- } catch (error) {
3709
- failed = true;
3710
- failure3 = error;
3711
- } finally {
3712
- await stream?.cancel();
3713
- controller.abort();
3714
- process.removeListener("SIGINT", interrupt);
3715
- process.removeListener("SIGTERM", interrupt);
3716
- process.stdout.removeListener("error", outputError);
3717
- }
3718
- if (failed && failure3?.code !== "EPIPE") {
3719
- if (failure3?.errorType === ErrorType.USER_REQUEST_PAYLOAD_ERROR)
3720
- usageError(failure3 instanceof Error ? failure3.message : "Invalid inference options");
3721
- fail(failure3);
3722
- }
3723
- });
3724
- }
3725
- }
3726
-
3727
- // src/cli/commands/aigateway/inventory.ts
3728
- import { readFile as readFile3 } from "fs/promises";
3729
- import {
3730
- AI_GATEWAY_DEPLOYMENT_STATUSES,
3731
- AI_GATEWAY_DEPLOYMENT_TYPES,
3732
- AI_GATEWAY_KNOWN_API_KEY_SCOPES,
3733
- AI_GATEWAY_KNOWN_MCP_AUTH_TYPES,
3734
- AI_GATEWAY_KNOWN_MCP_TRANSPORTS,
3735
- AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES,
3736
- customHostConfiguration,
3737
- GatewayApiKeyRotateRequestSchema,
3738
- GatewayApiKeyUpdateRequestSchema,
3739
- GatewayConfigCreateRequestSchema,
3740
- GatewayConfigUpdateRequestSchema,
3741
- GatewayDeploymentCreateRequestSchema,
3742
- GatewayDeploymentUpdateRequestSchema,
3743
- GatewayGuardrailCreateRequestSchema,
3744
- GatewayGuardrailUpdateRequestSchema,
3745
- GatewayIntegrationCreateRequestSchema,
3746
- GatewayIntegrationModelsBulkUpdateRequestSchema,
3747
- GatewayIntegrationUpdateRequestSchema,
3748
- GatewayIntegrationWorkspacesBulkUpdateRequestSchema,
3749
- GatewayOrganisationAuthSettingsUpdateRequestSchema,
3750
- GatewayOrganisationUpdateRequestSchema,
3751
- GatewayPluginCreateRequestSchema,
3752
- GatewayProviderCreateRequestSchema,
3753
- GatewayProviderUpdateRequestSchema,
3754
- GatewayServiceApiKeyCreateRequestSchema,
3755
- GatewayUserApiKeyCreateRequestSchema,
3756
- McpIntegrationCapabilitiesBulkUpdateRequestSchema,
3757
- McpIntegrationCreateRequestSchema,
3758
- McpIntegrationUpdateRequestSchema,
3759
- McpIntegrationWorkspacesBulkUpdateRequestSchema,
3760
- redactAIGatewaySecrets
3761
- } from "@cdot65/prisma-airs-sdk";
3762
-
3763
- // src/cli/debug-logger.ts
3764
- import {
3765
- appendFileSync,
3766
- mkdirSync,
3767
- readdirSync,
3768
- statSync,
3769
- unlinkSync,
3770
- writeFileSync
3771
- } from "fs";
3772
- import { dirname, join } from "path";
3773
- var AIRS_DOMAINS = [
3774
- "api.sase.paloaltonetworks.com",
3775
- "api.apps.paloaltonetworks.com",
3776
- "service.api.aisecurity.paloaltonetworks.com",
3777
- "auth.apps.paloaltonetworks.com",
3778
- "api.dlp.paloaltonetworks.com"
3779
- ];
3780
- function isAirsUrl(url) {
3781
- try {
3782
- const parsed = new URL(url);
3783
- return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
3784
- } catch {
3785
- return false;
3786
- }
3787
- }
3788
- var MASK = "***";
3789
- var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key|client[-_]?auth|auth[-_]?code|^key$/i;
3790
- function isSensitiveKey(key) {
3791
- return SENSITIVE_KEY_PATTERN.test(key);
3792
- }
3793
- function redactHeaders(headers) {
3794
- const out = {};
3795
- for (const [k, v] of Object.entries(headers)) {
3796
- out[k] = isSensitiveKey(k) || /^x-portkey-(config|metadata|forward-headers)$/i.test(k) ? MASK : v;
3797
- }
3798
- return out;
3799
- }
3800
- function redactDeep(value) {
3801
- if (Array.isArray(value)) {
3802
- return value.map(redactDeep);
3803
- }
3804
- if (value !== null && typeof value === "object") {
3805
- const out = {};
3806
- for (const [k, v] of Object.entries(value)) {
3807
- out[k] = isSensitiveKey(k) ? MASK : redactDeep(v);
3808
- }
3809
- return out;
3810
- }
3811
- return value;
3812
- }
3813
- function redactUrl(url) {
3814
- try {
3815
- const parsed = new URL(url);
3816
- let touched = false;
3817
- for (const key of parsed.searchParams.keys()) {
3818
- if (isSensitiveKey(key)) {
3819
- parsed.searchParams.set(key, MASK);
3820
- touched = true;
3821
- }
3822
- }
3823
- return touched ? parsed.toString() : url;
3824
- } catch {
3825
- return url;
3826
- }
3827
- }
3828
- function headersToRecord(headers) {
3829
- if (!headers) return {};
3830
- if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
3831
- const out = {};
3832
- headers.forEach((v, k) => {
3833
- out[k] = v;
3834
- });
3835
- return out;
3836
- }
3837
- if (Array.isArray(headers)) {
3838
- return Object.fromEntries(headers);
3839
- }
3840
- return headers;
3841
- }
3842
- function installDebugLogger(logPath, options = {}) {
3843
- mkdirSync(dirname(logPath), { recursive: true, mode: 448 });
3844
- writeFileSync(logPath, "", { encoding: "utf8", mode: 384, flag: "wx" });
3845
- const originalFetch = globalThis.fetch;
3846
- globalThis.fetch = async function debugFetch(input2, init2) {
3847
- const url = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
3848
- const rawHeaders = headersToRecord(
3849
- init2?.headers ?? (input2 instanceof Request ? input2.headers : void 0)
3850
- );
3851
- const inference = Object.keys(rawHeaders).some(
3852
- (name) => name.toLowerCase() === "x-portkey-api-key"
3853
- );
3854
- if (!isAirsUrl(url) && !inference) {
3855
- return originalFetch(input2, init2);
3856
- }
3857
- const dashboard = options.omitBodies || /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url) || /\/aiag\//.test(new URL(url).pathname);
3858
- const method = init2?.method ?? (input2 instanceof Request ? input2.method : "GET");
3859
- const reqHeaders = redactHeaders(rawHeaders);
3860
- const loggedUrl = redactUrl(url);
3861
- let reqBody;
3862
- if (inference || dashboard) reqBody = "[BODY OMITTED]";
3863
- else if (init2?.body) {
3864
- try {
3865
- reqBody = redactDeep(JSON.parse(String(init2.body)));
3866
- } catch {
3867
- const contentType = new Headers(rawHeaders).get("content-type") ?? "";
3868
- reqBody = contentType.includes("application/x-www-form-urlencoded") ? redactDeep(Object.fromEntries(new URLSearchParams(String(init2.body)))) : "[NON-JSON BODY OMITTED]";
3869
- }
3870
- }
3871
- const ts2 = (/* @__PURE__ */ new Date()).toISOString();
3872
- const startMs = Date.now();
3873
- let response;
3874
- let resBody;
3875
- let error;
3876
- try {
3877
- response = await originalFetch(input2, init2);
3878
- } catch (err) {
3879
- error = inference ? "Runtime request failed" : "Request failed";
3880
- const entry2 = JSON.stringify({
3881
- timestamp: ts2,
3882
- durationMs: Date.now() - startMs,
3883
- request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
3884
- error
3885
- });
3886
- appendFileSync(logPath, `${entry2}
3887
- `);
3888
- throw err;
3889
- }
3890
- const durationMs = Date.now() - startMs;
3891
- const resHeaders = {};
3892
- response.headers.forEach((v, k) => {
3893
- resHeaders[k] = v;
3894
- });
3895
- if (inference || dashboard || response.headers.get("content-type")?.includes("text/event-stream")) {
3896
- resBody = "[BODY OMITTED]";
3897
- } else {
3898
- const clone = response.clone();
3899
- try {
3900
- const text2 = await clone.text();
3901
- try {
3902
- resBody = redactDeep(JSON.parse(text2));
3903
- } catch {
3904
- resBody = "[NON-JSON BODY OMITTED]";
3905
- }
3906
- } catch {
3907
- resBody = "<unreadable>";
3908
- }
3909
- }
3910
- const entry = JSON.stringify({
3911
- timestamp: ts2,
3912
- durationMs,
3913
- request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
3914
- response: {
3915
- status: response.status,
3916
- statusText: response.statusText,
3917
- headers: redactHeaders(resHeaders),
3918
- body: resBody
3919
- }
3920
- });
3921
- appendFileSync(logPath, `${entry}
3922
- `);
3923
- return response;
3924
- };
3925
- return {
3926
- teardown() {
3927
- globalThis.fetch = originalFetch;
3928
- }
3929
- };
3930
- }
3931
-
3932
- // src/cli/tenant-input.ts
3933
- import { input, password } from "@inquirer/prompts";
3934
- async function promptTenantValue(message, secret = false) {
3935
- if (!process.stdin.isTTY || !process.stderr.isTTY)
3936
- throw new Error(
3937
- "Interactive setup requires a terminal. Use --config, or --tsg-id and --client-id with --client-secret-stdin. For tenant set, pass a value or use --stdin."
3938
- );
3939
- const prompt = secret ? password : input;
3940
- return prompt(
3941
- {
3942
- message,
3943
- validate: (value) => Boolean(value.trim()) || "A nonempty value is required"
3944
- },
3945
- { input: process.stdin, output: process.stderr }
3946
- );
3947
- }
3948
- async function readTenantStdin() {
3949
- if (process.stdin.isTTY) throw new Error("--stdin requires piped input");
3950
- const chunks = [];
3951
- let size = 0;
3952
- for await (const chunk of process.stdin) {
3953
- const buffer = Buffer.from(chunk);
3954
- size += buffer.length;
3955
- if (size > 65536) throw new Error("Configuration input exceeds 64 KiB");
3956
- chunks.push(buffer);
3957
- }
3958
- const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
3959
- if (!value.trim() || /[\r\n\0]/.test(value))
3960
- throw new Error("Provide one nonempty configuration value on stdin");
3961
- return value;
3962
- }
3565
+ // src/cli/commands/aigateway/admin-guardrails.ts
3566
+ import {
3567
+ GatewayAdminGuardrailCreateRequestSchema,
3568
+ GatewayAdminGuardrailMcpMappingRequestSchema,
3569
+ GatewayAdminGuardrailMcpSyncRequestSchema,
3570
+ GatewayGuardrailUpdateRequestSchema
3571
+ } from "@cdot65/prisma-airs-sdk";
3963
3572
 
3964
3573
  // src/cli/commands/aigateway/shared.ts
3965
- import { open, readFile as readFile2, unlink } from "fs/promises";
3574
+ import { open, readFile, unlink } from "fs/promises";
3966
3575
  import { AIGatewayClient as AIGatewayClient2 } from "@cdot65/prisma-airs-sdk";
3967
- import { dump as dump5, load } from "js-yaml";
3576
+ import { dump as dump4, load } from "js-yaml";
3968
3577
  async function defaultClientFactory() {
3969
3578
  const config = await loadConfig();
3970
3579
  return new AIGatewayClient2(aiGatewayClientOptions(config));
@@ -3988,7 +3597,7 @@ function addWriteOutput(command) {
3988
3597
  async function readGatewayRequest(path3) {
3989
3598
  let parsed;
3990
3599
  try {
3991
- const text2 = await readFile2(path3, "utf8");
3600
+ const text2 = await readFile(path3, "utf8");
3992
3601
  parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(text2) : JSON.parse(text2);
3993
3602
  } catch (error) {
3994
3603
  throw new CliUsageError(
@@ -4045,7 +3654,7 @@ function renderGatewayList(items, format, label2) {
4045
3654
  return;
4046
3655
  }
4047
3656
  if (format === "yaml") {
4048
- console.log(dump5(items, { noRefs: true, lineWidth: -1 }).trimEnd());
3657
+ console.log(dump4(items, { noRefs: true, lineWidth: -1 }).trimEnd());
4049
3658
  return;
4050
3659
  }
4051
3660
  if (items.length === 0) {
@@ -4063,298 +3672,830 @@ function renderGatewayDetail(value, format) {
4063
3672
  return;
4064
3673
  }
4065
3674
  if (format === "yaml") {
4066
- console.log(dump5(item, { noRefs: true, lineWidth: -1 }).trimEnd());
3675
+ console.log(dump4(item, { noRefs: true, lineWidth: -1 }).trimEnd());
4067
3676
  return;
4068
3677
  }
4069
- const rows = Object.entries(item).map(([key, raw]) => ({
4070
- key,
4071
- value: raw !== null && typeof raw === "object" ? JSON.stringify(raw) : raw
4072
- }));
4073
- const rendered = formatOutput(
4074
- rows,
4075
- [
4076
- { key: "key", label: "Field" },
4077
- { key: "value", label: "Value" }
4078
- ],
4079
- format === "pretty" ? "table" : format
4080
- );
4081
- if (rendered) console.log(rendered);
3678
+ const rows = Object.entries(item).map(([key, raw]) => ({
3679
+ key,
3680
+ value: raw !== null && typeof raw === "object" ? JSON.stringify(raw) : raw
3681
+ }));
3682
+ const rendered = formatOutput(
3683
+ rows,
3684
+ [
3685
+ { key: "key", label: "Field" },
3686
+ { key: "value", label: "Value" }
3687
+ ],
3688
+ format === "pretty" ? "table" : format
3689
+ );
3690
+ if (rendered) console.log(rendered);
3691
+ }
3692
+ async function runList(command, opts, label2, load4) {
3693
+ try {
3694
+ const format = await resolveOutput(command, opts);
3695
+ const client = await createAiGatewayClient();
3696
+ renderGatewayList(responseItems(await load4(client)), format, label2);
3697
+ } catch (error) {
3698
+ failAiGateway(error);
3699
+ }
3700
+ }
3701
+ async function runDetail(command, opts, load4) {
3702
+ try {
3703
+ const format = await resolveOutput(command, opts);
3704
+ const client = await createAiGatewayClient();
3705
+ renderGatewayDetail(await load4(client), format);
3706
+ } catch (error) {
3707
+ failAiGateway(error);
3708
+ }
3709
+ }
3710
+ async function runWrite(command, opts, prepareOrWrite, preparedWrite) {
3711
+ try {
3712
+ const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
3713
+ const prepared = preparedWrite ? await prepareOrWrite() : void 0;
3714
+ const client = await createAiGatewayClient();
3715
+ const result = preparedWrite ? await preparedWrite(client, prepared) : await prepareOrWrite(client);
3716
+ renderGatewayDetail(result, format);
3717
+ } catch (error) {
3718
+ failAiGateway(error);
3719
+ }
3720
+ }
3721
+ async function runConfirmedWrite(command, opts, prompt, prepareOrWrite, preparedWrite) {
3722
+ try {
3723
+ const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
3724
+ const prepared = preparedWrite ? await prepareOrWrite() : void 0;
3725
+ const action = prompt.replace(/\?$/, "").replace(/^./, (character) => character.toLowerCase());
3726
+ await confirmOrAbort(prompt, Boolean(opts.force), { action });
3727
+ const client = await createAiGatewayClient();
3728
+ const result = preparedWrite ? await preparedWrite(client, prepared) : await prepareOrWrite(client);
3729
+ renderGatewayDetail(result ?? { success: true }, format);
3730
+ } catch (error) {
3731
+ failAiGateway(error);
3732
+ }
3733
+ }
3734
+ async function runSecretWrite(command, opts, prepare, write, prompt, settings = {}) {
3735
+ let destinationHandle;
3736
+ let destinationReserved = false;
3737
+ let secretPersisted = false;
3738
+ const cleanupReservedDestination = async () => {
3739
+ if (destinationHandle) await destinationHandle.close().catch(() => void 0);
3740
+ destinationHandle = void 0;
3741
+ if (opts.secretOutput && destinationReserved && !secretPersisted) {
3742
+ await unlink(opts.secretOutput).catch(() => void 0);
3743
+ }
3744
+ destinationReserved = false;
3745
+ };
3746
+ try {
3747
+ const prepared = await prepare();
3748
+ const requiresDestination = settings.requiresDestination?.(prepared) ?? true;
3749
+ if (requiresDestination && !opts.secretOutput && !opts.showSecret) {
3750
+ throw new CliUsageError(
3751
+ "Choose --secret-output <path> (recommended) or --show-secret before requesting a one-time credential"
3752
+ );
3753
+ }
3754
+ const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
3755
+ if (requiresDestination && opts.secretOutput) {
3756
+ destinationHandle = await open(opts.secretOutput, "wx", 384);
3757
+ destinationReserved = true;
3758
+ }
3759
+ if (prompt) {
3760
+ const action = prompt.replace(/\?$/, "").replace(/^./, (character) => character.toLowerCase());
3761
+ await confirmOrAbort(prompt, Boolean(opts.force), {
3762
+ action,
3763
+ onAbort: cleanupReservedDestination
3764
+ });
3765
+ }
3766
+ const client = await createAiGatewayClient();
3767
+ const result = await write(client, prepared);
3768
+ if (requiresDestination && opts.secretOutput && destinationHandle) {
3769
+ await destinationHandle.writeFile(`${JSON.stringify(result, null, 2)}
3770
+ `, "utf8");
3771
+ secretPersisted = true;
3772
+ await destinationHandle.close();
3773
+ destinationHandle = void 0;
3774
+ renderGatewayDetail({ saved_to: opts.secretOutput }, format);
3775
+ return;
3776
+ }
3777
+ renderGatewayDetail(
3778
+ requiresDestination ? result : settings.redactResponse?.(result) ?? result,
3779
+ format
3780
+ );
3781
+ } catch (error) {
3782
+ await cleanupReservedDestination();
3783
+ failAiGateway(error);
3784
+ }
3785
+ }
3786
+
3787
+ // src/cli/commands/aigateway/structured-input.ts
3788
+ import {
3789
+ AISecSDKException,
3790
+ buildDottedObject,
3791
+ ErrorType,
3792
+ GatewayJsonObjectSchema,
3793
+ setDottedValue
3794
+ } from "@cdot65/prisma-airs-sdk";
3795
+ function collectOption(value, previous = []) {
3796
+ return [...previous, value];
3797
+ }
3798
+ function addStructuredInputOptions(command) {
3799
+ return command.option("--file <path>", "Advanced JSON/YAML request base; named flags override it").option(
3800
+ "--set <path=value>",
3801
+ "Set a typed JSON value at a request path (repeatable)",
3802
+ collectOption
3803
+ ).option(
3804
+ "--set-string <path=value>",
3805
+ "Set a literal string at a request path (repeatable)",
3806
+ collectOption
3807
+ );
3808
+ }
3809
+ function parseBooleanOption(value) {
3810
+ if (value === true || value === false) return value;
3811
+ if (value === "true") return true;
3812
+ if (value === "false") return false;
3813
+ throw new CliUsageError("Expected true or false");
3814
+ }
3815
+ function parseIntegerOption(value) {
3816
+ const text2 = String(value);
3817
+ if (!/^-?(0|[1-9]\d*)$/.test(text2)) throw new CliUsageError("Expected an integer");
3818
+ const parsed = Number(text2);
3819
+ if (!Number.isSafeInteger(parsed)) throw new CliUsageError("Expected a safe integer");
3820
+ return parsed;
3821
+ }
3822
+ function parseDateOption(value) {
3823
+ const parsed = new Date(String(value));
3824
+ if (Number.isNaN(parsed.getTime())) throw new CliUsageError("Expected an ISO-8601 timestamp");
3825
+ return parsed;
3826
+ }
3827
+ function parseCsvOption(value) {
3828
+ const values = String(value).split(",").map((item) => item.trim()).filter(Boolean);
3829
+ if (values.length === 0) throw new CliUsageError("Expected a non-empty comma-separated list");
3830
+ return values;
3831
+ }
3832
+ function parseJsonOption(value) {
3833
+ try {
3834
+ return JSON.parse(String(value));
3835
+ } catch {
3836
+ throw new CliUsageError("Expected valid JSON");
3837
+ }
3838
+ }
3839
+ function repeatableValues(value) {
3840
+ if (Array.isArray(value)) return value.map(String);
3841
+ return [String(value)];
3842
+ }
3843
+ function splitKeyValue(raw, label2) {
3844
+ const separator = raw.indexOf("=");
3845
+ if (separator <= 0) throw new CliUsageError(`Expected ${label2}=value`);
3846
+ return [raw.slice(0, separator), raw.slice(separator + 1)];
3847
+ }
3848
+ function parseBooleanBindingsOption(value) {
3849
+ return repeatableValues(value).map((raw) => {
3850
+ const [id, enabled] = splitKeyValue(raw, "id");
3851
+ return { id, enabled: parseBooleanOption(enabled) };
3852
+ });
3853
+ }
3854
+ function parseModelBindingsOption(value) {
3855
+ return repeatableValues(value).map((raw) => {
3856
+ const [slug, enabled] = splitKeyValue(raw, "slug");
3857
+ return { slug, enabled: parseBooleanOption(enabled) };
3858
+ });
3859
+ }
3860
+ function parseCapabilityBindingsOption(value) {
3861
+ return repeatableValues(value).map((raw) => {
3862
+ const [identity2, enabled] = splitKeyValue(raw, "type:name");
3863
+ const separator = identity2.indexOf(":");
3864
+ if (separator <= 0 || separator === identity2.length - 1) {
3865
+ throw new CliUsageError("Expected type:name=enabled");
3866
+ }
3867
+ return {
3868
+ type: identity2.slice(0, separator),
3869
+ name: identity2.slice(separator + 1),
3870
+ enabled: parseBooleanOption(enabled)
3871
+ };
3872
+ });
3873
+ }
3874
+ function parseStringMapOption(value) {
3875
+ const result = {};
3876
+ for (const raw of repeatableValues(value)) {
3877
+ const [key, item] = splitKeyValue(raw, "key");
3878
+ if (["__proto__", "constructor", "prototype"].includes(key)) {
3879
+ throw new CliUsageError(`Unsafe key: ${key}`);
3880
+ }
3881
+ if (Object.hasOwn(result, key)) throw new CliUsageError(`Duplicate key: ${key}`);
3882
+ result[key] = item;
3883
+ }
3884
+ return result;
4082
3885
  }
4083
- async function runList(command, opts, label2, load4) {
3886
+ function parseAssignment(raw, forceString) {
3887
+ const separator = raw.indexOf("=");
3888
+ if (separator <= 0) throw new CliUsageError("Expected path=value");
3889
+ const path3 = raw.slice(0, separator);
3890
+ const text2 = raw.slice(separator + 1);
3891
+ if (forceString) return [path3, text2];
4084
3892
  try {
4085
- const format = await resolveOutput(command, opts);
4086
- const client = await createAiGatewayClient();
4087
- renderGatewayList(responseItems(await load4(client)), format, label2);
4088
- } catch (error) {
4089
- failAiGateway(error);
3893
+ return [path3, JSON.parse(text2)];
3894
+ } catch {
3895
+ return [path3, text2];
4090
3896
  }
4091
3897
  }
4092
- async function runDetail(command, opts, load4) {
4093
- try {
4094
- const format = await resolveOutput(command, opts);
4095
- const client = await createAiGatewayClient();
4096
- renderGatewayDetail(await load4(client), format);
4097
- } catch (error) {
4098
- failAiGateway(error);
3898
+ function schemaMessage(error) {
3899
+ const issues = error?.issues;
3900
+ if (!Array.isArray(issues) || issues.length === 0)
3901
+ return "request body does not match its schema";
3902
+ return issues.map((issue) => {
3903
+ const path3 = issue.path?.length ? issue.path.map(String).join(".") : "<root>";
3904
+ return `${path3}: ${issue.message ?? "invalid value"}`;
3905
+ }).join("; ");
3906
+ }
3907
+ function optionName(option) {
3908
+ return `--${option.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`;
3909
+ }
3910
+ function asUsageError(error, prefix = "Invalid AI Gateway request") {
3911
+ if (error instanceof CliUsageError) return error;
3912
+ if (error instanceof AISecSDKException && error.errorType === ErrorType.USER_REQUEST_PAYLOAD_ERROR) {
3913
+ return new CliUsageError(`${prefix}: ${error.message}`);
4099
3914
  }
3915
+ return new CliUsageError(`${prefix}: ${schemaMessage(error)}`);
4100
3916
  }
4101
- async function runWrite(command, opts, prepareOrWrite, preparedWrite) {
3917
+ async function buildStructuredRequest(options, schema, fields = []) {
4102
3918
  try {
4103
- const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
4104
- const prepared = preparedWrite ? await prepareOrWrite() : void 0;
4105
- const client = await createAiGatewayClient();
4106
- const result = preparedWrite ? await preparedWrite(client, prepared) : await prepareOrWrite(client);
4107
- renderGatewayDetail(result, format);
3919
+ let body = {};
3920
+ if (options.file) {
3921
+ body = GatewayJsonObjectSchema.parse(await readGatewayRequest(options.file));
3922
+ }
3923
+ for (const field of fields) {
3924
+ const raw = options[field.option];
3925
+ if (raw === void 0 || Array.isArray(raw) && raw.length === 0) continue;
3926
+ let value;
3927
+ try {
3928
+ value = field.parse ? field.parse(raw) : raw;
3929
+ } catch (error) {
3930
+ if (error instanceof CliUsageError) {
3931
+ throw new CliUsageError(`Invalid ${optionName(field.option)}: ${error.message}`);
3932
+ }
3933
+ throw error;
3934
+ }
3935
+ body = setDottedValue(body, field.path, value);
3936
+ }
3937
+ const assignments = [
3938
+ ...(options.set ?? []).map((raw) => {
3939
+ try {
3940
+ const [path3, value] = parseAssignment(raw, false);
3941
+ return { path: path3, value };
3942
+ } catch (error) {
3943
+ if (error instanceof CliUsageError) {
3944
+ throw new CliUsageError(`Invalid --set '${raw}': ${error.message}`);
3945
+ }
3946
+ throw error;
3947
+ }
3948
+ }),
3949
+ ...(options.setString ?? []).map((raw) => {
3950
+ try {
3951
+ const [path3, value] = parseAssignment(raw, true);
3952
+ return { path: path3, value };
3953
+ } catch (error) {
3954
+ if (error instanceof CliUsageError) {
3955
+ throw new CliUsageError(`Invalid --set-string '${raw}': ${error.message}`);
3956
+ }
3957
+ throw error;
3958
+ }
3959
+ })
3960
+ ];
3961
+ buildDottedObject(assignments);
3962
+ for (const { path: path3, value } of assignments) body = setDottedValue(body, path3, value);
3963
+ return schema.parse(body);
4108
3964
  } catch (error) {
4109
- failAiGateway(error);
3965
+ throw asUsageError(error);
4110
3966
  }
4111
3967
  }
4112
- async function runConfirmedWrite(command, opts, prompt, prepareOrWrite, preparedWrite) {
3968
+
3969
+ // src/cli/commands/aigateway/admin-guardrails.ts
3970
+ function paginationInteger(value, flag, minimum, maximum) {
4113
3971
  try {
4114
- const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
4115
- const prepared = preparedWrite ? await prepareOrWrite() : void 0;
4116
- const action = prompt.replace(/\?$/, "").replace(/^./, (character) => character.toLowerCase());
4117
- await confirmOrAbort(prompt, Boolean(opts.force), { action });
4118
- const client = await createAiGatewayClient();
4119
- const result = preparedWrite ? await preparedWrite(client, prepared) : await prepareOrWrite(client);
4120
- renderGatewayDetail(result ?? { success: true }, format);
3972
+ const parsed = parseIntegerOption(value);
3973
+ if (parsed < minimum || parsed > maximum) {
3974
+ throw new CliUsageError(`${flag} must be an integer from ${minimum} to ${maximum}`);
3975
+ }
3976
+ return parsed;
4121
3977
  } catch (error) {
4122
- failAiGateway(error);
3978
+ fail(error);
4123
3979
  }
4124
3980
  }
4125
- async function runSecretWrite(command, opts, prepare, write, prompt, settings = {}) {
4126
- let destinationHandle;
4127
- let destinationReserved = false;
4128
- let secretPersisted = false;
4129
- const cleanupReservedDestination = async () => {
4130
- if (destinationHandle) await destinationHandle.close().catch(() => void 0);
4131
- destinationHandle = void 0;
4132
- if (opts.secretOutput && destinationReserved && !secretPersisted) {
4133
- await unlink(opts.secretOutput).catch(() => void 0);
4134
- }
4135
- destinationReserved = false;
4136
- };
4137
- try {
4138
- const prepared = await prepare();
4139
- const requiresDestination = settings.requiresDestination?.(prepared) ?? true;
4140
- if (requiresDestination && !opts.secretOutput && !opts.showSecret) {
4141
- throw new CliUsageError(
4142
- "Choose --secret-output <path> (recommended) or --show-secret before requesting a one-time credential"
4143
- );
4144
- }
4145
- const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
4146
- if (requiresDestination && opts.secretOutput) {
4147
- destinationHandle = await open(opts.secretOutput, "wx", 384);
4148
- destinationReserved = true;
4149
- }
4150
- if (prompt) {
4151
- const action = prompt.replace(/\?$/, "").replace(/^./, (character) => character.toLowerCase());
4152
- await confirmOrAbort(prompt, Boolean(opts.force), {
4153
- action,
4154
- onAbort: cleanupReservedDestination
4155
- });
4156
- }
4157
- const client = await createAiGatewayClient();
4158
- const result = await write(client, prepared);
4159
- if (requiresDestination && opts.secretOutput && destinationHandle) {
4160
- await destinationHandle.writeFile(`${JSON.stringify(result, null, 2)}
4161
- `, "utf8");
4162
- secretPersisted = true;
4163
- await destinationHandle.close();
4164
- destinationHandle = void 0;
4165
- renderGatewayDetail({ saved_to: opts.secretOutput }, format);
4166
- return;
3981
+ function registerAdminGuardrails(root) {
3982
+ const group = showHelpOnEmpty(
3983
+ root.command("admin-guardrails").description(
3984
+ "Manage organisation guardrails on the admin plane (mutations specification-tested)"
3985
+ )
3986
+ );
3987
+ const list = addReadOutput(
3988
+ group.command("list").alias("ls").description(
3989
+ "List organisation guardrails; optional workspace filtering may be denied by policy"
3990
+ ).option("--workspace <uuid>", "Optional workspace UUID filter").option(
3991
+ "--page-size <number>",
3992
+ "Page size, 1\u20131000",
3993
+ (value) => paginationInteger(value, "--page-size", 1, 1e3)
3994
+ ).option(
3995
+ "--current-page <number>",
3996
+ "Zero-based page number",
3997
+ (value) => paginationInteger(value, "--current-page", 0, Number.MAX_SAFE_INTEGER)
3998
+ )
3999
+ );
4000
+ list.action(
4001
+ (opts) => runList(
4002
+ list,
4003
+ opts,
4004
+ "Admin guardrails",
4005
+ (c) => c.adminGuardrails.list({
4006
+ workspaceId: opts.workspace,
4007
+ pageSize: opts.pageSize,
4008
+ currentPage: opts.currentPage
4009
+ })
4010
+ )
4011
+ );
4012
+ const get = addReadOutput(
4013
+ group.command("get <id>").description("Read an admin guardrail by UUID")
4014
+ );
4015
+ get.action((id, opts) => runDetail(get, opts, (c) => c.adminGuardrails.get(id)));
4016
+ const fields = [
4017
+ { option: "name", path: "name" },
4018
+ { option: "checks", path: "checks", parse: parseJsonOption },
4019
+ { option: "actions", path: "actions", parse: parseJsonOption }
4020
+ ];
4021
+ const common = (cmd) => addWriteOutput(
4022
+ addStructuredInputOptions(
4023
+ cmd.option("--name <name>", "Guardrail name").option("--checks <json>", "Checks array").option("--actions <json>", "Actions object")
4024
+ )
4025
+ );
4026
+ const create = common(
4027
+ group.command("create").description("Create an admin guardrail (specification-tested; no workspace fallback)")
4028
+ ).option("--target <target>", "llm or mcp_tools").option("--workspace <uuid>", "Optional workspace UUID").option("--organisation <uuid>", "Optional organisation UUID, not the numeric TSG");
4029
+ create.action(
4030
+ (opts) => runWrite(
4031
+ create,
4032
+ opts,
4033
+ () => buildStructuredRequest(opts, GatewayAdminGuardrailCreateRequestSchema, [
4034
+ ...fields,
4035
+ { option: "target", path: "target" },
4036
+ { option: "workspace", path: "workspace_id" },
4037
+ { option: "organisation", path: "organisation_id" }
4038
+ ]),
4039
+ (c, body) => c.adminGuardrails.create(body)
4040
+ )
4041
+ );
4042
+ const update = common(
4043
+ group.command("update <id>").description("Update an admin guardrail (specification-tested)")
4044
+ );
4045
+ update.action(
4046
+ (id, opts) => runWrite(
4047
+ update,
4048
+ opts,
4049
+ () => buildStructuredRequest(opts, GatewayGuardrailUpdateRequestSchema, fields),
4050
+ (c, body) => c.adminGuardrails.update(id, body)
4051
+ )
4052
+ );
4053
+ const remove = addWriteOutput(
4054
+ group.command("delete <id>").alias("rm").description("Permanently delete an admin guardrail").option("--force", "Skip confirmation prompt")
4055
+ );
4056
+ remove.action(
4057
+ (id, opts) => runConfirmedWrite(
4058
+ remove,
4059
+ opts,
4060
+ `Permanently delete admin guardrail ${id}?`,
4061
+ (c) => c.adminGuardrails.delete(id)
4062
+ )
4063
+ );
4064
+ const mappings = showHelpOnEmpty(
4065
+ group.command("mcp-servers").description("Manage admin guardrail MCP mappings; does not sign in to MCP")
4066
+ );
4067
+ const mappingsList = addReadOutput(
4068
+ mappings.command("list <guardrail-id>").alias("ls").description("List MCP mappings")
4069
+ );
4070
+ mappingsList.action(
4071
+ (id, opts) => runList(mappingsList, opts, "MCP mappings", (c) => c.adminGuardrails.getMcpServers(id))
4072
+ );
4073
+ const sync = addWriteOutput(
4074
+ addStructuredInputOptions(
4075
+ mappings.command("sync <guardrail-id>").description("Replace all mappings (specification-tested)").option("--force", "Skip replacement confirmation")
4076
+ )
4077
+ );
4078
+ sync.action(
4079
+ (id, opts) => runConfirmedWrite(
4080
+ sync,
4081
+ opts,
4082
+ `Replace all MCP mappings on admin guardrail ${id}?`,
4083
+ () => buildStructuredRequest(opts, GatewayAdminGuardrailMcpSyncRequestSchema),
4084
+ (c, body) => c.adminGuardrails.syncMcpServers(id, body)
4085
+ )
4086
+ );
4087
+ const upsert = addWriteOutput(
4088
+ addStructuredInputOptions(
4089
+ mappings.command("upsert <guardrail-id> <server-id>").description("Set one mapping (specification-tested)")
4090
+ )
4091
+ );
4092
+ upsert.action(
4093
+ (id, serverId, opts) => runWrite(
4094
+ upsert,
4095
+ opts,
4096
+ () => buildStructuredRequest(opts, GatewayAdminGuardrailMcpMappingRequestSchema),
4097
+ (c, body) => c.adminGuardrails.upsertMcpServer(id, serverId, body)
4098
+ )
4099
+ );
4100
+ }
4101
+
4102
+ // src/cli/commands/aigateway/inference.ts
4103
+ import { readFile as readFile2 } from "fs/promises";
4104
+ import {
4105
+ AIGatewayInferenceClient,
4106
+ ErrorType as ErrorType2,
4107
+ GatewayInferenceInputCreateChatCompletionRequestSchema,
4108
+ GatewayInferenceInputCreateEmbeddingRequestSchema,
4109
+ GatewayInferenceInputCreateResponseSchema
4110
+ } from "@cdot65/prisma-airs-sdk";
4111
+ import { dump as dump5 } from "js-yaml";
4112
+ function integer2(raw, flag) {
4113
+ const value = Number(raw);
4114
+ if (!Number.isSafeInteger(value) || value < 1)
4115
+ throw new CliUsageError(`${flag} must be a positive integer`);
4116
+ return value;
4117
+ }
4118
+ async function inferenceBody(kind, prompt, opts, defaultModel) {
4119
+ if (prompt !== void 0 && opts.file)
4120
+ throw new CliUsageError("Pass a prompt or --file, not both");
4121
+ if (prompt === void 0 && !opts.file) throw new CliUsageError("A prompt or --file is required");
4122
+ let body;
4123
+ if (opts.file) {
4124
+ let value;
4125
+ try {
4126
+ value = JSON.parse(await readFile2(opts.file, "utf8"));
4127
+ } catch {
4128
+ throw new CliUsageError("--file must name a readable JSON request file");
4167
4129
  }
4168
- renderGatewayDetail(
4169
- requiresDestination ? result : settings.redactResponse?.(result) ?? result,
4170
- format
4130
+ if (!value || typeof value !== "object" || Array.isArray(value))
4131
+ throw new CliUsageError("--file must contain a JSON object");
4132
+ body = value;
4133
+ } else {
4134
+ body = kind === "chat" ? { messages: [{ role: "user", content: prompt }] } : { input: prompt };
4135
+ }
4136
+ body = { ...body };
4137
+ const model = opts.model ?? body.model ?? defaultModel;
4138
+ if (typeof model !== "string" || !model.trim())
4139
+ throw new CliUsageError(
4140
+ "Set --model, the request model, or the matching inference model config"
4171
4141
  );
4172
- } catch (error) {
4173
- await cleanupReservedDestination();
4174
- failAiGateway(error);
4142
+ body.model = model;
4143
+ if (opts.stream !== void 0) body.stream = opts.stream;
4144
+ if (opts.maxTokens !== void 0)
4145
+ body[kind === "responses" ? "max_output_tokens" : "max_completion_tokens"] = integer2(
4146
+ opts.maxTokens,
4147
+ "--max-tokens"
4148
+ );
4149
+ if (opts.dimensions !== void 0) body.dimensions = integer2(opts.dimensions, "--dimensions");
4150
+ if (opts.encoding !== void 0) body.encoding_format = opts.encoding;
4151
+ if (kind === "responses" && body.store === void 0) body.store = false;
4152
+ return body;
4153
+ }
4154
+ function responseText(value) {
4155
+ if (!value || typeof value !== "object") return "";
4156
+ const record = value;
4157
+ if (record.type === "response.output_text.delta" && typeof record.delta === "string")
4158
+ return record.delta;
4159
+ if (Array.isArray(record.choices))
4160
+ return record.choices.map((choice) => choice.message?.content ?? choice.delta?.content ?? "").join("");
4161
+ if (Array.isArray(record.output))
4162
+ return record.output.flatMap((item) => Array.isArray(item.content) ? item.content : []).filter((part) => part.type === "output_text").map((part) => part.text).join("");
4163
+ return "";
4164
+ }
4165
+ function registerAiGatewayInference(parent) {
4166
+ const group = parent.command("inference").description("Call the gateway runtime (separate API key; no SCM OAuth required)");
4167
+ for (const kind of ["chat", "responses", "embeddings"]) {
4168
+ const command = group.command(`${kind} [prompt]`).description(
4169
+ `${kind === "embeddings" ? "Create embeddings" : "Generate a model response"} through your runtime gateway`
4170
+ ).option("--model <id>", "Provider-prefixed model ID; overrides request file and config").option("--endpoint <url>", "Runtime base URL including /v1; overrides config").option("--file <path>", "JSON request file (mutually exclusive with prompt)").option("--timeout <ms>", "Total deadline including streaming, in milliseconds", "60000").option(
4171
+ "--output <format>",
4172
+ "pretty, json or yaml; streaming json emits one chunk per line (JSONL)"
4173
+ );
4174
+ if (kind === "embeddings")
4175
+ command.option("--dimensions <n>", "Embedding dimensions").option("--encoding <format>", "float or base64");
4176
+ else
4177
+ command.option("--stream", "Stream text (pretty) or validated events (json/JSONL)").option("--max-tokens <n>", "Maximum completion/output tokens");
4178
+ command.action(async (prompt, opts) => {
4179
+ const controller = new AbortController();
4180
+ const interrupt = () => controller.abort(new Error("Inference interrupted"));
4181
+ const outputError = (error) => controller.abort(error);
4182
+ let stream;
4183
+ let failed = false;
4184
+ let failure3;
4185
+ try {
4186
+ const format = await resolveOutput(command, opts, { allowed: ["pretty", "json", "yaml"] });
4187
+ const config = await loadConfig({ aiGwInferenceEndpoint: opts.endpoint });
4188
+ const body = await inferenceBody(
4189
+ kind,
4190
+ prompt,
4191
+ opts,
4192
+ kind === "embeddings" ? config.aiGwEmbeddingModel : config.aiGwInferenceModel
4193
+ );
4194
+ if (body.stream === true && format === "yaml")
4195
+ throw new CliUsageError("Streaming supports --output pretty or json (JSONL), not yaml");
4196
+ const schema = kind === "chat" ? GatewayInferenceInputCreateChatCompletionRequestSchema : kind === "responses" ? GatewayInferenceInputCreateResponseSchema : GatewayInferenceInputCreateEmbeddingRequestSchema;
4197
+ const parsed = schema.safeParse(body);
4198
+ if (!parsed.success)
4199
+ throw new CliUsageError(
4200
+ `Invalid inference request: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.code}`).join("; ")}`
4201
+ );
4202
+ const client = new AIGatewayInferenceClient({
4203
+ endpoint: config.aiGwInferenceEndpoint,
4204
+ apiKey: config.aiGwInferenceApiKey,
4205
+ timeoutMs: integer2(opts.timeout ?? "60000", "--timeout"),
4206
+ numRetries: 0
4207
+ });
4208
+ process.once("SIGINT", interrupt);
4209
+ process.once("SIGTERM", interrupt);
4210
+ process.stdout.on("error", outputError);
4211
+ const write = (text2) => new Promise((resolve10, reject) => {
4212
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve10());
4213
+ });
4214
+ const requestOptions = { signal: controller.signal };
4215
+ const result = kind === "chat" ? await client.createChatCompletion(
4216
+ GatewayInferenceInputCreateChatCompletionRequestSchema.parse(body),
4217
+ requestOptions
4218
+ ) : kind === "responses" ? await client.createResponse(
4219
+ GatewayInferenceInputCreateResponseSchema.parse(body),
4220
+ requestOptions
4221
+ ) : await client.createEmbedding(
4222
+ GatewayInferenceInputCreateEmbeddingRequestSchema.parse(body),
4223
+ requestOptions
4224
+ );
4225
+ if (Symbol.asyncIterator in result) {
4226
+ stream = result;
4227
+ for await (const event of result) {
4228
+ if (format === "json") await write(`${JSON.stringify(event)}
4229
+ `);
4230
+ else {
4231
+ const text2 = responseText(event);
4232
+ if (text2) await write(text2);
4233
+ }
4234
+ if ("type" in event && (event.type === "response.failed" || event.type === "response.incomplete"))
4235
+ throw new Error(`Gateway generation ended with ${event.type}`);
4236
+ }
4237
+ if (format === "pretty") await write("\n");
4238
+ } else {
4239
+ const text2 = format === "yaml" ? dump5(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
4240
+ await write(`${text2}
4241
+ `);
4242
+ if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
4243
+ throw new Error(`Gateway generation ended with ${result.status}`);
4244
+ }
4245
+ } catch (error) {
4246
+ failed = true;
4247
+ failure3 = error;
4248
+ } finally {
4249
+ await stream?.cancel();
4250
+ controller.abort();
4251
+ process.removeListener("SIGINT", interrupt);
4252
+ process.removeListener("SIGTERM", interrupt);
4253
+ process.stdout.removeListener("error", outputError);
4254
+ }
4255
+ if (failed && failure3?.code !== "EPIPE") {
4256
+ if (failure3?.errorType === ErrorType2.USER_REQUEST_PAYLOAD_ERROR)
4257
+ usageError(failure3 instanceof Error ? failure3.message : "Invalid inference options");
4258
+ fail(failure3);
4259
+ }
4260
+ });
4175
4261
  }
4176
4262
  }
4177
4263
 
4178
- // src/cli/commands/aigateway/structured-input.ts
4264
+ // src/cli/commands/aigateway/inventory.ts
4265
+ import { readFile as readFile3 } from "fs/promises";
4179
4266
  import {
4180
- AISecSDKException,
4181
- buildDottedObject,
4182
- ErrorType as ErrorType2,
4183
- GatewayJsonObjectSchema,
4184
- setDottedValue
4267
+ AI_GATEWAY_DEPLOYMENT_STATUSES,
4268
+ AI_GATEWAY_DEPLOYMENT_TYPES,
4269
+ AI_GATEWAY_KNOWN_API_KEY_SCOPES,
4270
+ AI_GATEWAY_KNOWN_MCP_AUTH_TYPES,
4271
+ AI_GATEWAY_KNOWN_MCP_TRANSPORTS,
4272
+ AI_GATEWAY_MUTABLE_MCP_CAPABILITY_TYPES,
4273
+ customHostConfiguration,
4274
+ GatewayApiKeyRotateRequestSchema,
4275
+ GatewayApiKeyUpdateRequestSchema,
4276
+ GatewayConfigCreateRequestSchema,
4277
+ GatewayConfigUpdateRequestSchema,
4278
+ GatewayDeploymentCreateRequestSchema,
4279
+ GatewayDeploymentUpdateRequestSchema,
4280
+ GatewayGuardrailCreateRequestSchema,
4281
+ GatewayGuardrailUpdateRequestSchema as GatewayGuardrailUpdateRequestSchema2,
4282
+ GatewayIntegrationCreateRequestSchema,
4283
+ GatewayIntegrationModelsBulkUpdateRequestSchema,
4284
+ GatewayIntegrationUpdateRequestSchema,
4285
+ GatewayIntegrationWorkspacesBulkUpdateRequestSchema,
4286
+ GatewayOrganisationAuthSettingsUpdateRequestSchema,
4287
+ GatewayOrganisationUpdateRequestSchema,
4288
+ GatewayPluginCreateRequestSchema,
4289
+ GatewayProviderCreateRequestSchema,
4290
+ GatewayProviderUpdateRequestSchema,
4291
+ GatewayServiceApiKeyCreateRequestSchema,
4292
+ GatewayUserApiKeyCreateRequestSchema,
4293
+ McpIntegrationCapabilitiesBulkUpdateRequestSchema,
4294
+ McpIntegrationCreateRequestSchema,
4295
+ McpIntegrationUpdateRequestSchema,
4296
+ McpIntegrationWorkspacesBulkUpdateRequestSchema,
4297
+ redactAIGatewaySecrets
4185
4298
  } from "@cdot65/prisma-airs-sdk";
4186
- function collectOption(value, previous = []) {
4187
- return [...previous, value];
4188
- }
4189
- function addStructuredInputOptions(command) {
4190
- return command.option("--file <path>", "Advanced JSON/YAML request base; named flags override it").option(
4191
- "--set <path=value>",
4192
- "Set a typed JSON value at a request path (repeatable)",
4193
- collectOption
4194
- ).option(
4195
- "--set-string <path=value>",
4196
- "Set a literal string at a request path (repeatable)",
4197
- collectOption
4198
- );
4199
- }
4200
- function parseBooleanOption(value) {
4201
- if (value === true || value === false) return value;
4202
- if (value === "true") return true;
4203
- if (value === "false") return false;
4204
- throw new CliUsageError("Expected true or false");
4205
- }
4206
- function parseIntegerOption(value) {
4207
- const text2 = String(value);
4208
- if (!/^-?(0|[1-9]\d*)$/.test(text2)) throw new CliUsageError("Expected an integer");
4209
- const parsed = Number(text2);
4210
- if (!Number.isSafeInteger(parsed)) throw new CliUsageError("Expected a safe integer");
4211
- return parsed;
4212
- }
4213
- function parseDateOption(value) {
4214
- const parsed = new Date(String(value));
4215
- if (Number.isNaN(parsed.getTime())) throw new CliUsageError("Expected an ISO-8601 timestamp");
4216
- return parsed;
4217
- }
4218
- function parseCsvOption(value) {
4219
- const values = String(value).split(",").map((item) => item.trim()).filter(Boolean);
4220
- if (values.length === 0) throw new CliUsageError("Expected a non-empty comma-separated list");
4221
- return values;
4222
- }
4223
- function parseJsonOption(value) {
4299
+
4300
+ // src/cli/debug-logger.ts
4301
+ import {
4302
+ appendFileSync,
4303
+ mkdirSync,
4304
+ readdirSync,
4305
+ statSync,
4306
+ unlinkSync,
4307
+ writeFileSync
4308
+ } from "fs";
4309
+ import { dirname, join } from "path";
4310
+ var AIRS_DOMAINS = [
4311
+ "api.sase.paloaltonetworks.com",
4312
+ "api.apps.paloaltonetworks.com",
4313
+ "service.api.aisecurity.paloaltonetworks.com",
4314
+ "auth.apps.paloaltonetworks.com",
4315
+ "api.dlp.paloaltonetworks.com"
4316
+ ];
4317
+ function isAirsUrl(url) {
4224
4318
  try {
4225
- return JSON.parse(String(value));
4319
+ const parsed = new URL(url);
4320
+ return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
4226
4321
  } catch {
4227
- throw new CliUsageError("Expected valid JSON");
4322
+ return false;
4228
4323
  }
4229
4324
  }
4230
- function repeatableValues(value) {
4231
- if (Array.isArray(value)) return value.map(String);
4232
- return [String(value)];
4233
- }
4234
- function splitKeyValue(raw, label2) {
4235
- const separator = raw.indexOf("=");
4236
- if (separator <= 0) throw new CliUsageError(`Expected ${label2}=value`);
4237
- return [raw.slice(0, separator), raw.slice(separator + 1)];
4238
- }
4239
- function parseBooleanBindingsOption(value) {
4240
- return repeatableValues(value).map((raw) => {
4241
- const [id, enabled] = splitKeyValue(raw, "id");
4242
- return { id, enabled: parseBooleanOption(enabled) };
4243
- });
4244
- }
4245
- function parseModelBindingsOption(value) {
4246
- return repeatableValues(value).map((raw) => {
4247
- const [slug, enabled] = splitKeyValue(raw, "slug");
4248
- return { slug, enabled: parseBooleanOption(enabled) };
4249
- });
4250
- }
4251
- function parseCapabilityBindingsOption(value) {
4252
- return repeatableValues(value).map((raw) => {
4253
- const [identity2, enabled] = splitKeyValue(raw, "type:name");
4254
- const separator = identity2.indexOf(":");
4255
- if (separator <= 0 || separator === identity2.length - 1) {
4256
- throw new CliUsageError("Expected type:name=enabled");
4257
- }
4258
- return {
4259
- type: identity2.slice(0, separator),
4260
- name: identity2.slice(separator + 1),
4261
- enabled: parseBooleanOption(enabled)
4262
- };
4263
- });
4325
+ var MASK = "***";
4326
+ var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key|client[-_]?auth|auth[-_]?code|^key$/i;
4327
+ function isSensitiveKey(key) {
4328
+ return SENSITIVE_KEY_PATTERN.test(key);
4264
4329
  }
4265
- function parseStringMapOption(value) {
4266
- const result = {};
4267
- for (const raw of repeatableValues(value)) {
4268
- const [key, item] = splitKeyValue(raw, "key");
4269
- if (["__proto__", "constructor", "prototype"].includes(key)) {
4270
- throw new CliUsageError(`Unsafe key: ${key}`);
4330
+ function redactHeaders(headers) {
4331
+ const out = {};
4332
+ for (const [k, v] of Object.entries(headers)) {
4333
+ out[k] = isSensitiveKey(k) || /^x-portkey-(config|metadata|forward-headers)$/i.test(k) ? MASK : v;
4334
+ }
4335
+ return out;
4336
+ }
4337
+ function redactDeep(value) {
4338
+ if (Array.isArray(value)) {
4339
+ return value.map(redactDeep);
4340
+ }
4341
+ if (value !== null && typeof value === "object") {
4342
+ const out = {};
4343
+ for (const [k, v] of Object.entries(value)) {
4344
+ out[k] = isSensitiveKey(k) ? MASK : redactDeep(v);
4271
4345
  }
4272
- if (Object.hasOwn(result, key)) throw new CliUsageError(`Duplicate key: ${key}`);
4273
- result[key] = item;
4346
+ return out;
4274
4347
  }
4275
- return result;
4348
+ return value;
4276
4349
  }
4277
- function parseAssignment(raw, forceString) {
4278
- const separator = raw.indexOf("=");
4279
- if (separator <= 0) throw new CliUsageError("Expected path=value");
4280
- const path3 = raw.slice(0, separator);
4281
- const text2 = raw.slice(separator + 1);
4282
- if (forceString) return [path3, text2];
4350
+ function redactUrl(url) {
4283
4351
  try {
4284
- return [path3, JSON.parse(text2)];
4352
+ const parsed = new URL(url);
4353
+ let touched = false;
4354
+ for (const key of parsed.searchParams.keys()) {
4355
+ if (isSensitiveKey(key)) {
4356
+ parsed.searchParams.set(key, MASK);
4357
+ touched = true;
4358
+ }
4359
+ }
4360
+ return touched ? parsed.toString() : url;
4285
4361
  } catch {
4286
- return [path3, text2];
4362
+ return url;
4287
4363
  }
4288
4364
  }
4289
- function schemaMessage(error) {
4290
- const issues = error?.issues;
4291
- if (!Array.isArray(issues) || issues.length === 0)
4292
- return "request body does not match its schema";
4293
- return issues.map((issue) => {
4294
- const path3 = issue.path?.length ? issue.path.map(String).join(".") : "<root>";
4295
- return `${path3}: ${issue.message ?? "invalid value"}`;
4296
- }).join("; ");
4297
- }
4298
- function optionName(option) {
4299
- return `--${option.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`;
4300
- }
4301
- function asUsageError(error, prefix = "Invalid AI Gateway request") {
4302
- if (error instanceof CliUsageError) return error;
4303
- if (error instanceof AISecSDKException && error.errorType === ErrorType2.USER_REQUEST_PAYLOAD_ERROR) {
4304
- return new CliUsageError(`${prefix}: ${error.message}`);
4365
+ function headersToRecord(headers) {
4366
+ if (!headers) return {};
4367
+ if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
4368
+ const out = {};
4369
+ headers.forEach((v, k) => {
4370
+ out[k] = v;
4371
+ });
4372
+ return out;
4305
4373
  }
4306
- return new CliUsageError(`${prefix}: ${schemaMessage(error)}`);
4374
+ if (Array.isArray(headers)) {
4375
+ return Object.fromEntries(headers);
4376
+ }
4377
+ return headers;
4307
4378
  }
4308
- async function buildStructuredRequest(options, schema, fields = []) {
4309
- try {
4310
- let body = {};
4311
- if (options.file) {
4312
- body = GatewayJsonObjectSchema.parse(await readGatewayRequest(options.file));
4379
+ function installDebugLogger(logPath, options = {}) {
4380
+ mkdirSync(dirname(logPath), { recursive: true, mode: 448 });
4381
+ writeFileSync(logPath, "", { encoding: "utf8", mode: 384, flag: "wx" });
4382
+ const originalFetch = globalThis.fetch;
4383
+ globalThis.fetch = async function debugFetch(input2, init2) {
4384
+ const url = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
4385
+ const rawHeaders = headersToRecord(
4386
+ init2?.headers ?? (input2 instanceof Request ? input2.headers : void 0)
4387
+ );
4388
+ const inference = Object.keys(rawHeaders).some(
4389
+ (name) => name.toLowerCase() === "x-portkey-api-key"
4390
+ );
4391
+ if (!isAirsUrl(url) && !inference) {
4392
+ return originalFetch(input2, init2);
4313
4393
  }
4314
- for (const field of fields) {
4315
- const raw = options[field.option];
4316
- if (raw === void 0 || Array.isArray(raw) && raw.length === 0) continue;
4317
- let value;
4394
+ const dashboard = options.omitBodies || /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url) || /\/aiag\//.test(new URL(url).pathname);
4395
+ const method = init2?.method ?? (input2 instanceof Request ? input2.method : "GET");
4396
+ const reqHeaders = redactHeaders(rawHeaders);
4397
+ const loggedUrl = redactUrl(url);
4398
+ let reqBody;
4399
+ if (inference || dashboard) reqBody = "[BODY OMITTED]";
4400
+ else if (init2?.body) {
4318
4401
  try {
4319
- value = field.parse ? field.parse(raw) : raw;
4320
- } catch (error) {
4321
- if (error instanceof CliUsageError) {
4322
- throw new CliUsageError(`Invalid ${optionName(field.option)}: ${error.message}`);
4323
- }
4324
- throw error;
4402
+ reqBody = redactDeep(JSON.parse(String(init2.body)));
4403
+ } catch {
4404
+ const contentType = new Headers(rawHeaders).get("content-type") ?? "";
4405
+ reqBody = contentType.includes("application/x-www-form-urlencoded") ? redactDeep(Object.fromEntries(new URLSearchParams(String(init2.body)))) : "[NON-JSON BODY OMITTED]";
4325
4406
  }
4326
- body = setDottedValue(body, field.path, value);
4327
4407
  }
4328
- const assignments = [
4329
- ...(options.set ?? []).map((raw) => {
4330
- try {
4331
- const [path3, value] = parseAssignment(raw, false);
4332
- return { path: path3, value };
4333
- } catch (error) {
4334
- if (error instanceof CliUsageError) {
4335
- throw new CliUsageError(`Invalid --set '${raw}': ${error.message}`);
4336
- }
4337
- throw error;
4338
- }
4339
- }),
4340
- ...(options.setString ?? []).map((raw) => {
4408
+ const ts2 = (/* @__PURE__ */ new Date()).toISOString();
4409
+ const startMs = Date.now();
4410
+ let response;
4411
+ let resBody;
4412
+ let error;
4413
+ try {
4414
+ response = await originalFetch(input2, init2);
4415
+ } catch (err) {
4416
+ error = inference ? "Runtime request failed" : "Request failed";
4417
+ const entry2 = JSON.stringify({
4418
+ timestamp: ts2,
4419
+ durationMs: Date.now() - startMs,
4420
+ request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
4421
+ error
4422
+ });
4423
+ appendFileSync(logPath, `${entry2}
4424
+ `);
4425
+ throw err;
4426
+ }
4427
+ const durationMs = Date.now() - startMs;
4428
+ const resHeaders = {};
4429
+ response.headers.forEach((v, k) => {
4430
+ resHeaders[k] = v;
4431
+ });
4432
+ if (inference || dashboard || response.headers.get("content-type")?.includes("text/event-stream")) {
4433
+ resBody = "[BODY OMITTED]";
4434
+ } else {
4435
+ const clone = response.clone();
4436
+ try {
4437
+ const text2 = await clone.text();
4341
4438
  try {
4342
- const [path3, value] = parseAssignment(raw, true);
4343
- return { path: path3, value };
4344
- } catch (error) {
4345
- if (error instanceof CliUsageError) {
4346
- throw new CliUsageError(`Invalid --set-string '${raw}': ${error.message}`);
4347
- }
4348
- throw error;
4439
+ resBody = redactDeep(JSON.parse(text2));
4440
+ } catch {
4441
+ resBody = "[NON-JSON BODY OMITTED]";
4349
4442
  }
4350
- })
4351
- ];
4352
- buildDottedObject(assignments);
4353
- for (const { path: path3, value } of assignments) body = setDottedValue(body, path3, value);
4354
- return schema.parse(body);
4355
- } catch (error) {
4356
- throw asUsageError(error);
4443
+ } catch {
4444
+ resBody = "<unreadable>";
4445
+ }
4446
+ }
4447
+ const entry = JSON.stringify({
4448
+ timestamp: ts2,
4449
+ durationMs,
4450
+ request: { method, url: loggedUrl, headers: reqHeaders, body: reqBody },
4451
+ response: {
4452
+ status: response.status,
4453
+ statusText: response.statusText,
4454
+ headers: redactHeaders(resHeaders),
4455
+ body: resBody
4456
+ }
4457
+ });
4458
+ appendFileSync(logPath, `${entry}
4459
+ `);
4460
+ return response;
4461
+ };
4462
+ return {
4463
+ teardown() {
4464
+ globalThis.fetch = originalFetch;
4465
+ }
4466
+ };
4467
+ }
4468
+
4469
+ // src/cli/tenant-input.ts
4470
+ import { input, password } from "@inquirer/prompts";
4471
+ async function promptTenantValue(message, secret = false) {
4472
+ if (!process.stdin.isTTY || !process.stderr.isTTY)
4473
+ throw new Error(
4474
+ "Interactive setup requires a terminal. Use --config, or --tsg-id and --client-id with --client-secret-stdin. For tenant set, pass a value or use --stdin."
4475
+ );
4476
+ const prompt = secret ? password : input;
4477
+ return prompt(
4478
+ {
4479
+ message,
4480
+ validate: (value) => Boolean(value.trim()) || "A nonempty value is required"
4481
+ },
4482
+ { input: process.stdin, output: process.stderr }
4483
+ );
4484
+ }
4485
+ async function readTenantStdin() {
4486
+ if (process.stdin.isTTY) throw new Error("--stdin requires piped input");
4487
+ const chunks = [];
4488
+ let size = 0;
4489
+ for await (const chunk of process.stdin) {
4490
+ const buffer = Buffer.from(chunk);
4491
+ size += buffer.length;
4492
+ if (size > 65536) throw new Error("Configuration input exceeds 64 KiB");
4493
+ chunks.push(buffer);
4357
4494
  }
4495
+ const value = Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
4496
+ if (!value.trim() || /[\r\n\0]/.test(value))
4497
+ throw new Error("Provide one nonempty configuration value on stdin");
4498
+ return value;
4358
4499
  }
4359
4500
 
4360
4501
  // src/cli/commands/aigateway/inventory.ts
@@ -5202,7 +5343,7 @@ function registerAiGatewayInventory(root) {
5202
5343
  createSchema: GatewayGuardrailCreateRequestSchema,
5203
5344
  updateFields: guardrailUpdateFields,
5204
5345
  updateOptions: addGuardrailUpdateFields,
5205
- updateSchema: GatewayGuardrailUpdateRequestSchema
5346
+ updateSchema: GatewayGuardrailUpdateRequestSchema2
5206
5347
  },
5207
5348
  {
5208
5349
  create: (client, body) => client.guardrails.create(body),
@@ -5634,6 +5775,7 @@ function scopeNameLooksUnrelated(name, scopeName) {
5634
5775
  function registerAiGatewayCommand(program) {
5635
5776
  const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
5636
5777
  registerAiGatewayInventory(aigateway);
5778
+ registerAdminGuardrails(aigateway);
5637
5779
  registerAiGatewayReportCommand(aigateway);
5638
5780
  registerAiGatewayInference(aigateway);
5639
5781
  const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
@@ -6419,6 +6561,7 @@ import pLimit2 from "p-limit";
6419
6561
 
6420
6562
  // src/redteam/judge/providers.ts
6421
6563
  import { createHash as createHash2 } from "crypto";
6564
+ import { TypeSafeClient } from "@typesafe-ai/sdk";
6422
6565
  var ProviderError = class extends Error {
6423
6566
  constructor(message) {
6424
6567
  super(message);
@@ -6455,7 +6598,7 @@ function typesafeEndpoint(baseUrl, path3) {
6455
6598
  return `${url.toString().replace(/\/+$/, "")}${path3}`;
6456
6599
  }
6457
6600
  var TypeSafeHttpProvider = class {
6458
- name = "typesafe-http";
6601
+ name = "typesafe-sdk";
6459
6602
  model;
6460
6603
  url;
6461
6604
  apiKey;
@@ -6473,33 +6616,41 @@ var TypeSafeHttpProvider = class {
6473
6616
  this.sleep = options.sleep ?? defaultSleep;
6474
6617
  }
6475
6618
  async judge(request) {
6476
- const body = JSON.stringify({
6477
- state: request.state,
6478
- model: this.model,
6479
- questions: request.questions
6619
+ let received;
6620
+ const client = new TypeSafeClient({
6621
+ apiKey: this.apiKey,
6622
+ baseURL: this.url.slice(0, -"/v1/systemone".length),
6623
+ timeout: this.timeoutMs,
6624
+ retry: { maxRetries: 0 },
6625
+ // One retry owner: the adapter below.
6626
+ logLevel: "off",
6627
+ // Do not allow SDK environment settings to log scan text.
6628
+ fetch: async (input2, init2) => {
6629
+ received = await this.fetch(input2, { ...init2, redirect: "error" });
6630
+ return received;
6631
+ }
6480
6632
  });
6481
6633
  for (let attempt = 0; ; attempt += 1) {
6482
6634
  const started = performance.now();
6483
6635
  let response;
6484
6636
  try {
6485
- response = await this.fetch(this.url, {
6486
- method: "POST",
6487
- redirect: "error",
6488
- headers: {
6489
- Authorization: `Bearer ${this.apiKey}`,
6490
- "Content-Type": "application/json",
6491
- Accept: "application/json"
6492
- },
6493
- body,
6494
- signal: AbortSignal.timeout(this.timeoutMs)
6495
- });
6637
+ received = void 0;
6638
+ response = await client.systemOne({
6639
+ state: request.state,
6640
+ model: this.model,
6641
+ questions: request.questions
6642
+ }).asResponse();
6496
6643
  } catch (error) {
6497
- if (attempt >= this.maxRetries)
6498
- throw new ProviderError(
6499
- `TypeSafe API connection failed: ${error instanceof Error ? error.name : "Error"}`
6500
- );
6501
- await this.sleep(retryDelayMs(attempt, null));
6502
- continue;
6644
+ if (received && !received.ok) {
6645
+ response = received;
6646
+ } else {
6647
+ if (attempt >= this.maxRetries)
6648
+ throw new ProviderError(
6649
+ `TypeSafe API connection failed: ${error instanceof Error ? error.name : "Error"}`
6650
+ );
6651
+ await this.sleep(retryDelayMs(attempt, null));
6652
+ continue;
6653
+ }
6503
6654
  }
6504
6655
  if (response.ok) {
6505
6656
  let decoded;
@@ -6847,6 +6998,13 @@ function renderSummary(results) {
6847
6998
  "| Category | Judged | Judge ASR | AIRS ASR | Agreement |",
6848
6999
  "|---|---|---|---|---|"
6849
7000
  ];
7001
+ if (results.provider === "replay")
7002
+ lines.splice(
7003
+ 2,
7004
+ 0,
7005
+ "REPLAY ONLY: reused recorded answers; no new Jev evaluation or credential check was performed.",
7006
+ ""
7007
+ );
6850
7008
  for (const [name, block] of Object.entries(results.by_category))
6851
7009
  lines.push(
6852
7010
  `| ${name} | ${block.judged} | ${pct(block.asr)} | ${pct(block.agreement.airs_asr_from_threat_flags)} | ${pct(block.agreement.agreement_rate)} |`
@@ -8047,6 +8205,7 @@ async function backupTargets(opts) {
8047
8205
  import { lstat as lstat3, mkdir, readFile as readFile5 } from "fs/promises";
8048
8206
  import { join as join2, resolve as resolve3 } from "path";
8049
8207
  import { RedTeamClient } from "@cdot65/prisma-airs-sdk";
8208
+ import { Option as Option2 } from "commander";
8050
8209
  var JUDGE_PROVIDER_ERROR_EXIT_CODE = 4;
8051
8210
  var JUDGE_OUTPUT_FILES = ["results.json", "judgments.json", "summary.md"];
8052
8211
  function probability(raw, flag) {
@@ -8098,7 +8257,7 @@ function safeContext2() {
8098
8257
  function registerRedTeamJudgeCommand(redteam) {
8099
8258
  const command = redteam.command("judge [scanFile]").description(
8100
8259
  "Judge red-team attack outputs with TypeSafe Jev and compute an independent attack success rate"
8101
- ).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(
8260
+ ).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(
8102
8261
  "--base-url <url>",
8103
8262
  `TypeSafe API base URL (default: typesafeBaseUrl or ${DEFAULT_TYPESAFE_BASE_URL})`
8104
8263
  ).option(
@@ -8144,7 +8303,7 @@ async function runJudge(command, scanFile, opts) {
8144
8303
  const maxRetries = opts.maxRetries === "0" ? 0 : integer3(opts.maxRetries, "--max-retries", 10);
8145
8304
  const timeoutMs = integer3(opts.timeout, "--timeout");
8146
8305
  if (!opts.out.trim()) throw new CliUsageError("--out cannot be empty");
8147
- const needsConfig = opts.job !== void 0 || opts.provider === "typesafe" && !opts.dryRun;
8306
+ const needsConfig = opts.job !== void 0 || opts.provider === "typesafe" && !opts.dryRun && !opts.harnessCredentials;
8148
8307
  const config = needsConfig ? await loadConfig() : await loadConfig().catch(() => void 0);
8149
8308
  const format = await resolveOutput(command, opts, {
8150
8309
  allowed: ["pretty", "json"],
@@ -8183,8 +8342,8 @@ async function runJudge(command, scanFile, opts) {
8183
8342
  );
8184
8343
  }
8185
8344
  const { units, notes } = normalized;
8186
- const model = opts.model ?? config?.typesafeModel ?? DEFAULT_TYPESAFE_MODEL;
8187
- const baseUrl = opts.baseUrl ?? config?.typesafeBaseUrl ?? DEFAULT_TYPESAFE_BASE_URL;
8345
+ const model = opts.model ?? (opts.harnessCredentials ? process.env.TYPESAFE_DEFAULT_MODEL : config?.typesafeModel) ?? DEFAULT_TYPESAFE_MODEL;
8346
+ const baseUrl = opts.baseUrl ?? (opts.harnessCredentials ? process.env.TYPESAFE_BASE_URL : config?.typesafeBaseUrl) ?? DEFAULT_TYPESAFE_BASE_URL;
8188
8347
  if (opts.dryRun) {
8189
8348
  const first = units.find((unit) => !unit.is_error);
8190
8349
  const payload = {
@@ -8204,10 +8363,10 @@ async function runJudge(command, scanFile, opts) {
8204
8363
  throw new CliUsageError("--replay must contain a JSON object");
8205
8364
  provider = new ReplayProvider(recorded);
8206
8365
  } else {
8207
- const apiKey = config?.typesafeApiKey;
8366
+ const apiKey = opts.harnessCredentials ? process.env.TYPESAFE_API_KEY : config?.typesafeApiKey;
8208
8367
  if (typeof apiKey !== "string" || !apiKey.trim())
8209
8368
  throw new Error(
8210
- `TypeSafe API key is not configured (typesafeApiKey). ${settingRemedy(["typesafeApiKey"], safeContext2())}`
8369
+ 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())}`
8211
8370
  );
8212
8371
  provider = new TypeSafeHttpProvider({ apiKey, model, baseUrl, maxRetries, timeoutMs });
8213
8372
  }