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
7 changes: 7 additions & 0 deletions .changeset/acp-permission-raw-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@moonshot-ai/agent-core": minor
"@moonshot-ai/acp-adapter": minor
"@moonshot-ai/kimi-code": minor
---

Expose raw tool inputs on approval requests so ACP clients can inspect exact permission payloads.
13 changes: 7 additions & 6 deletions packages/acp-adapter/src/approval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,16 @@ export function permissionResponseToApprovalResponse(
}
switch (optionId) {
case APPROVE_ONCE_OPTION_ID:
// Legacy Python kimi-cli (< v0.9.0) used 'approve' as the
// allow-once optionId. Keep accepting it so custom ACP clients
// built against the old SDK are not silently rejected.
case 'approve':
// Legacy Python kimi-cli (< v0.9.0) used 'approve' as the
// allow-once optionId. Keep accepting it so custom ACP clients
// built against the old SDK are not silently rejected.
return { decision: 'approved' };
case APPROVE_ALWAYS_OPTION_ID:
// Legacy Python kimi-cli (< v0.9.0) used 'approve_for_session' as
// the allow-always optionId. Same backward-compatibility rationale
// as the 'approve' branch above.
case 'approve_for_session':
// Legacy Python kimi-cli (< v0.9.0) used 'approve_for_session' as
// the allow-always optionId. Same backward-compatibility rationale
// as the 'approve' branch above.
return { decision: 'approved', scope: 'session' };
case REJECT_OPTION_ID:
return { decision: 'rejected' };
Expand Down Expand Up @@ -276,6 +276,7 @@ export function buildPermissionToolCallUpdate(
return {
toolCallId,
title: req.toolName,
rawInput: req.rawInput,
content,
};
}
Expand Down
10 changes: 8 additions & 2 deletions packages/acp-adapter/test/approval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,15 @@ describe('buildPermissionToolCallUpdate (Phase 5.1 minimal shape)', () => {
toolCallId: 'abc',
toolName: 'Bash',
action: 'run command',
rawInput: { command: 'ls -la', timeout: 60 },
display: fakeDisplay,
};

it('prefixes the toolCallId with the turnId when one is known', () => {
const update = buildPermissionToolCallUpdate(42, baseReq);
expect(update.toolCallId).toBe('42:abc');
expect(update.title).toBe('Bash');
expect(update.rawInput).toEqual({ command: 'ls -la', timeout: 60 });
});

it('falls back to the raw SDK toolCallId when no turnId is tracked yet', () => {
Expand All @@ -238,7 +240,8 @@ describe('AcpSession ↔ requestPermission bridge (end-to-end via wire)', () =>
} as unknown as KimiHarness;

const { agentStream, clientStream } = makeInMemoryStreamPair();
new AgentSideConnection((c) => new AcpServer(harness, c), agentStream);
const agentConn = new AgentSideConnection((c) => new AcpServer(harness, c), agentStream);
void agentConn;
const client = new ApprovalClient();
client.reply = {
outcome: { outcome: 'selected', optionId: APPROVE_ONCE_OPTION_ID },
Expand Down Expand Up @@ -278,6 +281,7 @@ describe('AcpSession ↔ requestPermission bridge (end-to-end via wire)', () =>
toolCallId: 'tc-1',
toolName: 'Bash',
action: 'run command',
rawInput: { command: 'echo hi', timeout: 60 },
display: { kind: 'command', command: 'echo hi' },
};
const decision = await handle.invokeHandler(approvalReq);
Expand All @@ -297,6 +301,7 @@ describe('AcpSession ↔ requestPermission bridge (end-to-end via wire)', () =>
]);
expect(req.toolCall.toolCallId).toBe(`${turnId}:tc-1`);
expect(req.toolCall.title).toBe('Bash');
expect(req.toolCall.rawInput).toEqual({ command: 'echo hi', timeout: 60 });

// Settle the parked prompt with a turn.ended so the test exits
// cleanly.
Expand All @@ -320,7 +325,8 @@ describe('AcpSession ↔ requestPermission bridge (end-to-end via wire)', () =>
} as unknown as KimiHarness;

const { agentStream, clientStream } = makeInMemoryStreamPair();
new AgentSideConnection((c) => new AcpServer(harness, c), agentStream);
const agentConn = new AgentSideConnection((c) => new AcpServer(harness, c), agentStream);
void agentConn;
const client = new ApprovalClient();
// Override to throw so the bridge falls into the catch branch.
client.requestPermission = async (_p: RequestPermissionRequest) => {
Expand Down
1 change: 1 addition & 0 deletions packages/agent-core/src/agent/permission/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export class PermissionManager {
toolCallId: id,
toolName: name,
action,
rawInput: context.args,
display,
},
{ signal },
Expand Down
1 change: 1 addition & 0 deletions packages/agent-core/src/agent/permission/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface ApprovalRequest {
toolCallId: string;
toolName: string;
action: string;
rawInput?: unknown;
display: ToolInputDisplay;
}

Expand Down
1 change: 1 addition & 0 deletions packages/agent-core/src/rpc/sdk-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface ApprovalRequest {
readonly toolCallId: string;
readonly toolName: string;
readonly action: string;
readonly rawInput?: unknown;
readonly display: ToolInputDisplay;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core/test/agent/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ it('runs an agent turn through builtin tool approval and execution', async () =>
[emit] assistant.delta { "turnId": 0, "delta": "I will run that." }
[emit] tool.call.delta { "turnId": 0, "toolCallId": "call_bash", "name": "Bash", "argumentsPart": "{\\"command\\":\\"printf lookup-result\\",\\"timeout\\":60}" }
[wire] context.append_loop_event { "event": { "type": "content.part", "uuid": "<uuid-2>", "turnId": "0", "step": 1, "stepUuid": "<uuid-1>", "part": { "type": "text", "text": "I will run that." } }, "time": "<time>" }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf lookup-result", "display": { "kind": "command", "command": "printf lookup-result", "cwd": "<cwd>", "language": "bash" } }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf lookup-result", "rawInput": { "command": "printf lookup-result", "timeout": 60 }, "display": { "kind": "command", "command": "printf lookup-result", "cwd": "<cwd>", "language": "bash" } }
`);
expect(ctx.lastLlmInput()).toMatchInlineSnapshot(`
system: <system-prompt>
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core/test/agent/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe('Agent config', () => {
[emit] assistant.delta { "turnId": 0, "delta": "I will run Bash." }
[emit] tool.call.delta { "turnId": 0, "toolCallId": "call_bash", "name": "Bash", "argumentsPart": "{\\"command\\":\\"printf original-result\\",\\"timeout\\":60}" }
[wire] context.append_loop_event { "event": { "type": "content.part", "uuid": "<uuid-2>", "turnId": "0", "step": 1, "stepUuid": "<uuid-1>", "part": { "type": "text", "text": "I will run Bash." } }, "time": "<time>" }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf original-result", "display": { "kind": "command", "command": "printf original-result", "cwd": "<cwd>", "language": "bash" } }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf original-result", "rawInput": { "command": "printf original-result", "timeout": 60 }, "display": { "kind": "command", "command": "printf original-result", "cwd": "<cwd>", "language": "bash" } }
`);
expect(ctx.lastLlmInput()).toMatchInlineSnapshot(`
system: <system-prompt>
Expand Down
5 changes: 4 additions & 1 deletion packages/agent-core/test/agent/permission.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ describe('Agent permission', () => {
[emit] assistant.delta { "turnId": 0, "delta": "I will try Bash." }
[emit] tool.call.delta { "turnId": 0, "toolCallId": "call_bash", "name": "Bash", "argumentsPart": "{\\"command\\":\\"printf should-not-run\\",\\"timeout\\":60}" }
[wire] context.append_loop_event { "event": { "type": "content.part", "uuid": "<uuid-2>", "turnId": "0", "step": 1, "stepUuid": "<uuid-1>", "part": { "type": "text", "text": "I will try Bash." } }, "time": "<time>" }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf should-not-run", "display": { "kind": "command", "command": "printf should-not-run", "cwd": "<cwd>", "language": "bash" } }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf should-not-run", "rawInput": { "command": "printf should-not-run", "timeout": 60 }, "display": { "kind": "command", "command": "printf should-not-run", "cwd": "<cwd>", "language": "bash" } }
`);
expect(ctx.lastLlmInput()).toMatchInlineSnapshot(`
system: <system-prompt>
Expand Down Expand Up @@ -426,6 +426,7 @@ describe('Permission auto mode', () => {
expect.objectContaining({
toolName,
action,
rawInput: args,
display: {
kind: 'file_io',
operation,
Expand Down Expand Up @@ -1771,6 +1772,7 @@ describe('Plan mode Bash permission policy', () => {
toolCallId: 'call_plan_bash',
toolName: 'Bash',
action: 'run command',
rawInput: { command: 'ls -la', timeout: 60 },
display: {
kind: 'command',
command: 'ls -la',
Expand Down Expand Up @@ -1882,6 +1884,7 @@ describe('ExitPlanMode permission policy', () => {
toolCallId: 'call_exit',
toolName: 'ExitPlanMode',
action: 'Presenting plan and exiting plan mode',
rawInput: { options: planOptions },
display: {
kind: 'plan_review',
plan: '# Plan\n\n- Step',
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-core/test/agent/turn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1480,7 +1480,7 @@ describe('Agent turn flow', () => {
[emit] assistant.delta { "turnId": 0, "delta": "I will run Bash." }
[emit] tool.call.delta { "turnId": 0, "toolCallId": "call_bash", "name": "Bash", "argumentsPart": "{\\"command\\":\\"printf should-not-run\\",\\"timeout\\":60}" }
[wire] context.append_loop_event { "event": { "type": "content.part", "uuid": "<uuid-2>", "turnId": "0", "step": 1, "stepUuid": "<uuid-1>", "part": { "type": "text", "text": "I will run Bash." } }, "time": "<time>" }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf should-not-run", "display": { "kind": "command", "command": "printf should-not-run", "cwd": "<cwd>", "language": "bash" } }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf should-not-run", "rawInput": { "command": "printf should-not-run", "timeout": 60 }, "display": { "kind": "command", "command": "printf should-not-run", "cwd": "<cwd>", "language": "bash" } }
`);
expect(ctx.lastLlmInput()).toMatchInlineSnapshot(`
system: <system-prompt>
Expand Down Expand Up @@ -1541,7 +1541,7 @@ describe('Agent turn flow', () => {
[emit] assistant.delta { "turnId": 0, "delta": "I will ask first." }
[emit] tool.call.delta { "turnId": 0, "toolCallId": "call_bash", "name": "Bash", "argumentsPart": "{\\"command\\":\\"printf approved\\",\\"timeout\\":60}" }
[wire] context.append_loop_event { "event": { "type": "content.part", "uuid": "<uuid-2>", "turnId": "0", "step": 1, "stepUuid": "<uuid-1>", "part": { "type": "text", "text": "I will ask first." } }, "time": "<time>" }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf approved", "display": { "kind": "command", "command": "printf approved", "cwd": "<cwd>", "language": "bash" } }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf approved", "rawInput": { "command": "printf approved", "timeout": 60 }, "display": { "kind": "command", "command": "printf approved", "cwd": "<cwd>", "language": "bash" } }
`);
expect(ctx.lastLlmInput()).toMatchInlineSnapshot(`
system: <system-prompt>
Expand Down Expand Up @@ -1610,7 +1610,7 @@ describe('Agent turn flow', () => {
[emit] assistant.delta { "turnId": 0, "delta": "I will wait for approval." }
[emit] tool.call.delta { "turnId": 0, "toolCallId": "call_bash", "name": "Bash", "argumentsPart": "{\\"command\\":\\"printf should-not-run\\",\\"timeout\\":60}" }
[wire] context.append_loop_event { "event": { "type": "content.part", "uuid": "<uuid-2>", "turnId": "0", "step": 1, "stepUuid": "<uuid-1>", "part": { "type": "text", "text": "I will wait for approval." } }, "time": "<time>" }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf should-not-run", "display": { "kind": "command", "command": "printf should-not-run", "cwd": "<cwd>", "language": "bash" } }
[emit] requestApproval { "turnId": 0, "toolCallId": "call_bash", "toolName": "Bash", "action": "Running: printf should-not-run", "rawInput": { "command": "printf should-not-run", "timeout": 60 }, "display": { "kind": "command", "command": "printf should-not-run", "cwd": "<cwd>", "language": "bash" } }
`);
expect(ctx.lastLlmInput()).toMatchInlineSnapshot(`
system: <system-prompt>
Expand Down
1 change: 1 addition & 0 deletions packages/node-sdk/test/session-event-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe('Event public types', () => {
it('exposes approval and question reverse-RPC requests', () => {
expectTypeOf<ApprovalRequest['turnId']>().toEqualTypeOf<number | undefined>();
expectTypeOf<ApprovalRequest['toolName']>().toEqualTypeOf<string>();
expectTypeOf<ApprovalRequest['rawInput']>().toEqualTypeOf<unknown>();
expectTypeOf<QuestionRequest['questions'][number]['question']>().toEqualTypeOf<string>();
});

Expand Down