Skip to content

feat(setup): run the setup agent through the Go AI SDK with an OpenCode handler (5/6) - #898

Draft
atornsii wants to merge 8 commits into
atorns/agent-4-ai-sdk-relayfrom
atorns/agent-5-opencode-handler
Draft

atornsii wants to merge 8 commits into
atorns/agent-4-ai-sdk-relayfrom
atorns/agent-5-opencode-handler

Conversation

@atornsii

@atornsii atornsii commented Oct 20, 2026 •

Copy link
Copy Markdown

This runs the setup agent through the Go AI SDK. For a logged-in user, setup calls aiserver.Config("ldcli-setup-agent").Stream with a new OpenCode handler. The SDK resolves the config, composes the prompt from it, and calls the handler. The handler sends that prompt to the OpenCode session and turns its events into the SDK's stream events.

The SDK records the run: an opencode.session span, a chat span per reply with its tokens, an execute_tool span per tool call, and the tokens, duration, and success events for the config's Monitoring tab.

Notes

  • The handler implements the AI SDK handler contract for agent-mode configs of any provider, with its tracer under github.com/launchdarkly/go-ai-sdk/opencodeagents. It can move into go-ai-sdk unchanged.
  • The wizard still gets every OpenCode event.
  • If the SDK cannot resolve the config before the handler starts, for example because the config is off or in completion mode, the run uses the built-in instructions and task.
  • Closing a run that ended waits up to 5 seconds for the SDK to record it. Before, tracking was cut off.

Testing

Handler tests cover the chunks, the output, the token usage, the spans, errors, and a stream without an end. An end-to-end test with the real SDK, a fake relay, and a fake OpenCode checks that the config's prompt and model reach OpenCode and that the relay receives the trace export and the tokens, duration, and success events. A real run against a local gonfalon showed up on the config's Monitoring tab.

Diagram

flowchart TD
  setup["setup.go<br/>runner.SDK = source"]:::chg
  oc["opencode.go<br/>Start: SDK.Ready<br/>startWithSDK, driveWithSDK<br/>startTap, sdkDone"]:::chg
  aic["aiconfig.go<br/>Ready, key"]:::chg
  sdk[["aiserver.Config.Stream<br/>ComposePrompt, tracking"]]:::ext
  h["opencode_handler.go<br/>NewOpenCodeHandler<br/>stream: StartHandlerRun<br/>chat spans, toolSpans"]:::new
  cli["client.go from #894<br/>promptAsync with system and model"]:::earlier
  wiz["wizard from #895<br/>run.Events"]:::earlier
  relay["relay_proxy.go from #897<br/>spans and events out"]:::earlier
  setup -->|sets SDK| oc
  oc -->|Ready, key| aic
  oc -->|Stream facts, context| sdk
  sdk -->|calls handler| h
  h -->|SessionEvents: promptAsync| cli
  oc -.->|tap events| h
  oc -.->|same events| wiz
  sdk -.->|tracking, OTLP| relay
  classDef new fill:#e6f0fa,stroke:#1d6fb8,stroke-width:2px,color:#19202b
  classDef chg fill:#fbf1e1,stroke:#a86a12,stroke-width:2px,color:#19202b
  classDef earlier fill:#ffffff,stroke:#7a8494,stroke-dasharray:5 4,color:#19202b
  classDef ext fill:#eceefa,stroke:#4e5a9c,stroke-width:2px,color:#19202b
Loading

Blue boxes are new files, orange boxes are changed files, dashed boxes are files from an earlier PR, and purple boxes are outside ldcli. A solid arrow goes from the caller to the code that it calls. A dashed arrow shows data that moves without a direct call.

If the AI SDK is ready, Start uses startWithSDK. It starts OpenCode and a session, and then driveWithSDK calls aiserver.Config(...).Stream with the facts and the setup context. The AI SDK composes the prompt from the config and calls the handler in opencode_handler.go. The handler sends the prompt through the SessionEvents function, which calls promptAsync. A tap gives the handler a copy of each run event, and the handler turns the events into stream events, chat spans, and tool spans. The AI SDK sends the tracking events and spans out through the proxy. The wizard still reads every event from run.Events.

This PR uses the SDK client from #897 and the session calls from #894. The wizard from #895 does not change.

Stack

This is one of six PRs for the ldcli setup agent. Review and merge them in order, after #877 (Go 1.26):

  1. OpenCode adapter
  2. Wizard agent step
  3. macOS sandbox
  4. AI SDK and relay
  5. OpenCode handler
  6. Run fixes

They replace #876 and #878.

🤖 Generated with Claude Code

…de handler

The setup agent is now an AI SDK agent. For a logged-in user, setup
calls aiserver.Config("ldcli-setup-agent").Stream with a new OpenCode
handler. The AI SDK resolves the config through the relay, composes the
prompt from it, and calls the handler. The handler sends that prompt to
the OpenCode session and turns the session's events into the SDK's
stream events. The SDK records the run: an opencode.session span, a
chat span per reply with its tokens, an execute_tool span per tool call,
and the tokens, duration, and success events for the config's
Monitoring tab.

The handler implements the AI SDK's handler contract for agent-mode
configs of any provider, so it can move into go-ai-sdk unchanged. The
wizard still gets every OpenCode event. If the SDK cannot resolve the
config before the handler starts, the run uses the built-in
instructions and task.

Closing a run that ended waits up to 5 seconds for the SDK to record it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@atornsii
atornsii added this pull request to stack #902 October 20, 2026 15:48
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.

1 participant