Skip to content

AppBuilder - Make entire project card clickable - #2

Merged
eshurakov merged 1 commit into
mainfrom
eshurakov/app-builder-make-project-card-tappable
Feb 4, 2026
Merged

eshurakov merged 1 commit into
mainfrom
eshurakov/app-builder-make-project-card-tappable

Conversation

@eshurakov

Copy link
Copy Markdown
Contributor

Summary

  • Wrap the entire ProjectCard component in a Link so the whole card area is tappable, not just the title and timestamp
  • Add preventDefault to the delete button to maintain its independent click behavior

@eshurakov
eshurakov merged commit aa11688 into main Feb 4, 2026
10 of 11 checks passed
@eshurakov
eshurakov deleted the eshurakov/app-builder-make-project-card-tappable branch February 4, 2026 10:22
jrf0110 added a commit that referenced this pull request Apr 3, 2026
…review fixes

- Fix #1: Add WHERE clause to updateConfig (config.ts accepts wastelandId param)
- Fix #2: Update router.ts and ownership.ts imports from stub to Wasteland.do
- Fix #3: initializeWasteland/storeCredential/updateConfig now return records
- Fix #4: Implement all 6 missing RPC methods (updateMember, connectTown,
  disconnectTown, listConnectedTowns, getWantedBoard, refreshWantedBoard)
- Fix #5: Synchronous initializeDatabase(), no ensureInitialized/initPromise
- Fix #7: Delete WastelandDO.stub.ts

New files:
- 5 table definitions under db/tables/ (config, members, credentials,
  connected-towns, wanted-cache)
- 5 sub-modules under dos/wasteland/ with business logic
- Wasteland.do.ts: thin DO class delegating to sub-modules
chrarnoldus added a commit that referenced this pull request Apr 9, 2026
chrarnoldus added a commit that referenced this pull request Apr 9, 2026
…ries" (#2222)

Revert "feat(usage): prioritize auto_model from metadata in usage queries (#2…"

This reverts commit 78c1fad.
markijbema pushed a commit that referenced this pull request May 19, 2026
…llection

Review fix #2. Direct experiment upstreams bypass the
gateway-pinned `data_collection` setting — that setting only
applies when the request is forwarded through OpenRouter or Vercel
with a pinned provider config. For experimented public ids the
upstream is selected by the variant version, so the org's
preference would be silently ignored while we still capture
prompts to R2 for partner evaluation.

When an org sets `data_collection: 'deny'` and the request
resolves to an experiment, return the existing
`dataCollectionRequiredResponse()` instead of routing. The
existing model deny list still runs first via
`checkOrganizationModelRestrictions`. Anonymous users and
non-org authenticated users have no settings and continue to
route normally.
markijbema pushed a commit that referenced this pull request May 19, 2026
Follow-up to review fix #2. The previous commit only refused
experiment routing on the org-level setting
(`settings.data_collection === 'deny'`). A caller can also opt a
single request out of training/data-retention via OpenRouter's
request-body fields `provider.data_collection: 'deny'` or
`provider.zdr: true`. Direct experiment upstreams ignore those
fields entirely (we don't go through OpenRouter), but the
prompt-capture path was still uploading to R2 for partner
evaluation, violating the caller's stated intent.

Refuses experiment routing with `dataCollectionRequiredResponse()`
when `!isFreePromptTrainingAllowed(body.provider)` regardless of
org/anon/BYOK status. The check runs unconditionally right after
`getProvider` returns so it covers anonymous and non-org
authenticated users too.

Adds a unit test for `isFreePromptTrainingAllowed` covering all
opt-out shapes (`data_collection: 'deny'`, `zdr: true`,
`zdr: false`, both, neither, undefined) so a regression cannot
silently allow capture for users who explicitly opted out at the
request scope. Also tightens the `GatewayRequest` cast in
`persist.test.ts` per the test-file as-cast convention.
markijbema pushed a commit that referenced this pull request May 20, 2026
…hrough capture

Adapts the gateway path to the simpler model_experiment_request schema
landed in #3358. Addresses comments #2 + #3 on #3325.

- ExperimentPromptCapture: remove `systemPromptContent`, add
  `requestKind`. The full post-`transformRequest` body is now stored as
  a single content-addressed blob; the upstream API shape is recorded
  alongside it for downstream reporting.
- buildExperimentPromptCapture: collapse the chat_completions-only
  system-extraction branch; one body capture path for all kinds.
- persistExperimentAttribution: one R2 PUT instead of two, one hash
  written, no `__absent__` sentinel. `PROMPT_HASH_ABSENT` removed;
  `PROMPT_HASH_FAILED` and `PROMPT_HASH_DELETED` retained.
- Tests: drop the system-extraction cases (no longer applicable), add
  coverage for the three request kinds and a determinism check (content
  addressing relies on byte-stable serialization).

The dedup-on-shared-system-prompt micro-optimization had no measured
benefit. If we ever want it back, content-addressing the full body is
the better starting point anyway.
markijbema added a commit that referenced this pull request May 25, 2026
* feat(gateway): capture x-kilo-request and x-kilo-session headers

Phase 2 of the model-experiments rollout. Captures the per-message
client request id (used as the join key for PostHog feedback and for
`model_experiment_request.client_request_id` on experimented traffic)
and accepts `x-kilo-session` as a fallback session id when
`x-kilocode-taskid` is absent.

Also extends MicrodollarUsageContext with the experiment routing
fields (`modelExperimentVariantVersionId`,
`modelExperimentAllocationSubject`, `experimentPromptCapture`,
`clientRequestId`) so later phases can plumb them through without
touching every usage-context construction site.

* refactor(gateway): extract buildDirectProvider used by custom_llm path

Phase 3a of the model-experiments rollout. Pulls the kilo-internal/...
(custom_llm2) Provider construction into a shared
`buildDirectProvider` helper under
`apps/web/src/lib/ai-gateway/experiments/`. The experiment routing
path added in Phase 3b will consume the same helper, so direct-to-
upstream traffic for custom LLMs and experiments share one
implementation. Behavior is unchanged for custom_llm callers.

* feat(gateway): add experiment variant picker and routing-cache loader

Phase 3b of the model-experiments rollout. Adds
`apps/web/src/lib/ai-gateway/experiments/pick-variant.ts` with:

- `isPublicIdExperimented(publicId)` — fast Redis-backed membership
  check with Postgres fallback, returning `unavailable` when both
  cache and DB are inaccessible so the gateway can fail closed.
- `getRoutingExperimentForPublicId(publicId)` — Redis-cached
  resolved experiment for a public id, including a single Postgres
  `DISTINCT ON (variant_id)` query that picks each variant's current
  `model_experiment_variant_version` and decrypts `encrypted_api_key`
  once at cache-build time. Returns `none` (no routing-relevant
  experiment), `experiment` (active or paused), or `unavailable`
  (config/decrypt/validation failure or BYOK_ENCRYPTION_KEY missing).
- `pickModelExperimentVariant(input)` — deterministic picker using
  `getRandomNumber`. Allocation subject precedence is user → machine
  → ip; missing all subjects returns `unavailable`. Variants are
  walked by id-asc cumulative weight so live label edits never
  rebucket users.

The `upstream` JSONB blob is validated by ExperimentUpstreamSchema
before use; partner-issued api keys live only in the encrypted column
and the cached payload, never in tRPC responses.

* feat(gateway): route experimented public ids to direct upstream

Phase 3c + 3d of the model-experiments rollout.

`getProvider` now returns a discriminated `GetProviderResult`
union (`provider` | `not-found` | `unavailable`) and adds a
new branch for experimented public ids that runs after the BYOK
checks and before kilo-internal/custom_llm and the
kiloExclusiveModels lookup. Active experiments produce a direct
provider built via `buildDirectProvider` plus experiment routing
metadata; paused experiments map to a local model-unavailable
response so traffic doesn't silently fall through to default
routing; cache/DB/config failures map to a 503
temporarily-unavailable response.

Experiment traffic uses three new flags on the provider result:
`skipBalanceCheck` (preview traffic is provider-funded),
`skipProviderPin` (the partner endpoint is selected by the
variant version, not by the org config), and
`skipKiloExclusiveModelSettings` (which
`applyProviderSpecificLogic` honors so the registry does not
override the variant's `internal_id`). Server-side organization
model/data-collection policy still runs against the public id.

Also excludes experimented preview public ids from `kilo-auto/free`
candidate sets so they are never silently selected on a user's
behalf — users only ever reach the experiment path by opting in
to the dedicated preview model id.

The route handler captures `x-kilocode-machineid` once into
`machineIdHeader` and threads it (plus the resolved client IP)
into `getProvider` so the variant picker can fall back from
userId to machineId to clientIp.

* feat(r2): add experiment-prompts content-addressed storage helper

Phase 4a of the model-experiments rollout. New helper module
`apps/web/src/lib/r2/experiment-prompts.ts` exposes
`putPromptIfAbsent(content)` (HEAD-then-PUT under a sha256 hex key
for automatic dedup) and `getPromptByHash(sha)` (admin out-of-band
reads). The bucket comes from a new env var
`R2_EXPERIMENT_PROMPTS_BUCKET_NAME` exported from the existing R2
client module; per-environment buckets are
`kilo-experiment-prompts-dev` and `kilo-experiment-prompts-prod`.

The helper is wired up but not yet called from the request hot
path — Phase 4b will add the bounded prompt capture and
attribution writer that consumes it.

* feat(gateway): persist experiment attribution after microdollar write

Phase 4b + 4c of the model-experiments rollout.

Adds `apps/web/src/lib/ai-gateway/experiments/persist.ts` with two
exports:

- `buildExperimentPromptCapture(request)` produces a bounded
  capture of the canonical post-`transformRequest` upstream body,
  splitting out the leading `role: 'system'` message for
  chat_completions and tail-truncating either side at 4 MB. The
  capture is stored on `MicrodollarUsageContext` so the async
  `after()` path never retains a reference to the full uncapped
  request body.
- `persistExperimentAttribution(input)` writes one row into
  `model_experiment_request` after running two parallel
  content-addressed R2 puts via `putPromptIfAbsent`, using
  `__absent__` / `__failed__` sentinels for missing/failed
  sides. Errors are reported to Sentry and swallowed — the billing
  write must succeed independently of analytics.

`processUsage.ts` now surfaces the inserted `{ usageId, createdAt }`
out of `logMicrodollarUsage` and `processTokenData` so the
`accountForMicrodollarUsage` after-hook can chain
`persistExperimentAttribution` on the same row. The route handler
calls `buildExperimentPromptCapture` after provider-specific
transforms and stashes the result on the usage context only for
experimented requests.

* chore(experiments): drop unused MicrodollarUsageContext import

* docs(plan): update experimental-models-1 status for PR #3325

Marks Phases 2 + 3 as merged-pending against PR #3325 and Phase 4
as in-progress with 4a/4b/4c on the same PR. 4d (the
`model_experiment_request_stats` view) is now explicitly deferred
until a real report consumer needs it. Also records the concrete
output of each phase as it landed.

* fix(experiments): zero billable cost for provider-funded experiment traffic

Review fix #1. `skipBalanceCheck` only skipped the preflight
zero-balance rejection; it did not mark the usage as free, so
`processTokenData` would still record full upstream cost into
`microdollar_usage.cost` and the insert CTE would increment
`kilocode_users.microdollars_used`. A zero-balance user could be
charged real upstream cost on a preview experiment and pushed
negative.

Adds an explicit `provider_funded` flag to
`MicrodollarUsageContext` and treats it the same as `user_byok`
in the cost-zeroing branch (zeroes `cost_mUsd` and
`cacheDiscount_mUsd` while preserving `market_cost` for
reporting). The route handler sets the flag for experimented
requests, alongside the existing `skipBalanceCheck`.

* docs(plan): record stale membership cache as next-step follow-up

Captures the review-#4 finding from PR #3325: no TTL on the
EXPERIMENTED_PUBLIC_IDS_REDIS_KEY plus best-effort/swallowed
invalidation can leave a stale list indefinitely. Documents the
agreed fix (add a TTL so the cache self-heals via the existing
DB fallback path) and defers it to a follow-up PR.

* fix(experiments): refuse experiment routing when org disables data collection

Review fix #2. Direct experiment upstreams bypass the
gateway-pinned `data_collection` setting — that setting only
applies when the request is forwarded through OpenRouter or Vercel
with a pinned provider config. For experimented public ids the
upstream is selected by the variant version, so the org's
preference would be silently ignored while we still capture
prompts to R2 for partner evaluation.

When an org sets `data_collection: 'deny'` and the request
resolves to an experiment, return the existing
`dataCollectionRequiredResponse()` instead of routing. The
existing model deny list still runs first via
`checkOrganizationModelRestrictions`. Anonymous users and
non-org authenticated users have no settings and continue to
route normally.

* fix(experiments): cache encrypted upstream; decrypt only on pick

Review fix #3. The Redis per-public-id experiment cache previously
held decrypted partner-issued api keys for up to 10 minutes. That
defeated the BYOK_ENCRYPTION_KEY fail-closed behavior: removing or
rotating the env var did nothing for warm caches because
`getRoutingExperimentForPublicId` returned the cached payload
straight to the picker. The PR verification claim ('disabling
BYOK_ENCRYPTION_KEY returns 503') only held on cold caches.

Refactors the cache to hold CIPHERTEXT exclusively. Decryption +
the missing-key check now run inside
`pickModelExperimentVariant`, on the chosen variant only, on
every request. Three knock-on benefits:

1. Key rotation is effective immediately on the next request;
   warm caches no longer serve stale plaintext keys.
2. We decrypt one variant per request instead of N at cache-build
   time.
3. A Redis dump no longer leaks usable api keys.

Per-request cost is one AES-256-GCM decrypt (microseconds), which
is negligible against an LLM round-trip.

* fix(experiments): cap prompt capture by UTF-8 bytes, not UTF-16 units

Review fix #6. SYSTEM_PROMPT_CAP_BYTES / REQUEST_BODY_CAP_BYTES
were checked with `string.length` and trimmed with
`string.slice`, which both operate on UTF-16 code units. For
non-ASCII content the actual UTF-8 byte payload uploaded to R2
could be up to ~3× the intended cap (a single CJK char is 1 UTF-16
unit but 3 UTF-8 bytes; an emoji is 2 UTF-16 units but 4 UTF-8
bytes), so the cap was not bounding what the plan claimed it
bounded. Slicing UTF-16 code units could also split a surrogate
pair, leading the next UTF-8 encode to emit U+FFFD and breaking
the deterministic-prefix promise (truncated content would hash
differently across runtimes).

Adds `truncateToUtf8Bytes(s, maxBytes)` which encodes once, walks
back to a UTF-8 codepoint boundary using the 0b10xxxxxx
continuation-byte rule, and returns valid UTF-8 with bytes
<= maxBytes. Replaces the three call sites in
`buildExperimentPromptCapture` to measure with
`Buffer.byteLength` and truncate via the new helper.

* test(experiments): cover picker, fail-closed paths, and UTF-8 truncation

Adds two test files covering the new experiment routing surface:

- `persist.test.ts` (14 cases) — `truncateToUtf8Bytes` boundary
  cases (multi-byte CJK, surrogate-pair emoji, exact alignment,
  zero cap, determinism); `buildExperimentPromptCapture`
  extraction (leading vs mid-conversation system message,
  serialization of structured content, UTF-8-byte cap enforcement
  for both system and body sides).
- `pick-variant.test.ts` (13 cases) — `isPublicIdExperimented`
  positive/negative + paused-also-counted; picker determinism for
  the same userId; api-key decryption returns the right partner
  secret per variant; allocation-subject precedence
  user > machine > ip; `unavailable` when no allocation subject;
  `not-found` for paused so traffic doesn't silently fall
  through; hot-swap preserves the variant_id slot while serving
  the new variant_version_id; weighted-distribution sanity (1:3
  split lands ~25%); historical attribution via the old version
  row survives a hot-swap; completed experiments stop routing.

Full suite: 373 passed (up from 371).

* fix(experiments): honor request-level data-collection opt-out

Follow-up to review fix #2. The previous commit only refused
experiment routing on the org-level setting
(`settings.data_collection === 'deny'`). A caller can also opt a
single request out of training/data-retention via OpenRouter's
request-body fields `provider.data_collection: 'deny'` or
`provider.zdr: true`. Direct experiment upstreams ignore those
fields entirely (we don't go through OpenRouter), but the
prompt-capture path was still uploading to R2 for partner
evaluation, violating the caller's stated intent.

Refuses experiment routing with `dataCollectionRequiredResponse()`
when `!isFreePromptTrainingAllowed(body.provider)` regardless of
org/anon/BYOK status. The check runs unconditionally right after
`getProvider` returns so it covers anonymous and non-org
authenticated users too.

Adds a unit test for `isFreePromptTrainingAllowed` covering all
opt-out shapes (`data_collection: 'deny'`, `zdr: true`,
`zdr: false`, both, neither, undefined) so a regression cannot
silently allow capture for users who explicitly opted out at the
request scope. Also tightens the `GatewayRequest` cast in
`persist.test.ts` per the test-file as-cast convention.

* chore(experiments): drop plan references from code comments

Plans are ephemeral and get removed when the work is done. Comments
that pointed readers at `.plans/experimental-models-1.md` or
referenced specific plan sections now stand on their own — the
relevant context (sentinel meanings, why the api key column is
separate, scope of preview routing) is inlined where it matters
instead of forwarded to a doc that won't exist long-term.

Touched:
- apps/web/src/lib/r2/experiment-prompts.ts (header)
- apps/web/src/lib/ai-gateway/experiments/persist.ts (sentinel docs)
- apps/web/src/lib/ai-gateway/experiments/upstream-schema.ts (api_key)
- apps/web/src/app/admin/model-experiments/ModelExperimentsContent.tsx
  (admin UI subtitle: dropped trailing 'See .plans/...' sentence)
- packages/db/src/schema.ts (three model_experiment* table headers)

* chore(experiments): leave shared-with-main files for separate chore PR

The previous commit (b6b1ebc) cleaned plan references from five
files, three of which already exist on main from earlier merged
PRs (Phase 1 schema, Phase 5 admin UI). Touching cross-package
files unrelated to the gateway work blew up this PR's diff and
risked merge churn against PR #3329, which carries those same
three edits independently.

Restore the on-main copies of:
- packages/db/src/schema.ts
- apps/web/src/lib/ai-gateway/experiments/upstream-schema.ts
- apps/web/src/app/admin/model-experiments/ModelExperimentsContent.tsx

The branch-only files (persist.ts, experiment-prompts.ts) keep
their cleanups since they were authored on this branch and
land/ship together with the gateway work.

* revert(gateway): drop kilo-auto/free experimented-id safeguard

Reverts the `filterOutExperimentedPublicIds` filter added in
`72dd51171` for `kilo-auto/free` candidate selection.

The safeguard added a per-candidate `isPublicIdExperimented`
Redis check on every `kilo-auto/free` request — material extra
latency on a hot path — to defend against an invariant that holds
by construction: `autoFreeModels` and the frontier preset list
are hand-curated and never include dedicated preview experiment
ids. The explicit-opt-in property of preview models is preserved
without any runtime guard.

`getProvider`'s experiment-membership check is unchanged —
explicit requests to a preview public id still resolve through the
experiment routing path.

Plan updated to reflect the no-guard decision and rationale.

* chore(experiments): drop unused barrel index.ts

Every consumer in the repo deep-imports the specific file
(`./pick-variant`, `./persist`, `./upstream-schema`,
`./build-direct-provider`); nothing imported from the barrel.
Most `index.ts` files under `ai-gateway/` are real module
implementations, not pure re-exports — drop this one to match.

* refactor(experiments): drop system-prompt split, plumb request_kind through capture

Adapts the gateway path to the simpler model_experiment_request schema
landed in #3358. Addresses comments #2 + #3 on #3325.

- ExperimentPromptCapture: remove `systemPromptContent`, add
  `requestKind`. The full post-`transformRequest` body is now stored as
  a single content-addressed blob; the upstream API shape is recorded
  alongside it for downstream reporting.
- buildExperimentPromptCapture: collapse the chat_completions-only
  system-extraction branch; one body capture path for all kinds.
- persistExperimentAttribution: one R2 PUT instead of two, one hash
  written, no `__absent__` sentinel. `PROMPT_HASH_ABSENT` removed;
  `PROMPT_HASH_FAILED` and `PROMPT_HASH_DELETED` retained.
- Tests: drop the system-extraction cases (no longer applicable), add
  coverage for the three request kinds and a determinism check (content
  addressing relies on byte-stable serialization).

The dedup-on-shared-system-prompt micro-optimization had no measured
benefit. If we ever want it back, content-addressing the full body is
the better starting point anyway.

* refactor(gateway): make balance <= 0 the primary condition on the usage-limit guard

Reorders the conjuncts on the usage-limit guard so the salient check
reads first and `skipBalanceCheck` is clearly an override flag, not
the gating condition. Pure reorder — boolean evaluation is unchanged.

Addresses comment #1 on #3325.

* fix(experiments): backfill the experimented-public-ids redis cache on miss

Previously a Redis miss / corrupt cache / Redis error in
`isPublicIdExperimented` fell through to Postgres for a single-id
lookup and returned the answer without populating Redis. Result: every
request on a cold cache hammered Postgres until something else
happened to write the key (admin mutation, etc.).

On the cache-miss path we now rebuild the full routing-eligible public
id set from `model_experiment` (matching what
`recomputeExperimentedPublicIds` writes from the admin router) and
SET it back to Redis with the same TTL. Subsequent requests hit Redis
again. The backfill is fire-and-forget — if Redis is genuinely down
we still return the correct answer for this request.

Also drops the per-id-only DB query in favor of the full-set query
because the cache contract is the complete list, not per-id presence.

Addresses comment #4 on #3325.

* refactor(experiments): rename subjectPick → allocationSubject, fix lying 'unreachable' comment

Two small clarity fixes from #3325 review:

- Rename the local variable bound from `pickAllocationSubject(input)`
  from `subjectPick` to `allocationSubject`. Matches the field name
  on the result and answers the reviewer's 'is this the user?' question
  inline. Pure rename.

- Replace the 'Unreachable: getRandomNumber returns [0, totalWeight).
  Fail closed.' comment with an honest one. The branch is defensive
  fail-closed insurance for the case where `getRandomNumber`'s
  contract breaks (NaN, negative, weight rounding), not actually
  unreachable code. The captureMessage + 'unavailable' return is the
  right behavior; only the comment was wrong.

Addresses comments #5 and #6 on #3325.

* fix(gateway): return usage identity for zero-cost inserts

* fix(gateway): use JS-side identity values to avoid Postgres timestamp format

* refactor(gateway): extract experiment variant types

* refactor(gateway): narrow experiment attribution catch

* refactor(gateway): move prompt storage fallback

* refactor(gateway): simplify prompt hash fallback

* refactor(gateway): inline experiment attribution insert

* refactor(gateway): remove prompt truncation breadcrumb

* refactor(gateway): inline prompt capture serialization

* refactor(gateway): simplify experiment attribution guard

* fix(web): require BYOK encryption key

* refactor(gateway): remove missing BYOK key branch

* refactor(gateway): cache experimented ids from Redis only

* refactor(gateway): load experiment routing from Postgres

* test(gateway): avoid silent Redis membership skips

* feat(gateway): expose active experiment models

* feat(admin): show experiment request log

* fix(gateway): allow anonymous experiment models

* refactor(gateway): treat experiment models as free models

Resolve isPublicIdExperimented once per request and OR the result into the existing free-model gates: rate limit, anonymous-access, and balance check. Drop the now-redundant skipBalanceCheck flag from GetProviderProviderResult.

* chore(gateway): remove unrelated review changes

* refactor(gateway): fold experiment check into isFreeModel

Now that isFreeModel is async (PR #3393), let it answer for experimented public ids directly. Drops the route's local isExperimentedModel boolean from the auth and balance gates and removes the provider_funded flag — cost zeroing is handled by the isFreeModel check in processUsage.ts.

* chore(gateway): revert unnecessary comment edits

* fix(gateway): refuse experiments under enterprise allow-list

Enterprise provider allow-list is enforced via OpenRouter's body.provider.only field, which doesn't reach a direct partner upstream. Refuse experiment routing rather than silently bypassing the org's allow-list. Note Option 3 (unify direct-upstream routing abstraction) in the plan as the proper followup.

* fix(gateway): remove duplicate unfiltered experiment models

Experiment models were already added to restrictionCandidates and folded into the org-restrictions filter, then appended again unfiltered at the end. The duplicate append created two issues: catalog entries appeared twice, and Enterprise model_deny_list entries were silently re-exposed. Drop the duplicate append; the filter at line 32-42 covers experiments and matches the gateway's behavior (experiments do not set bypassAccessCheck and go through checkOrganizationModelRestrictions on the request path).

* fix(gateway): split membership and isFreeModel out of models.ts

models.ts is reachable from client bundles via constants like PRIMARY_DEFAULT_MODEL and preferredModels. Folding the experiment-membership check into isFreeModel inside models.ts dragged in the Redis client (and via pick-variant.ts the Postgres client), breaking the production build with 'Module not found' errors for net/tls/dns/promises and pg.

Split along two seams that already existed: pick-variant.ts now contains only the drizzle-using routing (getRoutingExperimentForPublicId, pickModelExperimentVariant), while a new membership.ts holds the Redis-only isPublicIdExperimented. The async isFreeModel itself moves to a new is-free-model.ts that imports the sync helpers from models.ts and the membership check from membership.ts. Net result: models.ts has no I/O dependencies, the fold is preserved for server callers, and the experiment-membership check still lives once in the codebase.

* docs(experiments): note response model-id leak as v1 followup
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.

2 participants