@agen-ai/provider-acp 0.4.0 → 0.4.1

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 (64) hide show
  1. package/README.md +355 -12
  2. package/dist/capabilities.d.ts +3 -1
  3. package/dist/capabilities.js +5 -4
  4. package/dist/configuration/catalog.js +0 -2
  5. package/dist/configuration/errors.js +0 -1
  6. package/dist/connection/contracts.d.ts +21 -0
  7. package/dist/connection/errors.js +0 -1
  8. package/dist/connection/initialize.d.ts +7 -3
  9. package/dist/connection/initialize.js +46 -17
  10. package/dist/connection/negotiation.d.ts +2 -1
  11. package/dist/connection/negotiation.js +15 -1
  12. package/dist/contracts.d.ts +3 -0
  13. package/dist/driver.d.ts +1 -1
  14. package/dist/driver.js +6 -39
  15. package/dist/driverFactory.d.ts +8 -0
  16. package/dist/driverFactory.js +48 -0
  17. package/dist/host/interactions.js +0 -1
  18. package/dist/host/leases.js +0 -1
  19. package/dist/host/router.js +0 -1
  20. package/dist/host/terminals.js +0 -2
  21. package/dist/index.d.ts +1 -0
  22. package/dist/instance.d.ts +7 -2
  23. package/dist/instance.js +102 -27
  24. package/dist/node/contracts.d.ts +6 -0
  25. package/dist/node/execution/errors.d.ts +11 -0
  26. package/dist/node/execution/errors.js +17 -0
  27. package/dist/node/execution/owner.d.ts +34 -0
  28. package/dist/node/execution/owner.js +162 -0
  29. package/dist/node/execution/types.d.ts +20 -0
  30. package/dist/node/execution/types.js +0 -0
  31. package/dist/node/index.d.ts +3 -1
  32. package/dist/node/index.js +16 -6
  33. package/dist/node/launch.d.ts +5 -8
  34. package/dist/node/launch.js +13 -1
  35. package/dist/permissions/contracts.d.ts +22 -0
  36. package/dist/permissions/contracts.js +0 -0
  37. package/dist/permissions/mapping.d.ts +13 -0
  38. package/dist/permissions/mapping.js +99 -0
  39. package/dist/permissions/selection.d.ts +5 -0
  40. package/dist/permissions/selection.js +96 -0
  41. package/dist/session/create.d.ts +7 -1
  42. package/dist/session/create.js +3 -1
  43. package/dist/session/errors.js +0 -1
  44. package/dist/session/mapping.d.ts +6 -0
  45. package/dist/session/mapping.js +5 -26
  46. package/dist/session/opening.d.ts +1 -0
  47. package/dist/session/opening.js +3 -1
  48. package/dist/session/queue.js +0 -1
  49. package/dist/session/requests/forms.js +0 -2
  50. package/dist/session/requests/pending.js +0 -1
  51. package/dist/session/resources.js +5 -3
  52. package/dist/session/session.d.ts +4 -1
  53. package/dist/session/session.js +67 -25
  54. package/dist/sessionInteractions/broker.d.ts +42 -0
  55. package/dist/sessionInteractions/broker.js +171 -0
  56. package/dist/sessionInteractions/contracts.d.ts +3 -0
  57. package/dist/sessionInteractions/contracts.js +0 -0
  58. package/dist/sessionInteractions/extensions.d.ts +3 -0
  59. package/dist/sessionInteractions/extensions.js +17 -0
  60. package/dist/sessionInteractions/grokFolderTrust.d.ts +43 -0
  61. package/dist/sessionInteractions/grokFolderTrust.js +107 -0
  62. package/dist/sessionInteractions/request.d.ts +38 -0
  63. package/dist/sessionInteractions/request.js +145 -0
  64. package/package.json +4 -4
@@ -0,0 +1,42 @@
1
+ import type { AcpJsonValue, AcpReverseRequestContext } from '@agen-ai/acp-transport';
2
+ import type { AgentProviderConversationId, AgentSessionId } from '@agen-ai/agent-protocol';
3
+ import { type AgentSessionInteractionContext } from '@agen-ai/agent-runtime';
4
+ import type { AcpProviderLimits } from '../limits.js';
5
+ export type SessionInteractionStop = Readonly<{
6
+ kind: 'session_closed';
7
+ }> | Readonly<{
8
+ kind: 'failed';
9
+ cause: Error;
10
+ }>;
11
+ /** One connection owns one create generation and one retained native decision. */
12
+ export declare class SessionInteractionBroker {
13
+ private readonly input;
14
+ private state;
15
+ private requestNumber;
16
+ private retained;
17
+ private readonly entered;
18
+ private openingTimer;
19
+ private readonly onStopped;
20
+ readonly handleRequest: (context: AcpReverseRequestContext) => Promise<AcpJsonValue>;
21
+ constructor(input: Readonly<{
22
+ stopped: AbortSignal;
23
+ limits: AcpProviderLimits;
24
+ onFailure: (cause: Error) => void;
25
+ }>);
26
+ prepareOpening(input: Readonly<{
27
+ sessionId: AgentSessionId;
28
+ workingDirectory: string;
29
+ interactions: AgentSessionInteractionContext;
30
+ signal?: AbortSignal;
31
+ }>): void;
32
+ onOpeningDispatched(): void;
33
+ bindNativeSession(nativeId: AgentProviderConversationId): void;
34
+ completeOpeningResponse(): void;
35
+ adopt(): void;
36
+ stop(reason: SessionInteractionStop): void;
37
+ private clearOpeningTimer;
38
+ private stateError;
39
+ private throwIfStopped;
40
+ join(): Promise<void>;
41
+ }
42
+ //# sourceMappingURL=broker.d.ts.map
@@ -0,0 +1,171 @@
1
+ import {
2
+ DEFAULT_AGENT_SESSION_INTERACTION_LIMITS,
3
+ throwIfAgentOperationAborted
4
+ } from "@agen-ai/agent-runtime";
5
+ import { AcpProviderSessionError } from "../session/errors.js";
6
+ import { folderTrustDecision, GROK_FOLDER_TRUST_METHOD, parseNativeFolderTrustRequest } from "./grokFolderTrust.js";
7
+ import { NativeSessionDecision, sessionDecisionError } from "./request.js";
8
+ class SessionInteractionBroker {
9
+ constructor(input) {
10
+ this.input = input;
11
+ input.stopped.addEventListener("abort", this.onStopped, { once: true });
12
+ if (input.stopped.aborted) this.onStopped();
13
+ }
14
+ state = { kind: "idle" };
15
+ requestNumber = 0;
16
+ retained;
17
+ entered = /* @__PURE__ */ new Set();
18
+ openingTimer;
19
+ onStopped = () => {
20
+ const cause = this.input.stopped.reason;
21
+ this.stop({ kind: "failed", cause: cause instanceof Error ? cause : new AcpProviderSessionError(
22
+ "acp_session_interaction_native_failed",
23
+ "The native session decision producer stopped."
24
+ ) });
25
+ };
26
+ handleRequest = (context) => {
27
+ try {
28
+ if (context.method !== GROK_FOLDER_TRUST_METHOD) throw failure(
29
+ "acp_session_interaction_invalid",
30
+ "The reverse request does not belong to the selected session mapping."
31
+ );
32
+ const native = parseNativeFolderTrustRequest(context.params);
33
+ const state = this.state;
34
+ if (state.kind === "stopped") throw state.cause;
35
+ if (state.kind === "idle" || state.kind === "prepared") throw failure(
36
+ "acp_session_interaction_owner_missing",
37
+ "The native decision has no dispatched session opening owner."
38
+ );
39
+ if (this.retained) throw failure(
40
+ "acp_session_interaction_capacity",
41
+ "The connection already retains a native session decision."
42
+ );
43
+ if (native.cwd !== state.owner.workingDirectory || "nativeId" in state && native.sessionId !== state.nativeId) throw failure(
44
+ "acp_session_interaction_binding",
45
+ "The native decision does not match its exact session binding."
46
+ );
47
+ const expiresAt = Date.now() + state.owner.limits.requestTimeoutMs;
48
+ const request = folderTrustDecision({ native, sessionId: state.owner.sessionId, requestNumber: ++this.requestNumber, expiresAt });
49
+ const descriptorBytes = Buffer.byteLength(JSON.stringify(request), "utf8");
50
+ const nativeBytes = Buffer.byteLength(JSON.stringify(native), "utf8");
51
+ if (descriptorBytes > state.owner.limits.maxRetainedBytes || descriptorBytes + nativeBytes > this.input.limits.maxQueuedBytes) throw failure(
52
+ "acp_session_interaction_capacity",
53
+ "The native session decision exceeds its retained byte budget."
54
+ );
55
+ const decision = new NativeSessionDecision({
56
+ request,
57
+ nativeContext: Object.freeze({ id: context.id, signal: context.signal }),
58
+ expiresAt,
59
+ accepting: () => this.state.kind === "active" || this.state.kind === "open",
60
+ fail: (cause) => this.stop({ kind: "failed", cause }),
61
+ retire: () => {
62
+ if (this.retained?.decision === decision) this.retained = void 0;
63
+ this.entered.delete(decision);
64
+ }
65
+ });
66
+ this.retained = { decision, nativeId: native.sessionId };
67
+ this.entered.add(decision);
68
+ decision.observeNativeSignal();
69
+ if (this.state.kind === "active" || this.state.kind === "open") decision.present(state.owner.interactions.onRequest);
70
+ return decision.nativeReply;
71
+ } catch (cause) {
72
+ const error = cause instanceof Error ? cause : failure(
73
+ "acp_session_interaction_invalid",
74
+ "The native session decision could not be admitted.",
75
+ cause
76
+ );
77
+ this.stop({ kind: "failed", cause: error });
78
+ const rejected = Promise.reject(error);
79
+ void rejected.catch(() => {
80
+ });
81
+ return rejected;
82
+ }
83
+ };
84
+ prepareOpening(input) {
85
+ throwIfAgentOperationAborted(input.signal);
86
+ if (this.state.kind !== "idle") throw this.stateError();
87
+ const onAbort = () => this.stop({ kind: "failed", cause: input.signal?.reason instanceof Error ? input.signal.reason : new DOMException("The session opening was aborted.", "AbortError") });
88
+ const owner = Object.freeze({
89
+ sessionId: input.sessionId,
90
+ workingDirectory: input.workingDirectory,
91
+ interactions: input.interactions,
92
+ limits: Object.freeze({ ...DEFAULT_AGENT_SESSION_INTERACTION_LIMITS, ...input.interactions.limits }),
93
+ removeAbort: () => input.signal?.removeEventListener("abort", onAbort)
94
+ });
95
+ this.state = { kind: "prepared", owner };
96
+ input.signal?.addEventListener("abort", onAbort, { once: true });
97
+ if (input.signal?.aborted) onAbort();
98
+ this.throwIfStopped();
99
+ }
100
+ onOpeningDispatched() {
101
+ if (this.state.kind !== "prepared") throw this.stateError();
102
+ this.state = { kind: "dispatched", owner: this.state.owner };
103
+ this.openingTimer = setTimeout(
104
+ () => this.stop({ kind: "failed", cause: failure(
105
+ "acp_session_interaction_opening_timeout",
106
+ "The native decision opening did not bind before its deadline."
107
+ ) }),
108
+ this.input.limits.initializationTimeoutMs
109
+ );
110
+ }
111
+ bindNativeSession(nativeId) {
112
+ if (this.state.kind !== "dispatched") throw this.stateError();
113
+ if (this.retained && this.retained.nativeId !== nativeId) {
114
+ const cause = failure("acp_session_interaction_binding", "The buffered decision claimed a different native session.");
115
+ this.stop({ kind: "failed", cause });
116
+ throw cause;
117
+ }
118
+ this.state = { kind: "bound", owner: this.state.owner, nativeId };
119
+ }
120
+ completeOpeningResponse() {
121
+ if (this.state.kind !== "bound") throw this.stateError();
122
+ this.clearOpeningTimer();
123
+ const owner = this.state.owner;
124
+ this.state = { kind: "active", owner, nativeId: this.state.nativeId };
125
+ this.retained?.decision.present(owner.interactions.onRequest);
126
+ this.throwIfStopped();
127
+ }
128
+ adopt() {
129
+ if (this.state.kind !== "active") throw this.stateError();
130
+ this.state.owner.removeAbort();
131
+ this.state = { kind: "open", owner: this.state.owner, nativeId: this.state.nativeId };
132
+ }
133
+ stop(reason) {
134
+ if (this.state.kind === "stopped") return;
135
+ const cause = reason.kind === "failed" ? reason.cause : failure(
136
+ "acp_session_interaction_closed",
137
+ "The session decision owner was closed."
138
+ );
139
+ if (this.state.kind !== "idle") this.state.owner.removeAbort();
140
+ this.clearOpeningTimer();
141
+ this.state = { kind: "stopped", cause };
142
+ this.retained?.decision.stop(reason.kind === "failed" ? Object.freeze({ kind: "failed", error: sessionDecisionError(
143
+ "session_interaction_failed",
144
+ "The session decision owner failed."
145
+ ) }) : Object.freeze({ kind: "session_closed" }), cause);
146
+ if (reason.kind === "failed") this.input.onFailure(cause);
147
+ }
148
+ clearOpeningTimer() {
149
+ if (this.openingTimer) clearTimeout(this.openingTimer);
150
+ this.openingTimer = void 0;
151
+ }
152
+ stateError() {
153
+ return this.state.kind === "stopped" ? this.state.cause : failure(
154
+ "acp_session_interaction_binding",
155
+ "The native session interaction lifecycle is out of order."
156
+ );
157
+ }
158
+ throwIfStopped() {
159
+ if (this.state.kind === "stopped") throw this.state.cause;
160
+ }
161
+ async join() {
162
+ while (this.entered.size > 0) await Promise.all([...this.entered].map((decision) => decision.join()));
163
+ if (this.state.kind === "stopped") this.input.stopped.removeEventListener("abort", this.onStopped);
164
+ }
165
+ }
166
+ function failure(reasonCode, message, cause) {
167
+ return new AcpProviderSessionError(reasonCode, message, cause === void 0 ? void 0 : { cause });
168
+ }
169
+ export {
170
+ SessionInteractionBroker
171
+ };
@@ -0,0 +1,3 @@
1
+ import type { CreateNodeAcpProviderDriverOptions } from '../node/contracts.js';
2
+ export type AcpSessionInteractionSelection = NonNullable<CreateNodeAcpProviderDriverOptions['sessionInteractions']>;
3
+ //# sourceMappingURL=contracts.d.ts.map
File without changes
@@ -0,0 +1,3 @@
1
+ import { type AcpExtensionRegistry } from '@agen-ai/acp-transport';
2
+ export declare function createSessionInteractionExtensionRegistry(): AcpExtensionRegistry;
3
+ //# sourceMappingURL=extensions.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { createAcpExtensionRegistry } from "@agen-ai/acp-transport";
2
+ import {
3
+ GROK_FOLDER_TRUST_METHOD,
4
+ validateNativeFolderTrustRequest,
5
+ validateNativeFolderTrustResult
6
+ } from "./grokFolderTrust.js";
7
+ function createSessionInteractionExtensionRegistry() {
8
+ return createAcpExtensionRegistry([{
9
+ method: GROK_FOLDER_TRUST_METHOD,
10
+ direction: "inbound_request",
11
+ validateParams: validateNativeFolderTrustRequest,
12
+ validateResult: validateNativeFolderTrustResult
13
+ }]);
14
+ }
15
+ export {
16
+ createSessionInteractionExtensionRegistry
17
+ };
@@ -0,0 +1,43 @@
1
+ import { type AgentProviderConversationId, type AgentSessionDecisionRequest, type AgentSessionId } from '@agen-ai/agent-protocol';
2
+ export declare const GROK_FOLDER_TRUST_METHOD = "_x.ai/folder_trust/request";
3
+ export declare const GROK_FOLDER_TRUST_CAPABILITY_META: Readonly<{
4
+ 'x.ai/folderTrust': Readonly<{
5
+ interactive: true;
6
+ }>;
7
+ }>;
8
+ declare const CONFIGURATION_EXPLANATIONS: Readonly<{
9
+ mcp: "Project tool servers";
10
+ plugins: "Project plugins";
11
+ permission: "Project tool-permission rules";
12
+ lsp: "Project language servers";
13
+ envrc: "Project shell environment scripts";
14
+ claude: "Imported compatibility settings";
15
+ hooks: "Project hooks";
16
+ agents: "Project agent definitions";
17
+ roles: "Project role definitions";
18
+ personas: "Project persona definitions";
19
+ workflows: "Project workflows";
20
+ instructions: "Project instructions";
21
+ skills: "Project skills";
22
+ }>;
23
+ type ConfigurationKind = keyof typeof CONFIGURATION_EXPLANATIONS;
24
+ export interface NativeFolderTrustRequest {
25
+ readonly sessionId: AgentProviderConversationId;
26
+ readonly cwd: string;
27
+ readonly workspace: string;
28
+ readonly configKinds: readonly ConfigurationKind[];
29
+ }
30
+ export type NativeFolderTrustResult = Readonly<{
31
+ outcome: 'trust' | 'reject';
32
+ }>;
33
+ export declare function parseNativeFolderTrustRequest(input: unknown): NativeFolderTrustRequest;
34
+ export declare function validateNativeFolderTrustRequest(input: unknown): boolean;
35
+ export declare function validateNativeFolderTrustResult(input: unknown): boolean;
36
+ export declare function folderTrustDecision(input: Readonly<{
37
+ native: NativeFolderTrustRequest;
38
+ sessionId: AgentSessionId;
39
+ requestNumber: number;
40
+ expiresAt: number;
41
+ }>): AgentSessionDecisionRequest;
42
+ export {};
43
+ //# sourceMappingURL=grokFolderTrust.d.ts.map
@@ -0,0 +1,107 @@
1
+ import { posix } from "node:path";
2
+ import {
3
+ parseAgentProviderConversationId,
4
+ parseAgentSessionDecisionRequest
5
+ } from "@agen-ai/agent-protocol";
6
+ import { AcpProviderSessionError } from "../session/errors.js";
7
+ const GROK_FOLDER_TRUST_METHOD = "_x.ai/folder_trust/request";
8
+ const GROK_FOLDER_TRUST_CAPABILITY_META = Object.freeze({
9
+ "x.ai/folderTrust": Object.freeze({ interactive: true })
10
+ });
11
+ const CONFIGURATION_EXPLANATIONS = Object.freeze({
12
+ mcp: "Project tool servers",
13
+ plugins: "Project plugins",
14
+ permission: "Project tool-permission rules",
15
+ lsp: "Project language servers",
16
+ envrc: "Project shell environment scripts",
17
+ claude: "Imported compatibility settings",
18
+ hooks: "Project hooks",
19
+ agents: "Project agent definitions",
20
+ roles: "Project role definitions",
21
+ personas: "Project persona definitions",
22
+ workflows: "Project workflows",
23
+ instructions: "Project instructions",
24
+ skills: "Project skills"
25
+ });
26
+ function configurationKind(input) {
27
+ return typeof input === "string" && Object.hasOwn(CONFIGURATION_EXPLANATIONS, input);
28
+ }
29
+ function exactObject(input, keys) {
30
+ if (input === null || typeof input !== "object" || Array.isArray(input)) return false;
31
+ const own = Reflect.ownKeys(input);
32
+ return own.length === keys.length && keys.every((key) => Object.hasOwn(input, key));
33
+ }
34
+ function invalidRequest() {
35
+ return new AcpProviderSessionError("acp_session_interaction_invalid", "The native workspace decision violates the selected mapping.");
36
+ }
37
+ function workspacePath(input) {
38
+ if (typeof input !== "string" || input.length === 0 || Buffer.byteLength(input, "utf8") > 4096 || /[\p{Cc}\p{Cf}]/u.test(input) || !posix.isAbsolute(input) || posix.normalize(input) !== input) {
39
+ throw invalidRequest();
40
+ }
41
+ return input;
42
+ }
43
+ function parseNativeFolderTrustRequest(input) {
44
+ if (!exactObject(input, ["sessionId", "cwd", "workspace", "configKinds"]) || Buffer.byteLength(JSON.stringify(input), "utf8") > 65536 || typeof input.sessionId !== "string" || Buffer.byteLength(input.sessionId, "utf8") > 512 || !Array.isArray(input.configKinds) || input.configKinds.length < 1 || input.configKinds.length > 13) {
45
+ throw invalidRequest();
46
+ }
47
+ const kinds = [];
48
+ for (const kind of input.configKinds) {
49
+ if (!configurationKind(kind) || kinds.includes(kind)) throw invalidRequest();
50
+ kinds.push(kind);
51
+ }
52
+ return Object.freeze({
53
+ sessionId: parseAgentProviderConversationId(input.sessionId),
54
+ cwd: workspacePath(input.cwd),
55
+ workspace: workspacePath(input.workspace),
56
+ configKinds: Object.freeze(kinds)
57
+ });
58
+ }
59
+ function validateNativeFolderTrustRequest(input) {
60
+ try {
61
+ parseNativeFolderTrustRequest(input);
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+ function validateNativeFolderTrustResult(input) {
68
+ return exactObject(input, ["outcome"]) && (input.outcome === "trust" || input.outcome === "reject");
69
+ }
70
+ function folderTrustDecision(input) {
71
+ const detected = input.native.configKinds.map((kind) => CONFIGURATION_EXPLANATIONS[kind]).join("; ");
72
+ return parseAgentSessionDecisionRequest({
73
+ kind: "decision",
74
+ requestId: `session-interaction:${input.requestNumber}`,
75
+ sessionId: input.sessionId,
76
+ title: "Allow workspace configuration?",
77
+ description: `Detected configuration: ${detected}. Workspace configuration can start tools or hooks and affect provider behavior. These detected categories do not restrict the scope of an authorization.`,
78
+ target: { kind: "workspace", path: input.native.workspace },
79
+ expiresAt: new Date(input.expiresAt).toISOString(),
80
+ choices: [
81
+ {
82
+ choiceId: "grant",
83
+ label: "Allow workspace configuration",
84
+ description: "Authorize configuration for the target workspace, including future configuration. The provider manages persistent trust, which may affect later or sibling sessions using the same profile. This choice does not confirm immediate loading or successful persistence.",
85
+ consequence: {
86
+ kind: "grant",
87
+ scope: "workspace",
88
+ persistence: { kind: "provider_managed", duration: "persistent", mayAffectOtherSessions: true }
89
+ }
90
+ },
91
+ {
92
+ choiceId: "decline",
93
+ label: "Keep configuration gated",
94
+ description: "Keep workspace configuration gated. Repeat questions may be suppressed for this workspace while this native agent lives. This choice does not revoke an existing grant or promise a persisted denial.",
95
+ consequence: { kind: "decline", configuration: "remains_gated" }
96
+ }
97
+ ]
98
+ });
99
+ }
100
+ export {
101
+ GROK_FOLDER_TRUST_CAPABILITY_META,
102
+ GROK_FOLDER_TRUST_METHOD,
103
+ folderTrustDecision,
104
+ parseNativeFolderTrustRequest,
105
+ validateNativeFolderTrustRequest,
106
+ validateNativeFolderTrustResult
107
+ };
@@ -0,0 +1,38 @@
1
+ import { type AcpJsonValue, type AcpReverseRequestContext } from '@agen-ai/acp-transport';
2
+ import { type AgentError, type AgentSessionDecisionRequest } from '@agen-ai/agent-protocol';
3
+ import { type AgentSessionInteraction, type AgentSessionInteractionHandler, type AgentSessionInteractionTerminal } from '@agen-ai/agent-runtime';
4
+ export declare function sessionDecisionError(code: string, message: string): AgentError;
5
+ export declare class NativeSessionDecision {
6
+ private readonly input;
7
+ readonly handle: AgentSessionInteraction;
8
+ readonly nativeReply: Promise<AcpJsonValue>;
9
+ private readonly answer;
10
+ private readonly terminal;
11
+ private readonly presentation;
12
+ private state;
13
+ private callback;
14
+ private callbackSettled;
15
+ private nativeSettled;
16
+ private presented;
17
+ private retired;
18
+ private readonly timer;
19
+ private readonly onNativeAbort;
20
+ constructor(input: Readonly<{
21
+ request: AgentSessionDecisionRequest;
22
+ nativeContext: Pick<AcpReverseRequestContext, 'id' | 'signal'>;
23
+ expiresAt: number;
24
+ accepting: () => boolean;
25
+ fail: (cause: Error) => void;
26
+ retire: () => void;
27
+ }>);
28
+ observeNativeSignal(): void;
29
+ present(handler: AgentSessionInteractionHandler): void;
30
+ stop(terminal: AgentSessionInteractionTerminal, cause: Error): void;
31
+ private finalize;
32
+ private expire;
33
+ private nativeAborted;
34
+ private respond;
35
+ private maybeRetire;
36
+ join(): Promise<void>;
37
+ }
38
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1,145 @@
1
+ import { AcpRequestError } from "@agen-ai/acp-transport";
2
+ import {
3
+ parseAgentSessionDecisionResponseFor
4
+ } from "@agen-ai/agent-protocol";
5
+ import {
6
+ throwIfAgentOperationAborted
7
+ } from "@agen-ai/agent-runtime";
8
+ import { AcpProviderSessionError } from "../session/errors.js";
9
+ import { deferred } from "../session/queue.js";
10
+ function sessionDecisionError(code, message) {
11
+ return Object.freeze({ category: "invalid_request", code, message, recovery: Object.freeze({ kind: "none" }) });
12
+ }
13
+ class NativeSessionDecision {
14
+ constructor(input) {
15
+ this.input = input;
16
+ this.handle = Object.freeze({
17
+ request: input.request,
18
+ signal: this.presentation.signal,
19
+ terminal: this.terminal.promise,
20
+ respond: (response) => this.respond(response)
21
+ });
22
+ this.nativeReply = this.answer.promise.then((result) => {
23
+ this.nativeSettled = true;
24
+ this.maybeRetire();
25
+ return result;
26
+ }, (cause) => {
27
+ this.nativeSettled = true;
28
+ this.maybeRetire();
29
+ throw cause;
30
+ });
31
+ void this.nativeReply.catch(() => {
32
+ });
33
+ this.timer = setTimeout(() => this.expire(), Math.max(0, input.expiresAt - Date.now()));
34
+ input.nativeContext.signal.addEventListener("abort", this.onNativeAbort, { once: true });
35
+ }
36
+ handle;
37
+ nativeReply;
38
+ answer = deferred();
39
+ terminal = deferred();
40
+ presentation = new AbortController();
41
+ state = { kind: "pending" };
42
+ callback;
43
+ callbackSettled = true;
44
+ nativeSettled = false;
45
+ presented = false;
46
+ retired = false;
47
+ timer;
48
+ onNativeAbort = () => this.nativeAborted();
49
+ observeNativeSignal() {
50
+ if (this.input.nativeContext.signal.aborted) this.nativeAborted();
51
+ }
52
+ present(handler) {
53
+ if (this.presented || this.state.kind !== "pending") return;
54
+ this.observeNativeSignal();
55
+ if (this.state.kind !== "pending" || !this.input.accepting()) return;
56
+ if (Date.now() >= this.input.expiresAt) {
57
+ this.expire();
58
+ return;
59
+ }
60
+ this.presented = true;
61
+ this.callbackSettled = false;
62
+ let synchronousFailure;
63
+ try {
64
+ this.callback = Promise.resolve(handler(this.handle));
65
+ } catch (cause) {
66
+ synchronousFailure = { cause };
67
+ this.callback = Promise.reject(cause);
68
+ }
69
+ void this.callback.then(() => {
70
+ this.callbackSettled = true;
71
+ this.maybeRetire();
72
+ }, (cause) => {
73
+ this.callbackSettled = true;
74
+ this.input.fail(callbackError(cause));
75
+ this.maybeRetire();
76
+ });
77
+ if (synchronousFailure) this.input.fail(callbackError(synchronousFailure.cause));
78
+ }
79
+ stop(terminal, cause) {
80
+ if (this.state.kind === "terminal") return;
81
+ this.finalize(terminal);
82
+ this.answer.reject(cause);
83
+ }
84
+ finalize(terminal) {
85
+ if (this.state.kind === "terminal") return;
86
+ this.state = { kind: "terminal", terminal: Object.freeze(terminal) };
87
+ clearTimeout(this.timer);
88
+ this.input.nativeContext.signal.removeEventListener("abort", this.onNativeAbort);
89
+ this.terminal.resolve(this.state.terminal);
90
+ this.presentation.abort(new DOMException("The session decision is no longer answerable.", "AbortError"));
91
+ this.maybeRetire();
92
+ }
93
+ expire() {
94
+ if (this.state.kind === "terminal") return;
95
+ const cause = new AcpProviderSessionError("acp_session_interaction_expired", "The session decision expired before acceptance.");
96
+ this.stop(Object.freeze({ kind: "expired" }), cause);
97
+ this.input.fail(cause);
98
+ }
99
+ nativeAborted() {
100
+ if (this.state.kind === "terminal") return;
101
+ const reason = this.input.nativeContext.signal.reason;
102
+ if (reason instanceof AcpRequestError && reason.reasonCode === "acp_reverse_request_canceled" && reason.requestId === this.input.nativeContext.id) {
103
+ this.stop(Object.freeze({ kind: "withdrawn" }), reason);
104
+ return;
105
+ }
106
+ this.input.fail(reason instanceof Error ? reason : new AcpProviderSessionError(
107
+ "acp_session_interaction_native_failed",
108
+ "The native session decision producer stopped."
109
+ ));
110
+ }
111
+ async respond(input) {
112
+ throwIfAgentOperationAborted(input.signal);
113
+ const response = parseAgentSessionDecisionResponseFor(this.handle.request, { choiceId: input.choiceId });
114
+ this.observeNativeSignal();
115
+ if (Date.now() >= this.input.expiresAt) this.expire();
116
+ if (!this.input.accepting() || this.state.kind === "terminal") {
117
+ return Object.freeze({ status: "rejected", requestState: "unavailable", error: sessionDecisionError(
118
+ "session_interaction_unavailable",
119
+ "The session decision is no longer available."
120
+ ) });
121
+ }
122
+ this.answer.resolve(Object.freeze({ outcome: response.choiceId === "grant" ? "trust" : "reject" }));
123
+ this.finalize(Object.freeze({ kind: "answered", choiceId: response.choiceId }));
124
+ return Object.freeze({ status: "delivered" });
125
+ }
126
+ maybeRetire() {
127
+ if (this.retired || this.state.kind !== "terminal" || !this.nativeSettled || !this.callbackSettled) return;
128
+ this.retired = true;
129
+ this.input.retire();
130
+ }
131
+ async join() {
132
+ await Promise.allSettled([this.nativeReply, ...this.callback ? [this.callback] : []]);
133
+ }
134
+ }
135
+ function callbackError(cause) {
136
+ return cause instanceof Error ? cause : new AcpProviderSessionError(
137
+ "acp_session_interaction_handler_failed",
138
+ "The session decision handler failed.",
139
+ { cause }
140
+ );
141
+ }
142
+ export {
143
+ NativeSessionDecision,
144
+ sessionDecisionError
145
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agen-ai/provider-acp",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "description": "Optional Agent Client Protocol provider for the AgenAI coding-agent runtime.",
6
6
  "type": "module",
@@ -27,9 +27,9 @@
27
27
  "./package.json": "./package.json"
28
28
  },
29
29
  "dependencies": {
30
- "@agen-ai/acp-transport": "^0.4.0",
31
- "@agen-ai/agent-protocol": "^0.4.0",
32
- "@agen-ai/agent-runtime": "^0.4.0"
30
+ "@agen-ai/agent-protocol": "^0.4.1",
31
+ "@agen-ai/acp-transport": "^0.4.1",
32
+ "@agen-ai/agent-runtime": "^0.4.1"
33
33
  },
34
34
  "devDependencies": {
35
35
  "tsup": "^8.5.0"