@bitmagic/asset-core 0.1.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/dist/forger-client.d.ts +42 -0
- package/dist/forger-client.d.ts.map +1 -0
- package/dist/forger-client.js +135 -0
- package/dist/forger-client.js.map +1 -0
- package/dist/generators/animation.d.ts +102 -0
- package/dist/generators/animation.d.ts.map +1 -0
- package/dist/generators/animation.js +234 -0
- package/dist/generators/animation.js.map +1 -0
- package/dist/generators/character.d.ts +40 -0
- package/dist/generators/character.d.ts.map +1 -0
- package/dist/generators/character.js +134 -0
- package/dist/generators/character.js.map +1 -0
- package/dist/generators/image.d.ts +43 -0
- package/dist/generators/image.d.ts.map +1 -0
- package/dist/generators/image.js +74 -0
- package/dist/generators/image.js.map +1 -0
- package/dist/generators/skybox.d.ts +18 -0
- package/dist/generators/skybox.d.ts.map +1 -0
- package/dist/generators/skybox.js +57 -0
- package/dist/generators/skybox.js.map +1 -0
- package/dist/generators/sound-effect.d.ts +31 -0
- package/dist/generators/sound-effect.d.ts.map +1 -0
- package/dist/generators/sound-effect.js +73 -0
- package/dist/generators/sound-effect.js.map +1 -0
- package/dist/http.d.ts +13 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +29 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/result.d.ts +6 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +9 -0
- package/dist/result.js.map +1 -0
- package/dist/terminal-failure.d.ts +152 -0
- package/dist/terminal-failure.d.ts.map +1 -0
- package/dist/terminal-failure.js +203 -0
- package/dist/terminal-failure.js.map +1 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +9 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +12 -0
- package/dist/validation.js.map +1 -0
- package/dist/world-patch.d.ts +25 -0
- package/dist/world-patch.d.ts.map +1 -0
- package/dist/world-patch.js +59 -0
- package/dist/world-patch.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CoreLogger, ForgerClient } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Asset Forger API client for authentication and request handling.
|
|
4
|
+
*
|
|
5
|
+
* Moved verbatim from game-play-agent; the only change is that the logger is
|
|
6
|
+
* injected so this package stays free of any host's logging stack.
|
|
7
|
+
*/
|
|
8
|
+
export interface AssetForgerCredentials {
|
|
9
|
+
username: string;
|
|
10
|
+
password: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class AssetForgerClient implements ForgerClient {
|
|
13
|
+
private readonly baseUrl;
|
|
14
|
+
private readonly credentials;
|
|
15
|
+
private readonly log;
|
|
16
|
+
constructor(logger: CoreLogger);
|
|
17
|
+
hasCredentials(): boolean;
|
|
18
|
+
getMissingCredentialsMessage(): string;
|
|
19
|
+
private authHeader;
|
|
20
|
+
private postJson;
|
|
21
|
+
/**
|
|
22
|
+
* Make a POST request to an Asset Forger endpoint
|
|
23
|
+
*
|
|
24
|
+
* @param endpoint - API endpoint path (e.g., '/ai/v1/generate-image-as-webp-url')
|
|
25
|
+
* @param payload - Request payload (object or pre-serialized JSON string)
|
|
26
|
+
* @param loggerName - Name for logging (e.g., 'SkyboxImageGenerator')
|
|
27
|
+
* @returns Response JSON
|
|
28
|
+
*/
|
|
29
|
+
post<T = unknown>(endpoint: string, payload: unknown, loggerName: string): Promise<T>;
|
|
30
|
+
/**
|
|
31
|
+
* Submit an async job and poll until it reaches a terminal state.
|
|
32
|
+
*
|
|
33
|
+
* POSTs `payload` to `jobsEndpoint` (expects 202 with `{jobId, statusUrl}`),
|
|
34
|
+
* then GETs `<baseUrl><statusUrl>` every ~5s with jitter until status is
|
|
35
|
+
* `succeeded` (returns `result.data`) or `failed` (throws). Transient poll
|
|
36
|
+
* failures (network errors, non-ok responses, JSON parse errors) are retried
|
|
37
|
+
* up to a small limit; the 404 race with the initial status-blob write in
|
|
38
|
+
* GCS just after job acceptance is covered by this retry path.
|
|
39
|
+
*/
|
|
40
|
+
postJobAndWait<T = unknown>(jobsEndpoint: string, payload: unknown, loggerName: string): Promise<T>;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=forger-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forger-client.d.ts","sourceRoot":"","sources":["../src/forger-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;GAKG;AAEH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,iBAAkB,YAAW,YAAY;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiF;IACzG,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgC;IAC5D,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;gBAErB,MAAM,EAAE,UAAU;IAgBvB,cAAc,IAAI,OAAO;IAIzB,4BAA4B,IAAI,MAAM;IAI7C,OAAO,CAAC,UAAU;YAMJ,QAAQ;IAgCtB;;;;;;;OAOG;IACU,IAAI,CAAC,CAAC,GAAG,OAAO,EAC3B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,CAAC;IAMb;;;;;;;;;OASG;IACU,cAAc,CAAC,CAAC,GAAG,OAAO,EACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,CAAC;CA+Ed"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
export class AssetForgerClient {
|
|
2
|
+
constructor(logger) {
|
|
3
|
+
this.baseUrl = process.env.ASSET_FORGER_URL || 'https://asset-forger.staging.bitmagic.cloud';
|
|
4
|
+
this.log = logger;
|
|
5
|
+
const username = process.env.ASSET_FORGER_USER;
|
|
6
|
+
const password = process.env.ASSET_FORGER_PASSWORD;
|
|
7
|
+
this.log.info(`Initializing AssetForgerClient with URL: ${this.baseUrl}`);
|
|
8
|
+
if (username && password) {
|
|
9
|
+
this.credentials = { username, password };
|
|
10
|
+
this.log.info('Asset Forger credentials configured');
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
this.credentials = null;
|
|
14
|
+
this.log.warn('Asset Forger credentials not found in environment variables');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
hasCredentials() {
|
|
18
|
+
return this.credentials !== null;
|
|
19
|
+
}
|
|
20
|
+
getMissingCredentialsMessage() {
|
|
21
|
+
return 'Asset Forger credentials not found in environment variables. Please set ASSET_FORGER_USER and ASSET_FORGER_PASSWORD.';
|
|
22
|
+
}
|
|
23
|
+
authHeader() {
|
|
24
|
+
// Callers must check hasCredentials() first.
|
|
25
|
+
const { username, password } = this.credentials;
|
|
26
|
+
return `Basic ${btoa(`${username}:${password}`)}`;
|
|
27
|
+
}
|
|
28
|
+
async postJson(endpoint, payload, loggerName) {
|
|
29
|
+
if (!this.credentials) {
|
|
30
|
+
throw new Error('Asset Forger credentials not configured');
|
|
31
|
+
}
|
|
32
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
33
|
+
this.log.info(`[${loggerName}] POST ${url}`);
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
'Authorization': this.authHeader(),
|
|
39
|
+
},
|
|
40
|
+
body: typeof payload === 'string' ? payload : JSON.stringify(payload),
|
|
41
|
+
});
|
|
42
|
+
this.log.info(`[${loggerName}] API Response Status: ${response.status} ${response.statusText}`);
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const errorText = await response.text();
|
|
45
|
+
this.log.error(`[${loggerName}] API Error Response:`, errorText);
|
|
46
|
+
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
|
|
47
|
+
}
|
|
48
|
+
return await response.json();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Make a POST request to an Asset Forger endpoint
|
|
52
|
+
*
|
|
53
|
+
* @param endpoint - API endpoint path (e.g., '/ai/v1/generate-image-as-webp-url')
|
|
54
|
+
* @param payload - Request payload (object or pre-serialized JSON string)
|
|
55
|
+
* @param loggerName - Name for logging (e.g., 'SkyboxImageGenerator')
|
|
56
|
+
* @returns Response JSON
|
|
57
|
+
*/
|
|
58
|
+
async post(endpoint, payload, loggerName) {
|
|
59
|
+
const json = await this.postJson(endpoint, payload, loggerName);
|
|
60
|
+
this.log.info(`[${loggerName}] Received response:`, json);
|
|
61
|
+
return json;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Submit an async job and poll until it reaches a terminal state.
|
|
65
|
+
*
|
|
66
|
+
* POSTs `payload` to `jobsEndpoint` (expects 202 with `{jobId, statusUrl}`),
|
|
67
|
+
* then GETs `<baseUrl><statusUrl>` every ~5s with jitter until status is
|
|
68
|
+
* `succeeded` (returns `result.data`) or `failed` (throws). Transient poll
|
|
69
|
+
* failures (network errors, non-ok responses, JSON parse errors) are retried
|
|
70
|
+
* up to a small limit; the 404 race with the initial status-blob write in
|
|
71
|
+
* GCS just after job acceptance is covered by this retry path.
|
|
72
|
+
*/
|
|
73
|
+
async postJobAndWait(jobsEndpoint, payload, loggerName) {
|
|
74
|
+
const submitJson = await this.postJson(jobsEndpoint, payload, loggerName);
|
|
75
|
+
const { jobId, statusUrl } = submitJson;
|
|
76
|
+
if (!jobId || !statusUrl) {
|
|
77
|
+
throw new Error(`Job submission returned invalid response: ${JSON.stringify(submitJson)}`);
|
|
78
|
+
}
|
|
79
|
+
this.log.info(`[${loggerName}] Job ${jobId} accepted, polling ${statusUrl}...`);
|
|
80
|
+
const statusFullUrl = `${this.baseUrl}${statusUrl}`;
|
|
81
|
+
// Upstream's Python wrapper retries once after a per-attempt 300s timeout,
|
|
82
|
+
// so legitimate slow successes can take ~380s. Must exceed that ceiling.
|
|
83
|
+
const overallTimeoutMs = 8 * 60 * 1000;
|
|
84
|
+
const basePollMs = 5000;
|
|
85
|
+
const minPollMs = 2000;
|
|
86
|
+
const maxConsecutivePollErrors = 5;
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
let consecutivePollErrors = 0;
|
|
89
|
+
for (;;) {
|
|
90
|
+
if (Date.now() - startTime > overallTimeoutMs) {
|
|
91
|
+
throw new Error(`Job ${jobId} exceeded client timeout of ${overallTimeoutMs}ms`);
|
|
92
|
+
}
|
|
93
|
+
// 5s ± 20% jitter, floored at 2s
|
|
94
|
+
const waitMs = Math.max(minPollMs, Math.round(basePollMs * (0.8 + Math.random() * 0.4)));
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
96
|
+
let statusJson;
|
|
97
|
+
try {
|
|
98
|
+
const statusResponse = await fetch(statusFullUrl, {
|
|
99
|
+
method: 'GET',
|
|
100
|
+
headers: { 'Authorization': this.authHeader() },
|
|
101
|
+
});
|
|
102
|
+
if (!statusResponse.ok) {
|
|
103
|
+
const errorText = await statusResponse.text().catch(() => '');
|
|
104
|
+
throw new Error(`non-ok ${statusResponse.status}: ${errorText}`);
|
|
105
|
+
}
|
|
106
|
+
statusJson = await statusResponse.json();
|
|
107
|
+
consecutivePollErrors = 0;
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
consecutivePollErrors += 1;
|
|
111
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
112
|
+
if (consecutivePollErrors >= maxConsecutivePollErrors) {
|
|
113
|
+
throw new Error(`Job ${jobId} polling failed: ${consecutivePollErrors} consecutive errors. Last: ${errMsg}`);
|
|
114
|
+
}
|
|
115
|
+
this.log.warn(`[${loggerName}] Status poll error (${consecutivePollErrors}/${maxConsecutivePollErrors}), will retry: ${errMsg}`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (statusJson.status === 'succeeded') {
|
|
119
|
+
this.log.info(`[${loggerName}] Job ${jobId} succeeded`);
|
|
120
|
+
if (!statusJson.result) {
|
|
121
|
+
throw new Error(`Job ${jobId} succeeded but has no result`);
|
|
122
|
+
}
|
|
123
|
+
return statusJson.result;
|
|
124
|
+
}
|
|
125
|
+
if (statusJson.status === 'failed') {
|
|
126
|
+
const errStr = typeof statusJson.error === 'string'
|
|
127
|
+
? statusJson.error
|
|
128
|
+
: JSON.stringify(statusJson.error ?? 'unknown error');
|
|
129
|
+
const httpPart = statusJson.httpStatus ? ` (httpStatus=${statusJson.httpStatus})` : '';
|
|
130
|
+
throw new Error(`Job ${jobId} failed${httpPart}: ${errStr}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=forger-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forger-client.js","sourceRoot":"","sources":["../src/forger-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AssetResultBase, GenerateDeps } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* `process.env.ANIMATION_MODEL` is the one env read this package gains beyond
|
|
5
|
+
* `forger-client.ts`'s existing `ASSET_FORGER_*` reads. It is not a violation
|
|
6
|
+
* of "no module globals": it configures the upstream call itself (which
|
|
7
|
+
* animation model asset-forger receives), exactly like the Forger URL and
|
|
8
|
+
* credentials env reads in `forger-client.ts`. It is read once per generation
|
|
9
|
+
* call, not cached at module load, so it stays test- and CLI-friendly.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The text-to-motion-3.0 model constrains animation length to 4-8 seconds;
|
|
13
|
+
* out-of-range values are clamped in buildRequest below.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ANIMATION_MODEL_TEXT_TO_MOTION_3 = "text-to-motion-3.0";
|
|
16
|
+
/** The older, faster model — also the fallback when text-to-motion-3.0 fails. */
|
|
17
|
+
export declare const ANIMATION_MODEL_TEXT_TO_MOTION_2 = "text-to-motion-2.0";
|
|
18
|
+
/**
|
|
19
|
+
* UI/request sentinel meaning "the older, faster model" (text-to-motion-2.0).
|
|
20
|
+
* Distinct from an absent override, which inherits the ANIMATION_MODEL env
|
|
21
|
+
* default. Used by the generate-animation tool's per-request override.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ANIMATION_MODEL_OLDER = "older";
|
|
24
|
+
/**
|
|
25
|
+
* Model for the asset-forger animation generator. Defaults to
|
|
26
|
+
* text-to-motion-3.0 when `ANIMATION_MODEL` is unset. Set `ANIMATION_MODEL`
|
|
27
|
+
* to another model id (or `older` for text-to-motion-2.0) to override.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getConfiguredAnimationModel(): string;
|
|
30
|
+
export declare const animationParamsSchema: z.ZodObject<{
|
|
31
|
+
description: z.ZodString;
|
|
32
|
+
cfgScale: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
33
|
+
length: z.ZodOptional<z.ZodNumber>;
|
|
34
|
+
seed: z.ZodOptional<z.ZodNumber>;
|
|
35
|
+
footIk: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
36
|
+
inPlace: z.ZodOptional<z.ZodBoolean>;
|
|
37
|
+
loop: z.ZodOptional<z.ZodBoolean>;
|
|
38
|
+
assetId: z.ZodOptional<z.ZodString>;
|
|
39
|
+
/**
|
|
40
|
+
* Explicit per-request model override, e.g. from the game-play-agent
|
|
41
|
+
* runtimeContext's `animationModel`. `undefined` inherits the
|
|
42
|
+
* ANIMATION_MODEL-configured default. Not part of the LLM-facing tool
|
|
43
|
+
* schema — the adapter resolves this from its own session state and passes
|
|
44
|
+
* it through.
|
|
45
|
+
*/
|
|
46
|
+
model: z.ZodOptional<z.ZodString>;
|
|
47
|
+
}, "strict", z.ZodTypeAny, {
|
|
48
|
+
description: string;
|
|
49
|
+
cfgScale: number;
|
|
50
|
+
footIk: boolean;
|
|
51
|
+
length?: number | undefined;
|
|
52
|
+
loop?: boolean | undefined;
|
|
53
|
+
assetId?: string | undefined;
|
|
54
|
+
seed?: number | undefined;
|
|
55
|
+
inPlace?: boolean | undefined;
|
|
56
|
+
model?: string | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
description: string;
|
|
59
|
+
length?: number | undefined;
|
|
60
|
+
loop?: boolean | undefined;
|
|
61
|
+
assetId?: string | undefined;
|
|
62
|
+
cfgScale?: number | undefined;
|
|
63
|
+
seed?: number | undefined;
|
|
64
|
+
footIk?: boolean | undefined;
|
|
65
|
+
inPlace?: boolean | undefined;
|
|
66
|
+
model?: string | undefined;
|
|
67
|
+
}>;
|
|
68
|
+
/**
|
|
69
|
+
* The schema's INPUT type — callers may omit the defaulted fields. The
|
|
70
|
+
* generator parses on entry and works with the output type internally, which
|
|
71
|
+
* is what lets it own its own defaults instead of each host patching them in.
|
|
72
|
+
*/
|
|
73
|
+
export type AnimationParams = z.input<typeof animationParamsSchema>;
|
|
74
|
+
export type AnimationResult = AssetResultBase & {
|
|
75
|
+
animationUrl?: string;
|
|
76
|
+
/** The id the animation was stored under: `assetId` when supplied, else the generator's own motionId. */
|
|
77
|
+
motionId?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Resolved animation length in seconds actually sent to asset-forger for the
|
|
80
|
+
* attempt that succeeded (after defaults and text-to-motion-3.0 clamping).
|
|
81
|
+
* The adapter passes this as `units` to `reportToolExecution` for
|
|
82
|
+
* per-second billing — it must reflect the duration actually generated.
|
|
83
|
+
*/
|
|
84
|
+
lengthSeconds?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Model actually used by the successful attempt (absent on total failure,
|
|
87
|
+
* since no attempt succeeded). The adapter passes this as `model` to
|
|
88
|
+
* `reportToolExecution`, which resolves the per-second rate via
|
|
89
|
+
* `cost_per_second_by_model[model] ?? cost_per_second`.
|
|
90
|
+
*/
|
|
91
|
+
model?: string;
|
|
92
|
+
/**
|
|
93
|
+
* True once a response with a resolvable animation URL was received.
|
|
94
|
+
* Left unset on any failure — including "the Forger responded but with no
|
|
95
|
+
* URL" — matching the original tool, which never billed a fully-failed
|
|
96
|
+
* generation (unlike the job-based character/skybox generators, this
|
|
97
|
+
* endpoint answers synchronously with no separate "job accepted" moment).
|
|
98
|
+
*/
|
|
99
|
+
billable?: boolean;
|
|
100
|
+
};
|
|
101
|
+
export declare function generateAnimationAsset(params: AnimationParams, deps: GenerateDeps): Promise<AnimationResult>;
|
|
102
|
+
//# sourceMappingURL=animation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animation.d.ts","sourceRoot":"","sources":["../../src/generators/animation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAA4B,YAAY,EAAc,MAAM,aAAa,CAAC;AAIvG;;;;;;;GAOG;AAEH;;;GAGG;AACH,eAAO,MAAM,gCAAgC,uBAAuB,CAAC;AAIrE,iFAAiF;AACjF,eAAO,MAAM,gCAAgC,uBAAuB,CAAC;AAErE;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAe7C;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,CAMpD;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;IAiBhC;;;;;;OAMG;;;;;;;;;;;;;;;;;;;;;;EAGM,CAAC;AAEZ;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAwIF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,eAAe,CAAC,CAwF1B"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { fail, ok } from '../result.js';
|
|
3
|
+
import { formatZodIssues } from '../validation.js';
|
|
4
|
+
/**
|
|
5
|
+
* `process.env.ANIMATION_MODEL` is the one env read this package gains beyond
|
|
6
|
+
* `forger-client.ts`'s existing `ASSET_FORGER_*` reads. It is not a violation
|
|
7
|
+
* of "no module globals": it configures the upstream call itself (which
|
|
8
|
+
* animation model asset-forger receives), exactly like the Forger URL and
|
|
9
|
+
* credentials env reads in `forger-client.ts`. It is read once per generation
|
|
10
|
+
* call, not cached at module load, so it stays test- and CLI-friendly.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The text-to-motion-3.0 model constrains animation length to 4-8 seconds;
|
|
14
|
+
* out-of-range values are clamped in buildRequest below.
|
|
15
|
+
*/
|
|
16
|
+
export const ANIMATION_MODEL_TEXT_TO_MOTION_3 = 'text-to-motion-3.0';
|
|
17
|
+
const TEXT_TO_MOTION_3_MIN_LENGTH = 4;
|
|
18
|
+
const TEXT_TO_MOTION_3_MAX_LENGTH = 8;
|
|
19
|
+
/** The older, faster model — also the fallback when text-to-motion-3.0 fails. */
|
|
20
|
+
export const ANIMATION_MODEL_TEXT_TO_MOTION_2 = 'text-to-motion-2.0';
|
|
21
|
+
/**
|
|
22
|
+
* UI/request sentinel meaning "the older, faster model" (text-to-motion-2.0).
|
|
23
|
+
* Distinct from an absent override, which inherits the ANIMATION_MODEL env
|
|
24
|
+
* default. Used by the generate-animation tool's per-request override.
|
|
25
|
+
*/
|
|
26
|
+
export const ANIMATION_MODEL_OLDER = 'older';
|
|
27
|
+
const DEFAULT_LENGTH_SECONDS = 3;
|
|
28
|
+
const FIXED_STEPS = 100;
|
|
29
|
+
// Per-attempt timeouts. text-to-motion-3.0 clips can take ~2.5min+ server-side
|
|
30
|
+
// (and retry internally in asset-forger), so it gets a generous 10min; the
|
|
31
|
+
// text-to-motion-2.0 fallback is faster, so 3min. Legacy non-T2M3 path stays at 6min.
|
|
32
|
+
const T2M3_TIMEOUT_MS = 600000; // 10 minutes
|
|
33
|
+
const FALLBACK_TIMEOUT_MS = 180000; // 3 minutes
|
|
34
|
+
const LEGACY_TIMEOUT_MS = 360000; // 6 minutes
|
|
35
|
+
const RETRY_DELAY_MS = 2000;
|
|
36
|
+
const ENDPOINT = '/ai/v1/generate-animation-as-glb-url';
|
|
37
|
+
/**
|
|
38
|
+
* Model for the asset-forger animation generator. Defaults to
|
|
39
|
+
* text-to-motion-3.0 when `ANIMATION_MODEL` is unset. Set `ANIMATION_MODEL`
|
|
40
|
+
* to another model id (or `older` for text-to-motion-2.0) to override.
|
|
41
|
+
*/
|
|
42
|
+
export function getConfiguredAnimationModel() {
|
|
43
|
+
const configured = process.env.ANIMATION_MODEL;
|
|
44
|
+
if (configured === ANIMATION_MODEL_OLDER) {
|
|
45
|
+
return ANIMATION_MODEL_TEXT_TO_MOTION_2;
|
|
46
|
+
}
|
|
47
|
+
return configured || ANIMATION_MODEL_TEXT_TO_MOTION_3;
|
|
48
|
+
}
|
|
49
|
+
export const animationParamsSchema = z.object({
|
|
50
|
+
description: z.string().min(1)
|
|
51
|
+
.describe('Simple action phrase describing the animation (e.g., "A person swimming slowly")'),
|
|
52
|
+
cfgScale: z.number().optional().default(6.0)
|
|
53
|
+
.describe('Classifier-free guidance scale (higher = more prompt adherence). Defaults to 6.0.'),
|
|
54
|
+
length: z.number().optional()
|
|
55
|
+
.describe('Animation length in seconds. Defaults to 3.0. Note: the text-to-motion-3.0 model only supports 4–8s (out-of-range values are clamped).'),
|
|
56
|
+
seed: z.number().optional()
|
|
57
|
+
.describe('Random seed for reproducibility. Omit for random results.'),
|
|
58
|
+
footIk: z.boolean().optional().default(true)
|
|
59
|
+
.describe('Enable foot IK (inverse kinematics) for grounded animations. Defaults to true. Set to false for airborne animations like jumping.'),
|
|
60
|
+
inPlace: z.boolean().optional()
|
|
61
|
+
.describe('Generate animation where the character stays in place (e.g., idle animations). Defaults to false.'),
|
|
62
|
+
loop: z.boolean().optional()
|
|
63
|
+
.describe('Whether the animation should loop continuously. Set to true for idle animations. Defaults to false.'),
|
|
64
|
+
assetId: z.string().optional()
|
|
65
|
+
.describe('Pre-generated asset id from the manifest. When provided, the animation is stored under this exact id (used as its motionId for playCustomAnimation) so code that references it resolves. If omitted, the generator motionId is used.'),
|
|
66
|
+
/**
|
|
67
|
+
* Explicit per-request model override, e.g. from the game-play-agent
|
|
68
|
+
* runtimeContext's `animationModel`. `undefined` inherits the
|
|
69
|
+
* ANIMATION_MODEL-configured default. Not part of the LLM-facing tool
|
|
70
|
+
* schema — the adapter resolves this from its own session state and passes
|
|
71
|
+
* it through.
|
|
72
|
+
*/
|
|
73
|
+
model: z.string().optional()
|
|
74
|
+
.describe('Explicit model override for this call. Omit to use the configured ANIMATION_MODEL default.'),
|
|
75
|
+
}).strict();
|
|
76
|
+
/**
|
|
77
|
+
* Resolve which model this call should start with: an explicit per-request
|
|
78
|
+
* `model` override wins (`text-to-motion-3.0` names that model directly,
|
|
79
|
+
* `older` forces text-to-motion-2.0 even over an ANIMATION_MODEL=3.0 env
|
|
80
|
+
* default); any other value (including absent) inherits the configured
|
|
81
|
+
* default. An unrecognized override string is not treated as an explicit
|
|
82
|
+
* model id — it falls through to the env default, matching the original
|
|
83
|
+
* tool's behaviour.
|
|
84
|
+
*/
|
|
85
|
+
function resolveEffectiveModel(modelOverride) {
|
|
86
|
+
if (modelOverride === ANIMATION_MODEL_TEXT_TO_MOTION_3) {
|
|
87
|
+
return ANIMATION_MODEL_TEXT_TO_MOTION_3;
|
|
88
|
+
}
|
|
89
|
+
if (modelOverride === ANIMATION_MODEL_OLDER) {
|
|
90
|
+
return ANIMATION_MODEL_TEXT_TO_MOTION_2;
|
|
91
|
+
}
|
|
92
|
+
return getConfiguredAnimationModel();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Attempt plan for a resolved starting model. text-to-motion-3.0 retries
|
|
96
|
+
* internally in asset-forger, so we NEVER retry it client-side; instead, on
|
|
97
|
+
* failure we fall back to text-to-motion-2.0 for a single attempt. Any other
|
|
98
|
+
* configured model keeps the legacy 2-attempt retry (same model twice).
|
|
99
|
+
*/
|
|
100
|
+
function buildAttemptPlan(effectiveModel) {
|
|
101
|
+
if (effectiveModel === ANIMATION_MODEL_TEXT_TO_MOTION_3) {
|
|
102
|
+
return [
|
|
103
|
+
{ model: ANIMATION_MODEL_TEXT_TO_MOTION_3, label: ANIMATION_MODEL_TEXT_TO_MOTION_3, timeoutMs: T2M3_TIMEOUT_MS },
|
|
104
|
+
{ model: ANIMATION_MODEL_TEXT_TO_MOTION_2, label: `${ANIMATION_MODEL_TEXT_TO_MOTION_2}-fallback`, timeoutMs: FALLBACK_TIMEOUT_MS },
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
return [
|
|
108
|
+
{ model: effectiveModel, label: effectiveModel, timeoutMs: LEGACY_TIMEOUT_MS },
|
|
109
|
+
{ model: effectiveModel, label: `${effectiveModel}-retry`, timeoutMs: LEGACY_TIMEOUT_MS },
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Builds the resolved asset-forger request for one attempt (applies the
|
|
114
|
+
* text-to-motion-3.0 length clamp). Returning the object (rather than only a
|
|
115
|
+
* JSON string) lets the caller read the resolved length/model back for
|
|
116
|
+
* per-second billing without re-parsing what was sent.
|
|
117
|
+
*/
|
|
118
|
+
function buildRequest(input, model, logger) {
|
|
119
|
+
const payload = {
|
|
120
|
+
prompt: input.description,
|
|
121
|
+
format: 'glb',
|
|
122
|
+
model,
|
|
123
|
+
bucmd: true,
|
|
124
|
+
footIk: input.footIk,
|
|
125
|
+
length: input.length ?? DEFAULT_LENGTH_SECONDS,
|
|
126
|
+
cfgScale: input.cfgScale,
|
|
127
|
+
steps: FIXED_STEPS,
|
|
128
|
+
...(input.seed !== undefined && { seed: input.seed }),
|
|
129
|
+
...(input.inPlace !== undefined && { inPlace: input.inPlace }),
|
|
130
|
+
};
|
|
131
|
+
// text-to-motion-3.0 only accepts 4-8s clips. Clamp the resolved length into
|
|
132
|
+
// range; other models (including the text-to-motion-2.0 fallback) send it
|
|
133
|
+
// as-is.
|
|
134
|
+
if (model === ANIMATION_MODEL_TEXT_TO_MOTION_3) {
|
|
135
|
+
const clamped = Math.min(TEXT_TO_MOTION_3_MAX_LENGTH, Math.max(TEXT_TO_MOTION_3_MIN_LENGTH, payload.length));
|
|
136
|
+
if (clamped !== payload.length) {
|
|
137
|
+
logger.info(`[animation] Clamped length ${payload.length}s -> ${clamped}s for ${model} (allowed ${TEXT_TO_MOTION_3_MIN_LENGTH}-${TEXT_TO_MOTION_3_MAX_LENGTH}s)`);
|
|
138
|
+
payload.length = clamped;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return payload;
|
|
142
|
+
}
|
|
143
|
+
function withTimeout(promise, timeoutMs) {
|
|
144
|
+
let timer;
|
|
145
|
+
const timeout = new Promise((_, reject) => {
|
|
146
|
+
timer = setTimeout(() => reject(new Error(`Animation generation timed out after ${timeoutMs / 60000} minutes`)), timeoutMs);
|
|
147
|
+
});
|
|
148
|
+
// Clearing the loser's timer matters beyond tidiness: an un-cleared 10-minute
|
|
149
|
+
// timeout keeps the process (and a test runner's event loop) alive long after
|
|
150
|
+
// every real attempt has already settled.
|
|
151
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
152
|
+
}
|
|
153
|
+
function sleep(ms) {
|
|
154
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
155
|
+
}
|
|
156
|
+
async function requestAnimation(forger, requestPayload, logger) {
|
|
157
|
+
logger.info({ payload: requestPayload }, '[animation] Request payload');
|
|
158
|
+
const response = await forger.post(ENDPOINT, JSON.stringify(requestPayload), 'AnimationGenerator');
|
|
159
|
+
if (!response.animationUrl) {
|
|
160
|
+
throw new Error('No animation URL found in API response');
|
|
161
|
+
}
|
|
162
|
+
return { animationUrl: response.animationUrl, motionId: response.motionId };
|
|
163
|
+
}
|
|
164
|
+
export async function generateAnimationAsset(params, deps) {
|
|
165
|
+
const { forger, logger, onProgress } = deps;
|
|
166
|
+
const parsed = animationParamsSchema.safeParse(params);
|
|
167
|
+
if (!parsed.success) {
|
|
168
|
+
logger.warn({ err: parsed.error }, '[animation] Invalid parameters');
|
|
169
|
+
return fail(formatZodIssues(parsed.error));
|
|
170
|
+
}
|
|
171
|
+
const input = parsed.data;
|
|
172
|
+
if (!forger.hasCredentials()) {
|
|
173
|
+
logger.error('[animation] Missing Asset Forger credentials');
|
|
174
|
+
return fail(forger.getMissingCredentialsMessage());
|
|
175
|
+
}
|
|
176
|
+
onProgress(`Generating animation: "${input.description}"`);
|
|
177
|
+
logger.info(`[animation] Starting generation for: "${input.description}"`);
|
|
178
|
+
const effectiveModel = resolveEffectiveModel(input.model);
|
|
179
|
+
const usingTextToMotion3 = effectiveModel === ANIMATION_MODEL_TEXT_TO_MOTION_3;
|
|
180
|
+
const attempts = buildAttemptPlan(effectiveModel);
|
|
181
|
+
let lastError = null;
|
|
182
|
+
for (let attempt = 0; attempt < attempts.length; attempt++) {
|
|
183
|
+
const { model, label, timeoutMs } = attempts[attempt];
|
|
184
|
+
const requestPayload = buildRequest(input, model, logger);
|
|
185
|
+
try {
|
|
186
|
+
const { animationUrl, motionId } = await withTimeout(requestAnimation(forger, requestPayload, logger), timeoutMs);
|
|
187
|
+
logger.info(`[animation] Successfully generated animation GLB URL: ${animationUrl}`);
|
|
188
|
+
logger.info(`[animation] Motion ID: ${motionId}`);
|
|
189
|
+
// The engine derives motionId from the asset id (see mergeGameData in
|
|
190
|
+
// game/src/types/game.ts) and loads the clip by URL, so honoring a
|
|
191
|
+
// pre-generated id from the manifest makes code that references it (via
|
|
192
|
+
// playCustomAnimation("<id>")) resolve. Falls back to the generator's
|
|
193
|
+
// own motionId when no id was pre-minted.
|
|
194
|
+
const finalMotionId = input.assetId ?? motionId;
|
|
195
|
+
const patch = {
|
|
196
|
+
type: 'pushRoot',
|
|
197
|
+
path: ['assets'],
|
|
198
|
+
value: {
|
|
199
|
+
id: finalMotionId,
|
|
200
|
+
name: input.description,
|
|
201
|
+
url: animationUrl,
|
|
202
|
+
type: 'animation',
|
|
203
|
+
prompt: input.description,
|
|
204
|
+
source: 'generated',
|
|
205
|
+
size: 0,
|
|
206
|
+
...(input.loop !== undefined && { loop: input.loop }),
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
return ok(`Animation generated: ${animationUrl}`, [patch], {
|
|
210
|
+
animationUrl,
|
|
211
|
+
motionId: finalMotionId,
|
|
212
|
+
lengthSeconds: requestPayload.length,
|
|
213
|
+
model,
|
|
214
|
+
billable: true,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
lastError = error instanceof Error ? error : new Error('Unknown error occurred');
|
|
219
|
+
logger.error({ err: lastError }, `[animation] ${label} attempt failed`);
|
|
220
|
+
}
|
|
221
|
+
if (attempt < attempts.length - 1) {
|
|
222
|
+
const next = attempts[attempt + 1];
|
|
223
|
+
const reason = usingTextToMotion3
|
|
224
|
+
? `${label} failed, falling back to ${next.label}`
|
|
225
|
+
: `attempt ${attempt + 1} failed, retrying`;
|
|
226
|
+
logger.warn(`[animation] ${reason} in ${RETRY_DELAY_MS}ms...`);
|
|
227
|
+
await sleep(RETRY_DELAY_MS);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const message = lastError?.message ?? 'Failed to generate animation';
|
|
231
|
+
logger.error({ err: lastError }, `[animation] Generation failed after ${attempts.length} attempts`);
|
|
232
|
+
return fail(message);
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=animation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"animation.js","sourceRoot":"","sources":["../../src/generators/animation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;GAOG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAAG,oBAAoB,CAAC;AACrE,MAAM,2BAA2B,GAAG,CAAC,CAAC;AACtC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAEtC,iFAAiF;AACjF,MAAM,CAAC,MAAM,gCAAgC,GAAG,oBAAoB,CAAC;AAErE;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAE7C,MAAM,sBAAsB,GAAG,CAAC,CAAC;AACjC,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB,+EAA+E;AAC/E,2EAA2E;AAC3E,sFAAsF;AACtF,MAAM,eAAe,GAAG,MAAM,CAAC,CAAC,aAAa;AAC7C,MAAM,mBAAmB,GAAG,MAAM,CAAC,CAAC,YAAY;AAChD,MAAM,iBAAiB,GAAG,MAAM,CAAC,CAAC,YAAY;AAC9C,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,MAAM,QAAQ,GAAG,sCAAsC,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B;IACzC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC/C,IAAI,UAAU,KAAK,qBAAqB,EAAE,CAAC;QACzC,OAAO,gCAAgC,CAAC;IAC1C,CAAC;IACD,OAAO,UAAU,IAAI,gCAAgC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;SAC3B,QAAQ,CAAC,kFAAkF,CAAC;IAC/F,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;SACzC,QAAQ,CAAC,mFAAmF,CAAC;IAChG,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC1B,QAAQ,CAAC,wIAAwI,CAAC;IACrJ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACxB,QAAQ,CAAC,2DAA2D,CAAC;IACxE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;SACzC,QAAQ,CAAC,mIAAmI,CAAC;IAChJ,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SAC5B,QAAQ,CAAC,mGAAmG,CAAC;IAChH,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SACzB,QAAQ,CAAC,qGAAqG,CAAC;IAClH,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC3B,QAAQ,CAAC,sOAAsO,CAAC;IACnP;;;;;;OAMG;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SACzB,QAAQ,CAAC,4FAA4F,CAAC;CAC1G,CAAC,CAAC,MAAM,EAAE,CAAC;AAgEZ;;;;;;;;GAQG;AACH,SAAS,qBAAqB,CAAC,aAAiC;IAC9D,IAAI,aAAa,KAAK,gCAAgC,EAAE,CAAC;QACvD,OAAO,gCAAgC,CAAC;IAC1C,CAAC;IACD,IAAI,aAAa,KAAK,qBAAqB,EAAE,CAAC;QAC5C,OAAO,gCAAgC,CAAC;IAC1C,CAAC;IACD,OAAO,2BAA2B,EAAE,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,cAAsB;IAC9C,IAAI,cAAc,KAAK,gCAAgC,EAAE,CAAC;QACxD,OAAO;YACL,EAAE,KAAK,EAAE,gCAAgC,EAAE,KAAK,EAAE,gCAAgC,EAAE,SAAS,EAAE,eAAe,EAAE;YAChH,EAAE,KAAK,EAAE,gCAAgC,EAAE,KAAK,EAAE,GAAG,gCAAgC,WAAW,EAAE,SAAS,EAAE,mBAAmB,EAAE;SACnI,CAAC;IACJ,CAAC;IACD,OAAO;QACL,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,iBAAiB,EAAE;QAC9E,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,GAAG,cAAc,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE;KAC1F,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,YAAY,CAAC,KAA2B,EAAE,KAAa,EAAE,MAAkB;IAClF,MAAM,OAAO,GAAgC;QAC3C,MAAM,EAAE,KAAK,CAAC,WAAW;QACzB,MAAM,EAAE,KAAK;QACb,KAAK;QACL,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,sBAAsB;QAC9C,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,WAAW;QAClB,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;QACrD,GAAG,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;KAC/D,CAAC;IAEF,6EAA6E;IAC7E,0EAA0E;IAC1E,SAAS;IACT,IAAI,KAAK,KAAK,gCAAgC,EAAE,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,2BAA2B,EAAE,IAAI,CAAC,GAAG,CAAC,2BAA2B,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7G,IAAI,OAAO,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,8BAA8B,OAAO,CAAC,MAAM,QAAQ,OAAO,SAAS,KAAK,aAAa,2BAA2B,IAAI,2BAA2B,IAAI,CAAC,CAAC;YAClK,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,WAAW,CAAI,OAAmB,EAAE,SAAiB;IAC5D,IAAI,KAAoC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAI,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;QAC3C,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,wCAAwC,SAAS,GAAG,KAAK,UAAU,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAC9H,CAAC,CAAC,CAAC;IACH,8EAA8E;IAC9E,8EAA8E;IAC9E,0CAA0C;IAC1C,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,MAAoB,EACpB,cAA2C,EAC3C,MAAkB;IAElB,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,6BAA6B,CAAC,CAAC;IAExE,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,QAAQ,EACR,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,EAC9B,oBAAoB,CACrB,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAuB,EACvB,IAAkB;IAElB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAE5C,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACvD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,gCAAgC,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;IAE1B,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAC7D,OAAO,IAAI,CAAC,MAAM,CAAC,4BAA4B,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,UAAU,CAAC,0BAA0B,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;IAC3D,MAAM,CAAC,IAAI,CAAC,yCAAyC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC;IAE3E,MAAM,cAAc,GAAG,qBAAqB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC1D,MAAM,kBAAkB,GAAG,cAAc,KAAK,gCAAgC,CAAC;IAC/E,MAAM,QAAQ,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAElD,IAAI,SAAS,GAAiB,IAAI,CAAC;IAEnC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC;QAC3D,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QACtD,MAAM,cAAc,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAE1D,IAAI,CAAC;YACH,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,MAAM,WAAW,CAClD,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAChD,SAAS,CACV,CAAC;YAEF,MAAM,CAAC,IAAI,CAAC,yDAAyD,YAAY,EAAE,CAAC,CAAC;YACrF,MAAM,CAAC,IAAI,CAAC,0BAA0B,QAAQ,EAAE,CAAC,CAAC;YAElD,sEAAsE;YACtE,mEAAmE;YACnE,wEAAwE;YACxE,sEAAsE;YACtE,0CAA0C;YAC1C,MAAM,aAAa,GAAG,KAAK,CAAC,OAAO,IAAI,QAAQ,CAAC;YAEhD,MAAM,KAAK,GAAe;gBACxB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,CAAC,QAAQ,CAAC;gBAChB,KAAK,EAAE;oBACL,EAAE,EAAE,aAAa;oBACjB,IAAI,EAAE,KAAK,CAAC,WAAW;oBACvB,GAAG,EAAE,YAAY;oBACjB,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,KAAK,CAAC,WAAW;oBACzB,MAAM,EAAE,WAAW;oBACnB,IAAI,EAAE,CAAC;oBACP,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;iBACtD;aACF,CAAC;YAEF,OAAO,EAAE,CACP,wBAAwB,YAAY,EAAE,EACtC,CAAC,KAAK,CAAC,EACP;gBACE,YAAY;gBACZ,QAAQ,EAAE,aAAa;gBACvB,aAAa,EAAE,cAAc,CAAC,MAAM;gBACpC,KAAK;gBACL,QAAQ,EAAE,IAAI;aACf,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;YACjF,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,eAAe,KAAK,iBAAiB,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,kBAAkB;gBAC/B,CAAC,CAAC,GAAG,KAAK,4BAA4B,IAAI,CAAC,KAAK,EAAE;gBAClD,CAAC,CAAC,WAAW,OAAO,GAAG,CAAC,mBAAmB,CAAC;YAC9C,MAAM,CAAC,IAAI,CAAC,eAAe,MAAM,OAAO,cAAc,OAAO,CAAC,CAAC;YAC/D,MAAM,KAAK,CAAC,cAAc,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,SAAS,EAAE,OAAO,IAAI,8BAA8B,CAAC;IACrE,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,uCAAuC,QAAQ,CAAC,MAAM,WAAW,CAAC,CAAC;IACpG,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { AssetResultBase, GenerateDeps } from '../types.js';
|
|
3
|
+
export declare const characterParamsSchema: z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
prompt: z.ZodString;
|
|
6
|
+
applyToPlayer: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
|
+
headScale: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
+
assetId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, "strict", z.ZodTypeAny, {
|
|
10
|
+
prompt: string;
|
|
11
|
+
name: string;
|
|
12
|
+
applyToPlayer: boolean;
|
|
13
|
+
headScale: number;
|
|
14
|
+
assetId?: string | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
prompt: string;
|
|
17
|
+
name: string;
|
|
18
|
+
assetId?: string | undefined;
|
|
19
|
+
applyToPlayer?: boolean | undefined;
|
|
20
|
+
headScale?: number | undefined;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* The schema's INPUT type — callers may omit the defaulted fields. The
|
|
24
|
+
* generator parses on entry and works with the output type internally, which
|
|
25
|
+
* is what lets it own its own defaults instead of each host patching them in.
|
|
26
|
+
*/
|
|
27
|
+
export type CharacterParams = z.input<typeof characterParamsSchema>;
|
|
28
|
+
export type CharacterResult = AssetResultBase & {
|
|
29
|
+
assetId?: string;
|
|
30
|
+
characterUrl?: string;
|
|
31
|
+
/**
|
|
32
|
+
* True once the Asset Forger has accepted the rigged-character job — the
|
|
33
|
+
* vendor bills on acceptance, so a downstream failure (no resolvable URL)
|
|
34
|
+
* still costs. Left unset when the job submission itself threw, which is
|
|
35
|
+
* how the tool has always billed.
|
|
36
|
+
*/
|
|
37
|
+
billable?: boolean;
|
|
38
|
+
};
|
|
39
|
+
export declare function generateCharacter(params: CharacterParams, deps: GenerateDeps): Promise<CharacterResult>;
|
|
40
|
+
//# sourceMappingURL=character.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"character.d.ts","sourceRoot":"","sources":["../../src/generators/character.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAc,MAAM,aAAa,CAAC;AAI7E,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;EAqBvB,CAAC;AAEZ;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAWF,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,eAAe,CAAC,CA6H1B"}
|