@brand-map/ai-bot-extension 0.0.10-alpha.10
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/dist/extension.js +7 -0
- package/dist/extension.manifest.json +140 -0
- package/package.json +72 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
function extension(input){return{callbacks:input.callbacks??[],eventHandlers:input.eventHandlers??[],hooks:input.hooks??[],jobs:input.jobs??[],manifest:{callbacks:(input.callbacks??[]).map((handler)=>({action:handler.action,name:handler.name})),config:input.config,description:input.description,developerUrl:input.developerUrl,displayName:input.displayName,docs:input.docs,eventHandlers:(input.eventHandlers??[]).map((handler)=>({event:handler.event,name:handler.name})),hooks:(input.hooks??[]).map((hook)=>({event:hook.event,name:hook.name})),jobs:(input.jobs??[]).map((job)=>({name:job.name,overlap:job.overlap,schedule:job.schedule,timezone:job.timezone})),logo:input.logo,name:input.name,permissions:input.permissions??[],publisher:input.publisher,version:input.version,workflows:(input.workflows??[]).map((workflow)=>({name:workflow.name}))},start:input.start,stop:input.stop,workflows:input.workflows??[]}}function callback(name,options,handler){const resolvedHandler=typeof options==="function"?options:handler;if(!resolvedHandler){throw new Error(`Extension callback "${name}" requires a handler`)}return{action:typeof options==="function"?name:options.action??name,handler:resolvedHandler,name}}function temporalWorkflow(name,handler){return{handler,name}}var AI_BOT_EXTENSION_NAME="ai-bot";var AI_BOT_VERSION="0.0.10-alpha.9";var DEFAULT_MODEL="gpt-4.1-mini";var DEFAULT_OPENAI_BASE_URL="https://api.openai.com/v1";var DEFAULT_ANTHROPIC_BASE_URL="https://api.anthropic.com/v1";var DEFAULT_MAX_COMMANDS=8;var RUN_HISTORY_PREFIX="ai-bot.runs.";var LAST_RUN_KEY="ai-bot.last-run";var MAX_EFFECT_RESULT_CHARS=8000;var MAX_MODEL_CONTEXT_CHARS=12000;var COMMANDS=[{command:"product.list",description:"List products with an optional read query.",mutates:false},{command:"product.retrieve",description:"Retrieve one product by id.",mutates:false},{command:"product.retrieveVariantByExternalSku",description:"Retrieve a variant by external SKU.",mutates:false},{command:"product.listVariants",description:"List variants for a product id.",mutates:false},{command:"product.listCategories",description:"List product categories.",mutates:false},{command:"price.listPriceList",description:"List price lists.",mutates:false},{command:"price.listPriceListPrice",description:"List price list prices.",mutates:false},{command:"stockLocation.listStockLocation",description:"List stock locations.",mutates:false},{command:"stockLocation.retrieveStockLocation",description:"Retrieve one stock location by id.",mutates:false},{command:"notification.listNotifications",description:"List notifications.",mutates:false},{command:"notification.retrieveNotification",description:"Retrieve one notification by id.",mutates:false},{command:"file.list",description:"List files.",mutates:false},{command:"file.retrieve",description:"Retrieve file metadata by id.",mutates:false},{command:"storage.db.get",description:"Read one extension storage record by key.",mutates:false},{command:"storage.db.list",description:"List extension storage records.",mutates:false},{command:"storage.cache.get",description:"Read one extension cache value by key.",mutates:false},{command:"product.create",description:"Create one or more products.",mutates:true},{command:"product.update",description:"Update one or more products.",mutates:true},{command:"product.createCategory",description:"Create a product category.",mutates:true},{command:"product.updateCategory",description:"Update a product category.",mutates:true},{command:"product.createVariant",description:"Create a product variant.",mutates:true},{command:"product.updateVariant",description:"Update a product variant.",mutates:true},{command:"product.addImage",description:"Add an image URL to a product.",mutates:true},{command:"product.upsertOption",description:"Create or update a product option.",mutates:true},{command:"product.upsertOptionValue",description:"Create or update a product option value.",mutates:true},{command:"product.linkVariantOptionValue",description:"Link a variant to an option value.",mutates:true},{command:"product.upsertAttribute",description:"Create or update a product attribute.",mutates:true},{command:"product.upsertAttributeValue",description:"Create or update a product attribute value.",mutates:true},{command:"price.createPriceList",description:"Create one or more price lists.",mutates:true},{command:"price.updatePriceList",description:"Update one or more price lists.",mutates:true},{command:"price.createPriceListPrice",description:"Create one or more price list prices.",mutates:true},{command:"price.updatePriceListPrice",description:"Update one or more price list prices.",mutates:true},{command:"price.setBasePrice",description:"Set a variant base price.",mutates:true},{command:"stockLocation.createStockLocation",description:"Create a stock location.",mutates:true},{command:"stockLocation.updateStockLocation",description:"Update a stock location.",mutates:true},{command:"stockLocation.upsertStockLocation",description:"Create or update a stock location.",mutates:true},{command:"inventory.enrollVariantInStore",description:"Enroll a variant in store inventory.",mutates:true},{command:"inventory.syncStockLevel",description:"Sync a stock level.",mutates:true},{command:"notification.createNotifications",description:"Create one or more notifications.",mutates:true},{command:"file.create",description:"Create a file from string content.",mutates:true},{command:"file.delete",description:"Delete one or more files by id.",mutates:true},{command:"storage.db.set",description:"Write an extension storage record.",mutates:true},{command:"storage.db.delete",description:"Delete extension storage records.",mutates:true},{command:"storage.cache.set",description:"Write an extension cache value.",mutates:true},{command:"storage.cache.delete",description:"Delete extension cache values.",mutates:true}];var COMMAND_BY_NAME=new Map(COMMANDS.map((command)=>[command.command,command]));var COMMAND_CATALOG_TEXT=COMMANDS.map((entry)=>`- ${entry.command} (${entry.mutates?"mutation":"read"}): ${entry.description}`).join(`
|
|
3
|
+
`);var AI_BOT_PERMISSIONS=["extension-storage:read","extension-storage:write","network:https://*/*","operation:file.create","operation:file.delete","operation:file.list","operation:file.retrieve","operation:inventory.enrollVariantInStore","operation:inventory.syncStockLevel","operation:notification.createNotifications","operation:notification.listNotifications","operation:notification.retrieveNotification","operation:price.createPriceList","operation:price.createPriceListPrice","operation:price.listPriceList","operation:price.listPriceListPrice","operation:price.setBasePrice","operation:price.updatePriceList","operation:price.updatePriceListPrice","operation:product.addImage","operation:product.create","operation:product.createCategory","operation:product.createVariant","operation:product.linkVariantOptionValue","operation:product.list","operation:product.listCategories","operation:product.listVariants","operation:product.retrieve","operation:product.retrieveVariantByExternalSku","operation:product.update","operation:product.updateCategory","operation:product.updateVariant","operation:product.upsertAttribute","operation:product.upsertAttributeValue","operation:product.upsertOption","operation:product.upsertOptionValue","operation:stockLocation.createStockLocation","operation:stockLocation.listStockLocation","operation:stockLocation.retrieveStockLocation","operation:stockLocation.updateStockLocation","operation:stockLocation.upsertStockLocation"];var DEFAULT_SYSTEM_PROMPT=["You are the Brand Map AI Bot harness.","You decide which exact extension commands should run for the user's task.","Return only JSON. Do not return shell commands. Do not invent commands.","Every command must use this shape:",'{"commands":[{"id":"short-id","command":"product.list","args":[{"limit":5}],"reason":"why this is needed"}]}',"Use an empty commands array when no safe action is needed.","Available commands:",COMMAND_CATALOG_TEXT].join(`
|
|
4
|
+
|
|
5
|
+
`);var BRAND_MAP_DEVELOPER_URL="https://brand-map.ru";var EXTENSION_LOGO={alt:"AI Bot icon",src:"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA5NiA5NiI+PHJlY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9Ijk2IiByeD0iMjAiIGZpbGw9IiMxMjE4MjYiLz48cGF0aCBkPSJNMjQgNDBjMC05Ljk0IDguMDYtMTggMTgtMThoMTJjOS45NCAwIDE4IDguMDYgMTggMTh2MThjMCA2LjYzLTUuMzcgMTItMTIgMTJIMzZjLTYuNjMgMC0xMi01LjM3LTEyLTEyVjQwWiIgZmlsbD0iIzJERDRCRiIvPjxwYXRoIGQ9Ik0zMiA0MmMwLTUuNTIgNC40OC0xMCAxMC0xMGgxMmM1LjUyIDAgMTAgNC40OCAxMCAxMHYxMmMwIDQuNDItMy41OCA4LTggOEg0MGMtNC40MiAwLTgtMy41OC04LThWNDJaIiBmaWxsPSIjRjhGQUZDIi8+PGNpcmNsZSBjeD0iNDIiIGN5PSI0OCIgcj0iNCIgZmlsbD0iIzEyMTgyNiIvPjxjaXJjbGUgY3g9IjU0IiBjeT0iNDgiIHI9IjQiIGZpbGw9IiMxMjE4MjYiLz48cGF0aCBkPSJNNDIgNjBoMTIiIHN0cm9rZT0iIzEyMTgyNiIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNNDggMTh2LTZNMjcgMjVsLTQtNE02OSAyNWw0LTRNMjYgNzZsLTUgNU03MCA3Nmw1IDUiIHN0cm9rZT0iI0E3OEJGQSIgc3Ryb2tlLXdpZHRoPSI1IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4="};function createAiBotExtension(options={}){const settings=readSettings(options);return extension({config:[{defaultValue:"openai",key:"aiProvider",label:"AI Provider",options:[{label:"OpenAI",value:"openai"},{label:"OpenAI Compatible",value:"openai-compatible"},{label:"Anthropic",value:"anthropic"}],type:"select"},{key:"apiToken",label:"AI Provider API Token",required:true,secret:true,type:"string"},{defaultValue:DEFAULT_OPENAI_BASE_URL,key:"apiBaseUrl",label:"AI API Base URL",type:"url"},{defaultValue:DEFAULT_MODEL,key:"model",label:"Model",type:"string"},{defaultValue:DEFAULT_MAX_COMMANDS,key:"maxCommands",label:"Max Commands",type:"number"},{defaultValue:false,description:"Allows the harness to execute write commands when workflow input also allows it.",key:"allowMutations",label:"Allow Mutations",type:"boolean"},{defaultValue:false,description:"Runs read commands but skips write commands.",key:"dryRun",label:"Dry Run",type:"boolean"},{defaultValue:DEFAULT_SYSTEM_PROMPT,key:"systemPrompt",label:"System Prompt",type:"string"}],description:"Runs a small AI harness as a Temporal workflow over approved Brand Map extension commands.",displayName:"AI Bot",developerUrl:BRAND_MAP_DEVELOPER_URL,logo:EXTENSION_LOGO,name:AI_BOT_EXTENSION_NAME,publisher:"Brand Map",permissions:AI_BOT_PERMISSIONS,callbacks:[callback("health",()=>({ok:true,service:AI_BOT_EXTENSION_NAME,workflow:"run-harness"})),callback("last-run",async({storage})=>{const record=await storage.db.get(LAST_RUN_KEY);return{run:record?.value??null}})],version:AI_BOT_VERSION,workflows:[temporalWorkflow("run-harness",async({brandMap,invocation,log,storage})=>{const input=readHarnessInput(invocation.input,settings);const startedAt=new Date().toISOString();const runId=`ai_bot_${invocation.invocationId}`;log.info("AI Bot Temporal workflow started",{allowMutations:input.allowMutations,dryRun:input.dryRun,invocationId:invocation.invocationId,runId});const result=await runHarness({brandMap,input,log,runId,settings,startedAt,storage}).catch(async(error)=>{const message=error instanceof Error?error.message:String(error);const failed={allowMutations:input.allowMutations,commands:[],dryRun:input.dryRun,effects:[],finishedAt:new Date().toISOString(),model:settings.model,planText:"",provider:settings.aiProvider,runId,startedAt,status:"failed",summary:{message,status:"failed"},task:input.task};await persistRun(storage,failed);return failed});log.info("AI Bot Temporal workflow completed",{commandCount:result.commands.length,effectCount:result.effects.length,invocationId:invocation.invocationId,runId,status:result.status});return result})]})}async function runHarness(input){if(!input.settings.apiToken){throw new Error("AI Bot requires apiToken in extension config.")}const planText=await callAiModel(input.settings,[{content:buildPlanningPrompt(input.input),role:"user"}]);const plannedCommands=normalizePlan(parseJsonObject(planText),input.input.maxCommands);const effects=[];for(const plannedCommand of plannedCommands){effects.push(await executePlannedCommand({brandMap:input.brandMap,command:plannedCommand,input:input.input,storage:input.storage}))}const summary=await createFinalSummary({effects,planText,settings:input.settings,task:input.input.task});const result={allowMutations:input.input.allowMutations,commands:plannedCommands,dryRun:input.input.dryRun,effects,finishedAt:new Date().toISOString(),model:input.settings.model,planText,provider:input.settings.aiProvider,runId:input.runId,startedAt:input.startedAt,status:effects.some((effect)=>effect.status==="error")?"failed":"completed",summary,task:input.input.task};await persistRun(input.storage,result);return result}async function createFinalSummary(input){const status=input.effects.some((effect)=>effect.status==="error")?"completed-with-errors":"completed";try{const summaryText=await callAiModel(input.settings,[{content:buildSummaryPrompt({effects:input.effects,planText:input.planText,task:input.task}),role:"user"}]);return toExtensionJson(parseJsonObject(summaryText,{message:summaryText,status}))}catch(error){return{errors:[error instanceof Error?error.message:String(error)],status:"summary-failed",summary:"Commands ran, but the final AI summary request failed."}}}async function executePlannedCommand(input){const startedAt=new Date().toISOString();const definition=COMMAND_BY_NAME.get(input.command.command);const base={args:input.command.args,command:input.command.command,finishedAt:startedAt,id:input.command.id,reason:input.command.reason,startedAt};if(!definition){return{...base,skippedReason:"Unsupported command.",status:"skipped"}}if(input.input.allowedCommands&&!input.input.allowedCommands.has(input.command.command)){return{...base,skippedReason:"Command was not listed in workflow input allowedCommands.",status:"skipped"}}if(definition.mutates&&input.input.dryRun){return{...base,skippedReason:"Dry run is enabled.",status:"skipped"}}if(definition.mutates&&!input.input.allowMutations){return{...base,skippedReason:"Mutation commands are disabled.",status:"skipped"}}try{const result=await dispatchCommand(input.brandMap,input.storage,input.command);return{...base,finishedAt:new Date().toISOString(),result:compactResult(result),status:"ok"}}catch(error){return{...base,error:error instanceof Error?error.message:String(error),finishedAt:new Date().toISOString(),status:"error"}}}async function dispatchCommand(brandMap,storage,command){const args=command.args;switch(command.command){case"product.list":return await brandMap.product.list(args[0]);case"product.retrieve":return await brandMap.product.retrieve(readStringArg(args,0),args[1]);case"product.retrieveVariantByExternalSku":return await brandMap.product.retrieveVariantByExternalSku(readStringArg(args,0));case"product.listVariants":return await brandMap.product.listVariants(readStringArg(args,0));case"product.listCategories":return await brandMap.product.listCategories(args[0]);case"product.create":return await brandMap.product.create(args[0]);case"product.update":return await brandMap.product.update(args[0]);case"product.createCategory":return await brandMap.product.createCategory(args[0]);case"product.updateCategory":return await brandMap.product.updateCategory(readStringArg(args,0),args[1]);case"product.createVariant":return await brandMap.product.createVariant(readStringArg(args,0),args[1]);case"product.updateVariant":return await brandMap.product.updateVariant(readStringArg(args,0),args[1]);case"product.addImage":return await brandMap.product.addImage(readStringArg(args,0),readStringArg(args,1),readOptionalNumberArg(args,2));case"product.upsertOption":return await brandMap.product.upsertOption(readStringArg(args,0),args[1]);case"product.upsertOptionValue":return await brandMap.product.upsertOptionValue(readStringArg(args,0),args[1]);case"product.linkVariantOptionValue":return await brandMap.product.linkVariantOptionValue(readStringArg(args,0),readStringArg(args,1));case"product.upsertAttribute":return await brandMap.product.upsertAttribute(readStringArg(args,0),args[1]);case"product.upsertAttributeValue":return await brandMap.product.upsertAttributeValue(args[0]);case"price.listPriceList":return await brandMap.price.listPriceList(args[0]);case"price.listPriceListPrice":return await brandMap.price.listPriceListPrice(args[0]);case"price.createPriceList":return await brandMap.price.createPriceList(args[0]);case"price.updatePriceList":return await brandMap.price.updatePriceList(args[0]);case"price.createPriceListPrice":return await brandMap.price.createPriceListPrice(args[0]);case"price.updatePriceListPrice":return await brandMap.price.updatePriceListPrice(args[0]);case"price.setBasePrice":return await brandMap.price.setBasePrice(args[0]);case"stockLocation.listStockLocation":return await brandMap.stockLocation.listStockLocation(args[0]);case"stockLocation.retrieveStockLocation":return await brandMap.stockLocation.retrieveStockLocation(readStringArg(args,0),args[1]);case"stockLocation.createStockLocation":return await brandMap.stockLocation.createStockLocation(args[0]);case"stockLocation.updateStockLocation":return await brandMap.stockLocation.updateStockLocation(args[0]);case"stockLocation.upsertStockLocation":return await brandMap.stockLocation.upsertStockLocation(args[0]);case"inventory.enrollVariantInStore":return await brandMap.inventory.enrollVariantInStore(args[0]);case"inventory.syncStockLevel":return await brandMap.inventory.syncStockLevel(args[0]);case"notification.createNotifications":return await brandMap.notification.createNotifications(args[0]);case"notification.listNotifications":return await brandMap.notification.listNotifications(args[0]);case"notification.retrieveNotification":return await brandMap.notification.retrieveNotification(readStringArg(args,0),args[1]);case"file.create":return await brandMap.file.create(args[0]);case"file.delete":return await brandMap.file.delete(args[0]);case"file.list":return await brandMap.file.list(args[0]);case"file.retrieve":return await brandMap.file.retrieve(readStringArg(args,0),args[1]);case"storage.db.get":return await storage.db.get(readStringArg(args,0));case"storage.db.list":return await storage.db.list(args[0]);case"storage.db.set":return await storage.db.set(args[0]);case"storage.db.delete":return await storage.db.delete(args[0]);case"storage.cache.get":return await storage.cache.get(readStringArg(args,0));case"storage.cache.set":return await storage.cache.set(args[0]);case"storage.cache.delete":return await storage.cache.delete(args[0]);default:throw new Error(`Unsupported command "${command.command}"`)}}async function callAiModel(settings,messages){assertAiBaseUrl(settings.apiBaseUrl);if(settings.aiProvider==="anthropic"){return await callAnthropic(settings,messages)}return await callOpenAiCompatible(settings,messages)}async function callOpenAiCompatible(settings,messages){const response=await settings.fetchImplementation(`${settings.apiBaseUrl}/chat/completions`,{body:JSON.stringify({messages:[{content:settings.systemPrompt,role:"system"},...messages],model:settings.model,response_format:{type:"json_object"},temperature:0.1}),headers:{authorization:`Bearer ${settings.apiToken}`,"content-type":"application/json"},method:"POST"});const payload=await readAiResponse(response);const content=readPath(payload,["choices",0,"message","content"]);if(typeof content!=="string"){throw new Error("AI provider response did not include choices[0].message.content.")}return content}async function callAnthropic(settings,messages){const response=await settings.fetchImplementation(`${settings.apiBaseUrl}/messages`,{body:JSON.stringify({max_tokens:2000,messages,model:settings.model,system:settings.systemPrompt,temperature:0.1}),headers:{"anthropic-version":"2023-06-01","content-type":"application/json","x-api-key":settings.apiToken??""},method:"POST"});const payload=await readAiResponse(response);const blocks=readPath(payload,["content"]);if(!Array.isArray(blocks)){throw new Error("AI provider response did not include content blocks.")}return blocks.map((block)=>isRecord(block)&&typeof block.text==="string"?block.text:"").join(`
|
|
6
|
+
`).trim()}async function readAiResponse(response){const text=await response.text();const payload=parseJsonObject(text,{message:text});if(!response.ok){const message=readAiErrorMessage(payload)??`AI provider request failed with status ${response.status}.`;throw new Error(message)}return payload}function buildPlanningPrompt(input){return truncateText(JSON.stringify({context:input.context,instructions:["Pick the minimum command sequence needed.","Use read commands before mutation commands when identifiers are unknown.","Mutation commands are only useful when allowMutations is true and dryRun is false.","Return only JSON with a commands array."],runtime:{allowMutations:input.allowMutations,allowedCommands:input.allowedCommands?Array.from(input.allowedCommands):null,dryRun:input.dryRun,maxCommands:input.maxCommands},task:input.task},null,2),MAX_MODEL_CONTEXT_CHARS)}function buildSummaryPrompt(input){return truncateText(JSON.stringify({effects:input.effects,instructions:["Summarize what happened for the user.","Mention commands that were skipped or failed.","Return only JSON with keys summary, status, actions, skipped, errors, nextSteps."],planText:input.planText,task:input.task},null,2),MAX_MODEL_CONTEXT_CHARS)}function normalizePlan(input,maxCommands){if(!isRecord(input)||!Array.isArray(input.commands)){throw new Error("AI plan must be a JSON object with a commands array.")}return input.commands.slice(0,maxCommands).map((entry,index)=>{if(!isRecord(entry)||typeof entry.command!=="string"){throw new Error(`AI plan command at index ${index} must include command.`)}return{args:readJsonArray(entry.args),command:entry.command,id:typeof entry.id==="string"&&entry.id.trim()?entry.id:`cmd-${index+1}`,reason:typeof entry.reason==="string"?entry.reason:null}})}function readHarnessInput(input,settings){const value=isRecord(input)?input:{};const task=readStringField(value,"task")??readStringField(value,"prompt");if(!task){throw new Error("AI Bot workflow input requires task.")}return{allowMutations:settings.allowMutations&&readBooleanField(value,"allowMutations",false),allowedCommands:readAllowedCommands(value.allowedCommands),context:toExtensionJson(value.context??null),dryRun:readBooleanField(value,"dryRun",settings.dryRun),maxCommands:clampInteger(readNumberField(value,"maxCommands")??settings.maxCommands,1,25),task}}function readSettings(options){const aiProvider=readProvider(options.aiProvider);const configuredApiBaseUrl=stringOption(options.apiBaseUrl);const apiBaseUrl=aiProvider==="anthropic"&&(!configuredApiBaseUrl||configuredApiBaseUrl===DEFAULT_OPENAI_BASE_URL)?DEFAULT_ANTHROPIC_BASE_URL:configuredApiBaseUrl??(aiProvider==="anthropic"?DEFAULT_ANTHROPIC_BASE_URL:DEFAULT_OPENAI_BASE_URL);return{aiProvider,allowMutations:booleanOption(options.allowMutations)??false,apiBaseUrl:apiBaseUrl.replace(/\/+$/,""),apiToken:stringOption(options.apiToken),dryRun:booleanOption(options.dryRun)??false,fetchImplementation:options.fetchImplementation??fetch,maxCommands:clampInteger(numberOption(options.maxCommands)??DEFAULT_MAX_COMMANDS,1,25),model:stringOption(options.model)??DEFAULT_MODEL,systemPrompt:stringOption(options.systemPrompt)??DEFAULT_SYSTEM_PROMPT}}async function persistRun(storage,result){const value=toExtensionJson(result);await storage.db.set({key:`${RUN_HISTORY_PREFIX}${result.runId}`,value});await storage.db.set({key:LAST_RUN_KEY,value})}function parseJsonObject(text,fallback){if(typeof text!=="string"){return text}const trimmed=stripJsonFence(text.trim());try{return JSON.parse(trimmed)}catch{const objectMatch=/\{[\s\S]*\}/.exec(trimmed);if(objectMatch){try{return JSON.parse(objectMatch[0])}catch{return fallback??text}}return fallback??text}}function stripJsonFence(text){return text.replace(/^```(?:json)?\s*/u,"").replace(/\s*```$/u,"").trim()}function readAiErrorMessage(payload){if(!isRecord(payload)){return null}const error=payload.error;if(typeof error==="string"){return error}if(isRecord(error)&&typeof error.message==="string"){return error.message}return null}function compactResult(value){const json=toExtensionJson(value);const text=JSON.stringify(json);if(text.length<=MAX_EFFECT_RESULT_CHARS){return json}return{truncated:true,value:text.slice(0,MAX_EFFECT_RESULT_CHARS)}}function toExtensionJson(value,depth=0){if(value===null||typeof value==="boolean"||typeof value==="string"){return value}if(typeof value==="number"){return Number.isFinite(value)?value:null}if(value===undefined){return null}if(value instanceof Date){return value.toISOString()}if(depth>6){return"[Truncated]"}if(Array.isArray(value)){return value.slice(0,100).map((entry)=>toExtensionJson(entry,depth+1))}if(typeof value==="object"){const output={};for(const[key,entry]of Object.entries(value).slice(0,100)){if(typeof entry!=="function"&&typeof entry!=="symbol"){output[key]=toExtensionJson(entry,depth+1)}}return output}return String(value)}function readJsonArray(value){if(!Array.isArray(value)){return[]}return value.map((entry)=>toExtensionJson(entry))}function readAllowedCommands(value){if(!Array.isArray(value)){return null}return new Set(value.filter((entry)=>typeof entry==="string"))}function readStringArg(args,index){const value=args[index];if(typeof value!=="string"||!value.trim()){throw new Error(`Command argument ${index} must be a non-empty string.`)}return value}function readOptionalNumberArg(args,index){const value=args[index];if(value===undefined||value===null){return}if(typeof value!=="number"||!Number.isFinite(value)){throw new Error(`Command argument ${index} must be a number.`)}return value}function readProvider(value){return value==="anthropic"||value==="openai-compatible"||value==="openai"?value:"openai"}function stringOption(value){return typeof value==="string"&&value.trim()?value.trim():null}function booleanOption(value){return typeof value==="boolean"?value:null}function numberOption(value){return typeof value==="number"&&Number.isFinite(value)?value:null}function readStringField(value,key){return stringOption(value[key])}function readBooleanField(value,key,defaultValue){return booleanOption(value[key])??defaultValue}function readNumberField(value,key){return numberOption(value[key])}function clampInteger(value,min,max){return Math.max(min,Math.min(max,Math.trunc(value)))}function isRecord(value){return typeof value==="object"&&value!==null&&!Array.isArray(value)}function readPath(value,path){let current=value;for(const segment of path){if(typeof segment==="number"){if(!Array.isArray(current)){return}current=current[segment];continue}if(!isRecord(current)){return}current=current[segment]}return current}function assertAiBaseUrl(value){const url=new URL(value);const isLocalHttp=url.protocol==="http:"&&(url.hostname==="localhost"||url.hostname==="127.0.0.1");if(url.protocol!=="https:"&&!isLocalHttp){throw new Error("AI API Base URL must use https, except localhost development URLs.")}}function truncateText(value,maxLength){if(value.length<=maxLength){return value}return`${value.slice(0,maxLength)}
|
|
7
|
+
[truncated]`}var extension_default=createAiBotExtension;var bundledExtension={packageName:"@brand-map/ai-bot-extension"};var exports_default=extension_default;export{exports_default as default,createAiBotExtension,bundledExtension};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"config": [
|
|
3
|
+
{
|
|
4
|
+
"defaultValue": "openai",
|
|
5
|
+
"key": "aiProvider",
|
|
6
|
+
"label": "AI Provider",
|
|
7
|
+
"options": [
|
|
8
|
+
{
|
|
9
|
+
"label": "OpenAI",
|
|
10
|
+
"value": "openai"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"label": "OpenAI Compatible",
|
|
14
|
+
"value": "openai-compatible"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"label": "Anthropic",
|
|
18
|
+
"value": "anthropic"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"type": "select"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "apiToken",
|
|
25
|
+
"label": "AI Provider API Token",
|
|
26
|
+
"required": true,
|
|
27
|
+
"secret": true,
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"defaultValue": "https://api.openai.com/v1",
|
|
32
|
+
"key": "apiBaseUrl",
|
|
33
|
+
"label": "AI API Base URL",
|
|
34
|
+
"type": "url"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"defaultValue": "gpt-4.1-mini",
|
|
38
|
+
"key": "model",
|
|
39
|
+
"label": "Model",
|
|
40
|
+
"type": "string"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"defaultValue": 8,
|
|
44
|
+
"key": "maxCommands",
|
|
45
|
+
"label": "Max Commands",
|
|
46
|
+
"type": "number"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"defaultValue": false,
|
|
50
|
+
"description": "Allows the harness to execute write commands when workflow input also allows it.",
|
|
51
|
+
"key": "allowMutations",
|
|
52
|
+
"label": "Allow Mutations",
|
|
53
|
+
"type": "boolean"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"defaultValue": false,
|
|
57
|
+
"description": "Runs read commands but skips write commands.",
|
|
58
|
+
"key": "dryRun",
|
|
59
|
+
"label": "Dry Run",
|
|
60
|
+
"type": "boolean"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"defaultValue": "You are the Brand Map AI Bot harness.\n\nYou decide which exact extension commands should run for the user's task.\n\nReturn only JSON. Do not return shell commands. Do not invent commands.\n\nEvery command must use this shape:\n\n{\"commands\":[{\"id\":\"short-id\",\"command\":\"product.list\",\"args\":[{\"limit\":5}],\"reason\":\"why this is needed\"}]}\n\nUse an empty commands array when no safe action is needed.\n\nAvailable commands:\n\n- product.list (read): List products with an optional read query.\n- product.retrieve (read): Retrieve one product by id.\n- product.retrieveVariantByExternalSku (read): Retrieve a variant by external SKU.\n- product.listVariants (read): List variants for a product id.\n- product.listCategories (read): List product categories.\n- price.listPriceList (read): List price lists.\n- price.listPriceListPrice (read): List price list prices.\n- stockLocation.listStockLocation (read): List stock locations.\n- stockLocation.retrieveStockLocation (read): Retrieve one stock location by id.\n- notification.listNotifications (read): List notifications.\n- notification.retrieveNotification (read): Retrieve one notification by id.\n- file.list (read): List files.\n- file.retrieve (read): Retrieve file metadata by id.\n- storage.db.get (read): Read one extension storage record by key.\n- storage.db.list (read): List extension storage records.\n- storage.cache.get (read): Read one extension cache value by key.\n- product.create (mutation): Create one or more products.\n- product.update (mutation): Update one or more products.\n- product.createCategory (mutation): Create a product category.\n- product.updateCategory (mutation): Update a product category.\n- product.createVariant (mutation): Create a product variant.\n- product.updateVariant (mutation): Update a product variant.\n- product.addImage (mutation): Add an image URL to a product.\n- product.upsertOption (mutation): Create or update a product option.\n- product.upsertOptionValue (mutation): Create or update a product option value.\n- product.linkVariantOptionValue (mutation): Link a variant to an option value.\n- product.upsertAttribute (mutation): Create or update a product attribute.\n- product.upsertAttributeValue (mutation): Create or update a product attribute value.\n- price.createPriceList (mutation): Create one or more price lists.\n- price.updatePriceList (mutation): Update one or more price lists.\n- price.createPriceListPrice (mutation): Create one or more price list prices.\n- price.updatePriceListPrice (mutation): Update one or more price list prices.\n- price.setBasePrice (mutation): Set a variant base price.\n- stockLocation.createStockLocation (mutation): Create a stock location.\n- stockLocation.updateStockLocation (mutation): Update a stock location.\n- stockLocation.upsertStockLocation (mutation): Create or update a stock location.\n- inventory.enrollVariantInStore (mutation): Enroll a variant in store inventory.\n- inventory.syncStockLevel (mutation): Sync a stock level.\n- notification.createNotifications (mutation): Create one or more notifications.\n- file.create (mutation): Create a file from string content.\n- file.delete (mutation): Delete one or more files by id.\n- storage.db.set (mutation): Write an extension storage record.\n- storage.db.delete (mutation): Delete extension storage records.\n- storage.cache.set (mutation): Write an extension cache value.\n- storage.cache.delete (mutation): Delete extension cache values.",
|
|
64
|
+
"key": "systemPrompt",
|
|
65
|
+
"label": "System Prompt",
|
|
66
|
+
"type": "string"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
"description": "Runs a small AI harness as a Temporal workflow over approved Brand Map extension commands.",
|
|
70
|
+
"developerUrl": "https://brand-map.ru",
|
|
71
|
+
"displayName": "AI Bot",
|
|
72
|
+
"eventHandlers": [],
|
|
73
|
+
"hooks": [],
|
|
74
|
+
"jobs": [],
|
|
75
|
+
"logo": {
|
|
76
|
+
"alt": "AI Bot icon",
|
|
77
|
+
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA5NiA5NiI+PHJlY3Qgd2lkdGg9Ijk2IiBoZWlnaHQ9Ijk2IiByeD0iMjAiIGZpbGw9IiMxMjE4MjYiLz48cGF0aCBkPSJNMjQgNDBjMC05Ljk0IDguMDYtMTggMTgtMThoMTJjOS45NCAwIDE4IDguMDYgMTggMTh2MThjMCA2LjYzLTUuMzcgMTItMTIgMTJIMzZjLTYuNjMgMC0xMi01LjM3LTEyLTEyVjQwWiIgZmlsbD0iIzJERDRCRiIvPjxwYXRoIGQ9Ik0zMiA0MmMwLTUuNTIgNC40OC0xMCAxMC0xMGgxMmM1LjUyIDAgMTAgNC40OCAxMCAxMHYxMmMwIDQuNDItMy41OCA4LTggOEg0MGMtNC40MiAwLTgtMy41OC04LThWNDJaIiBmaWxsPSIjRjhGQUZDIi8+PGNpcmNsZSBjeD0iNDIiIGN5PSI0OCIgcj0iNCIgZmlsbD0iIzEyMTgyNiIvPjxjaXJjbGUgY3g9IjU0IiBjeT0iNDgiIHI9IjQiIGZpbGw9IiMxMjE4MjYiLz48cGF0aCBkPSJNNDIgNjBoMTIiIHN0cm9rZT0iIzEyMTgyNiIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48cGF0aCBkPSJNNDggMTh2LTZNMjcgMjVsLTQtNE02OSAyNWw0LTRNMjYgNzZsLTUgNU03MCA3Nmw1IDUiIHN0cm9rZT0iI0E3OEJGQSIgc3Ryb2tlLXdpZHRoPSI1IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz48L3N2Zz4="
|
|
78
|
+
},
|
|
79
|
+
"name": "ai-bot",
|
|
80
|
+
"permissions": [
|
|
81
|
+
"extension-storage:read",
|
|
82
|
+
"extension-storage:write",
|
|
83
|
+
"network:https://*/*",
|
|
84
|
+
"operation:file.create",
|
|
85
|
+
"operation:file.delete",
|
|
86
|
+
"operation:file.list",
|
|
87
|
+
"operation:file.retrieve",
|
|
88
|
+
"operation:inventory.enrollVariantInStore",
|
|
89
|
+
"operation:inventory.syncStockLevel",
|
|
90
|
+
"operation:notification.createNotifications",
|
|
91
|
+
"operation:notification.listNotifications",
|
|
92
|
+
"operation:notification.retrieveNotification",
|
|
93
|
+
"operation:price.createPriceList",
|
|
94
|
+
"operation:price.createPriceListPrice",
|
|
95
|
+
"operation:price.listPriceList",
|
|
96
|
+
"operation:price.listPriceListPrice",
|
|
97
|
+
"operation:price.setBasePrice",
|
|
98
|
+
"operation:price.updatePriceList",
|
|
99
|
+
"operation:price.updatePriceListPrice",
|
|
100
|
+
"operation:product.addImage",
|
|
101
|
+
"operation:product.create",
|
|
102
|
+
"operation:product.createCategory",
|
|
103
|
+
"operation:product.createVariant",
|
|
104
|
+
"operation:product.linkVariantOptionValue",
|
|
105
|
+
"operation:product.list",
|
|
106
|
+
"operation:product.listCategories",
|
|
107
|
+
"operation:product.listVariants",
|
|
108
|
+
"operation:product.retrieve",
|
|
109
|
+
"operation:product.retrieveVariantByExternalSku",
|
|
110
|
+
"operation:product.update",
|
|
111
|
+
"operation:product.updateCategory",
|
|
112
|
+
"operation:product.updateVariant",
|
|
113
|
+
"operation:product.upsertAttribute",
|
|
114
|
+
"operation:product.upsertAttributeValue",
|
|
115
|
+
"operation:product.upsertOption",
|
|
116
|
+
"operation:product.upsertOptionValue",
|
|
117
|
+
"operation:stockLocation.createStockLocation",
|
|
118
|
+
"operation:stockLocation.listStockLocation",
|
|
119
|
+
"operation:stockLocation.retrieveStockLocation",
|
|
120
|
+
"operation:stockLocation.updateStockLocation",
|
|
121
|
+
"operation:stockLocation.upsertStockLocation"
|
|
122
|
+
],
|
|
123
|
+
"publisher": "Brand Map",
|
|
124
|
+
"callbacks": [
|
|
125
|
+
{
|
|
126
|
+
"action": "health",
|
|
127
|
+
"name": "health"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"action": "last-run",
|
|
131
|
+
"name": "last-run"
|
|
132
|
+
}
|
|
133
|
+
],
|
|
134
|
+
"version": "0.0.10-alpha.10",
|
|
135
|
+
"workflows": [
|
|
136
|
+
{
|
|
137
|
+
"name": "run-harness"
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brand-map/ai-bot-extension",
|
|
3
|
+
"version": "0.0.10-alpha.10",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./dist/extension.js",
|
|
7
|
+
"./entrypoint": "./dist/extension.js",
|
|
8
|
+
"./manifest": "./dist/extension.manifest.json",
|
|
9
|
+
"./package.json": "./package.json"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"registry": "https://registry.npmjs.org"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@brand-map/extension-sdk": "0.0.10-alpha.7"
|
|
19
|
+
},
|
|
20
|
+
"brandMap": {
|
|
21
|
+
"extension": {
|
|
22
|
+
"entrypoint": "./entrypoint",
|
|
23
|
+
"runtimeGrants": [
|
|
24
|
+
"extension-storage:read",
|
|
25
|
+
"extension-storage:write",
|
|
26
|
+
"network:https://*/*",
|
|
27
|
+
"operation:file.create",
|
|
28
|
+
"operation:file.delete",
|
|
29
|
+
"operation:file.list",
|
|
30
|
+
"operation:file.retrieve",
|
|
31
|
+
"operation:inventory.enrollVariantInStore",
|
|
32
|
+
"operation:inventory.syncStockLevel",
|
|
33
|
+
"operation:notification.createNotifications",
|
|
34
|
+
"operation:notification.listNotifications",
|
|
35
|
+
"operation:notification.retrieveNotification",
|
|
36
|
+
"operation:price.createPriceList",
|
|
37
|
+
"operation:price.createPriceListPrice",
|
|
38
|
+
"operation:price.listPriceList",
|
|
39
|
+
"operation:price.listPriceListPrice",
|
|
40
|
+
"operation:price.setBasePrice",
|
|
41
|
+
"operation:price.updatePriceList",
|
|
42
|
+
"operation:price.updatePriceListPrice",
|
|
43
|
+
"operation:product.addImage",
|
|
44
|
+
"operation:product.create",
|
|
45
|
+
"operation:product.createCategory",
|
|
46
|
+
"operation:product.createVariant",
|
|
47
|
+
"operation:product.linkVariantOptionValue",
|
|
48
|
+
"operation:product.list",
|
|
49
|
+
"operation:product.listCategories",
|
|
50
|
+
"operation:product.listVariants",
|
|
51
|
+
"operation:product.retrieve",
|
|
52
|
+
"operation:product.retrieveVariantByExternalSku",
|
|
53
|
+
"operation:product.update",
|
|
54
|
+
"operation:product.updateCategory",
|
|
55
|
+
"operation:product.updateVariant",
|
|
56
|
+
"operation:product.upsertAttribute",
|
|
57
|
+
"operation:product.upsertAttributeValue",
|
|
58
|
+
"operation:product.upsertOption",
|
|
59
|
+
"operation:product.upsertOptionValue",
|
|
60
|
+
"operation:stockLocation.createStockLocation",
|
|
61
|
+
"operation:stockLocation.listStockLocation",
|
|
62
|
+
"operation:stockLocation.retrieveStockLocation",
|
|
63
|
+
"operation:stockLocation.updateStockLocation",
|
|
64
|
+
"operation:stockLocation.upsertStockLocation"
|
|
65
|
+
],
|
|
66
|
+
"manifest": "./manifest",
|
|
67
|
+
"name": "ai-bot",
|
|
68
|
+
"official": true,
|
|
69
|
+
"source": "bundled"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|