@yanlinglabs/winter-provider-catalog 0.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/LICENSE +21 -0
- package/NOTICE +59 -0
- package/PROVENANCE.md +598 -0
- package/README.md +46 -0
- package/UPSTREAM.json +10 -0
- package/dist/extract/fetch.d.ts +62 -0
- package/dist/extract/ledgers.d.ts +113 -0
- package/dist/extract/literal-extractor.d.ts +94 -0
- package/dist/extract/merge.d.ts +213 -0
- package/dist/families.d.ts +55 -0
- package/dist/families.js +30 -0
- package/dist/index-t40pzh81.js +94 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +40027 -0
- package/dist/types.d.ts +385 -0
- package/dist/validate.d.ts +98 -0
- package/generated/catalog.json +39282 -0
- package/generated/denominator.json +65 -0
- package/generated/rejections.json +5780 -0
- package/generated/upstream-layer.json +37125 -0
- package/overlay/families.json +600 -0
- package/overlay/models.json +4691 -0
- package/overlay/providers.json +2383 -0
- package/package.json +50 -0
- package/schema/catalog.schema.json +238 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# `@yanlinglabs/winter-provider-catalog`
|
|
2
|
+
|
|
3
|
+
Winter's provider and model catalog as inert, validated DATA: 165 provider rows and 604 model rows
|
|
4
|
+
with their endpoints, auth kinds, pricing evidence, admission tier and provenance, plus the validator
|
|
5
|
+
and the vocabularies the JSON Schema restates.
|
|
6
|
+
|
|
7
|
+
No network, no filesystem, no Bun API — the catalog is a bundled JSON module import, which is what
|
|
8
|
+
lets it be decoded anywhere. `PROVENANCE.md` records where every row came from and under what
|
|
9
|
+
evidence; `bun run provenance:tiers -- --check` regenerates its census from the shipped data.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
This package is published to **two registries**, and which one you want depends on who you are.
|
|
14
|
+
|
|
15
|
+
### From public npm (anyone)
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install @yanlinglabs/winter-provider-catalog
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Nothing else is needed: the `@yanlinglabs` scope is public on npm.
|
|
22
|
+
|
|
23
|
+
### From GitHub Packages (the `yanlingLabs` org)
|
|
24
|
+
|
|
25
|
+
GitHub Packages needs the scope pointed at it and an authenticated read, even for a public package.
|
|
26
|
+
In your project's `.npmrc`:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
@yanlinglabs:registry=https://npm.pkg.github.com
|
|
30
|
+
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
…with `GITHUB_TOKEN` in the environment — a personal access token carrying `read:packages`, never a
|
|
34
|
+
literal in the file. Then `npm install @yanlinglabs/winter-provider-catalog` as usual.
|
|
35
|
+
|
|
36
|
+
**The published packages contain COMPILED OUTPUT ONLY.** Each tarball ships `dist/` — the bundled
|
|
37
|
+
JavaScript a consumer imports and the `.d.ts` declarations their type-checker reads — plus its data
|
|
38
|
+
files, `README.md` and `LICENSE`. It does **not** ship `src/`: the TypeScript sources live at
|
|
39
|
+
<https://github.com/yanlingLabs/winter-agent-sdk>, which is where to read them, file an issue, or send
|
|
40
|
+
a patch.
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT — see [`LICENSE`](./LICENSE), which ships in the published tarball.
|
|
45
|
+
|
|
46
|
+
Third-party attribution for the upstream catalog data this package derives from is in [`NOTICE`](./NOTICE), which ships in the tarball beside this file.
|
package/UPSTREAM.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "GENERATED — do not edit. The pin `scripts/provider-catalog.ts` stamps onto the merged catalog; the extractor's INPUT pin lives in third_party/omniroute-provider-source/UPSTREAM.json. `tagObject` is the annotated tag's own object id and `commit` is that tag PEELED — they are different objects, and recording only one of them would not be a pin.",
|
|
3
|
+
"upstream": {
|
|
4
|
+
"tag": "v3.8.50",
|
|
5
|
+
"tagObject": "6f5d4e00e817bc01b2ac16fdd66db3840c296416",
|
|
6
|
+
"commit": "5458026c216f77a3da68ea49152dc33470cfe2cb",
|
|
7
|
+
"extractorVersion": "winter.1",
|
|
8
|
+
"overlayVersion": "1"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** The recorded pin. All four fields are checked against the fetched clone before anything is read. */
|
|
2
|
+
export interface UpstreamPin {
|
|
3
|
+
repository: string;
|
|
4
|
+
tag: string;
|
|
5
|
+
/** The annotated tag's OWN object id. */
|
|
6
|
+
tagObject: string;
|
|
7
|
+
/** The tag peeled to a commit — this is what the catalog records as `upstream.commit`. */
|
|
8
|
+
commit: string;
|
|
9
|
+
}
|
|
10
|
+
/** One allowlist path entry. `pattern` is a git sparse-checkout pattern rooted at the repository. */
|
|
11
|
+
export interface AllowlistPath {
|
|
12
|
+
pattern: string;
|
|
13
|
+
role: "extract" | "claim" | "notice";
|
|
14
|
+
why: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MaterializedFile {
|
|
17
|
+
/** Repository-relative, `/`-separated. */
|
|
18
|
+
path: string;
|
|
19
|
+
/** git's own blob id — authoritative, and directly comparable with `git ls-tree` upstream. */
|
|
20
|
+
blobId: string;
|
|
21
|
+
/** sha256 of the materialized bytes, so a manifest entry is verifiable without git. */
|
|
22
|
+
sha256: string;
|
|
23
|
+
bytes: number;
|
|
24
|
+
role: AllowlistPath["role"];
|
|
25
|
+
/** The allowlist pattern that admitted this path. */
|
|
26
|
+
admittedBy: string;
|
|
27
|
+
}
|
|
28
|
+
export interface FetchResult {
|
|
29
|
+
/** Absolute path of the checkout root. Valid until `cleanup()`. */
|
|
30
|
+
root: string;
|
|
31
|
+
commit: string;
|
|
32
|
+
tagObject: string;
|
|
33
|
+
files: MaterializedFile[];
|
|
34
|
+
read(path: string): string;
|
|
35
|
+
cleanup(): void;
|
|
36
|
+
}
|
|
37
|
+
/** Thrown for every refusal in this module. A pin/boundary failure is never a warning. */
|
|
38
|
+
export declare class UpstreamFetchError extends Error {
|
|
39
|
+
constructor(message: string);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Translate one git sparse-checkout `--no-cone` pattern into a matcher.
|
|
43
|
+
*
|
|
44
|
+
* Deliberately supports only the subset the allowlist actually uses — a leading `/` anchor,
|
|
45
|
+
* `**` (any depth) and `*` (one segment) — and rejects anything else, because a pattern this
|
|
46
|
+
* function silently mis-parses is a boundary hole that looks like a boundary.
|
|
47
|
+
*/
|
|
48
|
+
export declare function compilePathPattern(pattern: string): (path: string) => boolean;
|
|
49
|
+
export interface FetchOptions {
|
|
50
|
+
/** Where the scratch checkout goes. Defaults to a fresh mkdtemp under the OS temp dir. */
|
|
51
|
+
parentDir?: string;
|
|
52
|
+
/** Overrides `pin.repository` — the tests point this at a `file://` fixture repository. */
|
|
53
|
+
repositoryOverride?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Clone the pinned tag, materialize exactly the allowlisted paths, verify, and hash.
|
|
57
|
+
*
|
|
58
|
+
* The caller MUST call `cleanup()` (the sync script does it in a `finally`): the checkout is a
|
|
59
|
+
* scratch directory outside the repository and nothing but the generated JSON and the two upstream
|
|
60
|
+
* notice files ever survives it.
|
|
61
|
+
*/
|
|
62
|
+
export declare function fetchUpstream(pin: UpstreamPin, paths: AllowlistPath[], options?: FetchOptions): FetchResult;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { MaterializedFile, UpstreamPin } from "./fetch.js";
|
|
2
|
+
export interface DenominatorReport {
|
|
3
|
+
/** Distinct upstream ids across every product-catalog category file. */
|
|
4
|
+
catalogueUnion: number;
|
|
5
|
+
/** Per-category distinct-id counts, in the order WS-13 §1's table lists them. */
|
|
6
|
+
byCategory: Array<{
|
|
7
|
+
category: string;
|
|
8
|
+
count: number;
|
|
9
|
+
}>;
|
|
10
|
+
/** Ids appearing in more than one category file — each would be double-counted by a naive sum. */
|
|
11
|
+
duplicatedAcrossCategories: string[];
|
|
12
|
+
/** Naive sum of per-category counts, i.e. what double-counting would produce. */
|
|
13
|
+
categorySum: number;
|
|
14
|
+
/** Keys of the backend `REGISTRY` map — the EXECUTABLE catalog, always smaller than the product one. */
|
|
15
|
+
registryEntries: number;
|
|
16
|
+
/**
|
|
17
|
+
* How many of those keys the literal walker could actually RESOLVE to an entry object.
|
|
18
|
+
*
|
|
19
|
+
* Reported separately and deliberately: many upstream entries are built by a helper call
|
|
20
|
+
* (`buildOpenAiCompatibleRegistryEntry({…})`), which the extractor rejects as an executable value,
|
|
21
|
+
* so their bindings do not exist. Folding the two numbers into one would report the extractor's
|
|
22
|
+
* own blind spot as an upstream fact — which is exactly the kind of quiet denominator error this
|
|
23
|
+
* report exists to expose.
|
|
24
|
+
*/
|
|
25
|
+
registryEntriesResolved: number;
|
|
26
|
+
/** Catalogued ids with no backend registry entry. */
|
|
27
|
+
catalogueWithoutRegistry: number;
|
|
28
|
+
/** Registry ids with no product-catalog row. */
|
|
29
|
+
registryWithoutCatalogue: number;
|
|
30
|
+
/** What upstream itself CLAIMS, per claim source. */
|
|
31
|
+
claims: Array<{
|
|
32
|
+
sourcePath: string;
|
|
33
|
+
claimed: number | undefined;
|
|
34
|
+
note: string;
|
|
35
|
+
}>;
|
|
36
|
+
/** Human-readable summary, embedded verbatim in PROVENANCE.md. */
|
|
37
|
+
summary: string;
|
|
38
|
+
}
|
|
39
|
+
export interface DenominatorInput {
|
|
40
|
+
/** category name -> the distinct upstream ids it contains. */
|
|
41
|
+
byCategory: ReadonlyMap<string, ReadonlySet<string>>;
|
|
42
|
+
/** Every key of the backend `REGISTRY` map, read from the object's TEXT (never from resolved values). */
|
|
43
|
+
registryIds: ReadonlySet<string>;
|
|
44
|
+
/** The subset whose entry object the literal walker resolved. */
|
|
45
|
+
registryIdsResolved: ReadonlySet<string>;
|
|
46
|
+
/** Claim source path -> its raw text, scanned for the headline provider count. */
|
|
47
|
+
claimSources: ReadonlyArray<{
|
|
48
|
+
sourcePath: string;
|
|
49
|
+
text: string;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Pull upstream's own headline provider count out of a claim source.
|
|
54
|
+
*
|
|
55
|
+
* Two spellings at the pin: the generated reference says "Total providers: **352**", the README says
|
|
56
|
+
* "352 AI providers". Both are matched, and a source that states neither returns `undefined` rather
|
|
57
|
+
* than a zero — "we could not find a claim" and "upstream claims none" are different facts.
|
|
58
|
+
*/
|
|
59
|
+
export declare function findClaimedProviderCount(text: string): number | undefined;
|
|
60
|
+
export declare function computeDenominator(input: DenominatorInput): DenominatorReport;
|
|
61
|
+
export interface ExtractionManifest {
|
|
62
|
+
$comment: string;
|
|
63
|
+
upstream: UpstreamPin & {
|
|
64
|
+
packageVersion?: string;
|
|
65
|
+
};
|
|
66
|
+
extractorVersion: string;
|
|
67
|
+
generatedFrom: string;
|
|
68
|
+
/** Files COPIED into this repository (upstream licence/notices), with their provenance. */
|
|
69
|
+
copiedFiles: Array<{
|
|
70
|
+
upstreamPath: string;
|
|
71
|
+
localPath: string;
|
|
72
|
+
blobId: string;
|
|
73
|
+
sha256: string;
|
|
74
|
+
bytes: number;
|
|
75
|
+
licence: string;
|
|
76
|
+
modifications: "none";
|
|
77
|
+
}>;
|
|
78
|
+
/** Files READ during extraction and deliberately NOT copied. */
|
|
79
|
+
readOnlyFiles: Array<{
|
|
80
|
+
path: string;
|
|
81
|
+
blobId: string;
|
|
82
|
+
sha256: string;
|
|
83
|
+
bytes: number;
|
|
84
|
+
role: string;
|
|
85
|
+
admittedBy: string;
|
|
86
|
+
}>;
|
|
87
|
+
outOfAllowlistImports: string[];
|
|
88
|
+
}
|
|
89
|
+
export interface ManifestInput {
|
|
90
|
+
pin: UpstreamPin & {
|
|
91
|
+
packageVersion?: string;
|
|
92
|
+
};
|
|
93
|
+
extractorVersion: string;
|
|
94
|
+
files: readonly MaterializedFile[];
|
|
95
|
+
/** upstream path -> where it was copied to inside this repository. */
|
|
96
|
+
copiedTo: ReadonlyMap<string, string>;
|
|
97
|
+
outOfAllowlistImports: readonly string[];
|
|
98
|
+
}
|
|
99
|
+
export declare function buildExtractionManifest(input: ManifestInput): ExtractionManifest;
|
|
100
|
+
export type ProvenanceClass = "copied-verbatim" | "mechanically-normalized" | "official-doc-derived" | "live-probe-proven" | "local-override";
|
|
101
|
+
export interface FieldProvenance {
|
|
102
|
+
field: string;
|
|
103
|
+
provenance: ProvenanceClass;
|
|
104
|
+
note: string;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The per-field classification report §6 asks for, as DATA.
|
|
108
|
+
*
|
|
109
|
+
* PROVENANCE.md renders this table rather than restating it in prose, because a hand-written
|
|
110
|
+
* provenance table is exactly the document that goes stale the first time the mapper changes and
|
|
111
|
+
* nobody notices — and a stale provenance claim is worse than none.
|
|
112
|
+
*/
|
|
113
|
+
export declare const FIELD_PROVENANCE: readonly FieldProvenance[];
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type LiteralValue = string | number | boolean | null | LiteralValue[] | {
|
|
2
|
+
[key: string]: LiteralValue;
|
|
3
|
+
};
|
|
4
|
+
/**
|
|
5
|
+
* Why something did not reach the catalog. The FIELD/VALUE classes are this module's; the CATEGORY
|
|
6
|
+
* and CURATION classes are `ledgers.ts`'s, and both live in one union so the committed ledger has a
|
|
7
|
+
* single closed vocabulary a reviewer can diff across upstream bumps.
|
|
8
|
+
*/
|
|
9
|
+
export type ExclusionClass = "executable-value" | "dynamic-expression" | "env-read" | "identity-header" | "url-builder" | "credential-material" | "unresolved-reference" | "unsupported-shape" | "category-no-auth" | "category-oauth" | "category-web-cookie" | "category-search" | "category-audio" | "category-upstream-proxy" | "category-cloud-agent" | "category-system" | "category-local-live-discovery" | "not-allowlisted" | "no-registry-entry" | "duplicate-id" | "unrepresentable-protocol"
|
|
10
|
+
/**
|
|
11
|
+
* NOT an exclusion. A row that DID reach the catalog, carrying a reviewed, recorded deviation from
|
|
12
|
+
* what the pinned tree literally says (a corrected wire id, an adapter the protocol does not imply,
|
|
13
|
+
* a per-row status). It shares the ledger with the exclusions because the ledger's job is "every
|
|
14
|
+
* place the catalog and its source differ, with the reason" — and filing a deliberate normalization
|
|
15
|
+
* under `unrepresentable-protocol` made the ledger's own counts lie about what was dropped.
|
|
16
|
+
*/
|
|
17
|
+
| "reviewed-normalization"
|
|
18
|
+
/** A row excluded because it is not a language model at all (WS-13 §4: `tts`/`stt`/media rows never feed the worker-model picker). */
|
|
19
|
+
| "out-of-scope";
|
|
20
|
+
export interface Rejection {
|
|
21
|
+
scope: "module" | "provider" | "model" | "field";
|
|
22
|
+
/** The upstream provider id this rejection belongs to, or `""` for a module-level one. */
|
|
23
|
+
upstreamId: string;
|
|
24
|
+
/** Dotted location inside the module, e.g. `geminiProvider.oauth`. */
|
|
25
|
+
path: string;
|
|
26
|
+
sourcePath: string;
|
|
27
|
+
reason: string;
|
|
28
|
+
exclusionClass: ExclusionClass;
|
|
29
|
+
}
|
|
30
|
+
export interface ExtractOptions {
|
|
31
|
+
/**
|
|
32
|
+
* Exported literals from other ALLOWLISTED modules, keyed `<sourcePath>#<exportName>` and
|
|
33
|
+
* `#<exportName>` (the latter is the "any allowlisted module" fallback used when an import
|
|
34
|
+
* specifier resolves to a materialized file whose own exports are already known).
|
|
35
|
+
*/
|
|
36
|
+
externals?: ReadonlyMap<string, LiteralValue>;
|
|
37
|
+
/** Every repository-relative path that was materialized, so an out-of-allowlist import is visible. */
|
|
38
|
+
materializedPaths?: ReadonlySet<string>;
|
|
39
|
+
}
|
|
40
|
+
export interface ModuleLiterals {
|
|
41
|
+
sourcePath: string;
|
|
42
|
+
/** Top-level `const` bindings whose initializer was fully accepted. */
|
|
43
|
+
values: Map<string, LiteralValue>;
|
|
44
|
+
rejections: Rejection[];
|
|
45
|
+
/** Import specifiers naming a module outside the materialized allowlist. */
|
|
46
|
+
outOfAllowlistImports: string[];
|
|
47
|
+
/** Imported name -> the module specifier it came from, for `unresolved-reference` reasons. */
|
|
48
|
+
importOrigins: Map<string, string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a relative import specifier against a repository-relative source path.
|
|
52
|
+
*
|
|
53
|
+
* Upstream mixes explicit `./shared.ts` specifiers with extensionless `./gateways` ones, so an
|
|
54
|
+
* `exists` predicate is consulted for the two implicit spellings. Without it every extensionless
|
|
55
|
+
* import inside the allowlist reads as OUT of the allowlist — a false boundary alarm that would
|
|
56
|
+
* bury the real ones.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveRelativeSpecifier(sourcePath: string, specifier: string, exists?: (path: string) => boolean): string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Parse one module and extract its accepted top-level literal bindings.
|
|
61
|
+
*
|
|
62
|
+
* Never throws on hostile input: a syntactically broken file yields no values and a module-scope
|
|
63
|
+
* rejection. Depth-bounded so a pathological nesting cannot overflow the stack.
|
|
64
|
+
*/
|
|
65
|
+
export declare function extractModuleLiterals(sourcePath: string, text: string, options?: ExtractOptions): ModuleLiterals;
|
|
66
|
+
/**
|
|
67
|
+
* Extract a whole materialized set, resolving cross-module identifier references between
|
|
68
|
+
* ALLOWLISTED files only.
|
|
69
|
+
*
|
|
70
|
+
* ITERATES TO A FIXPOINT rather than running a fixed number of passes, because upstream's import
|
|
71
|
+
* graph is neither topologically ordered nor shallow. The real chain at the pin is three deep and
|
|
72
|
+
* runs BACKWARDS against filename order:
|
|
73
|
+
*
|
|
74
|
+
* open-sse/config/providers/index.ts (REGISTRY -> each provider identifier)
|
|
75
|
+
* <- registry/openai/index.ts (a model spreads GPT_5_6_API_CAPABILITIES)
|
|
76
|
+
* <- shared.ts (where that constant is declared)
|
|
77
|
+
*
|
|
78
|
+
* `index.ts` sorts FIRST and `shared.ts` LAST, so a two-pass version resolved the leaf entries but
|
|
79
|
+
* handed `index.ts` the stale first-pass copies — and the whole GPT-5.6 family silently lost its
|
|
80
|
+
* context window, modalities and Responses endpoint while every row still looked plausible. A
|
|
81
|
+
* fixpoint has no such off-by-one: it stops when a pass adds nothing, and the pass budget exists
|
|
82
|
+
* only so a pathological graph terminates rather than looping.
|
|
83
|
+
*
|
|
84
|
+
* Values accumulate into ONE map that later files in the SAME pass can already read, so a forward
|
|
85
|
+
* dependency costs no extra pass at all.
|
|
86
|
+
*/
|
|
87
|
+
export declare function extractAll(files: ReadonlyArray<{
|
|
88
|
+
path: string;
|
|
89
|
+
text: string;
|
|
90
|
+
}>): {
|
|
91
|
+
modules: Map<string, ModuleLiterals>;
|
|
92
|
+
exports: Map<string, LiteralValue>;
|
|
93
|
+
passes: number;
|
|
94
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { AdmissionTier, ModelFamilyDescriptor, ProviderProtocol, WinterCatalog, WinterModelDescriptor, WinterProviderDescriptor } from "../types.js";
|
|
2
|
+
import type { ExclusionClass, LiteralValue, Rejection } from "./literal-extractor.js";
|
|
3
|
+
export interface AllowlistProviderRow {
|
|
4
|
+
upstreamId: string;
|
|
5
|
+
winterId: string;
|
|
6
|
+
expectedCategory: string;
|
|
7
|
+
/**
|
|
8
|
+
* The `status` every extracted model of this provider starts at. Absent means `candidate`.
|
|
9
|
+
*
|
|
10
|
+
* R6-16 puts the native-cloud families in the catalog as `experimental`; nothing here can ever
|
|
11
|
+
* reach `supported`, which requires the behavioural corpus (WS-13 §13).
|
|
12
|
+
*/
|
|
13
|
+
initialModelStatus?: "candidate" | "experimental";
|
|
14
|
+
/**
|
|
15
|
+
* The display name this provider's row must carry, when the product catalog's own `name` would be
|
|
16
|
+
* AMBIGUOUS rather than merely different.
|
|
17
|
+
*
|
|
18
|
+
* R6b-5 puts a vendor's two documented dialects on two rows, and the upstream product catalog has
|
|
19
|
+
* ONE name for the vendor — so `zai` and `zai-anthropic` both read "Z.AI" in a picker, which is a
|
|
20
|
+
* row a user cannot choose between. The override is a reviewed allowlist edit like `winterId` and
|
|
21
|
+
* `adapterIdOverride`, and it is RECORDED in the ledger; it is not a licence to rename providers
|
|
22
|
+
* for taste.
|
|
23
|
+
*/
|
|
24
|
+
displayNameOverride?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The adapter this provider's rows must name, when it is NOT the one its protocol implies.
|
|
27
|
+
*
|
|
28
|
+
* Vertex shares the GenerateContent dialect with the Gemini API, so deriving the adapter from the
|
|
29
|
+
* protocol named `winter.google-generate-content` — and a registry resolves an adapter BY ID, so a
|
|
30
|
+
* Vertex session would have been served by the Gemini API adapter with no location-scoped URL and
|
|
31
|
+
* no ADC credential. A protocol is not an adapter.
|
|
32
|
+
*/
|
|
33
|
+
adapterIdOverride?: string;
|
|
34
|
+
risk: {
|
|
35
|
+
class: "approved" | "review-required" | "blocked";
|
|
36
|
+
reasons: string[];
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* WS-13b §1: how the vendor charges for the credential Winter uses. COPIED VERBATIM onto the
|
|
40
|
+
* generated row — the extractor never derives it, because nothing in the pinned upstream tree
|
|
41
|
+
* states it and a derivation would be Winter guessing at a billing fact.
|
|
42
|
+
*/
|
|
43
|
+
pricingBasis: "token" | "subscription" | "free";
|
|
44
|
+
/**
|
|
45
|
+
* WS-13b §1 (D21): the documented third-party path this row ships through, with its citation.
|
|
46
|
+
* Also copied verbatim, and REQUIRED: an allowlist entry missing it fails the run rather than
|
|
47
|
+
* producing a row whose admission nobody can check — see step (0) at the top of
|
|
48
|
+
* `buildUpstreamLayer`, which refuses before anything is classified.
|
|
49
|
+
*/
|
|
50
|
+
admission: {
|
|
51
|
+
basis: "api-key" | "oauth-documented" | "keyless-documented" | "local" | "cloud-credential";
|
|
52
|
+
citation: string;
|
|
53
|
+
tier: AdmissionTier;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* WS-13b §7/§8.4 (fix-wave R-FW-2): the vendor's own second identity field, copied verbatim like
|
|
57
|
+
* the two above. Absent for every allowlist row today -- only a vendor that DOCUMENTS such a field
|
|
58
|
+
* gets one, and none of the extracted entries does.
|
|
59
|
+
*/
|
|
60
|
+
identityHeaders?: Record<string, string>;
|
|
61
|
+
}
|
|
62
|
+
/** A hand-reviewed, per-model deviation from what the pinned upstream tree says. Always recorded. */
|
|
63
|
+
export interface ModelOverride {
|
|
64
|
+
/** Corrects a wire id upstream spells differently from the provider's own documentation. */
|
|
65
|
+
id?: string;
|
|
66
|
+
/** Keeps the row OUT of the catalog entirely (WS-13 §4: a non-`llm` row never reaches the worker-model picker). */
|
|
67
|
+
exclude?: boolean;
|
|
68
|
+
/** Overrides the provider's `initialModelStatus` for this one row. */
|
|
69
|
+
status?: "candidate" | "experimental";
|
|
70
|
+
why: string;
|
|
71
|
+
}
|
|
72
|
+
export interface CategoryDisposition {
|
|
73
|
+
disposition: "blocked" | "candidate-pool" | "winter-owned";
|
|
74
|
+
exclusionClass: ExclusionClass;
|
|
75
|
+
reason: string;
|
|
76
|
+
}
|
|
77
|
+
export interface Allowlist {
|
|
78
|
+
allowlistVersion: number;
|
|
79
|
+
paths: Array<{
|
|
80
|
+
pattern: string;
|
|
81
|
+
role: "extract" | "claim" | "notice";
|
|
82
|
+
why: string;
|
|
83
|
+
}>;
|
|
84
|
+
providers: AllowlistProviderRow[];
|
|
85
|
+
categoryDispositions: Record<string, CategoryDisposition>;
|
|
86
|
+
/** providerId -> upstream model id -> a hand-reviewed override. Every field is optional but `why`. */
|
|
87
|
+
modelOverrides?: Record<string, Record<string, ModelOverride>>;
|
|
88
|
+
blocked: Array<{
|
|
89
|
+
upstreamId: string;
|
|
90
|
+
reason: string;
|
|
91
|
+
}>;
|
|
92
|
+
importBoundary: {
|
|
93
|
+
resolveIdentifiersWithin: string;
|
|
94
|
+
failOnUnresolvedFields: string[];
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Upstream `format` -> the path THAT FORMAT'S adapters append to a base URL.
|
|
99
|
+
*
|
|
100
|
+
* Removing this suffix from upstream's `baseUrl` is how the mapper reaches the API ROOT a
|
|
101
|
+
* `defaultEndpoints.api` must carry. See the long note at the `baseUrl` block in
|
|
102
|
+
* `buildUpstreamLayer` for why a consumed row cannot hold the verbatim path, and why this is the
|
|
103
|
+
* only transformation permitted (never a trim to an origin).
|
|
104
|
+
*
|
|
105
|
+
* Keyed on the upstream FORMAT, not on the Winter protocol or adapter: the suffix is a fact about
|
|
106
|
+
* the URL as upstream wrote it. `deepseek` is `format: "openai-responses"` at
|
|
107
|
+
* `https://api.deepseek.com/responses`; `openai` is `format: "openai"` at
|
|
108
|
+
* `https://api.openai.com/v1/chat/completions` even though its allowlist row overrides the adapter
|
|
109
|
+
* to Responses. Both strip their own format's suffix and land on the root their adapter expects.
|
|
110
|
+
*
|
|
111
|
+
* Exported so a test can assert the strip reproduces the five hand-authored overlay endpoints.
|
|
112
|
+
*/
|
|
113
|
+
export declare const FORMAT_ENDPOINT_SUFFIX: Readonly<Record<string, string>>;
|
|
114
|
+
/**
|
|
115
|
+
* Adapter id -> the ONE protocol it speaks. The reverse of `PROTOCOL_TO_ADAPTER`, plus the adapters
|
|
116
|
+
* a protocol does not imply.
|
|
117
|
+
*
|
|
118
|
+
* Exported because the cross-layer gate must key on the ADAPTER, not on the provider's `protocols`
|
|
119
|
+
* list: resolution hands a model to `provider.adapterId` and nothing reads `protocols` at all, so a
|
|
120
|
+
* gate that consulted the list passed a responses-only model under a provider that merely DECLARED
|
|
121
|
+
* `openai-responses` while its adapter spoke Chat Completions.
|
|
122
|
+
*/
|
|
123
|
+
export declare const ADAPTER_PROTOCOL: Readonly<Record<string, ProviderProtocol>>;
|
|
124
|
+
/**
|
|
125
|
+
* A model row as a LAYER carries it: everything a finished row has EXCEPT the two derived family
|
|
126
|
+
* fields (WS-13c §1).
|
|
127
|
+
*
|
|
128
|
+
* The two are stamped once, at assembly, by `stampFamilyFields` — and `stampFamilyFields` treats a
|
|
129
|
+
* value already on the row as an OVERLAY OVERRIDE it must never overwrite. So a layer that pre-filled
|
|
130
|
+
* them would freeze whatever it guessed: an upstream layer stamped with no families at hand would
|
|
131
|
+
* write `modelFamily: "other"` onto 540 rows, and the merge would then honour that "override"
|
|
132
|
+
* forever. The layer therefore does not carry them, and the committed `upstream-layer.json` is
|
|
133
|
+
* correct exactly as it stands.
|
|
134
|
+
*/
|
|
135
|
+
export type UnstampedModelDescriptor = Omit<WinterModelDescriptor, "modelFamily" | "canonicalModelId">;
|
|
136
|
+
export interface UpstreamLayer {
|
|
137
|
+
$comment: string;
|
|
138
|
+
providers: WinterProviderDescriptor[];
|
|
139
|
+
models: UnstampedModelDescriptor[];
|
|
140
|
+
rejections: LedgerRejection[];
|
|
141
|
+
}
|
|
142
|
+
/** A rejection row as it is COMMITTED — the extractor's `Rejection` plus its upstream identity. */
|
|
143
|
+
export interface LedgerRejection {
|
|
144
|
+
upstreamId: string;
|
|
145
|
+
scope: Rejection["scope"];
|
|
146
|
+
exclusionClass: ExclusionClass;
|
|
147
|
+
path: string;
|
|
148
|
+
sourcePath: string;
|
|
149
|
+
reason: string;
|
|
150
|
+
}
|
|
151
|
+
export interface BuildUpstreamLayerInput {
|
|
152
|
+
allowlist: Allowlist;
|
|
153
|
+
/** Upstream provider id -> its `RegistryEntry` literal. */
|
|
154
|
+
registry: ReadonlyMap<string, LiteralValue>;
|
|
155
|
+
/** Upstream provider id -> its product-catalog category (`apikey`, `web-cookie`, …). */
|
|
156
|
+
categories: ReadonlyMap<string, {
|
|
157
|
+
category: string;
|
|
158
|
+
sourcePath: string;
|
|
159
|
+
row: LiteralValue;
|
|
160
|
+
}>;
|
|
161
|
+
/** Upstream provider id -> the repository-relative path its registry entry was read from. */
|
|
162
|
+
registrySourcePaths: ReadonlyMap<string, string>;
|
|
163
|
+
commit: string;
|
|
164
|
+
/** ISO-8601 instant stamped onto every piece of extracted evidence. */
|
|
165
|
+
observedAt: string;
|
|
166
|
+
/** Field/value rejections the extractor already produced, keyed by source path. */
|
|
167
|
+
moduleRejections: readonly Rejection[];
|
|
168
|
+
}
|
|
169
|
+
export declare class ExtractionRefusal extends Error {
|
|
170
|
+
constructor(message: string);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Build the upstream layer from the materialized literals.
|
|
174
|
+
*
|
|
175
|
+
* Order of operations matters and is deliberate:
|
|
176
|
+
* 1. every upstream id in the product catalog is CLASSIFIED (category -> disposition);
|
|
177
|
+
* 2. an allowlisted id whose category is not `candidate-pool` FAILS the run — that is WS-13 §3
|
|
178
|
+
* step 7's blocked class transition, and it is the only reason the allowlist is checked against
|
|
179
|
+
* the catalog rather than simply trusted;
|
|
180
|
+
* 3. surviving ids are mapped, with unknown format/auth/executor values failing the row;
|
|
181
|
+
* 4. everything else lands in the ledger with a class.
|
|
182
|
+
*/
|
|
183
|
+
export declare function buildUpstreamLayer(input: BuildUpstreamLayerInput): UpstreamLayer;
|
|
184
|
+
/** Total, stable rejection ordering: a ledger diff between two upstream bumps must show CHANGES, not churn. */
|
|
185
|
+
export declare function compareRejections(a: LedgerRejection, b: LedgerRejection): number;
|
|
186
|
+
/** The overlay files a re-sync must NEVER write. Exported so the sync script's own guard and its test read the same list. */
|
|
187
|
+
export declare const OVERLAY_FILES: readonly ["overlay/providers.json", "overlay/models.json", "overlay/families.json"];
|
|
188
|
+
/**
|
|
189
|
+
* The overlay-wins merge, mirroring `scripts/provider-catalog.ts`.
|
|
190
|
+
*
|
|
191
|
+
* Used to VALIDATE a layer before the frozen script writes anything — never to write the committed
|
|
192
|
+
* catalog. Row-level, not field-level: WS-13 §7's rule is that upstream never overwrites overlay
|
|
193
|
+
* evidence, and a field-level merge would do exactly that for every field the overlay leaves out.
|
|
194
|
+
*
|
|
195
|
+
* WS-13c: the family stamp happens HERE and in the script, through the ONE `stampFamilyFields` both
|
|
196
|
+
* call — a second implementation of "which family is this" would make the standalone gate and the
|
|
197
|
+
* committed catalog disagree about a row while both reported success. `pipeline.test.ts` pins the
|
|
198
|
+
* two assemblers byte-identical on the real layers, which is what keeps the mirroring honest.
|
|
199
|
+
*
|
|
200
|
+
* `families` defaults to EMPTY, and that default is what the upstream layer's standalone check
|
|
201
|
+
* uses: the layer alone has none of the overlay rows the real slots point at, so validating it
|
|
202
|
+
* against the real families would fail `slot-model-missing` on rows that are present in the merged
|
|
203
|
+
* catalog and only there. With no families every row stamps `other`, which the validator accepts —
|
|
204
|
+
* the standalone gate is about the LAYER's own shape, and family assignment is the merged catalog's
|
|
205
|
+
* own gate (`bun run provider:catalog --check` plus the integrity suite).
|
|
206
|
+
*/
|
|
207
|
+
export declare function mergeLayers(upstream: {
|
|
208
|
+
providers: WinterProviderDescriptor[];
|
|
209
|
+
models: UnstampedModelDescriptor[];
|
|
210
|
+
}, overlay: {
|
|
211
|
+
providers: WinterProviderDescriptor[];
|
|
212
|
+
models: UnstampedModelDescriptor[];
|
|
213
|
+
}, pin: WinterCatalog["upstream"], families?: readonly ModelFamilyDescriptor[]): WinterCatalog;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { FamilySlot, ModelFamilyDescriptor, WinterCatalog, WinterModelDescriptor } from "./types.js";
|
|
2
|
+
export declare const SLOT_NAME_RE: RegExp;
|
|
3
|
+
export declare const FAMILY_ID_RE: RegExp;
|
|
4
|
+
export declare const CLAUDE_FAMILY_ID = "claude";
|
|
5
|
+
export declare const OTHER_FAMILY_ID = "other";
|
|
6
|
+
/** D25: reserved to the `claude` family, in the pinned order. */
|
|
7
|
+
export declare const CLAUDE_RESERVED_SLOT_NAMES: readonly string[];
|
|
8
|
+
/** "$10", "10 USD", "€2", "£1", "10 dollars" — pricing lives on rows, never in a slot description. */
|
|
9
|
+
export declare const CURRENCY_RE: RegExp;
|
|
10
|
+
/** The vendor's model identity with the provider's spelling removed (WS-13c §1, R13c-2). Deterministic; a per-row overlay `canonicalModelId` overrides it. */
|
|
11
|
+
export declare function canonicalModelIdOf(upstreamId: string): string;
|
|
12
|
+
export declare function familyIdOf(canonicalModelId: string, families: readonly ModelFamilyDescriptor[]): string;
|
|
13
|
+
export declare function stampFamilyFields<T extends {
|
|
14
|
+
upstreamId: string;
|
|
15
|
+
canonicalModelId?: string;
|
|
16
|
+
modelFamily?: string;
|
|
17
|
+
}>(rows: readonly T[], families: readonly ModelFamilyDescriptor[]): Array<T & {
|
|
18
|
+
canonicalModelId: string;
|
|
19
|
+
modelFamily: string;
|
|
20
|
+
}>;
|
|
21
|
+
export type SlotNameResolution = {
|
|
22
|
+
kind: "slot";
|
|
23
|
+
family: ModelFamilyDescriptor;
|
|
24
|
+
slot: FamilySlot;
|
|
25
|
+
advertised: boolean;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "ambiguous";
|
|
28
|
+
name: string;
|
|
29
|
+
candidates: string[];
|
|
30
|
+
} | {
|
|
31
|
+
kind: "unknown";
|
|
32
|
+
name: string;
|
|
33
|
+
};
|
|
34
|
+
/** WS-13c §3 acceptance: active set first; the Claude names always into `claude`; a unique foreign name; else ambiguous/unknown. */
|
|
35
|
+
export declare function resolveSlotName(name: string, activeFamilyId: string | undefined, families: readonly ModelFamilyDescriptor[]): SlotNameResolution;
|
|
36
|
+
export declare function familyOfModelKey(catalog: WinterCatalog, modelKey: string): ModelFamilyDescriptor | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Can this row serve a slot at all? (WS-13c §4 step 1.)
|
|
39
|
+
*
|
|
40
|
+
* ONE predicate, deliberately, and it is what fix round 1's I-3 closed: the validator's
|
|
41
|
+
* `slot-model-missing` check and `rowsForCanonicalId` had drifted into two answers — the validator
|
|
42
|
+
* asked only about `status`, the resolver also required a chat/responses endpoint. A slot whose only
|
|
43
|
+
* rows were `endpoints: ["embeddings"]` therefore VALIDATED and resolved to nothing, which makes the
|
|
44
|
+
* natural inference "the catalog validated, so every slot has a candidate row" false in exactly the
|
|
45
|
+
* place a lane relies on it.
|
|
46
|
+
*
|
|
47
|
+
* Structurally typed rather than taking a `WinterModelDescriptor`, because the validator's caller
|
|
48
|
+
* holds `unknown` JSON it has already shape-checked, not a narrowed row.
|
|
49
|
+
*/
|
|
50
|
+
export declare function isSlotServableRow(row: {
|
|
51
|
+
status: string;
|
|
52
|
+
endpoints: readonly string[];
|
|
53
|
+
}): boolean;
|
|
54
|
+
/** Candidate rows for a slot (WS-13c §4 step 1). The SAME predicate `validateCatalog` refuses a slot on. */
|
|
55
|
+
export declare function rowsForCanonicalId(catalog: WinterCatalog, canonicalModelId: string): WinterModelDescriptor[];
|
package/dist/families.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SLOT_NAME_RE2,
|
|
3
|
+
FAMILY_ID_RE2,
|
|
4
|
+
CLAUDE_FAMILY_ID2,
|
|
5
|
+
OTHER_FAMILY_ID2,
|
|
6
|
+
CLAUDE_RESERVED_SLOT_NAMES2,
|
|
7
|
+
CURRENCY_RE2,
|
|
8
|
+
canonicalModelIdOf2,
|
|
9
|
+
familyIdOf2,
|
|
10
|
+
stampFamilyFields2,
|
|
11
|
+
resolveSlotName2,
|
|
12
|
+
familyOfModelKey2,
|
|
13
|
+
isSlotServableRow2,
|
|
14
|
+
rowsForCanonicalId2
|
|
15
|
+
} from "./index-t40pzh81.js";
|
|
16
|
+
export {
|
|
17
|
+
CLAUDE_FAMILY_ID2 as CLAUDE_FAMILY_ID,
|
|
18
|
+
CLAUDE_RESERVED_SLOT_NAMES2 as CLAUDE_RESERVED_SLOT_NAMES,
|
|
19
|
+
CURRENCY_RE2 as CURRENCY_RE,
|
|
20
|
+
FAMILY_ID_RE2 as FAMILY_ID_RE,
|
|
21
|
+
OTHER_FAMILY_ID2 as OTHER_FAMILY_ID,
|
|
22
|
+
SLOT_NAME_RE2 as SLOT_NAME_RE,
|
|
23
|
+
canonicalModelIdOf2 as canonicalModelIdOf,
|
|
24
|
+
familyIdOf2 as familyIdOf,
|
|
25
|
+
familyOfModelKey2 as familyOfModelKey,
|
|
26
|
+
isSlotServableRow2 as isSlotServableRow,
|
|
27
|
+
resolveSlotName2 as resolveSlotName,
|
|
28
|
+
rowsForCanonicalId2 as rowsForCanonicalId,
|
|
29
|
+
stampFamilyFields2 as stampFamilyFields
|
|
30
|
+
};
|