blob: b4ad0995ca4f2bbae89acb62070361ff4554e0ad [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type { SessionEvent } from '@maka/core/events';
import type { SessionHeader, StoredMessage } from '@maka/core/session';
import { buildAskUserQuestionTool } from '../ask-user-question-tool.js';
import { PermissionEngine } from '../permission-engine.js';
import { ToolRuntime } from '../tool-runtime.js';
function header(): SessionHeader {
return {
id: 'session-1',
workspaceRoot: '/tmp/maka',
cwd: '/tmp/maka',
createdAt: 1,
lastUsedAt: 1,
name: 'Test',
titleIsManual: true,
isFlagged: false,
labels: [],
isArchived: false,
status: 'active',
statusUpdatedAt: 1,
hasUnread: false,
backend: 'ai-sdk',
llmConnectionSlug: 'c',
connectionLocked: true,
model: 'm',
permissionMode: 'ask',
schemaVersion: 1,
};
}
describe('AskUserQuestion runtime round trip', () => {
test('parks the tool, emits one request, and persists one nullable JSON result', async () => {
const appended: StoredMessage[] = [];
const events: SessionEvent[] = [];
let id = 0;
const runtime = new ToolRuntime({
sessionId: 'session-1',
header: header(),
connection: { providerType: 'openai', slug: 'c' } as never,
modelId: 'm',
appendMessage: async (message) => {
appended.push(message);
},
permissionEngine: new PermissionEngine({ newId: () => `permission-${++id}`, now: () => 1 }),
newId: () => `id-${++id}`,
now: () => 1,
getPermissionPauseTarget: () => null,
});
runtime.beginTurn('turn-1');
const resultPromise = runtime
.settleToolCall({
tool: buildAskUserQuestionTool(),
turnId: 'turn-1',
toolCallId: 'tool-1',
input: {
questions: [
{
question: 'Choose an approach',
options: [
{ label: 'Extend', description: 'Reuse the runtime seam' },
{ label: 'Separate' },
],
},
{
question: 'Keep the default?',
options: [{ label: 'Yes' }, { label: 'No' }],
},
],
},
abortSignal: new AbortController().signal,
eventSink: {
push: (event) => events.push(event),
pushAndWaitUntilConsumed: async (event) => {
events.push(event);
},
},
})
.then((settlement) => settlement.result);
await new Promise<void>((resolve) => setImmediate(resolve));
const request = events.find((event) => event.type === 'user_question_request');
assert.ok(request);
assert.equal(request.toolUseId, 'tool-1');
assert.equal(runtime.pendingUserQuestionCount('turn-1'), 1);
assert.equal(
runtime.respondToUserQuestion('turn-1', {
requestId: request.requestId,
answers: ['Extend', null],
}),
true,
);
assert.deepEqual(await resultPromise, {
answers: [
{ question: 'Choose an approach', answer: 'Extend' },
{ question: 'Keep the default?', answer: null },
],
});
const results = appended.filter((message) => message.type === 'tool_result');
assert.equal(results.length, 1);
assert.deepEqual(results[0]?.content, {
kind: 'json',
value: {
answers: [
{ question: 'Choose an approach', answer: 'Extend' },
{ question: 'Keep the default?', answer: null },
],
},
});
});
test('turn abort rejects the parked tool and ignores a late response', async () => {
const events: SessionEvent[] = [];
let id = 0;
const runtime = new ToolRuntime({
sessionId: 'session-1',
header: header(),
connection: { providerType: 'openai', slug: 'c' } as never,
modelId: 'm',
appendMessage: async () => {},
permissionEngine: new PermissionEngine({ newId: () => `permission-${++id}`, now: () => 1 }),
newId: () => `id-${++id}`,
now: () => 1,
getPermissionPauseTarget: () => null,
});
runtime.beginTurn('turn-1');
const resultPromise = runtime
.settleToolCall({
tool: buildAskUserQuestionTool(),
turnId: 'turn-1',
toolCallId: 'tool-1',
input: {
questions: [
{
question: 'Continue?',
options: [{ label: 'Yes' }, { label: 'No' }],
},
],
},
abortSignal: new AbortController().signal,
eventSink: {
push: (event) => events.push(event),
pushAndWaitUntilConsumed: async (event) => {
events.push(event);
},
},
})
.then((settlement) => settlement.result);
await new Promise<void>((resolve) => setImmediate(resolve));
const request = events.find((event) => event.type === 'user_question_request');
assert.ok(request);
runtime.endTurn('turn-1', 'aborted');
assert.deepEqual(await resultPromise, {
error: `Turn turn-1 aborted before user question ${request.requestId} was answered`,
});
assert.equal(
runtime.respondToUserQuestion('turn-1', {
requestId: request.requestId,
answers: ['Yes'],
}),
false,
);
});
});