Skip to content

[Epic] Detection blind spot: modern SDK / agent / client-object call shapes are invisible (recall) + own-API noise (precision) #142

Description

@AndresL230

Why

Tested the extension against real modern repos by scanning with the CLI and then manually reading every source file to compare:

  • sapling — a Gemini / pydantic-ai ed-tech backend (FastAPI + Next.js)
  • plate-gallery — FastAPI + Supabase + Gemini/OpenAI image moderation

Result: across the scanned trees the extension surfaced ~2 of 40+ distinct external call sites (~5%) and 0 of ~28 LLM/AI call sites — while emitting 215 "endpoints" in sapling/backend that are all internal FastAPI route definitions + pytest calls ($0, not outbound). The cost analyzer is blind to exactly the calls that cost money in modern Python AI codebases, and loud about the app's own surface.

Root cause (one defect, several faces)

The AST visitor (src/ast/call-visitor.ts) captures every call — client.post(...), _client.models.generate_content(...), agent.run(...) are all in the tree. Calls die in provider resolution, which fuses two different questions into one — "is this a call to a provider I recognize?" — and silently drops everything that isn't.

There are two silent drop points:

  1. Detection: src/scanner/core-scanner.ts:269if (!fp && !knownSdkProvider && !knownHttpHost) continue;
  2. Endpoint aggregation: src/scan-results.ts isHighConfidenceEndpointUrl rejecting sdk:// placeholders (already filed as Endpoint aggregation drops cross-file-resolved SDK calls with sdk:// placeholder URLs (A5 cross-file factory invisible to benchmark) #141).

A call only survives through one of three narrow channels:

  1. SDK-by-import-binding (resolveProvider, ast-scanner.ts:544). Root var must trace to a package in PACKAGE_TO_PROVIDER (ast-scanner.ts:98-114) / CLASS_TO_PACKAGE (import-resolver.ts:47) and have a fingerprint. JS-centric; Python Google-AI SDKs unmapped; agent frameworks hide the provider in a model string; clients built in with/function scope aren't bound.
  2. HTTP-by-literal-URL (ast-scanner.ts:731). Only ~8 hardcoded root names (HTTP_CLIENTS, :151), only when arg0 is a bare string literal (extractUrlFromArgs, :156). Misses client.post(...) on a session handle and any variable/f-string/multi-line URL.
  3. Regex route-defs/paths (core-scanner.ts:284/310). Any /-prefixed literal becomes an "endpoint", so FastAPI route DEFINITIONS and pytest paths dominate.

The host knowledge already exists — lookupHost resolves api.openai.com, generativelanguage.googleapis.com, *.supabase.* — the call-site extractors just never deliver the URL to it. (src/scanner/python-waste-detector.ts:18-30 even enumerates langchain/llamaindex/google.generativeai for N+1 detection; that awareness never reaches endpoint detection.)

The fix (Approach A — extend channels in place + a two-step seam)

Split provider resolution into two explicit steps: (1) is this outbound? (from call shape, no provider needed) → (2) who is it? (best-effort). A call that's outbound but unattributed becomes a logged unknown with a captured "calling card" (host fragment, SDK root, model string, method chain) so fingerprints can be backfilled — instead of vanishing.

Children & dependency order

Related: #137 (test-fixture noise), #141 (sdk:// dropped at aggregation), #136 (unknown-provider submit). Suggest grouping these under a new wave/11-modern-call-shapes label.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/detectiondocs/accuracy/detection.md — finding every call site, attributing correctlyenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions