@llm4ts/core 2.2.0 → 2.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.
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import type * as Scope from "effect/Scope";
|
|
3
|
+
import * as Stream from "effect/Stream";
|
|
4
|
+
import { ProviderError, type LlmError } from "../Errors.ts";
|
|
5
|
+
import type { ProcessExecutorShape } from "../ProcessExecutor.ts";
|
|
6
|
+
import { type JsonRecord, type JsonValue } from "./CliSupport.ts";
|
|
7
|
+
/**
|
|
8
|
+
* ACP (Agent Client Protocol, `gemini --experimental-acp`) message shapes
|
|
9
|
+
* are best-effort against public docs (geminicli.com/docs/cli/acp-mode,
|
|
10
|
+
* agentclientprotocol.com) — there is no installed `gemini` binary in CI to
|
|
11
|
+
* verify against (the repo's CI constraint is exactly "no installed
|
|
12
|
+
* provider CLIs"). Tests here exercise this module against a scripted fake
|
|
13
|
+
* peer, the same posture ClaudeAgentSession.test.ts takes against a fake
|
|
14
|
+
* `claude` process. A first live run against a real `gemini` is an
|
|
15
|
+
* integration smoke test, not a substitute for that determinism.
|
|
16
|
+
*/
|
|
17
|
+
export declare const geminiAcpArgv: (model: string | undefined) => ReadonlyArray<string>;
|
|
18
|
+
export declare const acpInitializeParams: JsonRecord;
|
|
19
|
+
export declare const acpNewSessionParams: (cwd: string, mcpServerUrl: string) => JsonRecord;
|
|
20
|
+
export declare const acpPromptParams: (sessionId: string, text: string) => JsonRecord;
|
|
21
|
+
export declare const acpInitializeLine: (id: number) => string;
|
|
22
|
+
export declare const acpNewSessionLine: (id: number, cwd: string, mcpServerUrl: string) => string;
|
|
23
|
+
export declare const acpPromptLine: (id: number, sessionId: string, text: string) => string;
|
|
24
|
+
export declare const acpPermissionResponseLine: (id: JsonValue, approved: boolean) => string;
|
|
25
|
+
export declare const acpUnsupportedFsResponseLine: (id: JsonValue) => string;
|
|
26
|
+
export declare class AcpTextChunk {
|
|
27
|
+
readonly text: string;
|
|
28
|
+
readonly _tag = "TextChunk";
|
|
29
|
+
constructor(text: string);
|
|
30
|
+
}
|
|
31
|
+
export declare class AcpToolCallObserved {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly input: JsonValue;
|
|
35
|
+
readonly _tag = "ToolCallObserved";
|
|
36
|
+
constructor(id: string, name: string, input: JsonValue);
|
|
37
|
+
}
|
|
38
|
+
export declare class AcpPermissionRequested {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly toolCallId: string;
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly _tag = "PermissionRequested";
|
|
43
|
+
constructor(id: string, toolCallId: string, name: string);
|
|
44
|
+
}
|
|
45
|
+
export type AcpSessionEvent = AcpTextChunk | AcpToolCallObserved | AcpPermissionRequested;
|
|
46
|
+
export interface AcpPromptResult {
|
|
47
|
+
readonly text: string;
|
|
48
|
+
readonly stopReason: string;
|
|
49
|
+
}
|
|
50
|
+
export interface AcpApprovalPolicy {
|
|
51
|
+
readonly decide: (toolName: string, input: JsonValue) => Effect.Effect<boolean>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The safety decision belongs to whichever side actually executes the tool
|
|
55
|
+
* (pi, over the MCP bridge) — gemini's own permission prompt is a formality
|
|
56
|
+
* for a call this session never runs itself, so the default is to approve.
|
|
57
|
+
*/
|
|
58
|
+
export declare const alwaysApprove: AcpApprovalPolicy;
|
|
59
|
+
export interface GeminiAcpSessionShape {
|
|
60
|
+
readonly events: Stream.Stream<AcpSessionEvent, LlmError>;
|
|
61
|
+
readonly newSession: (cwd: string, mcpServerUrl: string) => Effect.Effect<string, LlmError>;
|
|
62
|
+
readonly prompt: (sessionId: string, text: string) => Effect.Effect<AcpPromptResult, LlmError>;
|
|
63
|
+
readonly cancel: Effect.Effect<void>;
|
|
64
|
+
}
|
|
65
|
+
export declare const parseAcpSessionUpdate: (params: JsonValue | undefined) => AcpSessionEvent | undefined;
|
|
66
|
+
export declare const openGeminiAcpSession: (executor: ProcessExecutorShape, argv: readonly string[], cwd: string, envVars?: Readonly<Record<string, string>> | undefined, approval?: AcpApprovalPolicy | undefined) => Effect.Effect<GeminiAcpSessionShape, ProviderError | import("../Errors.ts").UsageLimitError | import("../Errors.ts").AuthenticationError | import("../Errors.ts").InvalidRequestError | import("../Errors.ts").ParseError | import("../Errors.ts").ToolError | import("../Errors.ts").ConfigError | import("../Errors.ts").RateLimitError | import("../Errors.ts").TimeoutError | import("../Errors.ts").TurnLimitError, Scope.Scope>;
|
|
67
|
+
//# sourceMappingURL=GeminiAcpSession.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeminiAcpSession.d.ts","sourceRoot":"","sources":["../../src/providers/GeminiAcpSession.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import * as Deferred from "effect/Deferred";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as PubSub from "effect/PubSub";
|
|
4
|
+
import * as Queue from "effect/Queue";
|
|
5
|
+
import * as Ref from "effect/Ref";
|
|
6
|
+
import * as Stream from "effect/Stream";
|
|
7
|
+
import { ProviderError } from "../Errors.js";
|
|
8
|
+
import { isJsonRecord, jsonField, jsonStringField, jsonText, parseJsonLine } from "./CliSupport.js";
|
|
9
|
+
/**
|
|
10
|
+
* ACP (Agent Client Protocol, `gemini --experimental-acp`) message shapes
|
|
11
|
+
* are best-effort against public docs (geminicli.com/docs/cli/acp-mode,
|
|
12
|
+
* agentclientprotocol.com) — there is no installed `gemini` binary in CI to
|
|
13
|
+
* verify against (the repo's CI constraint is exactly "no installed
|
|
14
|
+
* provider CLIs"). Tests here exercise this module against a scripted fake
|
|
15
|
+
* peer, the same posture ClaudeAgentSession.test.ts takes against a fake
|
|
16
|
+
* `claude` process. A first live run against a real `gemini` is an
|
|
17
|
+
* integration smoke test, not a substitute for that determinism.
|
|
18
|
+
*/
|
|
19
|
+
export const geminiAcpArgv = (model) => [
|
|
20
|
+
"gemini",
|
|
21
|
+
"--experimental-acp",
|
|
22
|
+
...(model === undefined ? [] : ["-m", model])
|
|
23
|
+
];
|
|
24
|
+
const clientCapabilities = {
|
|
25
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
26
|
+
terminal: false
|
|
27
|
+
};
|
|
28
|
+
export const acpInitializeParams = {
|
|
29
|
+
protocolVersion: 1,
|
|
30
|
+
clientInfo: { name: "llm4ts", version: "1" },
|
|
31
|
+
clientCapabilities
|
|
32
|
+
};
|
|
33
|
+
export const acpNewSessionParams = (cwd, mcpServerUrl) => ({
|
|
34
|
+
cwd,
|
|
35
|
+
mcpServers: [{ type: "http", url: mcpServerUrl }]
|
|
36
|
+
});
|
|
37
|
+
export const acpPromptParams = (sessionId, text) => ({
|
|
38
|
+
sessionId,
|
|
39
|
+
prompt: [{ type: "text", text }]
|
|
40
|
+
});
|
|
41
|
+
const jsonRpcRequest = (id, method, params) => jsonText({ jsonrpc: "2.0", id, method, params });
|
|
42
|
+
export const acpInitializeLine = (id) => jsonRpcRequest(id, "initialize", acpInitializeParams);
|
|
43
|
+
export const acpNewSessionLine = (id, cwd, mcpServerUrl) => jsonRpcRequest(id, "session/new", acpNewSessionParams(cwd, mcpServerUrl));
|
|
44
|
+
export const acpPromptLine = (id, sessionId, text) => jsonRpcRequest(id, "session/prompt", acpPromptParams(sessionId, text));
|
|
45
|
+
export const acpPermissionResponseLine = (id, approved) => jsonText({
|
|
46
|
+
jsonrpc: "2.0",
|
|
47
|
+
id,
|
|
48
|
+
result: { outcome: approved ? "Approved" : "Denied" }
|
|
49
|
+
});
|
|
50
|
+
export const acpUnsupportedFsResponseLine = (id) => jsonText({
|
|
51
|
+
jsonrpc: "2.0",
|
|
52
|
+
id,
|
|
53
|
+
error: { code: -32601, message: "fs access not supported by this client" }
|
|
54
|
+
});
|
|
55
|
+
export class AcpTextChunk {
|
|
56
|
+
text;
|
|
57
|
+
_tag = "TextChunk";
|
|
58
|
+
constructor(text) {
|
|
59
|
+
this.text = text;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export class AcpToolCallObserved {
|
|
63
|
+
id;
|
|
64
|
+
name;
|
|
65
|
+
input;
|
|
66
|
+
_tag = "ToolCallObserved";
|
|
67
|
+
constructor(id, name, input) {
|
|
68
|
+
this.id = id;
|
|
69
|
+
this.name = name;
|
|
70
|
+
this.input = input;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export class AcpPermissionRequested {
|
|
74
|
+
id;
|
|
75
|
+
toolCallId;
|
|
76
|
+
name;
|
|
77
|
+
_tag = "PermissionRequested";
|
|
78
|
+
constructor(id, toolCallId, name) {
|
|
79
|
+
this.id = id;
|
|
80
|
+
this.toolCallId = toolCallId;
|
|
81
|
+
this.name = name;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The safety decision belongs to whichever side actually executes the tool
|
|
86
|
+
* (pi, over the MCP bridge) — gemini's own permission prompt is a formality
|
|
87
|
+
* for a call this session never runs itself, so the default is to approve.
|
|
88
|
+
*/
|
|
89
|
+
export const alwaysApprove = {
|
|
90
|
+
decide: () => Effect.succeed(true)
|
|
91
|
+
};
|
|
92
|
+
const contentTextOf = (value) => jsonStringField(jsonField(value, "content"), "text");
|
|
93
|
+
/** `content_chunk` and `agent_message_chunk` are both seen across ACP revisions for a text delta. */
|
|
94
|
+
const textChunkUpdate = (update) => {
|
|
95
|
+
const type = jsonStringField(update, "type");
|
|
96
|
+
return type === "content_chunk" || type === "agent_message_chunk"
|
|
97
|
+
? contentTextOf(update)
|
|
98
|
+
: undefined;
|
|
99
|
+
};
|
|
100
|
+
const toolCallUpdate = (update) => {
|
|
101
|
+
if (jsonStringField(update, "type") !== "tool_call") {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const toolCall = jsonField(update, "toolCall");
|
|
105
|
+
const id = jsonStringField(toolCall, "id");
|
|
106
|
+
return id === undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: new AcpToolCallObserved(id, jsonStringField(toolCall, "name") ?? "", jsonField(toolCall, "input") ?? {});
|
|
109
|
+
};
|
|
110
|
+
export const parseAcpSessionUpdate = (params) => {
|
|
111
|
+
const update = jsonField(params, "update");
|
|
112
|
+
const text = textChunkUpdate(update);
|
|
113
|
+
return text !== undefined && text.length > 0 ? new AcpTextChunk(text) : toolCallUpdate(update);
|
|
114
|
+
};
|
|
115
|
+
export const openGeminiAcpSession = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.open")(function* (executor, argv, cwd, envVars = {}, approval = alwaysApprove) {
|
|
116
|
+
const [stdin, stdout] = yield* executor.runBidirectional(argv, cwd, envVars);
|
|
117
|
+
const events = yield* PubSub.unbounded();
|
|
118
|
+
// Accumulated directly by the background reader below rather than via a
|
|
119
|
+
// subscription `prompt` opens on demand: a subscription opened only when
|
|
120
|
+
// `prompt` runs can race a canned/fast-replaying stdout (real or faked in
|
|
121
|
+
// tests) that the reader has already drained by then. One prompt in
|
|
122
|
+
// flight at a time (the single-active-session assumption) makes
|
|
123
|
+
// reset-then-read at each call's boundary sufficient.
|
|
124
|
+
const accumulatedText = yield* Ref.make("");
|
|
125
|
+
const nextId = yield* Ref.make(0);
|
|
126
|
+
const pending = yield* Ref.make(new Map());
|
|
127
|
+
const allocateId = Ref.updateAndGet(nextId, (current) => current + 1);
|
|
128
|
+
const registerPending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.registerPending")(function* (id) {
|
|
129
|
+
const deferred = yield* Deferred.make();
|
|
130
|
+
yield* Ref.update(pending, (current) => {
|
|
131
|
+
const next = new Map(current);
|
|
132
|
+
next.set(id, deferred);
|
|
133
|
+
return next;
|
|
134
|
+
});
|
|
135
|
+
return deferred;
|
|
136
|
+
});
|
|
137
|
+
const resolvePending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.resolvePending")(function* (id, result, error) {
|
|
138
|
+
const current = yield* Ref.get(pending);
|
|
139
|
+
const deferred = current.get(id);
|
|
140
|
+
if (deferred === undefined) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
yield* Ref.update(pending, (map) => {
|
|
144
|
+
const next = new Map(map);
|
|
145
|
+
next.delete(id);
|
|
146
|
+
return next;
|
|
147
|
+
});
|
|
148
|
+
if (error !== undefined) {
|
|
149
|
+
yield* Deferred.fail(deferred, ProviderError.make({
|
|
150
|
+
message: `Gemini ACP error: ${jsonStringField(error, "message") ?? jsonText(error)}`
|
|
151
|
+
}));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
yield* Deferred.succeed(deferred, result ?? {});
|
|
155
|
+
});
|
|
156
|
+
const respondToPermission = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.respondToPermission")(function* (requestId, toolName, input) {
|
|
157
|
+
const approved = yield* approval.decide(toolName, input);
|
|
158
|
+
yield* Queue.offer(stdin, acpPermissionResponseLine(requestId, approved));
|
|
159
|
+
});
|
|
160
|
+
const handleAgentRequest = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.handleAgentRequest")(function* (id, method, params) {
|
|
161
|
+
switch (method) {
|
|
162
|
+
case "session/request_permission": {
|
|
163
|
+
const toolCall = jsonField(params, "toolCall");
|
|
164
|
+
const toolCallId = jsonStringField(toolCall, "id") ?? "";
|
|
165
|
+
const toolName = jsonStringField(toolCall, "name") ?? "";
|
|
166
|
+
yield* PubSub.publish(events, new AcpPermissionRequested(typeof id === "string" ? id : jsonText(id), toolCallId, toolName));
|
|
167
|
+
yield* respondToPermission(id, toolName, jsonField(toolCall, "input") ?? {});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
case "fs/read_text_file":
|
|
171
|
+
case "fs/write_text_file":
|
|
172
|
+
yield* Queue.offer(stdin, acpUnsupportedFsResponseLine(id));
|
|
173
|
+
return;
|
|
174
|
+
default:
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const handleLine = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.handleLine")(function* (line) {
|
|
179
|
+
const json = parseJsonLine(line);
|
|
180
|
+
if (json === undefined || !isJsonRecord(json)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const id = jsonField(json, "id");
|
|
184
|
+
const method = jsonStringField(json, "method");
|
|
185
|
+
if (method === "session/update") {
|
|
186
|
+
const event = parseAcpSessionUpdate(jsonField(json, "params"));
|
|
187
|
+
if (event !== undefined) {
|
|
188
|
+
if (event._tag === "TextChunk") {
|
|
189
|
+
yield* Ref.update(accumulatedText, (current) => current + event.text);
|
|
190
|
+
}
|
|
191
|
+
yield* PubSub.publish(events, event);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (method !== undefined) {
|
|
196
|
+
if (id !== undefined) {
|
|
197
|
+
yield* handleAgentRequest(id, method, jsonField(json, "params"));
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (typeof id === "number") {
|
|
202
|
+
yield* resolvePending(id, jsonField(json, "result"), jsonField(json, "error"));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
yield* stdout.pipe(Stream.mapEffect(handleLine), Stream.runDrain, Effect.catch(() => Effect.void), Effect.forkScoped);
|
|
206
|
+
const call = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.call")(function* (method, params) {
|
|
207
|
+
const id = yield* allocateId;
|
|
208
|
+
const deferred = yield* registerPending(id);
|
|
209
|
+
yield* Queue.offer(stdin, jsonRpcRequest(id, method, params));
|
|
210
|
+
return yield* Deferred.await(deferred);
|
|
211
|
+
});
|
|
212
|
+
const newSession = (cwd, mcpServerUrl) => Effect.gen(function* () {
|
|
213
|
+
yield* call("initialize", acpInitializeParams);
|
|
214
|
+
const result = yield* call("session/new", acpNewSessionParams(cwd, mcpServerUrl));
|
|
215
|
+
const sessionId = jsonStringField(result, "sessionId");
|
|
216
|
+
if (sessionId === undefined) {
|
|
217
|
+
return yield* ProviderError.make({
|
|
218
|
+
message: "Gemini ACP session/new did not return a sessionId"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return sessionId;
|
|
222
|
+
});
|
|
223
|
+
const prompt = (sessionId, text) => Effect.gen(function* () {
|
|
224
|
+
yield* Ref.set(accumulatedText, "");
|
|
225
|
+
const result = yield* call("session/prompt", acpPromptParams(sessionId, text));
|
|
226
|
+
const finalText = yield* Ref.get(accumulatedText);
|
|
227
|
+
return {
|
|
228
|
+
text: finalText,
|
|
229
|
+
stopReason: jsonStringField(result, "stopReason") ?? "Completed"
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
events: Stream.fromPubSub(events),
|
|
234
|
+
newSession,
|
|
235
|
+
prompt,
|
|
236
|
+
cancel: Queue.shutdown(stdin)
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
//# sourceMappingURL=GeminiAcpSession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GeminiAcpSession.js","sourceRoot":"","sources":["../../src/providers/GeminiAcpSession.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Effect-native LLM contracts, providers, tools, evaluation, and observability",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"./providers/ConnectorFactories": "./dist/providers/ConnectorFactories.js",
|
|
47
47
|
"./providers/CopilotConnector": "./dist/providers/CopilotConnector.js",
|
|
48
48
|
"./providers/CursorConnector": "./dist/providers/CursorConnector.js",
|
|
49
|
+
"./providers/GeminiAcpSession": "./dist/providers/GeminiAcpSession.js",
|
|
49
50
|
"./providers/GeminiApiProvider": "./dist/providers/GeminiApiProvider.js",
|
|
50
51
|
"./providers/GeminiCliProvider": "./dist/providers/GeminiCliProvider.js",
|
|
51
52
|
"./providers/GeminiModels": "./dist/providers/GeminiModels.js",
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import * as Deferred from "effect/Deferred"
|
|
2
|
+
import * as Effect from "effect/Effect"
|
|
3
|
+
import * as PubSub from "effect/PubSub"
|
|
4
|
+
import * as Queue from "effect/Queue"
|
|
5
|
+
import * as Ref from "effect/Ref"
|
|
6
|
+
import type * as Scope from "effect/Scope"
|
|
7
|
+
import * as Stream from "effect/Stream"
|
|
8
|
+
import { ProviderError, type LlmError } from "../Errors.ts"
|
|
9
|
+
import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
|
|
10
|
+
import {
|
|
11
|
+
isJsonRecord,
|
|
12
|
+
jsonField,
|
|
13
|
+
jsonStringField,
|
|
14
|
+
jsonText,
|
|
15
|
+
parseJsonLine,
|
|
16
|
+
type JsonRecord,
|
|
17
|
+
type JsonValue
|
|
18
|
+
} from "./CliSupport.ts"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* ACP (Agent Client Protocol, `gemini --experimental-acp`) message shapes
|
|
22
|
+
* are best-effort against public docs (geminicli.com/docs/cli/acp-mode,
|
|
23
|
+
* agentclientprotocol.com) — there is no installed `gemini` binary in CI to
|
|
24
|
+
* verify against (the repo's CI constraint is exactly "no installed
|
|
25
|
+
* provider CLIs"). Tests here exercise this module against a scripted fake
|
|
26
|
+
* peer, the same posture ClaudeAgentSession.test.ts takes against a fake
|
|
27
|
+
* `claude` process. A first live run against a real `gemini` is an
|
|
28
|
+
* integration smoke test, not a substitute for that determinism.
|
|
29
|
+
*/
|
|
30
|
+
export const geminiAcpArgv = (model: string | undefined): ReadonlyArray<string> => [
|
|
31
|
+
"gemini",
|
|
32
|
+
"--experimental-acp",
|
|
33
|
+
...(model === undefined ? [] : ["-m", model])
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
const clientCapabilities: JsonRecord = {
|
|
37
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
38
|
+
terminal: false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const acpInitializeParams: JsonRecord = {
|
|
42
|
+
protocolVersion: 1,
|
|
43
|
+
clientInfo: { name: "llm4ts", version: "1" },
|
|
44
|
+
clientCapabilities
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const acpNewSessionParams = (cwd: string, mcpServerUrl: string): JsonRecord => ({
|
|
48
|
+
cwd,
|
|
49
|
+
mcpServers: [{ type: "http", url: mcpServerUrl }]
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export const acpPromptParams = (sessionId: string, text: string): JsonRecord => ({
|
|
53
|
+
sessionId,
|
|
54
|
+
prompt: [{ type: "text", text }]
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const jsonRpcRequest = (id: number, method: string, params: JsonRecord): string =>
|
|
58
|
+
jsonText({ jsonrpc: "2.0", id, method, params })
|
|
59
|
+
|
|
60
|
+
export const acpInitializeLine = (id: number): string =>
|
|
61
|
+
jsonRpcRequest(id, "initialize", acpInitializeParams)
|
|
62
|
+
|
|
63
|
+
export const acpNewSessionLine = (id: number, cwd: string, mcpServerUrl: string): string =>
|
|
64
|
+
jsonRpcRequest(id, "session/new", acpNewSessionParams(cwd, mcpServerUrl))
|
|
65
|
+
|
|
66
|
+
export const acpPromptLine = (id: number, sessionId: string, text: string): string =>
|
|
67
|
+
jsonRpcRequest(id, "session/prompt", acpPromptParams(sessionId, text))
|
|
68
|
+
|
|
69
|
+
export const acpPermissionResponseLine = (id: JsonValue, approved: boolean): string =>
|
|
70
|
+
jsonText({
|
|
71
|
+
jsonrpc: "2.0",
|
|
72
|
+
id,
|
|
73
|
+
result: { outcome: approved ? "Approved" : "Denied" }
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
export const acpUnsupportedFsResponseLine = (id: JsonValue): string =>
|
|
77
|
+
jsonText({
|
|
78
|
+
jsonrpc: "2.0",
|
|
79
|
+
id,
|
|
80
|
+
error: { code: -32601, message: "fs access not supported by this client" }
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export class AcpTextChunk {
|
|
84
|
+
readonly _tag = "TextChunk"
|
|
85
|
+
constructor(readonly text: string) {}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class AcpToolCallObserved {
|
|
89
|
+
readonly _tag = "ToolCallObserved"
|
|
90
|
+
constructor(
|
|
91
|
+
readonly id: string,
|
|
92
|
+
readonly name: string,
|
|
93
|
+
readonly input: JsonValue
|
|
94
|
+
) {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class AcpPermissionRequested {
|
|
98
|
+
readonly _tag = "PermissionRequested"
|
|
99
|
+
constructor(
|
|
100
|
+
readonly id: string,
|
|
101
|
+
readonly toolCallId: string,
|
|
102
|
+
readonly name: string
|
|
103
|
+
) {}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type AcpSessionEvent = AcpTextChunk | AcpToolCallObserved | AcpPermissionRequested
|
|
107
|
+
|
|
108
|
+
export interface AcpPromptResult {
|
|
109
|
+
readonly text: string
|
|
110
|
+
readonly stopReason: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface AcpApprovalPolicy {
|
|
114
|
+
readonly decide: (toolName: string, input: JsonValue) => Effect.Effect<boolean>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The safety decision belongs to whichever side actually executes the tool
|
|
119
|
+
* (pi, over the MCP bridge) — gemini's own permission prompt is a formality
|
|
120
|
+
* for a call this session never runs itself, so the default is to approve.
|
|
121
|
+
*/
|
|
122
|
+
export const alwaysApprove: AcpApprovalPolicy = {
|
|
123
|
+
decide: () => Effect.succeed(true)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface GeminiAcpSessionShape {
|
|
127
|
+
readonly events: Stream.Stream<AcpSessionEvent, LlmError>
|
|
128
|
+
readonly newSession: (cwd: string, mcpServerUrl: string) => Effect.Effect<string, LlmError>
|
|
129
|
+
readonly prompt: (sessionId: string, text: string) => Effect.Effect<AcpPromptResult, LlmError>
|
|
130
|
+
readonly cancel: Effect.Effect<void>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const contentTextOf = (value: JsonValue | undefined): string | undefined =>
|
|
134
|
+
jsonStringField(jsonField(value, "content"), "text")
|
|
135
|
+
|
|
136
|
+
/** `content_chunk` and `agent_message_chunk` are both seen across ACP revisions for a text delta. */
|
|
137
|
+
const textChunkUpdate = (update: JsonValue | undefined): string | undefined => {
|
|
138
|
+
const type = jsonStringField(update, "type")
|
|
139
|
+
return type === "content_chunk" || type === "agent_message_chunk"
|
|
140
|
+
? contentTextOf(update)
|
|
141
|
+
: undefined
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const toolCallUpdate = (update: JsonValue | undefined): AcpToolCallObserved | undefined => {
|
|
145
|
+
if (jsonStringField(update, "type") !== "tool_call") {
|
|
146
|
+
return undefined
|
|
147
|
+
}
|
|
148
|
+
const toolCall = jsonField(update, "toolCall")
|
|
149
|
+
const id = jsonStringField(toolCall, "id")
|
|
150
|
+
return id === undefined
|
|
151
|
+
? undefined
|
|
152
|
+
: new AcpToolCallObserved(
|
|
153
|
+
id,
|
|
154
|
+
jsonStringField(toolCall, "name") ?? "",
|
|
155
|
+
jsonField(toolCall, "input") ?? {}
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export const parseAcpSessionUpdate = (
|
|
160
|
+
params: JsonValue | undefined
|
|
161
|
+
): AcpSessionEvent | undefined => {
|
|
162
|
+
const update = jsonField(params, "update")
|
|
163
|
+
const text = textChunkUpdate(update)
|
|
164
|
+
return text !== undefined && text.length > 0 ? new AcpTextChunk(text) : toolCallUpdate(update)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const openGeminiAcpSession = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.open")(
|
|
168
|
+
function* (
|
|
169
|
+
executor: ProcessExecutorShape,
|
|
170
|
+
argv: ReadonlyArray<string>,
|
|
171
|
+
cwd: string,
|
|
172
|
+
envVars: Readonly<Record<string, string>> = {},
|
|
173
|
+
approval: AcpApprovalPolicy = alwaysApprove
|
|
174
|
+
): Effect.fn.Return<GeminiAcpSessionShape, LlmError, Scope.Scope> {
|
|
175
|
+
const [stdin, stdout] = yield* executor.runBidirectional(argv, cwd, envVars)
|
|
176
|
+
const events = yield* PubSub.unbounded<AcpSessionEvent>()
|
|
177
|
+
// Accumulated directly by the background reader below rather than via a
|
|
178
|
+
// subscription `prompt` opens on demand: a subscription opened only when
|
|
179
|
+
// `prompt` runs can race a canned/fast-replaying stdout (real or faked in
|
|
180
|
+
// tests) that the reader has already drained by then. One prompt in
|
|
181
|
+
// flight at a time (the single-active-session assumption) makes
|
|
182
|
+
// reset-then-read at each call's boundary sufficient.
|
|
183
|
+
const accumulatedText = yield* Ref.make("")
|
|
184
|
+
const nextId = yield* Ref.make(0)
|
|
185
|
+
const pending = yield* Ref.make<ReadonlyMap<number, Deferred.Deferred<JsonValue, LlmError>>>(
|
|
186
|
+
new Map()
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const allocateId = Ref.updateAndGet(nextId, (current) => current + 1)
|
|
190
|
+
|
|
191
|
+
const registerPending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.registerPending")(
|
|
192
|
+
function* (id: number): Effect.fn.Return<Deferred.Deferred<JsonValue, LlmError>> {
|
|
193
|
+
const deferred = yield* Deferred.make<JsonValue, LlmError>()
|
|
194
|
+
yield* Ref.update(pending, (current) => {
|
|
195
|
+
const next = new Map(current)
|
|
196
|
+
next.set(id, deferred)
|
|
197
|
+
return next
|
|
198
|
+
})
|
|
199
|
+
return deferred
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const resolvePending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.resolvePending")(
|
|
204
|
+
function* (id: number, result: JsonValue | undefined, error: JsonValue | undefined) {
|
|
205
|
+
const current = yield* Ref.get(pending)
|
|
206
|
+
const deferred = current.get(id)
|
|
207
|
+
if (deferred === undefined) {
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
yield* Ref.update(pending, (map) => {
|
|
211
|
+
const next = new Map(map)
|
|
212
|
+
next.delete(id)
|
|
213
|
+
return next
|
|
214
|
+
})
|
|
215
|
+
if (error !== undefined) {
|
|
216
|
+
yield* Deferred.fail(
|
|
217
|
+
deferred,
|
|
218
|
+
ProviderError.make({
|
|
219
|
+
message: `Gemini ACP error: ${jsonStringField(error, "message") ?? jsonText(error)}`
|
|
220
|
+
})
|
|
221
|
+
)
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
yield* Deferred.succeed(deferred, result ?? {})
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
const respondToPermission = Effect.fn(
|
|
229
|
+
"@llm4ts/core/providers/GeminiAcpSession.respondToPermission"
|
|
230
|
+
)(function* (requestId: JsonValue, toolName: string, input: JsonValue) {
|
|
231
|
+
const approved = yield* approval.decide(toolName, input)
|
|
232
|
+
yield* Queue.offer(stdin, acpPermissionResponseLine(requestId, approved))
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
const handleAgentRequest = Effect.fn(
|
|
236
|
+
"@llm4ts/core/providers/GeminiAcpSession.handleAgentRequest"
|
|
237
|
+
)(function* (id: JsonValue, method: string, params: JsonValue | undefined) {
|
|
238
|
+
switch (method) {
|
|
239
|
+
case "session/request_permission": {
|
|
240
|
+
const toolCall = jsonField(params, "toolCall")
|
|
241
|
+
const toolCallId = jsonStringField(toolCall, "id") ?? ""
|
|
242
|
+
const toolName = jsonStringField(toolCall, "name") ?? ""
|
|
243
|
+
yield* PubSub.publish(
|
|
244
|
+
events,
|
|
245
|
+
new AcpPermissionRequested(
|
|
246
|
+
typeof id === "string" ? id : jsonText(id),
|
|
247
|
+
toolCallId,
|
|
248
|
+
toolName
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
yield* respondToPermission(id, toolName, jsonField(toolCall, "input") ?? {})
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
case "fs/read_text_file":
|
|
255
|
+
case "fs/write_text_file":
|
|
256
|
+
yield* Queue.offer(stdin, acpUnsupportedFsResponseLine(id))
|
|
257
|
+
return
|
|
258
|
+
default:
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const handleLine = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.handleLine")(function* (
|
|
264
|
+
line: string
|
|
265
|
+
) {
|
|
266
|
+
const json = parseJsonLine(line)
|
|
267
|
+
if (json === undefined || !isJsonRecord(json)) {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
const id = jsonField(json, "id")
|
|
271
|
+
const method = jsonStringField(json, "method")
|
|
272
|
+
|
|
273
|
+
if (method === "session/update") {
|
|
274
|
+
const event = parseAcpSessionUpdate(jsonField(json, "params"))
|
|
275
|
+
if (event !== undefined) {
|
|
276
|
+
if (event._tag === "TextChunk") {
|
|
277
|
+
yield* Ref.update(accumulatedText, (current) => current + event.text)
|
|
278
|
+
}
|
|
279
|
+
yield* PubSub.publish(events, event)
|
|
280
|
+
}
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (method !== undefined) {
|
|
285
|
+
if (id !== undefined) {
|
|
286
|
+
yield* handleAgentRequest(id, method, jsonField(json, "params"))
|
|
287
|
+
}
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (typeof id === "number") {
|
|
292
|
+
yield* resolvePending(id, jsonField(json, "result"), jsonField(json, "error"))
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
yield* stdout.pipe(
|
|
297
|
+
Stream.mapEffect(handleLine),
|
|
298
|
+
Stream.runDrain,
|
|
299
|
+
Effect.catch(() => Effect.void),
|
|
300
|
+
Effect.forkScoped
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
const call = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.call")(function* (
|
|
304
|
+
method: string,
|
|
305
|
+
params: JsonRecord
|
|
306
|
+
): Effect.fn.Return<JsonValue, LlmError> {
|
|
307
|
+
const id = yield* allocateId
|
|
308
|
+
const deferred = yield* registerPending(id)
|
|
309
|
+
yield* Queue.offer(stdin, jsonRpcRequest(id, method, params))
|
|
310
|
+
return yield* Deferred.await(deferred)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
const newSession = (cwd: string, mcpServerUrl: string): Effect.Effect<string, LlmError> =>
|
|
314
|
+
Effect.gen(function* () {
|
|
315
|
+
yield* call("initialize", acpInitializeParams)
|
|
316
|
+
const result = yield* call("session/new", acpNewSessionParams(cwd, mcpServerUrl))
|
|
317
|
+
const sessionId = jsonStringField(result, "sessionId")
|
|
318
|
+
if (sessionId === undefined) {
|
|
319
|
+
return yield* ProviderError.make({
|
|
320
|
+
message: "Gemini ACP session/new did not return a sessionId"
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
return sessionId
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
const prompt = (sessionId: string, text: string): Effect.Effect<AcpPromptResult, LlmError> =>
|
|
327
|
+
Effect.gen(function* () {
|
|
328
|
+
yield* Ref.set(accumulatedText, "")
|
|
329
|
+
const result = yield* call("session/prompt", acpPromptParams(sessionId, text))
|
|
330
|
+
const finalText = yield* Ref.get(accumulatedText)
|
|
331
|
+
return {
|
|
332
|
+
text: finalText,
|
|
333
|
+
stopReason: jsonStringField(result, "stopReason") ?? "Completed"
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
events: Stream.fromPubSub(events),
|
|
339
|
+
newSession,
|
|
340
|
+
prompt,
|
|
341
|
+
cancel: Queue.shutdown(stdin)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
)
|