Skip to content

[AI-1892] Publish the Go SDK from main (sdks/go v0.1.0) - #1814

Merged
bokan merged 8 commits into
mainfrom
ai-1892-go-sdk-release
Nov 9, 2026
Merged

bokan merged 8 commits into
mainfrom
ai-1892-go-sdk-release

Conversation

@bokan

@bokan bokan commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Why

AI-1892 publishes the Go server-side SDK. A Go module is released by tagging the repository, and go get ...@latest, @main and pkg.go.dev read the default branch, which is main. main has no sdks/ today, and accelerate, where the SDK is developed, stays the development branch. So main gets a copy of sdks/go from one accelerate commit, and the first version, sdks/go/v0.1.0, is tagged on this PR's merge commit.

How it works

accelerate (development)            main (release)
  #1812 Go SDK fixes                  this PR, from origin/main
  #1813 README, sdk-go skill   ──┐     ├─ sdks/go  = git checkout <SHA> -- sdks/go
  ...                            └──►  ├─ sdk-go.yml          (PR/push: Go 1.25 alone)
                                       ├─ release-sdk-go.yml  (tag: verify via proxy)
                                       ├─ release.yml guard   (PyPI only for v* tags)
                                       └─ merge ─► tag sdks/go/v0.1.0 ─► proxy.golang.org
File Why
sdks/go/** Copied unchanged from accelerate at 68ffd61c2 (after #1812, #1813 and #1863). git diff 68ffd61c2 HEAD -- sdks/go is empty
.github/workflows/sdk-go.yml main has no other Go: gofmt, go mod tidy -diff, go mod verify, vet (e2e included) and go test -race on Go 1.25.0, GOWORK=off, GOTOOLCHAIN=local
.github/workflows/release-sdk-go.yml On a sdks/go/v* tag: checks the tag is exactly sdks/go/vX.Y.Z and on main, tests the tagged copy, then go gets the version through proxy.golang.org into an empty module and builds examples/quickstart with it. Publishes nothing, creates no GitHub Release. Dispatchable to re-verify a tag, since it lives on the default branch
.github/workflows/release.yml Runs only for a release tagged v*. A GitHub Release for sdks/go/v0.1.0 or sdk-js-v* would otherwise reach uv publish
.github/workflows/run_tests.yml Runs a PulseAudio server before the Python tests, as accelerate does since #1562: Ubuntu 26.04's PortAudio fails import sounddevice without one, which has failed main's unit job since 2026-11-05
.gitattributes Marks sdks/go/acceleration/generated.go generated, so the diff stays reviewable
CLAUDE.md Never edit sdks/go here; merging accelerate into main conflicts on sdks/go, so take accelerate's tree; tag the merge commit by SHA; retract, never move

go generate does not run on main: it needs acceleration/api/openapi.yaml, which only accelerate has. The generated code is committed, so consumers need nothing.

Before merging

  1. Done: [AI-1892] Go SDK fixes before its first release #1812 and [AI-1892] Go SDK README, sdk-go skill and update log #1813 are merged into accelerate (8160088e0, cf2e643db), and [AI-1892] Go SDK: Leave returns a stop the router refused #1863 (68ffd61c2). fix(sdks/go): reconnect a dispatch worker whose router falls silent #1732 (a dispatch worker whose router falls silent) is still open and is not in this copy; it ships in the next patch.

  2. Done: sdks/go re-copied from 68ffd61c2, which adds [AI-1892] Go SDK: Leave returns a stop the router refused #1863 (Leave reports a stop the router refused) and AI-2992's spend limits and teams; git diff 68ffd61c2 HEAD -- sdks/go prints nothing.

  3. Not run: the release smoke set against a hosted router (it uses real models). It is the check to run before the next tag:

    cd sdks/go
    E2E_REQUIRED=1 go test -tags e2e -count=1 -v -run 'TestTheRouterIsReachable|TestAConversationInWriting|TestAFunctionRegisteredHere|TestASessionIsRecorded|TestSyncStores|TestTheUsageCallbackSeesEveryResponse' .

After merging

git fetch origin
MERGED=$(gh pr view 1814 --repo GetStream/Vision-Agents --json mergeCommit -q .mergeCommit.oid)
git tag sdks/go/v0.1.0 "$MERGED"
git push origin sdks/go/v0.1.0       # no GitHub Release

release-sdk-go.yml then verifies it. A broken version is never moved: retract it in sdks/go/go.mod on accelerate, copy again, and tag v0.1.1.

Validation

  • In this branch at 68ffd61c2's copy, sdks/go with GOWORK=off GOTOOLCHAIN=go1.25.0: go build, go vet (e2e included), go test -race ./..., go mod tidy -diff and go mod verify pass, and gofmt -l is empty.
  • Through the public proxy, from an empty module outside the repo on Go 1.25.0 with GOPRIVATE cleared, go get github.com/GetStream/Vision-Agents/sdks/go@52f871d4b resolved v0.0.0-20261106083818-52f871d4b173, and the quickstart built against it. That is the consumer job's flow, run on this branch's head before a tag exists.
  • An independent review found no blockers; its fixes (strict tag check, merge guidance, tag by SHA) are in. sdk-go.yml is path-filtered, so it should not be made a required check on main.
  • Not run: the smoke set (above), and either workflow on GitHub until this PR's checks run.

bokan added 4 commits November 6, 2026 09:38
sdks/go as it is at 413eaab on ai-1892-go-sdk-docs (#1813, on #1812),
unchanged. It is developed on accelerate and released from here.

AI-1892
A release made for sdks/go/vX.Y.Z or sdk-js-vX.Y.Z would otherwise run
uv publish from this workflow.

AI-1892
bokan added 2 commits November 6, 2026 10:07
…rges

Review of #1814: the version glob let v0.1.0-rc1 through, and a merge of
accelerate into main conflicts on sdks/go, which shares no history.

AI-1892
@coderabbitai

coderabbitai Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Walkthrough

The change adds a Go SDK with client, agent, stream, tool, and edge packages. It includes APIs for sessions, tools, routing, phone calls, usage, campaigns, and spend limits. The change also adds agent-directory synchronization, examples, tests, documentation, and workflows for SDK checks and tagged-release verification.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Some tools did not complete. Review the errors below.

ast-grep (0.51.1)

sdks/go/acceleration/generated.go

ast-grep timed out on this file

Review coverage is incomplete: 1 file could not be fully reviewed. Findings from completed review steps are included; see review info for details.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 32

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Major comments: 8

sdks/go/stream/dispatch.go: 1

447-458: Major · Stability & Availability · Heavy lift

Release pending holds when the connection closes.

If the connection closes before acknowledging a hold, a hosted tool without a timeout can wait indefinitely. On normal closure, Run also waits indefinitely for that tool. Fail pending holds when their connection ends.

sdks/go/stream/router.go: 1

408-425: Major · Stability & Availability · Quick win

Unblock reader sends on close.

If a caller stops draining a full output channel, Close leaves the reader blocked and the channel open. Repeated sessions can accumulate goroutines. Signal shutdown from each Close and select on that signal for every send in Transcriber.read, Voice.read, and Model.read.

sdks/go/stream/socket.go: 1

82-90: Major · Stability & Availability · Heavy lift

Preserve transport settings for socket handshakes.

When HTTPClient uses a custom CA, client certificate, or proxy, REST calls can succeed while socket handshakes fail or use another route. Provide a way to apply those transport settings to socket handshakes.

sdks/go/stream/errors.go: 1

166-169: Major · Stability & Availability · Quick win

Limit JSON error bodies before buffering them.

If a router or proxy returns a large JSON error, readable.Do reads the entire body into memory. This can exhaust memory and terminate the calling process. Enforce a size limit and return an error when the body exceeds it.

sdks/go/tools/run.go: 2

107-114: Major · Data Integrity & Integration · Heavy lift

Preserve deduplication when receipt writes fail.

If Store fails after a side effect succeeds, Run returns success and removes the in-flight entry. A redelivery can then execute the same tool again. Ensure completed calls remain deduplicated when receipt writes fail.


90-106: Major · Data Integrity & Integration · Heavy lift

Coordinate in-flight calls across workers.

If two workers receive the same call before either stores its receipt, both can execute the tool. Shared receipts do not prevent duplicate side effects. Coordinate in-flight calls through the shared store and return the first execution’s result to duplicates.

sdks/go/stream/accelerated.go: 1

901-908: Major · Stability & Availability · Heavy lift

Bound the tool drain during shutdown.

If a tool blocks without observing its context, p.running.Wait() can block Leave and Session.Close indefinitely, even after the shutdown context expires. The tool timeout only cancels its context. Bound the shutdown wait and isolate late tool completions from subsequent sessions.

sdks/go/client/responses.go: 1

96-101: Major · Stability & Availability · Quick win

Reject an empty next cursor.

If a page reports HasMore with an empty NextCursor, the next request fetches the first page again. If the router repeats that response, All keeps accumulating items until cancellation or resource exhaustion. Return an error instead.

Suggested fix

 			if !read.HasMore || read.NextCursor == nil {
 				return
 			}
+			if *read.NextCursor == "" {
+				stream.failed = fmt.Errorf("client: empty next cursor while reading the items of %s", i.sessionID)
+				return
+			}
 			cursor = *read.NextCursor
Minor comments: 18

sdks/go/stream/dispatch.go: 1

194-196: Minor · Stability & Availability · Quick win

Reject negative reporting intervals.

A negative ReportEvery reaches time.NewTicker after connection and panics in a goroutine, terminating the process. Return a configuration error instead.

Suggested fix

+	if options.ReportEvery < 0 {
+		return nil, errors.New("stream: report interval cannot be negative")
+	}
 	if options.ReportEvery == 0 {
 		options.ReportEvery = defaultReportEvery
 	}
sdks/go/stream/recorded.go: 1

58-59: Minor · Stability & Availability · Heavy lift

Read recording data incrementally.

RecordedCall reads the entire file before yielding audio. A large recording or concurrent recordings can exhaust memory and terminate the process, interrupting transcription. Read the WAV data in bounded chunks and close the file when iteration stops.

sdks/go/stream/routerconfig.go: 1

96-105: Minor · Data Integrity & Integration · Heavy lift

Make modality updates atomic across callers.

Concurrent ConfigureSTT and ConfigureTTS calls can read the same config and then replace it with different snapshots. The last write can silently restore the other modality’s old value. Use an atomic server-side modality update or a conditional replacement with retry.

sdks/go/stream/tool_line.go: 1

39-45: Minor · Stability & Availability · Heavy lift

Preserve all waiters for an overlapping hold.

When a tool calls DisallowInterruptions concurrently, the second request replaces the first waiter. A tool_held response then leaves the first request blocked until its context is canceled. Coordinate overlapping holds so each waiter receives the result and cleanup cannot remove another waiter.

sdks/go/stream/models.go: 1

99-110: Minor · Stability & Availability · Quick win

Reject a missing or unchanged pagination cursor.

If a model-list response reports has_more with a missing or unchanged cursor, and the requested name is absent, DefineModel and DeleteModel can repeat requests until the context expires. An uncancelled call can loop indefinitely. Return an error when pagination cannot advance.

sdks/go/stream/sts.go: 1

124-135: Minor · Stability & Availability · Quick win

Cancel blocked event sends on close.

If a caller stops reading after 64 turns, the reader can block on the next send. Close then closes the socket but leaves the reader blocked and Events open. Signal shutdown from Close and select on that signal for both event sends.

sdks/go/stream/socket.go: 1

106-108: Minor · Stability & Availability · Quick win

Prevent replacing an open connection.

If Open succeeds twice before Close, the second connection replaces the first without closing it. Close can then close only the second connection. Reject a second open and close any redundant connection.

sdks/go/stream/accelerated.go: 4

488-493: Minor · Stability & Availability · Quick win

Fall back to HTTP when the close write fails.

If socket.Send(Frame{"type": "close"}) returns an unclassified network error, Leave clears p.session and skips stopOverHTTP. The text session can remain active after the socket drops, and a later Leave cannot retry because p.session is nil. Run the HTTP stop path for every failed close write.


374-379: Minor · Stability & Availability · Quick win

Use a separate cleanup context for abandon.

When the handshake exceeds the caller’s deadline, Watch calls p.abandon(ctx, session.Id) with the expired context. The generated stop request attaches that context to the HTTP request, so the router cannot receive the stop request. Create a bounded cleanup context independent of the caller’s deadline before calling StopSessionWithResponse.


485-486: Minor · Stability & Availability · Quick win

A close-command timeout can leave a text session running.

If a proxy has not forwarded the close command when the five-second wait expires, closing the socket can discard it. Leave then returns nil without stopping the session over HTTP.


561-564: Minor · Data Integrity & Integration · Low value

Sub-millisecond tool timeouts select the backend default.

When Config.ToolTimeout is positive but shorter than one millisecond, Pipeline.request sends tool_timeout_ms: 0. The session therefore uses the default instead of the requested timeout.

sdks/go/agents/folder.go: 1

792-810: Minor · Data Integrity & Integration · Quick win

Check skill names after parsing frontmatter.

If two files declare the same name, loading succeeds and sync sends both definitions under that name. This violates the sync API’s per-config skill-name uniqueness contract. Check parsed names for duplicates while retaining the existing file-and-folder collision check.

sdks/go/agents/dispatch.go: 1

330-344: Minor · Stability & Availability · Heavy lift

Prevent enqueueing after the conversation runner exits.

If the runner exits between Ended and the send, Respond can return nil for a question that no goroutine will read. The dispatcher then reports the work as done without an error. Coordinate acceptance and runner shutdown so this question is not acknowledged as accepted.

sdks/go/examples/text/main.go: 1

63-67: Minor · Stability & Availability · Low value

Exit the input loop on cancellation.

On Linux, pressing Ctrl-C while stdin is idle can leave Scan blocked. The program then keeps the session open until the loop returns. Read input separately and select on ctx.Done() so cancellation reaches the deferred session close.

sdks/go/stream/router.go: 1

599-607: Minor · Data Integrity & Integration · Quick win

Do not label byte-backed images as JPEG when Image.MIME is empty.

contentOf encodes arbitrary Image.Data as data:image/jpeg;base64,... without inspecting or requiring its media type. A PNG or WebP therefore crosses the router boundary with an incorrect media type. Require the caller to provide MIME for byte-backed images or derive the type before constructing the data URL. The provider's rejection behavior is not established here.

sdks/go/tools/run.go: 1

149-153: Minor · Stability & Availability · Low value

Reject a nil receipt store in SetReceipts.

If a caller passes nil after registration, a subsequent identified call can panic before the tool runs. Return an error without replacing the current store.

Suggested fix

-func (r *Registry) SetReceipts(receipts Receipts) {
+func (r *Registry) SetReceipts(receipts Receipts) error {
+	if receipts == nil {
+		return errors.New("tools: receipt store must not be nil")
+	}
 	r.mu.Lock()
 	defer r.mu.Unlock()
 	r.receipts = receipts
+	return nil
 }
sdks/go/agents/config.go: 1

591-593: Minor · Data Integrity & Integration · Quick win

Skill lookup can select another config’s ID

If two configs have same-named skills, non-folder Agent.Sync can send the other config’s skill ID to UpdateSkillWithResponse. That request targets the wrong skill instead of updating the intended one.

sdks/go/agents/phone.go: 1

154-171: Minor · Stability & Availability · Heavy lift

An active agent can place a call it cannot join.

If the agent already holds a session, StartCall places the new call before Pipeline.ready rejects the join. The call can continue ringing without an agent to answer it.

Nitpick comments: 7

sdks/go/stream/dispatch_test.go: 1

478-483: Trivial · Maintainability & Code Quality · Quick win

Assert the latency in the load frame.

The test checks worker.LatencyMS() but not load["latency_ms"]. A frame with zero or missing latency can pass. Assert that the frame carries the measured latency. Coordinate the measurement and report so the test compares the same sample.

sdks/go/stream/tools_test.go: 1

273-285: Trivial · Functional Correctness · Quick win

Assert each call’s result.

The test can pass if both calls fail or both succeed. Choose the failing call by ID; concurrent execution does not guarantee invocation order.

Suggested fix

-	var calls atomic.Int32
 	pipeline := Accelerated(Config{Backend: Backend{URL: backend.URL, CustomerID: "acme"}})
-	s.Require().NoError(tools.Register(pipeline.Functions(), "flaky", "Fails the first time",
-		func(context.Context, struct{}) (any, error) {
-			if calls.Add(1) == 1 {
+	s.Require().NoError(tools.Register(pipeline.Functions(), "flaky", "Fails call-1",
+		func(ctx context.Context, _ struct{}) (any, error) {
+			if tools.ToolCallContextFrom(ctx).ID == "call-1" {
 				return nil, errors.New("try again")
 			}
 			return "fine", nil
 		}))
@@
 	s.Len(results, 2, "each call is answered on its own")
+	s.Contains(results["call-1"]["error"], "try again")
+	s.NotContains(results["call-1"], "output")
+	s.Equal("fine", results["call-2"]["output"])
+	s.NotContains(results["call-2"], "error")
sdks/go/e2e_test.go: 1

502-508: Trivial · Maintainability & Code Quality · Quick win

Verify the edited configuration on the second sync.

The test sends the same instructions twice and checks only the returned ID after the second sync. Returning the original config without updating it would pass. Sync an agent with different instructions under the same name, then read back the config and assert the new instructions.

sdks/go/edge/edge_test.go: 1

34-62: Trivial · Maintainability & Code Quality · Low value

Add regression tests for edge calls and monitor links.

CreateCall is not tested, and monitor-link tests use only call-1. Regressions in call defaults, creator validation, request construction, or path escaping can pass. Test those behaviors, including call IDs containing / and spaces.

sdks/go/stream/router_test.go: 2

234-248: Trivial · Maintainability & Code Quality · Low value

Assert the text in the speech frame.

The server returns synthesis_complete for any speak frame, and the test checks only completion. A change that sends different text would still pass. Record the received frame and assert that its text is "hello there".


320-332: Trivial · Maintainability & Code Quality · Quick win

Assert the failed recording job.

The test discards the job, so it would pass if Recording returned nil, err on failure. Assert the job and its diagnostic to protect the current result-plus-error behavior.

Suggested fix

-	_, err := routerFor(backend).STT().Recording(t.Context(),
+	job, err := routerFor(backend).STT().Recording(t.Context(),
 		Recorded{URL: "https://example.test/call.mp3"}, nil)
 
 	if err == nil {
 		t.Fatal("a failed job was handed back as a transcript")
 	}
+	if job == nil {
+		t.Fatal("the failed job was not returned")
+	}
+	if job.Id != "recording-1" || job.Status != acceleration.RecordingStatusFailed ||
+		job.Error == nil || *job.Error != "no provider took the recording" {
+		t.Errorf("the failed job is %+v", job)
+	}
sdks/go/stream/routerconfig_test.go: 1

179-207: Trivial · Functional Correctness · Quick win

Cover Search and LLM preservation in both update tests.

Add non-nil Search and LLM options to both initial fixtures. Assert both fields after ConfigureSTT and ConfigureTTS.

Suggested test coverage

 func TestConfigureSTTCarriesTheOtherModalitiesForward(t *testing.T) {
 	server := newConfigured(t)
 	voice := "sonic"
+	llmProviders := []string{"gpt"}
+	searchProviders := []string{"search"}

 	if _, err := clientFor(t, server).DefineRouter(t.Context(), RouterOptions{
 		Name: "healthcare",
 		TTS:  &acceleration.TtsOptions{Voice: &voice},
+		LLM:  &acceleration.LlmOptions{Providers: &llmProviders},
+		Search: &acceleration.SearchOptions{Providers: &searchProviders},
 		STS:  routerSTSOptions(),
 	}); err != nil {
@@
 	if stored.Tts == nil || stored.Tts.Voice == nil || *stored.Tts.Voice != "sonic" {
 		t.Error("writing how a config hears should not drop how it speaks")
 	}
+	if stored.Llm == nil || stored.Llm.Providers == nil || len(*stored.Llm.Providers) != 1 ||
+		(*stored.Llm.Providers)[0] != "gpt" {
+		t.Errorf("writing how a config hears should not drop how it answers: %+v", stored.Llm)
+	}
+	if stored.Search == nil || stored.Search.Providers == nil || len(*stored.Search.Providers) != 1 ||
+		(*stored.Search.Providers)[0] != "search" {
+		t.Errorf("writing how a config hears should not drop how it searches: %+v", stored.Search)
+	}
 	assertRouterSTSOptions(t, stored.Sts)
@@
 func TestConfigureTTSCarriesTheOtherModalitiesForward(t *testing.T) {
 	server := newConfigured(t)
 	no := false
+	llmProviders := []string{"gpt"}
+	searchProviders := []string{"search"}

 	if _, err := clientFor(t, server).DefineRouter(t.Context(), RouterOptions{
 		Name: "healthcare",
 		STT:  &acceleration.SttOptions{Providers: &[]string{"deepgram"}},
+		LLM:  &acceleration.LlmOptions{Providers: &llmProviders},
+		Search: &acceleration.SearchOptions{Providers: &searchProviders},
 		STS:  routerSTSOptions(),
 	}); err != nil {
@@
 	if stored.Stt == nil || stored.Stt.Providers == nil {
 		t.Error("writing how a config speaks should not drop how it hears")
 	}
+	if stored.Llm == nil || stored.Llm.Providers == nil || len(*stored.Llm.Providers) != 1 ||
+		(*stored.Llm.Providers)[0] != "gpt" {
+		t.Errorf("writing how a config speaks should not drop how it answers: %+v", stored.Llm)
+	}
+	if stored.Search == nil || stored.Search.Providers == nil || len(*stored.Search.Providers) != 1 ||
+		(*stored.Search.Providers)[0] != "search" {
+		t.Errorf("writing how a config speaks should not drop how it searches: %+v", stored.Search)
+	}
 	assertRouterSTSOptions(t, stored.Sts)
Additional comments: 3

sdks/go/stream/dispatch.go: 2

301-303: Minor · Potential issue · Heavy lift

Unverified finding
We couldn't verify this from the code. It is shown for review, not as a verified issue.

A hosted tool's result is discarded after a failed tell; the worker only logs the failure and provides no delivery recovery. Tools intentionally continue across reconnects, so a successful invocation can finish while no socket exists and never deliver its answer.

The model receives a timeout or failure despite successful tool execution; retrying a side-effecting tool may repeat its effects.

This can occur when: A hosted tool completes during a brief router disconnection, before its remote response deadline expires.


417-419: Minor · Potential issue · Heavy lift

Unverified finding
We couldn't verify this from the code. It is shown for review, not as a verified issue.

The reconnect handshake snapshots handling before the new socket is installed. Work that finishes after that snapshot but before serve installs the socket can lose its done frame while remaining included in the advertised active count.

The replacement router can retain an occupied capacity slot after the worker becomes idle, preventing further work from reaching it.

This can occur when: A capacity-one worker reconnects while handling one call; the call finishes during the new connection handshake, while d.socket is nil.

sdks/go/agents/dispatch.go: 1

449-455: Minor · Potential issue · Quick win

Unverified finding
We couldn't verify this from the code. It is shown for review, not as a verified issue.

await treats an incomplete resumed event with no command receipt as a settled turn. If the missing receipt instead means its status is unknown or still active, the next queued question can start prematurely.

The worker may overlap answers or abandon the first turn.

This can occur when: A session reconnects while an answer is running and receives Resumed.Complete=false with Resumed.Command=nil.

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 0cdb2e1 and ed9c60d.

Files ignored due to path filters: 1

  • sdks/go/go.sum is excluded by !**/*.sum
Files selected for processing: 95

  • .gitattributes
  • .github/workflows/release-sdk-go.yml
  • .github/workflows/release.yml
  • .github/workflows/sdk-go.yml
  • CLAUDE.md
  • sdks/go/.sdk_update_log
  • sdks/go/README.md
  • sdks/go/acceleration/enums_test.go
  • sdks/go/acceleration/generated.go
  • sdks/go/agents/agent.go
  • sdks/go/agents/agent_test.go
  • sdks/go/agents/config.go
  • sdks/go/agents/dispatch.go
  • sdks/go/agents/dispatch_test.go
  • sdks/go/agents/folder.go
  • sdks/go/agents/folder_test.go
  • sdks/go/agents/guardrail.go
  • sdks/go/agents/guardrail_test.go
  • sdks/go/agents/harness.go
  • sdks/go/agents/phone.go
  • sdks/go/agents/phone_test.go
  • sdks/go/api/oapi-codegen.yaml
  • sdks/go/client/client.go
  • sdks/go/client/client_test.go
  • sdks/go/client/detection.go
  • sdks/go/client/guests.go
  • sdks/go/client/memories.go
  • sdks/go/client/region_test.go
  • sdks/go/client/responses.go
  • sdks/go/client/sessions.go
  • sdks/go/client/settings.go
  • sdks/go/client/usage.go
  • sdks/go/client/usage_test.go
  • sdks/go/client/users.go
  • sdks/go/doc.go
  • sdks/go/e2e_test.go
  • sdks/go/edge/edge.go
  • sdks/go/edge/edge_test.go
  • sdks/go/examples/caregiving/agents/caregiving/agent.yaml
  • sdks/go/examples/caregiving/agents/caregiving/guardrail.md
  • sdks/go/examples/caregiving/agents/caregiving/instructions.md
  • sdks/go/examples/caregiving/agents/caregiving/knowledge/faq.md
  • sdks/go/examples/caregiving/agents/caregiving/knowledge/urls.yaml
  • sdks/go/examples/caregiving/agents/caregiving/skills/booking.md
  • sdks/go/examples/caregiving/main.go
  • sdks/go/examples/dispatch/main.go
  • sdks/go/examples/quickstart/agents/myagent/agent.yaml
  • sdks/go/examples/quickstart/agents/myagent/guardrail.md
  • sdks/go/examples/quickstart/agents/myagent/instructions.md
  • sdks/go/examples/quickstart/agents/myagent/knowledge/policy.md
  • sdks/go/examples/quickstart/agents/myagent/skills/refund.md
  • sdks/go/examples/quickstart/main.go
  • sdks/go/examples/text/main.go
  • sdks/go/examples/voice/main.go
  • sdks/go/go.mod
  • sdks/go/readme_test.go
  • sdks/go/stream/accelerated.go
  • sdks/go/stream/accelerated_test.go
  • sdks/go/stream/backend.go
  • sdks/go/stream/backend_test.go
  • sdks/go/stream/client.go
  • sdks/go/stream/connectors.go
  • sdks/go/stream/connectors_test.go
  • sdks/go/stream/conversation.go
  • sdks/go/stream/dispatch.go
  • sdks/go/stream/dispatch_images_test.go
  • sdks/go/stream/dispatch_test.go
  • sdks/go/stream/errors.go
  • sdks/go/stream/errors_test.go
  • sdks/go/stream/hosted_test.go
  • sdks/go/stream/models.go
  • sdks/go/stream/phone.go
  • sdks/go/stream/phone_test.go
  • sdks/go/stream/recorded.go
  • sdks/go/stream/recorded_test.go
  • sdks/go/stream/resume_test.go
  • sdks/go/stream/router.go
  • sdks/go/stream/router_test.go
  • sdks/go/stream/routerconfig.go
  • sdks/go/stream/routerconfig_test.go
  • sdks/go/stream/socket.go
  • sdks/go/stream/sts.go
  • sdks/go/stream/tool_line.go
  • sdks/go/stream/tool_progress_test.go
  • sdks/go/stream/tools_test.go
  • sdks/go/tools/call.go
  • sdks/go/tools/options.go
  • sdks/go/tools/registry.go
  • sdks/go/tools/run.go
  • sdks/go/tools/run_test.go
  • sdks/go/tools/schema.go
  • sdks/go/tools/schema_tags_test.go
  • sdks/go/tools/tools_test.go
  • sdks/go/tools/validate.go
  • sdks/go/tools/validate_test.go
Files not fully reviewed because their changes were unavailable: 1

  • sdks/go/acceleration/generated.go

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

Comment thread .github/workflows/release.yml Resolved
Comment thread .github/workflows/sdk-go.yml Resolved
Comment thread sdks/go/edge/edge.go Resolved
Comment thread sdks/go/go.mod Resolved
Comment thread sdks/go/readme_test.go Resolved
Comment thread sdks/go/stream/recorded.go Resolved
Comment thread sdks/go/stream/recorded.go Resolved
Comment thread sdks/go/stream/routerconfig.go Resolved
Comment thread sdks/go/stream/routerconfig.go Resolved
Comment thread sdks/go/stream/routerconfig.go Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review continued from previous batch...

Comment thread sdks/go/agents/agent.go Resolved
Comment thread sdks/go/agents/config.go Resolved
Comment thread sdks/go/agents/folder.go Resolved
Comment thread sdks/go/client/client.go Resolved
Comment thread sdks/go/client/sessions.go Resolved
Comment thread sdks/go/stream/conversation.go Resolved
Comment thread sdks/go/stream/phone.go Resolved
Comment thread sdks/go/tools/registry.go Resolved
Comment thread sdks/go/tools/schema.go Resolved
Comment thread sdks/go/tools/schema.go Resolved

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Additional comments: 1

sdks/go/agents/folder.go: 1

422-426: Minor · Potential issue · Low value

Unverified finding
We couldn't verify this from the code. It is shown for review, not as a verified issue.

The custom floor-control decoder processes repeated keys without detecting duplicates, so later model or wait values silently replace earlier ones instead of rejecting an ambiguous declaration.

Loading may succeed and synchronization may install a 1200 ms wait despite the conflicting declaration, bypassing the duplicate-key protection normally expected from strict YAML decoding.

This can occur when: An agent declaration contains floor_control: {model: stream/chorus-asr, max_wait_ms: 800, max_wait_ms: 1200}.

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between ed9c60d and 266839f.

Files selected for processing: 8

  • sdks/go/.sdk_update_log
  • sdks/go/acceleration/generated.go
  • sdks/go/agents/agent_test.go
  • sdks/go/agents/config.go
  • sdks/go/agents/folder.go
  • sdks/go/agents/folder_test.go
  • sdks/go/client/campaigns.go
  • sdks/go/client/campaigns_test.go
Files not fully reviewed because their changes were unavailable: 1

  • sdks/go/acceleration/generated.go

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread sdks/go/agents/folder.go Resolved
@bokan
bokan marked this pull request as ready for review November 9, 2026 11:09
bokan added a commit that referenced this pull request Nov 9, 2026
## Why

When a session's socket is already gone, `stream.Pipeline.Leave` (and so
`Session.Close`) stops the session over HTTP. It ignored the answer: a
403 or 500 from the router still made `Leave` return nil, so the caller
believed the session had ended while it may still be running. CodeRabbit
found it on #1814; the code came from #1812.

## Changes

- `stopOverHTTP` returns nil only for 204 (stopped) and 404 (the router
no longer holds the session, which `stopSession` answers for one already
ended). Any other status is returned as the router's
`*stream.RouterError`, and a failed resolve or client is returned
instead of dropped.
- Test: the fake router's stop answers a chosen status; 404 leaves
cleanly, 403 and 500 come back as `RouterError` with that status. The
403 and 500 cases fail without the fix.
- CHANGELOG entry under Bug Fixes.

## Validation

`GOWORK=off go test -race ./...` in `sdks/go` passes.

Ships in `sdks/go/v0.1.1`, re-copied to `main` like #1814.
… refused stop); ci: run a PulseAudio server for the Python tests, as accelerate does (#1562)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

Minor · Forward team selection through the agent creation API. · generated.go:5794

sdks/go/acceleration/generated.go:5794
Minor · Data Integrity & Integration · Quick win

Forward team selection through the agent creation API.

When a backend opens a session for support through agents.Sessions.Create, it cannot set Team. With an unscoped token and an engineering config tag, the session counts against engineering instead. Add Team to agents.SessionOptions and stream.Call, and forward it into the creation request.

Nitpick comments: 1

sdks/go/stream/accelerated_test.go: 1

718-755: Trivial · Maintainability & Code Quality · Low value

Test non-JSON stop failures.

Add a proxy-style 502 or 503 response and a transport failure to the Leave tests. A regression that swallows either failure could pass the current stop tests.

Review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 266839f and 2bd1b95.

Files selected for processing: 11

  • .github/workflows/run_tests.yml
  • sdks/go/README.md
  • sdks/go/acceleration/generated.go
  • sdks/go/client/client_test.go
  • sdks/go/client/limits.go
  • sdks/go/client/limits_test.go
  • sdks/go/client/sessions.go
  • sdks/go/client/teams.go
  • sdks/go/client/usd.go
  • sdks/go/stream/accelerated.go
  • sdks/go/stream/accelerated_test.go
Files not fully reviewed because their changes were unavailable: 1

  • sdks/go/acceleration/generated.go

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread sdks/go/client/limits.go Resolved
Comment thread sdks/go/client/sessions.go Resolved
Comment thread sdks/go/client/usd.go Resolved
Comment thread sdks/go/README.md Resolved
Comment thread sdks/go/README.md Resolved
Comment thread sdks/go/stream/accelerated.go Resolved
@bokan
bokan merged commit 1efe0b8 into main Nov 9, 2026
11 checks passed
@bokan
bokan deleted the ai-1892-go-sdk-release branch November 9, 2026 14:08
Nash0x7E2 added a commit that referenced this pull request Nov 12, 2026
## Why

`release.yml` runs on every published GitHub release. On this branch
nothing checks the tag, so a release made for any tag (a router tag, an
SDK tag) would run the integration job and then build and publish the
Python packages to PyPI. `main` has had a guard for this since #1814;
this branch never got it. Found in the AI-3210 audit.

## How it works

```
GitHub release published
├─ tag starts with "v"  (v0.7.0)
│   └─ integration -> build-core -> publish to PyPI -> test-core-index
└─ any other tag  (accelerate-v0.6.333, sdk-dotnet-v0.2.0)
    └─ integration skipped, build-core skipped
        └─ test-core-index skipped (it needs build-core)
```

| Release tag | Before | After |
| --- | --- | --- |
| `v0.7.0` | builds and publishes Python | same |
| `accelerate-v0.6.333` | builds and publishes Python | runs nothing |
| `sdk-dotnet-v0.2.0` | builds and publishes Python | runs nothing |

## Changes

- `integration` and `build-core` in `.github/workflows/release.yml` each
get `if: startsWith(github.event.release.tag_name, 'v')`, the same two
lines `main` has.

## Validation

- Unverified in CI: a `release` workflow only runs when a release is
published, and none was made to test this.
- The two `if:` lines match `main`'s `release.yml` (lines 14 and 22),
which carries the same guard.
- `git log -S"startsWith(github.event.release.tag_name"` on this branch
finds no earlier commit, so the guard was never here and was not removed
on purpose.
- A release with a `v` tag on a commit of this branch would still
publish. This PR does not change that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants