- Remove duplicate span append + tool call decision logging block (lines 328-426)
- Fix _extract_tool_calls_from_response to use getattr() instead of .get() on objects
- Fix _calculate_cost to use exact match first, then longest-prefix match (prevents gpt-4o-mini matching gpt-4 pricing)
- Fix test mock setup: set return_value BEFORE wrap_openai() so wrapper captures correct original
- All 11 OpenAI integration tests + 8 SDK tests passing (19/19)
- LangChain: AgentLensCallbackHandler with auto-span creation for
LLM calls, tool calls, chains, and agent decision logging
- Dashboard: trace list with search, status filters, pagination
- Dashboard: trace detail with Decision/Span/Event tabs
- Dashboard: sidebar layout, responsive design, dark theme
- SDK: client with BatchTransport, trace decorator/context manager,
log_decision, thread-local context stack, nested trace→span support
- API: POST /api/traces (batch ingest), GET /api/traces (paginated list),
GET /api/traces/[id] (full trace with relations), GET /api/health
- Tests: 8 unit tests for SDK (all passing)
- Transport: thread-safe buffer with background flush thread