@civitai/app-sdk 0.66.0 → 0.67.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 CHANGED
@@ -140,7 +140,7 @@ interface BlockInitPayload {
140
140
  | `isModelSlotContext(ctx)` / `isPageSlotContext(ctx)` | Runtime narrowing for the `slotId`-discriminated `BlockContext` union. Real checks on a value that crossed a `postMessage` boundary — they verify every field they assert, not just `slotId`. |
141
141
  | `fetchNestedDocument(opts)` | Embed one of your OWN bundled documents as an iframe `srcdoc`, because a nested `<iframe src>` of your own content **cannot load** — see [Nested documents](#nested-documents) below. `NestedDocumentError` carries a `.code` of `'invalid-url' \| 'http-error' \| 'network-error'`. |
142
142
  | `isSignedIn(viewer)` | **The sign-in gate.** `isSignedIn(useBlockContext().viewer)` — do not open-code it as `viewer !== null` or `viewer?.signedIn === true`. Which of those is correct has already changed once with the host contract, and this is the one place it is decided. It reads neither `viewer.id` nor `viewer.username` (both `@deprecated`), so nothing written through it changes when those are removed. Need the identity rather than the presence? `useViewer()` — scope-gated and audited per call. |
143
- | types | `BlockManifestV1`, `ManifestSettings` (+ field types), `BlockContext`, `ModelSlotContext`, `BlockCheckpointInfo`, `ShowcaseImage`, `BlockToken`, `WrappedToken`, `BlockSettings`, `ViewerInfo`, `Theme`, `WorkflowBody`, `BlockTextToImageParams`, `WorkflowBodyCustomComfy` (+ its two arms `WorkflowBodyCustomComfyRecipe` / `WorkflowBodyCustomComfyInline`, and `InlineComfyNode`), `WorkflowBodyStep` / `WorkflowBodyPassThroughStep` (the two arms of `kind: 'step'`), `WorkflowBodyTraining` (+ `AiToolkitTrainingParams`, `BlockTrainingQuote`, `BlockTrainingDatasetItem`, `BlockTrainingDatasetResult` and the `BLOCK_TRAINING_*` bounds), `WorkflowBodyWorkflow` (+ `WorkflowBodyWorkflowStep`, `WorkflowStepRef`, `WithWorkflowStepRefs`, `BlockWorkflowStepSnapshot` and `BLOCK_WORKFLOW_MAX_STEPS`), `BlockWorkflowSnapshot`, `WorkflowStatus`, `BlockInitPayload`, `ParentToBlockMessage`, `BlockToParentMessage`. |
143
+ | types | `BlockManifestV1`, `ManifestSettings` (+ field types), `BlockContext`, `ModelSlotContext`, `BlockCheckpointInfo`, `ShowcaseImage`, `BlockToken`, `WrappedToken`, `BlockSettings`, `ViewerInfo`, `Theme`, `WorkflowBody`, `BlockTextToImageParams`, `WorkflowBodyCustomComfy` (+ its two arms `WorkflowBodyCustomComfyRecipe` / `WorkflowBodyCustomComfyInline`, and `InlineComfyNode`), `WorkflowBodyStep` / `WorkflowBodyPassThroughStep` (the two arms of `kind: 'step'`), `WorkflowBodyTraining` (+ `AiToolkitTrainingParams`, `BlockTrainingQuote`, `BlockTrainingDatasetItem`, `BlockTrainingDatasetResult` and the `BLOCK_TRAINING_*` bounds), `WorkflowBodyWorkflow` (+ `WorkflowBodyWorkflowStep`, `WorkflowStepRef`, `WithWorkflowStepRefs`, `BlockWorkflowStepSnapshot` and `BLOCK_WORKFLOW_MAX_STEPS`), `BlockStepQuote` / `BlockRunConfirmedStepHostError` (+ `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB`, `BLOCK_STEP_QUOTE_ID_REGEX`), `BlockModel3dOutput`, `BlockWorkflowSnapshot`, `WorkflowStatus`, `BlockInitPayload`, `ParentToBlockMessage`, `BlockToParentMessage`. |
144
144
 
145
145
  `WorkflowBody`'s `customComfy` member is a discriminated union on `mode`, mirroring
146
146
  the host's `blockCustomComfyMemberSchema`. Narrow on the VALUE (`body.mode === 'inline'`),
@@ -209,6 +209,46 @@ const body: WorkflowBody = {
209
209
  };
210
210
  ```
211
211
 
212
+ **Above the per-call budget: a confirmed step.** A pass-through job the per-call budget refuses
213
+ can still run when the **viewer confirms its exact price** in Civitai's own dialog
214
+ (civitai/civitai#5786). When the orchestrator quotes a **fixed** price (positive, not variable
215
+ or metered) above the token's per-call budget and at most `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB`
216
+ (5,000), the `estimate` reply carries `snapshot.stepQuote` (`quoteId`, `total`, `stepType`,
217
+ `expiresAt`). Send the same body back with that `quoteId` on the `RUN_CONFIRMED_STEP` bridge
218
+ (`CONFIRMED_STEP_RESULT`); a `quoteId` body sent as an ordinary submit is refused. A quote is
219
+ single-use and expires after 15 minutes. Only a **single** estimate carries `stepQuote` — a
220
+ batch estimate stores nothing, so its cells never do. Where a quote would be granted, the
221
+ estimate **fails instead** (`{ workflowId: 'failed', status: 'failed', error }`) for a training
222
+ `$type` in any casing (`a training step type cannot run as a confirmed step — use kind:'training'`) and for an
223
+ `input` that is not plain JSON (`a confirmed step needs plain JSON input (objects, arrays,
224
+ strings, finite numbers, booleans, null) — <path> is <what>`; the suffix varies, so match on
225
+ the text before the dash). Each confirmation is its own job, so a
226
+ `submission-unconfirmed` result — the outcome is unknown — must never be retried automatically:
227
+ a retry is a new, separately charged job. Page apps only, `ai:write:budgeted`, behind the host
228
+ flag `app-blocks-confirmed-step` (off by default), and refused for development, dev-tunnel and
229
+ review tokens — so it is exercised against the mock host, not `dev:live`. Use it through
230
+ `@civitai/blocks-react`'s `useRunConfirmedStep()`.
231
+
232
+ **3D outputs.** A step that produces 3D models (`polyGen`) still lists every model url in
233
+ `snapshot.imageUrls`; `snapshot.model3dOutputs` (and the same field on an `AppWorkflow` row)
234
+ labels them. Omitted when there are none.
235
+
236
+ ```ts
237
+ import type { BlockModel3dOutput, BlockWorkflowSnapshot } from '@civitai/app-sdk/blocks';
238
+
239
+ // `format` is the model's file format; `variant` is the output it came from.
240
+ function pickModel(
241
+ snapshot: BlockWorkflowSnapshot,
242
+ format: string,
243
+ variant: string,
244
+ ): BlockModel3dOutput | undefined {
245
+ return snapshot.model3dOutputs?.find((m) => m.format === format && m.variant === variant);
246
+ }
247
+
248
+ const mesh = pickModel(snapshot, 'glb', 'model');
249
+ const rigged = pickModel(snapshot, 'fbx', 'riggedFbxModel');
250
+ ```
251
+
212
252
  `WorkflowBodyTraining` (`kind: 'training'`) is the **quoted** way to train: an ai-toolkit LoRA
213
253
  run on a dataset Civitai prepared from the viewer's own images, mirroring the host's
214
254
  `blockTrainingBodySchema` (civitai/civitai#5434). There is no `maxBuzz` and no timeout — the
@@ -259,10 +299,15 @@ const body: WorkflowBody = {
259
299
 
260
300
  - 🔴 **The estimate is not a maximum.** The orchestrator re-prices a step once the values it
261
301
  references are known and charges the difference.
262
- - **A workflow the host cannot price still proceeds**, reserved at the ceiling above — unless it
263
- contains a training step (refused without a quote, as on the single-step arm) or a `repeat`
264
- step at any depth (also refused without a quote, before any reservation, as
265
- `workflow_repeat_unbounded`).
302
+ - **A workflow the host cannot price has no estimate, but its submit still proceeds.** The
303
+ estimate (and a batch-estimate cell) fails with no `cost` and `snapshot.errorCode`
304
+ `workflow_quote_rejected` (the orchestrator refused the body; the same body will fail again),
305
+ `workflow_quote_unavailable` (no usable answer; the one a retry can clear) or
306
+ `workflow_quote_unpriced` (an answer without a price). The submit of the same body is reserved
307
+ at the ceiling above — unless it contains a training step (refused without a quote on both, as
308
+ on the single-step arm) or a `repeat` step at any depth (also refused on both, before any
309
+ reservation, as `workflow_repeat_unbounded`; its `error` states the cause). An older host
310
+ returns the ceiling as the estimate's `cost.total` instead of failing.
266
311
  - **One author fee per workflow**, on the quoted base, itemised as `cost.authorFee` on the
267
312
  estimate. If the workflow bills more, the fee charged is held to the fee shown; a workflow that
268
313
  does not succeed has its whole fee reversed, even if earlier steps delivered output.
@@ -277,8 +322,8 @@ const body: WorkflowBody = {
277
322
  - A refused body carries `snapshot.errorCode`: `workflow_step_denied` (a platform-internal
278
323
  `$type`), `workflow_ref_invalid` (a reference to the step itself, a later step, or nothing, or a
279
324
  step name that breaks the naming rules, or a malformed `repeat`/`merge`),
280
- `workflow_repeat_unbounded` (a `repeat` not over an array in `arguments`, or over 16 iterations)
281
- or `workflow_too_many_steps`. The single-step pass-through arm refuses `repeat` outright.
325
+ `workflow_repeat_unbounded` (a `repeat` not over an array in `arguments`, or over 16 iterations,
326
+ or a `repeat` with no price quote) or `workflow_too_many_steps`. The single-step pass-through arm refuses `repeat` outright.
282
327
  - Requires a host that knows the kind; an older host refuses the body.
283
328
 
284
329
  ### `defineBlock` validator rules
@@ -109,9 +109,14 @@ export { isModelSlotContext, isPageSlotContext } from './types.js';
109
109
  * in `types.ts` for which of the two it uses and why.
110
110
  */
111
111
  export { isSignedIn } from './types.js';
112
- export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockAnalyticsDeclaration, BlockAnalyticsEventDeclaration, BlockAnalyticsPropertyDeclaration, BlockManifest, BlockManifestGood, BlockManifestV1, BlockSettings, BlockToken, ContentRating, ManifestBooleanField, ManifestIframe, ManifestNumberField, ManifestPage, ManifestPreview, ManifestSettingField, ManifestSettings, ManifestStringField, ManifestTarget, ModelSlotContext, SettingScope, SettingWidget, Theme, ViewerInfo, BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockSourceImage, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockTextToImageParams, BlockWorkflowSnapshot, BlockEstimateBatchAggregate, BlockModelSubstitution, ModelSubstitutionReason, KnownBlockWorkflowErrorCode, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, WorkflowBodyWorkflow, WorkflowBodyWorkflowStep, WorkflowStepRef, WithWorkflowStepRefs, BlockWorkflowStepSnapshot, WorkflowStatus, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, WorkflowBodyTraining, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AiToolkitPlainEcosystem, AiToolkitLrScheduler, AiToolkitOptimizerType, BlockTrainingQuote, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, BlockPrepareTrainingDatasetHostError, BlockRunTrainingHostError, } from './types.js';
112
+ export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, BlockAnalyticsDeclaration, BlockAnalyticsEventDeclaration, BlockAnalyticsPropertyDeclaration, BlockManifest, BlockManifestGood, BlockManifestV1, BlockSettings, BlockToken, ContentRating, ManifestBooleanField, ManifestIframe, ManifestNumberField, ManifestPage, ManifestPreview, ManifestSettingField, ManifestSettings, ManifestStringField, ManifestTarget, ModelSlotContext, SettingScope, SettingWidget, Theme, ViewerInfo, BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockSourceImage, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockTextToImageParams, BlockWorkflowSnapshot, BlockEstimateBatchAggregate, BlockModelSubstitution, ModelSubstitutionReason, KnownBlockWorkflowErrorCode, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, WorkflowBodyWorkflow, WorkflowBodyWorkflowStep, WorkflowStepRef, WithWorkflowStepRefs, BlockWorkflowStepSnapshot, WorkflowStatus, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, WorkflowBodyTraining, AiToolkitTrainingParams, AiToolkitTrainingParamsBase, AiToolkitPlainEcosystem, AiToolkitLrScheduler, AiToolkitOptimizerType, BlockTrainingQuote, BlockTrainingDatasetItem, BlockTrainingDatasetResult, BlockTrainingRejectionReason, BlockPrepareTrainingDatasetHostError, BlockRunTrainingHostError, BlockStepQuote, BlockModel3dOutput, BlockRunConfirmedStepHostError, } from './types.js';
113
113
  /** Host bound for the App Blocks `kind: 'workflow'` body — see `WorkflowBodyWorkflow`. */
114
114
  export { BLOCK_WORKFLOW_MAX_STEPS, BLOCK_WORKFLOW_REPEAT_MAX_ITERATIONS } from './types.js';
115
+ /**
116
+ * Host bounds for a viewer-confirmed pass-through step, mirrored from
117
+ * civitai/civitai — see `BlockWorkflowSnapshot.stepQuote`.
118
+ */
119
+ export { BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB, BLOCK_STEP_QUOTE_ID_REGEX } from './types.js';
115
120
  /**
116
121
  * Host bounds for the App Blocks `kind: 'training'` flow, mirrored from
117
122
  * civitai/civitai's zod — see `WorkflowBodyTraining`.
@@ -110,6 +110,11 @@ export { isModelSlotContext, isPageSlotContext } from './types.js';
110
110
  export { isSignedIn } from './types.js';
111
111
  /** Host bound for the App Blocks `kind: 'workflow'` body — see `WorkflowBodyWorkflow`. */
112
112
  export { BLOCK_WORKFLOW_MAX_STEPS, BLOCK_WORKFLOW_REPEAT_MAX_ITERATIONS } from './types.js';
113
+ /**
114
+ * Host bounds for a viewer-confirmed pass-through step, mirrored from
115
+ * civitai/civitai — see `BlockWorkflowSnapshot.stepQuote`.
116
+ */
117
+ export { BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB, BLOCK_STEP_QUOTE_ID_REGEX } from './types.js';
113
118
  /**
114
119
  * Host bounds for the App Blocks `kind: 'training'` flow, mirrored from
115
120
  * civitai/civitai's zod — see `WorkflowBodyTraining`.
@@ -8,7 +8,7 @@
8
8
  * Wire format: `window.postMessage({ type, payload }, targetOrigin)`.
9
9
  */
10
10
  import type { ColorDomain } from './browsingLevel.js';
11
- import type { BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockContext, BlockSettings, Theme, ViewerInfo, WorkflowBody, BlockWorkflowSnapshot, BlockEstimateBatchAggregate, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostResult, BlockTrainingDatasetItem, BlockTrainingDatasetResult, WorkflowBodyTraining } from './types.js';
11
+ import type { BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockContext, BlockSettings, Theme, ViewerInfo, WorkflowBody, BlockWorkflowSnapshot, BlockEstimateBatchAggregate, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostResult, BlockTrainingDatasetItem, BlockTrainingDatasetResult, WorkflowBodyTraining, WorkflowBodyPassThroughStep } from './types.js';
12
12
  /**
13
13
  * Filter params for `GET_BUZZ_TRANSACTIONS`. All optional; the host validates
14
14
  * them server-side (they are NEVER trusted for auth — the account is self-bound
@@ -508,6 +508,13 @@ export type ParentToBlockMessage = {
508
508
  snapshot?: BlockWorkflowSnapshot;
509
509
  error?: string;
510
510
  };
511
+ } | {
512
+ type: 'CONFIRMED_STEP_RESULT';
513
+ payload: {
514
+ requestId: string;
515
+ snapshot?: BlockWorkflowSnapshot;
516
+ error?: string;
517
+ };
511
518
  } | {
512
519
  type: 'IMAGES_RESULT';
513
520
  payload: {
@@ -846,6 +853,12 @@ export type BlockToParentMessage = {
846
853
  requestId: string;
847
854
  body: WorkflowBodyTraining;
848
855
  };
856
+ } | {
857
+ type: 'RUN_CONFIRMED_STEP';
858
+ payload: {
859
+ requestId: string;
860
+ body: WorkflowBodyPassThroughStep;
861
+ };
849
862
  } | {
850
863
  type: 'GET_IMAGES_BY_IDS';
851
864
  payload: {
@@ -1093,7 +1106,7 @@ export type BlockToParentMessageType = BlockToParentMessage['type'];
1093
1106
  * it is a measurement, not an invariant, and civitai's own compile-time gate
1094
1107
  * (published SDK union ⊆ `keyof INVENTORY`) is what holds one direction of it.
1095
1108
  */
1096
- export declare const BLOCK_TO_PARENT_MESSAGE_TYPES: readonly ["BLOCK_HELLO", "BLOCK_READY", "BLOCK_ERROR", "BLOCK_MESSAGE_REJECTED", "REQUEST_TOKEN", "RESIZE_IFRAME", "SUBMIT_WORKFLOW", "ESTIMATE_WORKFLOW", "ESTIMATE_WORKFLOW_BATCH", "POLL_WORKFLOW", "CANCEL_WORKFLOW", "OPEN_BUZZ_PURCHASE", "GET_BUZZ_BALANCE", "GET_VIEWER", "GET_BUZZ_TRANSACTIONS", "GET_BUZZ_ACCOUNTS", "GET_DAILY_COMPENSATION", "GET_WILDCARD_PACK", "QUERY_APP_WORKFLOWS", "PUBLISH_GENERATION_OUTPUTS", "CREATE_POST_FROM_APP", "PREPARE_TRAINING_DATASET", "RUN_TRAINING", "GET_IMAGES_BY_IDS", "CANCEL_APP_WORKFLOW", "OPEN_CHECKPOINT_PICKER", "OPEN_RESOURCE_PICKER", "OPEN_IMAGE_UPLOAD", "SET_USER_CHECKPOINT", "NAVIGATE", "REQUEST_SIGN_IN", "REQUEST_CONSENT", "TRACK_EVENT", "APP_STORAGE_GET", "APP_STORAGE_SET", "APP_STORAGE_DELETE", "APP_STORAGE_LIST", "APP_STORAGE_QUOTA", "SHARED_LIST", "SHARED_GET_COUNT", "SHARED_GET_COUNTS", "SHARED_APPEND", "SHARED_VOTE", "SHARED_UNVOTE", "SHARED_WITHDRAW", "SHARED_UPDATE", "SHARED_GET", "SHARED_REPORT", "SAVE_IMAGE", "SET_COLLECTION_FOLLOW"];
1109
+ export declare const BLOCK_TO_PARENT_MESSAGE_TYPES: readonly ["BLOCK_HELLO", "BLOCK_READY", "BLOCK_ERROR", "BLOCK_MESSAGE_REJECTED", "REQUEST_TOKEN", "RESIZE_IFRAME", "SUBMIT_WORKFLOW", "ESTIMATE_WORKFLOW", "ESTIMATE_WORKFLOW_BATCH", "POLL_WORKFLOW", "CANCEL_WORKFLOW", "OPEN_BUZZ_PURCHASE", "GET_BUZZ_BALANCE", "GET_VIEWER", "GET_BUZZ_TRANSACTIONS", "GET_BUZZ_ACCOUNTS", "GET_DAILY_COMPENSATION", "GET_WILDCARD_PACK", "QUERY_APP_WORKFLOWS", "PUBLISH_GENERATION_OUTPUTS", "CREATE_POST_FROM_APP", "PREPARE_TRAINING_DATASET", "RUN_TRAINING", "RUN_CONFIRMED_STEP", "GET_IMAGES_BY_IDS", "CANCEL_APP_WORKFLOW", "OPEN_CHECKPOINT_PICKER", "OPEN_RESOURCE_PICKER", "OPEN_IMAGE_UPLOAD", "SET_USER_CHECKPOINT", "NAVIGATE", "REQUEST_SIGN_IN", "REQUEST_CONSENT", "TRACK_EVENT", "APP_STORAGE_GET", "APP_STORAGE_SET", "APP_STORAGE_DELETE", "APP_STORAGE_LIST", "APP_STORAGE_QUOTA", "SHARED_LIST", "SHARED_GET_COUNT", "SHARED_GET_COUNTS", "SHARED_APPEND", "SHARED_VOTE", "SHARED_UNVOTE", "SHARED_WITHDRAW", "SHARED_UPDATE", "SHARED_GET", "SHARED_REPORT", "SAVE_IMAGE", "SET_COLLECTION_FOLLOW"];
1097
1110
  /**
1098
1111
  * The bucket a message type falls into when the protocol does not declare it.
1099
1112
  *
@@ -72,6 +72,7 @@ export const BLOCK_TO_PARENT_MESSAGE_TYPES = [
72
72
  'CREATE_POST_FROM_APP',
73
73
  'PREPARE_TRAINING_DATASET',
74
74
  'RUN_TRAINING',
75
+ 'RUN_CONFIRMED_STEP',
75
76
  'GET_IMAGES_BY_IDS',
76
77
  'CANCEL_APP_WORKFLOW',
77
78
  'OPEN_CHECKPOINT_PICKER',
@@ -1251,7 +1251,39 @@ export type WorkflowBodyPassThroughStep = {
1251
1251
  * viewer as a maximum.
1252
1252
  */
1253
1253
  maxBuzz: number;
1254
+ /**
1255
+ * The viewer-confirmable quote this job is charged against —
1256
+ * {@link BlockStepQuote.quoteId} from this body's own estimate
1257
+ * (`sq_` + 32 lowercase hex characters, {@link BLOCK_STEP_QUOTE_ID_REGEX}).
1258
+ *
1259
+ * OMIT it everywhere except a CONFIRMED run: an estimate never carries it,
1260
+ * and neither does an ordinary submit. It is REQUIRED on
1261
+ * `RUN_CONFIRMED_STEP` (`useRunConfirmedStep()` in `@civitai/blocks-react`),
1262
+ * the only message that can run a job priced above the token's per-call
1263
+ * budget — see {@link BlockWorkflowSnapshot.stepQuote} for the flow.
1264
+ *
1265
+ * 🔴 A body carrying it is NOT runnable through `SUBMIT_WORKFLOW`. The server
1266
+ * charges a confirmed job only against a quote the viewer confirmed in
1267
+ * Civitai's own dialog from a signed-in session, so the same body sent as a
1268
+ * plain submit is refused. Change nothing else in the body between the
1269
+ * estimate and the run: the server checks it against the quoted one
1270
+ * (`quoteId` itself is excluded from that comparison).
1271
+ */
1272
+ quoteId?: string;
1254
1273
  };
1274
+ /**
1275
+ * Ceiling on what ONE viewer-confirmed pass-through job may cost, in Buzz. Host
1276
+ * constant `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB`. A fixed price above it gets
1277
+ * no {@link BlockWorkflowSnapshot.stepQuote} and cannot be run from an app.
1278
+ */
1279
+ export declare const BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB = 5000;
1280
+ /**
1281
+ * The shape of a confirmed-step quote id ({@link BlockStepQuote.quoteId}): `sq_`
1282
+ * followed by 32 lowercase hex characters. Host constant
1283
+ * `BLOCK_STEP_QUOTE_ID_REGEX`. Disjoint from the training quote's `tq_` shape,
1284
+ * so neither kind's id can name the other's quote.
1285
+ */
1286
+ export declare const BLOCK_STEP_QUOTE_ID_REGEX: RegExp;
1255
1287
  /**
1256
1288
  * The most steps one `kind: 'workflow'` body may carry. The host refuses a
1257
1289
  * longer list with `errorCode: 'workflow_too_many_steps'`; an empty list is
@@ -1386,12 +1418,21 @@ export type WorkflowBodyWorkflowStep = {
1386
1418
  * earlier step, the orchestrator re-prices that step once the referenced
1387
1419
  * values are known and charges the difference. Show the estimate as an
1388
1420
  * estimate; never as "you will pay at most".
1389
- * 2. **A workflow the host cannot price still proceeds.** It is reserved at
1390
- * the ceiling above instead of at a quote — unless it contains a training
1391
- * step, which is refused without a quote exactly as on the single-step arm
1392
- * (a `training-quote-*` `errorCode`), or a `repeat` step at any depth,
1393
- * which is refused without a quote too, before any reservation, with
1394
- * `errorCode: 'workflow_repeat_unbounded'` — retrying later may succeed.
1421
+ * 2. **A workflow the host cannot price has no estimate, but its submit still
1422
+ * proceeds.** `estimate()` (and that body's cell in a batch estimate) FAILS
1423
+ * with no `cost` and `errorCode` `'workflow_quote_rejected'`,
1424
+ * `'workflow_quote_unavailable'` or `'workflow_quote_unpriced'` — see
1425
+ * {@link BlockWorkflowSnapshot.errorCode} for which of them a retry can
1426
+ * clear. `submit()` of the same body is NOT refused: it is reserved at the
1427
+ * ceiling above instead of at a quote. So a failed estimate does not predict
1428
+ * a refused submit. Two exceptions are refused on BOTH, before any
1429
+ * reservation: a workflow containing a training step, exactly as on the
1430
+ * single-step arm (a `training-quote-*` `errorCode`), and one with a
1431
+ * `repeat` step at any depth (`errorCode: 'workflow_repeat_unbounded'`).
1432
+ * Their `error` states the cause; only "none could be obtained; try again"
1433
+ * is one a retry of the same body can clear.
1434
+ * (An OLDER host returns the ceiling as the estimate's `cost.total` instead
1435
+ * of failing, so there a total equal to the ceiling may be unpriced.)
1395
1436
  * 3. **One author fee per workflow**, not one per step, computed on the
1396
1437
  * QUOTED base. The estimate's `cost.authorFee` is that single fee, already
1397
1438
  * inside `cost.total`. If the workflow bills more than the quote, the fee
@@ -1700,15 +1741,21 @@ export interface BlockWorkflowSnapshot {
1700
1741
  *
1701
1742
  * WHERE IT APPEARS today: the refusal for a pass-through `step` body
1702
1743
  * ({@link WorkflowBodyPassThroughStep}) whose `$type` is a training type
1703
- * (`'training'` or `'imageResourceTraining'`) when the host could not get a
1744
+ * (`'training'` or `'imageResourceTraining'`, in any casing — the host
1745
+ * compares `$type` case-insensitively) when the host could not get a
1704
1746
  * price quote for it. That refusal is `{ workflowId: 'failed', status:
1705
1747
  * 'failed', error, errorCode }` with no `cost`, and it is the reply to
1706
1748
  * `estimate()` (which rejects with `WorkflowEstimateError`), to `submit()`
1707
1749
  * (which rejects with `WorkflowSubmitError`, before anything is reserved or
1708
1750
  * charged), and to that body's cell in a batch estimate. Read it from the
1709
- * error's `.snapshot.errorCode`. The `kind: 'training'` arm does NOT set it.
1751
+ * error's `.snapshot.errorCode`. A `kind: 'training'` ESTIMATE does not set
1752
+ * it. The confirmed SUBMIT of a training run or a confirmed step can — see
1753
+ * `'daily-limit-exceeded'` below.
1710
1754
  * A refused `kind: 'workflow'` body ({@link WorkflowBodyWorkflow}) sets it in
1711
- * the same shape, on the same three replies.
1755
+ * the same shape, on the same three replies — except the three
1756
+ * `workflow_quote_*` codes, which are ESTIMATE-ONLY (`estimate()` and a
1757
+ * batch-estimate cell): `submit()` of the same body defines no such refusal
1758
+ * and proceeds, reserved at the ceiling.
1712
1759
  *
1713
1760
  * The values the host sends today ({@link KnownBlockWorkflowErrorCode}):
1714
1761
  * - `'training-quote-rejected'` — the orchestrator refused the request
@@ -1717,6 +1764,15 @@ export interface BlockWorkflowSnapshot {
1717
1764
  * - `'training-quote-unavailable'` — no usable answer (an outage, a timeout,
1718
1765
  * a rate limit, insufficient funds, …). Retrying later may succeed.
1719
1766
  * - `'training-quote-unpriced'` — the orchestrator answered without a price.
1767
+ * - `'daily-limit-exceeded'` — a viewer-confirmed job (a `kind: 'training'`
1768
+ * run or a confirmed step) would exceed what remains today of the daily
1769
+ * Buzz limit THE VIEWER set for this app. Nothing was charged and no job
1770
+ * was started; the confirm cannot override it, and only the viewer can
1771
+ * raise the limit. This one arrives on the failed snapshot a confirmed
1772
+ * SUBMIT resolves (`{ workflowId: 'failed', status: 'failed', cost, error,
1773
+ * errorCode }`), not on an estimate. On a confirmed step the host also
1774
+ * reports it as the `error` code of `CONFIRMED_STEP_RESULT` — see
1775
+ * {@link BlockRunConfirmedStepHostError}.
1720
1776
  * - `'workflow_step_denied'` — a step of a `kind: 'workflow'` body names a
1721
1777
  * platform-internal `$type`. Remove or replace that step.
1722
1778
  * - `'workflow_ref_invalid'` — a {@link WorkflowStepRef} in a step's `input`
@@ -1728,10 +1784,30 @@ export interface BlockWorkflowSnapshot {
1728
1784
  * - `'workflow_repeat_unbounded'` — a `repeat` step iterates something other
1729
1785
  * than an array in the body's `arguments`, or more than
1730
1786
  * {@link BLOCK_WORKFLOW_REPEAT_MAX_ITERATIONS} times, nested repeats
1731
- * multiplied; or the workflow has a `repeat` and the host could get no
1732
- * price quote for it (`error` then reads `… a workflow with a repeat needs
1733
- * a price quote and none could be obtained; try again`, and a retry may
1734
- * succeed).
1787
+ * multiplied; or the workflow has a `repeat` and the host has no price
1788
+ * quote for it. `error` then reads `workflow step N: a workflow with a
1789
+ * repeat needs a price quote and <cause>`, where `<cause>` is one of the
1790
+ * three in the `workflow_quote_*` messages below. A retry of the same body
1791
+ * can succeed only when the cause is `none could be obtained; try again`;
1792
+ * otherwise the body is what has to change.
1793
+ * - `'workflow_quote_rejected'` — ESTIMATE only. The orchestrator refused
1794
+ * the body, so there is no price to show; resending the same body will not
1795
+ * help. `error` reads `No price quote for this workflow: the orchestrator
1796
+ * rejected the request: <reason>` when the reason is safe to show, else
1797
+ * `No price quote for this workflow: the orchestrator rejected the request;
1798
+ * check the step inputs and references (retrying the same body will not
1799
+ * help)`.
1800
+ * - `'workflow_quote_unavailable'` — ESTIMATE only. No usable answer (an
1801
+ * outage, a timeout, a rate limit, …). The one `workflow_quote_*` code a
1802
+ * retry of the same body can clear. `error` reads `No price quote for this
1803
+ * workflow: none could be obtained; try again`.
1804
+ * - `'workflow_quote_unpriced'` — ESTIMATE only. The orchestrator answered
1805
+ * without a price. `error` reads `No price quote for this workflow: the
1806
+ * orchestrator answered without one`.
1807
+ *
1808
+ * Each `workflow_quote_*` reply is `{ workflowId: 'failed', status: 'failed',
1809
+ * error, errorCode }` with NO `cost`. A host that predates these codes
1810
+ * resolves the estimate instead, with the spend ceiling as `cost.total`.
1735
1811
  *
1736
1812
  * 🔴 AN UNKNOWN VALUE IS POSSIBLE: the host may add codes, and the SDK's
1737
1813
  * inbound validator accepts any string here. Handle an unrecognised code as a
@@ -1939,6 +2015,84 @@ export interface BlockWorkflowSnapshot {
1939
2015
  * that predate civitai/civitai#5434.
1940
2016
  */
1941
2017
  trainingQuote?: BlockTrainingQuote;
2018
+ /**
2019
+ * The viewer-confirmable quote a pass-through `step` ESTIMATE
2020
+ * ({@link WorkflowBodyPassThroughStep}) produced, when the job costs more than
2021
+ * the app may spend without asking. Its presence means: this job cannot run
2022
+ * through `submit()`, and CAN run through `useRunConfirmedStep()` once the
2023
+ * viewer confirms `stepQuote.total` in Civitai's own dialog.
2024
+ *
2025
+ * THE FLOW, mirroring a training run's:
2026
+ * 1. `useBuzzWorkflow().estimate(body)` — the reply carries `stepQuote`.
2027
+ * 2. `useRunConfirmedStep().runConfirmedStep({ ...body, quoteId:
2028
+ * stepQuote.quoteId })` — Civitai shows the viewer the price (read back
2029
+ * from the server, never from the body) and submits on their confirm.
2030
+ * 3. `useBuzzWorkflow().watch(snapshot.workflowId)`.
2031
+ *
2032
+ * WHEN THE HOST SENDS IT — all of these, decided server-side:
2033
+ * - the orchestrator quoted a **FIXED** price for exactly this step:
2034
+ * positive, and not a variable or metered one. The viewer confirms what
2035
+ * they are charged, so a price that may move is never confirmable;
2036
+ * - that price is ABOVE the token's per-call budget and at most
2037
+ * {@link BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB} (5,000 Buzz);
2038
+ * - the block runs in a page app for a signed-in viewer on an ordinary
2039
+ * token. Development, dev-tunnel and review sessions never get one, and
2040
+ * neither does the model slot.
2041
+ * Everywhere else the field is ABSENT and the estimate is exactly what it was.
2042
+ * Absent is therefore not an error — it is also what every host that
2043
+ * predates civitai/civitai#5786 sends.
2044
+ *
2045
+ * 🔴 ONLY A SINGLE ESTIMATE CARRIES IT. A batch estimate
2046
+ * (`ESTIMATE_WORKFLOW_BATCH`, `useBatchEstimate()`) stores nothing, so its
2047
+ * cells never have a `stepQuote`, whatever they cost. Price a grid in a batch,
2048
+ * then estimate the one body you want to run on its own.
2049
+ *
2050
+ * 🔴 TWO BODIES MAKE THAT ESTIMATE FAIL INSTEAD OF RETURNING A PRICE — only
2051
+ * where a quote would otherwise have been granted (so not below the budget,
2052
+ * and not in a batch). The reply is the failure snapshot `{ workflowId:
2053
+ * 'failed', status: 'failed', error }`, which `estimate()` rejects with; the
2054
+ * same text arrives as `CONFIRMED_STEP_RESULT`'s `error` if such a body is
2055
+ * run. Neither has an {@link BlockWorkflowSnapshot.errorCode}; they are
2056
+ * server messages, so match on the text and keep a generic branch:
2057
+ * - `a training step type cannot run as a confirmed step — use
2058
+ * kind:'training'` — `$type` is `training` or `imageResourceTraining`, in
2059
+ * any casing.
2060
+ * Training has its own kind ({@link WorkflowBodyTraining}).
2061
+ * - `a confirmed step needs plain JSON input (objects, arrays, strings,
2062
+ * finite numbers, booleans, null) — <path> is <what>` — `input` holds a
2063
+ * `Date`, `Map`, `Set`, class instance, `BigInt`, `NaN`/`Infinity`, an
2064
+ * `undefined` array element or an empty slot in a sparse array
2065
+ * (`… — body.input.list[1] is an empty array slot`). The quote is bound to the exact body, so the
2066
+ * body must be exactly representable. The part after the dash names the
2067
+ * offending path (`body.input.when is a Date`) and varies: match on the
2068
+ * text BEFORE the dash.
2069
+ *
2070
+ * `total` is the price the viewer confirms and is charged. 🔴 It can differ
2071
+ * from `cost.total` on the same snapshot, which stays what an UNCONFIRMED
2072
+ * submit would reserve (`max(maxBuzz, quote)`); show `stepQuote.total` on a
2073
+ * confirm button. `expiresAt` is an ISO-8601 timestamp 15 minutes out; after
2074
+ * it the run is refused and you estimate again. A quote is single-use.
2075
+ */
2076
+ stepQuote?: BlockStepQuote;
2077
+ /**
2078
+ * What each 3D model in {@link BlockWorkflowSnapshot.imageUrls} IS — one label
2079
+ * per model a pass-through step produced (e.g. `polyGen`).
2080
+ *
2081
+ * Every model url is still listed in `imageUrls`, unchanged and in the same
2082
+ * order as before the field existed; this only says which entry is which.
2083
+ * Match on `url`:
2084
+ *
2085
+ * ```ts
2086
+ * const glb = snapshot.model3dOutputs?.find(
2087
+ * (m) => m.format === 'glb' && m.variant === 'model',
2088
+ * );
2089
+ * if (glb) viewer.load(glb.url);
2090
+ * ```
2091
+ *
2092
+ * OMITTED when the workflow produced no 3D model, and on hosts that predate
2093
+ * civitai/civitai#5786 — in which case `imageUrls` is all there is.
2094
+ */
2095
+ model3dOutputs?: BlockModel3dOutput[];
1942
2096
  /**
1943
2097
  * The steps of a `kind: 'workflow'` run ({@link WorkflowBodyWorkflow}), in
1944
2098
  * the orchestrator's order, each with its own status and results. The steps a
@@ -2014,6 +2168,42 @@ export interface BlockTrainingQuote {
2014
2168
  /** ISO-8601. The quote cannot be run after this. */
2015
2169
  expiresAt: string;
2016
2170
  }
2171
+ /** {@link BlockWorkflowSnapshot.stepQuote}. */
2172
+ export interface BlockStepQuote {
2173
+ /**
2174
+ * `sq_` + 32 lowercase hex characters ({@link BLOCK_STEP_QUOTE_ID_REGEX}).
2175
+ * Opaque — pass it back verbatim as the body's `quoteId`.
2176
+ */
2177
+ quoteId: string;
2178
+ /** The exact price the viewer confirms and is charged, in Buzz. */
2179
+ total: number;
2180
+ /** The orchestrator `$type` that was priced — the body's own `$type`. */
2181
+ stepType: string;
2182
+ /** ISO-8601. The quote cannot be run after this. */
2183
+ expiresAt: string;
2184
+ }
2185
+ /**
2186
+ * One entry of {@link BlockWorkflowSnapshot.model3dOutputs} /
2187
+ * {@link AppWorkflow.model3dOutputs} — the label for one 3D model url.
2188
+ */
2189
+ export interface BlockModel3dOutput {
2190
+ /** The model's url. Also present in the snapshot's `imageUrls` (an `AppWorkflow`'s `images`). */
2191
+ url: string;
2192
+ /**
2193
+ * The model's own file format as the orchestrator reports it — `'glb'`,
2194
+ * `'fbx'`, …. An open set: compare against the formats your viewer loads and
2195
+ * ignore the rest.
2196
+ */
2197
+ format: string;
2198
+ /**
2199
+ * Which output of the step this model is: the output field it came from,
2200
+ * dot-joined when nested — `'model'`, `'riggedFbxModel'`,
2201
+ * `'basicAnimations.walkingModel'`, …. An array index is a segment
2202
+ * (`'models.0'`), and it is `''` when the step's whole output is the model.
2203
+ * The names are the orchestrator's, per `$type`; nothing here lists them.
2204
+ */
2205
+ variant: string;
2206
+ }
2017
2207
  /**
2018
2208
  * Why the host substituted a checkpoint version — see
2019
2209
  * {@link BlockWorkflowSnapshot.modelSubstitutions}.
@@ -2040,7 +2230,7 @@ export type ModelSubstitutionReason = 'wrong-workflow' | 'unrecognized' | 'gated
2040
2230
  * and the SDK's inbound validator accepts any string. Handle an unrecognised
2041
2231
  * code as a generic failure.
2042
2232
  */
2043
- export type KnownBlockWorkflowErrorCode = 'training-quote-rejected' | 'training-quote-unavailable' | 'training-quote-unpriced' | 'workflow_step_denied' | 'workflow_ref_invalid' | 'workflow_too_many_steps' | 'workflow_repeat_unbounded';
2233
+ export type KnownBlockWorkflowErrorCode = 'training-quote-rejected' | 'training-quote-unavailable' | 'training-quote-unpriced' | 'daily-limit-exceeded' | 'workflow_step_denied' | 'workflow_ref_invalid' | 'workflow_too_many_steps' | 'workflow_repeat_unbounded' | 'workflow_quote_rejected' | 'workflow_quote_unavailable' | 'workflow_quote_unpriced';
2044
2234
  /** One entry of {@link BlockWorkflowSnapshot.modelSubstitutions}. */
2045
2235
  export interface BlockModelSubstitution {
2046
2236
  /** The checkpoint version id the block asked for. */
@@ -2696,6 +2886,13 @@ export interface AppWorkflow {
2696
2886
  * absence as "unknown", not as "no checkpoint".
2697
2887
  */
2698
2888
  trainedEpochs?: BlockTrainedEpoch[];
2889
+ /**
2890
+ * Labels for the 3D models among this workflow's {@link AppWorkflow.images} —
2891
+ * same shape and rule as {@link BlockWorkflowSnapshot.model3dOutputs}: every
2892
+ * labelled `url` is also an entry of `images`, and the field is OMITTED when
2893
+ * there are none (and on hosts that predate it).
2894
+ */
2895
+ model3dOutputs?: BlockModel3dOutput[];
2699
2896
  }
2700
2897
  /**
2701
2898
  * Per-viewer gated projection of ONE image returned by `GET_IMAGES_BY_IDS`
@@ -3013,5 +3210,37 @@ export type BlockPrepareTrainingDatasetHostError = 'review-mode' | 'block is not
3013
3210
  * run is a SECOND, separately charged run.
3014
3211
  */
3015
3212
  export type BlockRunTrainingHostError = 'review-mode' | 'block is not ready' | 'sign in to train' | 'invalid training request' | 'no block token' | 'declined' | 'submission-unconfirmed';
3213
+ /**
3214
+ * The HOST'S OWN refusal codes on a `CONFIRMED_STEP_RESULT`'s `error`. Mirrors
3215
+ * civitai/civitai's `RUN_CONFIRMED_STEP_HOST_ERRORS`
3216
+ * (`src/components/AppBlocks/runConfirmedStepGate.ts`, civitai/civitai#5786).
3217
+ *
3218
+ * 🔴 THE SET A BLOCK MAY RELY ON, NOT THE SET IT MAY RECEIVE: server messages
3219
+ * (an expired or already-used quote, a body that differs from the quoted one, a
3220
+ * price that changed, `confirmed steps from apps are not enabled`) arrive
3221
+ * verbatim in the same field.
3222
+ *
3223
+ * - `review-mode` — a mod-review sandbox with "run for real" off.
3224
+ * - `block is not ready` — the host handshake has not completed.
3225
+ * - `sign in to run` — anonymous viewer; there is no account to charge.
3226
+ * - `invalid confirmed step request` — the body is not a pass-through
3227
+ * `kind: 'step'` body naming an `sq_`-shaped `quoteId`.
3228
+ * - `no block token` — the host holds no block token yet.
3229
+ * - `declined` — the viewer dismissed the confirm. 🔴 GUARANTEED to mean NO JOB
3230
+ * WAS SUBMITTED. The quote is untouched and can still be run before it
3231
+ * expires.
3232
+ * - `daily-limit-exceeded` — the job would exceed what remains today of the
3233
+ * daily Buzz limit the VIEWER set for this app. NO job was submitted and
3234
+ * nothing was charged. The host has already told the viewer, in its own
3235
+ * chrome, and pointed them at where to raise the limit; there is no confirm
3236
+ * to click and nothing the app can do about it. Do not retry.
3237
+ * - `submission-unconfirmed` — the submit was SENT but its outcome is unknown.
3238
+ * 🔴 The job MAY BE RUNNING AND CHARGED. Every confirmation is its own job,
3239
+ * so a retry (which needs a new estimate and a new confirmation) starts a
3240
+ * SECOND, separately charged job if the first one did start. Check the
3241
+ * viewer's generations (`useAppWorkflows()`) before offering one, and never
3242
+ * retry automatically.
3243
+ */
3244
+ export type BlockRunConfirmedStepHostError = 'review-mode' | 'block is not ready' | 'sign in to run' | 'invalid confirmed step request' | 'no block token' | 'declined' | 'daily-limit-exceeded' | 'submission-unconfirmed';
3016
3245
  export {};
3017
3246
  //# sourceMappingURL=types.d.ts.map
@@ -115,6 +115,22 @@ export function isSignedIn(viewer) {
115
115
  return viewer !== null && viewer !== undefined;
116
116
  }
117
117
  // ============================================================
118
+ // Confirmed steps (a pass-through job above the per-call budget)
119
+ // ============================================================
120
+ /**
121
+ * Ceiling on what ONE viewer-confirmed pass-through job may cost, in Buzz. Host
122
+ * constant `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB`. A fixed price above it gets
123
+ * no {@link BlockWorkflowSnapshot.stepQuote} and cannot be run from an app.
124
+ */
125
+ export const BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB = 5000;
126
+ /**
127
+ * The shape of a confirmed-step quote id ({@link BlockStepQuote.quoteId}): `sq_`
128
+ * followed by 32 lowercase hex characters. Host constant
129
+ * `BLOCK_STEP_QUOTE_ID_REGEX`. Disjoint from the training quote's `tq_` shape,
130
+ * so neither kind's id can name the other's quote.
131
+ */
132
+ export const BLOCK_STEP_QUOTE_ID_REGEX = /^sq_[a-f0-9]{32}$/;
133
+ // ============================================================
118
134
  // App Blocks MULTI-STEP kind (`kind: 'workflow'`)
119
135
  // ============================================================
120
136
  /**
package/dist/index.d.ts CHANGED
@@ -25,11 +25,11 @@
25
25
  *
26
26
  * - **`./blocks`** — importing it RUNS `./safe-storage` for its side effect
27
27
  * (repairing `localStorage`/`sessionStorage` at an opaque origin). Folding
28
- * 167 symbols and that side effect into the root would make every OAuth app
28
+ * 172 symbols and that side effect into the root would make every OAuth app
29
29
  * that writes `import { exchangeCode } from '@civitai/app-sdk'` pay for the
30
30
  * Civitai-Apps contract it does not use. Blocks and OAuth apps are disjoint
31
31
  * audiences; see this package's README.
32
- * 🔴 That 167 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 172 is ASSERTED, not merely quoted — by
33
33
  * `packages/civitai-app-sdk/test/export-surface.test.ts`, which pins
34
34
  * `surface.get('./blocks').size`. It said 119 here against a real 125 until
35
35
  * 2026-10-04, because the existing checks pin the root as an exact union and
package/dist/index.js CHANGED
@@ -25,11 +25,11 @@
25
25
  *
26
26
  * - **`./blocks`** — importing it RUNS `./safe-storage` for its side effect
27
27
  * (repairing `localStorage`/`sessionStorage` at an opaque origin). Folding
28
- * 167 symbols and that side effect into the root would make every OAuth app
28
+ * 172 symbols and that side effect into the root would make every OAuth app
29
29
  * that writes `import { exchangeCode } from '@civitai/app-sdk'` pay for the
30
30
  * Civitai-Apps contract it does not use. Blocks and OAuth apps are disjoint
31
31
  * audiences; see this package's README.
32
- * 🔴 That 167 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 172 is ASSERTED, not merely quoted — by
33
33
  * `packages/civitai-app-sdk/test/export-surface.test.ts`, which pins
34
34
  * `surface.get('./blocks').size`. It said 119 here against a real 125 until
35
35
  * 2026-10-04, because the existing checks pin the root as an exact union and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civitai/app-sdk",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "OAuth + PKCE, encrypted-cookie sessions, scopes, and orchestrator helpers for building third-party Civitai apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",