dsh-tap 0.20.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/AGENTS.md +99 -0
- package/CHANGELOG.md +751 -0
- package/LICENSE +21 -0
- package/README.en.md +59 -0
- package/README.md +256 -0
- package/cordis.patch.yml +385 -0
- package/core/bridge.js +698 -0
- package/core/json-store.js +91 -0
- package/core/rotation.js +108 -0
- package/core/usage-meter.js +176 -0
- package/docs/diagnosis-cache-quota.md +181 -0
- package/docs/diagnosis-qoder-flash.md +67 -0
- package/docs/diagnosis-trae-3003.md +302 -0
- package/docs/goals/bridge-port-host-split.md +91 -0
- package/docs/goals/desktop-adaptation.md +106 -0
- package/docs/goals/qoder-cn-provider-design.md +308 -0
- package/docs/goals/settings-card-ux-redesign-plan.md +1680 -0
- package/docs/goals/settings-card-ux-redesign.md +162 -0
- package/docs/goals/trae-agent-v3.md +41 -0
- package/docs/goals/trae-work-cn-repair.md +44 -0
- package/docs/goals/v0.8-/351/242/235/345/272/246/345/217/257/350/247/201-/346/250/241/345/236/213/345/212/250/346/200/201/345/214/226-/345/244/232/346/234/215/345/212/241/345/225/206.md +85 -0
- package/docs/pitfalls.md +105 -0
- package/docs/reverse/trae-cloud-api.md +218 -0
- package/docs/reverse/trae-model-catalog.md +129 -0
- package/docs/reverse/traework-cn.md +520 -0
- package/docs/rules/STATE.md +198 -0
- package/docs/rules/content-moderation.md +54 -0
- package/docs/rules/dev-role-boundary.md +94 -0
- package/docs/rules/extra-providers.md +42 -0
- package/docs/rules/gateway-facts.md +91 -0
- package/docs/rules/oauth-handshake.md +76 -0
- package/docs/rules/prompt-cache.md +93 -0
- package/docs/rules/quota-signals.md +125 -0
- package/docs/rules/routing.md +84 -0
- package/docs/rules/templates/oauth-reverse-checklist.md +42 -0
- package/docs/rules/trae-surface.md +242 -0
- package/docs/rules/ua-validation.md +81 -0
- package/host-config.js +282 -0
- package/index.js +2306 -0
- package/lib/client.js +2893 -0
- package/local-scan.js +104 -0
- package/package.json +82 -0
- package/providers/ark/index.js +11 -0
- package/providers/bailian/index.js +10 -0
- package/providers/bigmodel/index.js +11 -0
- package/providers/codebuddy/agenttool.js +122 -0
- package/providers/codebuddy/catalog.js +227 -0
- package/providers/codebuddy/errors.js +44 -0
- package/providers/codebuddy/headers.js +36 -0
- package/providers/codebuddy/images.js +125 -0
- package/providers/codebuddy/index.js +123 -0
- package/providers/codebuddy/oauth.js +279 -0
- package/providers/deepseek/index.js +11 -0
- package/providers/moonshot/index.js +11 -0
- package/providers/openai-compat.js +177 -0
- package/providers/openrouter/index.js +27 -0
- package/providers/qoder/catalog.js +145 -0
- package/providers/qoder/cosy.js +419 -0
- package/providers/qoder/gateway.js +563 -0
- package/providers/qoder/index.js +116 -0
- package/providers/qoder/oauth.js +364 -0
- package/providers/qoder/qoder_auth.wasm +0 -0
- package/providers/qoder/quota.js +56 -0
- package/providers/qwen/index.js +15 -0
- package/providers/tool-pairing.js +129 -0
- package/providers/trae/catalog.js +103 -0
- package/providers/trae/errors.js +85 -0
- package/providers/trae/gateway.js +853 -0
- package/providers/trae/index.js +126 -0
- package/providers/trae/oauth.js +443 -0
- package/providers/trae/quota.js +75 -0
- package/providers/trae/remote.js +365 -0
- package/scripts/capture-cache.mjs +65 -0
- package/scripts/capture-traffic.mjs +83 -0
- package/scripts/hermes-probe-dev-role.mjs +263 -0
- package/scripts/measure-latency.mjs +253 -0
- package/scripts/probe-ark-thinking.mjs +298 -0
- package/scripts/probe-cache-decline.mjs +292 -0
- package/scripts/probe-cache-ttl.mjs +221 -0
- package/scripts/probe-cache.mjs +156 -0
- package/scripts/probe-codebuddy-efforts.mjs +355 -0
- package/scripts/probe-codebuddy-tier-wiring.mjs +244 -0
- package/scripts/probe-effort-gaps.mjs +133 -0
- package/scripts/probe-media.mjs +115 -0
- package/scripts/probe-moderation.mjs +159 -0
- package/scripts/probe-oauth.mjs +617 -0
- package/scripts/probe-qoder-attribution-arm8.mjs +92 -0
- package/scripts/probe-qoder-attribution-arm9.mjs +102 -0
- package/scripts/probe-qoder-attribution.mjs +266 -0
- package/scripts/probe-qoder-flash-confirm.mjs +94 -0
- package/scripts/probe-qoder-live.mjs +344 -0
- package/scripts/probe-qoder-matrix.mjs +274 -0
- package/scripts/probe-qoder-null-content.mjs +153 -0
- package/scripts/probe-qoder-pairing.mjs +308 -0
- package/scripts/probe-qoder-quota.mjs +162 -0
- package/scripts/probe-qoder-thinking-config.mjs +52 -0
- package/scripts/probe-qoder-thinking-efforts.mjs +269 -0
- package/scripts/probe-quota.mjs +136 -0
- package/scripts/probe-quota2.mjs +144 -0
- package/scripts/probe-routing.mjs +226 -0
- package/scripts/probe-trae-3003-diagnosis.mjs +139 -0
- package/scripts/probe-trae-agent-v3.mjs +399 -0
- package/scripts/probe-trae-efforts.mjs +149 -0
- package/scripts/probe-trae-live.mjs +147 -0
- package/scripts/probe-trae-max-effort.mjs +179 -0
- package/scripts/probe-trae-model-routing.mjs +381 -0
- package/scripts/probe-trae-thinking-scene.mjs +238 -0
- package/scripts/probe-trae-transport-outage.mjs +176 -0
- package/scripts/probe-ua.mjs +508 -0
- package/scripts/trae-model-catalog.mjs +632 -0
- package/scripts/verify-agents-md.mjs +45 -0
- package/scripts/verify-bridge.mjs +1041 -0
- package/scripts/verify-core-generic.mjs +302 -0
- package/scripts/verify-desktop-acceptance.mjs +184 -0
- package/scripts/verify-host-config.mjs +265 -0
- package/scripts/verify-models.mjs +390 -0
- package/scripts/verify-providers.mjs +255 -0
- package/scripts/verify-qoder-provider.mjs +1020 -0
- package/scripts/verify-rotation.mjs +308 -0
- package/scripts/verify-trae-model-catalog.mjs +284 -0
- package/scripts/verify-trae-provider.mjs +1451 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* UA-validation probe (topic 1 of docs/rules/): which UA fields does the
|
|
4
|
+
* gateway actually check, and what is the minimal mutation that triggers
|
|
5
|
+
* error 12403 ("check ua")?
|
|
6
|
+
*
|
|
7
|
+
* Hypotheses under test (formed 2026-08-19 from the upstream spell "UA 必须
|
|
8
|
+
* 是 CLI/unknown CodeBuddy/2.136.0,CodeBuddyCode/1.0 被拒 12403"):
|
|
9
|
+
* H1 the validator parses UA into `<name>/<version>` tokens and requires
|
|
10
|
+
* a product token `CodeBuddy/<x.y.z>`; other tokens are decorative
|
|
11
|
+
* H2 the version value itself is not validated (any semver passes)
|
|
12
|
+
* H3 `CodeBuddyCode/1.0` is rejected because the product NAME differs,
|
|
13
|
+
* not because of the version or the missing CLI prefix
|
|
14
|
+
* H4 companion headers (X-Product, X-IDE-*) are not checked on /v3/config
|
|
15
|
+
* H5 /v2/chat/completions does not validate UA at all (real-byte evidence
|
|
16
|
+
* already: docs/probes/bridge-flash-ab-2026-08-18.jsonl shows a
|
|
17
|
+
* deepseek-harness/0.1.0-rc.6 UA succeeding)
|
|
18
|
+
*
|
|
19
|
+
* Method: GET /v3/config (free, non-chat) with one mutated UA per request,
|
|
20
|
+
* 1.5s spacing, single account, read-only. Chat phase costs one max_tokens=1
|
|
21
|
+
* completion per arm (≈0.001 credit each) — disable with --skip-chat.
|
|
22
|
+
*
|
|
23
|
+
* Baseline UA strings come from shipped code / real captures, not from
|
|
24
|
+
* hand reconstruction: the CLI shape is what index.js sends (USER_AGENT),
|
|
25
|
+
* the /v2 baseline is what the bridge dump recorded on the wire.
|
|
26
|
+
*
|
|
27
|
+
* Usage: node scripts/probe-ua.mjs [--only name1,name2] [--skip-chat]
|
|
28
|
+
* [--out docs/probes/ua-YYYY-MM-DD.jsonl]
|
|
29
|
+
* Prediction round: edit the PREDICT_VARIANTS block after analysing the
|
|
30
|
+
* matrix, then run `node scripts/probe-ua.mjs --set predict`.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync, appendFileSync } from 'node:fs'
|
|
34
|
+
import { homedir } from 'node:os'
|
|
35
|
+
import { join, dirname } from 'node:path'
|
|
36
|
+
|
|
37
|
+
const DSH_HOME = process.env.DSH_HOME ?? join(homedir(), '.dsh')
|
|
38
|
+
const GATEWAY = 'https://copilot.tencent.com'
|
|
39
|
+
const BASELINE_UA = 'CLI/unknown CodeBuddy/2.136.0' // index.js USER_AGENT
|
|
40
|
+
const SPACING_MS = 1500
|
|
41
|
+
|
|
42
|
+
const argValue = (flag) => {
|
|
43
|
+
const i = process.argv.indexOf(flag)
|
|
44
|
+
return i > 0 ? process.argv[i + 1] : null
|
|
45
|
+
}
|
|
46
|
+
const ONLY = argValue('--only')?.split(',') ?? null
|
|
47
|
+
const SET = argValue('--set') ?? 'matrix' // matrix | predict | all
|
|
48
|
+
const SKIP_CHAT = process.argv.includes('--skip-chat')
|
|
49
|
+
const OUT = argValue('--out')
|
|
50
|
+
?? join('docs', 'probes', `ua-${new Date().toISOString().slice(0, 10)}.jsonl`)
|
|
51
|
+
|
|
52
|
+
// --- credential resolution: identical to the plugin's (api-key mode) -------
|
|
53
|
+
function resolveCredential() {
|
|
54
|
+
try {
|
|
55
|
+
const cfg = JSON.parse(readFileSync(join(DSH_HOME, 'codebuddy-plugin.json'), 'utf8'))
|
|
56
|
+
if (cfg.authMode === 'oauth') {
|
|
57
|
+
const auth = JSON.parse(readFileSync(join(DSH_HOME, 'codebuddy-plugin-auth.json'), 'utf8'))
|
|
58
|
+
if (auth?.auth?.accessToken) return { authorization: `Bearer ${auth.auth.accessToken}`, apiKey: null }
|
|
59
|
+
}
|
|
60
|
+
const active = (cfg.apiKeys ?? []).find((k) => k.name === cfg.activeApiKey)
|
|
61
|
+
if (active?.key) return { authorization: `Bearer ${active.key}`, apiKey: active.key }
|
|
62
|
+
} catch { /* fall through */ }
|
|
63
|
+
if (process.env.CODEBUDDY_API_KEY) {
|
|
64
|
+
return { authorization: `Bearer ${process.env.CODEBUDDY_API_KEY}`, apiKey: process.env.CODEBUDDY_API_KEY }
|
|
65
|
+
}
|
|
66
|
+
const credFile = join(DSH_HOME, '.credentials.yaml')
|
|
67
|
+
if (existsSync(credFile)) {
|
|
68
|
+
const m = readFileSync(credFile, 'utf8').match(/^\s*CODEBUDDY_API_KEY:\s*["']?([^"'\s]+)["']?\s*$/m)
|
|
69
|
+
if (m) return { authorization: `Bearer ${m[1]}`, apiKey: m[1] }
|
|
70
|
+
}
|
|
71
|
+
throw new Error('no CodeBuddy credential found')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// --- variant table -----------------------------------------------------------
|
|
75
|
+
// ua: null → omit the User-Agent header entirely; headers: extra/drop marks.
|
|
76
|
+
// dropXProduct removes the X-Product: SaaS header the catalog call normally
|
|
77
|
+
// carries (fetchModelCatalog dialect).
|
|
78
|
+
const MATRIX_VARIANTS = [
|
|
79
|
+
// group 0 — controls
|
|
80
|
+
{ name: 'control-baseline', ua: BASELINE_UA, note: 'shipped UA, expect 200' },
|
|
81
|
+
{ name: 'spell-rejected', ua: 'CodeBuddyCode/1.0', note: 'the spell says 12403' },
|
|
82
|
+
// group 1 — product token (H1/H3)
|
|
83
|
+
{ name: 'product-only', ua: 'CodeBuddy/2.136.0', note: 'no CLI prefix at all' },
|
|
84
|
+
{ name: 'product-other', ua: 'CLI/unknown OtherBuddy/2.136.0', note: 'same shape, wrong product' },
|
|
85
|
+
{ name: 'cli-only', ua: 'CLI/unknown', note: 'no product token' },
|
|
86
|
+
// group 2 — version value (H2)
|
|
87
|
+
{ name: 'version-major-bump', ua: 'CLI/unknown CodeBuddy/9.9.9', note: 'absurd future version' },
|
|
88
|
+
{ name: 'version-patch-bump', ua: 'CLI/unknown CodeBuddy/2.136.1', note: 'patch +1' },
|
|
89
|
+
{ name: 'version-low', ua: 'CLI/unknown CodeBuddy/0.0.1', note: 'ancient version' },
|
|
90
|
+
{ name: 'version-two-part', ua: 'CLI/unknown CodeBuddy/2.136', note: 'not full semver' },
|
|
91
|
+
{ name: 'version-nonnumeric', ua: 'CLI/unknown CodeBuddy/abc', note: 'not a number' },
|
|
92
|
+
// group 3 — prefix token shape
|
|
93
|
+
{ name: 'ide-ver-filled', ua: 'CLI/1.0 CodeBuddy/2.136.0', note: 'unknown → 1.0' },
|
|
94
|
+
{ name: 'ide-other', ua: 'VSC/unknown CodeBuddy/2.136.0', note: 'CLI → VSC' },
|
|
95
|
+
// group 4 — extra tokens / case
|
|
96
|
+
{ name: 'suffix-added', ua: 'CLI/unknown CodeBuddy/2.136.0 Extra/1.0', note: 'trailing token' },
|
|
97
|
+
{ name: 'case-cli-lower', ua: 'cli/unknown CodeBuddy/2.136.0', note: 'lowercase cli' },
|
|
98
|
+
{ name: 'case-product-lower', ua: 'CLI/unknown codebuddy/2.136.0', note: 'lowercase product' },
|
|
99
|
+
// group 5 — companion headers (H4)
|
|
100
|
+
{ name: 'no-xproduct', ua: BASELINE_UA, dropXProduct: true, note: 'baseline UA, X-Product dropped' },
|
|
101
|
+
{ name: 'no-ua-header', ua: null, note: 'User-Agent omitted entirely' },
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
// Pre-registered predictions, derived from the matrix results (2026-08-19).
|
|
105
|
+
// Rule candidate after the matrix: "the UA must contain the substring
|
|
106
|
+
// `codebuddy/` (case-insensitive); every other field is decorative".
|
|
107
|
+
// Evidence: product-only passed (no CLI prefix needed), CodeBuddy/abc and
|
|
108
|
+
// CodeBuddy/9.9.9 passed (no version check), codebuddy/2.136.0 passed
|
|
109
|
+
// (case-insensitive), CodeBuddyCode/1.0 FAILED though it contains
|
|
110
|
+
// `codebuddy` → the trailing slash must be part of the match.
|
|
111
|
+
// Discriminators:
|
|
112
|
+
// - notcodebuddy: substring rule → 200; token-exact-name rule → 12403
|
|
113
|
+
// - bare-product: substring-with-slash rule → 12403; substring-without-slash
|
|
114
|
+
// rule (`codebuddy` anywhere) → 200
|
|
115
|
+
const PREDICT_VARIANTS = [
|
|
116
|
+
{ name: 'predict-notcodebuddy', ua: 'NotCodeBuddy/1.0', expect: 200, rule: 'R1', note: 'substring rule predicts pass' },
|
|
117
|
+
{ name: 'predict-bare-product', ua: 'CodeBuddy', expect: 12403, rule: 'R1', note: 'no slash → substring absent' },
|
|
118
|
+
{ name: 'predict-uppercase', ua: 'X/y CODEBUDDY/1', expect: 200, rule: 'R1', note: 'uppercase token, minimal version' },
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
// Round 2 (2026-08-19): predict-uppercase MISSED (12403) while matrix
|
|
122
|
+
// `codebuddy/2.136.0` passed and `CodeBuddy/abc` passed — so neither full
|
|
123
|
+
// case-insensitivity nor a version-shape rule explains it. Refined rule
|
|
124
|
+
// R1′: the UA must contain one of the exact-case substrings `CodeBuddy/` or
|
|
125
|
+
// `codebuddy/` (the casings the official CLI / IDE plugins actually send).
|
|
126
|
+
// Pre-registered discriminators:
|
|
127
|
+
// - mixedcase-lowb / caps-b: R1′ → 12403; full-CI rule → 200
|
|
128
|
+
// - uppercase-fullver: isolates casing from the version `1` used in the
|
|
129
|
+
// missed arm — R1′ → 12403 even with the known-good version string
|
|
130
|
+
const PREDICT_VARIANTS_R2 = [
|
|
131
|
+
{ name: 'predict-mixedcase-lowb', ua: 'Codebuddy/1.0', expect: 12403, rule: 'R1′', note: 'lowercase b is not whitelisted' },
|
|
132
|
+
{ name: 'predict-caps-b', ua: 'codeBuddy/1.0', expect: 12403, rule: 'R1′', note: 'capital B only is not whitelisted' },
|
|
133
|
+
{ name: 'predict-uppercase-fullver', ua: 'X/y CODEBUDDY/2.136.0', expect: 12403, rule: 'R1′', note: 'case isolated from version shape' },
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
// Round 3 (2026-08-19): all R1′ predictions MISSED — Codebuddy/1.0,
|
|
137
|
+
// codeBuddy/1.0, CODEBUDDY/2.136.0 (with the X/y prefix!) all PASSED.
|
|
138
|
+
// Casing and prefix are therefore irrelevant. The only remaining difference
|
|
139
|
+
// in the failed arm `X/y CODEBUDDY/1` is the one-character version `1`.
|
|
140
|
+
// Matrix cross-check: CodeBuddy/abc (3) ✓, CodeBuddy/1.0 (3) ✓,
|
|
141
|
+
// CodeBuddy/2.136 (5) ✓, CodeBuddy/ (0, matrix product-only… no — bare
|
|
142
|
+
// `CodeBuddy` without slash failed). Candidate rule R2:
|
|
143
|
+
// UA must match /codebuddy\/\S{2,}/i — product token + version ≥ 2 chars.
|
|
144
|
+
// Pre-registered discriminators:
|
|
145
|
+
// - verlen1 vs verlen2: isolates the boundary exactly
|
|
146
|
+
// - verlen1-space: tokenized parser (\S) → 12403; raw substring (.) → 200
|
|
147
|
+
const PREDICT_VARIANTS_R3 = [
|
|
148
|
+
{ name: 'predict-verlen1', ua: 'CodeBuddy/a', expect: 12403, rule: 'R2', note: 'version length 1 → reject' },
|
|
149
|
+
{ name: 'predict-verlen2', ua: 'CodeBuddy/ab', expect: 200, rule: 'R2', note: 'version length 2 → pass' },
|
|
150
|
+
{ name: 'predict-verlen1-space', ua: 'CodeBuddy/1 ', expect: 12403, rule: 'R2', note: 'trailing space: \\S rule rejects, . rule passes' },
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
// Round 4 (2026-08-19): verlen1 (12403 HIT) and verlen1-space (12403 HIT)
|
|
154
|
+
// confirmed the lower boundary; verlen2 MISSED — `ab` (2 chars) was also
|
|
155
|
+
// rejected while matrix `abc` (3 chars) passed. Candidate rule R3:
|
|
156
|
+
// UA must match /codebuddy\/\S{3,}/i — version part ≥ 3 non-space chars.
|
|
157
|
+
// Pre-registered discriminators:
|
|
158
|
+
// - verlen3-digit: all-digit 3-char version → R3 says pass
|
|
159
|
+
// - verlen4: sanity confirm above the boundary
|
|
160
|
+
// - verlen2-trailspace: \S-rule → 12403; any-char(.{3,}) rule → 200
|
|
161
|
+
const PREDICT_VARIANTS_R4 = [
|
|
162
|
+
{ name: 'predict-verlen3-digit', ua: 'CodeBuddy/111', expect: 200, rule: 'R3', note: '3-char numeric version passes' },
|
|
163
|
+
{ name: 'predict-verlen4', ua: 'CodeBuddy/abcd', expect: 200, rule: 'R3', note: '4-char version passes' },
|
|
164
|
+
{ name: 'predict-verlen2-trailspace', ua: 'CodeBuddy/ab ', expect: 12403, rule: 'R3', note: 'space does not count toward the 3 chars' },
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
// Round 5 (2026-08-19): R3 collapsed — CodeBuddy/111 (3 chars) and
|
|
168
|
+
// CodeBuddy/abcd (4 chars) were BOTH rejected while matrix
|
|
169
|
+
// `CLI/unknown CodeBuddy/abc` passed. Re-tabulating all 28 observations
|
|
170
|
+
// yields a disjunctive rule R6 that fits every one:
|
|
171
|
+
// R6: UA passes iff it matches /cli\/\S+\s+codebuddy\/\S+/i (CLI form —
|
|
172
|
+
// any version string) OR /codebuddy\/\d+\.\d+/i (any UA carrying a
|
|
173
|
+
// semver-looking CodeBuddy/x.y token, dot required)
|
|
174
|
+
// Key fits: CLI/unknown CodeBuddy/abc ✓ (CLI form, version-free),
|
|
175
|
+
// CodeBuddy/abcd ✗ (no dot, no CLI prefix), X/y CODEBUDDY/1 ✗ (prefix not
|
|
176
|
+
// "cli", version has no dot), NotCodeBuddy/1.0 ✓ (substring match, token
|
|
177
|
+
// start not anchored).
|
|
178
|
+
// Pre-registered discriminators:
|
|
179
|
+
// - cli-anyver: CLI form frees the version grammar → 200 (a "version
|
|
180
|
+
// grammar" rule would say 12403 — standalone abcd failed)
|
|
181
|
+
// - cli-minimal: both \S+ slots minimal → 200
|
|
182
|
+
// - semver2-standalone: 2-part semver WITHOUT the CLI prefix (unobserved:
|
|
183
|
+
// matrix only tried 2.136 inside the CLI form) → 200
|
|
184
|
+
// - noncli-anyver: prefix word must be exactly CLI → 12403
|
|
185
|
+
// - cli-noslash: "CLI" without its slash breaks the CLI form → 12403
|
|
186
|
+
const PREDICT_VARIANTS_R5 = [
|
|
187
|
+
{ name: 'predict-cli-anyver', ua: 'CLI/unknown CodeBuddy/abcd', expect: 200, rule: 'R6', note: 'CLI form frees version grammar' },
|
|
188
|
+
{ name: 'predict-cli-minimal', ua: 'CLI/x CodeBuddy/z', expect: 200, rule: 'R6', note: 'minimal CLI form' },
|
|
189
|
+
{ name: 'predict-semver2-standalone', ua: 'CodeBuddy/2.136', expect: 200, rule: 'R6', note: '2-part semver, no CLI prefix' },
|
|
190
|
+
{ name: 'predict-noncli-anyver', ua: 'Wat/unknown CodeBuddy/zzz', expect: 12403, rule: 'R6', note: 'prefix must be CLI, version grammar not freed' },
|
|
191
|
+
{ name: 'predict-cli-noslash', ua: 'CLI CodeBuddy/abcd', expect: 12403, rule: 'R6', note: 'CLI without slash is not the CLI form' },
|
|
192
|
+
]
|
|
193
|
+
|
|
194
|
+
// agenttool scope round: the matrix showed /agenttool accepts even
|
|
195
|
+
// `CodeBuddy/ab` (predict-agenttool-bad-ua MISS) — the 12403 UA gate does
|
|
196
|
+
// NOT fire there for api-key auth, contradicting the spell. Re-test with
|
|
197
|
+
// the exact spell-rejected UA and a totally alien one.
|
|
198
|
+
// Round 6 (2026-08-19): R6's CLI-form disjunct collapsed (cli-anyver and
|
|
199
|
+
// cli-minimal both MISSED) while semver2-standalone / noncli-anyver /
|
|
200
|
+
// cli-noslash all HIT. The surviving rule R7 explains 37/38 observations:
|
|
201
|
+
// R7: UA passes iff it contains a substring matching /codebuddy\/\d+\.\d+/i
|
|
202
|
+
// (product token + at least x.y numeric version, dot mandatory)
|
|
203
|
+
// Sole contradiction: matrix `CLI/unknown CodeBuddy/abc` PASSED (R7 says
|
|
204
|
+
// reject). Retest it both ways; if it rejects now, the matrix row was an
|
|
205
|
+
// anomaly. Also probe the grammar edges: digit must follow the slash
|
|
206
|
+
// immediately (v-prefix), digit must follow the dot (1.x), and the token
|
|
207
|
+
// may sit anywhere in the UA.
|
|
208
|
+
const PREDICT_VARIANTS_R6 = [
|
|
209
|
+
{ name: 'retest-cli-abc', ua: 'CLI/unknown CodeBuddy/abc', expect: 12403, rule: 'R7', note: 'matrix anomaly retest, CLI form' },
|
|
210
|
+
{ name: 'retest-standalone-abc', ua: 'CodeBuddy/abc', expect: 12403, rule: 'R7', note: 'matrix anomaly retest, standalone' },
|
|
211
|
+
{ name: 'predict-vprefix', ua: 'CodeBuddy/v1.2', expect: 12403, rule: 'R7', note: 'digit must immediately follow the slash' },
|
|
212
|
+
{ name: 'predict-dot-nondigit', ua: 'CodeBuddy/1.x', expect: 12403, rule: 'R7', note: 'digit must immediately follow the dot' },
|
|
213
|
+
{ name: 'predict-embedded', ua: 'MyApp CodeBuddy/3.14 xyz', expect: 200, rule: 'R7', note: 'token may sit anywhere in the UA' },
|
|
214
|
+
{ name: 'predict-zerozero', ua: 'CodeBuddy/0.0', expect: 200, rule: 'R7', note: 'minimal semver token' },
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
// Round 7 (2026-08-19): five HITs, one MISS — CodeBuddy/1.x PASSED, so the
|
|
218
|
+
// character after the dot is unconstrained. Rule R8:
|
|
219
|
+
// R8: UA passes iff it contains /codebuddy\/\d+\./i
|
|
220
|
+
// (product token, ≥1 digit, a literal dot; nothing after the dot is
|
|
221
|
+
// checked; token position and casing unconstrained)
|
|
222
|
+
// The earlier `CLI/unknown CodeBuddy/abc` matrix pass did NOT reproduce in
|
|
223
|
+
// either retest (12403 both ways) — recorded as a transient anomaly, not
|
|
224
|
+
// part of the rule.
|
|
225
|
+
// Final boundary confirmations: is anything at all required after the dot,
|
|
226
|
+
// and may the version start with a dot?
|
|
227
|
+
const PREDICT_VARIANTS_R7 = [
|
|
228
|
+
{ name: 'predict-dot-terminal', ua: 'CodeBuddy/1.', expect: 200, rule: 'R8', note: 'nothing needed after the dot' },
|
|
229
|
+
{ name: 'predict-dot-first', ua: 'CodeBuddy/.5', expect: 12403, rule: 'R8', note: 'version must start with a digit' },
|
|
230
|
+
{ name: 'predict-multidigit-dot', ua: 'CodeBuddy/12.', expect: 200, rule: 'R8', note: 'multi-digit major, terminal dot' },
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
// Round 8 (2026-08-19): dot-terminal and multidigit-dot HIT, but dot-first
|
|
234
|
+
// (.5) MISSED my rejection prediction — a version may START with a dot.
|
|
235
|
+
// Combined with v1.2 (reject: dot exists but first char is `v`) and 111
|
|
236
|
+
// (reject: digits but no dot), the rule that fits all 41 observations:
|
|
237
|
+
// R9: UA passes iff it contains /codebuddy\/[\d.]*\./i
|
|
238
|
+
// i.e. immediately after the slash: a run of digits/dots containing
|
|
239
|
+
// at least one literal dot; anything may follow; token position and
|
|
240
|
+
// casing unconstrained.
|
|
241
|
+
// Pre-registered final discriminators:
|
|
242
|
+
// - baredot / doubledot: absurd per semver intuition, R9 says pass
|
|
243
|
+
// - xdot: dot present but first char outside [\d.] → reject (separates
|
|
244
|
+
// "starts with [\d.]" from "contains a dot anywhere")
|
|
245
|
+
// - dashdot: first char `-` → reject
|
|
246
|
+
const PREDICT_VARIANTS_R8 = [
|
|
247
|
+
{ name: 'predict-baredot', ua: 'CodeBuddy/.', expect: 200, rule: 'R9', note: 'a lone dot is a valid version' },
|
|
248
|
+
{ name: 'predict-doubledot', ua: 'CodeBuddy/..', expect: 200, rule: 'R9', note: 'two dots also valid' },
|
|
249
|
+
{ name: 'predict-xdot', ua: 'CodeBuddy/x.', expect: 12403, rule: 'R9', note: 'dot present but first char not in [\\d.]' },
|
|
250
|
+
{ name: 'predict-dashdot', ua: 'CodeBuddy/-.5', expect: 12403, rule: 'R9', note: 'first char `-` breaks the class' },
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
// Round 9 (2026-08-19): baredot/doubledot/xdot all HIT; dashdot MISSED my
|
|
254
|
+
// rejection prediction — `-` is accepted, pointing at the semver character
|
|
255
|
+
// class. Rule R10:
|
|
256
|
+
// R10: UA passes iff it contains /codebuddy\/[\d.-]*\./i
|
|
257
|
+
// (a run of SEMVER chars — digits, dots, hyphens — containing at
|
|
258
|
+
// least one dot, immediately after the product token)
|
|
259
|
+
// Pre-registered class boundary: `+` (build metadata) and `_` outside the
|
|
260
|
+
// class → reject; `1-2.3` inside → pass; dashdot retest guards against
|
|
261
|
+
// another abc-style transient.
|
|
262
|
+
const PREDICT_VARIANTS_R9 = [
|
|
263
|
+
{ name: 'predict-plusdot', ua: 'CodeBuddy/+.5', expect: 12403, rule: 'R10', note: '+ is not a semver version char here' },
|
|
264
|
+
{ name: 'predict-underscoredot', ua: 'CodeBuddy/_.5', expect: 12403, rule: 'R10', note: '_ outside the class' },
|
|
265
|
+
{ name: 'predict-hyphen-mid', ua: 'CodeBuddy/1-2.3', expect: 200, rule: 'R10', note: 'hyphen inside the run, dot present' },
|
|
266
|
+
{ name: 'retest-dashdot', ua: 'CodeBuddy/-.5', expect: 200, rule: 'R10', note: 'stability retest after the abc anomaly' },
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
// Round 10 (2026-08-19): plusdot and underscoredot both MISSED (passed),
|
|
270
|
+
// hyphen-mid and the dashdot retest HIT. The accepted pre-dot character
|
|
271
|
+
// class is wider than semver: every non-letter works, every ASCII letter
|
|
272
|
+
// fails. Rule R11, fitting all 45 observations:
|
|
273
|
+
// R11: UA passes iff it contains /codebuddy\/[^a-z\s]*\./i
|
|
274
|
+
// after the product token, a run of NON-ASCII-letter, non-space
|
|
275
|
+
// chars containing at least one literal dot; anything may follow
|
|
276
|
+
// (1.x passed); token position and casing unconstrained.
|
|
277
|
+
// Final discriminators: letter AFTER the dot is fine; letter BEFORE the
|
|
278
|
+
// dot fails; CJK separates "ASCII-letter class" from Unicode letters;
|
|
279
|
+
// a space before the dot breaks the run.
|
|
280
|
+
const PREDICT_VARIANTS_R10 = [
|
|
281
|
+
{ name: 'predict-symbol-letter-dot', ua: 'CodeBuddy/-.x', expect: 200, rule: 'R11', note: 'letter after the dot is unconstrained' },
|
|
282
|
+
{ name: 'predict-letter-before-dot', ua: 'CodeBuddy/a.1', expect: 12403, rule: 'R11', note: 'letter before the dot breaks the run' },
|
|
283
|
+
{ name: 'predict-cjk', ua: 'CodeBuddy/汉.5', expect: 200, rule: 'R11', note: 'CJK is outside the ASCII-letter class' },
|
|
284
|
+
{ name: 'predict-space-before-dot', ua: 'CodeBuddy/ .', expect: 12403, rule: 'R11', note: 'space breaks the run' },
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
// Round 11 (2026-08-19): symbol-letter-dot and space-before-dot HIT; the CJK
|
|
288
|
+
// arm is untestable (HTTP headers are ByteString — recorded as n/a); and
|
|
289
|
+
// letter-before-dot MISSED: `a.1` PASSED while matrix `v1.2` and `x.`
|
|
290
|
+
// FAILED. All three are "letter before a dot", so the run-class theory is
|
|
291
|
+
// dead. Fit across all 48 observations — R12:
|
|
292
|
+
// R12: the version after codebuddy/ must contain a dot AND satisfy one of
|
|
293
|
+
// (a) first char is not an ASCII letter (1.x, -.5, ., 12.)
|
|
294
|
+
// (b) it starts with <single letter>.<digit> (a.1)
|
|
295
|
+
// v1.2 fails (letter then digit before the dot), x. fails (letter, dot,
|
|
296
|
+
// no digit after), abcd/111/abc fail (no dot at all).
|
|
297
|
+
// Pre-registered discriminators over the before-dot grammar:
|
|
298
|
+
const PREDICT_VARIANTS_R11 = [
|
|
299
|
+
{ name: 'predict-two-letters-dot', ua: 'CodeBuddy/ab.1', expect: 12403, rule: 'R12', note: 'two letters before dot → reject' },
|
|
300
|
+
{ name: 'predict-letter-digit-dot', ua: 'CodeBuddy/a1.2', expect: 12403, rule: 'R12', note: 'letter+digit before dot → reject (v1.2 pattern)' },
|
|
301
|
+
{ name: 'predict-digit-letter-dot', ua: 'CodeBuddy/1a.2', expect: 200, rule: 'R12', note: 'digit first → (a) applies' },
|
|
302
|
+
{ name: 'predict-v-dot', ua: 'CodeBuddy/v.2', expect: 200, rule: 'R12', note: 'v alone before dot matches (b)' },
|
|
303
|
+
{ name: 'predict-x-dot-digit', ua: 'CodeBuddy/x.5', expect: 200, rule: 'R12', note: 'x. failed only because nothing followed the dot' },
|
|
304
|
+
{ name: 'predict-upper-dot-digit', ua: 'CodeBuddy/Z.9', expect: 200, rule: 'R12', note: 'uppercase letter also matches (b)' },
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
// Round 12 (2026-08-19): ab.1 and a1.2 HIT; 1a.2, v.2, x.5, Z.9 all MISSED
|
|
308
|
+
// (rejected). So EVERY letter-before-dot version rejects — except the
|
|
309
|
+
// one-off `a.1` pass, which now looks like the second anomaly of the same
|
|
310
|
+
// kind as the matrix `abc` pass (both reproduced as 12403 on retest).
|
|
311
|
+
// Hypothesis: the gateway fronts MULTIPLE validator builds; a minority node
|
|
312
|
+
// intermittently accepts looser UAs. Final flapping test: repeat `a.1`
|
|
313
|
+
// four times spaced 2s — mixed results confirm multi-validator flapping;
|
|
314
|
+
// uniform 12403 means the earlier pass was a transient; uniform 200 would
|
|
315
|
+
// force yet another rule revision (not expected).
|
|
316
|
+
const PREDICT_VARIANTS_R12 = [
|
|
317
|
+
{ name: 'flap-a1-1', ua: 'CodeBuddy/a.1', expect: 12403, rule: 'R13', note: 'flapping test 1/4' },
|
|
318
|
+
{ name: 'flap-a1-2', ua: 'CodeBuddy/a.1', expect: 12403, rule: 'R13', note: 'flapping test 2/4' },
|
|
319
|
+
{ name: 'flap-a1-3', ua: 'CodeBuddy/a.1', expect: 12403, rule: 'R13', note: 'flapping test 3/4' },
|
|
320
|
+
{ name: 'flap-a1-4', ua: 'CodeBuddy/a.1', expect: 12403, rule: 'R13', note: 'flapping test 4/4' },
|
|
321
|
+
{ name: 'control-good', ua: 'CodeBuddy/1.0', expect: 200, rule: 'R13', note: 'known-good control' },
|
|
322
|
+
{ name: 'retest-abc-final', ua: 'CodeBuddy/abc', expect: 12403, rule: 'R13', note: 'second anomaly confirmation' },
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
// agenttool scope round: the matrix showed /agenttool accepts even
|
|
326
|
+
// `CodeBuddy/ab` (predict-agenttool-bad-ua MISS) — the 12403 UA gate does
|
|
327
|
+
// NOT fire there for api-key auth, contradicting the spell. Re-test with
|
|
328
|
+
// the exact spell-rejected UA and a totally alien one.
|
|
329
|
+
const AGENTTOOL_ARMS_R2 = [
|
|
330
|
+
{ name: 'predict-agenttool-spell-ua', ua: 'CodeBuddyCode/1.0', expect: 200, rule: 'R-scope', note: 'spell claims 12403 on /agenttool; probe predicts the gate is absent' },
|
|
331
|
+
{ name: 'predict-agenttool-garbage', ua: 'Garbage/0.0', expect: 200, rule: 'R-scope', note: 'alien UA still passes /agenttool' },
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
// Endpoint-scope arms: does /agenttool share the /v3/config UA validator?
|
|
335
|
+
// bad-ua arm runs first — if the gate fires (12403) no search is executed.
|
|
336
|
+
const AGENTTOOL_ARMS = [
|
|
337
|
+
{ name: 'predict-agenttool-bad-ua', ua: 'CodeBuddy/ab', expect: 12403, rule: 'R3', note: 'same validator → 12403 before any search work' },
|
|
338
|
+
{ name: 'agenttool-baseline', ua: BASELINE_UA, expect: 0, rule: null, note: 'control: real tiny search through the shipped UA' },
|
|
339
|
+
]
|
|
340
|
+
|
|
341
|
+
const CHAT_ARMS = [
|
|
342
|
+
{ name: 'chat-garbage-ua', ua: 'Garbage/0.0', note: 'H5: /v2 ignores UA, expect 200' },
|
|
343
|
+
{ name: 'chat-cli-ua', ua: BASELINE_UA, note: 'control, expect 200' },
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
const CHAT_ARMS_PREDICT = [
|
|
347
|
+
// 11101 on the matrix run proved garbage UA passes the /v2 UA gate (it
|
|
348
|
+
// failed only on stream:false). This arm completes H5 with a real 200.
|
|
349
|
+
{ name: 'predict-chat-stream-garbage-ua', ua: 'Garbage/0.0', stream: true, expect: 0, rule: 'R2', note: 'stream:true + garbage UA → 200/code 0' },
|
|
350
|
+
]
|
|
351
|
+
|
|
352
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
353
|
+
|
|
354
|
+
function redact(value) {
|
|
355
|
+
if (typeof value !== 'string') return value
|
|
356
|
+
return value
|
|
357
|
+
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<redacted>')
|
|
358
|
+
.replace(/\b\d{6,}\b/g, '<redacted>')
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function probeConfig(cred, v) {
|
|
362
|
+
const headers = { Accept: 'application/json', Authorization: cred.authorization }
|
|
363
|
+
if (cred.apiKey) headers['x-api-key'] = cred.apiKey
|
|
364
|
+
if (!v.dropXProduct) headers['X-Product'] = 'SaaS'
|
|
365
|
+
if (v.ua !== null) headers['User-Agent'] = v.ua
|
|
366
|
+
const t0 = Date.now()
|
|
367
|
+
try {
|
|
368
|
+
const res = await fetch(`${GATEWAY}/v3/config`, { headers })
|
|
369
|
+
const text = await res.text()
|
|
370
|
+
let json = null
|
|
371
|
+
try { json = JSON.parse(text) } catch { /* non-JSON */ }
|
|
372
|
+
return {
|
|
373
|
+
name: v.name, ua: v.ua, note: v.note, expect: v.expect,
|
|
374
|
+
status: res.status, code: json?.code ?? null,
|
|
375
|
+
msg: redact(json?.msg ?? text.slice(0, 120)), ms: Date.now() - t0,
|
|
376
|
+
}
|
|
377
|
+
} catch (err) {
|
|
378
|
+
return { name: v.name, ua: v.ua, note: v.note, expect: v.expect, error: String(err?.message ?? err), ms: Date.now() - t0 }
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async function probeChat(cred, arm) {
|
|
383
|
+
const t0 = Date.now()
|
|
384
|
+
try {
|
|
385
|
+
const res = await fetch(`${GATEWAY}/v2/chat/completions`, {
|
|
386
|
+
method: 'POST',
|
|
387
|
+
headers: {
|
|
388
|
+
Authorization: cred.authorization,
|
|
389
|
+
'Content-Type': 'application/json',
|
|
390
|
+
Accept: 'application/json',
|
|
391
|
+
'User-Agent': arm.ua,
|
|
392
|
+
},
|
|
393
|
+
body: JSON.stringify({
|
|
394
|
+
model: 'deepseek-v3', stream: arm.stream === true, max_tokens: 1,
|
|
395
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
396
|
+
}),
|
|
397
|
+
})
|
|
398
|
+
if (arm.stream === true) {
|
|
399
|
+
// SSE: we only need the HTTP status — UA rejection (12403) would arrive
|
|
400
|
+
// as a JSON error body instead of an event stream.
|
|
401
|
+
const snippet = await res.text()
|
|
402
|
+
return {
|
|
403
|
+
name: arm.name, ua: arm.ua, note: arm.note, expect: arm.expect,
|
|
404
|
+
status: res.status, code: res.status === 200 ? 0 : null,
|
|
405
|
+
msg: redact(snippet.slice(0, 120)), ms: Date.now() - t0,
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const text = await res.text()
|
|
409
|
+
let json = null
|
|
410
|
+
try { json = JSON.parse(text) } catch { /* stream or non-JSON */ }
|
|
411
|
+
return {
|
|
412
|
+
name: arm.name, ua: arm.ua, note: arm.note, expect: arm.expect,
|
|
413
|
+
status: res.status, code: json?.code ?? null,
|
|
414
|
+
msg: redact(json?.msg ?? json?.error?.message ?? text.slice(0, 120)),
|
|
415
|
+
ms: Date.now() - t0,
|
|
416
|
+
}
|
|
417
|
+
} catch (err) {
|
|
418
|
+
return { name: arm.name, ua: arm.ua, note: arm.note, error: String(err?.message ?? err), ms: Date.now() - t0 }
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function probeAgentTool(cred, arm) {
|
|
423
|
+
const t0 = Date.now()
|
|
424
|
+
try {
|
|
425
|
+
const res = await fetch(`${GATEWAY}/agenttool/v1/search`, {
|
|
426
|
+
method: 'POST',
|
|
427
|
+
headers: {
|
|
428
|
+
Authorization: cred.authorization,
|
|
429
|
+
'Content-Type': 'application/json',
|
|
430
|
+
Accept: 'application/json',
|
|
431
|
+
'User-Agent': arm.ua,
|
|
432
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
433
|
+
},
|
|
434
|
+
body: JSON.stringify({ query: 'kimi', type: 'text2text', max_results: 1 }),
|
|
435
|
+
})
|
|
436
|
+
const text = await res.text()
|
|
437
|
+
let json = null
|
|
438
|
+
try { json = JSON.parse(text) } catch { /* non-JSON */ }
|
|
439
|
+
return {
|
|
440
|
+
name: arm.name, ua: arm.ua, note: arm.note, expect: arm.expect,
|
|
441
|
+
status: res.status, code: json?.code ?? null,
|
|
442
|
+
msg: redact(json?.msg ?? text.slice(0, 120)), ms: Date.now() - t0,
|
|
443
|
+
}
|
|
444
|
+
} catch (err) {
|
|
445
|
+
return { name: arm.name, ua: arm.ua, note: arm.note, expect: arm.expect, error: String(err?.message ?? err), ms: Date.now() - t0 }
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// --- driver ------------------------------------------------------------------
|
|
450
|
+
const cred = resolveCredential()
|
|
451
|
+
mkdirSync(dirname(OUT), { recursive: true })
|
|
452
|
+
const stamp = new Date().toISOString()
|
|
453
|
+
appendFileSync(OUT, JSON.stringify({ type: 'run', at: stamp, set: SET, only: ONLY, gateway: GATEWAY }) + '\n')
|
|
454
|
+
console.log(`evidence → ${OUT}`)
|
|
455
|
+
|
|
456
|
+
const variants = SET === 'predict' ? PREDICT_VARIANTS
|
|
457
|
+
: SET === 'predict2' ? PREDICT_VARIANTS_R2
|
|
458
|
+
: SET === 'predict3' ? PREDICT_VARIANTS_R3
|
|
459
|
+
: SET === 'predict4' ? PREDICT_VARIANTS_R4
|
|
460
|
+
: SET === 'predict5' ? PREDICT_VARIANTS_R5
|
|
461
|
+
: SET === 'predict6' ? PREDICT_VARIANTS_R6
|
|
462
|
+
: SET === 'predict7' ? PREDICT_VARIANTS_R7
|
|
463
|
+
: SET === 'predict8' ? PREDICT_VARIANTS_R8
|
|
464
|
+
: SET === 'predict9' ? PREDICT_VARIANTS_R9
|
|
465
|
+
: SET === 'predict10' ? PREDICT_VARIANTS_R10
|
|
466
|
+
: SET === 'predict11' ? PREDICT_VARIANTS_R11
|
|
467
|
+
: SET === 'predict12' ? PREDICT_VARIANTS_R12
|
|
468
|
+
: SET === 'all' ? [...MATRIX_VARIANTS, ...PREDICT_VARIANTS, ...PREDICT_VARIANTS_R2, ...PREDICT_VARIANTS_R3, ...PREDICT_VARIANTS_R4, ...PREDICT_VARIANTS_R5, ...PREDICT_VARIANTS_R6]
|
|
469
|
+
: MATRIX_VARIANTS
|
|
470
|
+
const picked = ONLY ? variants.filter((v) => ONLY.includes(v.name)) : variants
|
|
471
|
+
|
|
472
|
+
// expect 200 = the request passed the UA gate (HTTP 200; body shape differs
|
|
473
|
+
// per endpoint: /v3/config wraps code:0, /agenttool returns bare results).
|
|
474
|
+
// expect 12403 = rejected at the UA gate with that error code.
|
|
475
|
+
function judge(rec) {
|
|
476
|
+
if (rec.expect == null) return ''
|
|
477
|
+
const pass = rec.expect === 200 ? rec.status === 200 : rec.code === rec.expect
|
|
478
|
+
return pass ? ' HIT' : ' MISS'
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (picked.length === 0 && SET !== 'predict') console.log('no variants selected')
|
|
482
|
+
for (const v of picked) {
|
|
483
|
+
const rec = { type: 'probe', endpoint: '/v3/config', ...await probeConfig(cred, v) }
|
|
484
|
+
appendFileSync(OUT, JSON.stringify(rec) + '\n')
|
|
485
|
+
console.log(`${v.name.padEnd(20)} status=${rec.status ?? '-'} code=${rec.code ?? '-'} msg=${rec.msg ?? rec.error ?? ''}${judge(rec)}`)
|
|
486
|
+
await sleep(SPACING_MS)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (!SKIP_CHAT) {
|
|
490
|
+
const chatArms = SET === 'predict' ? CHAT_ARMS_PREDICT : SET === 'matrix' ? CHAT_ARMS : []
|
|
491
|
+
for (const arm of chatArms) {
|
|
492
|
+
const rec = { type: 'probe', endpoint: '/v2/chat/completions', ...await probeChat(cred, arm) }
|
|
493
|
+
appendFileSync(OUT, JSON.stringify(rec) + '\n')
|
|
494
|
+
console.log(`${arm.name.padEnd(20)} status=${rec.status ?? '-'} code=${rec.code ?? '-'} msg=${rec.msg ?? rec.error ?? ''}${judge(rec)}`)
|
|
495
|
+
await sleep(SPACING_MS)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (SET === 'predict4' || SET === 'predict5') {
|
|
500
|
+
const arms = SET === 'predict4' ? AGENTTOOL_ARMS : AGENTTOOL_ARMS_R2
|
|
501
|
+
for (const arm of arms) {
|
|
502
|
+
const rec = { type: 'probe', endpoint: '/agenttool/v1/search', ...await probeAgentTool(cred, arm) }
|
|
503
|
+
appendFileSync(OUT, JSON.stringify(rec) + '\n')
|
|
504
|
+
console.log(`${arm.name.padEnd(28)} status=${rec.status ?? '-'} code=${rec.code ?? '-'} msg=${rec.msg ?? rec.error ?? ''}${judge(rec)}`)
|
|
505
|
+
await sleep(SPACING_MS)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
console.log('done.')
|