Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Agent Detector Laravel Package

shipfastlabs/agent-detector

Lightweight PHP utility that detects whether your code is running inside an AI agent or automated dev environment. Identify Claude, Cursor, Gemini, Codex and more via env vars or a standalone detectAgent() helper.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer:

composer require laravel/agent-detector

First use case: Detect AI agents in your Laravel application's AppServiceProvider boot method or middleware:

use Laravel\AgentDetector\AgentDetector;

public function boot()
{
    $result = AgentDetector::detect();

    if ($result->isAgent) {
        Log::info("Running in AI environment: {$result->name}");
        // Customize behavior for AI agents
    }
}

Key starting points:

  1. AgentDetector::detect() - Core detection method
  2. detectAgent() - Standalone function alias
  3. KnownAgent enum - Predefined agent types (as of v2.0.5: Claude, Copilot, Copilot cloud agent, Kimi, Grok, Cline, Goose, Junie, OpenClaw, ZCode, Qwen Code, Crush, Windsurf, Zed Agent, Kiro IDE, Jules, and earlier built-ins)
  4. AgentResult object - Contains detection results

What's new in v2.0.5: detection for ZCode, Qwen Code, Crush, Windsurf, Zed Agent, Kiro IDE, Copilot cloud agent, and Jules was added — eight new enum cases in another patch release. If you were hand-rolling detection for any of these via AI_AGENT or a custom detection method, you can drop that code. No breaking changes; existing detection and namespaces are unchanged. v2.0.4 added Kimi, Grok, Cline, Goose, Junie, and OpenClaw — the same migration advice applies if you skipped it.

Implementation Patterns

Core Detection Workflow

// Basic detection
$result = AgentDetector::detect();

// Check for any agent
if ($result->isAgent) {
    // Agent detected
}

// Check specific agent
if ($result->knownAgent() === KnownAgent::Claude) {
    // Claude-specific logic
}

Handling the Recently Added Agents (v2.0.4 → v2.0.5)

All fourteen new agents are first-class enum cases, so the same branching style works without custom detection code:

use Laravel\AgentDetector\KnownAgent;

$agent = AgentDetector::detect()->knownAgent();

$profile = match (true) {
    $agent === KnownAgent::Claude,
    $agent === KnownAgent::Kimi      => 'long-context-assistant',
    $agent === KnownAgent::Copilot,
    $agent === KnownAgent::CopilotCloudAgent,
    $agent === KnownAgent::Cline,
    $agent === KnownAgent::Goose,
    $agent === KnownAgent::Junie,
    $agent === KnownAgent::Crush,
    $agent === KnownAgent::QwenCode,
    $agent === KnownAgent::ZCode     => 'ide-or-terminal-coder',
    $agent === KnownAgent::Windsurf,
    $agent === KnownAgent::ZedAgent,
    $agent === KnownAgent::KiroIDE   => 'editor-embedded-agent',
    $agent === KnownAgent::Grok,
    $agent === KnownAgent::OpenClaw,
    $agent === KnownAgent::Jules     => 'general-agent',
    default                          => 'unknown',
};

Capability Gating by Agent Family

A practical day-to-day pattern is to group agents into behavioural families rather than checking each case inline:

public function isInteractiveCodingAgent(AgentResult $result): bool
{
    return in_array($result->knownAgent(), [
        KnownAgent::Copilot,
        KnownAgent::CopilotCloudAgent,
        KnownAgent::Cline,
        KnownAgent::Goose,
        KnownAgent::Junie,
        KnownAgent::Crush,
        KnownAgent::QwenCode,
        KnownAgent::ZCode,
        KnownAgent::Windsurf,
        KnownAgent::ZedAgent,
        KnownAgent::KiroIDE,
    ], true);
}

This survives future releases that add more coding-oriented agents far better than a long if/else chain — v2.0.5 is the second consecutive patch release to prove the point.

Middleware Integration

public function handle(Request $request, Closure $next)
{
    $result = AgentDetector::detect();

    if ($result->isAgent && $result->knownAgent() === KnownAgent::Copilot) {
        return response()->json(['message' => 'Copilot detected'], 403);
    }

    return $next($request);
}

See the gotcha below before copying this: Copilot no longer covers every Copilot surface as of v2.0.5.

Service Provider Integration

public function register()
{
    $this->app->singleton('agent-detector', function () {
        return AgentDetector::detect();
    });
}

Custom Agent Handling

// Set environment variable
putenv('AI_AGENT=my-custom-agent');

// Or in .env
// AI_AGENT=my-custom-agent

// Then detect
$result = AgentDetector::detect();
if ($result->name === 'my-custom-agent') {
    // Custom logic
}

Note: before registering custom detection for ZCode, Qwen Code, Crush, Windsurf, Zed Agent, Kiro IDE, Copilot cloud agent, Jules, Kimi, Grok, Cline, Goose, Junie, or OpenClaw, confirm the package doesn't already cover it (v2.0.5+ does for all of these). Duplicate detection methods are harmless but make debugging output noisier.

Testing Patterns

// In PHPUnit tests
public function testAgentDetection()
{
    putenv('AI_AGENT=github-copilot');

    $result = AgentDetector::detect();
    $this->assertTrue($result->isAgent);
    $this->assertEquals(KnownAgent::Copilot, $result->knownAgent());
}

public function testNewlySupportedAgentsAreRecognised()
{
    foreach ([
        'zcode'      => KnownAgent::ZCode,
        'qwen-code'  => KnownAgent::QwenCode,
        'crush'      => KnownAgent::Crush,
        'windsurf'   => KnownAgent::Windsurf,
        'zed'        => KnownAgent::ZedAgent,
        'kiro'       => KnownAgent::KiroIDE,
        'jules'      => KnownAgent::Jules,
        // v2.0.4
        'kimi'       => KnownAgent::Kimi,
        'grok'       => KnownAgent::Grok,
        'cline'      => KnownAgent::Cline,
        'goose'      => KnownAgent::Goose,
        'junie'      => KnownAgent::Junie,
        'openclaw'   => KnownAgent::OpenClaw,
    ] as $envValue => $expected) {
        putenv("AI_AGENT={$envValue}");
        $this->assertSame($expected, AgentDetector::detect()->knownAgent());
    }
}

Adjust the raw env strings to the exact values the package matches on — the mapping above is illustrative; check KnownAgent / AGENT_ENV_VARS for the canonical identifiers and the exact enum case names (e.g. ZedAgent vs Zed, KiroIDE vs Kiro).

Gotchas and Tips

Common Pitfalls

  1. Environment Variables: Detection relies on environment variables. Ensure they're properly set in your deployment environment.

    # Example for testing
    AI_AGENT=github-copilot php artisan tinker
    
  2. Namespace Changes: v2.0.0 changed namespace from AgentDetector to Laravel\AgentDetector. Update imports if migrating.

  3. False Positives: Some environments might trigger multiple detection methods. Verify with AgentDetector::AGENT_ENV_VARS:

    print_r(AgentDetector::AGENT_ENV_VARS);
    
  4. Performance: Detection is lightweight but not free. Cache results if checking frequently:

    $result = cache()->remember('agent-detection', now()->addHours(1), function() {
        return AgentDetector::detect();
    });
    
  5. Exhaustive match on KnownAgent: v2.0.4 added six enum cases, v2.0.5 added eight more. Any match (KnownAgent::…) without a default arm — or a switch with no fallback — will now throw UnhandledMatchError / fall through for ZCode, Qwen Code, Crush, Windsurf, Zed Agent, Kiro IDE, Copilot cloud agent, Jules, and the v2.0.4 set. Always supply a default arm, and prefer in_array() family checks over enumeration.

  6. Copilot is no longer "all Copilot": Copilot cloud agent is a distinct case from Copilot. Any check like === KnownAgent::Copilot or in_array($agent, [KnownAgent::Copilot]) silently stops matching the cloud agent after upgrading. If you mean "any Copilot", match both cases or gate on a family list.

  7. Persisted enum values: If you store KnownAgent cases in a database, queue payloads, or cache keys, using ->name is safe; casting a stored string back to the enum for a new case is only safe after every consumer is on v2.0.5+. Rolling deploys can briefly mismatch — and note the v2.0.5 additions widen that window again.

  8. Cached detection results mask new agents: because detection results are often cached (see #4), an app that upgraded to v2.0.5 can still report unknown for Qwen Code or Crush until the cache TTL expires. Invalidate the cache key on deploy if agent identity drives behaviour.

Debugging Tips

  1. Inspect Detection Logic:

    $result = AgentDetector::detect();
    dd($result->name, $result->isAgent, $result->knownAgent());
    
  2. Check All Environment Variables:

    foreach (AgentDetector::AGENT_ENV_VARS as $var) {
        echo "$var = " . getenv($var) . "\n";
    }
    
  3. Confirm You're on v2.0.5+: a missing KnownAgent::Jules or KnownAgent::QwenCode case (or a "class constant not found" error) usually means Composer resolved an older tag. Check with:

    composer show laravel/agent-detector
    
  4. Custom Detection: Extend the detector by creating a custom class:

    class CustomAgentDetector extends AgentDetector
    {
        protected static function detectCustomAgent(): ?string
        {
            return getenv('MY_CUSTOM_VAR') ? 'my-custom-agent' : null;
        }
    }
    

Configuration Quirks

  1. KnownAgent Enum: Use label() method for human-readable names:

    echo KnownAgent::Claude->label(); // "Claude"
    

    The label is what you want in logs and UI — the raw name of the newer agents (e.g. OpenClaw, ZCode, KiroIDE) won't match how users write them.

  2. AgentResult Properties:

    • isAgent: Boolean indicating if any agent detected
    • name: String name of detected agent
    • knownAgent(): Returns KnownAgent enum if matches known agents — returns null for anything not in the enum, including custom AI_AGENT values
  3. Environment Variable Priority: The detector checks variables in a specific order. Newer agents may override older ones. Having added fourteen agents across two patch releases, environments with several agent variables set can resolve differently than they did on v2.0.3 or v2.0.4 — if a CI image or devcontainer sets more than one (common with IDE-embedded agents like Windsurf, Zed Agent, and Kiro IDE sharing a shell with a CLI coder), re-verify which one wins rather than assuming.

  4. Editor-embedded vs terminal agents: v2.0.5 skews heavily toward editor/IDE-embedded agents. Those typically export their marker variables into the integrated terminal, so shell commands run from inside the editor can detect differently than the same command run from an external terminal. Worth pinning down in any behaviour that changes based on detection.

Extension Points

  1. Add New Agent Detection:

    // In a service provider
    AgentDetector::addDetectionMethod(function() {
        return getenv('NEW_AGENT_VAR') ? 'new-agent' : null;
    });
    

    Check the release first — a fair number of "custom" agents have been absorbed into core over time (six in v2.0.4, eight more in v2.0.5), and you'll want to remove your shim once it lands upstream.

  2. Custom Agent Detection Class:

    class MyAgentDetector extends AgentDetector
    {
        public static function detect(): AgentResult
        {
            $result = parent::detect();
    
            if ($result->name === 'custom') {
                // Custom logic
            }
    
            return $result;
        }
    }
    
  3. Override Detection Logic:

    // Monkeypatch for testing
    AgentDetector::setDetectionMethods([...]);
    

    When overriding, re-check the default method list after each upgrade — v2.0.5's additions mean a hardcoded list silently drops detection for ZCode, Qwen Code, Crush, Windsurf, Zed Agent, Kiro IDE, Copilot cloud agent, Jules, and the v2.0.4 set.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings