@cat-factory/app 0.7.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
1
  import type {
2
2
  AddApiKeyInput,
3
3
  ApiKey,
4
- ModelDefaults,
5
4
  ModelOption,
6
5
  PersonalSubscriptionStatus,
7
6
  ServiceFragmentDefaults,
@@ -16,6 +15,11 @@ import type {
16
15
  TestLocalModelEndpointInput,
17
16
  UpsertLocalModelEndpointInput,
18
17
  } from '~/types/localModels'
18
+ import type {
19
+ OpenRouterCatalog,
20
+ OpenRouterRefreshResult,
21
+ UpsertOpenRouterCatalogInput,
22
+ } from '~/types/openrouter'
19
23
  import type { ApiContext } from './context'
20
24
 
21
25
  /**
@@ -100,18 +104,18 @@ export function modelsApi({ http, ws }: ApiContext) {
100
104
  body,
101
105
  }),
102
106
 
103
- // ---- per-agent-kind default models (workspace routing overrides) ------
104
- // The workspace's map of agentKind model id; a kind absent from the map
105
- // falls back to the deployment's env routing. `setModelDefaults` replaces the
106
- // whole map (the settings panel sends the full set on every change).
107
- getModelDefaults: (workspaceId: string) =>
108
- http<ModelDefaults>(`${ws(workspaceId)}/model-defaults`),
107
+ // ---- OpenRouter dynamic catalog (per-workspace gateway models) --------
108
+ // Browse OpenRouter's live catalog (`refresh`, leasing the workspace's pooled
109
+ // OpenRouter key server-side) and enable a subset; enabled models then surface
110
+ // in the per-workspace `/models` catalog with their context + price.
111
+ getOpenRouterCatalog: (workspaceId: string) =>
112
+ http<OpenRouterCatalog>(`${ws(workspaceId)}/openrouter/catalog`),
109
113
 
110
- setModelDefaults: (workspaceId: string, defaults: Record<string, string>) =>
111
- http<ModelDefaults>(`${ws(workspaceId)}/model-defaults`, {
112
- method: 'PUT',
113
- body: { defaults },
114
- }),
114
+ setOpenRouterCatalog: (workspaceId: string, body: UpsertOpenRouterCatalogInput) =>
115
+ http<OpenRouterCatalog>(`${ws(workspaceId)}/openrouter/catalog`, { method: 'PUT', body }),
116
+
117
+ refreshOpenRouterCatalog: (workspaceId: string) =>
118
+ http<OpenRouterRefreshResult>(`${ws(workspaceId)}/openrouter/refresh`, { method: 'POST' }),
115
119
 
116
120
  // The workspace's default service-fragment selection (the fragment ids new
117
121
  // services inherit). `setServiceFragmentDefaults` replaces the whole list.
@@ -3,9 +3,14 @@ import type {
3
3
  MergeThresholdPreset,
4
4
  UpdateMergePresetInput,
5
5
  } from '~/types/merge'
6
+ import type {
7
+ CreateModelPresetInput,
8
+ ModelPreset,
9
+ UpdateModelPresetInput,
10
+ } from '~/types/model-presets'
6
11
  import type { ApiContext } from './context'
7
12
 
8
- /** The per-workspace merge-threshold preset library (per-task auto-merge policy). */
13
+ /** The per-workspace preset libraries: merge-threshold policy + model->agent mapping. */
9
14
  export function presetsApi({ http, ws }: ApiContext) {
10
15
  return {
11
16
  // ---- merge threshold presets (per-task auto-merge policy library) -----
@@ -25,5 +30,23 @@ export function presetsApi({ http, ws }: ApiContext) {
25
30
  http(`${ws(workspaceId)}/merge-presets/${encodeURIComponent(presetId)}`, {
26
31
  method: 'DELETE',
27
32
  }),
33
+
34
+ // ---- model presets (per-task model->agent mapping library) ------------
35
+ listModelPresets: (workspaceId: string) =>
36
+ http<ModelPreset[]>(`${ws(workspaceId)}/model-presets`),
37
+
38
+ createModelPreset: (workspaceId: string, body: CreateModelPresetInput) =>
39
+ http<ModelPreset>(`${ws(workspaceId)}/model-presets`, { method: 'POST', body }),
40
+
41
+ updateModelPreset: (workspaceId: string, presetId: string, body: UpdateModelPresetInput) =>
42
+ http<ModelPreset>(`${ws(workspaceId)}/model-presets/${encodeURIComponent(presetId)}`, {
43
+ method: 'PATCH',
44
+ body,
45
+ }),
46
+
47
+ deleteModelPreset: (workspaceId: string, presetId: string) =>
48
+ http(`${ws(workspaceId)}/model-presets/${encodeURIComponent(presetId)}`, {
49
+ method: 'DELETE',
50
+ }),
28
51
  }
29
52
  }
@@ -29,9 +29,12 @@ import MergeThresholdsPanel from '~/components/settings/MergeThresholdsPanel.vue
29
29
  import IssueTrackerWritebackPanel from '~/components/settings/IssueTrackerWritebackPanel.vue'
30
30
  import WorkspaceSettingsPanel from '~/components/settings/WorkspaceSettingsPanel.vue'
31
31
  import DatadogPanel from '~/components/settings/DatadogPanel.vue'
32
- import ModelDefaultsPanel from '~/components/settings/ModelDefaultsPanel.vue'
32
+ import ModelConfigurationPanel from '~/components/settings/ModelConfigurationPanel.vue'
33
33
  import ServiceFragmentDefaultsPanel from '~/components/settings/ServiceFragmentDefaultsPanel.vue'
34
34
  import LocalModelEndpointsPanel from '~/components/settings/LocalModelEndpointsPanel.vue'
35
+ import OpenRouterCatalogPanel from '~/components/settings/OpenRouterCatalogPanel.vue'
36
+ import VendorCredentialsModal from '~/components/providers/VendorCredentialsModal.vue'
37
+ import PersonalCredentialModal from '~/components/providers/PersonalCredentialModal.vue'
35
38
 
36
39
  const workspace = useWorkspaceStore()
37
40
  const github = useGitHubStore()
@@ -119,9 +122,12 @@ watch(
119
122
  <IssueTrackerWritebackPanel />
120
123
  <WorkspaceSettingsPanel />
121
124
  <DatadogPanel />
122
- <ModelDefaultsPanel />
125
+ <ModelConfigurationPanel />
123
126
  <ServiceFragmentDefaultsPanel />
124
127
  <LocalModelEndpointsPanel />
128
+ <OpenRouterCatalogPanel />
129
+ <VendorCredentialsModal />
130
+ <PersonalCredentialModal />
125
131
  </template>
126
132
 
127
133
  <!-- Backend unreachable / bootstrap failed -->
@@ -77,6 +77,7 @@ export const useBoardStore = defineStore('board', () => {
77
77
  taskType?: CreateTaskType
78
78
  taskTypeFields?: TaskTypeFields
79
79
  mergePresetId?: string
80
+ modelPresetId?: string
80
81
  pipelineId?: string
81
82
  agentConfig?: Record<string, string>
82
83
  },
@@ -88,6 +89,7 @@ export const useBoardStore = defineStore('board', () => {
88
89
  ...(options?.taskType ? { taskType: options.taskType } : {}),
89
90
  ...(options?.taskTypeFields ? { taskTypeFields: options.taskTypeFields } : {}),
90
91
  ...(options?.mergePresetId ? { mergePresetId: options.mergePresetId } : {}),
92
+ ...(options?.modelPresetId ? { modelPresetId: options.modelPresetId } : {}),
91
93
  ...(options?.pipelineId ? { pipelineId: options.pipelineId } : {}),
92
94
  ...(options?.agentConfig ? { agentConfig: options.agentConfig } : {}),
93
95
  })
@@ -0,0 +1,65 @@
1
+ import { defineStore } from 'pinia'
2
+ import { computed, ref } from 'vue'
3
+ import type {
4
+ CreateModelPresetInput,
5
+ ModelPreset,
6
+ UpdateModelPresetInput,
7
+ } from '~/types/model-presets'
8
+ import { useWorkspaceStore } from '~/stores/workspace'
9
+
10
+ /**
11
+ * The workspace's model presets — the library a task picks its model→agent mapping
12
+ * from (each preset is a base model applied to every agent kind plus per-kind
13
+ * overrides). Hydrated from the workspace snapshot; managed via the Model Configuration
14
+ * settings screen. The backend always keeps at least one default preset (the built-in
15
+ * "Kimi K2.7", everything Kimi).
16
+ */
17
+ export const useModelPresetsStore = defineStore('modelPresets', () => {
18
+ const api = useApi()
19
+
20
+ const presets = ref<ModelPreset[]>([])
21
+
22
+ function hydrate(list: ModelPreset[]) {
23
+ presets.value = [...list].sort((a, b) => a.createdAt - b.createdAt)
24
+ }
25
+
26
+ /** The workspace default (fallback for a task that picks none). */
27
+ const defaultPreset = computed(() => presets.value.find((p) => p.isDefault) ?? null)
28
+
29
+ /** Resolve a task's effective preset by id, falling back to the default. */
30
+ function resolve(presetId: string | undefined): ModelPreset | null {
31
+ if (presetId) {
32
+ const picked = presets.value.find((p) => p.id === presetId)
33
+ if (picked) return picked
34
+ }
35
+ return defaultPreset.value
36
+ }
37
+
38
+ /** The model id a preset assigns to an agent kind (`overrides[kind] ?? baseModelId`). */
39
+ function modelForKind(preset: ModelPreset | null, agentKind: string): string | undefined {
40
+ if (!preset) return undefined
41
+ return preset.overrides[agentKind] ?? preset.baseModelId
42
+ }
43
+
44
+ async function create(input: CreateModelPresetInput) {
45
+ const ws = useWorkspaceStore()
46
+ const created = await api.createModelPreset(ws.requireId(), input)
47
+ await ws.refresh()
48
+ return created
49
+ }
50
+
51
+ async function update(presetId: string, patch: UpdateModelPresetInput) {
52
+ const ws = useWorkspaceStore()
53
+ const updated = await api.updateModelPreset(ws.requireId(), presetId, patch)
54
+ await ws.refresh()
55
+ return updated
56
+ }
57
+
58
+ async function remove(presetId: string) {
59
+ const ws = useWorkspaceStore()
60
+ await api.deleteModelPreset(ws.requireId(), presetId)
61
+ await ws.refresh()
62
+ }
63
+
64
+ return { presets, defaultPreset, resolve, modelForKind, hydrate, create, update, remove }
65
+ })
@@ -0,0 +1,52 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+ import type { OpenRouterModelMeta, OpenRouterRefreshResult } from '~/types/openrouter'
4
+
5
+ // The workspace's OpenRouter dynamic catalog: the enabled subset of OpenRouter's 300+
6
+ // gateway models. `enabled` is what's persisted (and surfaced in the model picker);
7
+ // `browse` is the live catalog from the last `refresh` (not persisted) the user picks from.
8
+ // Scoped to the workspace (its key lives in the workspace API-key pool).
9
+ export const useOpenRouterStore = defineStore('openrouter', () => {
10
+ const api = useApi()
11
+ const enabled = ref<OpenRouterModelMeta[]>([])
12
+ const browse = ref<OpenRouterModelMeta[]>([])
13
+ const loading = ref(false)
14
+ const refreshing = ref(false)
15
+ const refreshError = ref<string | null>(null)
16
+
17
+ async function load(workspaceId: string) {
18
+ loading.value = true
19
+ try {
20
+ const catalog = await api.getOpenRouterCatalog(workspaceId)
21
+ enabled.value = catalog.models
22
+ } catch {
23
+ // Auth disabled / not signed in / feature off → nothing surfaces.
24
+ enabled.value = []
25
+ } finally {
26
+ loading.value = false
27
+ }
28
+ }
29
+
30
+ /** Probe OpenRouter's live catalog (leases the workspace's pooled key server-side). */
31
+ async function refresh(workspaceId: string): Promise<OpenRouterRefreshResult> {
32
+ refreshing.value = true
33
+ refreshError.value = null
34
+ try {
35
+ const result = await api.refreshOpenRouterCatalog(workspaceId)
36
+ browse.value = result.models
37
+ if (!result.reachable) refreshError.value = result.error ?? 'OpenRouter is unreachable'
38
+ return result
39
+ } finally {
40
+ refreshing.value = false
41
+ }
42
+ }
43
+
44
+ /** Persist the enabled subset (the supplied models carry their browse-list metadata). */
45
+ async function save(workspaceId: string, models: OpenRouterModelMeta[]) {
46
+ const catalog = await api.setOpenRouterCatalog(workspaceId, { models })
47
+ enabled.value = catalog.models
48
+ return catalog
49
+ }
50
+
51
+ return { enabled, browse, loading, refreshing, refreshError, load, refresh, save }
52
+ })
package/app/stores/ui.ts CHANGED
@@ -73,7 +73,7 @@ export const useUiStore = defineStore('ui', () => {
73
73
  // Workspace-settings panel: issue-tracker writeback toggles (comment on PR open,
74
74
  // close linked issue on merge).
75
75
  const issueWritebackOpen = ref(false)
76
- const modelDefaultsOpen = ref(false)
76
+ const modelConfigOpen = ref(false)
77
77
  // Workspace-settings panel: the default service-fragment selection new services inherit.
78
78
  const serviceFragmentDefaultsOpen = ref(false)
79
79
  // LLM-vendor subscription credentials (the token pool powering the Claude Code
@@ -81,6 +81,8 @@ export const useUiStore = defineStore('ui', () => {
81
81
  const vendorCredentialsOpen = ref(false)
82
82
  // Per-user settings panel: the signed-in user's own-machine local model runners.
83
83
  const localModelsOpen = ref(false)
84
+ // Per-workspace settings panel: the OpenRouter dynamic catalog (browse/enable gateway models).
85
+ const openRouterOpen = ref(false)
84
86
 
85
87
  // Dedicated result-view overlay: a step whose agent kind declares a bespoke
86
88
  // visualization (via the archetype's `resultView`) opens here instead of the generic
@@ -302,11 +304,11 @@ export const useUiStore = defineStore('ui', () => {
302
304
  function closeDatadog() {
303
305
  datadogOpen.value = false
304
306
  }
305
- function openModelDefaults() {
306
- modelDefaultsOpen.value = true
307
+ function openModelConfig() {
308
+ modelConfigOpen.value = true
307
309
  }
308
- function closeModelDefaults() {
309
- modelDefaultsOpen.value = false
310
+ function closeModelConfig() {
311
+ modelConfigOpen.value = false
310
312
  }
311
313
  function openServiceFragmentDefaults() {
312
314
  serviceFragmentDefaultsOpen.value = true
@@ -326,6 +328,12 @@ export const useUiStore = defineStore('ui', () => {
326
328
  function closeLocalModels() {
327
329
  localModelsOpen.value = false
328
330
  }
331
+ function openOpenRouter() {
332
+ openRouterOpen.value = true
333
+ }
334
+ function closeOpenRouter() {
335
+ openRouterOpen.value = false
336
+ }
329
337
  function openRequirementReview(blockId: string) {
330
338
  resultView.value = { view: 'requirements-review', blockId, instanceId: null, stepIndex: null }
331
339
  }
@@ -372,10 +380,11 @@ export const useUiStore = defineStore('ui', () => {
372
380
  issueWritebackOpen,
373
381
  workspaceSettingsOpen,
374
382
  datadogOpen,
375
- modelDefaultsOpen,
383
+ modelConfigOpen,
376
384
  serviceFragmentDefaultsOpen,
377
385
  vendorCredentialsOpen,
378
386
  localModelsOpen,
387
+ openRouterOpen,
379
388
  resultView,
380
389
  closeResultView,
381
390
  stepDetail,
@@ -427,14 +436,16 @@ export const useUiStore = defineStore('ui', () => {
427
436
  closeWorkspaceSettings,
428
437
  openDatadog,
429
438
  closeDatadog,
430
- openModelDefaults,
431
- closeModelDefaults,
439
+ openModelConfig,
440
+ closeModelConfig,
432
441
  openServiceFragmentDefaults,
433
442
  closeServiceFragmentDefaults,
434
443
  openVendorCredentials,
435
444
  closeVendorCredentials,
436
445
  openLocalModels,
437
446
  closeLocalModels,
447
+ openOpenRouter,
448
+ closeOpenRouter,
438
449
  openRequirementReview,
439
450
  openClarityReview,
440
451
  closeRequirementReview,
@@ -10,7 +10,7 @@ import { useNotificationsStore } from '~/stores/notifications'
10
10
  import { useMergePresetsStore } from '~/stores/mergePresets'
11
11
  import { useWorkspaceSettingsStore } from '~/stores/workspaceSettings'
12
12
  import { useAgentConfigStore } from '~/stores/agentConfig'
13
- import { useModelDefaultsStore } from '~/stores/modelDefaults'
13
+ import { useModelPresetsStore } from '~/stores/modelPresets'
14
14
  import { useServiceFragmentDefaultsStore } from '~/stores/serviceFragmentDefaults'
15
15
  import { useRecurringPipelinesStore } from '~/stores/recurringPipelines'
16
16
  import { useServicesStore } from '~/stores/services'
@@ -71,8 +71,7 @@ export const useWorkspaceStore = defineStore(
71
71
  useMergePresetsStore().hydrate(snapshot.mergePresets ?? [])
72
72
  useWorkspaceSettingsStore().hydrate(snapshot.settings)
73
73
  useAgentConfigStore().hydrate(snapshot.agentConfigCatalog ?? [])
74
- useModelDefaultsStore().hydrate(snapshot.modelDefaults?.defaults)
75
- useModelDefaultsStore().hydrateDeployment(snapshot.deploymentModelDefaults)
74
+ useModelPresetsStore().hydrate(snapshot.modelPresets ?? [])
76
75
  useServiceFragmentDefaultsStore().hydrate(snapshot.serviceFragmentDefaults?.fragmentIds)
77
76
  useRecurringPipelinesStore().hydrate(snapshot.recurringPipelines ?? [])
78
77
  useTrackerStore().hydrate(snapshot.trackerSettings)
@@ -21,6 +21,7 @@ import type { RequirementReview } from './requirements'
21
21
  import type { ConsensusSession, ConsensusStepConfig, StepGating, TaskEstimate } from './consensus'
22
22
  import type { ClarityReview } from './clarity'
23
23
  import type { MergeThresholdPreset } from './merge'
24
+ import type { ModelPreset } from './model-presets'
24
25
  import type { PipelineSchedule } from './recurring'
25
26
  import type { Service, WorkspaceMount } from './services'
26
27
  import type { TrackerSettings, WritebackOverride } from './tracker'
@@ -126,6 +127,8 @@ export interface Block {
126
127
  pullRequest?: PullRequestRef
127
128
  /** task-only: selected merge threshold preset id; absent = workspace default. */
128
129
  mergePresetId?: string
130
+ /** task-only: selected model preset id (which model each agent runs); absent = workspace default. */
131
+ modelPresetId?: string
129
132
  /** task-only: pinned default pipeline id picked at creation; absent = none. */
130
133
  pipelineId?: string
131
134
  /** task-only: agent-contributed config values (id→value), e.g. the Tester's environment. */
@@ -385,8 +388,8 @@ export interface WorkspaceSnapshot {
385
388
  mergePresets?: MergeThresholdPreset[]
386
389
  /** Agent config-contribution descriptors (the task-level fields the board renders). */
387
390
  agentConfigCatalog?: AgentConfigDescriptor[]
388
- /** Per-agent-kind default model overrides for this workspace (agentKind model id). */
389
- modelDefaults?: ModelDefaults
391
+ /** The workspace's model presets (the Model Configuration library + per-task picker). */
392
+ modelPresets?: ModelPreset[]
390
393
  /**
391
394
  * The deployment's env-routing defaults as `provider:model` refs: the model a
392
395
  * kind runs on when neither the task nor the workspace pins one. `default` is the
@@ -435,15 +438,6 @@ export interface UpdateWorkspaceSettingsInput {
435
438
  taskLimitPerType?: Partial<Record<CreateTaskType, number>> | null
436
439
  }
437
440
 
438
- /**
439
- * A workspace's per-agent-kind default model choice. Keys are agent kinds, values
440
- * are model catalog ids (`ModelOption.id`). A kind absent from the map falls back
441
- * to the deployment's env-configured routing. Mirrors `@cat-factory/contracts`.
442
- */
443
- export interface ModelDefaults {
444
- defaults: Record<string, string>
445
- }
446
-
447
441
  /**
448
442
  * A workspace's default service-fragment selection: the best-practice fragment ids
449
443
  * new services inherit onto their `serviceFragmentIds`. Mirrors `@cat-factory/contracts`.
@@ -0,0 +1,33 @@
1
+ // Model-preset shapes, mirroring `@cat-factory/contracts` (model-presets.ts). A preset
2
+ // is a named, per-workspace model->agent mapping: one base model applied to every
3
+ // agent kind plus per-kind overrides. A task selects one (Block.modelPresetId); none
4
+ // resolves to the workspace default preset.
5
+
6
+ /** A named, per-workspace model preset a task can select. */
7
+ export interface ModelPreset {
8
+ id: string
9
+ name: string
10
+ /** The model every agent kind defaults to under this preset (a catalog id). */
11
+ baseModelId: string
12
+ /** Per-agent-kind model overrides on top of the base (agent kind → model id). */
13
+ overrides: Record<string, string>
14
+ /** The workspace's fallback preset, used by tasks that pick none. */
15
+ isDefault: boolean
16
+ createdAt: number
17
+ }
18
+
19
+ /** Create a model preset. */
20
+ export interface CreateModelPresetInput {
21
+ name: string
22
+ baseModelId: string
23
+ overrides?: Record<string, string>
24
+ isDefault?: boolean
25
+ }
26
+
27
+ /** Patch a model preset (all fields optional; `overrides` replaces the map). */
28
+ export interface UpdateModelPresetInput {
29
+ name?: string
30
+ baseModelId?: string
31
+ overrides?: Record<string, string>
32
+ isDefault?: boolean
33
+ }
@@ -19,15 +19,15 @@ export interface ModelCost {
19
19
  * A selectable LLM model, resolved to the flavour in use for this deployment
20
20
  * (served by `GET /models`). Mirrors `ModelOption` in `@cat-factory/contracts`.
21
21
  * The base `flavor`/`provider`/`model` is the always-available fallback
22
- * (cloudflare/direct), or the subscription itself for subscription-only models.
23
- * `subscription` (when present) is the alternative the picker prefers once the
24
- * workspace has a token for its vendor.
22
+ * (cloudflare/direct/openrouter), or the subscription itself for subscription-only
23
+ * models. `subscription` (when present) is the alternative the picker prefers once
24
+ * the workspace has a token for its vendor.
25
25
  */
26
26
  export interface ModelOption {
27
27
  id: string
28
28
  label: string
29
29
  description: string
30
- flavor: 'cloudflare' | 'direct' | 'subscription'
30
+ flavor: 'cloudflare' | 'direct' | 'openrouter' | 'subscription'
31
31
  /**
32
32
  * Whether this model is actually selectable for the workspace: a direct API key for
33
33
  * its provider, a connected subscription vendor, or Cloudflare AI enabled. Absent on
@@ -0,0 +1,45 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Per-workspace OpenRouter dynamic catalog. OpenRouter is a single OpenAI-
3
+ // compatible gateway to 300+ models reached via the workspace's API-key pool. A
4
+ // workspace browses the live catalog and enables a subset; the enabled models
5
+ // surface automatically in the per-workspace model picker (with their context +
6
+ // price) and feed the spend budget.
7
+ //
8
+ // Mirrors the `@cat-factory/contracts` `openrouter` schemas exactly, so a payload
9
+ // returned by the backend drops straight into the Pinia store without translation.
10
+ // ---------------------------------------------------------------------------
11
+
12
+ /** Metadata for one OpenRouter model (prices per 1M tokens, in the spend currency). */
13
+ export interface OpenRouterModelMeta {
14
+ /** OpenRouter `vendor/model` slug, e.g. `anthropic/claude-opus-4.8`. */
15
+ id: string
16
+ /** Human-readable model name from OpenRouter's catalog. */
17
+ name: string
18
+ /** Total context window (input + output tokens), when reported. */
19
+ contextLength?: number
20
+ /** Input price per 1M tokens, in the spend currency. */
21
+ inputPerMillion: number
22
+ /** Output price per 1M tokens, in the spend currency. */
23
+ outputPerMillion: number
24
+ }
25
+
26
+ /** A workspace's enabled OpenRouter models (the persisted subset). */
27
+ export interface OpenRouterCatalog {
28
+ models: OpenRouterModelMeta[]
29
+ createdAt: number
30
+ updatedAt: number
31
+ }
32
+
33
+ /** Replace a workspace's enabled OpenRouter models with this subset (+ metadata). */
34
+ export interface UpsertOpenRouterCatalogInput {
35
+ models: OpenRouterModelMeta[]
36
+ }
37
+
38
+ /** The result of probing OpenRouter's live `/models` for the browse list. */
39
+ export interface OpenRouterRefreshResult {
40
+ reachable: boolean
41
+ /** Every model OpenRouter currently serves (empty when unreachable). */
42
+ models: OpenRouterModelMeta[]
43
+ /** Human-readable failure reason when `reachable` is false. */
44
+ error?: string
45
+ }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@cat-factory/app",
3
- "version": "0.7.4",
3
+ "version": "0.9.0",
4
+ "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
4
5
  "repository": {
5
6
  "type": "git",
6
7
  "url": "git+https://github.com/kibertoad/cat-factory.git",
7
8
  "directory": "frontend/app"
8
9
  },
9
- "description": "Reusable Nuxt layer for the Agent Architecture Board SPA (components, stores, composables, pages). Consume it from a thin deployment app via `extends: ['@cat-factory/app']` and point it at your backend with NUXT_PUBLIC_API_BASE. See deploy/frontend for an example.",
10
10
  "files": [
11
11
  "app",
12
12
  "nuxt.config.ts"