dsh-vision-router 2.3.0 → 3.0.1
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/README.md +18 -3
- package/README.zh.md +16 -3
- package/docs/architecture/3.0-architecture-plan.md +252 -0
- package/docs/architecture/3.0-r0-contract-ledger.md +137 -0
- package/docs/architecture/3.0-r1-source-artifact-boundary.md +98 -0
- package/docs/architecture/3.0-r10-final-correctness-closure.md +207 -0
- package/docs/architecture/3.0-r11-adversarial-review.md +447 -0
- package/docs/architecture/3.0-r5-browser-go-no-go.md +165 -0
- package/docs/architecture/3.0-r5-browser-typescript-gate.md +105 -0
- package/docs/architecture/3.0-r6-compat-retirement.md +203 -0
- package/docs/architecture/3.0-r7-public-api.md +151 -0
- package/docs/architecture/3.0-r8-ci-release.md +248 -0
- package/docs/architecture/3.0-r9-final-audit.md +370 -0
- package/docs/architecture/dsh-support-window.md +13 -3
- package/docs/releases/v3.0.0.md +85 -0
- package/docs/releases/v3.0.1.md +77 -0
- package/entry.js +1 -1
- package/index.js +110 -71
- package/lib/agent-request-route-authority.js +1 -20
- package/lib/core-apply-lifecycle.js +37 -43
- package/lib/core-cache.js +87 -0
- package/lib/core-primitives.js +11 -144
- package/lib/core-vision-surface-domain.js +63 -0
- package/lib/core-vision-surface.js +11 -77
- package/lib/delegated-call-config.js +15 -24
- package/lib/doctor-cli.js +0 -0
- package/lib/doctor-runtime.js +4 -1
- package/lib/dsh-contract-compat.js +26 -230
- package/lib/dsh-host-capabilities.js +3 -9
- package/lib/dsh-settings-017-compat.js +17 -10
- package/lib/file-logger.js +8 -3
- package/lib/grounding-coordinate-runtime.js +1 -2
- package/lib/http-body-limit.js +97 -79
- package/lib/image-offload-compat.js +30 -21
- package/lib/image-resource-governor.js +210 -228
- package/lib/legacy-global-proxy-boundary.js +46 -3
- package/lib/live-model-client-prelude.js +12 -4
- package/lib/live-model-discovery.js +134 -43
- package/lib/local-vision-stabilizer.js +33 -18
- package/lib/maintenance-routes.js +1 -3
- package/lib/native-image-coexistence.js +15 -3
- package/lib/ollama-cold-start.js +87 -23
- package/lib/pi-ai-bridge-wire-compat.js +3 -2
- package/lib/proxy-dispatcher-pool.js +151 -92
- package/lib/proxy-routing.js +75 -51
- package/lib/proxy-url-compat.js +3 -10
- package/lib/public-config.js +23 -44
- package/lib/public-entry.d.ts +148 -0
- package/lib/public-entry.js +17 -4
- package/lib/replay-delegation.js +12 -7
- package/lib/replay-envelope-v2-compat.js +12 -7
- package/lib/runtime-composition.js +30 -10
- package/lib/screenshot-source-boundary.js +2 -2
- package/lib/session-affinity-runtime.js +113 -97
- package/lib/session-affinity.js +89 -76
- package/lib/session-event-history-compat.js +30 -0
- package/lib/session-query-readers.js +263 -0
- package/lib/session-surface-compat.js +30 -31
- package/lib/session-surface-policy-domain.js +85 -0
- package/lib/session-surface-policy.js +2 -111
- package/lib/session-turn-resolver.js +2 -6
- package/lib/session-vision-attachment-index.js +143 -0
- package/lib/session-vision-delegation-boundary.js +34 -0
- package/lib/session-vision-delegation.js +22 -0
- package/lib/session-vision-index.js +25 -107
- package/lib/session-vision-mode-authority.js +229 -192
- package/lib/session-vision-mode-boundary.js +65 -22
- package/lib/session-vision-policy-domain.js +304 -0
- package/lib/session-vision-policy.js +104 -0
- package/lib/session-vision-state.js +368 -378
- package/lib/settings-client-017-compat.js +16 -10
- package/lib/structured-flow-hardening.js +22 -4
- package/lib/tesseract-exec-compat.js +9 -1
- package/lib/turn-budget-context.js +47 -41
- package/lib/update-check.js +11 -2
- package/lib/vision-attachment-handle-runtime.js +176 -156
- package/lib/vision-backend-runtime-policy.js +16 -0
- package/lib/vision-background-benchmark.js +55 -6
- package/lib/vision-background-failure-policy.js +41 -41
- package/lib/vision-background-stop-store-core.js +217 -0
- package/lib/vision-background-stop-store.js +8 -179
- package/lib/vision-capability-benchmark.js +11 -61
- package/lib/vision-capability-evidence.js +35 -0
- package/lib/vision-capability-fingerprint.js +72 -0
- package/lib/vision-capability-identity.js +116 -61
- package/lib/vision-capability-router.js +13 -33
- package/lib/vision-chunk-assembler.js +95 -0
- package/lib/vision-diagnostics-routes.js +42 -4
- package/lib/vision-execution-order-apply.js +45 -55
- package/lib/vision-execution-order.js +19 -19
- package/lib/vision-execution-policy.js +12 -7
- package/lib/vision-model-registry.js +30 -17
- package/lib/vision-model-visibility-boundary-main.js +15 -16
- package/lib/vision-provider-transport.js +137 -147
- package/lib/vision-routing-authority.js +47 -48
- package/lib/vision-routing-product.js +26 -29
- package/lib/vision-routing-settings-prelude.js +8 -3
- package/lib/vision-runtime-performance-store.js +166 -0
- package/lib/vision-runtime-performance.js +13 -142
- package/lib/vision-tool-runtime-boundary.js +99 -30
- package/lib/web/dsh-settings-017-root-transport.js +27 -12
- package/lib/web/remote-settings-client.js +4 -7
- package/lib/web-capability-boundary.js +17 -7
- package/lib/wrapper-directory.js +14 -23
- package/package.json +37 -11
|
@@ -1,404 +1,394 @@
|
|
|
1
1
|
const DEFAULTS = Object.freeze({
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
maxSessions: 64,
|
|
3
|
+
idleTtlMs: 60 * 60 * 1000,
|
|
4
|
+
descriptionMaxEntries: 64,
|
|
5
|
+
descriptionMaxChars: 256 * 1024,
|
|
6
|
+
attachmentMaxEntries: 256,
|
|
7
|
+
});
|
|
8
|
+
const knownSessionMemoryViews = new WeakMap();
|
|
9
|
+
function objectRecord(value) {
|
|
10
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
11
|
+
? value
|
|
12
|
+
: undefined;
|
|
13
|
+
}
|
|
15
14
|
function isSessionKey(value) {
|
|
16
|
-
|
|
15
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function');
|
|
16
|
+
}
|
|
17
|
+
function attachmentRef(value) {
|
|
18
|
+
return objectRecord(value) ? value : undefined;
|
|
19
|
+
}
|
|
20
|
+
function attachmentIdOf(value) {
|
|
21
|
+
const record = objectRecord(value);
|
|
22
|
+
return record?.attachmentId ?? record?.id;
|
|
17
23
|
}
|
|
18
|
-
|
|
19
24
|
export function knownSessionVisionMemory(session) {
|
|
20
|
-
|
|
25
|
+
return isSessionKey(session) ? knownSessionMemoryViews.get(session) : undefined;
|
|
21
26
|
}
|
|
22
|
-
|
|
23
27
|
class WeightedLruMap {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
28
|
+
maxEntries;
|
|
29
|
+
maxWeight;
|
|
30
|
+
weightOf;
|
|
31
|
+
entries = new Map();
|
|
32
|
+
weight = 0;
|
|
33
|
+
constructor({ maxEntries, maxWeight = Infinity, weightOf = () => 1, } = {}) {
|
|
34
|
+
this.maxEntries = Math.max(1, Math.floor(Number(maxEntries) || 1));
|
|
35
|
+
this.maxWeight = Number.isFinite(Number(maxWeight)) && Number(maxWeight) >= 0
|
|
36
|
+
? Number(maxWeight)
|
|
37
|
+
: Infinity;
|
|
38
|
+
this.weightOf = typeof weightOf === 'function' ? weightOf : () => 1;
|
|
39
|
+
}
|
|
40
|
+
_weight(value, key) {
|
|
41
|
+
const weight = Number(this.weightOf(value, key));
|
|
42
|
+
return Number.isFinite(weight) && weight > 0 ? weight : 0;
|
|
43
|
+
}
|
|
44
|
+
get(key) {
|
|
45
|
+
const entry = this.entries.get(key);
|
|
46
|
+
if (entry === undefined)
|
|
47
|
+
return undefined;
|
|
48
|
+
this.entries.delete(key);
|
|
49
|
+
this.entries.set(key, entry);
|
|
50
|
+
return entry.value;
|
|
51
|
+
}
|
|
52
|
+
peek(key) {
|
|
53
|
+
return this.entries.get(key)?.value;
|
|
54
|
+
}
|
|
55
|
+
has(key) {
|
|
56
|
+
return this.entries.has(key);
|
|
57
|
+
}
|
|
58
|
+
set(key, value) {
|
|
59
|
+
const old = this.entries.get(key);
|
|
60
|
+
if (old !== undefined) {
|
|
61
|
+
this.weight -= old.weight;
|
|
62
|
+
this.entries.delete(key);
|
|
63
|
+
}
|
|
64
|
+
const weight = this._weight(value, key);
|
|
65
|
+
this.entries.set(key, { value, weight });
|
|
66
|
+
this.weight += weight;
|
|
67
|
+
this._trim();
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
delete(key) {
|
|
71
|
+
const entry = this.entries.get(key);
|
|
72
|
+
if (entry === undefined)
|
|
73
|
+
return false;
|
|
74
|
+
this.weight -= entry.weight;
|
|
75
|
+
return this.entries.delete(key);
|
|
76
|
+
}
|
|
77
|
+
clear() {
|
|
78
|
+
this.entries.clear();
|
|
79
|
+
this.weight = 0;
|
|
80
|
+
}
|
|
81
|
+
_trim() {
|
|
82
|
+
while (this.entries.size > this.maxEntries || this.weight > this.maxWeight) {
|
|
83
|
+
const oldest = this.entries.keys().next().value;
|
|
84
|
+
if (oldest === undefined)
|
|
85
|
+
break;
|
|
86
|
+
this.delete(oldest);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
get size() {
|
|
90
|
+
return this.entries.size;
|
|
91
|
+
}
|
|
92
|
+
keys() {
|
|
93
|
+
return this.entries.keys();
|
|
58
94
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
delete(key) {
|
|
67
|
-
const entry = this.entries.get(key)
|
|
68
|
-
if (entry === undefined) return false
|
|
69
|
-
this.weight -= entry.weight
|
|
70
|
-
return this.entries.delete(key)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
clear() {
|
|
74
|
-
this.entries.clear()
|
|
75
|
-
this.weight = 0
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
_trim() {
|
|
79
|
-
while (this.entries.size > this.maxEntries || this.weight > this.maxWeight) {
|
|
80
|
-
const oldest = this.entries.keys().next().value
|
|
81
|
-
if (oldest === undefined) break
|
|
82
|
-
this.delete(oldest)
|
|
95
|
+
values() {
|
|
96
|
+
return [...this.entries.values()].map((entry) => entry.value).values();
|
|
97
|
+
}
|
|
98
|
+
entriesIterator() {
|
|
99
|
+
return [...this.entries.entries()].map(([key, entry]) => [key, entry.value]).values();
|
|
83
100
|
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
get size() {
|
|
87
|
-
return this.entries.size
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
keys() {
|
|
91
|
-
return this.entries.keys()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
values() {
|
|
95
|
-
return [...this.entries.values()].map((entry) => entry.value).values()
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
entriesIterator() {
|
|
99
|
-
return [...this.entries.entries()].map(([key, entry]) => [key, entry.value]).values()
|
|
100
|
-
}
|
|
101
101
|
}
|
|
102
|
-
|
|
103
102
|
function normalizeId(value) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
if (value === undefined || value === null)
|
|
104
|
+
return undefined;
|
|
105
|
+
const id = String(value);
|
|
106
|
+
return id === '' ? undefined : id;
|
|
107
107
|
}
|
|
108
|
-
|
|
109
108
|
function textWeight(value) {
|
|
110
|
-
|
|
109
|
+
return typeof value === 'string' ? value.length : String(value ?? '').length;
|
|
111
110
|
}
|
|
112
|
-
|
|
113
111
|
function createState(key, stable, options, now) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
112
|
+
return {
|
|
113
|
+
key,
|
|
114
|
+
stable,
|
|
115
|
+
lastAccessAt: now,
|
|
116
|
+
descriptions: new WeightedLruMap({
|
|
117
|
+
maxEntries: options.descriptionMaxEntries,
|
|
118
|
+
maxWeight: options.descriptionMaxChars,
|
|
119
|
+
weightOf: textWeight,
|
|
120
|
+
}),
|
|
121
|
+
attachments: new WeightedLruMap({
|
|
122
|
+
maxEntries: options.attachmentMaxEntries,
|
|
123
|
+
}),
|
|
124
|
+
};
|
|
127
125
|
}
|
|
128
|
-
|
|
129
126
|
class SessionMemoryView extends Map {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
get size() {
|
|
158
|
-
return this.store.stateStats(this.session)?.descriptions ?? 0
|
|
159
|
-
}
|
|
127
|
+
store;
|
|
128
|
+
session;
|
|
129
|
+
constructor(store, session) {
|
|
130
|
+
super();
|
|
131
|
+
this.store = store;
|
|
132
|
+
this.session = session;
|
|
133
|
+
}
|
|
134
|
+
get(key) {
|
|
135
|
+
return this.store.getDescription(this.session, key);
|
|
136
|
+
}
|
|
137
|
+
has(key) {
|
|
138
|
+
return this.store.hasDescription(this.session, key);
|
|
139
|
+
}
|
|
140
|
+
set(key, value) {
|
|
141
|
+
this.store.setDescription(this.session, key, value);
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
delete(key) {
|
|
145
|
+
return this.store.deleteDescription(this.session, key);
|
|
146
|
+
}
|
|
147
|
+
clear() {
|
|
148
|
+
this.store.clearDescriptions(this.session);
|
|
149
|
+
}
|
|
150
|
+
get size() {
|
|
151
|
+
return this.store.stateStats(this.session)?.descriptions ?? 0;
|
|
152
|
+
}
|
|
160
153
|
}
|
|
161
|
-
|
|
162
154
|
class DescriptionFacade extends Map {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
get(key) {
|
|
169
|
-
const state = this.store.uniqueStableOwner(key)
|
|
170
|
-
return state?.descriptions.get(String(key))
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
has(key) {
|
|
174
|
-
const state = this.store.uniqueStableOwner(key)
|
|
175
|
-
return state?.descriptions.has(String(key)) === true
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
set(key, value) {
|
|
179
|
-
const state = this.store.uniqueStableOwner(key)
|
|
180
|
-
if (state !== undefined) {
|
|
181
|
-
state.descriptions.set(String(key), value)
|
|
182
|
-
this.store.touchState(state)
|
|
155
|
+
store;
|
|
156
|
+
constructor(store) {
|
|
157
|
+
super();
|
|
158
|
+
this.store = store;
|
|
183
159
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
delete(key) {
|
|
188
|
-
const state = this.store.uniqueStableOwner(key)
|
|
189
|
-
if (state === undefined) return false
|
|
190
|
-
this.store.touchState(state)
|
|
191
|
-
return state.descriptions.delete(String(key))
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
clear() {
|
|
195
|
-
for (const state of this.store.stableStates()) state.descriptions.clear()
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
get size() {
|
|
199
|
-
let size = 0
|
|
200
|
-
for (const state of this.store.stableStates()) size += state.descriptions.size
|
|
201
|
-
return size
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function createSessionVisionStateStore(config = {}) {
|
|
206
|
-
const options = {
|
|
207
|
-
maxSessions: Math.max(1, Math.floor(Number(config.maxSessions) || DEFAULTS.maxSessions)),
|
|
208
|
-
idleTtlMs:
|
|
209
|
-
Number.isFinite(Number(config.idleTtlMs)) && Number(config.idleTtlMs) >= 0
|
|
210
|
-
? Number(config.idleTtlMs)
|
|
211
|
-
: DEFAULTS.idleTtlMs,
|
|
212
|
-
descriptionMaxEntries: Math.max(
|
|
213
|
-
1,
|
|
214
|
-
Math.floor(Number(config.descriptionMaxEntries) || DEFAULTS.descriptionMaxEntries),
|
|
215
|
-
),
|
|
216
|
-
descriptionMaxChars: Math.max(
|
|
217
|
-
1,
|
|
218
|
-
Math.floor(Number(config.descriptionMaxChars) || DEFAULTS.descriptionMaxChars),
|
|
219
|
-
),
|
|
220
|
-
attachmentMaxEntries: Math.max(
|
|
221
|
-
1,
|
|
222
|
-
Math.floor(Number(config.attachmentMaxEntries) || DEFAULTS.attachmentMaxEntries),
|
|
223
|
-
),
|
|
224
|
-
}
|
|
225
|
-
const now = typeof config.now === 'function' ? config.now : Date.now
|
|
226
|
-
const statesById = new Map()
|
|
227
|
-
const weakStates = new WeakMap()
|
|
228
|
-
|
|
229
|
-
const prune = () => {
|
|
230
|
-
const cutoff = options.idleTtlMs <= 0 ? -Infinity : now() - options.idleTtlMs
|
|
231
|
-
for (const [key, state] of statesById) {
|
|
232
|
-
if (state.lastAccessAt < cutoff) statesById.delete(key)
|
|
160
|
+
get(key) {
|
|
161
|
+
const state = this.store.uniqueStableOwner(key);
|
|
162
|
+
return state?.descriptions.get(String(key));
|
|
233
163
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
statesById.delete(oldest)
|
|
164
|
+
has(key) {
|
|
165
|
+
const state = this.store.uniqueStableOwner(key);
|
|
166
|
+
return state?.descriptions.has(String(key)) === true;
|
|
238
167
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
statesById.set(state.key, state)
|
|
168
|
+
set(key, value) {
|
|
169
|
+
const state = this.store.uniqueStableOwner(key);
|
|
170
|
+
if (state !== undefined) {
|
|
171
|
+
state.descriptions.set(String(key), value);
|
|
172
|
+
this.store.touchState(state);
|
|
173
|
+
}
|
|
174
|
+
return this;
|
|
247
175
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const id = normalizeId(session.id)
|
|
255
|
-
if (id !== undefined) {
|
|
256
|
-
let state = statesById.get(id)
|
|
257
|
-
if (state === undefined && create) {
|
|
258
|
-
state = createState(id, true, options, now())
|
|
259
|
-
statesById.set(id, state)
|
|
260
|
-
prune()
|
|
261
|
-
}
|
|
262
|
-
if (state !== undefined) touchState(state)
|
|
263
|
-
return state
|
|
176
|
+
delete(key) {
|
|
177
|
+
const state = this.store.uniqueStableOwner(key);
|
|
178
|
+
if (state === undefined)
|
|
179
|
+
return false;
|
|
180
|
+
this.store.touchState(state);
|
|
181
|
+
return state.descriptions.delete(String(key));
|
|
264
182
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
weakStates.set(session, state)
|
|
183
|
+
clear() {
|
|
184
|
+
for (const state of this.store.stableStates())
|
|
185
|
+
state.descriptions.clear();
|
|
269
186
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
prune()
|
|
276
|
-
return [...statesById.values()]
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const uniqueStableOwner = (attachmentId) => {
|
|
280
|
-
const id = normalizeId(attachmentId)
|
|
281
|
-
if (id === undefined) return undefined
|
|
282
|
-
let owner
|
|
283
|
-
for (const state of stableStates()) {
|
|
284
|
-
if (!state.attachments.has(id) && !state.descriptions.has(id)) continue
|
|
285
|
-
if (owner !== undefined && owner !== state) return undefined
|
|
286
|
-
owner = state
|
|
187
|
+
get size() {
|
|
188
|
+
let size = 0;
|
|
189
|
+
for (const state of this.store.stableStates())
|
|
190
|
+
size += state.descriptions.size;
|
|
191
|
+
return size;
|
|
287
192
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
193
|
+
}
|
|
194
|
+
export function createSessionVisionStateStore(config = {}) {
|
|
195
|
+
const source = objectRecord(config) ?? {};
|
|
196
|
+
const options = {
|
|
197
|
+
maxSessions: Math.max(1, Math.floor(Number(source.maxSessions) || DEFAULTS.maxSessions)),
|
|
198
|
+
idleTtlMs: Number.isFinite(Number(source.idleTtlMs)) && Number(source.idleTtlMs) >= 0
|
|
199
|
+
? Number(source.idleTtlMs)
|
|
200
|
+
: DEFAULTS.idleTtlMs,
|
|
201
|
+
descriptionMaxEntries: Math.max(1, Math.floor(Number(source.descriptionMaxEntries) || DEFAULTS.descriptionMaxEntries)),
|
|
202
|
+
descriptionMaxChars: Math.max(1, Math.floor(Number(source.descriptionMaxChars) || DEFAULTS.descriptionMaxChars)),
|
|
203
|
+
attachmentMaxEntries: Math.max(1, Math.floor(Number(source.attachmentMaxEntries) || DEFAULTS.attachmentMaxEntries)),
|
|
204
|
+
};
|
|
205
|
+
const now = typeof source.now === 'function'
|
|
206
|
+
? source.now
|
|
207
|
+
: Date.now;
|
|
208
|
+
const statesById = new Map();
|
|
209
|
+
const weakStates = new WeakMap();
|
|
210
|
+
const prune = () => {
|
|
211
|
+
const cutoff = options.idleTtlMs <= 0 ? -Infinity : now() - options.idleTtlMs;
|
|
212
|
+
for (const [key, state] of statesById) {
|
|
213
|
+
if (state.lastAccessAt < cutoff)
|
|
214
|
+
statesById.delete(key);
|
|
215
|
+
}
|
|
216
|
+
while (statesById.size > options.maxSessions) {
|
|
217
|
+
const oldest = statesById.keys().next().value;
|
|
218
|
+
if (oldest === undefined)
|
|
219
|
+
break;
|
|
220
|
+
statesById.delete(oldest);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const touchState = (state) => {
|
|
224
|
+
state.lastAccessAt = now();
|
|
225
|
+
if (!state.stable || state.key === undefined)
|
|
226
|
+
return;
|
|
227
|
+
if (statesById.get(state.key) === state) {
|
|
228
|
+
statesById.delete(state.key);
|
|
229
|
+
statesById.set(state.key, state);
|
|
230
|
+
}
|
|
231
|
+
prune();
|
|
232
|
+
};
|
|
233
|
+
const stateFor = (session, create = true) => {
|
|
234
|
+
if (!isSessionKey(session))
|
|
235
|
+
return undefined;
|
|
236
|
+
prune();
|
|
237
|
+
const id = normalizeId(objectRecord(session)?.id);
|
|
238
|
+
if (id !== undefined) {
|
|
239
|
+
let state = statesById.get(id);
|
|
240
|
+
if (state === undefined && create) {
|
|
241
|
+
state = createState(id, true, options, now());
|
|
242
|
+
statesById.set(id, state);
|
|
243
|
+
prune();
|
|
244
|
+
}
|
|
245
|
+
if (state !== undefined)
|
|
246
|
+
touchState(state);
|
|
247
|
+
return state;
|
|
248
|
+
}
|
|
249
|
+
let state = weakStates.get(session);
|
|
250
|
+
if (state === undefined && create) {
|
|
251
|
+
state = createState(undefined, false, options, now());
|
|
252
|
+
weakStates.set(session, state);
|
|
253
|
+
}
|
|
254
|
+
if (state !== undefined)
|
|
255
|
+
touchState(state);
|
|
256
|
+
return state;
|
|
257
|
+
};
|
|
258
|
+
const stableStates = () => {
|
|
259
|
+
prune();
|
|
260
|
+
return [...statesById.values()];
|
|
261
|
+
};
|
|
262
|
+
const uniqueStableOwner = (attachmentId) => {
|
|
263
|
+
const id = normalizeId(attachmentId);
|
|
264
|
+
if (id === undefined)
|
|
265
|
+
return undefined;
|
|
266
|
+
let owner;
|
|
267
|
+
for (const state of stableStates()) {
|
|
268
|
+
if (!state.attachments.has(id) && !state.descriptions.has(id))
|
|
269
|
+
continue;
|
|
270
|
+
if (owner !== undefined && owner !== state)
|
|
271
|
+
return undefined;
|
|
272
|
+
owner = state;
|
|
273
|
+
}
|
|
274
|
+
if (owner !== undefined)
|
|
275
|
+
touchState(owner);
|
|
276
|
+
return owner;
|
|
277
|
+
};
|
|
278
|
+
const store = {
|
|
279
|
+
options,
|
|
280
|
+
stateFor,
|
|
281
|
+
stableStates,
|
|
282
|
+
uniqueStableOwner,
|
|
283
|
+
touchState,
|
|
284
|
+
memoryForSession(session) {
|
|
285
|
+
const memory = new SessionMemoryView(store, session);
|
|
286
|
+
if (isSessionKey(session))
|
|
287
|
+
knownSessionMemoryViews.set(session, memory);
|
|
288
|
+
return memory;
|
|
289
|
+
},
|
|
290
|
+
getDescription(session, attachmentId) {
|
|
291
|
+
const id = normalizeId(attachmentId);
|
|
292
|
+
if (id === undefined)
|
|
293
|
+
return undefined;
|
|
294
|
+
return stateFor(session, false)?.descriptions.get(id);
|
|
295
|
+
},
|
|
296
|
+
hasDescription(session, attachmentId) {
|
|
297
|
+
const id = normalizeId(attachmentId);
|
|
298
|
+
if (id === undefined)
|
|
299
|
+
return false;
|
|
300
|
+
return stateFor(session, false)?.descriptions.has(id) === true;
|
|
301
|
+
},
|
|
302
|
+
setDescription(session, attachmentId, description) {
|
|
303
|
+
const id = normalizeId(attachmentId);
|
|
304
|
+
if (id === undefined)
|
|
305
|
+
return false;
|
|
306
|
+
const state = stateFor(session, true);
|
|
307
|
+
if (state === undefined)
|
|
308
|
+
return false;
|
|
309
|
+
state.descriptions.set(id, description);
|
|
310
|
+
touchState(state);
|
|
311
|
+
return state.descriptions.has(id);
|
|
312
|
+
},
|
|
313
|
+
deleteDescription(session, attachmentId) {
|
|
314
|
+
const id = normalizeId(attachmentId);
|
|
315
|
+
if (id === undefined)
|
|
316
|
+
return false;
|
|
317
|
+
const state = stateFor(session, false);
|
|
318
|
+
if (state === undefined)
|
|
319
|
+
return false;
|
|
320
|
+
touchState(state);
|
|
321
|
+
return state.descriptions.delete(id);
|
|
322
|
+
},
|
|
323
|
+
clearDescriptions(session) {
|
|
324
|
+
const state = stateFor(session, false);
|
|
325
|
+
if (state !== undefined)
|
|
326
|
+
state.descriptions.clear();
|
|
327
|
+
},
|
|
328
|
+
recordAttachments(session, refs) {
|
|
329
|
+
if (!Array.isArray(refs) || refs.length === 0)
|
|
330
|
+
return;
|
|
331
|
+
const state = stateFor(session, true);
|
|
332
|
+
if (state === undefined)
|
|
333
|
+
return;
|
|
334
|
+
for (const value of refs) {
|
|
335
|
+
const ref = attachmentRef(value);
|
|
336
|
+
if (ref === undefined)
|
|
337
|
+
continue;
|
|
338
|
+
const id = normalizeId(attachmentIdOf(ref));
|
|
339
|
+
if (id !== undefined)
|
|
340
|
+
state.attachments.set(id, ref);
|
|
341
|
+
}
|
|
342
|
+
touchState(state);
|
|
343
|
+
},
|
|
344
|
+
lookupAttachment(session, attachmentId) {
|
|
345
|
+
const id = normalizeId(attachmentId);
|
|
346
|
+
if (id === undefined)
|
|
347
|
+
return undefined;
|
|
348
|
+
return stateFor(session, false)?.attachments.get(id);
|
|
349
|
+
},
|
|
350
|
+
forgetSession(sessionOrId) {
|
|
351
|
+
const id = typeof sessionOrId === 'string' || typeof sessionOrId === 'number'
|
|
352
|
+
? normalizeId(sessionOrId)
|
|
353
|
+
: normalizeId(objectRecord(sessionOrId)?.id);
|
|
354
|
+
if (id !== undefined)
|
|
355
|
+
return statesById.delete(id);
|
|
356
|
+
if (isSessionKey(sessionOrId)) {
|
|
357
|
+
knownSessionMemoryViews.delete(sessionOrId);
|
|
358
|
+
return weakStates.delete(sessionOrId);
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
},
|
|
362
|
+
stateStats(session) {
|
|
363
|
+
const state = stateFor(session, false);
|
|
364
|
+
if (state === undefined)
|
|
365
|
+
return undefined;
|
|
366
|
+
return {
|
|
367
|
+
stable: state.stable,
|
|
368
|
+
descriptions: state.descriptions.size,
|
|
369
|
+
descriptionChars: state.descriptions.weight,
|
|
370
|
+
attachments: state.attachments.size,
|
|
371
|
+
};
|
|
372
|
+
},
|
|
373
|
+
stats() {
|
|
374
|
+
prune();
|
|
375
|
+
let descriptions = 0;
|
|
376
|
+
let descriptionChars = 0;
|
|
377
|
+
let attachments = 0;
|
|
378
|
+
for (const state of statesById.values()) {
|
|
379
|
+
descriptions += state.descriptions.size;
|
|
380
|
+
descriptionChars += state.descriptions.weight;
|
|
381
|
+
attachments += state.attachments.size;
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
stableSessions: statesById.size,
|
|
385
|
+
descriptions,
|
|
386
|
+
descriptionChars,
|
|
387
|
+
attachments,
|
|
388
|
+
};
|
|
389
|
+
},
|
|
390
|
+
descriptionFacade: undefined,
|
|
391
|
+
};
|
|
392
|
+
store.descriptionFacade = new DescriptionFacade(store);
|
|
393
|
+
return store;
|
|
404
394
|
}
|