@danceiny/gotry 0.2.0-rc.32 → 0.2.0-rc.33
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 +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/capabilities/session/bridge-ledger.js +168 -67
- package/dist/capabilities/session/extension-bridge.js +140 -46
- package/dist/capabilities/session/gui-flight.js +281 -69
- package/dist/capabilities/session/gui-hotel.js +74 -17
- package/dist/capabilities/session-search.js +6 -5
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +129 -2
- package/dist/scripts/booking-copilot-dsh-readiness-proof.js +53 -5
- package/dist/scripts/booking-copilot-gateway-chat-adapter-tests.js +233 -0
- package/dist/scripts/booking-copilot-planner-live-probe.js +1 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +1 -1
- package/dist/scripts/bridge-ledger-tests.js +20 -18
- package/dist/scripts/fixtures/tenant-runaway-worker-fixture.mjs +73 -0
- package/dist/scripts/mutation-check.js +153 -0
- package/dist/scripts/nightly-evidence.js +4 -111
- package/dist/scripts/session-tests.js +42 -8
- package/dist/scripts/tenant-bridge-org-tests.js +1264 -0
- package/dist/scripts/tenant-capacity-probe.js +263 -0
- package/dist/scripts/tenant-compute-isolation-tests.js +707 -0
- package/dist/scripts/tenant-egress-credentials-tests.js +822 -0
- package/dist/scripts/tenant-executor-binding-tests.js +203 -0
- package/dist/scripts/tenant-prompt-partition-tests.js +265 -0
- package/dist/scripts/tenant-seam-adversarial-tests.js +957 -0
- package/dist/scripts/tenant-seam-quota-tests.js +383 -0
- package/dist/scripts/tenant-seam-test-kit.js +292 -0
- package/dist/scripts/tenant-state-copies-tests.js +607 -0
- package/dist/scripts/tenant-state-drill-tests.js +298 -0
- package/dist/scripts/tenant-state-drill-worker.js +164 -0
- package/dist/scripts/tenant-state-durable-conformance.js +101 -0
- package/dist/scripts/tenant-state-handoff-tests.js +854 -0
- package/dist/scripts/tenant-state-lease-conformance.js +142 -0
- package/dist/scripts/tenant-state-lease-worker.js +28 -0
- package/dist/scripts/tenant-state-router-tests.js +464 -0
- package/dist/scripts/tenant-state-test-kit.js +92 -0
- package/dist/scripts/tenant-state-tests.js +564 -0
- package/dist/src/backend/modules/booking-executor.js +61 -6
- package/dist/src/backend/modules/session-search.js +136 -39
- package/dist/src/booking-surface/dsh-llm-gateway-chat.js +434 -0
- package/dist/src/booking-surface/dsh-planner.js +150 -19
- package/dist/src/booking-surface/index.js +1 -0
- package/dist/src/booking-surface/managed-dsh-run-port.js +121 -7
- package/dist/src/booking-surface/server.js +193 -80
- package/dist/src/booking-surface/startup.js +153 -0
- package/dist/src/booking-surface/tenancy.js +188 -0
- package/dist/src/index.js +31 -1
- package/dist/src/llm-price-table.js +148 -0
- package/dist/src/tenant-seam/index.js +8 -0
- package/dist/src/tenant-seam/llm-broker.js +508 -0
- package/dist/src/tenant-seam/process-governor.js +281 -0
- package/dist/src/tenant-seam/quotas.js +360 -0
- package/dist/src/tenant-seam/seam.js +92 -0
- package/dist/src/tenant-state/durable-store.js +158 -0
- package/dist/src/tenant-state/epoch-copies.js +422 -0
- package/dist/src/tenant-state/errors.js +108 -0
- package/dist/src/tenant-state/ids.js +101 -0
- package/dist/src/tenant-state/index.js +11 -0
- package/dist/src/tenant-state/lease-store.js +203 -0
- package/dist/src/tenant-state/tenant-lease.js +150 -0
- package/dist/src/tenant-state/tenant-router.js +650 -0
- package/extension/manifest.json +2 -2
- package/package.json +4 -1
- package/ts/capabilities/session/bridge-ledger.ts +192 -78
- package/ts/capabilities/session/extension-bridge.ts +176 -65
- package/ts/capabilities/session/gui-flight.ts +318 -88
- package/ts/capabilities/session/gui-hotel.ts +72 -18
- package/ts/capabilities/session-search.ts +17 -5
- package/ts/src/booking-surface/dsh-llm-gateway-chat.js +434 -0
- package/ts/src/booking-surface/dsh-planner.ts +169 -19
- package/ts/src/booking-surface/index.ts +1 -0
- package/ts/src/booking-surface/managed-dsh-run-port.ts +150 -7
- package/ts/src/booking-surface/server.ts +215 -84
- package/ts/src/booking-surface/startup.ts +155 -0
- package/ts/src/booking-surface/tenancy.ts +242 -0
- package/ts/src/index.ts +28 -2
- package/ts/src/llm-price-table.ts +180 -0
- package/ts/src/tenant-seam/index.ts +9 -0
- package/ts/src/tenant-seam/llm-broker.ts +520 -0
- package/ts/src/tenant-seam/process-governor.ts +343 -0
- package/ts/src/tenant-seam/quotas.ts +433 -0
- package/ts/src/tenant-seam/seam.ts +142 -0
- package/ts/src/tenant-state/durable-store.ts +171 -0
- package/ts/src/tenant-state/epoch-copies.ts +491 -0
- package/ts/src/tenant-state/errors.ts +128 -0
- package/ts/src/tenant-state/ids.ts +108 -0
- package/ts/src/tenant-state/index.ts +12 -0
- package/ts/src/tenant-state/lease-store.ts +266 -0
- package/ts/src/tenant-state/tenant-lease.ts +175 -0
- package/ts/src/tenant-state/tenant-router.ts +743 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frozen LLM price table (ADR-20) and the one function that turns measured usage into dollars.
|
|
3
|
+
*
|
|
4
|
+
* Moved verbatim from `scripts/nightly-evidence.ts`, which re-exports it, so the nightly evidence producer, the
|
|
5
|
+
* persona simulation and the AaaS quota meter all price through the same code. Prices live in
|
|
6
|
+
* `data/llm-price-table.json` and change only by PR (never auto-applied). Pricing is a peak-conservative upper bound,
|
|
7
|
+
* an unknown model has no price, and usage a provider omitted is unprovable: both fail closed and never guess.
|
|
8
|
+
*
|
|
9
|
+
* Schema evolution (issue #49): `gotry_llm_price_table_v1` is DeepSeek only, with flat fields under `prices.<model>`;
|
|
10
|
+
* `gotry_llm_price_table_v2` is `providers.<id>.models.<model>`, provider-aware, and lets a provider without an
|
|
11
|
+
* off-peak column fill it with the peak price. `loadPriceTable` reads both.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from 'node:fs'
|
|
15
|
+
import type { LlmUsageTracker } from './dsh-llm.ts'
|
|
16
|
+
|
|
17
|
+
export interface PriceEntry {
|
|
18
|
+
input_cache_miss_usd_per_1m_peak: number
|
|
19
|
+
input_cache_miss_usd_per_1m_offpeak: number
|
|
20
|
+
input_cache_hit_usd_per_1m_peak: number
|
|
21
|
+
input_cache_hit_usd_per_1m_offpeak: number
|
|
22
|
+
output_usd_per_1m_peak: number
|
|
23
|
+
output_usd_per_1m_offpeak: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LlmPriceTable {
|
|
27
|
+
schema_version: 'gotry_llm_price_table_v1' | 'gotry_llm_price_table_v2'
|
|
28
|
+
aliases: Record<string, string>
|
|
29
|
+
prices: Record<string, PriceEntry>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function round6(value: number): number {
|
|
33
|
+
return Number(value.toFixed(6))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assertPriceEntry(label: string, value: unknown): PriceEntry {
|
|
37
|
+
const entry = typeof value === 'object' && value !== null ? value as Record<string, unknown> : null
|
|
38
|
+
if (!entry) throw new Error(`${label} must be an object`)
|
|
39
|
+
for (const field of ['input_cache_miss_usd_per_1m_peak', 'input_cache_hit_usd_per_1m_peak', 'output_usd_per_1m_peak']) {
|
|
40
|
+
const num = entry[field]
|
|
41
|
+
if (typeof num !== 'number' || !Number.isFinite(num) || num < 0) {
|
|
42
|
+
throw new Error(`${label}.${field} must be a non-negative finite number`)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return value as PriceEntry
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function loadPriceTable(path: string): LlmPriceTable {
|
|
49
|
+
const raw = JSON.parse(readFileSync(path, 'utf8')) as Record<string, unknown>
|
|
50
|
+
const schemaVersion = raw['schema_version']
|
|
51
|
+
if (schemaVersion === 'gotry_llm_price_table_v2') {
|
|
52
|
+
return loadPriceTableV2(raw)
|
|
53
|
+
}
|
|
54
|
+
if (schemaVersion === 'gotry_llm_price_table_v1') {
|
|
55
|
+
return loadPriceTableV1(raw)
|
|
56
|
+
}
|
|
57
|
+
throw new Error(`price table schema_version must be gotry_llm_price_table_v1 or _v2, got: ${String(schemaVersion)}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function loadPriceTableV1(raw: Record<string, unknown>): LlmPriceTable {
|
|
61
|
+
const aliases = typeof raw['aliases'] === 'object' && raw['aliases'] !== null ? raw['aliases'] as Record<string, string> : {}
|
|
62
|
+
for (const [from, to] of Object.entries(aliases)) {
|
|
63
|
+
if (typeof to !== 'string') throw new Error(`price table alias ${from} must map to a model name`)
|
|
64
|
+
}
|
|
65
|
+
const prices: Record<string, PriceEntry> = {}
|
|
66
|
+
for (const [name, entry] of Object.entries(typeof raw['prices'] === 'object' && raw['prices'] !== null ? raw['prices'] as Record<string, unknown> : {})) {
|
|
67
|
+
prices[name] = assertPriceEntry(`price table entry ${name}`, entry)
|
|
68
|
+
}
|
|
69
|
+
return { schema_version: 'gotry_llm_price_table_v1', aliases, prices }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface ProviderEntry {
|
|
73
|
+
family: string
|
|
74
|
+
price_strategy: 'tiered_peak_offpeak' | 'flat_no_offpeak' | string
|
|
75
|
+
api_base?: string
|
|
76
|
+
source_url?: string
|
|
77
|
+
aliases: Record<string, string>
|
|
78
|
+
models: Record<string, PriceEntry>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** v2 reader: providers.<id>.models.<model> flattened into one aliases/prices pair, so the exposed API does not change.
|
|
82
|
+
* Lookup for a model name:
|
|
83
|
+
* 1. an alias jumps to its target (qualified `providerId:model`);
|
|
84
|
+
* 2. otherwise the qualified `providerId:model` key is looked up in prices;
|
|
85
|
+
* 3. otherwise the bare `model` key (transparent with one provider, e.g. MiniMax-M3 is unique under minimax:models);
|
|
86
|
+
* 4. still not found: fail closed. */
|
|
87
|
+
function loadPriceTableV2(raw: Record<string, unknown>): LlmPriceTable {
|
|
88
|
+
const providersObj = typeof raw['providers'] === 'object' && raw['providers'] !== null ? raw['providers'] as Record<string, unknown> : null
|
|
89
|
+
if (!providersObj) throw new Error('price table v2 requires providers object')
|
|
90
|
+
const aliases: Record<string, string> = {}
|
|
91
|
+
const prices: Record<string, PriceEntry> = {}
|
|
92
|
+
for (const [providerId, rawProvider] of Object.entries(providersObj)) {
|
|
93
|
+
const provider = assertProviderEntry(`price table provider ${providerId}`, rawProvider)
|
|
94
|
+
for (const [aliasName, target] of Object.entries(provider.aliases)) {
|
|
95
|
+
if (typeof target !== 'string') throw new Error(`provider ${providerId} alias ${aliasName} must map to a model name`)
|
|
96
|
+
if (!provider.models[target]) {
|
|
97
|
+
throw new Error(`provider ${providerId} alias ${aliasName} -> ${target} has no entry; update data/llm-price-table.json via PR`)
|
|
98
|
+
}
|
|
99
|
+
aliases[aliasName] = target
|
|
100
|
+
}
|
|
101
|
+
for (const [modelName, entry] of Object.entries(provider.models)) {
|
|
102
|
+
const qualified = `${providerId}:${modelName}`
|
|
103
|
+
const validated = assertPriceEntry(`price table provider ${providerId} model ${modelName}`, entry)
|
|
104
|
+
if (prices[qualified]) throw new Error(`duplicate price table key ${qualified}`)
|
|
105
|
+
prices[qualified] = validated
|
|
106
|
+
if (prices[modelName]) throw new Error(`duplicate price table key ${modelName} (across providers, disambiguate via aliases or qualified lookup)`)
|
|
107
|
+
prices[modelName] = validated
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { schema_version: 'gotry_llm_price_table_v2', aliases, prices }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function assertProviderEntry(label: string, value: unknown): ProviderEntry {
|
|
114
|
+
const entry = typeof value === 'object' && value !== null ? value as Record<string, unknown> : null
|
|
115
|
+
if (!entry) throw new Error(`${label} must be an object`)
|
|
116
|
+
const family = entry['family']
|
|
117
|
+
if (typeof family !== 'string' || family.length === 0) throw new Error(`${label}.family must be a non-empty string`)
|
|
118
|
+
const strategy = entry['price_strategy']
|
|
119
|
+
if (strategy !== 'tiered_peak_offpeak' && strategy !== 'flat_no_offpeak') {
|
|
120
|
+
throw new Error(`${label}.price_strategy must be 'tiered_peak_offpeak' or 'flat_no_offpeak', got: ${String(strategy)}`)
|
|
121
|
+
}
|
|
122
|
+
const aliases = typeof entry['aliases'] === 'object' && entry['aliases'] !== null ? entry['aliases'] as Record<string, string> : {}
|
|
123
|
+
const models = typeof entry['models'] === 'object' && entry['models'] !== null ? entry['models'] as Record<string, PriceEntry> : {}
|
|
124
|
+
return {
|
|
125
|
+
family,
|
|
126
|
+
price_strategy: strategy,
|
|
127
|
+
api_base: typeof entry['api_base'] === 'string' ? entry['api_base'] as string : undefined,
|
|
128
|
+
source_url: typeof entry['source_url'] === 'string' ? entry['source_url'] as string : undefined,
|
|
129
|
+
aliases,
|
|
130
|
+
models,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Dollars for one run's measured usage (peak-conservative upper bound: the table's peak columns, never a guess). */
|
|
135
|
+
export function priceRunCost(model: string, usage: LlmUsageTracker, table: LlmPriceTable): number {
|
|
136
|
+
if (usage.responsesMissingUsage > 0) {
|
|
137
|
+
throw new Error(`usage provider omitted usage on ${usage.responsesMissingUsage} response(s) — cost unprovable, refusing to price (fail-closed)`)
|
|
138
|
+
}
|
|
139
|
+
const alias = table.aliases[model]
|
|
140
|
+
if (alias && !table.prices[alias]) throw new Error(`price table alias ${model} -> ${alias} has no entry; update data/llm-price-table.json via PR`)
|
|
141
|
+
const entry = table.prices[alias ?? model]
|
|
142
|
+
if (!entry) throw new Error(`price table has no entry for model ${model}; update data/llm-price-table.json via PR (fail-closed, no guessed price)`)
|
|
143
|
+
const cost = (usage.inputCacheMissTokens * entry.input_cache_miss_usd_per_1m_peak
|
|
144
|
+
+ usage.inputCacheHitTokens * entry.input_cache_hit_usd_per_1m_peak
|
|
145
|
+
+ usage.outputTokens * entry.output_usd_per_1m_peak) / 1_000_000
|
|
146
|
+
return round6(cost)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The usage a planner invocation reports: fresh input, cache-read input and output tokens, as the gateway adapter maps them. */
|
|
150
|
+
export interface MeasuredLlmUsage {
|
|
151
|
+
readonly inputTokens: number
|
|
152
|
+
readonly outputTokens: number
|
|
153
|
+
readonly cacheReadTokens?: number
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function nonNegativeInteger(name: string, value: unknown): number {
|
|
157
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative integer`)
|
|
158
|
+
return value
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** `priceRunCost` for one measured usage report. Malformed counts are rejected rather than priced. */
|
|
162
|
+
export function priceMeasuredUsage(model: string, usage: MeasuredLlmUsage, table: LlmPriceTable): { readonly tokens: number; readonly costUsd: number } {
|
|
163
|
+
const inputTokens = nonNegativeInteger('inputTokens', usage.inputTokens)
|
|
164
|
+
const outputTokens = nonNegativeInteger('outputTokens', usage.outputTokens)
|
|
165
|
+
const cacheReadTokens = nonNegativeInteger('cacheReadTokens', usage.cacheReadTokens ?? 0)
|
|
166
|
+
const costUsd = priceRunCost(model, {
|
|
167
|
+
calls: 1,
|
|
168
|
+
inputTokens: inputTokens + cacheReadTokens,
|
|
169
|
+
outputTokens,
|
|
170
|
+
inputCacheHitTokens: cacheReadTokens,
|
|
171
|
+
inputCacheMissTokens: inputTokens,
|
|
172
|
+
responsesMissingUsage: 0,
|
|
173
|
+
}, table)
|
|
174
|
+
return { tokens: inputTokens + cacheReadTokens + outputTokens, costUsd }
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Throws unless the table prices `model`. Used at composition time so an unpriced model stops the service before any spend. */
|
|
178
|
+
export function assertModelPriced(model: string, table: LlmPriceTable): void {
|
|
179
|
+
priceRunCost(model, { calls: 0, inputTokens: 0, outputTokens: 0, inputCacheHitTokens: 0, inputCacheMissTokens: 0, responsesMissingUsage: 0 }, table)
|
|
180
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The trusted seam of the AaaS line (docs/rfc/aaas-rfc.md §4.4): tenant derivation from the authenticated caller, and
|
|
3
|
+
* the quotas and metering that keep one tenant or organization from moving another's latency budget. Outside the kernel.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export * from './quotas.ts'
|
|
7
|
+
export * from './seam.ts'
|
|
8
|
+
export * from './llm-broker.ts'
|
|
9
|
+
export * from './process-governor.ts'
|