Skip to content
Draft
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
38 changes: 34 additions & 4 deletions cli/bun/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import runnerHelp from "../../conformance/cases/fixtures/runner-help.txt" with {
import dryRunTemplate from "../../shared/fixtures/human/dry-run.v1.txt" with { type: "text" };
import deterministicMockDescriptor from "../../shared/fixtures/transport/deterministic-mock-adapter.json" with { type: "json" };
import fakeProcessDescriptor from "../../shared/fixtures/transport/mock-adapter.json" with { type: "json" };
import { resolveConfiguration, writeUserHarnessSelection } from "./core/config";
import { resolveConfiguration, writeUserHarnessSelection, mutateUserConfiguration, prepareUserConfigurationMutation } from "./core/config";
import { serviceEnvironment } from "./core/service/endpoint";
import { failure } from "./core/errors";
import { argvErrorOutcome, runService } from "./core/service/index";
Expand Down Expand Up @@ -148,7 +148,33 @@ export async function runCli(args: readonly string[], dependencies: CliDependenc
return await runPrimeCleanup(parsed.value, mode, dependencies);
}

const config = await resolveConfiguration(parsed.global, dependencies);
// Set the operation rendering mode before resolution so partial errors retain JSON.
if(parsed.kind === "operation" && parsed.json) mode="json";
let config: EffectiveConfiguration;
if(parsed.kind === "operation" && ["config-migrate","config-unset"].includes(parsed.operation)) {
const preflight=await prepareUserConfigurationMutation(dependencies);
let mutation: NonNullable<EffectiveConfiguration["mutation"]>;
try {mutation=await mutateUserConfiguration(preflight,parsed.operation === "config-migrate" ? "migrate" : "unset",parsed.configKeys ?? []);}
catch(caught) {
if(caught instanceof RunnerFailure && caught.code === "CONFIG_INVALID") {
let explanation=configurationExplanation(preflight);
try {explanation=configurationExplanation(await resolveConfiguration(parsed.global,dependencies));}
catch(contextError) {
if(contextError instanceof RunnerFailure && contextError.details?.configurationExplanation!==undefined)explanation=contextError.details.configurationExplanation as Record<string,unknown>;
}
const diagnostics=Array.isArray(explanation.diagnostics)?explanation.diagnostics:[];
explanation.diagnostics=[...diagnostics,{code:"CONFIG_INVALID",severity:"error",source:String(caught.details?.source ?? preflight.userConfigPath),reason:String(caught.details?.reason ?? "Runner configuration is invalid.")}];
explanation.runtime={transport:null,permissionMode:null,authProfile:null,billingOwner:null,nativeLimits:null,nativeOutputLimits:null};
throw failure("CONFIG_INVALID",{...(caught.details ?? {}),configurationExplanation:explanation});
}
throw caught;
}
try {config=await resolveConfiguration(parsed.global,dependencies);}
catch(caught) {if(caught instanceof RunnerFailure && caught.code === "CONFIG_INVALID")throw failure("CONFIG_INVALID",{...(caught.details ?? {}),mutation});throw caught;}
config.mutation=mutation;
} else {
config=await resolveConfiguration(parsed.global,{...dependencies,...(parsed.kind === "operation"&&parsed.targetArgv!==undefined ? {targetArgv:parsed.targetArgv}:{})});
}
// The default hosted harness runs no language command, so a language
// command word that also names a service command is that rejection.
if (hostedRejection !== undefined && config.values.harness === "openprose" && parsed.global.dryRun !== true) {
Expand Down Expand Up @@ -235,11 +261,15 @@ async function runOperation(
? "Route: cached-chatgpt-login (Codex default; not saved)"
: operationValue === "claude"
? "Route: claude-subscription (Claude default; not saved)"
: operationValue === "agents-sdk"
? "Route: openai-api-key (Agents SDK default; not saved)"
: "Route: OpenProse account (external route cleared)";
const savedModel = selection.model !== null
? `Model: ${humanSafeScalar(selection.model)} (saved)`
: operationValue === "codex" || operationValue === "claude"
? "Model: harness default (not saved)"
: operationValue === "agents-sdk"
? "Model: gpt-6.1-sol (Agents SDK default; not saved)"
: "Model: OpenProse default (external model cleared)";
dependencies.writeStdout([
`Default harness: ${humanSafeScalar(operationValue)} (${changed ? "updated" : "already selected"})`,
Expand All @@ -252,7 +282,7 @@ async function runOperation(
} else dependencies.writeStdout(jsonLine(report));
return 0;
}
if (operation === "config-explain") {
if (["config-explain","config-migrate","config-unset"].includes(operation)) {
if (mode === "human") dependencies.writeStdout(humanConfiguration(config));
else dependencies.writeStdout(jsonLine(configurationExplanation(config)));
return 0;
Expand Down Expand Up @@ -1639,7 +1669,7 @@ function configurationProvenance(config: EffectiveConfiguration): Array<Record<s
key,
source: source(config.sources[key]?.kind ?? "default"),
location: config.sources[key]?.location ?? "built-in",
redacted: key === "authProfile",
redacted: false,
})),
];
}
Expand Down
23 changes: 21 additions & 2 deletions cli/bun/src/core/args.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CONFIGURATION_KEYS } from "./config";
import { parsePackageCommand } from "./package-args";
import { invocationFailure } from "./errors";
import { quote } from "./output";
Expand Down Expand Up @@ -152,6 +153,23 @@ function globalKind(name: string): boolean | undefined {

function parseOperation(global: GlobalFlags, tokens: readonly string[], full: readonly string[]): ParsedEntrypoint {
if (tokens[0] === "weave") return { kind: "weave", global, argv: [...tokens.slice(1)] };
if (tokens[0] === "config" && tokens[1] === "explain" && tokens.includes("--")) {
const separator = tokens.indexOf("--");
const prefix = tokens.slice(2, separator);
if (prefix.some(token => token !== "--json") || prefix.length > 1 || Object.keys(global).length > 0) invalid("Exact configuration explanation requires options inside its target vector.");
const target = parseEntrypoint(tokens.slice(separator + 1));
if (target.kind !== "language" || target.argv.length < 2 || (target.redirect !== undefined && target.redirect.language !== true)) invalid("Configuration explanation requires a local language command.");
return { kind: "operation", global: target.global, operation: "config-explain", json: prefix.includes("--json"), targetArgv: target.argv };
}
if (tokens[0] === "config" && ["migrate", "unset"].includes(tokens[1] ?? "")) {
if(tokens.length === 3 && ["--help","-h"].includes(tokens[2]!)) return {kind:"help",global};
const rest = tokens.slice(2); const json = rest.at(-1) === "--json";
if (json) rest.pop();
if (Object.keys(global).some(key => !["output", "color", "verbose"].includes(key))) invalid("Configuration mutation does not accept runner execution flags.");
if (tokens[1] === "migrate" && rest.length !== 0) invalid("Configuration migrate does not accept operands.");
if (tokens[1] === "unset" && (rest.length === 0 || rest.some(key => !CONFIGURATION_KEYS.includes(key)))) invalid("Configuration unset requires known configuration keys.");
return { kind: "operation", global, operation: tokens[1] === "migrate" ? "config-migrate" : "config-unset", json, configKeys: [...new Set(rest)] };
}
// Help is text in every mode, so `--json` beside a help request is
// dropped.
const unjson = helpWithoutJson(tokens) ?? tokens;
Expand Down Expand Up @@ -226,8 +244,8 @@ function withInvocationAction(error: RunnerFailure, action: string): RunnerFailu

function parseHarnessUse(global: GlobalFlags, args: readonly string[]): ParsedEntrypoint {
const harness = args[0];
if (harness === undefined || !["openprose", "prime", "omp", "codex", "claude"].includes(harness)) {
invalid("Harness selection must be one of openprose, prime, omp, codex, or claude.");
if (harness === undefined || !["openprose", "agents-sdk", "prime", "omp", "codex", "claude"].includes(harness)) {
invalid("Harness selection must be one of openprose, agents-sdk, prime, omp, codex, or claude.");
}

let json = false;
Expand Down Expand Up @@ -280,6 +298,7 @@ function knownRunnerHelpPath(args: readonly string[]): boolean {
}

export function inferOutputMode(args: readonly string[]): OutputMode {
if (args[0] === "cli" && args[1] === "config" && args.slice(2, args.indexOf("--") < 0 ? args.length : args.indexOf("--")).includes("--json")) return "json";
let index = 0;
let mode: OutputMode = "human";
while (index < args.length) {
Expand Down
Loading
Loading