@wix/mcp 1.0.97 → 1.0.98
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/build/bin-standalone.js +2 -8
- package/build/cjs/index.cjs +444 -11
- package/build/dts/api-call/availability-tool/index.js +1 -4
- package/build/dts/api-call/suggested-domain-tool/index.js +1 -4
- package/build/dts/auth/index.d.ts +14 -3
- package/build/dts/toolkit-entry.d.cts +14 -3
- package/build/dts/toolkit-entry.d.ts +14 -3
- package/build/dts/wixel/api.d.ts +23 -0
- package/build/dts/wixel/api.js +47 -0
- package/build/dts/wixel/app-icon-files.d.ts +19 -0
- package/build/dts/wixel/app-icon-files.js +77 -0
- package/build/dts/wixel/index.d.ts +1 -0
- package/build/dts/wixel/index.js +4 -0
- package/build/dts/wixel/wixel-asset-files-tool.d.ts +29 -0
- package/build/dts/wixel/wixel-asset-files-tool.js +327 -0
- package/build/dts/wixel/wixel-session-entries.d.ts +28 -0
- package/build/dts/wixel/wixel-session-entries.js +34 -1
- package/build/dts/wixel/wixel-session-tools.js +13 -2
- package/build/esm/index.js +443 -11
- package/package.json +2 -2
package/build/dts/wixel/api.d.ts
CHANGED
|
@@ -62,4 +62,27 @@ export interface WixelStylePresetSummary {
|
|
|
62
62
|
raw?: Record<string, unknown>;
|
|
63
63
|
}
|
|
64
64
|
export declare function getStylePresetsByIds({ siteAuthHeaders, stylePresetIds, apiBaseURL, conductorOverride }: GetStylePresetsOptions): Promise<WixelStylePresetSummary[]>;
|
|
65
|
+
export interface AssetExportOptions {
|
|
66
|
+
siteAuthHeaders: Record<string, string>;
|
|
67
|
+
projectId: string;
|
|
68
|
+
assetType: string;
|
|
69
|
+
apiBaseURL?: string;
|
|
70
|
+
conductorOverride?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface AssetExportState {
|
|
73
|
+
complete: boolean;
|
|
74
|
+
progress: number | null;
|
|
75
|
+
url: string | null;
|
|
76
|
+
errorDescription: string | null;
|
|
77
|
+
title: string | null;
|
|
78
|
+
}
|
|
79
|
+
export declare const WIXEL_HOST_EXPORT_ASSET_PATH = "/_api/wixel/v1/export/asset";
|
|
80
|
+
export declare const WIXEL_HOST_EXPORT_STATE_PATH = "/_api/wixel/v1/export/state";
|
|
81
|
+
export declare function startAssetExport({ assetId, params, ...options }: AssetExportOptions & {
|
|
82
|
+
assetId: string;
|
|
83
|
+
params?: Record<string, unknown>;
|
|
84
|
+
}): Promise<string>;
|
|
85
|
+
export declare function getAssetExportState({ jobId, ...options }: AssetExportOptions & {
|
|
86
|
+
jobId: string;
|
|
87
|
+
}): Promise<AssetExportState>;
|
|
65
88
|
//# sourceMappingURL=api.d.ts.map
|
package/build/dts/wixel/api.js
CHANGED
|
@@ -100,4 +100,51 @@ export async function getStylePresetsByIds({ siteAuthHeaders, stylePresetIds, ap
|
|
|
100
100
|
};
|
|
101
101
|
});
|
|
102
102
|
}
|
|
103
|
+
function wixelHostClient({ siteAuthHeaders, apiBaseURL = WWW_ASSET_V1_ASSET_API_BASE_URL, conductorOverride }) {
|
|
104
|
+
return createHttpClient({
|
|
105
|
+
headers: {
|
|
106
|
+
...COMMON_HEADERS,
|
|
107
|
+
...siteAuthHeaders,
|
|
108
|
+
...(conductorOverride ? { ...CONDUCTOR_OVERRIDE_HEADERS } : {})
|
|
109
|
+
},
|
|
110
|
+
baseURL: apiBaseURL
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// WixelHost.ExportAsset — the same export the editor's Download button starts.
|
|
114
|
+
// The host hands it to the asset type's own export SPI (icons, logo, slides…),
|
|
115
|
+
// which reads `params` (e.g. icons: fileType, size, layout).
|
|
116
|
+
// WixelHost REST paths on manage.wix.com — the same endpoints the editor's
|
|
117
|
+
// Download button reaches through @wix/ambassador-wixel-v1-asset-dynamic-payload.
|
|
118
|
+
// Called directly so the published package keeps zod as its only dependency
|
|
119
|
+
// and no new client library is added.
|
|
120
|
+
export const WIXEL_HOST_EXPORT_ASSET_PATH = '/_api/wixel/v1/export/asset';
|
|
121
|
+
export const WIXEL_HOST_EXPORT_STATE_PATH = '/_api/wixel/v1/export/state';
|
|
122
|
+
export async function startAssetExport({ assetId, params, ...options }) {
|
|
123
|
+
const { data } = await wixelHostClient(options).post(WIXEL_HOST_EXPORT_ASSET_PATH, {
|
|
124
|
+
projectId: options.projectId,
|
|
125
|
+
assetId,
|
|
126
|
+
assetType: options.assetType,
|
|
127
|
+
...(params ? { params } : {})
|
|
128
|
+
});
|
|
129
|
+
if (!data?.jobId) {
|
|
130
|
+
throw new Error('Wixel did not start an export job for this asset');
|
|
131
|
+
}
|
|
132
|
+
return data.jobId;
|
|
133
|
+
}
|
|
134
|
+
// WixelHost.GetJobState — progress, and the file URL once complete.
|
|
135
|
+
export async function getAssetExportState({ jobId, ...options }) {
|
|
136
|
+
const { data } = await wixelHostClient(options).post(WIXEL_HOST_EXPORT_STATE_PATH, {
|
|
137
|
+
jobId,
|
|
138
|
+
projectId: options.projectId,
|
|
139
|
+
assetType: options.assetType
|
|
140
|
+
});
|
|
141
|
+
const state = data?.state ?? {};
|
|
142
|
+
return {
|
|
143
|
+
complete: state.complete === true,
|
|
144
|
+
progress: state.progress ?? null,
|
|
145
|
+
url: state.url ?? null,
|
|
146
|
+
errorDescription: state.errorDescription ?? null,
|
|
147
|
+
title: state.title ?? null
|
|
148
|
+
};
|
|
149
|
+
}
|
|
103
150
|
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface AppIconFile {
|
|
2
|
+
/** Where the file goes in the app project. */
|
|
3
|
+
path: string;
|
|
4
|
+
/** Download URL (PNG files). */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Inline content (JSON files). */
|
|
7
|
+
content?: string;
|
|
8
|
+
}
|
|
9
|
+
/** The Wix Media file a URL points at, without any transform; null when it is not one. */
|
|
10
|
+
export declare function wixMediaFileUrl(url: string): string | null;
|
|
11
|
+
/** A square PNG of the file at `size` px, rendered by Wix Media. */
|
|
12
|
+
export declare function wixMediaSquarePng(fileUrl: string, size: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Every app-icon file for one exported PNG. Returns null when the PNG is not on
|
|
15
|
+
* Wix Media (the sizes cannot be made by URL), so the caller hands back the
|
|
16
|
+
* single file instead.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildAppIconFiles(exportedPngUrl: string, appName: string): AppIconFile[] | null;
|
|
19
|
+
//# sourceMappingURL=app-icon-files.d.ts.map
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// App-icon size pack for an app icon staged as an IMAGE asset (the app-icon
|
|
2
|
+
// skill stages each tile as a full-bleed 1024 × 1024 IMAGE). The export gives
|
|
3
|
+
// one PNG on Wix Media; every other size is a Wix Media resize of that file
|
|
4
|
+
// (`/v1/fill/w_N,h_N/…`), so the pack is a list of URLs plus the two small
|
|
5
|
+
// JSON files the platforms read — no image processing in the MCP server.
|
|
6
|
+
//
|
|
7
|
+
// ICONS sets get the same pack as a zip from the icons export itself
|
|
8
|
+
// (layout "appIcons"); this is the IMAGE counterpart.
|
|
9
|
+
const WIX_MEDIA_FILE = /^(https:\/\/static\.wixstatic\.com\/media\/[^/?#]+)/;
|
|
10
|
+
const ANDROID_MIPMAPS = [
|
|
11
|
+
['mdpi', 48],
|
|
12
|
+
['hdpi', 72],
|
|
13
|
+
['xhdpi', 96],
|
|
14
|
+
['xxhdpi', 144],
|
|
15
|
+
['xxxhdpi', 192]
|
|
16
|
+
];
|
|
17
|
+
/** The Wix Media file a URL points at, without any transform; null when it is not one. */
|
|
18
|
+
export function wixMediaFileUrl(url) {
|
|
19
|
+
return WIX_MEDIA_FILE.exec(url)?.[1] ?? null;
|
|
20
|
+
}
|
|
21
|
+
/** A square PNG of the file at `size` px, rendered by Wix Media. */
|
|
22
|
+
export function wixMediaSquarePng(fileUrl, size) {
|
|
23
|
+
return `${fileUrl}/v1/fill/w_${size},h_${size},al_c,q_95/icon-${size}.png`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Every app-icon file for one exported PNG. Returns null when the PNG is not on
|
|
27
|
+
* Wix Media (the sizes cannot be made by URL), so the caller hands back the
|
|
28
|
+
* single file instead.
|
|
29
|
+
*/
|
|
30
|
+
export function buildAppIconFiles(exportedPngUrl, appName) {
|
|
31
|
+
const file = wixMediaFileUrl(exportedPngUrl);
|
|
32
|
+
if (!file) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const png = (path, size) => ({
|
|
36
|
+
path,
|
|
37
|
+
url: wixMediaSquarePng(file, size)
|
|
38
|
+
});
|
|
39
|
+
return [
|
|
40
|
+
png('ios/AppIcon.appiconset/icon-1024.png', 1024),
|
|
41
|
+
{
|
|
42
|
+
path: 'ios/AppIcon.appiconset/Contents.json',
|
|
43
|
+
content: JSON.stringify({
|
|
44
|
+
images: [
|
|
45
|
+
{
|
|
46
|
+
filename: 'icon-1024.png',
|
|
47
|
+
idiom: 'universal',
|
|
48
|
+
platform: 'ios',
|
|
49
|
+
size: '1024x1024'
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
info: { author: 'wixel', version: 1 }
|
|
53
|
+
}, null, 2)
|
|
54
|
+
},
|
|
55
|
+
...ANDROID_MIPMAPS.map(([density, size]) => png(`android/res/mipmap-${density}/ic_launcher.png`, size)),
|
|
56
|
+
png('android/play-store-512.png', 512),
|
|
57
|
+
png('web/favicon-16.png', 16),
|
|
58
|
+
png('web/favicon-32.png', 32),
|
|
59
|
+
png('web/favicon-48.png', 48),
|
|
60
|
+
png('web/apple-touch-icon.png', 180),
|
|
61
|
+
png('web/icon-192.png', 192),
|
|
62
|
+
png('web/icon-512.png', 512),
|
|
63
|
+
{
|
|
64
|
+
path: 'web/site.webmanifest',
|
|
65
|
+
content: JSON.stringify({
|
|
66
|
+
name: appName,
|
|
67
|
+
short_name: appName.slice(0, 12),
|
|
68
|
+
icons: [
|
|
69
|
+
{ src: 'icon-192.png', sizes: '192x192', type: 'image/png' },
|
|
70
|
+
{ src: 'icon-512.png', sizes: '512x512', type: 'image/png' }
|
|
71
|
+
],
|
|
72
|
+
display: 'standalone'
|
|
73
|
+
}, null, 2)
|
|
74
|
+
}
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=app-icon-files.js.map
|
|
@@ -6,6 +6,7 @@ export { addGetWixelJobStatusTool } from './get-wixel-job-status-tool.js';
|
|
|
6
6
|
export { addMarkWixelJobAsFulfilledTool } from './mark-wixel-job-as-fulfilled-tool.js';
|
|
7
7
|
export { addGetWixelAssetTool } from './get-wixel-asset-tool.js';
|
|
8
8
|
export { addGetWixelProjectAssetsTool } from './get-wixel-project-assets-tool.js';
|
|
9
|
+
export { addGetWixelAssetFilesTool } from './wixel-asset-files-tool.js';
|
|
9
10
|
export declare function addWixelTools(server: WixMcpServer, authStrategy: McpAuthenticationStrategy, options?: {
|
|
10
11
|
conductorOverride?: string;
|
|
11
12
|
supportsWidgets?: () => boolean;
|
package/build/dts/wixel/index.js
CHANGED
|
@@ -3,12 +3,14 @@ import { addMarkWixelJobAsFulfilledTool } from './mark-wixel-job-as-fulfilled-to
|
|
|
3
3
|
import { addGetWixelAssetTool } from './get-wixel-asset-tool.js';
|
|
4
4
|
import { addGetWixelProjectAssetsTool } from './get-wixel-project-assets-tool.js';
|
|
5
5
|
import { addWixelSessionTools } from './wixel-session-tools.js';
|
|
6
|
+
import { addGetWixelAssetFilesTool } from './wixel-asset-files-tool.js';
|
|
6
7
|
export { WIXEL_ICON_SVG_URL, WIXEL_ICON_PNG_URL } from './constants.js';
|
|
7
8
|
export { addWixelSessionTools, addSendWixelMessageTool, addGetWixelTaskTool } from './wixel-session-tools.js';
|
|
8
9
|
export { addGetWixelJobStatusTool } from './get-wixel-job-status-tool.js';
|
|
9
10
|
export { addMarkWixelJobAsFulfilledTool } from './mark-wixel-job-as-fulfilled-tool.js';
|
|
10
11
|
export { addGetWixelAssetTool } from './get-wixel-asset-tool.js';
|
|
11
12
|
export { addGetWixelProjectAssetsTool } from './get-wixel-project-assets-tool.js';
|
|
13
|
+
export { addGetWixelAssetFilesTool } from './wixel-asset-files-tool.js';
|
|
12
14
|
export function addWixelTools(server, authStrategy, options = {}) {
|
|
13
15
|
addGetWixelJobStatusTool(server, authStrategy, options);
|
|
14
16
|
addMarkWixelJobAsFulfilledTool(server, authStrategy, options);
|
|
@@ -16,5 +18,7 @@ export function addWixelTools(server, authStrategy, options = {}) {
|
|
|
16
18
|
addGetWixelProjectAssetsTool(server, authStrategy, options);
|
|
17
19
|
// Conversation tools over the Wixel agent's A2A endpoint
|
|
18
20
|
addWixelSessionTools(server, authStrategy, options);
|
|
21
|
+
// Engine-ready files of an asset (sprite sheet, app-icon pack, PNGs)
|
|
22
|
+
addGetWixelAssetFilesTool(server, authStrategy, options);
|
|
19
23
|
}
|
|
20
24
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { McpAuthenticationStrategy } from '../auth/index.js';
|
|
2
|
+
import { WixMcpServer } from '../wix-mcp-server.js';
|
|
3
|
+
export declare const ASSET_FILE_LAYOUTS: readonly ["files", "spriteSheet", "appIcons"];
|
|
4
|
+
export type AssetFileLayout = (typeof ASSET_FILE_LAYOUTS)[number];
|
|
5
|
+
declare const ICONS_FILE_TYPES: readonly ["png", "pngTransparentBackground", "svg", "svgTransparentBackground"];
|
|
6
|
+
declare const ICONS_SIZES: readonly ["16px", "24px", "32px", "64px", "128px", "256px", "512px"];
|
|
7
|
+
export interface AssetFilesArgs {
|
|
8
|
+
metaSiteId: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
assetId?: string;
|
|
11
|
+
layout?: AssetFileLayout;
|
|
12
|
+
fileType?: (typeof ICONS_FILE_TYPES)[number];
|
|
13
|
+
size?: (typeof ICONS_SIZES)[number];
|
|
14
|
+
jobId?: string;
|
|
15
|
+
assetType?: string;
|
|
16
|
+
waitSeconds?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The export params for an asset type. Only ICONS reads layout / fileType /
|
|
20
|
+
* size; other types export with their own defaults. A layout that needs an
|
|
21
|
+
* icon set is refused for any other type instead of silently exporting
|
|
22
|
+
* something else.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildExportParams(assetType: string, args: Pick<AssetFilesArgs, 'layout' | 'fileType' | 'size'>): Record<string, unknown> | undefined;
|
|
25
|
+
export declare function addGetWixelAssetFilesTool(server: WixMcpServer, authStrategy: McpAuthenticationStrategy, options?: {
|
|
26
|
+
conductorOverride?: string;
|
|
27
|
+
}): void;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=wixel-asset-files-tool.d.ts.map
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { rethrowIfUnauthorized } from '../auth/index.js';
|
|
2
|
+
import { setWixelToolBi as setBi } from './wixel-bi.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { getAsset, getAssetExportState, queryAssets, startAssetExport } from './api.js';
|
|
5
|
+
import { buildAppIconFiles } from './app-icon-files.js';
|
|
6
|
+
import { getWixelSessionProjectId } from './wixel-session-entries.js';
|
|
7
|
+
// get_wixel_asset_files: the files of a Wixel asset, for a coding agent that
|
|
8
|
+
// builds something with them (a game, an app, a site). It runs the same export
|
|
9
|
+
// the editor's Download button runs and returns the file URL.
|
|
10
|
+
//
|
|
11
|
+
// It takes the conversation's metaSiteId + sessionId (both returned by
|
|
12
|
+
// send_wixel_message), so it does not depend on get_wixel_task having reported
|
|
13
|
+
// asset ids: without assetId it lists the project's assets to pick from.
|
|
14
|
+
//
|
|
15
|
+
// ICONS sets have two engine-ready layouts on top of the plain per-tile files:
|
|
16
|
+
// - spriteSheet: one aligned sheet PNG + Aseprite JSON frame map (Phaser
|
|
17
|
+
// createFromAseprite) + the aligned frames — for character pose sets;
|
|
18
|
+
// - appIcons: iOS app icon set, Android mipmaps + Play Store icon, web
|
|
19
|
+
// favicons + manifest — for an app or game icon.
|
|
20
|
+
export const ASSET_FILE_LAYOUTS = ['files', 'spriteSheet', 'appIcons'];
|
|
21
|
+
const ICONS_FILE_TYPES = [
|
|
22
|
+
'png',
|
|
23
|
+
'pngTransparentBackground',
|
|
24
|
+
'svg',
|
|
25
|
+
'svgTransparentBackground'
|
|
26
|
+
];
|
|
27
|
+
const ICONS_SIZES = [
|
|
28
|
+
'16px',
|
|
29
|
+
'24px',
|
|
30
|
+
'32px',
|
|
31
|
+
'64px',
|
|
32
|
+
'128px',
|
|
33
|
+
'256px',
|
|
34
|
+
'512px'
|
|
35
|
+
];
|
|
36
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
37
|
+
const MAX_WAIT_SECONDS = 25;
|
|
38
|
+
/**
|
|
39
|
+
* The export params for an asset type. Only ICONS reads layout / fileType /
|
|
40
|
+
* size; other types export with their own defaults. A layout that needs an
|
|
41
|
+
* icon set is refused for any other type instead of silently exporting
|
|
42
|
+
* something else.
|
|
43
|
+
*/
|
|
44
|
+
export function buildExportParams(assetType, args) {
|
|
45
|
+
const layout = args.layout ?? 'files';
|
|
46
|
+
if (assetType === 'IMAGE' && layout === 'appIcons') {
|
|
47
|
+
// An app icon from the app-icon skill is a full-bleed 1024 × 1024 IMAGE:
|
|
48
|
+
// export it as one full-size PNG; the sizes are made from it by URL.
|
|
49
|
+
return { format: 'png', quality: 100, scale: 1 };
|
|
50
|
+
}
|
|
51
|
+
if (assetType !== 'ICONS') {
|
|
52
|
+
if (layout !== 'files') {
|
|
53
|
+
throw new Error(layout === 'appIcons'
|
|
54
|
+
? `layout "appIcons" needs an ICONS or IMAGE asset (the app icon); this asset is ${assetType}`
|
|
55
|
+
: `layout "${layout}" needs an ICONS asset (an icon / sprite set); this asset is ${assetType}`);
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
if (layout === 'files') {
|
|
60
|
+
return {
|
|
61
|
+
// Engines and app projects want plain transparent PNGs, not the
|
|
62
|
+
// editor's default SVG-wrapped PNG.
|
|
63
|
+
fileType: args.fileType ?? 'pngTransparentBackground',
|
|
64
|
+
...(args.size ? { size: args.size } : { size: '512px' })
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
layout,
|
|
69
|
+
...(layout === 'spriteSheet' && args.size ? { size: args.size } : {})
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function waitForExport(read, waitSeconds) {
|
|
73
|
+
const deadline = Date.now() + waitSeconds * 1000;
|
|
74
|
+
let state = await read();
|
|
75
|
+
while (!state.complete && !state.errorDescription && Date.now() < deadline) {
|
|
76
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
77
|
+
state = await read();
|
|
78
|
+
}
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
81
|
+
function describeContents(layout, assetType) {
|
|
82
|
+
if (assetType !== 'ICONS') {
|
|
83
|
+
return 'The file is the asset as the editor downloads it.';
|
|
84
|
+
}
|
|
85
|
+
switch (layout) {
|
|
86
|
+
case 'spriteSheet':
|
|
87
|
+
return 'The zip holds <set>-sheet.png (frames on one grid, feet on one baseline), <set>-sheet.json (Aseprite array frame map: frames "0","1"…, one frameTags entry per pose — load with Phaser this.load.aseprite + this.anims.createFromAseprite) and frames/<n>-<pose>.png.';
|
|
88
|
+
case 'appIcons':
|
|
89
|
+
return 'The zip holds ios/AppIcon.appiconset (1024 px + Contents.json, drop into Assets.xcassets), android/res/mipmap-*/ic_launcher.png + android/play-store-512.png, and web/ favicons, apple-touch-icon.png, icon-192/512.png and site.webmanifest.';
|
|
90
|
+
default:
|
|
91
|
+
return 'The zip holds one transparent PNG per tile, named after the tile.';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function addGetWixelAssetFilesTool(server, authStrategy, options = {}) {
|
|
95
|
+
const { getSiteAuthHeaders } = authStrategy;
|
|
96
|
+
server.registerTool('get_wixel_asset_files', {
|
|
97
|
+
title: 'Get Wixel asset files',
|
|
98
|
+
description: `Download the files of an asset made in a Wixel conversation, to use them in code (a game, an app, a site). Pass the metaSiteId and sessionId that send_wixel_message returned.
|
|
99
|
+
- Without assetId: lists the project's assets (id, name, type) — pick one.
|
|
100
|
+
- With assetId: exports it and returns a zip URL. For an ICONS asset choose layout:
|
|
101
|
+
"files" (default) one transparent PNG per tile;
|
|
102
|
+
"spriteSheet" for a character's poses / animation frames: one aligned sheet + Aseprite JSON frame map (Phaser createFromAseprite) + aligned frames;
|
|
103
|
+
"appIcons" for an app or game icon: iOS app icon set, Android mipmaps + Play Store 512, web favicons + manifest.
|
|
104
|
+
An IMAGE asset (an app icon tile) also takes layout "appIcons": you get one URL per file instead of a zip.
|
|
105
|
+
- If the export is still running, call again with the returned jobId, assetType, layout and assetId.
|
|
106
|
+
Download the URL and unzip it into the project; never re-draw or re-generate assets the user already approved.`,
|
|
107
|
+
inputSchema: {
|
|
108
|
+
metaSiteId: z
|
|
109
|
+
.string()
|
|
110
|
+
.describe('metaSiteId returned by send_wixel_message'),
|
|
111
|
+
sessionId: z
|
|
112
|
+
.string()
|
|
113
|
+
.describe('sessionId returned by send_wixel_message'),
|
|
114
|
+
assetId: z
|
|
115
|
+
.string()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('Asset to export; omit to list the project assets'),
|
|
118
|
+
layout: z
|
|
119
|
+
.enum(ASSET_FILE_LAYOUTS)
|
|
120
|
+
.optional()
|
|
121
|
+
.describe('ICONS assets only: files (default), spriteSheet, appIcons'),
|
|
122
|
+
fileType: z
|
|
123
|
+
.enum(ICONS_FILE_TYPES)
|
|
124
|
+
.optional()
|
|
125
|
+
.describe('ICONS layout "files" only; default pngTransparentBackground'),
|
|
126
|
+
size: z
|
|
127
|
+
.enum(ICONS_SIZES)
|
|
128
|
+
.optional()
|
|
129
|
+
.describe('ICONS: tile size for "files" (default 512px), frame cell size for "spriteSheet" (default 256px)'),
|
|
130
|
+
jobId: z
|
|
131
|
+
.string()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe('Continue waiting on an export this tool started'),
|
|
134
|
+
assetType: z
|
|
135
|
+
.string()
|
|
136
|
+
.optional()
|
|
137
|
+
.describe('With jobId: the assetType this tool returned'),
|
|
138
|
+
waitSeconds: z
|
|
139
|
+
.number()
|
|
140
|
+
.min(0)
|
|
141
|
+
.max(MAX_WAIT_SECONDS)
|
|
142
|
+
.optional()
|
|
143
|
+
.describe('How long to wait for the export (default 25)')
|
|
144
|
+
},
|
|
145
|
+
annotations: {
|
|
146
|
+
readOnlyHint: true,
|
|
147
|
+
destructiveHint: false,
|
|
148
|
+
openWorldHint: false
|
|
149
|
+
}
|
|
150
|
+
}, async (args, extra) => {
|
|
151
|
+
setBi(extra, { siteId: args.metaSiteId });
|
|
152
|
+
const siteAuthHeaders = await getSiteAuthHeaders(args.metaSiteId);
|
|
153
|
+
try {
|
|
154
|
+
const projectId = await getWixelSessionProjectId({
|
|
155
|
+
authHeaders: siteAuthHeaders,
|
|
156
|
+
conductorOverride: options.conductorOverride,
|
|
157
|
+
sessionId: args.sessionId
|
|
158
|
+
});
|
|
159
|
+
if (!projectId) {
|
|
160
|
+
throw new Error('This Wixel conversation has no project yet — make something first');
|
|
161
|
+
}
|
|
162
|
+
const base = {
|
|
163
|
+
siteAuthHeaders,
|
|
164
|
+
projectId,
|
|
165
|
+
conductorOverride: options.conductorOverride
|
|
166
|
+
};
|
|
167
|
+
const waitSeconds = args.waitSeconds ?? MAX_WAIT_SECONDS;
|
|
168
|
+
if (args.jobId) {
|
|
169
|
+
if (!args.assetType) {
|
|
170
|
+
throw new Error('Pass the assetType returned with the jobId');
|
|
171
|
+
}
|
|
172
|
+
const assetType = args.assetType;
|
|
173
|
+
const state = await waitForExport(() => getAssetExportState({ ...base, assetType, jobId: args.jobId }), waitSeconds);
|
|
174
|
+
// The app-icon manifest is named after the asset, so a follow-up
|
|
175
|
+
// poll that carries the assetId looks the name up again.
|
|
176
|
+
// Only an IMAGE app-icon pack uses the name, and a failed lookup
|
|
177
|
+
// must not hide an export that is already done.
|
|
178
|
+
const assetName = state.complete &&
|
|
179
|
+
args.assetId &&
|
|
180
|
+
assetType === 'IMAGE' &&
|
|
181
|
+
args.layout === 'appIcons'
|
|
182
|
+
? await getAsset({ ...base, assetId: args.assetId })
|
|
183
|
+
.then((asset) => asset?.name)
|
|
184
|
+
.catch(() => undefined)
|
|
185
|
+
: undefined;
|
|
186
|
+
return exportResult(state, args, assetType, args.jobId, assetName);
|
|
187
|
+
}
|
|
188
|
+
if (!args.assetId) {
|
|
189
|
+
const { assets } = await queryAssets(base);
|
|
190
|
+
const list = (assets ?? [])
|
|
191
|
+
.filter((asset) => !asset.archived)
|
|
192
|
+
.map((asset) => ({
|
|
193
|
+
id: asset.id,
|
|
194
|
+
name: asset.name ?? null,
|
|
195
|
+
type: asset.type ?? null
|
|
196
|
+
}));
|
|
197
|
+
return {
|
|
198
|
+
isError: false,
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
201
|
+
type: 'text',
|
|
202
|
+
text: `${list.length} assets in this Wixel project:\n${list
|
|
203
|
+
.map((a) => `- ${a.name ?? '(unnamed)'} [${a.type}] id=${a.id}`)
|
|
204
|
+
.join('\n')}\nCall get_wixel_asset_files again with the assetId to download one.`
|
|
205
|
+
}
|
|
206
|
+
],
|
|
207
|
+
structuredContent: { success: true, projectId, assets: list }
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const asset = await getAsset({ ...base, assetId: args.assetId });
|
|
211
|
+
const assetType = asset?.type;
|
|
212
|
+
if (!assetType) {
|
|
213
|
+
throw new Error(`Asset ${args.assetId} was not found in this project`);
|
|
214
|
+
}
|
|
215
|
+
const params = buildExportParams(assetType, args);
|
|
216
|
+
const jobId = await startAssetExport({
|
|
217
|
+
...base,
|
|
218
|
+
assetType,
|
|
219
|
+
assetId: args.assetId,
|
|
220
|
+
params
|
|
221
|
+
});
|
|
222
|
+
const state = await waitForExport(() => getAssetExportState({ ...base, assetType, jobId }), waitSeconds);
|
|
223
|
+
return exportResult(state, args, assetType, jobId, asset?.name);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
rethrowIfUnauthorized(error);
|
|
227
|
+
// Log the message and request id only: an HttpError carries the request
|
|
228
|
+
// config, whose headers hold the user's site token.
|
|
229
|
+
console.error('❌ Error getting Wixel asset files:', {
|
|
230
|
+
message: error.message,
|
|
231
|
+
requestId: error.requestId
|
|
232
|
+
});
|
|
233
|
+
const errorMessage = error.message;
|
|
234
|
+
return {
|
|
235
|
+
isError: true,
|
|
236
|
+
content: [{ type: 'text', text: errorMessage }],
|
|
237
|
+
structuredContent: { success: false, error: errorMessage }
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function exportResult(state, args, assetType, jobId, assetName) {
|
|
243
|
+
if (state.errorDescription) {
|
|
244
|
+
return {
|
|
245
|
+
isError: true,
|
|
246
|
+
content: [
|
|
247
|
+
{
|
|
248
|
+
type: 'text',
|
|
249
|
+
text: `Export failed: ${state.errorDescription}`
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
structuredContent: {
|
|
253
|
+
success: false,
|
|
254
|
+
error: state.errorDescription,
|
|
255
|
+
jobId,
|
|
256
|
+
assetType
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
// A follow-up poll carries only what this reply tells the caller to pass
|
|
261
|
+
// back, so the layout travels with the jobId: without it the ready reply
|
|
262
|
+
// would describe plain files for a sprite sheet or an app-icon pack.
|
|
263
|
+
const layout = args.layout ?? 'files';
|
|
264
|
+
if (!state.complete || !state.url) {
|
|
265
|
+
return {
|
|
266
|
+
isError: false,
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
type: 'text',
|
|
270
|
+
text: `Export still running${state.progress != null ? ` (${state.progress}%)` : ''}. Call get_wixel_asset_files again with jobId "${jobId}", assetType "${assetType}", layout "${layout}"${args.assetId ? ` and assetId "${args.assetId}"` : ''}.`
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
structuredContent: {
|
|
274
|
+
success: true,
|
|
275
|
+
status: 'running',
|
|
276
|
+
progress: state.progress,
|
|
277
|
+
jobId,
|
|
278
|
+
assetType,
|
|
279
|
+
layout,
|
|
280
|
+
...(args.assetId ? { assetId: args.assetId } : {})
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (assetType === 'IMAGE' && layout === 'appIcons') {
|
|
285
|
+
const files = buildAppIconFiles(state.url, assetName || 'App');
|
|
286
|
+
if (files) {
|
|
287
|
+
return {
|
|
288
|
+
isError: false,
|
|
289
|
+
content: [
|
|
290
|
+
{
|
|
291
|
+
type: 'text',
|
|
292
|
+
text: `App icon files ready — download each URL to its path, write the JSON files as given:\n${files
|
|
293
|
+
.map((f) => `- ${f.path}: ${f.url ?? f.content}`)
|
|
294
|
+
.join('\n')}`
|
|
295
|
+
}
|
|
296
|
+
],
|
|
297
|
+
structuredContent: {
|
|
298
|
+
success: true,
|
|
299
|
+
status: 'ready',
|
|
300
|
+
url: state.url,
|
|
301
|
+
files,
|
|
302
|
+
jobId,
|
|
303
|
+
assetType,
|
|
304
|
+
layout
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return {
|
|
310
|
+
isError: false,
|
|
311
|
+
content: [
|
|
312
|
+
{
|
|
313
|
+
type: 'text',
|
|
314
|
+
text: `Files ready: ${state.url}\n${describeContents(args.layout, assetType)}`
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
structuredContent: {
|
|
318
|
+
success: true,
|
|
319
|
+
status: 'ready',
|
|
320
|
+
url: state.url,
|
|
321
|
+
jobId,
|
|
322
|
+
assetType,
|
|
323
|
+
layout: args.layout ?? 'files'
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=wixel-asset-files-tool.js.map
|
|
@@ -50,6 +50,28 @@ export interface WixelPendingQuestion {
|
|
|
50
50
|
toolCallId: string;
|
|
51
51
|
turnId?: string;
|
|
52
52
|
questions: WixelAskUserQuestion[];
|
|
53
|
+
/** Pictures the asking turn made before it asked — the sample a "make the
|
|
54
|
+
* rest in this look?" question is about. Empty when the turn made none. */
|
|
55
|
+
previewUrls?: string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The pictures the turn made before its question, newest last: the image
|
|
59
|
+
* tools' results in that turn up to the ask_user. A client that cannot render
|
|
60
|
+
* the stage (Claude Code, Cursor) otherwise approves a look it never sees.
|
|
61
|
+
*/
|
|
62
|
+
export declare function collectPreviewUrls(messages: SessionEntryMessage[], askIndex: number, turnId: string | undefined): string[];
|
|
63
|
+
interface SessionEntryPart {
|
|
64
|
+
text?: string;
|
|
65
|
+
functionCall?: {
|
|
66
|
+
id?: string;
|
|
67
|
+
name?: string;
|
|
68
|
+
args?: Record<string, unknown>;
|
|
69
|
+
};
|
|
70
|
+
functionResponse?: {
|
|
71
|
+
id?: string;
|
|
72
|
+
name?: string;
|
|
73
|
+
response?: unknown;
|
|
74
|
+
};
|
|
53
75
|
}
|
|
54
76
|
export interface WixelSessionActivity {
|
|
55
77
|
/** Unanswered ask_user the turn is suspended on (nothing will generate
|
|
@@ -66,6 +88,12 @@ export interface WixelSessionActivity {
|
|
|
66
88
|
* alone cannot distinguish the two.
|
|
67
89
|
*/
|
|
68
90
|
export declare function getWixelSessionActivity({ sessionId, ...base }: GetSessionEntriesOptions): Promise<WixelSessionActivity>;
|
|
91
|
+
interface SessionEntryMessage {
|
|
92
|
+
id?: string;
|
|
93
|
+
role?: string;
|
|
94
|
+
turnId?: string;
|
|
95
|
+
parts?: SessionEntryPart[];
|
|
96
|
+
}
|
|
69
97
|
export declare function getWixelPendingQuestion({ sessionId, requireParked, turnId, ...base }: GetSessionEntriesOptions & {
|
|
70
98
|
requireParked?: boolean;
|
|
71
99
|
/** The asking task's turn. A question another turn of the session asked —
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHttpClient } from '@wix/http-client';
|
|
2
2
|
import { WIXEL_AGENT_SESSIONS_API_BASE_URL } from './constants.js';
|
|
3
|
+
import { extractMediaUrls } from './wixel-a2a-client.js';
|
|
3
4
|
// Reads of the Wixel agent's internal sessions API (wixel-agent-service):
|
|
4
5
|
// the durable entries log of a session and the sessions listing. A2A tasks
|
|
5
6
|
// report a turn's state and reply, but a parked ask_user question — the
|
|
@@ -41,6 +42,36 @@ export async function getWixelSessionProjectId({ sessionId, ...base }) {
|
|
|
41
42
|
const session = data?.sessions?.find((entry) => entry.id === sessionId);
|
|
42
43
|
return session?.projectId ?? null;
|
|
43
44
|
}
|
|
45
|
+
/** Image tools whose results are the pictures a question can be about. */
|
|
46
|
+
const PREVIEW_TOOLS = new Set([
|
|
47
|
+
'generate_image',
|
|
48
|
+
'edit_image',
|
|
49
|
+
'remove_image_background'
|
|
50
|
+
]);
|
|
51
|
+
const MAX_PREVIEW_URLS = 4;
|
|
52
|
+
/**
|
|
53
|
+
* The pictures the turn made before its question, newest last: the image
|
|
54
|
+
* tools' results in that turn up to the ask_user. A client that cannot render
|
|
55
|
+
* the stage (Claude Code, Cursor) otherwise approves a look it never sees.
|
|
56
|
+
*/
|
|
57
|
+
export function collectPreviewUrls(messages, askIndex, turnId) {
|
|
58
|
+
const urls = [];
|
|
59
|
+
for (let i = 0; i <= askIndex; i++) {
|
|
60
|
+
const message = messages[i];
|
|
61
|
+
if (turnId && message.turnId !== turnId)
|
|
62
|
+
continue;
|
|
63
|
+
for (const part of message.parts ?? []) {
|
|
64
|
+
const response = part.functionResponse;
|
|
65
|
+
if (!response?.name || !PREVIEW_TOOLS.has(response.name))
|
|
66
|
+
continue;
|
|
67
|
+
for (const url of extractMediaUrls(JSON.stringify(response.response ?? ''))) {
|
|
68
|
+
if (!urls.includes(url))
|
|
69
|
+
urls.push(url);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return urls.slice(-MAX_PREVIEW_URLS);
|
|
74
|
+
}
|
|
44
75
|
/**
|
|
45
76
|
* Classify the session's current state from the durable entries log with a
|
|
46
77
|
* single read: parked on an ask_user question, actively generating, or idle.
|
|
@@ -169,10 +200,12 @@ export async function getWixelPendingQuestion({ sessionId, requireParked = false
|
|
|
169
200
|
continue;
|
|
170
201
|
if (ownTurn && message.turnId !== ownTurn)
|
|
171
202
|
return null;
|
|
203
|
+
const previewUrls = collectPreviewUrls(messages, i, message.turnId);
|
|
172
204
|
return {
|
|
173
205
|
toolCallId: call.id,
|
|
174
206
|
turnId: message.turnId,
|
|
175
|
-
questions
|
|
207
|
+
questions,
|
|
208
|
+
...(previewUrls.length ? { previewUrls } : {})
|
|
176
209
|
};
|
|
177
210
|
}
|
|
178
211
|
if (part.text || part.functionCall || part.functionResponse) {
|