@civitai/sdk 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/BREAKING.md +69 -0
  2. package/README.md +186 -0
  3. package/dist/app/index.d.ts +50 -0
  4. package/dist/app/index.js +64 -0
  5. package/dist/core/errors.d.ts +19 -0
  6. package/dist/core/errors.js +42 -0
  7. package/dist/core/get-transport.d.ts +11 -0
  8. package/dist/core/get-transport.js +101 -0
  9. package/dist/core/handshake.d.ts +135 -0
  10. package/dist/core/handshake.js +60 -0
  11. package/dist/core/messaging.d.ts +30 -0
  12. package/dist/core/messaging.js +23 -0
  13. package/dist/core/origin-matcher.d.ts +12 -0
  14. package/dist/core/origin-matcher.js +71 -0
  15. package/dist/core/transport.d.ts +60 -0
  16. package/dist/core/transport.js +37 -0
  17. package/dist/core/transports/iframe-transport.d.ts +27 -0
  18. package/dist/core/transports/iframe-transport.js +342 -0
  19. package/dist/host/index.d.ts +47 -0
  20. package/dist/host/index.js +92 -0
  21. package/dist/host/protocol.d.ts +86 -0
  22. package/dist/host/protocol.js +1 -0
  23. package/dist/http/index.d.ts +23 -0
  24. package/dist/http/index.js +76 -0
  25. package/dist/index.d.ts +23 -0
  26. package/dist/index.js +12 -0
  27. package/dist/orchestration/index.d.ts +53 -0
  28. package/dist/orchestration/index.js +81 -0
  29. package/dist/orchestration/steps.generated.d.ts +5 -0
  30. package/dist/orchestration/steps.generated.js +2 -0
  31. package/dist/session/index.d.ts +29 -0
  32. package/dist/session/index.js +23 -0
  33. package/dist/sign-in/index.d.ts +45 -0
  34. package/dist/sign-in/index.js +209 -0
  35. package/dist/site/index.d.ts +12 -0
  36. package/dist/site/index.js +8 -0
  37. package/dist/testing.d.ts +36 -0
  38. package/dist/testing.js +108 -0
  39. package/package.json +68 -0
@@ -0,0 +1,47 @@
1
+ import { type BlockTransport } from '../core/transport.js';
2
+ import type { Session } from '../session/index.js';
3
+ import type { DownloadRequest, PickedResource, ResourcePickerType } from './protocol.js';
4
+ export type { ConsentRefusal, DownloadRequest, PickedResource, ResourcePickerType, } from './protocol.js';
5
+ export interface HostCallOptions {
6
+ signal?: AbortSignal;
7
+ }
8
+ /** Asking the host page to show its own UI. Data goes over the API, not here. */
9
+ export interface Host {
10
+ /** Resizes the frame, clamped to the manifest's bounds. */
11
+ resize(height: number): void;
12
+ /** Keeps the frame as tall as `element` (the body by default). Returns a stop function. */
13
+ autoResize(element?: Element): () => void;
14
+ /** `fatal` swaps the block for the host's fallback, for a block that cannot continue. */
15
+ reportError(message: string, args?: {
16
+ fatal?: boolean;
17
+ }): void;
18
+ /** Deep-links within this app's own sub-paths; the host refuses anything else. */
19
+ navigate(path: string, args?: {
20
+ target?: 'current' | 'new_tab';
21
+ }): void;
22
+ /** Fires `false` when the page hides and `true` when it returns. */
23
+ onVisibilityChange(handler: (visible: boolean) => void): () => void;
24
+ /** Starts sign-in; the block re-initialises as a signed-in viewer. */
25
+ requestSignIn(args?: {
26
+ returnUrl?: string;
27
+ }): void;
28
+ /**
29
+ * Saves to the viewer's device through the host, because a sandboxed frame
30
+ * cannot. The host fetches only from origins it allowlists.
31
+ */
32
+ download(request: DownloadRequest, opts?: HostCallOptions): Promise<void>;
33
+ /** civitai's own picker. Resolves the one resource chosen, or `null` if dismissed. */
34
+ openResourcePicker(args: {
35
+ resourceType: ResourcePickerType;
36
+ baseModelGroup?: string;
37
+ }, opts?: HostCallOptions): Promise<PickedResource | null>;
38
+ /** `suggestedAmount` is a starting point the viewer can change. */
39
+ openBuzzPurchase(args?: {
40
+ suggestedAmount?: number;
41
+ }, opts?: HostCallOptions): Promise<{
42
+ purchased: boolean;
43
+ }>;
44
+ }
45
+ export declare function createHost(transport: BlockTransport): Host;
46
+ /** The token the host minted for this block, kept current as the host rotates it. */
47
+ export declare function createHostSession(transport: BlockTransport): Session;
@@ -0,0 +1,92 @@
1
+ import { createCaller, createListener, createNotifier } from '../core/messaging.js';
2
+ import { tokenFromWrapped } from '../core/transport.js';
3
+ const call = createCaller();
4
+ const notify = createNotifier();
5
+ const on = createListener();
6
+ export function createHost(transport) {
7
+ return {
8
+ resize: (height) => notify('RESIZE_IFRAME', { height }, { transport }),
9
+ autoResize(element) {
10
+ const target = element ?? globalThis.document?.body;
11
+ if (!target || typeof ResizeObserver === 'undefined')
12
+ return () => { };
13
+ let sent = -1;
14
+ const report = () => {
15
+ const height = Math.ceil(target.getBoundingClientRect().height);
16
+ if (height === sent)
17
+ return;
18
+ sent = height;
19
+ notify('RESIZE_IFRAME', { height }, { transport });
20
+ };
21
+ const observer = new ResizeObserver(report);
22
+ observer.observe(target);
23
+ report();
24
+ return () => observer.disconnect();
25
+ },
26
+ reportError: (message, args = {}) => notify('BLOCK_ERROR', { message, fatal: args.fatal ?? false }, { transport }),
27
+ navigate: (path, args = {}) => notify('NAVIGATE', { path, target: args.target ?? 'current' }, { transport }),
28
+ onVisibilityChange(handler) {
29
+ const offSuspend = on(transport, 'SUSPEND', () => handler(false));
30
+ const offResume = on(transport, 'RESUME', () => handler(true));
31
+ return () => {
32
+ offSuspend();
33
+ offResume();
34
+ };
35
+ },
36
+ requestSignIn: (args = {}) => notify('REQUEST_SIGN_IN', args, { transport }),
37
+ async download(request, opts = {}) {
38
+ await call('SAVE_IMAGE', request, { ...opts, transport });
39
+ },
40
+ async openResourcePicker(args, opts = {}) {
41
+ return (await call('OPEN_RESOURCE_PICKER', args, { ...opts, transport })).selected ?? null;
42
+ },
43
+ async openBuzzPurchase(args = {}, opts = {}) {
44
+ const { purchased } = await call('OPEN_BUZZ_PURCHASE', args, { ...opts, transport });
45
+ return { purchased };
46
+ },
47
+ };
48
+ }
49
+ /** The token the host minted for this block, kept current as the host rotates it. */
50
+ export function createHostSession(transport) {
51
+ return {
52
+ async getToken({ fresh, signal } = {}) {
53
+ if (!fresh)
54
+ return transport.snapshot.get().token.raw;
55
+ const { blockInstanceId } = transport.snapshot.get();
56
+ const { token } = await call('REQUEST_TOKEN', { blockInstanceId }, { transport, signal });
57
+ return tokenFromWrapped(token).raw;
58
+ },
59
+ requestGrants: (scopes, opts = {}) => requestGrants(transport, scopes, opts),
60
+ };
61
+ }
62
+ /**
63
+ * The host re-mints the token once the viewer consents, so a grant arrives as
64
+ * a token carrying the scopes. A viewer who closes the dialog sends nothing,
65
+ * so pass a `signal` to bound the wait.
66
+ */
67
+ function requestGrants(transport, scopes, { signal }) {
68
+ const granted = () => {
69
+ const held = new Set(transport.snapshot.get().token.scopes);
70
+ return scopes.every((scope) => held.has(scope));
71
+ };
72
+ if (granted())
73
+ return Promise.resolve(true);
74
+ if (signal?.aborted)
75
+ return Promise.reject(signal.reason);
76
+ return new Promise((resolve, reject) => {
77
+ const release = [];
78
+ const settle = (finish) => {
79
+ for (const off of release)
80
+ off();
81
+ finish();
82
+ };
83
+ release.push(transport.snapshot.subscribe(() => granted() && settle(() => resolve(true))));
84
+ release.push(on(transport, 'CONSENT_UNAVAILABLE', () => settle(() => resolve(false))));
85
+ if (signal) {
86
+ const onAbort = () => settle(() => reject(signal.reason));
87
+ signal.addEventListener('abort', onAbort, { once: true });
88
+ release.push(() => signal.removeEventListener('abort', onAbort));
89
+ }
90
+ notify('REQUEST_CONSENT', { scopes: [...scopes] }, { transport });
91
+ });
92
+ }
@@ -0,0 +1,86 @@
1
+ import type { WrappedToken } from '../core/handshake.js';
2
+ export type ResourcePickerType = 'Checkpoint' | 'LORA';
3
+ /** The one resource the viewer picked. Discovery only: nothing here is an entitlement. */
4
+ export interface PickedResource {
5
+ versionId: number;
6
+ modelId: number;
7
+ modelName: string;
8
+ versionName: string;
9
+ baseModel: string;
10
+ modelType: string;
11
+ strength?: number;
12
+ minStrength?: number;
13
+ maxStrength?: number;
14
+ trainedWords?: string[];
15
+ clipSkip?: number | null;
16
+ }
17
+ export interface DownloadRequest {
18
+ /** An own-output URL. The host allowlists the origin it will fetch. */
19
+ url: string;
20
+ filename?: string;
21
+ }
22
+ /** Why consent can never be granted in this environment. */
23
+ export type ConsentRefusal = 'ungrantable';
24
+ export type HostRequests = {
25
+ SAVE_IMAGE: {
26
+ params: DownloadRequest;
27
+ result: {
28
+ ok?: boolean;
29
+ };
30
+ };
31
+ OPEN_RESOURCE_PICKER: {
32
+ params: {
33
+ resourceType: ResourcePickerType;
34
+ baseModelGroup?: string;
35
+ };
36
+ result: {
37
+ selected?: PickedResource;
38
+ };
39
+ };
40
+ OPEN_BUZZ_PURCHASE: {
41
+ params: {
42
+ suggestedAmount?: number;
43
+ };
44
+ result: {
45
+ purchased: boolean;
46
+ newBalance?: number;
47
+ };
48
+ };
49
+ REQUEST_TOKEN: {
50
+ params: {
51
+ blockInstanceId: string;
52
+ };
53
+ result: {
54
+ token: WrappedToken;
55
+ };
56
+ };
57
+ };
58
+ export type HostNotifications = {
59
+ RESIZE_IFRAME: {
60
+ height: number;
61
+ };
62
+ BLOCK_ERROR: {
63
+ message: string;
64
+ fatal: boolean;
65
+ };
66
+ NAVIGATE: {
67
+ path: string;
68
+ target: 'current' | 'new_tab';
69
+ };
70
+ REQUEST_SIGN_IN: {
71
+ returnUrl?: string;
72
+ };
73
+ REQUEST_CONSENT: {
74
+ scopes?: string[];
75
+ };
76
+ };
77
+ export type HostPushes = {
78
+ /** The page is hidden; stop timers and polling until `RESUME`. */
79
+ SUSPEND: undefined;
80
+ RESUME: undefined;
81
+ /** `scopes` is advisory and may be empty; the message itself is the refusal. */
82
+ CONSENT_UNAVAILABLE: {
83
+ reason: ConsentRefusal;
84
+ scopes: string[];
85
+ };
86
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { CivitaiError } from '../core/errors.js';
2
+ import type { Session } from '../session/index.js';
3
+ export type QueryValue = string | number | boolean | null | undefined;
4
+ export type Query = Record<string, QueryValue | readonly QueryValue[]>;
5
+ export interface RequestOptions {
6
+ query?: Query;
7
+ body?: unknown;
8
+ signal?: AbortSignal;
9
+ }
10
+ export declare class ApiError extends CivitaiError {
11
+ readonly status: number;
12
+ /** The response body, parsed as JSON when it was JSON. */
13
+ readonly body: unknown;
14
+ constructor(status: number, message: string, body: unknown);
15
+ }
16
+ export interface HttpOptions {
17
+ session: Session;
18
+ baseUrl: string;
19
+ fetch?: typeof fetch;
20
+ }
21
+ export type Http = <T>(method: string, path: string, opts?: RequestOptions) => Promise<T>;
22
+ /** A JSON client that sends the session's token and survives one expired token. */
23
+ export declare function createHttp(options: HttpOptions): Http;
@@ -0,0 +1,76 @@
1
+ import { CivitaiError } from '../core/errors.js';
2
+ export class ApiError extends CivitaiError {
3
+ status;
4
+ /** The response body, parsed as JSON when it was JSON. */
5
+ body;
6
+ constructor(status, message, body) {
7
+ super(message);
8
+ this.name = 'ApiError';
9
+ this.status = status;
10
+ this.body = body;
11
+ }
12
+ }
13
+ /** A JSON client that sends the session's token and survives one expired token. */
14
+ export function createHttp(options) {
15
+ let baseUrl = options.baseUrl;
16
+ while (baseUrl.endsWith('/'))
17
+ baseUrl = baseUrl.slice(0, -1);
18
+ const send = (method, path, opts, token) => {
19
+ const doFetch = options.fetch ?? globalThis.fetch;
20
+ const headers = { Authorization: `Bearer ${token}` };
21
+ let body;
22
+ if (opts.body !== undefined) {
23
+ headers['Content-Type'] = 'application/json';
24
+ body = JSON.stringify(opts.body);
25
+ }
26
+ return doFetch(urlFor(baseUrl, path, opts.query), { method, headers, body, signal: opts.signal });
27
+ };
28
+ return async (method, path, opts = {}) => {
29
+ const { session } = options;
30
+ let response = await send(method, path, opts, await session.getToken({ signal: opts.signal }));
31
+ // One retry: a token that expired in flight is routine, a second refusal is not.
32
+ if (response.status === 401) {
33
+ const fresh = await session.getToken({ fresh: true, signal: opts.signal });
34
+ response = await send(method, path, opts, fresh);
35
+ }
36
+ return read(response);
37
+ };
38
+ }
39
+ function urlFor(baseUrl, path, query) {
40
+ const url = new URL(`${baseUrl}/${path.replace(/^\/+/, '')}`);
41
+ for (const [key, value] of Object.entries(query ?? {})) {
42
+ const values = Array.isArray(value) ? value : [value];
43
+ for (const item of values) {
44
+ if (item != null)
45
+ url.searchParams.append(key, String(item));
46
+ }
47
+ }
48
+ return url.toString();
49
+ }
50
+ async function read(response) {
51
+ const text = await response.text();
52
+ let body = text;
53
+ if (text !== '') {
54
+ try {
55
+ body = JSON.parse(text);
56
+ }
57
+ catch {
58
+ // Not JSON; keep the text so an error can still say what came back.
59
+ }
60
+ }
61
+ if (!response.ok) {
62
+ throw new ApiError(response.status, messageOf(body) ?? response.statusText, body);
63
+ }
64
+ return (text === '' ? undefined : body);
65
+ }
66
+ /** civitai reports `{ error }` or `{ message }`; the orchestrator reports problem details. */
67
+ function messageOf(body) {
68
+ if (body == null || typeof body !== 'object')
69
+ return undefined;
70
+ const fields = body;
71
+ for (const key of ['error', 'message', 'detail', 'title']) {
72
+ if (typeof fields[key] === 'string')
73
+ return fields[key];
74
+ }
75
+ return undefined;
76
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `const app = await initialize()`, then `app.site`, `app.orchestration` and,
3
+ * inside a civitai.com page, `app.host`. Outside one, `createSignIn()` gets the token.
4
+ */
5
+ export { initialize } from './app/index.js';
6
+ export type { AppClient, BlockAppClient, BlockInitializeOptions, TokenInitializeOptions, } from './app/index.js';
7
+ export { createSignIn, SignInError } from './sign-in/index.js';
8
+ export type { SignIn, SignInOptions } from './sign-in/index.js';
9
+ export { SCOPES } from './session/index.js';
10
+ export type { GrantOptions, Scope, TokenOptions, TokenSource } from './session/index.js';
11
+ export type { SiteClient } from './site/index.js';
12
+ export { isTerminal } from './orchestration/index.js';
13
+ export type { OrchestrationClient, Step, StepTemplate, WaitOptions, Workflow, WorkflowPage, WorkflowQuery, WorkflowStatus, WorkflowTemplate, } from './orchestration/index.js';
14
+ export { createHost } from './host/index.js';
15
+ export type { ConsentRefusal, DownloadRequest, Host, HostCallOptions, PickedResource, ResourcePickerType, } from './host/index.js';
16
+ export { ApiError } from './http/index.js';
17
+ export type { Query, QueryValue, RequestOptions } from './http/index.js';
18
+ export { BridgeError, CivitaiError } from './core/errors.js';
19
+ export type { BridgeErrorCode, BridgeFailureCode } from './core/errors.js';
20
+ export { getTransport } from './core/get-transport.js';
21
+ export type { DetectOptions } from './core/get-transport.js';
22
+ export type { BlockSnapshot, BlockTransport } from './core/transport.js';
23
+ export type { BlockCheckpointInfo, BlockContext, BlockSettings, BlockToken, ColorDomain, ModelSlotContext, ModelSlotId, PageSlotContext, PageSlotId, ShowcaseImage, Theme, UnknownSlotContext, ViewerInfo, } from './core/handshake.js';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `const app = await initialize()`, then `app.site`, `app.orchestration` and,
3
+ * inside a civitai.com page, `app.host`. Outside one, `createSignIn()` gets the token.
4
+ */
5
+ export { initialize } from './app/index.js';
6
+ export { createSignIn, SignInError } from './sign-in/index.js';
7
+ export { SCOPES } from './session/index.js';
8
+ export { isTerminal } from './orchestration/index.js';
9
+ export { createHost } from './host/index.js';
10
+ export { ApiError } from './http/index.js';
11
+ export { BridgeError, CivitaiError } from './core/errors.js';
12
+ export { getTransport } from './core/get-transport.js';
@@ -0,0 +1,53 @@
1
+ import type { Workflow as GeneratedWorkflow, WorkflowStatus, WorkflowTemplate as GeneratedWorkflowTemplate } from '@civitai/orchestration-client/dist/generated/types.gen.js';
2
+ import { type Http } from '../http/index.js';
3
+ import type { Step, StepTemplate } from './steps.generated.js';
4
+ export type { Step, StepTemplate, WorkflowStatus };
5
+ /** What to run. `currencies` defaults server-side, so it is optional here. */
6
+ export type WorkflowTemplate = Omit<GeneratedWorkflowTemplate, 'steps' | 'currencies'> & {
7
+ steps: StepTemplate[];
8
+ currencies?: GeneratedWorkflowTemplate['currencies'];
9
+ };
10
+ export type Workflow = Omit<GeneratedWorkflow, 'steps'> & {
11
+ readonly steps: Step[];
12
+ };
13
+ export interface WorkflowPage {
14
+ next: string;
15
+ items: Workflow[];
16
+ }
17
+ export declare const DEFAULT_ORCHESTRATION_URL = "https://orchestration.civitai.com";
18
+ export interface CallOptions {
19
+ signal?: AbortSignal;
20
+ }
21
+ export interface WaitOptions extends CallOptions {
22
+ /** Seconds the orchestrator may hold the reply. */
23
+ wait?: number;
24
+ /** What ends the hold early: the workflow finishing (the default), or any change to it. */
25
+ until?: 'completion' | 'change';
26
+ }
27
+ export interface WorkflowQuery {
28
+ cursor?: string;
29
+ take?: number;
30
+ tags?: string[];
31
+ excludeFailed?: boolean;
32
+ }
33
+ /**
34
+ * Workflows on the orchestrator, as the viewer. Steps are the orchestrator's
35
+ * own `WorkflowStepTemplate`s, so a step type it gains needs no release here.
36
+ */
37
+ export interface OrchestrationClient {
38
+ submitWorkflow(template: WorkflowTemplate, opts?: WaitOptions): Promise<Workflow>;
39
+ /** Prices a workflow without running it. */
40
+ estimateWorkflow(template: WorkflowTemplate, opts?: CallOptions): Promise<Workflow>;
41
+ getWorkflow(workflowId: string, opts?: WaitOptions): Promise<Workflow>;
42
+ /**
43
+ * Yields the workflow now and again each time it changes, ending at a final
44
+ * status. Break out of the loop to stop watching.
45
+ */
46
+ watchWorkflow(workflowId: string, opts?: CallOptions): AsyncGenerator<Workflow, void, undefined>;
47
+ /** Resolves once the workflow reaches a final status, whichever it is. */
48
+ waitForWorkflow(workflowId: string, opts?: CallOptions): Promise<Workflow>;
49
+ cancelWorkflow(workflowId: string, opts?: CallOptions): Promise<void>;
50
+ queryWorkflows(query?: WorkflowQuery, opts?: CallOptions): Promise<WorkflowPage>;
51
+ }
52
+ export declare function isTerminal(workflow: Workflow): boolean;
53
+ export declare function createOrchestrationClient(http: Http): OrchestrationClient;
@@ -0,0 +1,81 @@
1
+ import { ApiError } from '../http/index.js';
2
+ export const DEFAULT_ORCHESTRATION_URL = 'https://orchestration.civitai.com';
3
+ const WORKFLOWS = 'v2/consumer/workflows';
4
+ const TERMINAL = new Set([
5
+ 'succeeded',
6
+ 'failed',
7
+ 'canceled',
8
+ 'expired',
9
+ ]);
10
+ /** How long the orchestrator holds each read while watching, in seconds. */
11
+ const HELD_READ_SECONDS = 20;
12
+ /** Waits between consecutive failed reads while watching; a success resets it. */
13
+ const RETRY_BACKOFF_MS = [250, 1_000, 4_000];
14
+ export function isTerminal(workflow) {
15
+ return TERMINAL.has(workflow.status);
16
+ }
17
+ export function createOrchestrationClient(http) {
18
+ const getWorkflow = (workflowId, { wait, until, signal } = {}) => http('GET', `${WORKFLOWS}/${encodeURIComponent(workflowId)}`, {
19
+ query: { wait, until },
20
+ signal,
21
+ });
22
+ async function* watchWorkflow(workflowId, { signal } = {}) {
23
+ let seen;
24
+ let failures = 0;
25
+ let held = false;
26
+ for (;;) {
27
+ let workflow;
28
+ try {
29
+ workflow = await getWorkflow(workflowId, held ? { wait: HELD_READ_SECONDS, until: 'change', signal } : { signal });
30
+ failures = 0;
31
+ }
32
+ catch (error) {
33
+ if (signal?.aborted || !isTransient(error) || failures >= RETRY_BACKOFF_MS.length)
34
+ throw error;
35
+ await sleep(RETRY_BACKOFF_MS[failures++], signal);
36
+ continue;
37
+ }
38
+ held = true;
39
+ // Compared whole, so anything the orchestrator adds to a workflow counts as progress.
40
+ const snapshot = JSON.stringify(workflow);
41
+ if (snapshot !== seen) {
42
+ seen = snapshot;
43
+ yield workflow;
44
+ }
45
+ if (isTerminal(workflow))
46
+ return;
47
+ }
48
+ }
49
+ return {
50
+ submitWorkflow: (template, { wait, signal } = {}) => http('POST', WORKFLOWS, { body: template, query: { wait }, signal }),
51
+ estimateWorkflow: (template, { signal } = {}) => http('POST', WORKFLOWS, { body: template, query: { whatif: true }, signal }),
52
+ getWorkflow,
53
+ watchWorkflow,
54
+ async waitForWorkflow(workflowId, opts = {}) {
55
+ let last;
56
+ for await (const workflow of watchWorkflow(workflowId, opts))
57
+ last = workflow;
58
+ return last;
59
+ },
60
+ cancelWorkflow: async (workflowId, { signal } = {}) => {
61
+ await http('PUT', `${WORKFLOWS}/${encodeURIComponent(workflowId)}`, {
62
+ body: { status: 'canceled' },
63
+ signal,
64
+ });
65
+ },
66
+ queryWorkflows: (query = {}, { signal } = {}) => http('GET', WORKFLOWS, { query: { ...query }, signal }),
67
+ };
68
+ }
69
+ /** A blip is worth waiting out; a refusal is an answer. */
70
+ function isTransient(error) {
71
+ return !(error instanceof ApiError) || error.status >= 500 || error.status === 429;
72
+ }
73
+ function sleep(ms, signal) {
74
+ return new Promise((resolve, reject) => {
75
+ const timer = setTimeout(resolve, ms);
76
+ signal?.addEventListener('abort', () => {
77
+ clearTimeout(timer);
78
+ reject(signal.reason);
79
+ }, { once: true });
80
+ });
81
+ }
@@ -0,0 +1,5 @@
1
+ import type { AceStepAudioStepTemplate, AgeClassificationStepTemplate, AudioCaptioningStepTemplate, BlobArchiveStepTemplate, ChatCompletionStepTemplate, ComfyNodepackSnapshotStepTemplate, ComfyStepTemplate, ComposeMediaStepTemplate, ConvertImageStepTemplate, CustomComfyStepTemplate, EchoStepTemplate, ImageBackgroundRemovalStepTemplate, ImageGenStepTemplate, ImageResourceTrainingStepTemplate, ImageToSvgStepTemplate, ImageUploadStepTemplate, ImageUpscalerStepTemplate, MediaCaptioningStepTemplate, MediaHashStepTemplate, MediaRatingStepTemplate, MiniMaxMusic3StepTemplate, Model3dPreviewStepTemplate, ModelClamScanStepTemplate, ModelHashStepTemplate, ModelParseMetadataStepTemplate, ModelPickleScanStepTemplate, PolyGenStepTemplate, PreprocessImageStepTemplate, PreprocessVideoStepTemplate, PromptEnhancementStepTemplate, QwenImageBenchStepTemplate, ShieldstralModerationStepTemplate, TextToImageStepTemplate, TextToSpeechStepTemplate, TrainingStepTemplate, TranscodeStepTemplate, TranscriptionStepTemplate, VideoBackgroundRemovalStepTemplate, VideoEnhancementStepTemplate, VideoFrameExtractionStepTemplate, VideoGenStepTemplate, VideoInterpolationStepTemplate, VideoMetadataStepTemplate, VideoUpscalerStepTemplate, WdTaggingStepTemplate, WebScrapeStepTemplate, WebSearchStepTemplate, XGuardModerationStepTemplate, AceStepAudioStep, AgeClassificationStep, AudioCaptioningStep, BlobArchiveStep, ChatCompletionStep, ComfyNodepackSnapshotStep, ComfyStep, ComposeMediaStep, ConvertImageStep, CustomComfyStep, EchoStep, ImageBackgroundRemovalStep, ImageGenStep, ImageResourceTrainingStep, ImageToSvgStep, ImageUploadStep, ImageUpscalerStep, MediaCaptioningStep, MediaHashStep, MediaRatingStep, MiniMaxMusic3Step, Model3dPreviewStep, ModelClamScanStep, ModelHashStep, ModelParseMetadataStep, ModelPickleScanStep, PolyGenStep, PreprocessImageStep, PreprocessVideoStep, PromptEnhancementStep, QwenImageBenchStep, ShieldstralModerationStep, TextToImageStep, TextToSpeechStep, TrainingStep, TranscodeStep, TranscriptionStep, VideoBackgroundRemovalStep, VideoEnhancementStep, VideoFrameExtractionStep, VideoGenStep, VideoInterpolationStep, VideoMetadataStep, VideoUpscalerStep, WdTaggingStep, WebScrapeStep, WebSearchStep, XGuardModerationStep } from '@civitai/orchestration-client/dist/generated/types.gen.js';
2
+ /** Any step a workflow can be submitted with, discriminated by `$type`. */
3
+ export type StepTemplate = AceStepAudioStepTemplate | AgeClassificationStepTemplate | AudioCaptioningStepTemplate | BlobArchiveStepTemplate | ChatCompletionStepTemplate | ComfyNodepackSnapshotStepTemplate | ComfyStepTemplate | ComposeMediaStepTemplate | ConvertImageStepTemplate | CustomComfyStepTemplate | EchoStepTemplate | ImageBackgroundRemovalStepTemplate | ImageGenStepTemplate | ImageResourceTrainingStepTemplate | ImageToSvgStepTemplate | ImageUploadStepTemplate | ImageUpscalerStepTemplate | MediaCaptioningStepTemplate | MediaHashStepTemplate | MediaRatingStepTemplate | MiniMaxMusic3StepTemplate | Model3dPreviewStepTemplate | ModelClamScanStepTemplate | ModelHashStepTemplate | ModelParseMetadataStepTemplate | ModelPickleScanStepTemplate | PolyGenStepTemplate | PreprocessImageStepTemplate | PreprocessVideoStepTemplate | PromptEnhancementStepTemplate | QwenImageBenchStepTemplate | ShieldstralModerationStepTemplate | TextToImageStepTemplate | TextToSpeechStepTemplate | TrainingStepTemplate | TranscodeStepTemplate | TranscriptionStepTemplate | VideoBackgroundRemovalStepTemplate | VideoEnhancementStepTemplate | VideoFrameExtractionStepTemplate | VideoGenStepTemplate | VideoInterpolationStepTemplate | VideoMetadataStepTemplate | VideoUpscalerStepTemplate | WdTaggingStepTemplate | WebScrapeStepTemplate | WebSearchStepTemplate | XGuardModerationStepTemplate;
4
+ /** Any step a workflow reports back, discriminated by `$type`. */
5
+ export type Step = AceStepAudioStep | AgeClassificationStep | AudioCaptioningStep | BlobArchiveStep | ChatCompletionStep | ComfyNodepackSnapshotStep | ComfyStep | ComposeMediaStep | ConvertImageStep | CustomComfyStep | EchoStep | ImageBackgroundRemovalStep | ImageGenStep | ImageResourceTrainingStep | ImageToSvgStep | ImageUploadStep | ImageUpscalerStep | MediaCaptioningStep | MediaHashStep | MediaRatingStep | MiniMaxMusic3Step | Model3dPreviewStep | ModelClamScanStep | ModelHashStep | ModelParseMetadataStep | ModelPickleScanStep | PolyGenStep | PreprocessImageStep | PreprocessVideoStep | PromptEnhancementStep | QwenImageBenchStep | ShieldstralModerationStep | TextToImageStep | TextToSpeechStep | TrainingStep | TranscodeStep | TranscriptionStep | VideoBackgroundRemovalStep | VideoEnhancementStep | VideoFrameExtractionStep | VideoGenStep | VideoInterpolationStep | VideoMetadataStep | VideoUpscalerStep | WdTaggingStep | WebScrapeStep | WebSearchStep | XGuardModerationStep;
@@ -0,0 +1,2 @@
1
+ // Generated by scripts/gen-step-types.mjs from @civitai/orchestration-client/dist/generated/types.gen.js. Do not edit.
2
+ export {};
@@ -0,0 +1,29 @@
1
+ /** Every scope an app can hold, as the site names them. */
2
+ export declare const SCOPES: readonly ['ai:write:budgeted', 'apps:storage:read', 'apps:storage:shared:read', 'apps:storage:shared:write', 'apps:storage:write', 'buzz:read:self', 'collections:read:private', 'collections:read:self', 'collections:write:self', 'models:read:self', 'posts:write:self', 'social:tip:self', 'user:read:self'];
3
+ export type Scope = (typeof SCOPES)[number];
4
+ export interface TokenOptions {
5
+ /** Skip the held token and get a new one, e.g. after the API refused it. */
6
+ fresh?: boolean;
7
+ signal?: AbortSignal;
8
+ }
9
+ export interface GrantOptions {
10
+ signal?: AbortSignal;
11
+ }
12
+ /** Where the app's token comes from. Everything that calls an API reads it here. */
13
+ export interface Session {
14
+ getToken(opts?: TokenOptions): Promise<string>;
15
+ requestGrants(scopes: readonly Scope[], opts?: GrantOptions): Promise<boolean>;
16
+ }
17
+ export type TokenSource = string | ((opts: {
18
+ signal?: AbortSignal;
19
+ }) => string | Promise<string>);
20
+ export interface TokenSessionOptions {
21
+ token: TokenSource;
22
+ /** How to get a new token when the API refuses one. Defaults to reading `token` again. */
23
+ refresh?: (opts: {
24
+ signal?: AbortSignal;
25
+ }) => string | Promise<string>;
26
+ /** How to ask the viewer for more scopes. Defaults to refusing, since there is no one to ask. */
27
+ requestGrants?: (scopes: readonly Scope[], opts: GrantOptions) => boolean | Promise<boolean>;
28
+ }
29
+ export declare function createTokenSession(options: TokenSessionOptions): Session;
@@ -0,0 +1,23 @@
1
+ /** Every scope an app can hold, as the site names them. */
2
+ export const SCOPES = [
3
+ 'ai:write:budgeted',
4
+ 'apps:storage:read',
5
+ 'apps:storage:shared:read',
6
+ 'apps:storage:shared:write',
7
+ 'apps:storage:write',
8
+ 'buzz:read:self',
9
+ 'collections:read:private',
10
+ 'collections:read:self',
11
+ 'collections:write:self',
12
+ 'models:read:self',
13
+ 'posts:write:self',
14
+ 'social:tip:self',
15
+ 'user:read:self',
16
+ ];
17
+ export function createTokenSession(options) {
18
+ const read = (signal) => Promise.resolve(typeof options.token === 'string' ? options.token : options.token({ signal }));
19
+ return {
20
+ getToken: ({ fresh, signal } = {}) => fresh && options.refresh ? Promise.resolve(options.refresh({ signal })) : read(signal),
21
+ requestGrants: (scopes, opts = {}) => Promise.resolve(options.requestGrants ? options.requestGrants(scopes, opts) : false),
22
+ };
23
+ }
@@ -0,0 +1,45 @@
1
+ import { CivitaiError } from '../core/errors.js';
2
+ import type { GrantOptions, Scope, TokenSessionOptions } from '../session/index.js';
3
+ export declare const DEFAULT_AUTH_URL = "https://auth.civitai.com";
4
+ export declare class SignInError extends CivitaiError {
5
+ readonly status?: number;
6
+ constructor(message: string, status?: number, options?: ErrorOptions);
7
+ }
8
+ type SignInStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
9
+ export interface SignInOptions {
10
+ /** The OAuth client registered on Civitai as a public (browser) client. */
11
+ clientId: string;
12
+ scopes: readonly Scope[];
13
+ /** Must be registered on the client. Defaults to the current page without its query. */
14
+ redirectUri?: string;
15
+ /** Defaults to `https://auth.civitai.com`. */
16
+ authUrl?: string;
17
+ /** Holds the sign-in in flight and whether the viewer signed in before; never a token. Defaults to `localStorage`. */
18
+ storage?: SignInStorage;
19
+ fetch?: typeof fetch;
20
+ window?: Window;
21
+ }
22
+ /** Pass it to `initialize()` as it is: it is the token, its refresh and the way to ask for grants. */
23
+ export interface SignIn extends TokenSessionOptions {
24
+ token(): Promise<string>;
25
+ refresh(): Promise<string>;
26
+ requestGrants(scopes: readonly Scope[], opts?: GrantOptions): Promise<boolean>;
27
+ readonly signedIn: boolean;
28
+ /** Signed in on an earlier visit. Tokens live only in memory, so `signIn()` again; Civitai remembers the consent. */
29
+ readonly returning: boolean;
30
+ readonly grantedScopes: readonly Scope[];
31
+ /** Why the last return from Civitai did not sign in, e.g. the viewer declined. */
32
+ readonly error: SignInError | null;
33
+ /** Leaves the page for Civitai's sign-in and consent; it comes back to `redirectUri`. */
34
+ signIn(opts?: {
35
+ scopes?: readonly Scope[];
36
+ }): Promise<never>;
37
+ signOut(): Promise<void>;
38
+ }
39
+ export declare function scopeBitmask(scopes: readonly Scope[]): number;
40
+ /**
41
+ * Sign-in with Civitai for an app outside civitai.com. Completes the return
42
+ * from Civitai when the page has just come back from it.
43
+ */
44
+ export declare function createSignIn(options: SignInOptions): Promise<SignIn>;
45
+ export {};