Skip to content

feat: Microsoft Agent Framework compatibility and bypass mode#46

Merged
CaddyGlow merged 1 commit intomainfrom
feat/codex-msaf-compatibility
Mar 20, 2026
Merged

feat: Microsoft Agent Framework compatibility and bypass mode#46
CaddyGlow merged 1 commit intomainfrom
feat/codex-msaf-compatibility

Conversation

@CaddyGlow
Copy link
Copy Markdown
Owner

Summary

  • Add OpenAI Responses format support to mock handler with proper SSE events
  • Add format-aware mock adapter with target format detection
  • Add bypass mode to plugin factory with MockAdapter fallback
  • Add inject_detection_payload config toggle for generic API usage
  • Add openai_thinking_xml streaming configuration support

Replaces #44 (auto-closed when #43 merged). PR 2 of 3 from original #41.

Test plan

  • All pre-commit checks pass
  • 1101 unit/plugin tests pass

Add MSAF-compatible mock response handling and bypass mode infrastructure:
- Add OpenAI Responses format support to mock handler with proper SSE events
- Add prompt text extraction for context-aware mock responses
- Add format-aware mock adapter with target format detection
- Add bypass mode to plugin factory with MockAdapter fallback
- Add inject_detection_payload config toggle for generic API usage
- Add openai_thinking_xml streaming configuration
- Add format adapter streaming configuration support
- Add MSAF integration and real library tests
- Add bypass mode factory tests
Copilot AI review requested due to automatic review settings March 20, 2026 14:49
@CaddyGlow CaddyGlow merged commit e0ff167 into main Mar 20, 2026
5 checks passed
@CaddyGlow CaddyGlow deleted the feat/codex-msaf-compatibility branch March 20, 2026 14:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Microsoft Agent Framework–compatible behavior to the Codex proxy by making mock/bypass mode format-aware (OpenAI Chat vs OpenAI Responses vs Anthropic), adding a bypass-mode adapter fallback in provider factories, and propagating the openai_thinking_xml streaming setting through format adapters.

Changes:

  • Implement OpenAI Responses-format mock streaming (SSE event sequence) and endpoint/format-chain based target format detection in the mock adapter.
  • Add provider-factory bypass mode that returns MockAdapter (with clear warning/error handling) plus coverage for bypass behavior.
  • Add inject_detection_payload Codex config toggle and propagate openai_thinking_xml via format-adapter ContextVar, with unit/integration tests for MSAF-style workflows.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
uv.lock Updates locked dependencies (notably mcp bump) to support new functionality.
tests/unit/services/test_mock_adapter.py Extends mock-adapter tests for target format resolution and prompt extraction call path.
tests/unit/services/mocking/test_mock_handler.py Adds prompt extraction tests and validates OpenAI Responses SSE event sequences.
tests/unit/llms/test_llms_streaming_settings.py Verifies openai_thinking_xml propagation and ContextVar task isolation.
tests/unit/core/test_provider_factory_bypass.py Adds unit coverage for provider factory bypass mode behavior.
tests/plugins/codex/unit/test_adapter.py Adds coverage for disabling detection payload injection and preserving user reasoning.
tests/plugins/codex/integration/test_msaf_real_library.py Adds multi-step MSAF-style workflow tests through the proxy (httpx).
tests/plugins/codex/integration/test_msaf_compat.py Adds MSAF compatibility integration tests (no CLI injection, no thinking XML).
tests/plugins/codex/integration/test_codex_basic.py Adds integration validation for bypass-mode Responses streaming events.
ccproxy/services/mocking/mock_handler.py Implements format-aware mock generation + OpenAI Responses streaming SSE events + prompt extraction.
ccproxy/services/factories.py Wires OpenAI Responses mock adapter and propagates openai_thinking_xml into adapters/registry.
ccproxy/services/adapters/mock_adapter.py Adds format inference (format_chain / endpoint) and passes prompt/format into mock handler.
ccproxy/services/adapters/format_adapter.py Adds configure_streaming() and sets openai_thinking_xml via ContextVar during conversions.
ccproxy/plugins/codex/config.py Introduces inject_detection_payload configuration option.
ccproxy/plugins/codex/adapter.py Skips detection/template injection when configured; normalizes input and strips unsupported params.
ccproxy/llms/formatters/openai_to_openai/streams.py Respects ContextVar-controlled openai_thinking_xml when emitting thinking segments.
ccproxy/llms/formatters/openai_to_openai/responses.py Respects ContextVar-controlled openai_thinking_xml when merging/serializing thinking.
ccproxy/llms/formatters/context.py Adds ContextVar helpers for openai_thinking_xml.
ccproxy/core/plugins/factories.py Adds bypass-mode factory path returning MockAdapter and logging a lifecycle warning.
.ccproxy.codex.msaf.toml.example Adds example config for MSAF usage (incl. disabling detection injection and thinking XML).
Comments suppressed due to low confidence (1)

ccproxy/services/adapters/mock_adapter.py:116

  • handle_request() always creates a new RequestContext with a hard-coded request_id ("mock-request"), even when request.state.context already exists. This makes request/stream IDs collide across requests in bypass mode and breaks log correlation (the upstream middleware/decorators generate per-request UUIDs). Consider reusing request.state.context when present, otherwise generate a unique request_id (e.g., from X-Request-ID header or uuid4()).
        # Get endpoint from context or request URL
        endpoint = request.url.path
        if hasattr(request.state, "context"):
            ctx = request.state.context
            endpoint = ctx.metadata.get("endpoint", request.url.path)

        target_format = self._resolve_target_format(request, endpoint)
        model = "unknown"
        try:
            body_json = json.loads(body) if body else {}
            model = body_json.get("model", "unknown")
        except json.JSONDecodeError:
            pass
        except UnicodeDecodeError:
            pass
        except Exception as e:
            logger.debug("stream_flag_extraction_error", error=str(e))

        # Create request context
        ctx = RequestContext(
            request_id="mock-request",
            start_time=time.perf_counter(),
            logger=structlog.get_logger(__name__),
        )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -64,7 +107,6 @@ async def handle_request(
pass
except Exception as e:
logger.debug("stream_flag_extraction_error", error=str(e))
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the exception handler when parsing the request body for model, the log event name is stream_flag_extraction_error, which is misleading (this block isn't extracting the stream flag). Renaming the event (e.g., model_extraction_error or body_parse_error) would make logs easier to interpret.

Suggested change
logger.debug("stream_flag_extraction_error", error=str(e))
logger.debug("model_extraction_error", error=str(e))

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants