Merge pull request #96 from fsolla/cursor/pool-pin-fast-false-cc24
Pool — pin fast=false so Composer/Grok spawns are not billed as *-fast
This commit is contained in:
commit
e8f6e34e95
6 changed files with 100 additions and 40 deletions
|
|
@ -91,7 +91,7 @@ Os wrappers disparam `gh workflow run agent-pool.yml -f action=…`; acompanhe c
|
|||
|
||||
## Ciclo de vida de uma Issue no pool
|
||||
|
||||
`ready` → (tick: claim coordenado, flip + marcador) → `in-progress` → worker spawnado com o `model:` da Issue (fallback `composer-2.5`) → PR `--base main` com `Closes #N` → CI green → auto-merge → `done`+`in-prod` (flip determinístico do CI) → tick arquiva o agente e repõe o slot.
|
||||
`ready` → (tick: claim coordenado, flip + marcador) → `in-progress` → worker spawnado com o `model:` da Issue (fallback `composer-2.5` + **`fast=false`** — omitir `fast` na API vira `composer-2.5-fast` no usage) → PR `--base main` com `Closes #N` → CI green → auto-merge → `done`+`in-prod` (flip determinístico do CI) → tick arquiva o agente e repõe o slot.
|
||||
|
||||
- **Falha terminal** (run ERROR/CANCELLED/EXPIRED ou fim sem PR): tick comenta, move a Issue para `blocked` e arquiva o agente. **Triage humana**: ler o run em cursor.com/agents, decidir — re-`ready` manual se transitório (o circuit breaker recusa a 3ª tentativa automática), corrigir a spec se sistêmico.
|
||||
- **Worker travado**: archive em cursor.com/agents → o próximo tick reconcilia como falha documentada.
|
||||
|
|
@ -101,7 +101,7 @@ Os wrappers disparam `gh workflow run agent-pool.yml -f action=…`; acompanhe c
|
|||
|
||||
## Smoke remoto (aceite — rodar na primeira ativação)
|
||||
|
||||
1. 2 Issues `kind:chore` triviais, `ready`, sem `model:` → `start --max-slots 2` → 2 agents `pool-i<N>-…` em cursor.com/agents e as 2 Issues `in-progress` com comentário de claim. Zero duplicata. Modelo efetivo = `composer-2.5`.
|
||||
1. 2 Issues `kind:chore` triviais, `ready`, sem `model:` → `start --max-slots 2` → 2 agents `pool-i<N>-…` em cursor.com/agents e as 2 Issues `in-progress` com comentário de claim. Zero duplicata. Modelo efetivo = `composer-2.5` **sem** `-fast` (`originalModelName` / usage dashboard = `composer-2.5`, não `composer-2.5-fast`).
|
||||
2. 1 Issue `blocked` e 1 `needs:consent` plantadas na fila → permanecem intactas.
|
||||
3. 1 Issue com `model: cursor-grok-4.5-high` → run criado com esse modelo (doctor lista os ids válidos).
|
||||
4. (Opcional) par bipartido: Issue plan (`cursor-grok-4.5-high`) + Issue exec (`kimi-k3-low`, `depends` no plan) — o tick só spawna a exec quando o plan estiver `done`.
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Depois de escolher Grok, **obrigatório** pickar um effort. Escreva o slug compl
|
|||
| **Medium** | `cursor-grok-4.5-medium` | Cursor Grok 4.5 · Effort Medium | **Discovery / análise moderada**, critique/polish visual, harvest de padrões → guardrail, glossário a partir de evidência, chores de processo com trade-offs claros. Default quando Grok cabe e **não** é multi-domínio de alto risco. |
|
||||
| **High** | `cursor-grok-4.5-high` | Cursor Grok 4.5 · Effort High | **Multi-domínio ou design não óbvio** com custo de falha alto: fundações (auth/sessão/schema), Consent/LGPD, plan half de bipartição, arquitetura nova. |
|
||||
|
||||
**Proibido:** sufixo `-fast` em qualquer slug (`composer-2.5-fast`, `cursor-grok-4.5-high-fast`, …). O pool **não** envia `fast=true`; se a Issue trouxer `-fast`, o resolver remove o sufixo, resolve a base e emite warn.
|
||||
**Proibido:** sufixo `-fast` em qualquer slug (`composer-2.5-fast`, `cursor-grok-4.5-high-fast`, …). O pool **pina `fast=false`** em todo spawn cuja família Cloud exponha o param `fast` (Composer, Grok, …). Só omitir o param **não basta**: a API Create-Agent resolve o default da variante, que hoje é `fast=true` (`composer-2.5-fast` no usage dashboard). Se a Issue trouxer `-fast`, o resolver remove o sufixo, resolve a base + `fast=false` e emite warn.
|
||||
|
||||
**Heurística anti-viés:** se você ia marcar High por hábito, pergunte “a falha é cara **e** o desenho é aberto?”. Se só uma das duas → Medium (ou Low). Se nenhuma → Composer.
|
||||
|
||||
|
|
@ -52,11 +52,11 @@ Depois de escolher Grok, **obrigatório** pickar um effort. Escreva o slug compl
|
|||
|
||||
| Slug na Issue | API `model.id` | params |
|
||||
| ------------- | -------------- | ------ |
|
||||
| `cursor-grok-4.5-low` | `grok-4.5` | `effort=low` |
|
||||
| `cursor-grok-4.5-medium` | `grok-4.5` | `effort=medium` |
|
||||
| `cursor-grok-4.5-high` | `grok-4.5` | `effort=high` |
|
||||
| `cursor-grok-4.5-low` | `grok-4.5` | `effort=low`, `fast=false` |
|
||||
| `cursor-grok-4.5-medium` | `grok-4.5` | `effort=medium`, `fast=false` |
|
||||
| `cursor-grok-4.5-high` | `grok-4.5` | `effort=high`, `fast=false` |
|
||||
| `kimi-k3-low` | `kimi-k3` | `reasoning=low` |
|
||||
| `composer-2.5` | `composer-2.5` | — |
|
||||
| `composer-2.5` | `composer-2.5` | `fast=false` |
|
||||
|
||||
## Issues muito complexas (bipartir)
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ Não despachar Kimi K3 Low sem plano fechado na Issue de plan.
|
|||
- **Frontmatter da Issue / pool:** use exatamente os slugs da tabela (incl. `-low`/`-medium`/`-high`).
|
||||
- **Subagentes (`Task.model`):** prefira o slug da Issue quando o enum do produto listar (`composer-2.5`, `cursor-grok-4.5-high`, `kimi-k3-low`, …). **Nunca** escolha variantes `-fast` do enum. Se a Issue é `-low`/`-medium` e o enum não tem o slug, use `inherit` (sessão já no effort certo) ou `cursor-grok-4.5-high` só se a subtarefa for a parte difícil — **não** “promova” a Issue inteira para High.
|
||||
- **Sessão principal:** o agente não troca o próprio modelo. Se o atual não for o da tabela, diga em uma linha e siga.
|
||||
- **Pool:** `POOL_DEFAULT_MODEL_SLUG` = `composer-2.5`; `resolvePoolModel` mapeia os slugs canônicos → API.
|
||||
- **Pool:** `POOL_DEFAULT_MODEL_SLUG` = `composer-2.5`; `resolvePoolModel` mapeia os slugs canônicos → API e **sempre** inclui `fast=false` quando o modelo Cloud aceita o param (senão o dashboard fatura `*-fast`).
|
||||
|
||||
## Gates por fluxo
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**Recently resolved (2026-08-01):** **Pool pin `fast=false`** — `resolvePoolModel` agora envia `fast=false` em todo spawn cuja família Cloud anuncia o param (`composer-2.5`, `grok-4.5`, …). Antes o supervisor pedia só `{ id: "composer-2.5" }`; a API Create-Agent resolve o default da variante (`fast=true`) e o usage dashboard mostrava `composer-2.5-fast` em todos os workers do pool. Skills `model-selection` / `agent-pool` + plano do orquestrador atualizados.
|
||||
|
||||
**Recently resolved (2026-08-01):** **C8 F1–F2** (Issue #37) — freshness audit confirmou entrega em código desde 2026-07-19: locks advisory em batch (`acquireTextAdvisoryLocks`), bulk import com `.returning()` + leituras drizzle, overview derivando `total` de `totalDocs`, migration `pg_trgm`, guard `contactSearchQuery` (mín. 2 chars). Plano [`escala-dry-pos-c6.md`](docs/plans/escala-dry-pos-c6.md) fechado; follow-ups de filtros/forms permanecem no C9.
|
||||
|
||||
**Recently resolved (2026-08-01):** **Cutover residue cleanup** — removed dead `pnpm db:refresh:stage` / `scripts/refresh-stage.mjs` and `pnpm agent:promote` / `scripts/agent-promote.mjs`; closed `docs/CUTOVER-MAIN-ONLY.md`; scrubbed PR template `STAGE_DATABASE_URL` checklist item. Branch + Environment `stage` already deleted.
|
||||
|
|
|
|||
|
|
@ -215,15 +215,17 @@ Parâmetros (só relevantes ao backoff de retry de tick): `spawnRetryBackoff = p
|
|||
|
||||
- Fonte: frontmatter `model:` da Issue (escrito por `agent:register --model`, tabela de slugs
|
||||
em `.cursor/skills/model-selection/SKILL.md`).
|
||||
- Spawn: `model: { id: <slug> }` no body do `POST /v1/agents`. A API valida contra
|
||||
`GET /v1/models` (ids + aliases + params, ex.: `composer-2` com param `fast=true`).
|
||||
- Spawn: `model: { id: <slug>, params: [...] }` no body do `POST /v1/agents`. A API valida contra
|
||||
`GET /v1/models` (ids + aliases + params, ex.: `composer-2.5` com param `fast`).
|
||||
- **Namespace:** os slugs do repo (Task tool) e os da Cloud API são tabelas irmãs, não idênticas.
|
||||
O spike (Fase 0) despeja `GET /v1/models` e codifica o mapeamento explícito em
|
||||
`scripts/lib/agent-pool-models.mjs`: `Map<repoSlug, cloudModelId>` com entradas conhecidas
|
||||
(`composer-2.5` → id composer 2.5 da API,
|
||||
`cursor-grok-4.5-{low,medium,high}` → `grok-4.5` + `effort`,
|
||||
(`composer-2.5` → id composer 2.5 da API + **`fast=false`**,
|
||||
`cursor-grok-4.5-{low,medium,high}` → `grok-4.5` + `effort` + **`fast=false`**,
|
||||
`kimi-k3-low` → `kimi-k3` + `reasoning=low`; **sem** variantes `-fast`).
|
||||
- **Fallback:** `model:` ausente, desconhecido ou rejeitado pela API → **`composer-2.5`**
|
||||
Omitir `fast` faz a API cair no default da variante (`fast=true` → usage `composer-2.5-fast`);
|
||||
o resolver pina `fast=false` sempre que o modelo anuncia o param.
|
||||
- **Fallback:** `model:` ausente, desconhecido ou rejeitado pela API → **`composer-2.5` + `fast=false`**
|
||||
(pool "Cursor Models", custo incluído — regra 1 da model-selection). Warn no sumário do tick.
|
||||
- Validação: o tick consulta `GET /v1/models` uma vez por tick (cache em memória do processo)
|
||||
e nunca spawna com slug fora da lista — erro de validação não pode derrubar o tick.
|
||||
|
|
@ -320,8 +322,8 @@ ou deixar bloqueada.
|
|||
- Derivação de ativos (fixtures de Issues + runs): occupied / freed-by-done / waiting-auto-merge /
|
||||
failure-path / circuit breaker (2ª falha → blocked permanente).
|
||||
- Cálculo de `gap` e cap hard (nunca spawna além de `maxSlots` mesmo com fila longa).
|
||||
- Model mapping: slug válido → id Cloud (+ effort/reasoning); `-fast` **proibido** (strip + warn);
|
||||
ausente/inválido → `composer-2.5`; slug fora do `GET /v1/models` → fallback + warn.
|
||||
- Model mapping: slug válido → id Cloud (+ effort/reasoning) **+ `fast=false` quando o param existe**; `-fast` **proibido** (strip + warn + pin `fast=false`);
|
||||
ausente/inválido → `composer-2.5` + `fast=false`; slug fora do `GET /v1/models` → fallback + warn.
|
||||
- Prompt: contém `#N`, instrução de não-claim, `gh pr create --base stage`, `Closes #N`.
|
||||
- State: parse/serialize de variables; defaults.
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,14 @@
|
|||
* A slug ending in `-fast` is rejected (stripped + warn, then resolve the
|
||||
* base). Anything else unknown falls back to composer-2.5. The fallback
|
||||
* never crashes a tick.
|
||||
*
|
||||
* **Pin `fast=false`:** Cursor's Create-Agent API defaults omitted `fast` to
|
||||
* `true` (variant `composer-2.5-fast` / `…-fast` on the usage dashboard —
|
||||
* forum confirmation 2026-06). "Never send fast=true" is not enough; we must
|
||||
* send `fast=false` whenever the model advertises that param.
|
||||
*/
|
||||
|
||||
export const POOL_DEFAULT_MODEL_SLUG = 'composer-2.5'
|
||||
const POOL_DEFAULT_MODEL_SLUG = 'composer-2.5'
|
||||
|
||||
const GROK_EFFORT_SLUG = /^cursor-grok-4\.5-(low|medium|high)$/
|
||||
const KIMI_REASONING_SLUG = /^kimi-k3-(low|high|max)$/
|
||||
|
|
@ -33,6 +38,22 @@ const paramSupportsValue = (model, paramId, value) => {
|
|||
return values.some((entry) => entry.value === value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Append `fast=false` when the live (or offline) model supports it.
|
||||
* @param {{ id: string, params?: Array<{ id: string, value: string }> }} model
|
||||
* @param {{ id: string, parameters?: Array<{ id: string, values?: Array<{ value: string }> }> } | null} apiModel
|
||||
*/
|
||||
const pinFastFalse = (model, apiModel) => {
|
||||
const supportsFast =
|
||||
apiModel != null
|
||||
? paramSupportsValue(apiModel, 'fast', 'false')
|
||||
: // Offline / empty table: pin for families that advertise `fast` live.
|
||||
model.id === 'composer-2.5' || model.id === 'grok-4.5' || model.id === 'cursor-grok-4.5'
|
||||
if (!supportsFast) return model
|
||||
const params = (model.params ?? []).filter((param) => param.id !== 'fast')
|
||||
return { ...model, params: [...params, { id: 'fast', value: 'false' }] }
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a canonical (non-fast) slug against the live table.
|
||||
* @returns {{ model: { id: string, params?: Array<{ id: string, value: string }> }, ok: true } | { ok: false }}
|
||||
|
|
@ -44,11 +65,13 @@ const resolveCanonical = (slug, apiModels) => {
|
|||
const base =
|
||||
findModel(apiModels, 'grok-4.5') ??
|
||||
findModel(apiModels, 'cursor-grok-4.5') ??
|
||||
(apiModels.length === 0 ? { id: 'grok-4.5', parameters: [{ id: 'effort' }] } : null)
|
||||
(apiModels.length === 0
|
||||
? { id: 'grok-4.5', parameters: [{ id: 'effort' }, { id: 'fast' }] }
|
||||
: null)
|
||||
if (base && paramSupportsValue(base, 'effort', effort)) {
|
||||
return {
|
||||
ok: true,
|
||||
model: { id: base.id, params: [{ id: 'effort', value: effort }] },
|
||||
model: pinFastFalse({ id: base.id, params: [{ id: 'effort', value: effort }] }, base),
|
||||
}
|
||||
}
|
||||
return { ok: false }
|
||||
|
|
@ -63,14 +86,22 @@ const resolveCanonical = (slug, apiModels) => {
|
|||
if (base && paramSupportsValue(base, 'reasoning', reasoning)) {
|
||||
return {
|
||||
ok: true,
|
||||
model: { id: base.id, params: [{ id: 'reasoning', value: reasoning }] },
|
||||
model: pinFastFalse({ id: base.id, params: [{ id: 'reasoning', value: reasoning }] }, base),
|
||||
}
|
||||
}
|
||||
return { ok: false }
|
||||
}
|
||||
|
||||
const direct = findModel(apiModels, slug)
|
||||
if (direct) return { ok: true, model: { id: direct.id } }
|
||||
if (direct) return { ok: true, model: pinFastFalse({ id: direct.id }, direct) }
|
||||
|
||||
// Offline table: still emit the canonical composer shape with fast pinned.
|
||||
if (apiModels.length === 0 && (slug === POOL_DEFAULT_MODEL_SLUG || slug === 'composer-latest')) {
|
||||
return {
|
||||
ok: true,
|
||||
model: pinFastFalse({ id: POOL_DEFAULT_MODEL_SLUG }, null),
|
||||
}
|
||||
}
|
||||
return { ok: false }
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +118,7 @@ export const resolvePoolModel = (issueModel, apiModels = []) => {
|
|||
const known = findModel(apiModels, POOL_DEFAULT_MODEL_SLUG)
|
||||
const tableNote = apiModels.length === 0 ? ' (tabela /v1/models indisponível)' : ''
|
||||
return {
|
||||
model: { id: known?.id ?? POOL_DEFAULT_MODEL_SLUG },
|
||||
model: pinFastFalse({ id: known?.id ?? POOL_DEFAULT_MODEL_SLUG }, known),
|
||||
requested,
|
||||
usedFallback: requested !== null,
|
||||
...(requested
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { POOL_DEFAULT_MODEL_SLUG, resolvePoolModel } from '../../scripts/lib/agent-pool-models.mjs'
|
||||
import { resolvePoolModel } from '../../scripts/lib/agent-pool-models.mjs'
|
||||
|
||||
const API_MODELS = [
|
||||
{
|
||||
|
|
@ -31,29 +31,40 @@ const API_MODELS = [
|
|||
},
|
||||
]
|
||||
|
||||
const composerStandard = {
|
||||
id: 'composer-2.5',
|
||||
params: [{ id: 'fast', value: 'false' }],
|
||||
}
|
||||
|
||||
describe('resolvePoolModel', () => {
|
||||
it('resolves composer by exact id and alias', () => {
|
||||
it('resolves composer by exact id and alias, pinning fast=false', () => {
|
||||
expect(resolvePoolModel('composer-2.5', API_MODELS)).toEqual({
|
||||
model: { id: 'composer-2.5' },
|
||||
model: composerStandard,
|
||||
requested: 'composer-2.5',
|
||||
usedFallback: false,
|
||||
})
|
||||
expect(resolvePoolModel('composer-latest', API_MODELS).model).toEqual({ id: 'composer-2.5' })
|
||||
expect(resolvePoolModel('composer-latest', API_MODELS).model).toEqual(composerStandard)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['cursor-grok-4.5-low', 'low'],
|
||||
['cursor-grok-4.5-medium', 'medium'],
|
||||
['cursor-grok-4.5-high', 'high'],
|
||||
] as const)('maps %s → grok-4.5 effort=%s', (slug, effort) => {
|
||||
] as const)('maps %s → grok-4.5 effort=%s + fast=false', (slug, effort) => {
|
||||
expect(resolvePoolModel(slug, API_MODELS)).toEqual({
|
||||
model: { id: 'grok-4.5', params: [{ id: 'effort', value: effort }] },
|
||||
model: {
|
||||
id: 'grok-4.5',
|
||||
params: [
|
||||
{ id: 'effort', value: effort },
|
||||
{ id: 'fast', value: 'false' },
|
||||
],
|
||||
},
|
||||
requested: slug,
|
||||
usedFallback: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('maps kimi-k3-low → kimi-k3 reasoning=low', () => {
|
||||
it('maps kimi-k3-low → kimi-k3 reasoning=low (no fast param on kimi)', () => {
|
||||
expect(resolvePoolModel('kimi-k3-low', API_MODELS)).toEqual({
|
||||
model: { id: 'kimi-k3', params: [{ id: 'reasoning', value: 'low' }] },
|
||||
requested: 'kimi-k3-low',
|
||||
|
|
@ -61,31 +72,34 @@ describe('resolvePoolModel', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('rejects -fast: strips suffix, resolves base, warns', () => {
|
||||
it('rejects -fast: strips suffix, resolves base with fast=false, warns', () => {
|
||||
const composerFast = resolvePoolModel('composer-2.5-fast', API_MODELS)
|
||||
expect(composerFast.model).toEqual({ id: 'composer-2.5' })
|
||||
expect(composerFast.model).toEqual(composerStandard)
|
||||
expect(composerFast.usedFallback).toBe(false)
|
||||
expect(composerFast.warn).toContain('-fast')
|
||||
|
||||
const grokFast = resolvePoolModel('cursor-grok-4.5-high-fast', API_MODELS)
|
||||
expect(grokFast.model).toEqual({
|
||||
id: 'grok-4.5',
|
||||
params: [{ id: 'effort', value: 'high' }],
|
||||
params: [
|
||||
{ id: 'effort', value: 'high' },
|
||||
{ id: 'fast', value: 'false' },
|
||||
],
|
||||
})
|
||||
expect(grokFast.warn).toContain('-fast')
|
||||
expect(grokFast.model.params?.some((param) => param.id === 'fast')).toBeFalsy()
|
||||
expect(grokFast.model.params?.find((param) => param.id === 'fast')?.value).toBe('false')
|
||||
})
|
||||
|
||||
it('falls back when -fast base is also unknown', () => {
|
||||
const result = resolvePoolModel('modelo-inventado-fast', API_MODELS)
|
||||
expect(result.model).toEqual({ id: POOL_DEFAULT_MODEL_SLUG })
|
||||
expect(result.model).toEqual(composerStandard)
|
||||
expect(result.usedFallback).toBe(true)
|
||||
expect(result.warn).toContain('-fast')
|
||||
})
|
||||
|
||||
it('falls back to composer-2.5 with a warn for unknown slugs', () => {
|
||||
it('falls back to composer-2.5 (fast=false) with a warn for unknown slugs', () => {
|
||||
const result = resolvePoolModel('modelo-inventado-9', API_MODELS)
|
||||
expect(result.model).toEqual({ id: POOL_DEFAULT_MODEL_SLUG })
|
||||
expect(result.model).toEqual(composerStandard)
|
||||
expect(result.requested).toBe('modelo-inventado-9')
|
||||
expect(result.usedFallback).toBe(true)
|
||||
expect(result.warn).toContain('fallback')
|
||||
|
|
@ -94,15 +108,26 @@ describe('resolvePoolModel', () => {
|
|||
it('uses the default silently when the issue declares no model', () => {
|
||||
for (const absent of [undefined, null, '', ' ']) {
|
||||
const result = resolvePoolModel(absent, API_MODELS)
|
||||
expect(result.model).toEqual({ id: POOL_DEFAULT_MODEL_SLUG })
|
||||
expect(result.model).toEqual(composerStandard)
|
||||
expect(result.usedFallback).toBe(false)
|
||||
expect(result.warn).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('still emits canonical Cloud shapes for known slugs when the table is empty', () => {
|
||||
it('still emits canonical Cloud shapes (incl. fast=false) when the table is empty', () => {
|
||||
expect(resolvePoolModel('composer-2.5', [])).toEqual({
|
||||
model: composerStandard,
|
||||
requested: 'composer-2.5',
|
||||
usedFallback: false,
|
||||
})
|
||||
expect(resolvePoolModel('cursor-grok-4.5-medium', [])).toEqual({
|
||||
model: { id: 'grok-4.5', params: [{ id: 'effort', value: 'medium' }] },
|
||||
model: {
|
||||
id: 'grok-4.5',
|
||||
params: [
|
||||
{ id: 'effort', value: 'medium' },
|
||||
{ id: 'fast', value: 'false' },
|
||||
],
|
||||
},
|
||||
requested: 'cursor-grok-4.5-medium',
|
||||
usedFallback: false,
|
||||
})
|
||||
|
|
@ -114,13 +139,13 @@ describe('resolvePoolModel', () => {
|
|||
|
||||
it('falls back for unknown slugs when the live table is unavailable', () => {
|
||||
const result = resolvePoolModel('qualquer-coisa', [])
|
||||
expect(result.model).toEqual({ id: POOL_DEFAULT_MODEL_SLUG })
|
||||
expect(result.model).toEqual(composerStandard)
|
||||
expect(result.warn).toContain('/v1/models')
|
||||
})
|
||||
|
||||
it('ignores non-string frontmatter values', () => {
|
||||
const result = resolvePoolModel(42, API_MODELS)
|
||||
expect(result.model).toEqual({ id: POOL_DEFAULT_MODEL_SLUG })
|
||||
expect(result.model).toEqual(composerStandard)
|
||||
expect(result.usedFallback).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue