@llm4ts/core 2.2.1 → 2.3.1
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,75 @@
|
|
|
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
|
+
/**
|
|
20
|
+
* `name` and `headers` are required by gemini-cli 0.59.0's own schema for an
|
|
21
|
+
* `http`-type MCP server entry, confirmed against the real binary (a Zod
|
|
22
|
+
* `invalid_union` error naming exactly these two as missing) — the public
|
|
23
|
+
* ACP docs this was first built from show only `{type, url}` as a minimal
|
|
24
|
+
* example, which the real server rejects outright. `headers` is an empty
|
|
25
|
+
* array, not omitted: this is a local loopback bridge with no auth to add.
|
|
26
|
+
*/
|
|
27
|
+
export declare const acpNewSessionParams: (cwd: string, mcpServerUrl: string) => JsonRecord;
|
|
28
|
+
export declare const acpPromptParams: (sessionId: string, text: string) => JsonRecord;
|
|
29
|
+
export declare const acpInitializeLine: (id: number) => string;
|
|
30
|
+
export declare const acpNewSessionLine: (id: number, cwd: string, mcpServerUrl: string) => string;
|
|
31
|
+
export declare const acpPromptLine: (id: number, sessionId: string, text: string) => string;
|
|
32
|
+
export declare const acpPermissionResponseLine: (id: JsonValue, approved: boolean) => string;
|
|
33
|
+
export declare const acpUnsupportedFsResponseLine: (id: JsonValue) => string;
|
|
34
|
+
export declare class AcpTextChunk {
|
|
35
|
+
readonly text: string;
|
|
36
|
+
readonly _tag = "TextChunk";
|
|
37
|
+
constructor(text: string);
|
|
38
|
+
}
|
|
39
|
+
export declare class AcpToolCallObserved {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly name: string;
|
|
42
|
+
readonly input: JsonValue;
|
|
43
|
+
readonly _tag = "ToolCallObserved";
|
|
44
|
+
constructor(id: string, name: string, input: JsonValue);
|
|
45
|
+
}
|
|
46
|
+
export declare class AcpPermissionRequested {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly toolCallId: string;
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly _tag = "PermissionRequested";
|
|
51
|
+
constructor(id: string, toolCallId: string, name: string);
|
|
52
|
+
}
|
|
53
|
+
export type AcpSessionEvent = AcpTextChunk | AcpToolCallObserved | AcpPermissionRequested;
|
|
54
|
+
export interface AcpPromptResult {
|
|
55
|
+
readonly text: string;
|
|
56
|
+
readonly stopReason: string;
|
|
57
|
+
}
|
|
58
|
+
export interface AcpApprovalPolicy {
|
|
59
|
+
readonly decide: (toolName: string, input: JsonValue) => Effect.Effect<boolean>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The safety decision belongs to whichever side actually executes the tool
|
|
63
|
+
* (pi, over the MCP bridge) — gemini's own permission prompt is a formality
|
|
64
|
+
* for a call this session never runs itself, so the default is to approve.
|
|
65
|
+
*/
|
|
66
|
+
export declare const alwaysApprove: AcpApprovalPolicy;
|
|
67
|
+
export interface GeminiAcpSessionShape {
|
|
68
|
+
readonly events: Stream.Stream<AcpSessionEvent, LlmError>;
|
|
69
|
+
readonly newSession: (cwd: string, mcpServerUrl: string) => Effect.Effect<string, LlmError>;
|
|
70
|
+
readonly prompt: (sessionId: string, text: string) => Effect.Effect<AcpPromptResult, LlmError>;
|
|
71
|
+
readonly cancel: Effect.Effect<void>;
|
|
72
|
+
}
|
|
73
|
+
export declare const parseAcpSessionUpdate: (params: JsonValue | undefined) => AcpSessionEvent | undefined;
|
|
74
|
+
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>;
|
|
75
|
+
//# 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,270 @@
|
|
|
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
|
+
/**
|
|
34
|
+
* `name` and `headers` are required by gemini-cli 0.59.0's own schema for an
|
|
35
|
+
* `http`-type MCP server entry, confirmed against the real binary (a Zod
|
|
36
|
+
* `invalid_union` error naming exactly these two as missing) — the public
|
|
37
|
+
* ACP docs this was first built from show only `{type, url}` as a minimal
|
|
38
|
+
* example, which the real server rejects outright. `headers` is an empty
|
|
39
|
+
* array, not omitted: this is a local loopback bridge with no auth to add.
|
|
40
|
+
*/
|
|
41
|
+
export const acpNewSessionParams = (cwd, mcpServerUrl) => ({
|
|
42
|
+
cwd,
|
|
43
|
+
mcpServers: [{ type: "http", url: mcpServerUrl, name: "llm4ts-gemini-bridge", headers: [] }]
|
|
44
|
+
});
|
|
45
|
+
export const acpPromptParams = (sessionId, text) => ({
|
|
46
|
+
sessionId,
|
|
47
|
+
prompt: [{ type: "text", text }]
|
|
48
|
+
});
|
|
49
|
+
const jsonRpcRequest = (id, method, params) => jsonText({ jsonrpc: "2.0", id, method, params });
|
|
50
|
+
export const acpInitializeLine = (id) => jsonRpcRequest(id, "initialize", acpInitializeParams);
|
|
51
|
+
export const acpNewSessionLine = (id, cwd, mcpServerUrl) => jsonRpcRequest(id, "session/new", acpNewSessionParams(cwd, mcpServerUrl));
|
|
52
|
+
export const acpPromptLine = (id, sessionId, text) => jsonRpcRequest(id, "session/prompt", acpPromptParams(sessionId, text));
|
|
53
|
+
export const acpPermissionResponseLine = (id, approved) => jsonText({
|
|
54
|
+
jsonrpc: "2.0",
|
|
55
|
+
id,
|
|
56
|
+
result: { outcome: approved ? "Approved" : "Denied" }
|
|
57
|
+
});
|
|
58
|
+
export const acpUnsupportedFsResponseLine = (id) => jsonText({
|
|
59
|
+
jsonrpc: "2.0",
|
|
60
|
+
id,
|
|
61
|
+
error: { code: -32601, message: "fs access not supported by this client" }
|
|
62
|
+
});
|
|
63
|
+
export class AcpTextChunk {
|
|
64
|
+
text;
|
|
65
|
+
_tag = "TextChunk";
|
|
66
|
+
constructor(text) {
|
|
67
|
+
this.text = text;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export class AcpToolCallObserved {
|
|
71
|
+
id;
|
|
72
|
+
name;
|
|
73
|
+
input;
|
|
74
|
+
_tag = "ToolCallObserved";
|
|
75
|
+
constructor(id, name, input) {
|
|
76
|
+
this.id = id;
|
|
77
|
+
this.name = name;
|
|
78
|
+
this.input = input;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
export class AcpPermissionRequested {
|
|
82
|
+
id;
|
|
83
|
+
toolCallId;
|
|
84
|
+
name;
|
|
85
|
+
_tag = "PermissionRequested";
|
|
86
|
+
constructor(id, toolCallId, name) {
|
|
87
|
+
this.id = id;
|
|
88
|
+
this.toolCallId = toolCallId;
|
|
89
|
+
this.name = name;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The safety decision belongs to whichever side actually executes the tool
|
|
94
|
+
* (pi, over the MCP bridge) — gemini's own permission prompt is a formality
|
|
95
|
+
* for a call this session never runs itself, so the default is to approve.
|
|
96
|
+
*/
|
|
97
|
+
export const alwaysApprove = {
|
|
98
|
+
decide: () => Effect.succeed(true)
|
|
99
|
+
};
|
|
100
|
+
const contentTextOf = (value) => jsonStringField(jsonField(value, "content"), "text");
|
|
101
|
+
/** `content_chunk` and `agent_message_chunk` are both seen across ACP revisions for a text delta. */
|
|
102
|
+
const textChunkUpdate = (update) => {
|
|
103
|
+
const type = jsonStringField(update, "type");
|
|
104
|
+
return type === "content_chunk" || type === "agent_message_chunk"
|
|
105
|
+
? contentTextOf(update)
|
|
106
|
+
: undefined;
|
|
107
|
+
};
|
|
108
|
+
const toolCallUpdate = (update) => {
|
|
109
|
+
if (jsonStringField(update, "type") !== "tool_call") {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const toolCall = jsonField(update, "toolCall");
|
|
113
|
+
const id = jsonStringField(toolCall, "id");
|
|
114
|
+
return id === undefined
|
|
115
|
+
? undefined
|
|
116
|
+
: new AcpToolCallObserved(id, jsonStringField(toolCall, "name") ?? "", jsonField(toolCall, "input") ?? {});
|
|
117
|
+
};
|
|
118
|
+
export const parseAcpSessionUpdate = (params) => {
|
|
119
|
+
const update = jsonField(params, "update");
|
|
120
|
+
const text = textChunkUpdate(update);
|
|
121
|
+
return text !== undefined && text.length > 0 ? new AcpTextChunk(text) : toolCallUpdate(update);
|
|
122
|
+
};
|
|
123
|
+
export const openGeminiAcpSession = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.open")(function* (executor, argv, cwd, envVars = {}, approval = alwaysApprove) {
|
|
124
|
+
const [stdin, stdout] = yield* executor.runBidirectional(argv, cwd, envVars);
|
|
125
|
+
const events = yield* PubSub.unbounded();
|
|
126
|
+
// Accumulated directly by the background reader below rather than via a
|
|
127
|
+
// subscription `prompt` opens on demand: a subscription opened only when
|
|
128
|
+
// `prompt` runs can race a canned/fast-replaying stdout (real or faked in
|
|
129
|
+
// tests) that the reader has already drained by then. One prompt in
|
|
130
|
+
// flight at a time (the single-active-session assumption) makes
|
|
131
|
+
// reset-then-read at each call's boundary sufficient.
|
|
132
|
+
const accumulatedText = yield* Ref.make("");
|
|
133
|
+
const nextId = yield* Ref.make(0);
|
|
134
|
+
const pending = yield* Ref.make(new Map());
|
|
135
|
+
// Keyed by request id purely for error messages: which ACP call a
|
|
136
|
+
// response belongs to, so a failure names its method instead of just
|
|
137
|
+
// "Gemini ACP error: Internal error" with no way to tell initialize
|
|
138
|
+
// from session/new from session/prompt.
|
|
139
|
+
const pendingMethods = yield* Ref.make(new Map());
|
|
140
|
+
const allocateId = Ref.updateAndGet(nextId, (current) => current + 1);
|
|
141
|
+
const registerPending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.registerPending")(function* (id) {
|
|
142
|
+
const deferred = yield* Deferred.make();
|
|
143
|
+
yield* Ref.update(pending, (current) => {
|
|
144
|
+
const next = new Map(current);
|
|
145
|
+
next.set(id, deferred);
|
|
146
|
+
return next;
|
|
147
|
+
});
|
|
148
|
+
return deferred;
|
|
149
|
+
});
|
|
150
|
+
const resolvePending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.resolvePending")(function* (id, result, error) {
|
|
151
|
+
const current = yield* Ref.get(pending);
|
|
152
|
+
const deferred = current.get(id);
|
|
153
|
+
if (deferred === undefined) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
yield* Ref.update(pending, (map) => {
|
|
157
|
+
const next = new Map(map);
|
|
158
|
+
next.delete(id);
|
|
159
|
+
return next;
|
|
160
|
+
});
|
|
161
|
+
const methods = yield* Ref.get(pendingMethods);
|
|
162
|
+
const method = methods.get(id) ?? "unknown method";
|
|
163
|
+
yield* Ref.update(pendingMethods, (map) => {
|
|
164
|
+
const next = new Map(map);
|
|
165
|
+
next.delete(id);
|
|
166
|
+
return next;
|
|
167
|
+
});
|
|
168
|
+
if (error !== undefined) {
|
|
169
|
+
const code = jsonField(error, "code");
|
|
170
|
+
const data = jsonField(error, "data");
|
|
171
|
+
const detail = [
|
|
172
|
+
code === undefined ? undefined : `code=${jsonText(code)}`,
|
|
173
|
+
data === undefined ? undefined : `data=${jsonText(data)}`
|
|
174
|
+
]
|
|
175
|
+
.filter((part) => part !== undefined)
|
|
176
|
+
.join(" ");
|
|
177
|
+
yield* Deferred.fail(deferred, ProviderError.make({
|
|
178
|
+
message: `Gemini ACP ${method} failed: ` +
|
|
179
|
+
`${jsonStringField(error, "message") ?? jsonText(error)}` +
|
|
180
|
+
`${detail.length === 0 ? "" : ` (${detail})`}`
|
|
181
|
+
}));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
yield* Deferred.succeed(deferred, result ?? {});
|
|
185
|
+
});
|
|
186
|
+
const respondToPermission = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.respondToPermission")(function* (requestId, toolName, input) {
|
|
187
|
+
const approved = yield* approval.decide(toolName, input);
|
|
188
|
+
yield* Queue.offer(stdin, acpPermissionResponseLine(requestId, approved));
|
|
189
|
+
});
|
|
190
|
+
const handleAgentRequest = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.handleAgentRequest")(function* (id, method, params) {
|
|
191
|
+
switch (method) {
|
|
192
|
+
case "session/request_permission": {
|
|
193
|
+
const toolCall = jsonField(params, "toolCall");
|
|
194
|
+
const toolCallId = jsonStringField(toolCall, "id") ?? "";
|
|
195
|
+
const toolName = jsonStringField(toolCall, "name") ?? "";
|
|
196
|
+
yield* PubSub.publish(events, new AcpPermissionRequested(typeof id === "string" ? id : jsonText(id), toolCallId, toolName));
|
|
197
|
+
yield* respondToPermission(id, toolName, jsonField(toolCall, "input") ?? {});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
case "fs/read_text_file":
|
|
201
|
+
case "fs/write_text_file":
|
|
202
|
+
yield* Queue.offer(stdin, acpUnsupportedFsResponseLine(id));
|
|
203
|
+
return;
|
|
204
|
+
default:
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
const handleLine = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.handleLine")(function* (line) {
|
|
209
|
+
const json = parseJsonLine(line);
|
|
210
|
+
if (json === undefined || !isJsonRecord(json)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const id = jsonField(json, "id");
|
|
214
|
+
const method = jsonStringField(json, "method");
|
|
215
|
+
if (method === "session/update") {
|
|
216
|
+
const event = parseAcpSessionUpdate(jsonField(json, "params"));
|
|
217
|
+
if (event !== undefined) {
|
|
218
|
+
if (event._tag === "TextChunk") {
|
|
219
|
+
yield* Ref.update(accumulatedText, (current) => current + event.text);
|
|
220
|
+
}
|
|
221
|
+
yield* PubSub.publish(events, event);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (method !== undefined) {
|
|
226
|
+
if (id !== undefined) {
|
|
227
|
+
yield* handleAgentRequest(id, method, jsonField(json, "params"));
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (typeof id === "number") {
|
|
232
|
+
yield* resolvePending(id, jsonField(json, "result"), jsonField(json, "error"));
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
yield* stdout.pipe(Stream.mapEffect(handleLine), Stream.runDrain, Effect.catch(() => Effect.void), Effect.forkScoped);
|
|
236
|
+
const call = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.call")(function* (method, params) {
|
|
237
|
+
const id = yield* allocateId;
|
|
238
|
+
const deferred = yield* registerPending(id);
|
|
239
|
+
yield* Ref.update(pendingMethods, (map) => new Map(map).set(id, method));
|
|
240
|
+
yield* Queue.offer(stdin, jsonRpcRequest(id, method, params));
|
|
241
|
+
return yield* Deferred.await(deferred);
|
|
242
|
+
});
|
|
243
|
+
const newSession = (cwd, mcpServerUrl) => Effect.gen(function* () {
|
|
244
|
+
yield* call("initialize", acpInitializeParams);
|
|
245
|
+
const result = yield* call("session/new", acpNewSessionParams(cwd, mcpServerUrl));
|
|
246
|
+
const sessionId = jsonStringField(result, "sessionId");
|
|
247
|
+
if (sessionId === undefined) {
|
|
248
|
+
return yield* ProviderError.make({
|
|
249
|
+
message: "Gemini ACP session/new did not return a sessionId"
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return sessionId;
|
|
253
|
+
});
|
|
254
|
+
const prompt = (sessionId, text) => Effect.gen(function* () {
|
|
255
|
+
yield* Ref.set(accumulatedText, "");
|
|
256
|
+
const result = yield* call("session/prompt", acpPromptParams(sessionId, text));
|
|
257
|
+
const finalText = yield* Ref.get(accumulatedText);
|
|
258
|
+
return {
|
|
259
|
+
text: finalText,
|
|
260
|
+
stopReason: jsonStringField(result, "stopReason") ?? "Completed"
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
events: Stream.fromPubSub(events),
|
|
265
|
+
newSession,
|
|
266
|
+
prompt,
|
|
267
|
+
cancel: Queue.shutdown(stdin)
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
//# 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.1",
|
|
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,376 @@
|
|
|
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
|
+
/**
|
|
48
|
+
* `name` and `headers` are required by gemini-cli 0.59.0's own schema for an
|
|
49
|
+
* `http`-type MCP server entry, confirmed against the real binary (a Zod
|
|
50
|
+
* `invalid_union` error naming exactly these two as missing) — the public
|
|
51
|
+
* ACP docs this was first built from show only `{type, url}` as a minimal
|
|
52
|
+
* example, which the real server rejects outright. `headers` is an empty
|
|
53
|
+
* array, not omitted: this is a local loopback bridge with no auth to add.
|
|
54
|
+
*/
|
|
55
|
+
export const acpNewSessionParams = (cwd: string, mcpServerUrl: string): JsonRecord => ({
|
|
56
|
+
cwd,
|
|
57
|
+
mcpServers: [{ type: "http", url: mcpServerUrl, name: "llm4ts-gemini-bridge", headers: [] }]
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export const acpPromptParams = (sessionId: string, text: string): JsonRecord => ({
|
|
61
|
+
sessionId,
|
|
62
|
+
prompt: [{ type: "text", text }]
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const jsonRpcRequest = (id: number, method: string, params: JsonRecord): string =>
|
|
66
|
+
jsonText({ jsonrpc: "2.0", id, method, params })
|
|
67
|
+
|
|
68
|
+
export const acpInitializeLine = (id: number): string =>
|
|
69
|
+
jsonRpcRequest(id, "initialize", acpInitializeParams)
|
|
70
|
+
|
|
71
|
+
export const acpNewSessionLine = (id: number, cwd: string, mcpServerUrl: string): string =>
|
|
72
|
+
jsonRpcRequest(id, "session/new", acpNewSessionParams(cwd, mcpServerUrl))
|
|
73
|
+
|
|
74
|
+
export const acpPromptLine = (id: number, sessionId: string, text: string): string =>
|
|
75
|
+
jsonRpcRequest(id, "session/prompt", acpPromptParams(sessionId, text))
|
|
76
|
+
|
|
77
|
+
export const acpPermissionResponseLine = (id: JsonValue, approved: boolean): string =>
|
|
78
|
+
jsonText({
|
|
79
|
+
jsonrpc: "2.0",
|
|
80
|
+
id,
|
|
81
|
+
result: { outcome: approved ? "Approved" : "Denied" }
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
export const acpUnsupportedFsResponseLine = (id: JsonValue): string =>
|
|
85
|
+
jsonText({
|
|
86
|
+
jsonrpc: "2.0",
|
|
87
|
+
id,
|
|
88
|
+
error: { code: -32601, message: "fs access not supported by this client" }
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
export class AcpTextChunk {
|
|
92
|
+
readonly _tag = "TextChunk"
|
|
93
|
+
constructor(readonly text: string) {}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class AcpToolCallObserved {
|
|
97
|
+
readonly _tag = "ToolCallObserved"
|
|
98
|
+
constructor(
|
|
99
|
+
readonly id: string,
|
|
100
|
+
readonly name: string,
|
|
101
|
+
readonly input: JsonValue
|
|
102
|
+
) {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class AcpPermissionRequested {
|
|
106
|
+
readonly _tag = "PermissionRequested"
|
|
107
|
+
constructor(
|
|
108
|
+
readonly id: string,
|
|
109
|
+
readonly toolCallId: string,
|
|
110
|
+
readonly name: string
|
|
111
|
+
) {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type AcpSessionEvent = AcpTextChunk | AcpToolCallObserved | AcpPermissionRequested
|
|
115
|
+
|
|
116
|
+
export interface AcpPromptResult {
|
|
117
|
+
readonly text: string
|
|
118
|
+
readonly stopReason: string
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface AcpApprovalPolicy {
|
|
122
|
+
readonly decide: (toolName: string, input: JsonValue) => Effect.Effect<boolean>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The safety decision belongs to whichever side actually executes the tool
|
|
127
|
+
* (pi, over the MCP bridge) — gemini's own permission prompt is a formality
|
|
128
|
+
* for a call this session never runs itself, so the default is to approve.
|
|
129
|
+
*/
|
|
130
|
+
export const alwaysApprove: AcpApprovalPolicy = {
|
|
131
|
+
decide: () => Effect.succeed(true)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface GeminiAcpSessionShape {
|
|
135
|
+
readonly events: Stream.Stream<AcpSessionEvent, LlmError>
|
|
136
|
+
readonly newSession: (cwd: string, mcpServerUrl: string) => Effect.Effect<string, LlmError>
|
|
137
|
+
readonly prompt: (sessionId: string, text: string) => Effect.Effect<AcpPromptResult, LlmError>
|
|
138
|
+
readonly cancel: Effect.Effect<void>
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const contentTextOf = (value: JsonValue | undefined): string | undefined =>
|
|
142
|
+
jsonStringField(jsonField(value, "content"), "text")
|
|
143
|
+
|
|
144
|
+
/** `content_chunk` and `agent_message_chunk` are both seen across ACP revisions for a text delta. */
|
|
145
|
+
const textChunkUpdate = (update: JsonValue | undefined): string | undefined => {
|
|
146
|
+
const type = jsonStringField(update, "type")
|
|
147
|
+
return type === "content_chunk" || type === "agent_message_chunk"
|
|
148
|
+
? contentTextOf(update)
|
|
149
|
+
: undefined
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const toolCallUpdate = (update: JsonValue | undefined): AcpToolCallObserved | undefined => {
|
|
153
|
+
if (jsonStringField(update, "type") !== "tool_call") {
|
|
154
|
+
return undefined
|
|
155
|
+
}
|
|
156
|
+
const toolCall = jsonField(update, "toolCall")
|
|
157
|
+
const id = jsonStringField(toolCall, "id")
|
|
158
|
+
return id === undefined
|
|
159
|
+
? undefined
|
|
160
|
+
: new AcpToolCallObserved(
|
|
161
|
+
id,
|
|
162
|
+
jsonStringField(toolCall, "name") ?? "",
|
|
163
|
+
jsonField(toolCall, "input") ?? {}
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export const parseAcpSessionUpdate = (
|
|
168
|
+
params: JsonValue | undefined
|
|
169
|
+
): AcpSessionEvent | undefined => {
|
|
170
|
+
const update = jsonField(params, "update")
|
|
171
|
+
const text = textChunkUpdate(update)
|
|
172
|
+
return text !== undefined && text.length > 0 ? new AcpTextChunk(text) : toolCallUpdate(update)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export const openGeminiAcpSession = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.open")(
|
|
176
|
+
function* (
|
|
177
|
+
executor: ProcessExecutorShape,
|
|
178
|
+
argv: ReadonlyArray<string>,
|
|
179
|
+
cwd: string,
|
|
180
|
+
envVars: Readonly<Record<string, string>> = {},
|
|
181
|
+
approval: AcpApprovalPolicy = alwaysApprove
|
|
182
|
+
): Effect.fn.Return<GeminiAcpSessionShape, LlmError, Scope.Scope> {
|
|
183
|
+
const [stdin, stdout] = yield* executor.runBidirectional(argv, cwd, envVars)
|
|
184
|
+
const events = yield* PubSub.unbounded<AcpSessionEvent>()
|
|
185
|
+
// Accumulated directly by the background reader below rather than via a
|
|
186
|
+
// subscription `prompt` opens on demand: a subscription opened only when
|
|
187
|
+
// `prompt` runs can race a canned/fast-replaying stdout (real or faked in
|
|
188
|
+
// tests) that the reader has already drained by then. One prompt in
|
|
189
|
+
// flight at a time (the single-active-session assumption) makes
|
|
190
|
+
// reset-then-read at each call's boundary sufficient.
|
|
191
|
+
const accumulatedText = yield* Ref.make("")
|
|
192
|
+
const nextId = yield* Ref.make(0)
|
|
193
|
+
const pending = yield* Ref.make<ReadonlyMap<number, Deferred.Deferred<JsonValue, LlmError>>>(
|
|
194
|
+
new Map()
|
|
195
|
+
)
|
|
196
|
+
// Keyed by request id purely for error messages: which ACP call a
|
|
197
|
+
// response belongs to, so a failure names its method instead of just
|
|
198
|
+
// "Gemini ACP error: Internal error" with no way to tell initialize
|
|
199
|
+
// from session/new from session/prompt.
|
|
200
|
+
const pendingMethods = yield* Ref.make<ReadonlyMap<number, string>>(new Map())
|
|
201
|
+
|
|
202
|
+
const allocateId = Ref.updateAndGet(nextId, (current) => current + 1)
|
|
203
|
+
|
|
204
|
+
const registerPending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.registerPending")(
|
|
205
|
+
function* (id: number): Effect.fn.Return<Deferred.Deferred<JsonValue, LlmError>> {
|
|
206
|
+
const deferred = yield* Deferred.make<JsonValue, LlmError>()
|
|
207
|
+
yield* Ref.update(pending, (current) => {
|
|
208
|
+
const next = new Map(current)
|
|
209
|
+
next.set(id, deferred)
|
|
210
|
+
return next
|
|
211
|
+
})
|
|
212
|
+
return deferred
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
const resolvePending = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.resolvePending")(
|
|
217
|
+
function* (id: number, result: JsonValue | undefined, error: JsonValue | undefined) {
|
|
218
|
+
const current = yield* Ref.get(pending)
|
|
219
|
+
const deferred = current.get(id)
|
|
220
|
+
if (deferred === undefined) {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
yield* Ref.update(pending, (map) => {
|
|
224
|
+
const next = new Map(map)
|
|
225
|
+
next.delete(id)
|
|
226
|
+
return next
|
|
227
|
+
})
|
|
228
|
+
const methods = yield* Ref.get(pendingMethods)
|
|
229
|
+
const method = methods.get(id) ?? "unknown method"
|
|
230
|
+
yield* Ref.update(pendingMethods, (map) => {
|
|
231
|
+
const next = new Map(map)
|
|
232
|
+
next.delete(id)
|
|
233
|
+
return next
|
|
234
|
+
})
|
|
235
|
+
if (error !== undefined) {
|
|
236
|
+
const code = jsonField(error, "code")
|
|
237
|
+
const data = jsonField(error, "data")
|
|
238
|
+
const detail = [
|
|
239
|
+
code === undefined ? undefined : `code=${jsonText(code)}`,
|
|
240
|
+
data === undefined ? undefined : `data=${jsonText(data)}`
|
|
241
|
+
]
|
|
242
|
+
.filter((part) => part !== undefined)
|
|
243
|
+
.join(" ")
|
|
244
|
+
yield* Deferred.fail(
|
|
245
|
+
deferred,
|
|
246
|
+
ProviderError.make({
|
|
247
|
+
message:
|
|
248
|
+
`Gemini ACP ${method} failed: ` +
|
|
249
|
+
`${jsonStringField(error, "message") ?? jsonText(error)}` +
|
|
250
|
+
`${detail.length === 0 ? "" : ` (${detail})`}`
|
|
251
|
+
})
|
|
252
|
+
)
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
yield* Deferred.succeed(deferred, result ?? {})
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
const respondToPermission = Effect.fn(
|
|
260
|
+
"@llm4ts/core/providers/GeminiAcpSession.respondToPermission"
|
|
261
|
+
)(function* (requestId: JsonValue, toolName: string, input: JsonValue) {
|
|
262
|
+
const approved = yield* approval.decide(toolName, input)
|
|
263
|
+
yield* Queue.offer(stdin, acpPermissionResponseLine(requestId, approved))
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const handleAgentRequest = Effect.fn(
|
|
267
|
+
"@llm4ts/core/providers/GeminiAcpSession.handleAgentRequest"
|
|
268
|
+
)(function* (id: JsonValue, method: string, params: JsonValue | undefined) {
|
|
269
|
+
switch (method) {
|
|
270
|
+
case "session/request_permission": {
|
|
271
|
+
const toolCall = jsonField(params, "toolCall")
|
|
272
|
+
const toolCallId = jsonStringField(toolCall, "id") ?? ""
|
|
273
|
+
const toolName = jsonStringField(toolCall, "name") ?? ""
|
|
274
|
+
yield* PubSub.publish(
|
|
275
|
+
events,
|
|
276
|
+
new AcpPermissionRequested(
|
|
277
|
+
typeof id === "string" ? id : jsonText(id),
|
|
278
|
+
toolCallId,
|
|
279
|
+
toolName
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
yield* respondToPermission(id, toolName, jsonField(toolCall, "input") ?? {})
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
case "fs/read_text_file":
|
|
286
|
+
case "fs/write_text_file":
|
|
287
|
+
yield* Queue.offer(stdin, acpUnsupportedFsResponseLine(id))
|
|
288
|
+
return
|
|
289
|
+
default:
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
const handleLine = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.handleLine")(function* (
|
|
295
|
+
line: string
|
|
296
|
+
) {
|
|
297
|
+
const json = parseJsonLine(line)
|
|
298
|
+
if (json === undefined || !isJsonRecord(json)) {
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
const id = jsonField(json, "id")
|
|
302
|
+
const method = jsonStringField(json, "method")
|
|
303
|
+
|
|
304
|
+
if (method === "session/update") {
|
|
305
|
+
const event = parseAcpSessionUpdate(jsonField(json, "params"))
|
|
306
|
+
if (event !== undefined) {
|
|
307
|
+
if (event._tag === "TextChunk") {
|
|
308
|
+
yield* Ref.update(accumulatedText, (current) => current + event.text)
|
|
309
|
+
}
|
|
310
|
+
yield* PubSub.publish(events, event)
|
|
311
|
+
}
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (method !== undefined) {
|
|
316
|
+
if (id !== undefined) {
|
|
317
|
+
yield* handleAgentRequest(id, method, jsonField(json, "params"))
|
|
318
|
+
}
|
|
319
|
+
return
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (typeof id === "number") {
|
|
323
|
+
yield* resolvePending(id, jsonField(json, "result"), jsonField(json, "error"))
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
yield* stdout.pipe(
|
|
328
|
+
Stream.mapEffect(handleLine),
|
|
329
|
+
Stream.runDrain,
|
|
330
|
+
Effect.catch(() => Effect.void),
|
|
331
|
+
Effect.forkScoped
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
const call = Effect.fn("@llm4ts/core/providers/GeminiAcpSession.call")(function* (
|
|
335
|
+
method: string,
|
|
336
|
+
params: JsonRecord
|
|
337
|
+
): Effect.fn.Return<JsonValue, LlmError> {
|
|
338
|
+
const id = yield* allocateId
|
|
339
|
+
const deferred = yield* registerPending(id)
|
|
340
|
+
yield* Ref.update(pendingMethods, (map) => new Map(map).set(id, method))
|
|
341
|
+
yield* Queue.offer(stdin, jsonRpcRequest(id, method, params))
|
|
342
|
+
return yield* Deferred.await(deferred)
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
const newSession = (cwd: string, mcpServerUrl: string): Effect.Effect<string, LlmError> =>
|
|
346
|
+
Effect.gen(function* () {
|
|
347
|
+
yield* call("initialize", acpInitializeParams)
|
|
348
|
+
const result = yield* call("session/new", acpNewSessionParams(cwd, mcpServerUrl))
|
|
349
|
+
const sessionId = jsonStringField(result, "sessionId")
|
|
350
|
+
if (sessionId === undefined) {
|
|
351
|
+
return yield* ProviderError.make({
|
|
352
|
+
message: "Gemini ACP session/new did not return a sessionId"
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
return sessionId
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
const prompt = (sessionId: string, text: string): Effect.Effect<AcpPromptResult, LlmError> =>
|
|
359
|
+
Effect.gen(function* () {
|
|
360
|
+
yield* Ref.set(accumulatedText, "")
|
|
361
|
+
const result = yield* call("session/prompt", acpPromptParams(sessionId, text))
|
|
362
|
+
const finalText = yield* Ref.get(accumulatedText)
|
|
363
|
+
return {
|
|
364
|
+
text: finalText,
|
|
365
|
+
stopReason: jsonStringField(result, "stopReason") ?? "Completed"
|
|
366
|
+
}
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
events: Stream.fromPubSub(events),
|
|
371
|
+
newSession,
|
|
372
|
+
prompt,
|
|
373
|
+
cancel: Queue.shutdown(stdin)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
)
|