Skip to content
Open
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
3 changes: 2 additions & 1 deletion gui/src/hooks/useJsonConfigEditor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useRef, useState } from "react";
import type { TFn } from "../i18n/shared";

export interface Config {
port: number;
Expand All @@ -13,7 +14,7 @@ export function useJsonConfigEditor(deps: {
fetchConfig: () => Promise<void>;
fetchProviderQuotas: (refresh?: boolean) => Promise<void>;
onSaved: () => void;
t: (key: string, values?: Record<string, string>) => string;
t: TFn;
}) {
const { apiBase, config, notify, fetchConfig, fetchProviderQuotas, onSaved, t } = deps;
const [editing, setEditing] = useState(false);
Expand Down
3 changes: 2 additions & 1 deletion gui/src/hooks/useProviderAccountPools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { AccountLoadState } from "../components/provider-workspace/types";
import { accountNeedsReauth } from "../oauth-health-display";
import type { AccountQuota } from "../codex-quota-utils";
import { oauthAccountDisplayLabel } from "../provider-workspace/auth";
import type { TFn } from "../i18n/shared";

export interface Config {
port: number;
Expand Down Expand Up @@ -45,7 +46,7 @@ export function buildActiveAccountNeedsReauthMap(

export function useProviderAccountPools(deps: {
apiBase: string;
t: (key: string, ...args: unknown[]) => string;
t: TFn;
config: Config | null;
oauthStatus: Record<string, OAuthStatus>;
aliveRef: MutableRefObject<boolean>;
Expand Down
4 changes: 2 additions & 2 deletions gui/src/pages/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export default function Providers({ apiBase }: { apiBase: string }) {
}, [oauthStatus, codexPool.accounts, codexPool.loadState, codexActiveNeedsReauth]);

const pools = useProviderAccountPools({
apiBase, t: t as unknown as Parameters<typeof useProviderAccountPools>[0]["t"],
apiBase, t,
config, oauthStatus: oauthStatusWithCodex, aliveRef,
notify,
fetchConfig, fetchOauth, fetchProviderQuotas, codexActiveNeedsReauth,
Expand All @@ -187,7 +187,7 @@ export default function Providers({ apiBase }: { apiBase: string }) {
apiBase, config,
notify,
fetchConfig, fetchProviderQuotas, onSaved: () => setModelsRefreshToken(n => n + 1),
t: t as unknown as Parameters<typeof useJsonConfigEditor>[0]["t"],
t,
});
const {
draft, setDraft, jsonEditorOpen, jsonSaving, jsonLeaveOpen,
Expand Down
4 changes: 2 additions & 2 deletions gui/src/visibility-poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ function hiddenNow(): boolean {
*/
function scheduleInterval(fn: () => void, ms: number): ReturnType<typeof setInterval> {
if (typeof window !== "undefined" && typeof window.setInterval === "function") {
return window.setInterval(fn, ms) as unknown as ReturnType<typeof setInterval>;
return window.setInterval(fn, ms);
}
return setInterval(fn, ms);
}

function cancelInterval(handle: ReturnType<typeof setInterval>): void {
if (typeof window !== "undefined" && typeof window.clearInterval === "function") {
window.clearInterval(handle as unknown as number);
window.clearInterval(handle);
return;
}
clearInterval(handle);
Expand Down
1 change: 1 addition & 0 deletions gui/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
"strict": true,

/* Linting */
"noUnusedLocals": true,
Expand Down
1 change: 1 addition & 0 deletions gui/tsconfig.node.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
"strict": true,

/* Linting */
"noUnusedLocals": true,
Expand Down
3 changes: 1 addition & 2 deletions src/images/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ export async function planVideoBridge(
for (const t of parsed.context?.tools ?? []) {
// Skip namespaced tools — a namespaced MCP video_gen must not be intercepted.
if (t.namespace) continue;
const fnName = typeof t.name === "string" ? t.name
: (t as unknown as { function?: { name?: string } }).function?.name;
const fnName = t.name;
if (typeof fnName === "string" && isVideoGenName(fnName)) {
toolNames.add(fnName);
}
Expand Down
4 changes: 2 additions & 2 deletions src/lab/events/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,13 @@ export function validateSubject(raw: unknown, layer: EvidenceLayer): EvidenceSub
throw new LabValidationError("unknown_layer", String(_exhaustive));
}

function stripEventId(event: Record<string, unknown>): Record<string, unknown> {
function stripEventId(event: LabEvent): Omit<LabEvent, "eventId"> {
const { eventId: _omit, ...rest } = event;
return rest;
}

function enforceEventId(event: LabEvent): void {
const recomputed = eventIdForPayload(stripEventId(event as unknown as Record<string, unknown>));
const recomputed = eventIdForPayload(stripEventId(event));
if (event.eventId !== recomputed) {
throw new LabValidationError("event_id_mismatch", `eventId mismatch: got ${event.eventId}, expected ${recomputed}`);
}
Expand Down
57 changes: 43 additions & 14 deletions src/lab/live/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,23 @@ function pathForProtocol(protocol: string): string {
return "/responses";
}

function jsonRecord(value: unknown): Record<string, unknown> | undefined {
return value !== null && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: undefined;
}

/**
* JSON property reads historically tolerated scalar and array roots but threw for null.
* Preserve that boundary while keeping untrusted payloads out of the type system.
*/
function rootJsonFields(value: unknown): Record<string, unknown> {
if (value === null || value === undefined) {
throw new TypeError("expected a non-null JSON payload");
}
return jsonRecord(value) ?? {};
}

/** Upstream HTTP path for a trusted live-route request (CL-03 / CL-08 production dispatch). */
export function liveUpstreamRequestPath(protocol: string): string {
return pathForProtocol(protocol);
Expand All @@ -163,17 +180,25 @@ function chatObservation(body: string, status: number): NormalizedObservation {
if (!line) continue;
const payload = line.slice(5).trim();
if (!payload || payload === "[DONE]") { if (payload === "[DONE]") terminal = true; continue; }
let json: any;
try { json = JSON.parse(payload); } catch { continue; }
for (const choice of Array.isArray(json.choices) ? json.choices : []) {
if (typeof choice?.delta?.content === "string") text += choice.delta.content;
let parsedJson: unknown;
try { parsedJson = JSON.parse(payload) as unknown; } catch { continue; }
const json = rootJsonFields(parsedJson);
for (const rawChoice of Array.isArray(json.choices) ? json.choices : []) {
const choice = jsonRecord(rawChoice);
const delta = jsonRecord(choice?.delta);
if (typeof delta?.content === "string") text += delta.content;
if (choice?.finish_reason != null) terminal = true;
for (const call of Array.isArray(choice?.delta?.tool_calls) ? choice.delta.tool_calls : []) {
const idx = Number.isInteger(call?.index) ? call.index : toolParts.size;
for (const rawCall of Array.isArray(delta?.tool_calls) ? delta.tool_calls : []) {
const call = jsonRecord(rawCall);
const fn = jsonRecord(call?.function);
const rawIndex = call?.index;
const idx = typeof rawIndex === "number" && Number.isInteger(rawIndex)
? rawIndex
: toolParts.size;
const prior = toolParts.get(idx) ?? { id: "", name: "", arguments: "" };
if (typeof call?.id === "string") prior.id = call.id;
if (typeof call?.function?.name === "string") prior.name = call.function.name;
if (typeof call?.function?.arguments === "string") prior.arguments += call.function.arguments;
if (typeof fn?.name === "string") prior.name = fn.name;
if (typeof fn?.arguments === "string") prior.arguments += fn.arguments;
toolParts.set(idx, prior);
}
}
Expand All @@ -182,12 +207,16 @@ function chatObservation(body: string, status: number): NormalizedObservation {
output.push({ type: "function_call", call_id: row.id, name: row.name, arguments: row.arguments });
}
} else {
const json = JSON.parse(body) as any;
const choice = Array.isArray(json.choices) ? json.choices[0] : undefined;
if (typeof choice?.message?.content === "string") text = choice.message.content;
const json = rootJsonFields(JSON.parse(body) as unknown);
const choice = jsonRecord(Array.isArray(json.choices) ? json.choices[0] : undefined);
const message = jsonRecord(choice?.message);
if (typeof message?.content === "string") text = message.content;
terminal = choice?.finish_reason != null;
for (const call of Array.isArray(choice?.message?.tool_calls) ? choice.message.tool_calls : []) {
output.push({ type: "function_call", call_id: call.id, name: call.function?.name, arguments: call.function?.arguments });
for (const rawCall of Array.isArray(message?.tool_calls) ? message.tool_calls : []) {
const call = jsonRecord(rawCall);
if (!call) throw new TypeError("expected an OpenAI chat tool call object");
const fn = jsonRecord(call?.function);
output.push({ type: "function_call", call_id: call.id, name: fn?.name, arguments: fn?.arguments });
}
}
if (text) output.unshift({ type: "message", content: [{ type: "output_text", text }] });
Expand Down Expand Up @@ -333,4 +362,4 @@ export async function runLiveScenario(caseRecord: CaseRecord, routeContext: LabR
const retryPolicy = failureRules.find((rule) => rule.match.includes(failureSignal))?.retry ?? null;
return complete({ scenarioId: activeCase.id, suite: activeCase.suite, passed: false, classification: classified.classification, secondaryCode: classified.secondaryCode, assertionResults: [], diagnostics, routeSubject, transportError: error instanceof TransportError ? error.code : undefined }, retryPolicy);
}
}
}
4 changes: 1 addition & 3 deletions src/lab/public/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ export type PublicBundleVerificationResult =

export function verifyPublicEvidenceBundle(bundle: PublicEvidenceBundleV1): PublicBundleVerificationResult {
try {
const raw = bundle as unknown as Record<string, unknown>;
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { status: "schema_rejected" };
const allowed = new Set([
"schemaVersion",
"exportPolicyVersion",
Expand All @@ -212,7 +210,7 @@ export function verifyPublicEvidenceBundle(bundle: PublicEvidenceBundleV1): Publ
"bundleDigest",
"signature",
]);
if (Object.keys(raw).some((key) => !allowed.has(key))) return { status: "schema_rejected" };
if (Object.keys(bundle).some((key) => !allowed.has(key))) return { status: "schema_rejected" };
if (bundle.schemaVersion !== "public_evidence_bundle_v1" || bundle.exportPolicyVersion !== "public_export_policy_v1") {
return { status: "schema_rejected" };
}
Expand Down
12 changes: 11 additions & 1 deletion src/lib/self-launch-argv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@ interface SelfLaunchArgvOptions {
sourceEntrypoint?: string;
}

function bunStandaloneExecutable(): boolean | undefined {
const runtime: unknown = Bun;
if (runtime === null || typeof runtime !== "object" || !("isStandaloneExecutable" in runtime)) {
return undefined;
}
return typeof runtime.isStandaloneExecutable === "boolean"
? runtime.isStandaloneExecutable
: undefined;
}

/** Build argv for re-entering the current CLI in compiled or source mode. */
export function selfLaunchArgv(
args: readonly string[],
options: SelfLaunchArgvOptions = {},
): string[] {
const bunStandalone = (Bun as unknown as { isStandaloneExecutable?: boolean }).isStandaloneExecutable;
const bunStandalone = bunStandaloneExecutable();
const isStandaloneExecutable = options.isStandaloneExecutable ?? Boolean(bunStandalone);
if (isStandaloneExecutable) return [...args];
return [options.sourceEntrypoint ?? process.argv[1], ...args];
Expand Down
13 changes: 7 additions & 6 deletions src/oauth/command-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,13 @@ export function parseCommandCodeCallback(value: unknown, expectedState: string):
}
const body = value as Record<string, unknown>;
if (body.state !== expectedState) throw new Error("Command Code OAuth state mismatch");
for (const field of ["apiKey", "userId", "userName", "keyName"] as const) {
if (typeof body[field] !== "string" || body[field].length === 0) {
throw new Error(`Command Code callback missing ${field}`);
}
}
return body as unknown as CommandCodeCallback;
const { apiKey, state, userId, userName, keyName } = body;
if (typeof apiKey !== "string" || apiKey.length === 0) throw new Error("Command Code callback missing apiKey");
if (typeof state !== "string" || state.length === 0) throw new Error("Command Code callback missing state");
if (typeof userId !== "string" || userId.length === 0) throw new Error("Command Code callback missing userId");
if (typeof userName !== "string" || userName.length === 0) throw new Error("Command Code callback missing userName");
if (typeof keyName !== "string" || keyName.length === 0) throw new Error("Command Code callback missing keyName");
return Object.assign(body, { apiKey, state, userId, userName, keyName });
}

function createCallbackServer(state: string): {
Expand Down
Loading
Loading