@agen-ai/provider-acp 0.4.1 → 0.4.2

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 (43) hide show
  1. package/README.md +174 -14
  2. package/dist/capabilities.js +2 -2
  3. package/dist/connection/contracts.d.ts +3 -1
  4. package/dist/connection/initialize.js +25 -9
  5. package/dist/connection/negotiation.d.ts +2 -1
  6. package/dist/connection/negotiation.js +8 -1
  7. package/dist/host/interactions.d.ts +6 -1
  8. package/dist/host/interactions.js +42 -16
  9. package/dist/host/router.d.ts +3 -2
  10. package/dist/host/router.js +23 -20
  11. package/dist/instance.js +19 -9
  12. package/dist/node/contracts.d.ts +2 -2
  13. package/dist/node/index.js +1 -1
  14. package/dist/node/launch.js +3 -3
  15. package/dist/session/continuation.d.ts +33 -0
  16. package/dist/session/continuation.js +185 -0
  17. package/dist/session/descendants.d.ts +29 -0
  18. package/dist/session/descendants.js +97 -0
  19. package/dist/session/mapping.js +1 -1
  20. package/dist/session/native.d.ts +1 -0
  21. package/dist/session/native.js +8 -0
  22. package/dist/session/opening.js +1 -1
  23. package/dist/session/requests/elicitation.d.ts +1 -1
  24. package/dist/session/requests/elicitation.js +1 -1
  25. package/dist/session/requests/grokWithdrawals.d.ts +28 -0
  26. package/dist/session/requests/grokWithdrawals.js +135 -0
  27. package/dist/session/requests/pending.d.ts +17 -4
  28. package/dist/session/requests/pending.js +63 -7
  29. package/dist/session/requests/resolution.js +4 -2
  30. package/dist/session/session.d.ts +8 -0
  31. package/dist/session/session.js +140 -32
  32. package/dist/session/turn.d.ts +6 -1
  33. package/dist/sessionInteractions/broker.d.ts +11 -3
  34. package/dist/sessionInteractions/broker.js +35 -13
  35. package/dist/sessionInteractions/extensions.d.ts +2 -1
  36. package/dist/sessionInteractions/extensions.js +21 -1
  37. package/dist/sessionInteractions/grokFolderTrust.d.ts +0 -1
  38. package/dist/sessionInteractions/grokFolderTrust.js +0 -1
  39. package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
  40. package/dist/sessionInteractions/grokPlanReview.js +63 -0
  41. package/dist/sessionInteractions/request.d.ts +15 -7
  42. package/dist/sessionInteractions/request.js +46 -18
  43. package/package.json +4 -4
@@ -0,0 +1,63 @@
1
+ import {
2
+ parseAgentProviderConversationId,
3
+ parseAgentSessionPlanReviewRequest
4
+ } from "@agen-ai/agent-protocol";
5
+ import { AcpProviderSessionError } from "../session/errors.js";
6
+ const GROK_PLAN_REVIEW_METHOD = "_x.ai/exit_plan_mode";
7
+ const GROK_QUEUE_METHOD = "_x.ai/queue/changed";
8
+ const GROK_SESSION_NOTIFICATION_METHOD = "_x.ai/session_notification";
9
+ function parseNativePlanReviewRequest(input) {
10
+ if (input === null || typeof input !== "object" || Array.isArray(input) || Reflect.ownKeys(input).length !== 3 || !("sessionId" in input) || !("toolCallId" in input) || !("planContent" in input) || typeof input.sessionId !== "string" || Buffer.byteLength(input.sessionId, "utf8") > 256 || typeof input.toolCallId !== "string" || !input.toolCallId || Buffer.byteLength(input.toolCallId, "utf8") > 256 || typeof input.planContent !== "string" || !input.planContent || Buffer.byteLength(input.planContent, "utf8") > 65536) {
11
+ throw new AcpProviderSessionError("acp_session_interaction_invalid", "The native plan review violates the selected mapping.");
12
+ }
13
+ return Object.freeze({
14
+ sessionId: parseAgentProviderConversationId(input.sessionId),
15
+ toolCallId: input.toolCallId,
16
+ planContent: input.planContent
17
+ });
18
+ }
19
+ function validateNativePlanReviewRequest(input) {
20
+ try {
21
+ parseNativePlanReviewRequest(input);
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+ function validateNativePlanReviewResult(input) {
28
+ if (input === null || typeof input !== "object" || Array.isArray(input) || !("outcome" in input)) return false;
29
+ return input.outcome === "cancelled" ? Reflect.ownKeys(input).length === 2 && "feedback" in input && typeof input.feedback === "string" && Buffer.byteLength(input.feedback, "utf8") <= 16384 : Reflect.ownKeys(input).length === 1 && (input.outcome === "approved" || input.outcome === "abandoned");
30
+ }
31
+ function planReviewRequest(native, sessionId, requestNumber) {
32
+ return parseAgentSessionPlanReviewRequest({
33
+ kind: "plan_review",
34
+ requestId: `session-interaction:${requestNumber}`,
35
+ sessionId,
36
+ title: "Review the restored plan",
37
+ plan: {
38
+ planId: `session-plan:${requestNumber}`,
39
+ revision: "1",
40
+ title: "Restored plan",
41
+ content: { kind: "text", format: "markdown", text: native.planContent }
42
+ },
43
+ choices: [
44
+ { choiceId: "approve", action: "approve", label: "Approve plan" },
45
+ { choiceId: "revise", action: "revise", label: "Revise plan" },
46
+ { choiceId: "abandon", action: "abandon", label: "Abandon plan" }
47
+ ]
48
+ });
49
+ }
50
+ function nativePlanReviewReply(response) {
51
+ if (response.action === "revise") return Object.freeze({ outcome: "cancelled", feedback: response.feedback });
52
+ return Object.freeze({ outcome: response.action === "approve" ? "approved" : "abandoned" });
53
+ }
54
+ export {
55
+ GROK_PLAN_REVIEW_METHOD,
56
+ GROK_QUEUE_METHOD,
57
+ GROK_SESSION_NOTIFICATION_METHOD,
58
+ nativePlanReviewReply,
59
+ parseNativePlanReviewRequest,
60
+ planReviewRequest,
61
+ validateNativePlanReviewRequest,
62
+ validateNativePlanReviewResult
63
+ };
@@ -1,10 +1,16 @@
1
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';
2
+ import { type AgentError, type AgentSessionDecisionRequest, type AgentSessionPlanReviewRequest, type AgentSessionPlanReviewResponse } from '@agen-ai/agent-protocol';
3
+ import { type AgentRequestResolutionResult, type AgentProviderSessionInteraction, type AgentPreparedSessionResponse, type AgentProviderSessionInteractionContext, type AgentSessionInteractionTerminal } from '@agen-ai/agent-runtime';
4
+ export type PreparePlanContinuation = (input: Readonly<{
5
+ response: AgentSessionPlanReviewResponse;
6
+ signal: AbortSignal;
7
+ nativeSettled: Promise<void>;
8
+ dispatch: () => Promise<AgentRequestResolutionResult>;
9
+ }>) => AgentPreparedSessionResponse;
4
10
  export declare function sessionDecisionError(code: string, message: string): AgentError;
5
11
  export declare class NativeSessionDecision {
6
12
  private readonly input;
7
- readonly handle: AgentSessionInteraction;
13
+ readonly handle: AgentProviderSessionInteraction;
8
14
  readonly nativeReply: Promise<AcpJsonValue>;
9
15
  private readonly answer;
10
16
  private readonly terminal;
@@ -18,20 +24,22 @@ export declare class NativeSessionDecision {
18
24
  private readonly timer;
19
25
  private readonly onNativeAbort;
20
26
  constructor(input: Readonly<{
21
- request: AgentSessionDecisionRequest;
22
- nativeContext: Pick<AcpReverseRequestContext, 'id' | 'signal'>;
23
- expiresAt: number;
27
+ request: AgentSessionDecisionRequest | AgentSessionPlanReviewRequest;
28
+ nativeContext: Pick<AcpReverseRequestContext, 'id' | 'signal' | 'settled'>;
29
+ hostDeadlineAt?: number;
30
+ prepareContinuation?: PreparePlanContinuation;
24
31
  accepting: () => boolean;
25
32
  fail: (cause: Error) => void;
26
33
  retire: () => void;
27
34
  }>);
28
35
  observeNativeSignal(): void;
29
- present(handler: AgentSessionInteractionHandler): void;
36
+ present(handler: AgentProviderSessionInteractionContext['onRequest']): void;
30
37
  stop(terminal: AgentSessionInteractionTerminal, cause: Error): void;
31
38
  private finalize;
32
39
  private expire;
33
40
  private nativeAborted;
34
41
  private respond;
42
+ private prepareResponse;
35
43
  private maybeRetire;
36
44
  join(): Promise<void>;
37
45
  }
@@ -1,36 +1,39 @@
1
1
  import { AcpRequestError } from "@agen-ai/acp-transport";
2
2
  import {
3
- parseAgentSessionDecisionResponseFor
3
+ parseAgentSessionDecisionResponseFor,
4
+ parseAgentSessionPlanReviewResponseFor
4
5
  } from "@agen-ai/agent-protocol";
5
6
  import {
6
7
  throwIfAgentOperationAborted
7
8
  } from "@agen-ai/agent-runtime";
8
9
  import { AcpProviderSessionError } from "../session/errors.js";
9
10
  import { deferred } from "../session/queue.js";
11
+ import { nativePlanReviewReply } from "./grokPlanReview.js";
10
12
  function sessionDecisionError(code, message) {
11
13
  return Object.freeze({ category: "invalid_request", code, message, recovery: Object.freeze({ kind: "none" }) });
12
14
  }
13
15
  class NativeSessionDecision {
14
16
  constructor(input) {
15
17
  this.input = input;
16
- this.handle = Object.freeze({
18
+ const base = {
17
19
  request: input.request,
18
20
  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) => {
21
+ terminal: this.terminal.promise
22
+ };
23
+ this.handle = input.request.kind === "plan_review" ? Object.freeze({
24
+ ...base,
25
+ kind: "prepared",
26
+ request: input.request,
27
+ prepareResponse: (response) => this.prepareResponse(response)
28
+ }) : Object.freeze({ ...base, kind: "direct", respond: (response) => this.respond(response) });
29
+ this.nativeReply = this.answer.promise;
30
+ void input.nativeContext.settled.then(() => {
27
31
  this.nativeSettled = true;
28
32
  this.maybeRetire();
29
- throw cause;
30
33
  });
31
34
  void this.nativeReply.catch(() => {
32
35
  });
33
- this.timer = setTimeout(() => this.expire(), Math.max(0, input.expiresAt - Date.now()));
36
+ if (input.hostDeadlineAt !== void 0) this.timer = setTimeout(() => this.expire(), Math.max(0, input.hostDeadlineAt - Date.now()));
34
37
  input.nativeContext.signal.addEventListener("abort", this.onNativeAbort, { once: true });
35
38
  }
36
39
  handle;
@@ -53,7 +56,7 @@ class NativeSessionDecision {
53
56
  if (this.presented || this.state.kind !== "pending") return;
54
57
  this.observeNativeSignal();
55
58
  if (this.state.kind !== "pending" || !this.input.accepting()) return;
56
- if (Date.now() >= this.input.expiresAt) {
59
+ if (this.input.hostDeadlineAt !== void 0 && Date.now() >= this.input.hostDeadlineAt) {
57
60
  this.expire();
58
61
  return;
59
62
  }
@@ -93,7 +96,7 @@ class NativeSessionDecision {
93
96
  expire() {
94
97
  if (this.state.kind === "terminal") return;
95
98
  const cause = new AcpProviderSessionError("acp_session_interaction_expired", "The session decision expired before acceptance.");
96
- this.stop(Object.freeze({ kind: "expired" }), cause);
99
+ this.stop(Object.freeze({ kind: "expired", source: "host_policy" }), cause);
97
100
  this.input.fail(cause);
98
101
  }
99
102
  nativeAborted() {
@@ -110,9 +113,10 @@ class NativeSessionDecision {
110
113
  }
111
114
  async respond(input) {
112
115
  throwIfAgentOperationAborted(input.signal);
113
- const response = parseAgentSessionDecisionResponseFor(this.handle.request, { choiceId: input.choiceId });
116
+ if (this.input.request.kind !== "decision") throw new TypeError("Plan review requires prepared response ownership.");
117
+ const response = parseAgentSessionDecisionResponseFor(this.input.request, { kind: input.kind, choiceId: input.choiceId });
114
118
  this.observeNativeSignal();
115
- if (Date.now() >= this.input.expiresAt) this.expire();
119
+ if (this.input.hostDeadlineAt !== void 0 && Date.now() >= this.input.hostDeadlineAt) this.expire();
116
120
  if (!this.input.accepting() || this.state.kind === "terminal") {
117
121
  return Object.freeze({ status: "rejected", requestState: "unavailable", error: sessionDecisionError(
118
122
  "session_interaction_unavailable",
@@ -120,16 +124,40 @@ class NativeSessionDecision {
120
124
  ) });
121
125
  }
122
126
  this.answer.resolve(Object.freeze({ outcome: response.choiceId === "grant" ? "trust" : "reject" }));
123
- this.finalize(Object.freeze({ kind: "answered", choiceId: response.choiceId }));
127
+ this.finalize(Object.freeze({ kind: "answered", response }));
124
128
  return Object.freeze({ status: "delivered" });
125
129
  }
130
+ prepareResponse(input) {
131
+ throwIfAgentOperationAborted(input.signal);
132
+ if (this.input.request.kind !== "plan_review" || !this.input.prepareContinuation) throw new TypeError("This request has no continuation mapping.");
133
+ const response = parseAgentSessionPlanReviewResponseFor(this.input.request, input.response);
134
+ return this.input.prepareContinuation({ response, signal: input.signal, nativeSettled: this.input.nativeContext.settled, dispatch: async () => {
135
+ throwIfAgentOperationAborted(input.signal);
136
+ this.observeNativeSignal();
137
+ if (this.input.hostDeadlineAt !== void 0 && Date.now() >= this.input.hostDeadlineAt) this.expire();
138
+ if (!this.input.accepting() || this.state.kind === "terminal") return Object.freeze({
139
+ status: "rejected",
140
+ requestState: "unavailable",
141
+ error: sessionDecisionError("session_interaction_unavailable", "The plan review is no longer available.")
142
+ });
143
+ this.answer.resolve(nativePlanReviewReply(response));
144
+ this.finalize(Object.freeze({ kind: "answered", response: Object.freeze({
145
+ kind: "plan_review",
146
+ action: response.action,
147
+ choiceId: response.choiceId,
148
+ planId: response.planId,
149
+ revision: response.revision
150
+ }) }));
151
+ return Object.freeze({ status: "delivered" });
152
+ } });
153
+ }
126
154
  maybeRetire() {
127
155
  if (this.retired || this.state.kind !== "terminal" || !this.nativeSettled || !this.callbackSettled) return;
128
156
  this.retired = true;
129
157
  this.input.retire();
130
158
  }
131
159
  async join() {
132
- await Promise.allSettled([this.nativeReply, ...this.callback ? [this.callback] : []]);
160
+ await Promise.allSettled([this.input.nativeContext.settled, ...this.callback ? [this.callback] : []]);
133
161
  }
134
162
  }
135
163
  function callbackError(cause) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agen-ai/provider-acp",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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/agent-protocol": "^0.4.1",
31
- "@agen-ai/acp-transport": "^0.4.1",
32
- "@agen-ai/agent-runtime": "^0.4.1"
30
+ "@agen-ai/acp-transport": "^0.4.2",
31
+ "@agen-ai/agent-runtime": "^0.4.2",
32
+ "@agen-ai/agent-protocol": "^0.4.2"
33
33
  },
34
34
  "devDependencies": {
35
35
  "tsup": "^8.5.0"