Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ now read, each on its own labelled line. The URI leads and the name and descript
a long name cannot push the pointer past the result cap; a server's `title` is shown over its `name`
when it gives one. A part that already carried text is unchanged.

### The Microsoft Agent Framework Bot answers on a plain OpenAI key

Picked with an OpenAI key, the Microsoft Agent Framework Bot failed every run with "Connection
error.". Compose writes `OPENAI_BASE_URL` empty when the choice is a plain OpenAI key, and the
OpenAI SDK only defaults an absent URL, so it was given "" as the address. The Bot now falls back to
`https://api.openai.com/v1` for an empty value, as its Anthropic branch already did for
`ANTHROPIC_BASE_URL`. An OpenAI-compatible endpoint is unchanged.

### Skill selection keeps capabilities named across multiple JSON replies

When a model wraps its skill choice in prose or sends a revised JSON object, OpenBot reads each
Expand Down
5 changes: 4 additions & 1 deletion agent-microsoft/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ def _client() -> AnthropicClient | OpenAIChatClient:
# Compose exports missing overrides as ""; the SDK only defaults an absent URL.
base_url = (os.environ.get("ANTHROPIC_BASE_URL") or "").strip() or "https://api.anthropic.com"
return AnthropicClient(model=model, base_url=base_url)
return OpenAIChatClient(model)
# The same "" for OPENAI_BASE_URL, which Compose writes whenever the model screen chose a plain
# OpenAI key. The SDK only defaults an absent URL; given "", every request fails to connect.
base_url = (os.environ.get("OPENAI_BASE_URL") or "").strip() or "https://api.openai.com/v1"
return OpenAIChatClient(model, base_url=base_url)


agent = _client().as_agent(
Expand Down
41 changes: 41 additions & 0 deletions agent-microsoft/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

import httpx
import httpx2
import pytest
import uvicorn
from fastapi import FastAPI, Request
Expand Down Expand Up @@ -217,3 +218,43 @@ async def respond(transport, request):
assert '"RUN_FINISHED"' in response.text
assert '"RUN_ERROR"' not in response.text
assert "hello" in response.text


def test_an_openai_key_uses_the_official_endpoint_when_compose_sets_a_blank_url(monkeypatch):
# The same "" Compose writes for ANTHROPIC_BASE_URL above, on the other provider: the model screen
# sets only the key for a plain OpenAI key, and the SDK treats "" as an address, not as absent.
seen = []
provider_seen = []
provider_app = _provider_app(provider_seen)

async def respond(transport, request):
seen.append(
(request.url.scheme, request.url.host, request.url.path, request.headers.get("authorization"))
)
async with httpx2.ASGITransport(app=provider_app) as local_provider:
return await local_provider.handle_async_request(request)

# Keep the real framework and OpenAI clients; replace only the network transport. The OpenAI SDK
# sends through httpx2, not httpx, so patching httpx's transport here would let the request out.
monkeypatch.setattr(httpx2.AsyncHTTPTransport, "handle_async_request", respond)
monkeypatch.setenv("MANAGED_AGENT_TOKEN", TOKEN)
monkeypatch.setenv("BOT_PROVIDER", "openai")
monkeypatch.setenv("BOT_MODEL", "gpt-5.5")
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
monkeypatch.setenv("OPENAI_BASE_URL", "")
monkeypatch.setenv("ANTHROPIC_API_KEY", "")
monkeypatch.setenv("ANTHROPIC_BASE_URL", "")

from src import main

main = importlib.reload(main)
response = TestClient(main.app).post(
"/", json=RUN, headers={"x-openbot-agent-token": TOKEN}
)

assert seen == [("https", "api.openai.com", "/v1/responses", "Bearer test-key")]
assert provider_seen == [("openai", "gpt-5.5")]
assert response.status_code == 200
assert '"RUN_FINISHED"' in response.text
assert '"RUN_ERROR"' not in response.text
assert "hello" in response.text
Loading