ai-model-verifier 1.4.1 → 2.0.0
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 +79 -22
- package/dist/engine/context.d.ts +43 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +50 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/evidence.d.ts +40 -0
- package/dist/engine/evidence.d.ts.map +1 -0
- package/dist/engine/evidence.js +108 -0
- package/dist/engine/evidence.js.map +1 -0
- package/dist/engine/handshake.d.ts +16 -0
- package/dist/engine/handshake.d.ts.map +1 -0
- package/dist/{handshake.js → engine/handshake.js} +35 -63
- package/dist/engine/handshake.js.map +1 -0
- package/dist/engine/probe-runner.d.ts +14 -0
- package/dist/engine/probe-runner.d.ts.map +1 -0
- package/dist/engine/probe-runner.js +163 -0
- package/dist/engine/probe-runner.js.map +1 -0
- package/dist/engine/verdict.d.ts +15 -0
- package/dist/engine/verdict.d.ts.map +1 -0
- package/dist/engine/verdict.js +19 -0
- package/dist/engine/verdict.js.map +1 -0
- package/dist/highlight.d.ts +2 -2
- package/dist/highlight.d.ts.map +1 -1
- package/dist/highlight.js +10 -9
- package/dist/highlight.js.map +1 -1
- package/dist/{patterns.d.ts → identity/patterns.d.ts} +7 -0
- package/dist/identity/patterns.d.ts.map +1 -0
- package/dist/{patterns.js → identity/patterns.js} +7 -0
- package/dist/identity/patterns.js.map +1 -0
- package/dist/identity/signals.d.ts +15 -0
- package/dist/identity/signals.d.ts.map +1 -0
- package/dist/{signals.js → identity/signals.js} +15 -24
- package/dist/identity/signals.js.map +1 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/utils.d.ts +3 -1
- package/dist/internal/utils.d.ts.map +1 -1
- package/dist/internal/utils.js +12 -6
- package/dist/internal/utils.js.map +1 -1
- package/dist/models/catalog.d.ts +7 -0
- package/dist/models/catalog.d.ts.map +1 -0
- package/dist/models/catalog.js +23 -0
- package/dist/models/catalog.js.map +1 -0
- package/dist/models/facts.d.ts +56 -0
- package/dist/models/facts.d.ts.map +1 -0
- package/dist/models/facts.js +91 -0
- package/dist/models/facts.js.map +1 -0
- package/dist/models/index.d.ts +6 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +5 -0
- package/dist/models/index.js.map +1 -0
- package/dist/{substitution.d.ts → models/substitution.d.ts} +3 -4
- package/dist/models/substitution.d.ts.map +1 -0
- package/dist/{substitution.js → models/substitution.js} +3 -4
- package/dist/models/substitution.js.map +1 -0
- package/dist/models/tiers.d.ts +14 -0
- package/dist/models/tiers.d.ts.map +1 -0
- package/dist/models/tiers.js +35 -0
- package/dist/models/tiers.js.map +1 -0
- package/dist/probes/prompts.d.ts +23 -0
- package/dist/probes/prompts.d.ts.map +1 -0
- package/dist/probes/prompts.js +31 -0
- package/dist/probes/prompts.js.map +1 -0
- package/dist/probes/table.d.ts +37 -0
- package/dist/probes/table.d.ts.map +1 -0
- package/dist/probes/table.js +57 -0
- package/dist/probes/table.js.map +1 -0
- package/dist/registry.d.ts +26 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +23 -0
- package/dist/registry.js.map +1 -0
- package/dist/rules/index.d.ts +15 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +8 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/probe-ladder.d.ts +33 -0
- package/dist/rules/probe-ladder.d.ts.map +1 -0
- package/dist/rules/probe-ladder.js +156 -0
- package/dist/rules/probe-ladder.js.map +1 -0
- package/dist/{detectors → rules}/response-metadata.d.ts +11 -14
- package/dist/rules/response-metadata.d.ts.map +1 -0
- package/dist/{detectors → rules}/response-metadata.js +32 -15
- package/dist/rules/response-metadata.js.map +1 -0
- package/dist/rules/table.d.ts +27 -0
- package/dist/rules/table.d.ts.map +1 -0
- package/dist/rules/table.js +47 -0
- package/dist/rules/table.js.map +1 -0
- package/dist/{detectors → rules}/thinking-floor.d.ts +14 -29
- package/dist/rules/thinking-floor.d.ts.map +1 -0
- package/dist/rules/thinking-floor.js +119 -0
- package/dist/rules/thinking-floor.js.map +1 -0
- package/dist/rules/thinking-signature.d.ts +43 -0
- package/dist/rules/thinking-signature.d.ts.map +1 -0
- package/dist/rules/thinking-signature.js +72 -0
- package/dist/rules/thinking-signature.js.map +1 -0
- package/dist/{detectors → rules}/throughput.d.ts +7 -10
- package/dist/rules/throughput.d.ts.map +1 -0
- package/dist/{detectors → rules}/throughput.js +32 -11
- package/dist/rules/throughput.js.map +1 -0
- package/dist/{detectors → rules}/token-truth.d.ts +7 -13
- package/dist/rules/token-truth.d.ts.map +1 -0
- package/dist/rules/token-truth.js +112 -0
- package/dist/rules/token-truth.js.map +1 -0
- package/dist/{detectors → rules}/tokenizer-fingerprint.d.ts +7 -19
- package/dist/rules/tokenizer-fingerprint.d.ts.map +1 -0
- package/dist/{detectors → rules}/tokenizer-fingerprint.js +40 -58
- package/dist/rules/tokenizer-fingerprint.js.map +1 -0
- package/dist/rules/types.d.ts +57 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +2 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/transport.d.ts +10 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +40 -32
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +70 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/vendors/anthropic.d.ts +44 -0
- package/dist/vendors/anthropic.d.ts.map +1 -0
- package/dist/vendors/anthropic.js +114 -0
- package/dist/vendors/anthropic.js.map +1 -0
- package/dist/vendors/base-url.d.ts +3 -0
- package/dist/vendors/base-url.d.ts.map +1 -0
- package/dist/vendors/base-url.js +6 -0
- package/dist/vendors/base-url.js.map +1 -0
- package/dist/vendors/gemini.d.ts +44 -0
- package/dist/vendors/gemini.d.ts.map +1 -0
- package/dist/vendors/gemini.js +77 -0
- package/dist/vendors/gemini.js.map +1 -0
- package/dist/vendors/index.d.ts +9 -0
- package/dist/vendors/index.d.ts.map +1 -0
- package/dist/vendors/index.js +7 -0
- package/dist/vendors/index.js.map +1 -0
- package/dist/vendors/openai.d.ts +46 -0
- package/dist/vendors/openai.d.ts.map +1 -0
- package/dist/vendors/openai.js +77 -0
- package/dist/vendors/openai.js.map +1 -0
- package/dist/vendors/table.d.ts +113 -0
- package/dist/vendors/table.d.ts.map +1 -0
- package/dist/vendors/table.js +14 -0
- package/dist/vendors/table.js.map +1 -0
- package/dist/vendors/types.d.ts +93 -0
- package/dist/vendors/types.d.ts.map +1 -0
- package/dist/vendors/types.js +2 -0
- package/dist/vendors/types.js.map +1 -0
- package/dist/verify.d.ts +30 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +185 -0
- package/dist/verify.js.map +1 -0
- package/package.json +25 -10
- package/dist/detectors/response-metadata.d.ts.map +0 -1
- package/dist/detectors/response-metadata.js.map +0 -1
- package/dist/detectors/thinking-floor.d.ts.map +0 -1
- package/dist/detectors/thinking-floor.js +0 -135
- package/dist/detectors/thinking-floor.js.map +0 -1
- package/dist/detectors/thinking-signature.d.ts +0 -70
- package/dist/detectors/thinking-signature.d.ts.map +0 -1
- package/dist/detectors/thinking-signature.js +0 -177
- package/dist/detectors/thinking-signature.js.map +0 -1
- package/dist/detectors/throughput.d.ts.map +0 -1
- package/dist/detectors/throughput.js.map +0 -1
- package/dist/detectors/token-truth.d.ts.map +0 -1
- package/dist/detectors/token-truth.js +0 -142
- package/dist/detectors/token-truth.js.map +0 -1
- package/dist/detectors/tokenizer-fingerprint.d.ts.map +0 -1
- package/dist/detectors/tokenizer-fingerprint.js.map +0 -1
- package/dist/handshake.d.ts +0 -23
- package/dist/handshake.d.ts.map +0 -1
- package/dist/handshake.js.map +0 -1
- package/dist/internal/fixed-text.d.ts +0 -13
- package/dist/internal/fixed-text.d.ts.map +0 -1
- package/dist/internal/fixed-text.js +0 -13
- package/dist/internal/fixed-text.js.map +0 -1
- package/dist/models.d.ts +0 -5
- package/dist/models.d.ts.map +0 -1
- package/dist/models.js +0 -54
- package/dist/models.js.map +0 -1
- package/dist/nonce.d.ts +0 -4
- package/dist/nonce.d.ts.map +0 -1
- package/dist/nonce.js +0 -12
- package/dist/nonce.js.map +0 -1
- package/dist/patterns.d.ts.map +0 -1
- package/dist/patterns.js.map +0 -1
- package/dist/probes.d.ts +0 -10
- package/dist/probes.d.ts.map +0 -1
- package/dist/probes.js +0 -54
- package/dist/probes.js.map +0 -1
- package/dist/providers/anthropic.d.ts +0 -3
- package/dist/providers/anthropic.d.ts.map +0 -1
- package/dist/providers/anthropic.js +0 -67
- package/dist/providers/anthropic.js.map +0 -1
- package/dist/providers/config.d.ts +0 -34
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -14
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/gemini.d.ts +0 -3
- package/dist/providers/gemini.d.ts.map +0 -1
- package/dist/providers/gemini.js +0 -58
- package/dist/providers/gemini.js.map +0 -1
- package/dist/providers/openai.d.ts +0 -3
- package/dist/providers/openai.d.ts.map +0 -1
- package/dist/providers/openai.js +0 -56
- package/dist/providers/openai.js.map +0 -1
- package/dist/rules.d.ts +0 -6
- package/dist/rules.d.ts.map +0 -1
- package/dist/rules.js +0 -28
- package/dist/rules.js.map +0 -1
- package/dist/runner.d.ts +0 -39
- package/dist/runner.d.ts.map +0 -1
- package/dist/runner.js +0 -352
- package/dist/runner.js.map +0 -1
- package/dist/signals.d.ts +0 -11
- package/dist/signals.d.ts.map +0 -1
- package/dist/signals.js.map +0 -1
- package/dist/substitution.d.ts.map +0 -1
- package/dist/substitution.js.map +0 -1
- package/dist/verdict.d.ts +0 -26
- package/dist/verdict.d.ts.map +0 -1
- package/dist/verdict.js +0 -84
- package/dist/verdict.js.map +0 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Is that API endpoint really serving the model it claims?**
|
|
4
4
|
|
|
5
5
|
Resellers and relay stations sell "Claude Opus" or "GPT-5" and quietly serve
|
|
6
|
-
something cheaper. Given a `
|
|
6
|
+
something cheaper. Given a `baseUrl`, `apiKey` and `model`, this library
|
|
7
7
|
probes the endpoint and reports what it actually found: a substituted model, a
|
|
8
8
|
downgraded Claude tier, a stripped thinking signature, inflated token billing,
|
|
9
9
|
or a translation layer sitting in front of the real vendor.
|
|
@@ -13,36 +13,93 @@ engine runs in the browser, on a server, and inside our sync pipeline.
|
|
|
13
13
|
|
|
14
14
|
## Design
|
|
15
15
|
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
- **Adapters.** One object per wire format (`anthropic`, `openai`, `gemini`)
|
|
17
|
+
builds every request and reads every reply. Rules never see a URL.
|
|
18
|
+
- **Rules over evidence.** Every detection is a rule that declares the
|
|
19
|
+
evidence it needs (the four behavioural probes, a fixed-text pair, a thinking
|
|
20
|
+
reply, a floor reply). Evidence is collected once per run and shared; the
|
|
21
|
+
rule table's order is the verdict's precedence.
|
|
22
|
+
- **Model facts.** One glob table says which models think adaptively, which
|
|
23
|
+
tokenizer generation they bill with, which cannot switch reasoning off.
|
|
24
|
+
- **Isomorphic.** Web-standard APIs only, no `node:` imports. The HTTP call is
|
|
25
|
+
an injected `TransportFn`, so a browser can route through its backend and a
|
|
26
|
+
server through an SSRF-safe fetch.
|
|
22
27
|
- **Zero required dependencies.**
|
|
23
28
|
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm i ai-model-verifier
|
|
33
|
+
```
|
|
34
|
+
|
|
24
35
|
## Use
|
|
25
36
|
|
|
26
37
|
```ts
|
|
27
|
-
import {
|
|
28
|
-
|
|
38
|
+
import { verify } from "ai-model-verifier";
|
|
39
|
+
|
|
40
|
+
const result = await verify({
|
|
41
|
+
vendor: "anthropic",
|
|
42
|
+
baseUrl: "https://relay.example.com",
|
|
43
|
+
apiKey: process.env.RELAY_KEY!,
|
|
44
|
+
model: "claude-opus-4-6",
|
|
45
|
+
mode: "direct",
|
|
46
|
+
checks: { signature: true, tokenTruth: true, tokenizerFingerprint: true },
|
|
47
|
+
});
|
|
48
|
+
console.log(result.verdict, result.reasons, result.findings);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`runRules` judges chosen rules on a wire you already know works, without a
|
|
52
|
+
handshake, and is what a pipeline with its own connectivity probes wants:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { runRules } from "ai-model-verifier";
|
|
56
|
+
|
|
57
|
+
const run = await runRules({
|
|
58
|
+
vendor: "openai",
|
|
59
|
+
baseUrl,
|
|
60
|
+
apiKey,
|
|
61
|
+
model: "pool/claude-opus-4-6",
|
|
62
|
+
mode: "server",
|
|
63
|
+
transport: myFetch,
|
|
64
|
+
bodyExtras: { provider: ["bedrock"] },
|
|
65
|
+
only: ["coding-tool", "foreign", "served-model-mismatch", "quorum", "tokenizer-fingerprint"],
|
|
66
|
+
});
|
|
67
|
+
for (const f of run.findings) console.log(f.rule, f.severity, f.reason);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Extend without forking:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { createVerifier, defineRule, defineVendor, defineModelFacts } from "ai-model-verifier";
|
|
74
|
+
|
|
75
|
+
const { verify } = createVerifier({
|
|
76
|
+
vendors: [defineVendor({ id: "mywire", ... })],
|
|
77
|
+
rules: [defineRule({ id: "my-rule", layer: "probe", needs: ["probes"], applies: () => true, judge: async (ctx) => ... })],
|
|
78
|
+
omit: ["substituted"],
|
|
79
|
+
modelFacts: defineModelFacts([{ match: "my-model*", alwaysThinks: true }]),
|
|
80
|
+
});
|
|
29
81
|
```
|
|
30
82
|
|
|
31
83
|
## What it detects
|
|
32
84
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
85
|
+
Verdict rules, in precedence order: a must-think model answering with no
|
|
86
|
+
reasoning tokens (`thinking-floor`, opt-in); a tokenizer fingerprint matching a
|
|
87
|
+
cheaper tier under a caller-supplied calibration (`tokenizer-fingerprint`,
|
|
88
|
+
opt-in; ships no table, see the rule's note); `coding-tool` refusals (Kiro or
|
|
89
|
+
Amazon Q wearing a Claude badge); scam pages; CJK language leaks from a
|
|
90
|
+
substituted Chinese model; response-mixing proxies (`mux`); a foreign vendor
|
|
91
|
+
named as the maker; the reply's own `model` field naming another tier
|
|
92
|
+
(`served-model-mismatch`) or another model (`substituted`); and a probe quorum
|
|
93
|
+
that only fails on non-transient evidence.
|
|
94
|
+
|
|
95
|
+
Note rules report without judging: the Claude thinking signature, token
|
|
96
|
+
accounting against the billed usage, a self-reported tier, the response
|
|
97
|
+
envelope (which vendor's shape and id minted the reply) and throughput.
|
|
98
|
+
|
|
99
|
+
## Tests
|
|
100
|
+
|
|
101
|
+
`bun test` replays recordings made with the 1.4.1 build (`test/parity`) and a
|
|
102
|
+
set of synthetic endpoints. `test/parity/record.ts` records a new fixture.
|
|
46
103
|
|
|
47
104
|
## License
|
|
48
105
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ModelFacts } from "../models/facts";
|
|
2
|
+
import type { TierSignatures } from "../rules/tokenizer-fingerprint";
|
|
3
|
+
import type { TransportFn, TransportMode, TransportResult } from "../transport";
|
|
4
|
+
import type { Checks, ProbeAttempt } from "../types";
|
|
5
|
+
import type { BuiltRequest, ChatRequest, VendorAdapter, WireCtx } from "../vendors/types";
|
|
6
|
+
export type ResolvedChecks = {
|
|
7
|
+
signature: {
|
|
8
|
+
strict: boolean;
|
|
9
|
+
} | null;
|
|
10
|
+
tokenTruth: boolean;
|
|
11
|
+
thinkingFloor: {
|
|
12
|
+
minCompletionTokens: number | null;
|
|
13
|
+
} | null;
|
|
14
|
+
tokenizerFingerprint: {
|
|
15
|
+
signatures: TierSignatures | undefined;
|
|
16
|
+
} | null;
|
|
17
|
+
};
|
|
18
|
+
export declare function resolveChecks(checks: Checks | undefined): ResolvedChecks;
|
|
19
|
+
/** Everything a rule may read about the run. */
|
|
20
|
+
export type RunCtx<V extends string = string> = {
|
|
21
|
+
model: string;
|
|
22
|
+
facts: ModelFacts;
|
|
23
|
+
requestedVendor: V;
|
|
24
|
+
/** Wire the requests go over (after a handshake fallback, not the requested one). */
|
|
25
|
+
wire: VendorAdapter<V>;
|
|
26
|
+
mode: TransportMode;
|
|
27
|
+
direct: boolean;
|
|
28
|
+
baseUrl: string;
|
|
29
|
+
apiKey: string;
|
|
30
|
+
timeoutMs: number;
|
|
31
|
+
transport: TransportFn;
|
|
32
|
+
bodyExtras?: Record<string, unknown>;
|
|
33
|
+
nonce: () => string;
|
|
34
|
+
onProbe?: (attempt: ProbeAttempt) => void;
|
|
35
|
+
checks: ResolvedChecks;
|
|
36
|
+
};
|
|
37
|
+
export declare const wireCtx: (ctx: RunCtx) => WireCtx;
|
|
38
|
+
/** Multipart and binary bodies pass through untouched. */
|
|
39
|
+
export declare function mergeBodyExtras(body: unknown, extras?: Record<string, unknown>): unknown;
|
|
40
|
+
export declare function callWire(ctx: RunCtx, built: BuiltRequest): Promise<TransportResult>;
|
|
41
|
+
export declare const buildChat: (ctx: RunCtx, req: Omit<ChatRequest, "model">) => BuiltRequest;
|
|
42
|
+
export declare const chat: (ctx: RunCtx, req: Omit<ChatRequest, "model">) => Promise<TransportResult>;
|
|
43
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/engine/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,aAAa,EACb,OAAO,EACR,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACtC,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE;QAAE,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC7D,oBAAoB,EAAE;QAAE,UAAU,EAAE,cAAc,GAAG,SAAS,CAAA;KAAE,GAAG,IAAI,CAAC;CACzE,CAAC;AAEF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,CA2BxE;AAED,gDAAgD;AAChD,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC;IAClB,eAAe,EAAE,CAAC,CAAC;IACnB,qFAAqF;IACrF,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,WAAW,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IAC1C,MAAM,EAAE,cAAc,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,OAAO,QAAS,MAAM,KAAG,OAKpC,CAAC;AAEH,0DAA0D;AAC1D,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,EACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAGT;AAED,wBAAgB,QAAQ,CACtB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED,eAAO,MAAM,SAAS,QACf,MAAM,OACN,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,KAC9B,YAC4D,CAAC;AAEhE,eAAO,MAAM,IAAI,QAAS,MAAM,OAAO,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,6BAC7B,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isRecord } from "../internal/utils";
|
|
2
|
+
export function resolveChecks(checks) {
|
|
3
|
+
const c = checks ?? {};
|
|
4
|
+
return {
|
|
5
|
+
signature: c.signature
|
|
6
|
+
? {
|
|
7
|
+
strict: typeof c.signature === "object" && c.signature.strict === true,
|
|
8
|
+
}
|
|
9
|
+
: null,
|
|
10
|
+
tokenTruth: c.tokenTruth === true,
|
|
11
|
+
thinkingFloor: c.thinkingFloor
|
|
12
|
+
? {
|
|
13
|
+
minCompletionTokens: typeof c.thinkingFloor === "object"
|
|
14
|
+
? (c.thinkingFloor.minCompletionTokens ?? null)
|
|
15
|
+
: null,
|
|
16
|
+
}
|
|
17
|
+
: null,
|
|
18
|
+
tokenizerFingerprint: c.tokenizerFingerprint
|
|
19
|
+
? {
|
|
20
|
+
signatures: typeof c.tokenizerFingerprint === "object"
|
|
21
|
+
? c.tokenizerFingerprint.signatures
|
|
22
|
+
: undefined,
|
|
23
|
+
}
|
|
24
|
+
: null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export const wireCtx = (ctx) => ({
|
|
28
|
+
baseUrl: ctx.baseUrl,
|
|
29
|
+
apiKey: ctx.apiKey,
|
|
30
|
+
direct: ctx.direct,
|
|
31
|
+
facts: ctx.facts,
|
|
32
|
+
});
|
|
33
|
+
/** Multipart and binary bodies pass through untouched. */
|
|
34
|
+
export function mergeBodyExtras(body, extras) {
|
|
35
|
+
if (!extras || !isRecord(body))
|
|
36
|
+
return body;
|
|
37
|
+
return { ...body, ...extras };
|
|
38
|
+
}
|
|
39
|
+
export function callWire(ctx, built) {
|
|
40
|
+
return ctx.transport({
|
|
41
|
+
mode: ctx.mode,
|
|
42
|
+
url: built.url,
|
|
43
|
+
headers: built.headers,
|
|
44
|
+
reqBody: mergeBodyExtras(built.body, ctx.bodyExtras),
|
|
45
|
+
timeoutMs: ctx.timeoutMs,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export const buildChat = (ctx, req) => ctx.wire.ops.chat({ model: ctx.model, ...req }, wireCtx(ctx));
|
|
49
|
+
export const chat = (ctx, req) => callWire(ctx, buildChat(ctx, req));
|
|
50
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/engine/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAmB7C,MAAM,UAAU,aAAa,CAAC,MAA0B;IACtD,MAAM,CAAC,GAAG,MAAM,IAAI,EAAE,CAAC;IACvB,OAAO;QACL,SAAS,EAAE,CAAC,CAAC,SAAS;YACpB,CAAC,CAAC;gBACE,MAAM,EACJ,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,IAAI;aACjE;YACH,CAAC,CAAC,IAAI;QACR,UAAU,EAAE,CAAC,CAAC,UAAU,KAAK,IAAI;QACjC,aAAa,EAAE,CAAC,CAAC,aAAa;YAC5B,CAAC,CAAC;gBACE,mBAAmB,EACjB,OAAO,CAAC,CAAC,aAAa,KAAK,QAAQ;oBACjC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,mBAAmB,IAAI,IAAI,CAAC;oBAC/C,CAAC,CAAC,IAAI;aACX;YACH,CAAC,CAAC,IAAI;QACR,oBAAoB,EAAE,CAAC,CAAC,oBAAoB;YAC1C,CAAC,CAAC;gBACE,UAAU,EACR,OAAO,CAAC,CAAC,oBAAoB,KAAK,QAAQ;oBACxC,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,UAAU;oBACnC,CAAC,CAAC,SAAS;aAChB;YACH,CAAC,CAAC,IAAI;KACT,CAAC;AACJ,CAAC;AAqBD,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAW,EAAW,EAAE,CAAC,CAAC;IAChD,OAAO,EAAE,GAAG,CAAC,OAAO;IACpB,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,KAAK,EAAE,GAAG,CAAC,KAAK;CACjB,CAAC,CAAC;AAEH,0DAA0D;AAC1D,MAAM,UAAU,eAAe,CAC7B,IAAa,EACb,MAAgC;IAEhC,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,QAAQ,CACtB,GAAW,EACX,KAAmB;IAEnB,OAAO,GAAG,CAAC,SAAS,CAAC;QACnB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC,UAAU,CAAC;QACpD,SAAS,EAAE,GAAG,CAAC,SAAS;KACzB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,GAAW,EACX,GAA+B,EACjB,EAAE,CAChB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,GAAG,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;AAEhE,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,GAAW,EAAE,GAA+B,EAAE,EAAE,CACnE,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { TransportResult } from "../transport";
|
|
2
|
+
import { type RunCtx } from "./context";
|
|
3
|
+
import { type ProbeEval } from "./probe-runner";
|
|
4
|
+
export type FixedTextEvidence = {
|
|
5
|
+
short: TransportResult;
|
|
6
|
+
long: TransportResult;
|
|
7
|
+
};
|
|
8
|
+
export type ThinkingReplyEvidence = {
|
|
9
|
+
res: TransportResult;
|
|
10
|
+
/** Only when the strict replay ran: the endpoint accepted the block back. */
|
|
11
|
+
replayAccepted?: boolean;
|
|
12
|
+
} | null;
|
|
13
|
+
/**
|
|
14
|
+
* What the rules can ask for. Each key is collected at most once per run, so
|
|
15
|
+
* two rules reading the same replies (token truth and the fingerprint both use
|
|
16
|
+
* the fixed text) cost one set of requests.
|
|
17
|
+
*/
|
|
18
|
+
export type EvidenceBag = {
|
|
19
|
+
probes: ProbeEval[];
|
|
20
|
+
fixedText: FixedTextEvidence;
|
|
21
|
+
/** null when the wire has no count endpoint or the short reply had no usage. */
|
|
22
|
+
countTokens: TransportResult | null;
|
|
23
|
+
/** null when the model has no thinking mode to ask for. */
|
|
24
|
+
thinkingReply: ThinkingReplyEvidence;
|
|
25
|
+
/** null when the model may legitimately answer without thinking. */
|
|
26
|
+
floorReply: TransportResult | null;
|
|
27
|
+
};
|
|
28
|
+
export type EvidenceKey = keyof EvidenceBag;
|
|
29
|
+
/** Collection order when several keys are needed: probes first, then the extras. */
|
|
30
|
+
export declare const EVIDENCE_ORDER: readonly ["probes", "thinkingReply", "fixedText", "countTokens", "floorReply"];
|
|
31
|
+
export declare class EvidenceStore {
|
|
32
|
+
private readonly probes;
|
|
33
|
+
private readonly fixedText;
|
|
34
|
+
private readonly countTokens;
|
|
35
|
+
private readonly thinkingReply;
|
|
36
|
+
private readonly floorReply;
|
|
37
|
+
constructor(ctx: RunCtx);
|
|
38
|
+
get<K extends EvidenceKey>(key: K): Promise<EvidenceBag[K]>;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=evidence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence.d.ts","sourceRoot":"","sources":["../../src/engine/evidence.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAA2B,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAQ/D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,eAAe,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,GAAG,EAAE,eAAe,CAAC;IACrB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,IAAI,CAAC;AAET;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,gFAAgF;IAChF,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,2DAA2D;IAC3D,aAAa,EAAE,qBAAqB,CAAC;IACrC,oEAAoE;IACpE,UAAU,EAAE,eAAe,GAAG,IAAI,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC;AAE5C,oFAAoF;AACpF,eAAO,MAAM,cAAc,gFAMgB,CAAC;AA8E5C,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAwC;IACpE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAuC;IACrE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwC;IAEnE,YAAY,GAAG,EAAE,MAAM,EAMtB;IAED,GAAG,CAAC,CAAC,SAAS,WAAW,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CAe7D"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { COUNT_PROBE_MAX_TOKENS, FLOOR_PROMPT, LONG_PROMPT, SHORT_PROMPT, SIGNATURE_PROMPT, } from "../probes/prompts";
|
|
2
|
+
import { callWire, chat, wireCtx } from "./context";
|
|
3
|
+
import { collectProbes } from "./probe-runner";
|
|
4
|
+
const THINKING_BUDGET_TOKENS = 2000;
|
|
5
|
+
/** Thinking plus answer; too small and an adaptive model skips thinking to fit. */
|
|
6
|
+
const SIGNATURE_MAX_TOKENS = 16000;
|
|
7
|
+
/** Room for the thoughts plus the word; too small truncates thinking and reads as a fake. */
|
|
8
|
+
const FLOOR_MAX_TOKENS = 2048;
|
|
9
|
+
/** Collection order when several keys are needed: probes first, then the extras. */
|
|
10
|
+
export const EVIDENCE_ORDER = [
|
|
11
|
+
"probes",
|
|
12
|
+
"thinkingReply",
|
|
13
|
+
"fixedText",
|
|
14
|
+
"countTokens",
|
|
15
|
+
"floorReply",
|
|
16
|
+
];
|
|
17
|
+
const ok = (r) => r.status !== null && r.status < 400;
|
|
18
|
+
async function collectFixedText(ctx) {
|
|
19
|
+
const ask = (prompt) => chat(ctx, {
|
|
20
|
+
maxTokens: COUNT_PROBE_MAX_TOKENS,
|
|
21
|
+
messages: [{ role: "user", content: prompt }],
|
|
22
|
+
});
|
|
23
|
+
const [short, long] = await Promise.all([
|
|
24
|
+
ask(SHORT_PROMPT),
|
|
25
|
+
ask(LONG_PROMPT),
|
|
26
|
+
]);
|
|
27
|
+
return { short, long };
|
|
28
|
+
}
|
|
29
|
+
async function collectCountTokens(ctx, store) {
|
|
30
|
+
const count = ctx.wire.ops.countTokens;
|
|
31
|
+
if (!count)
|
|
32
|
+
return null;
|
|
33
|
+
const { short } = await store.get("fixedText");
|
|
34
|
+
if (!ok(short) || ctx.wire.read.meta(short.data).usage?.prompt == null)
|
|
35
|
+
return null;
|
|
36
|
+
return callWire(ctx, count({
|
|
37
|
+
model: ctx.model,
|
|
38
|
+
messages: [{ role: "user", content: SHORT_PROMPT }],
|
|
39
|
+
}, wireCtx(ctx)));
|
|
40
|
+
}
|
|
41
|
+
async function collectThinkingReply(ctx) {
|
|
42
|
+
const kind = ctx.facts.thinking;
|
|
43
|
+
if (kind === "none")
|
|
44
|
+
return null;
|
|
45
|
+
const messages = [{ role: "user", content: SIGNATURE_PROMPT }];
|
|
46
|
+
// Non-streaming on purpose: streaming an adaptive model silently drops the
|
|
47
|
+
// thinking block, so a real Claude would look unsigned.
|
|
48
|
+
const res = await chat(ctx, {
|
|
49
|
+
maxTokens: SIGNATURE_MAX_TOKENS,
|
|
50
|
+
messages,
|
|
51
|
+
thinking: { kind, budgetTokens: THINKING_BUDGET_TOKENS },
|
|
52
|
+
});
|
|
53
|
+
if (!ctx.checks.signature?.strict || !ok(res))
|
|
54
|
+
return { res };
|
|
55
|
+
const found = ctx.wire.read.thinkingBlock?.(res.data);
|
|
56
|
+
if (!found)
|
|
57
|
+
return { res };
|
|
58
|
+
// Hand the block back. Against the vendor this re-validates the signature;
|
|
59
|
+
// through a relay it does not (a forged 308-byte signature returned 200 on
|
|
60
|
+
// both upstreams measured), so it is reported as evidence, never a verdict.
|
|
61
|
+
const replay = await chat(ctx, {
|
|
62
|
+
maxTokens: 1,
|
|
63
|
+
messages: [...messages, { role: "assistant", content: [found.block] }],
|
|
64
|
+
});
|
|
65
|
+
// A network failure proves nothing; only an explicit rejection counts.
|
|
66
|
+
return { res, replayAccepted: replay.status === null || replay.status < 400 };
|
|
67
|
+
}
|
|
68
|
+
async function collectFloorReply(ctx) {
|
|
69
|
+
if (!ctx.facts.alwaysThinks)
|
|
70
|
+
return null;
|
|
71
|
+
return chat(ctx, {
|
|
72
|
+
maxTokens: FLOOR_MAX_TOKENS,
|
|
73
|
+
messages: [{ role: "user", content: FLOOR_PROMPT }],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const memo = (fn) => {
|
|
77
|
+
let p;
|
|
78
|
+
return () => (p ??= fn());
|
|
79
|
+
};
|
|
80
|
+
export class EvidenceStore {
|
|
81
|
+
probes;
|
|
82
|
+
fixedText;
|
|
83
|
+
countTokens;
|
|
84
|
+
thinkingReply;
|
|
85
|
+
floorReply;
|
|
86
|
+
constructor(ctx) {
|
|
87
|
+
this.probes = memo(() => collectProbes(ctx));
|
|
88
|
+
this.fixedText = memo(() => collectFixedText(ctx));
|
|
89
|
+
this.countTokens = memo(() => collectCountTokens(ctx, this));
|
|
90
|
+
this.thinkingReply = memo(() => collectThinkingReply(ctx));
|
|
91
|
+
this.floorReply = memo(() => collectFloorReply(ctx));
|
|
92
|
+
}
|
|
93
|
+
get(key) {
|
|
94
|
+
switch (key) {
|
|
95
|
+
case "probes":
|
|
96
|
+
return this.probes();
|
|
97
|
+
case "fixedText":
|
|
98
|
+
return this.fixedText();
|
|
99
|
+
case "countTokens":
|
|
100
|
+
return this.countTokens();
|
|
101
|
+
case "thinkingReply":
|
|
102
|
+
return this.thinkingReply();
|
|
103
|
+
case "floorReply":
|
|
104
|
+
return this.floorReply();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=evidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/engine/evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ConnectivityError } from "../types";
|
|
2
|
+
import type { VendorAdapter } from "../vendors/types";
|
|
3
|
+
import { type RunCtx } from "./context";
|
|
4
|
+
export type HandshakeOutcome<V extends string> = {
|
|
5
|
+
ok: true;
|
|
6
|
+
wire: VendorAdapter<V>;
|
|
7
|
+
status: number;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
reason: ConnectivityError;
|
|
11
|
+
status: number | null;
|
|
12
|
+
corsBlocked: boolean;
|
|
13
|
+
};
|
|
14
|
+
/** The requested wire first, then its declared fallbacks, until one answers. */
|
|
15
|
+
export declare function runHandshake<V extends string>(ctx: RunCtx<V>, vendors: readonly VendorAdapter<V>[]): Promise<HandshakeOutcome<V>>;
|
|
16
|
+
//# sourceMappingURL=handshake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/engine/handshake.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAqB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAE3D,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,MAAM,IACzC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAwEN,gFAAgF;AAChF,wBAAsB,YAAY,CAAC,CAAC,SAAS,MAAM,EACjD,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,EACd,OAAO,EAAE,SAAS,aAAa,CAAC,CAAC,CAAC,EAAE,GACnC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CA6C9B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { isTransientError } from "../identity/signals";
|
|
2
|
+
import { vendorFor } from "../vendors/table";
|
|
3
|
+
import { callWire, wireCtx } from "./context";
|
|
4
4
|
const HANDSHAKE_PROMPT = "hi";
|
|
5
5
|
const HANDSHAKE_MAX_TOKENS = 1;
|
|
6
6
|
function classifyStatus(status) {
|
|
@@ -40,23 +40,13 @@ function rejectsModel(data) {
|
|
|
40
40
|
msg.includes("not supported") ||
|
|
41
41
|
msg.includes("does not exist")));
|
|
42
42
|
}
|
|
43
|
-
async function
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
baseUrl: args.baseUrl,
|
|
47
|
-
apiKey: args.apiKey,
|
|
48
|
-
model: args.model,
|
|
49
|
-
prompt: HANDSHAKE_PROMPT,
|
|
43
|
+
async function tryWire(ctx, wire) {
|
|
44
|
+
const built = wire.ops.chat({
|
|
45
|
+
model: ctx.model,
|
|
50
46
|
maxTokens: HANDSHAKE_MAX_TOKENS,
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
const res = await
|
|
54
|
-
mode: args.mode,
|
|
55
|
-
url: built.url,
|
|
56
|
-
headers: built.headers,
|
|
57
|
-
reqBody: built.body,
|
|
58
|
-
timeoutMs: args.timeoutMs,
|
|
59
|
-
});
|
|
47
|
+
messages: [{ role: "user", content: HANDSHAKE_PROMPT }],
|
|
48
|
+
}, wireCtx(ctx));
|
|
49
|
+
const res = await callWire(ctx, built);
|
|
60
50
|
if (res.corsBlocked)
|
|
61
51
|
return { outcome: "cors", status: null, corsBlocked: true };
|
|
62
52
|
if (res.status === null)
|
|
@@ -68,15 +58,21 @@ async function tryFormat(args) {
|
|
|
68
58
|
corsBlocked: false,
|
|
69
59
|
};
|
|
70
60
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const order =
|
|
61
|
+
/** The requested wire first, then its declared fallbacks, until one answers. */
|
|
62
|
+
export async function runHandshake(ctx, vendors) {
|
|
63
|
+
const order = [
|
|
64
|
+
ctx.wire,
|
|
65
|
+
...ctx.wire.fallbackWires.flatMap((id) => {
|
|
66
|
+
const w = vendorFor(vendors, id);
|
|
67
|
+
return w ? [w] : [];
|
|
68
|
+
}),
|
|
69
|
+
];
|
|
74
70
|
let sawAuth = false;
|
|
75
71
|
let sawModelRejected = false;
|
|
76
72
|
let sawTransient = false;
|
|
77
73
|
let lastStatus = null;
|
|
78
|
-
for (const
|
|
79
|
-
const r = await
|
|
74
|
+
for (const wire of order) {
|
|
75
|
+
const r = await tryWire(ctx, wire);
|
|
80
76
|
lastStatus = r.status;
|
|
81
77
|
if (r.outcome === "cors")
|
|
82
78
|
return {
|
|
@@ -86,12 +82,7 @@ export async function runHandshake(opts) {
|
|
|
86
82
|
corsBlocked: true,
|
|
87
83
|
};
|
|
88
84
|
if (r.outcome === "ok")
|
|
89
|
-
return {
|
|
90
|
-
ok: true,
|
|
91
|
-
resolvedProvider: provider,
|
|
92
|
-
mode: opts.mode,
|
|
93
|
-
status: r.status,
|
|
94
|
-
};
|
|
85
|
+
return { ok: true, wire, status: r.status };
|
|
95
86
|
if (r.outcome === "auth")
|
|
96
87
|
sawAuth = true;
|
|
97
88
|
if (r.outcome === "model")
|
|
@@ -99,43 +90,24 @@ export async function runHandshake(opts) {
|
|
|
99
90
|
if (r.outcome === "transient")
|
|
100
91
|
sawTransient = true;
|
|
101
92
|
}
|
|
93
|
+
const fail = (reason) => ({
|
|
94
|
+
ok: false,
|
|
95
|
+
reason,
|
|
96
|
+
status: lastStatus,
|
|
97
|
+
corsBlocked: false,
|
|
98
|
+
});
|
|
102
99
|
// Before the key check: a gateway that rejects the model name often answers
|
|
103
|
-
// 403 on the second
|
|
100
|
+
// 403 on the second wire too, which would otherwise read as a bad key.
|
|
104
101
|
if (sawModelRejected)
|
|
105
|
-
return
|
|
106
|
-
ok: false,
|
|
107
|
-
reason: "model-rejected",
|
|
108
|
-
status: lastStatus,
|
|
109
|
-
corsBlocked: false,
|
|
110
|
-
};
|
|
102
|
+
return fail("model-rejected");
|
|
111
103
|
if (sawAuth)
|
|
112
|
-
return
|
|
113
|
-
ok: false,
|
|
114
|
-
reason: "invalid-key",
|
|
115
|
-
status: lastStatus,
|
|
116
|
-
corsBlocked: false,
|
|
117
|
-
};
|
|
104
|
+
return fail("invalid-key");
|
|
118
105
|
if (lastStatus === null)
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
status: null,
|
|
123
|
-
corsBlocked: false,
|
|
124
|
-
};
|
|
125
|
-
// Every format answered 429/5xx: the endpoint is having a bad moment, which
|
|
126
|
-
// is never evidence that it speaks no supported format.
|
|
106
|
+
return fail("unreachable");
|
|
107
|
+
// Every wire answered 429/5xx: the endpoint is having a bad moment, which is
|
|
108
|
+
// never evidence that it speaks no supported format.
|
|
127
109
|
if (sawTransient)
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
reason: "endpoint-busy",
|
|
131
|
-
status: lastStatus,
|
|
132
|
-
corsBlocked: false,
|
|
133
|
-
};
|
|
134
|
-
return {
|
|
135
|
-
ok: false,
|
|
136
|
-
reason: "no-format",
|
|
137
|
-
status: lastStatus,
|
|
138
|
-
corsBlocked: false,
|
|
139
|
-
};
|
|
110
|
+
return fail("endpoint-busy");
|
|
111
|
+
return fail("no-format");
|
|
140
112
|
}
|
|
141
113
|
//# sourceMappingURL=handshake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/engine/handshake.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ProbeSignal } from "../probes/table";
|
|
2
|
+
import type { ProbeOutcome } from "../types";
|
|
3
|
+
import { type RunCtx } from "./context";
|
|
4
|
+
/** A probe's outcome plus what the rules need but the result never carries. */
|
|
5
|
+
export type ProbeEval = ProbeOutcome & {
|
|
6
|
+
text: string | undefined;
|
|
7
|
+
raw: unknown;
|
|
8
|
+
corsBlocked: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function probeReason(pass: boolean, signal: ProbeSignal, muxFailure: boolean, transient: boolean): string;
|
|
11
|
+
export declare function collectProbes(ctx: RunCtx): Promise<ProbeEval[]>;
|
|
12
|
+
/** The probe that generated the most: the reply worth reading metadata from. */
|
|
13
|
+
export declare function richestProbe(probes: readonly ProbeEval[]): ProbeEval | null;
|
|
14
|
+
//# sourceMappingURL=probe-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe-runner.d.ts","sourceRoot":"","sources":["../../src/engine/probe-runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAuB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAS7D,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG;IACrC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,GAAG,EAAE,OAAO,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,wBAAgB,WAAW,CACzB,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,OAAO,EACnB,SAAS,EAAE,OAAO,GACjB,MAAM,CAUR;AAsJD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAE/D;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,SAAS,GAAG,IAAI,CAM3E"}
|