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
2 changes: 1 addition & 1 deletion packages/opencode-agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ async def main() -> None:
asyncio.run(main())
```

`base_url` is a running `opencode serve`. Each call opens its own session, including a judge routed to this handler. A judge runs as a full OpenCode agent: the server's tools, in the server's working directory, apply to the judge prompt. There is no tool-less judge mode. The handler yields AG-UI text and tool events. `config().stream()` yields `chunk` then `done`.
`base_url` is a running `opencode serve`. The handler owns every HTTP call to that server. A caller does not pass an HTTP client or transport. Each call opens its own session, including a judge routed to this handler. A judge runs as a full OpenCode agent: the server's tools, in the server's working directory, apply to the judge prompt. There is no tool-less judge mode. The handler yields AG-UI text and tool events. `config().stream()` yields `chunk` then `done`.
2 changes: 1 addition & 1 deletion packages/opencode-agents/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ A run opens one `invoke_agent` span and sets `launchdarkly.operation.type`. The

## Call shape

`create_opencode_handler(*, base_url, capture_content=None, model=None, agent=None, client=None)` returns a `ProviderHandler`. `base_url` is a running `opencode serve`. Each call opens its own session.
`create_opencode_handler(*, base_url, capture_content=None, model=None, agent=None)` returns a `ProviderHandler`. `base_url` is a running `opencode serve`. The handler owns the HTTP client. Each call opens its own session.

Assistant text is emitted only after `message.updated` marks that message `role == "assistant"`. A part snapshot is the full text of that part so far; a later delta appends. A text delta without `partID` is dropped.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
from contextlib import aclosing
from typing import Any

import httpx

from launchdarkly_ai_server import (
AiConfigRep,
ProviderHandler,
Expand Down Expand Up @@ -493,17 +491,17 @@ def create_opencode_handler(
capture_content: bool | None = None,
model: ModelResolver | None = None,
agent: str | None = None,
client: httpx.AsyncClient | None = None,
) -> ProviderHandler:
"""Return a handler that opens one OpenCode session per call.

``provides_for`` is ``('*', 'agent')``. *base_url* is a running
``opencode serve``. A judge routed here gets its own session and runs as a
full OpenCode agent: the server's tools, in the server's working directory,
apply to the judge prompt too. This package does not offer a tool-less
judge agent. A Vega-local handler was rejected: the session boundary
belongs to this package. *model* overrides provider and model selection.
*client* is for tests.
``opencode serve``. The handler opens its own HTTP client. A caller does
not pass a client or a transport. A judge routed here gets its own session
and runs as a full OpenCode agent: the server's tools, in the server's
working directory, apply to the judge prompt too. This package does not
offer a tool-less judge agent. A Vega-local handler was rejected: the
session boundary belongs to this package. *model* overrides provider and
model selection.
"""
if not isinstance(base_url, str) or not base_url:
raise TypeError("The OpenCode handler requires a base_url.")
Expand All @@ -513,7 +511,6 @@ def create_opencode_handler(
capture_content=capture_content,
model=model or default_model,
agent=agent,
client=client,
)


Expand All @@ -533,15 +530,13 @@ def opencode_agents(
raise TypeError("The OpenCode handler requires a base_url.")
model = kwargs.pop("model", None)
agent = kwargs.pop("agent", None)
client = kwargs.pop("client", None)
return _config(
key=config_key,
handler=_create_opencode_handler(
base_url=base_url,
capture_content=capture_content,
model=model or default_model,
agent=agent,
client=client,
),
**kwargs,
).invoke(user_input, context, variables, history)
Expand All @@ -553,13 +548,11 @@ def _create_opencode_handler(
capture_content: bool | None,
model: ModelResolver,
agent: str | None,
client: httpx.AsyncClient | None,
) -> ProviderHandler:
content_override = capture_content
bound_url = base_url
resolve_model = model
bound_agent = agent
bound_client = client

async def _stream_impl(
config: AiConfigRep,
Expand Down Expand Up @@ -594,7 +587,6 @@ async def _stream_impl(
provider_id=provider_id,
model_id=model_id,
agent=bound_agent,
client=bound_client,
capture=capture,
)
) as reduced:
Expand Down Expand Up @@ -669,7 +661,6 @@ async def _reduce_session(
provider_id: str,
model_id: str,
agent: str | None,
client: httpx.AsyncClient | None,
capture: bool,
) -> AsyncGenerator[dict[str, Any], None]:
"""Reduce the session ``POST /session`` created. The reducer is unchanged."""
Expand All @@ -684,7 +675,6 @@ async def _reduce_session(
model_id=model_id,
agent=agent,
session_ids=session_ids,
client=client,
)
) as raw_events:
async for raw in raw_events:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,17 @@ async def session_events(
model_id: str,
agent: str | None,
session_ids: list[str],
client: httpx.AsyncClient | None = None,
) -> AsyncGenerator[dict[str, Any], None]:
"""Open one session and yield its bus events.

The event stream is opened before the message post. The post runs
The handler owns the HTTP client. Callers pass ``base_url`` for a server
that is already running and do not supply a client or a transport. The
event stream is opened before the message post. The post runs
concurrently with the read. This session's ``session.idle`` ends the
turn. A failed message POST raises without waiting for another bus
frame. Leaving early cancels the post and aborts the session.
"""
owns_client = client is None
http = client or httpx.AsyncClient(
http = httpx.AsyncClient(
timeout=httpx.Timeout(HTTP_TIMEOUT_SECONDS, read=MESSAGE_TIMEOUT_SECONDS)
)
base = base_url.rstrip("/")
Expand Down Expand Up @@ -127,8 +127,7 @@ async def session_events(
)
except Exception:
pass
if owns_client:
await http.aclose()
await http.aclose()


async def _read_until_done(
Expand Down
Loading
Loading