Skip to content

fix(sms-bridge): use documented v1.18.23 message contract - #40

Merged
xnoto merged 1 commit into
mainfrom
fix/sms-bridge-opencode-v1-message-contract
Sep 5, 2026
Merged

fix(sms-bridge): use documented v1.18.23 message contract#40
xnoto merged 1 commit into
mainfrom
fix/sms-bridge-opencode-v1-message-contract

Conversation

@xnoto

@xnoto xnoto commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

OpenCode v1.18.23 — the deployed upstream, tag commit
ef2880f379129aa048be9e9353e30aa168d42c17 — does not document
POST /session/:id/prompt. The documented surface (verified in
packages/web/src/content/docs/server.mdx, the route group
packages/opencode/src/server/routes/instance/httpapi/groups/session.ts
where SessionPaths.prompt = "/session/:sessionID/message", and
PromptInput in packages/opencode/src/session/prompt.ts) is:

  • POST /session/:id/message — blocking send; body
    { messageID?, model?, agent?, noReply?, system?, tools?, parts };
    returns { info: Message, parts: Part[] }
  • POST /session/:id/prompt_async — async, returns 204 No Content
  • POST /session — create; documented body { parentID?, title? }
    only; returns Session

Changes, confined to opencode-sms-bridge/server.py and
opencode-sms-bridge/test_server.py:

  • Session creation now sends only documented fields: an empty body.
    The previously sent undocumented agent field is removed, and the
    session id is read from the directly returned Session object.
  • The routing agent for the channel is passed on the blocking message
    request as the documented top-level agent body field.
  • The message request body carries parts containing text — inbound
    text parts are joined into a single {"type": "text", "text": ...}
    part, preserving existing join semantics and the empty-text guard.
  • The response is parsed strictly from the documented top-level
    { info, parts } shape: info must be an object, parts a list;
    only text parts contribute to the reply; any other shape (including
    legacy data-wrapped shapes) fails safely.
  • The PII-safe bounded static error-code taxonomy is unchanged
    (ok, opencode-response-invalid, opencode-input-invalid,
    twilio-send-failed, opencode-request-failed:<operation>:<category>
    with operations session-create / prompt), and no raw response
    data, URLs, sessions, agents, or provider detail are logged or
    persisted.
  • Tests prove: exact request method/path/body for both the
    session-create and blocking message requests (opener-level, real
    request objects); agent placement on the message request and its
    absence from session create; no /prompt, /wait, or prompt_async
    routes are ever requested; single-request blocking behavior; strict
    { info, parts } extraction with non-text parts ignored and invalid
    shapes (lists, strings, empty, data-wrapped, wrong types,
    text-less) rejected with opencode-response-invalid; and the
    unchanged bounded taxonomy (no wait, message-list,
    prompt-async, or message operation codes).

Fixes #

None — owner-authorized evidence-based repair to the documented v1
message contract; no driving issue supplied.

Note: this repository has no pull-request template in .github/, the
root, or docs/; the makeitworkcloud/.github organization default
template is applied manually here.

Type of change

  • Bug fix
  • Feature / enhancement
  • Documentation
  • Infrastructure (OpenTofu root or module)
  • GitOps desired state (manifests, kustomize, charts, SOPS/KSOPS secrets)
  • Container image
  • CI / reusable workflow
  • Refactor / cleanup
  • Breaking change

Validation

  • Required pull-request checks pass — buildah run
    33974511866

    (jobs
    checks
    /
    detect
    /
    build)
    passed pre-commit (including Gitleaks), changed-image detection
    (opencode-sms-bridge), and the non-publishing bridge image build. The
    build's Containerfile step RUN python3 -m unittest discover -s /app -p "test_*.py" -v ran all 21 tests (Ran 21 tests in 0.105s — OK),
    including the exact-route/method/body flow test, the
    documented-fields-only session-create test, the blocking-message
    request test, the strict {info,parts} extraction and
    invalid-shape rejection tests, and the bounded-taxonomy test.
    Push to registry was correctly skipped in PR mode.
  • Generated or centrally distributed files were regenerated by their owning automation, not hand-edited — none changed; only opencode-sms-bridge/server.py and opencode-sms-bridge/test_server.py were edited.

No local tests, container builds, OpenTofu, SOPS, state, or live-system
operations were run or claimed. CI is the validation authority.

Impact and rollout

Producer changed: makeitworkcloud/images/opencode-sms-bridge is
the canonical bridge-image source. An approved merge automatically
publishes a new immutable GHCR image tag. This PR neither publishes nor
deploys anything itself.

Consumer unchanged: kustomize-cluster continues to select its
currently pinned image; this PR makes no GitOps or image-selection
change. After an approved merge and publication, a separate reviewed
GitOps PR and explicit confirmation are required before image selection,
Argo reconciliation, health verification, and a new approved-source SMS
test.

Behavior boundary: a text job now issues POST /api/session with
an empty body on first contact (agent no longer sent there) and
POST /api/session/{id}/message with the routing agent and a text part,
instead of the undocumented POST /api/session/{id}/prompt with a
{"prompt": {"text": ...}} body. Persisted detail codes are unchanged
in literal value: request failures still compose as
opencode-request-failed:<session-create|prompt>:<category> and
unusable results still fail with opencode-response-invalid. Responses
wrapped in a legacy data envelope are now rejected instead of
tolerated.

Rollback: if a later selected image regresses, use the canonical
GitOps workflow to select a reviewed immutable image tag. Do not
overwrite image tags or alter live state manually.

Safety and secrets

  • Contains no plaintext secrets, decrypted SOPS values, state files, kubeconfigs, tokens, or private endpoints.
  • No local OpenTofu init/plan/apply/destroy/import/state operations were run or claimed — plans come from pull-request checks.
  • Breaking or irreversible effects are described above with rollback notes.

AI-assisted change: an OpenCode agent materially produced this change;
reviewers should verify the cited v1.18.23 upstream evidence (blocking
/session/:id/message body and { info, parts } response,
prompt_async as the only async variant, /prompt undocumented), the
strict response parsing and its invalid-shape rejection, agent
placement, the unchanged bounded taxonomy, and CI results before merge.

OpenCode v1.18.23 (tag ef2880f379129aa048be9e9353e30aa168d42c17)
does not document POST /session/:id/prompt: the documented blocking
send is POST /session/:id/message with body
{ messageID?, model?, agent?, noReply?, system?, tools?, parts }
returning { info, parts }, while prompt_async is the 204 async
variant. The route group confirms session.prompt is mounted at
/session/:sessionID/message.

Replace the unsupported flow: session creation now sends only the
documented create fields (empty body; agent removed), the routing
agent moves onto the blocking message request, the request carries
a text part, and the response is parsed strictly from the top-level
{ info, parts } shape. Bounded PII-safe static error codes and the
no-raw-logging posture are unchanged.

Tests prove exact request method/path/body for both requests,
agent placement, absence of /prompt, /wait, and prompt_async routes,
single-part text joining, strict response parsing with invalid
shapes rejected, and the unchanged bounded taxonomy.
@xnoto
xnoto requested a review from a team as a code owner September 5, 2026 15:20
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

Image CI passed

Pre-commit validation passed. View the workflow run.

@xnoto
xnoto merged commit 6b48dde into main Sep 5, 2026
4 checks passed
@xnoto
xnoto deleted the fix/sms-bridge-opencode-v1-message-contract branch September 5, 2026 15:27
xnoto added a commit to makeitworkcloud/kustomize-cluster that referenced this pull request Sep 5, 2026
## Summary

Select
`ghcr.io/makeitworkcloud/opencode-sms-bridge:6b48ddeaa5897a3d07dc28aa929a941219ec8993`,
the immutable image published by the merged [images PR
#40](makeitworkcloud/images#40). It replaces the
currently selected `008a074f…` image, whose unsupported `/prompt`
contract produced `opencode-response-invalid` in the approved-source
runtime test.

**Verified producer evidence:** the producer's post-merge build-and-push
[run
33974862106](https://github.com/makeitworkcloud/images/actions/runs/33974862106)
passed and pushed the selected immutable tag (digest
`sha256:a54775a5216a5307064e9e221b866f5dfcf1054e2470c2402d3601a290bc2343`).

The selected repair uses OpenCode v1.18.23's documented blocking `POST
/session/:id/message` contract, creates sessions with documented fields
only, and sends the routing agent plus text part in the message request.
This PR changes only the immutable image pin in the
`opencode-sms-bridge` Application.

Fixes #

None — owner-authorized remediation rollout; no driving issue supplied.

## Type of change

- [x] Bug fix
- [ ] Feature / enhancement
- [ ] Documentation
- [ ] Infrastructure (OpenTofu root or module)
- [x] GitOps desired state (manifests, kustomize, charts, SOPS/KSOPS
secrets)
- [ ] Container image
- [ ] CI / reusable workflow
- [ ] Refactor / cleanup
- [ ] Breaking change

## Validation

- [ ] Required pull-request checks pass — pending. The only validation
authority is this repository's PR CI.
- [x] Generated or centrally distributed files were regenerated by their
owning automation, not hand-edited — none apply; this is a one-line
image-pin change.

No local OpenTofu, SOPS, container build, cluster, or live-system
operation was run or claimed.

## Impact and rollout

**Producer:** `makeitworkcloud/images` merged
`6b48ddeaa5897a3d07dc28aa929a941219ec8993` and published the selected
immutable bridge image. The producer build is complete.

**Consumer:** `workloads/apps/opencode-sms-bridge-app.yaml` in this
repository is the canonical GitOps image-selection point for the
`opencode-sms-bridge` Application. This PR changes no chart version,
configuration, Secret, Service, route, or Argo sync policy.

**Delivery stages:** authored and producer-published; GitOps selection
is proposed by this PR; PR validation is pending; merge, root-sync
submission, reconciliation, health, and functional SMS verification
remain incomplete. A merge triggers `test` on `main`; only after success
does the automatic `sync.yml` request root reconciliation. It does not
prove root/child health. After an explicitly authorized merge, verify
`gitops-workloads`, the child Application, the selected Pod image,
resource health, and one approved-source text-only SMS exchange
separately.

**Rollback:** use a reviewed GitOps PR to restore the previously
selected immutable tag `008a074f5d0bc75f0120c8b6b2d2ca468d4e2133`; do
not retag images or patch live resources.

## Safety and secrets

- [x] Contains no plaintext secrets, decrypted SOPS values, state files,
kubeconfigs, tokens, or private endpoints
- [x] No local OpenTofu init/plan/apply/destroy/import/state operations
were run or claimed — plans come from pull-request checks
- [x] Breaking or irreversible effects are described above with rollback
notes

AI-assisted change: an OpenCode agent materially produced this change;
review the producer tag and CI, the one-line diff, and the automatic
post-merge reconciliation behavior before merge.
xnoto added a commit that referenced this pull request Sep 5, 2026
PR #40 kept the documented v1 body and strict direct {info,parts}
parsing but left the requests on the legacy V2 /api surface. Upstream
v1.18.23 (ef2880f379129aa048be9e9353e30aa168d42c17) mounts the session
HttpApi at root "/session": create = POST /session (empty body, direct
Session.Info) and blocking prompt = POST /session/:sessionID/message
(direct streamed SessionV1.WithParts). /api/session* is the distinct
legacy V2 API (data-wrapped prompt admission, GET message listing, no
/wait), so the strict direct-shape parser failed the live
approved-source test with opencode-response-invalid.

Switch only the route prefix in opencode-sms-bridge/server.py:
POST /session with the documented empty body and
POST /session/{id}/message with message-level agent + text parts.
Response parsing, request bodies, and the bounded static error-code
taxonomy are unchanged. Tests pin the exact /session prefix, methods,
and bodies at the opener level and reject every /api route, /prompt,
/wait, and prompt_async.
xnoto added a commit that referenced this pull request Sep 5, 2026
## Summary

The live approved-source SMS test against image `6b48dde` reached
`inbound_queued`/`job_claimed` and then failed
`opencode-response-invalid`. Root cause: PR #40 adopted the documented
v1 body and strict direct `{info,parts}` parsing but left the requests
on the legacy V2 `/api` surface — an exact prefix/API mismatch.

Authoritative upstream evidence at `anomalyco/opencode` v1.18.23 commit
`ef2880f379129aa048be9e9353e30aa168d42c17`:

-
`packages/opencode/src/server/routes/instance/httpapi/groups/session.ts`:
  `const root = "/session"`; `SessionPaths.create = "/session"`,
  `SessionPaths.prompt = "/session/:sessionID/message"`.
-
`packages/opencode/src/server/routes/instance/httpapi/handlers/session.ts`:
  `create` returns the direct `Session.Info` (empty body accepted);
`prompt` streams the direct `SessionV1.WithParts` `{info,parts}` JSON —
  neither is data-wrapped.
- `packages/opencode/test/server/httpapi-session.test.ts`: proves the
legacy V2 `/api/session/:id/prompt` admission response is data-wrapped,
  GET `/api/session/:id/message` is the V2 message listing, and
  `/api/session/:id/wait` is unavailable — `/api` and `/session` are
  distinct APIs, so the strict direct-shape parser correctly rejected
  the V2 response.

Changes, confined to `opencode-sms-bridge/server.py` and
`opencode-sms-bridge/test_server.py`:

- Session creation: `POST /session` (was `POST /api/session`) with the
  documented empty body; id still read from the direct `Session.Info`.
- Blocking send: `POST /session/{id}/message` (was
  `/api/session/{id}/message`) with message-level `agent` + single text
  part; response still parsed strictly as direct `{info,parts}`.
- PII-safe bounded static error-code taxonomy unchanged.
- Tests pin the exact `/session` prefix/method/body at the opener level
  with real request objects and reject every `/api` route, `/prompt`,
  `/wait`, and `prompt_async`.

Fixes #

None — owner-authorized evidence-based repair to the v1 route prefix;
no driving issue supplied.

Note: this repository has no pull-request template in `.github/`, the
root, or `docs/`; the `makeitworkcloud/.github` organization default
template is applied manually here.

## Type of change

- [x] Bug fix
- [ ] Feature / enhancement
- [ ] Documentation
- [ ] Infrastructure (OpenTofu root or module)
- [ ] GitOps desired state (manifests, kustomize, charts, SOPS/KSOPS
secrets)
- [x] Container image
- [ ] CI / reusable workflow
- [ ] Refactor / cleanup
- [ ] Breaking change

## Validation

- [x] Required pull-request checks pass — buildah run

[33975811487](https://github.com/makeitworkcloud/images/actions/runs/33975811487)
(jobs
[checks](https://github.com/makeitworkcloud/images/actions/runs/33975811487/job/101332181039)
/
[detect](https://github.com/makeitworkcloud/images/actions/runs/33975811487/job/101332180972)
/ [build &
push](https://github.com/makeitworkcloud/images/actions/runs/33975811487/job/101332218007))
  passed pre-commit (including Gitleaks), changed-image detection
  (`opencode-sms-bridge`), and the non-publishing bridge image build.
  The build's Containerfile step `RUN python3 -m unittest discover -s
  /app -p "test_*.py" -v` ran all 21 tests (`Ran 21 tests in 0.100s —
  OK`), including the exact `/session` route/method/body flow test, the
  documented-fields-only session-create test, the single blocking
  message-request test, the `/api`-route rejection assertions, and the
  strict `{info,parts}` extraction and invalid-shape rejection tests.
  `Push to registry` was correctly skipped in PR mode.
- [x] Generated or centrally distributed files were regenerated by their
  owning automation, not hand-edited — none changed; only
`opencode-sms-bridge/server.py` and `opencode-sms-bridge/test_server.py`
  were edited.

No local tests, container builds, OpenTofu, SOPS, state, or live-system
operations were run or claimed. CI is the validation authority.

## Impact and rollout

**Producer changed:** `makeitworkcloud/images/opencode-sms-bridge` is
the
canonical bridge-image source. An approved merge automatically publishes
a new immutable GHCR image tag; this PR neither publishes nor deploys
anything itself.

**Consumer unchanged:** `kustomize-cluster` continues to select its
currently pinned image; this PR makes no GitOps or image-selection
change. After an approved merge and publication, a separate reviewed
GitOps PR and explicit confirmation are required before image selection,
Argo reconciliation, health verification, and a new approved-source SMS
test.

**Behavior boundary:** a text job now issues `POST /session` with an
empty body and `POST /session/{id}/message` with the routing agent and a
text part, instead of the same documented bodies on the legacy V2
`/api/session...` prefix. Persisted detail codes are unchanged in
literal value
(`opencode-request-failed:<session-create|prompt>:<category>`,
`opencode-response-invalid`, etc.). Direct V1 shapes parse as before;
legacy `data`-wrapped envelopes remain rejected.

**Rollback:** if a later selected image regresses, use the canonical
GitOps workflow to select a reviewed immutable image tag. Do not
overwrite image tags or alter live state manually.

## Safety and secrets

- [x] Contains no plaintext secrets, decrypted SOPS values, state files,
kubeconfigs, tokens, or private endpoints.
- [x] No local OpenTofu init/plan/apply/destroy/import/state operations
were run or claimed — plans come from pull-request checks.
- [x] Breaking or irreversible effects are described above with rollback
notes.

AI-assisted change: an OpenCode agent materially produced this change;
reviewers should verify the cited v1.18.23 upstream evidence (the
`/session` root with `create = /session` and `prompt =
/session/:sessionID/message`, direct
`Session.Info`/`SessionV1.WithParts`
responses, and the distinct legacy V2 `/api` surface), the strict
response parsing and its invalid-shape rejection, the unchanged bounded
taxonomy, and CI results before merge.
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