@blendification/results 0.1.1 → 0.2.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/CHANGELOG.md +22 -0
- package/README.md +55 -2
- package/package.json +4 -3
- package/src/client.js +50 -0
- package/src/index.js +1 -1
- package/types/index.d.ts +158 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
For licensees, who run research for client companies under one billing account.
|
|
6
|
+
|
|
7
|
+
- `forCompany(companyId)` returns a client that works in that company. It
|
|
8
|
+
sends the `x-blendification-company` header on every call, so one server can
|
|
9
|
+
serve several companies at once. The server refuses a call for a company you
|
|
10
|
+
cannot act in rather than running it in your own. `createResultsClient`
|
|
11
|
+
takes the same thing as a `companyId` option.
|
|
12
|
+
- `createClientCompany(name)` opens a client company under your billing account
|
|
13
|
+
and returns its `companyId`.
|
|
14
|
+
- `billing.overview()`, `billing.usage()` and `billing.rates()` return the
|
|
15
|
+
account (plan, cycle, balance and companies), the credits spent per company
|
|
16
|
+
and activity with AI usage split by service, and the price list. `usage`
|
|
17
|
+
takes a company, a cycle (`'all'` for all time) or a `from`/`to` window, and
|
|
18
|
+
a `Date` works as a bound.
|
|
19
|
+
- Types for all of the above, pinned to what the server sends by a drift test.
|
|
20
|
+
- `COMPANY_HEADER` is exported.
|
|
21
|
+
|
|
22
|
+
Needs the server deployed with it. An older server answers these tools as
|
|
23
|
+
unknown.
|
|
24
|
+
|
|
3
25
|
## 0.1.1
|
|
4
26
|
|
|
5
27
|
- `TrackedConversation`, the type of what `conversations()` yields, now has the fields the server sends: `conversationId`, `respondentName` and `respondentEmail`. 0.1.0 declared `id`, `name` and `email`, which were always undefined. The type also gains `abandoned`, `templateId` and the conversation's dates. It drops the catch-all index signature, so a misspelt field no longer compiles.
|
package/README.md
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Read Blendification results from your own page or server: how a survey wave is
|
|
4
4
|
going, who has finished, transcripts, results-group summaries and full exports.
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
A licensee can also open client companies, work in each, and see what the
|
|
6
|
+
research cost. The package also verifies the webhooks Blendification sends when
|
|
7
|
+
someone starts or finishes a conversation.
|
|
7
8
|
|
|
8
9
|
It talks to the same MCP endpoint an AI agent uses, so a page sees what its
|
|
9
10
|
owner's agent sees, through the same tenant checks.
|
|
@@ -83,8 +84,60 @@ const results = createResultsClient({
|
|
|
83
84
|
| `summary(resultsGroupId)` | A results group's name, counts, topics and reports |
|
|
84
85
|
| `exportGroup(resultsGroupId, { format: 'csv' })` | Starts an export, waits for it, and returns download links plus the first page |
|
|
85
86
|
| `watchProgress(args, { onUpdate, intervalMs })` | Polls `progress` and calls `onUpdate` when the counts change. Returns `stop()` |
|
|
87
|
+
| `billing.overview()`, `billing.usage({ companyId?, cycleId?, from?, to? })`, `billing.rates()` | The account, credits spent per company and activity, and the price list (below) |
|
|
88
|
+
| `createClientCompany(name)`, `forCompany(companyId)` | Open a client company, and a client that works in it (below) |
|
|
86
89
|
| `call(tool, args)` | Any MCP tool the token may use |
|
|
87
90
|
|
|
91
|
+
## Client companies and what they cost
|
|
92
|
+
|
|
93
|
+
A licensee runs research for its own customers, each in a client company of its
|
|
94
|
+
own. The client companies sit under the licensee's billing account, and their
|
|
95
|
+
usage rolls up to it. A licensee's server can open those companies, work in any
|
|
96
|
+
of them, and read what the work cost.
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
const results = createResultsClient({ mcpUrl, auth: clientCredentials({ clientId, clientSecret, scope: 'mcp/read mcp/write' }) });
|
|
100
|
+
|
|
101
|
+
const acme = await results.createClientCompany('Acme Health'); // { companyId, name, billingAccountId }
|
|
102
|
+
const forAcme = results.forCompany(acme.companyId); // every call from forAcme works in Acme
|
|
103
|
+
const p = await forAcme.progress({ templateId: '<template id>' });
|
|
104
|
+
|
|
105
|
+
const cycle = await results.billing.usage(); // this cycle, every company
|
|
106
|
+
const acmeCost = await results.billing.usage({ companyId: acme.companyId }); // one company
|
|
107
|
+
const november = await results.billing.usage({ from: '2026-11-01', to: '2026-11-30' });
|
|
108
|
+
const account = await results.billing.overview(); // plan, cycle dates, balance, the companies on the account
|
|
109
|
+
const prices = await results.billing.rates(); // credits per interview, analysis, deck, and the AI rates
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`forCompany(id)` sends the `x-blendification-company` header on each call. That
|
|
113
|
+
lets one server work for several companies at once. The server checks every
|
|
114
|
+
call, and refuses one for a company you cannot act in instead of running it in
|
|
115
|
+
your own. `createResultsClient({ ..., companyId })` does the same for a client
|
|
116
|
+
that only ever works in one company.
|
|
117
|
+
|
|
118
|
+
Spend is in credits.
|
|
119
|
+
|
|
120
|
+
- `byCompany` gives each company's credits per activity: interviews started
|
|
121
|
+
and completed, analysis reports, presentations, mixed-data runs and test
|
|
122
|
+
drives.
|
|
123
|
+
- `aiByService` splits AI model usage by what made the calls: the AI
|
|
124
|
+
interviewer, Ask Fusion, analysis or the knowledge graph.
|
|
125
|
+
- Credits granted, expired or adjusted are listed apart in `movements` and are
|
|
126
|
+
not spend.
|
|
127
|
+
- To turn credits into dollars, use the plan in `billing.overview()`. The
|
|
128
|
+
included credits come with `monthlyPriceUsd`, and the rest bill at
|
|
129
|
+
`overageRateUsdPerCredit`.
|
|
130
|
+
|
|
131
|
+
Who can do what:
|
|
132
|
+
|
|
133
|
+
- **Opening companies** needs a manager role on the account (Admin or Client
|
|
134
|
+
Manager) and write access.
|
|
135
|
+
- **Billing** is for the account's Owner and Admins, the people who see Billing
|
|
136
|
+
in the app.
|
|
137
|
+
- **A server's credentials** carry a role when Blendification issues them, so
|
|
138
|
+
ask for Admin if it should do both.
|
|
139
|
+
- **An Admin of a client company** sees only their own company's spend.
|
|
140
|
+
|
|
88
141
|
## Webhooks
|
|
89
142
|
|
|
90
143
|
Register a webhook over MCP (`webhook_create`) to have Blendification POST to
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blendification/results",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Read Blendification results (survey progress, conversations, transcripts, exports) from your own page or server, signed in with Blendification's OAuth, and verify the webhooks it sends.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Read Blendification results (survey progress, conversations, transcripts, exports) from your own page or server, signed in with Blendification's OAuth, open client companies and see what research costs, and verify the webhooks it sends.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Blendification",
|
|
7
7
|
"homepage": "https://github.com/blendification/blendification-platform/tree/main/packages/results-sdk#readme",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"survey",
|
|
20
20
|
"mcp",
|
|
21
21
|
"oauth",
|
|
22
|
-
"webhooks"
|
|
22
|
+
"webhooks",
|
|
23
|
+
"billing"
|
|
23
24
|
],
|
|
24
25
|
"type": "module",
|
|
25
26
|
"main": "src/index.js",
|
package/src/client.js
CHANGED
|
@@ -68,14 +68,31 @@ export function mergeProgress(total, page) {
|
|
|
68
68
|
|
|
69
69
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
70
70
|
|
|
71
|
+
/** The request header that names the company one call works in. */
|
|
72
|
+
export const COMPANY_HEADER = 'x-blendification-company';
|
|
73
|
+
|
|
74
|
+
/** A from/to bound as the server takes it: a Date becomes an ISO time, a string passes through. */
|
|
75
|
+
function timeArg(value) {
|
|
76
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
77
|
+
if (value instanceof Date) {
|
|
78
|
+
if (Number.isNaN(value.getTime())) throw new ResultsError('An invalid Date was passed as a usage window bound.');
|
|
79
|
+
return value.toISOString();
|
|
80
|
+
}
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
71
84
|
/**
|
|
72
85
|
* @param {object} opts
|
|
73
86
|
* @param {string} opts.mcpUrl - the MCP endpoint, e.g. https://<host>/mcp
|
|
74
87
|
* @param {object} opts.auth - pkceSignIn(...), clientCredentials(...), staticToken(...), or { getAccessToken }
|
|
75
88
|
* @param {Function} [opts.fetch]
|
|
89
|
+
* @param {string} [opts.companyId] - work in this company on every call (a client company under your
|
|
90
|
+
* billing account); sent as the x-blendification-company header. forCompany() makes one per company.
|
|
76
91
|
*/
|
|
77
92
|
export function createResultsClient(opts = {}) {
|
|
78
93
|
const { mcpUrl, auth } = opts;
|
|
94
|
+
const companyId = opts.companyId === undefined || opts.companyId === null ? null : String(opts.companyId).trim();
|
|
95
|
+
if (companyId === '') throw new ResultsError('companyId is empty. Pass a company id, or leave it out to work in your own company.');
|
|
79
96
|
if (!mcpUrl) throw new ResultsError('createResultsClient needs mcpUrl (the /mcp address your connection uses).');
|
|
80
97
|
try { new URL(mcpUrl); } catch { throw new ResultsError(`mcpUrl is not a URL: ${mcpUrl}`); }
|
|
81
98
|
if (!auth || typeof auth.getAccessToken !== 'function') throw new ResultsError('createResultsClient needs auth: pkceSignIn(), clientCredentials() or staticToken().');
|
|
@@ -93,6 +110,7 @@ export function createResultsClient(opts = {}) {
|
|
|
93
110
|
accept: 'application/json, text/event-stream',
|
|
94
111
|
'mcp-protocol-version': PROTOCOL_VERSION,
|
|
95
112
|
authorization: `Bearer ${token}`,
|
|
113
|
+
...(companyId ? { [COMPANY_HEADER]: companyId } : {}),
|
|
96
114
|
},
|
|
97
115
|
body: JSON.stringify({ jsonrpc: '2.0', id: ++rpcId, method, params }),
|
|
98
116
|
});
|
|
@@ -223,9 +241,41 @@ export function createResultsClient(opts = {}) {
|
|
|
223
241
|
return () => { stopped = true; };
|
|
224
242
|
}
|
|
225
243
|
|
|
244
|
+
/**
|
|
245
|
+
* The same connection, working in another company: every call it makes names
|
|
246
|
+
* that company, and the server refuses the call if you cannot act there. The
|
|
247
|
+
* way for one backend to serve several client companies at once.
|
|
248
|
+
*/
|
|
249
|
+
function forCompany(id) {
|
|
250
|
+
if (id === undefined || id === null || !String(id).trim()) throw new ResultsError('forCompany needs a company id.');
|
|
251
|
+
return createResultsClient({ ...opts, companyId: String(id).trim() });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Open a client company under your billing account; returns its companyId. Needs a manager role and write access. */
|
|
255
|
+
function createClientCompany(nameOrOpts) {
|
|
256
|
+
const name = typeof nameOrOpts === 'string' ? nameOrOpts : nameOrOpts?.name;
|
|
257
|
+
if (typeof name !== 'string' || !name.trim()) throw new ResultsError('createClientCompany needs a name.');
|
|
258
|
+
return call('create_client_company', { name: name.trim() });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const billing = {
|
|
262
|
+
/** The plan, the cycle, the balance and the companies on your billing account. */
|
|
263
|
+
overview: ({ limit } = {}) => call('billing_overview', { limit }),
|
|
264
|
+
/** Credits spent per company and activity, for the current cycle unless you name another window. */
|
|
265
|
+
usage: ({ companyId: forId, cycleId, from, to, limit } = {}) => call('billing_usage', {
|
|
266
|
+
companyId: forId, cycleId, from: timeArg(from), to: timeArg(to), limit,
|
|
267
|
+
}),
|
|
268
|
+
/** What each activity costs in credits, and the AI rates. */
|
|
269
|
+
rates: ({ includeModels } = {}) => call('billing_rates', { includeModels }),
|
|
270
|
+
};
|
|
271
|
+
|
|
226
272
|
return {
|
|
227
273
|
auth,
|
|
228
274
|
mcpUrl,
|
|
275
|
+
companyId,
|
|
276
|
+
forCompany,
|
|
277
|
+
createClientCompany,
|
|
278
|
+
billing,
|
|
229
279
|
call,
|
|
230
280
|
listTools,
|
|
231
281
|
progress,
|
package/src/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Webhook verification is in '@blendification/results/webhooks' (Node only).
|
|
13
13
|
*/
|
|
14
|
-
export { createResultsClient, mergeProgress, ResultsError, PROTOCOL_VERSION } from './client.js';
|
|
14
|
+
export { createResultsClient, mergeProgress, ResultsError, PROTOCOL_VERSION, COMPANY_HEADER } from './client.js';
|
|
15
15
|
export {
|
|
16
16
|
pkceSignIn,
|
|
17
17
|
clientCredentials,
|
package/types/index.d.ts
CHANGED
|
@@ -146,9 +146,165 @@ export interface ConversationPage {
|
|
|
146
146
|
message: string;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
/** A plan as billing_overview reports it. Credits are the unit everything is billed in. */
|
|
150
|
+
export interface BillingPlan {
|
|
151
|
+
planId: string | null;
|
|
152
|
+
name: string | null;
|
|
153
|
+
monthlyPriceUsd: number | null;
|
|
154
|
+
includedCreditsPerCycle: number;
|
|
155
|
+
/** What a credit beyond the included ones costs, in USD. */
|
|
156
|
+
overageRateUsdPerCredit: number;
|
|
157
|
+
isSponsored: boolean;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A company on the billing account. */
|
|
161
|
+
export interface BillingCompany {
|
|
162
|
+
companyId: string;
|
|
163
|
+
name: string | null;
|
|
164
|
+
/** The licensed company that owns the account; the others are its client companies. */
|
|
165
|
+
isLicensee: boolean;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* billing_overview. A drift test in services/analysis-engine pins these names
|
|
170
|
+
* to the object the server builds.
|
|
171
|
+
*/
|
|
172
|
+
export interface BillingOverview {
|
|
173
|
+
billingAccountId: string | null;
|
|
174
|
+
name: string | null;
|
|
175
|
+
/** 'account': you see every company on the account; 'company': an Admin of a client company sees only their own. */
|
|
176
|
+
view: 'account' | 'company';
|
|
177
|
+
status: string | null;
|
|
178
|
+
plan: BillingPlan;
|
|
179
|
+
cycle: { id: string | null; start: string | null; end: string | null };
|
|
180
|
+
trialEnd: string | null;
|
|
181
|
+
/** Credits left. */
|
|
182
|
+
balance: number;
|
|
183
|
+
lifetimeGranted: number;
|
|
184
|
+
lifetimeDebited: number;
|
|
185
|
+
overageUnits: number;
|
|
186
|
+
enforcement: string | null;
|
|
187
|
+
licenseeCompanyId: string | null;
|
|
188
|
+
homeCompanyId: string | null;
|
|
189
|
+
/** The company this call worked in: your own, or the one forCompany() / the header named. */
|
|
190
|
+
actingCompanyId: string | null;
|
|
191
|
+
companies: BillingCompany[];
|
|
192
|
+
/** Every company on the account, even when `companies` is cut to the limit. */
|
|
193
|
+
companyCount: number;
|
|
194
|
+
companiesTruncated: boolean;
|
|
195
|
+
unit: 'credits';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Spend on one kind of activity. `type` is the ledger's key; `label` says it in words. */
|
|
199
|
+
export interface UsageByType {
|
|
200
|
+
type: string;
|
|
201
|
+
label: string;
|
|
202
|
+
count: number;
|
|
203
|
+
spentCredits: number;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** AI spend by the service that made the model calls (the AI interviewer, Ask Fusion, analysis, ...). */
|
|
207
|
+
export interface UsageByService {
|
|
208
|
+
service: string;
|
|
209
|
+
label: string;
|
|
210
|
+
count: number;
|
|
211
|
+
spentCredits: number;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** One company's spend. companyId null is spend tied to no company (account level). */
|
|
215
|
+
export interface CompanyUsage {
|
|
216
|
+
companyId: string | null;
|
|
217
|
+
name: string | null;
|
|
218
|
+
spentCredits: number;
|
|
219
|
+
count: number;
|
|
220
|
+
types: UsageByType[];
|
|
221
|
+
aiByService: UsageByService[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Credits added or removed that are not anyone's use: grants, top-ups, expiries, adjustments. Signed. */
|
|
225
|
+
export interface CreditMovement {
|
|
226
|
+
type: string;
|
|
227
|
+
label: string;
|
|
228
|
+
kind: string | null;
|
|
229
|
+
count: number;
|
|
230
|
+
credits: number;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** billing_usage. Only use is spend; movements are reported apart. */
|
|
234
|
+
export interface BillingUsage {
|
|
235
|
+
billingAccountId: string | null;
|
|
236
|
+
view: 'account' | 'company';
|
|
237
|
+
/** The one company reported, or null for every company you can see. */
|
|
238
|
+
companyId: string | null;
|
|
239
|
+
/** Why that company: you asked, you asked for "all", the call worked in it, or the default. */
|
|
240
|
+
companyChosenBy: 'asked' | 'all' | 'acting' | 'default';
|
|
241
|
+
window: { scope: 'cycle' | 'all' | 'range' | null; cycleId: string | null; from: string | null; to: string | null };
|
|
242
|
+
unit: 'credits';
|
|
243
|
+
totals: { spentCredits: number; debitCount: number; addedCredits: number; removedCredits: number };
|
|
244
|
+
byType: UsageByType[];
|
|
245
|
+
aiByService: UsageByService[];
|
|
246
|
+
/** Largest spend first, cut to the limit; the rest are summed in otherCompanies. */
|
|
247
|
+
byCompany: CompanyUsage[];
|
|
248
|
+
otherCompanies: { count: number; spentCredits: number } | null;
|
|
249
|
+
movements: CreditMovement[];
|
|
250
|
+
entryCount: number;
|
|
251
|
+
/** True when the window held more ledger entries than one read covers: narrow it. */
|
|
252
|
+
truncated: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Credits per 1,000 input and output tokens, and per unit (a character, an audio second, an image). */
|
|
256
|
+
export interface AiRate {
|
|
257
|
+
inputPer1k: number;
|
|
258
|
+
outputPer1k: number;
|
|
259
|
+
perUnit: number;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface ModelRate extends AiRate {
|
|
263
|
+
/** provider:model */
|
|
264
|
+
model: string;
|
|
265
|
+
kinds?: Record<string, Partial<AiRate>>;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** billing_rates. The same for every account. */
|
|
269
|
+
export interface BillingRates {
|
|
270
|
+
version: string | null;
|
|
271
|
+
unit: 'credits';
|
|
272
|
+
events: Array<{ type: string; label: string; credits: number }>;
|
|
273
|
+
aiDefault: AiRate | null;
|
|
274
|
+
modelCount: number;
|
|
275
|
+
/** Null unless includeModels was asked for. */
|
|
276
|
+
models: ModelRate[] | null;
|
|
277
|
+
roundingMode: string | null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** create_client_company's answer. */
|
|
281
|
+
export interface ClientCompany {
|
|
282
|
+
companyId: string;
|
|
283
|
+
name: string;
|
|
284
|
+
billingAccountId: string | null;
|
|
285
|
+
message: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** A from/to bound: a Date, or YYYY-MM-DD (a whole day when it ends a window), or an ISO time. */
|
|
289
|
+
export type UsageBound = Date | string;
|
|
290
|
+
|
|
291
|
+
export interface BillingReads {
|
|
292
|
+
overview(opts?: { limit?: number }): Promise<BillingOverview>;
|
|
293
|
+
usage(opts?: { companyId?: string | 'all'; cycleId?: string | 'all'; from?: UsageBound; to?: UsageBound; limit?: number }): Promise<BillingUsage>;
|
|
294
|
+
rates(opts?: { includeModels?: boolean }): Promise<BillingRates>;
|
|
295
|
+
}
|
|
296
|
+
|
|
149
297
|
export interface ResultsClient {
|
|
150
298
|
auth: AuthProvider;
|
|
151
299
|
mcpUrl: string;
|
|
300
|
+
/** The company every call names (x-blendification-company), or null for your own. */
|
|
301
|
+
companyId: string | null;
|
|
302
|
+
/** The same connection working in another company. The server refuses a call in a company you cannot act in. */
|
|
303
|
+
forCompany(companyId: string): ResultsClient;
|
|
304
|
+
/** Open a client company under your billing account. Needs a manager role and write access. */
|
|
305
|
+
createClientCompany(nameOrOpts: string | { name: string }): Promise<ClientCompany>;
|
|
306
|
+
/** What research costs: the account, spend per company and activity, and the price list. */
|
|
307
|
+
billing: BillingReads;
|
|
152
308
|
call<T = any>(tool: string, args?: Record<string, unknown>): Promise<T>;
|
|
153
309
|
listTools(): Promise<Array<{ name: string; description: string; inputSchema: object }>>;
|
|
154
310
|
progress(args: ScopeArgs): Promise<Progress>;
|
|
@@ -163,5 +319,6 @@ export interface ResultsClient {
|
|
|
163
319
|
templates(args?: Record<string, unknown>): Promise<any>;
|
|
164
320
|
}
|
|
165
321
|
|
|
166
|
-
export declare
|
|
322
|
+
export declare const COMPANY_HEADER: 'x-blendification-company';
|
|
323
|
+
export declare function createResultsClient(opts: { mcpUrl: string; auth: AuthProvider; fetch?: typeof fetch; companyId?: string }): ResultsClient;
|
|
167
324
|
export declare function mergeProgress(total: Progress | null, page: Record<string, unknown>): Progress;
|