You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
Detection: src/scanner/core-scanner.ts:269 — if (!fp && !knownSdkProvider && !knownHttpHost) continue;
A call only survives through one of three narrow channels:
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.
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.
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.
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.
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 moderationResult: 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/backendthat 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:
src/scanner/core-scanner.ts:269—if (!fp && !knownSdkProvider && !knownHttpHost) continue;src/scan-results.tsisHighConfidenceEndpointUrlrejectingsdk://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:
resolveProvider,ast-scanner.ts:544). Root var must trace to a package inPACKAGE_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 inwith/function scope aren't bound.ast-scanner.ts:731). Only ~8 hardcoded root names (HTTP_CLIENTS,:151), only when arg0 is a bare string literal (extractUrlFromArgs,:156). Missesclient.post(...)on a session handle and any variable/f-string/multi-line URL.core-scanner.ts:284/310). Any/-prefixed literal becomes an "endpoint", so FastAPI route DEFINITIONS and pytest paths dominate.The host knowledge already exists —
lookupHostresolvesapi.openai.com,generativelanguage.googleapis.com,*.supabase.*— the call-site extractors just never deliver the URL to it. (src/scanner/python-waste-detector.ts:18-30even enumerates langchain/llamaindex/google.generativeaifor 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
unknownwith a captured "calling card" (host fragment, SDK root, model string, method chain) so fingerprints can be backfilled — instead of vanishing.Children & dependency order
unknownoutbound (both drop points) + enrich capture (unblocks theunknownfallback used by Detection: agent frameworks (pydantic-ai / LangChain / LlamaIndex) — resolve provider from the model string, treat .run()/.invoke() as metered #144/Detection: HTTP via client/session handles (client.post) + non-literal URLs (f-string/var/multi-line) are invisible #145)Related: #137 (test-fixture noise), #141 (
sdk://dropped at aggregation), #136 (unknown-provider submit). Suggest grouping these under a newwave/11-modern-call-shapeslabel.