@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.2.1",
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
+ )