@crossworks/client-types 0.232.112 → 0.232.122
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/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/model-pools-data.ts +8 -24
- package/src/model-pools.test.ts +74 -0
- package/src/model-pools.ts +88 -0
- package/src/runners-types.ts +2 -2
- package/src/types/integrity.ts +2 -2
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1029,7 +1029,7 @@ export type ProfilePreferences = {
|
|
|
1029
1029
|
* Read via projectColorTheme, never raw. */
|
|
1030
1030
|
colorTheme?: string;
|
|
1031
1031
|
/** Selectable header WORDMARK font key (Settings → Appearance → Fonts). The
|
|
1032
|
-
* font LIST lives in the web app (
|
|
1032
|
+
* font LIST lives in the web app (server/web/lib/display-fonts.ts); the server
|
|
1033
1033
|
* stores any well-formed slug and the client falls back to the default for
|
|
1034
1034
|
* keys it doesn't know, so trimming the library never strands the preference.
|
|
1035
1035
|
* Unset ⇒ the default wordmark face (Bricolage Grotesque). Read via
|
|
@@ -1108,7 +1108,7 @@ export type ProfilePreferences = {
|
|
|
1108
1108
|
* of the always-on guards (self-grant block, no-lower-via-update, SSRF). */
|
|
1109
1109
|
toolsmithRequireApproval?: boolean;
|
|
1110
1110
|
/** APP_VERSION the boot-time manifest reconcile last synced this brain to.
|
|
1111
|
-
* The reconcile (
|
|
1111
|
+
* The reconcile (server/web instrumentation → reconcileManifestOnBoot) runs once
|
|
1112
1112
|
* per version on a deployed/updated instance, so a self-hoster who only pulls a
|
|
1113
1113
|
* new image still gets new tools/skills/group-membership without running seed
|
|
1114
1114
|
* scripts. Equal to APP_VERSION ⇒ already reconciled, skip. */
|
package/src/model-pools-data.ts
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* at curation time). Do NOT hand-edit entries here: curate at /models/pools
|
|
7
7
|
* (by hand or via the Curator) and re-export with GET /api/model-pools/export.
|
|
8
8
|
*
|
|
9
|
+
* One hand-correction on 2026-09-02: the vision ("Read images") pool carried
|
|
10
|
+
* Nano Banana Pro (`google/gemini-3-pro-image`), which is an image GENERATOR
|
|
11
|
+
* (`text+image->text+image`). It is out, and `poolModelIssue` in
|
|
12
|
+
* ./model-pools.ts now rejects that class of entry on every write path.
|
|
13
|
+
*
|
|
9
14
|
* Seeded into `curated_models` for owners who have no curated entries yet
|
|
10
15
|
* (fresh installs at onboarding; empty existing brains on upgrade). Owners who
|
|
11
16
|
* have curated ANYTHING are never touched — their pools are their judgment.
|
|
@@ -2138,27 +2143,6 @@ export const CURATED_MODEL_POOLS: readonly CuratedTemplateEntry[] = [
|
|
|
2138
2143
|
{
|
|
2139
2144
|
pool: 'vision',
|
|
2140
2145
|
position: 1,
|
|
2141
|
-
name: 'Gemini 3 Pro Image (Nano Banana Pro)',
|
|
2142
|
-
vendor: 'Google',
|
|
2143
|
-
routes: [
|
|
2144
|
-
{
|
|
2145
|
-
provider: 'openrouter',
|
|
2146
|
-
model: 'google/gemini-3-pro-image',
|
|
2147
|
-
},
|
|
2148
|
-
],
|
|
2149
|
-
pricing: {
|
|
2150
|
-
source: 'openrouter',
|
|
2151
|
-
currency: 'USD',
|
|
2152
|
-
inputPerM: 2,
|
|
2153
|
-
capturedAt: '2026-08-22T15:31:39.522Z',
|
|
2154
|
-
outputPerM: 12,
|
|
2155
|
-
},
|
|
2156
|
-
rating: 5,
|
|
2157
|
-
note: 'purpose-built image model, flagship OCR/extraction quality, can also edit/generate images; 131K context',
|
|
2158
|
-
},
|
|
2159
|
-
{
|
|
2160
|
-
pool: 'vision',
|
|
2161
|
-
position: 2,
|
|
2162
2146
|
name: 'Claude Sonnet 5',
|
|
2163
2147
|
vendor: 'Anthropic',
|
|
2164
2148
|
routes: [
|
|
@@ -2183,7 +2167,7 @@ export const CURATED_MODEL_POOLS: readonly CuratedTemplateEntry[] = [
|
|
|
2183
2167
|
},
|
|
2184
2168
|
{
|
|
2185
2169
|
pool: 'vision',
|
|
2186
|
-
position:
|
|
2170
|
+
position: 2,
|
|
2187
2171
|
name: 'Qwen3 VL 235B A22B Instruct',
|
|
2188
2172
|
vendor: 'Alibaba/Qwen',
|
|
2189
2173
|
routes: [
|
|
@@ -2204,7 +2188,7 @@ export const CURATED_MODEL_POOLS: readonly CuratedTemplateEntry[] = [
|
|
|
2204
2188
|
},
|
|
2205
2189
|
{
|
|
2206
2190
|
pool: 'vision',
|
|
2207
|
-
position:
|
|
2191
|
+
position: 3,
|
|
2208
2192
|
name: 'Qwen3 VL 30B A3B Instruct',
|
|
2209
2193
|
vendor: 'Alibaba/Qwen',
|
|
2210
2194
|
routes: [
|
|
@@ -2225,7 +2209,7 @@ export const CURATED_MODEL_POOLS: readonly CuratedTemplateEntry[] = [
|
|
|
2225
2209
|
},
|
|
2226
2210
|
{
|
|
2227
2211
|
pool: 'vision',
|
|
2228
|
-
position:
|
|
2212
|
+
position: 4,
|
|
2229
2213
|
name: 'Nemotron Nano 12B V2 VL (free)',
|
|
2230
2214
|
vendor: 'NVIDIA',
|
|
2231
2215
|
routes: [
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pool/model fit. Pins the 2026-09-02 bug: the shipped vision ("Read images")
|
|
3
|
+
* pool carried Nano Banana Pro, an image GENERATOR. Generators accept image
|
|
4
|
+
* input exactly like readers do, so nothing on the input side caught it —
|
|
5
|
+
* the model billed image-generation tokens and returned a picture where the
|
|
6
|
+
* vision worker wanted text.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from 'vitest';
|
|
9
|
+
import { MODEL_POOLS, poolModelIssue } from './model-pools';
|
|
10
|
+
import { CURATED_MODEL_POOLS } from './model-pools-data';
|
|
11
|
+
|
|
12
|
+
const READER = { input: ['text', 'image'], output: ['text'] };
|
|
13
|
+
const GENERATOR = { input: ['image', 'text'], output: ['image', 'text'] };
|
|
14
|
+
const TEXT_ONLY = { input: ['text'], output: ['text'] };
|
|
15
|
+
|
|
16
|
+
describe('poolModelIssue', () => {
|
|
17
|
+
it('keeps an image generator out of the vision pool', () => {
|
|
18
|
+
expect(poolModelIssue('vision', GENERATOR)).toMatch(/OUTPUTS images/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('allows a real image reader in the vision pool', () => {
|
|
22
|
+
expect(poolModelIssue('vision', READER)).toBeNull();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('keeps a blind model out of the vision pool', () => {
|
|
26
|
+
expect(poolModelIssue('vision', TEXT_ONLY)).toMatch(/does not accept image input/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('keeps an image generator out of every text-out pool', () => {
|
|
30
|
+
for (const pool of MODEL_POOLS.filter((p) => p.modality.output === 'text')) {
|
|
31
|
+
expect(poolModelIssue(pool.id, GENERATOR), pool.id).toMatch(/OUTPUTS images/);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('wants a generator in the image_gen pool, and nothing else', () => {
|
|
36
|
+
expect(poolModelIssue('image_gen', GENERATOR)).toBeNull();
|
|
37
|
+
expect(poolModelIssue('image_gen', READER)).toMatch(/does not output images/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('never checks the audio pools — their models are not in the chat catalog', () => {
|
|
41
|
+
expect(poolModelIssue('tts', GENERATOR)).toBeNull();
|
|
42
|
+
expect(poolModelIssue('stt', TEXT_ONLY)).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('fails OPEN on an unloaded catalog and on an unknown pool', () => {
|
|
46
|
+
expect(poolModelIssue('vision', null)).toBeNull();
|
|
47
|
+
expect(poolModelIssue('vision', { input: [], output: [] })).toBeNull();
|
|
48
|
+
expect(poolModelIssue('not-a-pool', GENERATOR)).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('the shipped curated template', () => {
|
|
53
|
+
const slugs = (pool: string) =>
|
|
54
|
+
new Set(
|
|
55
|
+
CURATED_MODEL_POOLS.filter((e) => e.pool === pool).flatMap((e) =>
|
|
56
|
+
e.routes.map((r) => r.model),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
it('never lists the same model as both an image reader and an image generator', () => {
|
|
61
|
+
const overlap = [...slugs('vision')].filter((s) => slugs('image_gen').has(s));
|
|
62
|
+
expect(overlap).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('gives every pool contiguous positions from 0', () => {
|
|
66
|
+
for (const pool of MODEL_POOLS) {
|
|
67
|
+
const positions = CURATED_MODEL_POOLS.filter((e) => e.pool === pool.id)
|
|
68
|
+
.map((e) => e.position)
|
|
69
|
+
.sort((a, b) => a - b);
|
|
70
|
+
if (positions.length === 0) continue;
|
|
71
|
+
expect(positions, pool.id).toEqual(positions.map((_, i) => i));
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
package/src/model-pools.ts
CHANGED
|
@@ -17,8 +17,33 @@ export type ModelPoolDef = {
|
|
|
17
17
|
description: string;
|
|
18
18
|
/** Which side of the split it belongs to. */
|
|
19
19
|
group: 'agents' | 'workers';
|
|
20
|
+
/** What the pool's consumer needs the model to DO, in catalog terms. */
|
|
21
|
+
modality: PoolModality;
|
|
20
22
|
};
|
|
21
23
|
|
|
24
|
+
/**
|
|
25
|
+
* A pool's modality contract, expressed the way OpenRouter's catalog does
|
|
26
|
+
* (`architecture.input_modalities` / `output_modalities`).
|
|
27
|
+
*
|
|
28
|
+
* This exists because of one specific trap: "Read images" and "Image
|
|
29
|
+
* generation" BOTH accept image input, so the input side alone cannot tell
|
|
30
|
+
* them apart. A generator like Nano Banana Pro (`google/gemini-3-pro-image`,
|
|
31
|
+
* `text+image->text+image`) looks like a perfect vision model on inputs
|
|
32
|
+
* alone, and a curator reading names picks it for the reader pool — where it
|
|
33
|
+
* bills image-generation tokens and hands back a picture instead of the text
|
|
34
|
+
* the vision worker parses. The OUTPUT side is the decider: an image READER
|
|
35
|
+
* is just a capable text-out model that happens to accept pictures.
|
|
36
|
+
*/
|
|
37
|
+
export type PoolModality = {
|
|
38
|
+
/** Modalities the model must ACCEPT. Empty = text-only is fine. */
|
|
39
|
+
input: readonly ('image' | 'file')[];
|
|
40
|
+
/** What the consumer reads back. `audio` pools live in the provider voice
|
|
41
|
+
* catalogs, not OpenRouter's chat catalog, so they are never checked. */
|
|
42
|
+
output: 'text' | 'image' | 'audio';
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const TEXT_OUT: PoolModality = { input: [], output: 'text' };
|
|
46
|
+
|
|
22
47
|
export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
23
48
|
{
|
|
24
49
|
id: 'agents',
|
|
@@ -26,6 +51,7 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
26
51
|
description:
|
|
27
52
|
'The shared premium pool: the assistant persona, team responder, and every specialist (pages, tables, coder, appsmith, researcher…). Frontier chat models with strong tool use.',
|
|
28
53
|
group: 'agents',
|
|
54
|
+
modality: TEXT_OUT,
|
|
29
55
|
},
|
|
30
56
|
{
|
|
31
57
|
id: 'extractor',
|
|
@@ -33,18 +59,21 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
33
59
|
description:
|
|
34
60
|
'Reads every ingested item and produces the summary, facts, and entities. Highest call volume in the system — needs cheap, fast, reliable structured output.',
|
|
35
61
|
group: 'workers',
|
|
62
|
+
modality: TEXT_OUT,
|
|
36
63
|
},
|
|
37
64
|
{
|
|
38
65
|
id: 'summarizer',
|
|
39
66
|
label: 'Summarizer',
|
|
40
67
|
description: 'Condenses text wherever a summary is needed. Cheap, fast workhorse.',
|
|
41
68
|
group: 'workers',
|
|
69
|
+
modality: TEXT_OUT,
|
|
42
70
|
},
|
|
43
71
|
{
|
|
44
72
|
id: 'reflector',
|
|
45
73
|
label: 'Reflector',
|
|
46
74
|
description: 'Periodic memory-reflection passes over recent activity. Cheap, fast workhorse.',
|
|
47
75
|
group: 'workers',
|
|
76
|
+
modality: TEXT_OUT,
|
|
48
77
|
},
|
|
49
78
|
{
|
|
50
79
|
id: 'document',
|
|
@@ -52,18 +81,21 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
52
81
|
description:
|
|
53
82
|
'Reads whole documents natively (PDF understanding). Needs a multimodal model that accepts document input.',
|
|
54
83
|
group: 'workers',
|
|
84
|
+
modality: TEXT_OUT,
|
|
55
85
|
},
|
|
56
86
|
{
|
|
57
87
|
id: 'vision',
|
|
58
88
|
label: 'Read images',
|
|
59
89
|
description: 'Pulls text and meaning out of images (uploads, extracted document images).',
|
|
60
90
|
group: 'workers',
|
|
91
|
+
modality: { input: ['image'], output: 'text' },
|
|
61
92
|
},
|
|
62
93
|
{
|
|
63
94
|
id: 'image_gen',
|
|
64
95
|
label: 'Image generation',
|
|
65
96
|
description: 'Generates images. Provider-specific catalog (Gemini image, DALL-E, …).',
|
|
66
97
|
group: 'workers',
|
|
98
|
+
modality: { input: [], output: 'image' },
|
|
67
99
|
},
|
|
68
100
|
{
|
|
69
101
|
id: 'tts',
|
|
@@ -71,6 +103,7 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
71
103
|
description:
|
|
72
104
|
'Turns replies into speech. Provider-specific catalog (Grok voice, GPT-4o TTS voices, ElevenLabs).',
|
|
73
105
|
group: 'workers',
|
|
106
|
+
modality: { input: [], output: 'audio' },
|
|
74
107
|
},
|
|
75
108
|
{
|
|
76
109
|
id: 'stt',
|
|
@@ -78,6 +111,7 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
78
111
|
description:
|
|
79
112
|
'Speech to text for voice notes and video ingest (Whisper family, grok-stt, gpt-4o-mini-transcribe).',
|
|
80
113
|
group: 'workers',
|
|
114
|
+
modality: { input: [], output: 'audio' },
|
|
81
115
|
},
|
|
82
116
|
{
|
|
83
117
|
id: 'search',
|
|
@@ -85,12 +119,14 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
85
119
|
description:
|
|
86
120
|
'The standard web-search answer tier. Must be a search-native model (Perplexity Sonar family).',
|
|
87
121
|
group: 'workers',
|
|
122
|
+
modality: TEXT_OUT,
|
|
88
123
|
},
|
|
89
124
|
{
|
|
90
125
|
id: 'search_advanced',
|
|
91
126
|
label: 'Deep web search',
|
|
92
127
|
description: 'The strong search tier for hard or conflicting questions (sonar-pro class).',
|
|
93
128
|
group: 'workers',
|
|
129
|
+
modality: TEXT_OUT,
|
|
94
130
|
},
|
|
95
131
|
{
|
|
96
132
|
id: 'narrator',
|
|
@@ -98,6 +134,7 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
98
134
|
description:
|
|
99
135
|
'Turns tool outcomes and events into short prose for the user. Off the critical path — cheap.',
|
|
100
136
|
group: 'workers',
|
|
137
|
+
modality: TEXT_OUT,
|
|
101
138
|
},
|
|
102
139
|
{
|
|
103
140
|
id: 'suggester',
|
|
@@ -105,7 +142,58 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
|
|
|
105
142
|
description:
|
|
106
143
|
'Generates the follow-up suggestion chips after a reply. Cheapest of all; has a fallback chain (suggester → narrator → summarizer).',
|
|
107
144
|
group: 'workers',
|
|
145
|
+
modality: TEXT_OUT,
|
|
108
146
|
},
|
|
109
147
|
];
|
|
110
148
|
|
|
111
149
|
export const MODEL_POOL_IDS = new Set(MODEL_POOLS.map((p) => p.id));
|
|
150
|
+
|
|
151
|
+
const POOL_BY_ID = new Map(MODEL_POOLS.map((p) => [p.id, p]));
|
|
152
|
+
|
|
153
|
+
/** One model's modalities as OpenRouter reports them. */
|
|
154
|
+
export type ModelModalities = {
|
|
155
|
+
input: readonly string[];
|
|
156
|
+
output: readonly string[];
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Does this model belong in this pool? Returns the reason it does NOT, or
|
|
161
|
+
* null when it fits.
|
|
162
|
+
*
|
|
163
|
+
* Fail-open by design (same rule as the worker-config catalog check): a
|
|
164
|
+
* `null` modalities argument means the catalog never loaded, and an outage
|
|
165
|
+
* must never block a curator from recording their judgment. Only positive
|
|
166
|
+
* catalog evidence rejects. Audio pools (tts/stt) are never checked — their
|
|
167
|
+
* models live in the provider voice catalogs, not OpenRouter's chat catalog,
|
|
168
|
+
* so any "evidence" about them here would be an absence, not a fact.
|
|
169
|
+
*/
|
|
170
|
+
export function poolModelIssue(
|
|
171
|
+
poolId: string,
|
|
172
|
+
modalities: ModelModalities | null | undefined,
|
|
173
|
+
): string | null {
|
|
174
|
+
const pool = POOL_BY_ID.get(poolId);
|
|
175
|
+
if (!pool || !modalities) return null;
|
|
176
|
+
const want = pool.modality;
|
|
177
|
+
if (want.output === 'audio') return null;
|
|
178
|
+
const outputs = modalities.output ?? [];
|
|
179
|
+
const inputs = modalities.input ?? [];
|
|
180
|
+
if (outputs.length === 0 && inputs.length === 0) return null;
|
|
181
|
+
|
|
182
|
+
const makesImages = outputs.includes('image');
|
|
183
|
+
if (want.output === 'text' && makesImages) {
|
|
184
|
+
return (
|
|
185
|
+
`this model OUTPUTS images (${outputs.join('+')}) — it is an image generator, ` +
|
|
186
|
+
`and the ${pool.label} pool needs a text-out model. Put it in the Image generation ` +
|
|
187
|
+
`pool instead. Reading images is just a capable text-out model that accepts pictures.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (want.output === 'image' && !makesImages && outputs.length > 0) {
|
|
191
|
+
return `this model does not output images (${outputs.join('+')}) — the ${pool.label} pool needs a generator.`;
|
|
192
|
+
}
|
|
193
|
+
for (const need of want.input) {
|
|
194
|
+
if (inputs.length > 0 && !inputs.includes(need)) {
|
|
195
|
+
return `this model does not accept ${need} input (accepts ${inputs.join('+')}) — the ${pool.label} pool needs one that does.`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
package/src/runners-types.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* browser bundle. The server-only data layer lives in `./runners.ts`.
|
|
6
6
|
*
|
|
7
7
|
* Everything here mirrors DBOS's WorkflowStatus / StepInfo (the system-DB
|
|
8
|
-
* execution journal); see
|
|
8
|
+
* execution journal); see server/api/src/runs.ts for the original server/api-side
|
|
9
9
|
* read layer this parallels.
|
|
10
10
|
*/
|
|
11
11
|
|
|
@@ -42,7 +42,7 @@ export type RunnerRun = {
|
|
|
42
42
|
name: string;
|
|
43
43
|
status: string;
|
|
44
44
|
queue?: string;
|
|
45
|
-
/** Which
|
|
45
|
+
/** Which server/api process executed it (helps when scaled out). */
|
|
46
46
|
executorId?: string;
|
|
47
47
|
appVersion?: string;
|
|
48
48
|
/** How many times DBOS has tried to recover this run after a crash. */
|
package/src/types/integrity.ts
CHANGED
|
@@ -80,7 +80,7 @@ export type LandedState =
|
|
|
80
80
|
| 'skipped'
|
|
81
81
|
/** Success but a layer is missing (silent-miss / dim drift / duplicate edges). */
|
|
82
82
|
| 'fail'
|
|
83
|
-
/** No extractor_run after the stall window — is
|
|
83
|
+
/** No extractor_run after the stall window — is server/api + an extractor up? */
|
|
84
84
|
| 'stalled';
|
|
85
85
|
|
|
86
86
|
export type LandedItem = {
|
|
@@ -141,7 +141,7 @@ export type AuditReport = {
|
|
|
141
141
|
// ─── system config integrity ────────────────────────────────────────────────
|
|
142
142
|
//
|
|
143
143
|
// Read-only check of the agent/skill/tool/worker CONFIG graph against the
|
|
144
|
-
// declarative manifest (
|
|
144
|
+
// declarative manifest (server/web/lib/system-manifest). Catches the silent-drop
|
|
145
145
|
// cases the runtime resolvers hide: an agent referencing a skill/tool that has
|
|
146
146
|
// no row, a specialist not wired into the persona's delegate_to, a default
|
|
147
147
|
// worker missing for a kind. Same severity vocabulary as the corpus audit.
|