Feature: Session workflow coverage boost Coverage boost for session_workflow.py helper functions (M1, n4). Background: Given the session workflow coverage environment is set up # _extract_content Scenario: _extract_content extracts text from content attribute Given coverage boost a mock response with content "hello world" When coverage boost _extract_content is called Then coverage boost the extracted result should be "hello world" Scenario: _extract_content falls back to text attribute Given coverage boost a mock response with text "from text attr" and no content When coverage boost _extract_content is called Then coverage boost the extracted result should be "from text attr" Scenario: _extract_content handles list content Given coverage boost a mock response with list content containing text dicts and plain strings When coverage boost _extract_content is called Then coverage boost the result should contain the concatenated texts Scenario: _extract_content falls back to str for unknown types Given coverage boost a mock response with no content or text attribute When coverage boost _extract_content is called Then coverage boost the result should be a string # _extract_token_usage Scenario: _extract_token_usage reads from response_metadata.usage Given coverage boost a mock response with response_metadata usage input_tokens=100 output_tokens=50 When coverage boost _extract_token_usage is called Then coverage boost input tokens should be 100 and output tokens should be 50 Scenario: _extract_token_usage reads from response_metadata.token_usage Given coverage boost a mock response with response_metadata token_usage input_tokens=200 output_tokens=100 When coverage boost _extract_token_usage is called Then coverage boost input tokens should be 200 and output tokens should be 100 Scenario: _extract_token_usage reads prompt_tokens and completion_tokens Given coverage boost a mock response with response_metadata usage prompt_tokens=300 completion_tokens=150 When coverage boost _extract_token_usage is called Then coverage boost input tokens should be 300 and output tokens should be 150 Scenario: _extract_token_usage reads from usage_metadata Given coverage boost a mock response with usage_metadata input_tokens=400 output_tokens=200 When coverage boost _extract_token_usage is called Then coverage boost input tokens should be 400 and output tokens should be 200 Scenario: _extract_token_usage returns zeros for no metadata Given coverage boost a mock response with no usage metadata When coverage boost _extract_token_usage is called Then coverage boost input tokens should be 0 and output tokens should be 0 # _estimate_cost Scenario: _estimate_cost computes cost from token counts Given coverage boost input tokens 1000 and output tokens 500 When coverage boost _estimate_cost is called Then coverage boost the estimated cost should be positive # _history_to_langchain_messages Scenario: _history_to_langchain_messages converts all roles Given coverage boost session messages with roles SYSTEM, USER, ASSISTANT, and TOOL When coverage boost _history_to_langchain_messages is called Then coverage boost the result should contain SystemMessage, HumanMessage, AIMessage, and ToolMessage Scenario: _history_to_langchain_messages treats unknown role as human Given coverage boost a session message with an unknown role When coverage boost _history_to_langchain_messages is called Then coverage boost the result should contain a HumanMessage Scenario: _history_to_langchain_messages handles empty list Given coverage boost an empty list of session messages When coverage boost _history_to_langchain_messages is called Then coverage boost the result should be an empty list # LangChainSessionCaller.invoke() tool_results branch Scenario: LangChainSessionCaller.invoke appends tool results Given coverage boost a LangChainSessionCaller with a stub LLM and empty history When coverage boost invoke is called with tool_results containing one success and one failure Then coverage boost the accumulated messages should include tool result messages # LangChainSessionCaller.invoke() with tool_calls in response Scenario: LangChainSessionCaller.invoke extracts tool calls from response Given coverage boost a LangChainSessionCaller with a stub LLM that returns tool calls When coverage boost invoke is called for the first time Then coverage boost the LLMResponse should contain the extracted tool calls # _build_lc_messages_from_history Scenario: _build_lc_messages_from_history adds system prompt when absent Given coverage boost a SessionWorkflow with a stub service and no registry And coverage boost session history without a system message When coverage boost _build_lc_messages_from_history is called with a prompt Then coverage boost the first message should be a SystemMessage with the session system prompt # _MinimalStubLLM Scenario: _MinimalStubLLM.invoke returns stub response Given coverage boost a _MinimalStubLLM instance When coverage boost invoke on the stub is called Then coverage boost the stub response content should be "(no LLM configured)" And coverage boost the stub response should have empty tool_calls Scenario: _MinimalStubLLM.stream yields stub chunk Given coverage boost a _MinimalStubLLM instance When coverage boost stream on the stub is called Then coverage boost it should yield a chunk with content "(no LLM configured)"