@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.
- package/app/components/board/AddTaskModal.vue +45 -0
- package/app/components/layout/BoardToolbar.vue +0 -16
- package/app/components/layout/CommandBar.vue +4 -4
- package/app/components/layout/SideBar.vue +25 -2
- package/app/components/panels/inspector/TaskRunSettings.vue +59 -0
- package/app/components/pipeline/PipelineBuilder.vue +2 -2
- package/app/components/settings/ModelConfigurationPanel.vue +487 -0
- package/app/components/settings/OpenRouterCatalogPanel.vue +176 -0
- package/app/composables/api/board.ts +1 -0
- package/app/composables/api/models.ts +16 -12
- package/app/composables/api/presets.ts +24 -1
- package/app/pages/index.vue +8 -2
- package/app/stores/board.ts +2 -0
- package/app/stores/modelPresets.ts +65 -0
- package/app/stores/openrouter.ts +52 -0
- package/app/stores/ui.ts +19 -8
- package/app/stores/workspace.ts +2 -3
- package/app/types/domain.ts +5 -11
- package/app/types/model-presets.ts +33 -0
- package/app/types/models.ts +4 -4
- package/app/types/openrouter.ts +45 -0
- package/package.json +2 -2
- package/app/components/settings/ModelDefaultsPanel.vue +0 -250
- package/app/stores/modelDefaults.ts +0 -76
|
@@ -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
|
-
// ----
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
http<
|
|
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
|
-
|
|
111
|
-
http<
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
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
|
}
|
package/app/pages/index.vue
CHANGED
|
@@ -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
|
|
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
|
-
<
|
|
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 -->
|
package/app/stores/board.ts
CHANGED
|
@@ -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
|
|
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
|
|
306
|
-
|
|
307
|
+
function openModelConfig() {
|
|
308
|
+
modelConfigOpen.value = true
|
|
307
309
|
}
|
|
308
|
-
function
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
431
|
-
|
|
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,
|
package/app/stores/workspace.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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)
|
package/app/types/domain.ts
CHANGED
|
@@ -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
|
-
/**
|
|
389
|
-
|
|
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
|
+
}
|
package/app/types/models.ts
CHANGED
|
@@ -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
|
|
23
|
-
* `subscription` (when present) is the alternative the picker prefers once
|
|
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.
|
|
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"
|