testmcpy 0.7.1__tar.gz → 0.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {testmcpy-0.7.1/testmcpy.egg-info → testmcpy-0.7.2}/PKG-INFO +1 -1
- {testmcpy-0.7.1 → testmcpy-0.7.2}/pyproject.toml +1 -1
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/__init__.py +1 -1
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/llm_integration.py +78 -2
- {testmcpy-0.7.1 → testmcpy-0.7.2/testmcpy.egg-info}/PKG-INFO +1 -1
- {testmcpy-0.7.1 → testmcpy-0.7.2}/LICENSE +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/MANIFEST.in +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/NOTICE +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/README.md +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/setup.cfg +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/agent/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/agent/hooks.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/agent/models.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/agent/orchestrator.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/agent/prompts.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/agent/tools.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/auth_debugger.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/auth_flow_recorder.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/app.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/agent.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/baseline.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/export_db.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/mcp.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/metamorphic.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/multi_env.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/mutate.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/push.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/run.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/server.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/tools.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/tui.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/cli/commands/wizard.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/config.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/core/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/core/chat_session.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/core/docs_optimizer.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/core/mcp_manager.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/core/tool_comparison.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/core/tool_discovery.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/db.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/error_handlers.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/evals/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/evals/auth_evaluators.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/evals/base_evaluators.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/evals/evaluator_packs.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/base.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/curl.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/graphql.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/javascript_client.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/json_yaml.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/protobuf.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/python.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/python_client.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/thrift.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/typescript.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/formatters/typescript_client.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/llm_profiles.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/mcp_profiles.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/migrate_json.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/models.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/research/claude_sdk_detailed_exploration.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/research/claude_sdk_poc.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/research/claude_sdk_working_poc.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/research/test_ollama_tools.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/api.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/api.py.bak +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/auth_middleware.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/helpers/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/helpers/mcp_config.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/models.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/agent.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/auth.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/compare.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/compatibility.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/generation_logs.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/health.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/llm.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/mcp_profiles.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/metrics.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/results.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/search.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/security.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/smoke_reports.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/test_profiles.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/tests.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/routers/tools.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/state.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/server/websocket.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/smoke_test.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/__init__.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/baseline.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/ci_gate.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/comparison_runner.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/coverage_analyzer.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/html_report.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/mcp_client.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/metamorphic.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/model_registry.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/models.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/multi_env.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/oauth_flows.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/prompt_mutation.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/report_generator.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/runner_tools.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/schema_diff.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/test_runner.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/src/token_manager.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/storage.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/test_profiles.py +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/README.md +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/dist/assets/index-30Ed2JCz.css +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/dist/assets/index-6JiH0p1L.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/dist/index.html +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/index.html +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/package-lock.json +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/package.json +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/postcss.config.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/App.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/CommandPalette.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/CompareToolsTab.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/EditorStatusBar.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/EditorTabStrip.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/ErrorAlert.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/ErrorBoundary.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/LLMProfileSelector.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/LoadingSpinner.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/MCPProfileSelector.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/NotificationProvider.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/OptimizeDocsModal.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/OutputDiff.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/ParameterCard.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/SchemaCodeViewer.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/SkeletonLoader.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/StreamingLogViewer.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/TestGenerationModal.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/TestProfileSelector.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/TestResultPanel.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/TestStatusIndicator.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/ToolCallTimeline.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/ToolComparison.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/ToolDebugModal.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/TraceView.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/TypeBadge.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/components/Wizard.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/contexts/TestRunContext.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/contexts/ThemeContext.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/hooks/useEditorTheme.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/hooks/useKeyboardShortcuts.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/hooks/useSafeFetch.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/index.css +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/main.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/AuthDebugger.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/ChatInterface.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/CompatibilityMatrix.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/Configuration.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/GenerationHistory.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/LLMProfiles.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/MCPExplorer.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/MCPHealth.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/MCPProfiles.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/MetricsDashboard.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/ProfilesManager.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/Reports.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/RunComparison.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/SecurityDashboard.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/pages/TestManager.jsx +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/utils/__tests__/formatConverters.test.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/src/utils/formatConverters.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/tailwind.config.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy/ui/vite.config.js +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy.egg-info/SOURCES.txt +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy.egg-info/dependency_links.txt +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy.egg-info/entry_points.txt +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy.egg-info/requires.txt +0 -0
- {testmcpy-0.7.1 → testmcpy-0.7.2}/testmcpy.egg-info/top_level.txt +0 -0
|
@@ -93,7 +93,7 @@ testmcpy = [
|
|
|
93
93
|
|
|
94
94
|
[project]
|
|
95
95
|
name = "testmcpy"
|
|
96
|
-
version = "0.7.
|
|
96
|
+
version = "0.7.2"
|
|
97
97
|
description = "A comprehensive testing framework for validating LLM tool calling capabilities with MCP services"
|
|
98
98
|
authors = [{name = "Amin Ghadersohi"}]
|
|
99
99
|
license = "Apache-2.0"
|
|
@@ -2050,6 +2050,21 @@ class ClaudeSDKProvider(LLMProvider):
|
|
|
2050
2050
|
_assistant_logger = logging.getLogger(__name__ + ".AssistantProvider")
|
|
2051
2051
|
|
|
2052
2052
|
|
|
2053
|
+
def _format_seconds(seconds: float) -> str:
|
|
2054
|
+
"""Format a duration so sub-second values aren't rounded to ``0s``.
|
|
2055
|
+
|
|
2056
|
+
The SSE idle threshold is configurable down to fractions of a second
|
|
2057
|
+
(the unit tests override it to 0.3s). Using ``f"{x:.0f}s"`` would
|
|
2058
|
+
produce a misleading ``0s`` in those messages — switch to ms below
|
|
2059
|
+
1s, decimals below 10s, and integer seconds otherwise.
|
|
2060
|
+
"""
|
|
2061
|
+
if seconds < 1.0:
|
|
2062
|
+
return f"{seconds * 1000:.0f}ms"
|
|
2063
|
+
if seconds < 10.0:
|
|
2064
|
+
return f"{seconds:.1f}s"
|
|
2065
|
+
return f"{seconds:.0f}s"
|
|
2066
|
+
|
|
2067
|
+
|
|
2053
2068
|
@dataclass
|
|
2054
2069
|
class _SSEStreamState:
|
|
2055
2070
|
"""Mutable state accumulated as we parse a chatbot SSE response."""
|
|
@@ -2111,6 +2126,13 @@ class AssistantProvider(LLMProvider):
|
|
|
2111
2126
|
_DEFAULT_CONVERSATIONS_PATH = "/api/v1/copilot/conversations"
|
|
2112
2127
|
_DEFAULT_COMPLETIONS_PATH = "/api/v1/copilot/completions"
|
|
2113
2128
|
|
|
2129
|
+
# If the SSE stream emits no recognized event for this many seconds,
|
|
2130
|
+
# abort the stream. Defends against a chatbot backend that keeps the
|
|
2131
|
+
# connection open (preventing httpx's per-event read timeout from
|
|
2132
|
+
# firing) but stops emitting real progress. Observed in c29
|
|
2133
|
+
# (SC-105915). Class-level so subclasses / tests can override.
|
|
2134
|
+
SSE_IDLE_ABORT_SECONDS: float = 90.0
|
|
2135
|
+
|
|
2114
2136
|
def __init__(
|
|
2115
2137
|
self,
|
|
2116
2138
|
model: str = "default",
|
|
@@ -2235,6 +2257,16 @@ class AssistantProvider(LLMProvider):
|
|
|
2235
2257
|
|
|
2236
2258
|
log(f"[Assistant] POST {completions_url} (conversation={self._conversation_id})")
|
|
2237
2259
|
|
|
2260
|
+
# Idle abort: if the SSE stream emits NO recognized event within
|
|
2261
|
+
# this many seconds, give up. This catches a chatbot backend that
|
|
2262
|
+
# keeps the connection open (so httpx's per-event read timeout
|
|
2263
|
+
# never fires) but stops sending real progress — observed in
|
|
2264
|
+
# eval cycle c29 (SC-105915) where C00_9, C01_9, C02_7 hung
|
|
2265
|
+
# despite the per-test wall-clock added in v0.7.1.
|
|
2266
|
+
sse_idle_abort_seconds = self.SSE_IDLE_ABORT_SECONDS
|
|
2267
|
+
last_event_at = time.time()
|
|
2268
|
+
idle_aborted = False
|
|
2269
|
+
|
|
2238
2270
|
state = _SSEStreamState()
|
|
2239
2271
|
try:
|
|
2240
2272
|
async with self._client.stream(
|
|
@@ -2248,7 +2280,40 @@ class AssistantProvider(LLMProvider):
|
|
|
2248
2280
|
)
|
|
2249
2281
|
|
|
2250
2282
|
current_event: str | None = None
|
|
2251
|
-
|
|
2283
|
+
# Drive the line iterator manually so we can wrap each
|
|
2284
|
+
# await in asyncio.wait_for(...). httpx's aiter_lines()
|
|
2285
|
+
# blocks inside __anext__ when no bytes arrive — a plain
|
|
2286
|
+
# `async for` would be suspended forever. The wait_for
|
|
2287
|
+
# catches the case where the SSE connection stays open
|
|
2288
|
+
# but never sends another byte (real-world c29 hang).
|
|
2289
|
+
line_iter = resp.aiter_lines().__aiter__()
|
|
2290
|
+
budget_str = _format_seconds(sse_idle_abort_seconds)
|
|
2291
|
+
while True:
|
|
2292
|
+
elapsed = time.time() - last_event_at
|
|
2293
|
+
remaining = sse_idle_abort_seconds - elapsed
|
|
2294
|
+
if remaining <= 0:
|
|
2295
|
+
log(
|
|
2296
|
+
f"[Assistant] SSE idle abort: no recognized event for "
|
|
2297
|
+
f"{budget_str} — closing stream"
|
|
2298
|
+
)
|
|
2299
|
+
idle_aborted = True
|
|
2300
|
+
break
|
|
2301
|
+
try:
|
|
2302
|
+
raw_line = await asyncio.wait_for(line_iter.__anext__(), timeout=remaining)
|
|
2303
|
+
except StopAsyncIteration:
|
|
2304
|
+
break
|
|
2305
|
+
except asyncio.TimeoutError:
|
|
2306
|
+
# Budget is measured since the last *recognized* event.
|
|
2307
|
+
# Unrecognized noise (keepalives, malformed events) does
|
|
2308
|
+
# NOT reset last_event_at, so this fires correctly even
|
|
2309
|
+
# if bytes are arriving without real progress.
|
|
2310
|
+
log(
|
|
2311
|
+
f"[Assistant] SSE idle abort: no recognized event for "
|
|
2312
|
+
f"{budget_str} — closing stream"
|
|
2313
|
+
)
|
|
2314
|
+
idle_aborted = True
|
|
2315
|
+
break
|
|
2316
|
+
|
|
2252
2317
|
line = raw_line.strip()
|
|
2253
2318
|
if not line:
|
|
2254
2319
|
current_event = None
|
|
@@ -2273,6 +2338,8 @@ class AssistantProvider(LLMProvider):
|
|
|
2273
2338
|
continue
|
|
2274
2339
|
|
|
2275
2340
|
self._handle_sse_event(current_event, data, state, log)
|
|
2341
|
+
# A real event arrived — reset the idle timer.
|
|
2342
|
+
last_event_at = time.time()
|
|
2276
2343
|
|
|
2277
2344
|
except httpx.TimeoutException:
|
|
2278
2345
|
duration = time.time() - start_time
|
|
@@ -2296,13 +2363,22 @@ class AssistantProvider(LLMProvider):
|
|
|
2296
2363
|
duration = time.time() - start_time
|
|
2297
2364
|
if state.got_error and not state.response_text:
|
|
2298
2365
|
state.response_text = f"Error: {state.error_message}"
|
|
2366
|
+
elif idle_aborted and not state.response_text:
|
|
2367
|
+
# Surface the idle abort cleanly so evaluators don't see an
|
|
2368
|
+
# empty response with no explanation.
|
|
2369
|
+
state.response_text = (
|
|
2370
|
+
f"Error: SSE stream went idle for "
|
|
2371
|
+
f"{_format_seconds(sse_idle_abort_seconds)} without sending a "
|
|
2372
|
+
"final / error event. The chatbot backend kept the connection "
|
|
2373
|
+
"open but stopped emitting progress. Aborted to free the runner."
|
|
2374
|
+
)
|
|
2299
2375
|
|
|
2300
2376
|
log(
|
|
2301
2377
|
f"[Assistant] Done: {len(state.response_text)} chars, "
|
|
2302
2378
|
f"{len(state.tool_calls)} tool calls, {state.token_event_count} tokens, "
|
|
2303
2379
|
f"final={'yes' if state.got_final else 'no'}, "
|
|
2304
2380
|
f"error={'yes' if state.got_error else 'no'}, "
|
|
2305
|
-
f"{duration:.2f}s"
|
|
2381
|
+
f"{duration:.2f}s" + (" [SSE idle aborted]" if idle_aborted else "")
|
|
2306
2382
|
)
|
|
2307
2383
|
|
|
2308
2384
|
return LLMResult(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|