@crossworks/client-types 0.232.308 → 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 +1 -1
- package/src/dto/access.ts +30 -5
- package/src/index.ts +1 -0
- package/src/model-choices.test.ts +41 -0
- package/src/model-choices.ts +13 -3
- package/src/model-pools-data.json +33 -33
- package/src/purpose-limits.test.ts +17 -0
- package/src/purpose-limits.ts +25 -0
package/package.json
CHANGED
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
|
|
41
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
@@ -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
|
+
});
|
package/src/model-choices.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
162
|
-
"outputPerM": 0.
|
|
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.
|
|
183
|
-
"outputPerM": 2.
|
|
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.
|
|
288
|
-
"outputPerM": 0.
|
|
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.
|
|
309
|
-
"outputPerM": 0.
|
|
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.
|
|
498
|
-
"outputPerM": 0.
|
|
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.
|
|
519
|
-
"outputPerM": 0.
|
|
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.
|
|
708
|
-
"outputPerM": 0.
|
|
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.
|
|
729
|
-
"outputPerM": 0.
|
|
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.
|
|
918
|
-
"outputPerM": 0.
|
|
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.
|
|
939
|
-
"outputPerM": 0.
|
|
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.
|
|
1044
|
-
"outputPerM": 0.
|
|
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.
|
|
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.
|
|
1677
|
-
"outputPerM": 0.
|
|
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.
|
|
1698
|
-
"outputPerM": 0.
|
|
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.
|
|
1887
|
-
"outputPerM": 0.
|
|
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.
|
|
1908
|
-
"outputPerM": 0.
|
|
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.
|
|
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
|
+
}
|