@nextblock-cms/cortex 0.22.0 → 0.22.1
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/README.md +46 -46
- package/lib/ai-global-agent-db-tools.cjs +1 -1
- package/lib/ai-global-agent-db-tools.es.js +148 -122
- package/lib/ai-global-agent-tools.cjs +1 -1
- package/lib/ai-global-agent-tools.es.js +801 -800
- package/lib/fetch-host-guard.cjs +1 -1
- package/lib/fetch-host-guard.d.ts +13 -0
- package/lib/fetch-host-guard.es.js +15 -4
- package/lib/mcp-tokens.cjs +1 -1
- package/lib/mcp-tokens.d.ts +20 -9
- package/lib/mcp-tokens.es.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
# @nextblock-cms/cortex
|
|
2
|
-
|
|
3
|
-
Cortex AI, the premium AI module of [NextBlock](https://github.com/nextblock-cms/nextblock), the
|
|
4
|
-
open-core CMS for Next.js and Supabase. It powers the CMS assistant that builds and edits pages,
|
|
5
|
-
posts and products through tools, inline block and HTML generation, SEO metadata, image alt
|
|
6
|
-
text, and the MCP server that lets external agents work on a NextBlock site.
|
|
7
|
-
|
|
8
|
-
Models are called through [OpenRouter](https://openrouter.ai) with the Vercel AI SDK, and the
|
|
9
|
-
site operator chooses the model.
|
|
10
|
-
|
|
11
|
-
Projects created with `npm create nextblock@latest` already depend on it. It needs Next.js 16
|
|
12
|
-
(peer dependency), Node.js 22.12 or later, and the project's own `ai` at ^7 and
|
|
13
|
-
`@ai-sdk/openai-compatible` at ^3, the versions this package builds its models with.
|
|
14
|
-
|
|
15
|
-
## Licensing
|
|
16
|
-
|
|
17
|
-
The source is public under AGPL-3.0-or-later, and the features are license-gated under the
|
|
18
|
-
package id `cortex-ai`. A NextBlock project enables Cortex AI only after its license is active:
|
|
19
|
-
start the free trial or buy one in the CMS under **Settings → Packages**. The app checks it with
|
|
20
|
-
`verifyPackageOnline('cortex-ai')` from `@nextblock-cms/db/server`.
|
|
21
|
-
|
|
22
|
-
## Entry points
|
|
23
|
-
|
|
24
|
-
| Import | Use it from | What it holds |
|
|
25
|
-
| :-- | :-- | :-- |
|
|
26
|
-
| `@nextblock-cms/cortex` | server only | The OpenRouter client and model registry, the global agent and its tools, block and widget generation, SEO metadata, alt text, and the MCP server, tokens and tool registry. It imports `next/headers` and `server-only` modules. |
|
|
27
|
-
| `@nextblock-cms/cortex/client` | client or server | The browser-safe values a Client Component may need: `createCortexAiStoredModelSelection`, `formatCortexSiteBriefForPrompt` and `isCortexSiteBriefComplete` |
|
|
28
|
-
|
|
29
|
-
Types from the main entry are safe to import anywhere; runtime values used by a Client
|
|
30
|
-
Component must come from `/client`.
|
|
31
|
-
|
|
32
|
-
## Environment
|
|
33
|
-
|
|
34
|
-
- `OPENROUTER_API_KEY`: the server-side key, used when no key is stored in the CMS. A key saved
|
|
35
|
-
in the CMS takes precedence.
|
|
36
|
-
- `CORTEX_AI_ENCRYPTION_KEY`: required to save and read the key stored in the CMS. Changing it
|
|
37
|
-
makes stored keys unreadable.
|
|
38
|
-
|
|
39
|
-
## Documentation
|
|
40
|
-
|
|
41
|
-
- [Cortex AI architecture](https://github.com/nextblock-cms/nextblock/blob/master/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md)
|
|
42
|
-
- [Project overview](https://github.com/nextblock-cms/nextblock/blob/master/docs/01-PROJECT-OVERVIEW.md)
|
|
43
|
-
|
|
44
|
-
## License
|
|
45
|
-
|
|
46
|
-
AGPL-3.0-or-later.
|
|
1
|
+
# @nextblock-cms/cortex
|
|
2
|
+
|
|
3
|
+
Cortex AI, the premium AI module of [NextBlock](https://github.com/nextblock-cms/nextblock), the
|
|
4
|
+
open-core CMS for Next.js and Supabase. It powers the CMS assistant that builds and edits pages,
|
|
5
|
+
posts and products through tools, inline block and HTML generation, SEO metadata, image alt
|
|
6
|
+
text, and the MCP server that lets external agents work on a NextBlock site.
|
|
7
|
+
|
|
8
|
+
Models are called through [OpenRouter](https://openrouter.ai) with the Vercel AI SDK, and the
|
|
9
|
+
site operator chooses the model.
|
|
10
|
+
|
|
11
|
+
Projects created with `npm create nextblock@latest` already depend on it. It needs Next.js 16
|
|
12
|
+
(peer dependency), Node.js 22.12 or later, and the project's own `ai` at ^7 and
|
|
13
|
+
`@ai-sdk/openai-compatible` at ^3, the versions this package builds its models with.
|
|
14
|
+
|
|
15
|
+
## Licensing
|
|
16
|
+
|
|
17
|
+
The source is public under AGPL-3.0-or-later, and the features are license-gated under the
|
|
18
|
+
package id `cortex-ai`. A NextBlock project enables Cortex AI only after its license is active:
|
|
19
|
+
start the free trial or buy one in the CMS under **Settings → Packages**. The app checks it with
|
|
20
|
+
`verifyPackageOnline('cortex-ai')` from `@nextblock-cms/db/server`.
|
|
21
|
+
|
|
22
|
+
## Entry points
|
|
23
|
+
|
|
24
|
+
| Import | Use it from | What it holds |
|
|
25
|
+
| :-- | :-- | :-- |
|
|
26
|
+
| `@nextblock-cms/cortex` | server only | The OpenRouter client and model registry, the global agent and its tools, block and widget generation, SEO metadata, alt text, and the MCP server, tokens and tool registry. It imports `next/headers` and `server-only` modules. |
|
|
27
|
+
| `@nextblock-cms/cortex/client` | client or server | The browser-safe values a Client Component may need: `createCortexAiStoredModelSelection`, `formatCortexSiteBriefForPrompt` and `isCortexSiteBriefComplete` |
|
|
28
|
+
|
|
29
|
+
Types from the main entry are safe to import anywhere; runtime values used by a Client
|
|
30
|
+
Component must come from `/client`.
|
|
31
|
+
|
|
32
|
+
## Environment
|
|
33
|
+
|
|
34
|
+
- `OPENROUTER_API_KEY`: the server-side key, used when no key is stored in the CMS. A key saved
|
|
35
|
+
in the CMS takes precedence.
|
|
36
|
+
- `CORTEX_AI_ENCRYPTION_KEY`: required to save and read the key stored in the CMS. Changing it
|
|
37
|
+
makes stored keys unreadable.
|
|
38
|
+
|
|
39
|
+
## Documentation
|
|
40
|
+
|
|
41
|
+
- [Cortex AI architecture](https://github.com/nextblock-cms/nextblock/blob/master/docs/08-NEXTBLOCK-CORTEX-AI-ARCHITECTURE.md)
|
|
42
|
+
- [Project overview](https://github.com/nextblock-cms/nextblock/blob/master/docs/01-PROJECT-OVERVIEW.md)
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
AGPL-3.0-or-later.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
require("./zod-config.cjs");const e=require("./site-brief.cjs"),t=require("./block-content-schemas.cjs");let n=require("ai"),r=require("zod");var i=100,a=20,o=100,s=20,c=`[REDACTED]`,l=`cortex_ai_openrouter_api_key`,u=new Set([l,`bot_protection_secret`,`cortex_ai_pexels_api_key`,`cortex_ai_unsplash_access_key`,`email_secret`,`payment_secret`]),d=new Set([`cortex_ai_build_session`,`is_admin_created`,`migration_baseline_generation`,`security_settings`]),f={active_logo_id:r.z.string().uuid().nullable(),cortex_ai_site_brief:e.cortexSiteBriefSchema,footer_copyright:r.z.record(r.z.string().trim().min(2).max(12),r.z.string().max(500)),footer_show_attribution:r.z.boolean(),global_css:r.z.string().max(2e5),onboarding_state:r.z.record(r.z.string(),r.z.unknown()),site_description:r.z.string().max(1e3),site_keywords:r.z.string().max(1e3),site_title:r.z.string().max(200)};function p(e){return e.map(e=>{let t=e.path.map(String).join(`.`);return t?`${t}: ${e.message}`:e.message}).join(`; `)}function m(e,t){if(d.has(e))throw Error(`The "${e}" site setting cannot be written through Cortex AI database tools.`);let n=f[e];if(!n)return;let r=n.safeParse(t);if(!r.success)throw Error(`Invalid value for site setting "${e}": ${p(r.error.issues)}`)}async function h(e){let n=typeof e.blockType==`string`?e.blockType:``;if(!n)throw Error(`${e.label}: block_type is required.`);if(!e.content||typeof e.content!=`object`||Array.isArray(e.content))throw Error(`${e.label}: content must be a JSON object.`);let r=e.content;if(t.isValidBlockType(n)){let i=t.validateCortexBlockContent(n,r,e.context);if(!i.isValid)throw Error(`${e.label}: content is invalid for block type "${n}": ${i.errors.join(`; `)}`);return}let i=(await e.definitions()).find(e=>e.slug===n);if(!i)throw Error(`${e.label}: "${n}" is neither a built-in block type nor a custom block slug. Use list_custom_blocks to see the custom block definitions.`);let a=t.validateCustomBlockInstanceContent(i,r);if(!a.isValid)throw Error(`${e.label}: content is invalid for custom block "${n}": ${a.errors.join(`; `)}`)}async function g(e,n,r){if(n===`site_settings`){for(let t of e.rows??[])typeof t.key==`string`&&m(t.key,t.value);if(e.operation===`update`&&e.values){let t=typeof e.values.key==`string`?e.values.key:null,n=(e.filters??[]).find(e=>e.column===`key`&&e.operator===`eq`&&typeof e.value==`string`),r=t??(n?String(n.value):null);r&&(`value`in e.values?m(r,e.values.value):d.has(r)&&m(r,void 0))}return}if(n!==`blocks`)return;let i=null,a=()=>i??=t.loadCustomBlockDefinitions(T(r));if(e.operation===`insert`||e.operation===`upsert`){for(let[t,n]of(e.rows??[]).entries())await h({blockType:n.block_type,content:n.content??{},context:r,definitions:a,label:`rows[${t}]`});return}if(e.operation===`update`&&e.values){let t=`content`in e.values,i=`block_type`in e.values;if(!t&&!i)return;if(i&&!t)throw Error(`Changing block_type requires the new content for that type in the same update.`);let o=new Set;if(i)o.add(String(e.values.block_type));else{let t=T(r).from(`blocks`).select(`id, block_type`).limit(101);t=U(n,t,e.filters??[]);let{data:i,error:a}=await t;if(a)throw Error(`Failed to read the blocks to update: ${E(a)}`);for(let e of Array.isArray(i)?i:[])o.add(String(e.block_type??``))}for(let t of o)await h({blockType:t,content:e.values.content,context:r,definitions:a,label:`values (block_type "${t}")`})}}var _={blocks:{columns:[`id`,`page_id`,`post_id`,`language_id`,`block_type`,`content`,`order`,`created_at`,`updated_at`],description:`CMS page/post content blocks.`,primaryKey:[`id`]},coupon_freemius_mappings:{columns:[`id`,`coupon_id`,`product_id`,`freemius_product_id`,`freemius_coupon_id`,`freemius_coupon_code`,`sync_status`,`sync_error`,`remote_payload`,`last_synced_at`,`created_at`,`updated_at`],description:`Freemius coupon sync mappings.`,primaryKey:[`id`]},coupon_products:{columns:[`coupon_id`,`product_id`,`created_at`],description:`Coupon product allow-list rows.`,primaryKey:[`coupon_id`,`product_id`]},coupon_redemptions:{columns:[`id`,`coupon_id`,`order_id`,`coupon_code`,`provider`,`discount_total`,`user_id`,`customer_email`,`metadata`,`redeemed_at`],description:`Coupon redemption records.`,primaryKey:[`id`]},coupons:{columns:[`id`,`code`,`name`,`internal_note`,`provider_scope`,`discount_type`,`discount_amount`,`is_active`,`starts_at`,`ends_at`,`redemption_limit`,`redemptions_count`,`freemius_sync_status`,`freemius_sync_error`,`created_at`,`updated_at`],description:`Commerce coupons.`,primaryKey:[`id`]},cortex_ai_db_mutation_audit:{columns:[`id`,`actor_user_id`,`tool_name`,`action_name`,`target_tables`,`operation_summary`,`payload_hash`,`payload`,`preview`,`status`,`error_message`,`created_at`],description:`Read-only Cortex AI database mutation audit trail.`,primaryKey:[`id`],readOnly:!0},currencies:{columns:[`id`,`code`,`symbol`,`exchange_rate`,`is_default`,`is_active`,`rounding_mode`,`rounding_increment`,`rounding_charm_amount`,`auto_update_exchange_rate`,`exchange_rate_updated_at`,`exchange_rate_source`,`auto_sync_product_prices`,`created_at`,`updated_at`],description:`Storefront currencies and FX settings.`,primaryKey:[`id`]},freemius_plans:{columns:[`id`,`product_id`,`name`,`title`,`created_at`,`updated_at`],description:`Freemius plan metadata.`,primaryKey:[`id`]},freemius_pricing:{columns:[`id`,`plan_id`,`api_monthly_price`,`api_annual_price`,`api_lifetime_price`,`override_monthly_price`,`override_annual_price`,`override_lifetime_price`,`license_quota`,`is_active`,`created_at`,`updated_at`],description:`Freemius pricing metadata.`,primaryKey:[`id`]},inventory_items:{columns:[`sku`,`quantity`,`created_at`,`updated_at`],description:`Source-of-truth inventory by SKU.`,primaryKey:[`sku`]},languages:{columns:[`id`,`code`,`name`,`is_default`,`is_active`,`created_at`,`updated_at`],description:`CMS languages/locales.`,primaryKey:[`id`]},logos:{columns:[`id`,`name`,`media_id`,`created_at`],description:`Brand logos.`,primaryKey:[`id`]},media:{columns:[`id`,`uploader_id`,`file_name`,`object_key`,`file_type`,`size_bytes`,`description`,`width`,`height`,`blur_data_url`,`variants`,`file_path`,`folder`,`created_at`,`updated_at`],description:`Media metadata for uploaded assets.`,primaryKey:[`id`]},navigation_items:{columns:[`id`,`language_id`,`menu_key`,`label`,`url`,`parent_id`,`order`,`page_id`,`translation_group_id`,`created_at`,`updated_at`],description:`CMS navigation tree items.`,primaryKey:[`id`]},order_items:{columns:[`id`,`order_id`,`product_id`,`variant_id`,`quantity`,`price_at_purchase`],description:`Commerce order line items.`,primaryKey:[`id`]},orders:{columns:`id.user_id.status.total.stripe_session_id.payment_intent_id.customer_details.provider.freemius_product_id.freemius_plan_id.freemius_license_id.freemius_subscription_id.freemius_trial_id.freemius_user_id.freemius_trial_ends_at.freemius_last_event_type.freemius_last_synced_at.currency.subtotal.shipping_total.tax_total.tax_details.exchange_rate_at_purchase.inventory_deducted_at.invoice_number.paid_at.created_at.coupon_id.coupon_code.discount_total.discount_details`.split(`.`),description:`Commerce orders.`,primaryKey:[`id`]},package_activations:{columns:[`id`,`license_key`,`instance_name`,`package_id`,`status`,`meta`,`last_validated_at`,`created_at`],description:`NextBlock package activations.`,primaryKey:[`id`]},page_revisions:{columns:[`id`,`page_id`,`author_id`,`version`,`revision_type`,`content`,`created_at`],description:`Page revision history.`,primaryKey:[`id`],readOnly:!0},pages:{columns:[`id`,`language_id`,`author_id`,`title`,`slug`,`status`,`meta_title`,`meta_description`,`feature_image_id`,`version`,`translation_group_id`,`created_at`,`updated_at`],description:`CMS pages.`,primaryKey:[`id`]},post_revisions:{columns:[`id`,`post_id`,`author_id`,`version`,`revision_type`,`content`,`created_at`],description:`Post revision history.`,primaryKey:[`id`],readOnly:!0},product_revisions:{columns:[`id`,`product_id`,`author_id`,`version`,`revision_type`,`content`,`created_at`],description:`Product revision history.`,primaryKey:[`id`],readOnly:!0},posts:{columns:[`id`,`language_id`,`author_id`,`title`,`slug`,`label`,`excerpt`,`subtitle`,`status`,`published_at`,`meta_title`,`meta_description`,`feature_image_id`,`version`,`translation_group_id`,`created_at`,`updated_at`],description:`CMS posts/articles.`,primaryKey:[`id`]},product_attribute_terms:{columns:[`id`,`attribute_id`,`value`,`slug`,`sort_order`,`value_translations`,`created_at`,`updated_at`],description:`Product attribute terms.`,primaryKey:[`id`]},product_attributes:{columns:[`id`,`name`,`slug`,`name_translations`,`created_at`,`updated_at`],description:`Product attribute definitions.`,primaryKey:[`id`]},product_media:{columns:[`product_id`,`media_id`,`sort_order`],description:`Product media join rows.`,primaryKey:[`product_id`,`media_id`]},product_variants:{columns:[`id`,`product_id`,`sku`,`price_adjustment`,`price`,`prices`,`sale_price`,`sale_prices`,`stock_quantity`,`upc`,`main_media_id`,`created_at`,`updated_at`],description:`Sellable product variants.`,primaryKey:[`id`]},products:{columns:`id.language_id.translation_group_id.sku.title.slug.product_type.payment_provider.price.prices.sale_price.sale_prices.stock.status.meta_title.meta_description.short_description.description_json.metadata.freemius_plan_id.freemius_product_id.trial_period_days.trial_requires_payment_method.upc.is_taxable.created_at.updated_at`.split(`.`),description:`Product catalog records.`,primaryKey:[`id`]},profiles:{columns:[`id`,`updated_at`,`full_name`,`avatar_url`,`website`,`phone`,`role`],description:`Read-only user profiles. Never mutate through Cortex AI.`,primaryKey:[`id`],readOnly:!0},shipping_zone_locations:{columns:[`id`,`zone_id`,`country_code`,`state_code`,`postal_code`,`created_at`],description:`Shipping zone location match rules.`,primaryKey:[`id`]},shipping_zone_methods:{columns:[`id`,`zone_id`,`method_type`,`cost_amount`,`cost_currency`,`min_order_amount`,`name`,`name_translations`,`currency_pricing_mode`,`cost_amounts`,`min_order_amounts`,`created_at`,`updated_at`],description:`Shipping methods/rates.`,primaryKey:[`id`]},shipping_zones:{columns:[`id`,`name`,`priority_order`,`created_at`,`updated_at`],description:`Shipping zones.`,primaryKey:[`id`]},site_settings:{columns:[`key`,`value`],description:`Global site settings. Cortex AI API-key setting row is protected.`,primaryKey:[`key`]},tax_rates:{columns:[`id`,`country_code`,`state_code`,`tax_name`,`tax_rate`,`created_at`,`updated_at`],description:`Manual tax rates.`,primaryKey:[`id`]},translations:{columns:[`key`,`translations`,`created_at`,`updated_at`],description:`Shared translation strings.`,primaryKey:[`key`]},user_addresses:{columns:[`id`,`user_id`,`address_type`,`is_default`,`recipient_name`,`company_name`,`line1`,`line2`,`city`,`state`,`postal_code`,`country_code`,`created_at`,`updated_at`],description:`Read-only user addresses. Never mutate through Cortex AI.`,primaryKey:[`id`],readOnly:!0},variant_attribute_mapping:{columns:[`variant_id`,`attribute_term_id`],description:`Variant to attribute-term join rows.`,primaryKey:[`variant_id`,`attribute_term_id`]}},v=r.z.enum([`eq`,`neq`,`gt`,`gte`,`lt`,`lte`,`ilike`,`in`,`is`]),y=r.z.strictObject({column:r.z.string().trim().min(1).max(120),operator:v.default(`eq`),value:r.z.unknown()}),b=r.z.strictObject({ascending:r.z.boolean().default(!0),column:r.z.string().trim().min(1).max(120)}),x=r.z.strictObject({includeReadOnly:r.z.boolean().default(!0),table:r.z.string().trim().min(1).max(120).optional()}),S=r.z.strictObject({columns:r.z.array(r.z.string().trim().min(1).max(120)).min(1).max(40).optional(),filters:r.z.array(y).max(12).default([]),limit:r.z.number().int().min(1).max(i).default(a),offset:r.z.number().int().min(0).max(1e4).default(0),orderBy:b.optional(),table:r.z.string().trim().min(1).max(120)}),C=r.z.strictObject({filters:r.z.array(y).max(12).optional(),operation:r.z.enum([`delete`,`insert`,`update`,`upsert`]),rows:r.z.array(r.z.record(r.z.string(),r.z.unknown())).min(1).max(s).optional(),summary:r.z.string().trim().min(1).max(500).optional(),table:r.z.string().trim().min(1).max(120),values:r.z.record(r.z.string(),r.z.unknown()).optional()}),w=r.z.strictObject({actions:r.z.array(C).min(1).max(8),summary:r.z.string().trim().min(1).max(500).optional()});function T(e){if(!e?.supabase)throw Error(`Cortex AI database tools require a Supabase client.`);return e.supabase}function E(e){return e?typeof e==`object`&&`message`in e?String(e.message||`Unknown database error.`):String(e):`Unknown database error.`}function D(e){return typeof e!=`object`||!e?JSON.stringify(e):Array.isArray(e)?`[${e.map(e=>D(e)).join(`,`)}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${D(e[t])}`).join(`,`)}}`}function O(e){let t=2166136261,n=D(e);for(let e=0;e<n.length;e++)t^=n.charCodeAt(e),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,`0`)}function k(e){return e.replace(/\s+/g,` `).trim().toUpperCase()}function A(e,t,n){return`${k(`CONFIRM ${e} ${t}`)} #${O(n)}`}function j(e){return{confirmationPhrase:A(e.action,e.subject,e.payload),mutationExecuted:!1,preview:e.preview,requiresConfirmation:!0,success:!0}}function M(e){if(e.context?.skipConfirmation)return null;let t=j(e),n=k(e.context?.latestUserMessage||``),r=k(t.confirmationPhrase);return n&&!n.includes(r)?{...t,message:`The database target changed or the confirmation did not match. Please review and confirm again.`}:n.includes(r)?null:t}function N(e){return Object.prototype.hasOwnProperty.call(_,e)}function P(e){let t=e.trim();if(t.includes(`.`)||t.toLowerCase().startsWith(`auth`))throw Error(`Cortex AI cannot access auth schema tables.`);if(!N(t))throw Error(`Table "${t}" is not available to Cortex AI database tools.`);return t}function F(e){return _[e]}function I(e){return/(?:password|token|secret|api[_-]?key|private[_-]?key|credential)/i.test(e)}function L(e,t){if(!F(e).columns.includes(t))throw Error(`Column "${t}" is not available on table "${e}".`);if(I(t))throw Error(`Column "${t}" is protected and cannot be used through Cortex AI.`)}function R(e,t=`payload`){if(Array.isArray(e)){e.forEach((e,n)=>R(e,`${t}[${n}]`));return}if(e&&typeof e==`object`)for(let[n,r]of Object.entries(e)){if(I(n))throw Error(`Protected field "${t}.${n}" cannot be written through Cortex AI.`);R(r,`${t}.${n}`)}}function z(e){if(Array.isArray(e))return e.map(e=>z(e));if(!e||typeof e!=`object`)return e;let t={};for(let[n,r]of Object.entries(e))t[n]=I(n)?c:z(r);return t}function B(e){return e.map(e=>{if(e&&typeof e==`object`&&!Array.isArray(e)){let t=e.key;if(typeof t==`string`&&u.has(t))return{key:t,value:c}}return z(e)})}function V(e,t){if(e!==`site_settings`)return;let n=JSON.stringify(t).toLowerCase();for(let e of u)if(n.includes(e))throw Error(`The "${e}" site setting is protected.`)}function H(e,t){switch(t.operator){case`eq`:return e.eq(t.column,t.value);case`neq`:return e.neq(t.column,t.value);case`gt`:return e.gt(t.column,t.value);case`gte`:return e.gte(t.column,t.value);case`lt`:return e.lt(t.column,t.value);case`lte`:return e.lte(t.column,t.value);case`ilike`:if(typeof t.value!=`string`)throw Error(`Filter "${t.column}" with ilike requires a string value.`);return e.ilike(t.column,t.value);case`in`:if(!Array.isArray(t.value))throw Error(`Filter "${t.column}" with in requires an array value.`);return e.in(t.column,t.value);case`is`:return e.is(t.column,t.value)}}function U(e,t,n){let r=t;for(let t of n)L(e,t.column),r=H(r,t);return e===`site_settings`&&(r=r.neq(`key`,l)),r}function W(e,t){let n=F(e),r=t?.length?t:n.columns.filter(e=>!I(e));return r.forEach(t=>L(e,t)),r.join(`,`)}function G(e,t){return t.map(t=>`${t}=${String(e[t]??``)}`).join(`|`)}async function K(e,t,n){let r=F(t),i=e.from(t).select(r.primaryKey.join(`,`)).limit(101);i=U(t,i,n);let{data:a,error:s}=await i;if(s)throw Error(`Failed to preview database mutation: ${E(s)}`);let c=Array.isArray(a)?a:[];if(c.length>o)throw Error(`Refusing to mutate more than ${o} rows.`);return c}function q(e){let t=P(e.table),n=F(t),r=e.operation;if(n.readOnly)throw Error(`Table "${t}" is read-only for Cortex AI.`);if(V(t,e),(r===`update`||r===`delete`)&&(!e.filters||e.filters.length===0))throw Error(`${r} operations require at least one filter.`);if((r===`insert`||r===`upsert`)&&(!e.rows||e.rows.length===0))throw Error(`${r} operations require rows.`);if(r===`update`&&(!e.values||Object.keys(e.values).length===0))throw Error(`update operations require values.`);if(e.rows&&e.rows.length>s)throw Error(`Refusing to mutate more than ${s} input rows.`);let i=e.rows??(e.values?[e.values]:[]);for(let e of i){R(e);for(let n of Object.keys(e))L(t,n)}for(let n of e.filters??[])L(t,n.column);return t}function J(e){let{input:t,table:n,targetRows:r}=e,i=F(n),a=r.map(e=>G(e,i.primaryKey)).sort(),o=t.rows?.length??0,s=t.operation===`insert`||t.operation===`upsert`?o:r.length,c=t.summary||`${t.operation} ${s} ${s===1?`row`:`rows`} in ${n}.`;return{affectedCount:s,operation:t.operation,sampleTargetIds:a.slice(0,10),summary:c,table:n,targetIds:a}}async function Y(e){let t=e.context?.actorUserId;if(!t)return{auditError:`Missing actor user id.`,auditLogged:!1};try{let{error:n}=await T(e.context).from(`cortex_ai_db_mutation_audit`).insert({action_name:e.toolName,actor_user_id:t,error_message:e.errorMessage??null,operation_summary:e.summary,payload:z(e.input),payload_hash:O(e.input),preview:z(e.preview),status:e.status,target_tables:e.targetTables,tool_name:e.toolName});return n?{auditError:E(n),auditLogged:!1}:{auditLogged:!0}}catch(e){return{auditError:E(e),auditLogged:!1}}}async function ee(e,t,n){let r=F(t).primaryKey.join(`,`),i;e.operation===`insert`?i=n.from(t).insert(e.rows).select(r):e.operation===`upsert`?i=n.from(t).upsert(e.rows).select(r):e.operation===`update`?(i=n.from(t).update(e.values),i=U(t,i,e.filters??[]),i=i.select(r)):(i=n.from(t).delete(),i=U(t,i,e.filters??[]),i=i.select(r));let{data:a,error:o}=await i;if(o)throw Error(E(o));return Array.isArray(a)?a:[]}async function X(e){let t=x.parse(e),n=(t.table?[P(t.table)]:Object.keys(_)).filter(e=>t.includeReadOnly||!F(e).readOnly).map(e=>{let t=F(e);return{columns:t.columns.filter(e=>!I(e)),description:t.description,primaryKey:t.primaryKey,readOnly:!!t.readOnly,table:e}});return{safetyNotes:[`Use read_database_records for reads and execute_database_mutation/action_plan for writes.`,`No auth schema access, no arbitrary SQL, no password/secret/API-key fields.`,`profiles, user_addresses, and cortex_ai_db_mutation_audit are read-only.`,`Protected site_settings keys (never readable or writable): ${[...u].sort().join(`, `)}.`,`Write-protected site_settings keys (readable, never writable here): ${[...d].sort().join(`, `)}.`,`blocks.content is validated against its block_type (built-in schema or custom block definition) before any insert, upsert, or update; prefer the typed content tools, which also normalize and record revisions.`],success:!0,tables:n}}async function Z(e,t){let n=S.parse(e),r=P(n.table),i=W(r,n.columns),a=T(t).from(r).select(i).range(n.offset,n.offset+n.limit-1);a=U(r,a,n.filters),n.orderBy&&(L(r,n.orderBy.column),a=a.order(n.orderBy.column,{ascending:n.orderBy.ascending}));let{data:o,error:s}=await a;if(s)throw Error(`Failed to read database records: ${E(s)}`);let c=Array.isArray(o)?o:[];return{columns:i.split(`,`),limit:n.limit,offset:n.offset,rows:B(c),success:!0,table:r}}async function Q(e,t){let n=C.parse(e),r=q(n),i=T(t);await g(n,r,t);let a=J({input:n,table:r,targetRows:n.operation===`update`||n.operation===`delete`?await K(i,r,n.filters??[]):[]}),o={input:n,targetIds:a.targetIds},s=M({action:`DATABASE ${n.operation}`,context:t,payload:o,preview:a,subject:r});if(s)return s;try{let e=await ee(n,r,i),s=t?.skipAudit?{auditLogged:!1}:await Y({context:t,input:o,preview:a,status:`success`,summary:String(a.summary),targetTables:[r],toolName:`execute_database_mutation`});return{affectedCount:e.length||a.affectedCount,auditLogged:s.auditLogged,...s.auditError?{auditError:s.auditError}:{},mutationExecuted:!0,operation:n.operation,sampleTargetIds:e.map(e=>G(e,F(r).primaryKey)).slice(0,10),success:!0,summary:a.summary,table:r}}catch(e){let i=E(e),s=t?.skipAudit?{auditLogged:!1}:await Y({context:t,errorMessage:i,input:o,preview:a,status:`failure`,summary:String(a.summary),targetTables:[r],toolName:`execute_database_mutation`});return{auditLogged:s.auditLogged,...s.auditError?{auditError:s.auditError}:{},message:`Database mutation failed: ${i}`,mutationExecuted:!1,operation:n.operation,success:!1,table:r}}}async function $(e,t){let n=w.parse(e),r=[];for(let e of n.actions){let n=q(e);await g(e,n,t);let i=e.operation===`update`||e.operation===`delete`?await K(T(t),n,e.filters??[]):[];r.push({input:e,preview:J({input:e,table:n,targetRows:i}),table:n})}let i=r.map(({preview:e})=>String(e.summary)),a={actionCount:r.length,actionSummaries:i,summary:n.summary||`Run ${r.length} confirmed database actions.`,tables:Array.from(new Set(r.map(e=>e.table)))},o=r.map(({input:e,preview:t,table:n})=>({input:e,table:n,targetIds:t.targetIds})),s=M({action:`DATABASE ACTION PLAN`,context:t,payload:o,preview:a,subject:`${r.length} actions`});if(s)return s;let c=[],l=!1;for(let[e,n]of r.entries()){let i=await Q(n.input,{...t,skipAudit:!0,skipConfirmation:!0});if(c.push(i),i&&typeof i==`object`&&i.mutationExecuted===!0&&(l=!0),!i||typeof i!=`object`||i.success===!1){let n=await Y({context:t,errorMessage:i&&typeof i==`object`&&typeof i.message==`string`?i.message:`Database action ${e+1} failed.`,input:o,preview:a,status:`failure`,summary:String(a.summary),targetTables:a.tables,toolName:`execute_database_action_plan`});return{actionCount:r.length,auditLogged:n.auditLogged,...n.auditError?{auditError:n.auditError}:{},failedActionIndex:e,message:i&&typeof i==`object`&&typeof i.message==`string`?i.message:`Database action ${e+1} failed.`,mutationExecuted:l,results:c,success:!1}}}let u=await Y({context:t,input:o,preview:a,status:`success`,summary:String(a.summary),targetTables:a.tables,toolName:`execute_database_action_plan`});return{actionCount:r.length,auditLogged:u.auditLogged,...u.auditError?{auditError:u.auditError}:{},mutationExecuted:l,results:c,success:!0,summary:n.summary??null}}function te(e){return{describe_database_schema:(0,n.tool)({description:`Describe the public database tables Cortex AI can read or mutate, including read-only tables, primary keys, and writable columns. Use before broad database tasks when unsure about schema.`,execute:e=>X(e),inputSchema:x,strict:!0}),execute_database_action_plan:(0,n.tool)({description:`Execute up to 8 typed database mutations as one confirmed plan. Mutating: first returns one combined confirmation preview and Confirm button; after confirmation, runs each action in order and stops on the first failure.`,execute:t=>$(t,e),inputSchema:w,strict:!0}),execute_database_mutation:(0,n.tool)({description:`Run one typed insert, update, upsert, or delete against an allowed public database table. Mutating: always previews affected rows and requires exact user confirmation before execution. Does not support arbitrary SQL.`,execute:t=>Q(t,e),inputSchema:C,strict:!0}),read_database_records:(0,n.tool)({description:`Read records from an allowed public database table with validated columns, filters, ordering, limit, and offset. Read-only and redacts protected secret-like values.`,execute:t=>Z(t,e),inputSchema:S,strict:!0})}}exports.createCortexDatabaseAgentTools=te,exports.describeDatabaseSchemaInputSchema=x,exports.executeDatabaseActionPlan=$,exports.executeDatabaseActionPlanInputSchema=w,exports.executeDatabaseMutation=Q,exports.executeDatabaseMutationInputSchema=C,exports.executeDescribeDatabaseSchema=X,exports.executeReadDatabaseRecords=Z,exports.readDatabaseRecordsInputSchema=S;
|
|
1
|
+
require("./zod-config.cjs");const e=require("./site-brief.cjs"),t=require("./block-content-schemas.cjs");let n=require("ai"),r=require("zod");var i=100,a=20,o=100,s=20,c=`[REDACTED]`,l=`cortex_ai_openrouter_api_key`,u=new Set([l,`bot_protection_secret`,`cortex_ai_mcp_settings`,`cortex_ai_pexels_api_key`,`cortex_ai_unsplash_access_key`,`email_secret`,`payment_secret`]),d=/(?:_secret(?:_key)?|_api_key|_access_key|_private_key|_token|_password|_credentials?)$/i;function f(e){return u.has(e)||d.test(e)}var p=new Set([`cortex_ai_build_session`,`is_admin_created`,`migration_baseline_generation`,`security_settings`]),ee={active_logo_id:r.z.string().uuid().nullable(),cortex_ai_site_brief:e.cortexSiteBriefSchema,footer_copyright:r.z.record(r.z.string().trim().min(2).max(12),r.z.string().max(500)),footer_show_attribution:r.z.boolean(),global_css:r.z.string().max(2e5),onboarding_state:r.z.record(r.z.string(),r.z.unknown()),site_description:r.z.string().max(1e3),site_keywords:r.z.string().max(1e3),site_title:r.z.string().max(200)};function m(e){return e.map(e=>{let t=e.path.map(String).join(`.`);return t?`${t}: ${e.message}`:e.message}).join(`; `)}function h(e,t){if(p.has(e))throw Error(`The "${e}" site setting cannot be written through Cortex AI database tools.`);let n=ee[e];if(!n)return;let r=n.safeParse(t);if(!r.success)throw Error(`Invalid value for site setting "${e}": ${m(r.error.issues)}`)}async function g(e){let n=typeof e.blockType==`string`?e.blockType:``;if(!n)throw Error(`${e.label}: block_type is required.`);if(!e.content||typeof e.content!=`object`||Array.isArray(e.content))throw Error(`${e.label}: content must be a JSON object.`);let r=e.content;if(t.isValidBlockType(n)){let i=t.validateCortexBlockContent(n,r,e.context);if(!i.isValid)throw Error(`${e.label}: content is invalid for block type "${n}": ${i.errors.join(`; `)}`);return}let i=(await e.definitions()).find(e=>e.slug===n);if(!i)throw Error(`${e.label}: "${n}" is neither a built-in block type nor a custom block slug. Use list_custom_blocks to see the custom block definitions.`);let a=t.validateCustomBlockInstanceContent(i,r);if(!a.isValid)throw Error(`${e.label}: content is invalid for custom block "${n}": ${a.errors.join(`; `)}`)}async function _(e,n,r){if(n===`site_settings`){for(let t of e.rows??[])typeof t.key==`string`&&h(t.key,t.value);if(e.operation===`update`&&e.values){let t=typeof e.values.key==`string`?e.values.key:null,n=(e.filters??[]).find(e=>e.column===`key`&&e.operator===`eq`&&typeof e.value==`string`),r=t??(n?String(n.value):null);r&&(`value`in e.values?h(r,e.values.value):p.has(r)&&h(r,void 0))}return}if(n!==`blocks`)return;let i=null,a=()=>i??=t.loadCustomBlockDefinitions(E(r));if(e.operation===`insert`||e.operation===`upsert`){for(let[t,n]of(e.rows??[]).entries())await g({blockType:n.block_type,content:n.content??{},context:r,definitions:a,label:`rows[${t}]`});return}if(e.operation===`update`&&e.values){let t=`content`in e.values,i=`block_type`in e.values;if(!t&&!i)return;if(i&&!t)throw Error(`Changing block_type requires the new content for that type in the same update.`);let o=new Set;if(i)o.add(String(e.values.block_type));else{let t=E(r).from(`blocks`).select(`id, block_type`).limit(101);t=U(n,t,e.filters??[]);let{data:i,error:a}=await t;if(a)throw Error(`Failed to read the blocks to update: ${D(a)}`);for(let e of Array.isArray(i)?i:[])o.add(String(e.block_type??``))}for(let t of o)await g({blockType:t,content:e.values.content,context:r,definitions:a,label:`values (block_type "${t}")`})}}var v={blocks:{columns:[`id`,`page_id`,`post_id`,`language_id`,`block_type`,`content`,`order`,`created_at`,`updated_at`],description:`CMS page/post content blocks.`,primaryKey:[`id`]},coupon_freemius_mappings:{columns:[`id`,`coupon_id`,`product_id`,`freemius_product_id`,`freemius_coupon_id`,`freemius_coupon_code`,`sync_status`,`sync_error`,`remote_payload`,`last_synced_at`,`created_at`,`updated_at`],description:`Freemius coupon sync mappings.`,primaryKey:[`id`]},coupon_products:{columns:[`coupon_id`,`product_id`,`created_at`],description:`Coupon product allow-list rows.`,primaryKey:[`coupon_id`,`product_id`]},coupon_redemptions:{columns:[`id`,`coupon_id`,`order_id`,`coupon_code`,`provider`,`discount_total`,`user_id`,`customer_email`,`metadata`,`redeemed_at`],description:`Coupon redemption records.`,primaryKey:[`id`]},coupons:{columns:[`id`,`code`,`name`,`internal_note`,`provider_scope`,`discount_type`,`discount_amount`,`is_active`,`starts_at`,`ends_at`,`redemption_limit`,`redemptions_count`,`freemius_sync_status`,`freemius_sync_error`,`created_at`,`updated_at`],description:`Commerce coupons.`,primaryKey:[`id`]},cortex_ai_db_mutation_audit:{columns:[`id`,`actor_user_id`,`tool_name`,`action_name`,`target_tables`,`operation_summary`,`payload_hash`,`payload`,`preview`,`status`,`error_message`,`created_at`],description:`Read-only Cortex AI database mutation audit trail.`,primaryKey:[`id`],readOnly:!0},currencies:{columns:[`id`,`code`,`symbol`,`exchange_rate`,`is_default`,`is_active`,`rounding_mode`,`rounding_increment`,`rounding_charm_amount`,`auto_update_exchange_rate`,`exchange_rate_updated_at`,`exchange_rate_source`,`auto_sync_product_prices`,`created_at`,`updated_at`],description:`Storefront currencies and FX settings.`,primaryKey:[`id`]},freemius_plans:{columns:[`id`,`product_id`,`name`,`title`,`created_at`,`updated_at`],description:`Freemius plan metadata.`,primaryKey:[`id`]},freemius_pricing:{columns:[`id`,`plan_id`,`api_monthly_price`,`api_annual_price`,`api_lifetime_price`,`override_monthly_price`,`override_annual_price`,`override_lifetime_price`,`license_quota`,`is_active`,`created_at`,`updated_at`],description:`Freemius pricing metadata.`,primaryKey:[`id`]},inventory_items:{columns:[`sku`,`quantity`,`created_at`,`updated_at`],description:`Source-of-truth inventory by SKU.`,primaryKey:[`sku`]},languages:{columns:[`id`,`code`,`name`,`is_default`,`is_active`,`created_at`,`updated_at`],description:`CMS languages/locales.`,primaryKey:[`id`]},logos:{columns:[`id`,`name`,`media_id`,`created_at`],description:`Brand logos.`,primaryKey:[`id`]},media:{columns:[`id`,`uploader_id`,`file_name`,`object_key`,`file_type`,`size_bytes`,`description`,`width`,`height`,`blur_data_url`,`variants`,`file_path`,`folder`,`created_at`,`updated_at`],description:`Media metadata for uploaded assets.`,primaryKey:[`id`]},navigation_items:{columns:[`id`,`language_id`,`menu_key`,`label`,`url`,`parent_id`,`order`,`page_id`,`translation_group_id`,`created_at`,`updated_at`],description:`CMS navigation tree items.`,primaryKey:[`id`]},order_items:{columns:[`id`,`order_id`,`product_id`,`variant_id`,`quantity`,`price_at_purchase`],description:`Commerce order line items.`,primaryKey:[`id`]},orders:{columns:`id.user_id.status.total.stripe_session_id.payment_intent_id.customer_details.provider.freemius_product_id.freemius_plan_id.freemius_license_id.freemius_subscription_id.freemius_trial_id.freemius_user_id.freemius_trial_ends_at.freemius_last_event_type.freemius_last_synced_at.currency.subtotal.shipping_total.tax_total.tax_details.exchange_rate_at_purchase.inventory_deducted_at.invoice_number.paid_at.created_at.coupon_id.coupon_code.discount_total.discount_details`.split(`.`),description:`Commerce orders.`,primaryKey:[`id`]},package_activations:{columns:[`id`,`license_key`,`instance_name`,`package_id`,`status`,`meta`,`last_validated_at`,`created_at`],description:`NextBlock package activations.`,primaryKey:[`id`]},page_revisions:{columns:[`id`,`page_id`,`author_id`,`version`,`revision_type`,`content`,`created_at`],description:`Page revision history.`,primaryKey:[`id`],readOnly:!0},pages:{columns:[`id`,`language_id`,`author_id`,`title`,`slug`,`status`,`meta_title`,`meta_description`,`feature_image_id`,`version`,`translation_group_id`,`created_at`,`updated_at`],description:`CMS pages.`,primaryKey:[`id`]},post_revisions:{columns:[`id`,`post_id`,`author_id`,`version`,`revision_type`,`content`,`created_at`],description:`Post revision history.`,primaryKey:[`id`],readOnly:!0},product_revisions:{columns:[`id`,`product_id`,`author_id`,`version`,`revision_type`,`content`,`created_at`],description:`Product revision history.`,primaryKey:[`id`],readOnly:!0},posts:{columns:[`id`,`language_id`,`author_id`,`title`,`slug`,`label`,`excerpt`,`subtitle`,`status`,`published_at`,`meta_title`,`meta_description`,`feature_image_id`,`version`,`translation_group_id`,`created_at`,`updated_at`],description:`CMS posts/articles.`,primaryKey:[`id`]},product_attribute_terms:{columns:[`id`,`attribute_id`,`value`,`slug`,`sort_order`,`value_translations`,`created_at`,`updated_at`],description:`Product attribute terms.`,primaryKey:[`id`]},product_attributes:{columns:[`id`,`name`,`slug`,`name_translations`,`created_at`,`updated_at`],description:`Product attribute definitions.`,primaryKey:[`id`]},product_media:{columns:[`product_id`,`media_id`,`sort_order`],description:`Product media join rows.`,primaryKey:[`product_id`,`media_id`]},product_variants:{columns:[`id`,`product_id`,`sku`,`price_adjustment`,`price`,`prices`,`sale_price`,`sale_prices`,`stock_quantity`,`upc`,`main_media_id`,`created_at`,`updated_at`],description:`Sellable product variants.`,primaryKey:[`id`]},products:{columns:`id.language_id.translation_group_id.sku.title.slug.product_type.payment_provider.price.prices.sale_price.sale_prices.stock.status.meta_title.meta_description.short_description.description_json.metadata.freemius_plan_id.freemius_product_id.trial_period_days.trial_requires_payment_method.upc.is_taxable.created_at.updated_at`.split(`.`),description:`Product catalog records.`,primaryKey:[`id`]},profiles:{columns:[`id`,`updated_at`,`full_name`,`avatar_url`,`website`,`phone`,`role`],description:`Read-only user profiles. Never mutate through Cortex AI.`,primaryKey:[`id`],readOnly:!0},shipping_zone_locations:{columns:[`id`,`zone_id`,`country_code`,`state_code`,`postal_code`,`created_at`],description:`Shipping zone location match rules.`,primaryKey:[`id`]},shipping_zone_methods:{columns:[`id`,`zone_id`,`method_type`,`cost_amount`,`cost_currency`,`min_order_amount`,`name`,`name_translations`,`currency_pricing_mode`,`cost_amounts`,`min_order_amounts`,`created_at`,`updated_at`],description:`Shipping methods/rates.`,primaryKey:[`id`]},shipping_zones:{columns:[`id`,`name`,`priority_order`,`created_at`,`updated_at`],description:`Shipping zones.`,primaryKey:[`id`]},site_settings:{columns:[`key`,`value`],description:`Global site settings. Credential rows are redacted on read and refused on write (see safetyNotes).`,primaryKey:[`key`]},tax_rates:{columns:[`id`,`country_code`,`state_code`,`tax_name`,`tax_rate`,`created_at`,`updated_at`],description:`Manual tax rates.`,primaryKey:[`id`]},translations:{columns:[`key`,`translations`,`created_at`,`updated_at`],description:`Shared translation strings.`,primaryKey:[`key`]},user_addresses:{columns:[`id`,`user_id`,`address_type`,`is_default`,`recipient_name`,`company_name`,`line1`,`line2`,`city`,`state`,`postal_code`,`country_code`,`created_at`,`updated_at`],description:`Read-only user addresses. Never mutate through Cortex AI.`,primaryKey:[`id`],readOnly:!0},variant_attribute_mapping:{columns:[`variant_id`,`attribute_term_id`],description:`Variant to attribute-term join rows.`,primaryKey:[`variant_id`,`attribute_term_id`]}},y={site_settings:{column:`key`,isProtected:f}},b=r.z.enum([`eq`,`neq`,`gt`,`gte`,`lt`,`lte`,`ilike`,`in`,`is`]),x=r.z.strictObject({column:r.z.string().trim().min(1).max(120),operator:b.default(`eq`),value:r.z.unknown()}),te=r.z.strictObject({ascending:r.z.boolean().default(!0),column:r.z.string().trim().min(1).max(120)}),S=r.z.strictObject({includeReadOnly:r.z.boolean().default(!0),table:r.z.string().trim().min(1).max(120).optional()}),C=r.z.strictObject({columns:r.z.array(r.z.string().trim().min(1).max(120)).min(1).max(40).optional(),filters:r.z.array(x).max(12).default([]),limit:r.z.number().int().min(1).max(i).default(a),offset:r.z.number().int().min(0).max(1e4).default(0),orderBy:te.optional(),table:r.z.string().trim().min(1).max(120)}),w=r.z.strictObject({filters:r.z.array(x).max(12).optional(),operation:r.z.enum([`delete`,`insert`,`update`,`upsert`]),rows:r.z.array(r.z.record(r.z.string(),r.z.unknown())).min(1).max(s).optional(),summary:r.z.string().trim().min(1).max(500).optional(),table:r.z.string().trim().min(1).max(120),values:r.z.record(r.z.string(),r.z.unknown()).optional()}),T=r.z.strictObject({actions:r.z.array(w).min(1).max(8),summary:r.z.string().trim().min(1).max(500).optional()});function E(e){if(!e?.supabase)throw Error(`Cortex AI database tools require a Supabase client.`);return e.supabase}function D(e){return e?typeof e==`object`&&`message`in e?String(e.message||`Unknown database error.`):String(e):`Unknown database error.`}function O(e){return typeof e!=`object`||!e?JSON.stringify(e):Array.isArray(e)?`[${e.map(e=>O(e)).join(`,`)}]`:`{${Object.keys(e).sort().map(t=>`${JSON.stringify(t)}:${O(e[t])}`).join(`,`)}}`}function k(e){let t=2166136261,n=O(e);for(let e=0;e<n.length;e++)t^=n.charCodeAt(e),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,`0`)}function A(e){return e.replace(/\s+/g,` `).trim().toUpperCase()}function j(e,t,n){return`${A(`CONFIRM ${e} ${t}`)} #${k(n)}`}function ne(e){return{confirmationPhrase:j(e.action,e.subject,e.payload),mutationExecuted:!1,preview:e.preview,requiresConfirmation:!0,success:!0}}function M(e){if(e.context?.skipConfirmation)return null;let t=ne(e),n=A(e.context?.latestUserMessage||``),r=A(t.confirmationPhrase);return n&&!n.includes(r)?{...t,message:`The database target changed or the confirmation did not match. Please review and confirm again.`}:n.includes(r)?null:t}function N(e){return Object.prototype.hasOwnProperty.call(v,e)}function P(e){let t=e.trim();if(t.includes(`.`)||t.toLowerCase().startsWith(`auth`))throw Error(`Cortex AI cannot access auth schema tables.`);if(!N(t))throw Error(`Table "${t}" is not available to Cortex AI database tools.`);return t}function F(e){return v[e]}function I(e){return/(?:password|token|secret|api[_-]?key|private[_-]?key|credential)/i.test(e)}function L(e,t){if(!F(e).columns.includes(t))throw Error(`Column "${t}" is not available on table "${e}".`);if(I(t))throw Error(`Column "${t}" is protected and cannot be used through Cortex AI.`)}function R(e,t=`payload`){if(Array.isArray(e)){e.forEach((e,n)=>R(e,`${t}[${n}]`));return}if(e&&typeof e==`object`)for(let[n,r]of Object.entries(e)){if(I(n))throw Error(`Protected field "${t}.${n}" cannot be written through Cortex AI.`);R(r,`${t}.${n}`)}}function z(e){if(Array.isArray(e))return e.map(e=>z(e));if(!e||typeof e!=`object`)return e;let t={};for(let[n,r]of Object.entries(e))t[n]=I(n)?c:z(r);return t}function re(e,t){return Object.fromEntries(t.filter(t=>t in e).map(t=>[t,e[t]]))}function ie(e,t,n){let r=y[e];return t.map(e=>{if(!e||typeof e!=`object`||Array.isArray(e))return z(e);let t=e;if(!r)return z(t);let i=t[r.column];return re(typeof i==`string`&&r.isProtected(i)?Object.fromEntries(Object.keys(t).map(e=>[e,e===r.column?i:c])):z(t),n)})}function ae(e){let t=[],n=e=>{typeof e==`string`&&t.push(e)};for(let t of e.rows??[])n(t.key);n(e.values?.key);for(let t of e.filters??[])t.column===`key`&&(Array.isArray(t.value)?t.value.forEach(n):n(t.value));return t}function B(e,t){if(e!==`site_settings`)return;let n=JSON.stringify(t).toLowerCase();for(let e of u)if(n.includes(e))throw Error(`The "${e}" site setting is protected.`);for(let e of ae(t))if(f(e))throw Error(`The "${e}" site setting is protected.`)}function V(e,t){let n=y[e];if(n)for(let e of t){let t=e[n.column];if(typeof t==`string`&&n.isProtected(t))throw Error(`The "${t}" site setting is protected.`)}}function H(e,t){switch(t.operator){case`eq`:return e.eq(t.column,t.value);case`neq`:return e.neq(t.column,t.value);case`gt`:return e.gt(t.column,t.value);case`gte`:return e.gte(t.column,t.value);case`lt`:return e.lt(t.column,t.value);case`lte`:return e.lte(t.column,t.value);case`ilike`:if(typeof t.value!=`string`)throw Error(`Filter "${t.column}" with ilike requires a string value.`);return e.ilike(t.column,t.value);case`in`:if(!Array.isArray(t.value))throw Error(`Filter "${t.column}" with in requires an array value.`);return e.in(t.column,t.value);case`is`:return e.is(t.column,t.value)}}function U(e,t,n){let r=t;for(let t of n)L(e,t.column),r=H(r,t);return e===`site_settings`&&(r=r.neq(`key`,l)),r}function W(e,t){let n=F(e),r=t?.length?t:n.columns.filter(e=>!I(e));return r.forEach(t=>L(e,t)),r}function G(e,t){return t.map(t=>`${t}=${String(e[t]??``)}`).join(`|`)}async function K(e,t,n){let r=F(t),i=e.from(t).select(r.primaryKey.join(`,`)).limit(101);i=U(t,i,n);let{data:a,error:s}=await i;if(s)throw Error(`Failed to preview database mutation: ${D(s)}`);let c=Array.isArray(a)?a:[];if(c.length>o)throw Error(`Refusing to mutate more than ${o} rows.`);return V(t,c),c}function q(e){let t=P(e.table),n=F(t),r=e.operation;if(n.readOnly)throw Error(`Table "${t}" is read-only for Cortex AI.`);if(B(t,e),(r===`update`||r===`delete`)&&(!e.filters||e.filters.length===0))throw Error(`${r} operations require at least one filter.`);if((r===`insert`||r===`upsert`)&&(!e.rows||e.rows.length===0))throw Error(`${r} operations require rows.`);if(r===`update`&&(!e.values||Object.keys(e.values).length===0))throw Error(`update operations require values.`);if(e.rows&&e.rows.length>s)throw Error(`Refusing to mutate more than ${s} input rows.`);let i=e.rows??(e.values?[e.values]:[]);for(let e of i){R(e);for(let n of Object.keys(e))L(t,n)}for(let n of e.filters??[])L(t,n.column);return t}function J(e){let{input:t,table:n,targetRows:r}=e,i=F(n),a=r.map(e=>G(e,i.primaryKey)).sort(),o=t.rows?.length??0,s=t.operation===`insert`||t.operation===`upsert`?o:r.length,c=t.summary||`${t.operation} ${s} ${s===1?`row`:`rows`} in ${n}.`;return{affectedCount:s,operation:t.operation,sampleTargetIds:a.slice(0,10),summary:c,table:n,targetIds:a}}async function Y(e){let t=e.context?.actorUserId;if(!t)return{auditError:`Missing actor user id.`,auditLogged:!1};try{let{error:n}=await E(e.context).from(`cortex_ai_db_mutation_audit`).insert({action_name:e.toolName,actor_user_id:t,error_message:e.errorMessage??null,operation_summary:e.summary,payload:z(e.input),payload_hash:k(e.input),preview:z(e.preview),status:e.status,target_tables:e.targetTables,tool_name:e.toolName});return n?{auditError:D(n),auditLogged:!1}:{auditLogged:!0}}catch(e){return{auditError:D(e),auditLogged:!1}}}async function oe(e,t,n){let r=F(t).primaryKey.join(`,`),i;e.operation===`insert`?i=n.from(t).insert(e.rows).select(r):e.operation===`upsert`?i=n.from(t).upsert(e.rows).select(r):e.operation===`update`?(i=n.from(t).update(e.values),i=U(t,i,e.filters??[]),i=i.select(r)):(i=n.from(t).delete(),i=U(t,i,e.filters??[]),i=i.select(r));let{data:a,error:o}=await i;if(o)throw Error(D(o));return Array.isArray(a)?a:[]}async function X(e){let t=S.parse(e),n=(t.table?[P(t.table)]:Object.keys(v)).filter(e=>t.includeReadOnly||!F(e).readOnly).map(e=>{let t=F(e);return{columns:t.columns.filter(e=>!I(e)),description:t.description,primaryKey:t.primaryKey,readOnly:!!t.readOnly,table:e}});return{safetyNotes:[`Use read_database_records for reads and execute_database_mutation/action_plan for writes.`,`No auth schema access, no arbitrary SQL, no password/secret/API-key fields.`,`profiles, user_addresses, and cortex_ai_db_mutation_audit are read-only.`,`Protected site_settings keys (never readable or writable): ${[...u].sort().join(`, `)}, plus any key ending in _secret, _api_key, _access_key, _private_key, _token, _password or _credentials.`,`Write-protected site_settings keys (readable, never writable here): ${[...p].sort().join(`, `)}.`,`blocks.content is validated against its block_type (built-in schema or custom block definition) before any insert, upsert, or update; prefer the typed content tools, which also normalize and record revisions.`],success:!0,tables:n}}async function Z(e,t){let n=C.parse(e),r=P(n.table),i=W(r,n.columns),a=y[r],o=a&&!i.includes(a.column)?[...i,a.column]:i,s=E(t).from(r).select(o.join(`,`)).range(n.offset,n.offset+n.limit-1);s=U(r,s,n.filters),n.orderBy&&(L(r,n.orderBy.column),s=s.order(n.orderBy.column,{ascending:n.orderBy.ascending}));let{data:c,error:l}=await s;if(l)throw Error(`Failed to read database records: ${D(l)}`);let u=Array.isArray(c)?c:[];return{columns:i,limit:n.limit,offset:n.offset,rows:ie(r,u,i),success:!0,table:r}}async function Q(e,t){let n=w.parse(e),r=q(n),i=E(t);await _(n,r,t);let a=J({input:n,table:r,targetRows:n.operation===`update`||n.operation===`delete`?await K(i,r,n.filters??[]):[]}),o={input:n,targetIds:a.targetIds},s=M({action:`DATABASE ${n.operation}`,context:t,payload:o,preview:a,subject:r});if(s)return s;try{let e=await oe(n,r,i),s=t?.skipAudit?{auditLogged:!1}:await Y({context:t,input:o,preview:a,status:`success`,summary:String(a.summary),targetTables:[r],toolName:`execute_database_mutation`});return{affectedCount:e.length||a.affectedCount,auditLogged:s.auditLogged,...s.auditError?{auditError:s.auditError}:{},mutationExecuted:!0,operation:n.operation,sampleTargetIds:e.map(e=>G(e,F(r).primaryKey)).slice(0,10),success:!0,summary:a.summary,table:r}}catch(e){let i=D(e),s=t?.skipAudit?{auditLogged:!1}:await Y({context:t,errorMessage:i,input:o,preview:a,status:`failure`,summary:String(a.summary),targetTables:[r],toolName:`execute_database_mutation`});return{auditLogged:s.auditLogged,...s.auditError?{auditError:s.auditError}:{},message:`Database mutation failed: ${i}`,mutationExecuted:!1,operation:n.operation,success:!1,table:r}}}async function $(e,t){let n=T.parse(e),r=[];for(let e of n.actions){let n=q(e);await _(e,n,t);let i=e.operation===`update`||e.operation===`delete`?await K(E(t),n,e.filters??[]):[];r.push({input:e,preview:J({input:e,table:n,targetRows:i}),table:n})}let i=r.map(({preview:e})=>String(e.summary)),a={actionCount:r.length,actionSummaries:i,summary:n.summary||`Run ${r.length} confirmed database actions.`,tables:Array.from(new Set(r.map(e=>e.table)))},o=r.map(({input:e,preview:t,table:n})=>({input:e,table:n,targetIds:t.targetIds})),s=M({action:`DATABASE ACTION PLAN`,context:t,payload:o,preview:a,subject:`${r.length} actions`});if(s)return s;let c=[],l=!1;for(let[e,n]of r.entries()){let i=await Q(n.input,{...t,skipAudit:!0,skipConfirmation:!0});if(c.push(i),i&&typeof i==`object`&&i.mutationExecuted===!0&&(l=!0),!i||typeof i!=`object`||i.success===!1){let n=await Y({context:t,errorMessage:i&&typeof i==`object`&&typeof i.message==`string`?i.message:`Database action ${e+1} failed.`,input:o,preview:a,status:`failure`,summary:String(a.summary),targetTables:a.tables,toolName:`execute_database_action_plan`});return{actionCount:r.length,auditLogged:n.auditLogged,...n.auditError?{auditError:n.auditError}:{},failedActionIndex:e,message:i&&typeof i==`object`&&typeof i.message==`string`?i.message:`Database action ${e+1} failed.`,mutationExecuted:l,results:c,success:!1}}}let u=await Y({context:t,input:o,preview:a,status:`success`,summary:String(a.summary),targetTables:a.tables,toolName:`execute_database_action_plan`});return{actionCount:r.length,auditLogged:u.auditLogged,...u.auditError?{auditError:u.auditError}:{},mutationExecuted:l,results:c,success:!0,summary:n.summary??null}}function se(e){return{describe_database_schema:(0,n.tool)({description:`Describe the public database tables Cortex AI can read or mutate, including read-only tables, primary keys, and writable columns. Use before broad database tasks when unsure about schema.`,execute:e=>X(e),inputSchema:S,strict:!0}),execute_database_action_plan:(0,n.tool)({description:`Execute up to 8 typed database mutations as one confirmed plan. Mutating: first returns one combined confirmation preview and Confirm button; after confirmation, runs each action in order and stops on the first failure.`,execute:t=>$(t,e),inputSchema:T,strict:!0}),execute_database_mutation:(0,n.tool)({description:`Run one typed insert, update, upsert, or delete against an allowed public database table. Mutating: always previews affected rows and requires exact user confirmation before execution. Does not support arbitrary SQL.`,execute:t=>Q(t,e),inputSchema:w,strict:!0}),read_database_records:(0,n.tool)({description:`Read records from an allowed public database table with validated columns, filters, ordering, limit, and offset. Read-only and redacts protected secret-like values.`,execute:t=>Z(t,e),inputSchema:C,strict:!0})}}exports.createCortexDatabaseAgentTools=se,exports.describeDatabaseSchemaInputSchema=S,exports.executeDatabaseActionPlan=$,exports.executeDatabaseActionPlanInputSchema=T,exports.executeDatabaseMutation=Q,exports.executeDatabaseMutationInputSchema=w,exports.executeDescribeDatabaseSchema=X,exports.executeReadDatabaseRecords=Z,exports.readDatabaseRecordsInputSchema=C;
|