Skip to content

feat(models): add OpenCode Go provider with live model roster sync - #879

Open
Gadgitmatic wants to merge 2 commits into
truefoundry:mainfrom
Gadgitmatic:contrib/opencode-go-models
Open

Gadgitmatic wants to merge 2 commits into
truefoundry:mainfrom
Gadgitmatic:contrib/opencode-go-models

Conversation

@Gadgitmatic

@Gadgitmatic Gadgitmatic commented Sep 26, 2026 •

Copy link
Copy Markdown

Adds OpenCode Zen Go as a well-known OpenAI-compatible model provider, and keeps its model roster in sync with the provider instead of a static preset list.

What

  • opencode-go provider type: catalog presets, an adapter for its chat-completions models, and the per-session x-opencode-session / user-agent headers its gateway expects on model calls.
  • On every provider save (POST/PUT), the harness fetches GET {base_url}/models and unions the discovered ids with the configured models, so model pickers show the provider's full current roster. Discovery is best-effort: a 5s timeout, and any failure keeps the saved manifest unchanged.

Notes

  • Generated OpenAPI document and SDKs are intentionally not included; the SDK regeneration job adds them.
  • Includes @truefoundry/trueforge and @truefoundry/trueforge-core changesets.

Testing

  • pnpm --filter @truefoundry/trueforge typecheck
  • pnpm --filter @truefoundry/trueforge test (581 passed)
  • New unit tests cover roster expansion, discovery against the stored key on redacted keep, and HTTP/network failure fallbacks.

Note

Medium Risk
Introduces outbound model discovery on admin saves (after existing SSRF URL checks) and new runtime LLM headers for a provider type; failures are best-effort but mis-synced rosters could confuse model pickers.

Overview
Adds OpenCode Zen Go (opencode-go) as a well-known model provider: catalog seed presets, schema/default base URL, and routing through the shared OpenAI-compatible LLM adapter (chat-completions models only).

On every model-provider create/update, opencode-go manifests are refreshed from GET {base_url}/models (5s timeout, Bearer auth). Discovered model ids are unioned with existing entries so presets and already-configured models are kept; failures are logged and the save still succeeds with the incoming roster. PUT with a redacted API key uses the stored key for discovery.

During turns, OpenCode Go calls get x-opencode-session (TrueForge session id) and user-agent: TrueForge/{version} in addition to existing turn headers.

Unit tests cover roster expansion, redacted-key discovery, and HTTP/network failure fallbacks; preset PUT tests stub fetch so other catalog types stay offline.

Reviewed by Cursor Bugbot for commit 8518b1b. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8518b1b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@truefoundry/trueforge Patch
@truefoundry/trueforge-core Patch

Not sure what this means? Click here to learn what changesets are.

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

Gadgitmatic added 2 commits September 26, 2026 16:08
Wire OpenCode Zen Go as a well-known OpenAI-compatible provider: catalog presets, an adapter for its chat-completions models, and the per-session x-opencode-session header its gateway expects.
OpenCode Go publishes its live roster at GET {base_url}/models. Refresh it whenever the provider is saved so model pickers track the provider instead of the shipped presets: discovered ids are unioned with the configured models and discovery is best-effort (failures keep the stored manifest).

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

added.push({ model_id: id, name: name.data, properties: {} });
}
return added.length === 0 ? manifest : { ...manifest, models: [...manifest.models, ...added] };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Roster sync allows duplicate names

Medium Severity

expandOpenCodeGoModels only skips duplicate model_ids and never checks that a slugified name is unused. slugifyModelId folds punctuation, case, and letter-digit boundaries, so distinct upstream ids can share one NameSchema name. The saved manifest then violates unique-name rules, GET /models emits duplicate FQNs, and a later settings PUT fails validation on that stored list.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c34e5a1. Configure here.

@Gadgitmatic
Gadgitmatic force-pushed the contrib/opencode-go-models branch from c34e5a1 to 8518b1b Compare September 26, 2026 20:19

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8518b1b. Configure here.

...(apiKey === undefined ? {} : { authorization: `Bearer ${apiKey}` }),
},
signal: AbortSignal.timeout(MODEL_DISCOVERY_TIMEOUT_MS),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Discovery fetch bypasses SSRF guard

High Severity

Roster sync calls native fetch after only assertSafeOutboundUrl. That check does not cover redirects or DNS rebinding, which ssrfFetch exists to stop. Because well-known providers allow a base_url override, a settings save can make the server follow a public URL to an internal hop, including with the Authorization bearer.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8518b1b. Configure here.

This branch has not been deployed

No deployments
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