@shepai/cli 1.39.0 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/core/src/infrastructure/services/agents/common/agent-executor-factory.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/agent-executor-factory.service.js +5 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/agent-validator.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/agents/common/agent-validator.service.js +1 -0
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/gemini-cli-executor.service.d.ts +52 -0
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/gemini-cli-executor.service.d.ts.map +1 -0
- package/dist/packages/core/src/infrastructure/services/agents/common/executors/gemini-cli-executor.service.js +337 -0
- package/dist/src/presentation/tui/prompts/agent-select.prompt.d.ts +1 -1
- package/dist/src/presentation/tui/prompts/agent-select.prompt.js +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/cache/.previewinfo +1 -1
- package/web/.next/cache/.rscinfo +1 -1
- package/web/.next/cache/config.json +3 -3
- package/web/.next/fallback-build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +1 -1
- package/web/.next/required-server-files.json +1 -1
- package/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/page/server-reference-manifest.json +9 -9
- package/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__19354aea._.js +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
- package/web/.next/server/pages/500.html +2 -2
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +10 -10
- package/web/.next/standalone/src/presentation/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/src/presentation/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/src/presentation/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/src/presentation/web/.next/required-server-files.json +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/app/page/server-reference-manifest.json +9 -9
- package/web/.next/standalone/src/presentation/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__03a0dba7._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__19354aea._.js +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/chunks/ssr/[root-of-the-server]__f12b60be._.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/src/presentation/web/.next/server/server-reference-manifest.json +10 -10
- package/web/.next/standalone/src/presentation/web/server.js +1 -1
- package/web/.next/static/chunks/{429ee8f94032181c.js → 8bea0ce8a07abd50.js} +1 -1
- package/web/.next/trace +1 -1
- package/web/.next/trace-build +1 -1
- /package/web/.next/static/{t8z43Apwz2m0TFf1MnAKq → o1NQgNmvCnthgfg2Ubmf_}/_buildManifest.js +0 -0
- /package/web/.next/static/{t8z43Apwz2m0TFf1MnAKq → o1NQgNmvCnthgfg2Ubmf_}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{t8z43Apwz2m0TFf1MnAKq → o1NQgNmvCnthgfg2Ubmf_}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-executor-factory.service.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/common/agent-executor-factory.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yEAAyE,CAAC;AAC9G,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iFAAiF,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-executor-factory.service.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/common/agent-executor-factory.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yEAAyE,CAAC;AAC9G,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iFAAiF,CAAC;AAI7H,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;GAKG;AACH,qBAAa,oBAAqB,YAAW,qBAAqB;IAMpD,OAAO,CAAC,QAAQ,CAAC,KAAK;IALlC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAE3D;;OAEG;gBAC0B,KAAK,EAAE,aAAa;IAEjD;;;;;;;OAOG;IACH,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,GAAG,cAAc;IA0B9E;;;;OAIG;IACH,kBAAkB,IAAI,SAAS,EAAE;CAGlC"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { ClaudeCodeExecutorService } from './executors/claude-code-executor.service.js';
|
|
11
11
|
import { CursorExecutorService } from './executors/cursor-executor.service.js';
|
|
12
|
+
import { GeminiCliExecutorService } from './executors/gemini-cli-executor.service.js';
|
|
12
13
|
/**
|
|
13
14
|
* Factory that creates and caches agent executor instances.
|
|
14
15
|
*
|
|
@@ -45,6 +46,9 @@ export class AgentExecutorFactory {
|
|
|
45
46
|
case 'cursor':
|
|
46
47
|
executor = new CursorExecutorService(this.spawn);
|
|
47
48
|
break;
|
|
49
|
+
case 'gemini-cli':
|
|
50
|
+
executor = new GeminiCliExecutorService(this.spawn, _authConfig);
|
|
51
|
+
break;
|
|
48
52
|
default:
|
|
49
53
|
throw new Error(`Unsupported agent type: ${agentType}. Supported: ${this.getSupportedAgents().join(', ')}`);
|
|
50
54
|
}
|
|
@@ -57,6 +61,6 @@ export class AgentExecutorFactory {
|
|
|
57
61
|
* @returns Array of supported agent types
|
|
58
62
|
*/
|
|
59
63
|
getSupportedAgents() {
|
|
60
|
-
return ['claude-code', 'cursor'];
|
|
64
|
+
return ['claude-code', 'cursor', 'gemini-cli'];
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-validator.service.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/common/agent-validator.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,0EAA0E,CAAC;AAElF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-validator.service.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/infrastructure/services/agents/common/agent-validator.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,KAAK,EACV,eAAe,EACf,qBAAqB,EACtB,MAAM,0EAA0E,CAAC;AAElF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAW/C;;;;;GAKG;AACH,qBACa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC;;;OAGG;gBACiC,MAAM,EAAE,YAAY;IAIxD;;;;;OAKG;IACG,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,qBAAqB,CAAC;CA2BxE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI Executor Service
|
|
3
|
+
*
|
|
4
|
+
* Infrastructure implementation of IAgentExecutor for the Gemini CLI agent.
|
|
5
|
+
* Executes prompts via the `gemini` CLI subprocess with JSON output format.
|
|
6
|
+
*
|
|
7
|
+
* Uses constructor dependency injection for the spawn function
|
|
8
|
+
* to enable testability without mocking node:child_process directly.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentType, AgentFeature, AgentConfig } from '../../../../../domain/generated/output.js';
|
|
11
|
+
import type { IAgentExecutor, AgentExecutionOptions, AgentExecutionResult, AgentExecutionStreamEvent } from '../../../../../application/ports/output/agents/agent-executor.interface.js';
|
|
12
|
+
import type { SpawnFunction } from '../types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Executor service for Gemini CLI agent.
|
|
15
|
+
* Uses subprocess spawning to interact with the `gemini` CLI.
|
|
16
|
+
*/
|
|
17
|
+
export declare class GeminiCliExecutorService implements IAgentExecutor {
|
|
18
|
+
private readonly spawn;
|
|
19
|
+
private readonly authConfig?;
|
|
20
|
+
readonly agentType: AgentType;
|
|
21
|
+
/** When true, suppresses debug logging (set per-call via options.silent) */
|
|
22
|
+
private silent;
|
|
23
|
+
constructor(spawn: SpawnFunction, authConfig?: AgentConfig | undefined);
|
|
24
|
+
/** Debug logging — writes to stdout so it appears in the worker log file */
|
|
25
|
+
private log;
|
|
26
|
+
supportsFeature(feature: AgentFeature): boolean;
|
|
27
|
+
execute(prompt: string, options?: AgentExecutionOptions): Promise<AgentExecutionResult>;
|
|
28
|
+
executeStream(prompt: string, options?: AgentExecutionOptions): AsyncIterable<AgentExecutionStreamEvent>;
|
|
29
|
+
/**
|
|
30
|
+
* Parse a single stream-JSON line into an AgentExecutionStreamEvent.
|
|
31
|
+
* Returns null for events that should be skipped (init, user messages, unknown types).
|
|
32
|
+
*/
|
|
33
|
+
private parseStreamEvent;
|
|
34
|
+
/**
|
|
35
|
+
* Patterns in stderr that indicate a fatal API error even when exit code is 0.
|
|
36
|
+
* Gemini CLI sometimes exits 0 after exhausting retries on 429/5xx errors.
|
|
37
|
+
*/
|
|
38
|
+
private static readonly FATAL_STDERR_PATTERNS;
|
|
39
|
+
/**
|
|
40
|
+
* Check stderr for patterns indicating fatal API errors.
|
|
41
|
+
* Returns an error message if fatal patterns are found, null otherwise.
|
|
42
|
+
*/
|
|
43
|
+
private detectFatalStderrError;
|
|
44
|
+
/**
|
|
45
|
+
* Extract token usage from Gemini stats structure.
|
|
46
|
+
* Returns undefined if stats are missing (does not throw).
|
|
47
|
+
*/
|
|
48
|
+
private extractUsage;
|
|
49
|
+
private buildArgs;
|
|
50
|
+
private buildSpawnOptions;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=gemini-cli-executor.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini-cli-executor.service.d.ts","sourceRoot":"","sources":["../../../../../../../../../packages/core/src/infrastructure/services/agents/common/executors/gemini-cli-executor.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACZ,MAAM,2CAA2C,CAAC;AACnD,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,4EAA4E,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD;;;GAGG;AACH,qBAAa,wBAAyB,YAAW,cAAc;IAO3D,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAP9B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAA6B;IAE1D,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAS;gBAGJ,KAAK,EAAE,aAAa,EACpB,UAAU,CAAC,EAAE,WAAW,YAAA;IAG3C,4EAA4E;IAC5E,OAAO,CAAC,GAAG;IAMX,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAIzC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAsFtF,aAAa,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B,aAAa,CAAC,yBAAyB,CAAC;IAsG3C;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAmDxB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAI3C;IAEF;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAY9B;;;OAGG;IACH,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,SAAS;IAsBjB,OAAO,CAAC,iBAAiB;CAiB1B"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini CLI Executor Service
|
|
3
|
+
*
|
|
4
|
+
* Infrastructure implementation of IAgentExecutor for the Gemini CLI agent.
|
|
5
|
+
* Executes prompts via the `gemini` CLI subprocess with JSON output format.
|
|
6
|
+
*
|
|
7
|
+
* Uses constructor dependency injection for the spawn function
|
|
8
|
+
* to enable testability without mocking node:child_process directly.
|
|
9
|
+
*/
|
|
10
|
+
/** Features supported by Gemini CLI */
|
|
11
|
+
const SUPPORTED_FEATURES = new Set(['session-resume', 'streaming', 'tool-scoping']);
|
|
12
|
+
/**
|
|
13
|
+
* Executor service for Gemini CLI agent.
|
|
14
|
+
* Uses subprocess spawning to interact with the `gemini` CLI.
|
|
15
|
+
*/
|
|
16
|
+
export class GeminiCliExecutorService {
|
|
17
|
+
spawn;
|
|
18
|
+
authConfig;
|
|
19
|
+
agentType = 'gemini-cli';
|
|
20
|
+
/** When true, suppresses debug logging (set per-call via options.silent) */
|
|
21
|
+
silent = false;
|
|
22
|
+
constructor(spawn, authConfig) {
|
|
23
|
+
this.spawn = spawn;
|
|
24
|
+
this.authConfig = authConfig;
|
|
25
|
+
}
|
|
26
|
+
/** Debug logging — writes to stdout so it appears in the worker log file */
|
|
27
|
+
log(message) {
|
|
28
|
+
if (this.silent)
|
|
29
|
+
return;
|
|
30
|
+
const ts = new Date().toISOString();
|
|
31
|
+
process.stdout.write(`[${ts}] [gemini-cli-executor] ${message}\n`);
|
|
32
|
+
}
|
|
33
|
+
supportsFeature(feature) {
|
|
34
|
+
return SUPPORTED_FEATURES.has(feature);
|
|
35
|
+
}
|
|
36
|
+
async execute(prompt, options) {
|
|
37
|
+
this.silent = options?.silent ?? false;
|
|
38
|
+
const args = this.buildArgs(prompt, options, 'json');
|
|
39
|
+
const spawnOpts = this.buildSpawnOptions(options);
|
|
40
|
+
this.log(`Spawning: gemini ${args.map((a) => (a.length > 80 ? `${a.slice(0, 77)}...` : a)).join(' ')}`);
|
|
41
|
+
this.log(`Spawn options: ${JSON.stringify(spawnOpts)}`);
|
|
42
|
+
const proc = this.spawn('gemini', args, spawnOpts);
|
|
43
|
+
this.log(`Subprocess PID: ${proc.pid ?? 'undefined (spawn may have failed)'}`);
|
|
44
|
+
if (proc.stdin)
|
|
45
|
+
proc.stdin.end();
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
let stdout = '';
|
|
48
|
+
let stderr = '';
|
|
49
|
+
let timedOut = false;
|
|
50
|
+
let timeoutId;
|
|
51
|
+
if (options?.timeout) {
|
|
52
|
+
timeoutId = setTimeout(() => {
|
|
53
|
+
timedOut = true;
|
|
54
|
+
proc.kill();
|
|
55
|
+
}, options.timeout);
|
|
56
|
+
}
|
|
57
|
+
proc.stdout?.on('data', (chunk) => {
|
|
58
|
+
stdout += chunk.toString();
|
|
59
|
+
});
|
|
60
|
+
proc.stderr?.on('data', (chunk) => {
|
|
61
|
+
const data = chunk.toString();
|
|
62
|
+
stderr += data;
|
|
63
|
+
this.log(`stderr: ${data.trimEnd()}`);
|
|
64
|
+
});
|
|
65
|
+
proc.on('error', (error) => {
|
|
66
|
+
this.log(`Process error event: ${error.message}`);
|
|
67
|
+
if (timeoutId)
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
reject(error);
|
|
70
|
+
});
|
|
71
|
+
proc.on('close', (code) => {
|
|
72
|
+
this.log(`Process closed with code ${code}, stdout=${stdout.length} chars`);
|
|
73
|
+
if (timeoutId)
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
if (timedOut) {
|
|
76
|
+
reject(new Error('Agent execution timed out'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (code !== 0 && code !== null) {
|
|
80
|
+
const message = stderr.trim()
|
|
81
|
+
? `Process exited with code ${code}: ${stderr.trim()}`
|
|
82
|
+
: `Process exited with code ${code}`;
|
|
83
|
+
reject(new Error(message));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Gemini CLI may exit 0 despite fatal API errors (e.g. 429 rate limits).
|
|
87
|
+
// Check stderr for known fatal patterns before trusting the output.
|
|
88
|
+
const fatalError = this.detectFatalStderrError(stderr);
|
|
89
|
+
if (fatalError) {
|
|
90
|
+
reject(new Error(fatalError));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(stdout);
|
|
95
|
+
const result = { result: parsed.response ?? '' };
|
|
96
|
+
if (parsed.session_id)
|
|
97
|
+
result.sessionId = parsed.session_id;
|
|
98
|
+
const usage = this.extractUsage(parsed);
|
|
99
|
+
if (usage)
|
|
100
|
+
result.usage = usage;
|
|
101
|
+
resolve(result);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
reject(new Error(`Failed to parse Gemini JSON output: ${stdout.slice(0, 200)}`));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async *executeStream(prompt, options) {
|
|
110
|
+
this.silent = options?.silent ?? false;
|
|
111
|
+
const args = this.buildArgs(prompt, options, 'stream-json');
|
|
112
|
+
const spawnOpts = this.buildSpawnOptions(options);
|
|
113
|
+
const proc = this.spawn('gemini', args, spawnOpts);
|
|
114
|
+
if (proc.stdin)
|
|
115
|
+
proc.stdin.end();
|
|
116
|
+
let lineBuffer = '';
|
|
117
|
+
let stderr = '';
|
|
118
|
+
let timedOut = false;
|
|
119
|
+
let timeoutId;
|
|
120
|
+
const queue = [];
|
|
121
|
+
let resolve = null;
|
|
122
|
+
let error = null;
|
|
123
|
+
function enqueue(event) {
|
|
124
|
+
queue.push(event);
|
|
125
|
+
if (resolve) {
|
|
126
|
+
resolve();
|
|
127
|
+
resolve = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function waitForItem() {
|
|
131
|
+
if (queue.length > 0)
|
|
132
|
+
return Promise.resolve();
|
|
133
|
+
return new Promise((r) => {
|
|
134
|
+
resolve = r;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (options?.timeout) {
|
|
138
|
+
timeoutId = setTimeout(() => {
|
|
139
|
+
timedOut = true;
|
|
140
|
+
proc.kill();
|
|
141
|
+
enqueue({ type: 'error', content: 'Agent execution timed out', timestamp: new Date() });
|
|
142
|
+
enqueue(null);
|
|
143
|
+
}, options.timeout);
|
|
144
|
+
}
|
|
145
|
+
proc.stdout?.on('data', (chunk) => {
|
|
146
|
+
lineBuffer += chunk.toString();
|
|
147
|
+
const lines = lineBuffer.split('\n');
|
|
148
|
+
lineBuffer = lines.pop() ?? '';
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
const trimmed = line.trim();
|
|
151
|
+
if (!trimmed)
|
|
152
|
+
continue;
|
|
153
|
+
const event = this.parseStreamEvent(trimmed);
|
|
154
|
+
if (event)
|
|
155
|
+
enqueue(event);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
proc.stderr?.on('data', (chunk) => {
|
|
159
|
+
stderr += chunk.toString();
|
|
160
|
+
});
|
|
161
|
+
proc.on('error', (err) => {
|
|
162
|
+
if (timeoutId)
|
|
163
|
+
clearTimeout(timeoutId);
|
|
164
|
+
error = err;
|
|
165
|
+
enqueue(null);
|
|
166
|
+
});
|
|
167
|
+
proc.on('close', (code) => {
|
|
168
|
+
if (timeoutId)
|
|
169
|
+
clearTimeout(timeoutId);
|
|
170
|
+
if (timedOut)
|
|
171
|
+
return; // already handled by timeout callback
|
|
172
|
+
if (lineBuffer.trim()) {
|
|
173
|
+
const event = this.parseStreamEvent(lineBuffer.trim());
|
|
174
|
+
if (event)
|
|
175
|
+
enqueue(event);
|
|
176
|
+
}
|
|
177
|
+
if (code !== 0 && code !== null) {
|
|
178
|
+
const msg = stderr.trim()
|
|
179
|
+
? `Process exited with code ${code}: ${stderr.trim()}`
|
|
180
|
+
: `Process exited with code ${code}`;
|
|
181
|
+
enqueue({ type: 'error', content: msg, timestamp: new Date() });
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Gemini CLI may exit 0 despite fatal API errors (e.g. 429 rate limits)
|
|
185
|
+
const fatalError = this.detectFatalStderrError(stderr);
|
|
186
|
+
if (fatalError) {
|
|
187
|
+
enqueue({ type: 'error', content: fatalError, timestamp: new Date() });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
enqueue(null);
|
|
191
|
+
});
|
|
192
|
+
while (true) {
|
|
193
|
+
await waitForItem();
|
|
194
|
+
const item = queue.shift();
|
|
195
|
+
if (item === null || item === undefined) {
|
|
196
|
+
if (error !== null) {
|
|
197
|
+
yield {
|
|
198
|
+
type: 'error',
|
|
199
|
+
content: error.message,
|
|
200
|
+
timestamp: new Date(),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
yield item;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Parse a single stream-JSON line into an AgentExecutionStreamEvent.
|
|
210
|
+
* Returns null for events that should be skipped (init, user messages, unknown types).
|
|
211
|
+
*/
|
|
212
|
+
parseStreamEvent(line) {
|
|
213
|
+
try {
|
|
214
|
+
const parsed = JSON.parse(line);
|
|
215
|
+
const type = parsed.type;
|
|
216
|
+
switch (type) {
|
|
217
|
+
case 'init':
|
|
218
|
+
return null;
|
|
219
|
+
case 'message':
|
|
220
|
+
if (parsed.role === 'user')
|
|
221
|
+
return null;
|
|
222
|
+
if (parsed.role === 'assistant' && parsed.delta) {
|
|
223
|
+
return {
|
|
224
|
+
type: 'progress',
|
|
225
|
+
content: parsed.content ?? '',
|
|
226
|
+
timestamp: new Date(),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
case 'tool_use':
|
|
231
|
+
return {
|
|
232
|
+
type: 'progress',
|
|
233
|
+
content: `[tool_use: ${parsed.tool_name}]`,
|
|
234
|
+
timestamp: new Date(),
|
|
235
|
+
};
|
|
236
|
+
case 'tool_result':
|
|
237
|
+
return {
|
|
238
|
+
type: 'progress',
|
|
239
|
+
content: `[tool_result: ${parsed.status}]`,
|
|
240
|
+
timestamp: new Date(),
|
|
241
|
+
};
|
|
242
|
+
case 'result':
|
|
243
|
+
return {
|
|
244
|
+
type: 'result',
|
|
245
|
+
content: parsed.response ?? '',
|
|
246
|
+
timestamp: new Date(),
|
|
247
|
+
};
|
|
248
|
+
case 'error':
|
|
249
|
+
return {
|
|
250
|
+
type: 'error',
|
|
251
|
+
content: parsed.message ?? '',
|
|
252
|
+
timestamp: new Date(),
|
|
253
|
+
};
|
|
254
|
+
default:
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Non-JSON line — emit as raw progress
|
|
260
|
+
return { type: 'progress', content: line, timestamp: new Date() };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Patterns in stderr that indicate a fatal API error even when exit code is 0.
|
|
265
|
+
* Gemini CLI sometimes exits 0 after exhausting retries on 429/5xx errors.
|
|
266
|
+
*/
|
|
267
|
+
static FATAL_STDERR_PATTERNS = [
|
|
268
|
+
/RESOURCE_EXHAUSTED/i,
|
|
269
|
+
/failed with status 4\d{2}\. Retrying/i,
|
|
270
|
+
/failed with status 5\d{2}\. Retrying/i,
|
|
271
|
+
];
|
|
272
|
+
/**
|
|
273
|
+
* Check stderr for patterns indicating fatal API errors.
|
|
274
|
+
* Returns an error message if fatal patterns are found, null otherwise.
|
|
275
|
+
*/
|
|
276
|
+
detectFatalStderrError(stderr) {
|
|
277
|
+
for (const pattern of GeminiCliExecutorService.FATAL_STDERR_PATTERNS) {
|
|
278
|
+
if (pattern.test(stderr)) {
|
|
279
|
+
// Extract a concise summary from the noisy stderr
|
|
280
|
+
const lines = stderr.split('\n').filter((l) => l.trim());
|
|
281
|
+
const summary = lines.slice(0, 3).join(' | ').slice(0, 300);
|
|
282
|
+
return `Gemini CLI exited 0 but fatal error detected in stderr: ${summary}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Extract token usage from Gemini stats structure.
|
|
289
|
+
* Returns undefined if stats are missing (does not throw).
|
|
290
|
+
*/
|
|
291
|
+
extractUsage(parsed) {
|
|
292
|
+
const stats = parsed.stats;
|
|
293
|
+
if (!stats?.models)
|
|
294
|
+
return undefined;
|
|
295
|
+
const models = stats.models;
|
|
296
|
+
const firstModel = Object.values(models)[0];
|
|
297
|
+
if (!firstModel?.tokens)
|
|
298
|
+
return undefined;
|
|
299
|
+
const tokens = firstModel.tokens;
|
|
300
|
+
if (tokens.prompt === undefined || tokens.candidates === undefined)
|
|
301
|
+
return undefined;
|
|
302
|
+
return { inputTokens: tokens.prompt, outputTokens: tokens.candidates };
|
|
303
|
+
}
|
|
304
|
+
buildArgs(prompt, options, outputFormat = 'json') {
|
|
305
|
+
const args = ['-p', prompt, '--output-format', outputFormat, '-y'];
|
|
306
|
+
if (options?.resumeSession)
|
|
307
|
+
args.push('--resume', options.resumeSession);
|
|
308
|
+
if (options?.model)
|
|
309
|
+
args.push('-m', options.model);
|
|
310
|
+
if (options?.allowedTools?.length)
|
|
311
|
+
args.push('--allowed-tools', options.allowedTools.join(','));
|
|
312
|
+
// Unsupported options silently omitted: maxTurns, disableMcp
|
|
313
|
+
if (options?.systemPrompt) {
|
|
314
|
+
this.log('systemPrompt option is not supported by Gemini CLI — ignoring');
|
|
315
|
+
}
|
|
316
|
+
if (options?.outputSchema) {
|
|
317
|
+
this.log('outputSchema option is not supported by Gemini CLI — ignoring');
|
|
318
|
+
}
|
|
319
|
+
return args;
|
|
320
|
+
}
|
|
321
|
+
buildSpawnOptions(options) {
|
|
322
|
+
const spawnOpts = {};
|
|
323
|
+
if (options?.cwd)
|
|
324
|
+
spawnOpts.cwd = options.cwd;
|
|
325
|
+
// Strip CLAUDECODE env var to prevent "nested session" error when shep
|
|
326
|
+
// is invoked from within a Claude Code session.
|
|
327
|
+
const { CLAUDECODE: _, ...cleanEnv } = process.env;
|
|
328
|
+
// Inject GEMINI_API_KEY when using token auth
|
|
329
|
+
if (this.authConfig?.authMethod === 'token' && this.authConfig.token) {
|
|
330
|
+
spawnOpts.env = { ...cleanEnv, GEMINI_API_KEY: this.authConfig.token };
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
spawnOpts.env = cleanEnv;
|
|
334
|
+
}
|
|
335
|
+
return spawnOpts;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -20,7 +20,7 @@ export declare function createAgentSelectConfig(): {
|
|
|
20
20
|
}, {
|
|
21
21
|
readonly name: "Gemini CLI";
|
|
22
22
|
readonly value: AgentType.GeminiCli;
|
|
23
|
-
readonly
|
|
23
|
+
readonly description: "Google Gemini CLI";
|
|
24
24
|
}, {
|
|
25
25
|
readonly name: "Aider";
|
|
26
26
|
readonly value: AgentType.Aider;
|