@ycodium-ai/agent-acp 0.2.1991 → 0.2.2014
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.
- package/dist/index.js +217 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -24996,6 +24996,11 @@ var defaultVendorHooks = {
|
|
|
24996
24996
|
|
|
24997
24997
|
// ../../packages/ycodium-acp-executor/src/executor.ts
|
|
24998
24998
|
var DEFAULT_CANCEL_GRACE_MS = 5e3;
|
|
24999
|
+
var userInputAnswerOptionId = (question, answers) => {
|
|
25000
|
+
const answer = answers[question.id];
|
|
25001
|
+
const selected = typeof answer === "string" ? answer : Array.isArray(answer) && answer.length === 1 && typeof answer[0] === "string" ? answer[0] : void 0;
|
|
25002
|
+
return selected !== void 0 && question.options.some((option) => option.id === selected) ? selected : void 0;
|
|
25003
|
+
};
|
|
24999
25004
|
function defaultNowIso() {
|
|
25000
25005
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
25001
25006
|
}
|
|
@@ -25046,6 +25051,14 @@ function createAcpExecutor(options) {
|
|
|
25046
25051
|
singleResolver: false
|
|
25047
25052
|
});
|
|
25048
25053
|
const pendingUserInputs = /* @__PURE__ */ new Map();
|
|
25054
|
+
let pendingSettlement;
|
|
25055
|
+
const settledPromptKeys = [];
|
|
25056
|
+
const SETTLED_PROMPT_KEY_LIMIT = 32;
|
|
25057
|
+
const rememberSettledPromptKey = (key) => {
|
|
25058
|
+
if (settledPromptKeys.includes(key)) return;
|
|
25059
|
+
settledPromptKeys.push(key);
|
|
25060
|
+
if (settledPromptKeys.length > SETTLED_PROMPT_KEY_LIMIT) settledPromptKeys.shift();
|
|
25061
|
+
};
|
|
25049
25062
|
const toolCalls = /* @__PURE__ */ new Map();
|
|
25050
25063
|
let assistantSegment = { nextSegmentIndex: 0 };
|
|
25051
25064
|
let modeState;
|
|
@@ -25221,10 +25234,118 @@ function createAcpExecutor(options) {
|
|
|
25221
25234
|
emitResolved(entry, decision);
|
|
25222
25235
|
}
|
|
25223
25236
|
};
|
|
25237
|
+
const emitUserInputResolved = (pending, answers) => {
|
|
25238
|
+
emit({
|
|
25239
|
+
type: "user-input.resolved",
|
|
25240
|
+
...stamp(),
|
|
25241
|
+
provider: options.provider,
|
|
25242
|
+
threadId: options.threadId,
|
|
25243
|
+
...pending.turnId !== void 0 ? { turnId: pending.turnId } : {},
|
|
25244
|
+
requestId: pending.requestId,
|
|
25245
|
+
payload: { answers }
|
|
25246
|
+
});
|
|
25247
|
+
};
|
|
25248
|
+
const abortPendingSettlement = (reason) => {
|
|
25249
|
+
const waiting = pendingSettlement;
|
|
25250
|
+
if (waiting === void 0) return;
|
|
25251
|
+
pendingSettlement = void 0;
|
|
25252
|
+
waiting.settle({ error: new AcpExecutorRequestError("session/prompt", reason) });
|
|
25253
|
+
};
|
|
25254
|
+
const settleAllUserInputs = () => {
|
|
25255
|
+
for (const pending of pendingUserInputs.values()) {
|
|
25256
|
+
if (pending.settled) continue;
|
|
25257
|
+
pending.settled = true;
|
|
25258
|
+
pending.resolve({ kind: "cancelled" });
|
|
25259
|
+
}
|
|
25260
|
+
pendingUserInputs.clear();
|
|
25261
|
+
};
|
|
25262
|
+
const settleFromNotification = (method, params) => {
|
|
25263
|
+
const hook = hooks.promptSettlement;
|
|
25264
|
+
if (hook === void 0 || pendingSettlement === void 0) return false;
|
|
25265
|
+
const signal = hook.fromNotification(method, params);
|
|
25266
|
+
if (signal === void 0) return false;
|
|
25267
|
+
const waiting = pendingSettlement;
|
|
25268
|
+
if (signal.sessionId !== waiting.sessionId) return false;
|
|
25269
|
+
if (signal.promptKey !== void 0 && signal.promptKey !== waiting.promptKey) {
|
|
25270
|
+
return settledPromptKeys.includes(signal.promptKey);
|
|
25271
|
+
}
|
|
25272
|
+
pendingSettlement = void 0;
|
|
25273
|
+
rememberSettledPromptKey(waiting.promptKey);
|
|
25274
|
+
if (signal.outcome === "error") {
|
|
25275
|
+
waiting.settle({
|
|
25276
|
+
error: new AcpExecutorRequestError(
|
|
25277
|
+
"session/prompt",
|
|
25278
|
+
signal.detail ?? "prompt failed without an error detail"
|
|
25279
|
+
)
|
|
25280
|
+
});
|
|
25281
|
+
} else {
|
|
25282
|
+
waiting.settle({
|
|
25283
|
+
response: { stopReason: signal.stopReason ?? "end_turn" }
|
|
25284
|
+
});
|
|
25285
|
+
}
|
|
25286
|
+
return true;
|
|
25287
|
+
};
|
|
25288
|
+
const askUserInput = async (input2) => {
|
|
25289
|
+
failIfDisposed();
|
|
25290
|
+
const requestId = randomId();
|
|
25291
|
+
const { promise: settlement, resolve: settle2 } = Promise.withResolvers();
|
|
25292
|
+
const pending = {
|
|
25293
|
+
requestId,
|
|
25294
|
+
questions: input2.questions,
|
|
25295
|
+
turnId: activeTurnId,
|
|
25296
|
+
settled: false,
|
|
25297
|
+
resolve: settle2
|
|
25298
|
+
};
|
|
25299
|
+
pendingUserInputs.set(requestId, pending);
|
|
25300
|
+
emit({
|
|
25301
|
+
type: "user-input.requested",
|
|
25302
|
+
...stamp(),
|
|
25303
|
+
provider: options.provider,
|
|
25304
|
+
threadId: options.threadId,
|
|
25305
|
+
...activeTurnId !== void 0 ? { turnId: activeTurnId } : {},
|
|
25306
|
+
requestId,
|
|
25307
|
+
payload: { questions: input2.questions },
|
|
25308
|
+
raw: {
|
|
25309
|
+
source: "acp.jsonrpc",
|
|
25310
|
+
method: input2.method,
|
|
25311
|
+
payload: input2.payload
|
|
25312
|
+
}
|
|
25313
|
+
});
|
|
25314
|
+
const resolution = await settlement;
|
|
25315
|
+
pendingUserInputs.delete(requestId);
|
|
25316
|
+
emitUserInputResolved(pending, resolution.kind === "answered" ? resolution.answers : {});
|
|
25317
|
+
return resolution;
|
|
25318
|
+
};
|
|
25319
|
+
const makeExtensionContext = (method, params) => ({
|
|
25320
|
+
askUserInput: (questions) => askUserInput({ questions, method, payload: params })
|
|
25321
|
+
});
|
|
25322
|
+
const handlePermissionAsUserInput = async (params) => {
|
|
25323
|
+
if (hooks.permissionUserInput === void 0) return void 0;
|
|
25324
|
+
let question;
|
|
25325
|
+
try {
|
|
25326
|
+
question = hooks.permissionUserInput(params);
|
|
25327
|
+
} catch (error62) {
|
|
25328
|
+
throw RequestError.invalidParams({
|
|
25329
|
+
detail: error62 instanceof Error ? error62.message : String(error62)
|
|
25330
|
+
});
|
|
25331
|
+
}
|
|
25332
|
+
if (question === void 0) return void 0;
|
|
25333
|
+
const resolution = await askUserInput({
|
|
25334
|
+
questions: [question],
|
|
25335
|
+
method: CLIENT_METHODS.session_request_permission,
|
|
25336
|
+
payload: params
|
|
25337
|
+
});
|
|
25338
|
+
const optionId = resolution.kind === "answered" ? userInputAnswerOptionId(question, resolution.answers) : void 0;
|
|
25339
|
+
return {
|
|
25340
|
+
outcome: optionId !== void 0 ? { outcome: "selected", optionId } : { outcome: "cancelled" }
|
|
25341
|
+
};
|
|
25342
|
+
};
|
|
25224
25343
|
const handlePermissionRequest = async (params) => {
|
|
25225
25344
|
if (!isAcpRequestPermissionParams(params)) {
|
|
25226
25345
|
throw RequestError.invalidParams({ detail: "Malformed session/request_permission" });
|
|
25227
25346
|
}
|
|
25347
|
+
const userInputOutcome = await handlePermissionAsUserInput(params);
|
|
25348
|
+
if (userInputOutcome !== void 0) return userInputOutcome;
|
|
25228
25349
|
const permissionRequest = parsePermissionRequest(params);
|
|
25229
25350
|
const requestId = randomId();
|
|
25230
25351
|
const outcome = await new Promise((resolve) => {
|
|
@@ -25265,6 +25386,7 @@ function createAcpExecutor(options) {
|
|
|
25265
25386
|
handleSessionUpdate(notification.params);
|
|
25266
25387
|
return;
|
|
25267
25388
|
}
|
|
25389
|
+
if (settleFromNotification(notification.method, notification.params)) return;
|
|
25268
25390
|
void hooks.onExtensionNotification?.(notification.method, notification.params);
|
|
25269
25391
|
},
|
|
25270
25392
|
onRequest: async (method, params) => {
|
|
@@ -25272,7 +25394,12 @@ function createAcpExecutor(options) {
|
|
|
25272
25394
|
return handlePermissionRequest(params);
|
|
25273
25395
|
}
|
|
25274
25396
|
if (hooks.onExtensionRequest) {
|
|
25275
|
-
|
|
25397
|
+
const handled = await hooks.onExtensionRequest(
|
|
25398
|
+
method,
|
|
25399
|
+
params,
|
|
25400
|
+
makeExtensionContext(method, params)
|
|
25401
|
+
);
|
|
25402
|
+
if (handled !== void 0) return handled;
|
|
25276
25403
|
}
|
|
25277
25404
|
throw RequestError.methodNotFound(method);
|
|
25278
25405
|
}
|
|
@@ -25284,12 +25411,24 @@ function createAcpExecutor(options) {
|
|
|
25284
25411
|
return;
|
|
25285
25412
|
}
|
|
25286
25413
|
settleAllApprovals("cancel");
|
|
25414
|
+
settleAllUserInputs();
|
|
25415
|
+
abortPendingSettlement("connection closed");
|
|
25287
25416
|
promptWaiters.splice(0).forEach((wake) => wake());
|
|
25288
25417
|
},
|
|
25289
25418
|
() => {
|
|
25290
25419
|
connectionClosed = true;
|
|
25291
25420
|
}
|
|
25292
25421
|
);
|
|
25422
|
+
const onInputDead = () => {
|
|
25423
|
+
if (disposed) {
|
|
25424
|
+
return;
|
|
25425
|
+
}
|
|
25426
|
+
settleAllApprovals("cancel");
|
|
25427
|
+
settleAllUserInputs();
|
|
25428
|
+
promptWaiters.splice(0).forEach((wake) => wake());
|
|
25429
|
+
};
|
|
25430
|
+
options.readable.once("end", onInputDead);
|
|
25431
|
+
options.readable.once("error", onInputDead);
|
|
25293
25432
|
const applyRequestedModel = async (input2) => {
|
|
25294
25433
|
const requestedModelId = normalizeModelId(input2.requestedModelId);
|
|
25295
25434
|
if (requestedModelId === void 0 || requestedModelId === input2.state.currentModelId || input2.state.strategy === "session/set_model" && !input2.state.sessionModelsAdvertised || input2.state.strategy === "config-option" && input2.state.modelConfigId === void 0) {
|
|
@@ -25546,12 +25685,35 @@ function createAcpExecutor(options) {
|
|
|
25546
25685
|
expectedGeneration: started.generation
|
|
25547
25686
|
});
|
|
25548
25687
|
closeActiveAssistantSegment(turnId);
|
|
25688
|
+
const settlementHooks = hooks.promptSettlement;
|
|
25689
|
+
const promptKey = settlementHooks === void 0 ? void 0 : randomId();
|
|
25549
25690
|
const promptRpc = connection.request("session/prompt", {
|
|
25550
25691
|
sessionId: started.sessionId,
|
|
25551
|
-
prompt: promptParts
|
|
25692
|
+
prompt: promptParts,
|
|
25693
|
+
...settlementHooks !== void 0 && promptKey !== void 0 ? settlementHooks.requestFields(promptKey) : {}
|
|
25552
25694
|
});
|
|
25553
25695
|
try {
|
|
25554
|
-
|
|
25696
|
+
if (settlementHooks === void 0 || promptKey === void 0) {
|
|
25697
|
+
response = await promptRpc;
|
|
25698
|
+
} else {
|
|
25699
|
+
const { promise: early, resolve: settleEarly } = Promise.withResolvers();
|
|
25700
|
+
pendingSettlement = { promptKey, sessionId: started.sessionId, settle: settleEarly };
|
|
25701
|
+
try {
|
|
25702
|
+
const outcome = await Promise.race([
|
|
25703
|
+
promptRpc.then((value) => {
|
|
25704
|
+
const key = settlementHooks.responseKey?.(value);
|
|
25705
|
+
if (key !== void 0) rememberSettledPromptKey(key);
|
|
25706
|
+
return { response: value };
|
|
25707
|
+
}),
|
|
25708
|
+
early
|
|
25709
|
+
]);
|
|
25710
|
+
if (outcome.error !== void 0) throw outcome.error;
|
|
25711
|
+
response = outcome.response;
|
|
25712
|
+
} finally {
|
|
25713
|
+
pendingSettlement = void 0;
|
|
25714
|
+
void promptRpc.catch(() => void 0);
|
|
25715
|
+
}
|
|
25716
|
+
}
|
|
25555
25717
|
} catch (error62) {
|
|
25556
25718
|
if (disposed) {
|
|
25557
25719
|
throw new AcpExecutorDisposedError();
|
|
@@ -25573,6 +25735,17 @@ function createAcpExecutor(options) {
|
|
|
25573
25735
|
throw new AcpExecutorError("internal", "ACP prompt settled without a turn id");
|
|
25574
25736
|
}
|
|
25575
25737
|
const settledTurnId = turnId;
|
|
25738
|
+
let contextWindow;
|
|
25739
|
+
if (hooks.readContextWindow !== void 0) {
|
|
25740
|
+
try {
|
|
25741
|
+
contextWindow = await hooks.readContextWindow({
|
|
25742
|
+
sessionId: started.sessionId,
|
|
25743
|
+
promptResponse: response
|
|
25744
|
+
}) ?? void 0;
|
|
25745
|
+
} catch {
|
|
25746
|
+
contextWindow = void 0;
|
|
25747
|
+
}
|
|
25748
|
+
}
|
|
25576
25749
|
const finishPrompt = () => {
|
|
25577
25750
|
if (interruptedTurnIds.has(settledTurnId)) {
|
|
25578
25751
|
settleTurn({
|
|
@@ -25589,6 +25762,21 @@ function createAcpExecutor(options) {
|
|
|
25589
25762
|
};
|
|
25590
25763
|
}
|
|
25591
25764
|
const stopReason = hooks.completedStopReason?.(response) ?? response.stopReason;
|
|
25765
|
+
if (contextWindow !== void 0) {
|
|
25766
|
+
emit({
|
|
25767
|
+
type: "thread.token-usage.updated",
|
|
25768
|
+
...stamp(),
|
|
25769
|
+
provider: options.provider,
|
|
25770
|
+
threadId: options.threadId,
|
|
25771
|
+
turnId: settledTurnId,
|
|
25772
|
+
payload: {
|
|
25773
|
+
usage: {
|
|
25774
|
+
usedTokens: contextWindow.usedTokens,
|
|
25775
|
+
...contextWindow.maxTokens === void 0 ? {} : { maxTokens: contextWindow.maxTokens }
|
|
25776
|
+
}
|
|
25777
|
+
}
|
|
25778
|
+
});
|
|
25779
|
+
}
|
|
25592
25780
|
settleTurn({
|
|
25593
25781
|
expectedGeneration: started.generation,
|
|
25594
25782
|
expectedSessionId: started.sessionId,
|
|
@@ -25625,6 +25813,8 @@ function createAcpExecutor(options) {
|
|
|
25625
25813
|
interruptedTurnIds.add(targetTurnId);
|
|
25626
25814
|
}
|
|
25627
25815
|
settleAllApprovals("cancel");
|
|
25816
|
+
settleAllUserInputs();
|
|
25817
|
+
abortPendingSettlement("turn cancelled");
|
|
25628
25818
|
const runCancel = async () => {
|
|
25629
25819
|
if (sessionId) {
|
|
25630
25820
|
await connection.notify("session/cancel", { sessionId });
|
|
@@ -25687,11 +25877,32 @@ function createAcpExecutor(options) {
|
|
|
25687
25877
|
};
|
|
25688
25878
|
apply();
|
|
25689
25879
|
};
|
|
25880
|
+
const respondToUserInput = async (requestId, answers) => {
|
|
25881
|
+
failIfDisposed();
|
|
25882
|
+
const pending = pendingUserInputs.get(requestId);
|
|
25883
|
+
if (pending === void 0) {
|
|
25884
|
+
throw new AcpExecutorRequestError(
|
|
25885
|
+
"session/request_permission",
|
|
25886
|
+
`Unknown pending user-input request: ${requestId}`
|
|
25887
|
+
);
|
|
25888
|
+
}
|
|
25889
|
+
if (pending.settled) {
|
|
25890
|
+
return;
|
|
25891
|
+
}
|
|
25892
|
+
pending.settled = true;
|
|
25893
|
+
pendingUserInputs.delete(requestId);
|
|
25894
|
+
pending.resolve({
|
|
25895
|
+
kind: "answered",
|
|
25896
|
+
answers: isRecord3(answers) ? answers : {}
|
|
25897
|
+
});
|
|
25898
|
+
};
|
|
25690
25899
|
const dispose = async () => {
|
|
25691
25900
|
if (disposed) {
|
|
25692
25901
|
return;
|
|
25693
25902
|
}
|
|
25694
25903
|
settleAllApprovals("cancel");
|
|
25904
|
+
settleAllUserInputs();
|
|
25905
|
+
abortPendingSettlement("executor disposed");
|
|
25695
25906
|
notifyPromptSettled();
|
|
25696
25907
|
abortLocalPrompt?.();
|
|
25697
25908
|
emit({
|
|
@@ -25705,6 +25916,8 @@ function createAcpExecutor(options) {
|
|
|
25705
25916
|
generation += 1;
|
|
25706
25917
|
status = "disposed";
|
|
25707
25918
|
eventStream.close();
|
|
25919
|
+
options.readable.off("end", onInputDead);
|
|
25920
|
+
options.readable.off("error", onInputDead);
|
|
25708
25921
|
options.readable.pause();
|
|
25709
25922
|
if (!connectionClosed) {
|
|
25710
25923
|
await connection.awaitInboundIdle().catch(() => void 0);
|
|
@@ -25729,6 +25942,7 @@ function createAcpExecutor(options) {
|
|
|
25729
25942
|
prompt,
|
|
25730
25943
|
cancel,
|
|
25731
25944
|
respondToRequest,
|
|
25945
|
+
respondToUserInput,
|
|
25732
25946
|
dispose
|
|
25733
25947
|
};
|
|
25734
25948
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/agent-acp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2014",
|
|
4
4
|
"description": "Generic ACP executor: user-supplied CLI over Agent Client Protocol stdio.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ycodium",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@types/node": "24.12.4",
|
|
21
21
|
"esbuild": "0.28.1",
|
|
22
22
|
"vite-plus": "0.2.2",
|
|
23
|
-
"@ycodium-ai/plugin-api": "0.2.
|
|
23
|
+
"@ycodium-ai/plugin-api": "0.2.2014",
|
|
24
24
|
"ycodium-acp-executor": "0.0.0",
|
|
25
25
|
"ycodium-acp-protocol": "0.0.0"
|
|
26
26
|
},
|