@nextblock-cms/cortex 0.20.3 → 0.21.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/client.cjs.js +1 -1
- package/client.es.js +3 -7
- package/index.cjs.js +1 -1
- package/index.es.js +24 -277
- package/lib/ai-block-generation.cjs.js +3 -4
- package/lib/ai-block-generation.es.js +143 -182
- package/lib/ai-client.cjs.js +1 -1
- package/lib/ai-client.d.ts +1 -1
- package/lib/ai-client.es.js +101 -135
- package/lib/ai-config.cjs.js +1 -1
- package/lib/ai-config.es.js +89 -108
- package/lib/ai-cortex-widget-builder.cjs.js +1 -1
- package/lib/ai-cortex-widget-builder.es.js +56 -70
- package/lib/ai-global-agent-content-ops-tools.cjs.js +1 -1
- package/lib/ai-global-agent-content-ops-tools.d.ts +1943 -68
- package/lib/ai-global-agent-content-ops-tools.es.js +185 -199
- package/lib/ai-global-agent-custom-block-tools.cjs.js +1 -1
- package/lib/ai-global-agent-custom-block-tools.d.ts +2160 -95
- package/lib/ai-global-agent-custom-block-tools.es.js +226 -223
- package/lib/ai-global-agent-db-tools.cjs.js +1 -1
- package/lib/ai-global-agent-db-tools.d.ts +2708 -98
- package/lib/ai-global-agent-db-tools.es.js +1013 -986
- package/lib/ai-global-agent-ecommerce.cjs.js +1 -1
- package/lib/ai-global-agent-ecommerce.es.js +3 -7
- package/lib/ai-global-agent-site-tools.cjs.js +2 -3
- package/lib/ai-global-agent-site-tools.d.ts +10695 -500
- package/lib/ai-global-agent-site-tools.es.js +750 -757
- package/lib/ai-global-agent-theming-tools.cjs.js +1 -3
- package/lib/ai-global-agent-theming-tools.d.ts +2905 -105
- package/lib/ai-global-agent-theming-tools.es.js +326 -396
- package/lib/ai-global-agent-tools.cjs.js +2 -2
- package/lib/ai-global-agent-tools.d.ts +4 -2230
- package/lib/ai-global-agent-tools.es.js +3899 -4328
- package/lib/ai-key-crypto.cjs.js +1 -1
- package/lib/ai-key-crypto.es.js +63 -82
- package/lib/ai-key-verification.cjs.js +1 -1
- package/lib/ai-key-verification.es.js +74 -63
- package/lib/ai-model-catalog.cjs.js +1 -1
- package/lib/ai-model-catalog.es.js +18 -22
- package/lib/ai-model-registry.cjs.js +2 -2
- package/lib/ai-model-registry.es.js +246 -309
- package/lib/ai-seo-metadata.cjs.js +2 -3
- package/lib/ai-seo-metadata.es.js +143 -185
- package/lib/ai-vision.cjs.js +2 -2
- package/lib/ai-vision.d.ts +6 -6
- package/lib/ai-vision.es.js +161 -215
- package/lib/block-content-schemas.cjs.js +1 -1
- package/lib/block-content-schemas.es.js +324 -254
- package/lib/cortex-widget-registry.cjs.js +1 -1
- package/lib/cortex-widget-registry.es.js +35 -43
- package/lib/cortex-widget-schema.cjs.js +2 -3
- package/lib/cortex-widget-schema.es.js +280 -309
- package/lib/editor-document-from-html.cjs.js +1 -1
- package/lib/editor-document-from-html.es.js +334 -278
- package/lib/fetch-host-guard.cjs.js +1 -0
- package/lib/fetch-host-guard.d.ts +2 -0
- package/lib/fetch-host-guard.es.js +30 -0
- package/lib/mcp-server.cjs.js +1 -1
- package/lib/mcp-server.es.js +155 -173
- package/lib/mcp-tokens.cjs.js +1 -1
- package/lib/mcp-tokens.es.js +95 -107
- package/lib/mcp-tool-registry.cjs.js +5 -6
- package/lib/mcp-tool-registry.es.js +533 -411
- package/lib/site-brief.cjs.js +2 -2
- package/lib/site-brief.es.js +102 -108
- package/lib/vision-image-download.cjs.js +1 -0
- package/lib/vision-image-download.d.ts +43 -0
- package/lib/vision-image-download.es.js +120 -0
- package/lib/zod-config.cjs.js +1 -1
- package/lib/zod-config.es.js +5 -6
- package/package.json +8 -8
package/lib/site-brief.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
`)}exports.CORTEX_AI_BUILD_SESSION_SETTING_KEY=
|
|
1
|
+
require("./zod-config.cjs.js");let e=require("zod");var t=`cortex_ai_site_brief`,n=`cortex_ai_build_session`,r=e.z.string().trim().min(2).max(10),i=e.z.strictObject({purpose:e.z.string().trim().max(600).optional().describe(`What this page is for and what a visitor should do there.`),sections:e.z.array(e.z.string().trim().min(1).max(200)).max(20).optional().describe(`Planned sections, top to bottom, e.g. ["hero", "services", "testimonials", "contact form"].`),slug:e.z.string().trim().min(1).max(120).describe(`URL slug in the primary language, e.g. "home", "services", "contact".`),title:e.z.string().trim().min(1).max(200)}),a=e.z.strictObject({accent_color:e.z.string().trim().max(40).optional(),color_scheme:e.z.enum([`light`,`dark`]).optional().describe(`Overall look the client prefers.`),font_style:e.z.string().trim().max(120).optional().describe(`e.g. "clean sans-serif", "elegant serif".`),logo_notes:e.z.string().trim().max(600).optional().describe(`Whether they have a logo, where it is, or what to do meanwhile.`),primary_color:e.z.string().trim().max(40).optional().describe(`Hex or plain colour name, e.g. "#1e6f5c" or "forest green".`),secondary_color:e.z.string().trim().max(40).optional(),style_keywords:e.z.array(e.z.string().trim().min(1).max(60)).max(12).optional().describe(`e.g. ["minimal", "warm", "premium"].`),tone:e.z.string().trim().max(200).optional().describe(`Voice for the copy, e.g. "friendly and plain-spoken".`)}).partial(),o=e.z.strictObject({address:e.z.string().trim().max(400).optional(),email:e.z.string().trim().max(200).optional(),hours:e.z.string().trim().max(400).optional(),phone:e.z.string().trim().max(60).optional(),social:e.z.array(e.z.strictObject({platform:e.z.string().trim().min(1).max(60),url:e.z.string().trim().min(1).max(2048)})).max(12).optional()}).partial(),s=e.z.strictObject({audience:e.z.string().trim().max(1e3).optional().describe(`Who the site is for.`),brand:a.optional(),business_name:e.z.string().trim().min(1).max(160),contact:o.optional(),description:e.z.string().trim().max(3e3).optional().describe(`What the business does, in the client's own words where possible.`),ecommerce:e.z.strictObject({enabled:e.z.boolean().default(!1),products_summary:e.z.string().trim().max(1e3).optional()}).optional(),goals:e.z.array(e.z.string().trim().min(1).max(300)).max(10).optional().describe(`What the site must achieve, e.g. ["get quote requests", "show the menu"].`),keep_existing_content:e.z.boolean().default(!1).describe(`true when the client wants existing pages/posts kept rather than replaced.`),languages:e.z.array(r).min(1).max(10).default([`en`]).describe(`Locale codes the public site must offer, primary first.`),notes:e.z.string().trim().max(4e3).optional().describe(`Anything else the client said that matters.`),pages:e.z.array(i).max(30).default([]).describe(`The pages to build. A one-page landing site is a single "home" entry.`),primary_language:r.default(`en`),site_type:e.z.enum([`landing-page`,`multi-page`,`blog`,`store`,`portfolio`,`other`]).default(`multi-page`),status:e.z.enum([`draft`,`confirmed`,`built`]).default(`draft`).describe(`draft while interviewing, confirmed once the client approved the plan, built when the build finished.`),tagline:e.z.string().trim().max(200).optional(),updated_at:e.z.string().max(80).optional()}),c=s.partial(),l=e.z.strictObject({actorUserId:e.z.string().min(1),createdAt:e.z.string().min(1),expiresAt:e.z.string().min(1),id:e.z.string().min(1),summary:e.z.string().max(4e3)});function u(e){let t=s.safeParse(e);return t.success?t.data:null}function d(e){return!!(e&&e.business_name.trim()&&e.description?.trim())}function f(e,t){if(!e||!t.actorUserId||e.actorUserId!==t.actorUserId)return!1;let n=Date.parse(e.expiresAt),r=(t.now??new Date).getTime();return Number.isFinite(n)&&n>r}function p(e){let t=[`Business: ${e.business_name}${e.tagline?` — ${e.tagline}`:``}`];e.description&&t.push(`What they do: ${e.description}`),e.audience&&t.push(`Audience: ${e.audience}`),e.goals?.length&&t.push(`Goals: ${e.goals.join(`; `)}`),t.push(`Site type: ${e.site_type}. Languages: ${e.languages.join(`, `)} (primary ${e.primary_language}).`),e.pages.length>0&&t.push(`Pages: ${e.pages.map(e=>`${e.title} (/${e.slug})${e.purpose?` — ${e.purpose}`:``}`).join(`; `)}`);let n=e.brand;if(n&&Object.keys(n).length>0){let e=[n.primary_color?`primary ${n.primary_color}`:null,n.secondary_color?`secondary ${n.secondary_color}`:null,n.accent_color?`accent ${n.accent_color}`:null,n.color_scheme?`${n.color_scheme} scheme`:null,n.font_style?`fonts: ${n.font_style}`:null,n.tone?`tone: ${n.tone}`:null,n.style_keywords?.length?`style: ${n.style_keywords.join(`, `)}`:null,n.logo_notes?`logo: ${n.logo_notes}`:null].filter(Boolean);e.length>0&&t.push(`Brand: ${e.join(`; `)}`)}let r=e.contact;if(r&&Object.keys(r).length>0){let e=[r.email?`email ${r.email}`:null,r.phone?`phone ${r.phone}`:null,r.address?`address ${r.address}`:null,r.hours?`hours ${r.hours}`:null,r.social?.length?`social: ${r.social.map(e=>`${e.platform} ${e.url}`).join(`, `)}`:null].filter(Boolean);e.length>0&&t.push(`Contact: ${e.join(`; `)}`)}return e.ecommerce?.enabled&&t.push(`Ecommerce: yes${e.ecommerce.products_summary?` — ${e.ecommerce.products_summary}`:``}`),e.keep_existing_content&&t.push(`Keep existing content: yes (do not wipe pages or posts).`),e.notes&&t.push(`Notes: ${e.notes}`),t.push(`Brief status: ${e.status}.`),t.join(`
|
|
2
|
+
`)}exports.CORTEX_AI_BUILD_SESSION_SETTING_KEY=n,exports.CORTEX_AI_SITE_BRIEF_SETTING_KEY=t,exports.cortexBuildSessionSchema=l,exports.cortexSiteBriefInputSchema=c,exports.cortexSiteBriefSchema=s,exports.formatCortexSiteBriefForPrompt=p,exports.isCortexBuildSessionUsable=f,exports.isCortexSiteBriefComplete=d,exports.safeParseCortexSiteBrief=u;
|
package/lib/site-brief.es.js
CHANGED
|
@@ -1,113 +1,107 @@
|
|
|
1
|
-
import "./zod-config.es.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}).partial(),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
import { z as e } from "./zod-config.es.js";
|
|
2
|
+
//#region libs/cortex/src/lib/site-brief.ts
|
|
3
|
+
var t = "cortex_ai_site_brief", n = "cortex_ai_build_session", r = e.string().trim().min(2).max(10), i = e.strictObject({
|
|
4
|
+
purpose: e.string().trim().max(600).optional().describe("What this page is for and what a visitor should do there."),
|
|
5
|
+
sections: e.array(e.string().trim().min(1).max(200)).max(20).optional().describe("Planned sections, top to bottom, e.g. [\"hero\", \"services\", \"testimonials\", \"contact form\"]."),
|
|
6
|
+
slug: e.string().trim().min(1).max(120).describe("URL slug in the primary language, e.g. \"home\", \"services\", \"contact\"."),
|
|
7
|
+
title: e.string().trim().min(1).max(200)
|
|
8
|
+
}), a = e.strictObject({
|
|
9
|
+
accent_color: e.string().trim().max(40).optional(),
|
|
10
|
+
color_scheme: e.enum(["light", "dark"]).optional().describe("Overall look the client prefers."),
|
|
11
|
+
font_style: e.string().trim().max(120).optional().describe("e.g. \"clean sans-serif\", \"elegant serif\"."),
|
|
12
|
+
logo_notes: e.string().trim().max(600).optional().describe("Whether they have a logo, where it is, or what to do meanwhile."),
|
|
13
|
+
primary_color: e.string().trim().max(40).optional().describe("Hex or plain colour name, e.g. \"#1e6f5c\" or \"forest green\"."),
|
|
14
|
+
secondary_color: e.string().trim().max(40).optional(),
|
|
15
|
+
style_keywords: e.array(e.string().trim().min(1).max(60)).max(12).optional().describe("e.g. [\"minimal\", \"warm\", \"premium\"]."),
|
|
16
|
+
tone: e.string().trim().max(200).optional().describe("Voice for the copy, e.g. \"friendly and plain-spoken\".")
|
|
17
|
+
}).partial(), o = e.strictObject({
|
|
18
|
+
address: e.string().trim().max(400).optional(),
|
|
19
|
+
email: e.string().trim().max(200).optional(),
|
|
20
|
+
hours: e.string().trim().max(400).optional(),
|
|
21
|
+
phone: e.string().trim().max(60).optional(),
|
|
22
|
+
social: e.array(e.strictObject({
|
|
23
|
+
platform: e.string().trim().min(1).max(60),
|
|
24
|
+
url: e.string().trim().min(1).max(2048)
|
|
25
|
+
})).max(12).optional()
|
|
26
|
+
}).partial(), s = e.strictObject({
|
|
27
|
+
audience: e.string().trim().max(1e3).optional().describe("Who the site is for."),
|
|
28
|
+
brand: a.optional(),
|
|
29
|
+
business_name: e.string().trim().min(1).max(160),
|
|
30
|
+
contact: o.optional(),
|
|
31
|
+
description: e.string().trim().max(3e3).optional().describe("What the business does, in the client's own words where possible."),
|
|
32
|
+
ecommerce: e.strictObject({
|
|
33
|
+
enabled: e.boolean().default(!1),
|
|
34
|
+
products_summary: e.string().trim().max(1e3).optional()
|
|
35
|
+
}).optional(),
|
|
36
|
+
goals: e.array(e.string().trim().min(1).max(300)).max(10).optional().describe("What the site must achieve, e.g. [\"get quote requests\", \"show the menu\"]."),
|
|
37
|
+
keep_existing_content: e.boolean().default(!1).describe("true when the client wants existing pages/posts kept rather than replaced."),
|
|
38
|
+
languages: e.array(r).min(1).max(10).default(["en"]).describe("Locale codes the public site must offer, primary first."),
|
|
39
|
+
notes: e.string().trim().max(4e3).optional().describe("Anything else the client said that matters."),
|
|
40
|
+
pages: e.array(i).max(30).default([]).describe("The pages to build. A one-page landing site is a single \"home\" entry."),
|
|
41
|
+
primary_language: r.default("en"),
|
|
42
|
+
site_type: e.enum([
|
|
43
|
+
"landing-page",
|
|
44
|
+
"multi-page",
|
|
45
|
+
"blog",
|
|
46
|
+
"store",
|
|
47
|
+
"portfolio",
|
|
48
|
+
"other"
|
|
49
|
+
]).default("multi-page"),
|
|
50
|
+
status: e.enum([
|
|
51
|
+
"draft",
|
|
52
|
+
"confirmed",
|
|
53
|
+
"built"
|
|
54
|
+
]).default("draft").describe("draft while interviewing, confirmed once the client approved the plan, built when the build finished."),
|
|
55
|
+
tagline: e.string().trim().max(200).optional(),
|
|
56
|
+
updated_at: e.string().max(80).optional()
|
|
57
|
+
}), c = s.partial(), l = e.strictObject({
|
|
58
|
+
actorUserId: e.string().min(1),
|
|
59
|
+
createdAt: e.string().min(1),
|
|
60
|
+
expiresAt: e.string().min(1),
|
|
61
|
+
id: e.string().min(1),
|
|
62
|
+
summary: e.string().max(4e3)
|
|
54
63
|
});
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
function u(e) {
|
|
65
|
+
let t = s.safeParse(e);
|
|
66
|
+
return t.success ? t.data : null;
|
|
58
67
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
68
|
+
function d(e) {
|
|
69
|
+
return !!(e && e.business_name.trim() && e.description?.trim());
|
|
61
70
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return Number.isFinite(i) && i > n;
|
|
71
|
+
function f(e, t) {
|
|
72
|
+
if (!e || !t.actorUserId || e.actorUserId !== t.actorUserId) return !1;
|
|
73
|
+
let n = Date.parse(e.expiresAt), r = (t.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
74
|
+
return Number.isFinite(n) && n > r;
|
|
67
75
|
}
|
|
68
|
-
function
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
].filter(Boolean);
|
|
98
|
-
s.length > 0 && o.push(`Contact: ${s.join("; ")}`);
|
|
99
|
-
}
|
|
100
|
-
return t.ecommerce?.enabled && o.push(`Ecommerce: yes${t.ecommerce.products_summary ? ` — ${t.ecommerce.products_summary}` : ""}`), t.keep_existing_content && o.push("Keep existing content: yes (do not wipe pages or posts)."), t.notes && o.push(`Notes: ${t.notes}`), o.push(`Brief status: ${t.status}.`), o.join(`
|
|
101
|
-
`);
|
|
76
|
+
function p(e) {
|
|
77
|
+
let t = [`Business: ${e.business_name}${e.tagline ? ` — ${e.tagline}` : ""}`];
|
|
78
|
+
e.description && t.push(`What they do: ${e.description}`), e.audience && t.push(`Audience: ${e.audience}`), e.goals?.length && t.push(`Goals: ${e.goals.join("; ")}`), t.push(`Site type: ${e.site_type}. Languages: ${e.languages.join(", ")} (primary ${e.primary_language}).`), e.pages.length > 0 && t.push(`Pages: ${e.pages.map((e) => `${e.title} (/${e.slug})${e.purpose ? ` — ${e.purpose}` : ""}`).join("; ")}`);
|
|
79
|
+
let n = e.brand;
|
|
80
|
+
if (n && Object.keys(n).length > 0) {
|
|
81
|
+
let e = [
|
|
82
|
+
n.primary_color ? `primary ${n.primary_color}` : null,
|
|
83
|
+
n.secondary_color ? `secondary ${n.secondary_color}` : null,
|
|
84
|
+
n.accent_color ? `accent ${n.accent_color}` : null,
|
|
85
|
+
n.color_scheme ? `${n.color_scheme} scheme` : null,
|
|
86
|
+
n.font_style ? `fonts: ${n.font_style}` : null,
|
|
87
|
+
n.tone ? `tone: ${n.tone}` : null,
|
|
88
|
+
n.style_keywords?.length ? `style: ${n.style_keywords.join(", ")}` : null,
|
|
89
|
+
n.logo_notes ? `logo: ${n.logo_notes}` : null
|
|
90
|
+
].filter(Boolean);
|
|
91
|
+
e.length > 0 && t.push(`Brand: ${e.join("; ")}`);
|
|
92
|
+
}
|
|
93
|
+
let r = e.contact;
|
|
94
|
+
if (r && Object.keys(r).length > 0) {
|
|
95
|
+
let e = [
|
|
96
|
+
r.email ? `email ${r.email}` : null,
|
|
97
|
+
r.phone ? `phone ${r.phone}` : null,
|
|
98
|
+
r.address ? `address ${r.address}` : null,
|
|
99
|
+
r.hours ? `hours ${r.hours}` : null,
|
|
100
|
+
r.social?.length ? `social: ${r.social.map((e) => `${e.platform} ${e.url}`).join(", ")}` : null
|
|
101
|
+
].filter(Boolean);
|
|
102
|
+
e.length > 0 && t.push(`Contact: ${e.join("; ")}`);
|
|
103
|
+
}
|
|
104
|
+
return e.ecommerce?.enabled && t.push(`Ecommerce: yes${e.ecommerce.products_summary ? ` — ${e.ecommerce.products_summary}` : ""}`), e.keep_existing_content && t.push("Keep existing content: yes (do not wipe pages or posts)."), e.notes && t.push(`Notes: ${e.notes}`), t.push(`Brief status: ${e.status}.`), t.join("\n");
|
|
102
105
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
g as CORTEX_AI_SITE_BRIEF_SETTING_KEY,
|
|
106
|
-
x as cortexBuildSessionSchema,
|
|
107
|
-
_ as cortexSiteBriefInputSchema,
|
|
108
|
-
l as cortexSiteBriefSchema,
|
|
109
|
-
b as formatCortexSiteBriefForPrompt,
|
|
110
|
-
$ as isCortexBuildSessionUsable,
|
|
111
|
-
f as isCortexSiteBriefComplete,
|
|
112
|
-
y as safeParseCortexSiteBrief
|
|
113
|
-
};
|
|
106
|
+
//#endregion
|
|
107
|
+
export { n as CORTEX_AI_BUILD_SESSION_SETTING_KEY, t as CORTEX_AI_SITE_BRIEF_SETTING_KEY, l as cortexBuildSessionSchema, c as cortexSiteBriefInputSchema, s as cortexSiteBriefSchema, p as formatCortexSiteBriefForPrompt, f as isCortexBuildSessionUsable, d as isCortexSiteBriefComplete, u as safeParseCortexSiteBrief };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./fetch-host-guard.cjs.js");let t=require("node:dns"),n=require("node:http"),r=require("node:https"),i=require("node:net");var a=20971520,o=5,s=2e4,c=(()=>{let e=new i.BlockList;for(let[t,n]of[[`0.0.0.0`,8],[`10.0.0.0`,8],[`100.64.0.0`,10],[`127.0.0.0`,8],[`169.254.0.0`,16],[`172.16.0.0`,12],[`192.0.0.0`,24],[`192.0.2.0`,24],[`192.168.0.0`,16],[`198.18.0.0`,15],[`198.51.100.0`,24],[`203.0.113.0`,24],[`224.0.0.0`,4],[`240.0.0.0`,4]])e.addSubnet(t,n,`ipv4`);for(let[t,n]of[[`::`,128],[`::1`,128],[`64:ff9b::`,96],[`100::`,64],[`2001:db8::`,32],[`fc00::`,7],[`fe80::`,10],[`ff00::`,8]])e.addSubnet(t,n,`ipv6`);return e})();function l(e){let t=(0,i.isIP)(e);return t===4?c.check(e,`ipv4`):t!==6||c.check(e,`ipv6`)}var u=(e,n)=>(0,t.lookup)(e,{all:!0},(e,t)=>n(e,t??[]));function d(e=u){return(t,n,r)=>{e(t,(e,i)=>{if(e){r(e,``);return}let a=i.find(e=>l(e.address));if(i.length===0||a){r(Error(a?`Refusing to download the image: ${t} resolves to a private address (${a.address}).`:`Refusing to download the image: ${t} did not resolve to any address.`),``);return}if(n.all){r(null,i);return}let[o]=i;r(null,o.address,o.family)})}}function f(e,t,i){return new Promise((a,o)=>{let s=(e.protocol===`https:`?r.request:n.request)(e,{headers:{accept:`image/*`,"user-agent":`NextBlock-Cortex-AI`},lookup:t,method:`GET`,signal:i});s.on(`response`,a),s.on(`error`,o),s.end()})}async function p(e,t){let n=[],r=0;for await(let i of e){let a=i;if(r+=a.length,r>t)throw e.destroy(),Error(`The image is larger than the ${t}-byte limit for alt-text generation.`);n.push(a)}return new Uint8Array(Buffer.concat(n))}function m(e){let t=t=>e[t];if(e.length>=8&&t(0)===137&&t(1)===80&&t(2)===78&&t(3)===71)return`image/png`;if(e.length>=3&&t(0)===255&&t(1)===216&&t(2)===255)return`image/jpeg`;if(e.length>=6&&t(0)===71&&t(1)===73&&t(2)===70&&t(3)===56)return`image/gif`;if(e.length>=12&&t(0)===82&&t(1)===73&&t(2)===70&&t(3)===70&&t(8)===87&&t(9)===69&&t(10)===66&&t(11)===80)return`image/webp`;if(e.length>=12&&t(4)===102&&t(5)===116&&t(6)===121&&t(7)===112){let t=String.fromCharCode(...e.subarray(8,12));if(t===`avif`||t===`avis`)return`image/avif`}return null}async function h(t,n={}){let r=n.lookup??d(),a=n.maxBytes??20971520,c=n.abortSignal?AbortSignal.any([n.abortSignal,AbortSignal.timeout(s)]):AbortSignal.timeout(s),u=t;for(let t=0;;t++){if(u.protocol!==`https:`&&u.protocol!==`http:`)throw Error(`Refusing to download the image: ${u.protocol} URLs are not allowed.`);let n=u.hostname.replace(/^\[|\]$/g,``);if(e.isBlockedFetchHost(n)||(0,i.isIP)(n)!==0&&l(n))throw Error(`Refusing to download the image from ${u.hostname}: private or local host.`);let s=await f(u,r,c),d=s.statusCode??0;if(d>=300&&d<400&&s.headers.location){if(s.resume(),t>=o)throw Error(`Too many redirects while downloading the image.`);u=new URL(s.headers.location,u);continue}if(d<200||d>=300)throw s.resume(),Error(`The image URL answered HTTP ${d}.`);let h=await p(s,a),[g=``]=String(s.headers[`content-type`]??``).split(`;`),_=g.trim().toLowerCase(),v=_.startsWith(`image/`)?_:m(h);if(!v)throw Error(`The URL did not return an image (Content-Type: ${_||`none`}).`);return{data:h,mediaType:v}}}exports.CORTEX_AI_VISION_MAX_IMAGE_BYTES=a,exports.createPinnedLookup=d,exports.downloadCortexVisionImage=h,exports.isPrivateAddress=l,exports.sniffImageMediaType=m;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { LookupAddress } from 'node:dns';
|
|
2
|
+
import { LookupFunction } from 'node:net';
|
|
3
|
+
/**
|
|
4
|
+
* Download the image Cortex AI describes for alt text, so the AI SDK never has to.
|
|
5
|
+
*
|
|
6
|
+
* AI SDK 7 downloads a URL file part itself through a DNS-pinned undici Agent that
|
|
7
|
+
* @ai-sdk/provider-utils loads at runtime with `createRequire(...)('undici')`. That dynamic
|
|
8
|
+
* require is invisible to Next's output file tracer, so undici was missing from Vercel
|
|
9
|
+
* functions and from standalone builds, and every alt-text request threw there. Tracing it
|
|
10
|
+
* in next.config.js is not a fix for this repo: the glob that reaches the monorepo root
|
|
11
|
+
* breaks a scaffold's Turbopack build, and `npm run update` never rewrites an existing
|
|
12
|
+
* project's next.config.js. So the image is fetched here with Node's own http/https and
|
|
13
|
+
* handed to the model as bytes.
|
|
14
|
+
*
|
|
15
|
+
* The SSRF protection matches the SDK's: the hostname is checked first, and the DNS lookup
|
|
16
|
+
* is PINNED (the address that is checked is the address the socket connects to, so a
|
|
17
|
+
* rebinding hostname cannot swap in a private address afterwards). Redirects are followed
|
|
18
|
+
* by hand and every hop is checked the same way. The body is capped, and the media type
|
|
19
|
+
* comes from Content-Type or, when that is not an image type, from the file signature.
|
|
20
|
+
*/
|
|
21
|
+
/** Largest image accepted for alt-text generation. The model request inlines it as base64. */
|
|
22
|
+
export declare const CORTEX_AI_VISION_MAX_IMAGE_BYTES: number;
|
|
23
|
+
export declare function isPrivateAddress(address: string): boolean;
|
|
24
|
+
type ResolveAll = (hostname: string, callback: (error: NodeJS.ErrnoException | null, addresses: LookupAddress[]) => void) => void;
|
|
25
|
+
/**
|
|
26
|
+
* A `lookup` for http(s).request that refuses any answer containing a private address.
|
|
27
|
+
* `resolveAll` is injectable so tests can simulate DNS without the network.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createPinnedLookup(resolveAll?: ResolveAll): LookupFunction;
|
|
30
|
+
export type DownloadedVisionImage = {
|
|
31
|
+
data: Uint8Array;
|
|
32
|
+
mediaType: string;
|
|
33
|
+
};
|
|
34
|
+
export type DownloadVisionImageOptions = {
|
|
35
|
+
abortSignal?: AbortSignal;
|
|
36
|
+
/** Override for tests. Defaults to the pinned DNS lookup. */
|
|
37
|
+
lookup?: LookupFunction;
|
|
38
|
+
maxBytes?: number;
|
|
39
|
+
};
|
|
40
|
+
/** The media type from the first bytes, for servers that send no or a generic Content-Type. */
|
|
41
|
+
export declare function sniffImageMediaType(bytes: Uint8Array): string | null;
|
|
42
|
+
export declare function downloadCortexVisionImage(url: URL, options?: DownloadVisionImageOptions): Promise<DownloadedVisionImage>;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { isBlockedFetchHost as e } from "./fetch-host-guard.es.js";
|
|
2
|
+
import { lookup as t } from "node:dns";
|
|
3
|
+
import { request as n } from "node:http";
|
|
4
|
+
import { request as r } from "node:https";
|
|
5
|
+
import { BlockList as i, isIP as a } from "node:net";
|
|
6
|
+
//#region libs/cortex/src/lib/vision-image-download.ts
|
|
7
|
+
var o = 20971520, s = 5, c = 2e4, l = (() => {
|
|
8
|
+
let e = new i();
|
|
9
|
+
for (let [t, n] of [
|
|
10
|
+
["0.0.0.0", 8],
|
|
11
|
+
["10.0.0.0", 8],
|
|
12
|
+
["100.64.0.0", 10],
|
|
13
|
+
["127.0.0.0", 8],
|
|
14
|
+
["169.254.0.0", 16],
|
|
15
|
+
["172.16.0.0", 12],
|
|
16
|
+
["192.0.0.0", 24],
|
|
17
|
+
["192.0.2.0", 24],
|
|
18
|
+
["192.168.0.0", 16],
|
|
19
|
+
["198.18.0.0", 15],
|
|
20
|
+
["198.51.100.0", 24],
|
|
21
|
+
["203.0.113.0", 24],
|
|
22
|
+
["224.0.0.0", 4],
|
|
23
|
+
["240.0.0.0", 4]
|
|
24
|
+
]) e.addSubnet(t, n, "ipv4");
|
|
25
|
+
for (let [t, n] of [
|
|
26
|
+
["::", 128],
|
|
27
|
+
["::1", 128],
|
|
28
|
+
["64:ff9b::", 96],
|
|
29
|
+
["100::", 64],
|
|
30
|
+
["2001:db8::", 32],
|
|
31
|
+
["fc00::", 7],
|
|
32
|
+
["fe80::", 10],
|
|
33
|
+
["ff00::", 8]
|
|
34
|
+
]) e.addSubnet(t, n, "ipv6");
|
|
35
|
+
return e;
|
|
36
|
+
})();
|
|
37
|
+
function u(e) {
|
|
38
|
+
let t = a(e);
|
|
39
|
+
return t === 4 ? l.check(e, "ipv4") : t !== 6 || l.check(e, "ipv6");
|
|
40
|
+
}
|
|
41
|
+
var d = (e, n) => t(e, { all: !0 }, (e, t) => n(e, t ?? []));
|
|
42
|
+
function f(e = d) {
|
|
43
|
+
return (t, n, r) => {
|
|
44
|
+
e(t, (e, i) => {
|
|
45
|
+
if (e) {
|
|
46
|
+
r(e, "");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
let a = i.find((e) => u(e.address));
|
|
50
|
+
if (i.length === 0 || a) {
|
|
51
|
+
r(/* @__PURE__ */ Error(a ? `Refusing to download the image: ${t} resolves to a private address (${a.address}).` : `Refusing to download the image: ${t} did not resolve to any address.`), "");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (n.all) {
|
|
55
|
+
r(null, i);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let [o] = i;
|
|
59
|
+
r(null, o.address, o.family);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function p(e, t, i) {
|
|
64
|
+
return new Promise((a, o) => {
|
|
65
|
+
let s = (e.protocol === "https:" ? r : n)(e, {
|
|
66
|
+
headers: {
|
|
67
|
+
accept: "image/*",
|
|
68
|
+
"user-agent": "NextBlock-Cortex-AI"
|
|
69
|
+
},
|
|
70
|
+
lookup: t,
|
|
71
|
+
method: "GET",
|
|
72
|
+
signal: i
|
|
73
|
+
});
|
|
74
|
+
s.on("response", a), s.on("error", o), s.end();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function m(e, t) {
|
|
78
|
+
let n = [], r = 0;
|
|
79
|
+
for await (let i of e) {
|
|
80
|
+
let a = i;
|
|
81
|
+
if (r += a.length, r > t) throw e.destroy(), Error(`The image is larger than the ${t}-byte limit for alt-text generation.`);
|
|
82
|
+
n.push(a);
|
|
83
|
+
}
|
|
84
|
+
return new Uint8Array(Buffer.concat(n));
|
|
85
|
+
}
|
|
86
|
+
function h(e) {
|
|
87
|
+
let t = (t) => e[t];
|
|
88
|
+
if (e.length >= 8 && t(0) === 137 && t(1) === 80 && t(2) === 78 && t(3) === 71) return "image/png";
|
|
89
|
+
if (e.length >= 3 && t(0) === 255 && t(1) === 216 && t(2) === 255) return "image/jpeg";
|
|
90
|
+
if (e.length >= 6 && t(0) === 71 && t(1) === 73 && t(2) === 70 && t(3) === 56) return "image/gif";
|
|
91
|
+
if (e.length >= 12 && t(0) === 82 && t(1) === 73 && t(2) === 70 && t(3) === 70 && t(8) === 87 && t(9) === 69 && t(10) === 66 && t(11) === 80) return "image/webp";
|
|
92
|
+
if (e.length >= 12 && t(4) === 102 && t(5) === 116 && t(6) === 121 && t(7) === 112) {
|
|
93
|
+
let t = String.fromCharCode(...e.subarray(8, 12));
|
|
94
|
+
if (t === "avif" || t === "avis") return "image/avif";
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
async function g(t, n = {}) {
|
|
99
|
+
let r = n.lookup ?? f(), i = n.maxBytes ?? 20971520, o = n.abortSignal ? AbortSignal.any([n.abortSignal, AbortSignal.timeout(c)]) : AbortSignal.timeout(c), l = t;
|
|
100
|
+
for (let t = 0;; t++) {
|
|
101
|
+
if (l.protocol !== "https:" && l.protocol !== "http:") throw Error(`Refusing to download the image: ${l.protocol} URLs are not allowed.`);
|
|
102
|
+
let n = l.hostname.replace(/^\[|\]$/g, "");
|
|
103
|
+
if (e(n) || a(n) !== 0 && u(n)) throw Error(`Refusing to download the image from ${l.hostname}: private or local host.`);
|
|
104
|
+
let c = await p(l, r, o), d = c.statusCode ?? 0;
|
|
105
|
+
if (d >= 300 && d < 400 && c.headers.location) {
|
|
106
|
+
if (c.resume(), t >= s) throw Error("Too many redirects while downloading the image.");
|
|
107
|
+
l = new URL(c.headers.location, l);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (d < 200 || d >= 300) throw c.resume(), Error(`The image URL answered HTTP ${d}.`);
|
|
111
|
+
let f = await m(c, i), [g = ""] = String(c.headers["content-type"] ?? "").split(";"), _ = g.trim().toLowerCase(), v = _.startsWith("image/") ? _ : h(f);
|
|
112
|
+
if (!v) throw Error(`The URL did not return an image (Content-Type: ${_ || "none"}).`);
|
|
113
|
+
return {
|
|
114
|
+
data: f,
|
|
115
|
+
mediaType: v
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { o as CORTEX_AI_VISION_MAX_IMAGE_BYTES, f as createPinnedLookup, g as downloadCortexVisionImage, u as isPrivateAddress, h as sniffImageMediaType };
|
package/lib/zod-config.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
let e=require("zod");e.z.config({jitless:!0}),exports.z=e.z;
|
package/lib/zod-config.es.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { z as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
1
|
+
import { z as e } from "zod";
|
|
2
|
+
//#region libs/cortex/src/lib/zod-config.ts
|
|
3
|
+
e.config({ jitless: !0 });
|
|
4
|
+
//#endregion
|
|
5
|
+
export { e as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextblock-cms/cortex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"license": "AGPL-3.0-or-later",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"./package.json": "./package.json"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@ai-sdk/openai-compatible": "^
|
|
31
|
-
"@nextblock-cms/db": "^0.
|
|
32
|
-
"@nextblock-cms/ecommerce": "npm:@nextblock-cms/ecom@^0.
|
|
33
|
-
"@nextblock-cms/utils": "^0.
|
|
34
|
-
"ai": "^
|
|
35
|
-
"zod": "^4.
|
|
30
|
+
"@ai-sdk/openai-compatible": "^3.0.53",
|
|
31
|
+
"@nextblock-cms/db": "^0.21.0",
|
|
32
|
+
"@nextblock-cms/ecommerce": "npm:@nextblock-cms/ecom@^0.21.0",
|
|
33
|
+
"@nextblock-cms/utils": "^0.21.0",
|
|
34
|
+
"ai": "^7.0.107",
|
|
35
|
+
"zod": "^4.6.5"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"next": "^16.
|
|
38
|
+
"next": "^16.3.5"
|
|
39
39
|
}
|
|
40
40
|
}
|