Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 5 additions & 13 deletions sdk/typescript/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,6 @@ type Writable = Pick<NodeJS.WriteStream, "write"> & {
};
type SignalName = "SIGINT" | "SIGTERM";

const DISPLAY_SEVERITIES: readonly SeverityLevel[] = SCAN_SEVERITIES;
const MODEL_REASONING_EFFORTS = [
"minimal",
"low",
Expand Down Expand Up @@ -9135,10 +9134,8 @@ async function readDeepScanStop(
if (state?.terminalReason !== "capped") return undefined;
const { maxDiscoveryRuns, maxTimeHours } = state.config;
if (state.dispatchedCount >= maxDiscoveryRuns) {
const stillFindingIssues =
state.completionSequence > 0 && state.noNewStreak === 0;
return {
reason: `Reached the limit of ${maxDiscoveryRuns} review rounds. ${stillFindingIssues ? "The latest review still found new issues." : "More issues may remain."}`,
reason: `Reached the limit of ${maxDiscoveryRuns} review rounds. ${state.completionSequence > 0 && state.noNewStreak === 0 ? "The latest review still found new issues." : "More issues may remain."}`,
nextStep: `To scan further, rerun with --max-discovery-runs greater than ${maxDiscoveryRuns}.`,
};
}
Expand Down Expand Up @@ -9177,12 +9174,9 @@ function printScanSummary(
(severities.get(finding.severity.level) ?? 0) + 1,
);
}
const severitySummary = DISPLAY_SEVERITIES.map((severity) => {
const count = severities.get(severity);
return count === undefined ? null : `${count} ${severity}`;
})
.filter((value): value is string => value !== null)
.join(", ");
const severitySummary = SCAN_SEVERITIES.flatMap((severity) =>
severities.has(severity) ? `${severities.get(severity)} ${severity}` : [],
).join(", ");

const started = Date.parse(result.manifest.scan.startedAt);
const completed = Date.parse(result.manifest.scan.completedAt);
Expand Down Expand Up @@ -9643,9 +9637,7 @@ export class Progress {

#line(message: string): string {
const elapsedSeconds = this.elapsedSeconds;
const minutes = Math.floor(elapsedSeconds / 60);
const seconds = elapsedSeconds % 60;
return `[${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}] ${message}`;
return `[${String(Math.floor(elapsedSeconds / 60)).padStart(2, "0")}:${String(elapsedSeconds % 60).padStart(2, "0")}] ${message}`;
}

#observeStreamErrors(): void {
Expand Down
21 changes: 9 additions & 12 deletions sdk/typescript/src/cost-model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isRecord } from "./record.js";
import { isSafeNonNegativeInteger } from "./value.js";

export interface ScanCost {
model: string;
Expand Down Expand Up @@ -140,21 +141,21 @@ export function tokenUsage(value: unknown): ScanTokenUsage | null {
const legacyCacheWrite = value["cache_write_tokens"];
const cacheWrite =
canonicalCacheWrite === 0 &&
isTokenCount(input) &&
isTokenCount(cached) &&
isTokenCount(legacyCacheWrite) &&
isSafeNonNegativeInteger(input) &&
isSafeNonNegativeInteger(cached) &&
isSafeNonNegativeInteger(legacyCacheWrite) &&
legacyCacheWrite > 0 &&
cached + legacyCacheWrite <= input
? legacyCacheWrite
: (canonicalCacheWrite ?? legacyCacheWrite ?? 0);
const output = value["output_tokens"];
const reasoning = value["reasoning_output_tokens"] ?? 0;
if (
!isTokenCount(input) ||
!isTokenCount(cached) ||
!isTokenCount(cacheWrite) ||
!isTokenCount(output) ||
!isTokenCount(reasoning) ||
!isSafeNonNegativeInteger(input) ||
!isSafeNonNegativeInteger(cached) ||
!isSafeNonNegativeInteger(cacheWrite) ||
!isSafeNonNegativeInteger(output) ||
!isSafeNonNegativeInteger(reasoning) ||
cached + cacheWrite > input ||
reasoning > output
) {
Expand Down Expand Up @@ -339,7 +340,3 @@ export function formatUsd(value: number): string {
maximumFractionDigits: 9,
}).format(value);
}

function isTokenCount(value: unknown): value is number {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
}
29 changes: 7 additions & 22 deletions sdk/typescript/src/cost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
sessionStartedAt,
} from "./scan-sessions.js";
import {
scanProgressUpdatesFromEvent,
scanProgressUpdatesFromText,
type ScanProgress,
} from "./worker-progress.js";

Expand Down Expand Up @@ -344,10 +344,9 @@ export class ScanCostTracker {
this.#workerProgress.set(session.threadId, progress.filesCompleted);
const filesCompleted = Math.min(
expectedFilesTotal ?? Number.MAX_SAFE_INTEGER,
[...this.#workerProgress.values()].reduce(
(total, reviewed) => total + reviewed,
0,
),
this.#workerProgress
.values()
.reduce((total, reviewed) => total + reviewed, 0),
);
if (filesCompleted < this.#highestFilesCompleted) continue;
const update = {
Expand Down Expand Up @@ -621,12 +620,7 @@ function readSessionEvent(
payload["type"] === "agent_message" &&
typeof payload["message"] === "string"
) {
session.progress.push(
...scanProgressUpdatesFromEvent({
type: "item.completed",
item: { type: "agent_message", text: payload["message"] },
}),
);
session.progress.push(...scanProgressUpdatesFromText(payload["message"]));
}
if (repository === undefined) return;
if (payload["type"] !== "agent_message") {
Expand Down Expand Up @@ -746,13 +740,7 @@ function sessionProgressUpdates(
if (payload["type"] === "message" && payload["role"] === "assistant") {
const content = payload["content"];
if (!Array.isArray(content)) return [];
return scanProgressUpdatesFromEvent({
type: "item.completed",
item: {
type: "agent_message",
text: sessionContentText(content, false),
},
});
return scanProgressUpdatesFromText(sessionContentText(content, false));
}
if (
payload["type"] !== "function_call_output" &&
Expand All @@ -771,10 +759,7 @@ function sessionProgressUpdates(
if (payload["status"] === "failed" || output === null) {
return [];
}
return scanProgressUpdatesFromEvent({
type: "item.completed",
item: { type: "command_execution", aggregated_output: output },
});
return scanProgressUpdatesFromText(output);
}

function sessionContentText(
Expand Down
42 changes: 15 additions & 27 deletions sdk/typescript/src/deep-progress.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isRecord } from "./record.js";
import { isSafeNonNegativeInteger } from "./value.js";

export interface DeepScanProgress {
completed: number;
Expand All @@ -10,7 +11,6 @@ interface DeepScanProgressTrackerOptions {
read: (signal: AbortSignal) => Promise<unknown>;
onProgress: (progress: DeepScanProgress) => void;
onError?: (error: unknown) => void;
pollIntervalMs?: number;
}

const DEEP_PROGRESS_POLL_INTERVAL_MS = 5_000;
Expand All @@ -34,10 +34,7 @@ export class DeepScanProgressTracker {
this.#options.onError?.(error);
});
};
this.#timer = setInterval(
poll,
this.#options.pollIntervalMs ?? DEEP_PROGRESS_POLL_INTERVAL_MS,
);
this.#timer = setInterval(poll, DEEP_PROGRESS_POLL_INTERVAL_MS);
this.#timer.unref();
poll();
}
Expand Down Expand Up @@ -95,21 +92,20 @@ export function deepScanProgressFromWorkbench(
if (!isRecord(progress)) return null;
const independentReviews = progress["independentReviews"];
if (independentReviews === undefined) return null;
if (
!isRecord(independentReviews) ||
!isCount(independentReviews["completed"]) ||
!isCount(independentReviews["active"]) ||
!isPositiveCount(independentReviews["maximum"])
) {
throw new Error(
"Codex Security workbench returned invalid Deep Scan progress.",
);
if (isRecord(independentReviews)) {
const { completed, active, maximum } = independentReviews;
if (
isSafeNonNegativeInteger(completed) &&
isSafeNonNegativeInteger(active) &&
isSafeNonNegativeInteger(maximum) &&
maximum > 0
) {
return { completed, active, maximum };
}
}
return {
completed: independentReviews["completed"],
active: independentReviews["active"],
maximum: independentReviews["maximum"],
};
throw new Error(
"Codex Security workbench returned invalid Deep Scan progress.",
);
}

function sameProgress(
Expand All @@ -123,11 +119,3 @@ function sameProgress(
left.maximum === right.maximum
);
}

function isCount(value: unknown): value is number {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
}

function isPositiveCount(value: unknown): value is number {
return isCount(value) && value > 0;
}
3 changes: 1 addition & 2 deletions sdk/typescript/src/patch-tui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,8 @@ function detailLines(
return [];
}
if (key === "level") {
const level = safeLine(entry);
return [
`${indent}${level.replace(/^./u, (match) => match.toUpperCase())}`,
`${indent}${safeLine(entry).replace(/^./u, (match) => match.toUpperCase())}`,
];
}
if (
Expand Down
27 changes: 8 additions & 19 deletions sdk/typescript/src/scan-activity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { isRecord } from "./record.js";
import { parseJson } from "./value.js";

const MAX_ACTIVITY_PATHS = 8;
const MAX_PROSE_CHARACTERS = 1_000;
Expand Down Expand Up @@ -171,10 +172,8 @@ export function scanActivityFromSessionEvent(
? payload["text"]
: payload["message"];
if (kind === null || typeof text !== "string") return null;
const timestamp =
typeof event["timestamp"] === "string" ? event["timestamp"] : "";
return proseActivity(
`${type}:${timestamp}:${text}`,
`${type}:${typeof event["timestamp"] === "string" ? event["timestamp"] : ""}:${text}`,
kind,
delta ? "running" : "completed",
text,
Expand Down Expand Up @@ -303,19 +302,13 @@ function prose(text: string, limit?: number): string {

function customShellCommand(name: string, value: unknown): string | null {
if (name !== "exec" || typeof value !== "string") return null;
const match =
const quoted =
/\b(?:exec_command|shell_command)\s*\(\s*\{[\s\S]{0,1024}?\b(?:cmd|command)["']?\s*:\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`[^`]*`)/u.exec(
value,
);
if (match?.[1] === undefined) return null;
const quoted = match[1];
)?.[1];
if (quoted === undefined) return null;
if (quoted.startsWith('"')) {
try {
const parsed: unknown = JSON.parse(quoted);
return typeof parsed === "string" ? parsed : null;
} catch {
return null;
}
return parseJson(() => quoted) as string | null;
}
return quoted.slice(1, -1).replaceAll(/\\(['`\\])/gu, "$1");
}
Expand Down Expand Up @@ -361,12 +354,8 @@ function sessionCallArguments(
const value = payload["arguments"] ?? payload["input"];
if (isRecord(value)) return value;
if (typeof value !== "string") return {};
try {
const parsed: unknown = JSON.parse(value);
return isRecord(parsed) ? parsed : {};
} catch {
return {};
}
const parsed = parseJson(() => value);
return isRecord(parsed) ? parsed : {};
}

function commandRepositoryPaths(command: string, repository: string): string[] {
Expand Down
16 changes: 4 additions & 12 deletions sdk/typescript/src/scan-dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ export class ScanDashboard {
scrollStatus = `Esc components · ${scrollStatus}`;
const model = this.#options.model;

const lines = [
return this.#formatFrame([
` CODEX SECURITY · ${publication ? "PUBLISH · " : verification ? "VERIFY-FIX · " : ""}${basename(this.#options.repository)}${this.#options.componentName === undefined ? "" : ` · ${this.#options.componentName}`}${model === undefined ? "" : ` · ${model.model} (${model.reasoningEffort})`}${this.#view === "details" ? ` · DETAILS${this.#source === "all" ? "" : ` · ${typeof this.#source === "number" ? `worker ${this.#source}` : this.#source}`}` : ""}`,
divider,
...activity,
Expand All @@ -624,9 +624,7 @@ export class ScanDashboard {
]),
]),
` TIME ${time} · ${this.#budget === null ? scrollStatus : "Enter to apply · Ctrl+C to exit"}`,
];

return this.#formatFrame(lines);
]);
}

#formatFrame(lines: (string | DashboardActivityLine)[]): string {
Expand Down Expand Up @@ -1056,9 +1054,7 @@ function detailsDescription(
const itemType = typeof payload["type"] === "string" ? payload["type"] : type;
if (itemType === "token_count") return undefined;
if (itemType === "message" || itemType === "agent_message") {
const role =
typeof payload["role"] === "string" ? payload["role"] : "assistant";
return `${role}: ${detailsText(payload["content"] ?? payload["message"])}`;
return `${typeof payload["role"] === "string" ? payload["role"] : "assistant"}: ${detailsText(payload["content"] ?? payload["message"])}`;
}
if (itemType === "reasoning" || itemType.startsWith("agent_reasoning")) {
const text = detailsText(
Expand Down Expand Up @@ -1269,11 +1265,7 @@ function styleLine(
if (kind === "status" || kind === "warning") {
return `${prefix}\u001B[${style}m${marker}${separator}${description}\u001B[0m`;
}
const workerLabel =
worker === undefined ? "" : `\u001B[36m${worker}\u001B[39m`;
const prose =
kind === "message" ? `\u001B[1m${description}\u001B[22m` : description;
return `${prefix}\u001B[${style}m${marker}\u001B[39m${separator}${workerLabel}${prose}`;
return `${prefix}\u001B[${style}m${marker}\u001B[39m${separator}${worker === undefined ? "" : `\u001B[36m${worker}\u001B[39m`}${kind === "message" ? `\u001B[1m${description}\u001B[22m` : description}`;
}
return `\u001B[${style}m${value}\u001B[0m`;
}
Expand Down
4 changes: 1 addition & 3 deletions sdk/typescript/src/scan-history-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,7 @@ export function renderScanHistory(
lines.push(
` ${strong("CONFIGURATION")} ${Object.entries(config)
.map(([key, value]) => {
const rendered =
typeof value === "object" ? JSON.stringify(value) : value;
return `${clean(key)}=${clean(rendered)}`;
return `${clean(key)}=${clean(typeof value === "object" ? JSON.stringify(value) : value)}`;
})
.join(` ${accent("·")} `)}`,
);
Expand Down
9 changes: 9 additions & 0 deletions sdk/typescript/src/value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ export function isNonEmptyString(value: unknown): value is string {
return typeof value === "string" && value.length > 0;
}

/** @internal */
export function parseJson(read: () => string): unknown {
try {
return JSON.parse(read());
} catch {
return null;
}
}

/** @internal */
export const findingEntry = <T extends { findingId: string }>(
finding: T,
Expand Down
Loading
Loading