@agen-ai/provider-acp 0.4.1 → 0.4.3

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,135 @@
1
+ import { AcpProviderSessionError } from "../errors.js";
2
+ import { nativeIdentity, nativeObject } from "../native.js";
3
+ import { deferred } from "../queue.js";
4
+ const PERMISSION_STATE_OVERHEAD_BYTES = 256;
5
+ class GrokChildPermissionWithdrawals {
6
+ constructor(input) {
7
+ this.input = input;
8
+ }
9
+ children = /* @__PURE__ */ new Map();
10
+ requests = /* @__PURE__ */ new Map();
11
+ observe(identity, update) {
12
+ if (identity.attemptId === void 0 || update.sessionUpdate !== "pending_interaction" && update.sessionUpdate !== "interaction_resolved") return;
13
+ if (update.sessionUpdate === "pending_interaction" && update.kind !== "permission") return;
14
+ const toolId = nativeIdentity(update.tool_call_id, "child permission tool identity");
15
+ const child = this.child(identity);
16
+ const prior = child.interactions.get(toolId);
17
+ if (update.sessionUpdate === "pending_interaction") {
18
+ const turn2 = this.input.currentTurn();
19
+ if (child.finished || prior?.phase.kind === "retired" || !turn2 || turn2.nativeSettled || turn2.terminal || prior && prior.phase.turn !== turn2) throw this.stale();
20
+ if (!prior) child.interactions.set(toolId, {
21
+ phase: { kind: "announced", turn: turn2 },
22
+ release: this.input.reserveState({ childSessionId: identity.childSessionId, toolId }, PERMISSION_STATE_OVERHEAD_BYTES)
23
+ });
24
+ return;
25
+ }
26
+ if (!prior) {
27
+ child.interactions.set(toolId, {
28
+ phase: { kind: "retired" },
29
+ release: this.input.reserveState({ childSessionId: identity.childSessionId, toolId }, PERMISSION_STATE_OVERHEAD_BYTES)
30
+ });
31
+ return;
32
+ }
33
+ if (prior.phase.kind === "announced") {
34
+ prior.phase = { kind: "retired" };
35
+ return;
36
+ }
37
+ if (prior.phase.kind !== "attached") return;
38
+ const { turn, request } = prior.phase;
39
+ if (turn !== this.input.currentTurn() || turn.nativeSettled || turn.terminal) throw this.stale();
40
+ const hold = request.answered ? void 0 : { ready: deferred(), timer: setTimeout(
41
+ () => this.input.fail(
42
+ new AcpProviderSessionError("acp_native_permission_uncertain", "The resolved native child permission lacks exact cancellation corroboration.")
43
+ ),
44
+ this.input.timeoutMs
45
+ ) };
46
+ if (hold) void hold.ready.promise.catch(() => {
47
+ });
48
+ prior.phase = { kind: "resolved", turn, request, hold };
49
+ }
50
+ attach(request) {
51
+ if (request.kind !== "permission") return;
52
+ const tool = nativeObject(request.params.toolCall, "permission tool");
53
+ const meta = tool._meta === void 0 ? void 0 : nativeObject(tool._meta, "permission tool metadata");
54
+ if (meta?.["x.ai/requestingSessionId"] === void 0) return;
55
+ const childId = nativeIdentity(meta["x.ai/requestingSessionId"], "requesting child session identity");
56
+ if (childId === request.params.sessionId) return;
57
+ const child = this.children.get(childId);
58
+ if (!child) return;
59
+ const interaction = child.interactions.get(nativeIdentity(tool.toolCallId, "permission tool identity"));
60
+ if (child.finished || interaction && (interaction.phase.kind !== "announced" || interaction.phase.turn !== this.input.currentTurn())) throw this.stale();
61
+ if (!interaction || interaction.phase.kind !== "announced") return;
62
+ interaction.phase = { kind: "attached", turn: interaction.phase.turn, request };
63
+ this.requests.set(request, interaction);
64
+ }
65
+ finish(identity, update) {
66
+ if (identity.attemptId === void 0) return;
67
+ if (nativeIdentity(update.subagent_id, "finished subagent identity") !== identity.subagentId || nativeIdentity(update.attempt_id, "finished attempt identity") !== identity.attemptId) throw new AcpProviderSessionError(
68
+ "acp_native_permission_identity_conflict",
69
+ "The native child finish does not identify its exact announced attempt."
70
+ );
71
+ const child = this.child(identity);
72
+ if (child.finished) return;
73
+ child.finished = true;
74
+ for (const interaction of child.interactions.values()) {
75
+ const phase = interaction.phase;
76
+ if (phase.kind !== "attached" && phase.kind !== "resolved" || phase.turn !== this.input.currentTurn() || phase.turn.nativeSettled || phase.turn.terminal) continue;
77
+ if (phase.kind !== "resolved" || update.status !== "cancelled") {
78
+ if (!phase.request.answered) throw new AcpProviderSessionError(
79
+ "acp_native_permission_uncertain",
80
+ "The native child finish lacks the exact ordered permission cancellation facts."
81
+ );
82
+ continue;
83
+ }
84
+ this.input.withdraw(phase.request);
85
+ this.retire(interaction);
86
+ }
87
+ }
88
+ answerGate(request) {
89
+ const phase = this.requests.get(request)?.phase;
90
+ return phase?.kind === "resolved" ? phase.hold?.ready.promise : void 0;
91
+ }
92
+ settle(request) {
93
+ const interaction = this.requests.get(request);
94
+ if (interaction) this.retire(interaction);
95
+ this.requests.delete(request);
96
+ }
97
+ child(identity) {
98
+ let child = this.children.get(identity.childSessionId);
99
+ if (!child) {
100
+ child = { interactions: /* @__PURE__ */ new Map(), finished: false, release: this.input.reserveState({
101
+ childSessionId: identity.childSessionId,
102
+ subagentId: identity.subagentId,
103
+ attemptId: identity.attemptId
104
+ }) };
105
+ this.children.set(identity.childSessionId, child);
106
+ }
107
+ return child;
108
+ }
109
+ retire(interaction, error) {
110
+ const phase = interaction.phase;
111
+ if (phase.kind === "resolved" && phase.hold) {
112
+ clearTimeout(phase.hold.timer);
113
+ if (error) phase.hold.ready.reject(error);
114
+ else phase.hold.ready.resolve();
115
+ }
116
+ interaction.phase = { kind: "retired" };
117
+ }
118
+ stale() {
119
+ return new AcpProviderSessionError("acp_native_permission_stale", "The native child permission does not identify a fresh callback in its original turn.");
120
+ }
121
+ stop(error) {
122
+ for (const child of this.children.values()) {
123
+ for (const interaction of child.interactions.values()) {
124
+ this.retire(interaction, error);
125
+ interaction.release();
126
+ }
127
+ child.release();
128
+ }
129
+ this.children.clear();
130
+ this.requests.clear();
131
+ }
132
+ }
133
+ export {
134
+ GrokChildPermissionWithdrawals
135
+ };
@@ -1,5 +1,5 @@
1
- import type { AcpJsonObject, AcpJsonValue, AcpReverseRequestContext } from '@agen-ai/acp-transport';
2
- import type { AgentRequest } from '@agen-ai/agent-protocol';
1
+ import { type AcpJsonObject, type AcpJsonValue, type AcpReverseRequestContext } from '@agen-ai/acp-transport';
2
+ import type { AgentRequest, AgentRequestId } from '@agen-ai/agent-protocol';
3
3
  import type { MappedPermission } from '../mapping.js';
4
4
  import { deferred, type QueueEntry } from '../queue.js';
5
5
  import type { AcpFormFlow, PreparedAcpForm } from './forms.js';
@@ -16,12 +16,16 @@ export interface PendingSessionRequest {
16
16
  readonly kind: 'permission' | 'form';
17
17
  readonly params: AcpJsonObject;
18
18
  readonly form?: PreparedAcpForm;
19
- readonly host?: () => Promise<AcpJsonValue>;
19
+ readonly host?: (observeCallback: (work: Promise<void>) => void) => Promise<AcpJsonValue>;
20
+ hostWork: Promise<void> | undefined;
20
21
  readonly response: ReturnType<typeof deferred<AcpJsonValue>>;
21
22
  readonly signal: AbortSignal;
23
+ readonly transportSettled: Promise<void>;
22
24
  entry: QueueEntry<unknown> | undefined;
23
25
  mapped: MappedSessionRequest | undefined;
24
26
  answered: boolean;
27
+ publishedRequestId: AgentRequestId | undefined;
28
+ unavailable: boolean;
25
29
  readonly onAbort: () => void;
26
30
  }
27
31
  export declare function canceledRequestReply(kind: PendingSessionRequest['kind']): AcpJsonValue;
@@ -29,14 +33,23 @@ export declare function canceledRequestReply(kind: PendingSessionRequest['kind']
29
33
  export declare class PendingSessionRequests {
30
34
  private readonly input;
31
35
  private readonly pending;
36
+ private readonly retained;
37
+ private retainedBytes;
32
38
  constructor(input: {
33
39
  readonly maxPendingRequests: number;
40
+ readonly maxRequestMaterialBytes: number;
34
41
  readonly admit: (request: PendingSessionRequest, bytes: number) => QueueEntry<unknown>;
35
42
  readonly fail: (error: Error) => void;
43
+ readonly unavailable: (request: PendingSessionRequest, requestId: AgentRequestId) => void;
44
+ readonly settled: (request: PendingSessionRequest) => void;
45
+ readonly admitted?: (request: PendingSessionRequest) => void;
36
46
  });
37
47
  get size(): number;
38
- admit(context: AcpReverseRequestContext, kind: PendingSessionRequest['kind'], params: AcpJsonObject, form?: PreparedAcpForm, host?: () => Promise<AcpJsonValue>): Promise<AcpJsonValue>;
48
+ admit(context: AcpReverseRequestContext, kind: PendingSessionRequest['kind'], params: AcpJsonObject, form?: PreparedAcpForm, host?: PendingSessionRequest['host']): Promise<AcpJsonValue>;
39
49
  canAnswer(request: PendingSessionRequest): boolean;
50
+ withdraw(request: PendingSessionRequest, error: Error): void;
51
+ invokeHost(request: PendingSessionRequest): Promise<AcpJsonValue>;
52
+ join(): Promise<void>;
40
53
  answer(request: PendingSessionRequest, result: AcpJsonValue): boolean;
41
54
  reject(request: PendingSessionRequest, error: Error): void;
42
55
  private release;
@@ -1,3 +1,4 @@
1
+ import { AcpRequestError } from "@agen-ai/acp-transport";
1
2
  import { AcpProviderSessionError } from "../errors.js";
2
3
  import { nativeBytes } from "../native.js";
3
4
  import { deferred } from "../queue.js";
@@ -9,16 +10,24 @@ class PendingSessionRequests {
9
10
  this.input = input;
10
11
  }
11
12
  pending = /* @__PURE__ */ new Set();
13
+ retained = /* @__PURE__ */ new Set();
14
+ retainedBytes = 0;
12
15
  get size() {
13
16
  return this.pending.size;
14
17
  }
15
18
  admit(context, kind, params, form, host) {
16
- if (this.pending.size >= this.input.maxPendingRequests) {
19
+ if (this.retained.size >= this.input.maxPendingRequests) {
17
20
  const error = new AcpProviderSessionError("acp_request_queue_full", "The ACP pending request budget is exhausted.");
18
21
  this.input.fail(error);
19
22
  throw error;
20
23
  }
21
24
  const response = deferred();
25
+ const bytes = nativeBytes(params) * 2 + (form ? nativeBytes(form) : 0);
26
+ if (this.retainedBytes + bytes > this.input.maxRequestMaterialBytes) {
27
+ const error = new AcpProviderSessionError("acp_session_queue_full", "The ACP retained request material budget is exhausted.");
28
+ this.input.fail(error);
29
+ throw error;
30
+ }
22
31
  const request = {
23
32
  kind,
24
33
  params,
@@ -26,18 +35,37 @@ class PendingSessionRequests {
26
35
  ...host === void 0 ? {} : { host },
27
36
  response,
28
37
  signal: context.signal,
38
+ transportSettled: context.settled,
29
39
  entry: void 0,
30
40
  mapped: void 0,
31
41
  answered: false,
42
+ publishedRequestId: void 0,
43
+ unavailable: false,
44
+ hostWork: void 0,
32
45
  onAbort: () => {
33
- this.cancel(request);
34
- this.input.fail(new AcpProviderSessionError("acp_request_revoked", "The peer canceled a pending ACP interaction request."));
46
+ const cause = context.signal.reason;
47
+ if (cause instanceof AcpRequestError && cause.reasonCode === "acp_reverse_request_canceled" && cause.requestId === context.id) {
48
+ this.withdraw(request, cause);
49
+ return;
50
+ }
51
+ this.input.fail(cause instanceof Error ? cause : new AcpProviderSessionError("acp_request_revoked", "The native callback producer stopped."));
35
52
  }
36
53
  };
37
54
  try {
38
- request.entry = this.input.admit(request, nativeBytes(params) * 2 + (form ? nativeBytes(form) : 0));
55
+ request.entry = this.input.admit(request, bytes);
39
56
  this.pending.add(request);
57
+ this.retained.add(request);
58
+ this.retainedBytes += bytes;
59
+ void context.settled.then(async () => {
60
+ await request.hostWork;
61
+ context.signal.removeEventListener("abort", request.onAbort);
62
+ this.retained.delete(request);
63
+ this.retainedBytes -= bytes;
64
+ request.entry?.release();
65
+ this.input.settled(request);
66
+ });
40
67
  context.signal.addEventListener("abort", request.onAbort, { once: true });
68
+ this.input.admitted?.(request);
41
69
  if (context.signal.aborted) request.onAbort();
42
70
  return response.promise;
43
71
  } catch (error) {
@@ -46,7 +74,37 @@ class PendingSessionRequests {
46
74
  }
47
75
  }
48
76
  canAnswer(request) {
49
- return this.pending.has(request) && !request.answered && !request.signal.aborted;
77
+ return this.pending.has(request) && !request.answered && !request.unavailable && !request.signal.aborted;
78
+ }
79
+ withdraw(request, error) {
80
+ if (request.unavailable) return;
81
+ request.unavailable = true;
82
+ if (request.publishedRequestId !== void 0 && request.publishedRequestId === request.mapped?.request.requestId) {
83
+ this.input.unavailable(request, request.publishedRequestId);
84
+ }
85
+ if (!request.answered) this.reject(request, error);
86
+ }
87
+ invokeHost(request) {
88
+ if (!request.host) throw new TypeError("The native request has no host callback.");
89
+ const joined = deferred();
90
+ request.hostWork = joined.promise;
91
+ let callback = Promise.resolve();
92
+ let work;
93
+ try {
94
+ work = request.host((actual) => {
95
+ callback = actual;
96
+ });
97
+ } catch (cause) {
98
+ work = Promise.reject(cause);
99
+ }
100
+ void work.then(() => callback, () => callback).then(joined.resolve, () => joined.resolve());
101
+ return work;
102
+ }
103
+ async join() {
104
+ while (this.retained.size > 0) await Promise.all([...this.retained].map(async (request) => {
105
+ await request.transportSettled;
106
+ await request.hostWork;
107
+ }));
50
108
  }
51
109
  answer(request, result) {
52
110
  if (!this.canAnswer(request) || !this.release(request)) return false;
@@ -60,9 +118,7 @@ class PendingSessionRequests {
60
118
  release(request) {
61
119
  if (!this.pending.has(request) || request.answered) return false;
62
120
  request.answered = true;
63
- request.signal.removeEventListener("abort", request.onAbort);
64
121
  this.pending.delete(request);
65
- request.entry?.release();
66
122
  return true;
67
123
  }
68
124
  cancelAll() {
@@ -15,8 +15,10 @@ function resolveAcpRequest(requests, turn, request, mapped, input) {
15
15
  request.mapped = { ...mapped, request: mapped.flow.request };
16
16
  }
17
17
  if (reply !== void 0 && !requests.answer(request, reply)) return requestUnavailable();
18
- turn.waiting = void 0;
19
- turn.transition = reply === void 0 ? { nextRequest: request } : {};
18
+ if (reply === void 0) {
19
+ turn.waiting = void 0;
20
+ turn.transition = { nextRequest: request };
21
+ } else turn.transition = {};
20
22
  return { status: "delivered" };
21
23
  }
22
24
  function requestUnavailable() {
@@ -21,6 +21,9 @@ export declare class SessionState {
21
21
  private closePromise;
22
22
  private itemSequence;
23
23
  private requestSequence;
24
+ private readonly continuations;
25
+ private readonly descendants;
26
+ private readonly childWithdrawals;
24
27
  constructor(input: CreateAcpProviderSessionInput);
25
28
  open(): Promise<AgentProviderSession>;
26
29
  private requireUsable;
@@ -31,7 +34,12 @@ export declare class SessionState {
31
34
  private observeMetadata;
32
35
  private admitRequest;
33
36
  private clearTurnTimers;
37
+ private streamContinuation;
34
38
  startTurn(input: AgentProviderStartTurnInput): AgentProviderTurnExecution;
39
+ private createNativeTurn;
40
+ private execution;
41
+ private ownedStream;
42
+ private completeNativeTurn;
35
43
  private launchTurn;
36
44
  private emit;
37
45
  private drain;