Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .claude/skills/nexus-model-updates/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,24 @@ python3 .claude/skills/nexus-model-updates/scripts/check_model_registry.py \
models live in the image adapters' own catalogs, not in `<Provider>Models.ts`,
the structural gate does not see them, and the committed tool catalogs embed
their enum.
5. **A Live / realtime voice model** (Google `*-live*`, OpenAI `gpt-realtime*`)
5. **A video generation model** — follow `protocols/add-video-model.md`. They
live in one static array in `src/services/llm/types/VideoTypes.ts`. The
structural gate does not see them, and an id missing from that array is
rejected even when the provider lists it.
6. **A Live / realtime voice model** (Google `*-live*`, OpenAI `gpt-realtime*`)
— follow `protocols/add-realtime-voice-model.md`. These ids answer only over
a WebSocket session, so they do not belong in `<Provider>Models.ts`, the
structural gate does not see them, and the provider smoke lane cannot prove
them; the realtime smoke lane drives the shipped session instead.
6. **Before calling any of the above done** — run `protocols/verify-model.md`. You
7. **Before calling any of the above done** — run `protocols/verify-model.md`. You
MUST get a zero exit from
`scripts/check_model_registry.py --repo-root . <provider>`, and you MUST NOT
report a model as working on the strength of a registry entry: the entry is a
claim about an id, and only a live call tests it.
7. **End of a session that used this skill** — run `protocols/self-refine.md`.
8. **End of a session that used this skill** — run `protocols/self-refine.md`.

## Map
- `protocols/` the procedures: add-model, add-image-model,
- `protocols/` the procedures: add-model, add-image-model, add-video-model,
add-realtime-voice-model, change-default, add-provider-registry,
verify-model, self-refine.
- `references/` mechanism, read on demand: `registry-anatomy.md` (what a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ endpoint actually accepts, proven by one live generation through the adapter.
the resulting 400 looks like a broken model.

## Guidelines
- Pattern: Google's image docs show only Interactions API samples, but
`GeminiImageAdapter` calls `generateContent`. Before you add a Gemini image
id, check that `supportedGenerationMethods` in the keyed Google listing
(`references/provider-listings.md`) includes `generateContent`. Without a
key, record the entry as unverified.
- Pattern: take the output format from the response's `media_type`, not from
what was requested — Gemini returns JPEG regardless.
- Pattern: read the wire enum, not the UI enum. Google's `imageConfig.imageSize`
Expand Down
64 changes: 64 additions & 0 deletions .claude/skills/nexus-model-updates/protocols/add-video-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Protocol: add-video-model

Context: video generation models are in neither `<Provider>Models.ts` nor
an image adapter. One static array, `VIDEO_MODELS` in
`src/services/llm/types/VideoTypes.ts`, holds them. It decides what the video
settings offer and what `generateVideo` accepts. A model missing from that
array is rejected, even when the provider's live listing has it. The
`generateVideo` tool schema carries no model enum, so tool catalogs do not
change, and the structural gate does not see these entries either.

## Mission
A catalog entry that sends only aspect ratios, resolutions and durations the
provider accepts, on a transport the adapter already uses.

## Steps

1. **Check the transport first.** `GoogleVideoAdapter` uses
`models/<id>:predictLongRunning`. Google's newer video models are served
only by the Interactions API (`POST /v1beta/interactions`), and that is
new adapter work, not a catalog entry. `OpenRouterVideoAdapter` speaks
OpenRouter's unified `POST /api/v1/videos`, so any model in its video
catalog uses the same request shape.

2. **Source the facts.**

```bash
curl -s https://openrouter.ai/api/v1/videos/models | python3 -c '
import json,sys
for m in json.load(sys.stdin)["data"]:
print(m["id"], m.get("supported_resolutions"), m.get("supported_aspect_ratios"),
m.get("supported_durations"), "audio", m.get("generate_audio"),
"frames", m.get("supported_frame_images"))'
```

For Google direct, the parameter table on ai.google.dev/gemini-api/docs/video
is the source, and `/docs/deprecations` gives shutdown dates. Read it per
model: Veo on the Gemini API accepts `16:9`/`9:16` only, even though other
routes add `1:1`.

3. **Fit the entry to the unions.** `VideoAspectRatio` and `VideoResolution`
are narrow, closed unions. Declare the intersection of the listing and the
union. A model with no value in the intersection (for example a `2K`-only
model) needs a type change and is not a catalog-only addition. OpenRouter's
resolution enum is case-sensitive (`4K`), and the adapter maps the internal
`4k` to it. Keep that mapping when you touch `submitJob`.

4. **`supportsReferenceImage` means `input_references`** (reference-to-video)
on OpenRouter, not a first-frame image. A model whose listing names only
`supported_frame_images` and whose vendor does not document reference
images gets `false`.

5. **Defaults.** `resolveDefaultVideoSelection` falls back to the first entry of
the first configured provider in `VIDEO_PROVIDER_PRIORITY`, and the shipped
`defaultVideoModel` in `src/types/llm/ProviderTypes.ts` is an independent
value. Keep each provider's first entry stable unless you mean to move the
fallback. When the shipped default moves, the `shipped video default` test in
`tests/unit/VideoGenerationService.test.ts` must still pass.

6. **Live call.** `tests/debug/video-generation-live-smoke.test.ts` drives the
real adapters. Video generation is billed per second, so run it only with
the owner's go-ahead. Without that run, report the entry as unverified.

## Next
`verify-model.md` steps 1–3, then `self-refine.md`.
2 changes: 2 additions & 0 deletions .claude/skills/nexus-model-updates/refinement-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,5 @@ Append-only record of changes made by `protocols/self-refine.md`. Newest on top.
- 2026-10-09 | Cost audit found a model cache-read rate still using the usual discount ratio despite a published exception. | Corrected the existing model price from the provider table and updated the regression expectation; no workflow change. | Files: AnthropicModels.ts, LLMCostCalculator.cacheRates.test.ts.

- 2026-10-13: Authenticated model metadata can expose a lower output ceiling than streaming documentation. Used the keyed listing and kept transport-specific caps separate; no protocol change needed.

- 2026-10-14 | Updating the image and video catalogs, the skill had no route for video models: they live in a single static `VIDEO_MODELS` array in `src/services/llm/types/VideoTypes.ts` that the gate does not see. Several problems in that file were only visible in the provider's listing: Veo routes declared `1:1`, which no Veo route accepts; the OpenRouter adapter sent `4k` to a case-sensitive `4K` enum; and Google's Veo 3.1 previews shut down on 2026-10-22 with an Interactions-API-only successor. Gemini image docs now show only Interactions samples while the adapter calls `generateContent`, and there was no Google key available to check `supportedGenerationMethods`. | Added `protocols/add-video-model.md` and a SKILL.md workflow row, plus a guideline in `add-image-model.md` to confirm `generateContent` before adding a Gemini image id. | Files: `SKILL.md`, `protocols/add-video-model.md`, `protocols/add-image-model.md`.
17 changes: 17 additions & 0 deletions .claude/skills/nexus-storage/references/failure-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ default, so an unknown type is dropped in silence during replay.
Fix: write through the repository (event first, then cache) and add the `case` to
the applier. Re-verify by rebuilding the cache again and confirming survival.

## "After Rebuild cache the old value is still shown until reload"

Two separate causes; check which before fixing.

- A read answered from the repository `QueryCache` (shared by every repository,
60 s TTL) rather than SQLite. Anything that wipes or replays the database
(`StorageMaintenanceService.rebuildCache`, startup replay, `sync()`) must call
`getQueryCache().clear()` after the replay settles. Compare
`SELECT ... FROM workspaces` with `workspaceService.getWorkspace` in the
running plugin: a mismatch is this cause.
- A replayed event resolved a UNIQUE constraint with `INSERT OR REPLACE`, which
deletes the *other* row (same name, different id) together with its edits.
Appliers must decide name collisions explicitly; `WorkspaceEventApplier`
keeps both rows and disambiguates the later-created name. Replay shards are
listed in filename order, so any "first wins" rule has to compare timestamps
or ids, never arrival order.

## "A list tool returns an entity, but loading it by ID or name says not found"

Check `pluginStorage.migration.state` before blaming the point lookup. Affected
Expand Down
6 changes: 6 additions & 0 deletions .claude/skills/nexus-storage/refinement-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,9 @@ to 0). Edit `.skills/` and run `npm run sync:skills`; never edit a mirror. |
## 2026-10-13 — thinking disclosure implementation

Targeted chat message writes continue through the existing adapter/repository event path; no schema or event-type additions. Missing targets fail explicitly. Existing storage guidance applied without a protocol correction; no change.

## 2026-10-14 — Rebuild left stale reads; replay deleted same-name workspaces

- Observation: "Rebuild cache" left `getWorkspace` answering from the QueryCache, and `WorkspaceEventApplier` replayed a same-name, different-id `workspace_created` with INSERT OR REPLACE, deleting the other workspace. Neither symptom was in failure-modes.md, and the "first wins" rule was order-dependent because shards replay in listing order.
- Change: added the "old value is still shown until reload" entry to references/failure-modes.md.
- Files: references/failure-modes.md, refinement-log.md
12 changes: 8 additions & 4 deletions .cline/skills/nexus-model-updates/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,24 @@ python3 .claude/skills/nexus-model-updates/scripts/check_model_registry.py \
models live in the image adapters' own catalogs, not in `<Provider>Models.ts`,
the structural gate does not see them, and the committed tool catalogs embed
their enum.
5. **A Live / realtime voice model** (Google `*-live*`, OpenAI `gpt-realtime*`)
5. **A video generation model** — follow `protocols/add-video-model.md`. They
live in one static array in `src/services/llm/types/VideoTypes.ts`. The
structural gate does not see them, and an id missing from that array is
rejected even when the provider lists it.
6. **A Live / realtime voice model** (Google `*-live*`, OpenAI `gpt-realtime*`)
— follow `protocols/add-realtime-voice-model.md`. These ids answer only over
a WebSocket session, so they do not belong in `<Provider>Models.ts`, the
structural gate does not see them, and the provider smoke lane cannot prove
them; the realtime smoke lane drives the shipped session instead.
6. **Before calling any of the above done** — run `protocols/verify-model.md`. You
7. **Before calling any of the above done** — run `protocols/verify-model.md`. You
MUST get a zero exit from
`scripts/check_model_registry.py --repo-root . <provider>`, and you MUST NOT
report a model as working on the strength of a registry entry: the entry is a
claim about an id, and only a live call tests it.
7. **End of a session that used this skill** — run `protocols/self-refine.md`.
8. **End of a session that used this skill** — run `protocols/self-refine.md`.

## Map
- `protocols/` the procedures: add-model, add-image-model,
- `protocols/` the procedures: add-model, add-image-model, add-video-model,
add-realtime-voice-model, change-default, add-provider-registry,
verify-model, self-refine.
- `references/` mechanism, read on demand: `registry-anatomy.md` (what a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ endpoint actually accepts, proven by one live generation through the adapter.
the resulting 400 looks like a broken model.

## Guidelines
- Pattern: Google's image docs show only Interactions API samples, but
`GeminiImageAdapter` calls `generateContent`. Before you add a Gemini image
id, check that `supportedGenerationMethods` in the keyed Google listing
(`references/provider-listings.md`) includes `generateContent`. Without a
key, record the entry as unverified.
- Pattern: take the output format from the response's `media_type`, not from
what was requested — Gemini returns JPEG regardless.
- Pattern: read the wire enum, not the UI enum. Google's `imageConfig.imageSize`
Expand Down
64 changes: 64 additions & 0 deletions .cline/skills/nexus-model-updates/protocols/add-video-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Protocol: add-video-model

Context: video generation models are in neither `<Provider>Models.ts` nor
an image adapter. One static array, `VIDEO_MODELS` in
`src/services/llm/types/VideoTypes.ts`, holds them. It decides what the video
settings offer and what `generateVideo` accepts. A model missing from that
array is rejected, even when the provider's live listing has it. The
`generateVideo` tool schema carries no model enum, so tool catalogs do not
change, and the structural gate does not see these entries either.

## Mission
A catalog entry that sends only aspect ratios, resolutions and durations the
provider accepts, on a transport the adapter already uses.

## Steps

1. **Check the transport first.** `GoogleVideoAdapter` uses
`models/<id>:predictLongRunning`. Google's newer video models are served
only by the Interactions API (`POST /v1beta/interactions`), and that is
new adapter work, not a catalog entry. `OpenRouterVideoAdapter` speaks
OpenRouter's unified `POST /api/v1/videos`, so any model in its video
catalog uses the same request shape.

2. **Source the facts.**

```bash
curl -s https://openrouter.ai/api/v1/videos/models | python3 -c '
import json,sys
for m in json.load(sys.stdin)["data"]:
print(m["id"], m.get("supported_resolutions"), m.get("supported_aspect_ratios"),
m.get("supported_durations"), "audio", m.get("generate_audio"),
"frames", m.get("supported_frame_images"))'
```

For Google direct, the parameter table on ai.google.dev/gemini-api/docs/video
is the source, and `/docs/deprecations` gives shutdown dates. Read it per
model: Veo on the Gemini API accepts `16:9`/`9:16` only, even though other
routes add `1:1`.

3. **Fit the entry to the unions.** `VideoAspectRatio` and `VideoResolution`
are narrow, closed unions. Declare the intersection of the listing and the
union. A model with no value in the intersection (for example a `2K`-only
model) needs a type change and is not a catalog-only addition. OpenRouter's
resolution enum is case-sensitive (`4K`), and the adapter maps the internal
`4k` to it. Keep that mapping when you touch `submitJob`.

4. **`supportsReferenceImage` means `input_references`** (reference-to-video)
on OpenRouter, not a first-frame image. A model whose listing names only
`supported_frame_images` and whose vendor does not document reference
images gets `false`.

5. **Defaults.** `resolveDefaultVideoSelection` falls back to the first entry of
the first configured provider in `VIDEO_PROVIDER_PRIORITY`, and the shipped
`defaultVideoModel` in `src/types/llm/ProviderTypes.ts` is an independent
value. Keep each provider's first entry stable unless you mean to move the
fallback. When the shipped default moves, the `shipped video default` test in
`tests/unit/VideoGenerationService.test.ts` must still pass.

6. **Live call.** `tests/debug/video-generation-live-smoke.test.ts` drives the
real adapters. Video generation is billed per second, so run it only with
the owner's go-ahead. Without that run, report the entry as unverified.

## Next
`verify-model.md` steps 1–3, then `self-refine.md`.
2 changes: 2 additions & 0 deletions .cline/skills/nexus-model-updates/refinement-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,5 @@ Append-only record of changes made by `protocols/self-refine.md`. Newest on top.
- 2026-10-09 | Cost audit found a model cache-read rate still using the usual discount ratio despite a published exception. | Corrected the existing model price from the provider table and updated the regression expectation; no workflow change. | Files: AnthropicModels.ts, LLMCostCalculator.cacheRates.test.ts.

- 2026-10-13: Authenticated model metadata can expose a lower output ceiling than streaming documentation. Used the keyed listing and kept transport-specific caps separate; no protocol change needed.

- 2026-10-14 | Updating the image and video catalogs, the skill had no route for video models: they live in a single static `VIDEO_MODELS` array in `src/services/llm/types/VideoTypes.ts` that the gate does not see. Several problems in that file were only visible in the provider's listing: Veo routes declared `1:1`, which no Veo route accepts; the OpenRouter adapter sent `4k` to a case-sensitive `4K` enum; and Google's Veo 3.1 previews shut down on 2026-10-22 with an Interactions-API-only successor. Gemini image docs now show only Interactions samples while the adapter calls `generateContent`, and there was no Google key available to check `supportedGenerationMethods`. | Added `protocols/add-video-model.md` and a SKILL.md workflow row, plus a guideline in `add-image-model.md` to confirm `generateContent` before adding a Gemini image id. | Files: `SKILL.md`, `protocols/add-video-model.md`, `protocols/add-image-model.md`.
17 changes: 17 additions & 0 deletions .cline/skills/nexus-storage/references/failure-modes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ default, so an unknown type is dropped in silence during replay.
Fix: write through the repository (event first, then cache) and add the `case` to
the applier. Re-verify by rebuilding the cache again and confirming survival.

## "After Rebuild cache the old value is still shown until reload"

Two separate causes; check which before fixing.

- A read answered from the repository `QueryCache` (shared by every repository,
60 s TTL) rather than SQLite. Anything that wipes or replays the database
(`StorageMaintenanceService.rebuildCache`, startup replay, `sync()`) must call
`getQueryCache().clear()` after the replay settles. Compare
`SELECT ... FROM workspaces` with `workspaceService.getWorkspace` in the
running plugin: a mismatch is this cause.
- A replayed event resolved a UNIQUE constraint with `INSERT OR REPLACE`, which
deletes the *other* row (same name, different id) together with its edits.
Appliers must decide name collisions explicitly; `WorkspaceEventApplier`
keeps both rows and disambiguates the later-created name. Replay shards are
listed in filename order, so any "first wins" rule has to compare timestamps
or ids, never arrival order.

## "A list tool returns an entity, but loading it by ID or name says not found"

Check `pluginStorage.migration.state` before blaming the point lookup. Affected
Expand Down
6 changes: 6 additions & 0 deletions .cline/skills/nexus-storage/refinement-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,9 @@ to 0). Edit `.skills/` and run `npm run sync:skills`; never edit a mirror. |
## 2026-10-13 — thinking disclosure implementation

Targeted chat message writes continue through the existing adapter/repository event path; no schema or event-type additions. Missing targets fail explicitly. Existing storage guidance applied without a protocol correction; no change.

## 2026-10-14 — Rebuild left stale reads; replay deleted same-name workspaces

- Observation: "Rebuild cache" left `getWorkspace` answering from the QueryCache, and `WorkspaceEventApplier` replayed a same-name, different-id `workspace_created` with INSERT OR REPLACE, deleting the other workspace. Neither symptom was in failure-modes.md, and the "first wins" rule was order-dependent because shards replay in listing order.
- Change: added the "old value is still shown until reload" entry to references/failure-modes.md.
- Files: references/failure-modes.md, refinement-log.md
Loading
Loading