Support configured child sessions
Some checks failed
Test / test (3.3) (push) Failing after 9m35s
Test / test (3.4) (push) Failing after 9m37s
Test / test (3.2) (push) Failing after 9m37s

This commit is contained in:
2026-07-15 20:18:58 -07:00
parent 2e866a618b
commit de14d57634
5 changed files with 105 additions and 4 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true
module Opencode
VERSION = "0.0.1.alpha4"
VERSION = "0.0.1.alpha5"
end

View File

@@ -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" },