@forgeax-extension/character-3d 0.1.2

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 (110) hide show
  1. package/SOURCE_PROVENANCE.md +8 -0
  2. package/dist/assets/index-CIx3aXo1.js +4336 -0
  3. package/dist/assets/index-CIx3aXo1.js.map +1 -0
  4. package/dist/assets/index-CmhOHLEB.css +1 -0
  5. package/dist/hdr/README.md +28 -0
  6. package/dist/hdr/presets.json +8 -0
  7. package/dist/index.html +13 -0
  8. package/forgeax-extension.json +428 -0
  9. package/marketplace-card.json +56 -0
  10. package/package.json +59 -0
  11. package/packages/external-asset-meta/package.json +24 -0
  12. package/packages/external-asset-meta/src/cook.test.ts +211 -0
  13. package/packages/external-asset-meta/src/cook.ts +188 -0
  14. package/packages/external-asset-meta/src/draco3dgltf.d.ts +11 -0
  15. package/packages/external-asset-meta/src/index.ts +18 -0
  16. package/packages/external-asset-meta/src/normalize.test.ts +36 -0
  17. package/packages/external-asset-meta/src/normalize.ts +101 -0
  18. package/packages/external-asset-meta/src/types.ts +82 -0
  19. package/schemas/adopt-playable-character.args.json +26 -0
  20. package/schemas/adopt-playable-character.returns.json +31 -0
  21. package/schemas/apply-motion.args.json +47 -0
  22. package/schemas/apply-motion.returns.json +23 -0
  23. package/schemas/auto-rig.args.json +35 -0
  24. package/schemas/auto-rig.returns.json +23 -0
  25. package/schemas/delete-asset.args.json +18 -0
  26. package/schemas/delete-asset.returns.json +18 -0
  27. package/schemas/engine-import-status.args.json +18 -0
  28. package/schemas/engine-import-status.returns.json +50 -0
  29. package/schemas/export-playable-character.args.json +21 -0
  30. package/schemas/export-playable-character.returns.json +47 -0
  31. package/schemas/gen3d-asset-manifest.json +281 -0
  32. package/schemas/generate-meshy-text-mock.args.json +42 -0
  33. package/schemas/generate-meshy-text-mock.returns.json +301 -0
  34. package/schemas/get-credentials.args.json +7 -0
  35. package/schemas/get-credentials.returns.json +45 -0
  36. package/schemas/get-playable-profile.args.json +11 -0
  37. package/schemas/get-playable-profile.returns.json +188 -0
  38. package/schemas/image-to-3d.args.json +47 -0
  39. package/schemas/image-to-3d.returns.json +306 -0
  40. package/schemas/import-to-engine.args.json +18 -0
  41. package/schemas/import-to-engine.returns.json +35 -0
  42. package/schemas/list-assets.args.json +22 -0
  43. package/schemas/list-assets.returns.json +296 -0
  44. package/schemas/list-motions.args.json +29 -0
  45. package/schemas/list-motions.returns.json +41 -0
  46. package/schemas/pose-standardization.args.json +28 -0
  47. package/schemas/pose-standardization.returns.json +29 -0
  48. package/schemas/provider-status.args.json +7 -0
  49. package/schemas/provider-status.returns.json +70 -0
  50. package/schemas/refine-mesh.args.json +33 -0
  51. package/schemas/refine-mesh.returns.json +306 -0
  52. package/schemas/rename-asset.args.json +22 -0
  53. package/schemas/rename-asset.returns.json +14 -0
  54. package/schemas/retopo-lowpoly.args.json +34 -0
  55. package/schemas/retopo-lowpoly.returns.json +24 -0
  56. package/schemas/score-quality.args.json +36 -0
  57. package/schemas/score-quality.returns.json +12 -0
  58. package/schemas/set-credentials.args.json +32 -0
  59. package/schemas/set-credentials.returns.json +45 -0
  60. package/schemas/set-playable-motion-mapping.args.json +25 -0
  61. package/schemas/set-playable-motion-mapping.returns.json +33 -0
  62. package/schemas/set-playable-profile.args.json +32 -0
  63. package/schemas/set-playable-profile.returns.json +67 -0
  64. package/schemas/text-to-3d.args.json +51 -0
  65. package/schemas/text-to-3d.returns.json +306 -0
  66. package/schemas/upload-image.args.json +19 -0
  67. package/schemas/upload-image.returns.json +21 -0
  68. package/schemas/upload-video.args.json +14 -0
  69. package/schemas/upload-video.returns.json +14 -0
  70. package/schemas/views-to-3d.args.json +58 -0
  71. package/schemas/views-to-3d.returns.json +306 -0
  72. package/server/adopt-playable-character.ts +291 -0
  73. package/server/asset-storage.ts +136 -0
  74. package/server/asset-upload.ts +123 -0
  75. package/server/audit.ts +58 -0
  76. package/server/cache.ts +75 -0
  77. package/server/cos-uploader.ts +141 -0
  78. package/server/credentials-store.ts +186 -0
  79. package/server/engine-import.ts +238 -0
  80. package/server/env.ts +287 -0
  81. package/server/export-playable-character.ts +364 -0
  82. package/server/generate.ts +89 -0
  83. package/server/merge-playable-character.ts +259 -0
  84. package/server/motion-catalog.ts +137 -0
  85. package/server/per-game-store.ts +1016 -0
  86. package/server/providers/gateway-client.ts +171 -0
  87. package/server/providers/gateway-data.ts +182 -0
  88. package/server/providers/gateway-models.ts +49 -0
  89. package/server/providers/hunyuan-rest.ts +317 -0
  90. package/server/providers/hunyuan-workflow.ts +225 -0
  91. package/server/providers/meshy-direct-client.ts +223 -0
  92. package/server/providers/meshy.ts +548 -0
  93. package/server/providers/rodin-gateway.ts +193 -0
  94. package/server/providers/rodin.ts +253 -0
  95. package/server/providers/visvise.ts +329 -0
  96. package/server/rate-guard.ts +29 -0
  97. package/server/tool-handlers.ts +2193 -0
  98. package/shared/catalog.ts +307 -0
  99. package/shared/manifest.ts +378 -0
  100. package/shared/meshy-actions.ts +690 -0
  101. package/shared/playable-preview-url.test.ts +14 -0
  102. package/shared/playable-preview-url.ts +13 -0
  103. package/shared/playable-profile.test.ts +127 -0
  104. package/shared/playable-profile.ts +171 -0
  105. package/shared/provider-params.test.ts +172 -0
  106. package/shared/provider-params.ts +389 -0
  107. package/shared/quality/heuristics.test.ts +84 -0
  108. package/shared/quality/heuristics.ts +100 -0
  109. package/shared/rodin-image.test.ts +51 -0
  110. package/shared/rodin-image.ts +125 -0
@@ -0,0 +1,223 @@
1
+ // Local Meshy OpenAPI transport. Production Meshy stays on LiteLLM
2
+ // (`GatewayClient` + `/v1/3d/generations`). This client is only constructed
3
+ // when GEN3D_MESHY_DIRECT=1 and MESHY_API_KEY are set — a recording-machine
4
+ // override that talks to api.meshy.ai with the operator's own credits.
5
+ //
6
+ // It presents the same post/get/pollTask/download surface as GatewayClient
7
+ // so MeshyProvider does not grow a second generate/rig/animate implementation.
8
+ // Submit/poll go to `/openapi/v1/...`; responses are reshaped to the gateway
9
+ // `data[]` envelope that extractGatewayUrls / extractRigUrls already understand.
10
+
11
+ import { GatewayClient, httpError, type FetchLike } from './gateway-client';
12
+ import { MESHY_GATEWAY_MODELS } from './gateway-models';
13
+ import type { LitellmEnv } from '../env';
14
+
15
+ export interface DirectMeshyClientDeps {
16
+ env: LitellmEnv;
17
+ fetchImpl?: FetchLike;
18
+ sleep?: (ms: number) => Promise<void>;
19
+ }
20
+
21
+ const DIRECT_FAILURE = new Set(['failed', 'error', 'fail', 'canceled', 'cancelled']);
22
+
23
+ // Survives a new DirectMeshyClient between submit and poll. MeshyProvider
24
+ // used to construct a fresh client for every gateway() call.
25
+ const pollPathByTaskId = new Map<string, string>();
26
+
27
+ export function meshyDirectRoute(model: unknown): { submit: string; pollPrefix: string } | null {
28
+ switch (model) {
29
+ case MESHY_GATEWAY_MODELS.text:
30
+ case MESHY_GATEWAY_MODELS.refine:
31
+ // Meshy retired /openapi/v1/text-to-3d (404 NoMatchingRoute as of 2026-08).
32
+ return { submit: '/openapi/v2/text-to-3d', pollPrefix: '/openapi/v2/text-to-3d' };
33
+ case MESHY_GATEWAY_MODELS.image:
34
+ return { submit: '/openapi/v1/image-to-3d', pollPrefix: '/openapi/v1/image-to-3d' };
35
+ case MESHY_GATEWAY_MODELS.views:
36
+ return { submit: '/openapi/v1/multi-image-to-3d', pollPrefix: '/openapi/v1/multi-image-to-3d' };
37
+ case MESHY_GATEWAY_MODELS.autoRig:
38
+ return { submit: '/openapi/v1/rigging', pollPrefix: '/openapi/v1/rigging' };
39
+ case MESHY_GATEWAY_MODELS.animation:
40
+ return { submit: '/openapi/v1/animations', pollPrefix: '/openapi/v1/animations' };
41
+ default:
42
+ return null;
43
+ }
44
+ }
45
+
46
+ export function toGatewayMeshyTask(direct: Record<string, unknown>): Record<string, unknown> {
47
+ const status = normalizeDirectStatus(direct.status);
48
+ const data = collectDirectData(direct);
49
+ const id = firstString(direct.id, direct.result);
50
+ return {
51
+ ...direct,
52
+ ...(id ? { id } : {}),
53
+ status,
54
+ data,
55
+ };
56
+ }
57
+
58
+ export class DirectMeshyClient extends GatewayClient {
59
+ private readonly pollPathById = new Map<string, string>();
60
+ private readonly fetchDirect: FetchLike;
61
+ private readonly key: string;
62
+ private readonly host: string;
63
+
64
+ constructor(deps: DirectMeshyClientDeps) {
65
+ super(deps);
66
+ this.fetchDirect = deps.fetchImpl ?? ((url, init) => fetch(url, init));
67
+ this.key = deps.env.apiKey;
68
+ this.host = deps.env.baseUrl.replace(/\/+$/, '');
69
+ }
70
+
71
+ override async post(path: string, body: unknown): Promise<Record<string, unknown>> {
72
+ if (path !== '/v1/3d/generations') {
73
+ throw Object.assign(new Error(`meshy direct client does not post ${path}`), {
74
+ code: 'provider_bad_request',
75
+ });
76
+ }
77
+ const payload = asRecord(body);
78
+ if (!payload) {
79
+ throw Object.assign(new Error('meshy direct submit needs a JSON object'), {
80
+ code: 'provider_bad_request',
81
+ });
82
+ }
83
+ const route = meshyDirectRoute(payload.model);
84
+ if (!route) {
85
+ throw Object.assign(new Error(`meshy direct: unsupported model ${String(payload.model)}`), {
86
+ code: 'provider_bad_request',
87
+ });
88
+ }
89
+ const { model: _model, ...rest } = payload;
90
+ const resp = await this.fetchDirect(`${this.host}${route.submit}`, {
91
+ method: 'POST',
92
+ headers: {
93
+ 'Content-Type': 'application/json',
94
+ Authorization: `Bearer ${this.key}`,
95
+ },
96
+ body: JSON.stringify(rest),
97
+ });
98
+ const json = await readJson(resp);
99
+ const id = firstString(json.result, json.id);
100
+ if (!id) {
101
+ throw Object.assign(new Error('meshy direct submit returned no task id'), {
102
+ code: 'provider_no_task',
103
+ });
104
+ }
105
+ const pollPath = `${route.pollPrefix}/${id}`;
106
+ this.pollPathById.set(id, pollPath);
107
+ pollPathByTaskId.set(id, pollPath);
108
+ return { id, status: 'processing' };
109
+ }
110
+
111
+ override async get(path: string): Promise<Record<string, unknown>> {
112
+ const id = path.split('/').filter(Boolean).pop() ?? '';
113
+ const pollPath =
114
+ this.pollPathById.get(id) ?? pollPathByTaskId.get(id) ?? guessPollPath(path, id);
115
+ const resp = await this.fetchDirect(`${this.host}${pollPath}`, {
116
+ method: 'GET',
117
+ headers: { Authorization: `Bearer ${this.key}` },
118
+ });
119
+ return toGatewayMeshyTask(await readJson(resp));
120
+ }
121
+ }
122
+
123
+ function guessPollPath(gatewayPath: string, id: string): string {
124
+ if (gatewayPath.startsWith('/openapi/')) return gatewayPath;
125
+ return `/openapi/v1/image-to-3d/${id}`;
126
+ }
127
+
128
+ function normalizeDirectStatus(status: unknown): string {
129
+ const value = String(status ?? '').toLowerCase();
130
+ if (value === 'succeeded') return 'succeeded';
131
+ if (DIRECT_FAILURE.has(value)) return 'failed';
132
+ return value || 'pending';
133
+ }
134
+
135
+ function collectDirectData(direct: Record<string, unknown>): Array<Record<string, unknown>> {
136
+ const data: Array<Record<string, unknown>> = [];
137
+ const seen = new Set<string>();
138
+ const push = (url: unknown, format: string, name: string, type = 'mesh') => {
139
+ if (typeof url !== 'string' || !url || seen.has(`${name}:${format}`)) return;
140
+ seen.add(`${name}:${format}`);
141
+ data.push({ url, type, format, name: `${name}.${format}` });
142
+ };
143
+
144
+ const modelUrls = asRecord(direct.model_urls);
145
+ if (modelUrls) {
146
+ for (const [fmt, url] of Object.entries(modelUrls)) {
147
+ if (typeof url === 'string' && url) push(url, fmt, 'model');
148
+ }
149
+ }
150
+
151
+ // Official poll `result` is an object. Submit/pending may put the task id
152
+ // string in `result`, so fall back to the top-level payload for those fields.
153
+ const result = asRecord(direct.result) ?? {};
154
+ const character = asRecord(result.rigged_character) ?? asRecord(direct.rigged_character) ?? {};
155
+ // Official 2026 rig envelope is flat on `result`:
156
+ // rigged_character_glb_url / rigged_character_fbx_url
157
+ // Older fixtures nest `{ glb_url, fbx_url }` under `rigged_character`.
158
+ push(
159
+ firstString(result.rigged_character_glb_url, character.glb_url, direct.rigged_character_glb_url),
160
+ 'glb',
161
+ 'Character_output',
162
+ );
163
+ push(
164
+ firstString(result.rigged_character_fbx_url, character.fbx_url, direct.rigged_character_fbx_url),
165
+ 'fbx',
166
+ 'Character_output',
167
+ );
168
+
169
+ const animations = asRecord(result.basic_animations) ?? asRecord(direct.basic_animations);
170
+ if (animations) {
171
+ const walking = asRecord(animations.walking) ?? {};
172
+ const running = asRecord(animations.running) ?? {};
173
+ push(
174
+ firstString(animations.walking_glb_url, walking.glb_url),
175
+ 'glb',
176
+ 'Animation_Walking_withSkin',
177
+ );
178
+ push(
179
+ firstString(animations.walking_fbx_url, walking.fbx_url),
180
+ 'fbx',
181
+ 'Animation_Walking_withSkin',
182
+ );
183
+ push(
184
+ firstString(animations.running_glb_url, running.glb_url),
185
+ 'glb',
186
+ 'Animation_Running_withSkin',
187
+ );
188
+ push(
189
+ firstString(animations.running_fbx_url, running.fbx_url),
190
+ 'fbx',
191
+ 'Animation_Running_withSkin',
192
+ );
193
+ }
194
+
195
+ // Official animate result (PLAN 2.3). Same last-wins format key as generate.
196
+ push(firstString(result.animation_glb_url, direct.animation_glb_url), 'glb', 'animation');
197
+ push(firstString(result.animation_fbx_url, direct.animation_fbx_url), 'fbx', 'animation');
198
+
199
+ const thumb = firstString(direct.thumbnail_url, result.thumbnail_url);
200
+ if (thumb) data.push({ url: thumb, type: 'preview', format: 'png', name: 'preview.png' });
201
+ return data;
202
+ }
203
+
204
+ function asRecord(value: unknown): Record<string, unknown> | null {
205
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
206
+ ? (value as Record<string, unknown>)
207
+ : null;
208
+ }
209
+
210
+ function firstString(...values: unknown[]): string | null {
211
+ for (const value of values) {
212
+ if (typeof value === 'string' && value.trim()) return value.trim();
213
+ }
214
+ return null;
215
+ }
216
+
217
+ async function readJson(resp: Response): Promise<Record<string, unknown>> {
218
+ if (!resp.ok) {
219
+ const body = await resp.text().catch(() => '');
220
+ throw httpError(resp.status, body);
221
+ }
222
+ return (await resp.json()) as Record<string, unknown>;
223
+ }