From de14d5763472e7f6fb4237ac5cbc6afcbe6a8d49 Mon Sep 17 00:00:00 2001 From: Ajay Krishnan Date: Wed, 15 Jul 2026 20:18:58 -0700 Subject: [PATCH] Support configured child sessions --- CHANGELOG.md | 10 ++++++++ README.md | 23 ++++++++++++++++++- lib/opencode/client.rb | 28 ++++++++++++++++++++-- lib/opencode/version.rb | 2 +- test/opencode/smoke_test.rb | 46 +++++++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3248d10..31e19f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.0.1.alpha5 - 2026-07-15 + +### Added + +- Extend `Opencode::Client#create_session` with OpenCode's native parent, + agent, model, metadata, and workspace fields while preserving the existing + title and permission call shape. Session model strings are encoded with the + session endpoint's `{ providerID, id }` shape rather than the message + endpoint's `{ providerID, modelID }` shape. + ## 0.0.1.alpha4 - 2026-07-12 ### Fixed diff --git a/README.md b/README.md index f20fc15..15ff018 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,27 @@ Multi-tenant apps construct multiple clients with different `base_url`s — each ## Core API +### Configured and parent-linked sessions + +OpenCode can create a session under an existing parent and select its agent, +model, metadata, workspace, and permission policy in the same request: + +```ruby +child = client.create_session( + title: "Destination curator", + parent_id: parent_session_id, + agent: "destination-list-curator", + model: "openai/gpt-5.5", + metadata: { run_id: "9" }, + workspace_id: workspace_id, + permissions: permission_rules +) +``` + +Model strings use OpenCode's `provider/model` form; a preformatted model hash +with `providerID` and `id` keys is also accepted. These configured-session +fields require OpenCode 1.16.1 or newer. + ### Streaming (the headline) ```ruby @@ -166,7 +187,7 @@ bundle install bundle exec rake test ``` -16-test smoke covers Client end-to-end against WebMock-stubbed OpenCode endpoints. +The smoke suite covers Client end-to-end against WebMock-stubbed OpenCode endpoints. ## License diff --git a/lib/opencode/client.rb b/lib/opencode/client.rb index 479819f..75e8179 100644 --- a/lib/opencode/client.rb +++ b/lib/opencode/client.rb @@ -25,8 +25,24 @@ module Opencode @workspace = workspace end - def create_session(title: nil, permissions: nil) - body = { title: title, permission: permissions }.compact + def create_session( + title: nil, + permissions: nil, + parent_id: nil, + agent: nil, + model: nil, + metadata: nil, + workspace_id: nil + ) + body = { + title: title, + permission: permissions, + parentID: parent_id, + agent: agent, + model: format_session_model(model), + metadata: metadata, + workspaceID: workspace_id + }.compact post("/session", body) end @@ -476,6 +492,14 @@ module Opencode { providerID: provider, modelID: model_id } end + def format_session_model(model) + return nil unless model + return model if model.is_a?(Hash) + + provider, model_id = model.split("/", 2) + { providerID: provider, id: model_id } + end + def post(path, body) uri = build_uri(path) request = Net::HTTP::Post.new(uri) diff --git a/lib/opencode/version.rb b/lib/opencode/version.rb index d4e1dfa..7ce8ac9 100644 --- a/lib/opencode/version.rb +++ b/lib/opencode/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Opencode - VERSION = "0.0.1.alpha4" + VERSION = "0.0.1.alpha5" end diff --git a/test/opencode/smoke_test.rb b/test/opencode/smoke_test.rb index 9b26cef..14b8413 100644 --- a/test/opencode/smoke_test.rb +++ b/test/opencode/smoke_test.rb @@ -46,6 +46,7 @@ class SmokeTest < Minitest::Test def test_create_session_returns_session_id stub_request(:post, "#{BASE}/session") + .with(body: { title: "smoke", permission: [] }.to_json) .to_return(status: 200, body: { id: SESSION_ID, title: "smoke" }.to_json, headers: { "Content-Type" => "application/json" }) @@ -53,6 +54,51 @@ class SmokeTest < Minitest::Test assert_equal SESSION_ID, response[:id] end + def test_create_session_sends_native_child_and_configuration_fields + permissions = [ { permission: "skill", pattern: "*", action: "deny" } ] + expected_body = { + title: "curator", + permission: permissions, + parentID: "ses_parent", + agent: "destination-list-curator", + model: { providerID: "openrouter", id: "anthropic/claude-sonnet-4" }, + metadata: { run: "9" }, + workspaceID: "wrk_1" + } + + stub_request(:post, "#{BASE}/session") + .with(body: expected_body.to_json) + .to_return(status: 200, body: { id: SESSION_ID }.to_json, + headers: { "Content-Type" => "application/json" }) + + response = @client.create_session( + title: "curator", + permissions: permissions, + parent_id: "ses_parent", + agent: "destination-list-curator", + model: "openrouter/anthropic/claude-sonnet-4", + metadata: { run: "9" }, + workspace_id: "wrk_1" + ) + + assert_equal SESSION_ID, response[:id] + assert_requested :post, "#{BASE}/session", body: expected_body.to_json, times: 1 + end + + def test_create_session_preserves_a_preformatted_model + model = { providerID: "openai", id: "gpt-5.5", variant: "high" } + + stub_request(:post, "#{BASE}/session") + .with(body: { model: model }.to_json) + .to_return(status: 200, body: { id: SESSION_ID }.to_json, + headers: { "Content-Type" => "application/json" }) + + response = @client.create_session(model: model) + + assert_equal SESSION_ID, response[:id] + assert_requested :post, "#{BASE}/session", body: { model: model }.to_json, times: 1 + end + def test_update_session_patches_permissions_and_returns_the_updated_session permissions = [ { permission: "skill", pattern: "*", action: "deny" },