@civitai/app-sdk 0.57.0 → 0.59.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
@@ -139,7 +139,7 @@ interface BlockInitPayload {
139
139
  | `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`. |
140
140
  | `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'`. |
141
141
  | `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. |
142
- | 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'`), `BlockWorkflowSnapshot`, `WorkflowStatus`, `BlockInitPayload`, `ParentToBlockMessage`, `BlockToParentMessage`. |
142
+ | 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), `BlockWorkflowSnapshot`, `WorkflowStatus`, `BlockInitPayload`, `ParentToBlockMessage`, `BlockToParentMessage`. |
143
143
 
144
144
  `WorkflowBody`'s `customComfy` member is a discriminated union on `mode`, mirroring
145
145
  the host's `blockCustomComfyMemberSchema`. Narrow on the VALUE (`body.mode === 'inline'`),
@@ -189,10 +189,11 @@ own key:
189
189
  The trained checkpoint is not part of the block contract (an older
190
190
  host may still leak checkpoint urls into `imageUrls` / `stepOutputs` — a host defect, never
191
191
  something to build on); a host that supports it reports the trained epochs on
192
- `snapshot.trainedEpochs` without the checkpoint, and the viewer publishes
192
+ `snapshot.trainedEpochs` (and on each `AppWorkflow` row of the app-queue read) without the
193
+ checkpoint, and the viewer publishes
193
194
  one by being navigated (`useCivitaiNavigate`, `scope: 'site'`) to Civitai's own model wizard at
194
195
  `models/train/from-orchestrator?workflowId=<id>&epoch=<n>`. `snapshot.publishedModel` (and
195
- `AppWorkflow.publishedModel`) then reports the resulting model. Both fields are absent on hosts
196
+ `AppWorkflow.publishedModel`) then reports the resulting model. These fields are absent on hosts
196
197
  that predate them.
197
198
 
198
199
  ```ts
@@ -207,6 +208,18 @@ const body: WorkflowBody = {
207
208
  };
208
209
  ```
209
210
 
211
+ `WorkflowBodyTraining` (`kind: 'training'`) is the **quoted** way to train: an ai-toolkit LoRA
212
+ run on a dataset Civitai prepared from the viewer's own images, mirroring the host's
213
+ `blockTrainingBodySchema` (civitai/civitai#5434). There is no `maxBuzz` and no timeout — the
214
+ `estimate` reply carries `snapshot.trainingQuote` (`quoteId`, `total`, `imageCount`,
215
+ `expiresAt`), and the run starts only through the `RUN_TRAINING` bridge, after the viewer
216
+ confirms that price in Civitai's own dialog. A confirmed run may exceed the token's per-call
217
+ budget, up to `BLOCK_TRAINING_MAX_BUZZ_PER_RUN` (5,000). Page apps only, `ai:write:budgeted`,
218
+ behind the host flag `app-blocks-training-kind` (off by default), and refused from development
219
+ and review sessions. The dataset is prepared over `PREPARE_TRAINING_DATASET`
220
+ (civitai/civitai#5438). Use it through `@civitai/blocks-react`'s `usePrepareTrainingDataset()`
221
+ and `useRunTraining()`.
222
+
210
223
  ### `defineBlock` validator rules
211
224
 
212
225
  **There is no rule list here, and that is the point.** `defineBlock` does not
@@ -253,7 +266,7 @@ no entry fails the suite.
253
266
  > assigned server-side, so it cannot be enforced locally.
254
267
 
255
268
  **Where it runs.** Every scaffold that ships a `block.manifest.json`
256
- (`starters/civitai-block-starter` and all six `starters/examples/*`) registers
269
+ (`starters/civitai-block-starter` and every `starters/examples/*` app) registers
257
270
  `blockManifestPlugin` from `@civitai/app-sdk/vite` in its `vite.config.ts`. It
258
271
  fires from Vite's `configResolved`, the one hook called on both the dev-server
259
272
  and the build path — so `pnpm dev`, `pnpm dev:harness` and `pnpm build` all fail
@@ -109,5 +109,10 @@ 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, 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, BlockModelSubstitution, ModelSubstitutionReason, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, WorkflowStatus, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, AppWorkflowImage, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostRequest, BlockCreatePostResult, BlockCreatePostHostError, } from './types.js';
112
+ export type { BlockContext, KnownSlotId, ModelSlotId, PageSlotContext, PageSlotId, UnknownSlotContext, 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, BlockModelSubstitution, ModelSubstitutionReason, BlockStepToolCall, BlockTrainedEpoch, BlockPublishedModel, BuzzAccountType, ShowcaseImage, WorkflowBody, WorkflowBodyTextToImage, WorkflowBodyCustomComfy, WorkflowBodyCustomComfyRecipe, WorkflowBodyCustomComfyInline, InlineComfyNode, WorkflowBodyStep, WorkflowBodyPassThroughStep, 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';
113
+ /**
114
+ * Host bounds for the App Blocks `kind: 'training'` flow, mirrored from
115
+ * civitai/civitai's zod — see `WorkflowBodyTraining`.
116
+ */
117
+ export { BLOCK_TRAINING_MAX_BUZZ_PER_RUN, BLOCK_TRAINING_DATASET_MAX_ITEMS, BLOCK_TRAINING_CAPTION_MAX_CHARS, BLOCK_TRAINING_SAMPLE_PROMPTS_MAX, BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS, BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS, BLOCK_TRAINING_MODEL_KEY_MAX_CHARS, } from './types.js';
113
118
  //# sourceMappingURL=index.d.ts.map
@@ -108,4 +108,9 @@ export { isModelSlotContext, isPageSlotContext } from './types.js';
108
108
  * in `types.ts` for which of the two it uses and why.
109
109
  */
110
110
  export { isSignedIn } from './types.js';
111
+ /**
112
+ * Host bounds for the App Blocks `kind: 'training'` flow, mirrored from
113
+ * civitai/civitai's zod — see `WorkflowBodyTraining`.
114
+ */
115
+ export { BLOCK_TRAINING_MAX_BUZZ_PER_RUN, BLOCK_TRAINING_DATASET_MAX_ITEMS, BLOCK_TRAINING_CAPTION_MAX_CHARS, BLOCK_TRAINING_SAMPLE_PROMPTS_MAX, BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS, BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS, BLOCK_TRAINING_MODEL_KEY_MAX_CHARS, } from './types.js';
111
116
  //# sourceMappingURL=index.js.map
@@ -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, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostResult } from './types.js';
11
+ import type { BlockCheckpointInfo, BlockResourceInfo, BlockResourcePickerType, BlockUploadedImageInfo, BlockGenerationSourceImageInfo, BlockPendingImageInfo, BlockImageScanResult, BlockUploadPurpose, BlockNavigateScope, BlockContext, BlockSettings, Theme, ViewerInfo, WorkflowBody, BlockWorkflowSnapshot, BlockBuzzTransaction, BlockBuzzAccount, BlockDailyCompensationResource, BlockViewer, BlockWildcardPack, BlockWildcardPackErrorCode, AppWorkflow, BlockGatedImage, BlockCollectionFollowErrorCode, BlockCollectionFollowResult, BlockPostSource, BlockCreatePostResult, BlockTrainingDatasetItem, BlockTrainingDatasetResult, WorkflowBodyTraining } 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
@@ -213,7 +213,8 @@ export interface BlockInitPayload {
213
213
  * fail-closed defaults for you. `@civitai/blocks-react`'s
214
214
  * `useDomainMaturity()` is the React wrapper over the same thing — it is one
215
215
  * way to get this right, not the only one, since this module is
216
- * runtime-agnostic and two of the starters ship without React.
216
+ * runtime-agnostic and several starters — the Civitai App starter among
217
+ * them — ship without React.
217
218
  */
218
219
  effectiveBrowsingLevel?: number;
219
220
  }
@@ -464,6 +465,20 @@ export type ParentToBlockMessage = {
464
465
  result?: BlockCreatePostResult;
465
466
  error?: string;
466
467
  };
468
+ } | {
469
+ type: 'TRAINING_DATASET_RESULT';
470
+ payload: {
471
+ requestId: string;
472
+ result?: BlockTrainingDatasetResult;
473
+ error?: string;
474
+ };
475
+ } | {
476
+ type: 'TRAINING_RESULT';
477
+ payload: {
478
+ requestId: string;
479
+ snapshot?: BlockWorkflowSnapshot;
480
+ error?: string;
481
+ };
467
482
  } | {
468
483
  type: 'IMAGES_RESULT';
469
484
  payload: {
@@ -774,6 +789,18 @@ export type BlockToParentMessage = {
774
789
  /** Optional model-version gallery attach. Gated hard server-side. */
775
790
  modelVersionId?: number;
776
791
  };
792
+ } | {
793
+ type: 'PREPARE_TRAINING_DATASET';
794
+ payload: {
795
+ requestId: string;
796
+ items: BlockTrainingDatasetItem[];
797
+ };
798
+ } | {
799
+ type: 'RUN_TRAINING';
800
+ payload: {
801
+ requestId: string;
802
+ body: WorkflowBodyTraining;
803
+ };
777
804
  } | {
778
805
  type: 'GET_IMAGES_BY_IDS';
779
806
  payload: {
@@ -998,7 +1025,7 @@ export type BlockToParentMessageType = BlockToParentMessage['type'];
998
1025
  * it is a measurement, not an invariant, and civitai's own compile-time gate
999
1026
  * (published SDK union ⊆ `keyof INVENTORY`) is what holds one direction of it.
1000
1027
  */
1001
- 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", "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", "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"];
1028
+ 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", "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"];
1002
1029
  /**
1003
1030
  * The bucket a message type falls into when the protocol does not declare it.
1004
1031
  *
@@ -69,6 +69,8 @@ export const BLOCK_TO_PARENT_MESSAGE_TYPES = [
69
69
  'QUERY_APP_WORKFLOWS',
70
70
  'PUBLISH_GENERATION_OUTPUTS',
71
71
  'CREATE_POST_FROM_APP',
72
+ 'PREPARE_TRAINING_DATASET',
73
+ 'RUN_TRAINING',
72
74
  'GET_IMAGES_BY_IDS',
73
75
  'CANCEL_APP_WORKFLOW',
74
76
  'OPEN_CHECKPOINT_PICKER',
@@ -26,6 +26,7 @@ export declare const BLOCK_SCOPES: {
26
26
  readonly POSTS_WRITE_SELF: "posts:write:self";
27
27
  readonly GOODS_READ_SELF: "goods:read:self";
28
28
  readonly GOODS_PURCHASE_SELF: "goods:purchase:self";
29
+ readonly APPS_STORE_ITEMS_WRITE: "apps:store:items:write";
29
30
  };
30
31
  export type BlockScopeKey = keyof typeof BLOCK_SCOPES;
31
32
  export type BlockScope = (typeof BLOCK_SCOPES)[BlockScopeKey];
@@ -44,8 +45,8 @@ export type BlockScope = (typeof BLOCK_SCOPES)[BlockScopeKey];
44
45
  *
45
46
  * The 4th segment was added to mirror the server's 4-segment
46
47
  * `apps:storage:shared:read` / `apps:storage:shared:write` scopes (the SHARED
47
- * cross-user datastore). The SDK now tracks the server's 4-segment scopes
48
- * directly, so this is no longer a coordinated-major-bump situation — the
48
+ * cross-user datastore); `apps:store:items:write` is the same shape. The SDK
49
+ * now tracks the server's 4-segment scopes directly, so this is no longer a coordinated-major-bump situation — the
49
50
  * pattern just widened to accept the shapes the canonical enum already lists.
50
51
  */
51
52
  export declare const BLOCK_SCOPE_PATTERN: RegExp;
@@ -24,7 +24,7 @@ export const BLOCK_SCOPES = {
24
24
  // apps:storage:shared:* — the SHARED (app-global / cross-user) datastore. Same
25
25
  // no-OAuth-bit posture as apps:storage:*; the server gates them by presence in
26
26
  // the block's approved scope set (plus a min-trust gate + fail-closed flag).
27
- // These are the SDK's only 4-segment scopes — see BLOCK_SCOPE_PATTERN below.
27
+ // 4-segment (as is `apps:store:items:write`) — see BLOCK_SCOPE_PATTERN below.
28
28
  APPS_STORAGE_SHARED_READ: 'apps:storage:shared:read',
29
29
  APPS_STORAGE_SHARED_WRITE: 'apps:storage:shared:write',
30
30
  // collections:* — the App Blocks Collections surface. These have no OAuth bit
@@ -62,6 +62,22 @@ export const BLOCK_SCOPES = {
62
62
  // always needs an explicit grant. Do not collapse the two.
63
63
  GOODS_READ_SELF: 'goods:read:self',
64
64
  GOODS_PURCHASE_SELF: 'goods:purchase:self',
65
+ // apps:store:items:write — publish the VIEWER'S OWN app items into the App
66
+ // Store as sub-listing cards under the calling app (civitai/civitai#5511,
67
+ // `/api/v1/blocks/sub-listings/{upsert,withdraw,mine}`). No OAuth bit.
68
+ //
69
+ // SENSITIVE: it writes store cards every visitor sees, under the viewer's
70
+ // name, so a declaring manifest MUST carry a `scopeJustifications` entry or
71
+ // the server rejects it at submit (not mirrored by `defineBlock` — see
72
+ // KNOWN_GAPS in `../manifest/defineBlock.ts`).
73
+ //
74
+ // CONSENT-EXEMPT on the server, like `apps:storage:shared:write`: the real
75
+ // gates run per call (the parent's enable switch, the shared-write trust
76
+ // check, item authorship, text safety, rate limits, moderator approval), and
77
+ // a consent-gated scope would be dropped from tokens before any could run.
78
+ // Requires a signed-in (non-anonymous) subject. 4-segment, like the shared
79
+ // storage scopes — see BLOCK_SCOPE_PATTERN below.
80
+ APPS_STORE_ITEMS_WRITE: 'apps:store:items:write',
65
81
  };
66
82
  /**
67
83
  * Format helper for the block-scope shape — 3 OR 4 colon-separated lowercase
@@ -78,8 +94,8 @@ export const BLOCK_SCOPES = {
78
94
  *
79
95
  * The 4th segment was added to mirror the server's 4-segment
80
96
  * `apps:storage:shared:read` / `apps:storage:shared:write` scopes (the SHARED
81
- * cross-user datastore). The SDK now tracks the server's 4-segment scopes
82
- * directly, so this is no longer a coordinated-major-bump situation — the
97
+ * cross-user datastore); `apps:store:items:write` is the same shape. The SDK
98
+ * now tracks the server's 4-segment scopes directly, so this is no longer a coordinated-major-bump situation — the
83
99
  * pattern just widened to accept the shapes the canonical enum already lists.
84
100
  */
85
101
  export const BLOCK_SCOPE_PATTERN = /^[a-z]+:[a-z]+:[a-z]+(?::[a-z]+)?$/;
@@ -1249,6 +1249,166 @@ export type WorkflowBodyPassThroughStep = {
1249
1249
  */
1250
1250
  maxBuzz: number;
1251
1251
  };
1252
+ /** Ceiling on what ONE confirmed `kind: 'training'` run may cost, in Buzz. Host constant `BLOCK_TRAINING_MAX_BUZZ_PER_RUN`. */
1253
+ export declare const BLOCK_TRAINING_MAX_BUZZ_PER_RUN = 5000;
1254
+ /** Max images in one prepared training dataset. Host constant `BLOCK_TRAINING_DATASET_MAX_ITEMS`. */
1255
+ export declare const BLOCK_TRAINING_DATASET_MAX_ITEMS = 50;
1256
+ /** Max characters of one dataset caption. Host constant `BLOCK_TRAINING_CAPTION_MAX_CHARS`. */
1257
+ export declare const BLOCK_TRAINING_CAPTION_MAX_CHARS = 1000;
1258
+ /** Max sample prompts on a training body. Host constant `BLOCK_TRAINING_SAMPLE_PROMPTS_MAX`. */
1259
+ export declare const BLOCK_TRAINING_SAMPLE_PROMPTS_MAX = 6;
1260
+ /** Max characters of one sample prompt. Host constant `BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS`. */
1261
+ export declare const BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS = 1000;
1262
+ /** Max characters of the trigger word. Host constant `BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS`. */
1263
+ export declare const BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS = 64;
1264
+ /** Max characters of the base-model key. Host constant `BLOCK_TRAINING_MODEL_KEY_MAX_CHARS`. */
1265
+ export declare const BLOCK_TRAINING_MODEL_KEY_MAX_CHARS = 64;
1266
+ /** `lrScheduler` values the host's ai-toolkit schema accepts. */
1267
+ export type AiToolkitLrScheduler = 'constant' | 'constant_with_warmup' | 'cosine' | 'linear' | 'step';
1268
+ /** `optimizerType` values the host's ai-toolkit schema accepts. */
1269
+ export type AiToolkitOptimizerType = 'adamw' | 'adamw8bit' | 'adam8bit' | 'lion' | 'lion8bit' | 'adafactor' | 'adagrad' | 'prodigy' | 'prodigy8bit' | 'automagic';
1270
+ /** The fields every ai-toolkit ecosystem shares (`aiToolkitBaseParams` on the host). */
1271
+ export interface AiToolkitTrainingParamsBase {
1272
+ engine: 'ai-toolkit';
1273
+ /** Saved checkpoint count, integer 1…50. With `steps` absent it is also the length knob. */
1274
+ epochs?: number;
1275
+ /** Total training steps, integer ≥ 1 — the primary length (and price) knob. */
1276
+ steps?: number;
1277
+ /** Integer ≥ 1. Exactly `1` on the `qwen21` / `ming` / `yue2` ecosystems. */
1278
+ batchSize?: number;
1279
+ sampleCfgScale?: number;
1280
+ sampleStrength?: number;
1281
+ continueFrom?: string;
1282
+ resolution: number | null;
1283
+ /**
1284
+ * Learning rate. 🔴 Must be BELOW `0.1` unless `optimizerType` is `prodigy` or
1285
+ * `prodigy8bit` — a host refinement this type cannot express; a body that
1286
+ * breaks it is refused at estimate.
1287
+ */
1288
+ lr: number;
1289
+ textEncoderLr: number | null;
1290
+ trainTextEncoder: boolean;
1291
+ lrScheduler: AiToolkitLrScheduler;
1292
+ optimizerType: AiToolkitOptimizerType;
1293
+ networkDim: number | null;
1294
+ networkAlpha: number | null;
1295
+ noiseOffset: number | null;
1296
+ minSnrGamma: number | null;
1297
+ flipAugmentation: boolean;
1298
+ shuffleTokens: boolean;
1299
+ keepTokens: number;
1300
+ numRepeats?: number;
1301
+ }
1302
+ /**
1303
+ * The ai-toolkit ecosystems that take NO `modelVariant` — send the key absent.
1304
+ * (`qwen21` / `ming` / `yue2` also take none; they live on their own arm of
1305
+ * {@link AiToolkitTrainingParams} because they pin `batchSize` to `1`.)
1306
+ */
1307
+ export type AiToolkitPlainEcosystem = 'sd1' | 'sdxl' | 'chroma' | 'qwen' | 'zimageturbo' | 'zimagebase' | 'ltx2' | 'ltx23' | 'ltx25' | 'minimaxh3' | 'ernie' | 'anima' | 'boogu' | 'krea2' | 'mageflow' | 'ideogram4' | 'ace_step_15';
1308
+ /**
1309
+ * `params` of a {@link WorkflowBodyTraining} — the host's
1310
+ * `aiToolkitTrainingParamsSchema`, the same schema Civitai's own training form
1311
+ * validates against, discriminated on `ecosystem`.
1312
+ *
1313
+ * ⚠️ THE ECOSYSTEM LIST IS CLOSED HERE AND OPEN ON THE HOST. The host adds
1314
+ * ecosystems over time; until this package mirrors one, a body naming it does not
1315
+ * type-check. That is the cost of a typed copy, paid deliberately: a free
1316
+ * `string` would let a typo through to a refused estimate.
1317
+ */
1318
+ export type AiToolkitTrainingParams = AiToolkitTrainingParamsBase & ({
1319
+ ecosystem: AiToolkitPlainEcosystem;
1320
+ modelVariant?: undefined;
1321
+ } | {
1322
+ ecosystem: 'sd3';
1323
+ modelVariant: 'large' | 'medium';
1324
+ } | {
1325
+ ecosystem: 'flux1';
1326
+ modelVariant: 'dev' | 'schnell';
1327
+ } | {
1328
+ ecosystem: 'wan';
1329
+ modelVariant: '2.1' | '2.2';
1330
+ } | {
1331
+ ecosystem: 'flux2klein';
1332
+ modelVariant: '4b' | '9b';
1333
+ } | {
1334
+ ecosystem: 'qwen21' | 'ming' | 'yue2';
1335
+ modelVariant?: undefined;
1336
+ batchSize?: 1;
1337
+ } | {
1338
+ ecosystem: 'ace_step_15_xl';
1339
+ modelVariant: 'base' | 'sft';
1340
+ });
1341
+ /**
1342
+ * Train a LoRA with the ai-toolkit engine on a dataset Civitai prepared from the
1343
+ * VIEWER'S OWN images — the App Blocks `kind: 'training'` workflow body.
1344
+ *
1345
+ * ## Availability — read before building on it
1346
+ *
1347
+ * - 🔴 Behind the host flag **`app-blocks-training-kind`**, which ships OFF and is
1348
+ * evaluated per viewer. Where it is off, every training call is refused
1349
+ * (`training from apps is not enabled`).
1350
+ * - **Page apps only** (`/apps/run/<slug>`). The model slot answers both training
1351
+ * messages with an error, and the server refuses a non-page token.
1352
+ * - The manifest must declare **`ai:write:budgeted`** and the viewer must have
1353
+ * granted it; the viewer must be signed in.
1354
+ * - **Not from a development or review session**: dev tokens (`dev:live`, dev
1355
+ * tunnel) and review-sandbox tokens are refused, run-for-real included. Use the
1356
+ * mock host (`createMockHost` / `Harness`) to build the flow.
1357
+ *
1358
+ * ## The flow
1359
+ *
1360
+ * 1. **Dataset** — `usePrepareTrainingDataset()` (`PREPARE_TRAINING_DATASET`)
1361
+ * with `[{ imageId, caption }]`. Only the viewer's OWN scanned, unflagged
1362
+ * images within the page's maturity ceiling are admitted; the rest come back
1363
+ * in `rejected`. You get an opaque {@link WorkflowBodyTraining.datasetId}.
1364
+ * 2. **Quote** — `useBuzzWorkflow().estimate(body)` with this body, `quoteId`
1365
+ * ABSENT. The reply's {@link BlockWorkflowSnapshot.trainingQuote} is the
1366
+ * orchestrator's own price for exactly this run, stored server-side.
1367
+ * 3. **Run** — `useRunTraining().runTraining({ ...body, quoteId })`
1368
+ * (`RUN_TRAINING`). Civitai shows the viewer a consent dialog in ITS chrome —
1369
+ * price, base model, length, dataset size, all read back from the server,
1370
+ * never from your body — and submits only on their click.
1371
+ * 4. **Follow** — `useBuzzWorkflow().watch(snapshot.workflowId)`.
1372
+ *
1373
+ * 🔴 `useBuzzWorkflow().submit()` REFUSES THIS BODY before sending it. The server
1374
+ * charges a training run only against a quote the viewer confirmed from a
1375
+ * signed-in session, which only `RUN_TRAINING`'s dialog can record.
1376
+ *
1377
+ * ## Money
1378
+ *
1379
+ * There is no `maxBuzz` and no timeout knob, on purpose: the price is the quote.
1380
+ * A run may cost more than the token's per-call `buzzBudget` (the viewer confirms
1381
+ * the exact price), up to {@link BLOCK_TRAINING_MAX_BUZZ_PER_RUN} (5,000 Buzz);
1382
+ * an estimate above that is refused.
1383
+ */
1384
+ export interface WorkflowBodyTraining {
1385
+ kind: 'training';
1386
+ /** The handle `usePrepareTrainingDataset()` returned — `tds_` + 32 hex characters. */
1387
+ datasetId: string;
1388
+ /** The only engine accepted. */
1389
+ engine: 'ai-toolkit';
1390
+ /**
1391
+ * A base-model key from Civitai's training catalog (1…64 characters), resolved
1392
+ * and gated server-side. Custom AIRs are not accepted.
1393
+ */
1394
+ model: string;
1395
+ params: AiToolkitTrainingParams;
1396
+ /** At most {@link BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS} characters. Moderated like a prompt. */
1397
+ triggerWord: string;
1398
+ /**
1399
+ * At most {@link BLOCK_TRAINING_SAMPLE_PROMPTS_MAX} prompts of at most
1400
+ * {@link BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS} characters each. Moderated like
1401
+ * prompts.
1402
+ */
1403
+ samplePrompts: string[];
1404
+ /**
1405
+ * The quote this run is charged against — {@link BlockTrainingQuote.quoteId}
1406
+ * from the estimate (`tq_` + 32 hex characters). OMIT it on the estimate;
1407
+ * REQUIRED on `RUN_TRAINING`. The server checks the rest of the body against
1408
+ * the body that was quoted, so change nothing else between the two.
1409
+ */
1410
+ quoteId?: string;
1411
+ }
1252
1412
  /**
1253
1413
  * Body the block sends to `useBuzzWorkflow().{submit,estimate}`. A real
1254
1414
  * discriminated union keyed by `kind`:
@@ -1267,6 +1427,10 @@ export type WorkflowBodyPassThroughStep = {
1267
1427
  * an orchestrator `$type` directly and has the host forward `input`
1268
1428
  * unmodified. Bounded by a platform-internal denylist and by `maxBuzz`, not
1269
1429
  * by a registry.
1430
+ * - {@link WorkflowBodyTraining} (`kind: 'training'`) — an ai-toolkit LoRA run
1431
+ * on a server-prepared dataset of the viewer's own images. ESTIMATE ONLY
1432
+ * through `useBuzzWorkflow`; it runs through `useRunTraining()`, whose
1433
+ * host-chrome consent dialog is the only way to confirm its quote.
1270
1434
  *
1271
1435
  * `kind: 'step'` is therefore itself a union, discriminated on the PRESENCE of
1272
1436
  * `step` — the same nesting {@link WorkflowBodyCustomComfy} has on `mode`.
@@ -1283,7 +1447,7 @@ export type WorkflowBodyPassThroughStep = {
1283
1447
  * over `kind`. Host code that must handle every member gets a compile error
1284
1448
  * pointing at the new one, which is the intended behaviour.
1285
1449
  */
1286
- export type WorkflowBody = WorkflowBodyTextToImage | WorkflowBodyCustomComfy | WorkflowBodyStep | WorkflowBodyPassThroughStep;
1450
+ export type WorkflowBody = WorkflowBodyTextToImage | WorkflowBodyCustomComfy | WorkflowBodyStep | WorkflowBodyPassThroughStep | WorkflowBodyTraining;
1287
1451
  /**
1288
1452
  * The host-mediated view of an orchestrator workflow that an iframe block
1289
1453
  * receives over `postMessage`.
@@ -1493,6 +1657,33 @@ export interface BlockWorkflowSnapshot {
1493
1657
  * on any run nobody has started publishing.
1494
1658
  */
1495
1659
  publishedModel?: BlockPublishedModel;
1660
+ /**
1661
+ * The quote a {@link WorkflowBodyTraining} ESTIMATE produced — the orchestrator's
1662
+ * own price for exactly that run, stored server-side. Pass
1663
+ * `trainingQuote.quoteId` back as the body's `quoteId` on
1664
+ * `useRunTraining().runTraining()`; the consent dialog then shows the viewer
1665
+ * this price, read back from the server.
1666
+ *
1667
+ * `cost.total` on the same snapshot equals `trainingQuote.total`.
1668
+ * `expiresAt` is an ISO-8601 timestamp: after it the run is refused and you
1669
+ * estimate again. `imageCount` is the server-derived size of the prepared
1670
+ * dataset, never a number the block sent.
1671
+ *
1672
+ * Present ONLY on the reply to a `kind: 'training'` estimate. Absent on hosts
1673
+ * that predate civitai/civitai#5434.
1674
+ */
1675
+ trainingQuote?: BlockTrainingQuote;
1676
+ }
1677
+ /** {@link BlockWorkflowSnapshot.trainingQuote}. */
1678
+ export interface BlockTrainingQuote {
1679
+ /** `tq_` + 32 hex characters. Opaque — pass it back verbatim. */
1680
+ quoteId: string;
1681
+ /** The quoted price, in Buzz. */
1682
+ total: number;
1683
+ /** How many images the prepared dataset holds. */
1684
+ imageCount: number;
1685
+ /** ISO-8601. The quote cannot be run after this. */
1686
+ expiresAt: string;
1496
1687
  }
1497
1688
  /**
1498
1689
  * Why the host substituted a checkpoint version — see
@@ -1534,7 +1725,7 @@ export interface BlockStepToolCall {
1534
1725
  arguments: string;
1535
1726
  };
1536
1727
  }
1537
- /** One entry of {@link BlockWorkflowSnapshot.trainedEpochs}. */
1728
+ /** One entry of {@link BlockWorkflowSnapshot.trainedEpochs} / {@link AppWorkflow.trainedEpochs}. */
1538
1729
  export interface BlockTrainedEpoch {
1539
1730
  /**
1540
1731
  * Which pass-through training `$type` produced this epoch. The SDK's inbound
@@ -1742,11 +1933,13 @@ export interface BlockManifestV1 {
1742
1933
  */
1743
1934
  $schema?: 'https://civitai.com/schemas/app-block/v1.json' | (string & {});
1744
1935
  /**
1745
- * NOT a canonical manifest property, and NOT validated. Your app id lives in
1746
- * `civitai.app.json` (`{"appId": "..."}`), which is what the `civitai` CLI
1747
- * reads. The canonical does not forbid extra top-level keys, so the server
1748
- * ignores this one; the scaffolds still carry `"app_REPLACE_ME"` and it is
1749
- * inert.
1936
+ * NOT a canonical manifest property, and NOT validated. Nothing reads it: the
1937
+ * canonical does not forbid extra top-level keys, so the server ignores this
1938
+ * one, and the Go `civitai` CLI identifies an app by the manifest's
1939
+ * `blockId` (its `app submit` keys the submission on it). There is no
1940
+ * separate app-id file either — an earlier version of this comment pointed at
1941
+ * a `civitai.app.json`, which no current tool reads. Scaffolds that still
1942
+ * carry `"app_REPLACE_ME"` here are carrying an inert key.
1750
1943
  */
1751
1944
  appId?: string;
1752
1945
  blockId: string;
@@ -2081,6 +2274,18 @@ export interface AppWorkflow {
2081
2274
  * every workflow nobody has published.
2082
2275
  */
2083
2276
  publishedModel?: BlockPublishedModel;
2277
+ /**
2278
+ * The epochs of a pass-through `training` / `imageResourceTraining` step in
2279
+ * this workflow that produced a checkpoint. Same shape and rule as
2280
+ * {@link BlockWorkflowSnapshot.trainedEpochs}: only runs whose training
2281
+ * moderation status is approved are listed, the checkpoint itself is not
2282
+ * exposed, and the field is OMITTED when there are no epochs to list. Lets a
2283
+ * block offer "publish epoch n" from its queue without polling each run.
2284
+ *
2285
+ * 🔴 REQUIRES A HOST VERSION THAT EMITS IT. Absent on older hosts — treat
2286
+ * absence as "unknown", not as "no checkpoint".
2287
+ */
2288
+ trainedEpochs?: BlockTrainedEpoch[];
2084
2289
  }
2085
2290
  /**
2086
2291
  * Per-viewer gated projection of ONE image returned by `GET_IMAGES_BY_IDS`
@@ -2297,5 +2502,102 @@ export interface BlockCreatePostResult {
2297
2502
  * write, so a dismissal mid-write can never claim this code.
2298
2503
  */
2299
2504
  export type BlockCreatePostHostError = 'review-mode' | 'block is not ready' | 'sign in to post' | 'no images to post' | 'no block token' | 'declined';
2505
+ /**
2506
+ * One image of a training dataset request — an `Image` id the VIEWER owns, and
2507
+ * the caption to train it with.
2508
+ *
2509
+ * Host bounds (`blockTrainingDatasetItemsSchema`): `imageId` a positive integer,
2510
+ * `caption` at most {@link BLOCK_TRAINING_CAPTION_MAX_CHARS} characters, no other
2511
+ * keys; 1…{@link BLOCK_TRAINING_DATASET_MAX_ITEMS} items per request. A payload
2512
+ * outside them is refused by the host with `invalid training dataset` before any
2513
+ * server call. Captions are moderated like prompts.
2514
+ */
2515
+ export interface BlockTrainingDatasetItem {
2516
+ imageId: number;
2517
+ caption: string;
2518
+ }
2519
+ /**
2520
+ * Why the server left an image out of a prepared dataset. Mirrors
2521
+ * civitai/civitai's `BlockTrainingRejectionReason`.
2522
+ *
2523
+ * - `unavailable` — not found, OR not the viewer's own image. One reason for
2524
+ * both, on purpose: the server will not tell an app whether someone else's
2525
+ * image id exists.
2526
+ * - `unsupported-media` — a video or audio row.
2527
+ * - `not-eligible` — refused by moderation state or the page's maturity ceiling.
2528
+ * - `pending-scan` — not scanned yet; retry once the scan finishes.
2529
+ * - `import-failed` — the orchestrator did not accept the image.
2530
+ * - `import-unavailable` — the import timed out or the orchestrator was
2531
+ * unavailable; retryable.
2532
+ *
2533
+ * The SDK's inbound validator checks `reason` only as a STRING, so a reason the
2534
+ * host adds later still arrives — give an exhaustive `switch` a default branch.
2535
+ */
2536
+ export type BlockTrainingRejectionReason = 'unavailable' | 'unsupported-media' | 'not-eligible' | 'pending-scan' | 'import-failed' | 'import-unavailable';
2537
+ /**
2538
+ * The success payload of `TRAINING_DATASET_RESULT` — what
2539
+ * `usePrepareTrainingDataset().prepareDataset()` resolves with.
2540
+ */
2541
+ export interface BlockTrainingDatasetResult {
2542
+ /**
2543
+ * Opaque handle for {@link WorkflowBodyTraining.datasetId} (`tds_` + 32 hex
2544
+ * characters), bound to this viewer, app and install.
2545
+ */
2546
+ datasetId: string;
2547
+ /**
2548
+ * How many images were ADMITTED — always at least 1: when nothing is admitted
2549
+ * the server refuses the request instead of returning an empty dataset. The
2550
+ * server derives it; a run trains on exactly these.
2551
+ */
2552
+ count: number;
2553
+ /** The images left out, each with its reason. Empty when every image was admitted. */
2554
+ rejected: Array<{
2555
+ imageId: number;
2556
+ reason: BlockTrainingRejectionReason;
2557
+ }>;
2558
+ }
2559
+ /**
2560
+ * The HOST'S OWN refusal codes on a `TRAINING_DATASET_RESULT`'s `error`. Mirrors
2561
+ * civitai/civitai's `PREPARE_TRAINING_DATASET_HOST_ERRORS`
2562
+ * (`src/components/AppBlocks/prepareTrainingDatasetGate.ts`, civitai/civitai#5438).
2563
+ *
2564
+ * 🔴 THE SET A BLOCK MAY RELY ON, NOT THE SET IT MAY RECEIVE: `error` also carries
2565
+ * any server message verbatim (`training from apps is not enabled`, a rate limit,
2566
+ * a moderation refusal of a caption). Compare by EQUALITY against these members
2567
+ * and treat anything else as an opaque server message.
2568
+ *
2569
+ * - `review-mode` — a mod-review sandbox with "run for real" off.
2570
+ * - `block is not ready` — the host handshake has not completed.
2571
+ * - `sign in to train` — anonymous viewer; there are no images of theirs to read.
2572
+ * - `invalid training dataset` — `items` failed the host's item schema (shape,
2573
+ * count or caption length). Nothing was sent to the server.
2574
+ * - `no block token` — the host holds no block token yet.
2575
+ */
2576
+ export type BlockPrepareTrainingDatasetHostError = 'review-mode' | 'block is not ready' | 'sign in to train' | 'invalid training dataset' | 'no block token';
2577
+ /**
2578
+ * The HOST'S OWN refusal codes on a `TRAINING_RESULT`'s `error`. Mirrors
2579
+ * civitai/civitai's `RUN_TRAINING_HOST_ERRORS`
2580
+ * (`src/components/AppBlocks/runTrainingGate.ts`).
2581
+ *
2582
+ * 🔴 THE SET A BLOCK MAY RELY ON, NOT THE SET IT MAY RECEIVE: server messages
2583
+ * (an expired or already-used quote, a body that differs from the quoted one, an
2584
+ * ineligible dataset image) arrive verbatim in the same field.
2585
+ *
2586
+ * - `review-mode` — a mod-review sandbox with "run for real" off.
2587
+ * - `block is not ready` — the host handshake has not completed.
2588
+ * - `sign in to train` — anonymous viewer; there is no account to charge.
2589
+ * - `invalid training request` — the body is not a `kind: 'training'` body
2590
+ * naming a `quoteId`.
2591
+ * - `no block token` — the host holds no block token yet.
2592
+ * - `declined` — the viewer dismissed the consent dialog. 🔴 GUARANTEED to mean
2593
+ * NO RUN WAS SUBMITTED.
2594
+ * - `submission-unconfirmed` — the submit was SENT but its outcome is unknown
2595
+ * (the connection dropped, or the server attempted the run and could not
2596
+ * confirm it). 🔴 The run MAY BE RUNNING AND CHARGED. Check the viewer's
2597
+ * trainings (`useAppWorkflows()`, or send them to their Training page) before
2598
+ * offering a retry — a retry of the same body after the server did start the
2599
+ * run is a SECOND, separately charged run.
2600
+ */
2601
+ export type BlockRunTrainingHostError = 'review-mode' | 'block is not ready' | 'sign in to train' | 'invalid training request' | 'no block token' | 'declined' | 'submission-unconfirmed';
2300
2602
  export {};
2301
2603
  //# sourceMappingURL=types.d.ts.map
@@ -114,4 +114,30 @@ export function isPageSlotContext(ctx) {
114
114
  export function isSignedIn(viewer) {
115
115
  return viewer !== null && viewer !== undefined;
116
116
  }
117
+ // ============================================================
118
+ // App Blocks TRAINING kind (`kind: 'training'`)
119
+ // ============================================================
120
+ //
121
+ // 🔴 MIRRORED FROM THE HOST, NOT DESIGNED HERE. Every type in this section copies
122
+ // civitai/civitai's zod at `a32f506516` (main, after civitai/civitai#5434):
123
+ // `blockTrainingBodySchema` in `src/server/schema/blocks/workflow.schema.ts`, and
124
+ // `aiToolkitTrainingParamsSchema` in `src/server/schema/orchestrator/training.schema.ts`
125
+ // for `params`. The host's zod is the definition; this is the copy. A field the
126
+ // host adds is invisible here until this file is updated, and the host rejects a
127
+ // body that does not parse — so a mismatch shows up as a refused estimate, never
128
+ // as a silently different run.
129
+ /** Ceiling on what ONE confirmed `kind: 'training'` run may cost, in Buzz. Host constant `BLOCK_TRAINING_MAX_BUZZ_PER_RUN`. */
130
+ export const BLOCK_TRAINING_MAX_BUZZ_PER_RUN = 5000;
131
+ /** Max images in one prepared training dataset. Host constant `BLOCK_TRAINING_DATASET_MAX_ITEMS`. */
132
+ export const BLOCK_TRAINING_DATASET_MAX_ITEMS = 50;
133
+ /** Max characters of one dataset caption. Host constant `BLOCK_TRAINING_CAPTION_MAX_CHARS`. */
134
+ export const BLOCK_TRAINING_CAPTION_MAX_CHARS = 1000;
135
+ /** Max sample prompts on a training body. Host constant `BLOCK_TRAINING_SAMPLE_PROMPTS_MAX`. */
136
+ export const BLOCK_TRAINING_SAMPLE_PROMPTS_MAX = 6;
137
+ /** Max characters of one sample prompt. Host constant `BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS`. */
138
+ export const BLOCK_TRAINING_SAMPLE_PROMPT_MAX_CHARS = 1000;
139
+ /** Max characters of the trigger word. Host constant `BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS`. */
140
+ export const BLOCK_TRAINING_TRIGGER_WORD_MAX_CHARS = 64;
141
+ /** Max characters of the base-model key. Host constant `BLOCK_TRAINING_MODEL_KEY_MAX_CHARS`. */
142
+ export const BLOCK_TRAINING_MODEL_KEY_MAX_CHARS = 64;
117
143
  //# sourceMappingURL=types.js.map
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
- * 135 symbols and that side effect into the root would make every OAuth app
28
+ * 154 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 135 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 154 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
- * 135 symbols and that side effect into the root would make every OAuth app
28
+ * 154 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 135 is ASSERTED, not merely quoted — by
32
+ * 🔴 That 154 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.57.0",
3
+ "version": "0.59.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",
@@ -101,13 +101,14 @@
101
101
  "collections:read:private",
102
102
  "posts:write:self",
103
103
  "goods:read:self",
104
- "goods:purchase:self"
104
+ "goods:purchase:self",
105
+ "apps:store:items:write"
105
106
  ]
106
107
  }
107
108
  },
108
109
  "scopeJustifications": {
109
110
  "type": "object",
110
- "description": "Per-scope justification: a map of scope-id → free-text rationale explaining WHY the app needs that permission, shown to the moderator during review. REQUIRED for SENSITIVE scopes — any declared scope that can spend or read the viewer's Buzz, read the viewer's private data, or write data other users see (e.g. `ai:write:budgeted`, `social:tip:self`, `goods:purchase:self`, `buzz:read:self`, `collections:read:private`, `apps:storage:shared:write`, `posts:write:self`) MUST carry a non-empty justification here, or the manifest is rejected at submit time. OPTIONAL for non-sensitive scopes — omit those and the manifest stays valid. Every key MUST be a scope also present in `scopes` (justifications for scopes you don't request are rejected). Each value is a non-empty string of at most 500 characters. The requirement is enforced imperatively by the manifest validator (not expressed as JSON-Schema conditionals here). NOTE: the justification captures the developer's STATED rationale only; the platform does not verify the truth of the claims.",
111
+ "description": "Per-scope justification: a map of scope-id → free-text rationale explaining WHY the app needs that permission, shown to the moderator during review. REQUIRED for SENSITIVE scopes — any declared scope that can spend or read the viewer's Buzz, read the viewer's private data, or write data other users see (e.g. `ai:write:budgeted`, `social:tip:self`, `goods:purchase:self`, `buzz:read:self`, `collections:read:private`, `apps:storage:shared:write`, `posts:write:self`, `apps:store:items:write`) MUST carry a non-empty justification here, or the manifest is rejected at submit time. OPTIONAL for non-sensitive scopes — omit those and the manifest stays valid. Every key MUST be a scope also present in `scopes` (justifications for scopes you don't request are rejected). Each value is a non-empty string of at most 500 characters. The requirement is enforced imperatively by the manifest validator (not expressed as JSON-Schema conditionals here). NOTE: the justification captures the developer's STATED rationale only; the platform does not verify the truth of the claims.",
111
112
  "additionalProperties": {
112
113
  "type": "string",
113
114
  "minLength": 1,