@shawnstack/quickforge 1.3.25 → 1.3.26

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.
Files changed (34) hide show
  1. package/README.md +20 -14
  2. package/dist/assets/{anthropic-B1_Yrokl.js → anthropic-BcnDL7hi.js} +1 -1
  3. package/dist/assets/{azure-openai-responses-UMiOBCBd.js → azure-openai-responses-BEfdv0qd.js} +1 -1
  4. package/dist/assets/{google-BLE_Gcd1.js → google-C2y985rW.js} +1 -1
  5. package/dist/assets/{google-vertex-6_sIZLVc.js → google-vertex-Jf9zNsCF.js} +1 -1
  6. package/dist/assets/{icons-Bs7OG8yi.js → icons-BVM5--R9.js} +1 -1
  7. package/dist/assets/{index-C3bc5C3k.js → index-8Q1Ovled.js} +595 -547
  8. package/dist/assets/index-ZYbEKGUp.css +3 -0
  9. package/dist/assets/{mistral-DmZEmRkv.js → mistral-qYbgRY3z.js} +1 -1
  10. package/dist/assets/{openai-codex-responses-i_SmQGzQ.js → openai-codex-responses--aAgyYJM.js} +1 -1
  11. package/dist/assets/{openai-completions-BmmZFDDY.js → openai-completions-CHDluyXM.js} +1 -1
  12. package/dist/assets/{openai-responses-C8tPdeE9.js → openai-responses-UtRriBXu.js} +1 -1
  13. package/dist/assets/{openai-responses-shared-DchtjQNp.js → openai-responses-shared-G6WDDqJ8.js} +1 -1
  14. package/dist/assets/{openrouter-CcTv1G_v.js → openrouter-Dz9zwzUG.js} +1 -1
  15. package/dist/assets/{react-vendor-Cu-7p9CI.js → react-vendor-DAoL5p8_.js} +1 -1
  16. package/dist/index.html +4 -4
  17. package/package.json +2 -1
  18. package/server/agent-manager.mjs +102 -22
  19. package/server/approval-store.mjs +1 -1
  20. package/server/custom-commands.mjs +67 -9
  21. package/server/index.mjs +7 -0
  22. package/server/plugins/loader.mjs +56 -0
  23. package/server/plugins/manifest.mjs +174 -0
  24. package/server/plugins/registry.mjs +304 -0
  25. package/server/project-config.mjs +53 -4
  26. package/server/routes/agent.mjs +1 -16
  27. package/server/routes/plugins.mjs +63 -0
  28. package/server/routes/project.mjs +2 -0
  29. package/server/routes/tools.mjs +12 -1
  30. package/server/skills.mjs +64 -5
  31. package/server/storage.mjs +13 -6
  32. package/server/system-prompt.mjs +27 -5
  33. package/server/tool-wiring.mjs +26 -0
  34. package/dist/assets/index-C7oT9Rdw.css +0 -3
@@ -1 +1 @@
1
- import{c as e,d as t,u as n}from"./index-C3bc5C3k.js";import{t as r}from"./headers-5EYI0_pl.js";import{i,n as a,r as o,t as s}from"./github-copilot-headers-CMb2BbzT.js";import{r as c}from"./transform-messages-Dhj_4OTw.js";import{n as l}from"./openai-Bf1npfRy.js";import{t as u}from"./openai-prompt-cache-CErE62Yt.js";import{n as d,r as f,t as p}from"./openai-responses-shared-DchtjQNp.js";var m=new Set([`openai`,`openai-codex`,`opencode`]);function h(e){return e||(typeof process<`u`&&{}.PI_CACHE_RETENTION===`long`?`long`:`short`)}function g(e){return{sendSessionIdHeader:e.compat?.sendSessionIdHeader??!0,supportsLongCacheRetention:e.compat?.supportsLongCacheRetention??!0}}function _(e,t){return t===`long`&&e.supportsLongCacheRetention?`24h`:void 0}function v(e){if(e instanceof Error){let t=e.status,n=typeof t==`number`?t:void 0;return n===void 0?e.message:`OpenAI API error (${n}): ${e.message}`}try{return JSON.stringify(e)}catch{return String(e)}}var y=(n,i,a)=>{let o=new e;return(async()=>{let e={role:`assistant`,content:[],api:n.api,provider:n.provider,model:n.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:`stop`,timestamp:Date.now()};try{let s=a?.apiKey||t(n.provider)||``,c=h(a?.cacheRetention)===`none`?void 0:a?.sessionId,l=x(n,i,s,a?.headers,c),u=S(n,i,a),d=await a?.onPayload?.(u,n);d!==void 0&&(u=d);let p={...a?.signal?{signal:a.signal}:{},...a?.timeoutMs===void 0?{}:{timeout:a.timeoutMs},...a?.maxRetries===void 0?{}:{maxRetries:a.maxRetries}},{data:m,response:g}=await l.responses.create(u,p).withResponse();if(await a?.onResponse?.({status:g.status,headers:r(g.headers)},n),o.push({type:`start`,partial:e}),await f(m,e,o,n,{serviceTier:a?.serviceTier,applyServiceTierPricing:(e,t)=>w(e,t,n)}),a?.signal?.aborted)throw Error(`Request was aborted`);if(e.stopReason===`aborted`||e.stopReason===`error`)throw Error(`An unknown error occurred`);o.push({type:`done`,reason:e.stopReason,message:e}),o.end()}catch(t){for(let t of e.content)delete t.index,delete t.partialJson;e.stopReason=a?.signal?.aborted?`aborted`:`error`,e.errorMessage=v(t),o.push({type:`error`,reason:e.stopReason,error:e}),o.end()}})(),o},b=(e,r,i)=>{let a=i?.apiKey||t(e.provider);if(!a)throw Error(`No API key for provider: ${e.provider}`);let o=c(e,i,a),s=i?.reasoning?n(e,i.reasoning):void 0,l=s===`off`?void 0:s;return y(e,r,{...o,reasoningEffort:l})};function x(e,t,n,r,c){if(!n){if(!{}.OPENAI_API_KEY)throw Error(`OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.`);n={}.OPENAI_API_KEY}let u=g(e),d={...e.headers};if(e.provider===`github-copilot`){let e=a(t.messages),n=s({messages:t.messages,hasImages:e});Object.assign(d,n)}c&&(u.sendSessionIdHeader&&(d.session_id=c),d[`x-client-request-id`]=c),r&&Object.assign(d,r);let f=e.provider===`cloudflare-ai-gateway`?{...d,Authorization:d.Authorization??null,"cf-aig-authorization":`Bearer ${n}`}:d;return new l({apiKey:n,baseURL:o(e.provider)?i(e):e.baseUrl,dangerouslyAllowBrowser:!0,defaultHeaders:f})}function S(e,t,n){let r=p(e,t,m),i=h(n?.cacheRetention),a=g(e),o={model:e.id,input:r,stream:!0,prompt_cache_key:i===`none`?void 0:u(n?.sessionId),prompt_cache_retention:_(a,i),store:!1};return n?.maxTokens&&(o.max_output_tokens=n?.maxTokens),n?.temperature!==void 0&&(o.temperature=n?.temperature),n?.serviceTier!==void 0&&(o.service_tier=n.serviceTier),t.tools&&t.tools.length>0&&(o.tools=d(t.tools)),e.reasoning&&(n?.reasoningEffort||n?.reasoningSummary?(o.reasoning={effort:n?.reasoningEffort?e.thinkingLevelMap?.[n.reasoningEffort]??n.reasoningEffort:`medium`,summary:n?.reasoningSummary||`auto`},o.include=[`reasoning.encrypted_content`]):e.provider!==`github-copilot`&&e.thinkingLevelMap?.off!==null&&(o.reasoning={effort:e.thinkingLevelMap?.off??`none`})),o}function C(e,t){switch(t){case`flex`:return .5;case`priority`:return e.id===`gpt-5.5`?2.5:2;default:return 1}}function w(e,t,n){let r=C(n,t);r!==1&&(e.cost.input*=r,e.cost.output*=r,e.cost.cacheRead*=r,e.cost.cacheWrite*=r,e.cost.total=e.cost.input+e.cost.output+e.cost.cacheRead+e.cost.cacheWrite)}export{y as streamOpenAIResponses,b as streamSimpleOpenAIResponses};
1
+ import{c as e,d as t,u as n}from"./index-8Q1Ovled.js";import{t as r}from"./headers-5EYI0_pl.js";import{i,n as a,r as o,t as s}from"./github-copilot-headers-CMb2BbzT.js";import{r as c}from"./transform-messages-Dhj_4OTw.js";import{n as l}from"./openai-Bf1npfRy.js";import{t as u}from"./openai-prompt-cache-CErE62Yt.js";import{n as d,r as f,t as p}from"./openai-responses-shared-G6WDDqJ8.js";var m=new Set([`openai`,`openai-codex`,`opencode`]);function h(e){return e||(typeof process<`u`&&{}.PI_CACHE_RETENTION===`long`?`long`:`short`)}function g(e){return{sendSessionIdHeader:e.compat?.sendSessionIdHeader??!0,supportsLongCacheRetention:e.compat?.supportsLongCacheRetention??!0}}function _(e,t){return t===`long`&&e.supportsLongCacheRetention?`24h`:void 0}function v(e){if(e instanceof Error){let t=e.status,n=typeof t==`number`?t:void 0;return n===void 0?e.message:`OpenAI API error (${n}): ${e.message}`}try{return JSON.stringify(e)}catch{return String(e)}}var y=(n,i,a)=>{let o=new e;return(async()=>{let e={role:`assistant`,content:[],api:n.api,provider:n.provider,model:n.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:`stop`,timestamp:Date.now()};try{let s=a?.apiKey||t(n.provider)||``,c=h(a?.cacheRetention)===`none`?void 0:a?.sessionId,l=x(n,i,s,a?.headers,c),u=S(n,i,a),d=await a?.onPayload?.(u,n);d!==void 0&&(u=d);let p={...a?.signal?{signal:a.signal}:{},...a?.timeoutMs===void 0?{}:{timeout:a.timeoutMs},...a?.maxRetries===void 0?{}:{maxRetries:a.maxRetries}},{data:m,response:g}=await l.responses.create(u,p).withResponse();if(await a?.onResponse?.({status:g.status,headers:r(g.headers)},n),o.push({type:`start`,partial:e}),await f(m,e,o,n,{serviceTier:a?.serviceTier,applyServiceTierPricing:(e,t)=>w(e,t,n)}),a?.signal?.aborted)throw Error(`Request was aborted`);if(e.stopReason===`aborted`||e.stopReason===`error`)throw Error(`An unknown error occurred`);o.push({type:`done`,reason:e.stopReason,message:e}),o.end()}catch(t){for(let t of e.content)delete t.index,delete t.partialJson;e.stopReason=a?.signal?.aborted?`aborted`:`error`,e.errorMessage=v(t),o.push({type:`error`,reason:e.stopReason,error:e}),o.end()}})(),o},b=(e,r,i)=>{let a=i?.apiKey||t(e.provider);if(!a)throw Error(`No API key for provider: ${e.provider}`);let o=c(e,i,a),s=i?.reasoning?n(e,i.reasoning):void 0,l=s===`off`?void 0:s;return y(e,r,{...o,reasoningEffort:l})};function x(e,t,n,r,c){if(!n){if(!{}.OPENAI_API_KEY)throw Error(`OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.`);n={}.OPENAI_API_KEY}let u=g(e),d={...e.headers};if(e.provider===`github-copilot`){let e=a(t.messages),n=s({messages:t.messages,hasImages:e});Object.assign(d,n)}c&&(u.sendSessionIdHeader&&(d.session_id=c),d[`x-client-request-id`]=c),r&&Object.assign(d,r);let f=e.provider===`cloudflare-ai-gateway`?{...d,Authorization:d.Authorization??null,"cf-aig-authorization":`Bearer ${n}`}:d;return new l({apiKey:n,baseURL:o(e.provider)?i(e):e.baseUrl,dangerouslyAllowBrowser:!0,defaultHeaders:f})}function S(e,t,n){let r=p(e,t,m),i=h(n?.cacheRetention),a=g(e),o={model:e.id,input:r,stream:!0,prompt_cache_key:i===`none`?void 0:u(n?.sessionId),prompt_cache_retention:_(a,i),store:!1};return n?.maxTokens&&(o.max_output_tokens=n?.maxTokens),n?.temperature!==void 0&&(o.temperature=n?.temperature),n?.serviceTier!==void 0&&(o.service_tier=n.serviceTier),t.tools&&t.tools.length>0&&(o.tools=d(t.tools)),e.reasoning&&(n?.reasoningEffort||n?.reasoningSummary?(o.reasoning={effort:n?.reasoningEffort?e.thinkingLevelMap?.[n.reasoningEffort]??n.reasoningEffort:`medium`,summary:n?.reasoningSummary||`auto`},o.include=[`reasoning.encrypted_content`]):e.provider!==`github-copilot`&&e.thinkingLevelMap?.off!==null&&(o.reasoning={effort:e.thinkingLevelMap?.off??`none`})),o}function C(e,t){switch(t){case`flex`:return .5;case`priority`:return e.id===`gpt-5.5`?2.5:2;default:return 1}}function w(e,t,n){let r=C(n,t);r!==1&&(e.cost.input*=r,e.cost.output*=r,e.cost.cacheRead*=r,e.cost.cacheWrite*=r,e.cost.total=e.cost.input+e.cost.output+e.cost.cacheRead+e.cost.cacheWrite)}export{y as streamOpenAIResponses,b as streamSimpleOpenAIResponses};
@@ -1,4 +1,4 @@
1
- import{l as e,r as t}from"./index-C3bc5C3k.js";import{t as n}from"./sanitize-unicode-BhyPmlyt.js";import{t as r}from"./transform-messages-Dhj_4OTw.js";import{t as i}from"./hash-kZ2KD_no.js";function a(e,t){let n={v:1,id:e};return t&&(n.phase=t),JSON.stringify(n)}function o(e){if(e){if(e.startsWith(`{`))try{let t=JSON.parse(e);if(t.v===1&&typeof t.id==`string`)return t.phase===`commentary`||t.phase===`final_answer`?{id:t.id,phase:t.phase}:{id:t.id}}catch{}return{id:e}}}function s(e,t,a,s){let c=[],l=e=>{let t=e.replace(/[^a-zA-Z0-9_-]/g,`_`);return(t.length>64?t.slice(0,64):t).replace(/_+$/,``)},u=e=>{let t=`fc_${i(e)}`;return t.length>64?t.slice(0,64):t},d=r(t.messages,e,(t,n,r)=>{if(!a.has(e.provider)||!t.includes(`|`))return l(t);let[i,o]=t.split(`|`),s=l(i),c=r.provider!==e.provider||r.api!==e.api?u(o):l(o);return c.startsWith(`fc_`)||(c=l(`fc_${c}`)),`${s}|${c}`});if((s?.includeSystemPrompt??!0)&&t.systemPrompt){let r=e.reasoning?`developer`:`system`;c.push({role:r,content:n(t.systemPrompt)})}let f=0;for(let t of d){if(t.role===`user`)if(typeof t.content==`string`)c.push({role:`user`,content:[{type:`input_text`,text:n(t.content)}]});else{let e=t.content.map(e=>e.type===`text`?{type:`input_text`,text:n(e.text)}:{type:`input_image`,detail:`auto`,image_url:`data:${e.mimeType};base64,${e.data}`});if(e.length===0)continue;c.push({role:`user`,content:e})}else if(t.role===`assistant`){let r=[],a=t,s=a.model!==e.id&&a.provider===e.provider&&a.api===e.api;for(let e of t.content)if(e.type===`thinking`){if(e.thinkingSignature){let t=JSON.parse(e.thinkingSignature);r.push(t)}}else if(e.type===`text`){let t=e,a=o(t.textSignature),s=a?.id;s?s.length>64&&(s=`msg_${i(s)}`):s=`msg_${f}`,r.push({type:`message`,role:`assistant`,content:[{type:`output_text`,text:n(t.text),annotations:[]}],status:`completed`,id:s,phase:a?.phase})}else if(e.type===`toolCall`){let t=e,[n,i]=t.id.split(`|`),a=i;s&&a?.startsWith(`fc_`)&&(a=void 0),r.push({type:`function_call`,id:a,call_id:n,name:t.name,arguments:JSON.stringify(t.arguments)})}if(r.length===0)continue;c.push(...r)}else if(t.role===`toolResult`){let r=t.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
1
+ import{l as e,r as t}from"./index-8Q1Ovled.js";import{t as n}from"./sanitize-unicode-BhyPmlyt.js";import{t as r}from"./transform-messages-Dhj_4OTw.js";import{t as i}from"./hash-kZ2KD_no.js";function a(e,t){let n={v:1,id:e};return t&&(n.phase=t),JSON.stringify(n)}function o(e){if(e){if(e.startsWith(`{`))try{let t=JSON.parse(e);if(t.v===1&&typeof t.id==`string`)return t.phase===`commentary`||t.phase===`final_answer`?{id:t.id,phase:t.phase}:{id:t.id}}catch{}return{id:e}}}function s(e,t,a,s){let c=[],l=e=>{let t=e.replace(/[^a-zA-Z0-9_-]/g,`_`);return(t.length>64?t.slice(0,64):t).replace(/_+$/,``)},u=e=>{let t=`fc_${i(e)}`;return t.length>64?t.slice(0,64):t},d=r(t.messages,e,(t,n,r)=>{if(!a.has(e.provider)||!t.includes(`|`))return l(t);let[i,o]=t.split(`|`),s=l(i),c=r.provider!==e.provider||r.api!==e.api?u(o):l(o);return c.startsWith(`fc_`)||(c=l(`fc_${c}`)),`${s}|${c}`});if((s?.includeSystemPrompt??!0)&&t.systemPrompt){let r=e.reasoning?`developer`:`system`;c.push({role:r,content:n(t.systemPrompt)})}let f=0;for(let t of d){if(t.role===`user`)if(typeof t.content==`string`)c.push({role:`user`,content:[{type:`input_text`,text:n(t.content)}]});else{let e=t.content.map(e=>e.type===`text`?{type:`input_text`,text:n(e.text)}:{type:`input_image`,detail:`auto`,image_url:`data:${e.mimeType};base64,${e.data}`});if(e.length===0)continue;c.push({role:`user`,content:e})}else if(t.role===`assistant`){let r=[],a=t,s=a.model!==e.id&&a.provider===e.provider&&a.api===e.api;for(let e of t.content)if(e.type===`thinking`){if(e.thinkingSignature){let t=JSON.parse(e.thinkingSignature);r.push(t)}}else if(e.type===`text`){let t=e,a=o(t.textSignature),s=a?.id;s?s.length>64&&(s=`msg_${i(s)}`):s=`msg_${f}`,r.push({type:`message`,role:`assistant`,content:[{type:`output_text`,text:n(t.text),annotations:[]}],status:`completed`,id:s,phase:a?.phase})}else if(e.type===`toolCall`){let t=e,[n,i]=t.id.split(`|`),a=i;s&&a?.startsWith(`fc_`)&&(a=void 0),r.push({type:`function_call`,id:a,call_id:n,name:t.name,arguments:JSON.stringify(t.arguments)})}if(r.length===0)continue;c.push(...r)}else if(t.role===`toolResult`){let r=t.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
2
2
  `),i=t.content.some(e=>e.type===`image`),a=r.length>0,[o]=t.toolCallId.split(`|`),s;if(i&&e.input.includes(`image`)){let e=[];a&&e.push({type:`input_text`,text:n(r)});for(let n of t.content)n.type===`image`&&e.push({type:`input_image`,detail:`auto`,image_url:`data:${n.mimeType};base64,${n.data}`});s=e}else s=n(a?r:`(see attached image)`);c.push({type:`function_call_output`,call_id:o,output:s})}f++}return c}function c(e,t){let n=t?.strict===void 0?!1:t.strict;return e.map(e=>({type:`function`,name:e.name,description:e.description,parameters:e.parameters,strict:n}))}async function l(n,r,i,o,s){let c=null,l=null,d=r.content,f=()=>d.length-1;for await(let d of n)if(d.type===`response.created`)r.responseId=d.response.id;else if(d.type===`response.output_item.added`){let e=d.item;e.type===`reasoning`?(c=e,l={type:`thinking`,thinking:``},r.content.push(l),i.push({type:`thinking_start`,contentIndex:f(),partial:r})):e.type===`message`?(c=e,l={type:`text`,text:``},r.content.push(l),i.push({type:`text_start`,contentIndex:f(),partial:r})):e.type===`function_call`&&(c=e,l={type:`toolCall`,id:`${e.call_id}|${e.id}`,name:e.name,arguments:{},partialJson:e.arguments||``},r.content.push(l),i.push({type:`toolcall_start`,contentIndex:f(),partial:r}))}else if(d.type===`response.reasoning_summary_part.added`)c&&c.type===`reasoning`&&(c.summary=c.summary||[],c.summary.push(d.part));else if(d.type===`response.reasoning_summary_text.delta`){if(c?.type===`reasoning`&&l?.type===`thinking`){c.summary=c.summary||[];let e=c.summary[c.summary.length-1];e&&(l.thinking+=d.delta,e.text+=d.delta,i.push({type:`thinking_delta`,contentIndex:f(),delta:d.delta,partial:r}))}}else if(d.type===`response.reasoning_summary_part.done`){if(c?.type===`reasoning`&&l?.type===`thinking`){c.summary=c.summary||[];let e=c.summary[c.summary.length-1];e&&(l.thinking+=`
3
3
 
4
4
  `,e.text+=`
@@ -1 +1 @@
1
- import{d as e}from"./index-C3bc5C3k.js";import{t}from"./headers-5EYI0_pl.js";import{t as n}from"./sanitize-unicode-BhyPmlyt.js";import{n as r}from"./openai-Bf1npfRy.js";var i=async(n,r,i)=>{let c={api:n.api,provider:n.provider,model:n.id,output:[],stopReason:`stop`,timestamp:Date.now()};try{let l=i?.apiKey||e(n.provider);if(!l)throw Error(`No API key available for provider: ${n.provider}`);let u=a(n,l,i?.headers),d=o(n,r),f=await i?.onPayload?.(d,n);f!==void 0&&(d=f);let p={...i?.signal?{signal:i.signal}:{},...i?.timeoutMs===void 0?{}:{timeout:i.timeoutMs},...i?.maxRetries===void 0?{}:{maxRetries:i.maxRetries}},{data:m,response:h}=await u.chat.completions.create(d,p).withResponse();await i?.onResponse?.({status:h.status,headers:t(h.headers)},n);let g=m;c.responseId=g.id,g.usage&&(c.usage=s(g.usage,n));let _=g.choices[0];if(_){let e=_.message.content;typeof e==`string`&&e.length>0&&c.output.push({type:`text`,text:e});for(let e of _.message.images??[]){let t=typeof e.image_url==`string`?e.image_url:e.image_url?.url;if(!t?.startsWith(`data:`))continue;let n=t.match(/^data:([^;]+);base64,(.+)$/);n&&c.output.push({type:`image`,mimeType:n[1],data:n[2]})}}return c}catch(e){return c.stopReason=i?.signal?.aborted?`aborted`:`error`,c.errorMessage=e instanceof Error?e.message:JSON.stringify(e),c}};function a(e,t,n){return new r({apiKey:t,baseURL:e.baseUrl,dangerouslyAllowBrowser:!0,defaultHeaders:{...e.headers,...n}})}function o(e,t){let r=t.input.map(e=>e.type===`text`?{type:`text`,text:n(e.text)}:{type:`image_url`,image_url:{url:`data:${e.mimeType};base64,${e.data}`}});return{model:e.id,messages:[{role:`user`,content:r}],stream:!1,modalities:e.output.includes(`text`)?[`image`,`text`]:[`image`]}}function s(e,t){let n=e.prompt_tokens||0,r=e.prompt_tokens_details?.cached_tokens||0,i=e.prompt_tokens_details?.cache_write_tokens||0,a=i>0?Math.max(0,r-i):r,o=Math.max(0,n-a-i),s=e.completion_tokens||0,c={input:o,output:s,cacheRead:a,cacheWrite:i,totalTokens:o+s+a+i,cost:{input:t.cost.input/1e6*o,output:t.cost.output/1e6*s,cacheRead:t.cost.cacheRead/1e6*a,cacheWrite:t.cost.cacheWrite/1e6*i,total:0}};return c.cost.total=c.cost.input+c.cost.output+c.cost.cacheRead+c.cost.cacheWrite,c}export{i as generateImagesOpenRouter};
1
+ import{d as e}from"./index-8Q1Ovled.js";import{t}from"./headers-5EYI0_pl.js";import{t as n}from"./sanitize-unicode-BhyPmlyt.js";import{n as r}from"./openai-Bf1npfRy.js";var i=async(n,r,i)=>{let c={api:n.api,provider:n.provider,model:n.id,output:[],stopReason:`stop`,timestamp:Date.now()};try{let l=i?.apiKey||e(n.provider);if(!l)throw Error(`No API key available for provider: ${n.provider}`);let u=a(n,l,i?.headers),d=o(n,r),f=await i?.onPayload?.(d,n);f!==void 0&&(d=f);let p={...i?.signal?{signal:i.signal}:{},...i?.timeoutMs===void 0?{}:{timeout:i.timeoutMs},...i?.maxRetries===void 0?{}:{maxRetries:i.maxRetries}},{data:m,response:h}=await u.chat.completions.create(d,p).withResponse();await i?.onResponse?.({status:h.status,headers:t(h.headers)},n);let g=m;c.responseId=g.id,g.usage&&(c.usage=s(g.usage,n));let _=g.choices[0];if(_){let e=_.message.content;typeof e==`string`&&e.length>0&&c.output.push({type:`text`,text:e});for(let e of _.message.images??[]){let t=typeof e.image_url==`string`?e.image_url:e.image_url?.url;if(!t?.startsWith(`data:`))continue;let n=t.match(/^data:([^;]+);base64,(.+)$/);n&&c.output.push({type:`image`,mimeType:n[1],data:n[2]})}}return c}catch(e){return c.stopReason=i?.signal?.aborted?`aborted`:`error`,c.errorMessage=e instanceof Error?e.message:JSON.stringify(e),c}};function a(e,t,n){return new r({apiKey:t,baseURL:e.baseUrl,dangerouslyAllowBrowser:!0,defaultHeaders:{...e.headers,...n}})}function o(e,t){let r=t.input.map(e=>e.type===`text`?{type:`text`,text:n(e.text)}:{type:`image_url`,image_url:{url:`data:${e.mimeType};base64,${e.data}`}});return{model:e.id,messages:[{role:`user`,content:r}],stream:!1,modalities:e.output.includes(`text`)?[`image`,`text`]:[`image`]}}function s(e,t){let n=e.prompt_tokens||0,r=e.prompt_tokens_details?.cached_tokens||0,i=e.prompt_tokens_details?.cache_write_tokens||0,a=i>0?Math.max(0,r-i):r,o=Math.max(0,n-a-i),s=e.completion_tokens||0,c={input:o,output:s,cacheRead:a,cacheWrite:i,totalTokens:o+s+a+i,cost:{input:t.cost.input/1e6*o,output:t.cost.output/1e6*s,cacheRead:t.cost.cacheRead/1e6*a,cacheWrite:t.cost.cacheWrite/1e6*i,total:0}};return c.cost.total=c.cost.input+c.cost.output+c.cost.cacheRead+c.cost.cacheWrite,c}export{i as generateImagesOpenRouter};
@@ -1,4 +1,4 @@
1
- import{t as e}from"./rolldown-runtime-CkqCuyE9.js";import{nt as t}from"./icons-Bs7OG8yi.js";var n=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0<n;){var r=n-1>>>1,a=e[r];if(0<i(a,t))e[r]=t,e[n]=a,n=r;else break a}}function n(e){return e.length===0?null:e[0]}function r(e){if(e.length===0)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;a:for(var r=0,a=e.length,o=a>>>1;r<o;){var s=2*(r+1)-1,c=e[s],l=s+1,u=e[l];if(0>i(c,n))l<a&&0>i(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(l<a&&0>i(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,D());else{var t=n(l);t!==null&&A(x,t.startTime-e)}}var S=!1,C=-1,ee=5,w=-1;function T(){return g?!0:!(e.unstable_now()-w<ee)}function E(){if(g=!1,S){var t=e.unstable_now();w=t;var i=!0;try{a:{m=!1,h&&(h=!1,v(C),C=-1),p=!0;var a=f;try{b:{for(b(t),d=n(c);d!==null&&!(d.expirationTime>t&&T());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&A(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?D():S=!1}}}var D;if(typeof y==`function`)D=function(){y(E)};else if(typeof MessageChannel<`u`){var O=new MessageChannel,k=O.port2;O.port1.onmessage=E,D=function(){k.postMessage(null)}}else D=function(){_(E,0)};function A(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125<e?console.error(`forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported`):ee=0<e?Math.floor(1e3/e):5},e.unstable_getCurrentPriorityLevel=function(){return f},e.unstable_next=function(e){switch(f){case 1:case 2:case 3:var t=3;break;default:t=f}var n=f;f=t;try{return e()}finally{f=n}},e.unstable_requestPaint=function(){g=!0},e.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=f;f=e;try{return t()}finally{f=n}},e.unstable_scheduleCallback=function(r,i,a){var o=e.unstable_now();switch(typeof a==`object`&&a?(a=a.delay,a=typeof a==`number`&&0<a?o+a:o):a=o,r){case 1:var s=-1;break;case 2:s=250;break;case 5:s=1073741823;break;case 4:s=1e4;break;default:s=5e3}return s=a+s,r={id:u++,callback:i,priorityLevel:r,startTime:a,expirationTime:s,sortIndex:-1},a>o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,A(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,D()))),r},e.unstable_shouldYield=T,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),r=e(((e,t)=>{t.exports=n()})),i=e((e=>{var n=t();function r(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function i(){}var a={d:{f:i,r:function(){throw Error(r(522))},D:i,C:i,L:i,m:i,X:i,S:i,M:i},p:0,findDOMNode:null},o=Symbol.for(`react.portal`);function s(e,t,n){var r=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:o,key:r==null?null:``+r,children:e,containerInfo:t,implementation:n}}var c=n.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function l(e,t){if(e===`font`)return``;if(typeof t==`string`)return t===`use-credentials`?t:``}e.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,e.createPortal=function(e,t){var n=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)throw Error(r(299));return s(e,t,null,n)},e.flushSync=function(e){var t=c.T,n=a.p;try{if(c.T=null,a.p=2,e)return e()}finally{c.T=t,a.p=n,a.d.f()}},e.preconnect=function(e,t){typeof e==`string`&&(t?(t=t.crossOrigin,t=typeof t==`string`?t===`use-credentials`?t:``:void 0):t=null,a.d.C(e,t))},e.prefetchDNS=function(e){typeof e==`string`&&a.d.D(e)},e.preinit=function(e,t){if(typeof e==`string`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin),i=typeof t.integrity==`string`?t.integrity:void 0,o=typeof t.fetchPriority==`string`?t.fetchPriority:void 0;n===`style`?a.d.S(e,typeof t.precedence==`string`?t.precedence:void 0,{crossOrigin:r,integrity:i,fetchPriority:o}):n===`script`&&a.d.X(e,{crossOrigin:r,integrity:i,fetchPriority:o,nonce:typeof t.nonce==`string`?t.nonce:void 0})}},e.preinitModule=function(e,t){if(typeof e==`string`)if(typeof t==`object`&&t){if(t.as==null||t.as===`script`){var n=l(t.as,t.crossOrigin);a.d.M(e,{crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0})}}else t??a.d.M(e)},e.preload=function(e,t){if(typeof e==`string`&&typeof t==`object`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin);a.d.L(e,n,{crossOrigin:r,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,type:typeof t.type==`string`?t.type:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0,referrerPolicy:typeof t.referrerPolicy==`string`?t.referrerPolicy:void 0,imageSrcSet:typeof t.imageSrcSet==`string`?t.imageSrcSet:void 0,imageSizes:typeof t.imageSizes==`string`?t.imageSizes:void 0,media:typeof t.media==`string`?t.media:void 0})}},e.preloadModule=function(e,t){if(typeof e==`string`)if(t){var n=l(t.as,t.crossOrigin);a.d.m(e,{as:typeof t.as==`string`&&t.as!==`script`?t.as:void 0,crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0})}else a.d.m(e)},e.requestFormReset=function(e){a.d.r(e)},e.unstable_batchedUpdates=function(e,t){return e(t)},e.useFormState=function(e,t,n){return c.H.useFormState(e,t,n)},e.useFormStatus=function(){return c.H.useHostTransitionStatus()},e.version=`19.2.5`})),a=e(((e,t)=>{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=i()})),o=e((e=>{var n=r(),i=t(),o=a();function s(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function c(e){return!(!e||e.nodeType!==1&&e.nodeType!==9&&e.nodeType!==11)}function l(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do t=e,t.flags&4098&&(n=t.return),e=t.return;while(e)}return t.tag===3?n:null}function u(e){if(e.tag===13){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function d(e){if(e.tag===31){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function f(e){if(l(e)!==e)throw Error(s(188))}function p(e){var t=e.alternate;if(!t){if(t=l(e),t===null)throw Error(s(188));return t===e?e:null}for(var n=e,r=t;;){var i=n.return;if(i===null)break;var a=i.alternate;if(a===null){if(r=i.return,r!==null){n=r;continue}break}if(i.child===a.child){for(a=i.child;a;){if(a===n)return f(i),e;if(a===r)return f(i),t;a=a.sibling}throw Error(s(188))}if(n.return!==r.return)n=i,r=a;else{for(var o=!1,c=i.child;c;){if(c===n){o=!0,n=i,r=a;break}if(c===r){o=!0,r=i,n=a;break}c=c.sibling}if(!o){for(c=a.child;c;){if(c===n){o=!0,n=a,r=i;break}if(c===r){o=!0,r=a,n=i;break}c=c.sibling}if(!o)throw Error(s(189))}}if(n.alternate!==r)throw Error(s(190))}if(n.tag!==3)throw Error(s(188));return n.stateNode.current===n?e:t}function m(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e;for(e=e.child;e!==null;){if(t=m(e),t!==null)return t;e=e.sibling}return null}var h=Object.assign,g=Symbol.for(`react.element`),_=Symbol.for(`react.transitional.element`),v=Symbol.for(`react.portal`),y=Symbol.for(`react.fragment`),b=Symbol.for(`react.strict_mode`),x=Symbol.for(`react.profiler`),S=Symbol.for(`react.consumer`),C=Symbol.for(`react.context`),ee=Symbol.for(`react.forward_ref`),w=Symbol.for(`react.suspense`),T=Symbol.for(`react.suspense_list`),E=Symbol.for(`react.memo`),D=Symbol.for(`react.lazy`),O=Symbol.for(`react.activity`),k=Symbol.for(`react.memo_cache_sentinel`),A=Symbol.iterator;function te(e){return typeof e!=`object`||!e?null:(e=A&&e[A]||e[`@@iterator`],typeof e==`function`?e:null)}var j=Symbol.for(`react.client.reference`);function M(e){if(e==null)return null;if(typeof e==`function`)return e.$$typeof===j?null:e.displayName||e.name||null;if(typeof e==`string`)return e;switch(e){case y:return`Fragment`;case x:return`Profiler`;case b:return`StrictMode`;case w:return`Suspense`;case T:return`SuspenseList`;case O:return`Activity`}if(typeof e==`object`)switch(e.$$typeof){case v:return`Portal`;case C:return e.displayName||`Context`;case S:return(e._context.displayName||`Context`)+`.Consumer`;case ee:var t=e.render;return e=e.displayName,e||=(e=t.displayName||t.name||``,e===``?`ForwardRef`:`ForwardRef(`+e+`)`),e;case E:return t=e.displayName||null,t===null?M(e.type)||`Memo`:t;case D:t=e._payload,e=e._init;try{return M(e(t))}catch{}}return null}var N=Array.isArray,P=i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,F=o.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,ne={pending:!1,data:null,method:null,action:null},I=[],L=-1;function re(e){return{current:e}}function R(e){0>L||(e.current=I[L],I[L]=null,L--)}function z(e,t){L++,I[L]=e.current,e.current=t}var B=re(null),ie=re(null),ae=re(null),oe=re(null);function se(e,t){switch(z(ae,t),z(ie,e),z(B,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}R(B),z(B,e)}function ce(){R(B),R(ie),R(ae)}function V(e){e.memoizedState!==null&&z(oe,e);var t=B.current,n=Hd(t,e.type);t!==n&&(z(ie,e),z(B,n))}function le(e){ie.current===e&&(R(B),R(ie)),oe.current===e&&(R(oe),Qf._currentValue=ne)}var H,ue;function de(e){if(H===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);H=t&&t[1]||``,ue=-1<e.stack.indexOf(`
1
+ import{t as e}from"./rolldown-runtime-CkqCuyE9.js";import{it as t}from"./icons-BVM5--R9.js";var n=e((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0<n;){var r=n-1>>>1,a=e[r];if(0<i(a,t))e[r]=t,e[n]=a,n=r;else break a}}function n(e){return e.length===0?null:e[0]}function r(e){if(e.length===0)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;a:for(var r=0,a=e.length,o=a>>>1;r<o;){var s=2*(r+1)-1,c=e[s],l=s+1,u=e[l];if(0>i(c,n))l<a&&0>i(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(l<a&&0>i(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,D());else{var t=n(l);t!==null&&A(x,t.startTime-e)}}var S=!1,C=-1,ee=5,w=-1;function T(){return g?!0:!(e.unstable_now()-w<ee)}function E(){if(g=!1,S){var t=e.unstable_now();w=t;var i=!0;try{a:{m=!1,h&&(h=!1,v(C),C=-1),p=!0;var a=f;try{b:{for(b(t),d=n(c);d!==null&&!(d.expirationTime>t&&T());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&A(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?D():S=!1}}}var D;if(typeof y==`function`)D=function(){y(E)};else if(typeof MessageChannel<`u`){var O=new MessageChannel,k=O.port2;O.port1.onmessage=E,D=function(){k.postMessage(null)}}else D=function(){_(E,0)};function A(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125<e?console.error(`forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported`):ee=0<e?Math.floor(1e3/e):5},e.unstable_getCurrentPriorityLevel=function(){return f},e.unstable_next=function(e){switch(f){case 1:case 2:case 3:var t=3;break;default:t=f}var n=f;f=t;try{return e()}finally{f=n}},e.unstable_requestPaint=function(){g=!0},e.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=f;f=e;try{return t()}finally{f=n}},e.unstable_scheduleCallback=function(r,i,a){var o=e.unstable_now();switch(typeof a==`object`&&a?(a=a.delay,a=typeof a==`number`&&0<a?o+a:o):a=o,r){case 1:var s=-1;break;case 2:s=250;break;case 5:s=1073741823;break;case 4:s=1e4;break;default:s=5e3}return s=a+s,r={id:u++,callback:i,priorityLevel:r,startTime:a,expirationTime:s,sortIndex:-1},a>o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,A(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,D()))),r},e.unstable_shouldYield=T,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),r=e(((e,t)=>{t.exports=n()})),i=e((e=>{var n=t();function r(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function i(){}var a={d:{f:i,r:function(){throw Error(r(522))},D:i,C:i,L:i,m:i,X:i,S:i,M:i},p:0,findDOMNode:null},o=Symbol.for(`react.portal`);function s(e,t,n){var r=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:o,key:r==null?null:``+r,children:e,containerInfo:t,implementation:n}}var c=n.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function l(e,t){if(e===`font`)return``;if(typeof t==`string`)return t===`use-credentials`?t:``}e.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,e.createPortal=function(e,t){var n=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)throw Error(r(299));return s(e,t,null,n)},e.flushSync=function(e){var t=c.T,n=a.p;try{if(c.T=null,a.p=2,e)return e()}finally{c.T=t,a.p=n,a.d.f()}},e.preconnect=function(e,t){typeof e==`string`&&(t?(t=t.crossOrigin,t=typeof t==`string`?t===`use-credentials`?t:``:void 0):t=null,a.d.C(e,t))},e.prefetchDNS=function(e){typeof e==`string`&&a.d.D(e)},e.preinit=function(e,t){if(typeof e==`string`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin),i=typeof t.integrity==`string`?t.integrity:void 0,o=typeof t.fetchPriority==`string`?t.fetchPriority:void 0;n===`style`?a.d.S(e,typeof t.precedence==`string`?t.precedence:void 0,{crossOrigin:r,integrity:i,fetchPriority:o}):n===`script`&&a.d.X(e,{crossOrigin:r,integrity:i,fetchPriority:o,nonce:typeof t.nonce==`string`?t.nonce:void 0})}},e.preinitModule=function(e,t){if(typeof e==`string`)if(typeof t==`object`&&t){if(t.as==null||t.as===`script`){var n=l(t.as,t.crossOrigin);a.d.M(e,{crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0})}}else t??a.d.M(e)},e.preload=function(e,t){if(typeof e==`string`&&typeof t==`object`&&t&&typeof t.as==`string`){var n=t.as,r=l(n,t.crossOrigin);a.d.L(e,n,{crossOrigin:r,integrity:typeof t.integrity==`string`?t.integrity:void 0,nonce:typeof t.nonce==`string`?t.nonce:void 0,type:typeof t.type==`string`?t.type:void 0,fetchPriority:typeof t.fetchPriority==`string`?t.fetchPriority:void 0,referrerPolicy:typeof t.referrerPolicy==`string`?t.referrerPolicy:void 0,imageSrcSet:typeof t.imageSrcSet==`string`?t.imageSrcSet:void 0,imageSizes:typeof t.imageSizes==`string`?t.imageSizes:void 0,media:typeof t.media==`string`?t.media:void 0})}},e.preloadModule=function(e,t){if(typeof e==`string`)if(t){var n=l(t.as,t.crossOrigin);a.d.m(e,{as:typeof t.as==`string`&&t.as!==`script`?t.as:void 0,crossOrigin:n,integrity:typeof t.integrity==`string`?t.integrity:void 0})}else a.d.m(e)},e.requestFormReset=function(e){a.d.r(e)},e.unstable_batchedUpdates=function(e,t){return e(t)},e.useFormState=function(e,t,n){return c.H.useFormState(e,t,n)},e.useFormStatus=function(){return c.H.useHostTransitionStatus()},e.version=`19.2.5`})),a=e(((e,t)=>{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=i()})),o=e((e=>{var n=r(),i=t(),o=a();function s(e){var t=`https://react.dev/errors/`+e;if(1<arguments.length){t+=`?args[]=`+encodeURIComponent(arguments[1]);for(var n=2;n<arguments.length;n++)t+=`&args[]=`+encodeURIComponent(arguments[n])}return`Minified React error #`+e+`; visit `+t+` for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`}function c(e){return!(!e||e.nodeType!==1&&e.nodeType!==9&&e.nodeType!==11)}function l(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do t=e,t.flags&4098&&(n=t.return),e=t.return;while(e)}return t.tag===3?n:null}function u(e){if(e.tag===13){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function d(e){if(e.tag===31){var t=e.memoizedState;if(t===null&&(e=e.alternate,e!==null&&(t=e.memoizedState)),t!==null)return t.dehydrated}return null}function f(e){if(l(e)!==e)throw Error(s(188))}function p(e){var t=e.alternate;if(!t){if(t=l(e),t===null)throw Error(s(188));return t===e?e:null}for(var n=e,r=t;;){var i=n.return;if(i===null)break;var a=i.alternate;if(a===null){if(r=i.return,r!==null){n=r;continue}break}if(i.child===a.child){for(a=i.child;a;){if(a===n)return f(i),e;if(a===r)return f(i),t;a=a.sibling}throw Error(s(188))}if(n.return!==r.return)n=i,r=a;else{for(var o=!1,c=i.child;c;){if(c===n){o=!0,n=i,r=a;break}if(c===r){o=!0,r=i,n=a;break}c=c.sibling}if(!o){for(c=a.child;c;){if(c===n){o=!0,n=a,r=i;break}if(c===r){o=!0,r=a,n=i;break}c=c.sibling}if(!o)throw Error(s(189))}}if(n.alternate!==r)throw Error(s(190))}if(n.tag!==3)throw Error(s(188));return n.stateNode.current===n?e:t}function m(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e;for(e=e.child;e!==null;){if(t=m(e),t!==null)return t;e=e.sibling}return null}var h=Object.assign,g=Symbol.for(`react.element`),_=Symbol.for(`react.transitional.element`),v=Symbol.for(`react.portal`),y=Symbol.for(`react.fragment`),b=Symbol.for(`react.strict_mode`),x=Symbol.for(`react.profiler`),S=Symbol.for(`react.consumer`),C=Symbol.for(`react.context`),ee=Symbol.for(`react.forward_ref`),w=Symbol.for(`react.suspense`),T=Symbol.for(`react.suspense_list`),E=Symbol.for(`react.memo`),D=Symbol.for(`react.lazy`),O=Symbol.for(`react.activity`),k=Symbol.for(`react.memo_cache_sentinel`),A=Symbol.iterator;function te(e){return typeof e!=`object`||!e?null:(e=A&&e[A]||e[`@@iterator`],typeof e==`function`?e:null)}var j=Symbol.for(`react.client.reference`);function M(e){if(e==null)return null;if(typeof e==`function`)return e.$$typeof===j?null:e.displayName||e.name||null;if(typeof e==`string`)return e;switch(e){case y:return`Fragment`;case x:return`Profiler`;case b:return`StrictMode`;case w:return`Suspense`;case T:return`SuspenseList`;case O:return`Activity`}if(typeof e==`object`)switch(e.$$typeof){case v:return`Portal`;case C:return e.displayName||`Context`;case S:return(e._context.displayName||`Context`)+`.Consumer`;case ee:var t=e.render;return e=e.displayName,e||=(e=t.displayName||t.name||``,e===``?`ForwardRef`:`ForwardRef(`+e+`)`),e;case E:return t=e.displayName||null,t===null?M(e.type)||`Memo`:t;case D:t=e._payload,e=e._init;try{return M(e(t))}catch{}}return null}var N=Array.isArray,P=i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,F=o.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,ne={pending:!1,data:null,method:null,action:null},I=[],L=-1;function re(e){return{current:e}}function R(e){0>L||(e.current=I[L],I[L]=null,L--)}function z(e,t){L++,I[L]=e.current,e.current=t}var B=re(null),ie=re(null),ae=re(null),oe=re(null);function se(e,t){switch(z(ae,t),z(ie,e),z(B,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}R(B),z(B,e)}function ce(){R(B),R(ie),R(ae)}function V(e){e.memoizedState!==null&&z(oe,e);var t=B.current,n=Hd(t,e.type);t!==n&&(z(ie,e),z(B,n))}function le(e){ie.current===e&&(R(B),R(ie)),oe.current===e&&(R(oe),Qf._currentValue=ne)}var H,ue;function de(e){if(H===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);H=t&&t[1]||``,ue=-1<e.stack.indexOf(`
2
2
  at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
3
3
  `+H+e+ue}var fe=!1;function pe(e,t){if(!e||fe)return``;fe=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,`props`,{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}e.call(n.prototype)}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,`name`,{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
4
4
  `),l=s.split(`
package/dist/index.html CHANGED
@@ -11,13 +11,13 @@
11
11
  <meta name="apple-mobile-web-app-title" content="QuickForge" />
12
12
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
13
13
  <title>速构 QuickForge</title>
14
- <script type="module" crossorigin src="/assets/index-C3bc5C3k.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-8Q1Ovled.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CkqCuyE9.js">
16
16
  <link rel="modulepreload" crossorigin href="/assets/lit-vendor-Dr3cpBGF.js">
17
17
  <link rel="modulepreload" crossorigin href="/assets/css-utils-rkE68RDy.js">
18
- <link rel="modulepreload" crossorigin href="/assets/icons-Bs7OG8yi.js">
19
- <link rel="modulepreload" crossorigin href="/assets/react-vendor-Cu-7p9CI.js">
20
- <link rel="stylesheet" crossorigin href="/assets/index-C7oT9Rdw.css">
18
+ <link rel="modulepreload" crossorigin href="/assets/icons-BVM5--R9.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/react-vendor-DAoL5p8_.js">
20
+ <link rel="stylesheet" crossorigin href="/assets/index-ZYbEKGUp.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shawnstack/quickforge",
3
- "version": "1.3.25",
3
+ "version": "1.3.26",
4
4
  "description": "AI chat application with YOLO-mode local workspace tools. React + Vite + Tailwind CSS frontend, local Node.js storage server.",
5
5
  "keywords": [
6
6
  "ai",
@@ -36,6 +36,7 @@
36
36
  "bin",
37
37
  "server",
38
38
  "skills",
39
+ "plugins",
39
40
  "dist",
40
41
  "README.md",
41
42
  "LICENSE",
@@ -5,6 +5,7 @@ import { streamSimpleWithAiHttpLogging } from './ai-http-logger.mjs'
5
5
  import { loadSkillToolContext, abortRunningCommand } from './tools/index.mjs'
6
6
  import { createSkillTools, workspaceTools } from './tools/definitions.mjs'
7
7
  import { createMcpToolDefinitions, isMcpToolName } from './mcp/registry.mjs'
8
+ import { createPluginToolDefinitions, isPluginToolName } from './plugins/registry.mjs'
8
9
  import {
9
10
  composeSubagentSystemPrompt,
10
11
  formatSubagentTask,
@@ -31,7 +32,7 @@ import {
31
32
  resolveCustomCommandInvocation,
32
33
  } from './custom-commands.mjs'
33
34
  import { omitDetailsForLlm, serverConvertToLlm, messageText, lastAssistantText } from './message-converters.mjs'
34
- import { isPlainObject, mergeQuickForgeTiming, wrapToolDefinition, wrapMcpToolDefinition, sessionSkillsContext } from './tool-wiring.mjs'
35
+ import { isPlainObject, mergeQuickForgeTiming, wrapToolDefinition, wrapMcpToolDefinition, wrapPluginToolDefinition, sessionSkillsContext } from './tool-wiring.mjs'
35
36
  import {
36
37
  APPROVAL_TIMEOUT_MS,
37
38
  commandRestrictedTools,
@@ -71,6 +72,7 @@ async function createServerTools(projectId, projectContext, skillsContext, inclu
71
72
  allowedToolNames = null,
72
73
  includeSubagentTool = true,
73
74
  includeMcpTools = true,
75
+ includePluginTools = true,
74
76
  parentSessionId = null,
75
77
  } = options
76
78
  const allowedTools = allowedToolNames ? new Set(allowedToolNames) : null
@@ -103,6 +105,11 @@ async function createServerTools(projectId, projectContext, skillsContext, inclu
103
105
  tools.push(...mcpTools.filter(isAllowed).map((definition) => wrapMcpToolDefinition(definition, toolPermissions)))
104
106
  }
105
107
 
108
+ if (includePluginTools) {
109
+ const pluginTools = await createPluginToolDefinitions(projectContext)
110
+ tools.push(...pluginTools.filter(isAllowed).map((definition) => wrapPluginToolDefinition(definition, toolContext, toolPermissions)))
111
+ }
112
+
106
113
  return tools
107
114
  }
108
115
 
@@ -553,6 +560,14 @@ async function resolveCommandState(session, userMessage) {
553
560
  commandName: 'plan',
554
561
  }
555
562
  }
563
+ if (internalResponse?.review) {
564
+ return {
565
+ userMessage,
566
+ commandPrompt: formatReviewCommandPrompt(internalResponse.args),
567
+ permissions: { allowEdit: false, allowCommands: true, allowSubagents: false },
568
+ commandName: 'review',
569
+ }
570
+ }
556
571
 
557
572
  if (!session.projectContext?.workspaceRoot) return { userMessage }
558
573
 
@@ -599,6 +614,33 @@ ${taskText}
599
614
  </plan_command_invocation>`
600
615
  }
601
616
 
617
+ function formatReviewCommandPrompt(scope) {
618
+ const scopeText = String(scope || '').trim() || '(none; review the repository changes that appear relevant for a pre-commit check)'
619
+ return `<review_command_invocation name="review">
620
+ This /review command applies only to the current user request. Perform a pre-commit self-review of the code that is about to be committed.
621
+
622
+ Rules for this turn:
623
+ - Do not modify files.
624
+ - Do not create files.
625
+ - Do not stage, unstage, commit, tag, push, publish, or otherwise change repository state.
626
+ - Do not use write_file or edit_file.
627
+ - You may use read-only tools and shell commands to inspect the workspace and run validation checks.
628
+ - Do not use subagents; perform the review directly in this turn.
629
+ - Prefer safe inspection commands such as git status, git diff, git diff --cached, and targeted lint/build/test commands.
630
+ - Treat command output as evidence; distinguish confirmed issues from risks or suggestions.
631
+
632
+ Review checklist:
633
+ 1. Identify the changes under review, prioritizing staged changes when present and otherwise unstaged working tree changes.
634
+ 2. Look for correctness bugs, regressions, edge cases, missing error handling, security or privacy risks, and unintended side effects.
635
+ 3. Check whether tests, lint/build validation, or documentation/wiki updates are needed.
636
+ 4. Call out any risky commands that should not be run automatically.
637
+ 5. Output a concise review with severity, file/area, evidence, and recommended next steps. If no blocking issues are found, say so clearly.
638
+
639
+ User review scope or focus:
640
+ ${scopeText}
641
+ </review_command_invocation>`
642
+ }
643
+
602
644
  async function runSubagent(parentSession, params, parentSignal, onUpdate) {
603
645
  const profile = await getAgentProfile(params?.subagent)
604
646
  if (!profile || !profile.enabledAsSubagent) {
@@ -805,6 +847,57 @@ function applyActiveCommandPrompt(messages, commandPrompt) {
805
847
  return messages
806
848
  }
807
849
 
850
+ function textFromMessageContent(content) {
851
+ if (typeof content === 'string') return content
852
+ if (Array.isArray(content)) {
853
+ return content.filter((block) => block?.type === 'text').map((block) => block.text ?? '').join('\n')
854
+ }
855
+ return ''
856
+ }
857
+
858
+ function selectedCapabilityPrompt(capabilities) {
859
+ if (!Array.isArray(capabilities) || capabilities.length === 0) return null
860
+ const normalized = capabilities
861
+ .filter((capability) => capability && typeof capability === 'object')
862
+ .map((capability) => ({
863
+ type: String(capability.type || '').slice(0, 32),
864
+ pluginName: String(capability.pluginName || '').slice(0, 120),
865
+ name: String(capability.name || '').slice(0, 120),
866
+ label: String(capability.label || capability.name || '').slice(0, 160),
867
+ description: String(capability.description || '').slice(0, 400),
868
+ }))
869
+ .filter((capability) => capability.type && capability.pluginName && capability.name)
870
+ .slice(0, 4)
871
+ if (normalized.length === 0) return null
872
+
873
+ const lines = normalized.map((capability) => {
874
+ const toolHint = capability.type === 'tool' ? ` Tool name: plugin__${capability.pluginName}__${capability.name}.` : ''
875
+ const description = capability.description ? ` Description: ${capability.description}` : ''
876
+ return `- ${capability.label} (${capability.type}, plugin: ${capability.pluginName}, name: ${capability.name}).${toolHint}${description}`
877
+ }).join('\n')
878
+
879
+ return `The user selected the following QuickForge plugin capability mentions for this turn. Treat them as an explicit preference for routing and context. Use the selected capability when relevant, but do not force it if it is unrelated to the actual request.\n\n${lines}`
880
+ }
881
+
882
+ function applyActiveCapabilityPrompt(messages, capabilityPrompt) {
883
+ if (!capabilityPrompt) return messages
884
+
885
+ for (let index = messages.length - 1; index >= 0; index--) {
886
+ const message = messages[index]
887
+ if (message?.role !== 'user' && message?.role !== 'user-with-attachments') continue
888
+
889
+ const visibleText = textFromMessageContent(message.content)
890
+ const transformed = messages.slice()
891
+ transformed[index] = {
892
+ ...message,
893
+ content: `${capabilityPrompt}\n\nUser request:\n${visibleText}`,
894
+ }
895
+ return transformed
896
+ }
897
+
898
+ return messages
899
+ }
900
+
808
901
  function compactSummaryIndex(messages) {
809
902
  for (let index = messages.length - 1; index >= 0; index--) {
810
903
  const message = messages[index]
@@ -843,7 +936,10 @@ async function transformSessionContext(session, messages, signal) {
843
936
  }
844
937
  const transformedMessages = buildAutoCompactLoopMessages(session, messages)
845
938
  session.lastTransformedContextMessages = transformedMessages
846
- return applyActiveCommandPrompt(compactedContextMessages(transformedMessages), session?.activeCommandPrompt)
939
+ return applyActiveCapabilityPrompt(
940
+ applyActiveCommandPrompt(compactedContextMessages(transformedMessages), session?.activeCommandPrompt),
941
+ session?.activeCapabilityPrompt,
942
+ )
847
943
  }
848
944
 
849
945
  export const agentEvents = new EventEmitter()
@@ -998,7 +1094,7 @@ export async function createAgent(sessionId, config = {}) {
998
1094
  if (isSkillTool) return undefined
999
1095
  if (profileToolNames && !profileToolNames.includes(toolName)) return { block: true, reason: `Agent profile ${agentProfile.name} is not allowed to use ${toolName}.` }
1000
1096
  if (toolName === 'run_subagent') return undefined
1001
- if (isMcpToolName(toolName)) {
1097
+ if (isMcpToolName(toolName) || isPluginToolName(toolName)) {
1002
1098
  if (!currentSession?.yoloMode) return createApprovalPromise(currentSession, toolCallId, toolName, context.args)
1003
1099
  return undefined
1004
1100
  }
@@ -1296,29 +1392,11 @@ export async function rollbackSessionMessages(sessionId, rollbackMessageIndex) {
1296
1392
  return { session: getSessionState(sessionId), rollbackIndex }
1297
1393
  }
1298
1394
 
1299
- export async function replaceSessionMessages(sessionId, messages) {
1300
- const session = agentSessions.get(sessionId)
1301
- if (!session) return null
1302
- if (session.agent.state.isStreaming) {
1303
- throw Object.assign(new Error('Generation is still running. Stop it or wait until it finishes before rolling back.'), { statusCode: 409 })
1304
- }
1305
- updateSessionMessages(session, Array.isArray(messages) ? messages : [])
1306
- resetSessionCompaction(session)
1307
- session.status = 'idle'
1308
- session.finishedAt = new Date().toISOString()
1309
- await persistSession(session)
1310
- const nextMessages = session.agent.state.messages
1311
- const contextUsage = getSessionContextUsage(session)
1312
- emitSessionEvent(session, { type: 'message_end', messages: nextMessages, contextUsage })
1313
- emitSessionEvent(session, { type: 'agent_end', messages: nextMessages, contextUsage })
1314
- return getSessionState(sessionId)
1315
- }
1316
-
1317
1395
  /**
1318
1396
  * Send a user message to the agent and start the agent loop.
1319
1397
  * Returns immediately; events are streamed via the event bus.
1320
1398
  */
1321
- export async function runPrompt(sessionId, message) {
1399
+ export async function runPrompt(sessionId, message, selectedCapabilities = []) {
1322
1400
  let session = agentSessions.get(sessionId)
1323
1401
  if (!session) {
1324
1402
  session = await restoreAgent(sessionId)
@@ -1384,6 +1462,7 @@ export async function runPrompt(sessionId, message) {
1384
1462
  session.activeCommandName = commandState.commandName ?? null
1385
1463
  session.activeCommandPermissions = commandState.permissions ?? null
1386
1464
  session.activeCommandPrompt = commandState.commandPrompt ?? null
1465
+ session.activeCapabilityPrompt = selectedCapabilityPrompt(selectedCapabilities)
1387
1466
 
1388
1467
  // Fire and forget — events come through eventBus
1389
1468
  session.agent.prompt(userMessage).catch((err) => {
@@ -1397,6 +1476,7 @@ export async function runPrompt(sessionId, message) {
1397
1476
  session.activeCommandName = null
1398
1477
  session.activeCommandPermissions = null
1399
1478
  session.activeCommandPrompt = null
1479
+ session.activeCapabilityPrompt = null
1400
1480
  })
1401
1481
 
1402
1482
  return { sessionId, status: session.status }
@@ -49,7 +49,7 @@ export function commandToolPermissionError(session, toolName) {
49
49
  if (toolName === 'run_command' && permissions.allowCommands === false) {
50
50
  return `Command /${session.activeCommandName} does not allow running shell commands.`
51
51
  }
52
- if (toolName === 'run_subagent' && permissions.allowCommands === false) {
52
+ if (toolName === 'run_subagent' && (permissions.allowSubagents === false || permissions.allowCommands === false)) {
53
53
  return `Command /${session.activeCommandName} does not allow running subagents.`
54
54
  }
55
55
  if ((toolName === 'write_file' || toolName === 'edit_file') && permissions.allowEdit === false) {
@@ -1,6 +1,8 @@
1
1
  import { promises as fs } from 'node:fs'
2
2
  import path from 'node:path'
3
+ import { getEnabledPluginCommandSources } from './plugins/registry.mjs'
3
4
 
5
+ const commandsRelativeDirs = ['.claude/commands', '.opencode/commands', '.ai/commands']
4
6
  const commandsRelativeDir = '.ai/commands'
5
7
  const commandNamePattern = /^(?!.*--)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/
6
8
 
@@ -23,10 +25,9 @@ function configuredCommandDirectories(workspaceRoot, commandDir) {
23
25
  }
24
26
 
25
27
  function commandDirectories(workspaceRoot, commandDir) {
26
- const defaultDir = commandDirectory(workspaceRoot)
27
- if (!defaultDir) return []
28
+ if (!workspaceRoot) return []
28
29
 
29
- const dirs = [defaultDir]
30
+ const dirs = commandsRelativeDirs.map((dir) => path.join(path.resolve(workspaceRoot), dir))
30
31
  for (const configuredDir of configuredCommandDirectories(workspaceRoot, commandDir)) {
31
32
  if (!dirs.some((dir) => path.resolve(dir) === path.resolve(configuredDir))) {
32
33
  dirs.push(configuredDir)
@@ -102,7 +103,7 @@ function firstOptionalBoolean(...values) {
102
103
  return undefined
103
104
  }
104
105
 
105
- function commandFromFile(file, text) {
106
+ function commandFromFile(file, text, options = {}) {
106
107
  const parsed = parseFrontmatter(text)
107
108
  if (!parsed.body) return null
108
109
 
@@ -127,7 +128,9 @@ function commandFromFile(file, text) {
127
128
  ),
128
129
  body: parsed.body,
129
130
  filePath: file,
130
- relativePath: path.relative(path.dirname(path.dirname(file)), file).replace(/\\/g, '/'),
131
+ relativePath: options.relativePath || path.relative(path.dirname(path.dirname(file)), file).replace(/\\/g, '/'),
132
+ source: options.source,
133
+ pluginName: options.pluginName,
131
134
  }
132
135
  }
133
136
 
@@ -159,7 +162,7 @@ export function textFromUserMessage(message) {
159
162
  .join('\n')
160
163
  }
161
164
 
162
- async function listCommandsFromDirectory(dir) {
165
+ async function listCommandsFromDirectory(dir, options = {}) {
163
166
  let entries
164
167
  try {
165
168
  entries = await fs.readdir(dir, { withFileTypes: true })
@@ -173,7 +176,14 @@ async function listCommandsFromDirectory(dir) {
173
176
  if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.md')) continue
174
177
  const file = path.join(dir, entry.name)
175
178
  try {
176
- const command = commandFromFile(file, await fs.readFile(file, 'utf8'))
179
+ const relativePath = options.relativeRoot
180
+ ? `${options.relativeRoot}/${entry.name}`.replace(/\\/g, '/')
181
+ : undefined
182
+ const command = commandFromFile(file, await fs.readFile(file, 'utf8'), {
183
+ source: options.source,
184
+ pluginName: options.pluginName,
185
+ relativePath,
186
+ })
177
187
  if (command) commands.push(command)
178
188
  } catch (error) {
179
189
  console.warn(`Failed to load custom command ${file}:`, error.message || error)
@@ -183,9 +193,49 @@ async function listCommandsFromDirectory(dir) {
183
193
  return commands
184
194
  }
185
195
 
196
+ async function listCommandsFromFile(file, options = {}) {
197
+ if (!file.toLowerCase().endsWith('.md')) return []
198
+ try {
199
+ const command = commandFromFile(file, await fs.readFile(file, 'utf8'), options)
200
+ return command ? [command] : []
201
+ } catch (error) {
202
+ if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR' || error?.code === 'EACCES' || error?.code === 'EPERM') return []
203
+ console.warn(`Failed to load custom command ${file}:`, error.message || error)
204
+ return []
205
+ }
206
+ }
207
+
208
+ async function listCommandsFromPluginSource(source) {
209
+ const stat = await fs.stat(source.path).catch(() => null)
210
+ if (!stat) return []
211
+ const options = {
212
+ source: source.source,
213
+ pluginName: source.pluginName,
214
+ relativePath: source.relativePath,
215
+ relativeRoot: source.relativePath,
216
+ }
217
+ if (stat.isFile()) return listCommandsFromFile(source.path, options)
218
+ if (stat.isDirectory()) return listCommandsFromDirectory(source.path, options)
219
+ return []
220
+ }
221
+
222
+ async function listPluginCommands(workspaceRoot) {
223
+ if (!workspaceRoot) return []
224
+ const sources = await getEnabledPluginCommandSources({ workspaceRoot })
225
+ const commands = []
226
+ for (const source of sources) {
227
+ commands.push(...await listCommandsFromPluginSource(source))
228
+ }
229
+ return commands
230
+ }
231
+
186
232
  export async function listProjectCommands(workspaceRoot, commandDir) {
187
233
  const byName = new Map()
188
234
 
235
+ for (const command of (await listPluginCommands(workspaceRoot)).sort((a, b) => a.name.localeCompare(b.name))) {
236
+ byName.set(command.name, command)
237
+ }
238
+
189
239
  for (const dir of commandDirectories(workspaceRoot, commandDir)) {
190
240
  const commands = await listCommandsFromDirectory(dir)
191
241
  for (const command of commands.sort((a, b) => a.name.localeCompare(b.name))) {
@@ -254,6 +304,9 @@ export function parseInternalCommandInvocation(message) {
254
304
  const planMatch = text.match(/^\/plan(?:\s+([\s\S]*))?$/i)
255
305
  if (planMatch) return { type: 'plan', args: (planMatch[1] || '').trim() }
256
306
 
307
+ const reviewMatch = text.match(/^\/review(?:\s+([\s\S]*))?$/i)
308
+ if (reviewMatch) return { type: 'review', args: (reviewMatch[1] || '').trim() }
309
+
257
310
  const compactMatch = text.match(/^\/compact(?:\s+([\s\S]*))?$/i)
258
311
  if (compactMatch) return { type: 'compact', args: (compactMatch[1] || '').trim() }
259
312
 
@@ -278,6 +331,11 @@ export async function handleInternalCommand(invocation, workspaceRoot, commandDi
278
331
  return { plan: true, args: invocation.args }
279
332
  }
280
333
 
334
+ if (invocation.type === 'review') {
335
+ if (!workspaceRoot) return 'Review requires an active project chat.'
336
+ return { review: true, args: invocation.args || '' }
337
+ }
338
+
281
339
  if (invocation.type === 'clear') {
282
340
  return { clear: true }
283
341
  }
@@ -319,7 +377,7 @@ function formatCommandList(commands) {
319
377
  '/command new review',
320
378
  '```',
321
379
  '',
322
- 'Or add Markdown files under `.ai/commands/`, for example `.ai/commands/review.md`.',
380
+ 'Or add Markdown files under `.claude/commands/`, `.opencode/commands/`, or `.ai/commands/`, for example `.ai/commands/review.md`.',
323
381
  ].join('\n')
324
382
  }
325
383
 
@@ -335,7 +393,7 @@ function formatCommandList(commands) {
335
393
  '',
336
394
  ...rows,
337
395
  '',
338
- 'Command files live in `.ai/commands/*.md`. Use `$ARGUMENTS` inside a command file to insert invocation arguments.',
396
+ 'Command files live in `.claude/commands/*.md`, `.opencode/commands/*.md`, `.ai/commands/*.md`, or configured directories. Use `$ARGUMENTS` inside a command file to insert invocation arguments.',
339
397
  ].join('\n')
340
398
  }
341
399
 
package/server/index.mjs CHANGED
@@ -24,6 +24,7 @@ import { handleSharesApi } from './routes/shares.mjs'
24
24
  import { handleSharedConversationApi } from './routes/shared-conversation.mjs'
25
25
  import { handleLanAccessApi, renderLanUnlockPage } from './routes/lan-access.mjs'
26
26
  import { handleMcpApi } from './routes/mcp.mjs'
27
+ import { handlePluginsApi } from './routes/plugins.mjs'
27
28
  import { handleWorkspaceApi, handleGitApi } from './routes/workspace.mjs'
28
29
  import { handleTerminalApi, handleTerminalUpgrade } from './routes/terminal.mjs'
29
30
  import { serveStatic } from './routes/static.mjs'
@@ -227,6 +228,12 @@ async function handleApi(req, res, url) {
227
228
  return
228
229
  }
229
230
 
231
+ // Plugins
232
+ if (pathname === '/api/plugins' || pathname.startsWith('/api/plugins/')) {
233
+ await handlePluginsApi(req, res, url)
234
+ return
235
+ }
236
+
230
237
  // Project routes
231
238
  if (pathname === '/api/project' || pathname.startsWith('/api/project/')) {
232
239
  await handleProjectApi(req, res, url)
@@ -0,0 +1,56 @@
1
+ import { pathToFileURL } from 'node:url'
2
+
3
+ function isPlainObject(value) {
4
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value))
5
+ }
6
+
7
+ function contentToText(result) {
8
+ if (typeof result === 'string') return result
9
+ if (Array.isArray(result?.content)) {
10
+ return result.content.map((item) => {
11
+ if (typeof item === 'string') return item
12
+ if (item?.type === 'text') return item.text || ''
13
+ return JSON.stringify(item)
14
+ }).join('\n')
15
+ }
16
+ if (Object.prototype.hasOwnProperty.call(result || {}, 'content')) return String(result.content ?? '')
17
+ return JSON.stringify(result ?? null, null, 2)
18
+ }
19
+
20
+ export async function loadPlugin(manifest, context = {}) {
21
+ const mainPath = new URL(manifest.main, pathToFileURL(`${manifest.dir}/`))
22
+ const moduleUrl = `${mainPath.href}?quickforgePluginReload=${Date.now()}`
23
+ const module = await import(moduleUrl)
24
+ const factory = module.createPlugin || module.default
25
+ if (typeof factory !== 'function') {
26
+ throw new Error(`Plugin ${manifest.name} must export createPlugin(context) or a default factory function.`)
27
+ }
28
+
29
+ const plugin = await factory({
30
+ ...context,
31
+ plugin: {
32
+ name: manifest.name,
33
+ displayName: manifest.displayName,
34
+ version: manifest.version,
35
+ dir: manifest.dir,
36
+ },
37
+ })
38
+
39
+ if (!isPlainObject(plugin)) {
40
+ throw new Error(`Plugin ${manifest.name} factory must return an object.`)
41
+ }
42
+
43
+ const tools = isPlainObject(plugin.tools) ? plugin.tools : {}
44
+ return {
45
+ async callTool(toolName, params = {}, toolContext = {}) {
46
+ const handler = tools[toolName]
47
+ if (typeof handler !== 'function') throw new Error(`Plugin ${manifest.name} did not provide handler for tool ${toolName}.`)
48
+ const result = await handler(params || {}, toolContext)
49
+ return {
50
+ content: contentToText(result),
51
+ details: isPlainObject(result?.details) ? result.details : undefined,
52
+ isError: Boolean(result?.isError),
53
+ }
54
+ },
55
+ }
56
+ }