@civitai/app-sdk 0.66.0 → 0.68.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'`),
@@ -169,6 +169,10 @@ own key:
169
169
  `'chat-completion'`, …). The wire enum is derived from the host's step registry, so an
170
170
  unregistered id is rejected fail-closed at the schema, and `params` are validated by that
171
171
  step's own `.strict()` schema.
172
+ An estimate the host has no price quote for fails with no `cost` and `snapshot.errorCode`
173
+ `step_quote_rejected` (refused; the same body will fail again), `step_quote_unavailable` (no
174
+ usable answer; the one a retry can clear) or `step_quote_unpriced` (an answer without a price).
175
+ The submit is unchanged. An older host prices that estimate at the step's declared estimate.
172
176
  - **`WorkflowBodyPassThroughStep`** (`step` ABSENT) — names an **orchestrator `$type`** directly
173
177
  and the host forwards `input` unmodified. There is no registry lookup, no param schema, no
174
178
  prompt audit and no AIR scan; what bounds it is a denylist of platform-internal `$type`s
@@ -209,6 +213,46 @@ const body: WorkflowBody = {
209
213
  };
210
214
  ```
211
215
 
216
+ **Above the per-call budget: a confirmed step.** A pass-through job the per-call budget refuses
217
+ can still run when the **viewer confirms its exact price** in Civitai's own dialog
218
+ (civitai/civitai#5786). When the orchestrator quotes a **fixed** price (positive, not variable
219
+ or metered) above the token's per-call budget and at most `BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB`
220
+ (5,000), the `estimate` reply carries `snapshot.stepQuote` (`quoteId`, `total`, `stepType`,
221
+ `expiresAt`). Send the same body back with that `quoteId` on the `RUN_CONFIRMED_STEP` bridge
222
+ (`CONFIRMED_STEP_RESULT`); a `quoteId` body sent as an ordinary submit is refused. A quote is
223
+ single-use and expires after 15 minutes. Only a **single** estimate carries `stepQuote` — a
224
+ batch estimate stores nothing, so its cells never do. Where a quote would be granted, the
225
+ estimate **fails instead** (`{ workflowId: 'failed', status: 'failed', error }`) for a training
226
+ `$type` in any casing (`a training step type cannot run as a confirmed step — use kind:'training'`) and for an
227
+ `input` that is not plain JSON (`a confirmed step needs plain JSON input (objects, arrays,
228
+ strings, finite numbers, booleans, null) — <path> is <what>`; the suffix varies, so match on
229
+ the text before the dash). Each confirmation is its own job, so a
230
+ `submission-unconfirmed` result — the outcome is unknown — must never be retried automatically:
231
+ a retry is a new, separately charged job. Page apps only, `ai:write:budgeted`, behind the host
232
+ flag `app-blocks-confirmed-step` (off by default), and refused for development, dev-tunnel and
233
+ review tokens — so it is exercised against the mock host, not `dev:live`. Use it through
234
+ `@civitai/blocks-react`'s `useRunConfirmedStep()`.
235
+
236
+ **3D outputs.** A step that produces 3D models (`polyGen`) still lists every model url in
237
+ `snapshot.imageUrls`; `snapshot.model3dOutputs` (and the same field on an `AppWorkflow` row)
238
+ labels them. Omitted when there are none.
239
+
240
+ ```ts
241
+ import type { BlockModel3dOutput, BlockWorkflowSnapshot } from '@civitai/app-sdk/blocks';
242
+
243
+ // `format` is the model's file format; `variant` is the output it came from.
244
+ function pickModel(
245
+ snapshot: BlockWorkflowSnapshot,
246
+ format: string,
247
+ variant: string,
248
+ ): BlockModel3dOutput | undefined {
249
+ return snapshot.model3dOutputs?.find((m) => m.format === format && m.variant === variant);
250
+ }
251
+
252
+ const mesh = pickModel(snapshot, 'glb', 'model');
253
+ const rigged = pickModel(snapshot, 'fbx', 'riggedFbxModel');
254
+ ```
255
+
212
256
  `WorkflowBodyTraining` (`kind: 'training'`) is the **quoted** way to train: an ai-toolkit LoRA
213
257
  run on a dataset Civitai prepared from the viewer's own images, mirroring the host's
214
258
  `blockTrainingBodySchema` (civitai/civitai#5434). There is no `maxBuzz` and no timeout — the
@@ -259,10 +303,15 @@ const body: WorkflowBody = {
259
303
 
260
304
  - 🔴 **The estimate is not a maximum.** The orchestrator re-prices a step once the values it
261
305
  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`).
306
+ - **A workflow the host cannot price has no estimate, but its submit still proceeds.** The
307
+ estimate (and a batch-estimate cell) fails with no `cost` and `snapshot.errorCode`
308
+ `workflow_quote_rejected` (the orchestrator refused the body; the same body will fail again),
309
+ `workflow_quote_unavailable` (no usable answer; the one a retry can clear) or
310
+ `workflow_quote_unpriced` (an answer without a price). The submit of the same body is reserved
311
+ at the ceiling above — unless it contains a training step (refused without a quote on both, as
312
+ on the single-step arm) or a `repeat` step at any depth (also refused on both, before any
313
+ reservation, as `workflow_repeat_unbounded`; its `error` states the cause). An older host
314
+ returns the ceiling as the estimate's `cost.total` instead of failing.
266
315
  - **One author fee per workflow**, on the quoted base, itemised as `cost.authorFee` on the
267
316
  estimate. If the workflow bills more, the fee charged is held to the fee shown; a workflow that
268
317
  does not succeed has its whole fee reversed, even if earlier steps delivered output.
@@ -277,8 +326,8 @@ const body: WorkflowBody = {
277
326
  - A refused body carries `snapshot.errorCode`: `workflow_step_denied` (a platform-internal
278
327
  `$type`), `workflow_ref_invalid` (a reference to the step itself, a later step, or nothing, or a
279
328
  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.
329
+ `workflow_repeat_unbounded` (a `repeat` not over an array in `arguments`, or over 16 iterations,
330
+ or a `repeat` with no price quote) or `workflow_too_many_steps`. The single-step pass-through arm refuses `repeat` outright.
282
331
  - Requires a host that knows the kind; an older host refuses the body.
283
332
 
284
333
  ### `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,25 @@ 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.
1759
+ * A REGISTERED `step` body ({@link WorkflowBodyStep}) the host has no price
1760
+ * quote for gets one of the three `step_quote_*` codes, also ESTIMATE-ONLY
1761
+ * (`estimate()` and a batch-estimate cell); its `submit()` is unchanged. The
1762
+ * pass-through `step` arm does not set them.
1712
1763
  *
1713
1764
  * The values the host sends today ({@link KnownBlockWorkflowErrorCode}):
1714
1765
  * - `'training-quote-rejected'` — the orchestrator refused the request
@@ -1717,6 +1768,15 @@ export interface BlockWorkflowSnapshot {
1717
1768
  * - `'training-quote-unavailable'` — no usable answer (an outage, a timeout,
1718
1769
  * a rate limit, insufficient funds, …). Retrying later may succeed.
1719
1770
  * - `'training-quote-unpriced'` — the orchestrator answered without a price.
1771
+ * - `'daily-limit-exceeded'` — a viewer-confirmed job (a `kind: 'training'`
1772
+ * run or a confirmed step) would exceed what remains today of the daily
1773
+ * Buzz limit THE VIEWER set for this app. Nothing was charged and no job
1774
+ * was started; the confirm cannot override it, and only the viewer can
1775
+ * raise the limit. This one arrives on the failed snapshot a confirmed
1776
+ * SUBMIT resolves (`{ workflowId: 'failed', status: 'failed', cost, error,
1777
+ * errorCode }`), not on an estimate. On a confirmed step the host also
1778
+ * reports it as the `error` code of `CONFIRMED_STEP_RESULT` — see
1779
+ * {@link BlockRunConfirmedStepHostError}.
1720
1780
  * - `'workflow_step_denied'` — a step of a `kind: 'workflow'` body names a
1721
1781
  * platform-internal `$type`. Remove or replace that step.
1722
1782
  * - `'workflow_ref_invalid'` — a {@link WorkflowStepRef} in a step's `input`
@@ -1728,10 +1788,48 @@ export interface BlockWorkflowSnapshot {
1728
1788
  * - `'workflow_repeat_unbounded'` — a `repeat` step iterates something other
1729
1789
  * than an array in the body's `arguments`, or more than
1730
1790
  * {@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).
1791
+ * multiplied; or the workflow has a `repeat` and the host has no price
1792
+ * quote for it. `error` then reads `workflow step N: a workflow with a
1793
+ * repeat needs a price quote and <cause>`, where `<cause>` is one of the
1794
+ * three in the `workflow_quote_*` messages below. A retry of the same body
1795
+ * can succeed only when the cause is `none could be obtained; try again`;
1796
+ * otherwise the body is what has to change.
1797
+ * - `'workflow_quote_rejected'` — ESTIMATE only. The orchestrator refused
1798
+ * the body, so there is no price to show; resending the same body will not
1799
+ * help. `error` reads `No price quote for this workflow: the orchestrator
1800
+ * rejected the request: <reason>` when the reason is safe to show, else
1801
+ * `No price quote for this workflow: the orchestrator rejected the request;
1802
+ * check the step inputs and references (retrying the same body will not
1803
+ * help)`.
1804
+ * - `'workflow_quote_unavailable'` — ESTIMATE only. No usable answer (an
1805
+ * outage, a timeout, a rate limit, …). The one `workflow_quote_*` code a
1806
+ * retry of the same body can clear. `error` reads `No price quote for this
1807
+ * workflow: none could be obtained; try again`.
1808
+ * - `'workflow_quote_unpriced'` — ESTIMATE only. The orchestrator answered
1809
+ * without a price. `error` reads `No price quote for this workflow: the
1810
+ * orchestrator answered without one`.
1811
+ *
1812
+ * Each `workflow_quote_*` reply is `{ workflowId: 'failed', status: 'failed',
1813
+ * error, errorCode }` with NO `cost`. A host that predates these codes
1814
+ * resolves the estimate instead, with the spend ceiling as `cost.total`.
1815
+ * - `'step_quote_rejected'` — ESTIMATE only, registered `step` body. The
1816
+ * orchestrator refused the request; resending the same body will not help.
1817
+ * `error` reads `No price quote for this step: the orchestrator rejected
1818
+ * the request: <reason>` when the reason is safe to show, else `No price
1819
+ * quote for this step: the orchestrator rejected the request; check the
1820
+ * step inputs and references (retrying the same body will not help)`.
1821
+ * - `'step_quote_unavailable'` — ESTIMATE only, registered `step` body. No
1822
+ * usable answer (an outage, a timeout, a rate limit, …). The one
1823
+ * `step_quote_*` code a retry of the same body can clear. `error` reads
1824
+ * `No price quote for this step: none could be obtained; try again`.
1825
+ * - `'step_quote_unpriced'` — ESTIMATE only, registered `step` body. The
1826
+ * orchestrator answered without a price. `error` reads `No price quote for
1827
+ * this step: the orchestrator answered without one`.
1828
+ *
1829
+ * Each `step_quote_*` reply is `{ workflowId: 'failed', status: 'failed',
1830
+ * error, errorCode }` with NO `cost`. A host that predates these codes
1831
+ * resolves the estimate instead, priced at the step's declared estimate, so
1832
+ * there a registered-step estimate may not be a quote.
1735
1833
  *
1736
1834
  * 🔴 AN UNKNOWN VALUE IS POSSIBLE: the host may add codes, and the SDK's
1737
1835
  * inbound validator accepts any string here. Handle an unrecognised code as a
@@ -1939,6 +2037,84 @@ export interface BlockWorkflowSnapshot {
1939
2037
  * that predate civitai/civitai#5434.
1940
2038
  */
1941
2039
  trainingQuote?: BlockTrainingQuote;
2040
+ /**
2041
+ * The viewer-confirmable quote a pass-through `step` ESTIMATE
2042
+ * ({@link WorkflowBodyPassThroughStep}) produced, when the job costs more than
2043
+ * the app may spend without asking. Its presence means: this job cannot run
2044
+ * through `submit()`, and CAN run through `useRunConfirmedStep()` once the
2045
+ * viewer confirms `stepQuote.total` in Civitai's own dialog.
2046
+ *
2047
+ * THE FLOW, mirroring a training run's:
2048
+ * 1. `useBuzzWorkflow().estimate(body)` — the reply carries `stepQuote`.
2049
+ * 2. `useRunConfirmedStep().runConfirmedStep({ ...body, quoteId:
2050
+ * stepQuote.quoteId })` — Civitai shows the viewer the price (read back
2051
+ * from the server, never from the body) and submits on their confirm.
2052
+ * 3. `useBuzzWorkflow().watch(snapshot.workflowId)`.
2053
+ *
2054
+ * WHEN THE HOST SENDS IT — all of these, decided server-side:
2055
+ * - the orchestrator quoted a **FIXED** price for exactly this step:
2056
+ * positive, and not a variable or metered one. The viewer confirms what
2057
+ * they are charged, so a price that may move is never confirmable;
2058
+ * - that price is ABOVE the token's per-call budget and at most
2059
+ * {@link BLOCK_CONFIRMED_STEP_MAX_BUZZ_PER_JOB} (5,000 Buzz);
2060
+ * - the block runs in a page app for a signed-in viewer on an ordinary
2061
+ * token. Development, dev-tunnel and review sessions never get one, and
2062
+ * neither does the model slot.
2063
+ * Everywhere else the field is ABSENT and the estimate is exactly what it was.
2064
+ * Absent is therefore not an error — it is also what every host that
2065
+ * predates civitai/civitai#5786 sends.
2066
+ *
2067
+ * 🔴 ONLY A SINGLE ESTIMATE CARRIES IT. A batch estimate
2068
+ * (`ESTIMATE_WORKFLOW_BATCH`, `useBatchEstimate()`) stores nothing, so its
2069
+ * cells never have a `stepQuote`, whatever they cost. Price a grid in a batch,
2070
+ * then estimate the one body you want to run on its own.
2071
+ *
2072
+ * 🔴 TWO BODIES MAKE THAT ESTIMATE FAIL INSTEAD OF RETURNING A PRICE — only
2073
+ * where a quote would otherwise have been granted (so not below the budget,
2074
+ * and not in a batch). The reply is the failure snapshot `{ workflowId:
2075
+ * 'failed', status: 'failed', error }`, which `estimate()` rejects with; the
2076
+ * same text arrives as `CONFIRMED_STEP_RESULT`'s `error` if such a body is
2077
+ * run. Neither has an {@link BlockWorkflowSnapshot.errorCode}; they are
2078
+ * server messages, so match on the text and keep a generic branch:
2079
+ * - `a training step type cannot run as a confirmed step — use
2080
+ * kind:'training'` — `$type` is `training` or `imageResourceTraining`, in
2081
+ * any casing.
2082
+ * Training has its own kind ({@link WorkflowBodyTraining}).
2083
+ * - `a confirmed step needs plain JSON input (objects, arrays, strings,
2084
+ * finite numbers, booleans, null) — <path> is <what>` — `input` holds a
2085
+ * `Date`, `Map`, `Set`, class instance, `BigInt`, `NaN`/`Infinity`, an
2086
+ * `undefined` array element or an empty slot in a sparse array
2087
+ * (`… — body.input.list[1] is an empty array slot`). The quote is bound to the exact body, so the
2088
+ * body must be exactly representable. The part after the dash names the
2089
+ * offending path (`body.input.when is a Date`) and varies: match on the
2090
+ * text BEFORE the dash.
2091
+ *
2092
+ * `total` is the price the viewer confirms and is charged. 🔴 It can differ
2093
+ * from `cost.total` on the same snapshot, which stays what an UNCONFIRMED
2094
+ * submit would reserve (`max(maxBuzz, quote)`); show `stepQuote.total` on a
2095
+ * confirm button. `expiresAt` is an ISO-8601 timestamp 15 minutes out; after
2096
+ * it the run is refused and you estimate again. A quote is single-use.
2097
+ */
2098
+ stepQuote?: BlockStepQuote;
2099
+ /**
2100
+ * What each 3D model in {@link BlockWorkflowSnapshot.imageUrls} IS — one label
2101
+ * per model a pass-through step produced (e.g. `polyGen`).
2102
+ *
2103
+ * Every model url is still listed in `imageUrls`, unchanged and in the same
2104
+ * order as before the field existed; this only says which entry is which.
2105
+ * Match on `url`:
2106
+ *
2107
+ * ```ts
2108
+ * const glb = snapshot.model3dOutputs?.find(
2109
+ * (m) => m.format === 'glb' && m.variant === 'model',
2110
+ * );
2111
+ * if (glb) viewer.load(glb.url);
2112
+ * ```
2113
+ *
2114
+ * OMITTED when the workflow produced no 3D model, and on hosts that predate
2115
+ * civitai/civitai#5786 — in which case `imageUrls` is all there is.
2116
+ */
2117
+ model3dOutputs?: BlockModel3dOutput[];
1942
2118
  /**
1943
2119
  * The steps of a `kind: 'workflow'` run ({@link WorkflowBodyWorkflow}), in
1944
2120
  * the orchestrator's order, each with its own status and results. The steps a
@@ -2014,6 +2190,42 @@ export interface BlockTrainingQuote {
2014
2190
  /** ISO-8601. The quote cannot be run after this. */
2015
2191
  expiresAt: string;
2016
2192
  }
2193
+ /** {@link BlockWorkflowSnapshot.stepQuote}. */
2194
+ export interface BlockStepQuote {
2195
+ /**
2196
+ * `sq_` + 32 lowercase hex characters ({@link BLOCK_STEP_QUOTE_ID_REGEX}).
2197
+ * Opaque — pass it back verbatim as the body's `quoteId`.
2198
+ */
2199
+ quoteId: string;
2200
+ /** The exact price the viewer confirms and is charged, in Buzz. */
2201
+ total: number;
2202
+ /** The orchestrator `$type` that was priced — the body's own `$type`. */
2203
+ stepType: string;
2204
+ /** ISO-8601. The quote cannot be run after this. */
2205
+ expiresAt: string;
2206
+ }
2207
+ /**
2208
+ * One entry of {@link BlockWorkflowSnapshot.model3dOutputs} /
2209
+ * {@link AppWorkflow.model3dOutputs} — the label for one 3D model url.
2210
+ */
2211
+ export interface BlockModel3dOutput {
2212
+ /** The model's url. Also present in the snapshot's `imageUrls` (an `AppWorkflow`'s `images`). */
2213
+ url: string;
2214
+ /**
2215
+ * The model's own file format as the orchestrator reports it — `'glb'`,
2216
+ * `'fbx'`, …. An open set: compare against the formats your viewer loads and
2217
+ * ignore the rest.
2218
+ */
2219
+ format: string;
2220
+ /**
2221
+ * Which output of the step this model is: the output field it came from,
2222
+ * dot-joined when nested — `'model'`, `'riggedFbxModel'`,
2223
+ * `'basicAnimations.walkingModel'`, …. An array index is a segment
2224
+ * (`'models.0'`), and it is `''` when the step's whole output is the model.
2225
+ * The names are the orchestrator's, per `$type`; nothing here lists them.
2226
+ */
2227
+ variant: string;
2228
+ }
2017
2229
  /**
2018
2230
  * Why the host substituted a checkpoint version — see
2019
2231
  * {@link BlockWorkflowSnapshot.modelSubstitutions}.
@@ -2040,7 +2252,7 @@ export type ModelSubstitutionReason = 'wrong-workflow' | 'unrecognized' | 'gated
2040
2252
  * and the SDK's inbound validator accepts any string. Handle an unrecognised
2041
2253
  * code as a generic failure.
2042
2254
  */
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';
2255
+ 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' | 'step_quote_rejected' | 'step_quote_unavailable' | 'step_quote_unpriced';
2044
2256
  /** One entry of {@link BlockWorkflowSnapshot.modelSubstitutions}. */
2045
2257
  export interface BlockModelSubstitution {
2046
2258
  /** The checkpoint version id the block asked for. */
@@ -2696,6 +2908,13 @@ export interface AppWorkflow {
2696
2908
  * absence as "unknown", not as "no checkpoint".
2697
2909
  */
2698
2910
  trainedEpochs?: BlockTrainedEpoch[];
2911
+ /**
2912
+ * Labels for the 3D models among this workflow's {@link AppWorkflow.images} —
2913
+ * same shape and rule as {@link BlockWorkflowSnapshot.model3dOutputs}: every
2914
+ * labelled `url` is also an entry of `images`, and the field is OMITTED when
2915
+ * there are none (and on hosts that predate it).
2916
+ */
2917
+ model3dOutputs?: BlockModel3dOutput[];
2699
2918
  }
2700
2919
  /**
2701
2920
  * Per-viewer gated projection of ONE image returned by `GET_IMAGES_BY_IDS`
@@ -3013,5 +3232,37 @@ export type BlockPrepareTrainingDatasetHostError = 'review-mode' | 'block is not
3013
3232
  * run is a SECOND, separately charged run.
3014
3233
  */
3015
3234
  export type BlockRunTrainingHostError = 'review-mode' | 'block is not ready' | 'sign in to train' | 'invalid training request' | 'no block token' | 'declined' | 'submission-unconfirmed';
3235
+ /**
3236
+ * The HOST'S OWN refusal codes on a `CONFIRMED_STEP_RESULT`'s `error`. Mirrors
3237
+ * civitai/civitai's `RUN_CONFIRMED_STEP_HOST_ERRORS`
3238
+ * (`src/components/AppBlocks/runConfirmedStepGate.ts`, civitai/civitai#5786).
3239
+ *
3240
+ * 🔴 THE SET A BLOCK MAY RELY ON, NOT THE SET IT MAY RECEIVE: server messages
3241
+ * (an expired or already-used quote, a body that differs from the quoted one, a
3242
+ * price that changed, `confirmed steps from apps are not enabled`) arrive
3243
+ * verbatim in the same field.
3244
+ *
3245
+ * - `review-mode` — a mod-review sandbox with "run for real" off.
3246
+ * - `block is not ready` — the host handshake has not completed.
3247
+ * - `sign in to run` — anonymous viewer; there is no account to charge.
3248
+ * - `invalid confirmed step request` — the body is not a pass-through
3249
+ * `kind: 'step'` body naming an `sq_`-shaped `quoteId`.
3250
+ * - `no block token` — the host holds no block token yet.
3251
+ * - `declined` — the viewer dismissed the confirm. 🔴 GUARANTEED to mean NO JOB
3252
+ * WAS SUBMITTED. The quote is untouched and can still be run before it
3253
+ * expires.
3254
+ * - `daily-limit-exceeded` — the job would exceed what remains today of the
3255
+ * daily Buzz limit the VIEWER set for this app. NO job was submitted and
3256
+ * nothing was charged. The host has already told the viewer, in its own
3257
+ * chrome, and pointed them at where to raise the limit; there is no confirm
3258
+ * to click and nothing the app can do about it. Do not retry.
3259
+ * - `submission-unconfirmed` — the submit was SENT but its outcome is unknown.
3260
+ * 🔴 The job MAY BE RUNNING AND CHARGED. Every confirmation is its own job,
3261
+ * so a retry (which needs a new estimate and a new confirmation) starts a
3262
+ * SECOND, separately charged job if the first one did start. Check the
3263
+ * viewer's generations (`useAppWorkflows()`) before offering one, and never
3264
+ * retry automatically.
3265
+ */
3266
+ 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
3267
  export {};
3017
3268
  //# 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.68.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",