@anureo/adapter-acp 0.2.0 → 0.3.0
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 +60 -14
- package/package.json +15 -16
- package/src/adapter.d.ts +214 -0
- package/src/adapter.js +944 -0
- package/src/diagnostics.d.ts +36 -0
- package/src/diagnostics.js +91 -0
- package/src/dsh.d.ts +32 -0
- package/src/dsh.js +31 -0
- package/src/extensions.d.ts +25 -0
- package/src/extensions.js +30 -0
- package/src/file-changes.d.ts +9 -0
- package/src/file-changes.js +29 -0
- package/src/index.d.ts +3 -0
- package/src/index.js +3 -0
- package/src/mapping.d.ts +119 -0
- package/src/mapping.js +504 -0
- package/src/options.d.ts +103 -0
- package/src/options.js +0 -0
- package/src/request-error.d.ts +6 -0
- package/src/request-error.js +22 -0
- package/src/stdio.d.ts +9 -0
- package/src/stdio.js +108 -0
- package/src/stop.d.ts +18 -0
- package/src/stop.js +30 -0
- package/dist/file-changes.d.ts +0 -3
- package/dist/index.d.ts +0 -98
- package/dist/index.js +0 -852
- package/dist/mapping.d.ts +0 -30
package/src/stdio.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Adapts a subprocess to ACP's framed message streams and drains startup diagnostics.
|
|
2
|
+
import * as acp from '@agentclientprotocol/sdk';
|
|
3
|
+
import { HarnessSdkError, spawnHarnessProcess } from '@anureo/sdk';
|
|
4
|
+
import { StderrTail } from "./diagnostics.js";
|
|
5
|
+
/**
|
|
6
|
+
* Start the owned stdio channel used by AcpAdapter.initialize when connect is omitted.
|
|
7
|
+
* Reject with HARNESS_UNAVAILABLE for a missing command or failed spawn. Resolve after spawning
|
|
8
|
+
* so initialize can negotiate ACP over the returned stream and await channel disposal on teardown.
|
|
9
|
+
* AcpAdapter.withDiagnostics reads the tail captured so far when annotating handshake or
|
|
10
|
+
* connection-closure failures.
|
|
11
|
+
*/
|
|
12
|
+
export async function processStream(options) {
|
|
13
|
+
if (!options.command)
|
|
14
|
+
throw new HarnessSdkError('HARNESS_UNAVAILABLE', 'An ACP executable or stream factory is required');
|
|
15
|
+
const child = await spawnHarnessProcess({
|
|
16
|
+
command: options.command,
|
|
17
|
+
args: options.args,
|
|
18
|
+
cwd: options.cwd,
|
|
19
|
+
env: options.env,
|
|
20
|
+
});
|
|
21
|
+
const incoming = framedStdout(child.stdout, options);
|
|
22
|
+
const outgoing = framedStdin(child, options);
|
|
23
|
+
const tail = captureStderr(child, options);
|
|
24
|
+
return {
|
|
25
|
+
stream: acp.ndJsonStream(outgoing, incoming),
|
|
26
|
+
dispose: () => child.dispose(),
|
|
27
|
+
diagnostics: () => tail.text(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Supply ACP's ndJSON decoder with guarded stdout chunks. EOF closes the stream, and read or
|
|
32
|
+
* limit failures error it. Cancelling calls the stdout iterator's return method when present.
|
|
33
|
+
* Channel disposal owns child teardown.
|
|
34
|
+
*/
|
|
35
|
+
function framedStdout(stdout, options) {
|
|
36
|
+
const iterator = stdout[Symbol.asyncIterator]();
|
|
37
|
+
const checkIncoming = frameGuard(options, 'ACP frame exceeds its limit');
|
|
38
|
+
return new ReadableStream({
|
|
39
|
+
async pull(controller) {
|
|
40
|
+
try {
|
|
41
|
+
const value = await iterator.next();
|
|
42
|
+
if (value.done) {
|
|
43
|
+
controller.close();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
checkIncoming(value.value);
|
|
47
|
+
controller.enqueue(value.value);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
controller.error(error);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
async cancel() { await iterator.return?.(); },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Guard bytes from ACP's ndJSON encoder before forwarding them to child stdin. Writes await the
|
|
58
|
+
* process write callback and propagate limit or pipe failures. Closing ends stdin.
|
|
59
|
+
*/
|
|
60
|
+
function framedStdin(child, options) {
|
|
61
|
+
const checkOutgoing = frameGuard(options, 'ACP outgoing frame exceeds its limit');
|
|
62
|
+
return new WritableStream({
|
|
63
|
+
write(chunk) {
|
|
64
|
+
checkOutgoing(chunk);
|
|
65
|
+
return child.write(chunk);
|
|
66
|
+
},
|
|
67
|
+
close: () => child.closeInput(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Keep one direction's frame length across chunks, resetting on LF. Throw BACKPRESSURE_EXCEEDED
|
|
72
|
+
* before the caller forwards a chunk that takes a frame over the limit (8 MiB by default).
|
|
73
|
+
*/
|
|
74
|
+
function frameGuard(options, message) {
|
|
75
|
+
let lineBytes = 0;
|
|
76
|
+
return (chunk) => {
|
|
77
|
+
for (const byte of chunk) {
|
|
78
|
+
if (byte === 10) {
|
|
79
|
+
lineBytes = 0;
|
|
80
|
+
}
|
|
81
|
+
else if (++lineBytes > (options.maxFrameBytes ?? 8 * 1024 * 1024)) {
|
|
82
|
+
throw new HarnessSdkError('BACKPRESSURE_EXCEEDED', message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Start draining immediately so startup stderr is available even when no ACP frame arrives.
|
|
89
|
+
* processStream exposes the live tail as channel diagnostics. A pipe read failure ends capture
|
|
90
|
+
* and flushes buffered text without failing the protocol. Channel disposal does not await the drain.
|
|
91
|
+
*/
|
|
92
|
+
function captureStderr(child, options) {
|
|
93
|
+
const tail = new StderrTail(options.stderrTailLines ?? 200, options.onStderr);
|
|
94
|
+
const decoder = new TextDecoder();
|
|
95
|
+
void (async () => {
|
|
96
|
+
try {
|
|
97
|
+
for await (const chunk of child.stderr)
|
|
98
|
+
tail.write(decoder.decode(chunk, { stream: true }));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Pipe teardown can race the drain.
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
tail.flush();
|
|
105
|
+
}
|
|
106
|
+
})();
|
|
107
|
+
return tail;
|
|
108
|
+
}
|
package/src/stop.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TurnCompletedEvent } from '@anureo/protocol';
|
|
2
|
+
/** Status published in the AHP turn/completed payload. */
|
|
3
|
+
export type TurnStatus = TurnCompletedEvent['payload']['status'];
|
|
4
|
+
/** Internal prompt outcome with the terminal turn status and any preceding warning. */
|
|
5
|
+
export interface StopOutcome {
|
|
6
|
+
status: TurnStatus;
|
|
7
|
+
notice?: {
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Classify a prompt stop reason for AcpBackendSession.submitStandardInput, which publishes any
|
|
14
|
+
* warning before turn/completed. A lookup miss or non-string value yields a failed outcome
|
|
15
|
+
* with ACP_STOP_UNKNOWN.
|
|
16
|
+
* Treat returned table entries as read-only because later prompts reuse them.
|
|
17
|
+
*/
|
|
18
|
+
export declare function stopOutcome(stopReason: unknown): StopOutcome;
|
package/src/stop.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify a prompt stop reason for AcpBackendSession.submitStandardInput, which publishes any
|
|
3
|
+
* warning before turn/completed. A lookup miss or non-string value yields a failed outcome
|
|
4
|
+
* with ACP_STOP_UNKNOWN.
|
|
5
|
+
* Treat returned table entries as read-only because later prompts reuse them.
|
|
6
|
+
*/
|
|
7
|
+
export function stopOutcome(stopReason) {
|
|
8
|
+
const key = typeof stopReason === 'string' ? stopReason : '';
|
|
9
|
+
return STOP_REASONS[key] ?? {
|
|
10
|
+
status: 'failed',
|
|
11
|
+
notice: { code: 'ACP_STOP_UNKNOWN', message: 'Backend stopped for an unclassified reason' },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* AHP has no truncated status. Limits complete the turn with a warning so observers
|
|
16
|
+
* can distinguish a response that hit a limit from an ordinary end_turn.
|
|
17
|
+
*/
|
|
18
|
+
const STOP_REASONS = {
|
|
19
|
+
end_turn: { status: 'completed' },
|
|
20
|
+
max_tokens: {
|
|
21
|
+
status: 'completed',
|
|
22
|
+
notice: { code: 'ACP_STOP_MAX_TOKENS', message: 'Turn ended at the model output limit; the reply may be truncated' },
|
|
23
|
+
},
|
|
24
|
+
max_turn_requests: {
|
|
25
|
+
status: 'completed',
|
|
26
|
+
notice: { code: 'ACP_STOP_MAX_TURN_REQUESTS', message: 'Turn ended at the agent turn limit' },
|
|
27
|
+
},
|
|
28
|
+
cancelled: { status: 'interrupted' },
|
|
29
|
+
refusal: { status: 'failed' },
|
|
30
|
+
};
|
package/dist/file-changes.d.ts
DELETED
package/dist/index.d.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import * as acp from '@agentclientprotocol/sdk';
|
|
2
|
-
import { type HarnessAdapter, type BackendSession, type SessionEventSink, type SessionMethod } from '@anureo/sdk';
|
|
3
|
-
import { type Capabilities, type SessionBinding, type SessionOpenParams, type SessionState, type InitializeParams, type InitializeResult, type AhpRequestMap, type AhpResultMap, type JsonObject } from '@anureo/protocol';
|
|
4
|
-
export interface AcpFileSystemPort {
|
|
5
|
-
readTextFile?: (params: acp.ReadTextFileRequest) => Promise<acp.ReadTextFileResponse>;
|
|
6
|
-
writeTextFile?: (params: acp.WriteTextFileRequest) => Promise<acp.WriteTextFileResponse | void>;
|
|
7
|
-
}
|
|
8
|
-
export interface AcpTerminalPort {
|
|
9
|
-
createTerminal(params: acp.CreateTerminalRequest): Promise<acp.CreateTerminalResponse>;
|
|
10
|
-
terminalOutput(params: acp.TerminalOutputRequest): Promise<acp.TerminalOutputResponse>;
|
|
11
|
-
releaseTerminal(params: acp.ReleaseTerminalRequest): Promise<acp.ReleaseTerminalResponse | void>;
|
|
12
|
-
waitForTerminalExit(params: acp.WaitForTerminalExitRequest): Promise<acp.WaitForTerminalExitResponse>;
|
|
13
|
-
killTerminal(params: acp.KillTerminalRequest): Promise<acp.KillTerminalResponse | void>;
|
|
14
|
-
}
|
|
15
|
-
export interface AcpAdapterOptions {
|
|
16
|
-
command?: string;
|
|
17
|
-
args?: readonly string[];
|
|
18
|
-
cwd?: string;
|
|
19
|
-
env?: Record<string, string | undefined>;
|
|
20
|
-
fs?: AcpFileSystemPort;
|
|
21
|
-
terminal?: AcpTerminalPort;
|
|
22
|
-
/** Inject an official v1 message stream for remote transports or deterministic wire tests. */
|
|
23
|
-
connect?: () => Promise<{
|
|
24
|
-
stream: acp.Stream;
|
|
25
|
-
dispose(): Promise<void>;
|
|
26
|
-
}>;
|
|
27
|
-
requestTimeoutMs?: number;
|
|
28
|
-
maxFrameBytes?: number;
|
|
29
|
-
/** session/load snapshots have no turn boundaries; expose them separately from AHP realtime events. */
|
|
30
|
-
onLoadSnapshot?: (binding: SessionBinding, updates: readonly JsonObject[]) => void;
|
|
31
|
-
}
|
|
32
|
-
type ExtensionKey = 'fork' | 'revert' | 'steer' | 'history' | 'goalSet' | 'goalClear' | 'compact' | 'review';
|
|
33
|
-
interface Extensions {
|
|
34
|
-
methods: Partial<Record<ExtensionKey, string>>;
|
|
35
|
-
fork: Array<'before' | 'through'>;
|
|
36
|
-
revert: Array<'conversation' | 'files' | 'both'>;
|
|
37
|
-
subagents: boolean;
|
|
38
|
-
instructions: boolean;
|
|
39
|
-
userInput: boolean;
|
|
40
|
-
dynamicTool: boolean;
|
|
41
|
-
}
|
|
42
|
-
export declare class AcpAdapter implements HarnessAdapter {
|
|
43
|
-
readonly options: AcpAdapterOptions;
|
|
44
|
-
private connection;
|
|
45
|
-
private channel;
|
|
46
|
-
private info;
|
|
47
|
-
readonly sessions: Map<string, AcpBackendSession>;
|
|
48
|
-
private nativeSessions;
|
|
49
|
-
private opening;
|
|
50
|
-
private openTail;
|
|
51
|
-
private readonly deliveries;
|
|
52
|
-
private closing;
|
|
53
|
-
extension: Extensions;
|
|
54
|
-
capabilities: Capabilities;
|
|
55
|
-
constructor(options: AcpAdapterOptions);
|
|
56
|
-
get nativeCapabilities(): acp.AgentCapabilities;
|
|
57
|
-
get agent(): acp.ClientContext;
|
|
58
|
-
private backend;
|
|
59
|
-
private interaction;
|
|
60
|
-
initialize(params: InitializeParams): Promise<InitializeResult>;
|
|
61
|
-
private runtime;
|
|
62
|
-
call<M extends acp.AgentRequestMethod>(method: M, params: acp.AgentRequestParamsByMethod[M]): Promise<acp.AgentRequestResponsesByMethod[M]>;
|
|
63
|
-
call(method: string, params: unknown): Promise<unknown>;
|
|
64
|
-
private extensionInteraction;
|
|
65
|
-
openSession(params: SessionOpenParams, sink: SessionEventSink): Promise<BackendSession>;
|
|
66
|
-
dispose(): Promise<void>;
|
|
67
|
-
}
|
|
68
|
-
export declare class AcpBackendSession implements BackendSession {
|
|
69
|
-
readonly adapter: AcpAdapter;
|
|
70
|
-
private readonly params;
|
|
71
|
-
readonly sink: SessionEventSink;
|
|
72
|
-
nativeId: string;
|
|
73
|
-
activeTurn: string | undefined;
|
|
74
|
-
hydrating: boolean;
|
|
75
|
-
readonly loadedSnapshot: JsonObject[];
|
|
76
|
-
readonly terminals: Set<string>;
|
|
77
|
-
readonly capabilities: Capabilities;
|
|
78
|
-
private config;
|
|
79
|
-
private configKeys;
|
|
80
|
-
private configurationData;
|
|
81
|
-
private capabilityRevision;
|
|
82
|
-
private sessionTitle?;
|
|
83
|
-
private closed;
|
|
84
|
-
private readonly mapper;
|
|
85
|
-
private snapshotBytes;
|
|
86
|
-
constructor(adapter: AcpAdapter, params: SessionOpenParams, sink: SessionEventSink);
|
|
87
|
-
get binding(): SessionBinding;
|
|
88
|
-
state(): SessionState;
|
|
89
|
-
configure(data: Record<string, unknown>): void;
|
|
90
|
-
update(data: Record<string, unknown>): Promise<void>;
|
|
91
|
-
requireTerminal(id: string): void;
|
|
92
|
-
setConfiguration(patch: JsonObject): Promise<SessionState>;
|
|
93
|
-
private extension;
|
|
94
|
-
execute<M extends SessionMethod>(method: M, params: AhpRequestMap[M]): Promise<AhpResultMap[M]>;
|
|
95
|
-
dispose(): Promise<void>;
|
|
96
|
-
}
|
|
97
|
-
export declare function createAcpAdapter(options: AcpAdapterOptions): AcpAdapter;
|
|
98
|
-
export {};
|