@genex-ai/embed-sdk 0.17.0 → 0.21.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/README.md ADDED
@@ -0,0 +1,243 @@
1
+ # Genex embed SDK
2
+
3
+ `@genex-ai/embed-sdk` connects a game to player identity, saves, commerce and
4
+ player-funded generation. Initialize it with your game's slug, API URL and
5
+ trusted dashboard origins before using player APIs. See
6
+ [the play-identity contract](../../CLI_INTEGRATION.md#6-durable-game-state--leaderboards--play-identity)
7
+ for identity and storage.
8
+
9
+ ## Generate during play
10
+
11
+ Runtime generation requires a signed-in player in a production game and an
12
+ enabled Genex runtime service. Each player approves their own payment on Genex.
13
+ The game never handles an OpenRouter key or confirms a charge itself.
14
+
15
+ ```ts
16
+ import { generate, getGenerationModels } from '@genex-ai/embed-sdk';
17
+
18
+ // After initEmbed() and player sign-in, populate your model picker from Genex.
19
+ const { models } = await getGenerationModels();
20
+ const model = models[0];
21
+
22
+ generateButton.addEventListener('click', async () => {
23
+ if (!model) return;
24
+ const result = await generate({
25
+ modelId: model.id,
26
+ estimateCoins: 5, // Fixed price for a started attempt, chosen after benchmarking.
27
+ prompt: 'Invent a friendly creature. Return its name and color as JSON.',
28
+ outputFormat: 'json',
29
+ schema: {
30
+ type: 'object',
31
+ properties: { name: { type: 'string' }, color: { type: 'string' } },
32
+ required: ['name', 'color'],
33
+ additionalProperties: false,
34
+ },
35
+ allowExternal: true,
36
+ });
37
+ if (result.status === 'succeeded') {
38
+ // Validate the shape your game expects before rendering it as data.
39
+ showCreature(result.output);
40
+ }
41
+ });
42
+ ```
43
+
44
+ Call `generate()` directly inside the click handler: a standalone game must
45
+ reserve a confirmation popup before asynchronous work. Embedded games ask the
46
+ trusted Genex parent to show its modal. Native WebViews and local-test identity
47
+ return `native_unsupported` and `local_test_unsupported` until they have supported
48
+ confirmation surfaces.
49
+
50
+ `GenerateOptions` requires `estimateCoins` (an integer from 0 to 1,000,000), `modelId`, `prompt`, `outputFormat: 'text' | 'json'`,
51
+ optional bounded `schema`, `allowExternal` (default false), `idempotencyKey` and
52
+ `timeoutMs` (default ten minutes). Reuse an idempotency key only for the same
53
+ operation; store it if you need retries to survive a reload. Only models returned
54
+ by Genex are accepted. This first adapter generates text and JSON, including
55
+ creature descriptions, dialogue, rules and other structured game data.
56
+
57
+ SDK **0.21.0+** returns the execution `status`, optional `generationId`, and
58
+ `output` plus `source` on success. Cancellation is a normal `canceled` result,
59
+ but it does not imply that consumed work was free. `pending` with
60
+ `error: 'wait_timeout'` means the SDK stopped waiting; it does not cancel or charge
61
+ again. Persist `generationId` and resume with `waitForGeneration(id)`, or inspect
62
+ the current server state with `getGeneration(id)`. Results also carry the
63
+ server's `billingStatus: 'pending'|'final'`, `chargedCoins`,
64
+ `chargedDisplayUsdCents`, `reservedCoins` and `reservedDisplayUsdCents` when
65
+ available, including on failures. These USD amounts use the frozen coin value;
66
+ never derive them from an estimate or the current catalog. An execution can end
67
+ while billing remains pending. `waitForGeneration()` waits for execution;
68
+ continue reading `getGeneration(id)` for later billing settlement. Missing
69
+ receipt fields mean unavailable information, not zero cost.
70
+
71
+ The Genex modal shows the fixed attempt price, its USD equivalent, the game and
72
+ the frozen request before approval. New public quotes carry
73
+ `quote.billingPolicy: 'declared-v1'` and `kind: 'fixed'`, with
74
+ `maxCoins = priceCoins = estimateCoins`. Despite its name, `estimateCoins`
75
+ is the developer-declared **fixed price of a started attempt**: declaring 5 coins
76
+ charges 5 even when actual usage would cost 2. Failure, cancellation or reaching
77
+ the budget limit after work starts also charges the full price; a usable result
78
+ is not guaranteed. No model work means zero charge. Zero is accepted only for a
79
+ server-authorized free or personal-only option.
80
+
81
+ The provider hard budget is derived from that price after the frozen platform
82
+ tariff, then capped by operator limits. Output is clipped to remaining funding;
83
+ an input that cannot fit is refused before a call. Unresolved provider expense
84
+ keeps billing pending and the reservation held until verified. The trusted UI
85
+ must acknowledge the quote's exact `billingPolicy` at confirmation; an old UI
86
+ cannot approve new terms. Previously issued `consumed-v1` quotes keep actual-
87
+ usage billing and quotes without a policy keep their original promise.
88
+
89
+ With `allowExternal: true`, the same quote can offer configured personal Claude
90
+ and ChatGPT connections alongside coins. Each personal choice costs zero coin;
91
+ its own plan and usage limits apply. Funding choices are frozen by the quote; connector metadata remains server-owned
92
+ and follows the current provider configuration. Once chosen, funding never switches providers
93
+ or falls back to paid generation. Personal-only offerings never gain free coin
94
+ execution. Genex neither collects nor hosts subscription credentials.
95
+
96
+ `source: 'external'` means user-supplied output, not proof that a particular model
97
+ generated it; `modelProvenance: 'unverified'` makes that explicit. Treat every generated output as data. Never execute returned code
98
+ or use a claimed model/source as authority to mint coins, rewards or items.
99
+
100
+ ## Registered generation workflows
101
+
102
+ SDK **0.21.0+** supports multi-step generation and usage receipts through an
103
+ operator-registered workflow. This requires the game's trusted server executor;
104
+ an ordinary creator API key cannot register a workflow or dispatch its steps.
105
+ `generate()` and `requestWorkflow()` both require the fixed `estimateCoins`
106
+ price and use the declared-attempt policy above.
107
+
108
+ ```ts
109
+ import { getWorkflowOfferings, requestWorkflow, getEmbedToken } from '@genex-ai/embed-sdk';
110
+
111
+ // Load from this game's registered workflow before enabling the model picker.
112
+ const catalog = await getWorkflowOfferings(configuredWorkflowId);
113
+ // Keep the complete selected offering: availability, funding, model and prices.
114
+ renderModelPicker(catalog.offerings);
115
+
116
+ createButton.addEventListener('click', async () => {
117
+ // Save the selected offering, exact input and benchmarked fixed price together.
118
+ const { offeringId, input, estimateCoins, idempotencyKey } = pendingCreation;
119
+ const approval = await requestWorkflow({
120
+ workflowId: catalog.workflowId, offeringId, input, estimateCoins, allowExternal: true, idempotencyKey,
121
+ });
122
+ if (approval.generationId) saveGenerationId(approval.generationId);
123
+ if (approval.status !== 'authorized' || !approval.generationId) return;
124
+ // Application-specific endpoint: the game backend verifies and claims the
125
+ // approved Genex run, binds it to one durable job, and enqueues its executor.
126
+ await enqueueApprovedWorkflow({
127
+ generationId: approval.generationId, embedToken: getEmbedToken(), input,
128
+ });
129
+ });
130
+ ```
131
+
132
+ Call `requestWorkflow()` directly from the click, before an earlier `await`, so
133
+ the SDK can reserve the standalone popup. It returns **approval**, not the
134
+ finished creature: `{ status: 'authorized'|'canceled'|'expired'|'failed'|'pending',
135
+ generationId?, funding?, error? }`, plus available billing receipt fields.
136
+ Waiting for completion before the backend
137
+ claims and enqueues the run would deadlock. Authorized replay also covers an
138
+ already-completed run, allowing the backend to recover the same job or artifact.
139
+ Personal-provider instructions remain open after authorization.
140
+
141
+ `input` is `{ operation: 'create'|'refactor', description, bundleId, creatureId?,
142
+ revisionDigest? }`; refactors require both creature identity and its revision
143
+ digest. Persist this input, offering and idempotency key before submission; reuse
144
+ them only for the same operation after sign-in or reload. Persist the returned
145
+ generation ID. Read with `getGeneration(id)` and wait with
146
+ `waitForGeneration(id)` after enqueueing. A wait timeout does not cancel work.
147
+ Recovering local state never starts a fresh charge without another player click.
148
+ Read a fresh embed token when sending an approved run to the game backend; never
149
+ persist or log the token.
150
+
151
+ The catalog's `estimatedCoins` and `priceCoins` are suggested price aliases,
152
+ and its maximum describes operator limits. Choose the game's fixed attempt price
153
+ after development benchmarks, pass it as `estimateCoins`, and show coin plus USD.
154
+ The player's final approval always uses the server quote. Preserve the selected
155
+ model, bundle, funding, availability and operator-owned tariff; the game cannot
156
+ change provider prices, dispatch costs or settlement.
157
+
158
+ For new public workflow quotes, `maxCoins` is the same fixed amount as
159
+ `priceCoins`, not a promise to bill actual usage. The operator-owned multiplier
160
+ (2 or 3, default 3) determines how much model work that fixed price can fund.
161
+ The whole started attempt is charged once, including failed or canceled work;
162
+ no model work is zero and unknown expense keeps the hold pending. Only the
163
+ selected model is admitted. The trusted executor validates and delivers the
164
+ artifact; the browser cannot settle it.
165
+
166
+ Use `resumeWorkflow(generationId, { timeoutMs? })` directly from a click to
167
+ reopen a saved approval. It reads the original request without creating a quote
168
+ or requiring a new price, including pre-0.21 consumed and legacy approvals.
169
+ Use `requestWorkflow()` with required `estimateCoins` for new operations.
170
+
171
+ Legacy quotes without `billingPolicy` retain their original fixed-price and
172
+ refund terms. Never replace a saved quote's policy with the current catalog's
173
+ policy, infer a missing multiplier, or reprice an approved operation.
174
+
175
+ The designated `google/gemini-3.8-flash` and `z-ai/glm-5.3-flash` offerings,
176
+ and personal Claude/ChatGPT offerings, cost **0 coins**. Personal-provider
177
+ offerings use the player's own account through provider-specific, Genex-owned
178
+ MCP connectors. Claude uses `/player/mcp`; ChatGPT uses `/player/chatgpt/mcp`.
179
+ ChatGPT connector availability depends on account and workspace policy. One
180
+ external request may be active per player across both providers. Follow the
181
+ trusted instructions to fetch and submit each exact stage and attempt; no
182
+ subscription credentials pass through the game and no paid fallback is allowed.
183
+ Submitted results remain user-supplied; the executor must validate them before
184
+ delivery. Generic and workflow `allowExternal` can offer both configured providers.
185
+
186
+ These workflow endpoints still require signed-in production play, including
187
+ zero-coin offerings. Airena's existing sponsored free guest path is separate;
188
+ do not weaken the workflow's player/session checks to reproduce it.
189
+
190
+ The [runtime API contract](../../CLI_INTEGRATION.md#10-player-funded-runtime-generation)
191
+ describes both APIs, executor authorization, recovery and connector endpoints.
192
+
193
+
194
+ ## Benchmark your own coin costs on the server
195
+
196
+ SDK **0.21.0+** provides `@genex-ai/embed-sdk/development`. Use it only in a
197
+ local Node script or trusted server with your own full creator bearer credential
198
+ and an owned `projectId`. It needs no production play token and cannot choose a
199
+ player wallet. Restricted API keys and personal-only offerings are refused.
200
+ Keep the credential out of game code, browser environment variables and logs;
201
+ the subpath is disabled for browser resolution and rejects browser execution.
202
+
203
+ `maxCoins` is an explicit positive maximum from your own wallet. Development
204
+ uses `consumed-v1`: actual model usage at the frozen normal tariff, including
205
+ failed usage, up to that maximum. Public-sponsored Gemini/GLM models also use
206
+ this normal paid tariff when benchmarking. Actual zero cost is zero coin;
207
+ unknown expense remains held. No personal or paid fallback occurs.
208
+
209
+ ```ts
210
+ import { developmentGenerate } from '@genex-ai/embed-sdk/development';
211
+
212
+ const receipt = await developmentGenerate({
213
+ apiUrl: process.env.GENEX_API_URL!,
214
+ creatorToken: process.env.GENEX_CREATOR_TOKEN!,
215
+ projectId: process.env.GENEX_PROJECT_ID!,
216
+ maxCoins: 5,
217
+ request: {
218
+ modelId: 'your-configured-model-id',
219
+ prompt: 'Invent a friendly creature name.',
220
+ outputFormat: 'text',
221
+ idempotencyKey: 'calibration-sample-001',
222
+ },
223
+ });
224
+ // A 5-coin maximum can return chargedCoins: 2. Use repeated samples to choose
225
+ // the public fixed estimateCoins price; public fixed 5 still charges 5.
226
+ if (receipt.billingStatus === 'final') useForCalibration(receipt.chargedCoins, receipt.usage);
227
+ ```
228
+
229
+ The full receipt includes execution status, output on success, charged/reserved
230
+ coins and USD, plus development-only `usage: { costUsdPicos, costUsd,
231
+ maxProviderUsdPicos, unknownProviderUsdPicos }`. Unknown cost is null rather
232
+ than zero; workflow cost aggregates known actual step costs and reports remaining
233
+ unknown exposure separately. No token counts are inferred.
234
+
235
+ For a registered workflow, call `developmentRequestWorkflow({ apiUrl,
236
+ creatorToken, projectId, workflowId, maxCoins, request: { offeringId, input,
237
+ idempotencyKey? } })`. It returns the accepted, claimed workflow immediately so
238
+ your trusted executor can enqueue it. After enqueueing, call
239
+ `waitForDevelopmentGeneration(config, id, timeoutMs?)`. Both generic and workflow
240
+ benchmarks are recoverable with `getDevelopmentGeneration(config, id)` and
241
+ `cancelDevelopmentGeneration(config, id)`. The wait ends at execution plus final
242
+ billing, or returns the latest server view at timeout; timeout does not cancel
243
+ or claim a refund. Save the operation ID and idempotency key for recovery.
@@ -0,0 +1,249 @@
1
+ // ../embed-protocol/src/constants.ts
2
+ var NATIVE_CHANNEL = "genex-native";
3
+ var NATIVE_PROTOCOL_VERSION = 1;
4
+ var NATIVE_MARKER_PARAM = "genex_native";
5
+ var NATIVE_MARKER_VALUE = "1";
6
+ var NATIVE_PROTOCOL_PARAM = "genex_protocol";
7
+ var NATIVE_ATTEMPT_PARAM = "genex_attempt";
8
+ var NATIVE_NONCE_PARAM = "genex_nonce";
9
+ var NATIVE_RECEIVER = "__genexNative";
10
+ var MAX_BRIDGE_MESSAGE_BYTES = 4096;
11
+ var BRIDGE_ID_RE = /^[0-9a-f]{32}$/;
12
+ var SLUG_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
13
+ function isBridgeId(value) {
14
+ return typeof value === "string" && BRIDGE_ID_RE.test(value);
15
+ }
16
+ function isBridgeSlug(value) {
17
+ return typeof value === "string" && SLUG_RE.test(value);
18
+ }
19
+
20
+ // ../embed-protocol/src/base64url.ts
21
+ var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
22
+ var LOOKUP = (() => {
23
+ const table = new Array(128).fill(-1);
24
+ for (let i = 0; i < ALPHABET.length; i++) table[ALPHABET.charCodeAt(i)] = i;
25
+ return table;
26
+ })();
27
+ function utf8Bytes(text) {
28
+ const out = [];
29
+ for (let i = 0; i < text.length; i++) {
30
+ let code = text.charCodeAt(i);
31
+ if (code >= 55296 && code <= 56319) {
32
+ const next = i + 1 < text.length ? text.charCodeAt(i + 1) : 0;
33
+ if (next >= 56320 && next <= 57343) {
34
+ code = 65536 + (code - 55296 << 10) + (next - 56320);
35
+ i++;
36
+ } else {
37
+ code = 65533;
38
+ }
39
+ } else if (code >= 56320 && code <= 57343) {
40
+ code = 65533;
41
+ }
42
+ if (code < 128) out.push(code);
43
+ else if (code < 2048) out.push(192 | code >> 6, 128 | code & 63);
44
+ else if (code < 65536) {
45
+ out.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
46
+ } else {
47
+ out.push(
48
+ 240 | code >> 18,
49
+ 128 | code >> 12 & 63,
50
+ 128 | code >> 6 & 63,
51
+ 128 | code & 63
52
+ );
53
+ }
54
+ }
55
+ return out;
56
+ }
57
+ function utf8Text(bytes) {
58
+ let out = "";
59
+ for (let i = 0; i < bytes.length; ) {
60
+ const b0 = bytes[i];
61
+ let code;
62
+ let size;
63
+ if (b0 < 128) {
64
+ code = b0;
65
+ size = 1;
66
+ } else if ((b0 & 224) === 192) {
67
+ code = b0 & 31;
68
+ size = 2;
69
+ } else if ((b0 & 240) === 224) {
70
+ code = b0 & 15;
71
+ size = 3;
72
+ } else if ((b0 & 248) === 240) {
73
+ code = b0 & 7;
74
+ size = 4;
75
+ } else return null;
76
+ if (i + size > bytes.length) return null;
77
+ for (let k = 1; k < size; k++) {
78
+ const b = bytes[i + k];
79
+ if ((b & 192) !== 128) return null;
80
+ code = code << 6 | b & 63;
81
+ }
82
+ if (size === 2 && code < 128) return null;
83
+ if (size === 3 && (code < 2048 || code >= 55296 && code <= 57343)) return null;
84
+ if (size === 4 && (code < 65536 || code > 1114111)) return null;
85
+ if (code > 65535) {
86
+ const v = code - 65536;
87
+ out += String.fromCharCode(55296 + (v >> 10), 56320 + (v & 1023));
88
+ } else {
89
+ out += String.fromCharCode(code);
90
+ }
91
+ i += size;
92
+ }
93
+ return out;
94
+ }
95
+ function utf8ByteLength(text) {
96
+ return utf8Bytes(text).length;
97
+ }
98
+ function encodeBase64UrlJson(value) {
99
+ const bytes = utf8Bytes(JSON.stringify(value));
100
+ let out = "";
101
+ for (let i = 0; i < bytes.length; i += 3) {
102
+ const b0 = bytes[i];
103
+ const b1 = bytes[i + 1];
104
+ const b2 = bytes[i + 2];
105
+ out += ALPHABET[b0 >> 2];
106
+ out += ALPHABET[(b0 & 3) << 4 | (b1 ?? 0) >> 4];
107
+ if (b1 === void 0) break;
108
+ out += ALPHABET[(b1 & 15) << 2 | (b2 ?? 0) >> 6];
109
+ if (b2 === void 0) break;
110
+ out += ALPHABET[b2 & 63];
111
+ }
112
+ return out;
113
+ }
114
+ function decodeBase64UrlJson(encoded) {
115
+ if (encoded.length % 4 === 1) return void 0;
116
+ const bytes = [];
117
+ let buffer = 0;
118
+ let bits = 0;
119
+ for (let i = 0; i < encoded.length; i++) {
120
+ const code = encoded.charCodeAt(i);
121
+ const value = code < 128 ? LOOKUP[code] : -1;
122
+ if (value < 0) return void 0;
123
+ buffer = buffer << 6 | value;
124
+ bits += 6;
125
+ if (bits >= 8) {
126
+ bits -= 8;
127
+ bytes.push(buffer >> bits & 255);
128
+ }
129
+ }
130
+ if (bits > 0 && (buffer & (1 << bits) - 1) !== 0) return void 0;
131
+ const text = utf8Text(bytes);
132
+ if (text === null) return void 0;
133
+ try {
134
+ return JSON.parse(text);
135
+ } catch {
136
+ return void 0;
137
+ }
138
+ }
139
+
140
+ // ../embed-protocol/src/messages.ts
141
+ var MAX_TICKET_LENGTH = 2048;
142
+ var MAX_EXPIRES_AT_LENGTH = 32;
143
+ var ISO_INSTANT_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
144
+ function isRecord(value) {
145
+ return typeof value === "object" && value !== null && !Array.isArray(value);
146
+ }
147
+ function hasExactKeys(value, keys) {
148
+ const own = Object.keys(value);
149
+ if (own.length !== keys.length) return false;
150
+ for (const key of keys) {
151
+ if (!Object.prototype.hasOwnProperty.call(value, key)) return false;
152
+ }
153
+ return true;
154
+ }
155
+ var BASE_KEYS = ["channel", "v", "type", "slug", "attemptId", "nonce"];
156
+ function envelopeBaseIsValid(value) {
157
+ return value.channel === NATIVE_CHANNEL && value.v === NATIVE_PROTOCOL_VERSION && isBridgeSlug(value.slug) && isBridgeId(value.attemptId) && isBridgeId(value.nonce);
158
+ }
159
+ function parseBridgeNativeCommand(value) {
160
+ if (!isRecord(value) || !envelopeBaseIsValid(value)) return void 0;
161
+ switch (value.type) {
162
+ case "ticket":
163
+ if (!hasExactKeys(value, [...BASE_KEYS, "ticket", "expiresAt"])) return void 0;
164
+ if (typeof value.ticket !== "string" || value.ticket.length === 0 || value.ticket.length > MAX_TICKET_LENGTH) {
165
+ return void 0;
166
+ }
167
+ if (typeof value.expiresAt !== "string" || value.expiresAt.length > MAX_EXPIRES_AT_LENGTH || !ISO_INSTANT_RE.test(value.expiresAt)) {
168
+ return void 0;
169
+ }
170
+ return value;
171
+ case "guest":
172
+ case "cancel":
173
+ if (!hasExactKeys(value, BASE_KEYS)) return void 0;
174
+ return value;
175
+ case "foreground":
176
+ if (!hasExactKeys(value, [...BASE_KEYS, "active"])) return void 0;
177
+ if (typeof value.active !== "boolean") return void 0;
178
+ return value;
179
+ case "network":
180
+ if (!hasExactKeys(value, [...BASE_KEYS, "online"])) return void 0;
181
+ if (typeof value.online !== "boolean") return void 0;
182
+ return value;
183
+ default:
184
+ return void 0;
185
+ }
186
+ }
187
+
188
+ // ../embed-protocol/src/transport.ts
189
+ function read(raw, parse) {
190
+ if (typeof raw !== "string" || raw.length === 0) return { ok: false, reason: "undecodable" };
191
+ if (utf8ByteLength(raw) > MAX_BRIDGE_MESSAGE_BYTES) return { ok: false, reason: "too-large" };
192
+ const decoded = decodeBase64UrlJson(raw);
193
+ if (decoded === void 0) return { ok: false, reason: "undecodable" };
194
+ const message = parse(decoded);
195
+ if (message === void 0) return { ok: false, reason: "invalid" };
196
+ return { ok: true, message };
197
+ }
198
+ function readBridgeNativeCommand(raw) {
199
+ return read(raw, parseBridgeNativeCommand);
200
+ }
201
+ function encodeBridgeMessage(message) {
202
+ const encoded = encodeBase64UrlJson(message);
203
+ if (encoded.length > MAX_BRIDGE_MESSAGE_BYTES) return void 0;
204
+ return encoded;
205
+ }
206
+
207
+ // ../embed-protocol/src/entry-url.ts
208
+ function readParam(search, key) {
209
+ const query = search.charAt(0) === "?" ? search.slice(1) : search;
210
+ if (query.length === 0) return null;
211
+ for (const pair of query.split("&")) {
212
+ const eq = pair.indexOf("=");
213
+ if (eq === -1) continue;
214
+ if (pair.slice(0, eq) === key) return pair.slice(eq + 1);
215
+ }
216
+ return null;
217
+ }
218
+ function hasNativeMarker(search) {
219
+ return readParam(search, NATIVE_MARKER_PARAM) === NATIVE_MARKER_VALUE;
220
+ }
221
+ function readNativeEntry(search) {
222
+ if (!hasNativeMarker(search)) return null;
223
+ const attemptId = readParam(search, NATIVE_ATTEMPT_PARAM);
224
+ const nonce = readParam(search, NATIVE_NONCE_PARAM);
225
+ const protocol = Number(readParam(search, NATIVE_PROTOCOL_PARAM));
226
+ if (!isBridgeId(attemptId) || !isBridgeId(nonce)) return null;
227
+ if (protocol !== NATIVE_PROTOCOL_VERSION) return null;
228
+ return { attemptId, nonce, protocol };
229
+ }
230
+
231
+ // ../embed-protocol/src/runtime-generation.ts
232
+ function isGenerationId(value) {
233
+ return typeof value === "string" && /^[A-Za-z0-9_-]{1,128}$/.test(value);
234
+ }
235
+ function isGenerationTerminal(status) {
236
+ return ["succeeded", "failed", "unknown", "canceled", "expired"].includes(status);
237
+ }
238
+
239
+ export {
240
+ NATIVE_CHANNEL,
241
+ NATIVE_PROTOCOL_VERSION,
242
+ NATIVE_RECEIVER,
243
+ readBridgeNativeCommand,
244
+ encodeBridgeMessage,
245
+ hasNativeMarker,
246
+ readNativeEntry,
247
+ isGenerationId,
248
+ isGenerationTerminal
249
+ };