@frockbot/cloudflare 0.0.0 → 0.7.291
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/build-artifact.ts +130 -0
- package/build-flutter-web.ts +508 -0
- package/deployment-config/README.md +414 -0
- package/deployment-config/cli.ts +228 -0
- package/deployment-config/generate.ts +689 -0
- package/deployment-config/jsonc.ts +18 -0
- package/deployment-config/profile-schema.generated.ts +391 -0
- package/deployment-config/profile.schema.json +358 -0
- package/deployment-config/profile.ts +124 -0
- package/migrations/0001_better_auth.sql +15 -0
- package/migrations/0002_drop_account_issuer.sql +8 -0
- package/package.json +61 -5
- package/release-version.ts +20 -0
- package/src/account-admission.ts +179 -0
- package/src/account-deletion.ts +274 -0
- package/src/admin-entrypoint.ts +194 -0
- package/src/admin-identities.ts +26 -0
- package/src/audit.ts +103 -0
- package/src/auth-package.access.ts +24 -0
- package/src/auth-package.ts +36 -0
- package/src/avatar-state-cleanup.ts +107 -0
- package/src/billing-computer.ts +267 -0
- package/src/billing-readiness.ts +21 -0
- package/src/billing.ts +461 -0
- package/src/bot-capabilities.ts +480 -0
- package/src/bot-recovery.ts +1 -0
- package/src/bot-state-channel.ts +702 -0
- package/src/bot-state.ts +3995 -0
- package/src/bot-template-cleanup.ts +98 -0
- package/src/bot-title-cleanup.ts +104 -0
- package/src/brand-icon.png +0 -0
- package/src/brand-logo.ts +2 -0
- package/src/brand.ts +36 -0
- package/src/client-compatibility.ts +51 -0
- package/src/compaction-announcement-cleanup.ts +113 -0
- package/src/computer-egress.ts +110 -0
- package/src/computer-host.ts +92 -0
- package/src/computer-screenshot-cleanup.ts +78 -0
- package/src/contracts.ts +832 -0
- package/src/debug.ts +272 -0
- package/src/default-packages-marker-cleanup.ts +39 -0
- package/src/deployment-policy-admin-host.ts +121 -0
- package/src/deployment-policy.ts +426 -0
- package/src/directory-profile-cleanup.ts +101 -0
- package/src/durable-rpc.ts +753 -0
- package/src/durable-session.ts +21 -0
- package/src/entry-boundary.ts +103 -0
- package/src/frock-ai.ts +338 -0
- package/src/gateway.ts +1392 -0
- package/src/group-chat.ts +736 -0
- package/src/hidden-bot-notifications-cleanup.ts +32 -0
- package/src/index.ts +2798 -0
- package/src/insights.ts +15 -0
- package/src/machine-messages-cleanup.ts +121 -0
- package/src/machine-socket.ts +164 -0
- package/src/memory-records.ts +181 -0
- package/src/memory.ts +145 -0
- package/src/model-rates.ts +252 -0
- package/src/native-auth.ts +1006 -0
- package/src/native-sessions.ts +121 -0
- package/src/notification-state-cleanup.ts +18 -0
- package/src/ollama-web-search-cleanup.ts +69 -0
- package/src/package-page-shapes-cleanup.ts +190 -0
- package/src/plugin-egress.ts +31 -0
- package/src/plugin-page-route.ts +72 -0
- package/src/plugin-panels-cleanup.ts +101 -0
- package/src/prepared-input-cleanup.ts +105 -0
- package/src/production-secrets.ts +484 -0
- package/src/project-cleanup.ts +123 -0
- package/src/project-events-cleanup.ts +152 -0
- package/src/publication-state-cleanup.ts +92 -0
- package/src/push.ts +498 -0
- package/src/request-body.ts +165 -0
- package/src/routine-state-cleanup.ts +57 -0
- package/src/search.ts +88 -0
- package/src/sidebar-label-cleanup.ts +165 -0
- package/src/skill-index-cleanup.ts +53 -0
- package/src/supersede-cleanup.ts +162 -0
- package/src/test-chat-cleanup.ts +93 -0
- package/src/uploads.ts +486 -0
- package/src/user-application.ts +1068 -0
- package/src/user-configuration.ts +4491 -0
- package/src/voice-assistant.ts +4999 -0
- package/src/voice-dictation.ts +743 -0
- package/src/working-context-cleanup.ts +96 -0
- package/src/workspace.ts +201 -0
- package/wrangler.jsonc +385 -0
- package/README.md +0 -3
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// The hosted model rate table on Cloudflare: its storage inside the
|
|
2
|
+
// `DeploymentPolicy` authority, and the short-lived copy each Bot object reads
|
|
3
|
+
// its prices from (docs/billing.md).
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
decodeHostedModelRatesV1,
|
|
7
|
+
decodeReportUnpricedServedModelRequestV1,
|
|
8
|
+
decodeSaveHostedModelRatesRequestV1,
|
|
9
|
+
decodeUnpricedServedModelV1,
|
|
10
|
+
hostedModelLimitsV1,
|
|
11
|
+
MODEL_RATES_HISTORY_LIMIT,
|
|
12
|
+
seedHostedModelRatesV1,
|
|
13
|
+
servedRateV1,
|
|
14
|
+
type HostedModelRatesV1,
|
|
15
|
+
type HostedModelRatesViewV1,
|
|
16
|
+
type UnpricedServedModelV1,
|
|
17
|
+
} from "@frockbot/app/billing/rates";
|
|
18
|
+
import { rpcJsonSnapshotV1 } from "./durable-rpc.js";
|
|
19
|
+
|
|
20
|
+
const CURRENT_KEY = "model-rates:v1:current";
|
|
21
|
+
const VERSION_PREFIX = "model-rates:v1:version:";
|
|
22
|
+
const UNPRICED_PREFIX = "model-rates:v1:unpriced:";
|
|
23
|
+
export const MODEL_RATES_SEED_RECEIPT_KEY =
|
|
24
|
+
"maintenance:model-rates-seed:2026-09-24";
|
|
25
|
+
|
|
26
|
+
/** The synchronous key-value half of SQLite-backed Durable Object storage. */
|
|
27
|
+
export interface ModelRatesStorageV1 {
|
|
28
|
+
kv: Pick<SyncKvStorage, "get" | "put" | "list">;
|
|
29
|
+
transactionSync<T>(callback: () => T): T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Zero-padded, so the key order is the version order. */
|
|
33
|
+
function versionKey(version: number): string {
|
|
34
|
+
return `${VERSION_PREFIX}${String(version).padStart(12, "0")}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function unpricedKey(servedModel: string | null): string {
|
|
38
|
+
return `${UNPRICED_PREFIX}${servedModel ?? ""}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Writes version 1 from the prices production ran on before the table, when
|
|
43
|
+
* no version exists. Runs whenever the authority starts; the receipt makes a
|
|
44
|
+
* second start a no-op, and it never overwrites a table an administrator
|
|
45
|
+
* saved.
|
|
46
|
+
*/
|
|
47
|
+
export function seedHostedModelRatesStorageV1(
|
|
48
|
+
storage: ModelRatesStorageV1,
|
|
49
|
+
now: () => Date = () => new Date(),
|
|
50
|
+
): void {
|
|
51
|
+
storage.transactionSync(() => {
|
|
52
|
+
if (storage.kv.get(MODEL_RATES_SEED_RECEIPT_KEY) !== undefined) return;
|
|
53
|
+
const present = storage.kv.get(CURRENT_KEY) !== undefined;
|
|
54
|
+
if (!present) {
|
|
55
|
+
const seed = seedHostedModelRatesV1(now().toISOString());
|
|
56
|
+
storage.kv.put(versionKey(seed.version), seed);
|
|
57
|
+
storage.kv.put(CURRENT_KEY, seed.version);
|
|
58
|
+
}
|
|
59
|
+
storage.kv.put(MODEL_RATES_SEED_RECEIPT_KEY, {
|
|
60
|
+
at: now().toISOString(),
|
|
61
|
+
seeded: present ? 0 : 1,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function currentVersion(storage: ModelRatesStorageV1): number | undefined {
|
|
67
|
+
const version = storage.kv.get<unknown>(CURRENT_KEY);
|
|
68
|
+
if (version === undefined) return undefined;
|
|
69
|
+
if (!Number.isSafeInteger(version) || (version as number) < 1) {
|
|
70
|
+
throw new Error("stored hosted model rate version is invalid");
|
|
71
|
+
}
|
|
72
|
+
return version as number;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function currentHostedModelRatesV1(
|
|
76
|
+
storage: ModelRatesStorageV1,
|
|
77
|
+
): HostedModelRatesV1 {
|
|
78
|
+
const version = currentVersion(storage);
|
|
79
|
+
if (version === undefined) {
|
|
80
|
+
throw new Error("this deployment has no hosted model rates");
|
|
81
|
+
}
|
|
82
|
+
const table = decodeHostedModelRatesV1(
|
|
83
|
+
storage.kv.get(versionKey(version)),
|
|
84
|
+
"stored hosted model rates",
|
|
85
|
+
);
|
|
86
|
+
if (table.version !== version) {
|
|
87
|
+
throw new Error("stored hosted model rates name another version");
|
|
88
|
+
}
|
|
89
|
+
return table;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function hostedModelRatesViewV1(
|
|
93
|
+
storage: ModelRatesStorageV1,
|
|
94
|
+
): HostedModelRatesViewV1 {
|
|
95
|
+
const current = currentHostedModelRatesV1(storage);
|
|
96
|
+
const history = [
|
|
97
|
+
...storage.kv.list({
|
|
98
|
+
prefix: VERSION_PREFIX,
|
|
99
|
+
reverse: true,
|
|
100
|
+
limit: MODEL_RATES_HISTORY_LIMIT,
|
|
101
|
+
}),
|
|
102
|
+
].map(([, value]) =>
|
|
103
|
+
decodeHostedModelRatesV1(value, "stored hosted model rates"),
|
|
104
|
+
);
|
|
105
|
+
const unpriced = [...storage.kv.list({ prefix: UNPRICED_PREFIX })]
|
|
106
|
+
.map(([, value]) => decodeUnpricedServedModelV1(value))
|
|
107
|
+
.filter(
|
|
108
|
+
(report) =>
|
|
109
|
+
report.servedModel === null ||
|
|
110
|
+
!servedRateV1(current, report.servedModel),
|
|
111
|
+
)
|
|
112
|
+
.toSorted((a, b) => b.lastSeenAt.localeCompare(a.lastSeenAt));
|
|
113
|
+
return { schemaVersion: 1, current, history, unpriced };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A compare-and-swap answered as a value, like the authority's other writes:
|
|
118
|
+
* a lost race is an answer the admin portal shows, not a failure.
|
|
119
|
+
*/
|
|
120
|
+
export type ModelRatesWriteV1 =
|
|
121
|
+
| { status: "applied"; value: HostedModelRatesV1 }
|
|
122
|
+
| { status: "conflict"; currentRevision: number };
|
|
123
|
+
|
|
124
|
+
/** Each save is the next version; a saved version is never rewritten. */
|
|
125
|
+
export function saveHostedModelRatesV1(
|
|
126
|
+
storage: ModelRatesStorageV1,
|
|
127
|
+
input: unknown,
|
|
128
|
+
now: () => Date = () => new Date(),
|
|
129
|
+
): ModelRatesWriteV1 {
|
|
130
|
+
const request = decodeSaveHostedModelRatesRequestV1(input);
|
|
131
|
+
return storage.transactionSync<ModelRatesWriteV1>(() => {
|
|
132
|
+
const current = currentVersion(storage) ?? 0;
|
|
133
|
+
if (request.command.baseVersion !== current) {
|
|
134
|
+
return { status: "conflict", currentRevision: current };
|
|
135
|
+
}
|
|
136
|
+
const next: HostedModelRatesV1 = {
|
|
137
|
+
schemaVersion: 1,
|
|
138
|
+
version: current + 1,
|
|
139
|
+
createdAt: now().toISOString(),
|
|
140
|
+
createdBy: request.createdBy,
|
|
141
|
+
routes: request.command.routes,
|
|
142
|
+
served: request.command.served,
|
|
143
|
+
};
|
|
144
|
+
storage.kv.put(versionKey(next.version), next);
|
|
145
|
+
storage.kv.put(CURRENT_KEY, next.version);
|
|
146
|
+
return { status: "applied", value: next };
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function reportUnpricedServedModelV1(
|
|
151
|
+
storage: ModelRatesStorageV1,
|
|
152
|
+
input: unknown,
|
|
153
|
+
now: () => Date = () => new Date(),
|
|
154
|
+
): void {
|
|
155
|
+
const report = decodeReportUnpricedServedModelRequestV1(input);
|
|
156
|
+
storage.transactionSync(() => {
|
|
157
|
+
const key = unpricedKey(report.servedModel);
|
|
158
|
+
const stored = storage.kv.get<unknown>(key);
|
|
159
|
+
const at = now().toISOString();
|
|
160
|
+
const next: UnpricedServedModelV1 = {
|
|
161
|
+
schemaVersion: 1,
|
|
162
|
+
servedModel: report.servedModel,
|
|
163
|
+
route: report.route,
|
|
164
|
+
version: report.version,
|
|
165
|
+
firstSeenAt:
|
|
166
|
+
stored === undefined
|
|
167
|
+
? at
|
|
168
|
+
: decodeUnpricedServedModelV1(stored).firstSeenAt,
|
|
169
|
+
lastSeenAt: at,
|
|
170
|
+
};
|
|
171
|
+
storage.kv.put(key, next);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// --- The Bot object's copy -------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
/** How long a Bot object prices from its copy before reading the table again. */
|
|
178
|
+
export const HOSTED_MODEL_RATES_CACHE_MS_V1 = 60_000;
|
|
179
|
+
|
|
180
|
+
export interface HostedModelRatesAuthorityV1 {
|
|
181
|
+
readModelRates(input: unknown): Promise<unknown>;
|
|
182
|
+
reportUnpricedServedModel(input: unknown): Promise<unknown>;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface HostedModelRatesReaderV1 {
|
|
186
|
+
rates(): Promise<HostedModelRatesV1>;
|
|
187
|
+
/** Each route's prepaid bounds, keyed by the model id it prices. */
|
|
188
|
+
limits(): Promise<ReturnType<typeof hostedModelLimitsV1>>;
|
|
189
|
+
reportUnpriced(report: {
|
|
190
|
+
servedModel: string | null;
|
|
191
|
+
route: string;
|
|
192
|
+
version: number;
|
|
193
|
+
}): Promise<void>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The table as one Bot object prices from it: read at most once a minute, so
|
|
198
|
+
* a model call is not a call to the authority. A failed read falls back to the
|
|
199
|
+
* copy already held — the table it last priced from is still a real table —
|
|
200
|
+
* and only a Bot that has never read one is refused.
|
|
201
|
+
*/
|
|
202
|
+
export function createHostedModelRatesReaderV1(
|
|
203
|
+
authority: () => HostedModelRatesAuthorityV1,
|
|
204
|
+
options: { now?: () => number; ttlMs?: number } = {},
|
|
205
|
+
): HostedModelRatesReaderV1 {
|
|
206
|
+
const now = options.now ?? Date.now;
|
|
207
|
+
const ttlMs = options.ttlMs ?? HOSTED_MODEL_RATES_CACHE_MS_V1;
|
|
208
|
+
let held: { table: HostedModelRatesV1; readAt: number } | undefined;
|
|
209
|
+
let reading: Promise<HostedModelRatesV1> | undefined;
|
|
210
|
+
const reported = new Map<string, number>();
|
|
211
|
+
const read = async (): Promise<HostedModelRatesV1> => {
|
|
212
|
+
try {
|
|
213
|
+
const table = decodeHostedModelRatesV1(
|
|
214
|
+
rpcJsonSnapshotV1(
|
|
215
|
+
await authority().readModelRates({ schemaVersion: 1 }),
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
held = { table, readAt: now() };
|
|
219
|
+
return table;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (held) return held.table;
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const rates = (): Promise<HostedModelRatesV1> => {
|
|
226
|
+
if (held && now() - held.readAt < ttlMs) return Promise.resolve(held.table);
|
|
227
|
+
// Concurrent calls share one read; a failed one is not kept.
|
|
228
|
+
reading ??= read().finally(() => {
|
|
229
|
+
reading = undefined;
|
|
230
|
+
});
|
|
231
|
+
return reading;
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
rates,
|
|
235
|
+
limits: async () => hostedModelLimitsV1(await rates()),
|
|
236
|
+
async reportUnpriced(report) {
|
|
237
|
+
const key = report.servedModel ?? "";
|
|
238
|
+
const last = reported.get(key);
|
|
239
|
+
if (last !== undefined && now() - last < ttlMs) return;
|
|
240
|
+
reported.set(key, now());
|
|
241
|
+
try {
|
|
242
|
+
await authority().reportUnpricedServedModel({
|
|
243
|
+
schemaVersion: 1,
|
|
244
|
+
...report,
|
|
245
|
+
});
|
|
246
|
+
} catch {
|
|
247
|
+
// The settlement carries the flag; the next unpriced call reports again.
|
|
248
|
+
reported.delete(key);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|