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
70 changes: 69 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ type GlobalFlags = PermissionFlags & {
ttl: number;
verbose?: boolean;
format: OutputFormat;
sessionMeta?: string;
claudeAgent?: string;
};

type PromptFlags = {
Expand Down Expand Up @@ -315,7 +317,15 @@ function addGlobalFlags(command: Command): Command {
"Queue owner idle TTL before shutdown (0 = keep alive forever) (default: 300)",
parseTtlSeconds,
)
.option("--verbose", "Enable verbose debug logs");
.option("--verbose", "Enable verbose debug logs")
.option(
"--session-meta <json>",
"JSON metadata to pass as _meta in newSession request",
)
.option(
"--claude-agent <name>",
"Agent name for claude-agent-acp (shorthand for --session-meta)",
);
}

function addSessionOption(command: Command): Command {
Expand Down Expand Up @@ -400,9 +410,64 @@ function resolveGlobalFlags(command: Command, config: ResolvedAcpxConfig): Globa
approveAll: opts.approveAll ? true : undefined,
approveReads: opts.approveReads ? true : undefined,
denyAll: opts.denyAll ? true : undefined,
sessionMeta: opts.sessionMeta,
claudeAgent: opts.claudeAgent,
};
}

function resolveSessionMeta(
globalFlags: GlobalFlags,
): Record<string, unknown> | undefined {
let meta: Record<string, unknown> | undefined;

if (globalFlags.sessionMeta) {
try {
meta = JSON.parse(globalFlags.sessionMeta) as Record<string, unknown>;
} catch {
throw new InvalidArgumentError("--session-meta must be valid JSON");
}
if (typeof meta !== "object" || meta === null || Array.isArray(meta)) {
throw new InvalidArgumentError("--session-meta must be a JSON object");
}
}

if (globalFlags.claudeAgent) {
const claudeMeta = {
claudeCode: { options: { agent: globalFlags.claudeAgent } },
};
meta = meta ? deepMerge(meta, claudeMeta) : claudeMeta;
}

return meta;
}

function deepMerge(
target: Record<string, unknown>,
source: Record<string, unknown>,
): Record<string, unknown> {
const result = { ...target };
for (const key of Object.keys(source)) {
const srcVal = source[key];
const tgtVal = result[key];
if (
typeof srcVal === "object" &&
srcVal !== null &&
!Array.isArray(srcVal) &&
typeof tgtVal === "object" &&
tgtVal !== null &&
!Array.isArray(tgtVal)
) {
result[key] = deepMerge(
tgtVal as Record<string, unknown>,
srcVal as Record<string, unknown>,
);
} else {
result[key] = srcVal;
}
}
return result;
}

function resolveOutputPolicy(format: OutputFormat, jsonStrict: boolean): OutputPolicy {
return {
format,
Expand Down Expand Up @@ -786,6 +851,7 @@ async function handleExec(
nonInteractivePermissions: globalFlags.nonInteractivePermissions,
authCredentials: config.auth,
authPolicy: globalFlags.authPolicy,
sessionMeta: resolveSessionMeta(globalFlags),
outputFormatter,
suppressSdkConsoleErrors: outputPolicy.suppressSdkConsoleErrors,
timeoutMs: globalFlags.timeout,
Expand Down Expand Up @@ -1050,6 +1116,7 @@ async function handleSessionsNew(
nonInteractivePermissions: globalFlags.nonInteractivePermissions,
authCredentials: config.auth,
authPolicy: globalFlags.authPolicy,
sessionMeta: resolveSessionMeta(globalFlags),
timeoutMs: globalFlags.timeout,
verbose: globalFlags.verbose,
});
Expand Down Expand Up @@ -1086,6 +1153,7 @@ async function handleSessionsEnsure(
nonInteractivePermissions: globalFlags.nonInteractivePermissions,
authCredentials: config.auth,
authPolicy: globalFlags.authPolicy,
sessionMeta: resolveSessionMeta(globalFlags),
timeoutMs: globalFlags.timeout,
verbose: globalFlags.verbose,
});
Expand Down
1 change: 1 addition & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ export class AcpClient {
const result = await connection.newSession({
cwd: asAbsoluteCwd(cwd),
mcpServers: [],
...(this.options.sessionMeta ? { _meta: this.options.sessionMeta } : {}),
});
return {
sessionId: result.sessionId,
Expand Down
6 changes: 6 additions & 0 deletions src/session-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export type RunOnceOptions = {
nonInteractivePermissions?: NonInteractivePermissionPolicy;
authCredentials?: Record<string, string>;
authPolicy?: AuthPolicy;
sessionMeta?: Record<string, unknown>;
outputFormatter: OutputFormatter;
suppressSdkConsoleErrors?: boolean;
verbose?: boolean;
Expand All @@ -107,6 +108,7 @@ export type SessionCreateOptions = {
nonInteractivePermissions?: NonInteractivePermissionPolicy;
authCredentials?: Record<string, string>;
authPolicy?: AuthPolicy;
sessionMeta?: Record<string, unknown>;
verbose?: boolean;
} & TimedRunOptions;

Expand Down Expand Up @@ -134,6 +136,7 @@ export type SessionEnsureOptions = {
nonInteractivePermissions?: NonInteractivePermissionPolicy;
authCredentials?: Record<string, string>;
authPolicy?: AuthPolicy;
sessionMeta?: Record<string, unknown>;
verbose?: boolean;
walkBoundary?: string;
} & TimedRunOptions;
Expand Down Expand Up @@ -785,6 +788,7 @@ export async function runOnce(options: RunOnceOptions): Promise<RunPromptResult>
nonInteractivePermissions: options.nonInteractivePermissions,
authCredentials: options.authCredentials,
authPolicy: options.authPolicy,
sessionMeta: options.sessionMeta,
suppressSdkConsoleErrors: options.suppressSdkConsoleErrors,
verbose: options.verbose,
onSessionUpdate: (notification) => output.onSessionUpdate(notification),
Expand Down Expand Up @@ -832,6 +836,7 @@ export async function createSession(
nonInteractivePermissions: options.nonInteractivePermissions,
authCredentials: options.authCredentials,
authPolicy: options.authPolicy,
sessionMeta: options.sessionMeta,
verbose: options.verbose,
});

Expand Down Expand Up @@ -904,6 +909,7 @@ export async function ensureSession(
nonInteractivePermissions: options.nonInteractivePermissions,
authCredentials: options.authCredentials,
authPolicy: options.authPolicy,
sessionMeta: options.sessionMeta,
timeoutMs: options.timeoutMs,
verbose: options.verbose,
});
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export type AcpClientOptions = {
nonInteractivePermissions?: NonInteractivePermissionPolicy;
authCredentials?: Record<string, string>;
authPolicy?: AuthPolicy;
sessionMeta?: Record<string, unknown>;
suppressSdkConsoleErrors?: boolean;
verbose?: boolean;
onSessionUpdate?: (notification: SessionNotification) => void;
Expand Down