@danceiny/gotry 0.0.1-rc.14 → 0.0.1-rc.16
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 +26 -20
- package/README.zh-CN.md +27 -21
- package/bin/gotry-bootstrap.js +331 -3
- package/cordis.gotry-patch.yml +10 -0
- package/data/airline-airports.json +39 -0
- package/dist/capabilities/agent-reach-deep.js +1 -1
- package/dist/capabilities/agent-reach.js +1 -1
- package/dist/capabilities/anything.js +1 -1
- package/dist/capabilities/artifacts.js +217 -0
- package/dist/capabilities/effect.js +293 -0
- package/dist/capabilities/fact-log.js +41 -0
- package/dist/capabilities/flyai.js +1 -1
- package/dist/capabilities/hbcli.js +43 -1
- package/dist/capabilities/incident-log.js +1 -1
- package/dist/capabilities/opensky.js +1 -1
- package/dist/capabilities/resilience.js +90 -0
- package/dist/capabilities/session/action-cache.js +1 -1
- package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
- package/dist/capabilities/session/adapters/meituan-local.js +1 -1
- package/dist/capabilities/session/benchmark.js +2 -1
- package/dist/capabilities/session/extension-bridge.js +345 -0
- package/dist/capabilities/session/extension-channel.js +93 -0
- package/dist/capabilities/session/extract.js +1 -1
- package/dist/capabilities/session/health-watch.js +216 -0
- package/dist/capabilities/session/read-guard.js +1 -1
- package/dist/capabilities/session/transport.js +1 -1
- package/dist/capabilities/session/wizard.js +377 -0
- package/dist/capabilities/session-consent.js +1 -1
- package/dist/capabilities/session-login.js +77 -1
- package/dist/capabilities/session-search.js +69 -2
- package/dist/capabilities/weather.js +1 -1
- package/dist/scripts/action-cache-tests.js +1 -1
- package/dist/scripts/agent-reach-deep-tests.js +1 -1
- package/dist/scripts/agent-reach-tests.js +1 -1
- package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
- package/dist/scripts/anything-tests.js +1 -1
- package/dist/scripts/async-collect.js +1 -1
- package/dist/scripts/booking-saga-tests.js +187 -0
- package/dist/scripts/bootstrap-tests.js +49 -3
- package/dist/scripts/build-changelog.js +279 -0
- package/dist/scripts/changelog-tests.js +165 -0
- package/dist/scripts/companion-tests.js +1 -1
- package/dist/scripts/diff-test.js +1 -1
- package/dist/scripts/effect-tests.js +474 -0
- package/dist/scripts/engine-run.js +1 -1
- package/dist/scripts/engine-tests.js +1 -1
- package/dist/scripts/extension-tests.js +468 -0
- package/dist/scripts/fact-gate-tests.js +318 -0
- package/dist/scripts/flyai-tests.js +1 -1
- package/dist/scripts/hbcli-e2e-tests.js +180 -0
- package/dist/scripts/hbcli-tests.js +1 -1
- package/dist/scripts/health-watch-cli.js +51 -0
- package/dist/scripts/i18n-tests.js +1 -1
- package/dist/scripts/incident-tests.js +1 -1
- package/dist/scripts/journey-tests.js +1 -1
- package/dist/scripts/ledger-tests.js +1 -1
- package/dist/scripts/ledger-workflow-crash.js +1 -1
- package/dist/scripts/memory-capture-tests.js +1 -1
- package/dist/scripts/memory-decay-tests.js +1 -1
- package/dist/scripts/memory-metrics.js +1 -1
- package/dist/scripts/memory-value-report.js +1 -1
- package/dist/scripts/nightly-evidence-tests.js +186 -0
- package/dist/scripts/nightly-evidence.js +289 -0
- package/dist/scripts/nudge-digest.js +1 -1
- package/dist/scripts/onboarding-tests.js +218 -0
- package/dist/scripts/opensky-check.js +1 -1
- package/dist/scripts/opensky-tests.js +1 -1
- package/dist/scripts/price-drift-tests.js +441 -0
- package/dist/scripts/price-drift-watch.js +491 -0
- package/dist/scripts/probe-poi-tests.js +1 -1
- package/dist/scripts/product-metrics.js +1 -1
- package/dist/scripts/publish-preverify.js +1 -1
- package/dist/scripts/realtime-pricing-tests.js +1 -1
- package/dist/scripts/replay-async.js +1 -1
- package/dist/scripts/replay-real.js +1 -1
- package/dist/scripts/replay.js +1 -1
- package/dist/scripts/session-attach-diagnose.js +1 -1
- package/dist/scripts/session-attach-poc.js +1 -1
- package/dist/scripts/session-benchmark.js +1 -1
- package/dist/scripts/session-extract-tests.js +1 -1
- package/dist/scripts/session-login.js +1 -1
- package/dist/scripts/session-tests.js +1 -1
- package/dist/scripts/skeleton-check.js +1 -1
- package/dist/scripts/skeleton-integration-test.js +1 -1
- package/dist/scripts/skills-contract-tests.js +1 -1
- package/dist/scripts/smoke.js +159 -1
- package/dist/scripts/state-cli-tests.js +1 -1
- package/dist/scripts/state-cli.js +1 -1
- package/dist/scripts/time-eval-tests.js +1 -1
- package/dist/scripts/travel-timeline-tests.js +1 -1
- package/dist/scripts/unified-tests.js +1 -1
- package/dist/scripts/weather-tests.js +1 -1
- package/dist/scripts/wizard-bootstrap.js +32 -0
- package/dist/scripts/z3-race-tests.js +1 -1
- package/dist/src/artifact-gate.js +310 -0
- package/dist/src/bookable-facts.js +349 -0
- package/dist/src/booking-saga.js +153 -0
- package/dist/src/bridge.js +1 -1
- package/dist/src/companions.js +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/dsh-llm.js +31 -7
- package/dist/src/engine.js +1 -1
- package/dist/src/i18n.js +1 -1
- package/dist/src/index.js +322 -46
- package/dist/src/journey.js +1 -1
- package/dist/src/loop.js +1 -1
- package/dist/src/memory-capture.js +1 -1
- package/dist/src/memory-decay.js +1 -1
- package/dist/src/memory-utility.js +1 -1
- package/dist/src/mock-llm.js +1 -1
- package/dist/src/model.js +1 -1
- package/dist/src/realtime-pricing.js +14 -7
- package/dist/src/slot-spec.js +1 -1
- package/dist/src/state-ledger.js +1 -1
- package/dist/src/time-anchor.js +1 -1
- package/dist/src/tool-packet.js +1 -1
- package/dist/src/travel-slots.js +1 -1
- package/dist/src/travel-timeline.js +1 -1
- package/dist/src/unified.js +1 -1
- package/dist/src/wish-pool.js +1 -1
- package/dist/src/z3-shared.js +1 -1
- package/extension/README.md +26 -0
- package/extension/background.js +160 -0
- package/extension/content-bridge.js +41 -0
- package/extension/content-main.js +62 -0
- package/extension/manifest.json +32 -0
- package/package.json +8 -2
- package/ts/capabilities/artifacts.ts +235 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/hbcli.ts +46 -0
- package/ts/capabilities/session/benchmark.ts +4 -1
- package/ts/capabilities/session/extension-bridge.ts +354 -0
- package/ts/capabilities/session/extension-channel.ts +92 -0
- package/ts/capabilities/session/health-watch.ts +257 -0
- package/ts/capabilities/session/wizard.ts +282 -0
- package/ts/capabilities/session-login.ts +53 -2
- package/ts/capabilities/session-search.ts +68 -3
- package/ts/src/artifact-gate.ts +365 -0
- package/ts/src/bookable-facts.ts +557 -0
- package/ts/src/booking-saga.ts +133 -0
- package/ts/src/dsh-llm.ts +40 -3
- package/ts/src/index.ts +230 -28
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const FETCH_TIMEOUT_MS = 8000;
|
|
4
|
+
const DEFAULT_PROVIDERS = [
|
|
5
|
+
{
|
|
6
|
+
id: 'deepseek',
|
|
7
|
+
source_url: 'https://api-docs.deepseek.com/quick_start/pricing',
|
|
8
|
+
baseline_path: 'data/llm-price-baseline-deepseek.json',
|
|
9
|
+
parse: parseDeepseekHtml
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
id: 'minimax',
|
|
13
|
+
source_url: 'https://platform.minimax.io/docs/guides/pricing-paygo',
|
|
14
|
+
baseline_path: 'data/llm-price-baseline-minimax.json',
|
|
15
|
+
parse: parseMinimaxHtml
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'openai',
|
|
19
|
+
source_url: 'https://openai.com/api/pricing/',
|
|
20
|
+
baseline_path: 'data/llm-price-baseline-openai.json',
|
|
21
|
+
snapshot_path: 'data/llm-pricing-snapshot-openai.html',
|
|
22
|
+
parse: parseOpenaiHtml
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'anthropic',
|
|
26
|
+
source_url: 'https://www.anthropic.com/pricing',
|
|
27
|
+
baseline_path: 'data/llm-price-baseline-anthropic.json',
|
|
28
|
+
snapshot_path: 'data/llm-pricing-snapshot-anthropic.html',
|
|
29
|
+
parse: parseAnthropicHtml
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
async function fetchWithTimeout(url, timeoutMs) {
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timer = setTimeout(()=>controller.abort(), timeoutMs);
|
|
35
|
+
try {
|
|
36
|
+
const res = await fetch(url, {
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
headers: {
|
|
39
|
+
'User-Agent': 'GoTry-price-drift-watch/1 (issue #49)'
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
43
|
+
return await res.text();
|
|
44
|
+
} finally{
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function parseDeepseekHtml(html) {
|
|
49
|
+
const rows = [];
|
|
50
|
+
const rowRe = /<tr[^>]*>([\s\S]*?)<\/tr>/g;
|
|
51
|
+
const cellRe = /<td[^>]*>([\s\S]*?)<\/td>/g;
|
|
52
|
+
let tr;
|
|
53
|
+
while((tr = rowRe.exec(html)) !== null){
|
|
54
|
+
const cells = [];
|
|
55
|
+
let m;
|
|
56
|
+
while((m = cellRe.exec(tr[1])) !== null)cells.push(m[1].replace(/<[^>]+>/g, '').trim());
|
|
57
|
+
if (cells.length < 4) continue;
|
|
58
|
+
const model = cells[0];
|
|
59
|
+
if (!/^deepseek-v[34]/.test(model)) continue;
|
|
60
|
+
const miss = Number(cells[1]);
|
|
61
|
+
const hit = Number(cells[2]);
|
|
62
|
+
const out = Number(cells[3]);
|
|
63
|
+
if (!Number.isFinite(miss) || !Number.isFinite(out)) continue;
|
|
64
|
+
rows.push({
|
|
65
|
+
model,
|
|
66
|
+
input_cache_miss_usd_per_1m: miss,
|
|
67
|
+
input_cache_hit_usd_per_1m: Number.isFinite(hit) ? hit : null,
|
|
68
|
+
output_usd_per_1m: out
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return rows.length > 0 ? rows : null;
|
|
72
|
+
}
|
|
73
|
+
function parseMinimaxHtml(html) {
|
|
74
|
+
const rows = [];
|
|
75
|
+
const lineRe = /<p[^>]*>([\s\S]*?)<\/p>/g;
|
|
76
|
+
let p;
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
while((p = lineRe.exec(html)) !== null){
|
|
79
|
+
const text = p[1].replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/\s+/g, ' ').trim();
|
|
80
|
+
const modelMatch = text.match(/(MiniMax-M2(?:\.1)?(?:-highspeed)?|MiniMax-M3)/);
|
|
81
|
+
if (!modelMatch) continue;
|
|
82
|
+
const model = modelMatch[1];
|
|
83
|
+
const numbers = Array.from(text.matchAll(/\$([0-9]+(?:\.[0-9]+)?)\s*\/\s*M\s*tokens?/g)).map((m)=>Number(m[1]));
|
|
84
|
+
if (numbers.length < 2) continue;
|
|
85
|
+
const [miss, out] = numbers;
|
|
86
|
+
const hit = numbers.length >= 3 ? numbers[2] : null;
|
|
87
|
+
if (seen.has(model)) continue;
|
|
88
|
+
seen.add(model);
|
|
89
|
+
rows.push({
|
|
90
|
+
model,
|
|
91
|
+
input_cache_miss_usd_per_1m: miss,
|
|
92
|
+
input_cache_hit_usd_per_1m: hit,
|
|
93
|
+
output_usd_per_1m: out
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return rows.length > 0 ? rows : null;
|
|
97
|
+
}
|
|
98
|
+
export function parseOpenaiHtml(html) {
|
|
99
|
+
const fromNextData = parseOpenaiNextData(html);
|
|
100
|
+
if (fromNextData) return fromNextData;
|
|
101
|
+
return parseOpenaiParagraphFallback(html);
|
|
102
|
+
}
|
|
103
|
+
function parseOpenaiNextData(html) {
|
|
104
|
+
const m = html.match(/<script[^>]*id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);
|
|
105
|
+
if (!m) return null;
|
|
106
|
+
try {
|
|
107
|
+
const data = JSON.parse(m[1]);
|
|
108
|
+
const models = data?.props?.pageProps?.models;
|
|
109
|
+
if (!Array.isArray(models) || models.length === 0) return null;
|
|
110
|
+
const rows = [];
|
|
111
|
+
for (const m of models){
|
|
112
|
+
const name = String(m['name'] ?? m['model'] ?? '').trim();
|
|
113
|
+
if (!name) continue;
|
|
114
|
+
const rawMiss = Number(m['input_cost_per_token'] ?? m['input_per_million'] ?? m['input']);
|
|
115
|
+
const rawOut = Number(m['output_cost_per_token'] ?? m['output_per_million'] ?? m['output']);
|
|
116
|
+
const rawHit = Number(m['cached_input_cost_per_token'] ?? m['cached_input_per_million'] ?? m['cache_read']);
|
|
117
|
+
if (!Number.isFinite(rawMiss) || !Number.isFinite(rawOut)) continue;
|
|
118
|
+
rows.push({
|
|
119
|
+
model: name,
|
|
120
|
+
input_cache_miss_usd_per_1m: rawMiss < 1 ? rawMiss * 1_000_000 : rawMiss,
|
|
121
|
+
input_cache_hit_usd_per_1m: Number.isFinite(rawHit) ? rawHit < 1 ? rawHit * 1_000_000 : rawHit : null,
|
|
122
|
+
output_usd_per_1m: rawOut < 1 ? rawOut * 1_000_000 : rawOut
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return rows.length > 0 ? rows : null;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function parseOpenaiParagraphFallback(html) {
|
|
131
|
+
const rows = [];
|
|
132
|
+
const lineRe = /<p[^>]*>([\s\S]*?)<\/p>|<li[^>]*>([\s\S]*?)<\/li>/g;
|
|
133
|
+
let m;
|
|
134
|
+
while((m = lineRe.exec(html)) !== null){
|
|
135
|
+
const text = (m[1] ?? m[2] ?? '').replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/\s+/g, ' ').trim();
|
|
136
|
+
const modelMatch = text.match(/(gpt-4o(?:-mini)?|gpt-4\.1(?:-mini)?|gpt-5(?:-mini)?|o[1-9](?:-mini|-pro)?)/);
|
|
137
|
+
if (!modelMatch) continue;
|
|
138
|
+
const numbers = Array.from(text.matchAll(/\$([0-9]+(?:\.[0-9]+)?)\s*\/\s*(?:1M|M)/g)).map((x)=>Number(x[1]));
|
|
139
|
+
if (numbers.length < 2) continue;
|
|
140
|
+
const [miss, out] = numbers;
|
|
141
|
+
rows.push({
|
|
142
|
+
model: modelMatch[1],
|
|
143
|
+
input_cache_miss_usd_per_1m: miss,
|
|
144
|
+
input_cache_hit_usd_per_1m: null,
|
|
145
|
+
output_usd_per_1m: out
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return rows.length > 0 ? rows : null;
|
|
149
|
+
}
|
|
150
|
+
function parseAnthropicHtml(html) {
|
|
151
|
+
const fromNextData = parseAnthropicNextData(html);
|
|
152
|
+
if (fromNextData) return fromNextData;
|
|
153
|
+
return parseAnthropicParagraphFallback(html);
|
|
154
|
+
}
|
|
155
|
+
function parseAnthropicNextData(html) {
|
|
156
|
+
const m = html.match(/<script[^>]*id="__NEXT_DATA__"[^>]*>([\s\S]*?)<\/script>/);
|
|
157
|
+
if (!m) return null;
|
|
158
|
+
try {
|
|
159
|
+
const data = JSON.parse(m[1]);
|
|
160
|
+
const pricing = data?.props?.pageProps?.pricing;
|
|
161
|
+
if (!Array.isArray(pricing) || pricing.length === 0) return null;
|
|
162
|
+
const rows = [];
|
|
163
|
+
for (const p of pricing){
|
|
164
|
+
const name = String(p['model'] ?? p['name'] ?? '').trim();
|
|
165
|
+
if (!name) continue;
|
|
166
|
+
const miss = Number(p['input_cost_per_mtok'] ?? p['input']);
|
|
167
|
+
const out = Number(p['output_cost_per_mtok'] ?? p['output']);
|
|
168
|
+
const hit = Number(p['cache_read_cost_per_mtok'] ?? p['cache_read']);
|
|
169
|
+
if (!Number.isFinite(miss) || !Number.isFinite(out)) continue;
|
|
170
|
+
rows.push({
|
|
171
|
+
model: name,
|
|
172
|
+
input_cache_miss_usd_per_1m: miss,
|
|
173
|
+
input_cache_hit_usd_per_1m: Number.isFinite(hit) ? hit : null,
|
|
174
|
+
output_usd_per_1m: out
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return rows.length > 0 ? rows : null;
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function parseAnthropicParagraphFallback(html) {
|
|
183
|
+
const rows = [];
|
|
184
|
+
const lineRe = /<p[^>]*>([\s\S]*?)<\/p>|<li[^>]*>([\s\S]*?)<\/li>/g;
|
|
185
|
+
let m;
|
|
186
|
+
while((m = lineRe.exec(html)) !== null){
|
|
187
|
+
const text = (m[1] ?? m[2] ?? '').replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/\s+/g, ' ').trim();
|
|
188
|
+
const modelMatch = text.match(/(claude-(?:opus|sonnet|haiku)-[0-9](?:\.[0-9])?(?:-[0-9]+k)?)/);
|
|
189
|
+
if (!modelMatch) continue;
|
|
190
|
+
const numbers = Array.from(text.matchAll(/\$([0-9]+(?:\.[0-9]+)?)\s*\/\s*(?:1M|M|MT)/g)).map((x)=>Number(x[1]));
|
|
191
|
+
if (numbers.length < 2) continue;
|
|
192
|
+
const [miss, out] = numbers;
|
|
193
|
+
rows.push({
|
|
194
|
+
model: modelMatch[1],
|
|
195
|
+
input_cache_miss_usd_per_1m: miss,
|
|
196
|
+
input_cache_hit_usd_per_1m: null,
|
|
197
|
+
output_usd_per_1m: out
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return rows.length > 0 ? rows : null;
|
|
201
|
+
}
|
|
202
|
+
function diffRows(baseline, current) {
|
|
203
|
+
const baseMap = new Map(baseline.map((r)=>[
|
|
204
|
+
r.model,
|
|
205
|
+
r
|
|
206
|
+
]));
|
|
207
|
+
const currMap = new Map(current.map((r)=>[
|
|
208
|
+
r.model,
|
|
209
|
+
r
|
|
210
|
+
]));
|
|
211
|
+
const drift = [];
|
|
212
|
+
for (const [model, curr] of currMap){
|
|
213
|
+
const base = baseMap.get(model);
|
|
214
|
+
if (!base) {
|
|
215
|
+
drift.push({
|
|
216
|
+
model,
|
|
217
|
+
field: 'input_cache_miss_usd_per_1m',
|
|
218
|
+
from: null,
|
|
219
|
+
to: curr.input_cache_miss_usd_per_1m,
|
|
220
|
+
direction: 'new'
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (base.input_cache_miss_usd_per_1m !== curr.input_cache_miss_usd_per_1m) {
|
|
225
|
+
drift.push({
|
|
226
|
+
model,
|
|
227
|
+
field: 'input_cache_miss_usd_per_1m',
|
|
228
|
+
from: base.input_cache_miss_usd_per_1m,
|
|
229
|
+
to: curr.input_cache_miss_usd_per_1m,
|
|
230
|
+
direction: curr.input_cache_miss_usd_per_1m > base.input_cache_miss_usd_per_1m ? 'up' : 'down'
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
if (base.input_cache_hit_usd_per_1m !== curr.input_cache_hit_usd_per_1m) {
|
|
234
|
+
drift.push({
|
|
235
|
+
model,
|
|
236
|
+
field: 'input_cache_hit_usd_per_1m',
|
|
237
|
+
from: base.input_cache_hit_usd_per_1m,
|
|
238
|
+
to: curr.input_cache_hit_usd_per_1m,
|
|
239
|
+
direction: (curr.input_cache_hit_usd_per_1m ?? 0) > (base.input_cache_hit_usd_per_1m ?? 0) ? 'up' : 'down'
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (base.output_usd_per_1m !== curr.output_usd_per_1m) {
|
|
243
|
+
drift.push({
|
|
244
|
+
model,
|
|
245
|
+
field: 'output_usd_per_1m',
|
|
246
|
+
from: base.output_usd_per_1m,
|
|
247
|
+
to: curr.output_usd_per_1m,
|
|
248
|
+
direction: curr.output_usd_per_1m > base.output_usd_per_1m ? 'up' : 'down'
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const [model, base] of baseMap){
|
|
253
|
+
if (!currMap.has(model)) {
|
|
254
|
+
drift.push({
|
|
255
|
+
model,
|
|
256
|
+
field: 'input_cache_miss_usd_per_1m',
|
|
257
|
+
from: base.input_cache_miss_usd_per_1m,
|
|
258
|
+
to: null,
|
|
259
|
+
direction: 'removed'
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return drift;
|
|
264
|
+
}
|
|
265
|
+
function loadBaseline(path) {
|
|
266
|
+
if (!existsSync(path)) return null;
|
|
267
|
+
try {
|
|
268
|
+
const raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
269
|
+
if (raw['schema_version'] !== 'gotry_llm_price_baseline_v1') throw new Error(`baseline ${path} schema_version must be gotry_llm_price_baseline_v1, got ${String(raw['schema_version'])}`);
|
|
270
|
+
const rows = raw['rows'];
|
|
271
|
+
if (!Array.isArray(rows)) throw new Error(`baseline ${path} rows must be array`);
|
|
272
|
+
return raw;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function loadSnapshot(path) {
|
|
278
|
+
if (!existsSync(path)) return null;
|
|
279
|
+
try {
|
|
280
|
+
const text = readFileSync(path, 'utf8');
|
|
281
|
+
return text.length > 0 ? text : null;
|
|
282
|
+
} catch {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function writeSnapshot(path, html) {
|
|
287
|
+
writeFileSync(path, html, 'utf8');
|
|
288
|
+
}
|
|
289
|
+
function writeBaseline(path, rows, sourceUrl) {
|
|
290
|
+
const payload = {
|
|
291
|
+
schema_version: 'gotry_llm_price_baseline_v1',
|
|
292
|
+
as_of: new Date().toISOString(),
|
|
293
|
+
source_url: sourceUrl,
|
|
294
|
+
rows
|
|
295
|
+
};
|
|
296
|
+
writeFileSync(path, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
297
|
+
}
|
|
298
|
+
export async function runPriceDriftWatch(options) {
|
|
299
|
+
const observedAt = options.observedAt().toISOString();
|
|
300
|
+
const providers = {};
|
|
301
|
+
const providerSpecs = options.providers ?? DEFAULT_PROVIDERS;
|
|
302
|
+
const fetcher = options.fetcher ?? ((url)=>fetchWithTimeout(url, FETCH_TIMEOUT_MS));
|
|
303
|
+
for (const spec of providerSpecs){
|
|
304
|
+
const baseline = loadBaseline(spec.baseline_path);
|
|
305
|
+
const snapshot = spec.snapshot_path ? loadSnapshot(spec.snapshot_path) : null;
|
|
306
|
+
if (options.mode === 'offline') {
|
|
307
|
+
if (!baseline) {
|
|
308
|
+
providers[spec.id] = {
|
|
309
|
+
status: 'skipped_no_baseline',
|
|
310
|
+
source_url: spec.source_url,
|
|
311
|
+
reason: `no baseline fixture at ${spec.baseline_path}; run --fetch once to seed`
|
|
312
|
+
};
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (snapshot === null) {
|
|
316
|
+
if (spec.snapshot_path) {
|
|
317
|
+
providers[spec.id] = {
|
|
318
|
+
status: 'skipped_no_snapshot',
|
|
319
|
+
source_url: spec.source_url,
|
|
320
|
+
reason: `no snapshot at ${spec.snapshot_path}; run --fetch once to seed (or: founder manually save official pricing page HTML as the snapshot)`
|
|
321
|
+
};
|
|
322
|
+
} else {
|
|
323
|
+
providers[spec.id] = {
|
|
324
|
+
status: 'unchanged',
|
|
325
|
+
source_url: spec.source_url,
|
|
326
|
+
reason: 'offline mode; baseline present, run --fetch to compare against current official prices'
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const currentFromSnapshot = spec.parse(snapshot);
|
|
332
|
+
if (!currentFromSnapshot) {
|
|
333
|
+
providers[spec.id] = {
|
|
334
|
+
status: 'skipped_parse_error',
|
|
335
|
+
source_url: spec.source_url,
|
|
336
|
+
reason: 'snapshot present but parser failed to extract rows; refresh snapshot or fix parser'
|
|
337
|
+
};
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const driftFromSnapshot = diffRows(baseline.rows, currentFromSnapshot) ?? [];
|
|
341
|
+
if (driftFromSnapshot.length === 0) {
|
|
342
|
+
providers[spec.id] = {
|
|
343
|
+
status: 'unchanged',
|
|
344
|
+
source_url: spec.source_url,
|
|
345
|
+
reason: 'offline; baseline == snapshot-derived current'
|
|
346
|
+
};
|
|
347
|
+
} else {
|
|
348
|
+
providers[spec.id] = {
|
|
349
|
+
status: 'drift',
|
|
350
|
+
source_url: spec.source_url,
|
|
351
|
+
fetched_at: observedAt,
|
|
352
|
+
drift: driftFromSnapshot,
|
|
353
|
+
reason: 'offline diff baseline vs snapshot'
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
let html;
|
|
359
|
+
try {
|
|
360
|
+
html = await fetcher(spec.source_url);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
providers[spec.id] = {
|
|
363
|
+
status: 'skipped_no_network',
|
|
364
|
+
source_url: spec.source_url,
|
|
365
|
+
reason: `fetch failed: ${error.message}`
|
|
366
|
+
};
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (spec.snapshot_path) writeSnapshot(spec.snapshot_path, html);
|
|
370
|
+
const current = spec.parse(html);
|
|
371
|
+
if (!current) {
|
|
372
|
+
providers[spec.id] = {
|
|
373
|
+
status: 'skipped_parse_error',
|
|
374
|
+
source_url: spec.source_url,
|
|
375
|
+
reason: 'parser did not extract any rows; snapshot was written anyway (manual review of fixture + parser required)'
|
|
376
|
+
};
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (!baseline) {
|
|
380
|
+
writeBaseline(spec.baseline_path, current, spec.source_url);
|
|
381
|
+
providers[spec.id] = {
|
|
382
|
+
status: 'fetched_no_table_entry',
|
|
383
|
+
source_url: spec.source_url,
|
|
384
|
+
fetched_at: observedAt,
|
|
385
|
+
reason: 'first fetch; baseline fixture written; no current llm-price-table.json entry (add a PR if you want this provider in nightly-evidence cost math)'
|
|
386
|
+
};
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const drift = diffRows(baseline.rows, current) ?? [];
|
|
390
|
+
if (drift.length === 0) {
|
|
391
|
+
providers[spec.id] = {
|
|
392
|
+
status: 'unchanged',
|
|
393
|
+
source_url: spec.source_url,
|
|
394
|
+
fetched_at: observedAt
|
|
395
|
+
};
|
|
396
|
+
} else {
|
|
397
|
+
providers[spec.id] = {
|
|
398
|
+
status: 'drift',
|
|
399
|
+
source_url: spec.source_url,
|
|
400
|
+
fetched_at: observedAt,
|
|
401
|
+
drift
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const suggestedPrParagraph = renderSuggestedPr(providers, observedAt);
|
|
406
|
+
return {
|
|
407
|
+
schema_version: 'gotry_llm_price_drift_v1',
|
|
408
|
+
observed_at: observedAt,
|
|
409
|
+
mode: options.mode,
|
|
410
|
+
providers,
|
|
411
|
+
suggested_pr_paragraph: suggestedPrParagraph
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function renderSuggestedPr(providers, observedAt) {
|
|
415
|
+
const lines = [
|
|
416
|
+
`## LLM 价格漂移监测(${observedAt})`
|
|
417
|
+
];
|
|
418
|
+
for (const [id, report] of Object.entries(providers)){
|
|
419
|
+
if (report.status === 'drift' && report.drift && report.drift.length > 0) {
|
|
420
|
+
const driftItems = report.drift;
|
|
421
|
+
lines.push(`- **${id}**(${report.source_url}):${driftItems.length} 项变动`);
|
|
422
|
+
for (const d of driftItems){
|
|
423
|
+
lines.push(` - \`${d.model}\` ${d.field}: ${d.from ?? '∅'} → ${d.to ?? '∅'} (${d.direction})`);
|
|
424
|
+
}
|
|
425
|
+
lines.push(' - 建议:按 ADR-11 复核,人工提 PR 改 `data/llm-price-table.json`。');
|
|
426
|
+
} else if (report.status === 'unchanged') {
|
|
427
|
+
lines.push(`- **${id}**:无变动`);
|
|
428
|
+
} else if (report.status === 'fetched_no_table_entry') {
|
|
429
|
+
lines.push(`- **${id}**:首次 fetch,baseline 已写入(${report.reason ?? ''})`);
|
|
430
|
+
} else {
|
|
431
|
+
lines.push(`- **${id}** [${report.status}]:${report.reason ?? ''}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
lines.push('');
|
|
435
|
+
lines.push('纪律:本监测只产 diff,不自动改价表;价格调整必须人工 PR,符合 ADR-11 「peak only-high-not-low」。');
|
|
436
|
+
return lines.join('\n');
|
|
437
|
+
}
|
|
438
|
+
function formatMarkdown(report) {
|
|
439
|
+
const lines = [
|
|
440
|
+
`# LLM 价格漂移监测(${report.observed_at},mode=${report.mode})`,
|
|
441
|
+
''
|
|
442
|
+
];
|
|
443
|
+
for (const [id, r] of Object.entries(report.providers)){
|
|
444
|
+
lines.push(`## ${id} [${r.status}]`);
|
|
445
|
+
lines.push(`source: ${r.source_url}`);
|
|
446
|
+
if (r.reason) lines.push(`reason: ${r.reason}`);
|
|
447
|
+
if (r.drift && r.drift.length > 0) {
|
|
448
|
+
lines.push('| model | field | from | to | direction |');
|
|
449
|
+
lines.push('|---|---|---|---|---|');
|
|
450
|
+
for (const d of r.drift)lines.push(`| \`${d.model}\` | ${d.field} | ${d.from ?? '∅'} | ${d.to ?? '∅'} | ${d.direction} |`);
|
|
451
|
+
}
|
|
452
|
+
lines.push('');
|
|
453
|
+
}
|
|
454
|
+
lines.push('---');
|
|
455
|
+
lines.push('');
|
|
456
|
+
lines.push(report.suggested_pr_paragraph);
|
|
457
|
+
return lines.join('\n');
|
|
458
|
+
}
|
|
459
|
+
function arg(name) {
|
|
460
|
+
const index = process.argv.indexOf(name);
|
|
461
|
+
return index >= 0 ? process.argv[index + 1] : undefined;
|
|
462
|
+
}
|
|
463
|
+
async function main() {
|
|
464
|
+
const asJson = (arg('--format') ?? 'markdown') === 'json';
|
|
465
|
+
const mode = process.argv.includes('--fetch') ? 'fetch' : 'offline';
|
|
466
|
+
mkdirSync('gotry-state', {
|
|
467
|
+
recursive: true
|
|
468
|
+
});
|
|
469
|
+
try {
|
|
470
|
+
const report = await runPriceDriftWatch({
|
|
471
|
+
mode,
|
|
472
|
+
observedAt: ()=>new Date()
|
|
473
|
+
});
|
|
474
|
+
if (asJson) console.log(JSON.stringify(report, null, 2));
|
|
475
|
+
else console.log(formatMarkdown(report));
|
|
476
|
+
} catch (error) {
|
|
477
|
+
const payload = {
|
|
478
|
+
state: 'error',
|
|
479
|
+
error: error.message
|
|
480
|
+
};
|
|
481
|
+
if (asJson) console.log(JSON.stringify(payload, null, 2));
|
|
482
|
+
else console.error(`price-drift-watch error: ${error.message}`);
|
|
483
|
+
process.exitCode = 1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (process.argv[1]?.endsWith('price-drift-watch.ts')) {
|
|
487
|
+
await main();
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/price-drift-watch.ts
|
|
@@ -85,4 +85,4 @@ pass('7. 金标准对话噪音回归(2026-08-28 巡检:访谈答案/多段首查
|
|
|
85
85
|
console.log('\nprobePoi TESTS: 6 类 OK(不崩 + 5 类触发 + 1 类不触发返 null)');
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/probe-poi-tests.ts
|
|
88
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/probe-poi-tests.ts
|
|
@@ -112,4 +112,4 @@ assert.equal(bare.spec.segments[0].options[0].move.services[0].priceCny, 3400, '
|
|
|
112
112
|
console.log('\nREALTIME PRICING TESTS: 5/5 OK(hit 覆写+证据/error 降级/日期词表闸/求解集成/无段零触碰)');
|
|
113
113
|
|
|
114
114
|
|
|
115
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/realtime-pricing-tests.ts
|
|
115
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/realtime-pricing-tests.ts
|
package/dist/scripts/replay.js
CHANGED
|
@@ -258,4 +258,4 @@ check('FlyAI 无完整 itemList 对象时保持 fail-closed', ()=>{
|
|
|
258
258
|
console.log(`SESSION DOUBLE-SOURCE CONTRACT: ${passed} pass`);
|
|
259
259
|
|
|
260
260
|
|
|
261
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/session-benchmark.ts
|
|
261
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/session-benchmark.ts
|
|
@@ -78,4 +78,4 @@ console.log(`\nSESSION-EXTRACT: ${pass} pass, ${fail} fail`);
|
|
|
78
78
|
process.exit(fail > 0 ? 1 : 0);
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/session-extract-tests.ts
|
|
81
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/session-extract-tests.ts
|
|
@@ -402,4 +402,4 @@ console.log('H. flyaiSearch hotel(飞猪官方 search-hotel)');
|
|
|
402
402
|
}console.log(`\nSESSION P1: ${pass} pass, ${fail} fail`);
|
|
403
403
|
|
|
404
404
|
|
|
405
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/session-tests.ts
|
|
405
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/session-tests.ts
|
|
@@ -19,4 +19,4 @@ assert.ok((r.skeleton_notes ?? []).some((n)=>n.includes('HKG') && n.includes('
|
|
|
19
19
|
console.log(`骨架集成 OK:EK329 在枢纽否定下存活,标注=[${r.skeleton_notes?.join(' | ')}]`);
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/skeleton-integration-test.ts
|
|
22
|
+
//# sourceURL=/Users/bytedance/work/gotry-publish/ts/scripts/skeleton-integration-test.ts
|