Skip to content

file transfer (go): parse images/files, wire directive sink to Rust parity - #345

Merged
brentrager merged 1 commit into
mainfrom
ft-go
Aug 11, 2026
Merged

file transfer (go): parse images/files, wire directive sink to Rust parity#345
brentrager merged 1 commit into
mainfrom
ft-go

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

The spec file-transfer contract (#342) added send_message.files[] and the send_file directive convention on eventual_response.directive, but the Go server implemented none of the behavior — it parsed message only and never set directive. This brings Go to parity with Rust (which already handles images + directive from #236).

Solution

  • Regen types (go/protocol/types_gen.go) from the protocol: file-transfer contract (send_message.files[] + send_file directive) #342 spec via scripts/generate-go.sh: adds Files (and the previously-unregenerated Skill field from th-b30a6a: send_message grows an optional skill — the engine resolves it, not the client #338). Generated file, not hand-edited.
  • Parse images[] + files[] fail-soft in handleSendMessage. Captured as json.RawMessage and unmarshalled separately so a malformed array is dropped wholesale rather than failing the whole-frame parse — matching Rust's from_value(...).ok().unwrap_or_default() (Go's Unmarshal partially populates a slice on a bad element, so the partial result is discarded on error).
  • TurnContext (go/server/turn_context.go) — the Go analog of the Rust ToolProviderContext file-transfer fields. Carries the turn's Images + Files and a directive sink, attached to the turn's context.Context (withTurnContext). The engine dispatches every tool with that ctx (SmoothAgent.dispatchToolTool.Execute), so a host tool reads it via TurnContextFrom(ctx). Directive is last-write-wins and concurrency-safe under ParallelToolCalls.
  • Drain the directive sink after the turn onto eventual_response.directive (double-nested data.data.directive), omitted when no tool wrote one (back-compat) — mirroring the Rust runner.rs drain + protocol.rs emit.

Known gap — images not attached to the model (documented in turn_context.go)

Spec step "attach images → model turn" is blocked on an external core release, not implemented here. The pinned Go engine core (smooth-operator-core/go, a separate repo pinned by pseudo-version) has ChatMessage.Content string with no multimodal content parts and no AgentOptions user-images option, and openai.go serializes content as a plain string. Unlike Rust's core (ImageContent + with_user_images), that support simply does not exist in the Go core and cannot be added from this repo. Attaching images to the model requires a smooth-operator-core/go release adding multimodal ChatMessage content + AgentOptions.UserImages + openai.go serialization, then a pin bump. Until then images are surfaced to host tools only (which is itself a spec-required surface — the Rust ToolProviderContext.images field). Files were never model-bound by design.

Tests

go/server/turn_context_test.go: images/files reach a tool via TurnContext; a tool's directive lands on eventual_response; directive omitted when none written; fail-soft on malformed images/files (turn still completes, attachments dropped); directive last-write-wins.

Verification

Both modules (go, go/server) — the go.yml PR lane:

  • gofmt -l . clean
  • go vet ./... clean
  • go build ./... OK
  • go test ./... -race251 passed, no data races

Changeset

None — consistent with prior Go-server PRs (e.g. #324 tool-hook seam), which shipped without one. Changesets in this repo target the JS-published packages; this is a Go-only change (behavior + regenerated Go types), no spec or TS change.

🤖 Generated with Claude Code

…arity

Bring the Go smooth-operator server to parity with Rust on the file-transfer
contract (spec #342): send_message.images[] / files[] attachments and the
send_file directive convention on eventual_response.directive.

- Regen go/protocol/types_gen.go from the #342 spec: adds Files (and the
  previously-unregenerated Skill field). Generated file, not hand-edited.
- Parse images[] + files[] fail-soft in handleSendMessage (a malformed array is
  dropped wholesale, never rejecting the turn — matching Rust's
  from_value(...).ok().unwrap_or_default()).
- New TurnContext (go/server/turn_context.go): the Go analog of the Rust
  ToolProviderContext file-transfer fields. Carries the turn's images + files
  and a directive sink, attached to the turn's context.Context so a host tool
  reads it via TurnContextFrom(ctx) (the engine dispatches every tool with that
  ctx). Directive is last-write-wins, concurrency-safe under ParallelToolCalls.
- Drain the directive sink after the turn onto eventual_response.directive
  (omitted when no tool wrote one — back-compat), mirroring the Rust drain.
- Tests: images/files reach a tool, a tool's directive lands on
  eventual_response, absent-directive omission, fail-soft on malformed
  images/files, directive last-write-wins.

Known gap (documented in turn_context.go): images are NOT attached to the model
turn as OpenAI image_url content parts. The pinned Go engine core
(smooth-operator-core/go) ChatMessage.Content is a plain string with no
multimodal content and no AgentOptions user-images option; unlike Rust's core
(ImageContent + with_user_images), that support does not exist in the Go core,
which is an external, pinned dependency from a separate repo. Attaching images
to the model requires a smooth-operator-core/go release adding multimodal
ChatMessage content + AgentOptions.UserImages + openai.go serialization, then a
pin bump — out of scope for this single-repo PR. Until then images are surfaced
to host tools only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: adaf827

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@brentrager
brentrager merged commit 508dd76 into main Aug 11, 2026
2 checks passed
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