Skip to content

[Feat] Add secure trial inference setup - #1710

Merged
mrubens merged 18 commits into
developfrom
feat/free-trial-inference
Aug 27, 2026
Merged

[Feat] Add secure trial inference setup#1710
mrubens merged 18 commits into
developfrom
feat/free-trial-inference

Conversation

@mrubens

@mrubens mrubens commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What

Free-trial inference for hosted deployments: hosting injects a Roomote-minted, spend-capped OpenRouter key, and the app exposes it as a first-class hidden Roomote inference provider (roomote/... model ids, a default 'Roomote Trial' preset on Luna) that new deployments can choose during setup — 'Use free Roomote trial inference' vs connecting their own provider. Enforcement of the budget is the key's own OpenRouter spend limit; the existing credit-balance readout becomes the burndown.

How

  • Roomote inference provider (roomote): hidden catalog entry backed by R_TRIAL_OPENROUTER_API_KEY, gateway upstream = OpenRouter, its own rebased model catalog and trial preset. Only hosting can enable it; it is never a user connection.
  • DB-stored key: the hosting-injected env variable is a delivery mechanism only. The first admin setup-status read imports it once into encrypted Settings storage (stamping trialInferenceKeyImportedAt in setup state so it is never re-imported), and every runtime read — inference gateway, credit balance, provider status, the wizard's trial offer — resolves the stored key alone. Deleting the Roomote inference provider disables the trial permanently, even while hosting keeps injecting the variable.
  • Setup flow: the inference step offers the trial choice when the stored key connects the provider and no operator provider exists; choosing it seeds the trial preset and Roomote model catalog as ordinary editable config inside a row-locked transaction that never overwrites existing choices.
  • Safety: the key name is in CONTROL_PLANE_ENV_VAR_NAMES (reserved from the generic env editor, stripped from job env) and the worker-env blocklist; the gateway path allowlist restricts it to inference endpoints; sandboxes never see it. A Roomote-model task on a deployment whose key is gone gets an actionable 'connect an inference provider' error.

Pairs with

RooCodeInc/Roomote-Cloud#212 (per-deployment key minting with a $5 cap, one grant per account).

Notes

  • Out-of-credit mid-task surfaces as a provider error; a friendlier launch gate is a follow-up.
  • Trial-branded burndown UI beyond the existing credits line is a follow-up.

…back key

A hosting provisioner can inject a capped, Roomote-minted OpenRouter key as
R_TRIAL_OPENROUTER_API_KEY. The key acts as the OpenRouter credential only
while no operator key is configured (runtime or saved), so connecting a real
key always wins. A fresh deployment holding the trial key and no inference
choices of its own is seeded with OpenRouter's new Efficient preset
(gpt-5.6-luna for every role) as ordinary editable config, so the setup
wizard skips the inference step, first tasks run on an inexpensive default,
and every model/provider control keeps working.

- Efficient preset on the OpenRouter catalog entry
- resolveModelProviderEnvValue gains a runtime-env-only trial fallback tier
- gateway coverage and control-plane key resolution honor the fallback, so
  the key stays on the control plane and never reaches a sandbox (also
  blocked from worker env outright)
- buildSetupModelStatus satisfies the provider through the fallback and
  flags trialKeySatisfied so Settings keeps the connect/edit affordances
- first-boot seeding in the setup status command, guarded to never
  overwrite operator choices
- docs: environment-variables entry
@roomote-community

roomote-community Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

No new changes since the prior review. See task

  • packages/types/src/model-provider-config.ts:1833 Trial key can be persisted through generic settings and reaches a sandbox.
  • apps/web/src/trpc/commands/setup-new/index.ts:324 Concurrent setup saves can be overwritten by trial seeding.
  • apps/web/src/trpc/commands/setup-new/index.ts:343 Non-coding runtime model overrides do not prevent trial seeding.
  • apps/web/src/trpc/commands/setup-new/index.test.ts:1363 New seed test depends on the host R_MODEL environment.
  • apps/web/src/components/settings/InferenceProviderSection.tsx:155 Trial-only Settings enables Save without a replacement key, but the server rejects that submission.
  • apps/web/src/components/settings/InferenceProviderSection.tsx:155 Trial-only OpenRouter rows offer Delete once another provider is connected, but the server rejects deletion because the key is not saved.
  • apps/web/src/components/settings/ProviderCreditBalanceLine.tsx:62 Settings client tests still expect removed credit-label and masked-key UI, so the focused suite fails.
  • apps/web/src/components/settings/InferenceProviderSection.tsx:152 Users cannot disable managed Roomote trial inference after selecting it.
  • apps/docs/models.mdx:32 Docs promise that Roomote inference can be deleted, but Settings and the server still forbid deletion.

Reviewed 86b3b8f

Comment thread packages/types/src/model-provider-config.ts Outdated
Comment thread apps/web/src/trpc/commands/setup-new/index.ts Outdated
Comment thread apps/web/src/trpc/commands/setup-new/index.test.ts
Comment thread apps/web/src/trpc/commands/setup-new/index.ts Outdated
…t hermeticity

- Reserve R_TRIAL_OPENROUTER_API_KEY (via TRIAL_MODEL_PROVIDER_ENV_VAR_FALLBACKS)
  in CONTROL_PLANE_ENV_VAR_NAMES so the generic environment editor rejects it
  and the job env-injection denylist strips a persisted copy from sandboxes
- Take a FOR UPDATE lock on the deployment_settings row (inserting the bare
  row first if missing) so trial seeding cannot overwrite a concurrent
  operator save
- Treat any role-model env override (R_SMALL_MODEL, R_PLANNING_MODEL, ...)
  as an operator model choice that blocks seeding, not just R_MODEL
- Make the seeding tests hermetic: stub all role-model and provider
  credential env vars so host shells with R_MODEL or provider keys set
  cannot change outcomes
…efaults

The preset previously pinned codeReview and planning to medium while the
shared role defaults are high. Leaving the efforts unset means the runtime
applies the same per-role defaults as a hand-configured model, and the
preset can never drift from them.
Comment thread apps/web/src/components/settings/InferenceProviderSection.tsx Outdated
The inference step no longer auto-skips on trial deployments. Instead of
seeding on the first setup-status read, the step now leads with a 'Start
with free credits' card (rendered when a provider is trial-key satisfied)
next to the existing connect-your-own-provider flow.

- new setupNew.chooseTrialInference mutation: applies the Efficient preset
  and records the provider choice inside the same FOR UPDATE-locked
  transaction; no-ops once any inference choice exists and refuses when a
  real provider is already connected
- getSetupNewStatusCommand no longer seeds; a fresh trial deployment has no
  model config, so the step renders naturally until a choice is made
- trial-satisfied providers no longer present as configured-via-env in the
  step, so picking OpenRouter from the list asks for a real key
- tests: command guards, trial card render/click, editable key field
The credentials dialog treated any runtime-satisfied key as an existing
primary credential, so on a trial-only deployment the unlocked Edit flow
enabled Save with an empty form that the server validator then rejected.
A trial fallback key is not an operator credential: the empty form stays
unsubmittable until a real key is typed.
@roomote-roomote

roomote-roomote Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Applied #1735's setup UI and restored both Back paths in 2d248f58. Focused setup/backend coverage and the full pnpm check gate pass. Refreshed browser proof was attempted, but the running web fixture served the pre-consolidation route and could not honestly capture the new Back state; no stale screenshots were added.

Comment thread apps/web/src/components/settings/InferenceProviderSection.tsx Outdated
@roomote-roomote roomote-roomote Bot changed the title [Feat] Free-trial inference via a provisioner-injected OpenRouter fallback key [Feat] Add secure trial inference setup Aug 27, 2026
Comment thread apps/web/src/components/settings/ProviderCreditBalanceLine.tsx
…ff env

The hosting-injected R_TRIAL_OPENROUTER_API_KEY becomes a delivery
mechanism only: the first admin setup-status read imports it once into
encrypted Settings storage (stamping trialInferenceKeyImportedAt so it is
never re-imported), and every runtime read — the inference gateway, credit
balance, provider status, the wizard's trial offer — resolves the stored
key alone. Deleting the Roomote inference provider therefore disables the
trial permanently, even while hosting keeps injecting the variable.

- resolveModelProviderEnvValue / resolveModelRuntimeEnv skip the runtime
  env tier for the Roomote inference key name
- buildSetupModelStatus never counts the env value for the roomote
  provider; connection = the stored row
- chooseSetupTrialInference requires the stored key and runs the import
  first; the wizard gates the trial card on savedApiKeySatisfied
- docs note that deleting the provider disables the trial permanently
Comment thread apps/docs/models.mdx
The docs promised deletion as the disable path, but the delete command
hard-rejected the roomote provider and the Settings row had no control.
Roomote inference now deletes like any stored-key provider: its credential
is the imported Settings row, the existing last-provider guard still
applies, and the import marker keeps the hosting-injected variable from
re-creating it. The managed row gains a delete control that names the
permanent consequence.
The balance test injected the key through the runtime env, which the
stored-key model deliberately ignores. It now seeds a real encrypted
Settings row (round-tripping actual encryption rather than the mock the
column type bypassed on insert) and proves the env-delivered value never
reaches the balance endpoint, plus a case for no-stored-key returning
null.
Review fixes for the free-trial inference feature:

- Count the hidden roomote row toward the last-provider delete guard only
  while the trial is actually in use, and mirror that in the Settings UI,
  so a merely-imported trial key can no longer unlock deleting the last
  real provider
- Include the GitHub Copilot subscription in the delete guard's status
  build so trial deletion works when Copilot is the only other provider
- Stop counting a leftover task-model-settings row as a prior inference
  choice, so choosing the trial seeds models instead of silently
  no-opping after the last provider was deleted
- Strip the settings-only trial key from control-plane helper OpenCode
  envs so deleting the provider disables the trial there too
- Re-import a rotated injected trial key while the stored key exists,
  and skip the write transaction and row lock on the common
  already-imported status-read path
- Cache the roomote gateway key with a 30s TTL to keep DB reads and
  decryption off the per-request hot path
- Centralize the settings-only env-var policy behind one predicate and
  the roomote/OpenRouter model-id aliasing behind shared helpers
- Filter hosting-managed providers by the catalog hidden flag instead of
  id checks, merge the duplicated credit-balance fetchers, skip the
  roomote body rewrite's JSON parse on the common path, and fix stale
  docs and the test mock's env precedence
@mrubens
mrubens merged commit 85b4c47 into develop Aug 27, 2026
18 checks passed
@mrubens
mrubens deleted the feat/free-trial-inference branch August 27, 2026 19:38
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.

3 participants