Skip to content

Commit 7719ba7

Browse files
authored
Add v0, Cowork, and CODEX_CI agent detection (#12)
1 parent 846a9a1 commit 7719ba7

4 files changed

Lines changed: 60 additions & 13 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,19 @@ $result = detectAgent();
5252
| Custom | `AI_AGENT` env var |
5353
| Cursor | `CURSOR_AGENT` env var |
5454
| Gemini | `GEMINI_CLI` env var |
55-
| Codex | `CODEX_SANDBOX` or `CODEX_THREAD_ID` env var |
55+
| Codex | `CODEX_SANDBOX`, `CODEX_CI`, or `CODEX_THREAD_ID` env var |
5656
| Augment CLI | `AUGMENT_AGENT` env var |
5757
| AMP | `AMP_CURRENT_THREAD_ID` env var |
5858
| Opencode | `OPENCODE_CLIENT` or `OPENCODE` env var |
5959
| Claude | `CLAUDECODE` or `CLAUDE_CODE` env var |
60+
| Cowork | `CLAUDE_CODE_IS_COWORK` with `CLAUDECODE` or `CLAUDE_CODE` env var |
6061
| Copilot | `AI_AGENT=github-copilot`, `AI_AGENT=github-copilot-cli`, `COPILOT_MODEL`, `COPILOT_ALLOW_ALL`, `COPILOT_GITHUB_TOKEN`, or `COPILOT_CLI` env var |
6162
| Replit | `REPL_ID` env var |
6263
| Devin | `/opt/.devin` file exists |
6364
| Antigravity | `ANTIGRAVITY_AGENT` env var |
6465
| Pi | `PI_CODING_AGENT` env var |
6566
| Kiro CLI | `KIRO_AGENT_PATH` env var |
67+
| v0 | `AI_AGENT=v0` env var |
6668

6769
### Custom Agent
6870

src/AgentDetector.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public static function detect(): AgentResult
2525
$agentsWithEnvVars = [
2626
'cursor' => ['CURSOR_AGENT'],
2727
'gemini' => ['GEMINI_CLI'],
28-
'codex' => ['CODEX_SANDBOX', 'CODEX_THREAD_ID'],
28+
'codex' => ['CODEX_SANDBOX', 'CODEX_CI', 'CODEX_THREAD_ID'],
2929
'augment-cli' => ['AUGMENT_AGENT'],
3030
'opencode' => ['OPENCODE_CLIENT', 'OPENCODE'],
3131
'amp' => ['AMP_CURRENT_THREAD_ID'],
@@ -40,6 +40,10 @@ public static function detect(): AgentResult
4040
foreach ($agentsWithEnvVars as $agent => $envVars) {
4141
foreach ($envVars as $envVar) {
4242
if (getenv($envVar) !== false) {
43+
if ($agent === 'claude' && getenv('CLAUDE_CODE_IS_COWORK') !== false) {
44+
return new AgentResult(true, 'cowork');
45+
}
46+
4347
return new AgentResult(true, $agent);
4448
}
4549
}

src/KnownAgent.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ enum KnownAgent: string
88
{
99
case Cursor = 'cursor';
1010
case Claude = 'claude';
11+
case Cowork = 'cowork';
1112
case Devin = 'devin';
1213
case Replit = 'replit';
1314
case Gemini = 'gemini';
1415
case Codex = 'codex';
16+
case V0 = 'v0';
1517
case AugmentCli = 'augment-cli';
1618
case Opencode = 'opencode';
1719
case Amp = 'amp';

tests/AgentDetectorTest.php

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
'CURSOR_AGENT',
1414
'GEMINI_CLI',
1515
'CODEX_SANDBOX',
16+
'CODEX_CI',
1617
'CODEX_THREAD_ID',
1718
'AUGMENT_AGENT',
1819
'OPENCODE_CLIENT',
1920
'OPENCODE',
2021
'AMP_CURRENT_THREAD_ID',
2122
'CLAUDECODE',
2223
'CLAUDE_CODE',
24+
'CLAUDE_CODE_IS_COWORK',
2325
'COPILOT_MODEL',
2426
'COPILOT_ALLOW_ALL',
2527
'COPILOT_GITHUB_TOKEN',
@@ -41,13 +43,15 @@
4143
'CURSOR_AGENT',
4244
'GEMINI_CLI',
4345
'CODEX_SANDBOX',
46+
'CODEX_CI',
4447
'CODEX_THREAD_ID',
4548
'AUGMENT_AGENT',
4649
'OPENCODE_CLIENT',
4750
'OPENCODE',
4851
'AMP_CURRENT_THREAD_ID',
4952
'CLAUDECODE',
5053
'CLAUDE_CODE',
54+
'CLAUDE_CODE_IS_COWORK',
5155
'COPILOT_MODEL',
5256
'COPILOT_ALLOW_ALL',
5357
'COPILOT_GITHUB_TOKEN',
@@ -94,6 +98,16 @@
9498
->and($result->knownAgent())->toBe(KnownAgent::Copilot);
9599
});
96100

101+
it('detects v0 via AI_AGENT', function (): void {
102+
putenv('AI_AGENT=v0');
103+
104+
$result = AgentDetector::detect();
105+
106+
expect($result->isAgent)->toBeTrue()
107+
->and($result->name)->toBe('v0')
108+
->and($result->knownAgent())->toBe(KnownAgent::V0);
109+
});
110+
97111
it('does not detect an agent when AI_AGENT is not set', function (): void {
98112
$result = AgentDetector::detect();
99113

@@ -132,6 +146,16 @@
132146
->and($result->knownAgent())->toBe(KnownAgent::Codex);
133147
});
134148

149+
it('detects codex via CODEX_CI', function (): void {
150+
putenv('CODEX_CI=true');
151+
152+
$result = AgentDetector::detect();
153+
154+
expect($result->isAgent)->toBeTrue()
155+
->and($result->name)->toBe('codex')
156+
->and($result->knownAgent())->toBe(KnownAgent::Codex);
157+
});
158+
135159
it('detects codex via CODEX_THREAD_ID', function (): void {
136160
putenv('CODEX_THREAD_ID=some-thread-id');
137161

@@ -202,6 +226,28 @@
202226
->and($result->knownAgent())->toBe(KnownAgent::Claude);
203227
});
204228

229+
it('detects cowork via Claude Code cowork env vars', function (): void {
230+
putenv('CLAUDE_CODE=1');
231+
putenv('CLAUDE_CODE_IS_COWORK=1');
232+
233+
$result = AgentDetector::detect();
234+
235+
expect($result->isAgent)->toBeTrue()
236+
->and($result->name)->toBe('cowork')
237+
->and($result->knownAgent())->toBe(KnownAgent::Cowork);
238+
});
239+
240+
it('does not detect cowork without Claude Code', function (): void {
241+
putenv('CLAUDE_CODE_IS_COWORK=1');
242+
243+
$GLOBALS['__mock_file_exists'] = fn (string $path): bool => false;
244+
245+
$result = AgentDetector::detect();
246+
247+
expect($result->isAgent)->toBeFalse()
248+
->and($result->name)->toBeNull();
249+
});
250+
205251
it('detects copilot via COPILOT_MODEL', function (): void {
206252
putenv('COPILOT_MODEL=gpt-5.2');
207253

@@ -303,22 +349,13 @@
303349
});
304350

305351
// Priority order
306-
it('prioritizes AI_AGENT over CURSOR_TRACE_ID', function (): void {
352+
it('prioritizes AI_AGENT over CURSOR_AGENT', function (): void {
307353
putenv('AI_AGENT=custom');
308-
putenv('CURSOR_TRACE_ID=trace');
309-
310-
$result = AgentDetector::detect();
311-
312-
expect($result->name)->toBe('custom');
313-
});
314-
315-
it('prioritizes CURSOR_TRACE_ID over CURSOR_AGENT', function (): void {
316-
putenv('CURSOR_TRACE_ID=trace');
317354
putenv('CURSOR_AGENT=true');
318355

319356
$result = AgentDetector::detect();
320357

321-
expect($result->name)->toBe('cursor');
358+
expect($result->name)->toBe('custom');
322359
});
323360

324361
it('prioritizes CURSOR_AGENT over CLAUDECODE', function (): void {
@@ -390,6 +427,8 @@
390427
'cursor' => ['CURSOR_AGENT', '1', KnownAgent::Cursor],
391428
'gemini' => ['GEMINI_CLI', 'true', KnownAgent::Gemini],
392429
'codex' => ['CODEX_SANDBOX', 'true', KnownAgent::Codex],
430+
'codex ci' => ['CODEX_CI', 'true', KnownAgent::Codex],
431+
'v0' => ['AI_AGENT', 'v0', KnownAgent::V0],
393432
'augment-cli' => ['AUGMENT_AGENT', 'true', KnownAgent::AugmentCli],
394433
'opencode' => ['OPENCODE_CLIENT', 'true', KnownAgent::Opencode],
395434
'amp' => ['AMP_CURRENT_THREAD_ID', 'thread-id', KnownAgent::Amp],

0 commit comments

Comments
 (0)