jeopi-catalog 16.5.1 → 16.5.6
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/CHANGELOG.md +17 -0
- package/dist/types/discovery/codex.d.ts +3 -2
- package/dist/types/model-cache.d.ts +5 -1
- package/dist/types/model-manager.d.ts +16 -0
- package/dist/types/provider-models/special.d.ts +2 -0
- package/package.json +3 -3
- package/src/discovery/codex.ts +2 -2
- package/src/model-cache.ts +67 -13
- package/src/model-manager.ts +86 -7
- package/src/model-thinking.ts +3 -0
- package/src/models.json +245 -777
- package/src/provider-models/openai-compat.ts +50 -12
- package/src/provider-models/special.ts +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.5.6] - 2026-10-01
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Exclude all model request headers from the SQLite model cache and invalidate pre-fix cache rows with SQLite secure deletion enabled. Restore headers only from matching current static models or trusted provider/configuration sources; models with unreconstructible dynamic headers require fresh discovery and are not returned as authenticated offline fallbacks. Adapted from oh-my-pi's header omission, restoration, and Copilot variant fixes.
|
|
10
|
+
|
|
11
|
+
## [16.5.5] - 2026-09-30
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Refresh OpenAI account model lists with current OpenAI metadata from models.dev, including GPT-6 and GPT-6.1, dated snapshots, and fine-tunes; exclude audio, image, and specialized models unsupported by the coding agent.
|
|
16
|
+
- Replace stale OpenAI and Codex model lists after successful discovery and isolate cached lists by endpoint and credentials.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Support scoped catalog regeneration with repeatable `gen:models --provider <provider-id>` options.
|
|
21
|
+
|
|
5
22
|
## [16.4.3] - 2026-07-22
|
|
6
23
|
|
|
7
24
|
### Fixed
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FetchImpl } from "jeopi-utils";
|
|
1
2
|
import type { ModelSpec } from "../types";
|
|
2
3
|
/**
|
|
3
4
|
* Fetch options for OpenAI Codex model discovery.
|
|
@@ -18,9 +19,9 @@ export interface CodexModelDiscoveryOptions {
|
|
|
18
19
|
/** Abort signal for network request cancellation. */
|
|
19
20
|
signal?: AbortSignal;
|
|
20
21
|
/** Optional fetch implementation override for tests. */
|
|
21
|
-
fetchFn?:
|
|
22
|
+
fetchFn?: FetchImpl;
|
|
22
23
|
/** Optional registry fetch implementation override for client version lookup. */
|
|
23
|
-
registryFetchFn?:
|
|
24
|
+
registryFetchFn?: FetchImpl;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Normalized Codex discovery response.
|
|
@@ -4,6 +4,10 @@ interface CacheEntry<TApi extends Api = Api> {
|
|
|
4
4
|
fresh: boolean;
|
|
5
5
|
authoritative: boolean;
|
|
6
6
|
updatedAt: number;
|
|
7
|
+
/** Model ids whose request headers were omitted from disk. */
|
|
8
|
+
headerOmittedModelIds: readonly string[];
|
|
9
|
+
/** Model ids whose headers did not match a trusted local source. */
|
|
10
|
+
unrestorableHeaderModelIds: readonly string[];
|
|
7
11
|
/**
|
|
8
12
|
* Hash of the static catalog slice that was merged into `models` when this
|
|
9
13
|
* row was written. `resolveProviderModels` compares against the current
|
|
@@ -13,5 +17,5 @@ interface CacheEntry<TApi extends Api = Api> {
|
|
|
13
17
|
staticFingerprint: string;
|
|
14
18
|
}
|
|
15
19
|
export declare function readModelCache<TApi extends Api>(providerId: string, ttlMs: number, now: () => number, dbPath?: string): CacheEntry<TApi> | null;
|
|
16
|
-
export declare function writeModelCache<TApi extends Api>(providerId: string, updatedAt: number, models: Model<TApi>[], authoritative: boolean, staticFingerprint: string, dbPath?: string): void;
|
|
20
|
+
export declare function writeModelCache<TApi extends Api>(providerId: string, updatedAt: number, models: Model<TApi>[], authoritative: boolean, staticFingerprint: string, dbPath?: string, staticHeaderSources?: readonly Model<TApi>[], restorableHeaderFallback?: Record<string, string>, restoreCachedHeaders?: (model: Model<Api>) => Record<string, string> | undefined): void;
|
|
17
21
|
export {};
|
|
@@ -30,6 +30,17 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
30
30
|
dynamicModelsAuthoritative?: boolean;
|
|
31
31
|
/** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
|
|
32
32
|
dropCachedModelIdsOnStaticMismatch?: readonly string[];
|
|
33
|
+
/** Trusted provider-wide headers restored locally, never persisted. */
|
|
34
|
+
restorableHeaderFallback?: Record<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* Reconstruct the complete header set from authoritative local configuration.
|
|
37
|
+
* Takes `Model<Api>` (not `Model<TApi>`): the parameter position is
|
|
38
|
+
* contravariant, so binding it to `TApi` would make `ModelManagerOptions<TApi>`
|
|
39
|
+
* invariant in `TApi` and break narrow-to-`ModelManagerOptions<Api>` assignment
|
|
40
|
+
* at every per-provider descriptor factory. Implementations needing identity
|
|
41
|
+
* use `model.id` / `model.requestModelId`, both present regardless of `api`.
|
|
42
|
+
*/
|
|
43
|
+
restoreCachedHeaders?: (model: Model<Api>) => Record<string, string> | undefined;
|
|
33
44
|
/** Optional dynamic endpoint fetcher. */
|
|
34
45
|
fetchDynamicModels?: () => Promise<readonly ModelSpec<TApi>[] | null>;
|
|
35
46
|
/** Optional models.dev fallback hook. */
|
|
@@ -59,6 +70,11 @@ export interface ModelManager<TApi extends Api = Api> {
|
|
|
59
70
|
* Creates a reusable provider model manager.
|
|
60
71
|
*/
|
|
61
72
|
export declare function createModelManager<TApi extends Api = Api, TModelsDevPayload = unknown>(options: ModelManagerOptions<TApi, TModelsDevPayload>): ModelManager<TApi>;
|
|
73
|
+
/** Restore omitted headers only from trusted local sources; unresolved models must not be used. */
|
|
74
|
+
export declare function restoreCachedModelHeaders<TApi extends Api>(cachedModels: readonly ModelSpec<TApi>[], staticModels: readonly Model<TApi>[], headerOmittedModelIds: readonly string[], unrestorableHeaderModelIds: readonly string[], restorableHeaderFallback?: Record<string, string>, restoreCachedHeaders?: (model: Model<Api>) => Record<string, string> | undefined): {
|
|
75
|
+
models: Model<TApi>[];
|
|
76
|
+
unresolvedModelIds: ReadonlySet<string>;
|
|
77
|
+
};
|
|
62
78
|
/**
|
|
63
79
|
* Resolves provider models with source precedence:
|
|
64
80
|
* static -> models.dev -> cache -> dynamic.
|
|
@@ -5,6 +5,8 @@ export interface OpenAICodexModelManagerConfig {
|
|
|
5
5
|
accessToken?: string;
|
|
6
6
|
accountId?: string;
|
|
7
7
|
clientVersion?: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
fetch?: FetchImpl;
|
|
8
10
|
}
|
|
9
11
|
export declare function openaiCodexModelManagerOptions(config?: OpenAICodexModelManagerConfig): ModelManagerOptions<"openai-codex-responses">;
|
|
10
12
|
export interface CursorModelManagerConfig {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-catalog",
|
|
4
|
-
"version": "16.5.
|
|
4
|
+
"version": "16.5.6",
|
|
5
5
|
"description": "Model catalog for jeopi: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@bufbuild/protobuf": "^2.12.0",
|
|
37
|
-
"jeopi-utils": "16.5.
|
|
37
|
+
"jeopi-utils": "16.5.6",
|
|
38
38
|
"arktype": "2.2.3",
|
|
39
39
|
"zod": "^4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"jeopi-ai": "16.5.
|
|
42
|
+
"jeopi-ai": "16.5.6",
|
|
43
43
|
"@types/bun": "^1.3.14"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
package/src/discovery/codex.ts
CHANGED
|
@@ -62,9 +62,9 @@ export interface CodexModelDiscoveryOptions {
|
|
|
62
62
|
/** Abort signal for network request cancellation. */
|
|
63
63
|
signal?: AbortSignal;
|
|
64
64
|
/** Optional fetch implementation override for tests. */
|
|
65
|
-
fetchFn?:
|
|
65
|
+
fetchFn?: FetchImpl;
|
|
66
66
|
/** Optional registry fetch implementation override for client version lookup. */
|
|
67
|
-
registryFetchFn?:
|
|
67
|
+
registryFetchFn?: FetchImpl;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
package/src/model-cache.ts
CHANGED
|
@@ -6,16 +6,10 @@ import { Database } from "bun:sqlite";
|
|
|
6
6
|
import { getModelDbPath } from "jeopi-utils";
|
|
7
7
|
import type { Api, Model, ModelSpec } from "./types";
|
|
8
8
|
|
|
9
|
-
// Rows persist ModelSpec
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
// (cached specs still carrying `thinking.mode: "google-level"` and the old
|
|
14
|
-
// 3.5-flash effort routing); v6 invalidated rows that may contain the retired
|
|
15
|
-
// unknown-limit sentinels (222222/8888); v5 invalidated rows predating
|
|
16
|
-
// effort-tier variant collapsing (raw `-low`/`-high`/`-thinking` member ids);
|
|
17
|
-
// v4 dropped the pre-efforts ThinkingConfig shape.
|
|
18
|
-
const CACHE_SCHEMA_VERSION = 8;
|
|
9
|
+
// Rows persist sparse ModelSpec metadata, never request headers: arbitrary
|
|
10
|
+
// provider-defined header names may carry credentials. v9 records restoration
|
|
11
|
+
// provenance and invalidates older rows, including v8 credential-bearing rows.
|
|
12
|
+
const CACHE_SCHEMA_VERSION = 9;
|
|
19
13
|
|
|
20
14
|
interface CacheRow {
|
|
21
15
|
provider_id: string;
|
|
@@ -24,6 +18,8 @@ interface CacheRow {
|
|
|
24
18
|
authoritative: number;
|
|
25
19
|
static_fingerprint: string;
|
|
26
20
|
models: string;
|
|
21
|
+
header_omitted_model_ids: string;
|
|
22
|
+
unrestorable_header_model_ids: string;
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
interface TableInfoRow {
|
|
@@ -35,6 +31,10 @@ interface CacheEntry<TApi extends Api = Api> {
|
|
|
35
31
|
fresh: boolean;
|
|
36
32
|
authoritative: boolean;
|
|
37
33
|
updatedAt: number;
|
|
34
|
+
/** Model ids whose request headers were omitted from disk. */
|
|
35
|
+
headerOmittedModelIds: readonly string[];
|
|
36
|
+
/** Model ids whose headers did not match a trusted local source. */
|
|
37
|
+
unrestorableHeaderModelIds: readonly string[];
|
|
38
38
|
/**
|
|
39
39
|
* Hash of the static catalog slice that was merged into `models` when this
|
|
40
40
|
* row was written. `resolveProviderModels` compares against the current
|
|
@@ -52,6 +52,8 @@ function openDb(resolvedPath: string): Database {
|
|
|
52
52
|
// Install the busy handler BEFORE any lock-taking statement. See
|
|
53
53
|
// https://github.com/can1357/oh-my-pi/issues/2421.
|
|
54
54
|
db.run("PRAGMA busy_timeout = 3000");
|
|
55
|
+
// Scrub deleted legacy credential-bearing cells rather than retaining them in free pages.
|
|
56
|
+
db.run("PRAGMA secure_delete = ON");
|
|
55
57
|
db.run("PRAGMA journal_mode = WAL");
|
|
56
58
|
db.run(`
|
|
57
59
|
CREATE TABLE IF NOT EXISTS model_cache (
|
|
@@ -60,6 +62,8 @@ function openDb(resolvedPath: string): Database {
|
|
|
60
62
|
updated_at INTEGER NOT NULL,
|
|
61
63
|
authoritative INTEGER NOT NULL DEFAULT 0,
|
|
62
64
|
static_fingerprint TEXT NOT NULL DEFAULT '',
|
|
65
|
+
header_omitted_model_ids TEXT NOT NULL DEFAULT '[]',
|
|
66
|
+
unrestorable_header_model_ids TEXT NOT NULL DEFAULT '[]',
|
|
63
67
|
models TEXT NOT NULL
|
|
64
68
|
)
|
|
65
69
|
`);
|
|
@@ -98,6 +102,12 @@ function migrateCacheSchema(db: Database): void {
|
|
|
98
102
|
if (!columns.some(column => column.name === "static_fingerprint")) {
|
|
99
103
|
db.run("ALTER TABLE model_cache ADD COLUMN static_fingerprint TEXT NOT NULL DEFAULT ''");
|
|
100
104
|
}
|
|
105
|
+
if (!columns.some(column => column.name === "header_omitted_model_ids")) {
|
|
106
|
+
db.run("ALTER TABLE model_cache ADD COLUMN header_omitted_model_ids TEXT NOT NULL DEFAULT '[]'");
|
|
107
|
+
}
|
|
108
|
+
if (!columns.some(column => column.name === "unrestorable_header_model_ids")) {
|
|
109
|
+
db.run("ALTER TABLE model_cache ADD COLUMN unrestorable_header_model_ids TEXT NOT NULL DEFAULT '[]'");
|
|
110
|
+
}
|
|
101
111
|
} finally {
|
|
102
112
|
stmt.finalize();
|
|
103
113
|
}
|
|
@@ -124,6 +134,15 @@ export function readModelCache<TApi extends Api>(
|
|
|
124
134
|
return null;
|
|
125
135
|
}
|
|
126
136
|
const models = JSON.parse(row.models) as ModelSpec<TApi>[];
|
|
137
|
+
const headerOmittedModelIds: unknown = JSON.parse(row.header_omitted_model_ids);
|
|
138
|
+
const unrestorableHeaderModelIds: unknown = JSON.parse(row.unrestorable_header_model_ids);
|
|
139
|
+
if (
|
|
140
|
+
!Array.isArray(headerOmittedModelIds) ||
|
|
141
|
+
!headerOmittedModelIds.every(id => typeof id === "string") ||
|
|
142
|
+
!Array.isArray(unrestorableHeaderModelIds) ||
|
|
143
|
+
!unrestorableHeaderModelIds.every(id => typeof id === "string")
|
|
144
|
+
)
|
|
145
|
+
return null;
|
|
127
146
|
const ageMs = now() - row.updated_at;
|
|
128
147
|
const fresh = Number.isFinite(ageMs) && ageMs >= 0 && ageMs <= ttlMs;
|
|
129
148
|
return {
|
|
@@ -131,6 +150,8 @@ export function readModelCache<TApi extends Api>(
|
|
|
131
150
|
fresh,
|
|
132
151
|
authoritative: row.authoritative === 1,
|
|
133
152
|
updatedAt: row.updated_at,
|
|
153
|
+
headerOmittedModelIds,
|
|
154
|
+
unrestorableHeaderModelIds,
|
|
134
155
|
staticFingerprint: row.static_fingerprint ?? "",
|
|
135
156
|
};
|
|
136
157
|
} finally {
|
|
@@ -142,6 +163,14 @@ export function readModelCache<TApi extends Api>(
|
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
165
|
|
|
166
|
+
function headersEqual(left: Record<string, string>, right: Record<string, string> | undefined): boolean {
|
|
167
|
+
if (!right) return false;
|
|
168
|
+
return (
|
|
169
|
+
Object.keys(left).length === Object.keys(right).length &&
|
|
170
|
+
Object.keys(left).every(key => Object.hasOwn(right, key) && left[key] === right[key])
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
145
174
|
export function writeModelCache<TApi extends Api>(
|
|
146
175
|
providerId: string,
|
|
147
176
|
updatedAt: number,
|
|
@@ -149,19 +178,44 @@ export function writeModelCache<TApi extends Api>(
|
|
|
149
178
|
authoritative: boolean,
|
|
150
179
|
staticFingerprint: string,
|
|
151
180
|
dbPath?: string,
|
|
181
|
+
staticHeaderSources: readonly Model<TApi>[] = [],
|
|
182
|
+
restorableHeaderFallback?: Record<string, string>,
|
|
183
|
+
restoreCachedHeaders?: (model: Model<Api>) => Record<string, string> | undefined,
|
|
152
184
|
): void {
|
|
153
185
|
try {
|
|
154
186
|
withModelCacheDb(dbPath, db => {
|
|
187
|
+
const headerOmittedModelIds: string[] = [];
|
|
188
|
+
const unrestorableHeaderModelIds: string[] = [];
|
|
189
|
+
const staticById = new Map(staticHeaderSources.map(model => [model.id, model]));
|
|
190
|
+
const cachedModels = models.map(model => {
|
|
191
|
+
const { headers, compatConfig, ...metadata } = model;
|
|
192
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
193
|
+
headerOmittedModelIds.push(model.id);
|
|
194
|
+
const source =
|
|
195
|
+
staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined);
|
|
196
|
+
if (
|
|
197
|
+
!headersEqual(headers, source?.headers ?? restorableHeaderFallback) &&
|
|
198
|
+
!headersEqual(headers, restoreCachedHeaders?.({ ...model, headers: undefined }))
|
|
199
|
+
) {
|
|
200
|
+
unrestorableHeaderModelIds.push(model.id);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return { ...metadata, compat: compatConfig };
|
|
204
|
+
});
|
|
155
205
|
db.run(
|
|
156
|
-
`INSERT OR REPLACE INTO model_cache (
|
|
157
|
-
|
|
206
|
+
`INSERT OR REPLACE INTO model_cache (
|
|
207
|
+
provider_id, version, updated_at, authoritative, static_fingerprint,
|
|
208
|
+
header_omitted_model_ids, unrestorable_header_model_ids, models
|
|
209
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
158
210
|
[
|
|
159
211
|
providerId,
|
|
160
212
|
CACHE_SCHEMA_VERSION,
|
|
161
213
|
updatedAt,
|
|
162
214
|
authoritative ? 1 : 0,
|
|
163
215
|
staticFingerprint,
|
|
164
|
-
JSON.stringify(
|
|
216
|
+
JSON.stringify(headerOmittedModelIds),
|
|
217
|
+
JSON.stringify(unrestorableHeaderModelIds),
|
|
218
|
+
JSON.stringify(cachedModels),
|
|
165
219
|
],
|
|
166
220
|
);
|
|
167
221
|
});
|
package/src/model-manager.ts
CHANGED
|
@@ -41,6 +41,17 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
41
41
|
dynamicModelsAuthoritative?: boolean;
|
|
42
42
|
/** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
|
|
43
43
|
dropCachedModelIdsOnStaticMismatch?: readonly string[];
|
|
44
|
+
/** Trusted provider-wide headers restored locally, never persisted. */
|
|
45
|
+
restorableHeaderFallback?: Record<string, string>;
|
|
46
|
+
/**
|
|
47
|
+
* Reconstruct the complete header set from authoritative local configuration.
|
|
48
|
+
* Takes `Model<Api>` (not `Model<TApi>`): the parameter position is
|
|
49
|
+
* contravariant, so binding it to `TApi` would make `ModelManagerOptions<TApi>`
|
|
50
|
+
* invariant in `TApi` and break narrow-to-`ModelManagerOptions<Api>` assignment
|
|
51
|
+
* at every per-provider descriptor factory. Implementations needing identity
|
|
52
|
+
* use `model.id` / `model.requestModelId`, both present regardless of `api`.
|
|
53
|
+
*/
|
|
54
|
+
restoreCachedHeaders?: (model: Model<Api>) => Record<string, string> | undefined;
|
|
44
55
|
/** Optional dynamic endpoint fetcher. */
|
|
45
56
|
fetchDynamicModels?: () => Promise<readonly ModelSpec<TApi>[] | null>;
|
|
46
57
|
/** Optional models.dev fallback hook. */
|
|
@@ -101,6 +112,37 @@ function passModelList<TApi extends Api>(value: unknown): Model<TApi>[] {
|
|
|
101
112
|
return out;
|
|
102
113
|
}
|
|
103
114
|
|
|
115
|
+
/** Restore omitted headers only from trusted local sources; unresolved models must not be used. */
|
|
116
|
+
export function restoreCachedModelHeaders<TApi extends Api>(
|
|
117
|
+
cachedModels: readonly ModelSpec<TApi>[],
|
|
118
|
+
staticModels: readonly Model<TApi>[],
|
|
119
|
+
headerOmittedModelIds: readonly string[],
|
|
120
|
+
unrestorableHeaderModelIds: readonly string[],
|
|
121
|
+
restorableHeaderFallback?: Record<string, string>,
|
|
122
|
+
restoreCachedHeaders?: (model: Model<Api>) => Record<string, string> | undefined,
|
|
123
|
+
): { models: Model<TApi>[]; unresolvedModelIds: ReadonlySet<string> } {
|
|
124
|
+
const models = passModelList<TApi>(cachedModels);
|
|
125
|
+
const unresolvedModelIds = new Set<string>();
|
|
126
|
+
if (headerOmittedModelIds.length === 0) return { models, unresolvedModelIds };
|
|
127
|
+
const omittedIds = new Set(headerOmittedModelIds);
|
|
128
|
+
const unrestorableIds = new Set(unrestorableHeaderModelIds);
|
|
129
|
+
const staticById = new Map(staticModels.map(model => [model.id, model]));
|
|
130
|
+
const restored = models.map(model => {
|
|
131
|
+
if (!omittedIds.has(model.id)) return model;
|
|
132
|
+
const source =
|
|
133
|
+
staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined);
|
|
134
|
+
const headers = unrestorableIds.has(model.id)
|
|
135
|
+
? undefined
|
|
136
|
+
: (source?.headers ?? restorableHeaderFallback ?? restoreCachedHeaders?.(model));
|
|
137
|
+
if (!headers) {
|
|
138
|
+
unresolvedModelIds.add(model.id);
|
|
139
|
+
return model;
|
|
140
|
+
}
|
|
141
|
+
return { ...model, headers };
|
|
142
|
+
});
|
|
143
|
+
return { models: restored, unresolvedModelIds };
|
|
144
|
+
}
|
|
145
|
+
|
|
104
146
|
/**
|
|
105
147
|
* Resolves provider models with source precedence:
|
|
106
148
|
* static -> models.dev -> cache -> dynamic.
|
|
@@ -119,10 +161,21 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
119
161
|
? passModelList<TApi>(options.staticModels)
|
|
120
162
|
: (getBundledModels(options.providerId as GeneratedProvider) as Model<TApi>[]);
|
|
121
163
|
const cache = readModelCache<TApi>(cacheProviderId, ttlMs, now, dbPath);
|
|
164
|
+
const restoredCache = restoreCachedModelHeaders(
|
|
165
|
+
cache?.models ?? [],
|
|
166
|
+
staticModels,
|
|
167
|
+
cache?.headerOmittedModelIds ?? [],
|
|
168
|
+
cache?.unrestorableHeaderModelIds ?? [],
|
|
169
|
+
options.restorableHeaderFallback,
|
|
170
|
+
options.restoreCachedHeaders,
|
|
171
|
+
);
|
|
172
|
+
const usableCachedModels = restoredCache.models.filter(model => !restoredCache.unresolvedModelIds.has(model.id));
|
|
173
|
+
const cacheHasUnresolvedHeaders = restoredCache.unresolvedModelIds.size > 0;
|
|
122
174
|
const dynamicModelsAuthoritative = options.dynamicModelsAuthoritative ?? false;
|
|
123
175
|
const staticFingerprint = fingerprintStatic(staticModels, dynamicModelsAuthoritative);
|
|
124
176
|
const cacheFingerprintMatches = cache?.staticFingerprint === staticFingerprint && staticFingerprint.length > 0;
|
|
125
|
-
const hasUsableFreshCache =
|
|
177
|
+
const hasUsableFreshCache =
|
|
178
|
+
(cache?.fresh ?? false) && !cacheHasUnresolvedHeaders && (!dynamicModelsAuthoritative || cacheFingerprintMatches);
|
|
126
179
|
const dynamicFetcher = options.fetchDynamicModels;
|
|
127
180
|
const hasDynamicFetcher = typeof dynamicFetcher === "function";
|
|
128
181
|
const hasAuthoritativeCache = ((cache?.authoritative ?? false) && hasUsableFreshCache) || !hasDynamicFetcher;
|
|
@@ -139,8 +192,14 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
139
192
|
// was merged in last time, the cache row IS the authoritative merge result.
|
|
140
193
|
// Re-running `mergeDynamicModels(static, cache)` would just rebuild the same
|
|
141
194
|
// objects (~800ms in the steady-state cold-start profile for `omp -p hi`).
|
|
142
|
-
if (
|
|
143
|
-
|
|
195
|
+
if (
|
|
196
|
+
!shouldFetchFromNetwork &&
|
|
197
|
+
cache?.fresh &&
|
|
198
|
+
hasAuthoritativeCache &&
|
|
199
|
+
cacheFingerprintMatches &&
|
|
200
|
+
!cacheHasUnresolvedHeaders
|
|
201
|
+
) {
|
|
202
|
+
return { models: collapseBuiltModelVariants(restoredCache.models), stale: false };
|
|
144
203
|
}
|
|
145
204
|
|
|
146
205
|
const [fetchedModelsDevModels, fetchedDynamicModels] = shouldFetchFromNetwork
|
|
@@ -153,7 +212,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
153
212
|
const cacheModels = dynamicFetchSucceeded
|
|
154
213
|
? []
|
|
155
214
|
: prepareCacheModelsForStaticMismatch(
|
|
156
|
-
|
|
215
|
+
usableCachedModels,
|
|
157
216
|
staticModels,
|
|
158
217
|
cacheFingerprintMatches,
|
|
159
218
|
options.dropCachedModelIdsOnStaticMismatch,
|
|
@@ -162,7 +221,10 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
162
221
|
const mergedWithCache = mergeDynamicModels(mergeModelSources(staticModels, modelsDevModels), cacheModels);
|
|
163
222
|
const mergedModels = mergeDynamicModels(mergedWithCache, dynamicModels);
|
|
164
223
|
const models = collapseBuiltModelVariants(
|
|
165
|
-
dynamicModelsAuthoritative && dynamicFetchSucceeded
|
|
224
|
+
(dynamicModelsAuthoritative && dynamicFetchSucceeded
|
|
225
|
+
? retainModelIds(mergedModels, dynamicModels)
|
|
226
|
+
: mergedModels
|
|
227
|
+
).filter(model => dynamicFetchSucceeded || !restoredCache.unresolvedModelIds.has(model.id)),
|
|
166
228
|
);
|
|
167
229
|
const dynamicAuthoritative = !hasDynamicFetcher || dynamicFetchSucceeded || shouldUseFreshCacheAsAuthoritative;
|
|
168
230
|
if (shouldFetchFromNetwork) {
|
|
@@ -178,11 +240,25 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
178
240
|
true,
|
|
179
241
|
staticFingerprint,
|
|
180
242
|
dbPath,
|
|
243
|
+
staticModels,
|
|
244
|
+
options.restorableHeaderFallback,
|
|
245
|
+
options.restoreCachedHeaders,
|
|
181
246
|
);
|
|
182
247
|
} else {
|
|
183
248
|
// Dynamic fetch failed — update cache with a non-authoritative snapshot so
|
|
184
249
|
// stale state remains visible while retry backoff still applies.
|
|
185
250
|
const latestCache = readModelCache<TApi>(cacheProviderId, ttlMs, now, dbPath);
|
|
251
|
+
const latestRestoredCache = restoreCachedModelHeaders(
|
|
252
|
+
latestCache?.models ?? cache?.models ?? [],
|
|
253
|
+
staticModels,
|
|
254
|
+
latestCache?.headerOmittedModelIds ?? cache?.headerOmittedModelIds ?? [],
|
|
255
|
+
latestCache?.unrestorableHeaderModelIds ?? cache?.unrestorableHeaderModelIds ?? [],
|
|
256
|
+
options.restorableHeaderFallback,
|
|
257
|
+
options.restoreCachedHeaders,
|
|
258
|
+
);
|
|
259
|
+
// Dropping unresolved rows would also erase the evidence that a same-id
|
|
260
|
+
// static model lacks required headers. Keep that provenance until discovery succeeds.
|
|
261
|
+
if (latestRestoredCache.unresolvedModelIds.size > 0) return { models, stale: true };
|
|
186
262
|
writeModelCache(
|
|
187
263
|
cacheProviderId,
|
|
188
264
|
now(),
|
|
@@ -190,16 +266,19 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
190
266
|
mergeDynamicModels(
|
|
191
267
|
mergeModelSources(staticModels, modelsDevModels),
|
|
192
268
|
prepareCacheModelsForStaticMismatch(
|
|
193
|
-
|
|
269
|
+
latestRestoredCache.models.filter(model => !latestRestoredCache.unresolvedModelIds.has(model.id)),
|
|
194
270
|
staticModels,
|
|
195
271
|
cacheFingerprintMatches,
|
|
196
272
|
options.dropCachedModelIdsOnStaticMismatch,
|
|
197
273
|
),
|
|
198
|
-
),
|
|
274
|
+
).filter(model => !latestRestoredCache.unresolvedModelIds.has(model.id)),
|
|
199
275
|
),
|
|
200
276
|
false,
|
|
201
277
|
staticFingerprint,
|
|
202
278
|
dbPath,
|
|
279
|
+
staticModels,
|
|
280
|
+
options.restorableHeaderFallback,
|
|
281
|
+
options.restoreCachedHeaders,
|
|
203
282
|
);
|
|
204
283
|
}
|
|
205
284
|
}
|
package/src/model-thinking.ts
CHANGED
|
@@ -513,6 +513,9 @@ function impliesMandatoryReasoning(parsed: ParsedModel, modelId: string): boolea
|
|
|
513
513
|
}
|
|
514
514
|
if (isMinimaxM2FamilyModelId(modelId)) return true;
|
|
515
515
|
if (OPENAI_O_SERIES_RE.test(bareModelId(modelId))) return true;
|
|
516
|
+
// These GPT-6 models reject "none"; Sol/Luna 6.0 still accept it.
|
|
517
|
+
const canonicalOpenAIId = bareModelId(modelId).replace(/^ft:/, "").split(":")[0];
|
|
518
|
+
if (/^gpt-(?:6-astra|6\.1-sol)(?:$|-)/i.test(canonicalOpenAIId)) return true;
|
|
516
519
|
return findThinkingVariantToken(modelId) !== undefined;
|
|
517
520
|
}
|
|
518
521
|
|