@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.
- package/dist/cli/index.js +838 -679
- 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/
|
|
3566
|
-
import {
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
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
|
|
3574
|
+
import { open, readFile, unlink } from "fs/promises";
|
|
3966
3575
|
import { AIGatewayClient as AIGatewayClient2 } from "@cdot65/prisma-airs-sdk";
|
|
3967
|
-
import { dump as
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
} catch (error) {
|
|
4089
|
-
failAiGateway(error);
|
|
3893
|
+
return [path3, JSON.parse(text2)];
|
|
3894
|
+
} catch {
|
|
3895
|
+
return [path3, text2];
|
|
4090
3896
|
}
|
|
4091
3897
|
}
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
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
|
|
3917
|
+
async function buildStructuredRequest(options, schema, fields = []) {
|
|
4102
3918
|
try {
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
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
|
-
|
|
3965
|
+
throw asUsageError(error);
|
|
4110
3966
|
}
|
|
4111
3967
|
}
|
|
4112
|
-
|
|
3968
|
+
|
|
3969
|
+
// src/cli/commands/aigateway/admin-guardrails.ts
|
|
3970
|
+
function paginationInteger(value, flag, minimum, maximum) {
|
|
4113
3971
|
try {
|
|
4114
|
-
const
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
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
|
-
|
|
3978
|
+
fail(error);
|
|
4123
3979
|
}
|
|
4124
3980
|
}
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
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
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
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
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
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/
|
|
4264
|
+
// src/cli/commands/aigateway/inventory.ts
|
|
4265
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
4179
4266
|
import {
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
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
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
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
|
-
|
|
4319
|
+
const parsed = new URL(url);
|
|
4320
|
+
return AIRS_DOMAINS.some((d) => parsed.hostname === d || parsed.hostname.endsWith(`.${d}`));
|
|
4226
4321
|
} catch {
|
|
4227
|
-
|
|
4322
|
+
return false;
|
|
4228
4323
|
}
|
|
4229
4324
|
}
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
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
|
|
4266
|
-
const
|
|
4267
|
-
for (const
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
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
|
-
|
|
4273
|
-
result[key] = item;
|
|
4346
|
+
return out;
|
|
4274
4347
|
}
|
|
4275
|
-
return
|
|
4348
|
+
return value;
|
|
4276
4349
|
}
|
|
4277
|
-
function
|
|
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
|
-
|
|
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
|
|
4362
|
+
return url;
|
|
4287
4363
|
}
|
|
4288
4364
|
}
|
|
4289
|
-
function
|
|
4290
|
-
|
|
4291
|
-
if (
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
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
|
-
|
|
4374
|
+
if (Array.isArray(headers)) {
|
|
4375
|
+
return Object.fromEntries(headers);
|
|
4376
|
+
}
|
|
4377
|
+
return headers;
|
|
4307
4378
|
}
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
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
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
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
|
-
|
|
4320
|
-
} catch
|
|
4321
|
-
|
|
4322
|
-
|
|
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
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
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
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
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
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
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:
|
|
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-
|
|
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
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
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
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
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 (
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
)
|
|
6501
|
-
|
|
6502
|
-
|
|
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
|
}
|