@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.
- package/README.md +174 -14
- package/dist/capabilities.js +2 -2
- package/dist/connection/contracts.d.ts +3 -1
- package/dist/connection/initialize.js +25 -9
- package/dist/connection/negotiation.d.ts +2 -1
- package/dist/connection/negotiation.js +8 -1
- package/dist/host/interactions.d.ts +6 -1
- package/dist/host/interactions.js +42 -16
- package/dist/host/router.d.ts +3 -2
- package/dist/host/router.js +23 -20
- package/dist/instance.js +19 -9
- package/dist/node/contracts.d.ts +2 -2
- package/dist/node/index.js +1 -1
- package/dist/node/launch.js +3 -3
- package/dist/session/continuation.d.ts +33 -0
- package/dist/session/continuation.js +185 -0
- package/dist/session/descendants.d.ts +29 -0
- package/dist/session/descendants.js +97 -0
- package/dist/session/mapping.js +1 -1
- package/dist/session/native.d.ts +1 -0
- package/dist/session/native.js +8 -0
- package/dist/session/opening.js +1 -1
- package/dist/session/requests/elicitation.d.ts +1 -1
- package/dist/session/requests/elicitation.js +1 -1
- package/dist/session/requests/grokWithdrawals.d.ts +28 -0
- package/dist/session/requests/grokWithdrawals.js +135 -0
- package/dist/session/requests/pending.d.ts +17 -4
- package/dist/session/requests/pending.js +63 -7
- package/dist/session/requests/resolution.js +4 -2
- package/dist/session/session.d.ts +8 -0
- package/dist/session/session.js +140 -32
- package/dist/session/turn.d.ts +6 -1
- package/dist/sessionInteractions/broker.d.ts +11 -3
- package/dist/sessionInteractions/broker.js +35 -13
- package/dist/sessionInteractions/extensions.d.ts +2 -1
- package/dist/sessionInteractions/extensions.js +21 -1
- package/dist/sessionInteractions/grokFolderTrust.d.ts +0 -1
- package/dist/sessionInteractions/grokFolderTrust.js +0 -1
- package/dist/sessionInteractions/grokPlanReview.d.ts +16 -0
- package/dist/sessionInteractions/grokPlanReview.js +63 -0
- package/dist/sessionInteractions/request.d.ts +15 -7
- package/dist/sessionInteractions/request.js +46 -18
- 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
|
|
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?:
|
|
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.
|
|
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
|
-
|
|
34
|
-
|
|
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,
|
|
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
|
-
|
|
19
|
-
|
|
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;
|