@unchainedshop/api 4.0.0-rc.14 → 4.0.0-rc.16
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/lib/chat/defaultSystemPrompt.d.ts +3 -0
- package/lib/chat/defaultSystemPrompt.d.ts.map +1 -0
- package/lib/chat/defaultSystemPrompt.js +7 -0
- package/lib/chat/defaultSystemPrompt.js.map +1 -0
- package/lib/chat/generateImageHandler.d.ts +7 -0
- package/lib/chat/generateImageHandler.d.ts.map +1 -0
- package/lib/chat/generateImageHandler.js +64 -0
- package/lib/chat/generateImageHandler.js.map +1 -0
- package/lib/chat/normalizeToolsIndex.d.ts +7 -0
- package/lib/chat/normalizeToolsIndex.d.ts.map +1 -0
- package/lib/chat/normalizeToolsIndex.js +11 -0
- package/lib/chat/normalizeToolsIndex.js.map +1 -0
- package/lib/chat/utils.d.ts +13 -0
- package/lib/chat/utils.d.ts.map +1 -0
- package/lib/chat/utils.js +32 -0
- package/lib/chat/utils.js.map +1 -0
- package/lib/context.d.ts +3 -0
- package/lib/context.d.ts.map +1 -1
- package/lib/context.js.map +1 -1
- package/lib/express/chatHandler.d.ts +5 -0
- package/lib/express/chatHandler.d.ts.map +1 -0
- package/lib/express/chatHandler.js +116 -0
- package/lib/express/chatHandler.js.map +1 -0
- package/lib/express/createBulkImportMiddleware.d.ts.map +1 -1
- package/lib/express/createBulkImportMiddleware.js +6 -7
- package/lib/express/createBulkImportMiddleware.js.map +1 -1
- package/lib/express/createERCMetadataMiddleware.d.ts.map +1 -1
- package/lib/express/createERCMetadataMiddleware.js +12 -15
- package/lib/express/createERCMetadataMiddleware.js.map +1 -1
- package/lib/express/createTempUploadMiddleware.d.ts.map +1 -1
- package/lib/express/createTempUploadMiddleware.js +4 -8
- package/lib/express/createTempUploadMiddleware.js.map +1 -1
- package/lib/express/index.d.ts +1 -0
- package/lib/express/index.d.ts.map +1 -1
- package/lib/express/index.js +1 -0
- package/lib/express/index.js.map +1 -1
- package/lib/fastify/bulkImportHandler.js +5 -6
- package/lib/fastify/bulkImportHandler.js.map +1 -1
- package/lib/fastify/chatHandler.d.ts +4 -0
- package/lib/fastify/chatHandler.d.ts.map +1 -0
- package/lib/fastify/chatHandler.js +110 -0
- package/lib/fastify/chatHandler.js.map +1 -0
- package/lib/fastify/ercMetadataHandler.d.ts.map +1 -1
- package/lib/fastify/ercMetadataHandler.js +3 -6
- package/lib/fastify/ercMetadataHandler.js.map +1 -1
- package/lib/fastify/index.d.ts +6 -1
- package/lib/fastify/index.d.ts.map +1 -1
- package/lib/fastify/index.js +28 -0
- package/lib/fastify/index.js.map +1 -1
- package/lib/fastify/mcpHandler.js +2 -2
- package/lib/fastify/mcpHandler.js.map +1 -1
- package/lib/fastify/tempUploadHandler.d.ts.map +1 -1
- package/lib/fastify/tempUploadHandler.js +3 -6
- package/lib/fastify/tempUploadHandler.js.map +1 -1
- package/lib/mcp/tools/order/schemas.d.ts +2 -2
- package/lib/mcp/tools/product/handlers/addProductMedia.d.ts +26 -1
- package/lib/mcp/tools/product/handlers/addProductMedia.d.ts.map +1 -1
- package/lib/mcp/tools/product/handlers/addProductMedia.js +4 -4
- package/lib/mcp/tools/product/handlers/addProductMedia.js.map +1 -1
- package/lib/mcp/tools/product/handlers/updateProduct.d.ts.map +1 -1
- package/lib/mcp/tools/product/handlers/updateProduct.js +10 -2
- package/lib/mcp/tools/product/handlers/updateProduct.js.map +1 -1
- package/lib/mcp/tools/product/schemas.d.ts +62 -62
- package/lib/mcp/tools/product/schemas.d.ts.map +1 -1
- package/lib/mcp/tools/product/schemas.js +3 -3
- package/lib/mcp/tools/product/schemas.js.map +1 -1
- package/lib/mcp/tools/users/schemas.d.ts +16 -16
- package/lib/mcp/utils/getNormalizedProductDetails.d.ts.map +1 -1
- package/lib/mcp/utils/getNormalizedProductDetails.js +3 -1
- package/lib/mcp/utils/getNormalizedProductDetails.js.map +1 -1
- package/lib/resolvers/mutations/index.d.ts +4 -1
- package/lib/resolvers/mutations/index.d.ts.map +1 -1
- package/lib/resolvers/mutations/index.js +8 -2
- package/lib/resolvers/mutations/index.js.map +1 -1
- package/lib/resolvers/mutations/orders/checkoutCart.js +1 -1
- package/lib/resolvers/mutations/orders/checkoutCart.js.map +1 -1
- package/lib/resolvers/mutations/orders/updateCart.js +6 -6
- package/lib/resolvers/mutations/orders/updateCart.js.map +1 -1
- package/lib/resolvers/mutations/orders/updateCartDeliveryPickUp.d.ts +8 -0
- package/lib/resolvers/mutations/orders/updateCartDeliveryPickUp.d.ts.map +1 -0
- package/lib/resolvers/mutations/orders/updateCartDeliveryPickUp.js +38 -0
- package/lib/resolvers/mutations/orders/updateCartDeliveryPickUp.js.map +1 -0
- package/lib/resolvers/mutations/orders/updateCartDeliveryShipping.d.ts +8 -0
- package/lib/resolvers/mutations/orders/updateCartDeliveryShipping.d.ts.map +1 -0
- package/lib/resolvers/mutations/orders/updateCartDeliveryShipping.js +38 -0
- package/lib/resolvers/mutations/orders/updateCartDeliveryShipping.js.map +1 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentGeneric.d.ts +7 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentGeneric.d.ts.map +1 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentGeneric.js +37 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentGeneric.js.map +1 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentInvoice.d.ts +7 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentInvoice.d.ts.map +1 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentInvoice.js +37 -0
- package/lib/resolvers/mutations/orders/updateCartPaymentInvoice.js.map +1 -0
- package/lib/resolvers/mutations/products/updateProductCommerce.d.ts.map +1 -1
- package/lib/resolvers/mutations/products/updateProductCommerce.js +11 -2
- package/lib/resolvers/mutations/products/updateProductCommerce.js.map +1 -1
- package/lib/resolvers/queries/shopInfo.d.ts.map +1 -1
- package/lib/resolvers/queries/shopInfo.js +49 -3
- package/lib/resolvers/queries/shopInfo.js.map +1 -1
- package/lib/resolvers/type/index.d.ts +21 -1
- package/lib/resolvers/type/index.d.ts.map +1 -1
- package/lib/resolvers/type/media-types.d.ts.map +1 -1
- package/lib/resolvers/type/media-types.js +3 -1
- package/lib/resolvers/type/media-types.js.map +1 -1
- package/lib/resolvers/type/order/order-payment-types.d.ts.map +1 -1
- package/lib/resolvers/type/order/order-payment-types.js +0 -1
- package/lib/resolvers/type/order/order-payment-types.js.map +1 -1
- package/lib/resolvers/type/price-types.d.ts +1 -1
- package/lib/resolvers/type/price-types.d.ts.map +1 -1
- package/lib/resolvers/type/price-types.js +5 -2
- package/lib/resolvers/type/price-types.js.map +1 -1
- package/lib/resolvers/type/product/product-bundle-types.d.ts +20 -0
- package/lib/resolvers/type/product/product-bundle-types.d.ts.map +1 -1
- package/lib/resolvers/type/product/product-bundle-types.js +2 -2
- package/lib/resolvers/type/product/product-bundle-types.js.map +1 -1
- package/lib/resolvers/type/shop-types.d.ts.map +1 -1
- package/lib/resolvers/type/shop-types.js.map +1 -1
- package/lib/schema/mutation.d.ts.map +1 -1
- package/lib/schema/mutation.js +36 -7
- package/lib/schema/mutation.js.map +1 -1
- package/lib/schema/query.d.ts.map +1 -1
- package/lib/schema/query.js +2 -0
- package/lib/schema/query.js.map +1 -1
- package/lib/schema/types/payment.d.ts.map +1 -1
- package/lib/schema/types/payment.js +0 -5
- package/lib/schema/types/payment.js.map +1 -1
- package/lib/schema/types/product/bundle-product.d.ts.map +1 -1
- package/lib/schema/types/product/bundle-product.js +18 -7
- package/lib/schema/types/product/bundle-product.js.map +1 -1
- package/lib/schema/types/shop.d.ts.map +1 -1
- package/lib/schema/types/shop.js +3 -0
- package/lib/schema/types/shop.js.map +1 -1
- package/package.json +18 -8
- package/src/chat/defaultSystemPrompt.ts +7 -0
- package/src/chat/generateImageHandler.ts +78 -0
- package/src/chat/normalizeToolsIndex.ts +11 -0
- package/src/chat/utils.ts +31 -0
- package/src/context.ts +3 -0
- package/src/express/chatHandler.ts +147 -0
- package/src/express/createBulkImportMiddleware.ts +7 -7
- package/src/express/createERCMetadataMiddleware.ts +13 -15
- package/src/express/createTempUploadMiddleware.ts +4 -9
- package/src/express/index.ts +2 -0
- package/src/fastify/bulkImportHandler.ts +6 -6
- package/src/fastify/chatHandler.ts +140 -0
- package/src/fastify/ercMetadataHandler.ts +3 -6
- package/src/fastify/index.ts +37 -1
- package/src/fastify/mcpHandler.ts +2 -2
- package/src/fastify/tempUploadHandler.ts +3 -6
- package/src/mcp/tools/product/handlers/addProductMedia.ts +4 -5
- package/src/mcp/tools/product/handlers/updateProduct.ts +12 -1
- package/src/mcp/tools/product/schemas.ts +7 -3
- package/src/mcp/utils/getNormalizedProductDetails.ts +4 -1
- package/src/resolvers/mutations/index.ts +8 -2
- package/src/resolvers/mutations/orders/checkoutCart.ts +1 -1
- package/src/resolvers/mutations/orders/updateCart.ts +6 -6
- package/src/resolvers/mutations/orders/updateCartDeliveryPickUp.ts +51 -0
- package/src/resolvers/mutations/orders/updateCartDeliveryShipping.ts +51 -0
- package/src/resolvers/mutations/orders/updateCartPaymentGeneric.ts +50 -0
- package/src/resolvers/mutations/orders/updateCartPaymentInvoice.ts +50 -0
- package/src/resolvers/mutations/products/updateProductCommerce.ts +12 -4
- package/src/resolvers/queries/shopInfo.ts +49 -3
- package/src/resolvers/type/media-types.ts +4 -1
- package/src/resolvers/type/order/order-payment-types.ts +0 -1
- package/src/resolvers/type/price-types.ts +5 -3
- package/src/resolvers/type/product/product-bundle-types.ts +2 -2
- package/src/resolvers/type/shop-types.ts +0 -1
- package/src/schema/mutation.ts +36 -7
- package/src/schema/query.ts +2 -0
- package/src/schema/types/payment.ts +0 -5
- package/src/schema/types/product/bundle-product.ts +18 -7
- package/src/schema/types/shop.ts +3 -0
- package/src/resolvers/mutations/orders/updateOrderPaymentCard.ts +0 -37
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const defaultSystemPrompt = `Use tools when requested but do not provide summaries or explanations after tool calls. Only return the direct tool results. Do not include the data in your summary, just write a summary about it in one short paragraph and never list all the fields of a result, just summarize paragraph about your findings, if necessary.
|
|
2
|
+
Rules for using the Unchained MCP tools:
|
|
3
|
+
- All prices are integer type and to correctly determine the currency decimal points you need to check currencies.
|
|
4
|
+
- Determine the currently used language / locale for creating and updating products, filters and assortments by checking Shop Info.
|
|
5
|
+
`;
|
|
6
|
+
|
|
7
|
+
export default defaultSystemPrompt;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { experimental_generateImage, ImageModel, tool } from 'ai';
|
|
2
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
const inputSchema = z.object({
|
|
6
|
+
prompt: z.string().describe('The prompt to generate the image from'),
|
|
7
|
+
size: z
|
|
8
|
+
.enum([
|
|
9
|
+
'512x512',
|
|
10
|
+
'768x768',
|
|
11
|
+
'1024x1024',
|
|
12
|
+
'512x896',
|
|
13
|
+
'640x1120',
|
|
14
|
+
'768x1344',
|
|
15
|
+
'1024x1792',
|
|
16
|
+
'896x512',
|
|
17
|
+
'1120x640',
|
|
18
|
+
'1344x768',
|
|
19
|
+
'1792x1024',
|
|
20
|
+
])
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('The size of the image (default is 1024x1024).'),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const generateImageHandler =
|
|
26
|
+
(req: any) =>
|
|
27
|
+
({
|
|
28
|
+
model,
|
|
29
|
+
uploadUrl = `${process.env.ROOT_URL}/temp-upload`,
|
|
30
|
+
}: {
|
|
31
|
+
model: ImageModel;
|
|
32
|
+
uploadUrl?: string;
|
|
33
|
+
}) => {
|
|
34
|
+
return tool<any>({
|
|
35
|
+
description: 'Generate an image based on the prompt',
|
|
36
|
+
inputSchema: inputSchema as any,
|
|
37
|
+
name: 'generate_image',
|
|
38
|
+
execute: async ({ prompt, size = '1024x1024' }) => {
|
|
39
|
+
try {
|
|
40
|
+
const { image } = await experimental_generateImage({
|
|
41
|
+
model,
|
|
42
|
+
prompt,
|
|
43
|
+
size: size as any,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const buffer = Buffer.from(image.base64, 'base64');
|
|
47
|
+
const fileType = await fileTypeFromBuffer(buffer);
|
|
48
|
+
const extension = fileType?.ext || 'png';
|
|
49
|
+
const mime = fileType?.mime || 'image/png';
|
|
50
|
+
|
|
51
|
+
const filename = `image-${Date.now()}.${extension}`;
|
|
52
|
+
const file = new File([buffer], filename, { type: mime });
|
|
53
|
+
const formData = new FormData();
|
|
54
|
+
formData.append('image', file);
|
|
55
|
+
|
|
56
|
+
const uploadRes = await fetch(uploadUrl, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
body: formData,
|
|
59
|
+
credentials: 'include',
|
|
60
|
+
headers: {
|
|
61
|
+
Cookie: req?.headers?.cookie || req?.headers?.get('cookie') || '',
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!uploadRes.ok) {
|
|
66
|
+
throw new Error(`Image upload failed: ${uploadRes.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { url } = await uploadRes.json();
|
|
70
|
+
return { imageUrl: url, prompt };
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return `Error Generating image: ${(error as Error).message}`;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export default generateImageHandler;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { categorizeTools } from './utils.js';
|
|
2
|
+
|
|
3
|
+
export default function normalizeToolsIndex(mcpTools: any) {
|
|
4
|
+
const tools = Object.entries(mcpTools).map(([name, tool]: any) => ({
|
|
5
|
+
name,
|
|
6
|
+
description: tool.description || '',
|
|
7
|
+
parameters: tool.parameters || {},
|
|
8
|
+
category: categorizeTools(name),
|
|
9
|
+
}));
|
|
10
|
+
return tools;
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ImageModel, NoSuchToolError, InvalidArgumentError, streamText } from 'ai';
|
|
2
|
+
|
|
3
|
+
type StreamTextParams = Parameters<typeof streamText>[0];
|
|
4
|
+
|
|
5
|
+
export type ChatConfiguration = Omit<StreamTextParams, 'messages'> & {
|
|
6
|
+
unchainedMCPUrl?: string;
|
|
7
|
+
imageGenerationTool?: { model: ImageModel; uploadUrl?: string };
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const errorHandler = (error: any): string => {
|
|
11
|
+
if (NoSuchToolError.isInstance(error)) return 'NoSuchToolError';
|
|
12
|
+
if (InvalidArgumentError.isInstance(error)) return 'InvalidToolArgumentsError';
|
|
13
|
+
/* if (ToolExecutionError.isInstance(error)) return 'ToolExecutionError'; */
|
|
14
|
+
if (error?.message?.toLowerCase()?.includes('forbidden')) return 'NetworkError';
|
|
15
|
+
if (error?.message?.toLowerCase()?.includes('limit')) return 'LimitExceeded';
|
|
16
|
+
return `Failed to stream response: ${error?.message || 'Unknown error'}`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const categorizeTools = (toolName: string): string => {
|
|
20
|
+
const name = toolName.toLowerCase();
|
|
21
|
+
|
|
22
|
+
if (name.includes('product')) return 'Product Management';
|
|
23
|
+
if (name.includes('order')) return 'Order Management';
|
|
24
|
+
if (name.includes('customer') || name.includes('user')) return 'Customer Management';
|
|
25
|
+
if (name.includes('shop') || name.includes('settings')) return 'Shop Configuration';
|
|
26
|
+
if (name.includes('assortment')) return 'Assortments';
|
|
27
|
+
if (name.includes('filter')) return 'Filters';
|
|
28
|
+
if (name.includes('image') || name.includes('generate')) return 'Media & Content';
|
|
29
|
+
|
|
30
|
+
return 'Other Tools';
|
|
31
|
+
};
|
package/src/context.ts
CHANGED
|
@@ -29,6 +29,9 @@ export interface CustomAdminUiProperties {
|
|
|
29
29
|
export interface AdminUiConfig {
|
|
30
30
|
customProperties?: CustomAdminUiProperties[];
|
|
31
31
|
singleSignOnURL?: string;
|
|
32
|
+
defaultProductTags?: string[];
|
|
33
|
+
defaultAssortmentTags?: string[];
|
|
34
|
+
defaultUserTags?: string[];
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export interface UnchainedHTTPServerContext {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import type { Express, Request, RequestHandler, Response } from 'express';
|
|
3
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
+
import {
|
|
5
|
+
convertToModelMessages,
|
|
6
|
+
experimental_createMCPClient as createMCPClient,
|
|
7
|
+
MCPTransport,
|
|
8
|
+
stepCountIs,
|
|
9
|
+
streamText,
|
|
10
|
+
ToolSet,
|
|
11
|
+
} from 'ai';
|
|
12
|
+
import { ChatConfiguration, errorHandler } from '../chat/utils.js';
|
|
13
|
+
import generateImageHandler from '../chat/generateImageHandler.js';
|
|
14
|
+
import defaultSystemPrompt from '../chat/defaultSystemPrompt.js';
|
|
15
|
+
import normalizeToolsIndex from '../chat/normalizeToolsIndex.js';
|
|
16
|
+
|
|
17
|
+
export const expressRouter = () => {
|
|
18
|
+
const router = express.Router();
|
|
19
|
+
|
|
20
|
+
const staticURL = import.meta.resolve('@unchainedshop/admin-ui');
|
|
21
|
+
const staticPath = new URL(staticURL).pathname.split('/').slice(0, -1).join('/');
|
|
22
|
+
|
|
23
|
+
router.use(express.static(staticPath));
|
|
24
|
+
|
|
25
|
+
router.get(/(.*)/, (_, res) => {
|
|
26
|
+
res.sendFile(`${staticPath}/index.html`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
return router;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const setupMCPChatHandler = (chatConfiguration: ChatConfiguration & any): RequestHandler => {
|
|
33
|
+
if (!chatConfiguration || !chatConfiguration.model) {
|
|
34
|
+
throw new Error('Model is required');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
tools: additionalTools = {},
|
|
39
|
+
unchainedMCPUrl = `${process.env.ROOT_URL}/mcp`,
|
|
40
|
+
model,
|
|
41
|
+
imageGenerationTool,
|
|
42
|
+
...restChatConfig
|
|
43
|
+
}: any = chatConfiguration;
|
|
44
|
+
|
|
45
|
+
const system = chatConfiguration.system ?? defaultSystemPrompt;
|
|
46
|
+
|
|
47
|
+
const mcpChatHandler: RequestHandler = async (req: Request, res: Response): Promise<void> => {
|
|
48
|
+
if (req.method === 'OPTIONS') {
|
|
49
|
+
res.setHeader('access-control-allow-credentials', 'true');
|
|
50
|
+
res.setHeader('access-control-allow-private-network', 'true');
|
|
51
|
+
res.status(200).end();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (req.method !== 'POST' && req.method !== 'GET') {
|
|
55
|
+
res.status(405).json({ error: 'Method Not Allowed. Use POST.' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const client = await createMCPClient({
|
|
60
|
+
transport: new StreamableHTTPClientTransport(new URL(unchainedMCPUrl), {
|
|
61
|
+
requestInit: {
|
|
62
|
+
headers: {
|
|
63
|
+
Cookie: req.headers.cookie || '',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}) as MCPTransport,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const defaultUnchainedTools = await client.tools();
|
|
71
|
+
const tools: ToolSet = {
|
|
72
|
+
...defaultUnchainedTools,
|
|
73
|
+
...additionalTools,
|
|
74
|
+
};
|
|
75
|
+
if (imageGenerationTool) {
|
|
76
|
+
tools.generateImage = generateImageHandler(req)(imageGenerationTool) as any;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (req.method === 'GET') {
|
|
80
|
+
res.status(200).json({
|
|
81
|
+
tools: normalizeToolsIndex(tools),
|
|
82
|
+
cached: false,
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const { messages } = req.body;
|
|
87
|
+
|
|
88
|
+
const cacheControlledTools = { ...tools };
|
|
89
|
+
const keys = Object.keys(cacheControlledTools);
|
|
90
|
+
const lastKey = keys[keys.length - 1];
|
|
91
|
+
|
|
92
|
+
cacheControlledTools[lastKey] = {
|
|
93
|
+
...cacheControlledTools[lastKey],
|
|
94
|
+
providerOptions: {
|
|
95
|
+
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const normalizedMessages = convertToModelMessages(messages, { tools: cacheControlledTools });
|
|
100
|
+
|
|
101
|
+
if (normalizedMessages.length > 0) {
|
|
102
|
+
const lastIndex = normalizedMessages.length - 1;
|
|
103
|
+
normalizedMessages[lastIndex] = {
|
|
104
|
+
...normalizedMessages[lastIndex],
|
|
105
|
+
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const result = streamText({
|
|
109
|
+
stopWhen: stepCountIs(10),
|
|
110
|
+
...restChatConfig,
|
|
111
|
+
system,
|
|
112
|
+
model,
|
|
113
|
+
tools: cacheControlledTools,
|
|
114
|
+
onFinish: async () => {
|
|
115
|
+
await client?.close();
|
|
116
|
+
},
|
|
117
|
+
messages: normalizedMessages.slice(-20),
|
|
118
|
+
providerOptions: {
|
|
119
|
+
anthropic: {
|
|
120
|
+
cacheControl: {
|
|
121
|
+
type: 'ephemeral',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
result.pipeUIMessageStreamToResponse(res, {
|
|
128
|
+
onError: errorHandler,
|
|
129
|
+
});
|
|
130
|
+
} catch (err) {
|
|
131
|
+
await client?.close();
|
|
132
|
+
res.status(500).json({ error: errorHandler(err) });
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return mcpChatHandler;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const connectChat = (app: Express, chatConfiguration: ChatConfiguration) => {
|
|
140
|
+
const handler = setupMCPChatHandler(chatConfiguration);
|
|
141
|
+
if (!handler) {
|
|
142
|
+
throw new Error('Invalid chat configuration: model is required.');
|
|
143
|
+
}
|
|
144
|
+
app.post('/chat', express.json({ limit: '10mb' }), handler);
|
|
145
|
+
app.options('/chat', handler);
|
|
146
|
+
app.get('/chat/tools', handler);
|
|
147
|
+
};
|
|
@@ -7,8 +7,7 @@ const logger = createLogger('unchained:bulk-import');
|
|
|
7
7
|
|
|
8
8
|
const methodWrongHandler = (res) => {
|
|
9
9
|
logger.error('Method not supported, return 404');
|
|
10
|
-
res.
|
|
11
|
-
res.end();
|
|
10
|
+
res.status(404).end();
|
|
12
11
|
return;
|
|
13
12
|
};
|
|
14
13
|
|
|
@@ -41,6 +40,9 @@ export default async function bulkImportMiddleware(
|
|
|
41
40
|
meta: {},
|
|
42
41
|
});
|
|
43
42
|
|
|
43
|
+
const validationStream = await context.services.files.createDownloadStream({ fileId: file._id });
|
|
44
|
+
await context.bulkImporter.validateEventStream(validationStream);
|
|
45
|
+
|
|
44
46
|
input.payloadId = file._id;
|
|
45
47
|
input.payloadSize = file.size;
|
|
46
48
|
|
|
@@ -53,12 +55,10 @@ export default async function bulkImportMiddleware(
|
|
|
53
55
|
priority: 10,
|
|
54
56
|
});
|
|
55
57
|
|
|
56
|
-
res.
|
|
57
|
-
res.end(JSON.stringify(work));
|
|
58
|
+
res.status(200).send(work);
|
|
58
59
|
} catch (e) {
|
|
59
|
-
logger.error(e
|
|
60
|
-
res.
|
|
61
|
-
res.end(JSON.stringify({ name: e.name, code: e.code, message: e.message }));
|
|
60
|
+
logger.error(e);
|
|
61
|
+
res.status(503).send({ name: e.name, code: e.code, message: e.message });
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { createLogger } from '@unchainedshop/logger';
|
|
3
2
|
import { Context } from '../context.js';
|
|
4
3
|
import { Request, RequestHandler } from 'express';
|
|
@@ -9,8 +8,7 @@ const { ROOT_URL = 'http://localhost:4010' } = process.env;
|
|
|
9
8
|
|
|
10
9
|
const methodWrongHandler = (res) => {
|
|
11
10
|
logger.error('Method not supported, return 404');
|
|
12
|
-
res.
|
|
13
|
-
res.end();
|
|
11
|
+
res.status(404).end();
|
|
14
12
|
return;
|
|
15
13
|
};
|
|
16
14
|
|
|
@@ -25,16 +23,20 @@ const ercMetadataMiddleware: RequestHandler = async (
|
|
|
25
23
|
|
|
26
24
|
const { services, modules, loaders, locale } = req.unchainedContext;
|
|
27
25
|
const url = new URL(req.url, ROOT_URL);
|
|
28
|
-
const parsedPath = path.parse(url.pathname);
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
// Replace path.parse with URL/string methods
|
|
28
|
+
const pathname = url.pathname;
|
|
29
|
+
if (!pathname.toLowerCase().endsWith('.json')) throw new Error('Invalid ERC Metadata URI');
|
|
30
|
+
|
|
31
|
+
const fileName = pathname.split('/').pop();
|
|
32
|
+
const chainTokenId = fileName.slice(0, fileName.lastIndexOf('.'));
|
|
31
33
|
|
|
32
34
|
const [, productId, localeOrTokenFilename, tokenFileName] = url.pathname.split('/');
|
|
33
35
|
|
|
34
36
|
const product = await loaders.productLoader.load({ productId });
|
|
35
37
|
|
|
36
38
|
const [token] = await modules.warehousing.findTokens({
|
|
37
|
-
chainTokenId
|
|
39
|
+
chainTokenId,
|
|
38
40
|
contractAddress: product?.tokenization?.contractAddress,
|
|
39
41
|
});
|
|
40
42
|
|
|
@@ -48,16 +50,12 @@ const ercMetadataMiddleware: RequestHandler = async (
|
|
|
48
50
|
return methodWrongHandler(res);
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
res.
|
|
53
|
-
'Content-Length': Buffer.byteLength(body),
|
|
54
|
-
'Content-Type': 'text/plain',
|
|
55
|
-
});
|
|
56
|
-
res.end(body);
|
|
53
|
+
res.status(200);
|
|
54
|
+
return res.send(ercMetadata);
|
|
57
55
|
} catch (e) {
|
|
58
|
-
logger.error(e
|
|
59
|
-
res.
|
|
60
|
-
res.
|
|
56
|
+
logger.error(e);
|
|
57
|
+
res.status(503);
|
|
58
|
+
res.send({ name: e.name, code: e.code, message: e.message });
|
|
61
59
|
return;
|
|
62
60
|
}
|
|
63
61
|
};
|
|
@@ -8,8 +8,7 @@ const logger = createLogger('unchained:temp-upload');
|
|
|
8
8
|
|
|
9
9
|
const methodWrongHandler = (res) => {
|
|
10
10
|
logger.error('Method not supported, return 404');
|
|
11
|
-
res.
|
|
12
|
-
res.end();
|
|
11
|
+
res.status(404).end();
|
|
13
12
|
return;
|
|
14
13
|
};
|
|
15
14
|
|
|
@@ -47,14 +46,10 @@ export default async function tempUploadMiddleware(
|
|
|
47
46
|
const fileUploadAdapter = getFileAdapter();
|
|
48
47
|
const rawUrl = await fileUploadAdapter.createDownloadURL(file);
|
|
49
48
|
const url = context.modules.files.normalizeUrl(rawUrl, {});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
res.writeHead(200);
|
|
53
|
-
res.end(body);
|
|
49
|
+
res.status(200).send({ fileId: file._id, url, expires: expires.toISOString() });
|
|
54
50
|
} catch (e) {
|
|
55
|
-
logger.error(e
|
|
56
|
-
res.
|
|
57
|
-
res.end(JSON.stringify({ name: e.name, code: e.code, message: e.message }));
|
|
51
|
+
logger.error(e);
|
|
52
|
+
res.status(503).send({ name: e.name, code: e.code, message: e.message });
|
|
58
53
|
return;
|
|
59
54
|
}
|
|
60
55
|
}
|
package/src/express/index.ts
CHANGED
|
@@ -16,6 +16,8 @@ import createTempUploadMiddleware from './createTempUploadMiddleware.js';
|
|
|
16
16
|
import createMCPMiddleware from './createMCPMiddleware.js';
|
|
17
17
|
import { API_EVENTS } from '../events.js';
|
|
18
18
|
|
|
19
|
+
export * from './chatHandler.js';
|
|
20
|
+
|
|
19
21
|
const resolveUserRemoteAddress = (req: e.Request) => {
|
|
20
22
|
const remoteAddress =
|
|
21
23
|
(req.headers['x-real-ip'] as string) ||
|
|
@@ -32,6 +32,9 @@ const bulkImportHandler: RouteHandlerMethod = async (
|
|
|
32
32
|
chunkSizeBytes: 1024 * 1024 * 5, // 5MB chunks
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
+
const validationStream = await context.services.files.createDownloadStream({ fileId: file._id });
|
|
36
|
+
await context.bulkImporter.validateEventStream(validationStream);
|
|
37
|
+
|
|
35
38
|
input.payloadId = file._id;
|
|
36
39
|
input.payloadSize = file.size;
|
|
37
40
|
|
|
@@ -44,15 +47,12 @@ const bulkImportHandler: RouteHandlerMethod = async (
|
|
|
44
47
|
priority: 10,
|
|
45
48
|
});
|
|
46
49
|
|
|
47
|
-
const body = JSON.stringify(work);
|
|
48
50
|
res.status(200);
|
|
49
|
-
res.
|
|
50
|
-
res.header('Content-Type', 'application/json');
|
|
51
|
-
return res.send(body);
|
|
51
|
+
return res.send(work);
|
|
52
52
|
} catch (e) {
|
|
53
|
-
logger.error(e
|
|
53
|
+
logger.error(e);
|
|
54
54
|
res.status(503);
|
|
55
|
-
return res.send(
|
|
55
|
+
return res.send({ name: e.name, code: e.code, message: e.message });
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { FastifyInstance, RouteHandlerMethod, FastifyRequest } from 'fastify';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import {
|
|
4
|
+
convertToModelMessages,
|
|
5
|
+
experimental_createMCPClient as createMCPClient,
|
|
6
|
+
MCPTransport,
|
|
7
|
+
stepCountIs,
|
|
8
|
+
streamText,
|
|
9
|
+
ToolSet,
|
|
10
|
+
} from 'ai';
|
|
11
|
+
import generateImageHandler from '../chat/generateImageHandler.js';
|
|
12
|
+
import defaultSystemPrompt from '../chat/defaultSystemPrompt.js';
|
|
13
|
+
import normalizeToolsIndex from '../chat/normalizeToolsIndex.js';
|
|
14
|
+
import { ChatConfiguration, errorHandler } from '../chat/utils.js';
|
|
15
|
+
import { createLogger } from '@unchainedshop/logger';
|
|
16
|
+
|
|
17
|
+
const logger = createLogger('unchained:api:chat');
|
|
18
|
+
|
|
19
|
+
const setupMCPChatHandler = (chatConfiguration: ChatConfiguration & any) => {
|
|
20
|
+
if (!chatConfiguration?.model) {
|
|
21
|
+
throw new Error('Model is required');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
tools: additionalTools = {},
|
|
26
|
+
unchainedMCPUrl = `${process.env.ROOT_URL}/mcp`,
|
|
27
|
+
imageGenerationTool,
|
|
28
|
+
...restChatConfig
|
|
29
|
+
} = chatConfiguration;
|
|
30
|
+
|
|
31
|
+
const system = chatConfiguration.system ?? defaultSystemPrompt;
|
|
32
|
+
|
|
33
|
+
const mcpChatHandler: RouteHandlerMethod = async (req: FastifyRequest, res) => {
|
|
34
|
+
let client;
|
|
35
|
+
try {
|
|
36
|
+
if (req.method === 'OPTIONS') {
|
|
37
|
+
res.headers({
|
|
38
|
+
'access-control-allow-credentials': 'true',
|
|
39
|
+
'access-control-allow-private-network': 'true',
|
|
40
|
+
});
|
|
41
|
+
return res.status(200).send();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
client = await createMCPClient({
|
|
45
|
+
transport: new StreamableHTTPClientTransport(new URL(unchainedMCPUrl), {
|
|
46
|
+
authProvider: null,
|
|
47
|
+
requestInit: {
|
|
48
|
+
headers: {
|
|
49
|
+
Cookie: req.headers.cookie || '',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}) as MCPTransport,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const defaultUnchainedTools = await client.tools();
|
|
56
|
+
const tools: ToolSet = {
|
|
57
|
+
...defaultUnchainedTools,
|
|
58
|
+
...additionalTools,
|
|
59
|
+
};
|
|
60
|
+
if (imageGenerationTool) {
|
|
61
|
+
tools.generateImage = generateImageHandler(req)(imageGenerationTool) as any;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (req.method === 'GET') {
|
|
65
|
+
return res.status(200).send({
|
|
66
|
+
tools: normalizeToolsIndex(tools),
|
|
67
|
+
cached: false,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { messages } = req.body as any;
|
|
72
|
+
|
|
73
|
+
const cacheControlledTools = { ...tools };
|
|
74
|
+
const keys = Object.keys(cacheControlledTools);
|
|
75
|
+
const lastKey = keys[keys.length - 1];
|
|
76
|
+
|
|
77
|
+
cacheControlledTools[lastKey] = {
|
|
78
|
+
...cacheControlledTools[lastKey],
|
|
79
|
+
providerOptions: {
|
|
80
|
+
anthropic: { cacheControl: { type: 'ephemeral' } },
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const normalizedMessages = convertToModelMessages(messages, { tools: cacheControlledTools });
|
|
85
|
+
|
|
86
|
+
if (normalizedMessages.length > 0) {
|
|
87
|
+
const lastIndex = normalizedMessages.length - 1;
|
|
88
|
+
normalizedMessages[lastIndex] = {
|
|
89
|
+
...normalizedMessages[lastIndex],
|
|
90
|
+
providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const result = streamText({
|
|
94
|
+
stopWhen: stepCountIs(500),
|
|
95
|
+
...restChatConfig,
|
|
96
|
+
messages: normalizedMessages.slice(-20),
|
|
97
|
+
system,
|
|
98
|
+
tools: cacheControlledTools,
|
|
99
|
+
onFinish: async () => {
|
|
100
|
+
await client?.close();
|
|
101
|
+
},
|
|
102
|
+
providerOptions: {
|
|
103
|
+
anthropic: {
|
|
104
|
+
cacheControl: {
|
|
105
|
+
type: 'ephemeral',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return res.send(
|
|
112
|
+
result.toUIMessageStreamResponse({
|
|
113
|
+
onError: errorHandler,
|
|
114
|
+
}),
|
|
115
|
+
);
|
|
116
|
+
} catch (err: any) {
|
|
117
|
+
logger.error(err);
|
|
118
|
+
await client?.close();
|
|
119
|
+
res.status(500);
|
|
120
|
+
return res.send({ error: errorHandler(err) });
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
return mcpChatHandler;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const connectChat = (app: FastifyInstance, chatConfiguration: ChatConfiguration) => {
|
|
127
|
+
const handler = setupMCPChatHandler(chatConfiguration);
|
|
128
|
+
|
|
129
|
+
app.route({
|
|
130
|
+
url: '/chat',
|
|
131
|
+
method: ['POST', 'OPTIONS'],
|
|
132
|
+
handler,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
app.route({
|
|
136
|
+
url: '/chat/tools',
|
|
137
|
+
method: ['GET', 'OPTIONS'],
|
|
138
|
+
handler,
|
|
139
|
+
});
|
|
140
|
+
};
|
|
@@ -39,15 +39,12 @@ const ercMetadataHandler: RouteHandlerMethod = async (
|
|
|
39
39
|
|
|
40
40
|
if (!ercMetadata) return notFoundHandler(res);
|
|
41
41
|
|
|
42
|
-
const body = JSON.stringify(ercMetadata);
|
|
43
42
|
res.status(200);
|
|
44
|
-
res.
|
|
45
|
-
res.header('Content-Type', 'application/json');
|
|
46
|
-
return res.send(body);
|
|
43
|
+
return res.send(ercMetadata);
|
|
47
44
|
} catch (e) {
|
|
48
|
-
logger.error(e
|
|
45
|
+
logger.error(e);
|
|
49
46
|
res.status(503);
|
|
50
|
-
return res.send(
|
|
47
|
+
return res.send({ name: e.name, code: e.code, message: e.message });
|
|
51
48
|
}
|
|
52
49
|
};
|
|
53
50
|
|
package/src/fastify/index.ts
CHANGED
|
@@ -11,10 +11,12 @@ import { User } from '@unchainedshop/core-users';
|
|
|
11
11
|
import fastifySession from '@fastify/session';
|
|
12
12
|
import fastifyCookie from '@fastify/cookie';
|
|
13
13
|
import fastifyMultipart from '@fastify/multipart';
|
|
14
|
-
import { FastifyBaseLogger, FastifyInstance, FastifyRequest } from 'fastify';
|
|
14
|
+
import { FastifyBaseLogger, FastifyInstance, FastifyPluginAsync, FastifyRequest } from 'fastify';
|
|
15
15
|
import { createLogger } from '@unchainedshop/logger';
|
|
16
16
|
import mcpHandler from './mcpHandler.js';
|
|
17
17
|
import tempUploadHandler from './tempUploadHandler.js';
|
|
18
|
+
import fastifyStatic from '@fastify/static';
|
|
19
|
+
export * from './chatHandler.js';
|
|
18
20
|
|
|
19
21
|
const resolveUserRemoteAddress = (req: FastifyRequest) => {
|
|
20
22
|
const remoteAddress =
|
|
@@ -234,3 +236,37 @@ export const connect = (
|
|
|
234
236
|
registered();
|
|
235
237
|
});
|
|
236
238
|
};
|
|
239
|
+
|
|
240
|
+
interface FastifyRouterOptions {
|
|
241
|
+
prefix?: string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export const fastifyRouter: FastifyPluginAsync<FastifyRouterOptions> = async (
|
|
245
|
+
fastify: FastifyInstance,
|
|
246
|
+
opts,
|
|
247
|
+
) => {
|
|
248
|
+
try {
|
|
249
|
+
const staticURL = import.meta.resolve('@unchainedshop/admin-ui');
|
|
250
|
+
const staticPath = new URL(staticURL).pathname.split('/').slice(0, -1).join('/');
|
|
251
|
+
|
|
252
|
+
await fastify.register(fastifyStatic, {
|
|
253
|
+
root: staticPath,
|
|
254
|
+
prefix: opts.prefix || '/',
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
fastify.setNotFoundHandler((request, reply) => {
|
|
258
|
+
if (request.raw.method === 'GET') {
|
|
259
|
+
return reply.type('text/html').sendFile('index.html');
|
|
260
|
+
} else {
|
|
261
|
+
return reply.status(404).send();
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
} catch {
|
|
265
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
266
|
+
// Trying the default admin ui dev port
|
|
267
|
+
fastify.get('/', async (request, reply) => {
|
|
268
|
+
return reply.redirect('http://localhost:3000');
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
@@ -85,8 +85,8 @@ const mcpHandler: RouteHandlerMethod = async (
|
|
|
85
85
|
|
|
86
86
|
return res.status(405).send('Method Not Allowed');
|
|
87
87
|
} catch (e) {
|
|
88
|
-
logger.error(e
|
|
89
|
-
return res.status(503).send(
|
|
88
|
+
logger.error(e);
|
|
89
|
+
return res.status(503).send({ name: e.name, code: e.code, message: e.message });
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
92
|
|