@crossworks/client-types 0.232.207 → 0.232.210

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossworks/client-types",
3
- "version": "0.232.207",
3
+ "version": "0.232.210",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -197,7 +197,8 @@ export type AiWorkerKind =
197
197
  | 'search'
198
198
  | 'search_advanced'
199
199
  | 'narrator'
200
- | 'suggester';
200
+ | 'suggester'
201
+ | 'decider';
201
202
 
202
203
  /** An AI worker as returned by `GET /api/ai-workers`. `params` is jsonb (shape
203
204
  * varies by kind) — kept loose here; the form narrows per kind. */
package/src/dto/rows.ts CHANGED
@@ -690,6 +690,18 @@ export type ContextSnapshot = {
690
690
  };
691
691
  personaNotes: { count: number };
692
692
  corpusMap: { count: number; truncated: boolean };
693
+ /** The decider's `context_pruning` use, when it ran on this turn. In
694
+ * `shadow` the counts say what WOULD have been dropped; in `live` they were.
695
+ * Absent when the use is off or the call failed (nothing changed). */
696
+ pruning?: {
697
+ mode: 'shadow' | 'live';
698
+ threshold: number;
699
+ wouldDrop: { facts: number; contentHits: number; chunkHits: number };
700
+ /** Characters of item text under the threshold (≈ tokens × 4). */
701
+ charsSaved: number;
702
+ ms: number;
703
+ cached: boolean;
704
+ };
693
705
  };
694
706
 
695
707
  export type BackupFrequency = 'daily' | 'weekly';
@@ -2190,5 +2190,26 @@
2190
2190
  },
2191
2191
  "rating": 2,
2192
2192
  "note": "FREE, 128K context, real vision-language model (not just text); rate-limited, best for low-stakes/high-volume extraction"
2193
+ },
2194
+ {
2195
+ "pool": "decider",
2196
+ "position": 0,
2197
+ "name": "TypeSafe: Jev 1.13",
2198
+ "vendor": "TypeSafe",
2199
+ "routes": [
2200
+ {
2201
+ "provider": "openrouter",
2202
+ "model": "typesafe/jev-1.13"
2203
+ }
2204
+ ],
2205
+ "pricing": {
2206
+ "inputPerM": 0.042,
2207
+ "outputPerM": 0,
2208
+ "currency": "USD",
2209
+ "capturedAt": "2026-09-22",
2210
+ "source": "openrouter"
2211
+ },
2212
+ "rating": null,
2213
+ "note": "The only typed-decision model on OpenRouter (alpha decisions endpoint). Hand-added 2026-09-22 with the decider worker; re-curate at /models/pools when a second one ships."
2193
2214
  }
2194
2215
  ]
@@ -132,3 +132,26 @@ describe('the shipped curated template', () => {
132
132
  }
133
133
  });
134
134
  });
135
+
136
+ describe('poolModelIssue: the decider pool', () => {
137
+ it('accepts a decisions-out model and rejects a chat model', () => {
138
+ expect(poolModelIssue('decider', { input: ['text'], output: ['decisions'] })).toBeNull();
139
+ expect(poolModelIssue('decider', { input: ['text'], output: ['text'] })).toMatch(
140
+ /not decisions/,
141
+ );
142
+ });
143
+
144
+ it('keeps a decisions-out model out of every text pool', () => {
145
+ expect(poolModelIssue('summarizer', { input: ['text'], output: ['decisions'] })).toMatch(
146
+ /belongs in the Decider pool/,
147
+ );
148
+ expect(poolModelIssue('agents', { input: ['text'], output: ['decisions'] })).toMatch(
149
+ /belongs in the Decider pool/,
150
+ );
151
+ });
152
+
153
+ it('stays fail-open with no catalog evidence', () => {
154
+ expect(poolModelIssue('decider', null)).toBeNull();
155
+ expect(poolModelIssue('decider', { input: [], output: [] })).toBeNull();
156
+ });
157
+ });
@@ -43,7 +43,7 @@ export type PoolModality = {
43
43
  * a voice route was always "unknown"); since the fetch asks for
44
44
  * `output_modalities=all` they carry positive evidence like everything
45
45
  * else. */
46
- output: 'text' | 'image' | 'speech' | 'transcription';
46
+ output: 'text' | 'image' | 'speech' | 'transcription' | 'decisions';
47
47
  };
48
48
 
49
49
  const TEXT_OUT: PoolModality = { input: [], output: 'text' };
@@ -148,6 +148,14 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
148
148
  group: 'workers',
149
149
  modality: TEXT_OUT,
150
150
  },
151
+ {
152
+ id: 'decider',
153
+ label: 'Decider (typed decisions)',
154
+ description:
155
+ 'Typed-decision models (TypeSafe Jev): choice / score / yes-no answers with probabilities, no prose. Behind the experimental per-use switches (passage scoring first). Output modality `decisions`, so no chat model fits here and it fits nowhere else.',
156
+ group: 'workers',
157
+ modality: { input: [], output: 'decisions' },
158
+ },
151
159
  ];
152
160
 
153
161
  export const MODEL_POOL_IDS = new Set(MODEL_POOLS.map((p) => p.id));
@@ -210,6 +218,18 @@ export function poolModelIssue(
210
218
  if (outputs.length === 0 && inputs.length === 0) return null;
211
219
 
212
220
  const makesImages = outputs.includes('image');
221
+ // The decider pool is the one place a `decisions`-out model belongs — and
222
+ // the one place a text-out chat model does not: a chat model answers a
223
+ // decisions request with nothing usable, and Jev answers a chat request
224
+ // with nothing at all.
225
+ if (want.output === 'decisions') {
226
+ return outputs.length > 0 && !outputs.includes('decisions')
227
+ ? `this model outputs ${outputs.join('+')}, not decisions — the ${pool.label} pool needs a typed-decision model such as typesafe/jev-1.13.`
228
+ : null;
229
+ }
230
+ if (want.output === 'text' && outputs.includes('decisions')) {
231
+ return `this model outputs decisions, not text — it belongs in the Decider pool, not ${pool.label}.`;
232
+ }
213
233
  if (want.output === 'text' && makesImages) {
214
234
  return (
215
235
  `this model OUTPUTS images (${outputs.join('+')}) — it is an image generator, ` +