


























@@ -3,7 +3,12 @@ import os from "node:os";
33import path from "node:path";
44import { normalizeAccountId as normalizeSharedAccountId } from "openclaw/plugin-sdk/account-id";
55import { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
6-import { MAX_DATE_TIMESTAMP_MS, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
6+import {
7+MAX_DATE_TIMESTAMP_MS,
8+resolveDateTimestampMs,
9+resolveTimestampMsToIsoString,
10+timestampMsToIsoString,
11+} from "openclaw/plugin-sdk/number-runtime";
712import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
813import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
914import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
@@ -14,11 +19,13 @@ const PREFERENCE_MAX_ENTRIES = 2_000;
1419const MAX_PLUGIN_STATE_KEY_BYTES = 512;
1520const textEncoder = new TextEncoder();
1621let lastPreferenceTimestampMs = 0;
22+let lastPreferenceOrder = 0;
17231824type ModelPickerPreferencesEntry = {
1925scopeKey: string;
2026modelRef: string;
2127updatedAt: string;
28+updatedOrder?: number;
2229};
23302431type LegacyModelPickerPreferencesEntry = {
@@ -125,6 +132,7 @@ function sanitizeStoredPreferenceEntry(value: unknown): ModelPickerPreferencesEn
125132scopeKey?: unknown;
126133modelRef?: unknown;
127134updatedAt?: unknown;
135+updatedOrder?: unknown;
128136};
129137if (typeof typedValue.scopeKey !== "string" || typeof typedValue.modelRef !== "string") {
130138return undefined;
@@ -137,6 +145,10 @@ function sanitizeStoredPreferenceEntry(value: unknown): ModelPickerPreferencesEn
137145scopeKey: typedValue.scopeKey,
138146 modelRef,
139147updatedAt: typeof typedValue.updatedAt === "string" ? typedValue.updatedAt : "",
148+updatedOrder:
149+typeof typedValue.updatedOrder === "number" && Number.isSafeInteger(typedValue.updatedOrder)
150+ ? typedValue.updatedOrder
151+ : undefined,
140152};
141153}
142154@@ -153,6 +165,21 @@ function timestampMs(value: string): number {
153165return Number.isFinite(parsed) ? parsed : 0;
154166}
155167168+function timestampOrder(value?: number): number {
169+return value !== undefined && value >= 0 ? value : 0;
170+}
171+172+function comparePreferenceEntries(
173+left: { key: string; value: ModelPickerPreferencesEntry },
174+right: { key: string; value: ModelPickerPreferencesEntry },
175+): number {
176+return (
177+timestampMs(right.value.updatedAt) - timestampMs(left.value.updatedAt) ||
178+timestampOrder(right.value.updatedOrder) - timestampOrder(left.value.updatedOrder) ||
179+left.key.localeCompare(right.key)
180+);
181+}
182+156183function legacyUpdatedAtForIndex(updatedAt: string, index: number, total: number): string {
157184const baseMs = timestampMs(updatedAt);
158185const anchorMs = Math.min(baseMs + Math.max(0, total), MAX_DATE_TIMESTAMP_MS);
@@ -165,9 +192,31 @@ function legacyUpdatedAtForIndex(updatedAt: string, index: number, total: number
165192);
166193}
167194168-function nextPreferenceTimestampIso(): string {
169-lastPreferenceTimestampMs = Math.max(Date.now(), lastPreferenceTimestampMs + 1);
170-return new Date(lastPreferenceTimestampMs).toISOString();
195+function nextPreferenceTimestamp(existingEntries: ModelPickerPreferencesEntry[]): {
196+updatedAt: string;
197+updatedOrder: number;
198+} {
199+const existingMaxTimestampMs = existingEntries.reduce(
200+(max, entry) => Math.max(max, timestampMs(entry.updatedAt)),
201+0,
202+);
203+lastPreferenceTimestampMs = Math.min(
204+Math.max(
205+resolveDateTimestampMs(Date.now(), 0),
206+lastPreferenceTimestampMs + 1,
207+existingMaxTimestampMs + 1,
208+),
209+MAX_DATE_TIMESTAMP_MS,
210+);
211+const existingMaxOrder = existingEntries.reduce(
212+(max, entry) => Math.max(max, timestampOrder(entry.updatedOrder)),
213+0,
214+);
215+lastPreferenceOrder = Math.max(lastPreferenceOrder + 1, existingMaxOrder + 1);
216+return {
217+updatedAt: resolveTimestampMsToIsoString(lastPreferenceTimestampMs),
218+updatedOrder: lastPreferenceOrder,
219+};
171220}
172221173222function normalizeLegacyPreferenceKey(key: string): string | undefined {
@@ -246,10 +295,13 @@ export async function readDiscordModelPickerRecentModels(params: {
246295await importLegacyPreferences(params.env);
247296const store = openPreferenceStore(params.env);
248297const recent = (await store.entries())
249-.map((entry) => sanitizeStoredPreferenceEntry(entry.value))
250-.filter((entry): entry is ModelPickerPreferencesEntry => entry?.scopeKey === key)
251-.toSorted((left, right) => timestampMs(right.updatedAt) - timestampMs(left.updatedAt))
252-.map((entry) => entry.modelRef);
298+.map((entry) => ({ key: entry.key, value: sanitizeStoredPreferenceEntry(entry.value) }))
299+.filter(
300+(entry): entry is { key: string; value: ModelPickerPreferencesEntry } =>
301+entry.value?.scopeKey === key,
302+)
303+.toSorted(comparePreferenceEntries)
304+.map((entry) => entry.value.modelRef);
253305if (!params.allowedModelRefs || params.allowedModelRefs.size === 0) {
254306return sanitizeRecentModels(recent, limit);
255307}
@@ -277,10 +329,14 @@ export async function recordDiscordModelPickerRecentModel(params: {
277329try {
278330await importLegacyPreferences(params.env);
279331const store = openPreferenceStore(params.env);
332+const existingEntries = (await store.entries())
333+.map((entry) => sanitizeStoredPreferenceEntry(entry.value))
334+.filter((entry): entry is ModelPickerPreferencesEntry => entry?.scopeKey === key);
335+const timestamp = nextPreferenceTimestamp(existingEntries);
280336await store.register(buildPreferenceModelKey(key, normalizedModelRef), {
281337scopeKey: key,
282338modelRef: normalizedModelRef,
283-updatedAt: nextPreferenceTimestampIso(),
339+...timestamp,
284340});
285341const limit = Math.max(1, Math.min(params.limit ?? DEFAULT_RECENT_LIMIT, 10));
286342const scopedEntries = (await store.entries())
@@ -289,11 +345,7 @@ export async function recordDiscordModelPickerRecentModel(params: {
289345(entry): entry is { key: string; value: ModelPickerPreferencesEntry } =>
290346entry.value?.scopeKey === key,
291347)
292-.toSorted(
293-(left, right) =>
294-timestampMs(right.value.updatedAt) - timestampMs(left.value.updatedAt) ||
295-left.key.localeCompare(right.key),
296-);
348+.toSorted(comparePreferenceEntries);
297349await Promise.all(scopedEntries.slice(limit).map((entry) => store.delete(entry.key)));
298350} catch {
299351return;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。