@borgee/agents-host 0.2.84 → 0.2.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/dist/agents-host.js +15 -5
  3. package/dist/background-runs.d.ts +2 -1
  4. package/dist/background-runs.js +48 -17
  5. package/dist/chat/chat-control-plane.d.ts +1 -0
  6. package/dist/chat/sdk-chat-control-plane.d.ts +3 -0
  7. package/dist/chat/sdk-chat-control-plane.js +15 -0
  8. package/dist/plugin-sdk.js +136 -20
  9. package/dist/plugin-sdk.js.map +3 -3
  10. package/dist/providers/claude/adapter.d.ts +1 -0
  11. package/dist/providers/claude/adapter.js +31 -3
  12. package/dist/providers/claude/background-run-observer.d.ts +32 -0
  13. package/dist/providers/claude/background-run-observer.js +381 -0
  14. package/dist/providers/claude/cli-client.d.ts +12 -0
  15. package/dist/providers/claude/cli-client.js +449 -69
  16. package/dist/providers/claude/foreground-handoff.d.ts +4 -0
  17. package/dist/providers/claude/foreground-handoff.js +45 -0
  18. package/dist/providers/claude/task-cancellation-protocol.d.ts +14 -0
  19. package/dist/providers/claude/task-cancellation-protocol.js +21 -0
  20. package/dist/providers/copilot/activity-metadata.d.ts +3 -0
  21. package/dist/providers/copilot/activity-metadata.js +19 -0
  22. package/dist/providers/copilot/cli-client.js +5 -0
  23. package/dist/providers/provider-adapter.d.ts +8 -0
  24. package/dist/providers/provider-adapter.js +4 -0
  25. package/dist/types.d.ts +8 -0
  26. package/dist/vendor/claude-agent-acp/LICENSE +191 -0
  27. package/dist/vendor/claude-agent-acp/NOTICE +8 -0
  28. package/dist/vendor/claude-agent-acp/dist/acp-agent.d.ts +1017 -0
  29. package/dist/vendor/claude-agent-acp/dist/acp-agent.d.ts.map +1 -0
  30. package/dist/vendor/claude-agent-acp/dist/acp-agent.js +6305 -0
  31. package/dist/vendor/claude-agent-acp/dist/borgee-foreground-handoff-bridge.js +322 -0
  32. package/dist/vendor/claude-agent-acp/dist/borgee-task-cancellation-bridge.js +101 -0
  33. package/dist/vendor/claude-agent-acp/dist/borgee-task-lifecycle-bridge.js +58 -0
  34. package/dist/vendor/claude-agent-acp/dist/elicitation.d.ts +130 -0
  35. package/dist/vendor/claude-agent-acp/dist/elicitation.d.ts.map +1 -0
  36. package/dist/vendor/claude-agent-acp/dist/elicitation.js +304 -0
  37. package/dist/vendor/claude-agent-acp/dist/index.d.ts +3 -0
  38. package/dist/vendor/claude-agent-acp/dist/index.d.ts.map +1 -0
  39. package/dist/vendor/claude-agent-acp/dist/index.js +75 -0
  40. package/dist/vendor/claude-agent-acp/dist/lib.d.ts +6 -0
  41. package/dist/vendor/claude-agent-acp/dist/lib.d.ts.map +1 -0
  42. package/dist/vendor/claude-agent-acp/dist/lib.js +5 -0
  43. package/dist/vendor/claude-agent-acp/dist/settings.d.ts +68 -0
  44. package/dist/vendor/claude-agent-acp/dist/settings.d.ts.map +1 -0
  45. package/dist/vendor/claude-agent-acp/dist/settings.js +185 -0
  46. package/dist/vendor/claude-agent-acp/dist/tools.d.ts +102 -0
  47. package/dist/vendor/claude-agent-acp/dist/tools.d.ts.map +1 -0
  48. package/dist/vendor/claude-agent-acp/dist/tools.js +1000 -0
  49. package/dist/vendor/claude-agent-acp/dist/utils.d.ts +16 -0
  50. package/dist/vendor/claude-agent-acp/dist/utils.d.ts.map +1 -0
  51. package/dist/vendor/claude-agent-acp/dist/utils.js +81 -0
  52. package/dist/vendor/claude-agent-acp/package.json +85 -0
  53. package/package.json +14 -10
@@ -0,0 +1,322 @@
1
+ export const CLAUDE_FOREGROUND_HANDOFF_METHOD = '_borgee/session/foreground_handoff';
2
+ export const CLAUDE_FOREGROUND_LIFECYCLE_EVENT = 'settled_with_background';
3
+
4
+ const AUTONOMOUS_ORIGIN_KINDS = new Set([
5
+ 'task-notification',
6
+ 'peer',
7
+ 'coordinator',
8
+ 'observer',
9
+ 'observer-activity',
10
+ ]);
11
+
12
+ function isPositiveSafeInteger(value) {
13
+ return Number.isSafeInteger(value) && value > 0;
14
+ }
15
+
16
+ function activeForegroundTurn(session) {
17
+ const owner = session?.activeTurn?.borgeeForegroundTurn;
18
+ return isPositiveSafeInteger(owner) ? owner : undefined;
19
+ }
20
+
21
+ function ensureOwnershipMaps(session) {
22
+ session.borgeeForegroundTurns ??= new Map();
23
+ session.borgeeTaskOwners ??= new Map();
24
+ session.borgeeToolOwners ??= new Map();
25
+ }
26
+
27
+ function ownerForPromptUuid(session, uuid) {
28
+ if (typeof uuid !== 'string') {
29
+ return undefined;
30
+ }
31
+ return session.borgeeForegroundTurns?.get(uuid);
32
+ }
33
+
34
+ function ownerForTool(session, toolUseId) {
35
+ if (typeof toolUseId !== 'string') {
36
+ return undefined;
37
+ }
38
+ return session.borgeeToolOwners?.get(toolUseId);
39
+ }
40
+
41
+ function ownerForTask(session, taskId) {
42
+ if (typeof taskId !== 'string') {
43
+ return undefined;
44
+ }
45
+ return session.borgeeTaskOwners?.get(taskId);
46
+ }
47
+
48
+ export function registerClaudeForegroundTurn(session, turn) {
49
+ ensureOwnershipMaps(session);
50
+ const next = isPositiveSafeInteger(session.borgeeNextForegroundTurn)
51
+ ? session.borgeeNextForegroundTurn + 1
52
+ : 1;
53
+ session.borgeeNextForegroundTurn = next;
54
+ turn.borgeeForegroundTurn = next;
55
+ session.borgeeForegroundTurns.set(turn.promptUuid, next);
56
+ return next;
57
+ }
58
+
59
+ export function parseClaudeForegroundHandoffRequest(value) {
60
+ if (
61
+ typeof value !== 'object'
62
+ || value === null
63
+ || Array.isArray(value)
64
+ || typeof value.sessionId !== 'string'
65
+ || value.sessionId.length === 0
66
+ ) {
67
+ throw new Error('foreground handoff requires a non-empty sessionId');
68
+ }
69
+ return { sessionId: value.sessionId };
70
+ }
71
+
72
+ export function admitClaudeForegroundHandoff(session) {
73
+ if (!session || session.queryClosed) {
74
+ return { outcome: 'not_found' };
75
+ }
76
+ const turn = session.activeTurn
77
+ ?? session.turnQueue?.find((candidate) => !candidate.settled);
78
+ if (!turn || turn.settled) {
79
+ return { outcome: 'not_found' };
80
+ }
81
+ const foregroundTurn = isPositiveSafeInteger(turn.borgeeForegroundTurn)
82
+ ? turn.borgeeForegroundTurn
83
+ : registerClaudeForegroundTurn(session, turn);
84
+ turn.borgeeForegroundHandoffRequested = true;
85
+ const released = turn.borgeeReleaseDeferredForeground?.() === true;
86
+ return {
87
+ outcome: released ? 'released' : 'admitted',
88
+ foregroundTurn,
89
+ };
90
+ }
91
+
92
+ export function shouldDeferClaudeForegroundTurn(turn) {
93
+ return turn?.borgeeForegroundHandoffRequested !== true;
94
+ }
95
+
96
+ export function bindClaudeDeferredForegroundRelease(turn, release) {
97
+ turn.borgeeReleaseDeferredForeground = release;
98
+ }
99
+
100
+ export function emitClaudeForegroundLifecycle(client, sessionId, turn, event = CLAUDE_FOREGROUND_LIFECYCLE_EVENT) {
101
+ const foregroundTurn = isPositiveSafeInteger(turn?.borgeeForegroundTurn)
102
+ ? turn.borgeeForegroundTurn
103
+ : undefined;
104
+ return client.sessionUpdate({
105
+ sessionId,
106
+ update: {
107
+ sessionUpdate: 'session_info_update',
108
+ _meta: {
109
+ claudeCode: {
110
+ foregroundLifecycle: {
111
+ event,
112
+ ...(foregroundTurn ? { foregroundTurn } : {}),
113
+ },
114
+ },
115
+ },
116
+ },
117
+ });
118
+ }
119
+
120
+ export function noteClaudeUpdateOwnership(session, message) {
121
+ ensureOwnershipMaps(session);
122
+ const messageUuids = [
123
+ ...(message?.type === 'user' && typeof message.uuid === 'string'
124
+ ? [message.uuid]
125
+ : []),
126
+ ...(typeof message?.user_message_uuid === 'string'
127
+ ? [message.user_message_uuid]
128
+ : []),
129
+ ...(Array.isArray(message?.user_message_uuids)
130
+ ? message.user_message_uuids.filter((value) => typeof value === 'string')
131
+ : []),
132
+ ];
133
+ let owner = messageUuids
134
+ .map((uuid) => ownerForPromptUuid(session, uuid))
135
+ .find(isPositiveSafeInteger);
136
+ owner ??= ownerForTool(session, message?.parent_tool_use_id);
137
+
138
+ const subtype = message?.type === 'system' ? message.subtype : undefined;
139
+ if (subtype === 'task_started') {
140
+ owner ??= ownerForTool(session, message.tool_use_id);
141
+ owner ??= activeForegroundTurn(session);
142
+ owner ??= session.borgeeUpdateOwner;
143
+ if (isPositiveSafeInteger(owner)) {
144
+ session.borgeeTaskOwners.set(message.task_id, owner);
145
+ if (typeof message.tool_use_id === 'string') {
146
+ session.borgeeToolOwners.set(message.tool_use_id, owner);
147
+ }
148
+ }
149
+ } else if (subtype === 'task_notification' || subtype === 'task_updated') {
150
+ owner ??= ownerForTask(session, message.task_id);
151
+ if (subtype === 'task_notification' && isPositiveSafeInteger(owner)) {
152
+ session.borgeeAutonomousOwner = owner;
153
+ }
154
+ }
155
+
156
+ const originKind = message?.origin?.kind;
157
+ owner ??= ownerForTask(session, message?.origin?.senderTaskId);
158
+ if (originKind === 'task-notification') {
159
+ owner ??= session.borgeeAutonomousOwner;
160
+ }
161
+ if (messageUuids.length > 0 || AUTONOMOUS_ORIGIN_KINDS.has(originKind)) {
162
+ owner ??= session.borgeeAutonomousOwner;
163
+ owner ??= session.borgeeUpdateOwner;
164
+ owner ??= activeForegroundTurn(session);
165
+ } else {
166
+ owner ??= session.borgeeUpdateOwner;
167
+ owner ??= activeForegroundTurn(session);
168
+ }
169
+ if (isPositiveSafeInteger(owner)) {
170
+ session.borgeeUpdateOwner = owner;
171
+ }
172
+
173
+ const activeOwner = activeForegroundTurn(session);
174
+ if (messageUuids.length > 0) {
175
+ session.borgeeForegroundLane = owner === activeOwner ? 'foreground' : 'background';
176
+ if (owner === activeOwner) {
177
+ session.borgeeAutonomousOwner = undefined;
178
+ }
179
+ return;
180
+ }
181
+ if (typeof originKind === 'string' && AUTONOMOUS_ORIGIN_KINDS.has(originKind)) {
182
+ session.borgeeForegroundLane = 'background';
183
+ return;
184
+ }
185
+ if (
186
+ typeof message?.parent_tool_use_id === 'string'
187
+ || (isPositiveSafeInteger(owner) && isPositiveSafeInteger(activeOwner) && owner !== activeOwner)
188
+ ) {
189
+ session.borgeeForegroundLane = 'background';
190
+ return;
191
+ }
192
+ if (message?.type === 'user') {
193
+ session.borgeeForegroundLane =
194
+ originKind === undefined || originKind === 'human' || originKind === 'channel'
195
+ ? 'foreground'
196
+ : 'background';
197
+ }
198
+ }
199
+
200
+ function ownerForNotification(session, notification, ownerHint) {
201
+ if (isPositiveSafeInteger(ownerHint)) {
202
+ return ownerHint;
203
+ }
204
+ const update = notification?.update;
205
+ const provider = update?._meta?.claudeCode;
206
+ const explicitOwner = provider?.foregroundTurn;
207
+ if (isPositiveSafeInteger(explicitOwner)) {
208
+ return explicitOwner;
209
+ }
210
+ const taskId = provider?.taskLifecycle?.taskId;
211
+ return ownerForTool(session, update?.toolCallId)
212
+ ?? ownerForTool(session, provider?.parentToolUseId)
213
+ ?? ownerForTask(session, taskId)
214
+ ?? session?.borgeeUpdateOwner
215
+ ?? activeForegroundTurn(session);
216
+ }
217
+
218
+ export function stampClaudeUpdateOwnership(session, notification, ownerHint) {
219
+ if (!session) {
220
+ return notification;
221
+ }
222
+ ensureOwnershipMaps(session);
223
+ const owner = ownerForNotification(session, notification, ownerHint);
224
+ const lane = session.borgeeForegroundLane;
225
+ if (isPositiveSafeInteger(owner) && typeof notification?.update?.toolCallId === 'string') {
226
+ session.borgeeToolOwners.set(notification.update.toolCallId, owner);
227
+ }
228
+ if (
229
+ !isPositiveSafeInteger(owner)
230
+ && lane !== 'foreground'
231
+ && lane !== 'background'
232
+ ) {
233
+ return notification;
234
+ }
235
+ const activeOwner = activeForegroundTurn(session);
236
+ const resolvedLane =
237
+ isPositiveSafeInteger(owner)
238
+ && isPositiveSafeInteger(activeOwner)
239
+ && owner !== activeOwner
240
+ ? 'background'
241
+ : lane;
242
+ return {
243
+ ...notification,
244
+ update: {
245
+ ...notification.update,
246
+ _meta: {
247
+ ...notification.update._meta,
248
+ claudeCode: {
249
+ ...notification.update._meta?.claudeCode,
250
+ ...(resolvedLane === 'foreground' || resolvedLane === 'background'
251
+ ? { foregroundLane: resolvedLane }
252
+ : {}),
253
+ ...(isPositiveSafeInteger(owner) ? { foregroundTurn: owner } : {}),
254
+ },
255
+ },
256
+ },
257
+ };
258
+ }
259
+
260
+ export function noteClaudePermissionOwnership(session, toolUseId, agentId) {
261
+ if (!session || typeof toolUseId !== 'string') {
262
+ return;
263
+ }
264
+ ensureOwnershipMaps(session);
265
+ const owner = ownerForTool(session, toolUseId)
266
+ ?? ownerForTask(session, agentId)
267
+ ?? activeForegroundTurn(session)
268
+ ?? session.borgeeUpdateOwner;
269
+ if (isPositiveSafeInteger(owner)) {
270
+ session.borgeeToolOwners.set(toolUseId, owner);
271
+ }
272
+ }
273
+
274
+ export function stampClaudePermissionOwnership(session, params) {
275
+ if (!session || typeof params?.toolCall?.toolCallId !== 'string') {
276
+ return params;
277
+ }
278
+ ensureOwnershipMaps(session);
279
+ const provider = params.toolCall._meta?.claudeCode;
280
+ const owner = ownerForTool(session, params.toolCall.toolCallId)
281
+ ?? ownerForTool(session, provider?.parentToolUseId)
282
+ ?? activeForegroundTurn(session)
283
+ ?? session.borgeeUpdateOwner;
284
+ if (!isPositiveSafeInteger(owner)) {
285
+ return params;
286
+ }
287
+ return {
288
+ ...params,
289
+ toolCall: {
290
+ ...params.toolCall,
291
+ _meta: {
292
+ ...params.toolCall._meta,
293
+ claudeCode: {
294
+ ...provider,
295
+ foregroundTurn: owner,
296
+ },
297
+ },
298
+ },
299
+ };
300
+ }
301
+
302
+ export function sendClaudeHookUpdate(client, session, hookInput, notification) {
303
+ const owner = ownerForTask(session, hookInput?.agent_id)
304
+ ?? ownerForPromptUuid(session, hookInput?.prompt_id)
305
+ ?? session?.borgeeUpdateOwner
306
+ ?? activeForegroundTurn(session);
307
+ return client.sessionUpdate(stampClaudeUpdateOwnership(session, notification, owner));
308
+ }
309
+
310
+ const wrappedClients = new WeakSet();
311
+
312
+ export function bindClaudeSessionUpdateOwnership(client, resolveSession) {
313
+ if (wrappedClients.has(client)) {
314
+ return client;
315
+ }
316
+ const sessionUpdate = client.sessionUpdate.bind(client);
317
+ client.sessionUpdate = (notification) => sessionUpdate(
318
+ stampClaudeUpdateOwnership(resolveSession(notification?.sessionId), notification),
319
+ );
320
+ wrappedClients.add(client);
321
+ return client;
322
+ }
@@ -0,0 +1,101 @@
1
+ export const CLAUDE_TASK_CANCELLATION_METHOD = '_borgee/claude/task/cancel/v1';
2
+ export const CLAUDE_TASK_CANCELLATION_VERSION = 1;
3
+ export const CLAUDE_TASK_CANCELLATION_CAPABILITY = Object.freeze({
4
+ method: CLAUDE_TASK_CANCELLATION_METHOD,
5
+ version: CLAUDE_TASK_CANCELLATION_VERSION,
6
+ });
7
+
8
+ const taskStates = new WeakMap();
9
+
10
+ function stateFor(session) {
11
+ let state = taskStates.get(session);
12
+ if (!state) {
13
+ state = {
14
+ backgroundTaskIds: new Set(),
15
+ stopRequests: new Map(),
16
+ };
17
+ taskStates.set(session, state);
18
+ }
19
+ return state;
20
+ }
21
+
22
+ function removeTask(state, taskId) {
23
+ state.backgroundTaskIds.delete(taskId);
24
+ state.stopRequests.delete(taskId);
25
+ }
26
+
27
+ export function trackClaudeTaskLifecycle(session, message) {
28
+ const state = stateFor(session);
29
+ switch (message.subtype) {
30
+ case 'background_tasks_changed': {
31
+ const taskIds = new Set(message.tasks.map((task) => task.task_id));
32
+ state.backgroundTaskIds = taskIds;
33
+ for (const taskId of state.stopRequests.keys()) {
34
+ if (!taskIds.has(taskId)) {
35
+ state.stopRequests.delete(taskId);
36
+ }
37
+ }
38
+ break;
39
+ }
40
+ case 'task_updated':
41
+ if (
42
+ message.patch.status === 'completed'
43
+ || message.patch.status === 'failed'
44
+ || message.patch.status === 'killed'
45
+ || message.patch.is_backgrounded === false
46
+ ) {
47
+ removeTask(state, message.task_id);
48
+ } else if (message.patch.is_backgrounded === true) {
49
+ state.backgroundTaskIds.add(message.task_id);
50
+ }
51
+ break;
52
+ case 'task_notification':
53
+ removeTask(state, message.task_id);
54
+ break;
55
+ }
56
+ }
57
+
58
+ export function parseClaudeTaskCancellationRequest(params) {
59
+ if (!params || typeof params !== 'object') {
60
+ throw new TypeError('Claude task cancellation params must be an object');
61
+ }
62
+ const { sessionId, taskId } = params;
63
+ if (typeof sessionId !== 'string' || sessionId.length === 0) {
64
+ throw new TypeError('Claude task cancellation requires a non-empty sessionId');
65
+ }
66
+ if (typeof taskId !== 'string' || taskId.length === 0) {
67
+ throw new TypeError('Claude task cancellation requires a non-empty taskId');
68
+ }
69
+ return { sessionId, taskId };
70
+ }
71
+
72
+ export async function stopClaudeBackgroundTask(session, taskId) {
73
+ if (!session || session.queryClosed) {
74
+ return { outcome: 'not_found' };
75
+ }
76
+ const state = stateFor(session);
77
+ if (!state.backgroundTaskIds.has(taskId)) {
78
+ return { outcome: 'not_found' };
79
+ }
80
+ if (typeof session.query?.stopTask !== 'function') {
81
+ return { outcome: 'unsupported' };
82
+ }
83
+
84
+ const existing = state.stopRequests.get(taskId);
85
+ if (existing) {
86
+ return existing;
87
+ }
88
+ const request = (async () => {
89
+ await session.query.stopTask(taskId);
90
+ return { outcome: 'accepted' };
91
+ })();
92
+ state.stopRequests.set(taskId, request);
93
+ try {
94
+ return await request;
95
+ } catch (error) {
96
+ if (state.stopRequests.get(taskId) === request) {
97
+ state.stopRequests.delete(taskId);
98
+ }
99
+ throw error;
100
+ }
101
+ }
@@ -0,0 +1,58 @@
1
+ function lifecycleForMessage(message) {
2
+ switch (message.subtype) {
3
+ case 'task_started':
4
+ return {
5
+ event: 'started',
6
+ taskId: message.task_id,
7
+ ...(message.tool_use_id ? { toolUseId: message.tool_use_id } : {}),
8
+ description: message.description,
9
+ ...(message.subagent_type ? { subagentType: message.subagent_type } : {}),
10
+ ...(message.task_type ? { taskType: message.task_type } : {}),
11
+ };
12
+ case 'task_notification':
13
+ return {
14
+ event: 'notification',
15
+ taskId: message.task_id,
16
+ ...(message.tool_use_id ? { toolUseId: message.tool_use_id } : {}),
17
+ status: message.status,
18
+ summary: message.summary,
19
+ ...(message.usage ? { usage: message.usage } : {}),
20
+ };
21
+ case 'task_updated':
22
+ return {
23
+ event: 'updated',
24
+ taskId: message.task_id,
25
+ patch: message.patch,
26
+ };
27
+ case 'background_tasks_changed':
28
+ return {
29
+ event: 'background_tasks_changed',
30
+ tasks: message.tasks.map((task) => ({
31
+ taskId: task.task_id,
32
+ taskType: task.task_type,
33
+ description: task.description,
34
+ })),
35
+ };
36
+ default:
37
+ return undefined;
38
+ }
39
+ }
40
+
41
+ export async function emitClaudeTaskLifecycle(sendUpdate, sessionId, message) {
42
+ const taskLifecycle = lifecycleForMessage(message);
43
+ if (!taskLifecycle) {
44
+ return false;
45
+ }
46
+ await sendUpdate({
47
+ sessionId,
48
+ update: {
49
+ sessionUpdate: 'session_info_update',
50
+ _meta: {
51
+ claudeCode: {
52
+ taskLifecycle,
53
+ },
54
+ },
55
+ },
56
+ });
57
+ return true;
58
+ }
@@ -0,0 +1,130 @@
1
+ import { CreateElicitationResponse } from "@agentclientprotocol/sdk";
2
+ import type { CreateElicitationRequest } from "@agentclientprotocol/sdk";
3
+ import type { ElicitationRequest, ElicitationResult } from "@anthropic-ai/claude-agent-sdk";
4
+ import type { AskUserQuestionInput } from "@anthropic-ai/claude-agent-sdk/sdk-tools.js";
5
+ /**
6
+ * Bridges between the Claude Agent SDK's elicitation/dialog callbacks and ACP's
7
+ * (unstable) elicitation protocol.
8
+ *
9
+ * Two distinct SDK surfaces flow through here:
10
+ *
11
+ * 1. `onElicitation` — fired when an MCP server requests user input. These map
12
+ * directly onto ACP `session/create_elicitation` (form or url mode).
13
+ * 2. The built-in AskUserQuestion tool — when a `canUseTool` callback is
14
+ * registered the SDK routes its permission check through `canUseTool`
15
+ * (not the interactive `permission_ask_user_question` dialog). We render
16
+ * its questions as an ACP form elicitation and feed the user's selections
17
+ * back as the tool's `updatedInput`, which the tool's own `call()` reads.
18
+ */
19
+ /** Modes the connected client advertised support for. */
20
+ export type ElicitationSupport = {
21
+ form: boolean;
22
+ url: boolean;
23
+ };
24
+ /**
25
+ * Convert an MCP elicitation request (from the SDK's `onElicitation` callback)
26
+ * into an ACP `CreateElicitationRequest`. Returns `null` when the request can't
27
+ * be represented (e.g. a url-mode request with no url).
28
+ */
29
+ export declare function mcpElicitationToCreateRequest(request: ElicitationRequest, sessionId: string): CreateElicitationRequest | null;
30
+ /**
31
+ * Map an ACP elicitation response back to the MCP `ElicitResult` the SDK expects
32
+ * to hand back to the requesting server.
33
+ */
34
+ export declare function createElicitationResponseToElicitResult(response: CreateElicitationResponse): ElicitationResult;
35
+ /**
36
+ * A single question as supplied by the AskUserQuestion tool. Derived from the
37
+ * SDK's input type so the shape stays in sync; the SDK validates the model's
38
+ * tool call against this schema before it reaches us.
39
+ */
40
+ export type AskUserQuestion = AskUserQuestionInput["questions"][number];
41
+ /**
42
+ * Pull the well-formed questions out of an AskUserQuestion tool input. Returns
43
+ * `null` when there are no usable questions — including the case where every
44
+ * entry is malformed and filtering leaves an empty list — so callers can treat
45
+ * "nothing to ask" uniformly.
46
+ */
47
+ export declare function extractAskUserQuestions(input: Record<string, unknown>): AskUserQuestion[] | null;
48
+ /**
49
+ * Render the AskUserQuestion tool's questions as an ACP form elicitation.
50
+ *
51
+ * Fields are keyed by a short stable id (`question_<n>`) rather than the full
52
+ * question text, so the question text appears in exactly one place per field.
53
+ * Single-select questions use a titled `oneOf` enum; multi-select questions use
54
+ * an array with a titled `anyOf` item enum. The enum `const` is always the
55
+ * option label, since that is what the tool records as the answer; an option's
56
+ * secondary text travels in the enum option's own `description` field.
57
+ *
58
+ * Each question is followed by its own optional free-text "custom answer" field
59
+ * (`question_<n>_custom`), mirroring the CLI's per-question "Other" box: the
60
+ * user can type their own answer instead of picking an option, scoped to that
61
+ * specific question. Nothing is marked required, so the user can also just skip
62
+ * — matching the built-in tool, which always offers Skip + a free-text box.
63
+ */
64
+ export declare function askUserQuestionsToCreateRequest(questions: AskUserQuestion[], sessionId: string, toolCallId: string | undefined): CreateElicitationRequest;
65
+ /** Outcome of an AskUserQuestion elicitation, decoupled from any transport. */
66
+ export type AskUserQuestionOutcome = {
67
+ action: "answered";
68
+ updatedInput: Record<string, unknown>;
69
+ } | {
70
+ action: "cancel";
71
+ };
72
+ /**
73
+ * Fold an ACP elicitation response into the AskUserQuestion tool's input.
74
+ *
75
+ * Selected labels are read back from the indexed form fields and written into
76
+ * `answers` as a `{ [questionText]: label }` map (comma-joining multi-selects)
77
+ * — the key shape the tool's own `call()` reads. A non-empty per-question
78
+ * custom-answer field (`question_<n>_custom`) takes precedence over that
79
+ * question's selection, since the user typed their own answer instead of
80
+ * picking one. Decline yields empty answers (the model is told the user skipped
81
+ * rather than the turn aborting); cancel — and any custom/future action we
82
+ * don't understand — aborts the tool call.
83
+ */
84
+ export declare function applyAskElicitationResponse(response: CreateElicitationResponse, toolInput: Record<string, unknown>, questions: AskUserQuestion[]): AskUserQuestionOutcome;
85
+ /**
86
+ * The `request_user_dialog` kind the CLI emits when a model refusal has a
87
+ * fallback available but needs user consent before retrying (e.g. Claude Fable
88
+ * declining a request with Opus available as the fallback). Declaring this
89
+ * kind in `supportedDialogKinds` is the opt-in: the CLI fails closed and never
90
+ * emits an undeclared kind — the flow degrades to the classic refusal error
91
+ * ending the turn.
92
+ */
93
+ export declare const REFUSAL_FALLBACK_DIALOG_KIND = "refusal_fallback_prompt";
94
+ /**
95
+ * Payload of the `refusal_fallback_prompt` dialog. The dialog protocol
96
+ * transports payloads opaquely, so this shape is recovered from the CLI's own
97
+ * schema (v2.1.177): `originalModel`/`fallbackModel` are required strings;
98
+ * `apiRefusalCategory` (nullable), `guidanceText`, and
99
+ * `retractedMessageUuids` are optional. We ignore `retractedMessageUuids` —
100
+ * ACP has no way to retract already-streamed chunks.
101
+ */
102
+ export type RefusalFallbackPrompt = {
103
+ originalModel: string;
104
+ fallbackModel: string;
105
+ apiRefusalCategory: string | null;
106
+ guidanceText?: string;
107
+ };
108
+ /**
109
+ * Validate the opaque dialog payload into a {@link RefusalFallbackPrompt}.
110
+ * Returns `null` when the required fields are missing or mistyped (a newer CLI
111
+ * may reshape the payload), so the caller can cancel the dialog and let the
112
+ * CLI apply its default behavior instead of rendering something misleading.
113
+ */
114
+ export declare function extractRefusalFallbackPrompt(payload: Record<string, unknown>): RefusalFallbackPrompt | null;
115
+ /**
116
+ * Render the refusal-fallback consent prompt as an ACP form elicitation: a
117
+ * single-select between retrying on the fallback model and keeping the
118
+ * refusal. The enum `const`s are the dialog's wire result values, so the
119
+ * response maps back without a translation table.
120
+ */
121
+ export declare function refusalFallbackToCreateRequest(prompt: RefusalFallbackPrompt, sessionId: string): CreateElicitationRequest;
122
+ /**
123
+ * Map the elicitation response back to the dialog's result enum. Only an
124
+ * explicit accept-with-retry resolves to `retry_fallback`; decline, cancel, a
125
+ * skipped field, or an unrecognized value all keep the refusal — the dialog's
126
+ * own default — so a dismissed or half-filled form can never trigger a model
127
+ * switch the user didn't ask for.
128
+ */
129
+ export declare function refusalFallbackResultFromResponse(response: CreateElicitationResponse): string;
130
+ //# sourceMappingURL=elicitation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elicitation.d.ts","sourceRoot":"","sources":["../src/elicitation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,EACV,wBAAwB,EAKzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAC5F,OAAO,KAAK,EACV,oBAAoB,EAErB,MAAM,6CAA6C,CAAC;AAErD;;;;;;;;;;;;;GAaG;AAEH,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,MAAM,GAChB,wBAAwB,GAAG,IAAI,CAyBjC;AAgBD;;;GAGG;AACH,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,yBAAyB,GAClC,iBAAiB,CAUnB;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,EAAE,GAAG,IAAI,CAUhG;AAgCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,+BAA+B,CAC7C,SAAS,EAAE,eAAe,EAAE,EAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,GAC7B,wBAAwB,CA2D1B;AAED,+EAA+E;AAC/E,MAAM,MAAM,sBAAsB,GAChC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEvF;;;;;;;;;;;GAWG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,yBAAyB,EACnC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,SAAS,EAAE,eAAe,EAAE,GAC3B,sBAAsB,CAkCxB;AAgBD;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,4BAA4B,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,qBAAqB,GAAG,IAAI,CAW9B;AAYD;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,qBAAqB,EAC7B,SAAS,EAAE,MAAM,GAChB,wBAAwB,CA+B1B;AAED;;;;;;GAMG;AACH,wBAAgB,iCAAiC,CAAC,QAAQ,EAAE,yBAAyB,GAAG,MAAM,CAG7F"}