@junctionpanel/server 0.1.19 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/client/daemon-client.d.ts +4 -2
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +2 -1
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +4 -3
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +29 -0
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +8 -0
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/providers/gemini-agent.d.ts +425 -0
- package/dist/server/server/agent/providers/gemini-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/gemini-agent.js +973 -0
- package/dist/server/server/agent/providers/gemini-agent.js.map +1 -0
- package/dist/server/server/persistence-hooks.d.ts.map +1 -1
- package/dist/server/server/persistence-hooks.js +4 -1
- package/dist/server/server/persistence-hooks.js.map +1 -1
- package/dist/server/server/session.d.ts +2 -1
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +76 -56
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/shared/messages.d.ts +18 -0
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +2 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
import { execSync, spawn } from "node:child_process";
|
|
2
|
+
import { readdir, readFile, realpath, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir, tmpdir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { applyProviderEnv, isProviderCommandAvailable, resolveProviderCommandPrefix, } from "../provider-launch-config.js";
|
|
8
|
+
import { ToolEditInputSchema, ToolEditOutputSchema, ToolReadInputSchema, ToolReadOutputSchema, ToolSearchInputSchema, ToolShellInputSchema, ToolShellOutputSchema, ToolWriteInputSchema, ToolWriteOutputSchema, toEditToolDetail, toReadToolDetail, toSearchToolDetail, toShellToolDetail, toWriteToolDetail, } from "./tool-call-detail-primitives.js";
|
|
9
|
+
import { coerceToolCallId, nonEmptyString } from "./tool-call-mapper-utils.js";
|
|
10
|
+
const GEMINI_PROVIDER = "gemini";
|
|
11
|
+
const GEMINI_MODES = ["default", "auto_edit", "yolo", "plan"];
|
|
12
|
+
const GEMINI_GLOBAL_DIR = path.join(homedir(), ".gemini");
|
|
13
|
+
const GEMINI_TMP_DIR = path.join(GEMINI_GLOBAL_DIR, "tmp");
|
|
14
|
+
const GEMINI_CAPABILITIES = {
|
|
15
|
+
supportsStreaming: true,
|
|
16
|
+
supportsSessionPersistence: true,
|
|
17
|
+
supportsDynamicModes: true,
|
|
18
|
+
supportsMcpServers: true,
|
|
19
|
+
supportsReasoningStream: false,
|
|
20
|
+
supportsToolInvocations: true,
|
|
21
|
+
};
|
|
22
|
+
const DEFAULT_GEMINI_MODE = "default";
|
|
23
|
+
export const GEMINI_MODEL_CATALOG = [
|
|
24
|
+
{
|
|
25
|
+
id: "auto-gemini-2.5",
|
|
26
|
+
label: "Auto 2.5",
|
|
27
|
+
description: "Automatic routing across Gemini 2.5 models",
|
|
28
|
+
isDefault: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "auto-gemini-3",
|
|
32
|
+
label: "Auto 3 Preview",
|
|
33
|
+
description: "Automatic routing across Gemini 3 preview models",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "gemini-2.5-pro",
|
|
37
|
+
label: "Gemini 2.5 Pro",
|
|
38
|
+
description: "High-capability reasoning model",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "gemini-2.5-flash",
|
|
42
|
+
label: "Gemini 2.5 Flash",
|
|
43
|
+
description: "Fast general-purpose model",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "gemini-2.5-flash-lite",
|
|
47
|
+
label: "Gemini 2.5 Flash Lite",
|
|
48
|
+
description: "Low-latency lightweight model",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "gemini-3-pro-preview",
|
|
52
|
+
label: "Gemini 3 Pro Preview",
|
|
53
|
+
description: "Preview reasoning model",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: "gemini-3.1-pro-preview",
|
|
57
|
+
label: "Gemini 3.1 Pro Preview",
|
|
58
|
+
description: "Preview reasoning model with updated capabilities",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "gemini-3.1-pro-preview-customtools",
|
|
62
|
+
label: "Gemini 3.1 Pro Preview Custom Tools",
|
|
63
|
+
description: "Preview custom-tools model",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "gemini-3-flash-preview",
|
|
67
|
+
label: "Gemini 3 Flash Preview",
|
|
68
|
+
description: "Preview fast model",
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
const GEMINI_RUNTIME_MODES = [
|
|
72
|
+
{
|
|
73
|
+
id: "default",
|
|
74
|
+
label: "Always Ask",
|
|
75
|
+
description: "Gemini CLI default approval mode",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "auto_edit",
|
|
79
|
+
label: "Auto Edit",
|
|
80
|
+
description: "Auto-approve edit tools while keeping other approvals enabled",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "yolo",
|
|
84
|
+
label: "Bypass",
|
|
85
|
+
description: "Auto-approve all tool calls",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "plan",
|
|
89
|
+
label: "Plan Mode",
|
|
90
|
+
description: "Read-only planning mode",
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
const GeminiStreamInitSchema = z.object({
|
|
94
|
+
type: z.literal("init"),
|
|
95
|
+
session_id: z.string().trim().min(1),
|
|
96
|
+
model: z.string().trim().min(1).optional(),
|
|
97
|
+
});
|
|
98
|
+
const GeminiStreamMessageSchema = z.object({
|
|
99
|
+
type: z.literal("message"),
|
|
100
|
+
role: z.enum(["user", "assistant"]),
|
|
101
|
+
content: z.string(),
|
|
102
|
+
delta: z.boolean().optional(),
|
|
103
|
+
});
|
|
104
|
+
const GeminiStreamToolUseSchema = z.object({
|
|
105
|
+
type: z.literal("tool_use"),
|
|
106
|
+
tool_name: z.string().trim().min(1),
|
|
107
|
+
tool_id: z.string().trim().min(1),
|
|
108
|
+
parameters: z.unknown().optional(),
|
|
109
|
+
});
|
|
110
|
+
const GeminiStreamToolResultSchema = z.object({
|
|
111
|
+
type: z.literal("tool_result"),
|
|
112
|
+
tool_id: z.string().trim().min(1),
|
|
113
|
+
status: z.string().trim().min(1),
|
|
114
|
+
output: z.unknown().optional(),
|
|
115
|
+
error: z.unknown().optional(),
|
|
116
|
+
});
|
|
117
|
+
const GeminiStreamErrorSchema = z.object({
|
|
118
|
+
type: z.literal("error"),
|
|
119
|
+
severity: z.string().optional(),
|
|
120
|
+
message: z.string().trim().min(1),
|
|
121
|
+
});
|
|
122
|
+
const GeminiStreamResultSchema = z.object({
|
|
123
|
+
type: z.literal("result"),
|
|
124
|
+
status: z.string().trim().min(1),
|
|
125
|
+
stats: z
|
|
126
|
+
.object({
|
|
127
|
+
total_tokens: z.number().finite().optional(),
|
|
128
|
+
input_tokens: z.number().finite().optional(),
|
|
129
|
+
output_tokens: z.number().finite().optional(),
|
|
130
|
+
cached: z.number().finite().optional(),
|
|
131
|
+
input: z.number().finite().optional(),
|
|
132
|
+
duration_ms: z.number().finite().optional(),
|
|
133
|
+
tool_calls: z.number().finite().optional(),
|
|
134
|
+
})
|
|
135
|
+
.passthrough()
|
|
136
|
+
.optional(),
|
|
137
|
+
});
|
|
138
|
+
const GeminiStreamEventSchema = z.discriminatedUnion("type", [
|
|
139
|
+
GeminiStreamInitSchema,
|
|
140
|
+
GeminiStreamMessageSchema,
|
|
141
|
+
GeminiStreamToolUseSchema,
|
|
142
|
+
GeminiStreamToolResultSchema,
|
|
143
|
+
GeminiStreamErrorSchema,
|
|
144
|
+
GeminiStreamResultSchema,
|
|
145
|
+
]);
|
|
146
|
+
const GeminiRecordedThoughtSchema = z
|
|
147
|
+
.object({
|
|
148
|
+
thought: z.string().optional(),
|
|
149
|
+
text: z.string().optional(),
|
|
150
|
+
})
|
|
151
|
+
.passthrough();
|
|
152
|
+
const GeminiRecordedToolCallSchema = z
|
|
153
|
+
.object({
|
|
154
|
+
id: z.string().optional(),
|
|
155
|
+
name: z.string().optional(),
|
|
156
|
+
args: z.unknown().optional(),
|
|
157
|
+
result: z.unknown().optional(),
|
|
158
|
+
resultDisplay: z.string().optional(),
|
|
159
|
+
status: z.string().optional(),
|
|
160
|
+
})
|
|
161
|
+
.passthrough();
|
|
162
|
+
const GeminiRecordedMessageSchema = z
|
|
163
|
+
.object({
|
|
164
|
+
id: z.string().optional(),
|
|
165
|
+
type: z.string(),
|
|
166
|
+
content: z.unknown().optional(),
|
|
167
|
+
displayContent: z.unknown().optional(),
|
|
168
|
+
thoughts: z.array(GeminiRecordedThoughtSchema).optional(),
|
|
169
|
+
toolCalls: z.array(GeminiRecordedToolCallSchema).optional(),
|
|
170
|
+
})
|
|
171
|
+
.passthrough();
|
|
172
|
+
const GeminiRecordedSessionSchema = z
|
|
173
|
+
.object({
|
|
174
|
+
sessionId: z.string().trim().min(1),
|
|
175
|
+
startTime: z.string(),
|
|
176
|
+
lastUpdated: z.string(),
|
|
177
|
+
summary: z.string().optional(),
|
|
178
|
+
kind: z.string().optional(),
|
|
179
|
+
messages: z.array(GeminiRecordedMessageSchema),
|
|
180
|
+
})
|
|
181
|
+
.passthrough();
|
|
182
|
+
function resolveGeminiBinary() {
|
|
183
|
+
try {
|
|
184
|
+
const geminiPath = execSync("which gemini", { encoding: "utf8" }).trim();
|
|
185
|
+
if (geminiPath) {
|
|
186
|
+
return geminiPath;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// fall through
|
|
191
|
+
}
|
|
192
|
+
throw new Error("Gemini CLI not found. Please install gemini globally so Junction can launch the provider.");
|
|
193
|
+
}
|
|
194
|
+
export function normalizeGeminiMode(modeId) {
|
|
195
|
+
if (!modeId) {
|
|
196
|
+
return DEFAULT_GEMINI_MODE;
|
|
197
|
+
}
|
|
198
|
+
if (GEMINI_MODES.includes(modeId)) {
|
|
199
|
+
return modeId;
|
|
200
|
+
}
|
|
201
|
+
if (modeId === "bypassPermissions") {
|
|
202
|
+
return "yolo";
|
|
203
|
+
}
|
|
204
|
+
throw new Error(`Unknown Gemini mode '${modeId}'. Valid modes: ${GEMINI_MODES.join(", ")}`);
|
|
205
|
+
}
|
|
206
|
+
function normalizePromptInput(prompt) {
|
|
207
|
+
if (typeof prompt === "string") {
|
|
208
|
+
return prompt;
|
|
209
|
+
}
|
|
210
|
+
const textBlocks = prompt
|
|
211
|
+
.filter((block) => block.type === "text")
|
|
212
|
+
.map((block) => block.text);
|
|
213
|
+
if (textBlocks.length === 0) {
|
|
214
|
+
throw new Error("Gemini CLI does not currently support image-only prompts in Junction.");
|
|
215
|
+
}
|
|
216
|
+
return textBlocks.join("\n\n");
|
|
217
|
+
}
|
|
218
|
+
function mapGeminiUsage(stats) {
|
|
219
|
+
if (!stats) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
inputTokens: stats.input_tokens ?? stats.input,
|
|
224
|
+
cachedInputTokens: stats.cached,
|
|
225
|
+
outputTokens: stats.output_tokens,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function normalizeToolStatus(status) {
|
|
229
|
+
const normalized = status?.trim().toLowerCase() ?? "";
|
|
230
|
+
if (normalized === "success" || normalized === "completed" || normalized === "done") {
|
|
231
|
+
return "completed";
|
|
232
|
+
}
|
|
233
|
+
if (normalized === "error" ||
|
|
234
|
+
normalized === "failed" ||
|
|
235
|
+
normalized === "failure") {
|
|
236
|
+
return "failed";
|
|
237
|
+
}
|
|
238
|
+
if (normalized === "canceled" ||
|
|
239
|
+
normalized === "cancelled" ||
|
|
240
|
+
normalized === "aborted" ||
|
|
241
|
+
normalized === "interrupted") {
|
|
242
|
+
return "canceled";
|
|
243
|
+
}
|
|
244
|
+
return "running";
|
|
245
|
+
}
|
|
246
|
+
function toPlainTextToolDetail(toolName, output) {
|
|
247
|
+
const text = nonEmptyString(typeof output === "string" ? output : undefined) ??
|
|
248
|
+
(typeof output === "object" && output !== null && "message" in output
|
|
249
|
+
? nonEmptyString(output.message)
|
|
250
|
+
: undefined);
|
|
251
|
+
return {
|
|
252
|
+
type: "plain_text",
|
|
253
|
+
label: toolName,
|
|
254
|
+
...(text ? { text } : {}),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function deriveGeminiToolDetail(toolName, input, output) {
|
|
258
|
+
const normalized = toolName.trim().toLowerCase();
|
|
259
|
+
if (normalized === "run_shell_command" ||
|
|
260
|
+
normalized === "shell" ||
|
|
261
|
+
normalized === "bash" ||
|
|
262
|
+
normalized === "exec_command") {
|
|
263
|
+
const parsedInput = ToolShellInputSchema.safeParse(input);
|
|
264
|
+
const parsedOutput = ToolShellOutputSchema.safeParse(output);
|
|
265
|
+
return (toShellToolDetail(parsedInput.success ? parsedInput.data : null, parsedOutput.success ? parsedOutput.data : null) ?? toPlainTextToolDetail(toolName, output));
|
|
266
|
+
}
|
|
267
|
+
if (normalized === "read_file" ||
|
|
268
|
+
normalized === "read" ||
|
|
269
|
+
normalized === "view") {
|
|
270
|
+
const parsedInput = ToolReadInputSchema.safeParse(input);
|
|
271
|
+
const parsedOutput = ToolReadOutputSchema.safeParse(output);
|
|
272
|
+
return (toReadToolDetail(parsedInput.success ? parsedInput.data : null, parsedOutput.success ? parsedOutput.data : null) ?? toPlainTextToolDetail(toolName, output));
|
|
273
|
+
}
|
|
274
|
+
if (normalized === "write_file" ||
|
|
275
|
+
normalized === "write" ||
|
|
276
|
+
normalized === "create_file") {
|
|
277
|
+
const parsedInput = ToolWriteInputSchema.safeParse(input);
|
|
278
|
+
const parsedOutput = ToolWriteOutputSchema.safeParse(output);
|
|
279
|
+
return (toWriteToolDetail(parsedInput.success ? parsedInput.data : null, parsedOutput.success ? parsedOutput.data : null) ?? toPlainTextToolDetail(toolName, output));
|
|
280
|
+
}
|
|
281
|
+
if (normalized === "replace" ||
|
|
282
|
+
normalized === "edit" ||
|
|
283
|
+
normalized === "apply_patch" ||
|
|
284
|
+
normalized === "apply_diff") {
|
|
285
|
+
const parsedInput = ToolEditInputSchema.safeParse(input);
|
|
286
|
+
const parsedOutput = ToolEditOutputSchema.safeParse(output);
|
|
287
|
+
return (toEditToolDetail(parsedInput.success ? parsedInput.data : null, parsedOutput.success ? parsedOutput.data : null) ?? toPlainTextToolDetail(toolName, output));
|
|
288
|
+
}
|
|
289
|
+
if (normalized === "grep" ||
|
|
290
|
+
normalized === "glob" ||
|
|
291
|
+
normalized === "search_text" ||
|
|
292
|
+
normalized === "search" ||
|
|
293
|
+
normalized === "web_search" ||
|
|
294
|
+
normalized === "google_search" ||
|
|
295
|
+
normalized === "web_fetch") {
|
|
296
|
+
const parsedInput = ToolSearchInputSchema.safeParse(input);
|
|
297
|
+
return (toSearchToolDetail(parsedInput.success ? parsedInput.data : null) ??
|
|
298
|
+
toPlainTextToolDetail(toolName, output));
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
type: "unknown",
|
|
302
|
+
input: input ?? null,
|
|
303
|
+
output: output ?? null,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
export function toGeminiToolTimelineItem(params) {
|
|
307
|
+
const callId = coerceToolCallId({
|
|
308
|
+
providerPrefix: GEMINI_PROVIDER,
|
|
309
|
+
rawCallId: params.callId,
|
|
310
|
+
toolName: params.toolName,
|
|
311
|
+
input: params.input,
|
|
312
|
+
});
|
|
313
|
+
const status = normalizeToolStatus(params.status);
|
|
314
|
+
const detail = deriveGeminiToolDetail(params.toolName, params.input ?? null, params.output ?? null);
|
|
315
|
+
if (status === "failed") {
|
|
316
|
+
return {
|
|
317
|
+
type: "tool_call",
|
|
318
|
+
callId,
|
|
319
|
+
name: params.toolName,
|
|
320
|
+
status,
|
|
321
|
+
detail,
|
|
322
|
+
error: params.error ?? { message: "Tool call failed" },
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
type: "tool_call",
|
|
327
|
+
callId,
|
|
328
|
+
name: params.toolName,
|
|
329
|
+
status,
|
|
330
|
+
detail,
|
|
331
|
+
error: null,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function toGeminiModelDefinitions() {
|
|
335
|
+
return GEMINI_MODEL_CATALOG.map((model) => ({
|
|
336
|
+
provider: GEMINI_PROVIDER,
|
|
337
|
+
id: model.id,
|
|
338
|
+
label: model.label,
|
|
339
|
+
description: model.description,
|
|
340
|
+
...("isDefault" in model && model.isDefault === true ? { isDefault: true } : {}),
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
async function resolveProjectTempDirForCwd(cwd) {
|
|
344
|
+
const normalizedCwd = await normalizeComparablePath(cwd);
|
|
345
|
+
let entries;
|
|
346
|
+
try {
|
|
347
|
+
entries = await readdir(GEMINI_TMP_DIR);
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
for (const entry of entries) {
|
|
353
|
+
const candidateDir = path.join(GEMINI_TMP_DIR, entry);
|
|
354
|
+
const markerPath = path.join(candidateDir, ".project_root");
|
|
355
|
+
try {
|
|
356
|
+
const marker = (await readFile(markerPath, "utf8")).trim();
|
|
357
|
+
const normalizedMarker = await normalizeComparablePath(marker);
|
|
358
|
+
if (normalizedMarker === normalizedCwd) {
|
|
359
|
+
return candidateDir;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// ignore non-project entries
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
async function listKnownGeminiProjects() {
|
|
369
|
+
let entries;
|
|
370
|
+
try {
|
|
371
|
+
entries = await readdir(GEMINI_TMP_DIR);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
const projects = [];
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
const tempDir = path.join(GEMINI_TMP_DIR, entry);
|
|
379
|
+
const markerPath = path.join(tempDir, ".project_root");
|
|
380
|
+
try {
|
|
381
|
+
const cwd = (await readFile(markerPath, "utf8")).trim();
|
|
382
|
+
if (cwd.length > 0) {
|
|
383
|
+
projects.push({ cwd, tempDir });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// ignore
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return projects;
|
|
391
|
+
}
|
|
392
|
+
async function normalizeComparablePath(inputPath) {
|
|
393
|
+
try {
|
|
394
|
+
return await realpath(inputPath);
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return path.resolve(inputPath);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function readGeminiSessionFile(filePath) {
|
|
401
|
+
try {
|
|
402
|
+
const raw = await readFile(filePath, "utf8");
|
|
403
|
+
const parsed = JSON.parse(raw);
|
|
404
|
+
const session = GeminiRecordedSessionSchema.safeParse(parsed);
|
|
405
|
+
if (!session.success) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
if (session.data.kind === "subagent") {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
return session.data;
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function extractRecordedContentText(content) {
|
|
418
|
+
if (typeof content === "string") {
|
|
419
|
+
return content;
|
|
420
|
+
}
|
|
421
|
+
if (Array.isArray(content)) {
|
|
422
|
+
return content.map((entry) => extractRecordedContentText(entry)).join("");
|
|
423
|
+
}
|
|
424
|
+
if (content && typeof content === "object") {
|
|
425
|
+
const record = content;
|
|
426
|
+
const directText = nonEmptyString(record.text) ??
|
|
427
|
+
nonEmptyString(record.content) ??
|
|
428
|
+
nonEmptyString(record.displayText);
|
|
429
|
+
if (directText) {
|
|
430
|
+
return directText;
|
|
431
|
+
}
|
|
432
|
+
if ("parts" in record) {
|
|
433
|
+
return extractRecordedContentText(record.parts);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return "";
|
|
437
|
+
}
|
|
438
|
+
function getGeminiSessionTitle(session) {
|
|
439
|
+
const summary = nonEmptyString(session.summary);
|
|
440
|
+
if (summary) {
|
|
441
|
+
return summary;
|
|
442
|
+
}
|
|
443
|
+
for (const message of session.messages) {
|
|
444
|
+
if (message.type !== "user") {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const text = extractRecordedContentText(message.content ?? message.displayContent).trim();
|
|
448
|
+
if (text.length > 0) {
|
|
449
|
+
return text;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
export function buildGeminiHistoryTimeline(session) {
|
|
455
|
+
const items = [];
|
|
456
|
+
for (const message of session.messages) {
|
|
457
|
+
if (message.type === "user") {
|
|
458
|
+
const text = extractRecordedContentText(message.content ?? message.displayContent).trim();
|
|
459
|
+
if (text.length > 0) {
|
|
460
|
+
items.push({
|
|
461
|
+
type: "user_message",
|
|
462
|
+
text,
|
|
463
|
+
...(message.id ? { messageId: message.id } : {}),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
if (message.type !== "gemini") {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
const assistantText = extractRecordedContentText(message.content ?? message.displayContent).trim();
|
|
472
|
+
if (assistantText.length > 0) {
|
|
473
|
+
items.push({ type: "assistant_message", text: assistantText });
|
|
474
|
+
}
|
|
475
|
+
for (const thought of message.thoughts ?? []) {
|
|
476
|
+
const text = nonEmptyString(thought.text) ?? nonEmptyString(thought.thought);
|
|
477
|
+
if (text) {
|
|
478
|
+
items.push({ type: "reasoning", text });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (const toolCall of message.toolCalls ?? []) {
|
|
482
|
+
const toolName = nonEmptyString(toolCall.name) ?? "tool";
|
|
483
|
+
items.push(toGeminiToolTimelineItem({
|
|
484
|
+
toolName,
|
|
485
|
+
callId: toolCall.id,
|
|
486
|
+
status: toolCall.status,
|
|
487
|
+
input: toolCall.args,
|
|
488
|
+
output: toolCall.resultDisplay ?? toolCall.result,
|
|
489
|
+
error: normalizeToolStatus(toolCall.status) === "failed"
|
|
490
|
+
? toolCall.result ?? { message: "Tool call failed" }
|
|
491
|
+
: null,
|
|
492
|
+
}));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return items;
|
|
496
|
+
}
|
|
497
|
+
async function listGeminiSessionsForProject(params) {
|
|
498
|
+
const chatsDir = path.join(params.tempDir, "chats");
|
|
499
|
+
let chatFiles;
|
|
500
|
+
try {
|
|
501
|
+
chatFiles = await readdir(chatsDir);
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
const sessions = [];
|
|
507
|
+
for (const fileName of chatFiles) {
|
|
508
|
+
if (!fileName.startsWith("session-") || !fileName.endsWith(".json")) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const filePath = path.join(chatsDir, fileName);
|
|
512
|
+
const session = await readGeminiSessionFile(filePath);
|
|
513
|
+
if (!session) {
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
sessions.push({
|
|
517
|
+
provider: GEMINI_PROVIDER,
|
|
518
|
+
sessionId: session.sessionId,
|
|
519
|
+
cwd: params.cwd,
|
|
520
|
+
title: getGeminiSessionTitle(session),
|
|
521
|
+
lastActivityAt: new Date(session.lastUpdated),
|
|
522
|
+
persistence: {
|
|
523
|
+
provider: GEMINI_PROVIDER,
|
|
524
|
+
sessionId: session.sessionId,
|
|
525
|
+
nativeHandle: filePath,
|
|
526
|
+
metadata: {
|
|
527
|
+
cwd: params.cwd,
|
|
528
|
+
filePath,
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
timeline: buildGeminiHistoryTimeline(session),
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
return sessions
|
|
535
|
+
.sort((a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime())
|
|
536
|
+
.slice(0, params.limit ?? sessions.length);
|
|
537
|
+
}
|
|
538
|
+
function toGeminiMcpServerConfig(config) {
|
|
539
|
+
if (config.type === "stdio") {
|
|
540
|
+
return {
|
|
541
|
+
command: config.command,
|
|
542
|
+
...(config.args ? { args: config.args } : {}),
|
|
543
|
+
...(config.env ? { env: config.env } : {}),
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
type: config.type,
|
|
548
|
+
url: config.url,
|
|
549
|
+
...(config.headers ? { headers: config.headers } : {}),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
async function writeGeminiOverlayConfig(modeId, mcpServers) {
|
|
553
|
+
const overlay = {};
|
|
554
|
+
if (modeId === "plan") {
|
|
555
|
+
overlay.experimental = { plan: true };
|
|
556
|
+
}
|
|
557
|
+
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
558
|
+
overlay.mcpServers = Object.fromEntries(Object.entries(mcpServers).map(([name, serverConfig]) => [
|
|
559
|
+
name,
|
|
560
|
+
toGeminiMcpServerConfig(serverConfig),
|
|
561
|
+
]));
|
|
562
|
+
}
|
|
563
|
+
if (Object.keys(overlay).length === 0) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
const dir = await mkdtemp(path.join(tmpdir(), "junction-gemini-settings-"));
|
|
567
|
+
const settingsPath = path.join(dir, "settings.json");
|
|
568
|
+
await writeFile(settingsPath, JSON.stringify(overlay, null, 2), "utf8");
|
|
569
|
+
return { dir, settingsPath };
|
|
570
|
+
}
|
|
571
|
+
export class GeminiAgentClient {
|
|
572
|
+
constructor(logger, runtimeSettings) {
|
|
573
|
+
this.provider = GEMINI_PROVIDER;
|
|
574
|
+
this.capabilities = GEMINI_CAPABILITIES;
|
|
575
|
+
this.logger = logger.child({ module: "agent", provider: GEMINI_PROVIDER });
|
|
576
|
+
this.runtimeSettings = runtimeSettings;
|
|
577
|
+
}
|
|
578
|
+
async createSession(config) {
|
|
579
|
+
const geminiConfig = this.assertConfig(config);
|
|
580
|
+
return new GeminiAgentSession(geminiConfig, this.logger, this.runtimeSettings);
|
|
581
|
+
}
|
|
582
|
+
async resumeSession(handle, overrides) {
|
|
583
|
+
const cwd = overrides?.cwd ?? handle.metadata?.cwd;
|
|
584
|
+
if (!cwd) {
|
|
585
|
+
throw new Error("Gemini resume requires the original working directory");
|
|
586
|
+
}
|
|
587
|
+
const config = this.assertConfig({
|
|
588
|
+
provider: GEMINI_PROVIDER,
|
|
589
|
+
cwd,
|
|
590
|
+
...overrides,
|
|
591
|
+
});
|
|
592
|
+
return new GeminiAgentSession(config, this.logger, this.runtimeSettings, handle.sessionId);
|
|
593
|
+
}
|
|
594
|
+
async listModels(_options) {
|
|
595
|
+
return toGeminiModelDefinitions();
|
|
596
|
+
}
|
|
597
|
+
async listPersistedAgents(options) {
|
|
598
|
+
const projects = await listKnownGeminiProjects();
|
|
599
|
+
const sessions = await Promise.all(projects.map((project) => listGeminiSessionsForProject({
|
|
600
|
+
...project,
|
|
601
|
+
limit: options?.limit,
|
|
602
|
+
})));
|
|
603
|
+
return sessions
|
|
604
|
+
.flat()
|
|
605
|
+
.sort((a, b) => b.lastActivityAt.getTime() - a.lastActivityAt.getTime())
|
|
606
|
+
.slice(0, options?.limit ?? 20);
|
|
607
|
+
}
|
|
608
|
+
async isAvailable() {
|
|
609
|
+
return isProviderCommandAvailable(this.runtimeSettings?.command, resolveGeminiBinary);
|
|
610
|
+
}
|
|
611
|
+
assertConfig(config) {
|
|
612
|
+
if (config.provider !== GEMINI_PROVIDER) {
|
|
613
|
+
throw new Error(`GeminiAgentClient received config for provider '${config.provider}'`);
|
|
614
|
+
}
|
|
615
|
+
return { ...config, provider: GEMINI_PROVIDER };
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
class GeminiAgentSession {
|
|
619
|
+
constructor(config, logger, runtimeSettings, sessionId) {
|
|
620
|
+
this.provider = GEMINI_PROVIDER;
|
|
621
|
+
this.capabilities = GEMINI_CAPABILITIES;
|
|
622
|
+
this.currentChild = null;
|
|
623
|
+
this.currentRunInterrupted = false;
|
|
624
|
+
this.overlayDir = null;
|
|
625
|
+
this.pendingPermissions = new Map();
|
|
626
|
+
this.config = config;
|
|
627
|
+
this.logger = logger.child({ sessionProvider: GEMINI_PROVIDER });
|
|
628
|
+
this.runtimeSettings = runtimeSettings;
|
|
629
|
+
this.currentMode = normalizeGeminiMode(config.modeId);
|
|
630
|
+
this.sessionId = sessionId ?? null;
|
|
631
|
+
this.config.thinkingOptionId = undefined;
|
|
632
|
+
}
|
|
633
|
+
get id() {
|
|
634
|
+
return this.sessionId;
|
|
635
|
+
}
|
|
636
|
+
async run(prompt, options) {
|
|
637
|
+
const timeline = [];
|
|
638
|
+
let finalText = "";
|
|
639
|
+
let usage;
|
|
640
|
+
let canceled = false;
|
|
641
|
+
for await (const event of this.stream(prompt, options)) {
|
|
642
|
+
if (event.type === "timeline") {
|
|
643
|
+
timeline.push(event.item);
|
|
644
|
+
if (event.item.type === "assistant_message") {
|
|
645
|
+
finalText += event.item.text;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
else if (event.type === "turn_completed") {
|
|
649
|
+
usage = event.usage;
|
|
650
|
+
}
|
|
651
|
+
else if (event.type === "turn_failed") {
|
|
652
|
+
throw new Error(event.error);
|
|
653
|
+
}
|
|
654
|
+
else if (event.type === "turn_canceled") {
|
|
655
|
+
canceled = true;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
sessionId: this.sessionId ?? "",
|
|
660
|
+
finalText,
|
|
661
|
+
usage,
|
|
662
|
+
timeline,
|
|
663
|
+
...(canceled ? { canceled } : {}),
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
async *stream(prompt, _options) {
|
|
667
|
+
if (this.currentChild) {
|
|
668
|
+
throw new Error("Gemini session is already running");
|
|
669
|
+
}
|
|
670
|
+
const promptText = normalizePromptInput(prompt);
|
|
671
|
+
const modeId = this.currentMode;
|
|
672
|
+
const overlay = await writeGeminiOverlayConfig(modeId, this.config.mcpServers);
|
|
673
|
+
if (overlay) {
|
|
674
|
+
await this.cleanupOverlay();
|
|
675
|
+
this.overlayDir = overlay.dir;
|
|
676
|
+
}
|
|
677
|
+
const launchPrefix = resolveProviderCommandPrefix(this.runtimeSettings?.command, resolveGeminiBinary);
|
|
678
|
+
const env = applyProviderEnv(process.env, this.runtimeSettings);
|
|
679
|
+
if (overlay) {
|
|
680
|
+
env.GEMINI_CLI_SYSTEM_SETTINGS_PATH = overlay.settingsPath;
|
|
681
|
+
}
|
|
682
|
+
const args = [
|
|
683
|
+
...launchPrefix.args,
|
|
684
|
+
"-p",
|
|
685
|
+
promptText,
|
|
686
|
+
"--output-format",
|
|
687
|
+
"stream-json",
|
|
688
|
+
"--approval-mode",
|
|
689
|
+
modeId,
|
|
690
|
+
];
|
|
691
|
+
const modelId = typeof this.config.model === "string" && this.config.model.trim().length > 0
|
|
692
|
+
? this.config.model.trim()
|
|
693
|
+
: null;
|
|
694
|
+
if (modelId) {
|
|
695
|
+
args.push("--model", modelId);
|
|
696
|
+
}
|
|
697
|
+
if (this.sessionId) {
|
|
698
|
+
args.push("--resume", this.sessionId);
|
|
699
|
+
}
|
|
700
|
+
const child = spawn(launchPrefix.command, args, {
|
|
701
|
+
cwd: this.config.cwd,
|
|
702
|
+
env,
|
|
703
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
704
|
+
});
|
|
705
|
+
this.currentChild = child;
|
|
706
|
+
this.currentRunInterrupted = false;
|
|
707
|
+
const stderrChunks = [];
|
|
708
|
+
child.stderr.on("data", (chunk) => {
|
|
709
|
+
const text = chunk.toString("utf8");
|
|
710
|
+
if (text.length > 0) {
|
|
711
|
+
stderrChunks.push(text);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
const closePromise = new Promise((resolve, reject) => {
|
|
715
|
+
child.once("error", reject);
|
|
716
|
+
child.once("close", (code, signal) => resolve({ code, signal }));
|
|
717
|
+
});
|
|
718
|
+
const lineReader = readline.createInterface({
|
|
719
|
+
input: child.stdout,
|
|
720
|
+
crlfDelay: Infinity,
|
|
721
|
+
});
|
|
722
|
+
const pendingToolCalls = new Map();
|
|
723
|
+
let sawResult = false;
|
|
724
|
+
let failureMessage = null;
|
|
725
|
+
yield {
|
|
726
|
+
type: "turn_started",
|
|
727
|
+
provider: GEMINI_PROVIDER,
|
|
728
|
+
};
|
|
729
|
+
try {
|
|
730
|
+
for await (const rawLine of lineReader) {
|
|
731
|
+
const line = rawLine.trim();
|
|
732
|
+
if (!line) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
let parsedJson;
|
|
736
|
+
try {
|
|
737
|
+
parsedJson = JSON.parse(line);
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
const parsedEvent = GeminiStreamEventSchema.safeParse(parsedJson);
|
|
743
|
+
if (!parsedEvent.success) {
|
|
744
|
+
continue;
|
|
745
|
+
}
|
|
746
|
+
const event = parsedEvent.data;
|
|
747
|
+
if (event.type === "init") {
|
|
748
|
+
this.sessionId = event.session_id;
|
|
749
|
+
if (event.model) {
|
|
750
|
+
this.config.model = event.model;
|
|
751
|
+
}
|
|
752
|
+
yield {
|
|
753
|
+
type: "thread_started",
|
|
754
|
+
provider: GEMINI_PROVIDER,
|
|
755
|
+
sessionId: event.session_id,
|
|
756
|
+
};
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (event.type === "message") {
|
|
760
|
+
if (event.role === "assistant" && event.content.length > 0) {
|
|
761
|
+
yield {
|
|
762
|
+
type: "timeline",
|
|
763
|
+
provider: GEMINI_PROVIDER,
|
|
764
|
+
item: {
|
|
765
|
+
type: "assistant_message",
|
|
766
|
+
text: event.content,
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (event.type === "tool_use") {
|
|
773
|
+
pendingToolCalls.set(event.tool_id, {
|
|
774
|
+
toolName: event.tool_name,
|
|
775
|
+
input: event.parameters,
|
|
776
|
+
});
|
|
777
|
+
yield {
|
|
778
|
+
type: "timeline",
|
|
779
|
+
provider: GEMINI_PROVIDER,
|
|
780
|
+
item: toGeminiToolTimelineItem({
|
|
781
|
+
toolName: event.tool_name,
|
|
782
|
+
callId: event.tool_id,
|
|
783
|
+
status: "running",
|
|
784
|
+
input: event.parameters,
|
|
785
|
+
}),
|
|
786
|
+
};
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (event.type === "tool_result") {
|
|
790
|
+
const pending = pendingToolCalls.get(event.tool_id);
|
|
791
|
+
const toolName = pending?.toolName ?? "tool";
|
|
792
|
+
yield {
|
|
793
|
+
type: "timeline",
|
|
794
|
+
provider: GEMINI_PROVIDER,
|
|
795
|
+
item: toGeminiToolTimelineItem({
|
|
796
|
+
toolName,
|
|
797
|
+
callId: event.tool_id,
|
|
798
|
+
status: event.status,
|
|
799
|
+
input: pending?.input,
|
|
800
|
+
output: event.output,
|
|
801
|
+
error: event.error,
|
|
802
|
+
}),
|
|
803
|
+
};
|
|
804
|
+
pendingToolCalls.delete(event.tool_id);
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
if (event.type === "error") {
|
|
808
|
+
failureMessage = event.message;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (event.type === "result") {
|
|
812
|
+
sawResult = true;
|
|
813
|
+
if (event.status !== "success") {
|
|
814
|
+
failureMessage = failureMessage ?? `Gemini CLI result status: ${event.status}`;
|
|
815
|
+
yield {
|
|
816
|
+
type: "turn_failed",
|
|
817
|
+
provider: GEMINI_PROVIDER,
|
|
818
|
+
error: failureMessage,
|
|
819
|
+
};
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
yield {
|
|
823
|
+
type: "turn_completed",
|
|
824
|
+
provider: GEMINI_PROVIDER,
|
|
825
|
+
usage: mapGeminiUsage(event.stats),
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
finally {
|
|
831
|
+
lineReader.close();
|
|
832
|
+
const { code, signal } = await closePromise.catch((error) => ({
|
|
833
|
+
code: 1,
|
|
834
|
+
signal: null,
|
|
835
|
+
error,
|
|
836
|
+
}));
|
|
837
|
+
this.currentChild = null;
|
|
838
|
+
if (!sawResult) {
|
|
839
|
+
if (this.currentRunInterrupted || signal) {
|
|
840
|
+
yield {
|
|
841
|
+
type: "turn_canceled",
|
|
842
|
+
provider: GEMINI_PROVIDER,
|
|
843
|
+
reason: "Interrupted",
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
else {
|
|
847
|
+
const stderr = stderrChunks.join("").trim();
|
|
848
|
+
const message = failureMessage ??
|
|
849
|
+
stderr ??
|
|
850
|
+
(typeof code === "number" && code !== 0
|
|
851
|
+
? `Gemini CLI exited with code ${code}`
|
|
852
|
+
: "Gemini CLI ended without a result event");
|
|
853
|
+
yield {
|
|
854
|
+
type: "turn_failed",
|
|
855
|
+
provider: GEMINI_PROVIDER,
|
|
856
|
+
error: message,
|
|
857
|
+
};
|
|
858
|
+
this.logger.warn({ code, signal, message }, "Gemini run failed");
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
this.currentRunInterrupted = false;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
async *streamHistory() {
|
|
865
|
+
if (!this.sessionId) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const session = await this.loadRecordedSession(this.sessionId);
|
|
869
|
+
if (!session) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
for (const item of buildGeminiHistoryTimeline(session)) {
|
|
873
|
+
yield {
|
|
874
|
+
type: "timeline",
|
|
875
|
+
provider: GEMINI_PROVIDER,
|
|
876
|
+
item,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
async getRuntimeInfo() {
|
|
881
|
+
return {
|
|
882
|
+
provider: GEMINI_PROVIDER,
|
|
883
|
+
sessionId: this.sessionId,
|
|
884
|
+
model: this.config.model ?? null,
|
|
885
|
+
thinkingOptionId: null,
|
|
886
|
+
modeId: this.currentMode,
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
async getAvailableModes() {
|
|
890
|
+
return GEMINI_RUNTIME_MODES;
|
|
891
|
+
}
|
|
892
|
+
async getCurrentMode() {
|
|
893
|
+
return this.currentMode;
|
|
894
|
+
}
|
|
895
|
+
async setMode(modeId) {
|
|
896
|
+
this.currentMode = normalizeGeminiMode(modeId);
|
|
897
|
+
this.config.modeId = this.currentMode;
|
|
898
|
+
}
|
|
899
|
+
getPendingPermissions() {
|
|
900
|
+
return Array.from(this.pendingPermissions.values());
|
|
901
|
+
}
|
|
902
|
+
async respondToPermission(requestId, _response) {
|
|
903
|
+
throw new Error(`Gemini permission request '${requestId}' cannot be resumed through the headless CLI integration`);
|
|
904
|
+
}
|
|
905
|
+
describePersistence() {
|
|
906
|
+
if (!this.sessionId) {
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
return {
|
|
910
|
+
provider: GEMINI_PROVIDER,
|
|
911
|
+
sessionId: this.sessionId,
|
|
912
|
+
nativeHandle: this.sessionId,
|
|
913
|
+
metadata: {
|
|
914
|
+
cwd: this.config.cwd,
|
|
915
|
+
},
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
async interrupt() {
|
|
919
|
+
this.currentRunInterrupted = true;
|
|
920
|
+
const child = this.currentChild;
|
|
921
|
+
if (!child) {
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
child.kill("SIGINT");
|
|
925
|
+
}
|
|
926
|
+
async close() {
|
|
927
|
+
await this.interrupt();
|
|
928
|
+
await this.cleanupOverlay();
|
|
929
|
+
}
|
|
930
|
+
async setModel(modelId) {
|
|
931
|
+
this.config.model =
|
|
932
|
+
typeof modelId === "string" && modelId.trim().length > 0
|
|
933
|
+
? modelId.trim()
|
|
934
|
+
: undefined;
|
|
935
|
+
}
|
|
936
|
+
async setThinkingOption(_thinkingOptionId) {
|
|
937
|
+
this.config.thinkingOptionId = undefined;
|
|
938
|
+
}
|
|
939
|
+
async loadRecordedSession(sessionId) {
|
|
940
|
+
const projectTempDir = await resolveProjectTempDirForCwd(this.config.cwd);
|
|
941
|
+
if (!projectTempDir) {
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
const chatsDir = path.join(projectTempDir, "chats");
|
|
945
|
+
let files;
|
|
946
|
+
try {
|
|
947
|
+
files = await readdir(chatsDir);
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
for (const fileName of files) {
|
|
953
|
+
if (!fileName.startsWith("session-") || !fileName.endsWith(".json")) {
|
|
954
|
+
continue;
|
|
955
|
+
}
|
|
956
|
+
const filePath = path.join(chatsDir, fileName);
|
|
957
|
+
const session = await readGeminiSessionFile(filePath);
|
|
958
|
+
if (session?.sessionId === sessionId) {
|
|
959
|
+
return session;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
async cleanupOverlay() {
|
|
965
|
+
if (!this.overlayDir) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const dir = this.overlayDir;
|
|
969
|
+
this.overlayDir = null;
|
|
970
|
+
await rm(dir, { recursive: true, force: true });
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
//# sourceMappingURL=gemini-agent.js.map
|