@crossworks/client-types 0.232.309 → 0.232.314

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.309",
3
+ "version": "0.232.314",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/dto/access.ts CHANGED
@@ -37,8 +37,11 @@ export type MemberItemAuthor = { name: string; acceptedAt: string | null };
37
37
 
38
38
  export type AccessNodeView = {
39
39
  item: AccessItemView;
40
- /** What the item's link or embeds need: a page's files and drawings, a
41
- * folder's contents, a drawing's images. Each carries its own level. */
40
+ /** What the item's link or embeds need, each at its own level. For a page,
41
+ * drawing or note: what it embeds (images, files, drawings, child pages,
42
+ * transitively), which goes down with it when it is lowered (0.232.314
43
+ * on; before that it only did on "Lower them too"). For a folder: its
44
+ * contents, which keep their own levels. */
42
45
  closure: AccessItemView[];
43
46
  share: AccessLinkView | null;
44
47
  /** Descendant pages (pages only; 0 otherwise). */
@@ -47,17 +50,39 @@ export type AccessNodeView = {
47
50
  canLower: boolean;
48
51
  /** Whether the item can carry a link (a folder only under `files`). */
49
52
  canLink: boolean;
53
+ /** True for a page, drawing or note: lowering it lowers its embeds with it
54
+ * (embedding means sharing), so the control says what will be shared
55
+ * instead of offering "Lower them too". False for a folder, whose
56
+ * contents keep their levels. Absent from brains before 0.232.314, which
57
+ * lower embeds only on request. */
58
+ embedsFollow?: boolean;
50
59
  /** Set when a member wrote it and an admin accepted it into the brain (the
51
60
  * member-authored badge). Absent from brains before 0.232.285. */
52
61
  author?: MemberItemAuthor | null;
53
62
  };
54
63
 
55
- /** PATCH /api/access/nodes/:id { audience, withClosure?, raiseClosure? } */
64
+ /** One item that went down with the item that embeds it. */
65
+ export type AccessLoweredView = {
66
+ id: string;
67
+ type: string;
68
+ title: string;
69
+ from: AccessLevel;
70
+ to: AccessLevel;
71
+ };
72
+
73
+ /** PATCH /api/access/nodes/:id { audience, withClosure?, raiseClosure? }.
74
+ * `withClosure` only matters for a folder (its contents); a page's,
75
+ * drawing's or note's embeds always follow it down. */
56
76
  export type AccessNodeUpdate = {
57
77
  item: AccessItemView;
58
- /** Closure items lowered with it (only when `withClosure`). */
78
+ /** Everything lowered with it, at its new level: the embeds that followed
79
+ * it and, with `withClosure`, a folder's contents. */
59
80
  lowered: AccessItemView[];
60
- /** Closure items still above the new level (when not `withClosure`). */
81
+ /** The embeds that followed it down, with the level each left and took.
82
+ * Absent from brains before 0.232.314. */
83
+ alsoLowered?: AccessLoweredView[];
84
+ /** Closure items still above the new level: an embed that can never go
85
+ * below admin, or a folder's contents when not `withClosure`. */
61
86
  stillAbove: AccessItemView[];
62
87
  /** Closure items raised with it (only when `raiseClosure`). Absent from
63
88
  * brains before 0.232.264. */
package/src/index.ts CHANGED
@@ -37,6 +37,7 @@ export type {
37
37
  AccessItemView,
38
38
  AccessLevel,
39
39
  AccessLinkView,
40
+ AccessLoweredView,
40
41
  AccessNodeUpdate,
41
42
  AccessNodeView,
42
43
  MemberItemAuthor,
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ ASSISTANT_MODEL_CHOICES,
4
+ WORKER_MODEL_CHOICES,
5
+ formatUsdPerM,
6
+ templatePrice,
7
+ } from './model-choices';
8
+
9
+ describe('formatUsdPerM', () => {
10
+ it('rounds float noise from per-token × 1e6 to cents', () => {
11
+ expect(formatUsdPerM(0.13199999999999998)).toBe('$0.13');
12
+ expect(formatUsdPerM(0.5279999999999999)).toBe('$0.53');
13
+ expect(formatUsdPerM(2.0547999999999997)).toBe('$2.05');
14
+ expect(formatUsdPerM(1.5999999999999999)).toBe('$1.60');
15
+ });
16
+
17
+ it('drops .00 on whole dollars, like the hand-written heads', () => {
18
+ expect(formatUsdPerM(2)).toBe('$2');
19
+ expect(formatUsdPerM(15)).toBe('$15');
20
+ expect(formatUsdPerM(0)).toBe('$0');
21
+ });
22
+
23
+ it('keeps two significant figures under a cent instead of $0.00', () => {
24
+ expect(formatUsdPerM(0.0075)).toBe('$0.0075');
25
+ expect(formatUsdPerM(0.001234)).toBe('$0.0012');
26
+ });
27
+
28
+ it('marks an unknown side', () => {
29
+ expect(formatUsdPerM(null)).toBe('?');
30
+ expect(templatePrice({ inputPerM: 0.14, outputPerM: null })).toBe('$0.14 · ? /M');
31
+ });
32
+ });
33
+
34
+ describe('shipped model cards', () => {
35
+ it('never show a price with float noise', () => {
36
+ const noisy = [...ASSISTANT_MODEL_CHOICES, ...WORKER_MODEL_CHOICES]
37
+ .filter((c) => /\d\.\d{5,}/.test(c.price))
38
+ .map((c) => `${c.name}: ${c.price}`);
39
+ expect(noisy).toEqual([]);
40
+ });
41
+ });
@@ -118,7 +118,18 @@ const BASE_WORKER_CHOICES: readonly ModelChoice[] = [
118
118
  // template ships with the repo (model-pools-data.ts), so new installs offer
119
119
  // the full curated range with no network call.
120
120
 
121
- function templatePrice(
121
+ /** One $/M price for a card, in the hand-written heads' style: cents with
122
+ * whole dollars bare ("$2", "$1.60", "$0.13"), and two significant figures
123
+ * under a cent so a tiny price never rounds to "$0.00". The snapshot holds
124
+ * raw per-token × 1e6 floats (0.13199999999999998), so never print it bare. */
125
+ export function formatUsdPerM(v: number | null): string {
126
+ if (v == null || !Number.isFinite(v)) return '?';
127
+ if (v > 0 && v < 0.01) return `$${Number(v.toPrecision(2))}`;
128
+ const s = v.toFixed(2);
129
+ return `$${s.endsWith('.00') ? s.slice(0, -3) : s}`;
130
+ }
131
+
132
+ export function templatePrice(
122
133
  p: {
123
134
  inputPerM: number | null;
124
135
  outputPerM: number | null;
@@ -126,8 +137,7 @@ function templatePrice(
126
137
  ): string {
127
138
  if (!p || (p.inputPerM == null && p.outputPerM == null)) return 'price varies';
128
139
  if (p.inputPerM === 0 && p.outputPerM === 0) return 'Free';
129
- const f = (v: number | null) => (v == null ? '?' : `$${v}`);
130
- return `${f(p.inputPerM)} · ${f(p.outputPerM)} /M`;
140
+ return `${formatUsdPerM(p.inputPerM)} · ${formatUsdPerM(p.outputPerM)} /M`;
131
141
  }
132
142
 
133
143
  function extendFromPool(pool: string, base: readonly ModelChoice[]): readonly ModelChoice[] {
@@ -11,7 +11,7 @@
11
11
  }
12
12
  ],
13
13
  "pricing": {
14
- "inputPerM": 1.5999999999999999,
14
+ "inputPerM": 1.6,
15
15
  "outputPerM": 4.8,
16
16
  "currency": "USD",
17
17
  "capturedAt": "2026-09-22T14:41:05.911Z",
@@ -158,8 +158,8 @@
158
158
  }
159
159
  ],
160
160
  "pricing": {
161
- "inputPerM": 0.13199999999999998,
162
- "outputPerM": 0.5279999999999999,
161
+ "inputPerM": 0.132,
162
+ "outputPerM": 0.528,
163
163
  "currency": "USD",
164
164
  "capturedAt": "2026-09-22T14:41:05.911Z",
165
165
  "source": "openrouter"
@@ -179,8 +179,8 @@
179
179
  }
180
180
  ],
181
181
  "pricing": {
182
- "inputPerM": 0.6537999999999999,
183
- "outputPerM": 2.0547999999999997,
182
+ "inputPerM": 0.6538,
183
+ "outputPerM": 2.0548,
184
184
  "currency": "USD",
185
185
  "capturedAt": "2026-09-22T14:41:05.911Z",
186
186
  "source": "openrouter"
@@ -284,8 +284,8 @@
284
284
  }
285
285
  ],
286
286
  "pricing": {
287
- "inputPerM": 0.09999999999999999,
288
- "outputPerM": 0.19999999999999998,
287
+ "inputPerM": 0.1,
288
+ "outputPerM": 0.2,
289
289
  "currency": "USD",
290
290
  "capturedAt": "2026-09-22T14:41:05.911Z",
291
291
  "source": "openrouter"
@@ -305,8 +305,8 @@
305
305
  }
306
306
  ],
307
307
  "pricing": {
308
- "inputPerM": 0.13199999999999998,
309
- "outputPerM": 0.5279999999999999,
308
+ "inputPerM": 0.132,
309
+ "outputPerM": 0.528,
310
310
  "currency": "USD",
311
311
  "capturedAt": "2026-09-22T14:41:05.911Z",
312
312
  "source": "openrouter"
@@ -494,8 +494,8 @@
494
494
  }
495
495
  ],
496
496
  "pricing": {
497
- "inputPerM": 0.09999999999999999,
498
- "outputPerM": 0.19999999999999998,
497
+ "inputPerM": 0.1,
498
+ "outputPerM": 0.2,
499
499
  "currency": "USD",
500
500
  "capturedAt": "2026-09-22T14:41:05.911Z",
501
501
  "source": "openrouter"
@@ -515,8 +515,8 @@
515
515
  }
516
516
  ],
517
517
  "pricing": {
518
- "inputPerM": 0.13199999999999998,
519
- "outputPerM": 0.5279999999999999,
518
+ "inputPerM": 0.132,
519
+ "outputPerM": 0.528,
520
520
  "currency": "USD",
521
521
  "capturedAt": "2026-09-22T14:41:05.911Z",
522
522
  "source": "openrouter"
@@ -704,8 +704,8 @@
704
704
  }
705
705
  ],
706
706
  "pricing": {
707
- "inputPerM": 0.09999999999999999,
708
- "outputPerM": 0.19999999999999998,
707
+ "inputPerM": 0.1,
708
+ "outputPerM": 0.2,
709
709
  "currency": "USD",
710
710
  "capturedAt": "2026-09-22T14:41:05.911Z",
711
711
  "source": "openrouter"
@@ -725,8 +725,8 @@
725
725
  }
726
726
  ],
727
727
  "pricing": {
728
- "inputPerM": 0.13199999999999998,
729
- "outputPerM": 0.5279999999999999,
728
+ "inputPerM": 0.132,
729
+ "outputPerM": 0.528,
730
730
  "currency": "USD",
731
731
  "capturedAt": "2026-09-22T14:41:05.911Z",
732
732
  "source": "openrouter"
@@ -914,8 +914,8 @@
914
914
  }
915
915
  ],
916
916
  "pricing": {
917
- "inputPerM": 0.09999999999999999,
918
- "outputPerM": 0.19999999999999998,
917
+ "inputPerM": 0.1,
918
+ "outputPerM": 0.2,
919
919
  "currency": "USD",
920
920
  "capturedAt": "2026-09-22T14:41:05.911Z",
921
921
  "source": "openrouter"
@@ -935,8 +935,8 @@
935
935
  }
936
936
  ],
937
937
  "pricing": {
938
- "inputPerM": 0.13199999999999998,
939
- "outputPerM": 0.5279999999999999,
938
+ "inputPerM": 0.132,
939
+ "outputPerM": 0.528,
940
940
  "currency": "USD",
941
941
  "capturedAt": "2026-09-22T14:41:05.911Z",
942
942
  "source": "openrouter"
@@ -1040,8 +1040,8 @@
1040
1040
  }
1041
1041
  ],
1042
1042
  "pricing": {
1043
- "inputPerM": 0.09999999999999999,
1044
- "outputPerM": 0.19999999999999998,
1043
+ "inputPerM": 0.1,
1044
+ "outputPerM": 0.2,
1045
1045
  "currency": "USD",
1046
1046
  "capturedAt": "2026-09-22T14:41:05.911Z",
1047
1047
  "source": "openrouter"
@@ -1103,7 +1103,7 @@
1103
1103
  }
1104
1104
  ],
1105
1105
  "pricing": {
1106
- "inputPerM": 0.19999999999999998,
1106
+ "inputPerM": 0.2,
1107
1107
  "outputPerM": 1.2,
1108
1108
  "currency": "USD",
1109
1109
  "capturedAt": "2026-09-22T14:41:05.911Z",
@@ -1673,8 +1673,8 @@
1673
1673
  }
1674
1674
  ],
1675
1675
  "pricing": {
1676
- "inputPerM": 0.09999999999999999,
1677
- "outputPerM": 0.19999999999999998,
1676
+ "inputPerM": 0.1,
1677
+ "outputPerM": 0.2,
1678
1678
  "currency": "USD",
1679
1679
  "capturedAt": "2026-09-22T14:41:05.911Z",
1680
1680
  "source": "openrouter"
@@ -1694,8 +1694,8 @@
1694
1694
  }
1695
1695
  ],
1696
1696
  "pricing": {
1697
- "inputPerM": 0.13199999999999998,
1698
- "outputPerM": 0.5279999999999999,
1697
+ "inputPerM": 0.132,
1698
+ "outputPerM": 0.528,
1699
1699
  "currency": "USD",
1700
1700
  "capturedAt": "2026-09-22T14:41:05.911Z",
1701
1701
  "source": "openrouter"
@@ -1883,8 +1883,8 @@
1883
1883
  }
1884
1884
  ],
1885
1885
  "pricing": {
1886
- "inputPerM": 0.09999999999999999,
1887
- "outputPerM": 0.19999999999999998,
1886
+ "inputPerM": 0.1,
1887
+ "outputPerM": 0.2,
1888
1888
  "currency": "USD",
1889
1889
  "capturedAt": "2026-09-22T14:41:05.911Z",
1890
1890
  "source": "openrouter"
@@ -1904,8 +1904,8 @@
1904
1904
  }
1905
1905
  ],
1906
1906
  "pricing": {
1907
- "inputPerM": 0.13199999999999998,
1908
- "outputPerM": 0.5279999999999999,
1907
+ "inputPerM": 0.132,
1908
+ "outputPerM": 0.528,
1909
1909
  "currency": "USD",
1910
1910
  "capturedAt": "2026-09-22T14:41:05.911Z",
1911
1911
  "source": "openrouter"
@@ -2030,7 +2030,7 @@
2030
2030
  }
2031
2031
  ],
2032
2032
  "pricing": {
2033
- "inputPerM": 0.041999999999999996,
2033
+ "inputPerM": 0.042,
2034
2034
  "outputPerM": 0,
2035
2035
  "currency": "USD",
2036
2036
  "capturedAt": "2026-09-22T14:41:05.911Z",
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { PURPOSE_MAX_CHARS, purposeTooLongError } from './purpose-limits';
3
+
4
+ describe('PURPOSE_MAX_CHARS', () => {
5
+ it('is 600, the ceiling the identity block renders', () => {
6
+ expect(PURPOSE_MAX_CHARS).toBe(600);
7
+ });
8
+ });
9
+
10
+ describe('purposeTooLongError', () => {
11
+ it('names the length, the limit, and where the personality lives', () => {
12
+ const msg = purposeTooLongError(3888);
13
+ expect(msg).toContain('3,888');
14
+ expect(msg).toContain('600');
15
+ expect(msg).toContain('Agent Studio');
16
+ });
17
+ });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The size ceiling on the brain's purpose (`preferences.purpose`), shared by
3
+ * the onboarding wizard, Settings → Profile, the routes that save it, and the
4
+ * identity block that injects it into every turn.
5
+ *
6
+ * It used to be three private copies of `600`, and both routes silently
7
+ * `.slice()`d anything longer. On 2026-09-28 a user pasted a 3,888-character
8
+ * assistant persona prompt into the onboarding purpose box: 3,288 characters
9
+ * vanished without a word and the assistant kept its default personality. The
10
+ * routes now refuse an over-long purpose with a 400 and this message, and the
11
+ * UI counts against the same number.
12
+ */
13
+
14
+ /** Max characters of the brain's purpose, measured AFTER trimming. */
15
+ export const PURPOSE_MAX_CHARS = 600;
16
+
17
+ /** The 400 message for a purpose over the limit. */
18
+ export function purposeTooLongError(length: number): string {
19
+ return (
20
+ `The purpose is ${length.toLocaleString('en-US')} characters; the limit is ` +
21
+ `${PURPOSE_MAX_CHARS.toLocaleString('en-US')}. Keep it to one or two sentences on what ` +
22
+ 'this brain is for. The assistant’s personality is set in Agent Studio or ' +
23
+ 'Settings → Agents, not here.'
24
+ );
25
+ }